ðOpen UI Advent Calendar: Day 17 / Customizable Select Element Ep.15
Published on December 17, 2024
Customizable Select Elementã®é¢é£ä»æ§: `<selectedcontent>` - `slot`å±æ§ãš`behavior`å±æ§ã䜿çšå»æ¢ãåããCSE Anatomyãæ¹èšãHTMLå²äžåãšãªããUAããLight DOMãžå€æŽãå ããå®è£ æ€èšãž
Table of Contents
Table of Contents
ã¯ããã«
Ep.14ã§ã¯ã<selectlist>
ã®slot
å±æ§ãšbehavior
å±æ§ã®äœ¿çšãå»æ¢ãããçµç·¯ãã話ãããŸãããslot
å±æ§ãšbehavior
å±æ§ã¯ãéžæããã<option>
ã<button>
ã«ã¹ãããããŠã«ã¹ã¿ãã€ãºã§ããããã«ãããããã®æ段ã ã£ãã®ã§ããããã®æ段ãå»æ¢ãããããšã«ãããããããã©ã話ãé²ãã®ããã¿ãŠãããŸãã
2024/12/9æç¹ã§ã®selectã®åããŒãã®å®çŸ©
Customizable Select Elementã®é¢é£ä»æ§
ãããŸã§ã®æŽç
ãããŸã§ã®çµç·¯ãäžæŠæŽçããŠãããŸããslot
å±æ§ãšbehavior
å±æ§ãå»æ¢ããããŸã§ã䞻㫠4 ã€ã® Issue ãé¢é£ããã£ãŠããŸããã
- Hooking up native controller code to user-provided UI parts - MSEdgeExplainers/ControlUICustomization/explainer.md at main · MicrosoftEdge/MSEdgeExplainers
slot
å±æ§ãšpart
å±æ§ã䜿ã£ãŠãéžæããã<option>
ã<button>
ã«ã¹ãããããŠã«ã¹ã¿ãã€ãºã§ããããã«ãããææ³ããåæã« MS ã® Explainer ã§ææ¡ããã
- [SELECT] The use of âpartâ clashes with custom elements containing
· Issue #354 · openui/open-ui part
å±æ§ã䜿ããšã<selectmenu>
ã Shadow DOM ã§ã©ãããããŠããå Žåã«ãå€éšããã¹ã¿ã€ã«ãé©çšãããåé¡ãææããããããã§part
å±æ§ãbehavior
å±æ§ã«å€æŽããã
- [select] Should the inner HTML & styles of the selected option be copied into selected-value? · Issue #571 · openui/open-ui
slot
å±æ§ãšbehavior
å±æ§ã䜿çšãããéžæããã<option>
ã®ã¯ããŒã³ãäœæããŠ<button>
ã«åæ ããããšãææ¡ïŒè°è«ãããããäžæŠåãäžããããããã®ãŸãŸslot
å±æ§ãšbehavior
å±æ§ã®äœ¿çšã¯ç¶è¡
- [select] Donât reuse slot="" and ::part(); behavior="" is also strange · Issue #702 · openui/open-ui
- WHATWG åŽããã
slot
å±æ§ãšbehavior
å±æ§ã®äœ¿çšãžã®çåãåãããçµæãšããŠslot
å±æ§ãšbehavior
å±æ§ã®å»æ¢ã決å®ãæ«å®ä»£æ¿æ¡ãšããŠãéžæããã<option>
ã®ã¯ããŒã³ãåæ ãã<selectedvalue>
ãææ¡ããã
- WHATWG åŽããã
ãšããæµãã蟿ã£ãŠã<selectedvalue>
ãææ¡ããããšãããŸã§æ¥ãŸããã
æ°ãã<selectlist>
ã®Anatomy
slot
å±æ§ãšbehavior
å±æ§ã®å»æ¢åŸã«åºãããæ°ãã Explainer ã§ã¯ãIssueã§ã®ã³ã¡ã³ãã«åºã¥ãã次ã®ãã㪠Anatomy ãšãªããŸããã
slot
å±æ§ãšbehavior
å±æ§ã®å»æ¢ã«ããã§ãå·®åã®ã¿ã«ãã©ãŒã«ã¹ããŠã¿ãŸãã
- button (slot) - The portion of the element which is rendered in the position of the button which opens the listbox. It should contain a button to open the listbox. If this part is not provided by the author, then
<selectlist>
will automatically create one. All child elements of the<selectlist>
, except<listbox>
,<option>
s, and<optgroup>
s will be slotted into this slot.<button type=selectlist>
- The button which opens the listbox when clicked. Thetype=selectlist
attribute indicates to the browser that this button should open the listbox.<selectedvalue>
- The element which contains the text of the currently selected option. Every time that the user selects an option, the browser will replace the text content of this element with the text content of the selected option.<listbox>
- The wrapper that contains the<option>
(s) and<optgroup>
(s). If this part was not provided by the author, then<selectlist>
will automatically create one.
- button (slot) - listboxãéãbuttonãé 眮ãããã¹ãããéšåããã®ã¹ãããã«ã¯ãlistboxãéãããã®buttonãé 眮ããããããAuthorããã®éšåãæäŸããªãå Žåã
<selectlist>
ãèªåçã«äœæããã<selectlist>
ã®åèŠçŽ ã§ã<listbox>
ã<option>
ã<optgroup>
以å€ã®å šãŠã®èŠçŽ ã¯ããã®ã¹ãããã«é 眮ãããã<button type=selectlist>
- ã¯ãªãã¯ããããšãã«listboxãéãbuttonãtype=selectlist
å±æ§å€ã¯ããã®ãã¿ã³ãlistboxãéãããšããã©ãŠã¶ã«ç€ºãã<selectedvalue>
- çŸåšéžæãããŠããoptionã®ããã¹ããå«ãèŠçŽ ããŠãŒã¶ãoptionãéžæãããã³ã«ããã©ãŠã¶ã¯ãã®èŠçŽ ã®ããã¹ãã³ã³ãã³ããéžæãããoptionã®ããã¹ãã³ã³ãã³ãã§çœ®ãæããã<listbox>
-<option>
ãš<optgroup>
ãå«ãã©ãããŒãããAuthorããã®éšåãæäŸããªãå Žåã<selectlist>
ãèªåçã«äœæããã
䜿çšäŸãã次ã®ããã«ãªã£ãŠããŸãã
<selectlist>
<button type=selectlist>
<span>selected option:</span>
<selectedoption></selectedoption>
</button>
<listbox>
<option>one</option>
<option>two</option>
</listbox>
</selectlist>
ãã®å·®åãèŠãéããããªãçŸåšã® CSE ã®ä»æ§ã«è¿ã圢ã«ãªã£ãããã«èŠããŸãã
ãããããã®æ®µéã§ã¯ã<selectedvalue>
ã¯éžæããã<option>
ã®äžèº«ããŸãã£ãšã¯ããŒã³ããŠããçŸåšã®ä»æ§ãšã¯ç°ãªãããŸã åã«ãã¬ãŒã³ããã¹ãã§çœ®æããã®ã¿ãšãããµãã«èªã¿åããŸãã
èŠçŽ ã¯ããŒã³ã®è©±ã¯ã©ããªã£ãã®ããæ¢ãåã«ã<selectedvalue>
ã¯â<selectedvalue>
âã§ã¯ãªããªãã®ã§ãäžæŠè»œã確èªããŠãããŸãã
<selectedvalue>
ã<selectedoption>
ãž
ãããã<selectedvalue>
ã§ææ¡ãããã®ã¯ã以ååçã®æ©èœãæã£ãŠããããbehavior
ã®å»æ¢ãšãšãã«æ¶ããŠããŸã£ãbehavior=selected-value
å±æ§å€ã«ç±æ¥ããããã§ããã
äžèš Issue ã§ã¯ã<selectedoption>
ãæååè£ãšããŠæããããç¹ã«å€§ããªå察ããªãã<selectedoption>
ãæ¡çšãããŠExplainerã«åæ ãããŸãã
ããã§åã³ãå
ã
éžæããã<option>
ã®ã¯ããŒã³ãæ€èšãããèæ¯ã®ããIssueã«æ»ããŸãã
ãã㧠Jarhar ãææ¡ãããããã¬ãŒã³ããã¹ãã ãã§ã¯ãªããéžæããã<option>
ã®äžèº«ããŸãã£ãšã¯ããŒã³ãã<selectedoption>
ã®å®è£
ãããHTMLã§åããŠæ¡çšãããå®è£
ã®åºçºç¹ã§ããããçŸåšã®<selectedcontent>
ã®å
ãšãªããŸãã
ïŒâ» ãã以åã®æç¹ã§ã<selectedmenu>
â<selectlist>
ã«å€æŽãããŠããã®ã§ããã®å
ã¯<selectlist>
ãšèšè¿°ããŸãïŒ
éžæããã<option>
ã®åèŠçŽ ã<selectedoption>
ã«ã¯ããŒã³ããææ¡ã®ååºçº
Jarjar ã®ã³ã¡ã³ããéåžžã«ãããŸãšãŸã£ãŠããã®ã§ããã®ç¯ã¯ã³ã¡ã³ããæèš³ãããã®ã§ãã
ãã®ææ¡ã¯ãMason Freed ãš Domenic ãšè©±ãåãããçµæãè³ã£ãçµè«ã®ãŸãšãã§ãã
TL;DR: ãã©ãŠã¶ããéžæããã<option>
ã®å
容ã<selectedoption>
ã«cloneNode()
ããŠããã®åNodeã眮ãæããã¹ãã ãšèããŠãããæšæºåã«åããŠããè¯ã解決çãèŠã€ããã°ãããã«åãæ¿ããããšãèããŠããããçŸåšã¯ãChromiumã§cloneNode()
ãçšãããããã¿ã€ããå®è£
ããŠããã
次ã«ãäžèšçµè«ã®éçšã§èæ ®ããããå®è£ æ¹éã®æ¯èŒæ€èšãããŠããŸãã
1. éžæããã<option>
ã®åNodeã<selectedoption>
ã®ShadowRootã«ã¯ããŒã³ãã
<selectedoption>
㯠UA ã® ShadowRoot ãæã¡ãéžæããã<option>
ãå€ãããã³ã«<selectedoption>
ã® ShadowRoot ã®ãã¹ãŠã®åãåé€ãã<option>
ãcloneNode()
ããŠããã®ã¯ããŒã³ã®åå Node ã<selectedoption>
ã®ShadowRootã«è¿œå ããŸãã
ãŸããAuthor åŽãã UA ShadowRoot å
ã®<selectedoption>
ã®ã¹ã¿ã€ã«ãããäžã§ã<selectedoption>
ã® ShadowRoot å
ã®ã³ã³ãã³ããã¿ãŒã²ããã«æå®ããããã®ã::selectedoption()
ã®ãããªç䌌èŠçŽ ãæäŸããå¿
èŠããããŸãã
ããã¯ãSVG ã®<use>
ã®äœ¿ãæ¹ã«äŒŒãŠããŸãã
ãã®ææ³ã®åé¡ç¹:
- ã¹ã¯ãªããã UA ShadowRoot ã§ãã
<selectedoption>
ã<selectlist>
ã«ã¢ã¯ã»ã¹ã§ããŠããŸããŸãã document.querySelector(::selectedoption)
ã¯ãUA ShadowRoot å ã® Node ã«ã¹ã¯ãªããã§ã¢ã¯ã»ã¹ããå£ãšãªãåŸãŸãããããã ShadowTree ãé¡ãããšãã§ããŸãã<script>
èŠçŽ ãã¯ããŒã³ããå Žåã¯ã©ããªãã§ããããïŒïŒcurrentScript
ã䜿çšããŠããªãŒå ã® Node ã«ã¢ã¯ã»ã¹ã§ããïŒ<script>
ãã¯ããŒã³ããªãããã«ããããšãã§ããŸããã<iframe>
ã¯ã©ãã§ããããïŒïŒframeElement
ã䜿çšã§ããïŒ- ã¯ããŒã³/è¿œå äžã«ã¹ã¯ãªãããåæçã«å®è¡ãããªãããã«ããããã«ãSide effects due to tree insertion or removal (script, iframe) #808ã解決ããå¿ èŠããããããããŸããã
2. éžæããã<option>
ã®åNodeã<selectedoption>
ã®Light DOMã«ã¯ããŒã³ãã
1 ã®ããã«ãã¯ããŒã³å
ã« ShadowRoot ãæã€ä»£ããã«ã<selectedoption>
ã® Light DOM å Node ãã¯ããŒã³ããã Node ã§çœ®ãæããŸãã
ãã®ææ³ã®åé¡ç¹:
- HTML ã«ã¯çŸåšãLight DOM ããã®ããã«å€æŽãããã®ã¯ãããŸãããä»æ§ã¯
<selectedoption>
ã®å Node ãèšå®ããããšããAuthor ã® Conformance Error ãšããããšãã§ããŸãã - ã¯ããŒã³/è¿œå äžã«ã¹ã¯ãªãããåæçã«å®è¡ãããªãããã«ããããã«ãSide effects due to tree insertion or removal (script, iframe) #808ã解決ããå¿ èŠããããããããŸããã
3. CSSã§ã³ã³ãã³ããå
éšçã«ãã©ãŒãªã³ã°ããããã®element()
ã®ãµããŒã
Firefox 㯠CSS ã§ã³ã³ãã³ãããã©ãŒãªã³ã°ããæ¹æ³ãå®è£ ããŠããŸã:

ãããšåçã®æ©èœãå®è£
ããéžæããã<option>
ã®ã¬ã³ããªã³ã°çµæã<selectedoption>
ã«ãã©ãŒãªã³ã°ããããšãã§ããŸãã
ãã®ææ³ã®åé¡ç¹:
<selectedoption>
ãš<option>
ããããã«ãå¥ã®ã¹ã¿ã€ãªã³ã°ãé©çšã§ããªãã- Listbox ã«è¡šç€ºããã
<option>
ã®ããã¯ã¹ãµã€ãºã¯ã<selectlist>
ã®<button>
éšåã®ããã¯ã¹ãµã€ãºãšã¯éåžžç°ãªããŸãããã®ããããã©ãŒãªã³ã°ãããç»å㯠crop ãŸã㯠stretch ããŠãã£ãããããå¿ èŠããããŸãã <selectedoption>
ã« DOM ã®å®æ ãæäŸããªãããã<selectedoption>
ã®äžèº«ãäœã§ããããç¥ãè¡ããããŸãããæ¯æŽæè¡ã¯ãããèªã¿åããªããã察åŠããããã®ä¿®æ£ãå¿ èŠã§ãã- Chromium ãš WebKit ã«ã¯ããã®å®è£ ããªããããå®è£ ã³ã¹ããããããŸãã
4. Magical mirroring
3 ã®ãããªãåãªãç»åãšããŠã®ãã©ãŒãªã³ã°ã§ã¯ãªããéžæããã<option>
ã®å Node ã<selectedoption>
ã®Flat treeãŸãã¯Layout treeã«ãçŸããããã«ãã©ãŒãªã³ã°ããæ¹æ³ãèŠã€ããããšãã§ãããããããŸããã
é¡äŒŒã®ã¢ã€ãã¢ãææ¡ãããŠããŸãããåããã®ãæå³ãããã¯äžæã§ã: <mirror>
element, like <slot>
, but not limited to ShadowDOM, elements from anywhere can be assigned to it · Issue #6507 · whatwg/html
ãã®ææ³ã®åé¡ç¹:
- å®è£ ãé£ãããäžå¯èœãããããŸããã
- Node ã DOM ããªãŒãFlat treeãLayout tree ã«äžåºŠã ãçŸãããšããäžå€æ¡ä»¶ãéæããå¿ èŠããããŸãã
- ãã©ãŒãªã³ã°ã§ã¯ããåã Nodeãã<
selectedoption>
ãš<option>
ã®äž¡æ¹ã®åãšããŠçŸããŸããCSS ã»ã¬ã¯ã¿ã§ã¯ã©ããªãã§ããããïŒäŸãã°ãselectedoption > .foo { ... }
ãšoption > .foo { ... }
ã¯ãç°ãªãã¹ã¿ã€ãªã³ã°ã®ç®æšãéæããããã«ç°ãªãåäœãäžããããšãæåŸ ãããŠããŸãããCSS ã»ã¬ã¯ã¿ã¯ãNodeããéžæããããããåã Nodeããã¿ãŒã²ããã«ããŸãã
5. ãããããŠå šå¡ã«ã¹ã¯ãªãããè¿œå ããã
ãLight DOM ãžã®ã¯ããŒã³ããå®çŸããããã«å¿ èŠãªã次ã®ãããªã¹ã¯ãªãããéçºè ã«æäŸããŸã:
selectlist.addEventListener('change', () => {
while (selectedoption.firstChild) {
selectedoption.firstChild.remove();
}
for (const newChild of selectmenu.selectedOption.cloneNode(true)) {
selectedoption.appendChild(newChild);
}
selectedoption.className = selectmenu.selectedOption.className;
});
ãã®ææ³ã®åé¡ç¹:
- éçºè
ã® 80%ãã
<selectlist>
ã®ãŠãŒã¹ã±ãŒã¹ããã®åäœãããããšãæåŸ ããŠãããšãããŠããŸãããããªäžããã®ã¹ã¯ãªãããã³ããŒïŒããŒã¹ãããŠåäœãããå¿ èŠããããªãã°ã<selectlist>
ã®æã€äŸ¡å€ã倱ãããŠããŸããŸãã宣èšçãªè§£æ±ºçã®æäŸã¯éèŠã§ãããã§ããªããã°ãéçºè ã¯<selectlist>
ã䜿çšããªããããããŸããã
ãã®ããã«ãäžå£ã«ã¯ããŒã³ãããšèšã£ãŠãããŸããŸãªææ³ãèããããããããã« pros/cons ãããããšãããããŸããåã«ã¯ããŒã³ããã«ããŠã Light DOM ã«ã¯ããŒã³ããã®ããShadow DOM ã«ã¯ããŒã³ããã®ãã«å€æã®äœå°ããã£ããããã©ãŒãªã³ã°ã¯å®è£ ã DOM çãªæžå¿µããã£ããããŸãã
ããã«å¯ŸããŠãDomenic ã¯ãã¯ããŒã³ã«ãã Light DOM ã®å€æŽãšããã®ã¯åäŸã®ãªãããšã§ã¯ãããããã®ãŠãŒã¹ã±ãŒã¹ãéæããããã®å¯äžã®åççãªéžæè¢ã§ãããšèããŠããããšãã£ãŒãããã¯ããŸãã
Light DOM ã«å¥ã®èŠçŽ ã® Light DOM ãã¯ããŒã³ãããšã<selectedoption>
ã¯å
·äœçã«ã¯æ¬¡ã®ãããªæåãããããšã«ãªããŸãã
次ã®å³ã§ã¯ãéžæããã<option>
ã®å Node ãã<selectedoption>
ã®Light DOMã«ã¯ããŒã³ãããæ§åã瀺ããŠããŸãã
Light DOM ã«çŽæ¥æ¿å
¥ããããããã¯ããŒã³ãããèŠçŽ ã¯ãAuthor Style Sheet ã®é©çšãå¯èœãšãªããŸãã
UAã«ãã£ãŠLight DOMã«ã¯ããŒã³ãããNodeãçŽæ¥æ¿å
¥ãããæ§å
ãããããèŠçŽ ãèªèº«ã® Light DOM ãå€æŽãããšããæåã¯ãä»ã®é·å¹Žã® HTML æ©èœã«å¯Ÿãããªã¯ãšã¹ãã解決ããããã«ãå¿ èŠãããããªããš Domenic ã¯è¿°ã¹ãŸãã
äŸãã°ãäžèšã®ããã«ãUA ã Light DOM ãå€æŽããŠè¯ãã®ã§ããã°ããããŸã§å®çŸã§ããªãã£ã次ã®ãããªãŠãŒã¹ã±ãŒã¹ãã«ããŒã§ããããã«ãªããããããŸãã:
<include src="foo.html"></include>
ã®ããã«ãfoo.html ã®å 容ã<include>
ã® Light DOM å ã«å ¥ãããã«ããæ©èœ<relativetime>2023-08-28T00:00:00Z</relativetime>
ã®ããã«ãAuthor åŽããã® input ã«å¯ŸããŠãUA ãå éšçã«æ¥æãèšç®ããåçã« Light DOM ãå€æŽããããšã§çµæã衚瀺ããæ©èœã<p>
ã®ããã«ãAuthor ã® input ããã®ãŸãŸè¡šç€ºãããã®ã§ã¯ãªããUA ãèœåçã« Light DOM ãå€æŽããå¿ èŠãããã®ããã€ã³ãã
Domenic ã¯ããã®å®è£ ã«ãã£ãŠããã©ãããã©ãŒã ãé·å¹ŽèŠããã§ãããLight DOM ã¯å®å šã« Author ã®é åã§ãããUA ã¹ã¯ãªããã«ãã£ãŠå€æŽãããã¹ãã§ã¯ãªãããšããå¶çŽã«å¯ŸããŠãä»åæèçã«ãã®å¢çãè¶ããããšãã§ããã°ãä»åŸã®æ°ããéžæè¢ãéãããããããªããšãã蚌æ ãæäŸãããããšè¿°ã¹ãŠããŸãã
ããããŠã<selectedoption>
ã®ææ¡ãåã¹ã¿ãŒããåããéžæããã<option>
ã<selectedcontent>
ã® Light DOM ã«ã¯ããŒã³ããä»æ§ãçå®ïŒå®è£
ãããŠããããšã«ãªããŸãã
HTML å²äžåãšãªããUA ãã Light DOM ãžã®ã¯ããŒã³è¿œå å®è£ ãCSE ã®ã¿ãªãããHTML ã®æ°ããªå¯èœæ§ãåãéããã£ãããšèšããéåžžã«èå³æ·±ããã®ãšãªã£ãŠããããã§ãïŒ
次åããã¯ããã®<selectedoption>
ã«ã€ããŠãã©ã®ãããªå®è£
äžã®èª²é¡ããããã©ã解決ãããŠããã®ããèŠãŠãããŸãã
ããã§ã¯ããŸãææ¥â
See you tomorrow!