1. Introduction
This section is not normative.
2. 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 <dashed-function>), the value behaves as if it contained a var(), with the actual check against the property’s grammar delayed until computed-value time.
@function --negative( --value) { result : calc ( -1 *var ( --value)); }
Then, that function can be referenced with
in some declaration (assuming --gap
is defined elsewhere):
html{ padding : --negative ( var ( --gap)); }
A custom function consists of a name (<function-name>), a list of parameters, a list of dependencies, a function body, and optionally a return type described by a syntax definition.
A function parameter consists of a name (<custom-property-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.
2.1. The @function Rule
The @function rule defines a custom function, and its syntax is:
<@function> = @function <function-name> [ ( <function-parameter-list> ) ]? [ using ( <function-dependency-list> ) ]? [ returns <css-type> ]? { <declaration-rule-list> } <function-name> = <dashed-ident> <function-parameter-list> = <function-parameter># <function-dependency-list> = <function-parameter># <function-parameter> = <custom-property-name> <css-type>? [ : <declaration-value> ]? <css-type> = <syntax-component> | <type()> <type()> = type( <syntax> )
The name of the resulting custom function is given by the <function-name>, the function parameters are optionally given by <function-parameter-list>, the function dependencies are optionally given by <function-dependency-list>, and the return type is optionally given by the <css-type> following the "returns" keyword.
type ()
function may be omitted:
@function --foo ( --a <length>) { /* ... */ } @function --foo ( --a <color>) { /* ... */ } @function --foo ( --a <length>+) { /* ... */ }
However,
any <syntax> that requires a <syntax-combinator> needs to be wrapped in the
function:
@function --foo ( --atype ( <number> | <percentage>)) { /* ... */ }
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 <function-name> of a @function rule is a tree-scoped name.
If the <function-parameter-list> contains the same <custom-property-name> more than once, or if the <function-dependency-list> contains the same <custom-property-name> more than once, then the @function rule is invalid.
The body of a @function rule accepts conditional group rules, such as @media. Additionally, it accepts the following descriptors:
-
The result descriptor, which determines the result of 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.
2.2. The result Descriptor
Name: | result |
---|---|
For: | @function |
Value: | <declaration-value>? |
Initial: | n/a (see prose) |
The result descriptor determines the result of evaluating the custom function that is defined by a @function rule. Using locally substituted var() functions, it can reference function parameters, function dependencies, local variables, as well as other custom functions via <dashed-function>s.
The result descriptor itself does not have a type, but its resolved value is type checked during the substitution of a <dashed-function>.
3. 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 <dashed-function>.
A <dashed-function> is a functional notation whose function name starts with two dashes (U+002D HYPHEN-MINUS). Its syntax is:
--*( <declaration-value># )
A <dashed-function> can only be used where var() is allowed.
If a property contains one or more <dashed-function>s, the entire property’s grammar must be assumed to be valid at parse time. At computed-value time, every <dashed-function> must be 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 <dashed-function> 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 result descriptor, invokes local substitution, rather than the computed-value based substitution described in [css-variables].
-
Let function be the result of dereferencing the dashed function’s name as a tree-scoped reference. If no such name exists, return failure.
-
Let dependency values be an initially empty list.
-
For each dependency in function’s 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.
-
-
Evaluate a custom function, using function, dashed function and dependency values.
-
If failure was returned, return failure.
-
Otherwise, replace the <dashed-function> 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 <dashed-function> becomes invalid at computed-value time.
3.1. Evaluating Custom Functions
-
If the number of values in dashed function’s argument list is greater than the number of values in function’s parameters, return failure.
-
For each value parameter in function’s 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.
-
-
Let result be the resolved local value of the result descriptor, using function, dashed function, and dependency values.
-
If function has a return type, set result to the result of resolving a typed value, using result as the value, and the syntax definition associated with the return type as the syntax.
-
If result is the guaranteed-invalid value, return failure.
-
Otherwise, return result.
-
If value is not the guaranteed-invalid value, and parameter has a type, set value to the result of 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.
-
If value is the guaranteed-invalid value, and parameter has a default value, set value to one of the following:
- If parameter has a type
-
The result of 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.
-
Return value.
-
If value is the guaranteed-invalid value, return value.
-
Compute value as if it were the value associated with a registered custom property whose syntax definition is syntax.
-
If this would lead to a declaration being invalid at computed-value time, return the guaranteed-invalid value.
-
Otherwise, return that value.
3.2. Parameters and Locals
The function parameters and function dependencies of a custom function are available for 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 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.
-
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 parameter within function
-
The corresponding argument value within the dashed function.
- Otherwise, if the custom property name matches a dependency within function
-
The corresponding value of that dependency within dependency values.
- Otherwise
-
If substitution value is not the guaranteed-invalid value, replace the var() function by that value.
-
Otherwise, if the var() function has a fallback value as its second argument, replace the var() function by the locally resolved fallback value.
-
Otherwise, return failure.
A resolved local value is the value of a local variable or descriptor, except:
-
Any var() functions are replaced by local substitution.
-
Any <dashed-function>s are replaced by dashed function substitution.
If any substitution algorithm returns failure, then the resolved local value of a local variable is the guaranteed-invalid value.
3.3. Cycles
4. Execution Model of Custom Functions
Like the rest of CSS, custom functions adhere to a declarative model.
The local variable descriptors and 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 result descriptor of --mypi
is
.
@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.
4.1. 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 : 16 px ; @media ( width >1000 px ) { result : 20 px ; } }
The value of the result descriptor
is
if the media query’s condition is true,
and
otherwise.
@function --suitable-font-size () { @media ( width >1000 px ) { result : 20 px ; } result:16 px ; }
The value of the result descriptor
is always
in the above example.
@function --suitable-font-size () { --size : 16 px ; @media ( width >1000 px ) { --size : 20 px ; } result:var ( --size); }
5. 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 CSSNestedDeclarations
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 >1000 px ) { /* ... */ } --y:var ( --x); }
The above will appear in the CSSOM as:
@function --bar () { /* CSSNestedDeclarations { */ --x:42 ; result : var ( --y); /* } */ @media ( width >1000 px ) { /* ... */ } /* CSSNestedDeclarations { */ --y:var ( --x); /* } */ }
Should we indeed use CSSNestedDeclarations
for this purpose?
The style
attribute of the CSSNestedDeclarations
rule
should probably not be a regular CSSStyleDeclaration
,
since only custom properties
and the result descriptor
are relevant.