@ctrl-ui/react 0.9.5 → 0.9.6
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/README.md +2 -2
- package/dist/action-bar.js +16 -8
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +3 -1
- package/dist/audio-visualizer.js +1 -1
- package/dist/blocks/agent-run-clock.d.ts +5 -0
- package/dist/blocks/agent-run-clock.js +52 -0
- package/dist/blocks/agent-run-sidebar.d.ts +13 -0
- package/dist/blocks/agent-run-sidebar.js +100 -0
- package/dist/blocks/agent-run-status.d.ts +32 -0
- package/dist/blocks/agent-run-status.js +27 -0
- package/dist/blocks/agent-run.d.ts +80 -0
- package/dist/blocks/agent-run.js +254 -0
- package/dist/blocks/coding-agent.d.ts +1 -1
- package/dist/blocks/design-canvas-data.d.ts +1 -1
- package/dist/blocks/design-canvas.d.ts +2 -1
- package/dist/blocks/design-canvas.js +15 -8
- package/dist/chat-message.js +33 -39
- package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
- package/dist/knob-contracts/infinite-canvas-knobs.js +1 -7
- package/dist/knob-contracts/swap-knobs.d.ts +4 -0
- package/dist/knob-contracts/swap-knobs.js +9 -0
- package/dist/knob-contracts/timeline-knobs.d.ts +1 -1
- package/dist/knob-contracts/timeline-knobs.js +1 -0
- package/dist/knob-contracts/tool-approval-knobs.d.ts +4 -0
- package/dist/knob-contracts/tool-approval-knobs.js +32 -0
- package/dist/lib/infinite-canvas-camera-store.d.ts +65 -0
- package/dist/lib/infinite-canvas-camera-store.js +233 -0
- package/dist/lib/infinite-canvas-camera.d.ts +33 -0
- package/dist/lib/infinite-canvas-camera.js +98 -0
- package/dist/lib/infinite-canvas-navigation.d.ts +19 -0
- package/dist/lib/infinite-canvas-navigation.js +395 -0
- package/dist/markdown-editor/bubble-menu.d.ts +6 -0
- package/dist/markdown-editor/bubble-menu.js +45 -0
- package/dist/markdown-editor/toolbar.d.ts +17 -1
- package/dist/markdown-editor/toolbar.js +103 -81
- package/dist/styles/index.css +10 -8
- package/dist/styles/recipes/activity.css +8 -0
- package/dist/styles/recipes/button-group.css +12 -6
- package/dist/styles/recipes/button.css +12 -2
- package/dist/styles/recipes/chat-message-edit.css +6 -21
- package/dist/styles/recipes/infinite-canvas.css +17 -57
- package/dist/styles/recipes/markdown-editor.css +12 -0
- package/dist/styles/recipes/swap.css +143 -0
- package/dist/styles/recipes/timeline.css +11 -0
- package/dist/styles/recipes/tool-approval.css +267 -0
- package/dist/styles/recipes/track-highlight.css +19 -6
- package/dist/styles/theme.css +6 -5
- package/dist/styles/type-scale.css +1 -1
- package/dist/tool-approval.d.ts +49 -0
- package/dist/tool-approval.js +260 -0
- package/dist/ui/code.js +13 -3
- package/dist/ui/command.js +1 -0
- package/dist/ui/infinite-canvas.d.ts +19 -22
- package/dist/ui/infinite-canvas.js +264 -325
- package/dist/ui/rich-tooltip.d.ts +2 -1
- package/dist/ui/rich-tooltip.js +32 -29
- package/dist/ui/swap.d.ts +14 -0
- package/dist/ui/swap.js +69 -0
- package/dist/ui/timeline.d.ts +1 -1
- package/dist/ui/timeline.js +1 -1
- package/dist/user-ask.js +1 -1
- package/lint/no-raw-type-classes.grit +2 -2
- package/package.json +2 -2
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { Button } from "./button.js";
|
|
4
|
-
import "../lib/canvas-
|
|
5
|
-
import {
|
|
4
|
+
import { createInfiniteCanvasCamera, infiniteCanvasGridStyle, infiniteCanvasTransformCss } from "../lib/infinite-canvas-camera-store.js";
|
|
5
|
+
import { ARROW_KEY_DIRECTIONS, INFINITE_CANVAS_KEY_SHORTCUTS, attachInfiniteCanvasNavigation, keyboardStep, runInfiniteCanvasKeyCommand } from "../lib/infinite-canvas-navigation.js";
|
|
6
|
+
import { Toolbar, ToolbarButton, ToolbarSeparator } from "./toolbar.js";
|
|
7
|
+
import { createContext, use, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
8
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
6
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { MinusIcon, PlusIcon } from "lucide-react";
|
|
10
|
+
import { GripVerticalIcon, MaximizeIcon, MinusIcon, PlusIcon } from "lucide-react";
|
|
8
11
|
//#region src/ui/infinite-canvas.tsx
|
|
9
12
|
const DEFAULT_TRANSFORM = {
|
|
10
13
|
x: 0,
|
|
@@ -13,386 +16,322 @@ const DEFAULT_TRANSFORM = {
|
|
|
13
16
|
};
|
|
14
17
|
const DEFAULT_MIN_SCALE = .35;
|
|
15
18
|
const DEFAULT_MAX_SCALE = 2.5;
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const MAX_WHEEL_ZOOM_DELTA = 50;
|
|
19
|
-
const CANVAS_KEY_SHORTCUTS = "ArrowLeft ArrowRight ArrowUp ArrowDown + - 0";
|
|
20
|
-
const NOTCHED_WHEEL_DELTA = 40;
|
|
21
|
-
const KEYBOARD_PAN_DIRECTIONS = {
|
|
22
|
-
ArrowLeft: {
|
|
23
|
-
x: -1,
|
|
24
|
-
y: 0
|
|
25
|
-
},
|
|
26
|
-
ArrowRight: {
|
|
27
|
-
x: 1,
|
|
28
|
-
y: 0
|
|
29
|
-
},
|
|
30
|
-
ArrowUp: {
|
|
31
|
-
x: 0,
|
|
32
|
-
y: -1
|
|
33
|
-
},
|
|
34
|
-
ArrowDown: {
|
|
35
|
-
x: 0,
|
|
36
|
-
y: 1
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
const KEYBOARD_ZOOM_FACTORS = {
|
|
40
|
-
"+": 1.15,
|
|
41
|
-
"=": 1.15,
|
|
42
|
-
"-": 1 / 1.15
|
|
43
|
-
};
|
|
44
|
-
function keyboardPanStep(shiftKey) {
|
|
45
|
-
return shiftKey ? 96 : KEYBOARD_PAN_STEP;
|
|
46
|
-
}
|
|
19
|
+
const CONTROL_ZOOM_FACTOR = 1.2;
|
|
20
|
+
const ITEM_KEY_SHORTCUTS = "ArrowLeft ArrowRight ArrowUp ArrowDown";
|
|
47
21
|
const InfiniteCanvasContext = createContext(null);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const worldX = (point.x - transform.x) / transform.scale;
|
|
53
|
-
const worldY = (point.y - transform.y) / transform.scale;
|
|
54
|
-
return {
|
|
55
|
-
x: point.x - worldX * nextScale,
|
|
56
|
-
y: point.y - worldY * nextScale,
|
|
57
|
-
scale: nextScale
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function wheelDeltaMultiplier(event, viewportHeight) {
|
|
61
|
-
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) return 16;
|
|
62
|
-
if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) return viewportHeight;
|
|
63
|
-
return 1;
|
|
64
|
-
}
|
|
65
|
-
function panFromWheel(transform, event, viewportHeight) {
|
|
66
|
-
const deltaMultiplier = wheelDeltaMultiplier(event, viewportHeight);
|
|
67
|
-
const horizontalDelta = event.shiftKey && event.deltaX === 0 ? event.deltaY : event.deltaX;
|
|
68
|
-
const verticalDelta = event.shiftKey && event.deltaX === 0 ? 0 : event.deltaY;
|
|
69
|
-
return {
|
|
70
|
-
x: transform.x - horizontalDelta * deltaMultiplier,
|
|
71
|
-
y: transform.y - verticalDelta * deltaMultiplier,
|
|
72
|
-
scale: transform.scale
|
|
73
|
-
};
|
|
22
|
+
const InfiniteCanvasStoreContext = createContext(null);
|
|
23
|
+
const InfiniteCanvasItemContext = createContext(null);
|
|
24
|
+
function selectScale(transform) {
|
|
25
|
+
return transform.scale;
|
|
74
26
|
}
|
|
75
27
|
function useInfiniteCanvas() {
|
|
76
28
|
const context = use(InfiniteCanvasContext);
|
|
77
29
|
if (!context) throw new Error("useInfiniteCanvas must be used within InfiniteCanvas");
|
|
78
30
|
return context;
|
|
79
31
|
}
|
|
80
|
-
function
|
|
32
|
+
function useInfiniteCanvasStore() {
|
|
33
|
+
const context = use(InfiniteCanvasStoreContext);
|
|
34
|
+
if (!context) throw new Error("InfiniteCanvas parts must be used within InfiniteCanvas");
|
|
35
|
+
return context;
|
|
36
|
+
}
|
|
37
|
+
function useInfiniteCanvasTransform(select) {
|
|
38
|
+
const { store, initialTransform } = useInfiniteCanvasStore();
|
|
39
|
+
return useSyncExternalStore(store.subscribe, () => select ? select(store.getCommittedTransform()) : store.getCommittedTransform(), () => select ? select(initialTransform) : initialTransform);
|
|
40
|
+
}
|
|
41
|
+
function InfiniteCanvas({ transform, defaultTransform = DEFAULT_TRANSFORM, onTransformChange, minScale: minScaleProp = DEFAULT_MIN_SCALE, maxScale: maxScaleProp = DEFAULT_MAX_SCALE, panOnDrag = "background", keyboardNavigation = true, ref, className, children, style, onPointerDown, onWheel, onKeyDown, "aria-label": ariaLabel = "Infinite canvas", "aria-keyshortcuts": ariaKeyShortcuts, ...props }) {
|
|
81
42
|
const minScale = minScaleProp > 0 ? minScaleProp : DEFAULT_MIN_SCALE;
|
|
82
43
|
const maxScale = Math.max(minScale, maxScaleProp);
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
44
|
+
const cameraOptions = {
|
|
45
|
+
minScale,
|
|
46
|
+
maxScale,
|
|
47
|
+
defaultTransform,
|
|
48
|
+
controlled: transform !== void 0,
|
|
49
|
+
onTransformChange
|
|
50
|
+
};
|
|
51
|
+
const [initialTransform] = useState(() => transform ?? defaultTransform);
|
|
52
|
+
const [store] = useState(() => createInfiniteCanvasCamera(initialTransform, cameraOptions));
|
|
87
53
|
const rootRef = useRef(null);
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
onTransformChange?.(bounded, { reason });
|
|
102
|
-
}
|
|
103
|
-
function zoomBy(factor, reason) {
|
|
104
|
-
const currentTransform = transformRef.current;
|
|
105
|
-
const nextScale = clampScale(currentTransform.scale * factor, minScale, maxScale);
|
|
106
|
-
const root = rootRef.current;
|
|
107
|
-
commitTransform(zoomAroundPoint(currentTransform, nextScale, root ? {
|
|
108
|
-
x: root.clientWidth / 2,
|
|
109
|
-
y: root.clientHeight / 2
|
|
110
|
-
} : {
|
|
111
|
-
x: 0,
|
|
112
|
-
y: 0
|
|
113
|
-
}), reason);
|
|
114
|
-
}
|
|
115
|
-
function reset(reason) {
|
|
116
|
-
commitTransform(defaultTransform, reason);
|
|
117
|
-
}
|
|
118
|
-
function beginPan(event) {
|
|
119
|
-
onPointerDown?.(event);
|
|
120
|
-
if (event.defaultPrevented || event.target !== event.currentTarget || !event.isPrimary || event.button !== 0) return;
|
|
121
|
-
event.preventDefault();
|
|
122
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
123
|
-
panRef.current = {
|
|
124
|
-
pointerId: event.pointerId,
|
|
125
|
-
startPointer: {
|
|
126
|
-
x: event.clientX,
|
|
127
|
-
y: event.clientY
|
|
128
|
-
},
|
|
129
|
-
startTransform: transformRef.current
|
|
54
|
+
const navigationRef = useRef(null);
|
|
55
|
+
const navigationOptionsRef = useRef({
|
|
56
|
+
panOnDrag,
|
|
57
|
+
onWheel
|
|
58
|
+
});
|
|
59
|
+
const controlledX = transform?.x;
|
|
60
|
+
const controlledY = transform?.y;
|
|
61
|
+
const controlledScale = transform?.scale;
|
|
62
|
+
useLayoutEffect(() => {
|
|
63
|
+
store.configure(cameraOptions);
|
|
64
|
+
navigationOptionsRef.current = {
|
|
65
|
+
panOnDrag,
|
|
66
|
+
onWheel
|
|
130
67
|
};
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
function updatePan(event) {
|
|
134
|
-
onPointerMove?.(event);
|
|
135
|
-
if (event.defaultPrevented) return;
|
|
136
|
-
const pan = panRef.current;
|
|
137
|
-
if (!pan || pan.pointerId !== event.pointerId) return;
|
|
138
|
-
commitTransform({
|
|
139
|
-
x: pan.startTransform.x + event.clientX - pan.startPointer.x,
|
|
140
|
-
y: pan.startTransform.y + event.clientY - pan.startPointer.y,
|
|
141
|
-
scale: pan.startTransform.scale
|
|
142
|
-
}, "pointer");
|
|
143
|
-
}
|
|
144
|
-
function finishPan(event) {
|
|
145
|
-
const pan = panRef.current;
|
|
146
|
-
if (!pan || pan.pointerId !== event.pointerId) return;
|
|
147
|
-
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
148
|
-
panRef.current = null;
|
|
149
|
-
setPanning(false);
|
|
150
|
-
}
|
|
151
|
-
function endPan(event) {
|
|
152
|
-
onPointerUp?.(event);
|
|
153
|
-
if (!event.defaultPrevented) finishPan(event);
|
|
154
|
-
}
|
|
155
|
-
function cancelPan(event) {
|
|
156
|
-
onPointerCancel?.(event);
|
|
157
|
-
if (!event.defaultPrevented) finishPan(event);
|
|
158
|
-
}
|
|
159
|
-
const handleWheel = useEffectEvent((event) => {
|
|
160
|
-
onWheel?.(event);
|
|
161
|
-
if (event.defaultPrevented) return;
|
|
162
|
-
const root = rootRef.current;
|
|
163
|
-
if (!root) return;
|
|
164
|
-
event.preventDefault();
|
|
165
|
-
const currentTransform = transformRef.current;
|
|
166
|
-
if (event.ctrlKey || event.metaKey) {
|
|
167
|
-
const bounds = root.getBoundingClientRect();
|
|
168
|
-
const point = {
|
|
169
|
-
x: event.clientX - bounds.left,
|
|
170
|
-
y: event.clientY - bounds.top
|
|
171
|
-
};
|
|
172
|
-
const zoomDelta = event.deltaY * wheelDeltaMultiplier(event, root.clientHeight);
|
|
173
|
-
const boundedZoomDelta = Math.min(Math.max(zoomDelta, -50), MAX_WHEEL_ZOOM_DELTA);
|
|
174
|
-
const nextScale = clampScale(currentTransform.scale * Math.exp(-boundedZoomDelta * WHEEL_ZOOM_SPEED), minScale, maxScale);
|
|
175
|
-
const notchedWheel = Math.abs(zoomDelta) >= NOTCHED_WHEEL_DELTA;
|
|
176
|
-
commitTransform(zoomAroundPoint(currentTransform, nextScale, point), "wheel", notchedWheel);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
commitTransform(panFromWheel(currentTransform, event, root.clientHeight), "wheel");
|
|
68
|
+
navigationRef.current?.syncPanReady();
|
|
180
69
|
});
|
|
70
|
+
useLayoutEffect(() => {
|
|
71
|
+
if (controlledX === void 0 || controlledY === void 0 || controlledScale === void 0) return;
|
|
72
|
+
store.adopt({
|
|
73
|
+
x: controlledX,
|
|
74
|
+
y: controlledY,
|
|
75
|
+
scale: controlledScale
|
|
76
|
+
});
|
|
77
|
+
}, [
|
|
78
|
+
store,
|
|
79
|
+
controlledX,
|
|
80
|
+
controlledY,
|
|
81
|
+
controlledScale
|
|
82
|
+
]);
|
|
181
83
|
useEffect(() => {
|
|
182
84
|
const root = rootRef.current;
|
|
183
85
|
if (!root) return;
|
|
184
|
-
|
|
185
|
-
root
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
86
|
+
store.attachRoot(root);
|
|
87
|
+
const navigation = attachInfiniteCanvasNavigation(root, store, () => navigationOptionsRef.current);
|
|
88
|
+
navigationRef.current = navigation;
|
|
89
|
+
return () => {
|
|
90
|
+
navigation.detach();
|
|
91
|
+
navigationRef.current = null;
|
|
92
|
+
store.dispose();
|
|
93
|
+
store.attachRoot(null);
|
|
94
|
+
};
|
|
95
|
+
}, [store]);
|
|
96
|
+
const context = useMemo(() => ({
|
|
97
|
+
...store.camera,
|
|
98
|
+
minScale,
|
|
99
|
+
maxScale
|
|
100
|
+
}), [
|
|
101
|
+
store,
|
|
102
|
+
minScale,
|
|
103
|
+
maxScale
|
|
104
|
+
]);
|
|
105
|
+
const storeContext = useMemo(() => ({
|
|
106
|
+
store,
|
|
107
|
+
initialTransform
|
|
108
|
+
}), [store, initialTransform]);
|
|
109
|
+
function beginBackgroundPan(event) {
|
|
110
|
+
onPointerDown?.(event);
|
|
111
|
+
navigationRef.current?.beginBackgroundPan(event.nativeEvent);
|
|
208
112
|
}
|
|
209
|
-
function
|
|
113
|
+
function navigateWithKeyboard(event) {
|
|
210
114
|
onKeyDown?.(event);
|
|
211
|
-
if (event.defaultPrevented || event.target !== event.currentTarget) return;
|
|
115
|
+
if (!keyboardNavigation || event.defaultPrevented || event.target !== event.currentTarget) return;
|
|
212
116
|
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
213
|
-
if (
|
|
117
|
+
if (runInfiniteCanvasKeyCommand(store, event.key, event.shiftKey)) event.preventDefault();
|
|
214
118
|
}
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
};
|
|
222
|
-
const canvasStyle = {
|
|
223
|
-
...style,
|
|
224
|
-
backgroundPosition: `${transform.x}px ${transform.y}px`,
|
|
225
|
-
backgroundSize: `${24 * transform.scale}px ${24 * transform.scale}px`
|
|
226
|
-
};
|
|
227
|
-
return /* @__PURE__ */ jsx(InfiniteCanvasContext.Provider, {
|
|
228
|
-
value: context,
|
|
229
|
-
children: /* @__PURE__ */ jsx("section", {
|
|
230
|
-
ref: rootRef,
|
|
119
|
+
const keyShortcuts = [keyboardNavigation ? INFINITE_CANVAS_KEY_SHORTCUTS : "", ariaKeyShortcuts].filter(Boolean).join(" ");
|
|
120
|
+
const root = useRender({
|
|
121
|
+
defaultTagName: "section",
|
|
122
|
+
ref: [ref ?? null, rootRef],
|
|
123
|
+
props: {
|
|
124
|
+
tabIndex: 0,
|
|
231
125
|
...props,
|
|
232
126
|
"data-control-ui": "infinite-canvas",
|
|
233
127
|
"data-control-family": "infinite-canvas",
|
|
234
128
|
"data-slot": "root",
|
|
235
|
-
"data-panning": panning || void 0,
|
|
236
|
-
"data-easing": easing || void 0,
|
|
237
129
|
"aria-label": ariaLabel,
|
|
238
130
|
role: "application",
|
|
239
|
-
"aria-keyshortcuts":
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
onKeyDown: moveWithKeyboard,
|
|
131
|
+
"aria-keyshortcuts": keyShortcuts || void 0,
|
|
132
|
+
className: cn("relative isolate overflow-hidden overscroll-none touch-none data-panning:select-none", className),
|
|
133
|
+
style: {
|
|
134
|
+
...style,
|
|
135
|
+
...infiniteCanvasGridStyle(initialTransform)
|
|
136
|
+
},
|
|
137
|
+
onPointerDown: beginBackgroundPan,
|
|
138
|
+
onKeyDown: navigateWithKeyboard,
|
|
248
139
|
children
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return /* @__PURE__ */ jsx(InfiniteCanvasStoreContext.Provider, {
|
|
143
|
+
value: storeContext,
|
|
144
|
+
children: /* @__PURE__ */ jsx(InfiniteCanvasContext.Provider, {
|
|
145
|
+
value: context,
|
|
146
|
+
children: root
|
|
249
147
|
})
|
|
250
148
|
});
|
|
251
149
|
}
|
|
252
150
|
function InfiniteCanvasContent({ className, style, ...props }) {
|
|
253
|
-
const {
|
|
151
|
+
const { store, initialTransform } = useInfiniteCanvasStore();
|
|
254
152
|
return /* @__PURE__ */ jsx("div", {
|
|
153
|
+
ref: store.attachContent,
|
|
255
154
|
...props,
|
|
256
155
|
"data-control-ui": "infinite-canvas",
|
|
257
156
|
"data-control-family": "infinite-canvas",
|
|
258
157
|
"data-slot": "content",
|
|
259
|
-
"data-scale":
|
|
260
|
-
className: cn("
|
|
158
|
+
"data-scale": initialTransform.scale,
|
|
159
|
+
className: cn("absolute top-0 left-0 h-0 w-0", className),
|
|
261
160
|
style: {
|
|
262
161
|
...style,
|
|
263
|
-
transform:
|
|
162
|
+
transform: infiniteCanvasTransformCss(initialTransform)
|
|
264
163
|
}
|
|
265
164
|
});
|
|
266
165
|
}
|
|
267
|
-
function
|
|
268
|
-
const
|
|
166
|
+
function useInfiniteCanvasItemDrag(position, onPositionChange, handlers) {
|
|
167
|
+
const canvas = useInfiniteCanvas();
|
|
269
168
|
const dragRef = useRef(null);
|
|
270
169
|
const [dragging, setDragging] = useState(false);
|
|
271
|
-
function beginDrag(event) {
|
|
272
|
-
onPointerDown?.(event);
|
|
273
|
-
if (event.defaultPrevented || !onPositionChange || !event.isPrimary || event.button !== 0) return;
|
|
274
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
275
|
-
dragRef.current = {
|
|
276
|
-
pointerId: event.pointerId,
|
|
277
|
-
startPointer: {
|
|
278
|
-
x: event.clientX,
|
|
279
|
-
y: event.clientY
|
|
280
|
-
},
|
|
281
|
-
startPosition: {
|
|
282
|
-
x,
|
|
283
|
-
y
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
setDragging(true);
|
|
287
|
-
}
|
|
288
|
-
function updateDrag(event) {
|
|
289
|
-
onPointerMove?.(event);
|
|
290
|
-
const drag = dragRef.current;
|
|
291
|
-
if (event.defaultPrevented || !drag || drag.pointerId !== event.pointerId) return;
|
|
292
|
-
onPositionChange?.({
|
|
293
|
-
x: drag.startPosition.x + (event.clientX - drag.startPointer.x) / transform.scale,
|
|
294
|
-
y: drag.startPosition.y + (event.clientY - drag.startPointer.y) / transform.scale
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
170
|
function finishDrag(event) {
|
|
298
171
|
if (dragRef.current?.pointerId !== event.pointerId) return;
|
|
299
172
|
if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
|
|
300
173
|
dragRef.current = null;
|
|
301
174
|
setDragging(false);
|
|
302
175
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
176
|
+
return {
|
|
177
|
+
dragging,
|
|
178
|
+
dragHandlers: {
|
|
179
|
+
onPointerDown(event) {
|
|
180
|
+
handlers.onPointerDown?.(event);
|
|
181
|
+
if (event.defaultPrevented || !onPositionChange || !event.isPrimary || event.button !== 0) return;
|
|
182
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
183
|
+
dragRef.current = {
|
|
184
|
+
pointerId: event.pointerId,
|
|
185
|
+
startWorld: canvas.toWorldPoint(event),
|
|
186
|
+
startPosition: position
|
|
187
|
+
};
|
|
188
|
+
setDragging(true);
|
|
189
|
+
},
|
|
190
|
+
onPointerMove(event) {
|
|
191
|
+
handlers.onPointerMove?.(event);
|
|
192
|
+
const drag = dragRef.current;
|
|
193
|
+
if (event.defaultPrevented || drag?.pointerId !== event.pointerId) return;
|
|
194
|
+
const world = canvas.toWorldPoint(event);
|
|
195
|
+
onPositionChange?.({
|
|
196
|
+
x: drag.startPosition.x + world.x - drag.startWorld.x,
|
|
197
|
+
y: drag.startPosition.y + world.y - drag.startWorld.y
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
onPointerUp(event) {
|
|
201
|
+
handlers.onPointerUp?.(event);
|
|
202
|
+
finishDrag(event);
|
|
203
|
+
},
|
|
204
|
+
onPointerCancel(event) {
|
|
205
|
+
handlers.onPointerCancel?.(event);
|
|
206
|
+
finishDrag(event);
|
|
207
|
+
},
|
|
208
|
+
onLostPointerCapture(event) {
|
|
209
|
+
handlers.onLostPointerCapture?.(event);
|
|
210
|
+
finishDrag(event);
|
|
211
|
+
},
|
|
212
|
+
onKeyDown(event) {
|
|
213
|
+
handlers.onKeyDown?.(event);
|
|
214
|
+
if (event.defaultPrevented || !onPositionChange || event.target !== event.currentTarget) return;
|
|
215
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
216
|
+
const direction = ARROW_KEY_DIRECTIONS[event.key];
|
|
217
|
+
if (!direction) return;
|
|
218
|
+
event.preventDefault();
|
|
219
|
+
const step = keyboardStep(event.shiftKey) / canvas.getTransform().scale;
|
|
220
|
+
onPositionChange({
|
|
221
|
+
x: position.x + direction.x * step,
|
|
222
|
+
y: position.y + direction.y * step
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function InfiniteCanvasItem({ x, y, onPositionChange, movableItemLabel = "Movable item", className, style, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onLostPointerCapture, onKeyDown, ...props }) {
|
|
229
|
+
const position = useMemo(() => ({
|
|
230
|
+
x,
|
|
231
|
+
y
|
|
232
|
+
}), [x, y]);
|
|
233
|
+
const { dragging, dragHandlers } = useInfiniteCanvasItemDrag(position, onPositionChange, {
|
|
234
|
+
onPointerDown,
|
|
235
|
+
onPointerMove,
|
|
236
|
+
onPointerUp,
|
|
237
|
+
onPointerCancel,
|
|
238
|
+
onLostPointerCapture,
|
|
239
|
+
onKeyDown
|
|
240
|
+
});
|
|
241
|
+
const movable = onPositionChange ? {
|
|
242
|
+
role: "group",
|
|
243
|
+
"aria-roledescription": movableItemLabel,
|
|
244
|
+
"aria-keyshortcuts": ITEM_KEY_SHORTCUTS,
|
|
245
|
+
tabIndex: 0
|
|
246
|
+
} : null;
|
|
247
|
+
return /* @__PURE__ */ jsx(InfiniteCanvasItemContext.Provider, {
|
|
248
|
+
value: position,
|
|
249
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
250
|
+
...movable,
|
|
251
|
+
...props,
|
|
252
|
+
...dragHandlers,
|
|
253
|
+
"data-control-ui": "infinite-canvas",
|
|
254
|
+
"data-control-family": "infinite-canvas",
|
|
255
|
+
"data-slot": "item",
|
|
256
|
+
"data-dragging": dragging || void 0,
|
|
257
|
+
className: cn("absolute", onPositionChange && "touch-none select-none", className),
|
|
258
|
+
style: {
|
|
259
|
+
...style,
|
|
260
|
+
left: x,
|
|
261
|
+
top: y
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function InfiniteCanvasItemHandle({ onPositionChange, className, children, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onLostPointerCapture, onKeyDown, "aria-label": ariaLabel = "Move item", ...props }) {
|
|
267
|
+
const position = use(InfiniteCanvasItemContext);
|
|
268
|
+
if (!position) throw new Error("InfiniteCanvasItemHandle must be used within InfiniteCanvasItem");
|
|
269
|
+
const { dragging, dragHandlers } = useInfiniteCanvasItemDrag(position, onPositionChange, {
|
|
270
|
+
onPointerDown,
|
|
271
|
+
onPointerMove,
|
|
272
|
+
onPointerUp,
|
|
273
|
+
onPointerCancel,
|
|
274
|
+
onLostPointerCapture,
|
|
275
|
+
onKeyDown
|
|
276
|
+
});
|
|
277
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
278
|
+
type: "button",
|
|
279
|
+
variant: "ghost",
|
|
280
|
+
size: "xs",
|
|
281
|
+
iconOnly: true,
|
|
282
|
+
"aria-label": ariaLabel,
|
|
283
|
+
"aria-keyshortcuts": ITEM_KEY_SHORTCUTS,
|
|
342
284
|
...props,
|
|
343
|
-
|
|
344
|
-
"data-control-family": "infinite-canvas",
|
|
345
|
-
"data-slot": "item",
|
|
285
|
+
...dragHandlers,
|
|
346
286
|
"data-dragging": dragging || void 0,
|
|
347
|
-
className: cn("
|
|
348
|
-
|
|
349
|
-
...style,
|
|
350
|
-
left: x,
|
|
351
|
-
top: y
|
|
352
|
-
}
|
|
287
|
+
className: cn("touch-none", className),
|
|
288
|
+
children: children ?? /* @__PURE__ */ jsx(GripVerticalIcon, {})
|
|
353
289
|
});
|
|
354
290
|
}
|
|
355
291
|
function InfiniteCanvasControls({ className, ...props }) {
|
|
356
292
|
const canvas = useInfiniteCanvas();
|
|
357
|
-
const
|
|
358
|
-
|
|
293
|
+
const scale = useInfiniteCanvasTransform(selectScale);
|
|
294
|
+
const percentage = Math.round(scale * 100);
|
|
295
|
+
return /* @__PURE__ */ jsx("div", {
|
|
359
296
|
"data-control-ui": "infinite-canvas",
|
|
360
297
|
"data-control-family": "infinite-canvas",
|
|
361
298
|
"data-slot": "controls",
|
|
362
|
-
className: cn("absolute z-40
|
|
299
|
+
className: cn("absolute z-40", className),
|
|
363
300
|
...props,
|
|
364
|
-
children:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
301
|
+
children: /* @__PURE__ */ jsxs(Toolbar, {
|
|
302
|
+
variant: "floating",
|
|
303
|
+
"aria-label": "Canvas view",
|
|
304
|
+
children: [
|
|
305
|
+
/* @__PURE__ */ jsx(ToolbarButton, {
|
|
306
|
+
iconOnly: true,
|
|
307
|
+
disabled: scale <= canvas.minScale,
|
|
308
|
+
"aria-label": "Zoom out",
|
|
309
|
+
onClick: () => canvas.zoomBy(1 / CONTROL_ZOOM_FACTOR),
|
|
310
|
+
children: /* @__PURE__ */ jsx(MinusIcon, {})
|
|
311
|
+
}),
|
|
312
|
+
/* @__PURE__ */ jsxs(ToolbarButton, {
|
|
313
|
+
className: "tabular-nums",
|
|
314
|
+
"aria-label": `Reset canvas zoom, currently ${percentage}%`,
|
|
315
|
+
onClick: () => canvas.reset(),
|
|
316
|
+
children: [percentage, "%"]
|
|
317
|
+
}),
|
|
318
|
+
/* @__PURE__ */ jsx(ToolbarButton, {
|
|
319
|
+
iconOnly: true,
|
|
320
|
+
disabled: scale >= canvas.maxScale,
|
|
321
|
+
"aria-label": "Zoom in",
|
|
322
|
+
onClick: () => canvas.zoomBy(CONTROL_ZOOM_FACTOR),
|
|
323
|
+
children: /* @__PURE__ */ jsx(PlusIcon, {})
|
|
324
|
+
}),
|
|
325
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
326
|
+
/* @__PURE__ */ jsx(ToolbarButton, {
|
|
327
|
+
iconOnly: true,
|
|
328
|
+
"aria-label": "Fit view",
|
|
329
|
+
onClick: () => canvas.fit(),
|
|
330
|
+
children: /* @__PURE__ */ jsx(MaximizeIcon, {})
|
|
331
|
+
})
|
|
332
|
+
]
|
|
333
|
+
})
|
|
395
334
|
});
|
|
396
335
|
}
|
|
397
336
|
//#endregion
|
|
398
|
-
export { InfiniteCanvas, InfiniteCanvasContent, InfiniteCanvasControls, InfiniteCanvasItem, useInfiniteCanvas };
|
|
337
|
+
export { InfiniteCanvas, InfiniteCanvasContent, InfiniteCanvasControls, InfiniteCanvasItem, InfiniteCanvasItemHandle, useInfiniteCanvas, useInfiniteCanvasTransform };
|
|
@@ -48,8 +48,9 @@ export type RichTooltipContentProps = Omit<ComponentProps<typeof Popover.Popup>,
|
|
|
48
48
|
collisionPadding?: ComponentProps<typeof Popover.Positioner>["collisionPadding"];
|
|
49
49
|
arrow?: boolean;
|
|
50
50
|
scrollAnchorIntoView?: boolean;
|
|
51
|
+
container?: ComponentProps<typeof Popover.Portal>["container"];
|
|
51
52
|
};
|
|
52
|
-
export declare function RichTooltipContent({ className, children, side, align, sideOffset, anchor, collisionPadding, arrow, scrollAnchorIntoView, ...props }: RichTooltipContentProps): import("react").JSX.Element;
|
|
53
|
+
export declare function RichTooltipContent({ className, children, side, align, sideOffset, anchor, collisionPadding, arrow, scrollAnchorIntoView, container, ...props }: RichTooltipContentProps): import("react").JSX.Element;
|
|
53
54
|
export declare function RichTooltipMedia({ className, ...props }: ComponentProps<"div"> & {
|
|
54
55
|
style?: CSSProperties & PopupKnobStyle;
|
|
55
56
|
}): import("react").JSX.Element;
|