diff --git a/css-mixins-1/Overview.bs b/css-mixins-1/Overview.bs new file mode 100644 index 00000000000..634e147c224 --- /dev/null +++ b/css-mixins-1/Overview.bs @@ -0,0 +1,577 @@ +
+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
+
+ + + + + +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=]. + 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, + 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=]. + * 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|. + 5. If failure was returned, return failure. + 6. 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=]. + * 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. + 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|, + 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 : CSSGroupingRule { };
+
+ +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. + +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.