@component-anatomy/storybook 0.0.2 → 0.2.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/src/constants.ts CHANGED
@@ -4,18 +4,24 @@ export const PANEL_ID = `${ADDON_ID}/panel`;
4
4
  /** Story parameter key: `parameters.anatomy = { ... }` */
5
5
  export const PARAM_KEY = 'anatomy';
6
6
 
7
- /** Channel events used to sync the manager panel with the preview iframe. */
7
+ /**
8
+ * Channel events used to sync the manager panel — and the `<Anatomy>` MDX doc
9
+ * block, which runs in the preview iframe — with the story canvas.
10
+ *
11
+ * Every payload carries the `storyId` it concerns; see `channel.ts` for the
12
+ * payload types and the `matchesStory` filter each listener applies.
13
+ */
8
14
  export const EVENTS = {
9
- /** preview → manager: a part became active in the canvas (hover/programmatic). */
15
+ /** preview → consumers: a part became active in the canvas (hover/programmatic). */
10
16
  PART_ENTER: `${ADDON_ID}/part-enter`,
11
- /** preview → manager: no part is active anymore. */
17
+ /** preview → consumers: no part is active anymore. */
12
18
  PART_LEAVE: `${ADDON_ID}/part-leave`,
13
- /** preview → manager: resolved part list for the current story. */
19
+ /** preview → consumers: resolved part list for a story. */
14
20
  PARTS: `${ADDON_ID}/parts`,
15
- /** manager → preview: the user hovers/focuses a panel entry. */
21
+ /** consumers → preview: the user hovers/focuses a panel entry. */
16
22
  HOVER_ITEM: `${ADDON_ID}/hover-item`,
17
- /** manager → preview: the user left a panel entry. */
23
+ /** consumers → preview: the user left a panel entry. */
18
24
  LEAVE_ITEM: `${ADDON_ID}/leave-item`,
19
- /** manager → preview: the panel mounted and wants the current part list. */
25
+ /** consumers → preview: a panel/block mounted and wants the current part list. */
20
26
  PARTS_REQUEST: `${ADDON_ID}/parts-request`,
21
27
  } as const;
package/src/index.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  export { ADDON_ID, PANEL_ID, PARAM_KEY, EVENTS } from './constants.js';
2
2
  export type { AnatomyParameters } from './types.js';
3
+
4
+ // The `<Anatomy>` doc block lives in the `./blocks` subpath, not here: this
5
+ // entry is loaded at config time by `.storybook/main.ts` (and built to CJS),
6
+ // while the block needs React and `@storybook/addon-docs`, both optional
7
+ // peers that must not become load-bearing for `addons: ['...']` to work.
package/src/preview.ts CHANGED
@@ -1,13 +1,21 @@
1
1
  /**
2
2
  * Preview-side (iframe) entry. Registers a global decorator that mounts a
3
3
  * component-anatomy controller over the story canvas and syncs hover state
4
- * with the manager panel over the addon channel.
4
+ * with the manager panel and with any `<Anatomy>` doc block on the same
5
+ * docs page — over the addon channel.
6
+ *
7
+ * The decorator runs in docs view too: the docs `Story` block renders each
8
+ * story through `renderStoryToElement`, which sets `context.canvasElement`
9
+ * exactly as it does in story view. That is what makes auto-discovery and
10
+ * hover sync work inside MDX.
5
11
  */
6
12
  import { addons, useEffect } from 'storybook/preview-api';
7
13
  import type { Renderer, PartialStoryFn, StoryContext } from 'storybook/internal/types';
8
14
  import { createAnatomy } from '@component-anatomy/core';
9
15
 
10
16
  import { EVENTS, PARAM_KEY } from './constants.js';
17
+ import { matchesStory } from './channel.js';
18
+ import type { HoverItemEvent, StoryScopedEvent } from './channel.js';
11
19
  import type { AnatomyParameters } from './types.js';
12
20
 
13
21
  export const withComponentAnatomy = (
@@ -23,6 +31,8 @@ export const withComponentAnatomy = (
23
31
  const canvas = context.canvasElement as unknown as HTMLElement;
24
32
  if (!canvas) return;
25
33
 
34
+ const storyId = context.id;
35
+
26
36
  const root = params.root
27
37
  ? canvas.querySelector<HTMLElement>(params.root) ?? canvas
28
38
  : canvas;
@@ -39,29 +49,42 @@ export const withComponentAnatomy = (
39
49
  });
40
50
 
41
51
  const announceParts = () =>
42
- channel.emit(EVENTS.PARTS, { storyId: context.id, parts: controller.getParts() });
52
+ channel.emit(EVENTS.PARTS, { storyId, parts: controller.getParts() });
43
53
 
44
54
  announceParts();
45
55
 
46
56
  const offEnter = controller.on('part:enter', (partId) =>
47
- channel.emit(EVENTS.PART_ENTER, { partId })
57
+ channel.emit(EVENTS.PART_ENTER, { storyId, partId })
48
58
  );
49
59
  const offLeave = controller.on('part:leave', () =>
50
- channel.emit(EVENTS.PART_LEAVE, {})
60
+ channel.emit(EVENTS.PART_LEAVE, { storyId })
51
61
  );
52
62
 
53
- const onHoverItem = ({ partId }: { partId: string }) => controller.highlight(partId);
54
- const onLeaveItem = () => controller.unhighlight();
63
+ // A docs page mounts several stories at once, so every controller sees
64
+ // every panel/block event only act on the ones addressed to this story.
65
+ const onHoverItem = (event: HoverItemEvent) => {
66
+ if (!matchesStory(event?.storyId, storyId)) return;
67
+ controller.highlight(event.partId);
68
+ };
69
+ const onLeaveItem = (event: StoryScopedEvent = {}) => {
70
+ if (!matchesStory(event?.storyId, storyId)) return;
71
+ controller.unhighlight();
72
+ };
73
+ const onPartsRequest = (event: StoryScopedEvent = {}) => {
74
+ if (!matchesStory(event?.storyId, storyId)) return;
75
+ announceParts();
76
+ };
55
77
 
56
78
  channel.on(EVENTS.HOVER_ITEM, onHoverItem);
57
79
  channel.on(EVENTS.LEAVE_ITEM, onLeaveItem);
58
- // The panel may mount after the story rendered — let it ask for the list.
59
- channel.on(EVENTS.PARTS_REQUEST, announceParts);
80
+ // The panel or block may mount after the story rendered — let it ask for
81
+ // the list rather than racing the first announcement.
82
+ channel.on(EVENTS.PARTS_REQUEST, onPartsRequest);
60
83
 
61
84
  return () => {
62
85
  channel.off(EVENTS.HOVER_ITEM, onHoverItem);
63
86
  channel.off(EVENTS.LEAVE_ITEM, onLeaveItem);
64
- channel.off(EVENTS.PARTS_REQUEST, announceParts);
87
+ channel.off(EVENTS.PARTS_REQUEST, onPartsRequest);
65
88
  offEnter();
66
89
  offLeave();
67
90
  controller.destroy();