From b161deed8bf109ed6ef5397f277cb34a19e2c204 Mon Sep 17 00:00:00 2001
From: Anders Hartvoll Ruud
Date: Thu, 23 May 2024 21:06:51 +0200
Subject: [PATCH 1/6] [css-mixins] Add CSS Functions and Mixins specification
To begin with, only @function is described.
---
css-mixins-1/Overview.bs | 537 +++++++++++++++++++++++++++++++++++++++
1 file changed, 537 insertions(+)
create mode 100644 css-mixins-1/Overview.bs
diff --git a/css-mixins-1/Overview.bs b/css-mixins-1/Overview.bs
new file mode 100644
index 00000000000..74d0695802b
--- /dev/null
+++ b/css-mixins-1/Overview.bs
@@ -0,0 +1,537 @@
+
+Title: CSS Functions and Mixins Module
+Shortname: css-mixins
+Level: 1
+Status: ED
+Work Status: Exploring
+Group: CSSWG
+ED: https://drafts.csswg.org/css-mixins/
+TR: https://www.w3.org/TR/css-mixins-1/
+Editor: Miriam E. Suzanne, Invited Expert, http://miriamsuzanne.com/contact, w3cid 117151
+Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
+Abstract: This module provides the ability to define custom functional notations.
+Default Highlight: css
+
+
+
+spec:infra; type:dfn; text:list
+spec:css-properties-values-api; type:dfn; text:syntax string
+
+
+
+
+Introduction {#intro}
+=====================
+
+ This section is not normative.
+
+Issue: TODO
+
+
+
+Defining Custom Functions {#defining-custom-functions}
+======================================================
+
+ A [=custom function=] can be thought of as an advanced [=custom property=],
+ which instead of being substituted by a single fixed value
+ provides its substitution value based on [=function parameters=]
+ and [=local variables=].
+
+ Whenever a declaration's value contains a reference to a [=custom function=]
+ (a <>),
+ the value behaves as if it contained a ''var()'',
+ with the actual check against the property's grammar delayed until computed-value time.
+
+
+ A simple [=custom function=] to negate a value can be defined as follows:
+
+ @function --negative (--value) {
+ result: calc(-1 * var(--value));
+ }
+
+ Then, that function can be referenced with
--negative()
+ in some declaration (assuming
--gap
is defined elsewhere):
+
+ html { padding: --negative(var(--gap)); }
+
+
+
+ A custom function consists of a name (<>),
+ a list of [=function parameter|parameters=],
+ a list of [=function dependency|dependencies=],
+ a function body,
+ and optionally a return type described by a [=syntax definition=].
+
+ A function parameter consists of a name (<>);
+ optionally a parameter type, described by a [=syntax definition=];
+ and optionally a default value.
+
+ A function dependency,
+ is a special [=function parameter=],
+ that represents
+ a [=local variable=], [=function parameter=], or [=custom property=]
+ being implicitly passed as an argument from the calling context.
+
+The @function Rule {#function-rule}
+----------------------------------------------
+
+The ''@function'' rule defines a [=custom function=],
+and its syntax is:
+
+
+@function <> [( <> )]?
+ [using (<>)]?
+ [returns type(<>)]?
+{
+ <>
+}
+
+<> = <>
+<> = <>#
+<> = <>#
+<> = <> type(<>)? [ : <> ]?
+
+
+The name of the resulting [=custom function=] is given by the <>,
+the [=function parameters=] are optionally given by <>,
+the [=function dependencies=] are optionally given by <>,
+and the [=return type=] is optionally given by the type string following the "returns" keyword.
+
+If a [=syntax string=] provided for a [=function parameter=] or [=function dependency=] is unrecognized,
+or the [=syntax string=] provided for the [=return type=] is unrecognized,
+then the ''@function'' rule is invalid.
+
+Issue: Should duplicates be disallowed across parameters/dependencies
+ as well?
+
+If more than one ''@function'' exists for a given name,
+then the rule in the stronger cascade layer wins,
+and rules defined later win within the same layer.
+
+The <> of a ''@function'' rule is a [=tree-scoped name=].
+
+If the <>
+contains the same <> more than once,
+or if the <>
+contains the same <> more than once,
+then the ''@function'' rule is invalid.
+
+The body of a ''@function'' rule accepts [=conditional group rules=],
+such as ''@media'', as well as the ''@nest'' rule.
+Additionally, it accepts the following descriptors:
+
+ * The '@function/result' descriptor,
+ which determines the result of [=evaluating a custom function|evaluating the function=].
+ * [=Custom properties=],
+ acting like [=local variable=] descriptors.
+
+Unknown descriptors are invalid and ignored,
+but do not make the ''@function'' rule itself invalid.
+
+The '@function/result' Descriptor {#the-result-descriptor}
+----------------------------------------------------------
+
+
+Name: result
+Value: <>?
+For: @function
+Initial: n/a (see prose)
+
+
+The '@function/result' descriptor
+determines the result of [=evaluate a custom function|evaluating=]
+the [=custom function=] that is defined by a ''@function'' rule.
+Using [=locally substitute a var()|locally substituted=] ''var()'' functions,
+it can reference [=function parameters=], [=function dependencies=], [=local variables=],
+as well as other [=custom functions=] via <>s.
+
+The '@function/result' descriptor itself does not have a type,
+but its [=resolved local value|resolved=] value is type checked
+during the [=substitute a dashed function|substitution=] of a <>.
+
+
+
+Using Custom Functions {#using-custom-functions}
+================================================
+
+Similar to how the value of a [=custom property=] can be substituted
+into the value of another property with ''var()'',
+the result of a [=custom function=] evaluation can be substituted
+with a <>.
+
+A <> is a [=functional notation=]
+whose function name starts with two dashes (U+002D HYPHEN-MINUS).
+Its syntax is:
+
+
+ --*() = --*( <># )
+
+
+Issue: Mention semicolon upgrades.
+
+A <> can only be used where ''var()'' is allowed.
+
+If a property contains one or more <>s,
+the entire property’s grammar must be assumed to be valid at parse time.
+At computed-value time,
+every <> must be [=substitute a dashed function|substituted=]
+before finally being checked against the property's grammar.
+
+When a value is being computed,
+substitution of ''var()'', ''env()'' and ''attr()''
+must take place before <> substitution.
+
+ Note: This means arguments passed to a custom function
+ never contain ''var()'', or similar functions.
+
+A ''var()'' function within a [=local variable=],
+or within the ''@function/result'' descriptor,
+invokes [=locally substitute a var()|local substitution=],
+rather than the computed-value based substitution
+described in [[!css-variables]].
+
+
+ To substitute a dashed function in a value,
+ with |dashed function| being a <>:
+
+ 1. Let |function| be the result of dereferencing
+ the |dashed function|'s name as a [=tree-scoped reference=].
+ If no such name exists, return failure.
+ 2. Let |dependency values| be an initially empty [=list=].
+ 2. For each |dependency| in |function|'s [=function dependency|dependencies=]:
+ * Let |dependency value| be the value that would be substituted
+ if a ''var()'' function had been specified explicitly
+ at the end of |dashed function|'s argument list,
+ with |dependency| as its only argument.
+ * If that substitution would have made a containing declaration
+ [=invalid at computed-value time=],
+ set |dependency value| to the [=guaranteed-invalid value=].
+ * If |dependency value| is the [=guaranteed-invalid value=],
+ and |dependency| has a [=default value=],
+ set |dependency value| to that [=default value=].
+ * If |dependency| has a [=parameter type|type=],
+ set |dependency value| to the result of
+ [=resolve a typed value|resolving a typed value=],
+ using |dependency value| as the value,
+ and the [=syntax definition=] associated with the dependency's type as the syntax.
+ * Append |dependency value| to |dependency values|.
+ 1. [=Evaluate a custom function=],
+ using |function|, |dashed function| and |dependency values|.
+ 2. If failure was returned, return failure.
+ 3. Otherwise,
+ replace the <> with the [=equivalent token sequence=]
+ of the value resulting from the evaluation.
+
+
+If [=substitute a dashed function=] fails,
+and the substitution is taking place on a property's value,
+then the declaration containing the <> becomes
+[=invalid at computed-value time=].
+
+Evaluating Custom Functions {#evaluating-custom-functions}
+----------------------------------------------------------
+
+
+ To evaluate a custom function,
+ with |function| being a [=custom function=],
+ |dashed function| being the <> invoking that |function|,
+ and |dependency values| being a [=list=] of values.
+
+ 1. If the number of values in |dashed function|'s argument list
+ is greater than the number of values in |function|'s [=function parameter|parameters=],
+ return failure.
+ 2. For each value |parameter| in |function|'s [=function parameter|parameters=],
+ let |argument| be the corresponding value in |dashed function|'s argument list
+ at the same index:
+ * If |argument| does not exist,
+ set |argument| to the [=guaranteed-invalid value=].
+ * If |argument| is the [=guaranteed-invalid value=],
+ and |parameter| has a [=default value=],
+ set |argument| to that [=default value=].
+ * If |parameter| has a [=parameter type|type=],
+ set |argument| to the result of
+ [=resolve a typed value|resolving a typed value=],
+ using |argument| as the |value|,
+ and the [=syntax definition=] associated with the parameter's type as the |syntax|.
+ * Replace the value in |dashed function|'s argument list with |argument|.
+ 3. Let |result| be the [=resolved local value=]
+ of the '@function/result' descriptor,
+ using |function|, |dashed function|, and |dependency values|.
+ 4. If |function| has a [=return type=],
+ set |result| to the result of [=resolve a typed value|resolving a typed value=],
+ using |result| as the |value|,
+ and the [=syntax definition=] associated with the [=return type=] as the |syntax|.
+ 5. If |result| is the [=guaranteed-invalid value=],
+ return failure.
+ 6. Otherwise,
+ return |result|.
+
+
+
+ To
resolve a typed value,
+ with value |value|,
+ and [=syntax definition=] |syntax|:
+
+ 1. If |value| is the [=guaranteed-invalid value=],
+ return |value|.
+ 2.
Compute
+ |value| as if it were the value associated with a [=registered custom property=]
+ whose [=syntax definition=] is |syntax|.
+ 3. If this would lead to a declaration being [=invalid at computed-value time=],
+ return the [=guaranteed-invalid value=].
+ 4. Otherwise, return that value.
+
+
+Parameters and Locals {#parameters}
+-----------------------------------
+
+ The [=function parameters=] and [=function dependencies=] of a [=custom function=]
+ are available for [=locally substitute a var()|local substitution=]
+ as if they were declared as [=local variables=]
+ at the start of the ''@function'' rule body.
+
+ Note: A [=local variable=] with the same name
+ as a [=function parameter=]/[=function dependency=] is allowed,
+ but will make the parameter/dependency unreachable
+ for [=locally substitute a var()|substitution=]
+
+ A local variable
+ is a custom property defined with the body of a [=custom function=].
+ It is only visible within the function where it is defined.
+
+
+ To
locally substitute a var() within a value,
+ with |function| being a [=custom function=],
+ |dashed function| being the <
> invoking that |function|,
+ and |dependency values| being a [=list=] of values:
+
+ 1. Let |substitution value| be one of the following options,
+ depending on the [=custom property=] named in the first argument of the ''var()'' function:
+
+
+ : If the [=custom property=] name matches a [=local variable=] within |function|
+ :: The [=resolved local value=] of that [=local variable=].
+
+ : Otherwise, if the [=custom property=] name matches a [=function parameter|parameter=] within |function|
+ :: The corresponding argument value within the |dashed function|.
+
+ : Otherwise, if the [=custom property=] name matches a [=function dependency|dependency=] within |function|
+ :: The corresponding value of that [=function dependency|dependency=]
+ within |dependency values|.
+
+ : Otherwise
+ :: The [=guaranteed-invalid value=].
+
+
+ 2. If |substitution value| is not the [=guaranteed-invalid value=],
+ replace the ''var()'' function by that value.
+
+ 3. Otherwise, if the ''var()'' function has a fallback value as its second argument,
+ replace the ''var()'' function by the [=resolved local value|locally resolved=] fallback value.
+
+ 4. Otherwise, return failure.
+
+
+A resolved local value is the value of a [=local variable=] or [=descriptor=], except:
+
+ * Any ''var()'' functions are replaced by [=locally substitute a var()|local substitution=].
+ * Any ''env()'' or ''attr()'' functions are substituted normally.
+ * Any <>s are replaced by [=substitute a dashed function|dashed function substitution=].
+
+If any substitution algorithm returns failure,
+then the [=resolved local value=] of a [=local variable=]
+is the [=guaranteed-invalid value=].
+
+Cycles {#cycles}
+----------------
+
+Issue: TODO
+
+
+
+Execution Model of Custom Functions {#execution-model}
+======================================================
+
+Like the rest of CSS,
+[=custom functions=] adhere to a declarative model.
+
+The [=local variable=] descriptors
+and '@function/result' descriptor
+can appear in any order,
+and may be provided multiple times.
+If this happens, then declarations appearing later win over earlier ones.
+
+
+
+ @function --mypi() {
+ result: 3;
+ result: 3.14;
+ }
+
+ The value of the '@function/result' descriptor of
--mypi
+ is
3.14
.
+
+
+
+
+
+ @function --circle-area(--r) {
+ result: calc(pi * var(--r2));
+ --r2: var(--r) * var(--r);
+ }
+
+ [=Local variable=] descriptors may appear before or after
+ they are referenced.
+
+
+
+Conditional Rules {#conditional-rules}
+--------------------------------------
+
+A [=conditional group rule=] that appears within a ''@function''
+becomes a [=nested group rule=],
+with the additional restriction
+that only descriptors allowed within ''@function''
+are allowed within the [=nested group rule=].
+
+[=Conditional group rules=] within ''@function''
+are processed as normal,
+acting as if the contents of the rule were present
+at the [=conditional group rule=]'s location
+when the condition is true,
+or acting as if nothing exists at that location otherwise.
+
+
+
+ @function --suitable-font-size() {
+ result: 16px;
+ @media (width > 1000px) {
+ result: 20px;
+ }
+ }
+
+ The value of the '@function/result' descriptor
+ is
20px
if the media query's condition is true,
+ and
16px
otherwise.
+
+
+
+
+ Note that due to the execution model,
+ "early return" is not possible within a ''@function'':
+
+ @function --suitable-font-size() {
+ @media (width > 1000px) {
+ result: 20px;
+ }
+ result: 16px;
+ }
+
+ The value of the '@function/result' descriptor
+ is always
16px
in the above example.
+
+
+
+
+ [=Local variables=] are also valid within conditional rules:
+
+ @function --suitable-font-size() {
+ --size: 16px;
+ @media (width > 1000px) {
+ --size: 20px;
+ }
+ result: var(--size);
+ }
+
+
+
+
+Interaction with @nest {#at-nest}
+----------------------------
+
+''@nest'' rules are valid within ''@function'',
+with the additional restriction
+that only descriptors allowed within ''@function''
+are allowed within the ''@nest'' rule.
+
+A ''@nest'' rule within ''@function'' does nothing:
+it acts like its contents were present at its location.
+
+
+
+CSSOM {#cssom}
+==============
+
+The {{CSSFunctionRule}} interface represents a ''@function'' rule.
+
+
+[Exposed=Window]
+interface CSSFunctionRule : CSSSGroupingRule { };
+
+
+While declarations may be specified directly within an ''@function'' rule,
+they are not represented as such in the CSSOM.
+Instead, consecutive segments of declarations
+appear as if wrapped in ''@nest'' rules.
+
+Note: This also applies to the "leading" declarations in the ''@function'' rule,
+ i.e those that do not follow another nested rule.
+
+
+
+ @function --bar() {
+ --x: 42;
+ result: var(--y);
+ @media (width > 1000px) {
+ /* ... */
+ }
+ --y: var(--x);
+ }
+
+
+ The above will appear in the CSSOM as:
+
+
+ @function --bar() {
+ @nest {
+ --x: 42;
+ result: var(--y);
+ }
+ @media (width > 1000px) {
+ /* ... */
+ }
+ @nest {
+ --y: var(--x);
+ }
+ }
+
+
+
+Issue: Should we indeed use ''@nest'' for this purpose?
+ The style
attribute of the ''@nest'' rule
+ should probably not be a regular {{CSSStyleDeclaration}},
+ since only custom properties
+ and the '@function/result' descriptor
+ are relevant.
\ No newline at end of file
From 84e52f53f8cf73d7bbc28653c05a5695bc94529e Mon Sep 17 00:00:00 2001
From: Tab Atkins Jr
Date: Fri, 24 May 2024 14:55:16 -0700
Subject: [PATCH 2/6] bikeshed-clean
---
css-mixins-1/Overview.bs | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/css-mixins-1/Overview.bs b/css-mixins-1/Overview.bs
index 74d0695802b..9776122e972 100644
--- a/css-mixins-1/Overview.bs
+++ b/css-mixins-1/Overview.bs
@@ -16,6 +16,7 @@ Default Highlight: css
spec:infra; type:dfn; text:list
spec:css-properties-values-api; type:dfn; text:syntax string
+spec:css-syntax-3; type:dfn; text:descriptor;
Introduction {#intro}
@@ -162,6 +170,13 @@ during the [=substitute a dashed function|substitution=] of a <
Using Custom Functions {#using-custom-functions}
@@ -361,6 +376,14 @@ Cycles {#cycles}
Issue: TODO
Execution Model of Custom Functions {#execution-model}
From b98ff4ae54635adff53055a1f2febfa2cdd2e6e4 Mon Sep 17 00:00:00 2001
From: Anders Hartvoll Ruud
Date: Mon, 27 May 2024 14:06:39 +0200
Subject: [PATCH 4/6] "An at-function" -> "A function"
There's apparently no right answer to "an at-foo" vs "a foo",
but we can at least stay consistent within the same spec.
---
css-mixins-1/Overview.bs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/css-mixins-1/Overview.bs b/css-mixins-1/Overview.bs
index 22a61b09e91..a031cc1c4fc 100644
--- a/css-mixins-1/Overview.bs
+++ b/css-mixins-1/Overview.bs
@@ -515,7 +515,7 @@ The {{CSSFunctionRule}} interface represents a ''@function'' rule.
interface CSSFunctionRule : CSSGroupingRule { };
-While declarations may be specified directly within an ''@function'' rule,
+While declarations may be specified directly within a ''@function'' rule,
they are not represented as such in the CSSOM.
Instead, consecutive segments of declarations
appear as if wrapped in ''@nest'' rules.
From 8fdd46e52826eda14be3c8332a270f4e78d3289c Mon Sep 17 00:00:00 2001
From: Anders Hartvoll Ruud
Date: Mon, 27 May 2024 15:28:04 +0200
Subject: [PATCH 5/6] Resolve to the default value when the type is wrong.
---
css-mixins-1/Overview.bs | 64 +++++++++++++++++++++++++---------------
1 file changed, 40 insertions(+), 24 deletions(-)
diff --git a/css-mixins-1/Overview.bs b/css-mixins-1/Overview.bs
index a031cc1c4fc..c8432ef16e9 100644
--- a/css-mixins-1/Overview.bs
+++ b/css-mixins-1/Overview.bs
@@ -226,7 +226,7 @@ described in [[!css-variables]].
the |dashed function|'s name as a [=tree-scoped reference=].
If no such name exists, return failure.
2. Let |dependency values| be an initially empty [=list=].
- 2. For each |dependency| in |function|'s [=function dependency|dependencies=]:
+ 3. For each |dependency| in |function|'s [=function dependency|dependencies=]:
* Let |dependency value| be the value that would be substituted
if a ''var()'' function had been specified explicitly
at the end of |dashed function|'s argument list,
@@ -234,19 +234,13 @@ described in [[!css-variables]].
* If that substitution would have made a containing declaration
[=invalid at computed-value time=],
set |dependency value| to the [=guaranteed-invalid value=].
- * If |dependency value| is the [=guaranteed-invalid value=],
- and |dependency| has a [=default value=],
- set |dependency value| to that [=default value=].
- * If |dependency| has a [=parameter type|type=],
- set |dependency value| to the result of
- [=resolve a typed value|resolving a typed value=],
- using |dependency value| as the value,
- and the [=syntax definition=] associated with the dependency's type as the syntax.
- * Append |dependency value| to |dependency values|.
- 1. [=Evaluate a custom function=],
+ * Append the result of [=resolving an argument=] to |dependency values|,
+ using |dependency value| as value,
+ and |dependency| as parameter.
+ 4. [=Evaluate a custom function=],
using |function|, |dashed function| and |dependency values|.
- 2. If failure was returned, return failure.
- 3. Otherwise,
+ 5. If failure was returned, return failure.
+ 6. Otherwise,
replace the <> with the [=equivalent token sequence=]
of the value resulting from the evaluation.
@@ -273,28 +267,50 @@ Evaluating Custom Functions {#evaluating-custom-functions}
at the same index:
* If |argument| does not exist,
set |argument| to the [=guaranteed-invalid value=].
- * If |argument| is the [=guaranteed-invalid value=],
- and |parameter| has a [=default value=],
- set |argument| to that [=default value=].
- * If |parameter| has a [=parameter type|type=],
- set |argument| to the result of
- [=resolve a typed value|resolving a typed value=],
- using |argument| as the |value|,
- and the [=syntax definition=] associated with the parameter's type as the |syntax|.
- * Replace the value in |dashed function|'s argument list with |argument|.
+ * Replace the value in |dashed function|'s argument list
+ with the result of [=resolving an argument=],
+ using |argument| as value,
+ and |parameter| as parameter.
3. Let |result| be the [=resolved local value=]
of the '@function/result' descriptor,
using |function|, |dashed function|, and |dependency values|.
4. If |function| has a [=return type=],
set |result| to the result of [=resolve a typed value|resolving a typed value=],
- using |result| as the |value|,
- and the [=syntax definition=] associated with the [=return type=] as the |syntax|.
+ using |result| as the value,
+ and the [=syntax definition=] associated with the [=return type=] as the syntax.
5. If |result| is the [=guaranteed-invalid value=],
return failure.
6. Otherwise,
return |result|.
+
+ To
resolve an argument,
+ with value |value|,
+ and [=function parameter|parameter=] |parameter|:
+
+ 1. If |value| is not the [=guaranteed-invalid value=],
+ and |parameter| has a [=parameter type|type=],
+ set |value| to the result of [=resolve a typed value|resolving a typed value=]
+ using |value| as the value,
+ and the [=syntax definition=] associated with |parameter|'s type as the syntax.
+
This step may cause |value| to become [=guaranteed-invalid value|guaranteed-invalid=].
+ 2. If |value| is the [=guaranteed-invalid value=],
+ and |parameter| has a [=default value=],
+ set |value| to one of the following:
+
+ : If |parameter| has a [=parameter type|type=]
+ :: The result of [=resolve a typed value|resolving a typed value=]
+ using the |parameter|'s [=default value=] as the value,
+ and the [=syntax definition=] associated with |parameter|'s type as the syntax.
+
+ : Otherwise
+ :: The |parameter|'s [=default value=].
+
+ 3. Return |value|.
+
+
+
To
resolve a typed value,
with value |value|,
From 8eb21709c5abe6780cad0ebc9a33a236dccaf291 Mon Sep 17 00:00:00 2001
From: Miriam Suzanne
Date: Tue, 28 May 2024 14:58:43 -0600
Subject: [PATCH 6/6] Update css-mixins-1/Overview.bs
---
css-mixins-1/Overview.bs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/css-mixins-1/Overview.bs b/css-mixins-1/Overview.bs
index c8432ef16e9..634e147c224 100644
--- a/css-mixins-1/Overview.bs
+++ b/css-mixins-1/Overview.bs
@@ -113,7 +113,7 @@ and its syntax is:
The name of the resulting [=custom function=] is given by the <>,
the [=function parameters=] are optionally given by <>,
-the [=function dependencies=] are optionally given by <>,
+the [=function dependencies=] are optionally given by <>,
and the [=return type=] is optionally given by the type string following the "returns" keyword.
If a [=syntax string=] provided for a [=function parameter=] or [=function dependency=] is unrecognized,