Skip to content

Commit

Permalink
feat: support mount at shadow root (#1105)
Browse files Browse the repository at this point in the history
  • Loading branch information
eagleoflqj committed Jun 19, 2023
1 parent 794d6ac commit 316c808
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 17 deletions.
43 changes: 43 additions & 0 deletions packages/css-render/__tests__/mount/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,46 @@ describe('anchorMetaName', () => {
style.unmount()
console.log(document.head)
})

describe('shadow root', () => {
it('style mounted on head and shadow root not affect each other', () => {
const outerDiv = document.createElement('div')
outerDiv.classList.add('a')
document.body.appendChild(outerDiv)
const innerDiv = outerDiv.cloneNode(true)
const shadow = outerDiv.attachShadow({mode: 'open'})
shadow.appendChild(innerDiv)

const style = c('.a', ({ props }) => ({
backgroundColor: props.backgroundColor
}))
style.mount({
id: 'outer',
props: {
backgroundColor: 'red'
}
})
style.mount({
id: 'inner',
props: {
backgroundColor: 'lime'
},
parent: shadow
})
expect(getComputedStyle(document.querySelector('.a')!).backgroundColor).to.equal(
'rgb(255, 0, 0)'
)
expect(getComputedStyle(shadow.querySelector('.a')!).backgroundColor).to.equal(
'rgb(0, 255, 0)'
)

style.unmount({
id: 'outer'
})
style.unmount({
id: 'inner',
parent: shadow
})
expect(style.els.length).to.equal(0)
})
})
8 changes: 5 additions & 3 deletions packages/css-render/src/c.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function wrappedMount<T extends undefined | SsrAdapter> (
options: MountOption<T> = {}
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
): T extends undefined ? HTMLStyleElement : void {
const { id, ssr, props, head = false, force = false, anchorMetaName } = options
const { id, ssr, props, head = false, force = false, anchorMetaName, parent } = options
const targetElement = mount(
this.instance,
this,
Expand All @@ -34,6 +34,7 @@ function wrappedMount<T extends undefined | SsrAdapter> (
head,
force,
anchorMetaName,
parent,
ssr
)
return targetElement as any
Expand All @@ -46,9 +47,10 @@ function wrappedUnmount (
/* istanbul ignore next */
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const {
id
id,
parent
} = options
unmount(this.instance, this, id)
unmount(this.instance, this, id, parent)
}

const createCNode: baseCreateCNodeForCssRenderInstance = function (
Expand Down
26 changes: 17 additions & 9 deletions packages/css-render/src/mount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ if (typeof window !== 'undefined') {
}

export function unmount (
intance: CssRenderInstance,
instance: CssRenderInstance,
node: CNode,
id: MountId
id: MountId,
parent: ParentNode | undefined
): void {
const { els } = node
// If id is undefined, unmount all styles
if (id === undefined) {
els.forEach(removeElement)
node.els = []
} else {
const target = queryElement(id)
const target = queryElement(id, parent)
// eslint-disable-next-line
if (target && els.includes(target)) {
removeElement(target)
Expand All @@ -49,6 +50,7 @@ function mount (
head: boolean,
force: boolean,
anchorMetaName: string | undefined,
parent: ParentNode | undefined,
ssrAdapter: SsrAdapter
): void
function mount (
Expand All @@ -59,6 +61,7 @@ function mount (
head: boolean,
force: boolean,
anchorMetaName: string | undefined,
parent: ParentNode | undefined,
ssrAdapter?: undefined
): HTMLStyleElement
function mount (
Expand All @@ -69,6 +72,7 @@ function mount (
head: boolean,
force: boolean,
anchorMetaName: string | undefined,
parent: ParentNode | undefined,
ssrAdapter?: SsrAdapter
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
): HTMLStyleElement | void
Expand All @@ -80,6 +84,7 @@ function mount (
head: boolean,
force: boolean,
anchorMetaName: string | undefined,
parent: ParentNode | undefined,
ssrAdapter?: SsrAdapter
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
): HTMLStyleElement | void {
Expand All @@ -92,7 +97,10 @@ function mount (
ssrAdapter.adapter(id, style ?? node.render(props))
return
}
const queriedTarget = queryElement(id)
if (parent === undefined) {
parent = document.head
}
const queriedTarget = queryElement(id, parent)
if (queriedTarget !== null && !force) {
return queriedTarget
}
Expand All @@ -101,23 +109,23 @@ function mount (
target.textContent = style
if (queriedTarget !== null) return queriedTarget
if (anchorMetaName) {
const anchorMetaEl = document.head.querySelector(
const anchorMetaEl = parent.querySelector(
`meta[name="${anchorMetaName}"]`
)
if (anchorMetaEl) {
document.head.insertBefore(target, anchorMetaEl)
parent.insertBefore(target, anchorMetaEl)
addElementToList(node.els, target)
return target
}
}

if (head) {
document.head.insertBefore(
parent.insertBefore(
target,
document.head.querySelector('style, link')
parent.querySelector('style, link')
)
} else {
document.head.appendChild(target)
parent.appendChild(target)
}
addElementToList(node.els, target)
return target
Expand Down
4 changes: 3 additions & 1 deletion packages/css-render/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface CRenderOption {
export type MountId = string | undefined
export interface UnmountOption {
id?: MountId
parent?: ParentNode
}

export interface MountOption<T extends SsrAdapter | undefined = undefined> {
Expand All @@ -35,10 +36,11 @@ export interface MountOption<T extends SsrAdapter | undefined = undefined> {
head?: boolean
force?: boolean
anchorMetaName?: string
parent?: ParentNode
}

/** find related */
export type CFindTarget = (target: string) => HTMLStyleElement | null
export type CFindTarget = (target: string, parent?: ParentNode) => HTMLStyleElement | null

/** CNode */
export interface CNode {
Expand Down
4 changes: 2 additions & 2 deletions packages/css-render/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export function removeElement (el: HTMLStyleElement | null): void {
if (parentElement) parentElement.removeChild(el)
}

export function queryElement (id: string): HTMLStyleElement | null {
return document.head.querySelector(`style[cssr-id="${id}"]`)
export function queryElement (id: string, parent?: ParentNode): HTMLStyleElement | null {
return (parent ?? document.head).querySelector(`style[cssr-id="${id}"]`)
}

export function createElement (id: string): HTMLStyleElement {
Expand Down
16 changes: 14 additions & 2 deletions packages/docs/docs/mount.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ type mount = (
props?: any,
ssr?: SsrAdapter
anchorMetaName?: string
parent?: ParentNode
}
) => HTMLStyleElement
```
### `id`
- If `id` or `options` is `undefined`, every call of mount method will create a new `style` element with rendered style and mount it `document.head`. For example: `style.mount()` or `style.mount({ props: {} })`.
- If `id` is a `string`. It will mount the style on a `style[cssr-id="${id}"]` element to `document.head`. For example: `<head><style cssr-id="id">...</style></head>`. If the element already exists, the `mount` method will **not** refresh the content of the element.
- If `id` or `options` is `undefined`, every call of mount method will create a new `style` element with rendered style and mount it to `parent`. For example: `style.mount()` or `style.mount({ props: {} })`.
- If `id` is a `string`. It will mount the style on a `style[cssr-id="${id}"]` element to `parent`. For example: `<head><style cssr-id="id">...</style></head>`. If the element already exists, the `mount` method will **not** refresh the content of the element.
### `props`
The `props` will be used as the render function's `props` during this mount.
### `ssr`
Expand All @@ -31,6 +32,11 @@ When mount the style in SSR environment, you should put correct ssr adapter in i
The name of a meta tag as a mount position anchor.
### `parent`
The parent element that mounts the style.
Default `document.head`.
This is useful for shadow DOM.
### Return Value
In non-ssr environment, the id element for the style to be mounted on.
Expand All @@ -42,10 +48,16 @@ Unmount the style of the CNode.
type unmount = (
options?: {
id?: string
parent?: ParentNode
}
) => void
```
### `id`
- If `id` or `options` is `undefined`, every mounted elements of the `CNode` will be unmounted.
- If `id` is a `string`. It will unmount `style[cssr-id="${id}"]` element mounted by the `CNode`.
### `parent`
The parent of mounted style.
Default `document.head`.

1 comment on commit 316c808

@vercel
Copy link

@vercel vercel bot commented on 316c808 Jun 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

css-render – ./

css-render-07akioni.vercel.app
css-render-git-master-07akioni.vercel.app
css-render.vercel.app

Please sign in to comment.