[css-houdini-drafts] [css-properties-values-api] Functions to access individual items in --properties registered with a multiplied syntax (#1059)

James0x57 has just created a new issue for https://github.com/w3c/css-houdini-drafts:

== [css-properties-values-api] Functions to access individual items in --properties registered with a multiplied syntax ==
[Multipliers in the Spec](https://drafts.css-houdini.org/css-properties-values-api/#multipliers)

When a --custom-property is registered with a multiplied syntax such as:
```css
@property --margi-paddi {
  syntax: "<length>*";
  initial-value: initial;
  inherits: false;
}
```
it would be very useful to have a CSS functions to access individual items from the list.
Among other things, it allows custom properties to have an author-defined shorthand syntax that can be expanded into other properties.

Pairing this with style containers unlocks a lot more potential too.

## Overview of the MVP

* Allowing `syntax: "*+"` and `syntax: "*#"`
* only 2 functions: `nth-item()` and `items-length()`
* specifying a minimum maximum list length for browser devs

## MVP: Wildcard multiplied syntaxes

* `syntax: "*+"` // space separated list of anything
* `syntax: "*#"` // comma separated list of anything

Custom property values are already minimally parsed to make sure parens match, this just lets authors opt-in to adding meaning to the top-level spaces (or commas) in a custom property value a little bit sooner during computed value time -- similar to how they're already parsed for other multiplied syntaxes but without giving meaning to the stuff in between.

The reason this is important will be made clear in the more complex examples below, but it's essentially a shortcut to mixed-syntax lists before the syntax for syntax gets more and more expressive.

## MVP: Two Functions
Though many more possibilities exist, these two functions give us the most potential with the least effort.

We can currently register two types of multiplied value lists and these functions operate on the list after they've been computed. So what delimiter was used doesn't matter for these. We'll likely, eventually, be able to register properties with other delimiters separating the items, and those lists could just as well use these functions too.

### items-length( &lt;custom-property-name> )
```css
--number-of-items: items-length(--registered-multiplied-var);
```
This should return an &lt;integer> between 0 and max length if the the custom property referenced is a valid list, even if the list is empty.
If the custom propery is invalid or not a multiplied syntax, it should return the guaranteed invalid value.

### nth-item( &lt;custom-property-name>, &lt;integer>[, &lt;declaration-value>] )
```css
/* return the first item in the list, or the guarenteed-invalid-value */
--first-item: nth-item(--registered-multiplied-var, 1);
/* 1-based for consistency within CSS */

/* return the var(--index)th item in the list, or none if it doesn't exist */
--indexth-item: nth-item(--registered-multiplied-var, var(--index), none);

/* returns `fallback` because -2 is less than the first index, 1 (same behavior for indexes beyond the list) */
--x: 4;
--y: 6;
--calcth-item: nth-item(--registered-multiplied-var, calc(var(--x) - var(--y)), fallback);

/* If any of the values inside are not valid, then the entire expression is invalid. */
/* invalid at computed value time, `foobar` is not of type "<integer>" */
--invalid-item: nth-item(--registered-multiplied-var, var(--garinvalid, foobar), unreachable-fallback);
```

## MVP: Minimum maximum length
Browsers will likely have a maximum list length; I'm not sure if this is already specified.

128 is probably reasonable minimum max-length? Thoughts? Maybe 255?

IMO, specifying a minimum value for this is an MVP detail so authors can reliably expect each implementation to handle at least X items.
My personal experiences running into unexpected browser limits in CSS experiments has taken quite a lot of effort, searching, and bugging browser devs to identify what's failing. Mentioning it here in hopes that it will be at least considered if not specified up front 💜

## Examples!

### Basic examples

cycle through a list of colors with an animation:

```css
@property --many-colors {
  syntax: "<color>+";
  initial-value: initial;
  inherits: false;
}

@property --index {
  syntax: "<integer>";
  initial-value: 1;
  inherits: false;
}

@keyframes indexer { to { --index: var(--length); } }

.cool-colors {
  --many-colors: #ff0000, green, blue, rebeccapurple;
  --length: items-length(--many-colors);
  animation: indexer 10s step-end;
  color: nth-item(--many-colors, var(--index), transparent);
  transition: color calc(10s / var(--length)) linear;
}
```

use mathematical or other calculations to determine if the element is displayed:

```css
@property --display-options {
  syntax: "*+";
  initial-value: initial;
  inherits: false;
}
div {
  --display-options: none inline-block block;
  --calc: clamp(1, var(--display-choice, 1), items-length(--display-options));
  display: nth-item(--display-options, var(--calc));
}
```

### Example paired with style containers
Pairing this with recent `@‍container style(...)` almost eliminates the need for a CSS library to have more than a few utility classes - or even have just one.
I'm sure this general idea of arrays in css var values has been thought about a lot, but I didn't find any issues tracking it and I wanted to try getting it on the radar now specifically so this might follow closely behind style containers. The combination is incredibly useful and full of potential; Greater than the sum of its parts imo!
cc @mirisuzanne 

```css
@property --fewer-utility-classes {
  syntax: "<custom-ident>*";
  initial-value: initial;
  inherits: false;
}

@property --display-size {
  syntax: "tiny | small | normal | big | huge";
  initial-value: normal;
  inherits: false;
}

.just-one-class-for-my-library {
  --fewer-utility-classes: button primary 52px; /* this would be provided by an end user of this library */
  
  container-type: style;
  --display-type: nth-item(--fewer-utility-classes, 1, none);
  --display-variant: nth-item(--fewer-utility-classes, 2, default);
  --display-size: nth-item(--fewer-utility-classes, 3, normal); /* understand: --display-size computes to invalid in this case */

  @container style(--display-type: link) { ... }
  @container style(--display-type: button) { ... }
  @container style(--display-variant: default) { ... }
  @container style(--display-variant: primary) { ... }
  @container style(--display-variant: secondary) { ... }
  @container style(--display-size: tiny) { ... }
  ...
}
```

### Example of multipliers# containing multipliers+

Using the [proposed Mass Property Registration Syntax](https://github.com/w3c/css-houdini-drafts/issues/1058) here for brevity

```css
@property --csv {
  syntax: "*#"; /* comma separated list of any syntax */
  initial-value: initial;
  inherits: false;
}
@property --ssv-* {
  syntax: "*+"; /* space separated list of any syntax */
  initial-value: initial;
  inherits: false;
}
.unlimited-utility {
  --csv: button secondary, warning big, high-contrast-warning huge;

  --ssv-default-state: nth-item(--csv, 1);

  --default-type: nth-item(--ssv-default-state, 1);
  --default-variant: nth-item(--ssv-default-state, 2); /* computes to `secondary` */
  --default-size: nth-item(--ssv-default-state, 3, normal); /* computes to `normal` */
  
  &:hover {
    --ssv-hover-state: nth-item(--csv, 2);
    --hover-type: nth-item(--ssv-hover-state, 1);
    ...
  }
  &:focus {
    --ssv-focus-state: nth-item(--csv, 3);
    ...
  }
  /* use style containers to make any ident values useful */
}
```

These functions will become _even more useful_ when we have an @‍if or other CVT conditionals, but there are already many clamp() based comparitors for numeric values we could use today, if one were so inclined ~

## What's not here on purpose?

* Something similar to js **array.slice()** because there's not a lot authors can do with parsing and conditionals, most lists will have to be very explicit about what goes where. When we have more robust conditionals (and/or loops), slice() will become useful.
* sum()/reduce(), indexOf(), reverse(), and other implicit loops because they're relatively expensive, would be redundant if generic loops become a thing, and the performance trade off is only to allow underspecified list ordering, which isn't necessary.
* nth-last-item() because we have items-length(), calc(), and nth-item(), nth-last-item() would be redundant even if it's trivial after implementing nth-item(). Therefore I didn't consider it MVP. (but would totally appreciate its inclusion if this moves forward!)
* **nth-item-of-syntax()** because, as (super) useful as that would be, it would set an expectation that the items were typed and if it's a multiplied wildcard syntax list, that expectation is wrong. Many many lists will probably end up with syntax `"*+"` if they contain a mix of identifiers and other syntaxes (how the author consumes them is for them to decide/document) until a more extensive syntax-syntax is written. So the expectation is wrong _often_ and that's no good (for now). Once syntax's syntax becomes super robust though, this would be a great companion upgrade.
* push(), pop(), unshift(), shift() because these modify state of existing properties, which would cause re-render etc etc

## Other considerations?
* Can a list contain an item that's the `intial` keyword without invalidating the entire list? (Probably not but would be worth mentioning in the spec directly so it's clear if it's not already mentioned)
* using `values-length(...)` and `nth-value(...)` instead of `item` might be a more spec-consistent name?

// Jane

PS: I know browsers parse everything into tokenized lists at CVT in part to determine validity, so I am _assuming_ a registered var with multiplied syntax is _already_ in an array internally at Computed Value Time, so this might not be as big of an ask as it might look like.
If that's *not* true, I understand this request may not be practical and might be closed without much further consideration.
Worth a shot either way!

Please view or discuss this issue at https://github.com/w3c/css-houdini-drafts/issues/1059 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Thursday, 9 December 2021 06:01:32 UTC