Skip to content

Commit

Permalink
In progress noodling
Browse files Browse the repository at this point in the history
  • Loading branch information
owenniblock committed Mar 11, 2024
1 parent 6e90dbc commit d941dfc
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 30 deletions.
38 changes: 38 additions & 0 deletions custom-elements.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,21 @@
}
]
},
{
"kind": "method",
"name": "renderShadow",
"static": true
},
{
"kind": "method",
"name": "setCSPTrustedTypesPolicy",
"static": true,
"parameters": [
{
"name": "policy"
}
]
},
{
"kind": "field",
"name": "onChange"
Expand Down Expand Up @@ -377,6 +392,29 @@
}
]
},
{
"kind": "method",
"name": "renderShadow",
"static": true
},
{
"kind": "method",
"name": "setCSPTrustedTypesPolicy",
"static": true,
"return": {
"type": {
"text": "void"
}
},
"parameters": [
{
"name": "policy",
"type": {
"text": "CSPTrustedTypesPolicy | Promise<CSPTrustedTypesPolicy> | null"
}
}
]
},
{
"kind": "field",
"name": "onChange"
Expand Down
6 changes: 3 additions & 3 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ <h2>Set initially selected tab</h2>

<h2>Set default tab</h2>

<tab-container default-tab-index="1">
<tab-container default-tab="1">
<button type="button" id="tab-one" role="tab">Tab one</button>
<button type="button" id="tab-two" role="tab">Tab two</button>
<button type="button" id="tab-three" role="tab">Tab three</button>
Expand Down Expand Up @@ -140,7 +140,7 @@ <h2>Panel with extra buttons</h2>

</main>

<!--<script src="../dist/index.js" type="module"></script>-->
<script src="https://unpkg.com/@github/tab-container-element@latest/dist/index.js" type="module"></script>
<script src="../dist/index.js" type="module"></script>
<!--<script src="https://unpkg.com/@github/tab-container-element@latest/dist/index.js" type="module"></script>-->
</body>
</html>
90 changes: 63 additions & 27 deletions src/tab-container-element.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
// CSP trusted types: We don't want to add `@types/trusted-types` as a
// dependency, so we use the following types as a stand-in.
interface CSPTrustedTypesPolicy {
createHTML: (s: string) => CSPTrustedHTMLToStringable
}
// Note: basically every object (and some primitives) in JS satisfy this
// `CSPTrustedHTMLToStringable` interface, but this is the most compatible shape
// we can use.
interface CSPTrustedHTMLToStringable {
toString: () => string
}

const HTMLElement = globalThis.HTMLElement || (null as unknown as (typeof window)['HTMLElement'])
const manualSlotsSupported = 'assign' in (globalThis.HTMLSlotElement?.prototype || {})
const html = String.raw

const shadowHTML = html`
<div style="display: flex" part="tablist-wrapper">
<slot part="before-tabs" name="before-tabs"></slot>
<div part="tablist-tab-wrapper">
<slot part="tablist" name="tablist"></slot>
</div>
<slot part="after-tabs" name="after-tabs"></slot>
</div>
<slot part="panel" name="panel" role="presentation"></slot>
<slot part="after-panels" name="after-panels"></slot>
`

export interface ElementRender {
renderShadow(): string
shadowRootOptions?: {
shadowrootmode?: 'open' | 'closed',

Check failure on line 32 in src/tab-container-element.ts

View workflow job for this annotation

GitHub Actions / build

Delete `,`
delegatesFocus?: boolean,

Check failure on line 33 in src/tab-container-element.ts

View workflow job for this annotation

GitHub Actions / build

Delete `,`
}
}

export interface CSPRenderer {
setCSPTrustedTypesPolicy(policy: CSPTrustedTypesPolicy | Promise<CSPTrustedTypesPolicy> | null): void
}

export class TabContainerChangeEvent extends Event {
constructor(type: string, {tab, panel, ...init}: EventInit & {tab?: Element; panel?: Element}) {
Expand All @@ -25,12 +62,29 @@ export class TabContainerChangeEvent extends Event {
}
}

let cspTrustedTypesPolicyPromise: Promise<CSPTrustedTypesPolicy> | null = null

export class TabContainerElement extends HTMLElement {
static define(tag = 'tab-container', registry = customElements) {
registry.define(tag, this)
return this
}

static observedAttributes = ['vertical']

static renderShadow() {
return shadowHTML
}

Check failure on line 78 in src/tab-container-element.ts

View workflow job for this annotation

GitHub Actions / build

Delete `··`
static shadowRootOptions = {
shadowrootmode: 'open'

Check failure on line 80 in src/tab-container-element.ts

View workflow job for this annotation

GitHub Actions / build

Insert `,`
}

// Passing `null` clears the policy.
static setCSPTrustedTypesPolicy(policy: CSPTrustedTypesPolicy | Promise<CSPTrustedTypesPolicy> | null): void {
cspTrustedTypesPolicyPromise = policy === null ? policy : Promise.resolve(policy)
}

get onChange() {
return this.onTabContainerChange
}
Expand Down Expand Up @@ -83,8 +137,6 @@ export class TabContainerElement extends HTMLElement {
this.onTabContainerChanged = listener
}

static observedAttributes = ['vertical']

get #tabList() {
const slot = this.#tabListSlot
if (this.#tabListTabWrapper.hasAttribute('role')) {
Expand Down Expand Up @@ -150,33 +202,17 @@ export class TabContainerElement extends HTMLElement {

#setupComplete = false
#internals!: ElementInternals | null
connectedCallback(): void {
async connectedCallback(): Promise<void> {
this.#internals ||= this.attachInternals ? this.attachInternals() : null
const shadowRoot = this.shadowRoot || this.attachShadow({mode: 'open', slotAssignment: 'manual'})
const tabListContainer = document.createElement('div')
tabListContainer.style.display = 'flex'
tabListContainer.setAttribute('part', 'tablist-wrapper')
const tabListTabWrapper = document.createElement('div')
tabListTabWrapper.setAttribute('part', 'tablist-tab-wrapper')
const tabListSlot = document.createElement('slot')
tabListSlot.setAttribute('part', 'tablist')
tabListSlot.setAttribute('name', 'tablist')
tabListTabWrapper.append(tabListSlot)
const panelSlot = document.createElement('slot')
panelSlot.setAttribute('part', 'panel')
panelSlot.setAttribute('name', 'panel')
panelSlot.setAttribute('role', 'presentation')
const beforeTabSlot = document.createElement('slot')
beforeTabSlot.setAttribute('part', 'before-tabs')
beforeTabSlot.setAttribute('name', 'before-tabs')
const afterTabSlot = document.createElement('slot')
afterTabSlot.setAttribute('part', 'after-tabs')
afterTabSlot.setAttribute('name', 'after-tabs')
tabListContainer.append(beforeTabSlot, tabListTabWrapper, afterTabSlot)
const afterSlot = document.createElement('slot')
afterSlot.setAttribute('part', 'after-panels')
afterSlot.setAttribute('name', 'after-panels')
shadowRoot.replaceChildren(tabListContainer, panelSlot, afterSlot)
if (cspTrustedTypesPolicyPromise) {
const cspTrustedTypesPolicy = await cspTrustedTypesPolicyPromise
// eslint-disable-next-line github/no-inner-html
shadowRoot.innerHTML = cspTrustedTypesPolicy.createHTML(shadowHTML).toString()
} else {
// eslint-disable-next-line github/no-inner-html
shadowRoot.innerHTML = shadowHTML
}

if (this.#internals && 'role' in this.#internals) {
this.#internals.role = 'presentation'
Expand Down

0 comments on commit d941dfc

Please sign in to comment.