@component-anatomy/storybook 0.0.2 → 0.1.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/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();