W3C home > Mailing lists > Public > www-style@w3.org > April 2014

Towards a better way to handle lists in CSS

From: François REMY <francois.remy.dev@outlook.com>
Date: Wed, 2 Apr 2014 11:06:56 +0200
Message-ID: <DUB404-EAS235D53D90950209D485A4CEA56D0@phx.gbl>
To: "'www-style list'" <www-style@w3.org>
CC: "'Tab Atkins Jr.'" <jackalmage@gmail.com>
Dear CSSWG Members,

Now that the CSS Custom Properties L1 specification can be considered a closed deal, I would like to move on another topic of interest: List-Valued Properties.

TLDR:
===================
Here's the goal of this proposal: making sure we can modify and add easily items to list-valued properties:

	.zoom-on-hover {
		transform[hover-zoom]: scale(1.0);
	}
	
	.zoom-on-hover:hover {
		transform[hover-zoom]: scale(1.1);
	}
	
	.upside-down {
		transform[]: rotate(180deg); 
	}


BACKGROUND:
====================
Alongside other people [1][2], I already introduced List-Valued properties in the past [3] and Tab Atkins subsequently gathered feedback on the matter [4]. Since then, I think the problem has reborn a growing number of times, as list-valued properties such as "transform", "transition" and "background" are getting used by more and more developers [1][5][…].

As a follow-up to my initial proposal and the debate about how to solve the ordering issue, I would like to introduce the idea that I came up with when trying to deal with this in a proper way, and reopen this thread now that we're in a time where many CSS Specifications are about to reach completion soon.


PROPOSAL (PART 1):
=====================
Firstly, this proposal is based upon a new primitive, called “cascade”. This keyword can appear anywhere in a property and is replaced at the cascading time by the specified value of the property if the current declaration was ignored. Let’s give an example here:

	.a {
		transition: scale(1.1) cascade;
	}
	
	.b {
		transition: rotate(180deg) cascade;
	}
	
	// an element matching both “.a” and “.b” would receive:
	.a.b { transition: rotate(180deg) scale(1.1) none; }

This new primitive can solve some use cases and be useful at some other places, but doesn’t provide an efficient way for dealing with list manipulations: it’s not possible to define a class which, in combination with “a”, would modify the scaling factor to 1.25. This is where “list-items” are introduced.


PROPOSAL (PART 2):
=====================
List-items is the primitive behind the syntactic sugar introduced at the end of this mail, and provide all the tools required for precise and effective ordering and reassignment of declaratively constructed lists. Let’s start with an example:

	.zoom-on-hover {
		transform: item(value: scale(1.0); name: hover-zoom) cascade;
	}
	
	.zoom-on-hover:hover {
		transform: item(value: scale(1.1); name: hover-zoom) cascade;
	}
	
	.upside-down {
		transform: item(value: rotate(180deg)) cascade; 
		//same as: transform: rotate(180deg) cascade;
	}
	
	// flattened representations:
	.upside-down.zoom-on-hover { 
		transform: 
			item(value: rotate(180deg))
			item(value: scale(1.0); name: hover-zoom)
			none;
	}
	.upside-down.zoom-on-hover { 
		transform: 
			item(value: scale(1.1); name: hover-zoom)
			item(value: rotate(180deg))
			item(value: scale(1.0); name: hover-zoom)
			none;
	}

Item values can have three properties: a value, a name, and a priority index. The first step in the value computation is to merge all entries sharing the same name. The algorithm for doing so is the following one: 

	- The POSITION of the resulting item is the position of the LAST item in the list having that name
	- The VALUE of the resulting item is the value of the FIRST item in the list having that name
	- The PRIORITY index of the resulting item is the value of the FIRST item in the list having that name and an index specified (if none has one, the default value of zero shall be used).

In this case, the result would be:

	// flattened representations:
	.upside-down.zoom-on-hover { 
		transform: 
			item(value: rotate(180deg); priority: 0)
			item(value: scale(1.0); priority: 0)
			item(none; priority: 0);
	}
	.upside-down.zoom-on-hover { 
		transform: 
			item(value: rotate(180deg); priority: 0)
			item(value: scale(1.1); priority: 0)
			item(none; priority: 0);
	}

The second step in the value computation is to reorder the values by priority index (highest orders go first) by conserving relative positions of elements having the same priority (stable sorting). In this case, all elements have the 0 priority so nothing happens.

Finally, all items are replaced by their value:

	// flattened representations:
	.upside-down.zoom-on-hover { 
		transform: 
			rotate(180deg)
			scale(1.0)
			none
	}
	.upside-down.zoom-on-hover { 
		transform: 
			rotate(180deg) 
			scale(1.1) 
			none; 
	}

This strategy allows basically all operations you may want to perform on a list:

	(a) You can add elements to a list without caring about the other elements that may already be in that list.

	(b) You can modify the value of a component of the list by name without changing his position by adding a front item to the list sharing the name of the other.

	(c) You can reorder an element of a list by adding a priority to him, so that it doesn’t matter in which order the rules matches.

	(d) You can remove elements by neutering them (setting their value to none or any other noop)


PROPOSAL (PART 3):
=====================
However, I would argue this isn’t very beautiful to declare items in that way. First, you have to care about “comma-vs-spaces-separated” lists, and then you have a lot of syntax to write. I therefore propose a syntactic sugar for the previous proposal:

	.selector {
		property[]: anonymous-item-value; 
		property[name]: named-item-value;
		property[name:index]: named-item-value;
	}

Mapping to

	.selector {
		property: item(value: anonymous-item-value) cascade;
		property: item(value: named-item-value; name: name) cascade;
		property: item(value: named-item-value; name: name; priority: index) cascade;
	}

The initial example would then be written:

	.zoom-on-hover {
		transform[hover-zoom]: scale(1.0);
	}
	
	.zoom-on-hover:hover {
		transform[hover-zoom]: scale(1.1);
	}
	
	.upside-down {
		transform[]: rotate(180deg); 
	}

Thoughts?
François

[1] http://isawsomecode.tumblr.com/post/68084204042/css-variables-vs-multiple-background-images
[2] http://lists.w3.org/Archives/Public/www-style/2012Apr/0231.html
[3] http://lists.w3.org/Archives/Public/www-style/2013Apr/0711.html
[4] http://www.xanthir.com/blog/b4KZ0
[5] https://twitter.com/meyerweb/status/446460626215665664 
Received on Wednesday, 2 April 2014 09:07:34 UTC

This archive was generated by hypermail 2.3.1 : Monday, 2 May 2016 14:39:20 UTC