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.
- package/dist/components/Accordion/Accordion.module.css +121 -124
- package/dist/components/ActionRow/ActionRow.module.css +25 -24
- package/dist/components/Alert/Alert.module.css +74 -76
- package/dist/components/Avatar/Avatar.module.css +66 -66
- package/dist/components/Badge/Badge.module.css +50 -48
- package/dist/components/Breadcrumbs/Breadcrumbs.module.css +32 -33
- package/dist/components/Button/Button.d.ts +2 -2
- package/dist/components/Button/Button.js +1 -1
- package/dist/components/Button/Button.module.css +150 -152
- package/dist/components/Card/Card.module.css +37 -39
- package/dist/components/Chart/Chart.module.css +213 -245
- package/dist/components/Chart/behaviors/DraggablePuck.d.ts +36 -0
- package/dist/components/Chart/behaviors/DraggablePuck.js +94 -0
- package/dist/components/Chart/behaviors/Markers.js +6 -4
- package/dist/components/Chart/behaviors/SelectionUpdate.js +2 -4
- package/dist/components/Chart/behaviors/index.d.ts +1 -1
- package/dist/components/Chart/behaviors/index.js +1 -1
- package/dist/components/Chart/engine/CoordinateSystem.d.ts +59 -0
- package/dist/components/Chart/engine/CoordinateSystem.js +126 -0
- package/dist/components/Chart/engine/Engine.d.ts +102 -0
- package/dist/components/Chart/engine/Engine.js +226 -0
- package/dist/components/Chart/engine/Scheduler.d.ts +59 -0
- package/dist/components/Chart/engine/Scheduler.js +139 -0
- package/dist/components/Chart/engine/SpatialMap.d.ts +114 -0
- package/dist/components/Chart/engine/SpatialMap.js +270 -0
- package/dist/components/Chart/engine/index.d.ts +13 -0
- package/dist/components/Chart/engine/index.js +9 -0
- package/dist/components/Chart/engine/types.d.ts +137 -0
- package/dist/components/Chart/engine/types.js +47 -0
- package/dist/components/Chart/hooks/useEngine.d.ts +43 -0
- package/dist/components/Chart/hooks/useEngine.js +128 -0
- package/dist/components/Chart/sensors/DataHoverSensor/DataHoverSensor.d.ts +17 -19
- package/dist/components/Chart/sensors/DataHoverSensor/DataHoverSensor.js +38 -51
- package/dist/components/Chart/sensors/DragSensor/DragSensor.d.ts +38 -0
- package/dist/components/Chart/sensors/DragSensor/DragSensor.js +105 -0
- package/dist/components/Chart/sensors/DragSensor/index.d.ts +2 -0
- package/dist/components/Chart/sensors/DragSensor/index.js +1 -0
- package/dist/components/Chart/sensors/{KeyboardSensor.d.ts → KeyboardSensor/KeyboardSensor.d.ts} +1 -1
- package/dist/components/Chart/sensors/KeyboardSensor/KeyboardSensor.js +86 -0
- package/dist/components/Chart/sensors/KeyboardSensor/index.d.ts +1 -0
- package/dist/components/Chart/sensors/KeyboardSensor/index.js +1 -0
- package/dist/components/Chart/sensors/{SelectionSensor.d.ts → SelectionSensor/SelectionSensor.d.ts} +2 -2
- package/dist/components/Chart/sensors/SelectionSensor/SelectionSensor.js +39 -0
- package/dist/components/Chart/sensors/SelectionSensor/index.d.ts +1 -0
- package/dist/components/Chart/sensors/SelectionSensor/index.js +1 -0
- package/dist/components/Chart/sensors/SensorManager/SensorManager.js +36 -41
- package/dist/components/Chart/sensors/index.d.ts +1 -0
- package/dist/components/Chart/sensors/index.js +3 -2
- package/dist/components/Chart/sensors/utils/search.d.ts +1 -1
- package/dist/components/Chart/sensors/utils/search.js +25 -4
- package/dist/components/Chart/state/store/chart.store.js +18 -0
- package/dist/components/Chart/state/store/slices/series.slice.d.ts +1 -0
- package/dist/components/Chart/state/store/slices/series.slice.js +3 -2
- package/dist/components/Chart/subcomponents/Axis/Axis.module.css +32 -33
- package/dist/components/Chart/subcomponents/BarSeries/BarSeries.js +6 -1
- package/dist/components/Chart/subcomponents/BarSeries/BarSeries.module.css +11 -9
- package/dist/components/Chart/subcomponents/Cursor/Cursor.js +8 -1
- package/dist/components/Chart/subcomponents/Cursor/Cursor.module.css +14 -13
- package/dist/components/Chart/subcomponents/CustomSeries/CustomSeries.js +4 -0
- package/dist/components/Chart/subcomponents/CustomSeries/CustomSeries.module.css +5 -3
- package/dist/components/Chart/subcomponents/Footer/Footer.module.css +5 -3
- package/dist/components/Chart/subcomponents/Grid/Grid.module.css +12 -11
- package/dist/components/Chart/subcomponents/Header/Header.module.css +8 -7
- package/dist/components/Chart/subcomponents/InteractionLayer/InteractionLayer.d.ts +4 -4
- package/dist/components/Chart/subcomponents/InteractionLayer/InteractionLayer.js +39 -76
- package/dist/components/Chart/subcomponents/Legend/Legend.module.css +30 -32
- package/dist/components/Chart/subcomponents/LineSeries/LineSeries.js +9 -3
- package/dist/components/Chart/subcomponents/LineSeries/LineSeries.module.css +21 -21
- package/dist/components/Chart/subcomponents/Root/Root.js +113 -7
- package/dist/components/Chart/subcomponents/Root/Root.module.css +70 -82
- package/dist/components/Chart/subcomponents/ScatterSeries/ScatterSeries.js +6 -1
- package/dist/components/Chart/subcomponents/ScatterSeries/ScatterSeries.module.css +7 -5
- package/dist/components/Chart/subcomponents/Series/Series.module.css +118 -128
- package/dist/components/Chart/subcomponents/SeriesPoint/SeriesPoint.module.css +10 -8
- package/dist/components/Chart/subcomponents/Tooltip/Tooltip.js +2 -3
- package/dist/components/Chart/subcomponents/Tooltip/Tooltip.module.css +52 -67
- package/dist/components/Chart/types/context.d.ts +9 -0
- package/dist/components/Chart/types/events.d.ts +5 -7
- package/dist/components/Chart/types/interaction.d.ts +24 -2
- package/dist/components/Chart/types/interaction.js +1 -0
- package/dist/components/Checkbox/Checkbox.module.css +57 -59
- package/dist/components/Chip/Chip.module.css +105 -115
- package/dist/components/Combobox/Combobox.d.ts +2 -1
- package/dist/components/Combobox/Combobox.js +2 -2
- package/dist/components/Combobox/Combobox.module.css +233 -210
- package/dist/components/CopyButton/CopyButton.module.css +84 -90
- package/dist/components/Drawer/Drawer.module.css +126 -145
- package/dist/components/Dropdown/Dropdown.d.ts +3 -1
- package/dist/components/Dropdown/Dropdown.js +3 -3
- package/dist/components/Dropdown/Dropdown.module.css +52 -32
- package/dist/components/FileUpload/FileUpload.js +24 -0
- package/dist/components/FileUpload/FileUpload.module.css +295 -313
- package/dist/components/Form/Form.module.css +35 -39
- package/dist/components/Image/Image.module.css +53 -54
- package/dist/components/Input/Input.d.ts +4 -2
- package/dist/components/Input/Input.js +2 -2
- package/dist/components/Input/Input.module.css +135 -119
- package/dist/components/Label/Label.module.css +17 -15
- package/dist/components/Layout/Layout.module.css +95 -111
- package/dist/components/Link/Link.module.css +67 -65
- package/dist/components/Modal/Modal.module.css +112 -132
- package/dist/components/Page/Page.module.css +21 -21
- package/dist/components/Pagination/Pagination.module.css +56 -56
- package/dist/components/Popover/Popover.module.css +17 -16
- package/dist/components/ProgressBar/ProgressBar.module.css +36 -37
- package/dist/components/RadioGroup/RadioGroup.module.css +74 -77
- package/dist/components/Select/Select.d.ts +2 -1
- package/dist/components/Select/Select.js +2 -2
- package/dist/components/Select/Select.module.css +133 -98
- package/dist/components/Sheet/Sheet.module.css +134 -154
- package/dist/components/Sidebar/Sidebar.module.css +72 -74
- package/dist/components/Sidebar/subcomponents/Footer/Footer.module.css +7 -5
- package/dist/components/Sidebar/subcomponents/Group/Group.module.css +80 -85
- package/dist/components/Sidebar/subcomponents/Header/Header.module.css +12 -10
- package/dist/components/Sidebar/subcomponents/Item/Item.module.css +54 -55
- package/dist/components/Sidebar/subcomponents/MobileOverlay/MobileOverlay.module.css +38 -38
- package/dist/components/Sidebar/subcomponents/MobileTrigger/MobileTrigger.module.css +5 -3
- package/dist/components/Sidebar/subcomponents/Nav/Nav.module.css +13 -11
- package/dist/components/Sidebar/subcomponents/Rail/Rail.module.css +62 -63
- package/dist/components/Sidebar/subcomponents/Section/Section.module.css +86 -91
- package/dist/components/Skeleton/Skeleton.module.css +28 -26
- package/dist/components/Slat/Slat.module.css +93 -94
- package/dist/components/Slider/Slider.module.css +116 -121
- package/dist/components/Spinner/Spinner.module.css +28 -27
- package/dist/components/SplitButton/SplitButton.d.ts +3 -1
- package/dist/components/SplitButton/SplitButton.js +2 -2
- package/dist/components/SplitButton/SplitButton.module.css +104 -87
- package/dist/components/Switch/Switch.module.css +64 -63
- package/dist/components/Table/FilterBuilder/FilterBuilder.module.css +36 -36
- package/dist/components/Table/FilterBuilder/FilterConditionRow.js +1 -1
- package/dist/components/Table/FilterBuilder/FilterConditionRow.module.css +21 -22
- package/dist/components/Table/FilterBuilder/FilterGroup.js +4 -4
- package/dist/components/Table/FilterBuilder/FilterGroup.module.css +355 -389
- package/dist/components/Table/FilterBuilder/FilterSheet.module.css +68 -71
- package/dist/components/Table/Table.d.ts +4 -2
- package/dist/components/Table/Table.js +50 -13
- package/dist/components/Table/Table.module.css +210 -188
- package/dist/components/Table/TableHeaderFilter.js +1 -1
- package/dist/components/Table/TableHeaderFilter.module.css +51 -57
- package/dist/components/Tabs/Tabs.module.css +79 -80
- package/dist/components/Text/Text.module.css +108 -131
- package/dist/components/Textarea/Textarea.d.ts +3 -1
- package/dist/components/Textarea/Textarea.js +2 -2
- package/dist/components/Textarea/Textarea.module.css +114 -94
- package/dist/components/Toast/Toast.module.css +82 -82
- package/dist/components/Tooltip/Tooltip.module.css +17 -16
- package/dist/styles/globals.css +1677 -1691
- package/dist/styles/palettes.d.ts +0 -5
- package/dist/styles/palettes.js +0 -8
- package/dist/styles/themes/definitions.d.ts +0 -8
- package/dist/styles/themes/definitions.js +117 -5
- package/dist/styles/types.d.ts +2 -0
- package/dist/styles/types.js +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/.agent/skills/doom/a2ui/a2ui-examples.md +0 -289
- package/.agent/skills/doom/a2ui/a2ui-principles.md +0 -49
- package/.agent/skills/doom/a2ui/a2ui-protocol.md +0 -191
- package/.agent/skills/doom/components/a2ui.md +0 -46
- package/.agent/skills/doom/components/accordion.md +0 -44
- package/.agent/skills/doom/components/actionrow.md +0 -33
- package/.agent/skills/doom/components/alert.md +0 -35
- package/.agent/skills/doom/components/avatar.md +0 -36
- package/.agent/skills/doom/components/badge.md +0 -29
- package/.agent/skills/doom/components/breadcrumbs.md +0 -33
- package/.agent/skills/doom/components/button.md +0 -43
- package/.agent/skills/doom/components/card.md +0 -41
- package/.agent/skills/doom/components/chart.md +0 -106
- package/.agent/skills/doom/components/checkbox.md +0 -38
- package/.agent/skills/doom/components/chip.md +0 -35
- package/.agent/skills/doom/components/combobox.md +0 -50
- package/.agent/skills/doom/components/copybutton.md +0 -41
- package/.agent/skills/doom/components/drawer.md +0 -69
- package/.agent/skills/doom/components/dropdown.md +0 -33
- package/.agent/skills/doom/components/fileupload.md +0 -49
- package/.agent/skills/doom/components/form.md +0 -55
- package/.agent/skills/doom/components/icon.md +0 -47
- package/.agent/skills/doom/components/image.md +0 -48
- package/.agent/skills/doom/components/input.md +0 -54
- package/.agent/skills/doom/components/label.md +0 -32
- package/.agent/skills/doom/components/layout.md +0 -92
- package/.agent/skills/doom/components/link.md +0 -39
- package/.agent/skills/doom/components/modal.md +0 -71
- package/.agent/skills/doom/components/page.md +0 -41
- package/.agent/skills/doom/components/pagination.md +0 -32
- package/.agent/skills/doom/components/popover.md +0 -45
- package/.agent/skills/doom/components/progressbar.md +0 -37
- package/.agent/skills/doom/components/radiogroup.md +0 -45
- package/.agent/skills/doom/components/select.md +0 -43
- package/.agent/skills/doom/components/sheet.md +0 -71
- package/.agent/skills/doom/components/sidebar.md +0 -92
- package/.agent/skills/doom/components/skeleton.md +0 -35
- package/.agent/skills/doom/components/slat.md +0 -46
- package/.agent/skills/doom/components/slider.md +0 -51
- package/.agent/skills/doom/components/spinner.md +0 -34
- package/.agent/skills/doom/components/splitbutton.md +0 -35
- package/.agent/skills/doom/components/switch.md +0 -40
- package/.agent/skills/doom/components/table.md +0 -82
- package/.agent/skills/doom/components/tabs.md +0 -65
- package/.agent/skills/doom/components/text.md +0 -42
- package/.agent/skills/doom/components/textarea.md +0 -46
- package/.agent/skills/doom/components/toast.md +0 -59
- package/.agent/skills/doom/components/tooltip.md +0 -35
- package/.agent/skills/doom/index.md +0 -167
- package/.agent/skills/doom/styles/aesthetic.md +0 -151
- package/.agent/skills/doom/styles/css-variables.md +0 -80
- package/.agent/skills/doom/styles/mixins.md +0 -97
- package/.agent/skills/doom/styles/tokens.md +0 -129
- package/.agent/skills/doom/styles/utilities.md +0 -84
- package/dist/components/Chart/sensors/KeyboardSensor.js +0 -82
- package/dist/components/Chart/sensors/SelectionSensor.js +0 -41
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
// INPUT SIGNAL (The Universal Input Format)
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* The source of an input signal. Used for multiplayer and debugging.
|
|
12
|
+
*/
|
|
13
|
+
export var InputSource;
|
|
14
|
+
(function (InputSource) {
|
|
15
|
+
InputSource["MOUSE"] = "mouse";
|
|
16
|
+
InputSource["TOUCH"] = "touch";
|
|
17
|
+
InputSource["KEYBOARD"] = "keyboard";
|
|
18
|
+
InputSource["REMOTE"] = "remote";
|
|
19
|
+
})(InputSource || (InputSource = {}));
|
|
20
|
+
/**
|
|
21
|
+
* The type of input action being performed.
|
|
22
|
+
*/
|
|
23
|
+
export var InputAction;
|
|
24
|
+
(function (InputAction) {
|
|
25
|
+
InputAction["START"] = "START";
|
|
26
|
+
InputAction["MOVE"] = "MOVE";
|
|
27
|
+
InputAction["END"] = "END";
|
|
28
|
+
InputAction["CANCEL"] = "CANCEL";
|
|
29
|
+
InputAction["KEY"] = "KEY";
|
|
30
|
+
})(InputAction || (InputAction = {}));
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// SCHEDULER TYPES
|
|
33
|
+
// =============================================================================
|
|
34
|
+
/**
|
|
35
|
+
* Priority levels for the scheduler.
|
|
36
|
+
* Critical tasks run synchronously to enable preventDefault().
|
|
37
|
+
* Visual tasks are batched to requestAnimationFrame.
|
|
38
|
+
*/
|
|
39
|
+
export var TaskPriority;
|
|
40
|
+
(function (TaskPriority) {
|
|
41
|
+
/** Must run immediately (pointer down, to enable preventDefault) */
|
|
42
|
+
TaskPriority[TaskPriority["CRITICAL"] = 0] = "CRITICAL";
|
|
43
|
+
/** Can be batched to next frame (hover updates, tooltip positioning) */
|
|
44
|
+
TaskPriority[TaskPriority["VISUAL"] = 1] = "VISUAL";
|
|
45
|
+
/** Low priority, can be deferred (analytics, logging) */
|
|
46
|
+
TaskPriority[TaskPriority["IDLE"] = 2] = "IDLE";
|
|
47
|
+
})(TaskPriority || (TaskPriority = {}));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEngine Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook that manages the Engine lifecycle, connecting the
|
|
5
|
+
* pure Engine to React's component tree.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Create and dispose Engine instance
|
|
9
|
+
* - Sync data to spatial index
|
|
10
|
+
* - Handle container attachment and resize
|
|
11
|
+
* - Wire up event handler with stable callback pattern
|
|
12
|
+
*/
|
|
13
|
+
import { Engine, EngineEvent } from "../engine";
|
|
14
|
+
export interface UseEngineOptions<T = unknown> {
|
|
15
|
+
/** Data points to index for spatial queries */
|
|
16
|
+
data?: T[];
|
|
17
|
+
/** Function to get X coordinate from data item */
|
|
18
|
+
getX?: (d: T) => number;
|
|
19
|
+
/** Function to get Y coordinate from data item */
|
|
20
|
+
getY?: (d: T) => number;
|
|
21
|
+
/** Function to get series ID from data item */
|
|
22
|
+
getSeriesId?: (d: T) => string;
|
|
23
|
+
/** Function to get data index from data item */
|
|
24
|
+
getDataIndex?: (d: T, index: number) => number;
|
|
25
|
+
/** Plot bounds for coordinate calculations */
|
|
26
|
+
plotBounds?: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
/** Event handler for engine events */
|
|
33
|
+
onEvent?: (event: EngineEvent<T>) => void;
|
|
34
|
+
/** Magnetic radius for snapping (pixels) */
|
|
35
|
+
magneticRadius?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface UseEngineResult<T = unknown> {
|
|
38
|
+
/** The Engine instance */
|
|
39
|
+
engine: Engine<T>;
|
|
40
|
+
/** Ref to attach to the container element */
|
|
41
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
42
|
+
}
|
|
43
|
+
export declare function useEngine<T = unknown>(options?: UseEngineOptions<T>): UseEngineResult<T>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEngine Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook that manages the Engine lifecycle, connecting the
|
|
5
|
+
* pure Engine to React's component tree.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Create and dispose Engine instance
|
|
9
|
+
* - Sync data to spatial index
|
|
10
|
+
* - Handle container attachment and resize
|
|
11
|
+
* - Wire up event handler with stable callback pattern
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { Engine } from "../engine/index.js";
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// HOOK IMPLEMENTATION
|
|
17
|
+
// =============================================================================
|
|
18
|
+
export function useEngine(options = {}) {
|
|
19
|
+
const { data, getX, getY, getSeriesId, getDataIndex, plotBounds, onEvent, magneticRadius, } = options;
|
|
20
|
+
// Container ref for DOM attachment
|
|
21
|
+
const containerRef = useRef(null);
|
|
22
|
+
// Track container element state for triggering effects
|
|
23
|
+
const [containerElement, setContainerElement] = useState(null);
|
|
24
|
+
// =========================================================================
|
|
25
|
+
// Engine Creation (Stable)
|
|
26
|
+
// =========================================================================
|
|
27
|
+
const engine = useMemo(() => new Engine({
|
|
28
|
+
magneticRadius,
|
|
29
|
+
useDomHitTesting: true,
|
|
30
|
+
}),
|
|
31
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
|
+
[]);
|
|
33
|
+
// =========================================================================
|
|
34
|
+
// Event Handler (Stable Callback Pattern)
|
|
35
|
+
// =========================================================================
|
|
36
|
+
const onEventRef = useRef(onEvent);
|
|
37
|
+
onEventRef.current = onEvent;
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
engine.setHandler((event) => {
|
|
40
|
+
var _a;
|
|
41
|
+
(_a = onEventRef.current) === null || _a === void 0 ? void 0 : _a.call(onEventRef, event);
|
|
42
|
+
});
|
|
43
|
+
}, [engine]);
|
|
44
|
+
// =========================================================================
|
|
45
|
+
// Plot Bounds Sync
|
|
46
|
+
// =========================================================================
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!plotBounds) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const currentRect = engine.getContainerRect();
|
|
52
|
+
if (currentRect) {
|
|
53
|
+
engine.updateBounds(currentRect, plotBounds);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
engine.setContainer(null, null, plotBounds);
|
|
57
|
+
}
|
|
58
|
+
}, [engine, plotBounds]);
|
|
59
|
+
// =========================================================================
|
|
60
|
+
// Data Sync to Spatial Index
|
|
61
|
+
// =========================================================================
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!data || !getX || !getY) {
|
|
64
|
+
engine.updateData([]);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const points = data.map((d, i) => {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
return ({
|
|
70
|
+
x: getX(d),
|
|
71
|
+
y: getY(d),
|
|
72
|
+
data: d,
|
|
73
|
+
seriesId: (_a = getSeriesId === null || getSeriesId === void 0 ? void 0 : getSeriesId(d)) !== null && _a !== void 0 ? _a : "default",
|
|
74
|
+
dataIndex: (_b = getDataIndex === null || getDataIndex === void 0 ? void 0 : getDataIndex(d, i)) !== null && _b !== void 0 ? _b : i,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
engine.updateData(points);
|
|
78
|
+
}, [engine, data, getX, getY, getSeriesId, getDataIndex]);
|
|
79
|
+
// =========================================================================
|
|
80
|
+
// Container Ref Detection
|
|
81
|
+
// Refs don't trigger re-renders, so we poll on RAF to detect changes
|
|
82
|
+
// =========================================================================
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (containerRef.current && containerRef.current !== containerElement) {
|
|
85
|
+
setContainerElement(containerRef.current);
|
|
86
|
+
}
|
|
87
|
+
const rafId = requestAnimationFrame(() => {
|
|
88
|
+
if (containerRef.current !== containerElement) {
|
|
89
|
+
setContainerElement(containerRef.current);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return () => cancelAnimationFrame(rafId);
|
|
93
|
+
}, [containerElement]);
|
|
94
|
+
// =========================================================================
|
|
95
|
+
// Container Attachment & ResizeObserver
|
|
96
|
+
// =========================================================================
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!containerElement) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
engine.setContainer(containerElement, null, plotBounds);
|
|
102
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
const rect = entry.target.getBoundingClientRect();
|
|
105
|
+
engine.updateBounds(rect, plotBounds);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
resizeObserver.observe(containerElement);
|
|
109
|
+
return () => {
|
|
110
|
+
resizeObserver.disconnect();
|
|
111
|
+
};
|
|
112
|
+
}, [engine, containerElement, plotBounds]);
|
|
113
|
+
// =========================================================================
|
|
114
|
+
// Cleanup on Unmount
|
|
115
|
+
// =========================================================================
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
return () => {
|
|
118
|
+
engine.dispose();
|
|
119
|
+
};
|
|
120
|
+
}, [engine]);
|
|
121
|
+
// =========================================================================
|
|
122
|
+
// Return Value
|
|
123
|
+
// =========================================================================
|
|
124
|
+
return {
|
|
125
|
+
engine,
|
|
126
|
+
containerRef,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
import { Sensor } from "../../types/events";
|
|
2
|
-
import {
|
|
2
|
+
import { InteractionChannel } from "../../types/interaction";
|
|
3
3
|
export interface HoverSensorOptions {
|
|
4
|
-
/**
|
|
5
|
-
* How to find the target.
|
|
6
|
-
*/
|
|
7
|
-
mode: HoverMode;
|
|
8
4
|
/**
|
|
9
5
|
* Name for the interaction channel.
|
|
10
6
|
* Defaults to 'primary-hover'.
|
|
11
7
|
*/
|
|
12
8
|
name?: InteractionChannel | string;
|
|
9
|
+
/**
|
|
10
|
+
* When true, only fire when the pointer is directly over a tagged DOM element
|
|
11
|
+
* (DOM hit-test match). Proximal quadtree candidates that have no backing
|
|
12
|
+
* element are ignored. Useful for area-fill charts like treemaps where
|
|
13
|
+
* magnetic snapping across empty gaps is undesirable.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
exactHit?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* When true, all series at the primary candidate's X position are included
|
|
19
|
+
* as targets (vertical-slice behaviour). Useful for multi-series line/bar/area
|
|
20
|
+
* charts where series share the same X domain values.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
verticalSlice?: boolean;
|
|
13
24
|
}
|
|
14
25
|
/**
|
|
15
26
|
* The DataHoverSensor detects pointer movements over the chart plot
|
|
16
|
-
* and identifies the closest data targets
|
|
17
|
-
*
|
|
18
|
-
* It coordinates with the interaction store to trigger hover-based
|
|
19
|
-
* behaviors like tooltips and markers.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```tsx
|
|
23
|
-
* Root({ sensors: [DataHoverSensor()] })
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```tsx
|
|
28
|
-
* DataHoverSensor({ mode: 'exact' })
|
|
29
|
-
* ```
|
|
27
|
+
* and identifies the closest data targets.
|
|
30
28
|
*/
|
|
31
29
|
export declare const DataHoverSensor: (options?: HoverSensorOptions) => Sensor;
|
|
@@ -1,58 +1,45 @@
|
|
|
1
|
+
import { InputAction } from "../../engine/index.js";
|
|
1
2
|
import { InteractionChannel } from "../../types/interaction.js";
|
|
2
|
-
import { findClosestTargets } from "../utils/search.js";
|
|
3
3
|
/**
|
|
4
4
|
* The DataHoverSensor detects pointer movements over the chart plot
|
|
5
|
-
* and identifies the closest data targets
|
|
6
|
-
*
|
|
7
|
-
* It coordinates with the interaction store to trigger hover-based
|
|
8
|
-
* behaviors like tooltips and markers.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```tsx
|
|
12
|
-
* Root({ sensors: [DataHoverSensor()] })
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* DataHoverSensor({ mode: 'exact' })
|
|
18
|
-
* ```
|
|
5
|
+
* and identifies the closest data targets.
|
|
19
6
|
*/
|
|
20
|
-
export const DataHoverSensor = (options = {
|
|
21
|
-
const {
|
|
22
|
-
return (
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (targets.length > 0) {
|
|
31
|
-
const isTouch = "touches" in event.nativeEvent ||
|
|
32
|
-
"changedTouches" in event.nativeEvent;
|
|
33
|
-
upsertInteraction(name, {
|
|
34
|
-
pointer: {
|
|
35
|
-
x: event.coordinates.chartX,
|
|
36
|
-
y: event.coordinates.chartY,
|
|
37
|
-
containerX: event.coordinates.containerX,
|
|
38
|
-
containerY: event.coordinates.containerY,
|
|
39
|
-
isTouch,
|
|
40
|
-
},
|
|
41
|
-
targets,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
removeInteraction(name);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const handleLeave = () => {
|
|
7
|
+
export const DataHoverSensor = (options = {}) => {
|
|
8
|
+
const { name = InteractionChannel.PRIMARY_HOVER, exactHit = false, verticalSlice = false, } = options;
|
|
9
|
+
return (event, { upsertInteraction, removeInteraction }) => {
|
|
10
|
+
const { signal, primaryCandidate, sliceCandidates, chartX, chartY, isWithinPlot, } = event;
|
|
11
|
+
if (signal.action !== InputAction.MOVE &&
|
|
12
|
+
signal.action !== InputAction.CANCEL) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (signal.action === InputAction.CANCEL ||
|
|
16
|
+
(!isWithinPlot && signal.source !== "touch")) {
|
|
49
17
|
removeInteraction(name);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// When exactHit is true, discard proximal (quadtree-only) candidates that
|
|
21
|
+
// have no backing DOM element — the pointer is over empty space.
|
|
22
|
+
const candidate = exactHit && primaryCandidate && !primaryCandidate.element
|
|
23
|
+
? undefined
|
|
24
|
+
: primaryCandidate;
|
|
25
|
+
if (candidate) {
|
|
26
|
+
const isTouch = signal.source === "touch";
|
|
27
|
+
const targets = verticalSlice && sliceCandidates.length > 0
|
|
28
|
+
? sliceCandidates
|
|
29
|
+
: [candidate];
|
|
30
|
+
upsertInteraction(name, {
|
|
31
|
+
pointer: {
|
|
32
|
+
x: chartX,
|
|
33
|
+
y: chartY,
|
|
34
|
+
containerX: signal.x,
|
|
35
|
+
containerY: signal.y,
|
|
36
|
+
isTouch,
|
|
37
|
+
},
|
|
38
|
+
targets,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
removeInteraction(name);
|
|
43
|
+
}
|
|
57
44
|
};
|
|
58
45
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Sensor } from "../../types/events";
|
|
2
|
+
import { InteractionChannel } from "../../types/interaction";
|
|
3
|
+
export interface DragSensorOptions<T = any> {
|
|
4
|
+
/**
|
|
5
|
+
* Interaction channel name.
|
|
6
|
+
* @default InteractionChannel.DRAG
|
|
7
|
+
*/
|
|
8
|
+
name?: InteractionChannel | string;
|
|
9
|
+
/**
|
|
10
|
+
* Callback fired when a drag ends.
|
|
11
|
+
*/
|
|
12
|
+
onDragEnd?: (originalData: T, newValue: {
|
|
13
|
+
x: any;
|
|
14
|
+
y: any;
|
|
15
|
+
}, pixelPosition: {
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
|
+
}) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Callback fired during drag.
|
|
21
|
+
*/
|
|
22
|
+
onDrag?: (originalData: T, currentValue: {
|
|
23
|
+
x: any;
|
|
24
|
+
y: any;
|
|
25
|
+
}, pixelPosition: {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
}) => void;
|
|
29
|
+
/**
|
|
30
|
+
* Radius in pixels for hit detection.
|
|
31
|
+
* @default 20
|
|
32
|
+
*/
|
|
33
|
+
hitRadius?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* DragSensor enables dragging data points ("pucks") on a chart.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DragSensor: <T = any>(options?: DragSensorOptions<T>) => Sensor<T>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { InputAction } from "../../engine/index.js";
|
|
2
|
+
import { InteractionChannel, } from "../../types/interaction.js";
|
|
3
|
+
/**
|
|
4
|
+
* DragSensor enables dragging data points ("pucks") on a chart.
|
|
5
|
+
*/
|
|
6
|
+
export const DragSensor = (options = {}) => {
|
|
7
|
+
const { name = InteractionChannel.DRAG, onDragEnd, onDrag, hitRadius = 20, } = options;
|
|
8
|
+
let isDragging = false;
|
|
9
|
+
let dragTarget = null;
|
|
10
|
+
let startPosition = null;
|
|
11
|
+
return (event, { getChartContext, upsertInteraction, removeInteraction }) => {
|
|
12
|
+
const { signal, primaryCandidate, chartX, chartY } = event;
|
|
13
|
+
// 1. START DRAG
|
|
14
|
+
if (signal.action === InputAction.START) {
|
|
15
|
+
if (primaryCandidate && primaryCandidate.distance <= hitRadius) {
|
|
16
|
+
// Start dragging exact target
|
|
17
|
+
const target = primaryCandidate;
|
|
18
|
+
const initialData = target.data;
|
|
19
|
+
if (initialData) {
|
|
20
|
+
isDragging = true;
|
|
21
|
+
startPosition = { x: chartX, y: chartY };
|
|
22
|
+
const interactionTarget = {
|
|
23
|
+
data: initialData,
|
|
24
|
+
coordinate: target.coordinate,
|
|
25
|
+
seriesId: target.seriesId,
|
|
26
|
+
};
|
|
27
|
+
dragTarget = interactionTarget;
|
|
28
|
+
// Register interaction
|
|
29
|
+
const interaction = {
|
|
30
|
+
target: interactionTarget,
|
|
31
|
+
startPosition,
|
|
32
|
+
currentPosition: startPosition,
|
|
33
|
+
currentValue: { x: null, y: null },
|
|
34
|
+
isDragging: true,
|
|
35
|
+
};
|
|
36
|
+
upsertInteraction(name, interaction);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// 2. MOVE DRAG
|
|
42
|
+
if (signal.action === InputAction.MOVE) {
|
|
43
|
+
if (!isDragging || !dragTarget || !startPosition) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const currentPosition = { x: chartX, y: chartY };
|
|
47
|
+
const ctx = getChartContext();
|
|
48
|
+
if (!ctx || !ctx.chartStore) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const state = ctx.chartStore.getState();
|
|
52
|
+
const { x: xScale, y: yScale } = state.scales;
|
|
53
|
+
// Invert
|
|
54
|
+
let xValue = null;
|
|
55
|
+
let yValue = null;
|
|
56
|
+
if (xScale && "invert" in xScale) {
|
|
57
|
+
xValue = xScale.invert(currentPosition.x);
|
|
58
|
+
}
|
|
59
|
+
if (yScale && "invert" in yScale) {
|
|
60
|
+
yValue = yScale.invert(currentPosition.y);
|
|
61
|
+
}
|
|
62
|
+
const currentValue = { x: xValue, y: yValue };
|
|
63
|
+
// Update interaction
|
|
64
|
+
const interaction = {
|
|
65
|
+
target: dragTarget,
|
|
66
|
+
startPosition,
|
|
67
|
+
currentPosition,
|
|
68
|
+
currentValue,
|
|
69
|
+
isDragging: true,
|
|
70
|
+
};
|
|
71
|
+
upsertInteraction(name, interaction);
|
|
72
|
+
if (onDrag) {
|
|
73
|
+
onDrag(dragTarget.data, currentValue, currentPosition);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// 3. END / CANCEL DRAG
|
|
78
|
+
if (signal.action === InputAction.END ||
|
|
79
|
+
signal.action === InputAction.CANCEL) {
|
|
80
|
+
if (!isDragging || !dragTarget) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const finalPosition = { x: chartX, y: chartY };
|
|
84
|
+
const ctx = getChartContext();
|
|
85
|
+
const state = ctx.chartStore.getState();
|
|
86
|
+
const { x: xScale, y: yScale } = state.scales;
|
|
87
|
+
let xValue = null;
|
|
88
|
+
let yValue = null;
|
|
89
|
+
if (xScale && "invert" in xScale) {
|
|
90
|
+
xValue = xScale.invert(finalPosition.x);
|
|
91
|
+
}
|
|
92
|
+
if (yScale && "invert" in yScale) {
|
|
93
|
+
yValue = yScale.invert(finalPosition.y);
|
|
94
|
+
}
|
|
95
|
+
if (signal.action === InputAction.END && onDragEnd) {
|
|
96
|
+
onDragEnd(dragTarget.data, { x: xValue, y: yValue }, finalPosition);
|
|
97
|
+
}
|
|
98
|
+
// Cleanup
|
|
99
|
+
isDragging = false;
|
|
100
|
+
dragTarget = null;
|
|
101
|
+
startPosition = null;
|
|
102
|
+
removeInteraction(name);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DragSensor } from "./DragSensor.js";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { InputAction } from "../../engine/index.js";
|
|
2
|
+
import { resolveAccessor } from "../../types/accessors.js";
|
|
3
|
+
import { InteractionChannel } from "../../types/interaction.js";
|
|
4
|
+
/**
|
|
5
|
+
* Professional-grade Keyboard Sensor for A11y.
|
|
6
|
+
* Allows navigating data points using ArrowKeys.
|
|
7
|
+
*/
|
|
8
|
+
export const KeyboardSensor = (options = {}) => {
|
|
9
|
+
const { name = InteractionChannel.PRIMARY_HOVER } = options;
|
|
10
|
+
let focusedIndex = -1;
|
|
11
|
+
return (event, { getChartContext, upsertInteraction, removeInteraction }) => {
|
|
12
|
+
const { signal } = event;
|
|
13
|
+
// Only handle KEY actions
|
|
14
|
+
if (signal.action !== InputAction.KEY || !signal.key) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const ctx = getChartContext();
|
|
18
|
+
const { chartStore } = ctx;
|
|
19
|
+
const state = chartStore.getState();
|
|
20
|
+
const { data, scales, config } = state;
|
|
21
|
+
if (!data || data.length === 0 || !scales.x) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Update Focus
|
|
25
|
+
let changed = false;
|
|
26
|
+
if (signal.key === "ArrowRight") {
|
|
27
|
+
focusedIndex = Math.min(focusedIndex + 1, data.length - 1);
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
else if (signal.key === "ArrowLeft") {
|
|
31
|
+
focusedIndex = Math.max(focusedIndex - 1, 0);
|
|
32
|
+
changed = true;
|
|
33
|
+
if (focusedIndex < 0) {
|
|
34
|
+
focusedIndex = 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (signal.key === "Escape") {
|
|
38
|
+
focusedIndex = -1;
|
|
39
|
+
removeInteraction(name);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (changed && focusedIndex >= 0) {
|
|
43
|
+
const d = data[focusedIndex];
|
|
44
|
+
const { x: xScale, y: yScale } = scales;
|
|
45
|
+
// Resolve coordinates
|
|
46
|
+
// TODO: Accessor resolution should be centralized or consistent with Root
|
|
47
|
+
const xAcc = config.x || ((v) => v[0]); // fallback
|
|
48
|
+
const yAcc = config.y || ((v) => v[1]);
|
|
49
|
+
const getX = resolveAccessor(xAcc);
|
|
50
|
+
const getY = resolveAccessor(yAcc);
|
|
51
|
+
const xVal = getX(d);
|
|
52
|
+
const yVal = getY(d);
|
|
53
|
+
const xPos = xScale(xVal) || 0;
|
|
54
|
+
const yPos = yScale(yVal) || 0;
|
|
55
|
+
// Query Engine for targets at this location (Vertical Slice)
|
|
56
|
+
// We use a small search radius to mimic "nearest" behavior if needed,
|
|
57
|
+
// but typically we want everything at this X.
|
|
58
|
+
// The spatial index might be quadtree.
|
|
59
|
+
// For now, let's use the primary data point we just found as the target
|
|
60
|
+
// + any others the engine finds nearby.
|
|
61
|
+
// Simplest interaction: Just highlight the data point we navigated to.
|
|
62
|
+
// If we want multi-series, we should query engine.
|
|
63
|
+
// const candidates = engine.spatialIndex?.find(xPos, yPos, 10) || [];
|
|
64
|
+
// Construct target manually since we know the data point
|
|
65
|
+
const primaryTarget = {
|
|
66
|
+
type: "data-point",
|
|
67
|
+
data: d,
|
|
68
|
+
seriesId: "default", // TODO: Multi-series support
|
|
69
|
+
dataIndex: focusedIndex,
|
|
70
|
+
coordinate: { x: xPos, y: yPos },
|
|
71
|
+
distance: 0,
|
|
72
|
+
};
|
|
73
|
+
upsertInteraction(name, {
|
|
74
|
+
pointer: {
|
|
75
|
+
x: xPos,
|
|
76
|
+
y: yPos,
|
|
77
|
+
containerX: xPos + state.dimensions.margin.left,
|
|
78
|
+
containerY: yPos + state.dimensions.margin.top,
|
|
79
|
+
isTouch: false,
|
|
80
|
+
},
|
|
81
|
+
targets: [primaryTarget], // Cast to InteractionTarget
|
|
82
|
+
target: primaryTarget,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./KeyboardSensor";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./KeyboardSensor.js";
|
package/dist/components/Chart/sensors/{SelectionSensor.d.ts → SelectionSensor/SelectionSensor.d.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Sensor } from "
|
|
1
|
+
import { Sensor } from "../../types/events";
|
|
2
2
|
/**
|
|
3
3
|
* Professional-grade Selection Sensor.
|
|
4
|
-
* Coordinates with
|
|
4
|
+
* Coordinates with Engine to choose data points on click/start.
|
|
5
5
|
*/
|
|
6
6
|
export declare const SelectionSensor: (options?: {
|
|
7
7
|
name?: string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { InputAction } from "../../engine/index.js";
|
|
2
|
+
import { InteractionChannel } from "../../types/interaction.js";
|
|
3
|
+
/**
|
|
4
|
+
* Professional-grade Selection Sensor.
|
|
5
|
+
* Coordinates with Engine to choose data points on click/start.
|
|
6
|
+
*/
|
|
7
|
+
export const SelectionSensor = (options = {}) => {
|
|
8
|
+
const { name = InteractionChannel.SELECTION } = options;
|
|
9
|
+
return ({ signal, primaryCandidate }, { getChartContext, upsertInteraction }) => {
|
|
10
|
+
// Only handle START action (roughly equivalent to mouse down)
|
|
11
|
+
if (signal.action !== InputAction.START) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!primaryCandidate) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const ctx = getChartContext();
|
|
18
|
+
const { chartStore } = ctx;
|
|
19
|
+
const state = chartStore.getState();
|
|
20
|
+
const selectedDatum = primaryCandidate.data;
|
|
21
|
+
const currentInteraction = state.interactions.get(name);
|
|
22
|
+
const currentSelection = (currentInteraction === null || currentInteraction === void 0 ? void 0 : currentInteraction.selection) || [];
|
|
23
|
+
// Simple toggle logic
|
|
24
|
+
// TODO: support multi-select with shift key?
|
|
25
|
+
// signal.originalEvent is the native event if we need modifiers.
|
|
26
|
+
const isAlreadySelected = currentSelection.includes(selectedDatum);
|
|
27
|
+
let nextSelection;
|
|
28
|
+
if (isAlreadySelected) {
|
|
29
|
+
nextSelection = currentSelection.filter((d) => d !== selectedDatum);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
nextSelection = [...currentSelection, selectedDatum];
|
|
33
|
+
}
|
|
34
|
+
upsertInteraction(name, {
|
|
35
|
+
selection: nextSelection,
|
|
36
|
+
mode: "discrete", // or "continuous"
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SelectionSensor";
|