1
+ // CSP trusted types: We don't want to add `@types/trusted-types` as a
2
+ // dependency, so we use the following types as a stand-in.
3
+ interface CSPTrustedTypesPolicy {
4
+ createHTML : ( s : string ) => CSPTrustedHTMLToStringable
5
+ }
6
+ // Note: basically every object (and some primitives) in JS satisfy this
7
+ // `CSPTrustedHTMLToStringable` interface, but this is the most compatible shape
8
+ // we can use.
9
+ interface CSPTrustedHTMLToStringable {
10
+ toString : ( ) => string
11
+ }
12
+
1
13
const HTMLElement = globalThis . HTMLElement || ( null as unknown as ( typeof window ) [ 'HTMLElement' ] )
2
14
const manualSlotsSupported = 'assign' in ( globalThis . HTMLSlotElement ?. prototype || { } )
15
+ const html = String . raw
16
+
17
+ const shadowHTML = html `
18
+ < div style ="display: flex " part ="tablist-wrapper ">
19
+ < slot part ="before-tabs " name ="before-tabs "> </ slot >
20
+ < div part ="tablist-tab-wrapper ">
21
+ < slot part ="tablist " name ="tablist "> </ slot >
22
+ </ div >
23
+ < slot part ="after-tabs " name ="after-tabs "> </ slot >
24
+ </ div >
25
+ < slot part ="panel " name ="panel " role ="presentation "> </ slot >
26
+ < slot part ="after-panels " name ="after-panels "> </ slot >
27
+ `
28
+
29
+ export interface ElementRender {
30
+ renderShadow ( ) : string
31
+ shadowRootOptions ?: {
32
+ shadowrootmode ?: 'open' | 'closed' ,
33
+ delegatesFocus ?: boolean ,
34
+ }
35
+ }
36
+
37
+ export interface CSPRenderer {
38
+ setCSPTrustedTypesPolicy ( policy : CSPTrustedTypesPolicy | Promise < CSPTrustedTypesPolicy > | null ) : void
39
+ }
3
40
4
41
export class TabContainerChangeEvent extends Event {
5
42
constructor ( type : string , { tab, panel, ...init } : EventInit & { tab ?: Element ; panel ?: Element } ) {
@@ -25,12 +62,29 @@ export class TabContainerChangeEvent extends Event {
25
62
}
26
63
}
27
64
65
+ let cspTrustedTypesPolicyPromise : Promise < CSPTrustedTypesPolicy > | null = null
66
+
28
67
export class TabContainerElement extends HTMLElement {
29
68
static define ( tag = 'tab-container' , registry = customElements ) {
30
69
registry . define ( tag , this )
31
70
return this
32
71
}
33
72
73
+ static observedAttributes = [ 'vertical' ]
74
+
75
+ static renderShadow ( ) {
76
+ return shadowHTML
77
+ }
78
+
79
+ static shadowRootOptions = {
80
+ shadowrootmode : 'open'
81
+ }
82
+
83
+ // Passing `null` clears the policy.
84
+ static setCSPTrustedTypesPolicy ( policy : CSPTrustedTypesPolicy | Promise < CSPTrustedTypesPolicy > | null ) : void {
85
+ cspTrustedTypesPolicyPromise = policy === null ? policy : Promise . resolve ( policy )
86
+ }
87
+
34
88
get onChange ( ) {
35
89
return this . onTabContainerChange
36
90
}
@@ -83,8 +137,6 @@ export class TabContainerElement extends HTMLElement {
83
137
this . onTabContainerChanged = listener
84
138
}
85
139
86
- static observedAttributes = [ 'vertical' ]
87
-
88
140
get #tabList( ) {
89
141
const slot = this . #tabListSlot
90
142
if ( this . #tabListTabWrapper. hasAttribute ( 'role' ) ) {
@@ -150,33 +202,17 @@ export class TabContainerElement extends HTMLElement {
150
202
151
203
#setupComplete = false
152
204
#internals! : ElementInternals | null
153
- connectedCallback ( ) : void {
205
+ async connectedCallback ( ) : Promise < void > {
154
206
this . #internals ||= this . attachInternals ? this . attachInternals ( ) : null
155
207
const shadowRoot = this . shadowRoot || this . attachShadow ( { mode : 'open' , slotAssignment : 'manual' } )
156
- const tabListContainer = document . createElement ( 'div' )
157
- tabListContainer . style . display = 'flex'
158
- tabListContainer . setAttribute ( 'part' , 'tablist-wrapper' )
159
- const tabListTabWrapper = document . createElement ( 'div' )
160
- tabListTabWrapper . setAttribute ( 'part' , 'tablist-tab-wrapper' )
161
- const tabListSlot = document . createElement ( 'slot' )
162
- tabListSlot . setAttribute ( 'part' , 'tablist' )
163
- tabListSlot . setAttribute ( 'name' , 'tablist' )
164
- tabListTabWrapper . append ( tabListSlot )
165
- const panelSlot = document . createElement ( 'slot' )
166
- panelSlot . setAttribute ( 'part' , 'panel' )
167
- panelSlot . setAttribute ( 'name' , 'panel' )
168
- panelSlot . setAttribute ( 'role' , 'presentation' )
169
- const beforeTabSlot = document . createElement ( 'slot' )
170
- beforeTabSlot . setAttribute ( 'part' , 'before-tabs' )
171
- beforeTabSlot . setAttribute ( 'name' , 'before-tabs' )
172
- const afterTabSlot = document . createElement ( 'slot' )
173
- afterTabSlot . setAttribute ( 'part' , 'after-tabs' )
174
- afterTabSlot . setAttribute ( 'name' , 'after-tabs' )
175
- tabListContainer . append ( beforeTabSlot , tabListTabWrapper , afterTabSlot )
176
- const afterSlot = document . createElement ( 'slot' )
177
- afterSlot . setAttribute ( 'part' , 'after-panels' )
178
- afterSlot . setAttribute ( 'name' , 'after-panels' )
179
- shadowRoot . replaceChildren ( tabListContainer , panelSlot , afterSlot )
208
+ if ( cspTrustedTypesPolicyPromise ) {
209
+ const cspTrustedTypesPolicy = await cspTrustedTypesPolicyPromise
210
+ // eslint-disable-next-line github/no-inner-html
211
+ shadowRoot . innerHTML = cspTrustedTypesPolicy . createHTML ( shadowHTML ) . toString ( )
212
+ } else {
213
+ // eslint-disable-next-line github/no-inner-html
214
+ shadowRoot . innerHTML = shadowHTML
215
+ }
180
216
181
217
if ( this . #internals && 'role' in this . #internals) {
182
218
this . #internals. role = 'presentation'
0 commit comments