@component-anatomy/storybook 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/index.js +3 -0
- package/dist/index.js.map +2 -2
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +3 -0
- package/dist/preview.js.map +2 -2
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/preview.ts +5 -0
- package/src/types.ts +7 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Storybook addon that adds an **Anatomy** panel — an interactive part list sync
|
|
|
5
5
|
- Hover a part in the panel → the element is highlighted in the canvas
|
|
6
6
|
- Hover a `data-part` element in the canvas → the panel entry activates
|
|
7
7
|
- The same table renders **inside MDX** via `@component-anatomy/storybook/blocks`
|
|
8
|
-
- Works with **Storybook 10 and 11**, any renderer (React, Vue, HTML, Web Components…)
|
|
8
|
+
- Works with **Storybook 10 and 11**, any renderer (React, Vue, HTML, Web Components…) — parts inside open shadow roots included
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -70,6 +70,7 @@ export const Anatomy: Story = {
|
|
|
70
70
|
theme: { accent: '#0d9488' }, // theme tokens for the canvas overlays
|
|
71
71
|
overlayLabel: true, // floating name chip
|
|
72
72
|
overlayPadding: 2, // inflate highlight boxes (px)
|
|
73
|
+
shadowParts: false, // also read native `part` attributes in shadow roots
|
|
73
74
|
root: '.my-component', // narrow the anatomy root (CSS selector)
|
|
74
75
|
disable: false, // turn off for a story
|
|
75
76
|
} satisfies AnatomyParameters,
|
|
@@ -79,6 +80,8 @@ export const Anatomy: Story = {
|
|
|
79
80
|
|
|
80
81
|
Omit `parts` (pass `{}`) and the panel lists parts auto-discovered from `data-part` attributes, with names derived from the ids.
|
|
81
82
|
|
|
83
|
+
Web components work as they are: parts are found inside open shadow roots, and the panel updates when a component renders them late (Lit, Stencil) or is defined late. Set `shadowParts: true` to read the anatomy a component already exposes for `::part()` (`part="track"`) instead of adding `data-part`. Closed shadow roots can't be reached.
|
|
84
|
+
|
|
82
85
|
`preset` and `theme` reach the panel and the `<Anatomy>` block too, not just the canvas overlays: the active row is accented with the same color the overlay paints. Where that color would be illegible on Storybook's own panel — `contrast` is black, the manager is dark by default — the table falls back to the most colorful alternative that meets WCAG AA, so `contrast` accents with its yellow on a dark panel and with black on a light one.
|
|
83
86
|
|
|
84
87
|
Parameters follow Storybook's normal inheritance — project-wide defaults in `.storybook/preview.ts`, per-component in `meta.parameters`, per-story overrides in `story.parameters`.
|
|
@@ -160,8 +163,9 @@ against 10 and 11.
|
|
|
160
163
|
|
|
161
164
|
## Example
|
|
162
165
|
|
|
163
|
-
A complete Storybook 11 setup with Button/Slider/Tabs stories
|
|
164
|
-
|
|
166
|
+
A complete Storybook 11 setup with Button/Slider/Tabs stories, web components
|
|
167
|
+
(vanilla, Lit, Shoelace, and Ionic built with Stencil) — and two MDX pages using
|
|
168
|
+
the `<Anatomy>` block — lives in [`examples/storybook`](https://github.com/julien-deramond/component-anatomy/tree/main/examples/storybook), deployed at https://julien-deramond.github.io/component-anatomy/storybook/.
|
|
165
169
|
|
|
166
170
|
## Docs
|
|
167
171
|
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ var withComponentAnatomy = (storyFn, context) => {
|
|
|
40
40
|
const controller = createAnatomy({
|
|
41
41
|
root,
|
|
42
42
|
parts: params.parts,
|
|
43
|
+
shadowParts: params.shadowParts,
|
|
43
44
|
preset: params.preset,
|
|
44
45
|
theme: params.theme,
|
|
45
46
|
overlay: {
|
|
@@ -49,6 +50,7 @@ var withComponentAnatomy = (storyFn, context) => {
|
|
|
49
50
|
});
|
|
50
51
|
const announceParts = () => channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });
|
|
51
52
|
announceParts();
|
|
53
|
+
const offChange = controller.on("parts:change", announceParts);
|
|
52
54
|
const offEnter = controller.on(
|
|
53
55
|
"part:enter",
|
|
54
56
|
(partId) => channel.emit(EVENTS.PART_ENTER, { storyId, partId })
|
|
@@ -76,6 +78,7 @@ var withComponentAnatomy = (storyFn, context) => {
|
|
|
76
78
|
channel.off(EVENTS.HOVER_ITEM, onHoverItem);
|
|
77
79
|
channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
78
80
|
channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);
|
|
81
|
+
offChange();
|
|
79
82
|
offEnter();
|
|
80
83
|
offLeave();
|
|
81
84
|
controller.destroy();
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/preview.ts", "../src/constants.ts", "../src/channel.ts"],
|
|
4
|
-
"sourcesContent": ["import { definePreviewAddon } from 'storybook/internal/csf';\n\nimport annotations from './preview.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport { ADDON_ID, PANEL_ID, PARAM_KEY, EVENTS } from './constants.js';\nexport type { AnatomyParameters } from './types.js';\n\n/**\n * What this addon contributes to a CSF Next project's type context: a typed\n * `parameters.anatomy` on every meta and story of a preview that registers it.\n */\nexport type ComponentAnatomyTypes = {\n parameters: {\n /** @see {@link AnatomyParameters} */\n anatomy?: AnatomyParameters;\n };\n};\n\n/**\n * The addon's preview annotations, for a CSF Next `preview.ts`:\n *\n * ```ts\n * import { definePreview } from '@storybook/your-framework';\n * import componentAnatomy from '@component-anatomy/storybook';\n *\n * export default definePreview({\n * addons: [componentAnatomy()],\n * });\n * ```\n *\n * `.storybook/main.ts` must still list the addon \u2014 `addons:\n * ['@component-anatomy/storybook']` \u2014 since that is what loads the manager\n * panel. What changes under CSF Next is the preview side: a `preview.ts` built\n * with `definePreview` composes *only* its own `addons`, and Storybook drops\n * every addon annotation main.ts would otherwise have contributed. Without the\n * call below, the canvas decorator never mounts and the panel stays empty.\n *\n * Registering in both places is safe \u2014 the two paths are mutually exclusive,\n * so the decorator is composed once either way.\n *\n * The `./blocks` subpath, not this entry, holds the `<Anatomy>` MDX block: it\n * needs React and `@storybook/addon-docs`, both optional peers that must not\n * become load-bearing for a Storybook that only wants the panel.\n */\nexport default () => definePreviewAddon<ComponentAnatomyTypes>(annotations);\n", "/**\n * Preview-side (iframe) entry. Registers a global decorator that mounts a\n * component-anatomy controller over the story canvas and syncs hover state\n * with the manager panel \u2014 and with any `<Anatomy>` doc block on the same\n * docs page \u2014 over the addon channel.\n *\n * The decorator runs in docs view too: the docs `Story` block renders each\n * story through `renderStoryToElement`, which sets `context.canvasElement`\n * exactly as it does in story view. That is what makes auto-discovery and\n * hover sync work inside MDX.\n */\nimport { addons, useEffect } from 'storybook/preview-api';\nimport type {\n ProjectAnnotations,\n Renderer,\n PartialStoryFn,\n StoryContext,\n} from 'storybook/internal/types';\nimport { createAnatomy } from '@component-anatomy/core';\n\nimport { EVENTS, PARAM_KEY } from './constants.js';\nimport { matchesStory } from './channel.js';\nimport type { HoverItemEvent, StoryScopedEvent } from './channel.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport const withComponentAnatomy = (\n storyFn: PartialStoryFn<Renderer>,\n context: StoryContext<Renderer>\n) => {\n const params = context.parameters?.[PARAM_KEY] as AnatomyParameters | undefined;\n\n useEffect(() => {\n if (!params || params.disable) return;\n\n const channel = addons.getChannel();\n const canvas = context.canvasElement as unknown as HTMLElement;\n if (!canvas) return;\n\n const storyId = context.id;\n\n const root = params.root\n ? canvas.querySelector<HTMLElement>(params.root) ?? canvas\n : canvas;\n\n const controller = createAnatomy({\n root,\n parts: params.parts,\n preset: params.preset,\n theme: params.theme,\n overlay: {\n label: params.overlayLabel !== false,\n padding: params.overlayPadding,\n },\n });\n\n const announceParts = () =>\n channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });\n\n announceParts();\n\n const offEnter = controller.on('part:enter', (partId) =>\n channel.emit(EVENTS.PART_ENTER, { storyId, partId })\n );\n const offLeave = controller.on('part:leave', () =>\n channel.emit(EVENTS.PART_LEAVE, { storyId })\n );\n\n // A docs page mounts several stories at once, so every controller sees\n // every panel/block event \u2014 only act on the ones addressed to this story.\n const onHoverItem = (event: HoverItemEvent) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.highlight(event.partId);\n };\n const onLeaveItem = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.unhighlight();\n };\n const onPartsRequest = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n announceParts();\n };\n\n channel.on(EVENTS.HOVER_ITEM, onHoverItem);\n channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);\n // The panel or block may mount after the story rendered \u2014 let it ask for\n // the list rather than racing the first announcement.\n channel.on(EVENTS.PARTS_REQUEST, onPartsRequest);\n\n return () => {\n channel.off(EVENTS.HOVER_ITEM, onHoverItem);\n channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);\n channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);\n offEnter();\n offLeave();\n controller.destroy();\n };\n }, [context.id]);\n\n return storyFn();\n};\n\nexport const decorators = [withComponentAnatomy];\n\n/**\n * The same annotations as a default export, which is the shape\n * `definePreviewAddon` takes in the package's main entry (see `index.ts`) and\n * the shape a consumer gets from `@component-anatomy/storybook/preview`.\n *\n * Storybook reads `module.default[field] ?? module[field]`, so a preview\n * annotation module that exports both is read exactly once \u2014 the named\n * `decorators` above stays for anyone importing it directly.\n */\nconst annotations: ProjectAnnotations<Renderer> = {\n decorators: [withComponentAnatomy],\n};\n\nexport default annotations;\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/**\n * Channel events used to sync the manager panel \u2014 and the `<Anatomy>` MDX doc\n * block, which runs in the preview iframe \u2014 with the story canvas.\n *\n * Every payload carries the `storyId` it concerns; see `channel.ts` for the\n * payload types and the `matchesStory` filter each listener applies.\n */\nexport const EVENTS = {\n /** preview \u2192 consumers: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 consumers: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 consumers: resolved part list for a story. */\n PARTS: `${ADDON_ID}/parts`,\n /** consumers \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** consumers \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** consumers \u2192 preview: a panel/block mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n", "/**\n * Shared channel payload contract between the preview decorator, the manager\n * panel, and the MDX doc block.\n *\n * Every payload carries the `storyId` it refers to. In story view this is\n * redundant \u2014 only one story is mounted \u2014 but a docs page mounts *many*\n * stories at once, each with its own controller, and each `<Anatomy>` block\n * must talk to exactly one of them. Without addressing, hovering a part in\n * one block highlights the matching part in every other story on the page.\n */\nimport type { AnatomyPartDefinition } from '@component-anatomy/core';\n\n/** preview \u2192 consumers: the resolved part list for one story. */\nexport type PartsEvent = { storyId?: string; parts: AnatomyPartDefinition[] };\n\n/** preview \u2192 consumers: a part became active in that story's canvas. */\nexport type PartEnterEvent = { storyId?: string; partId: string };\n\n/** consumer \u2192 preview: highlight this part in that story's canvas. */\nexport type HoverItemEvent = { storyId?: string; partId: string };\n\n/** Payload for the events that only need to name a story. */\nexport type StoryScopedEvent = { storyId?: string };\n\n/**\n * Whether an event addressed to `eventStoryId` concerns `storyId`.\n *\n * A missing id on *either* side matches everything. That keeps the protocol\n * backward compatible: a manager panel from a newer build still understands\n * an older preview bundle that emits unaddressed events, and vice versa.\n */\nexport const matchesStory = (\n eventStoryId: string | undefined,\n storyId: string | undefined\n): boolean => !eventStoryId || !storyId || eventStoryId === storyId;\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,0BAA0B;;;ACWnC,SAAS,QAAQ,iBAAiB;AAOlC,SAAS,qBAAqB;;;AClBvB,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AASlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;;;ACKO,IAAM,eAAe,CAC1B,cACA,YACY,CAAC,gBAAgB,CAAC,WAAW,iBAAiB;;;AFTrD,IAAM,uBAAuB,CAClC,SACA,YACG;AACH,QAAM,SAAS,QAAQ,aAAa,SAAS;AAE7C,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,QAAS;AAE/B,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAQ;AAEb,UAAM,UAAU,QAAQ;AAExB,UAAM,OAAO,OAAO,OAChB,OAAO,cAA2B,OAAO,IAAI,KAAK,SAClD;AAEJ,UAAM,aAAa,cAAc;AAAA,MAC/B;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,QACP,OAAO,OAAO,iBAAiB;AAAA,QAC/B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,MACpB,QAAQ,KAAK,OAAO,OAAO,EAAE,SAAS,OAAO,WAAW,SAAS,EAAE,CAAC;AAEtE,kBAAc;
|
|
4
|
+
"sourcesContent": ["import { definePreviewAddon } from 'storybook/internal/csf';\n\nimport annotations from './preview.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport { ADDON_ID, PANEL_ID, PARAM_KEY, EVENTS } from './constants.js';\nexport type { AnatomyParameters } from './types.js';\n\n/**\n * What this addon contributes to a CSF Next project's type context: a typed\n * `parameters.anatomy` on every meta and story of a preview that registers it.\n */\nexport type ComponentAnatomyTypes = {\n parameters: {\n /** @see {@link AnatomyParameters} */\n anatomy?: AnatomyParameters;\n };\n};\n\n/**\n * The addon's preview annotations, for a CSF Next `preview.ts`:\n *\n * ```ts\n * import { definePreview } from '@storybook/your-framework';\n * import componentAnatomy from '@component-anatomy/storybook';\n *\n * export default definePreview({\n * addons: [componentAnatomy()],\n * });\n * ```\n *\n * `.storybook/main.ts` must still list the addon \u2014 `addons:\n * ['@component-anatomy/storybook']` \u2014 since that is what loads the manager\n * panel. What changes under CSF Next is the preview side: a `preview.ts` built\n * with `definePreview` composes *only* its own `addons`, and Storybook drops\n * every addon annotation main.ts would otherwise have contributed. Without the\n * call below, the canvas decorator never mounts and the panel stays empty.\n *\n * Registering in both places is safe \u2014 the two paths are mutually exclusive,\n * so the decorator is composed once either way.\n *\n * The `./blocks` subpath, not this entry, holds the `<Anatomy>` MDX block: it\n * needs React and `@storybook/addon-docs`, both optional peers that must not\n * become load-bearing for a Storybook that only wants the panel.\n */\nexport default () => definePreviewAddon<ComponentAnatomyTypes>(annotations);\n", "/**\n * Preview-side (iframe) entry. Registers a global decorator that mounts a\n * component-anatomy controller over the story canvas and syncs hover state\n * with the manager panel \u2014 and with any `<Anatomy>` doc block on the same\n * docs page \u2014 over the addon channel.\n *\n * The decorator runs in docs view too: the docs `Story` block renders each\n * story through `renderStoryToElement`, which sets `context.canvasElement`\n * exactly as it does in story view. That is what makes auto-discovery and\n * hover sync work inside MDX.\n */\nimport { addons, useEffect } from 'storybook/preview-api';\nimport type {\n ProjectAnnotations,\n Renderer,\n PartialStoryFn,\n StoryContext,\n} from 'storybook/internal/types';\nimport { createAnatomy } from '@component-anatomy/core';\n\nimport { EVENTS, PARAM_KEY } from './constants.js';\nimport { matchesStory } from './channel.js';\nimport type { HoverItemEvent, StoryScopedEvent } from './channel.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport const withComponentAnatomy = (\n storyFn: PartialStoryFn<Renderer>,\n context: StoryContext<Renderer>\n) => {\n const params = context.parameters?.[PARAM_KEY] as AnatomyParameters | undefined;\n\n useEffect(() => {\n if (!params || params.disable) return;\n\n const channel = addons.getChannel();\n const canvas = context.canvasElement as unknown as HTMLElement;\n if (!canvas) return;\n\n const storyId = context.id;\n\n const root = params.root\n ? canvas.querySelector<HTMLElement>(params.root) ?? canvas\n : canvas;\n\n const controller = createAnatomy({\n root,\n parts: params.parts,\n shadowParts: params.shadowParts,\n preset: params.preset,\n theme: params.theme,\n overlay: {\n label: params.overlayLabel !== false,\n padding: params.overlayPadding,\n },\n });\n\n const announceParts = () =>\n channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });\n\n announceParts();\n // Parts can show up after this effect ran: a web component renders its\n // shadow root asynchronously (Lit, Stencil), or is defined late.\n const offChange = controller.on('parts:change', announceParts);\n\n const offEnter = controller.on('part:enter', (partId) =>\n channel.emit(EVENTS.PART_ENTER, { storyId, partId })\n );\n const offLeave = controller.on('part:leave', () =>\n channel.emit(EVENTS.PART_LEAVE, { storyId })\n );\n\n // A docs page mounts several stories at once, so every controller sees\n // every panel/block event \u2014 only act on the ones addressed to this story.\n const onHoverItem = (event: HoverItemEvent) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.highlight(event.partId);\n };\n const onLeaveItem = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.unhighlight();\n };\n const onPartsRequest = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n announceParts();\n };\n\n channel.on(EVENTS.HOVER_ITEM, onHoverItem);\n channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);\n // The panel or block may mount after the story rendered \u2014 let it ask for\n // the list rather than racing the first announcement.\n channel.on(EVENTS.PARTS_REQUEST, onPartsRequest);\n\n return () => {\n channel.off(EVENTS.HOVER_ITEM, onHoverItem);\n channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);\n channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);\n offChange();\n offEnter();\n offLeave();\n controller.destroy();\n };\n }, [context.id]);\n\n return storyFn();\n};\n\nexport const decorators = [withComponentAnatomy];\n\n/**\n * The same annotations as a default export, which is the shape\n * `definePreviewAddon` takes in the package's main entry (see `index.ts`) and\n * the shape a consumer gets from `@component-anatomy/storybook/preview`.\n *\n * Storybook reads `module.default[field] ?? module[field]`, so a preview\n * annotation module that exports both is read exactly once \u2014 the named\n * `decorators` above stays for anyone importing it directly.\n */\nconst annotations: ProjectAnnotations<Renderer> = {\n decorators: [withComponentAnatomy],\n};\n\nexport default annotations;\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/**\n * Channel events used to sync the manager panel \u2014 and the `<Anatomy>` MDX doc\n * block, which runs in the preview iframe \u2014 with the story canvas.\n *\n * Every payload carries the `storyId` it concerns; see `channel.ts` for the\n * payload types and the `matchesStory` filter each listener applies.\n */\nexport const EVENTS = {\n /** preview \u2192 consumers: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 consumers: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 consumers: resolved part list for a story. */\n PARTS: `${ADDON_ID}/parts`,\n /** consumers \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** consumers \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** consumers \u2192 preview: a panel/block mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n", "/**\n * Shared channel payload contract between the preview decorator, the manager\n * panel, and the MDX doc block.\n *\n * Every payload carries the `storyId` it refers to. In story view this is\n * redundant \u2014 only one story is mounted \u2014 but a docs page mounts *many*\n * stories at once, each with its own controller, and each `<Anatomy>` block\n * must talk to exactly one of them. Without addressing, hovering a part in\n * one block highlights the matching part in every other story on the page.\n */\nimport type { AnatomyPartDefinition } from '@component-anatomy/core';\n\n/** preview \u2192 consumers: the resolved part list for one story. */\nexport type PartsEvent = { storyId?: string; parts: AnatomyPartDefinition[] };\n\n/** preview \u2192 consumers: a part became active in that story's canvas. */\nexport type PartEnterEvent = { storyId?: string; partId: string };\n\n/** consumer \u2192 preview: highlight this part in that story's canvas. */\nexport type HoverItemEvent = { storyId?: string; partId: string };\n\n/** Payload for the events that only need to name a story. */\nexport type StoryScopedEvent = { storyId?: string };\n\n/**\n * Whether an event addressed to `eventStoryId` concerns `storyId`.\n *\n * A missing id on *either* side matches everything. That keeps the protocol\n * backward compatible: a manager panel from a newer build still understands\n * an older preview bundle that emits unaddressed events, and vice versa.\n */\nexport const matchesStory = (\n eventStoryId: string | undefined,\n storyId: string | undefined\n): boolean => !eventStoryId || !storyId || eventStoryId === storyId;\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,0BAA0B;;;ACWnC,SAAS,QAAQ,iBAAiB;AAOlC,SAAS,qBAAqB;;;AClBvB,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AASlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;;;ACKO,IAAM,eAAe,CAC1B,cACA,YACY,CAAC,gBAAgB,CAAC,WAAW,iBAAiB;;;AFTrD,IAAM,uBAAuB,CAClC,SACA,YACG;AACH,QAAM,SAAS,QAAQ,aAAa,SAAS;AAE7C,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,QAAS;AAE/B,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAQ;AAEb,UAAM,UAAU,QAAQ;AAExB,UAAM,OAAO,OAAO,OAChB,OAAO,cAA2B,OAAO,IAAI,KAAK,SAClD;AAEJ,UAAM,aAAa,cAAc;AAAA,MAC/B;AAAA,MACA,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,QACP,OAAO,OAAO,iBAAiB;AAAA,QAC/B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,MACpB,QAAQ,KAAK,OAAO,OAAO,EAAE,SAAS,OAAO,WAAW,SAAS,EAAE,CAAC;AAEtE,kBAAc;AAGd,UAAM,YAAY,WAAW,GAAG,gBAAgB,aAAa;AAE7D,UAAM,WAAW,WAAW;AAAA,MAAG;AAAA,MAAc,CAAC,WAC5C,QAAQ,KAAK,OAAO,YAAY,EAAE,SAAS,OAAO,CAAC;AAAA,IACrD;AACA,UAAM,WAAW,WAAW;AAAA,MAAG;AAAA,MAAc,MAC3C,QAAQ,KAAK,OAAO,YAAY,EAAE,QAAQ,CAAC;AAAA,IAC7C;AAIA,UAAM,cAAc,CAAC,UAA0B;AAC7C,UAAI,CAAC,aAAa,OAAO,SAAS,OAAO,EAAG;AAC5C,iBAAW,UAAU,MAAM,MAAM;AAAA,IACnC;AACA,UAAM,cAAc,CAAC,QAA0B,CAAC,MAAM;AACpD,UAAI,CAAC,aAAa,OAAO,SAAS,OAAO,EAAG;AAC5C,iBAAW,YAAY;AAAA,IACzB;AACA,UAAM,iBAAiB,CAAC,QAA0B,CAAC,MAAM;AACvD,UAAI,CAAC,aAAa,OAAO,SAAS,OAAO,EAAG;AAC5C,oBAAc;AAAA,IAChB;AAEA,YAAQ,GAAG,OAAO,YAAY,WAAW;AACzC,YAAQ,GAAG,OAAO,YAAY,WAAW;AAGzC,YAAQ,GAAG,OAAO,eAAe,cAAc;AAE/C,WAAO,MAAM;AACX,cAAQ,IAAI,OAAO,YAAY,WAAW;AAC1C,cAAQ,IAAI,OAAO,YAAY,WAAW;AAC1C,cAAQ,IAAI,OAAO,eAAe,cAAc;AAChD,gBAAU;AACV,eAAS;AACT,eAAS;AACT,iBAAW,QAAQ;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,QAAQ,EAAE,CAAC;AAEf,SAAO,QAAQ;AACjB;AAaA,IAAM,cAA4C;AAAA,EAChD,YAAY,CAAC,oBAAoB;AACnC;AAEA,IAAO,kBAAQ;;;AD5Ef,IAAO,gBAAQ,MAAM,mBAA0C,eAAW;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/preview.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,kBAAkB,EAClB,QAAQ,EACR,cAAc,EACd,YAAY,EACb,MAAM,0BAA0B,CAAC;AAQlC,eAAO,MAAM,oBAAoB,YACtB,cAAc,CAAC,QAAQ,CAAC,WACxB,YAAY,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,kBAAkB,EAClB,QAAQ,EACR,cAAc,EACd,YAAY,EACb,MAAM,0BAA0B,CAAC;AAQlC,eAAO,MAAM,oBAAoB,YACtB,cAAc,CAAC,QAAQ,CAAC,WACxB,YAAY,CAAC,QAAQ,CAAC,QA6EhC,CAAC;AAEF,eAAO,MAAM,UAAU,iCAAyB,CAAC;AAEjD;;;;;;;;GAQG;AACH,QAAA,MAAM,WAAW,EAAE,kBAAkB,CAAC,QAAQ,CAE7C,CAAC;eAEa,WAAW"}
|
package/dist/preview.js
CHANGED
|
@@ -37,6 +37,7 @@ var withComponentAnatomy = (storyFn, context) => {
|
|
|
37
37
|
const controller = createAnatomy({
|
|
38
38
|
root,
|
|
39
39
|
parts: params.parts,
|
|
40
|
+
shadowParts: params.shadowParts,
|
|
40
41
|
preset: params.preset,
|
|
41
42
|
theme: params.theme,
|
|
42
43
|
overlay: {
|
|
@@ -46,6 +47,7 @@ var withComponentAnatomy = (storyFn, context) => {
|
|
|
46
47
|
});
|
|
47
48
|
const announceParts = () => channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });
|
|
48
49
|
announceParts();
|
|
50
|
+
const offChange = controller.on("parts:change", announceParts);
|
|
49
51
|
const offEnter = controller.on(
|
|
50
52
|
"part:enter",
|
|
51
53
|
(partId) => channel.emit(EVENTS.PART_ENTER, { storyId, partId })
|
|
@@ -73,6 +75,7 @@ var withComponentAnatomy = (storyFn, context) => {
|
|
|
73
75
|
channel.off(EVENTS.HOVER_ITEM, onHoverItem);
|
|
74
76
|
channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
75
77
|
channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);
|
|
78
|
+
offChange();
|
|
76
79
|
offEnter();
|
|
77
80
|
offLeave();
|
|
78
81
|
controller.destroy();
|
package/dist/preview.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/preview.ts", "../src/constants.ts", "../src/channel.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Preview-side (iframe) entry. Registers a global decorator that mounts a\n * component-anatomy controller over the story canvas and syncs hover state\n * with the manager panel \u2014 and with any `<Anatomy>` doc block on the same\n * docs page \u2014 over the addon channel.\n *\n * The decorator runs in docs view too: the docs `Story` block renders each\n * story through `renderStoryToElement`, which sets `context.canvasElement`\n * exactly as it does in story view. That is what makes auto-discovery and\n * hover sync work inside MDX.\n */\nimport { addons, useEffect } from 'storybook/preview-api';\nimport type {\n ProjectAnnotations,\n Renderer,\n PartialStoryFn,\n StoryContext,\n} from 'storybook/internal/types';\nimport { createAnatomy } from '@component-anatomy/core';\n\nimport { EVENTS, PARAM_KEY } from './constants.js';\nimport { matchesStory } from './channel.js';\nimport type { HoverItemEvent, StoryScopedEvent } from './channel.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport const withComponentAnatomy = (\n storyFn: PartialStoryFn<Renderer>,\n context: StoryContext<Renderer>\n) => {\n const params = context.parameters?.[PARAM_KEY] as AnatomyParameters | undefined;\n\n useEffect(() => {\n if (!params || params.disable) return;\n\n const channel = addons.getChannel();\n const canvas = context.canvasElement as unknown as HTMLElement;\n if (!canvas) return;\n\n const storyId = context.id;\n\n const root = params.root\n ? canvas.querySelector<HTMLElement>(params.root) ?? canvas\n : canvas;\n\n const controller = createAnatomy({\n root,\n parts: params.parts,\n preset: params.preset,\n theme: params.theme,\n overlay: {\n label: params.overlayLabel !== false,\n padding: params.overlayPadding,\n },\n });\n\n const announceParts = () =>\n channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });\n\n announceParts();\n\n const offEnter = controller.on('part:enter', (partId) =>\n channel.emit(EVENTS.PART_ENTER, { storyId, partId })\n );\n const offLeave = controller.on('part:leave', () =>\n channel.emit(EVENTS.PART_LEAVE, { storyId })\n );\n\n // A docs page mounts several stories at once, so every controller sees\n // every panel/block event \u2014 only act on the ones addressed to this story.\n const onHoverItem = (event: HoverItemEvent) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.highlight(event.partId);\n };\n const onLeaveItem = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.unhighlight();\n };\n const onPartsRequest = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n announceParts();\n };\n\n channel.on(EVENTS.HOVER_ITEM, onHoverItem);\n channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);\n // The panel or block may mount after the story rendered \u2014 let it ask for\n // the list rather than racing the first announcement.\n channel.on(EVENTS.PARTS_REQUEST, onPartsRequest);\n\n return () => {\n channel.off(EVENTS.HOVER_ITEM, onHoverItem);\n channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);\n channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);\n offEnter();\n offLeave();\n controller.destroy();\n };\n }, [context.id]);\n\n return storyFn();\n};\n\nexport const decorators = [withComponentAnatomy];\n\n/**\n * The same annotations as a default export, which is the shape\n * `definePreviewAddon` takes in the package's main entry (see `index.ts`) and\n * the shape a consumer gets from `@component-anatomy/storybook/preview`.\n *\n * Storybook reads `module.default[field] ?? module[field]`, so a preview\n * annotation module that exports both is read exactly once \u2014 the named\n * `decorators` above stays for anyone importing it directly.\n */\nconst annotations: ProjectAnnotations<Renderer> = {\n decorators: [withComponentAnatomy],\n};\n\nexport default annotations;\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/**\n * Channel events used to sync the manager panel \u2014 and the `<Anatomy>` MDX doc\n * block, which runs in the preview iframe \u2014 with the story canvas.\n *\n * Every payload carries the `storyId` it concerns; see `channel.ts` for the\n * payload types and the `matchesStory` filter each listener applies.\n */\nexport const EVENTS = {\n /** preview \u2192 consumers: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 consumers: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 consumers: resolved part list for a story. */\n PARTS: `${ADDON_ID}/parts`,\n /** consumers \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** consumers \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** consumers \u2192 preview: a panel/block mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n", "/**\n * Shared channel payload contract between the preview decorator, the manager\n * panel, and the MDX doc block.\n *\n * Every payload carries the `storyId` it refers to. In story view this is\n * redundant \u2014 only one story is mounted \u2014 but a docs page mounts *many*\n * stories at once, each with its own controller, and each `<Anatomy>` block\n * must talk to exactly one of them. Without addressing, hovering a part in\n * one block highlights the matching part in every other story on the page.\n */\nimport type { AnatomyPartDefinition } from '@component-anatomy/core';\n\n/** preview \u2192 consumers: the resolved part list for one story. */\nexport type PartsEvent = { storyId?: string; parts: AnatomyPartDefinition[] };\n\n/** preview \u2192 consumers: a part became active in that story's canvas. */\nexport type PartEnterEvent = { storyId?: string; partId: string };\n\n/** consumer \u2192 preview: highlight this part in that story's canvas. */\nexport type HoverItemEvent = { storyId?: string; partId: string };\n\n/** Payload for the events that only need to name a story. */\nexport type StoryScopedEvent = { storyId?: string };\n\n/**\n * Whether an event addressed to `eventStoryId` concerns `storyId`.\n *\n * A missing id on *either* side matches everything. That keeps the protocol\n * backward compatible: a manager panel from a newer build still understands\n * an older preview bundle that emits unaddressed events, and vice versa.\n */\nexport const matchesStory = (\n eventStoryId: string | undefined,\n storyId: string | undefined\n): boolean => !eventStoryId || !storyId || eventStoryId === storyId;\n"],
|
|
5
|
-
"mappings": ";AAWA,SAAS,QAAQ,iBAAiB;AAOlC,SAAS,qBAAqB;;;AClBvB,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AASlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;;;ACKO,IAAM,eAAe,CAC1B,cACA,YACY,CAAC,gBAAgB,CAAC,WAAW,iBAAiB;;;AFTrD,IAAM,uBAAuB,CAClC,SACA,YACG;AACH,QAAM,SAAS,QAAQ,aAAa,SAAS;AAE7C,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,QAAS;AAE/B,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAQ;AAEb,UAAM,UAAU,QAAQ;AAExB,UAAM,OAAO,OAAO,OAChB,OAAO,cAA2B,OAAO,IAAI,KAAK,SAClD;AAEJ,UAAM,aAAa,cAAc;AAAA,MAC/B;AAAA,MACA,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,QACP,OAAO,OAAO,iBAAiB;AAAA,QAC/B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,MACpB,QAAQ,KAAK,OAAO,OAAO,EAAE,SAAS,OAAO,WAAW,SAAS,EAAE,CAAC;AAEtE,kBAAc;
|
|
4
|
+
"sourcesContent": ["/**\n * Preview-side (iframe) entry. Registers a global decorator that mounts a\n * component-anatomy controller over the story canvas and syncs hover state\n * with the manager panel \u2014 and with any `<Anatomy>` doc block on the same\n * docs page \u2014 over the addon channel.\n *\n * The decorator runs in docs view too: the docs `Story` block renders each\n * story through `renderStoryToElement`, which sets `context.canvasElement`\n * exactly as it does in story view. That is what makes auto-discovery and\n * hover sync work inside MDX.\n */\nimport { addons, useEffect } from 'storybook/preview-api';\nimport type {\n ProjectAnnotations,\n Renderer,\n PartialStoryFn,\n StoryContext,\n} from 'storybook/internal/types';\nimport { createAnatomy } from '@component-anatomy/core';\n\nimport { EVENTS, PARAM_KEY } from './constants.js';\nimport { matchesStory } from './channel.js';\nimport type { HoverItemEvent, StoryScopedEvent } from './channel.js';\nimport type { AnatomyParameters } from './types.js';\n\nexport const withComponentAnatomy = (\n storyFn: PartialStoryFn<Renderer>,\n context: StoryContext<Renderer>\n) => {\n const params = context.parameters?.[PARAM_KEY] as AnatomyParameters | undefined;\n\n useEffect(() => {\n if (!params || params.disable) return;\n\n const channel = addons.getChannel();\n const canvas = context.canvasElement as unknown as HTMLElement;\n if (!canvas) return;\n\n const storyId = context.id;\n\n const root = params.root\n ? canvas.querySelector<HTMLElement>(params.root) ?? canvas\n : canvas;\n\n const controller = createAnatomy({\n root,\n parts: params.parts,\n shadowParts: params.shadowParts,\n preset: params.preset,\n theme: params.theme,\n overlay: {\n label: params.overlayLabel !== false,\n padding: params.overlayPadding,\n },\n });\n\n const announceParts = () =>\n channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });\n\n announceParts();\n // Parts can show up after this effect ran: a web component renders its\n // shadow root asynchronously (Lit, Stencil), or is defined late.\n const offChange = controller.on('parts:change', announceParts);\n\n const offEnter = controller.on('part:enter', (partId) =>\n channel.emit(EVENTS.PART_ENTER, { storyId, partId })\n );\n const offLeave = controller.on('part:leave', () =>\n channel.emit(EVENTS.PART_LEAVE, { storyId })\n );\n\n // A docs page mounts several stories at once, so every controller sees\n // every panel/block event \u2014 only act on the ones addressed to this story.\n const onHoverItem = (event: HoverItemEvent) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.highlight(event.partId);\n };\n const onLeaveItem = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n controller.unhighlight();\n };\n const onPartsRequest = (event: StoryScopedEvent = {}) => {\n if (!matchesStory(event?.storyId, storyId)) return;\n announceParts();\n };\n\n channel.on(EVENTS.HOVER_ITEM, onHoverItem);\n channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);\n // The panel or block may mount after the story rendered \u2014 let it ask for\n // the list rather than racing the first announcement.\n channel.on(EVENTS.PARTS_REQUEST, onPartsRequest);\n\n return () => {\n channel.off(EVENTS.HOVER_ITEM, onHoverItem);\n channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);\n channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);\n offChange();\n offEnter();\n offLeave();\n controller.destroy();\n };\n }, [context.id]);\n\n return storyFn();\n};\n\nexport const decorators = [withComponentAnatomy];\n\n/**\n * The same annotations as a default export, which is the shape\n * `definePreviewAddon` takes in the package's main entry (see `index.ts`) and\n * the shape a consumer gets from `@component-anatomy/storybook/preview`.\n *\n * Storybook reads `module.default[field] ?? module[field]`, so a preview\n * annotation module that exports both is read exactly once \u2014 the named\n * `decorators` above stays for anyone importing it directly.\n */\nconst annotations: ProjectAnnotations<Renderer> = {\n decorators: [withComponentAnatomy],\n};\n\nexport default annotations;\n", "export const ADDON_ID = 'component-anatomy';\nexport const PANEL_ID = `${ADDON_ID}/panel`;\n\n/** Story parameter key: `parameters.anatomy = { ... }` */\nexport const PARAM_KEY = 'anatomy';\n\n/**\n * Channel events used to sync the manager panel \u2014 and the `<Anatomy>` MDX doc\n * block, which runs in the preview iframe \u2014 with the story canvas.\n *\n * Every payload carries the `storyId` it concerns; see `channel.ts` for the\n * payload types and the `matchesStory` filter each listener applies.\n */\nexport const EVENTS = {\n /** preview \u2192 consumers: a part became active in the canvas (hover/programmatic). */\n PART_ENTER: `${ADDON_ID}/part-enter`,\n /** preview \u2192 consumers: no part is active anymore. */\n PART_LEAVE: `${ADDON_ID}/part-leave`,\n /** preview \u2192 consumers: resolved part list for a story. */\n PARTS: `${ADDON_ID}/parts`,\n /** consumers \u2192 preview: the user hovers/focuses a panel entry. */\n HOVER_ITEM: `${ADDON_ID}/hover-item`,\n /** consumers \u2192 preview: the user left a panel entry. */\n LEAVE_ITEM: `${ADDON_ID}/leave-item`,\n /** consumers \u2192 preview: a panel/block mounted and wants the current part list. */\n PARTS_REQUEST: `${ADDON_ID}/parts-request`,\n} as const;\n", "/**\n * Shared channel payload contract between the preview decorator, the manager\n * panel, and the MDX doc block.\n *\n * Every payload carries the `storyId` it refers to. In story view this is\n * redundant \u2014 only one story is mounted \u2014 but a docs page mounts *many*\n * stories at once, each with its own controller, and each `<Anatomy>` block\n * must talk to exactly one of them. Without addressing, hovering a part in\n * one block highlights the matching part in every other story on the page.\n */\nimport type { AnatomyPartDefinition } from '@component-anatomy/core';\n\n/** preview \u2192 consumers: the resolved part list for one story. */\nexport type PartsEvent = { storyId?: string; parts: AnatomyPartDefinition[] };\n\n/** preview \u2192 consumers: a part became active in that story's canvas. */\nexport type PartEnterEvent = { storyId?: string; partId: string };\n\n/** consumer \u2192 preview: highlight this part in that story's canvas. */\nexport type HoverItemEvent = { storyId?: string; partId: string };\n\n/** Payload for the events that only need to name a story. */\nexport type StoryScopedEvent = { storyId?: string };\n\n/**\n * Whether an event addressed to `eventStoryId` concerns `storyId`.\n *\n * A missing id on *either* side matches everything. That keeps the protocol\n * backward compatible: a manager panel from a newer build still understands\n * an older preview bundle that emits unaddressed events, and vice versa.\n */\nexport const matchesStory = (\n eventStoryId: string | undefined,\n storyId: string | undefined\n): boolean => !eventStoryId || !storyId || eventStoryId === storyId;\n"],
|
|
5
|
+
"mappings": ";AAWA,SAAS,QAAQ,iBAAiB;AAOlC,SAAS,qBAAqB;;;AClBvB,IAAM,WAAW;AACjB,IAAM,WAAW,GAAG,QAAQ;AAG5B,IAAM,YAAY;AASlB,IAAM,SAAS;AAAA;AAAA,EAEpB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,OAAO,GAAG,QAAQ;AAAA;AAAA,EAElB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,YAAY,GAAG,QAAQ;AAAA;AAAA,EAEvB,eAAe,GAAG,QAAQ;AAC5B;;;ACKO,IAAM,eAAe,CAC1B,cACA,YACY,CAAC,gBAAgB,CAAC,WAAW,iBAAiB;;;AFTrD,IAAM,uBAAuB,CAClC,SACA,YACG;AACH,QAAM,SAAS,QAAQ,aAAa,SAAS;AAE7C,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,QAAS;AAE/B,UAAM,UAAU,OAAO,WAAW;AAClC,UAAM,SAAS,QAAQ;AACvB,QAAI,CAAC,OAAQ;AAEb,UAAM,UAAU,QAAQ;AAExB,UAAM,OAAO,OAAO,OAChB,OAAO,cAA2B,OAAO,IAAI,KAAK,SAClD;AAEJ,UAAM,aAAa,cAAc;AAAA,MAC/B;AAAA,MACA,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,SAAS;AAAA,QACP,OAAO,OAAO,iBAAiB;AAAA,QAC/B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,gBAAgB,MACpB,QAAQ,KAAK,OAAO,OAAO,EAAE,SAAS,OAAO,WAAW,SAAS,EAAE,CAAC;AAEtE,kBAAc;AAGd,UAAM,YAAY,WAAW,GAAG,gBAAgB,aAAa;AAE7D,UAAM,WAAW,WAAW;AAAA,MAAG;AAAA,MAAc,CAAC,WAC5C,QAAQ,KAAK,OAAO,YAAY,EAAE,SAAS,OAAO,CAAC;AAAA,IACrD;AACA,UAAM,WAAW,WAAW;AAAA,MAAG;AAAA,MAAc,MAC3C,QAAQ,KAAK,OAAO,YAAY,EAAE,QAAQ,CAAC;AAAA,IAC7C;AAIA,UAAM,cAAc,CAAC,UAA0B;AAC7C,UAAI,CAAC,aAAa,OAAO,SAAS,OAAO,EAAG;AAC5C,iBAAW,UAAU,MAAM,MAAM;AAAA,IACnC;AACA,UAAM,cAAc,CAAC,QAA0B,CAAC,MAAM;AACpD,UAAI,CAAC,aAAa,OAAO,SAAS,OAAO,EAAG;AAC5C,iBAAW,YAAY;AAAA,IACzB;AACA,UAAM,iBAAiB,CAAC,QAA0B,CAAC,MAAM;AACvD,UAAI,CAAC,aAAa,OAAO,SAAS,OAAO,EAAG;AAC5C,oBAAc;AAAA,IAChB;AAEA,YAAQ,GAAG,OAAO,YAAY,WAAW;AACzC,YAAQ,GAAG,OAAO,YAAY,WAAW;AAGzC,YAAQ,GAAG,OAAO,eAAe,cAAc;AAE/C,WAAO,MAAM;AACX,cAAQ,IAAI,OAAO,YAAY,WAAW;AAC1C,cAAQ,IAAI,OAAO,YAAY,WAAW;AAC1C,cAAQ,IAAI,OAAO,eAAe,cAAc;AAChD,gBAAU;AACV,eAAS;AACT,eAAS;AACT,iBAAW,QAAQ;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,QAAQ,EAAE,CAAC;AAEf,SAAO,QAAQ;AACjB;AAEO,IAAM,aAAa,CAAC,oBAAoB;AAW/C,IAAM,cAA4C;AAAA,EAChD,YAAY,CAAC,oBAAoB;AACnC;AAEA,IAAO,kBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ import type { AnatomyPartDefinition, AnatomyPresetName, AnatomyTheme } from '@co
|
|
|
17
17
|
export type AnatomyParameters = {
|
|
18
18
|
/**
|
|
19
19
|
* Part definitions. If omitted, parts are auto-discovered from
|
|
20
|
-
* `data-part` attributes in the rendered story
|
|
20
|
+
* `data-part` attributes in the rendered story, including inside open
|
|
21
|
+
* shadow roots.
|
|
21
22
|
*/
|
|
22
23
|
parts?: AnatomyPartDefinition[];
|
|
23
24
|
/** Named visual preset for the canvas overlays. Default: 'default'. */
|
|
@@ -28,6 +29,11 @@ export type AnatomyParameters = {
|
|
|
28
29
|
overlayLabel?: boolean;
|
|
29
30
|
/** Inflate highlight boxes by N pixels. Default: 0. */
|
|
30
31
|
overlayPadding?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Also treat the native `part` attribute (CSS Shadow Parts) on elements
|
|
34
|
+
* inside a web component's open shadow root as a part id. Default: false.
|
|
35
|
+
*/
|
|
36
|
+
shadowParts?: boolean;
|
|
31
37
|
/**
|
|
32
38
|
* CSS selector to narrow the anatomy root inside the story canvas.
|
|
33
39
|
* Defaults to the whole canvas element.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACb,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACb,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;OAIG;IACH,KAAK,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAChC,uEAAuE;IACvE,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,sEAAsE;IACtE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@component-anatomy/storybook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Storybook addon — interactive component anatomy panel synced with the story canvas",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Julien Déramond",
|
|
@@ -65,15 +65,15 @@
|
|
|
65
65
|
"access": "public"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@component-anatomy/core": "^0.
|
|
68
|
+
"@component-anatomy/core": "^0.2.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@storybook/addon-docs": "11.0.0-alpha.
|
|
72
|
-
"@types/react": "^19.
|
|
71
|
+
"@storybook/addon-docs": "11.0.0-alpha.1",
|
|
72
|
+
"@types/react": "^19.3.0",
|
|
73
73
|
"esbuild": "^0.28.2",
|
|
74
|
-
"react": "^19.
|
|
75
|
-
"react-dom": "^19.
|
|
76
|
-
"storybook": "11.0.0-alpha.
|
|
74
|
+
"react": "^19.3.0",
|
|
75
|
+
"react-dom": "^19.3.0",
|
|
76
|
+
"storybook": "11.0.0-alpha.1",
|
|
77
77
|
"typescript": "^7.0.2"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
package/src/preview.ts
CHANGED
|
@@ -45,6 +45,7 @@ export const withComponentAnatomy = (
|
|
|
45
45
|
const controller = createAnatomy({
|
|
46
46
|
root,
|
|
47
47
|
parts: params.parts,
|
|
48
|
+
shadowParts: params.shadowParts,
|
|
48
49
|
preset: params.preset,
|
|
49
50
|
theme: params.theme,
|
|
50
51
|
overlay: {
|
|
@@ -57,6 +58,9 @@ export const withComponentAnatomy = (
|
|
|
57
58
|
channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });
|
|
58
59
|
|
|
59
60
|
announceParts();
|
|
61
|
+
// Parts can show up after this effect ran: a web component renders its
|
|
62
|
+
// shadow root asynchronously (Lit, Stencil), or is defined late.
|
|
63
|
+
const offChange = controller.on('parts:change', announceParts);
|
|
60
64
|
|
|
61
65
|
const offEnter = controller.on('part:enter', (partId) =>
|
|
62
66
|
channel.emit(EVENTS.PART_ENTER, { storyId, partId })
|
|
@@ -90,6 +94,7 @@ export const withComponentAnatomy = (
|
|
|
90
94
|
channel.off(EVENTS.HOVER_ITEM, onHoverItem);
|
|
91
95
|
channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);
|
|
92
96
|
channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);
|
|
97
|
+
offChange();
|
|
93
98
|
offEnter();
|
|
94
99
|
offLeave();
|
|
95
100
|
controller.destroy();
|
package/src/types.ts
CHANGED
|
@@ -22,7 +22,8 @@ import type {
|
|
|
22
22
|
export type AnatomyParameters = {
|
|
23
23
|
/**
|
|
24
24
|
* Part definitions. If omitted, parts are auto-discovered from
|
|
25
|
-
* `data-part` attributes in the rendered story
|
|
25
|
+
* `data-part` attributes in the rendered story, including inside open
|
|
26
|
+
* shadow roots.
|
|
26
27
|
*/
|
|
27
28
|
parts?: AnatomyPartDefinition[];
|
|
28
29
|
/** Named visual preset for the canvas overlays. Default: 'default'. */
|
|
@@ -33,6 +34,11 @@ export type AnatomyParameters = {
|
|
|
33
34
|
overlayLabel?: boolean;
|
|
34
35
|
/** Inflate highlight boxes by N pixels. Default: 0. */
|
|
35
36
|
overlayPadding?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Also treat the native `part` attribute (CSS Shadow Parts) on elements
|
|
39
|
+
* inside a web component's open shadow root as a part id. Default: false.
|
|
40
|
+
*/
|
|
41
|
+
shadowParts?: boolean;
|
|
36
42
|
/**
|
|
37
43
|
* CSS selector to narrow the anatomy root inside the story canvas.
|
|
38
44
|
* Defaults to the whole canvas element.
|