Skip to content

Commit 653248c

Browse files
committed
[css-properties-values-api] Changes suggested by TF at F2F
1 parent 6cd4b68 commit 653248c

File tree

2 files changed

+338
-250
lines changed

2 files changed

+338
-250
lines changed
+268
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
Introduction {#introduction}
2+
============================
3+
4+
[[css-variables]] defines a new <<var()>> function that can be used to
5+
insert the values of custom properties into other CSS property values. Where
6+
possible, this mechanism should be preferred above the computed value modification
7+
facilities of this specification.
8+
9+
Supported syntax strings {#supported-syntax-strings}
10+
----------------------------------------------------
11+
12+
: "&lt;'[property-name]'>", Where [property-name] is any existing CSS property
13+
name or any string that matches the <<custom-property-name>> production.
14+
:: Any value that parses as a value of the specified property.
15+
16+
17+
The apply hook {#the-apply-hook}
18+
================================
19+
20+
<pre class='idl'>
21+
22+
interface ElementProxy {
23+
readonly attribute StylePropertyMapReadOnly inputStyle;
24+
readonly attribute StylePropertyMap outputStyle;
25+
readonly attribute DOMString? pseudo;
26+
};
27+
28+
dictionary ApplyDescriptor {
29+
sequence&lt;DOMString&gt; inputProperties;
30+
sequence&lt;DOMString&gt; outputProperties;
31+
};
32+
33+
callback VoidFunction = void ();
34+
35+
interface StyleWorklet : WorkletGlobalContext {
36+
void registerApplyHook(DOMString name, VoidFunction applyCtor, ApplyDescriptor config);
37+
void unregisterApplyHook(DOMString name);
38+
};
39+
</pre>
40+
41+
<div class='note'>
42+
The applyCtor is a JavaScript class with the following interface:
43+
44+
<pre class='idl'>
45+
callback interface ApplyClass {
46+
void apply(ElementProxy element);
47+
};
48+
</pre>
49+
</div>
50+
51+
The {{ElementProxy}} interface {#the-elementproxy-interface}
52+
------------------------------------------------------------
53+
54+
{{ElementProxy}} objects represent the partial state of DOM objects that are
55+
available to apply hooks running in <a>worklet global scope</a>s. They provide
56+
the following attributes:
57+
58+
: inputStyle
59+
:: The set of styles that the apply hook has registered a dependency on.
60+
: outputStyle
61+
:: The final result of running this apply hook.
62+
: pseudo
63+
:: The pseudo name of the PseudoElement that this ElementProxy proxies, or
64+
null if this ElementProxy proxies an Element.
65+
66+
Issue(73): Do we need the pseudo attribute on ElementProxy for level 1?
67+
68+
Issue(74): Come up with a better name than ElementProxy. This is more of a computation context.
69+
70+
The {{ApplyDescriptor}} dictionary {#the-applydescriptor-dictionary}
71+
--------------------------------------------------------------------
72+
73+
: <dfn dict-member for=ApplyDescriptor>inputProperties</dfn>
74+
:: The apply function is only called for elements or
75+
pseudoelements on which the listed properties all have non-initial values.
76+
77+
Issue(4): It should it be possible to access properties on the parent.
78+
79+
Issue(2): Should this be available only if explicitly requested in inputProperties?
80+
81+
: <dfn dict-member for=ApplyDescriptor>outputProperties</dfn>
82+
:: This value defines the properties for which the apply function can modify the used
83+
value.
84+
85+
Apply classes {#apply-class-objects}
86+
------------------------------------
87+
88+
<dfn>Apply classes</dfn> provide apply hook behavior. Each <a>apply class</a>
89+
must provide an apply function that will be invoked when apply hooks are
90+
being processed.
91+
92+
The {{StyleWorklet}} interface {#the-styleworklet-interface}
93+
------------------------------------------------------------
94+
95+
<dfn interface>StyleWorklet</dfn> objects provide the context within which apply hooks
96+
are invoked. Each {{StyleWorklet}} contains a <dfn>name map of apply hooks</dfn>,
97+
a <dfn>name map of inputs</dfn>, a <dfn>name map of outputs</dfn>,
98+
and a <dfn>list of affected output properties</dfn>, all of which are
99+
initially empty.
100+
101+
The {{registerApplyHook()}} function {#the-registerapplyhook-function}
102+
----------------------------------------------------------------------
103+
104+
The <dfn method for=StyleWorklet>registerApplyHook(DOMString name, VoidFunction applyCtor, ApplyDescriptor config)</dfn>
105+
function registers a new apply hook for processing computed style.
106+
107+
When {{registerApplyHook(name, applyCtor, config)}} is called, the user agent must run the following steps:
108+
109+
1. If |name| is not a valid <<ident>>, <a>throw</a> a {{NotSupportedError}} and abort
110+
these steps.
111+
112+
1. If |name| is a key in the <a>name map of apply hooks</a>, <a>throw</a> a
113+
{{NotSupportedError}} and abort these steps.
114+
115+
1. Let <var>outputProperties</var> be the value of |config|'s
116+
{{ApplyDescriptor/outputProperties}}.
117+
118+
1. If the |outputProperties| contains a property
119+
that is in the <a>list of affected output properties</a>, <a>throw</a> a
120+
{{NotSupportedError}} and abort these steps.
121+
122+
Issue(49): This is too inflexible. There’s a strong use case around writing to the
123+
same native property for different elements. Maybe throw exception to
124+
window.onError in this case?
125+
126+
1. If the result of <a>IsConstructor</a>(argument=|applyCtor|) is false,
127+
<a>throw</a> a {{NotSupportedError}} and abort these steps.
128+
129+
1. Let <var>prototype</var> be the result of <a>Get</a>(O=|applyCtor|, P="prototype").
130+
131+
1. If the result of <a>IsCallable</a>(argument=<a>Get</a>(O=|prototype|, P="apply"))
132+
is false, <a>throw</a> a {{NotSupportedError}} and abort these steps.
133+
134+
1. Let <var>applyInstance</var> be the result of <a>Construct</a>(|applyCtor|).
135+
136+
1. Add the key-value pair (|name| - |applyInstance|) to the
137+
<a>name map of apply hooks</a> of the {{StyleWorklet}}.
138+
139+
1. Add each property in |outputProperties| to
140+
the <a>list of affected output properties</a> of the {{StyleWorklet}}.
141+
142+
1. Add the key-value pair (|name| - |outputProperties|) to the
143+
<a>name map of outputs</a> of the {{StyleWorklet}}.
144+
145+
1. Let <var>inputProperties</var> be the value of |config|'s
146+
{{ApplyDescriptor/inputProperties}}.
147+
148+
1. Add the key-value pair (|name| - |inputProperties|) to the
149+
<a>name map of inputs</a> of the {{StyleWorklet}}.
150+
151+
Issue: This is one instance per apply hook. Do we want one instance per invocation?
152+
153+
Invoking apply hooks {#invoking-apply-hooks}
154+
--------------------------------------------
155+
156+
Each time style is recomputed for an Element, each registered ApplyDescriptor/applyHook
157+
for which any of the matching {{ApplyDescriptor/inputProperties}} changes as a result of
158+
that recomputation is invoked. This invocation happens after any transitions or animations
159+
registered on the Element have finished applying, in the context of a {{StyleWorklet}}.
160+
161+
Note: apply hooks are called after transitions/animations so that custom properties
162+
can be transitioned and still have their effect apply correctly.
163+
164+
Implementations may memoize the result of apply callbacks relative to the
165+
complete set of inputs provided to apply (that is, the set of attributes on
166+
{{ElementProxy}}).
167+
168+
This invocation takes place by following these steps for each key <var>name</var>
169+
in the <a>name map of apply hooks</a>:
170+
171+
1. Let <var>inputs</var> be the result of looking up <var>name</var> on the
172+
{{StyleWorklet}}'s <a>name map of inputs</a>.
173+
174+
1. Let <var>inputStyleMap</var> be a new {{StylePropertyMapReadOnly}} populated
175+
with only the <a>computed value</a>s for properties listed in |inputs|.
176+
177+
1. Let <var>proxy</var> be a new {{ElementProxy}}.
178+
179+
Issue: Need to fill out the ElementProxy.
180+
181+
1. <a>invoke a method on a class inside a Worklet</a> given "apply" as the
182+
<var>methodPropertyKey</var> and [|proxy|] as the <var>arguments</var> with
183+
the following options:
184+
185+
* To <a>create a worklet global scope</a> the user agent will return a new
186+
{{StyleWorklet}}
187+
* To <a>lookup a class instance on a worklet global scope</a> given a
188+
<var>workletGlobalScope</var> the user agent will return the result of
189+
looking up <var>name</var> on the <var>workletGlobalScope</var>'s
190+
<a>name map of apply hooks</a>.
191+
192+
If an exception is thrown then abort these steps.
193+
194+
Issue: Need to deal with the output.
195+
196+
Note: Apply hooks run in parallel on a given Element. The output of an apply
197+
hook is never used as input to another apply hook on the same Element - instead,
198+
a snapshot of style state is taken, all apply hooks are run with subsets of the
199+
same input snapshot, then the resulting output is written into the used style.
200+
201+
Examples {#examples}
202+
====================
203+
204+
Example 1: Polyfill scale, translate, rotate {#example-1}
205+
---------------------------------------------------------
206+
207+
<pre class='lang-markup'>
208+
&lt;script&gt;
209+
["--scale-x", "--scale-y"].forEach(function(name) {
210+
document.registerProperty({
211+
name: name,
212+
syntax: "&lt;number&gt;",
213+
inherits: false,
214+
initialValue: "1"
215+
});
216+
});
217+
218+
["--translate-x", "--translate-y"].forEach(function(name) {
219+
document.registerProperty({
220+
name: name,
221+
syntax: "&lt;length&gt;",
222+
inherits: false,
223+
initialValue: "0px"
224+
});
225+
});
226+
227+
document.registerProperty({
228+
name: "--rotate",
229+
syntax: "&lt;angle&gt;",
230+
inherits: false,
231+
initialValue: "0deg"
232+
});
233+
&lt;/script&gt;
234+
&lt;style&gt;
235+
236+
#myElement {
237+
--translate-x: 5px;
238+
--translate-y: 10px;
239+
--rotate: 10deg;
240+
--scale-x: 25;
241+
--scale-y: 25;
242+
}
243+
244+
.foobar {
245+
--rotate: 20deg;
246+
}
247+
&lt;/style&gt;
248+
249+
&lt;script&gt;
250+
this.registerApplyHook("transform-properties", class {
251+
apply(el) {
252+
el.outputStyle.set('transform', new TransformValue(
253+
[
254+
new Translation(el.inputStyle.get('--translate-x'),
255+
el.inputStyle.get('--translate-y')),
256+
new Rotation(el.inputStyle.get('--rotate')),
257+
new Scale(el.inputStyle.get('--scale-x'),
258+
el.inputStyle.get('--scale-y'))
259+
].concat(el.inputStyle.get('transform'))));
260+
}}, {
261+
inputProperties: ["--translate-x", "--translate-y",
262+
"--scale-x", "--scale-y",
263+
"--rotate", "transform"],
264+
outputProperties: ["transform"]
265+
}
266+
});
267+
&lt;/script&gt;
268+
</pre>

0 commit comments

Comments
 (0)