doom-design-system 0.5.0 → 0.6.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.
Files changed (211) hide show
  1. package/dist/components/Accordion/Accordion.module.css +121 -124
  2. package/dist/components/ActionRow/ActionRow.module.css +25 -24
  3. package/dist/components/Alert/Alert.module.css +74 -76
  4. package/dist/components/Avatar/Avatar.module.css +66 -66
  5. package/dist/components/Badge/Badge.module.css +50 -48
  6. package/dist/components/Breadcrumbs/Breadcrumbs.module.css +32 -33
  7. package/dist/components/Button/Button.d.ts +2 -2
  8. package/dist/components/Button/Button.js +1 -1
  9. package/dist/components/Button/Button.module.css +150 -152
  10. package/dist/components/Card/Card.module.css +37 -39
  11. package/dist/components/Chart/Chart.module.css +213 -245
  12. package/dist/components/Chart/behaviors/DraggablePuck.d.ts +36 -0
  13. package/dist/components/Chart/behaviors/DraggablePuck.js +94 -0
  14. package/dist/components/Chart/behaviors/Markers.js +6 -4
  15. package/dist/components/Chart/behaviors/SelectionUpdate.js +2 -4
  16. package/dist/components/Chart/behaviors/index.d.ts +1 -1
  17. package/dist/components/Chart/behaviors/index.js +1 -1
  18. package/dist/components/Chart/engine/CoordinateSystem.d.ts +59 -0
  19. package/dist/components/Chart/engine/CoordinateSystem.js +126 -0
  20. package/dist/components/Chart/engine/Engine.d.ts +102 -0
  21. package/dist/components/Chart/engine/Engine.js +226 -0
  22. package/dist/components/Chart/engine/Scheduler.d.ts +59 -0
  23. package/dist/components/Chart/engine/Scheduler.js +139 -0
  24. package/dist/components/Chart/engine/SpatialMap.d.ts +114 -0
  25. package/dist/components/Chart/engine/SpatialMap.js +270 -0
  26. package/dist/components/Chart/engine/index.d.ts +13 -0
  27. package/dist/components/Chart/engine/index.js +9 -0
  28. package/dist/components/Chart/engine/types.d.ts +137 -0
  29. package/dist/components/Chart/engine/types.js +47 -0
  30. package/dist/components/Chart/hooks/useEngine.d.ts +43 -0
  31. package/dist/components/Chart/hooks/useEngine.js +128 -0
  32. package/dist/components/Chart/sensors/DataHoverSensor/DataHoverSensor.d.ts +17 -19
  33. package/dist/components/Chart/sensors/DataHoverSensor/DataHoverSensor.js +38 -51
  34. package/dist/components/Chart/sensors/DragSensor/DragSensor.d.ts +38 -0
  35. package/dist/components/Chart/sensors/DragSensor/DragSensor.js +105 -0
  36. package/dist/components/Chart/sensors/DragSensor/index.d.ts +2 -0
  37. package/dist/components/Chart/sensors/DragSensor/index.js +1 -0
  38. package/dist/components/Chart/sensors/{KeyboardSensor.d.ts → KeyboardSensor/KeyboardSensor.d.ts} +1 -1
  39. package/dist/components/Chart/sensors/KeyboardSensor/KeyboardSensor.js +86 -0
  40. package/dist/components/Chart/sensors/KeyboardSensor/index.d.ts +1 -0
  41. package/dist/components/Chart/sensors/KeyboardSensor/index.js +1 -0
  42. package/dist/components/Chart/sensors/{SelectionSensor.d.ts → SelectionSensor/SelectionSensor.d.ts} +2 -2
  43. package/dist/components/Chart/sensors/SelectionSensor/SelectionSensor.js +39 -0
  44. package/dist/components/Chart/sensors/SelectionSensor/index.d.ts +1 -0
  45. package/dist/components/Chart/sensors/SelectionSensor/index.js +1 -0
  46. package/dist/components/Chart/sensors/SensorManager/SensorManager.js +36 -41
  47. package/dist/components/Chart/sensors/index.d.ts +1 -0
  48. package/dist/components/Chart/sensors/index.js +3 -2
  49. package/dist/components/Chart/sensors/utils/search.d.ts +1 -1
  50. package/dist/components/Chart/sensors/utils/search.js +25 -4
  51. package/dist/components/Chart/state/store/chart.store.js +18 -0
  52. package/dist/components/Chart/state/store/slices/series.slice.d.ts +1 -0
  53. package/dist/components/Chart/state/store/slices/series.slice.js +3 -2
  54. package/dist/components/Chart/subcomponents/Axis/Axis.module.css +32 -33
  55. package/dist/components/Chart/subcomponents/BarSeries/BarSeries.js +6 -1
  56. package/dist/components/Chart/subcomponents/BarSeries/BarSeries.module.css +11 -9
  57. package/dist/components/Chart/subcomponents/Cursor/Cursor.js +8 -1
  58. package/dist/components/Chart/subcomponents/Cursor/Cursor.module.css +14 -13
  59. package/dist/components/Chart/subcomponents/CustomSeries/CustomSeries.js +4 -0
  60. package/dist/components/Chart/subcomponents/CustomSeries/CustomSeries.module.css +5 -3
  61. package/dist/components/Chart/subcomponents/Footer/Footer.module.css +5 -3
  62. package/dist/components/Chart/subcomponents/Grid/Grid.module.css +12 -11
  63. package/dist/components/Chart/subcomponents/Header/Header.module.css +8 -7
  64. package/dist/components/Chart/subcomponents/InteractionLayer/InteractionLayer.d.ts +4 -4
  65. package/dist/components/Chart/subcomponents/InteractionLayer/InteractionLayer.js +39 -76
  66. package/dist/components/Chart/subcomponents/Legend/Legend.module.css +30 -32
  67. package/dist/components/Chart/subcomponents/LineSeries/LineSeries.js +9 -3
  68. package/dist/components/Chart/subcomponents/LineSeries/LineSeries.module.css +21 -21
  69. package/dist/components/Chart/subcomponents/Root/Root.js +113 -7
  70. package/dist/components/Chart/subcomponents/Root/Root.module.css +70 -82
  71. package/dist/components/Chart/subcomponents/ScatterSeries/ScatterSeries.js +6 -1
  72. package/dist/components/Chart/subcomponents/ScatterSeries/ScatterSeries.module.css +7 -5
  73. package/dist/components/Chart/subcomponents/Series/Series.module.css +118 -128
  74. package/dist/components/Chart/subcomponents/SeriesPoint/SeriesPoint.module.css +10 -8
  75. package/dist/components/Chart/subcomponents/Tooltip/Tooltip.js +2 -3
  76. package/dist/components/Chart/subcomponents/Tooltip/Tooltip.module.css +52 -67
  77. package/dist/components/Chart/types/context.d.ts +9 -0
  78. package/dist/components/Chart/types/events.d.ts +5 -7
  79. package/dist/components/Chart/types/interaction.d.ts +24 -2
  80. package/dist/components/Chart/types/interaction.js +1 -0
  81. package/dist/components/Checkbox/Checkbox.module.css +57 -59
  82. package/dist/components/Chip/Chip.module.css +105 -115
  83. package/dist/components/Combobox/Combobox.d.ts +2 -1
  84. package/dist/components/Combobox/Combobox.js +2 -2
  85. package/dist/components/Combobox/Combobox.module.css +233 -210
  86. package/dist/components/CopyButton/CopyButton.module.css +84 -90
  87. package/dist/components/Drawer/Drawer.module.css +126 -145
  88. package/dist/components/Dropdown/Dropdown.d.ts +3 -1
  89. package/dist/components/Dropdown/Dropdown.js +3 -3
  90. package/dist/components/Dropdown/Dropdown.module.css +52 -32
  91. package/dist/components/FileUpload/FileUpload.js +24 -0
  92. package/dist/components/FileUpload/FileUpload.module.css +295 -313
  93. package/dist/components/Form/Form.module.css +35 -39
  94. package/dist/components/Image/Image.module.css +53 -54
  95. package/dist/components/Input/Input.d.ts +4 -2
  96. package/dist/components/Input/Input.js +2 -2
  97. package/dist/components/Input/Input.module.css +135 -119
  98. package/dist/components/Label/Label.module.css +17 -15
  99. package/dist/components/Layout/Layout.module.css +95 -111
  100. package/dist/components/Link/Link.module.css +67 -65
  101. package/dist/components/Modal/Modal.module.css +112 -132
  102. package/dist/components/Page/Page.module.css +21 -21
  103. package/dist/components/Pagination/Pagination.module.css +56 -56
  104. package/dist/components/Popover/Popover.module.css +17 -16
  105. package/dist/components/ProgressBar/ProgressBar.module.css +36 -37
  106. package/dist/components/RadioGroup/RadioGroup.module.css +74 -77
  107. package/dist/components/Select/Select.d.ts +2 -1
  108. package/dist/components/Select/Select.js +2 -2
  109. package/dist/components/Select/Select.module.css +133 -98
  110. package/dist/components/Sheet/Sheet.module.css +134 -154
  111. package/dist/components/Sidebar/Sidebar.module.css +72 -74
  112. package/dist/components/Sidebar/subcomponents/Footer/Footer.module.css +7 -5
  113. package/dist/components/Sidebar/subcomponents/Group/Group.module.css +80 -85
  114. package/dist/components/Sidebar/subcomponents/Header/Header.module.css +12 -10
  115. package/dist/components/Sidebar/subcomponents/Item/Item.module.css +54 -55
  116. package/dist/components/Sidebar/subcomponents/MobileOverlay/MobileOverlay.module.css +38 -38
  117. package/dist/components/Sidebar/subcomponents/MobileTrigger/MobileTrigger.module.css +5 -3
  118. package/dist/components/Sidebar/subcomponents/Nav/Nav.module.css +13 -11
  119. package/dist/components/Sidebar/subcomponents/Rail/Rail.module.css +62 -63
  120. package/dist/components/Sidebar/subcomponents/Section/Section.module.css +86 -91
  121. package/dist/components/Skeleton/Skeleton.module.css +28 -26
  122. package/dist/components/Slat/Slat.module.css +93 -94
  123. package/dist/components/Slider/Slider.module.css +116 -121
  124. package/dist/components/Spinner/Spinner.module.css +28 -27
  125. package/dist/components/SplitButton/SplitButton.d.ts +3 -1
  126. package/dist/components/SplitButton/SplitButton.js +2 -2
  127. package/dist/components/SplitButton/SplitButton.module.css +104 -87
  128. package/dist/components/Switch/Switch.module.css +64 -63
  129. package/dist/components/Table/FilterBuilder/FilterBuilder.module.css +36 -36
  130. package/dist/components/Table/FilterBuilder/FilterConditionRow.js +1 -1
  131. package/dist/components/Table/FilterBuilder/FilterConditionRow.module.css +21 -22
  132. package/dist/components/Table/FilterBuilder/FilterGroup.js +4 -4
  133. package/dist/components/Table/FilterBuilder/FilterGroup.module.css +355 -389
  134. package/dist/components/Table/FilterBuilder/FilterSheet.module.css +68 -71
  135. package/dist/components/Table/Table.d.ts +4 -2
  136. package/dist/components/Table/Table.js +50 -13
  137. package/dist/components/Table/Table.module.css +210 -188
  138. package/dist/components/Table/TableHeaderFilter.js +1 -1
  139. package/dist/components/Table/TableHeaderFilter.module.css +51 -57
  140. package/dist/components/Tabs/Tabs.module.css +79 -80
  141. package/dist/components/Text/Text.module.css +108 -131
  142. package/dist/components/Textarea/Textarea.d.ts +3 -1
  143. package/dist/components/Textarea/Textarea.js +2 -2
  144. package/dist/components/Textarea/Textarea.module.css +114 -94
  145. package/dist/components/Toast/Toast.module.css +82 -82
  146. package/dist/components/Tooltip/Tooltip.module.css +17 -16
  147. package/dist/styles/globals.css +1677 -1691
  148. package/dist/styles/palettes.d.ts +0 -5
  149. package/dist/styles/palettes.js +0 -8
  150. package/dist/styles/themes/definitions.d.ts +0 -8
  151. package/dist/styles/themes/definitions.js +117 -5
  152. package/dist/styles/types.d.ts +2 -0
  153. package/dist/styles/types.js +1 -0
  154. package/dist/tsconfig.build.tsbuildinfo +1 -1
  155. package/package.json +4 -4
  156. package/.agent/skills/doom/a2ui/a2ui-examples.md +0 -289
  157. package/.agent/skills/doom/a2ui/a2ui-principles.md +0 -49
  158. package/.agent/skills/doom/a2ui/a2ui-protocol.md +0 -191
  159. package/.agent/skills/doom/components/a2ui.md +0 -46
  160. package/.agent/skills/doom/components/accordion.md +0 -44
  161. package/.agent/skills/doom/components/actionrow.md +0 -33
  162. package/.agent/skills/doom/components/alert.md +0 -35
  163. package/.agent/skills/doom/components/avatar.md +0 -36
  164. package/.agent/skills/doom/components/badge.md +0 -29
  165. package/.agent/skills/doom/components/breadcrumbs.md +0 -33
  166. package/.agent/skills/doom/components/button.md +0 -43
  167. package/.agent/skills/doom/components/card.md +0 -41
  168. package/.agent/skills/doom/components/chart.md +0 -106
  169. package/.agent/skills/doom/components/checkbox.md +0 -38
  170. package/.agent/skills/doom/components/chip.md +0 -35
  171. package/.agent/skills/doom/components/combobox.md +0 -50
  172. package/.agent/skills/doom/components/copybutton.md +0 -41
  173. package/.agent/skills/doom/components/drawer.md +0 -69
  174. package/.agent/skills/doom/components/dropdown.md +0 -33
  175. package/.agent/skills/doom/components/fileupload.md +0 -49
  176. package/.agent/skills/doom/components/form.md +0 -55
  177. package/.agent/skills/doom/components/icon.md +0 -47
  178. package/.agent/skills/doom/components/image.md +0 -48
  179. package/.agent/skills/doom/components/input.md +0 -54
  180. package/.agent/skills/doom/components/label.md +0 -32
  181. package/.agent/skills/doom/components/layout.md +0 -92
  182. package/.agent/skills/doom/components/link.md +0 -39
  183. package/.agent/skills/doom/components/modal.md +0 -71
  184. package/.agent/skills/doom/components/page.md +0 -41
  185. package/.agent/skills/doom/components/pagination.md +0 -32
  186. package/.agent/skills/doom/components/popover.md +0 -45
  187. package/.agent/skills/doom/components/progressbar.md +0 -37
  188. package/.agent/skills/doom/components/radiogroup.md +0 -45
  189. package/.agent/skills/doom/components/select.md +0 -43
  190. package/.agent/skills/doom/components/sheet.md +0 -71
  191. package/.agent/skills/doom/components/sidebar.md +0 -92
  192. package/.agent/skills/doom/components/skeleton.md +0 -35
  193. package/.agent/skills/doom/components/slat.md +0 -46
  194. package/.agent/skills/doom/components/slider.md +0 -51
  195. package/.agent/skills/doom/components/spinner.md +0 -34
  196. package/.agent/skills/doom/components/splitbutton.md +0 -35
  197. package/.agent/skills/doom/components/switch.md +0 -40
  198. package/.agent/skills/doom/components/table.md +0 -82
  199. package/.agent/skills/doom/components/tabs.md +0 -65
  200. package/.agent/skills/doom/components/text.md +0 -42
  201. package/.agent/skills/doom/components/textarea.md +0 -46
  202. package/.agent/skills/doom/components/toast.md +0 -59
  203. package/.agent/skills/doom/components/tooltip.md +0 -35
  204. package/.agent/skills/doom/index.md +0 -167
  205. package/.agent/skills/doom/styles/aesthetic.md +0 -151
  206. package/.agent/skills/doom/styles/css-variables.md +0 -80
  207. package/.agent/skills/doom/styles/mixins.md +0 -97
  208. package/.agent/skills/doom/styles/tokens.md +0 -129
  209. package/.agent/skills/doom/styles/utilities.md +0 -84
  210. package/dist/components/Chart/sensors/KeyboardSensor.js +0 -82
  211. package/dist/components/Chart/sensors/SelectionSensor.js +0 -41
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Scheduler
3
+ *
4
+ * Manages the execution timing of engine tasks.
5
+ * Critical tasks (pointer down) run synchronously.
6
+ * Visual tasks (hover) are batched to requestAnimationFrame.
7
+ */
8
+ import { TaskPriority } from "./types.js";
9
+ /**
10
+ * The Scheduler controls when events are dispatched to sensors.
11
+ * It uses a priority-based system to ensure critical interactions
12
+ * (like drag start) happen immediately, while visual updates
13
+ * (like hover highlights) are batched for performance.
14
+ */
15
+ export class Scheduler {
16
+ constructor() {
17
+ this.visualQueue = [];
18
+ this.idleQueue = [];
19
+ this.rafId = null;
20
+ this.handler = null;
21
+ }
22
+ /**
23
+ * Register the handler that will process events.
24
+ * In the full system, this is the EventBus.dispatch method.
25
+ */
26
+ setHandler(handler) {
27
+ this.handler = handler;
28
+ }
29
+ /**
30
+ * Schedule a task for execution.
31
+ *
32
+ * @param priority - The priority level of the task
33
+ * @param event - The engine event to dispatch
34
+ */
35
+ schedule(priority, event) {
36
+ const task = {
37
+ priority,
38
+ event,
39
+ timestamp: performance.now(),
40
+ };
41
+ switch (priority) {
42
+ case TaskPriority.CRITICAL:
43
+ // Critical tasks execute immediately (synchronously)
44
+ this.executeCritical(task);
45
+ break;
46
+ case TaskPriority.VISUAL:
47
+ // Visual tasks are batched to the next animation frame
48
+ this.visualQueue.push(task);
49
+ this.scheduleVisualFlush();
50
+ break;
51
+ case TaskPriority.IDLE:
52
+ // Idle tasks are deferred even further
53
+ this.idleQueue.push(task);
54
+ this.scheduleIdleFlush();
55
+ break;
56
+ }
57
+ }
58
+ /**
59
+ * Execute a critical task immediately.
60
+ * This is synchronous to allow preventDefault() on the original event.
61
+ */
62
+ executeCritical(task) {
63
+ if (this.handler) {
64
+ this.handler(task.event);
65
+ }
66
+ }
67
+ /**
68
+ * Schedule a flush of the visual queue on the next animation frame.
69
+ */
70
+ scheduleVisualFlush() {
71
+ if (this.rafId !== null) {
72
+ return;
73
+ } // Already scheduled
74
+ this.rafId = requestAnimationFrame(() => {
75
+ this.flushVisualQueue();
76
+ this.rafId = null;
77
+ });
78
+ }
79
+ /**
80
+ * Process all queued visual tasks.
81
+ * Only the most recent event per input ID is processed (coalescing).
82
+ */
83
+ flushVisualQueue() {
84
+ if (!this.handler || this.visualQueue.length === 0) {
85
+ return;
86
+ }
87
+ // Coalesce: Group by input ID, keep only the latest
88
+ const latestByInputId = new Map();
89
+ for (const task of this.visualQueue) {
90
+ const existing = latestByInputId.get(task.event.signal.id);
91
+ if (!existing || task.timestamp > existing.timestamp) {
92
+ latestByInputId.set(task.event.signal.id, task);
93
+ }
94
+ }
95
+ // Clear the queue
96
+ this.visualQueue = [];
97
+ // Dispatch coalesced events
98
+ for (const task of Array.from(latestByInputId.values())) {
99
+ this.handler(task.event);
100
+ }
101
+ }
102
+ /**
103
+ * Schedule a flush of the idle queue using requestIdleCallback.
104
+ */
105
+ scheduleIdleFlush() {
106
+ if (typeof requestIdleCallback !== "undefined") {
107
+ requestIdleCallback(() => this.flushIdleQueue());
108
+ }
109
+ else {
110
+ // Fallback for browsers without requestIdleCallback
111
+ setTimeout(() => this.flushIdleQueue(), 50);
112
+ }
113
+ }
114
+ /**
115
+ * Process all queued idle tasks.
116
+ */
117
+ flushIdleQueue() {
118
+ if (!this.handler || this.idleQueue.length === 0) {
119
+ return;
120
+ }
121
+ const tasks = this.idleQueue;
122
+ this.idleQueue = [];
123
+ for (const task of tasks) {
124
+ this.handler(task.event);
125
+ }
126
+ }
127
+ /**
128
+ * Cancel all pending tasks and clean up.
129
+ */
130
+ dispose() {
131
+ if (this.rafId !== null) {
132
+ cancelAnimationFrame(this.rafId);
133
+ this.rafId = null;
134
+ }
135
+ this.visualQueue = [];
136
+ this.idleQueue = [];
137
+ this.handler = null;
138
+ }
139
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * SpatialMap
3
+ *
4
+ * The "Hybrid Radar" that finds interaction candidates.
5
+ * Combines DOM-based hit testing with Quadtree spatial queries.
6
+ */
7
+ import { InteractionCandidate } from "./types";
8
+ /**
9
+ * Standard data attributes used to tag interactive DOM elements.
10
+ * These are read by the SpatialMap during DOM-based hit testing.
11
+ */
12
+ export declare const CHART_DATA_ATTRS: {
13
+ readonly TYPE: "data-chart-type";
14
+ readonly SERIES_ID: "data-chart-series";
15
+ readonly INDEX: "data-chart-index";
16
+ readonly DRAGGABLE: "data-chart-draggable";
17
+ };
18
+ /**
19
+ * A point that has been indexed in the Quadtree.
20
+ * This is stored in memory for fast spatial queries.
21
+ */
22
+ export interface IndexedPoint<T = unknown> {
23
+ x: number;
24
+ y: number;
25
+ data: T;
26
+ seriesId: string;
27
+ dataIndex: number;
28
+ seriesColor?: string;
29
+ draggable?: boolean;
30
+ suppressMarker?: boolean;
31
+ }
32
+ export interface SpatialMapOptions {
33
+ /**
34
+ * The radius (in pixels) for "magnetic" snapping to data points.
35
+ * Points within this radius will be returned as candidates.
36
+ * @default 20
37
+ */
38
+ magneticRadius?: number;
39
+ /**
40
+ * Whether to use DOM-based hit testing (elementsFromPoint).
41
+ * Disable for unit testing without a DOM.
42
+ * @default true
43
+ */
44
+ useDomHitTesting?: boolean;
45
+ }
46
+ /**
47
+ * SpatialMap provides the "Hybrid Radar" for finding interaction targets.
48
+ *
49
+ * It combines two strategies:
50
+ * 1. **DOM Hit Testing (Broad Phase):** Uses `elementsFromPoint` to find
51
+ * large, complex shapes (bars, areas, labels) accurately.
52
+ * 2. **Quadtree (Fine Phase):** Uses a spatial tree to find nearby data
53
+ * points with "magnetic" snapping (great for scatter/line charts).
54
+ */
55
+ export declare class SpatialMap<T = unknown> {
56
+ private tree;
57
+ private points;
58
+ private xBuckets;
59
+ private containerElement;
60
+ private options;
61
+ constructor(options?: SpatialMapOptions);
62
+ /**
63
+ * Set the container element for DOM-based hit testing.
64
+ * Elements outside this container are ignored.
65
+ */
66
+ setContainer(element: Element | null): void;
67
+ /**
68
+ * Update the spatial index with new data points.
69
+ * Call this when data changes.
70
+ *
71
+ * @param points - The data points with their pixel coordinates
72
+ */
73
+ updateIndex(points: IndexedPoint<T>[]): void;
74
+ /**
75
+ * Clear the spatial index.
76
+ */
77
+ clear(): void;
78
+ /**
79
+ * Find all indexed points sharing the given X coordinate (O(k) bucket lookup).
80
+ * Returns every point at that X regardless of Y distance — intended for
81
+ * multi-series vertical-slice hover.
82
+ *
83
+ * @param x - The exact X pixel coordinate (as stored in the index)
84
+ */
85
+ findAllAtX(x: number): InteractionCandidate<T>[];
86
+ /**
87
+ * Find all interaction candidates near a point.
88
+ *
89
+ * @param x - X coordinate (relative to plot area for Quadtree)
90
+ * @param y - Y coordinate (relative to plot area for Quadtree)
91
+ * @param containerPoint - Optional container-relative coordinates for DOM hit testing
92
+ * @returns Sorted array of candidates (closest first)
93
+ */
94
+ find(x: number, y: number, containerPoint?: {
95
+ x: number;
96
+ y: number;
97
+ }): InteractionCandidate<T>[];
98
+ /**
99
+ * Find candidates using DOM elementsFromPoint.
100
+ */
101
+ private findFromDOM;
102
+ /**
103
+ * Convert a DOM element into an InteractionCandidate.
104
+ */
105
+ private hydrateElement;
106
+ /**
107
+ * Find candidates using the Quadtree (nearby data points).
108
+ */
109
+ private findFromTree;
110
+ /**
111
+ * Get the current options.
112
+ */
113
+ getOptions(): Required<SpatialMapOptions>;
114
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * SpatialMap
3
+ *
4
+ * The "Hybrid Radar" that finds interaction candidates.
5
+ * Combines DOM-based hit testing with Quadtree spatial queries.
6
+ */
7
+ import { quadtree } from "d3-quadtree";
8
+ // =============================================================================
9
+ // DATA ATTRIBUTES (For DOM Element Tagging)
10
+ // =============================================================================
11
+ /**
12
+ * Standard data attributes used to tag interactive DOM elements.
13
+ * These are read by the SpatialMap during DOM-based hit testing.
14
+ */
15
+ export const CHART_DATA_ATTRS = {
16
+ TYPE: "data-chart-type",
17
+ SERIES_ID: "data-chart-series",
18
+ INDEX: "data-chart-index",
19
+ DRAGGABLE: "data-chart-draggable",
20
+ };
21
+ /**
22
+ * SpatialMap provides the "Hybrid Radar" for finding interaction targets.
23
+ *
24
+ * It combines two strategies:
25
+ * 1. **DOM Hit Testing (Broad Phase):** Uses `elementsFromPoint` to find
26
+ * large, complex shapes (bars, areas, labels) accurately.
27
+ * 2. **Quadtree (Fine Phase):** Uses a spatial tree to find nearby data
28
+ * points with "magnetic" snapping (great for scatter/line charts).
29
+ */
30
+ export class SpatialMap {
31
+ constructor(options = {}) {
32
+ var _a, _b;
33
+ this.tree = null;
34
+ this.points = [];
35
+ this.xBuckets = new Map();
36
+ this.containerElement = null;
37
+ this.options = {
38
+ magneticRadius: (_a = options.magneticRadius) !== null && _a !== void 0 ? _a : 40,
39
+ useDomHitTesting: (_b = options.useDomHitTesting) !== null && _b !== void 0 ? _b : true,
40
+ };
41
+ }
42
+ /**
43
+ * Set the container element for DOM-based hit testing.
44
+ * Elements outside this container are ignored.
45
+ */
46
+ setContainer(element) {
47
+ this.containerElement = element;
48
+ }
49
+ /**
50
+ * Update the spatial index with new data points.
51
+ * Call this when data changes.
52
+ *
53
+ * @param points - The data points with their pixel coordinates
54
+ */
55
+ updateIndex(points) {
56
+ var _a;
57
+ this.points = points;
58
+ if (points.length === 0) {
59
+ this.tree = null;
60
+ this.xBuckets = new Map();
61
+ return;
62
+ }
63
+ this.tree = quadtree()
64
+ .x((d) => d.x)
65
+ .y((d) => d.y)
66
+ .addAll(points);
67
+ // Build the X-bucket index for O(k) vertical-slice lookups
68
+ this.xBuckets = new Map();
69
+ for (const p of points) {
70
+ const bucket = (_a = this.xBuckets.get(p.x)) !== null && _a !== void 0 ? _a : [];
71
+ bucket.push(p);
72
+ this.xBuckets.set(p.x, bucket);
73
+ }
74
+ }
75
+ /**
76
+ * Clear the spatial index.
77
+ */
78
+ clear() {
79
+ this.points = [];
80
+ this.tree = null;
81
+ this.xBuckets = new Map();
82
+ }
83
+ /**
84
+ * Find all indexed points sharing the given X coordinate (O(k) bucket lookup).
85
+ * Returns every point at that X regardless of Y distance — intended for
86
+ * multi-series vertical-slice hover.
87
+ *
88
+ * @param x - The exact X pixel coordinate (as stored in the index)
89
+ */
90
+ findAllAtX(x) {
91
+ var _a;
92
+ const bucket = (_a = this.xBuckets.get(x)) !== null && _a !== void 0 ? _a : [];
93
+ return bucket.map((p) => ({
94
+ type: "data-point",
95
+ data: p.data,
96
+ seriesId: p.seriesId,
97
+ dataIndex: p.dataIndex,
98
+ coordinate: { x: p.x, y: p.y },
99
+ distance: 0,
100
+ seriesColor: p.seriesColor,
101
+ draggable: p.draggable,
102
+ suppressMarker: p.suppressMarker,
103
+ }));
104
+ }
105
+ /**
106
+ * Find all interaction candidates near a point.
107
+ *
108
+ * @param x - X coordinate (relative to plot area for Quadtree)
109
+ * @param y - Y coordinate (relative to plot area for Quadtree)
110
+ * @param containerPoint - Optional container-relative coordinates for DOM hit testing
111
+ * @returns Sorted array of candidates (closest first)
112
+ */
113
+ find(x, y, containerPoint) {
114
+ const candidates = [];
115
+ // Phase 1: DOM Hit Testing (Broad Phase)
116
+ if (this.options.useDomHitTesting) {
117
+ // Use container coordinates if available, otherwise assume x/y are container-relative
118
+ // (This fallback retains backward compatibility but might be wrong if x/y are plot-relative)
119
+ const domX = containerPoint ? containerPoint.x : x;
120
+ const domY = containerPoint ? containerPoint.y : y;
121
+ const domCandidates = this.findFromDOM(domX, domY);
122
+ candidates.push(...domCandidates);
123
+ }
124
+ // Phase 2: Quadtree (Fine Phase)
125
+ const treeCandidates = this.findFromTree(x, y);
126
+ candidates.push(...treeCandidates);
127
+ candidates.sort((a, b) => {
128
+ var _a, _b;
129
+ const zDiff = ((_a = b.zIndex) !== null && _a !== void 0 ? _a : 0) - ((_b = a.zIndex) !== null && _b !== void 0 ? _b : 0);
130
+ if (zDiff !== 0) {
131
+ return zDiff;
132
+ }
133
+ return a.distance - b.distance;
134
+ });
135
+ return candidates;
136
+ }
137
+ /**
138
+ * Find candidates using DOM elementsFromPoint.
139
+ */
140
+ findFromDOM(x, y) {
141
+ if (!this.containerElement) {
142
+ return [];
143
+ }
144
+ const rect = this.containerElement.getBoundingClientRect();
145
+ const viewportX = rect.left + x;
146
+ const viewportY = rect.top + y;
147
+ const elements = document.elementsFromPoint(viewportX, viewportY);
148
+ const candidates = [];
149
+ for (const element of elements) {
150
+ if (!this.containerElement.contains(element)) {
151
+ continue;
152
+ }
153
+ const chartType = element.getAttribute(CHART_DATA_ATTRS.TYPE);
154
+ if (!chartType) {
155
+ continue;
156
+ }
157
+ const candidate = this.hydrateElement(element, chartType, x, y);
158
+ if (candidate) {
159
+ candidates.push(candidate);
160
+ }
161
+ }
162
+ return candidates;
163
+ }
164
+ /**
165
+ * Convert a DOM element into an InteractionCandidate.
166
+ */
167
+ hydrateElement(element, type, pointerX, pointerY) {
168
+ var _a, _b;
169
+ const seriesId = (_a = element.getAttribute(CHART_DATA_ATTRS.SERIES_ID)) !== null && _a !== void 0 ? _a : undefined;
170
+ const indexStr = element.getAttribute(CHART_DATA_ATTRS.INDEX);
171
+ const draggable = element.getAttribute(CHART_DATA_ATTRS.DRAGGABLE) === "true";
172
+ const rect = element.getBoundingClientRect();
173
+ const containerRect = (_b = this.containerElement) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
174
+ if (!containerRect) {
175
+ return null;
176
+ }
177
+ const elementCenterX = rect.left + rect.width / 2 - containerRect.left;
178
+ const elementCenterY = rect.top + rect.height / 2 - containerRect.top;
179
+ const distance = Math.hypot(pointerX - elementCenterX, pointerY - elementCenterY);
180
+ let data;
181
+ let dataIndex;
182
+ if (indexStr !== null && seriesId) {
183
+ dataIndex = parseInt(indexStr, 10);
184
+ const point = this.points.find((p) => p.seriesId === seriesId && p.dataIndex === dataIndex);
185
+ if (point) {
186
+ data = point.data;
187
+ }
188
+ }
189
+ // Fall back to D3's __data__ binding for custom renders that don't index their points
190
+ if (!data) {
191
+ const d3Data = element.__data__;
192
+ if (d3Data && !Array.isArray(d3Data)) {
193
+ data = d3Data;
194
+ }
195
+ }
196
+ const zIndex = parseZIndex(element);
197
+ if (!data) {
198
+ return null;
199
+ }
200
+ return {
201
+ type: type,
202
+ data,
203
+ seriesId,
204
+ dataIndex,
205
+ coordinate: { x: elementCenterX, y: elementCenterY },
206
+ distance,
207
+ element,
208
+ draggable,
209
+ zIndex,
210
+ };
211
+ }
212
+ /**
213
+ * Find candidates using the Quadtree (nearby data points).
214
+ */
215
+ findFromTree(x, y) {
216
+ if (!this.tree) {
217
+ return [];
218
+ }
219
+ const candidates = [];
220
+ const radius = this.options.magneticRadius;
221
+ this.tree.visit((node, x0, y0, x1, y1) => {
222
+ if (x0 > x + radius ||
223
+ x1 < x - radius ||
224
+ y0 > y + radius ||
225
+ y1 < y - radius) {
226
+ return true; // Skip this branch
227
+ }
228
+ // Leaf nodes don't have the 'length' property defined
229
+ if (!("length" in node)) {
230
+ let current = node;
231
+ while (current) {
232
+ const point = current.data;
233
+ if (point) {
234
+ const distance = Math.hypot(point.x - x, point.y - y);
235
+ if (distance <= radius) {
236
+ candidates.push({
237
+ type: "data-point",
238
+ data: point.data,
239
+ seriesId: point.seriesId,
240
+ dataIndex: point.dataIndex,
241
+ coordinate: { x: point.x, y: point.y },
242
+ distance,
243
+ seriesColor: point.seriesColor,
244
+ draggable: point.draggable,
245
+ suppressMarker: point.suppressMarker,
246
+ });
247
+ }
248
+ }
249
+ current = current.next;
250
+ }
251
+ }
252
+ return false; // Continue visiting
253
+ });
254
+ return candidates;
255
+ }
256
+ /**
257
+ * Get the current options.
258
+ */
259
+ getOptions() {
260
+ return Object.assign({}, this.options);
261
+ }
262
+ }
263
+ /**
264
+ * Parse the z-index from an element's computed style.
265
+ */
266
+ function parseZIndex(element) {
267
+ const style = getComputedStyle(element);
268
+ const zIndex = parseInt(style.zIndex, 10);
269
+ return isNaN(zIndex) ? 0 : zIndex;
270
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Engine Module
3
+ *
4
+ * Public API for the Chart interaction engine.
5
+ */
6
+ export type { EngineOptions } from "./Engine";
7
+ export { Engine } from "./Engine";
8
+ export type { TaskHandler } from "./Scheduler";
9
+ export { Scheduler } from "./Scheduler";
10
+ export type { IndexedPoint, SpatialMapOptions } from "./SpatialMap";
11
+ export { CHART_DATA_ATTRS, SpatialMap } from "./SpatialMap";
12
+ export type { CandidateType, EngineEvent, InputSignal, InteractionCandidate, ScheduledTask, TaskPriority, } from "./types";
13
+ export { InputAction, InputSource, TaskPriority as TaskPriorityEnum, } from "./types";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Engine Module
3
+ *
4
+ * Public API for the Chart interaction engine.
5
+ */
6
+ export { Engine } from "./Engine.js";
7
+ export { Scheduler } from "./Scheduler.js";
8
+ export { CHART_DATA_ATTRS, SpatialMap } from "./SpatialMap.js";
9
+ export { InputAction, InputSource, TaskPriority as TaskPriorityEnum, } from "./types.js";
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Engine Type Definitions
3
+ *
4
+ * These types define the core data structures for the Hyper-Engine architecture.
5
+ * They are intentionally decoupled from React and DOM to enable pure logic testing.
6
+ */
7
+ /**
8
+ * The source of an input signal. Used for multiplayer and debugging.
9
+ */
10
+ export declare enum InputSource {
11
+ MOUSE = "mouse",
12
+ TOUCH = "touch",
13
+ KEYBOARD = "keyboard",
14
+ REMOTE = "remote"
15
+ }
16
+ /**
17
+ * The type of input action being performed.
18
+ */
19
+ export declare enum InputAction {
20
+ START = "START",
21
+ MOVE = "MOVE",
22
+ END = "END",
23
+ CANCEL = "CANCEL",
24
+ KEY = "KEY"
25
+ }
26
+ /**
27
+ * InputSignal is the normalized, DOM-independent representation of a user input.
28
+ * This is what the Engine processes - NOT raw PointerEvents.
29
+ *
30
+ * Think of this as the "packet" that travels through the engine pipeline.
31
+ */
32
+ export interface InputSignal {
33
+ /** Unique identifier for this input stream (e.g., pointerId for multi-touch) */
34
+ id: number;
35
+ /** The action being performed */
36
+ action: InputAction;
37
+ /** Where this input came from */
38
+ source: InputSource;
39
+ /** X coordinate relative to the chart container (NOT the viewport) */
40
+ x: number;
41
+ /** Y coordinate relative to the chart container */
42
+ y: number;
43
+ /** High-resolution timestamp (performance.now()) for frame scheduling */
44
+ timestamp: number;
45
+ /** User identifier for multiplayer support. 'local' for the current user. */
46
+ userId: string;
47
+ /** Optional: For keyboard events, the key that was pressed */
48
+ key?: string;
49
+ /** Optional: Modifier keys state */
50
+ modifiers?: {
51
+ shift: boolean;
52
+ ctrl: boolean;
53
+ alt: boolean;
54
+ meta: boolean;
55
+ };
56
+ }
57
+ /**
58
+ * The type of element that was hit by the spatial query.
59
+ */
60
+ export type CandidateType = "data-point" | "bar" | "area" | "label" | "axis" | "legend-item" | "custom";
61
+ /**
62
+ * InteractionCandidate is a "hydrated" hit result.
63
+ * It contains everything a Sensor needs to make a decision.
64
+ */
65
+ export interface InteractionCandidate<T = unknown> {
66
+ /** What kind of element this is */
67
+ type: CandidateType;
68
+ /** The underlying data object (if applicable) */
69
+ data?: T;
70
+ /** The series this belongs to (if applicable) */
71
+ seriesId?: string;
72
+ /** The index within the series (for data points) */
73
+ dataIndex?: number;
74
+ /** The color of the series (for styling behaviors) */
75
+ seriesColor?: string;
76
+ /** The pixel coordinates of this candidate (for snapping) */
77
+ coordinate: {
78
+ x: number;
79
+ y: number;
80
+ };
81
+ /** Distance from the pointer (for sorting by proximity) */
82
+ distance: number;
83
+ /** The DOM element that was hit (for DOM-sourced candidates) */
84
+ element?: Element;
85
+ /** Whether this candidate is draggable */
86
+ draggable?: boolean;
87
+ /** Whether this candidate should suppress marker rendering */
88
+ suppressMarker?: boolean;
89
+ /** Z-index for layering (higher = on top) */
90
+ zIndex?: number;
91
+ }
92
+ /**
93
+ * EngineEvent is the fully processed event that gets dispatched to Sensors.
94
+ * It combines the raw input with the spatial query results.
95
+ */
96
+ export interface EngineEvent<T = unknown> {
97
+ /** The normalized input that triggered this event */
98
+ signal: InputSignal;
99
+ /** All candidates found by the spatial query, sorted by relevance */
100
+ candidates: InteractionCandidate<T>[];
101
+ /** The primary (closest/most relevant) candidate, if any */
102
+ primaryCandidate?: InteractionCandidate<T>;
103
+ /**
104
+ * All indexed points sharing the primary candidate's X position, regardless
105
+ * of Y distance. Pre-computed via an O(k) bucket lookup (k = number of series
106
+ * at that X). Always an array; empty when there is no primary candidate.
107
+ * Intended for multi-series vertical-slice hover.
108
+ */
109
+ sliceCandidates: InteractionCandidate<T>[];
110
+ /** X coordinate relative to the plot area (after margins) */
111
+ chartX: number;
112
+ /** Y coordinate relative to the plot area */
113
+ chartY: number;
114
+ /** Whether the pointer is within the plot area bounds */
115
+ isWithinPlot: boolean;
116
+ }
117
+ /**
118
+ * Priority levels for the scheduler.
119
+ * Critical tasks run synchronously to enable preventDefault().
120
+ * Visual tasks are batched to requestAnimationFrame.
121
+ */
122
+ export declare enum TaskPriority {
123
+ /** Must run immediately (pointer down, to enable preventDefault) */
124
+ CRITICAL = 0,
125
+ /** Can be batched to next frame (hover updates, tooltip positioning) */
126
+ VISUAL = 1,
127
+ /** Low priority, can be deferred (analytics, logging) */
128
+ IDLE = 2
129
+ }
130
+ /**
131
+ * A task to be scheduled by the engine.
132
+ */
133
+ export interface ScheduledTask<T = unknown> {
134
+ priority: TaskPriority;
135
+ event: EngineEvent<T>;
136
+ timestamp: number;
137
+ }