1. 소개
Shadow DOM은 작성자가 페이지를 "컴포넌트"로 분리할 수 있게 합니다. 이는 세부 사항이 외부 페이지가 아니라 컴포넌트 자체에만 관련되는 마크업의 하위 트리입니다. 이로 인해 페이지의 한 부분을 위한 스타일이 우연히 과도하게 적용되어 페이지의 다른 부분이 잘못 보일 가능성이 줄어듭니다. 그러나 이러한 스타일링 장벽은 페이지가 실제로 컴포넌트와 상호작용하기를 원할 때 그렇게 하기 어렵게 만들기도 합니다.
이 명세는 ::part() 의사 요소를 정의합니다. 이는 작성자가 shadow tree 안에서 특정하게 의도적으로 노출된 요소를 외부 페이지의 컨텍스트에서 스타일링할 수 있게 합니다. 외부 페이지가 특정 값 (예: 테마 색상)을 컴포넌트에 전달하여 컴포넌트가 원하는 대로 사용할 수 있게 하는 custom properties와 함께, 이러한 의사 요소는 컴포넌트와 외부 페이지가 모든 제어를 포기하지 않으면서도 캡슐화를 유지한 채 안전하고 강력한 방식으로 상호작용할 수 있게 합니다.
테스트
shadow parts에 대한 일반 테스트
- all-hosts.html (라이브 테스트) (소스)
- animation-part.html (라이브 테스트) (소스)
- chaining-invalid-selector.html (라이브 테스트) (소스)
- complex-matching.html (라이브 테스트) (소스)
- complex-non-matching.html (라이브 테스트) (소스)
- different-host.html (라이브 테스트) (소스)
- double-forward.html (라이브 테스트) (소스)
- grouping-with-checked.html (라이브 테스트) (소스)
- grouping-with-disabled.html (라이브 테스트) (소스)
- host-stylesheet.html (라이브 테스트) (소스)
- inner-host.html (라이브 테스트) (소스)
- interaction-with-nested-pseudo-class.html (라이브 테스트) (소스)
- interaction-with-placeholder.html (라이브 테스트) (소스)
- interaction-with-pseudo-elements.html (라이브 테스트) (소스)
- invalidation-complex-selector-forward.html (라이브 테스트) (소스)
- invalidation-complex-selector.html (라이브 테스트) (소스)
- invalidation-part-pseudo.html (라이브 테스트) (소스)
- multiple-parts.html (라이브 테스트) (소스)
- part-after-combinator-invalidation.html (라이브 테스트) (소스)
- part-mutation-pseudo.html (라이브 테스트) (소스)
- part-nested-pseudo.html (라이브 테스트) (소스)
- precedence-part-vs-part.html (라이브 테스트) (소스)
- pseudo-classes-after-part.html (라이브 테스트) (소스)
- pseudo-elements-after-part.html (라이브 테스트) (소스)
- serialization.html (라이브 테스트) (소스)
- simple-forward-shorthand.html (라이브 테스트) (소스)
- simple-forward.html (라이브 테스트) (소스)
- simple-important.html (라이브 테스트) (소스)
- simple-important-important.html (라이브 테스트) (소스)
- simple-important-inline.html (라이브 테스트) (소스)
- simple.html (라이브 테스트) (소스)
- simple-important.html (라이브 테스트) (소스)
- simple-inline.html (라이브 테스트) (소스)
- style-sharing.html (라이브 테스트) (소스)
1.1. 동기
custom element가 내장 요소만큼 충분히 유용하고 능력 있게 되려면 그 일부를 외부에서 스타일링할 수 있어야 합니다. 외부에서 정확히 무엇을 스타일링할 수 있는지는 요소 작성자가 제어해야 합니다. 또한 custom element가 스타일링을 위한 안정적인 "API"를 제공할 수 있어야 합니다. 즉, custom element의 일부를 스타일링하는 데 사용되는 선택자는 요소의 내부 세부 사항을 노출하거나 이에 대한 지식을 요구해서는 안 됩니다. custom element 작성자는 선택자를 그대로 둔 채 요소의 내부 세부 사항을 변경할 수 있어야 합니다.
shadow tree 내부를 스타일링하기 위해 이전에 제안되었던 방법인 >>> 결합자는 그 자체의 목적에 비해 너무 강력한 것으로 드러났습니다. 이는 컴포넌트의 내부 구조를 너무 많이 들여다볼 수 있게 하여, Shadow DOM 사용이 가져오는 캡슐화 이점 일부를 무력화했습니다. 이러한 이유와 다른 성능 관련 이유로, >>> 결합자는 결국 폐기되었습니다.
이로 인해 shadow tree 안을 스타일링하는 유일한 방법으로 custom properties를 사용하는 방식만 남았습니다. 컴포넌트는 내부를 스타일링하기 위해 특정 custom properties를 사용한다고 알리고, 외부 페이지는 shadow host에 원하는 대로 이러한 속성을 설정하여, 상속이 필요한 위치까지 값을 전달하도록 합니다. 이는 많은 단순한 테마 적용 사용 사례에서 매우 잘 작동합니다.
그러나 이 방식이 무너지는 경우도 있습니다. 컴포넌트가 shadow tree 안의 어떤 대상에 대해 임의의 스타일링을 허용하려면, 이를 수행하는 유일한 방법은 수백 개의 custom properties를 정의하는 것입니다(제어를 허용하려는 CSS 속성마다 하나씩). 이는 사용성과 성능 양쪽 이유로 명백히 터무니없는 일입니다. 작성자가 :hover 같은 의사 클래스에 따라 컴포넌트를 다르게 스타일링하려는 경우 상황은 더 복잡해집니다. 컴포넌트는 각 의사 클래스마다 (그리고 :hover:focus 같은 각 조합마다) 사용되는 custom properties를 복제해야 하며, 이는 조합 폭발을 초래합니다. 이로 인해 사용성과 성능 문제가 더욱 악화됩니다.
우리는 이 경우를 훨씬 더 우아하고 성능 좋게 처리하기 위해 ::part()를 도입합니다. 모든 것을 custom property 이름에 묶어 넣는 대신, 기능은 원래 그래야 하는 것처럼 선택자와 스타일 규칙 구문 안에 존재합니다. 이는 컴포넌트 작성자와 컴포넌트 사용자 모두에게 훨씬 더 사용하기 쉽고, 훨씬 더 나은 성능을 가져야 하며, 더 나은 캡슐화/API 표면을 허용합니다.
::part()가 이론적으로 완전히 새로운 힘을 전혀 제공하지 않는다는 점은 중요합니다. 이는 >>> 결합자의 재탕이 아니며, 작성자가 이미 custom properties로 할 수 있는 일에 대한 더 편리하고 일관된 구문일 뿐입니다. 요소의 명시적으로 "공개된" 부분 (part element map)을 단지 우연히 포함하고 있는 하위 부분과 분리함으로써, 이는 캡슐화에도 도움이 됩니다. 작성자는 우발적인 과도한 스타일링을 걱정하지 않고 ::part()를 사용할 수 있습니다.
2. Shadow 요소 노출:
shadow tree 안의 요소는 part 및 exportparts 속성을 사용하여 트리 외부의 스타일시트가 스타일링할 수 있도록 내보낼 수 있습니다.각 요소에는 토큰의 ordered set인 part name list가 있습니다.
각 요소에는 전달되는 내부 part에 대한 string과 그것이 노출될 이름을 제공하는 string을 포함하는 tuples의 list인 forwarded part name list가 있습니다.
각 shadow root는 키가 strings이고 값이 요소의 ordered sets인 part element map을 가진 것으로 생각할 수 있습니다.
part element map은 이 명세에서 스타일을 계산하는 알고리즘의 일부로만 설명됩니다. DOM을 통해 노출되지 않습니다. 이를 계산하는 비용이 클 수 있으며, 노출하면 닫힌 shadow root 내부의 요소에 접근할 수 있게 될 수 있기 때문입니다.
Part element maps는 요소의 추가 및 제거, 그리고 DOM 내 요소의 part name lists와 forwarded part name lists 변경에 의해 영향을 받습니다.
-
outerRoot 안의 각 descendant el에 대해:
-
el의 part name list에 있는 각 name에 대해, el을 outerRoot의 part element map[name]에 append합니다.
-
el 자체가 shadow host이면 innerRoot를 그 shadow root로 둡니다.
-
innerRoot의 part element map을 계산합니다.
-
el의 forwarded part name list에 있는 각 innerName/outerName에 대해:
-
innerName이 ident이면:
-
innerParts를 innerRoot의 part element map[innerName]으로 둡니다.
-
innerParts 안의 요소들을 outerRoot의 part element map[outerName]에 Append합니다.
-
-
innerName이 pseudo-element name이면:
-
innerRoot의 해당 이름을 가진 pseudo-element(s)를 outerRoot의 part element map[outerName]에 Append합니다.
-
-
-
2.1. Shadow 요소 이름 지정:
part
속성
shadow tree 안의 모든 요소는
part
속성을 가질 수 있습니다.
이는 요소를 shadow
tree 외부에 노출하는 데 사용됩니다.
테스트
part 속성은 이 요소의 part 이름을 나타내는 토큰의 공백으로 구분된 목록으로 구문 분석됩니다.
참고: 하나의 part에 여러 이름을 주어도 괜찮습니다. "part name"은 id나 tagname이 아니라 class와 유사한 것으로 간주해야 합니다.
<style>
c-e::part(textspan) { color: red; }
</style>
<template id="c-e-template">
<span part="textspan">This text will be red</span>
</template>
<c-e></c-e>
<script>
// Add template as custom element c-e
...
</script>
2.2. Shadow 요소 전달:
exportparts
속성
shadow tree 안의 모든 요소는
exportparts 속성을 가질 수 있습니다.
요소가 shadow host인 경우,
이는 이 shadow tree 외부의 규칙에 의해
shadow
tree 내부 host의 part를 스타일링할 수 있도록 하는 데 사용됩니다
(마치 host와 같은 트리 안에 있는,
part 속성으로 이름 지정된 요소인 것처럼).
테스트
exportparts 속성은 part 매핑의 쉼표로 구분된 목록으로 구문 분석됩니다. 각 part 매핑은 다음 중 하나입니다.
innerIdent : outerIdent-
innerIdent/outerIdent를 el의 forwarded part name list에 추가합니다. ident-
ident/ident를 el의 forwarded part name list에 추가합니다.참고: 이는
ident : ident의 축약형입니다. ::ident : outerIdent-
::ident가 fully styleable pseudo-element의 이름이면,::ident/outerIdent를 el의 forward part name list에 추가합니다. 그렇지 않으면 아무것도 하지 않습니다. - 그 밖의 모든 것
-
오류 복구 / 향후 호환성을 위해 무시됩니다.
참고: 하나의 sub-part를 여러 이름에 매핑해도 괜찮습니다.
<style>
c-e::part(textspan) { color: red; }
</style>
<template id="c-e-outer-template">
<c-e-inner exportparts="innerspan: textspan"></c-e-inner>
</template>
<template id="c-e-inner-template">
<span part="innerspan">
This text will be red because the containing shadow
host forwards innerspan to the document as "textspan"
and the document style matches it.
</span>
<span part="textspan">
This text will not be red because textspan 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>
exportparts
속성에서 사용되어,
그것이 들어 있는 컴포넌트에 대해 ::part()인 것처럼 가장할 수 있습니다.
<template id=custom-element-template>
<p exportparts="::before : preceding-text, ::after : following-text">
Main text.
</template>
그 템플릿을 사용하는 요소는 x-component::part(preceding-text) 같은 선택자를 사용하여 그 shadow 안의 p::before 의사 요소를 대상으로 삼을 수 있으므로, 컴포넌트 사용자는 preceding text가 의사 요소로 구현되어 있다는 사실을 알 필요가 없습니다.
3. Shadow 요소 선택: ::part() 의사 요소
::part()
의사 요소는
part
속성을 통해 노출된 요소를 선택할 수 있게 합니다.
구문은 다음과 같습니다.
::part() = ::part( <ident>+ )
테스트
::part() 의사 요소는 originating element가 shadow host일 때에만 무엇인가와 일치합니다.
part="label")
있다면,
x-button::part(label)로 이를 선택할 수 있습니다.
tabstrip 컨트롤은 part="tab"을 가진
여러 요소를 가질 수 있으며,
이들 모두는 ::part(tab)에 의해 선택됩니다.
한 번에 하나의 tab만 활성 상태라면,
part="tab active"로 특별히 표시한 다음 ::part(tab active) (또는 순서가 중요하지 않으므로
::part(active tab))로 선택할 수 있습니다.
::part() 의사 요소는 fully styleable pseudo-element입니다. originating element의 shadow root의 part element map이 지정된 <ident>를 contains하면, ::part()는 해당 ident에 키로 연결된 요소를 나타냅니다. 여러 ident가 제공되고 part element map이 그것들을 모두 포함하면, 각 ident에 키로 연결된 요소의 교집합을 나타냅니다. 그렇지 않으면 아무것도 일치하지 않습니다.
::part() 의사 요소는 originating element의 shadow tree 안에서의 위치에 따라 상속합니다.
<x-panel>의 내부 confirm button이
part="label => confirm-label" 같은 것을 사용하여 button의 내부 part를 panel 자체의
part element map으로 전달했다면,
x-panel::part(confirm-label) 같은 선택자는 다른 label은 무시하고
그 button의 label 하나만 선택했을 것입니다.
4. Element
인터페이스에 대한 확장
partial interface Element { [SameObject ,PutForwards =value ]readonly attribute DOMTokenList ; };part
테스트
part 속성의 getter는 연결된 요소가 컨텍스트 객체이고 연결된 속성의 로컬 이름이 part인 DOMTokenList 객체를 반환해야 합니다. 이 특정 DOMTokenList 객체의 토큰 집합은 요소의 parts라고도 알려져 있습니다.
이를 DOM 명세의 superglobal로 정의하십시오. [w3c/csswg-drafts Issue #3424]
5. 구문 분석을 위한 마이크로구문
5.1. part 매핑 구문 분석 규칙
유효한 part 매핑은 U+003A COLON 문자로 구분되고 U+003A COLON 앞이나 뒤에 임의 개수의 공백 문자가 올 수 있는 토큰의 tuple입니다. 토큰은 U+003A COLON 또는 U+002C COMMA 문자를 포함해서는 안 됩니다.
part 매핑을 구문 분석하는 규칙은 다음과 같습니다.
-
input을 구문 분석 중인 문자열로 둡니다.
-
position을 input 안을 가리키는 포인터로 두고, 처음에는 문자열의 시작을 가리키도록 합니다.
-
공백 문자인 code point의 시퀀스를 수집합니다.
-
공백 문자 또는 U+003A COLON 문자가 아닌 code point의 시퀀스를 수집하고, first token을 그 결과로 둡니다.
-
first token이 비어 있으면 error를 반환합니다.
-
공백 문자인 code point의 시퀀스를 수집합니다.
-
input의 끝에 도달했으면 tuple (first token, first token)을 반환합니다.
-
position의 문자가 U+003A COLON 문자가 아니면 error를 반환합니다.
-
U+003A COLON 문자를 소비합니다.
-
공백 문자인 code point의 시퀀스를 수집합니다.
-
공백 문자 또는 U+003A COLON 문자가 아닌 code point의 시퀀스를 수집하고, second token을 그 결과로 둡니다.
-
second token이 비어 있으면 error를 반환합니다.
-
공백 문자인 code point의 시퀀스를 수집합니다.
-
position이 input의 끝을 지나 있지 않으면 error를 반환합니다.
-
tuple (first token, second token)을 반환합니다.
5.2. part 매핑 목록 구문 분석 규칙
유효한 part 매핑 목록은 U+002C COMMA 문자로 구분되고 U+002C COMMA 앞이나 뒤에 임의 개수의 공백 문자가 올 수 있는 여러 유효한 part 매핑입니다.
part 매핑 목록을 구문 분석하는 규칙은 다음과 같습니다.
-
input을 구문 분석 중인 문자열로 둡니다.
-
문자열 input을 쉼표로 분할합니다. unparsed mappings를 그 결과 문자열 목록으로 둡니다.
-
mappings를 처음에는 비어 있는, 토큰의 tuples의 list로 둡니다. 이 list가 이 알고리즘의 결과가 됩니다.
-
unparsed mappings 안의 각 문자열 unparsed mapping에 대해, 다음 하위 단계를 실행합니다.
-
unparsed mapping이 비어 있거나 공백 문자만 포함하면, 루프의 다음 반복으로 계속합니다.
-
mapping을 part 매핑 구문 분석 규칙을 사용하여 unparsed mapping을 구문 분석한 결과로 둡니다.
-
mapping이 error이면 루프의 다음 반복으로 계속합니다. 이를 통해 클라이언트는 이해하지 못하는 새 구문을 건너뛸 수 있습니다.
-
mapping을 mappings에 추가합니다.
-
6. 개인정보 보호 고려사항
이 명세는 페이지의 요소를 스타일링 대상으로 지정하는 새로운 방법을 정의합니다. 이러한 요소들은 이미 다른 방식으로 완전히 스타일링할 수 있습니다. 따라서 새로운 개인정보 보호 고려사항을 도입하지 않습니다.
7. 보안 고려사항
shadow trees는 의도적으로 보안 경계가 아니라 페이지 작성자를 위한 편의 기능일 뿐이므로, 이러한 방식으로 선택자에 노출하더라도 새로운 보안 고려사항을 도입하지 않습니다.
8. 변경 사항
2018년 11월 15일 First Public Working Draft 이후의 변경 사항
-
::part()에서 여러 이름 지원 추가
-
'part name map'을 forwarded part name list로 이름 변경
-
'part element list' 알고리즘 재구성
-
여러 ::part() 세부 사항을 fully styleable pseudo-element로 이동
-
Web Platform Tests 적용 범위 추가
-
사소한 편집 개선