@component-anatomy/core 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,463 @@
1
+ // src/registry.ts
2
+ var AnatomyRegistry = class {
3
+ constructor(root) {
4
+ this.observer = null;
5
+ this.root = root;
6
+ }
7
+ /**
8
+ * Returns a Map of partId → matching HTMLElements.
9
+ * Always queries the live DOM — never returns a stale cache.
10
+ */
11
+ query() {
12
+ const map = /* @__PURE__ */ new Map();
13
+ const nodes = this.root.querySelectorAll("[data-part]");
14
+ nodes.forEach((el) => {
15
+ const id = el.dataset.part;
16
+ if (!id) return;
17
+ const existing = map.get(id) ?? [];
18
+ existing.push(el);
19
+ map.set(id, existing);
20
+ });
21
+ return map;
22
+ }
23
+ /**
24
+ * Returns all unique part IDs present in the DOM, in document order.
25
+ */
26
+ partIds() {
27
+ const seen = /* @__PURE__ */ new Set();
28
+ const result = [];
29
+ this.root.querySelectorAll("[data-part]").forEach((el) => {
30
+ const id = el.dataset.part;
31
+ if (id && !seen.has(id)) {
32
+ seen.add(id);
33
+ result.push(id);
34
+ }
35
+ });
36
+ return result;
37
+ }
38
+ /**
39
+ * Watches the root for DOM mutations and calls the callback when [data-part]
40
+ * elements are added or removed. Returns a cleanup function.
41
+ */
42
+ observe(callback) {
43
+ this.observer = new MutationObserver((mutations) => {
44
+ const relevant = mutations.some(
45
+ (m) => Array.from(m.addedNodes).concat(Array.from(m.removedNodes)).some(
46
+ (n) => n instanceof HTMLElement && (n.hasAttribute("data-part") || n.querySelector("[data-part]") !== null)
47
+ )
48
+ );
49
+ if (relevant) callback();
50
+ });
51
+ this.observer.observe(this.root, { childList: true, subtree: true });
52
+ return () => {
53
+ this.observer?.disconnect();
54
+ this.observer = null;
55
+ };
56
+ }
57
+ destroy() {
58
+ this.observer?.disconnect();
59
+ this.observer = null;
60
+ }
61
+ };
62
+
63
+ // src/overlay.ts
64
+ var OVERLAY_CLASS = "ca-overlay";
65
+ var LABEL_CLASS = "ca-overlay-label";
66
+ var STYLE_ID = "ca-overlay-styles";
67
+ function injectStyles() {
68
+ if (document.getElementById(STYLE_ID)) return;
69
+ const style = document.createElement("style");
70
+ style.id = STYLE_ID;
71
+ style.textContent = `
72
+ .${OVERLAY_CLASS} {
73
+ position: fixed;
74
+ pointer-events: none;
75
+ box-sizing: border-box;
76
+ border-radius: var(--ca-overlay-radius, 4px);
77
+ background: var(--ca-overlay-bg, rgba(99, 102, 241, 0.18));
78
+ outline: var(--ca-overlay-border-width, 2px)
79
+ var(--ca-overlay-border-style, solid)
80
+ var(--ca-overlay-border, rgba(99, 102, 241, 0.75));
81
+ outline-offset: 1px;
82
+ z-index: var(--ca-overlay-z, 9998);
83
+ transition: opacity var(--ca-transition, 150ms) ease;
84
+ }
85
+ .${OVERLAY_CLASS}.ca-overlay--hidden {
86
+ opacity: 0;
87
+ }
88
+
89
+ /* Name label chip \u2014 floats above the overlay */
90
+ .${LABEL_CLASS} {
91
+ position: fixed;
92
+ pointer-events: none;
93
+ z-index: var(--ca-overlay-z, 9999);
94
+ display: inline-flex;
95
+ align-items: center;
96
+ gap: 4px;
97
+ padding: 2px 7px 2px 5px;
98
+ border-radius: 4px;
99
+ background: var(--ca-label-bg, rgba(79, 70, 229, 1));
100
+ color: var(--ca-label-fg, #fff);
101
+ font-family: var(--ca-label-font, ui-monospace, 'Cascadia Code', 'Fira Mono', monospace);
102
+ font-size: var(--ca-label-font-size, 11px);
103
+ font-weight: 500;
104
+ letter-spacing: 0.02em;
105
+ white-space: nowrap;
106
+ line-height: 1.6;
107
+ box-shadow: 0 1px 4px rgba(0,0,0,0.25);
108
+ transition: opacity var(--ca-transition, 150ms) ease;
109
+ }
110
+ .${LABEL_CLASS}.ca-overlay--hidden {
111
+ opacity: 0;
112
+ }
113
+ .${LABEL_CLASS}::before {
114
+ content: '';
115
+ display: inline-block;
116
+ width: 5px;
117
+ height: 5px;
118
+ border-radius: 50%;
119
+ background: color-mix(in srgb, var(--ca-label-fg, #fff) 60%, transparent);
120
+ flex-shrink: 0;
121
+ }
122
+ `;
123
+ document.head.appendChild(style);
124
+ }
125
+ function applyVars(el, vars) {
126
+ for (const [prop, value] of Object.entries(vars)) {
127
+ el.style.setProperty(prop, value);
128
+ }
129
+ }
130
+ var AnatomyOverlay = class {
131
+ constructor(vars = {}, options = {}) {
132
+ this.overlays = [];
133
+ this.labels = [];
134
+ this.activeElements = [];
135
+ this.activePart = null;
136
+ this.rafId = null;
137
+ this.vars = vars;
138
+ this.options = options;
139
+ injectStyles();
140
+ }
141
+ /**
142
+ * Replace the per-instance theme variables. Re-applies to any overlays
143
+ * currently on screen.
144
+ */
145
+ setVars(vars) {
146
+ this.vars = vars;
147
+ if (this.overlays.length > 0 || this.labels.length > 0) {
148
+ [...this.overlays, ...this.labels].forEach((el) => {
149
+ el.removeAttribute("style");
150
+ applyVars(el, vars);
151
+ });
152
+ this._position();
153
+ }
154
+ }
155
+ /**
156
+ * Show highlight overlays + label chip over each of the given elements.
157
+ */
158
+ show(elements, part) {
159
+ this.hide();
160
+ this.activeElements = elements;
161
+ this.activePart = part;
162
+ const showLabel = this.options.label !== false;
163
+ elements.forEach((element, index) => {
164
+ const ctx = { part, element, index };
165
+ const div = document.createElement("div");
166
+ div.className = `${OVERLAY_CLASS} ca-overlay--hidden`;
167
+ if (this.options.className) div.className += ` ${this.options.className}`;
168
+ div.setAttribute("aria-hidden", "true");
169
+ applyVars(div, this.vars);
170
+ document.body.appendChild(div);
171
+ this.overlays.push(div);
172
+ if (this.options.decorateOverlay) {
173
+ this.options.decorateOverlay(div, ctx);
174
+ }
175
+ if (showLabel) {
176
+ const label = document.createElement("div");
177
+ label.className = `${LABEL_CLASS} ca-overlay--hidden`;
178
+ label.setAttribute("aria-hidden", "true");
179
+ applyVars(label, this.vars);
180
+ const custom = this.options.renderLabel?.(ctx);
181
+ if (typeof custom === "string") label.textContent = custom;
182
+ else if (custom instanceof Node) label.appendChild(custom);
183
+ else label.textContent = part.name;
184
+ document.body.appendChild(label);
185
+ this.labels.push(label);
186
+ }
187
+ });
188
+ requestAnimationFrame(() => {
189
+ this._position();
190
+ this.overlays.forEach((o) => o.classList.remove("ca-overlay--hidden"));
191
+ this.labels.forEach((l) => l.classList.remove("ca-overlay--hidden"));
192
+ });
193
+ }
194
+ /**
195
+ * Remove all overlays and labels from the DOM.
196
+ */
197
+ hide() {
198
+ if (this.rafId !== null) {
199
+ cancelAnimationFrame(this.rafId);
200
+ this.rafId = null;
201
+ }
202
+ this.overlays.forEach((o) => o.remove());
203
+ this.labels.forEach((l) => l.remove());
204
+ this.overlays = [];
205
+ this.labels = [];
206
+ this.activeElements = [];
207
+ this.activePart = null;
208
+ }
209
+ /**
210
+ * Recompute overlay positions. Call on scroll or resize.
211
+ */
212
+ reposition() {
213
+ if (this.overlays.length === 0) return;
214
+ if (this.rafId !== null) cancelAnimationFrame(this.rafId);
215
+ this.rafId = requestAnimationFrame(() => {
216
+ this._position();
217
+ this.rafId = null;
218
+ });
219
+ }
220
+ destroy() {
221
+ this.hide();
222
+ }
223
+ _position() {
224
+ const LABEL_OFFSET = 6;
225
+ const LABEL_HEIGHT = 22;
226
+ const pad = this.options.padding ?? 0;
227
+ this.activeElements.forEach((el, i) => {
228
+ const overlay = this.overlays[i];
229
+ const label = this.labels[i];
230
+ if (!overlay) return;
231
+ const rect = el.getBoundingClientRect();
232
+ const top = rect.top - pad;
233
+ const left = rect.left - pad;
234
+ const width = rect.width + pad * 2;
235
+ const height = rect.height + pad * 2;
236
+ overlay.style.top = `${top}px`;
237
+ overlay.style.left = `${left}px`;
238
+ overlay.style.width = `${width}px`;
239
+ overlay.style.height = `${height}px`;
240
+ if (!label) return;
241
+ const labelTop = top - LABEL_HEIGHT - LABEL_OFFSET;
242
+ const finalTop = labelTop < 4 ? top + height + LABEL_OFFSET : labelTop;
243
+ label.style.top = `${finalTop}px`;
244
+ label.style.left = `${left}px`;
245
+ });
246
+ }
247
+ };
248
+
249
+ // src/theme.ts
250
+ var TOKEN_TO_VAR = {
251
+ overlayBg: "--ca-overlay-bg",
252
+ overlayBorder: "--ca-overlay-border",
253
+ overlayBorderWidth: "--ca-overlay-border-width",
254
+ overlayRadius: "--ca-overlay-radius",
255
+ labelBg: "--ca-label-bg",
256
+ labelFg: "--ca-label-fg",
257
+ labelFont: "--ca-label-font",
258
+ labelFontSize: "--ca-label-font-size",
259
+ zIndex: "--ca-overlay-z",
260
+ transitionMs: "--ca-transition"
261
+ };
262
+ var presets = {
263
+ /** The built-in indigo look. Empty on purpose: it lives in the stylesheet defaults. */
264
+ default: {},
265
+ /** Quiet: no fill, thin neutral border, subdued label. */
266
+ minimal: {
267
+ overlayBg: "transparent",
268
+ overlayBorder: "rgba(107, 114, 128, 0.9)",
269
+ overlayBorderWidth: "1px",
270
+ overlayRadius: "2px",
271
+ labelBg: "#374151",
272
+ labelFg: "#f9fafb"
273
+ },
274
+ /** High-visibility: strong yellow/black, thick border. WCAG-friendly. */
275
+ contrast: {
276
+ overlayBg: "rgba(250, 204, 21, 0.25)",
277
+ overlayBorder: "#000000",
278
+ overlayBorderWidth: "3px",
279
+ overlayRadius: "0px",
280
+ labelBg: "#000000",
281
+ labelFg: "#facc15"
282
+ },
283
+ /** Technical drawing look: blue dashed outline on a light wash. */
284
+ blueprint: {
285
+ overlayBg: "rgba(37, 99, 235, 0.08)",
286
+ overlayBorder: "#2563eb",
287
+ overlayBorderWidth: "1.5px",
288
+ overlayRadius: "0px",
289
+ overlayBorderStyle: "dashed",
290
+ labelBg: "#1d4ed8",
291
+ labelFg: "#ffffff"
292
+ }
293
+ };
294
+ function resolveThemeVars(preset, theme) {
295
+ const merged = {
296
+ ...preset ? presets[preset] : void 0,
297
+ ...theme
298
+ };
299
+ const vars = {};
300
+ if (merged.accent) {
301
+ vars["--ca-overlay-border"] = merged.accent;
302
+ vars["--ca-overlay-bg"] = `color-mix(in srgb, ${merged.accent} 15%, transparent)`;
303
+ vars["--ca-label-bg"] = merged.accent;
304
+ }
305
+ for (const [token, cssVar] of Object.entries(TOKEN_TO_VAR)) {
306
+ const value = merged[token];
307
+ if (value !== void 0) {
308
+ vars[cssVar] = typeof value === "number" ? token === "zIndex" ? String(value) : token === "transitionMs" ? `${value}ms` : `${value}px` : String(value);
309
+ }
310
+ }
311
+ if (merged.overlayBorderStyle) {
312
+ vars["--ca-overlay-border-style"] = merged.overlayBorderStyle;
313
+ }
314
+ return vars;
315
+ }
316
+
317
+ // src/controller.ts
318
+ function idToName(id) {
319
+ return id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
320
+ }
321
+ function createController(options) {
322
+ const { root, panel } = options;
323
+ const registry = new AnatomyRegistry(root);
324
+ const overlay = new AnatomyOverlay(
325
+ resolveThemeVars(options.preset, options.theme),
326
+ options.overlay ?? {}
327
+ );
328
+ let parts = options.parts ?? registry.partIds().map((id) => ({ id, name: idToName(id) }));
329
+ function findPart(partId) {
330
+ return parts.find((p) => p.id === partId) ?? { id: partId, name: idToName(partId) };
331
+ }
332
+ const listeners = /* @__PURE__ */ new Map();
333
+ function emit(event, partId) {
334
+ listeners.get(event)?.forEach((fn) => fn(partId));
335
+ }
336
+ const cleanupFns = [];
337
+ function highlight(partId, source = "panel") {
338
+ const elements = registry.query().get(partId) ?? [];
339
+ overlay.show(elements, findPart(partId));
340
+ if (panel) {
341
+ panel.querySelectorAll("[data-anatomy-item]").forEach((el) => {
342
+ if (el.dataset.anatomyItem === partId) {
343
+ el.setAttribute("data-active", "");
344
+ if (source === "preview") {
345
+ el.scrollIntoView({ behavior: "smooth", block: "nearest" });
346
+ }
347
+ } else {
348
+ el.removeAttribute("data-active");
349
+ }
350
+ });
351
+ }
352
+ emit("part:enter", partId);
353
+ }
354
+ function unhighlight() {
355
+ overlay.hide();
356
+ if (panel) {
357
+ panel.querySelectorAll("[data-anatomy-item]").forEach((el) => {
358
+ el.removeAttribute("data-active");
359
+ });
360
+ }
361
+ emit("part:leave", "");
362
+ }
363
+ function attachElementListeners() {
364
+ const map = registry.query();
365
+ map.forEach((elements, partId) => {
366
+ elements.forEach((el) => {
367
+ const enter = () => highlight(partId, "preview");
368
+ const leave = () => unhighlight();
369
+ el.addEventListener("mouseenter", enter);
370
+ el.addEventListener("mouseleave", leave);
371
+ cleanupFns.push(() => {
372
+ el.removeEventListener("mouseenter", enter);
373
+ el.removeEventListener("mouseleave", leave);
374
+ });
375
+ });
376
+ });
377
+ }
378
+ function attachPanelListeners() {
379
+ if (!panel) return;
380
+ panel.querySelectorAll("[data-anatomy-item]").forEach((el) => {
381
+ const partId = el.dataset.anatomyItem ?? "";
382
+ const enter = () => highlight(partId, "panel");
383
+ const leave = () => unhighlight();
384
+ el.addEventListener("mouseenter", enter);
385
+ el.addEventListener("mouseleave", leave);
386
+ el.addEventListener("focus", enter);
387
+ el.addEventListener("blur", leave);
388
+ cleanupFns.push(() => {
389
+ el.removeEventListener("mouseenter", enter);
390
+ el.removeEventListener("mouseleave", leave);
391
+ el.removeEventListener("focus", enter);
392
+ el.removeEventListener("blur", leave);
393
+ });
394
+ });
395
+ }
396
+ const onScroll = () => overlay.reposition();
397
+ const onResize = () => overlay.reposition();
398
+ const resizeObserver = new ResizeObserver(() => overlay.reposition());
399
+ function setup() {
400
+ attachElementListeners();
401
+ attachPanelListeners();
402
+ window.addEventListener("scroll", onScroll, { passive: true, capture: true });
403
+ window.addEventListener("resize", onResize, { passive: true });
404
+ resizeObserver.observe(root);
405
+ const stopObserving = registry.observe(() => {
406
+ teardownListeners();
407
+ if (!options.parts) {
408
+ parts = registry.partIds().map((id) => ({ id, name: idToName(id) }));
409
+ }
410
+ attachElementListeners();
411
+ attachPanelListeners();
412
+ });
413
+ cleanupFns.push(stopObserving);
414
+ }
415
+ function teardownListeners() {
416
+ const observerCleanup = cleanupFns.pop();
417
+ cleanupFns.forEach((fn) => fn());
418
+ cleanupFns.length = 0;
419
+ if (observerCleanup) cleanupFns.push(observerCleanup);
420
+ }
421
+ setup();
422
+ return {
423
+ highlight,
424
+ unhighlight,
425
+ refresh() {
426
+ unhighlight();
427
+ teardownListeners();
428
+ if (!options.parts) {
429
+ parts = registry.partIds().map((id) => ({ id, name: idToName(id) }));
430
+ }
431
+ attachElementListeners();
432
+ attachPanelListeners();
433
+ },
434
+ destroy() {
435
+ unhighlight();
436
+ cleanupFns.forEach((fn) => fn());
437
+ cleanupFns.length = 0;
438
+ window.removeEventListener("scroll", onScroll, { capture: true });
439
+ window.removeEventListener("resize", onResize);
440
+ resizeObserver.disconnect();
441
+ overlay.destroy();
442
+ registry.destroy();
443
+ listeners.clear();
444
+ },
445
+ on(event, handler) {
446
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
447
+ listeners.get(event).add(handler);
448
+ return () => listeners.get(event)?.delete(handler);
449
+ },
450
+ getParts() {
451
+ return parts.slice();
452
+ },
453
+ setTheme(theme, preset) {
454
+ overlay.setVars(resolveThemeVars(preset, theme));
455
+ }
456
+ };
457
+ }
458
+ export {
459
+ createController as createAnatomy,
460
+ presets,
461
+ resolveThemeVars
462
+ };
463
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/registry.ts", "../src/overlay.ts", "../src/theme.ts", "../src/controller.ts"],
4
+ "sourcesContent": ["/**\n * AnatomyRegistry \u2014 discovers and tracks [data-part] elements within a root.\n * Intentionally stateless between calls: query() always reads the live DOM.\n */\nexport class AnatomyRegistry {\n private root: HTMLElement;\n private observer: MutationObserver | null = null;\n\n constructor(root: HTMLElement) {\n this.root = root;\n }\n\n /**\n * Returns a Map of partId \u2192 matching HTMLElements.\n * Always queries the live DOM \u2014 never returns a stale cache.\n */\n query(): Map<string, HTMLElement[]> {\n const map = new Map<string, HTMLElement[]>();\n const nodes = this.root.querySelectorAll<HTMLElement>('[data-part]');\n\n nodes.forEach((el) => {\n const id = el.dataset.part;\n if (!id) return;\n const existing = map.get(id) ?? [];\n existing.push(el);\n map.set(id, existing);\n });\n\n return map;\n }\n\n /**\n * Returns all unique part IDs present in the DOM, in document order.\n */\n partIds(): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n this.root.querySelectorAll<HTMLElement>('[data-part]').forEach((el) => {\n const id = el.dataset.part;\n if (id && !seen.has(id)) {\n seen.add(id);\n result.push(id);\n }\n });\n return result;\n }\n\n /**\n * Watches the root for DOM mutations and calls the callback when [data-part]\n * elements are added or removed. Returns a cleanup function.\n */\n observe(callback: () => void): () => void {\n this.observer = new MutationObserver((mutations) => {\n const relevant = mutations.some((m) =>\n Array.from(m.addedNodes).concat(Array.from(m.removedNodes)).some(\n (n) =>\n n instanceof HTMLElement &&\n (n.hasAttribute('data-part') ||\n n.querySelector('[data-part]') !== null)\n )\n );\n if (relevant) callback();\n });\n\n this.observer.observe(this.root, { childList: true, subtree: true });\n\n return () => {\n this.observer?.disconnect();\n this.observer = null;\n };\n }\n\n destroy() {\n this.observer?.disconnect();\n this.observer = null;\n }\n}\n", "import type {\n AnatomyPartDefinition,\n OverlayOptions,\n OverlayRenderContext,\n} from './types.js';\n\n/**\n * AnatomyOverlay \u2014 renders position:fixed highlight boxes + name label chips\n * over target elements. Injected into document.body to escape ancestor\n * stacking contexts and CSS transforms.\n *\n * Theming: the injected stylesheet reads `--ca-*` custom properties with\n * built-in fallbacks (the \"default\" look). Per-instance themes are applied\n * as inline custom properties on each overlay/label element, so multiple\n * controllers with different themes can coexist on one page while global\n * `--ca-*` variables keep working as a page-wide default.\n */\n\nconst OVERLAY_CLASS = 'ca-overlay';\nconst LABEL_CLASS = 'ca-overlay-label';\nconst STYLE_ID = 'ca-overlay-styles';\n\nfunction injectStyles() {\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = `\n .${OVERLAY_CLASS} {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: var(--ca-overlay-radius, 4px);\n background: var(--ca-overlay-bg, rgba(99, 102, 241, 0.18));\n outline: var(--ca-overlay-border-width, 2px)\n var(--ca-overlay-border-style, solid)\n var(--ca-overlay-border, rgba(99, 102, 241, 0.75));\n outline-offset: 1px;\n z-index: var(--ca-overlay-z, 9998);\n transition: opacity var(--ca-transition, 150ms) ease;\n }\n .${OVERLAY_CLASS}.ca-overlay--hidden {\n opacity: 0;\n }\n\n /* Name label chip \u2014 floats above the overlay */\n .${LABEL_CLASS} {\n position: fixed;\n pointer-events: none;\n z-index: var(--ca-overlay-z, 9999);\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 7px 2px 5px;\n border-radius: 4px;\n background: var(--ca-label-bg, rgba(79, 70, 229, 1));\n color: var(--ca-label-fg, #fff);\n font-family: var(--ca-label-font, ui-monospace, 'Cascadia Code', 'Fira Mono', monospace);\n font-size: var(--ca-label-font-size, 11px);\n font-weight: 500;\n letter-spacing: 0.02em;\n white-space: nowrap;\n line-height: 1.6;\n box-shadow: 0 1px 4px rgba(0,0,0,0.25);\n transition: opacity var(--ca-transition, 150ms) ease;\n }\n .${LABEL_CLASS}.ca-overlay--hidden {\n opacity: 0;\n }\n .${LABEL_CLASS}::before {\n content: '';\n display: inline-block;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--ca-label-fg, #fff) 60%, transparent);\n flex-shrink: 0;\n }\n `;\n document.head.appendChild(style);\n}\n\nfunction applyVars(el: HTMLElement, vars: Record<string, string>) {\n for (const [prop, value] of Object.entries(vars)) {\n el.style.setProperty(prop, value);\n }\n}\n\nexport class AnatomyOverlay {\n private overlays: HTMLDivElement[] = [];\n private labels: HTMLDivElement[] = [];\n private activeElements: HTMLElement[] = [];\n private activePart: AnatomyPartDefinition | null = null;\n private rafId: number | null = null;\n\n private vars: Record<string, string>;\n private options: OverlayOptions;\n\n constructor(vars: Record<string, string> = {}, options: OverlayOptions = {}) {\n this.vars = vars;\n this.options = options;\n injectStyles();\n }\n\n /**\n * Replace the per-instance theme variables. Re-applies to any overlays\n * currently on screen.\n */\n setVars(vars: Record<string, string>): void {\n this.vars = vars;\n if (this.overlays.length > 0 || this.labels.length > 0) {\n [...this.overlays, ...this.labels].forEach((el) => {\n el.removeAttribute('style'); // clear stale vars (positioning restored below)\n applyVars(el, vars);\n });\n this._position();\n }\n }\n\n /**\n * Show highlight overlays + label chip over each of the given elements.\n */\n show(elements: HTMLElement[], part: AnatomyPartDefinition): void {\n this.hide();\n this.activeElements = elements;\n this.activePart = part;\n\n const showLabel = this.options.label !== false;\n\n elements.forEach((element, index) => {\n const ctx: OverlayRenderContext = { part, element, index };\n\n const div = document.createElement('div');\n div.className = `${OVERLAY_CLASS} ca-overlay--hidden`;\n if (this.options.className) div.className += ` ${this.options.className}`;\n div.setAttribute('aria-hidden', 'true');\n applyVars(div, this.vars);\n document.body.appendChild(div);\n this.overlays.push(div);\n\n if (this.options.decorateOverlay) {\n this.options.decorateOverlay(div, ctx);\n }\n\n if (showLabel) {\n const label = document.createElement('div');\n label.className = `${LABEL_CLASS} ca-overlay--hidden`;\n label.setAttribute('aria-hidden', 'true');\n applyVars(label, this.vars);\n\n const custom = this.options.renderLabel?.(ctx);\n if (typeof custom === 'string') label.textContent = custom;\n else if (custom instanceof Node) label.appendChild(custom);\n else label.textContent = part.name;\n\n document.body.appendChild(label);\n this.labels.push(label);\n }\n });\n\n // Position after paint so getBoundingClientRect is accurate\n requestAnimationFrame(() => {\n this._position();\n this.overlays.forEach((o) => o.classList.remove('ca-overlay--hidden'));\n this.labels.forEach((l) => l.classList.remove('ca-overlay--hidden'));\n });\n }\n\n /**\n * Remove all overlays and labels from the DOM.\n */\n hide(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.overlays.forEach((o) => o.remove());\n this.labels.forEach((l) => l.remove());\n this.overlays = [];\n this.labels = [];\n this.activeElements = [];\n this.activePart = null;\n }\n\n /**\n * Recompute overlay positions. Call on scroll or resize.\n */\n reposition(): void {\n if (this.overlays.length === 0) return;\n if (this.rafId !== null) cancelAnimationFrame(this.rafId);\n this.rafId = requestAnimationFrame(() => {\n this._position();\n this.rafId = null;\n });\n }\n\n destroy(): void {\n this.hide();\n }\n\n private _position(): void {\n const LABEL_OFFSET = 6; // px gap between label and overlay top edge\n const LABEL_HEIGHT = 22; // approximate label height\n const pad = this.options.padding ?? 0;\n\n this.activeElements.forEach((el, i) => {\n const overlay = this.overlays[i];\n const label = this.labels[i];\n if (!overlay) return;\n\n const rect = el.getBoundingClientRect();\n const top = rect.top - pad;\n const left = rect.left - pad;\n const width = rect.width + pad * 2;\n const height = rect.height + pad * 2;\n\n // Position the highlight box\n overlay.style.top = `${top}px`;\n overlay.style.left = `${left}px`;\n overlay.style.width = `${width}px`;\n overlay.style.height = `${height}px`;\n\n if (!label) return;\n\n // Place label chip above the overlay; flip below if too close to top\n const labelTop = top - LABEL_HEIGHT - LABEL_OFFSET;\n const finalTop = labelTop < 4 ? top + height + LABEL_OFFSET : labelTop;\n label.style.top = `${finalTop}px`;\n label.style.left = `${left}px`;\n });\n }\n}\n", "import type { AnatomyTheme, AnatomyPresetName } from './types.js';\n\n/**\n * Theming \u2014 resolves { preset, theme } into a set of CSS custom properties\n * applied per controller instance.\n *\n * Resolution order (lowest \u2192 highest priority):\n * 1. Stylesheet defaults (the `var(--ca-*, fallback)` chain \u2014 the \"default\" look)\n * 2. Global CSS variables set by the user on :root or an ancestor\n * 3. A named preset passed to createAnatomy()\n * 4. Individual `theme` tokens passed to createAnatomy()\n *\n * The `accent` token is a shorthand: when provided, overlay border, overlay\n * background and label background are derived from it (via color-mix) unless\n * they are explicitly set too.\n */\n\n/** Maps theme token names \u2192 CSS custom property names used by the stylesheet. */\nexport const TOKEN_TO_VAR: Record<\n keyof Omit<AnatomyTheme, 'accent' | 'overlayBorderStyle'>,\n string\n> = {\n overlayBg: '--ca-overlay-bg',\n overlayBorder: '--ca-overlay-border',\n overlayBorderWidth: '--ca-overlay-border-width',\n overlayRadius: '--ca-overlay-radius',\n labelBg: '--ca-label-bg',\n labelFg: '--ca-label-fg',\n labelFont: '--ca-label-font',\n labelFontSize: '--ca-label-font-size',\n zIndex: '--ca-overlay-z',\n transitionMs: '--ca-transition',\n};\n\n/**\n * Built-in presets. Each is a plain AnatomyTheme \u2014 you can also import these,\n * spread them, and tweak individual tokens.\n */\nexport const presets: Record<AnatomyPresetName, AnatomyTheme> = {\n /** The built-in indigo look. Empty on purpose: it lives in the stylesheet defaults. */\n default: {},\n\n /** Quiet: no fill, thin neutral border, subdued label. */\n minimal: {\n overlayBg: 'transparent',\n overlayBorder: 'rgba(107, 114, 128, 0.9)',\n overlayBorderWidth: '1px',\n overlayRadius: '2px',\n labelBg: '#374151',\n labelFg: '#f9fafb',\n },\n\n /** High-visibility: strong yellow/black, thick border. WCAG-friendly. */\n contrast: {\n overlayBg: 'rgba(250, 204, 21, 0.25)',\n overlayBorder: '#000000',\n overlayBorderWidth: '3px',\n overlayRadius: '0px',\n labelBg: '#000000',\n labelFg: '#facc15',\n },\n\n /** Technical drawing look: blue dashed outline on a light wash. */\n blueprint: {\n overlayBg: 'rgba(37, 99, 235, 0.08)',\n overlayBorder: '#2563eb',\n overlayBorderWidth: '1.5px',\n overlayRadius: '0px',\n overlayBorderStyle: 'dashed',\n labelBg: '#1d4ed8',\n labelFg: '#ffffff',\n },\n};\n\n/**\n * Resolve a { preset, theme } pair into a flat CSS-variable map.\n * Only *customized* tokens are returned \u2014 an empty result means\n * \"use the stylesheet defaults\", which keeps global `--ca-*` variables\n * set by users fully functional (backward compatible).\n */\nexport function resolveThemeVars(\n preset?: AnatomyPresetName,\n theme?: AnatomyTheme\n): Record<string, string> {\n const merged: AnatomyTheme = {\n ...(preset ? presets[preset] : undefined),\n ...theme,\n };\n\n const vars: Record<string, string> = {};\n\n // Accent shorthand \u2014 derive dependent tokens unless explicitly overridden.\n if (merged.accent) {\n vars['--ca-overlay-border'] = merged.accent;\n vars['--ca-overlay-bg'] = `color-mix(in srgb, ${merged.accent} 15%, transparent)`;\n vars['--ca-label-bg'] = merged.accent;\n }\n\n for (const [token, cssVar] of Object.entries(TOKEN_TO_VAR)) {\n const value = merged[token as keyof AnatomyTheme];\n if (value !== undefined) {\n vars[cssVar] =\n typeof value === 'number'\n ? token === 'zIndex'\n ? String(value)\n : token === 'transitionMs'\n ? `${value}ms`\n : `${value}px`\n : String(value);\n }\n }\n\n if (merged.overlayBorderStyle) {\n vars['--ca-overlay-border-style'] = merged.overlayBorderStyle;\n }\n\n return vars;\n}\n", "import { AnatomyRegistry } from './registry.js';\nimport { AnatomyOverlay } from './overlay.js';\nimport { resolveThemeVars } from './theme.js';\nimport type {\n AnatomyOptions,\n AnatomyController,\n AnatomyEvent,\n AnatomyEventHandler,\n AnatomyPartDefinition,\n} from './types.js';\n\n/** Capitalizes an ID like \"slider-thumb\" \u2192 \"Slider Thumb\" */\nfunction idToName(id: string): string {\n return id\n .replace(/[-_]/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nexport function createController(options: AnatomyOptions): AnatomyController {\n const { root, panel } = options;\n\n const registry = new AnatomyRegistry(root);\n const overlay = new AnatomyOverlay(\n resolveThemeVars(options.preset, options.theme),\n options.overlay ?? {}\n );\n\n // Resolve part definitions: explicit or auto-discovered\n let parts: AnatomyPartDefinition[] = options.parts ??\n registry.partIds().map((id) => ({ id, name: idToName(id) }));\n\n function findPart(partId: string): AnatomyPartDefinition {\n return parts.find((p) => p.id === partId) ?? { id: partId, name: idToName(partId) };\n }\n\n // Event emitter\n const listeners = new Map<AnatomyEvent, Set<AnatomyEventHandler>>();\n\n function emit(event: AnatomyEvent, partId: string) {\n listeners.get(event)?.forEach((fn) => fn(partId));\n }\n\n // Cleanup registry\n const cleanupFns: Array<() => void> = [];\n\n function highlight(partId: string, source: 'preview' | 'panel' = 'panel') {\n const elements = registry.query().get(partId) ?? [];\n overlay.show(elements, findPart(partId));\n\n // Mark matching panel items active + scroll into view when triggered from preview\n if (panel) {\n panel.querySelectorAll<HTMLElement>('[data-anatomy-item]').forEach((el) => {\n if (el.dataset.anatomyItem === partId) {\n el.setAttribute('data-active', '');\n // Only auto-scroll when hover comes from the component, not the panel itself\n if (source === 'preview') {\n el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n }\n } else {\n el.removeAttribute('data-active');\n }\n });\n }\n\n emit('part:enter', partId);\n }\n\n function unhighlight() {\n overlay.hide();\n\n if (panel) {\n panel.querySelectorAll<HTMLElement>('[data-anatomy-item]').forEach((el) => {\n el.removeAttribute('data-active');\n });\n }\n\n // emit part:leave with empty string as signal (last active unknown at this point)\n emit('part:leave', '');\n }\n\n function attachElementListeners() {\n const map = registry.query();\n\n map.forEach((elements, partId) => {\n elements.forEach((el) => {\n // Mouse only \u2014 no focus/blur here.\n // Preview elements are a live component; adding tabstops breaks\n // their natural tab order and confuses assistive technology.\n // Keyboard navigation is handled via the panel entries only.\n const enter = () => highlight(partId, 'preview');\n const leave = () => unhighlight();\n el.addEventListener('mouseenter', enter);\n el.addEventListener('mouseleave', leave);\n cleanupFns.push(() => {\n el.removeEventListener('mouseenter', enter);\n el.removeEventListener('mouseleave', leave);\n });\n });\n });\n }\n\n function attachPanelListeners() {\n if (!panel) return;\n\n panel.querySelectorAll<HTMLElement>('[data-anatomy-item]').forEach((el) => {\n const partId = el.dataset.anatomyItem ?? '';\n const enter = () => highlight(partId, 'panel');\n const leave = () => unhighlight();\n el.addEventListener('mouseenter', enter);\n el.addEventListener('mouseleave', leave);\n el.addEventListener('focus', enter);\n el.addEventListener('blur', leave);\n cleanupFns.push(() => {\n el.removeEventListener('mouseenter', enter);\n el.removeEventListener('mouseleave', leave);\n el.removeEventListener('focus', enter);\n el.removeEventListener('blur', leave);\n });\n });\n }\n\n // Scroll + resize \u2192 reposition active overlays\n const onScroll = () => overlay.reposition();\n const onResize = () => overlay.reposition();\n\n const resizeObserver = new ResizeObserver(() => overlay.reposition());\n\n function setup() {\n attachElementListeners();\n attachPanelListeners();\n\n window.addEventListener('scroll', onScroll, { passive: true, capture: true });\n window.addEventListener('resize', onResize, { passive: true });\n resizeObserver.observe(root);\n\n // Watch for dynamic DOM changes\n const stopObserving = registry.observe(() => {\n teardownListeners();\n // Re-resolve auto-discovered parts if none were explicitly provided\n if (!options.parts) {\n parts = registry.partIds().map((id) => ({ id, name: idToName(id) }));\n }\n attachElementListeners();\n attachPanelListeners();\n });\n cleanupFns.push(stopObserving);\n }\n\n function teardownListeners() {\n // Run all registered cleanup functions except the MutationObserver (last one)\n // We rebuild element/panel listeners on refresh but keep the observer running\n const observerCleanup = cleanupFns.pop(); // MutationObserver cleanup is last\n cleanupFns.forEach((fn) => fn());\n cleanupFns.length = 0;\n if (observerCleanup) cleanupFns.push(observerCleanup); // put it back\n }\n\n setup();\n\n return {\n highlight,\n unhighlight,\n\n refresh() {\n unhighlight();\n teardownListeners();\n if (!options.parts) {\n parts = registry.partIds().map((id) => ({ id, name: idToName(id) }));\n }\n attachElementListeners();\n attachPanelListeners();\n },\n\n destroy() {\n unhighlight();\n cleanupFns.forEach((fn) => fn());\n cleanupFns.length = 0;\n window.removeEventListener('scroll', onScroll, { capture: true });\n window.removeEventListener('resize', onResize);\n resizeObserver.disconnect();\n overlay.destroy();\n registry.destroy();\n listeners.clear();\n },\n\n on(event, handler) {\n if (!listeners.has(event)) listeners.set(event, new Set());\n listeners.get(event)!.add(handler);\n return () => listeners.get(event)?.delete(handler);\n },\n\n getParts() {\n return parts.slice();\n },\n\n setTheme(theme, preset) {\n overlay.setVars(resolveThemeVars(preset, theme));\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ import type { AnatomyPartDefinition, OverlayOptions } from './types.js';
2
+ export declare class AnatomyOverlay {
3
+ private overlays;
4
+ private labels;
5
+ private activeElements;
6
+ private activePart;
7
+ private rafId;
8
+ private vars;
9
+ private options;
10
+ constructor(vars?: Record<string, string>, options?: OverlayOptions);
11
+ /**
12
+ * Replace the per-instance theme variables. Re-applies to any overlays
13
+ * currently on screen.
14
+ */
15
+ setVars(vars: Record<string, string>): void;
16
+ /**
17
+ * Show highlight overlays + label chip over each of the given elements.
18
+ */
19
+ show(elements: HTMLElement[], part: AnatomyPartDefinition): void;
20
+ /**
21
+ * Remove all overlays and labels from the DOM.
22
+ */
23
+ hide(): void;
24
+ /**
25
+ * Recompute overlay positions. Call on scroll or resize.
26
+ */
27
+ reposition(): void;
28
+ destroy(): void;
29
+ private _position;
30
+ }
31
+ //# sourceMappingURL=overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../src/overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EAEf,MAAM,YAAY,CAAC;AAmFpB,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,KAAK,CAAuB;IAEpC,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,OAAO,CAAiB;gBAEpB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAAE,OAAO,GAAE,cAAmB;IAM3E;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAW3C;;OAEG;IACH,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,qBAAqB,GAAG,IAAI;IA8ChE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;OAEG;IACH,UAAU,IAAI,IAAI;IASlB,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,SAAS;CA+BlB"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * AnatomyRegistry — discovers and tracks [data-part] elements within a root.
3
+ * Intentionally stateless between calls: query() always reads the live DOM.
4
+ */
5
+ export declare class AnatomyRegistry {
6
+ private root;
7
+ private observer;
8
+ constructor(root: HTMLElement);
9
+ /**
10
+ * Returns a Map of partId → matching HTMLElements.
11
+ * Always queries the live DOM — never returns a stale cache.
12
+ */
13
+ query(): Map<string, HTMLElement[]>;
14
+ /**
15
+ * Returns all unique part IDs present in the DOM, in document order.
16
+ */
17
+ partIds(): string[];
18
+ /**
19
+ * Watches the root for DOM mutations and calls the callback when [data-part]
20
+ * elements are added or removed. Returns a cleanup function.
21
+ */
22
+ observe(callback: () => void): () => void;
23
+ destroy(): void;
24
+ }
25
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,QAAQ,CAAiC;gBAErC,IAAI,EAAE,WAAW;IAI7B;;;OAGG;IACH,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;IAenC;;OAEG;IACH,OAAO,IAAI,MAAM,EAAE;IAanB;;;OAGG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAqBzC,OAAO;CAIR"}
@@ -0,0 +1,30 @@
1
+ import type { AnatomyTheme, AnatomyPresetName } from './types.js';
2
+ /**
3
+ * Theming — resolves { preset, theme } into a set of CSS custom properties
4
+ * applied per controller instance.
5
+ *
6
+ * Resolution order (lowest → highest priority):
7
+ * 1. Stylesheet defaults (the `var(--ca-*, fallback)` chain — the "default" look)
8
+ * 2. Global CSS variables set by the user on :root or an ancestor
9
+ * 3. A named preset passed to createAnatomy()
10
+ * 4. Individual `theme` tokens passed to createAnatomy()
11
+ *
12
+ * The `accent` token is a shorthand: when provided, overlay border, overlay
13
+ * background and label background are derived from it (via color-mix) unless
14
+ * they are explicitly set too.
15
+ */
16
+ /** Maps theme token names → CSS custom property names used by the stylesheet. */
17
+ export declare const TOKEN_TO_VAR: Record<keyof Omit<AnatomyTheme, 'accent' | 'overlayBorderStyle'>, string>;
18
+ /**
19
+ * Built-in presets. Each is a plain AnatomyTheme — you can also import these,
20
+ * spread them, and tweak individual tokens.
21
+ */
22
+ export declare const presets: Record<AnatomyPresetName, AnatomyTheme>;
23
+ /**
24
+ * Resolve a { preset, theme } pair into a flat CSS-variable map.
25
+ * Only *customized* tokens are returned — an empty result means
26
+ * "use the stylesheet defaults", which keeps global `--ca-*` variables
27
+ * set by users fully functional (backward compatible).
28
+ */
29
+ export declare function resolveThemeVars(preset?: AnatomyPresetName, theme?: AnatomyTheme): Record<string, string>;
30
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAElE;;;;;;;;;;;;;GAaG;AAEH,iFAAiF;AACjF,eAAO,MAAM,YAAY,EAAE,MAAM,CAC/B,MAAM,IAAI,CAAC,YAAY,EAAE,QAAQ,GAAG,oBAAoB,CAAC,EACzD,MAAM,CAYP,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,iBAAiB,EAAE,YAAY,CAkC3D,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,CAAC,EAAE,iBAAiB,EAC1B,KAAK,CAAC,EAAE,YAAY,GACnB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkCxB"}