-
Notifications
You must be signed in to change notification settings - Fork 672
/
Overview.bs
460 lines (380 loc) · 19.7 KB
/
Overview.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
<pre class='metadata'>
Title: CSS Shadow Parts
Shortname: css-shadow-parts
Level: 1
Group: CSSWG
Status: ED
Work Status: exploring
URL: http://drafts.csswg.org/css-shadow-parts/
TR: https://www.w3.org/TR/css-shadow-parts-1/
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
Editor: Fergal Daly, Google, [email protected]
Abstract: This specification defines the ''::part()'' pseudo-element on <a>shadow hosts</a>, allowing <a>shadow hosts</a> to selectively expose chosen elements from their <a>shadow tree</a> to the outside page for styling purposes.
</pre>
<pre class=link-defaults>
spec:selectors-4;
type:selector; text::hover
type:dfn; text:live profile
type:dfn; text:structural pseudo-class
spec:dom; type:dfn; for:/; text:shadow root
spec:infra;
type:dfn;
text:string
text:list
</pre>
Introduction {#intro}
=====================
Shadow DOM allows authors to separate their page into "components",
subtrees of markup whose details are only relevant to the component itself,
not the outside page.
This reduces the chance of a style meant for one part of the page
accidentally over-applying and making a different part of the page look wrong.
However, this styling barrier also makes it harder for a page to interact with its components
when it actually <em>wants</em> to do so.
This specification defines the ''::part()'' pseudo-element,
which allows an author to style specific, purposely exposed elements in a <a>shadow tree</a>
from the outside page's context.
In combination with <a>custom properties</a>,
which let the outside page pass particular values
(such as theme colors)
into the component for it to do with as it will,
these pseudo-elements allow components and the outside page
to interact in safe, powerful ways,
maintaining encapsulation
without surrendering all control.
Motivation {#motivation}
------------------------
For custom elements to be fully useful and as capable as built-in elements
it should be possible for parts of them to be styled from outside.
Exactly what can be styled from outside should be controlled by the element author.
Also, it should be possible for a custom element to present a stable "API" for styling.
That is, the selector used to style a part of a custom element
should not expose or require knowledge of the internal details of the element.
The custom element author should be able to change the internal details of the element
while leaving the selectors untouched.
The previous proposed method for styling inside the shadow tree,
the >>> combinator,
turned out to be <em>too powerful</em> for its own good;
it exposed too much of a component's internal structure to scrutiny,
defeating some of the encapsulation benefits that using Shadow DOM brings.
For this,
and other performance-related reasons,
the >>> combinator was eventually removed from the <a>live profile</a>.
This left us with using <a>custom properties</a> as the only way to style into a shadow tree:
the component would advertise that it uses certain <a>custom properties</a> to style its internals,
and the outer page could then set those properties as it wished on the <a>shadow host</a>,
letting inheritance push the values down to where they were needed.
This works very well for many simple theming use-cases.
However, there are some cases where this falls down.
If a component wishes to allow arbitrary styling of something in its shadow tree,
the only way to do so is to define hundreds of <a>custom properties</a>
(one per CSS property they wish to allow control of),
which is obviously ridiculous
for both usability and performance reasons.
The situation is compounded if authors wish to style the component differently
based on pseudo-classes like '':hover'';
the component needs to duplicate the <a>custom properties</a> used
for each pseudo-class
(and each combination,
like '':hover:focus'',
resulting in a combinatorial explosion).
This makes the usability and performance problems even worse.
We introduce ''::part()'' to handle this case much more elegantly and performantly.
Rather than bundling everything into <a>custom property</a> names,
the functionality lives in selectors and style rule syntax,
like it's meant to.
This is far more usable for both component authors
and component users,
should have much better performance,
and allows for better encapsulation/API surface.
It's important to note that ''::part()''
offers <em>absolutely zero new theoretical power</em>.
It is not a rehash of the <css>>>></css> combinator,
it is simply a more convenient and consistent syntax
for something authors can already do with <a>custom properties</a>.
By separating out the explicitly "published" parts of an element
(the <a>part element map</a>)
from the sub-parts that it merely happens to contain,
it also helps with encapsulation,
as authors can use ''::part()'' without fear of accidental over-styling.
<!-- Big Text: parts
████▌ ███▌ ████▌ █████▌ ███▌
█▌ █▌ ▐█ ▐█ █▌ █▌ █▌ █▌ █▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
████▌ █▌ █▌ ████▌ █▌ ███▌
█▌ █████▌ █▌▐█ █▌ █▌
█▌ █▌ █▌ █▌ ▐█ █▌ █▌ █▌
█▌ █▌ █▌ █▌ █▌ █▌ ███▌
-->
Exposing a Shadow Element: {#exposing}
=============================================================
Elements in a <a>shadow tree</a> may be exported for styling by stylesheets outside the tree
using the part and exportparts attributes.
Each element has a <dfn export for="element">part name list</dfn>
which is an [=ordered set=] of tokens.
Each element has a <dfn export for="element">forwarded part name list</dfn>
which is a [=list=] of [=pairs=]
containing a [=string=] for the inner part being forwarded
and a [=string=] giving the name it will be exposed as.
Each <a>shadow root</a> can be thought of as having a <dfn export for="shadow root">part element map</dfn>
with keys that are [=strings=]
and values that are [=ordered sets=] of elements.
The [=part element map=] is described
only as part of the algorithm for calculating style in this spec.
It is not exposed via the DOM,
as calculating it may be expensive
and exposing it could allow access to elements inside closed shadow roots.
[=Part element maps=] are affected by the addition and removal of elements
and changes to the [=part name lists=] and [=forwarded part name lists=] of elements in the DOM.
<div algorithm>
To <dfn>calculate the [=part element map=]</dfn> of a shadow root, |outerRoot|:
1. For each [=descendant=] |el| within |outerRoot|:
1. For each |name| in |el|'s [=part name list=],
[=list/append=] |el| to |outerRoot|'s [=part element map=][|name|].
2. If |el| is a [=shadow host=] itself
then let |innerRoot| be its shadow root.
3. [=calculate the part element map|Calculate=] |innerRoot|'s [=part element map=].
4. For each |innerName|/|outerName| in |el|'s [=forwarded part name list=]:
1. If |innerName| is an ident:
1. Let |innerParts| be |innerRoot|'s [=part element map=][|innerName|]
2. [=list/Append=] the elements in |innerParts|
to |outerRoot|'s [=part element map=][|outerName|]
2. If |innerName| is a pseudo-element name:
1. [=list/Append=] |innerRoot|'s pseudo-element(s) with that name
to |outerRoot|'s [=part element map=][|outerName|].
</div>
<!-- Big Text: part=""
████▌ ███▌ ████▌ █████▌ ▐█▌ ▐█▌ ▐█▌ ▐█▌
█▌ █▌ ▐█ ▐█ █▌ █▌ █▌ ▐█▌ ▐█▌ ▐█▌ ▐█▌
█▌ █▌ █▌ █▌ █▌ █▌ █▌ ████ █ █ █ █
████▌ █▌ █▌ ████▌ █▌
█▌ █████▌ █▌▐█ █▌ ████
█▌ █▌ █▌ █▌ ▐█ █▌
█▌ █▌ █▌ █▌ █▌ █▌
-->
Naming a Shadow Element: the <{html-global/part}> attribute {#part-attr}
------------------------------------------------------------------------
Any element in a shadow tree can have a <dfn element-attr for=html-global>part</dfn> attribute.
This is used to expose the element outside of the <a>shadow tree</a>.
The part attribute is parsed as a space-separated list of tokens representing the part names of this element.
Note: It's okay to give a part multiple names.
The "part name" should be considered similar to a class,
not an id or tagname.
<pre class="example">
<style>
c-e<b>::part(textspan)</b> { color: red; }
</style>
<template id="c-e-template">
<span <b>part="textspan"</b>>This text will be red</span>
</template>
<c-e></c-e>
<script>
// Add template as custom element c-e
...
</script>
</pre>
<!-- Big Text: exportparts
█████▌ █ █ ████▌ ███▌ ████▌ █████▌ ████▌ ███▌ ████▌ █████▌ ███▌
█▌ █ █ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ ▐█ ▐█ █▌ █▌ █▌ █▌ █▌
█▌ █ █ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌
████ █ ████▌ █▌ █▌ ████▌ █▌ ████▌ █▌ █▌ ████▌ █▌ ███▌
█▌ █ █ █▌ █▌ █▌ █▌▐█ █▌ █▌ █████▌ █▌▐█ █▌ █▌
█▌ █ █ █▌ █▌ █▌ █▌ ▐█ █▌ █▌ █▌ █▌ █▌ ▐█ █▌ █▌ █▌
█████▌ █ █ █▌ ███▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ ███▌
-->
Forwarding a Shadow Element: the <{html-global/exportparts}> attribute {#exportparts-attr}
----------------------------------------------------------------------------------
Any element in a shadow tree can have a <dfn element-attr for=html-global>exportparts</dfn> attribute.
If the element is a shadow host,
this is used to allow styling of parts from hosts inside the <a>shadow tree</a>
by rules outside this the <a>shadow tree</a>
(as if they were elements in the same tree as the host,
named by a part attribute).
The exportparts attribute is parsed as a comma-separated list of part mappings.
Each part mapping is one of:
<dl class=switch>
: <code>innerIdent : outerIdent</code>
:: Adds <code>innerIdent</code>/<code>outerIdent</code> to el's <a>forwarded part name list</a>.
: <code>ident</code>
:: Adds <code>ident</code>/<code>ident</code> to el's <a>forwarded part name list</a>.
Note: This is shorthand for <code>ident : ident</code>.
: <code>::ident : outerIdent</code>
:: If <code>::ident</code> is the name of a [=part-like pseudo-element=],
adds <code>::ident</code>/<code>outerIdent</code>
to el's [=forward part name list=].
Otherwise, does nothing.
: anything else
:: Ignored for error-recovery / future compatibility.
</dl>
Note: It's okay to map a sub-part to several names.
<pre class="example">
<style>
c-e<b>::part(textspan)</b> { color: red; }
</style>
<template id="c-e-outer-template">
<c-e-inner <b>exportparts="innerspan: textspan"</b>></c-e-inner>
</template>
<template id="c-e-inner-template">
<span <b>part="innerspan"</b>>
This text will be red because the containing shadow
host forwards <b>innerspan</b> to the document as "textspan"
and the document style matches it.
</span>
<span <b>part="textspan"</b>>
This text will not be red because <b>textspan</b> in the document style
cannot match against the part inside the inner custom element
if it is not forwarded.
</span>
</template>
<c-e></c-e>
<script>
// Add template as custom elements c-e-inner, c-e-outer
...
</script>
</pre>
<div class=example>
For example, a [=part-like pseudo-element=]
can be used in the <{html-global/exportparts}> attribute,
to masquerade as a ''::part()''
for the component it's in:
<xmp class=html>
<template id=custom-element-template>
<p exportparts="::before : preceding-text, ::after : following-text">
Main text.
</template>
</xmp>
An element using that template
can use a selector like ''x-component::part(preceding-text)''
to target the ''p::before'' pseudo-element in its shadow,
so users of the component don't need to know
that the preceding text is implemented as a pseudo-element.
</div>
<!-- Big Text: ::part()
█▌ █▌ ████▌ ███▌ ████▌ █████▌ ██ ██
███▌ ███▌ █▌ █▌ ▐█ ▐█ █▌ █▌ █▌ █▌ ▐█
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ ▐█
████▌ █▌ █▌ ████▌ █▌ █▌ ▐█
█▌ █▌ █▌ █████▌ █▌▐█ █▌ █▌ ▐█
███▌ ███▌ █▌ █▌ █▌ █▌ ▐█ █▌ █▌ ▐█
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ ██ ██
-->
Selecting a Shadow Element: the ''::part()'' pseudo-element {#part}
===================================================================
The <dfn selector>::part()</dfn> pseudo-element
allows you to select elements that have been exposed via a <{html-global/part}> attribute.
The syntax is:
<pre class=prod>
::part() = ::part( <<ident>>+ )
</pre>
The ''::part()'' pseudo-element only matches anything
when the <a>originating element</a> is a <a>shadow host</a>.
<div class="example">
For example,
if you have a custom button
that contains a "label" element that is exposed for styling
(via <code>part="label"</code>),
you can select it with
''x-button::part(label)''.
</div>
<div class="example">
Part names act similarly to classes:
multiple elements can have the same part name,
and a single element can have multiple part names.
A tabstrip control might have multiple elements
with <code>part="tab"</code>,
all of which are selected by ''::part(tab)''.
If a single tab is active at a time,
it can be specially indicated with <code>part="tab active"</code>
and then selected by ''::part(tab active)''
(or ''::part(active tab)'', as order doesn't matter).
</div>
The ''::part()'' pseudo-element is a [=part-like pseudo-element=].
If the <a>originating element's</a> <a>shadow root's</a> <a>part element map</a>
[=map/contains=] the specified <<ident>>,
''::part()'' represents the elements keyed to that ident;
if multiple idents are provided and the [=part element map=] contains them all,
it represents the intersection of the elements keyed to each ident.
Otherwise, it matches nothing.
''::part()'' pseudo-elements inherit according to their position
in the [=originating element's=] shadow tree.
<div class=example>
For example,
''x-panel::part(confirm-button)::part(label)''
never matches anything.
This is because doing so would expose more structural information
than is intended.
If the <code><x-panel></code>'s internal confirm button had used something like
<code>part="label => confirm-label"</code>
to forward the button's internal parts up into the panel's own <a>part element map</a>,
then a selector like
''x-panel::part(confirm-label)''
would select just the one button's label,
ignoring any other labels.
</div>
<!-- Big Text: CSSOM
███▌ ███▌ ███▌ ███▌ █ █
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ ██ ██
█▌ █▌ █▌ █▌ █▌ █▌█ █▐█
█▌ ███▌ ███▌ █▌ █▌ █▌ █ ▐█
█▌ █▌ █▌ █▌ █▌ █▌ ▐█
█▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ █▌ ▐█
███▌ ███▌ ███▌ ███▌ █▌ ▐█
-->
Extensions to the {{Element}} Interface {#idl}
==============================================
<pre class=idl>
partial interface Element {
[SameObject, PutForwards=value] readonly attribute DOMTokenList part;
};
</pre>
The part attribute’s getter must return a DOMTokenList object
whose associated element is the context object
and whose associated attribute’s local name is part.
The token set of this particular DOMTokenList object are also known as the element’s parts.
Issue(w3c/csswg-drafts#3424): Define this as a superglobal in the DOM spec.
Microsyntaxes for parsing {#parsing}
==============================================
Rules for parsing part mappings {#parsing-mapping}
----------------------------------------------
A <dfn export>valid part mapping</dfn> is a [=pair=] of tokens
separated by a U+003A COLON character
and any number of space characters before or after the U+003A COLON
The tokens must not contain U+003A COLON or U+002C COMMA characters.
The rules for parsing a part mapping are as follows:
1. Let <var>input</var> be the string being parsed.
1. Let <var>position</var> be a pointer into <var>input</var>, initially pointing at the start of the string.
1. [=Collect a sequence of code points=] that are space characters
1. [=Collect a sequence of code points=] that are not space characters or U+003A COLON characters,
and let <var>first token</var> be the result.
1. If <var>first token</var> is empty then return error.
1. [=Collect a sequence of code points=] that are space characters.
1. If the end of the <var>input</var> has been reached, return the [=pair=] <var>first token</var>/<var>first token</var>
1. If character at <var>position</var> is not a U+003A COLON character, return error.
1. Consume the U+003A COLON character.
1. [=Collect a sequence of code points=] that are space characters.
1. [=Collect a sequence of code points=] that are not space characters or U+003A COLON characters.
and let <var>second token</var> be the result.
1. If <var>second token</var> is empty then return error.
1. [=Collect a sequence of code points=] that are space characters.
1. If <var>position</var> is not past the end of <var>input</var> then return error.
1. Return the [=pair=] <var>first token</var>/<var>second token</var>.
Rules for parsing a list of part mappings {#parsing-mapping-list}
----------------------------------------------
A <dfn export>valid list of part mappings</dfn> is a number of valid part mappings
separated by a U+002C COMMA character
and any number of space characters before or after the U+002C COMMA
The rules for parsing a list of part mappings are as follow:
1. Let <var>input</var> be the string being parsed.
1. <span data-x="split a string on commas">Split the string <var>input</var> on
commas</span>. Let <var>unparsed mappings</var> be the resulting list of strings.
1. Let <var>mappings</var> be an initially empty [=list=] of [=pairs=] of tokens.
This [=list=] will be the result of this algorithm.
1. For each string <var>unparsed mapping</var> in <var>unparsed mappings</var>,
run the following substeps:
1. If <var>unparsed mapping</var> is empty or contains only space characters,
continue to the next iteration of the loop.
1. Let <var>mapping</var> be the result of parsing <var>unparsed mapping</var>
using the <span>rules for parsing part mappings</span>.
1. If <var>mapping</var> is an error then continue to the next iteration of the loop.
This allows clients to skip over new syntax that is not understood.
1. Append <var>mapping</var> to <var>mappings</var>.