Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions __tests__/unit/options/parse-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,34 @@ describe('parseOptions', () => {
);
});

it('accepts a ShadowRoot instance as the container target', () => {
const host = document.createElement('div');
document.body.appendChild(host);

const shadowRoot = host.attachShadow({ mode: 'open' });
const parsed = parseOptions({
container: shadowRoot as unknown as Element,
} as any);

expect(parsed.container).toBe(shadowRoot);
});

it('resolves a container selector that only exists inside a ShadowRoot', () => {
const host = document.createElement('div');
document.body.appendChild(host);

const shadowRoot = host.attachShadow({ mode: 'open' });
const shadowContainer = document.createElement('div');
shadowContainer.id = 'shadow-container';
shadowRoot.appendChild(shadowContainer);

const parsed = parseOptions({
container: '#shadow-container',
});

expect(parsed.container).toBe(shadowContainer);
});

it('skips design when structure is null', () => {
itemMap.set('custom-item', {
type: 'custom-item',
Expand Down
24 changes: 24 additions & 0 deletions __tests__/unit/renderer/renderer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import * as rendererModule from '../../../src/renderer/renderer';

const getObservationTarget = (rendererModule as any).getObservationTarget as (
container: Element | ShadowRoot,
) => Element | ShadowRoot;

describe('renderer/Renderer', () => {
it('uses the ShadowRoot itself as the observation target', () => {
const host = document.createElement('div');
const shadowRoot = host.attachShadow({ mode: 'open' });

expect(getObservationTarget(shadowRoot)).toBe(shadowRoot);
});

it('uses the container element as the observation target when it lives inside ShadowRoot', () => {
const host = document.createElement('div');
const shadowRoot = host.attachShadow({ mode: 'open' });
const container = document.createElement('div');
shadowRoot.appendChild(container);

expect(getObservationTarget(container)).toBe(container);
});
});
29 changes: 27 additions & 2 deletions src/options/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function parseOptions(

const parsedContainer =
typeof container === 'string'
? document.querySelector(container) || document.createElement('div')
? resolveContainer(container) || document.createElement('div')
: container;

const templateOptions: TemplateOptions | undefined = template
Expand All @@ -52,7 +52,7 @@ export function parseOptions(
: undefined;

const parsed: Partial<ParsedInfographicOptions> = {
container: parsedContainer as HTMLElement,
container: parsedContainer as Element | ShadowRoot,
padding: parsePadding(padding),
};

Expand Down Expand Up @@ -90,6 +90,31 @@ export function parseOptions(
return parsed;
}

function resolveContainer(selector: string): Element | null {
const directMatch = document.querySelector(selector);
if (directMatch) return directMatch;

return queryInShadowRoots(document, selector);
}

function queryInShadowRoots(
root: Document | ShadowRoot,
selector: string,
): Element | null {
for (const element of root.querySelectorAll('*')) {
const shadowRoot = element.shadowRoot;
if (!shadowRoot) continue;

const match = shadowRoot.querySelector(selector);
if (match) return match;

const nestedMatch = queryInShadowRoots(shadowRoot, selector);
if (nestedMatch) return nestedMatch;
}

return null;
}

export function parseData(
data?: Data,
template?: string,
Expand Down
6 changes: 3 additions & 3 deletions src/options/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type { Data, Padding, ParsedData } from '../types';
import type { Path } from '../utils';

export interface InfographicOptions {
/** 容器,可以是选择器或者 HTMLElement */
container?: string | HTMLElement;
/** 容器,可以是选择器、Element 或 ShadowRoot */
container?: string | Element | ShadowRoot;
/** 宽度 */
width?: number | string;
/** 高度 */
Expand Down Expand Up @@ -37,7 +37,7 @@ export interface InfographicOptions {
}

export interface ParsedInfographicOptions {
container: HTMLElement;
container: Element | ShadowRoot;
width?: number | string;
height?: number | string;
padding?: Padding;
Expand Down
11 changes: 9 additions & 2 deletions src/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class Renderer implements IRenderer {
if (isNode) {
postRender();
} else {
const observationTarget = getObservationTarget(this.options.container);
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
Expand All @@ -86,9 +87,9 @@ export class Renderer implements IRenderer {
});

try {
observer.observe(document, {
observer.observe(observationTarget, {
childList: true,
subtree: true,
subtree: observationTarget !== this.options.container,
});
} catch (error) {
// Fallback for micro-app environments that proxy document.
Expand All @@ -102,6 +103,12 @@ export class Renderer implements IRenderer {
}
}

export function getObservationTarget(container: Element | ShadowRoot) {
if (container instanceof ShadowRoot || container instanceof Element)
return container;
return document;
}

function renderTemplate(svg: SVGSVGElement, options: ParsedInfographicOptions) {
fill(svg, options);

Expand Down
Loading