@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/lib/infinite-canvas-camera.ts
|
|
2
|
+
const MAX_CAMERA_OFFSET_PX = 1e7;
|
|
3
|
+
const WHEEL_LINE_HEIGHT_PX = 16;
|
|
4
|
+
const DOM_DELTA_LINE = 1;
|
|
5
|
+
const DOM_DELTA_PAGE = 2;
|
|
6
|
+
function clampScale(scale, minScale, maxScale) {
|
|
7
|
+
return Math.min(Math.max(scale, minScale), maxScale);
|
|
8
|
+
}
|
|
9
|
+
function sameTransform(left, right) {
|
|
10
|
+
return left.x === right.x && left.y === right.y && left.scale === right.scale;
|
|
11
|
+
}
|
|
12
|
+
function boundedTransform(next, minScale, maxScale) {
|
|
13
|
+
if (!(Number.isFinite(next.x) && Number.isFinite(next.y) && Number.isFinite(next.scale)) || next.scale <= 0) return null;
|
|
14
|
+
return {
|
|
15
|
+
x: Math.min(Math.max(next.x, -1e7), MAX_CAMERA_OFFSET_PX),
|
|
16
|
+
y: Math.min(Math.max(next.y, -1e7), MAX_CAMERA_OFFSET_PX),
|
|
17
|
+
scale: clampScale(next.scale, minScale, maxScale)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function toWorldPoint(transform, point) {
|
|
21
|
+
return {
|
|
22
|
+
x: (point.x - transform.x) / transform.scale,
|
|
23
|
+
y: (point.y - transform.y) / transform.scale
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function zoomAroundPoint(transform, nextScale, point) {
|
|
27
|
+
const world = toWorldPoint(transform, point);
|
|
28
|
+
return {
|
|
29
|
+
x: point.x - world.x * nextScale,
|
|
30
|
+
y: point.y - world.y * nextScale,
|
|
31
|
+
scale: nextScale
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function pinchTransform(start, startMidpoint, midpoint, nextScale) {
|
|
35
|
+
const zoomed = zoomAroundPoint(start, nextScale, startMidpoint);
|
|
36
|
+
return {
|
|
37
|
+
x: zoomed.x + midpoint.x - startMidpoint.x,
|
|
38
|
+
y: zoomed.y + midpoint.y - startMidpoint.y,
|
|
39
|
+
scale: nextScale
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function wheelDeltaMultiplier(deltaMode, pageHeight) {
|
|
43
|
+
if (deltaMode === DOM_DELTA_LINE) return WHEEL_LINE_HEIGHT_PX;
|
|
44
|
+
if (deltaMode === DOM_DELTA_PAGE) return pageHeight;
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
function wheelDeltaPixels(event, pageHeight) {
|
|
48
|
+
const multiplier = wheelDeltaMultiplier(event.deltaMode, pageHeight);
|
|
49
|
+
const verticalAsHorizontal = event.shiftKey && event.deltaX === 0;
|
|
50
|
+
return {
|
|
51
|
+
x: (verticalAsHorizontal ? event.deltaY : event.deltaX) * multiplier,
|
|
52
|
+
y: (verticalAsHorizontal ? 0 : event.deltaY) * multiplier
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function unionBounds(bounds) {
|
|
56
|
+
if (bounds.length === 0) return null;
|
|
57
|
+
let left = Number.POSITIVE_INFINITY;
|
|
58
|
+
let top = Number.POSITIVE_INFINITY;
|
|
59
|
+
let right = Number.NEGATIVE_INFINITY;
|
|
60
|
+
let bottom = Number.NEGATIVE_INFINITY;
|
|
61
|
+
for (const entry of bounds) {
|
|
62
|
+
left = Math.min(left, entry.x);
|
|
63
|
+
top = Math.min(top, entry.y);
|
|
64
|
+
right = Math.max(right, entry.x + entry.width);
|
|
65
|
+
bottom = Math.max(bottom, entry.y + entry.height);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
x: left,
|
|
69
|
+
y: top,
|
|
70
|
+
width: right - left,
|
|
71
|
+
height: bottom - top
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function fitTransform(bounds, area, limits) {
|
|
75
|
+
const availableWidth = Math.max(1, area.width - limits.padding * 2);
|
|
76
|
+
const availableHeight = Math.max(1, area.height - limits.padding * 2);
|
|
77
|
+
const scale = clampScale(Math.min(availableWidth / Math.max(bounds.width, 1), availableHeight / Math.max(bounds.height, 1)), limits.minScale, limits.maxScale);
|
|
78
|
+
return {
|
|
79
|
+
x: area.x + area.width / 2 - (bounds.x + bounds.width / 2) * scale,
|
|
80
|
+
y: area.y + area.height / 2 - (bounds.y + bounds.height / 2) * scale,
|
|
81
|
+
scale
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function revealAxisOffset(start, size, areaStart, areaSize, padding) {
|
|
85
|
+
const visibleStart = areaStart + padding;
|
|
86
|
+
const visibleEnd = areaStart + areaSize - padding;
|
|
87
|
+
if (size > visibleEnd - visibleStart || start < visibleStart) return visibleStart - start;
|
|
88
|
+
if (start + size > visibleEnd) return visibleEnd - (start + size);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
function revealOffset(screenBounds, area, padding) {
|
|
92
|
+
return {
|
|
93
|
+
x: revealAxisOffset(screenBounds.x, screenBounds.width, area.x, area.width, padding),
|
|
94
|
+
y: revealAxisOffset(screenBounds.y, screenBounds.height, area.y, area.height, padding)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { boundedTransform, clampScale, fitTransform, pinchTransform, revealOffset, sameTransform, toWorldPoint, unionBounds, wheelDeltaMultiplier, wheelDeltaPixels, zoomAroundPoint };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { InfiniteCanvasPoint } from "./infinite-canvas-camera.js";
|
|
2
|
+
import { InfiniteCanvasCameraStore } from "./infinite-canvas-camera-store.js";
|
|
3
|
+
//#region src/lib/infinite-canvas-navigation.d.ts
|
|
4
|
+
export type InfiniteCanvasPanOnDrag = "background" | "anywhere" | "never";
|
|
5
|
+
export type InfiniteCanvasNavigationOptions = {
|
|
6
|
+
panOnDrag: InfiniteCanvasPanOnDrag;
|
|
7
|
+
onWheel?: (event: WheelEvent) => void;
|
|
8
|
+
};
|
|
9
|
+
export type InfiniteCanvasNavigation = {
|
|
10
|
+
beginBackgroundPan: (event: PointerEvent) => void;
|
|
11
|
+
syncPanReady: () => void;
|
|
12
|
+
detach: () => void;
|
|
13
|
+
};
|
|
14
|
+
export declare const INFINITE_CANVAS_KEY_SHORTCUTS = "ArrowLeft ArrowRight ArrowUp ArrowDown + - 0 1";
|
|
15
|
+
export declare const ARROW_KEY_DIRECTIONS: Partial<Record<string, InfiniteCanvasPoint>>;
|
|
16
|
+
export declare function keyboardStep(shiftKey: boolean): number;
|
|
17
|
+
export declare function runInfiniteCanvasKeyCommand(camera: InfiniteCanvasCameraStore, key: string, shiftKey: boolean): boolean;
|
|
18
|
+
export declare function attachInfiniteCanvasNavigation(root: HTMLElement, camera: InfiniteCanvasCameraStore, readOptions: () => InfiniteCanvasNavigationOptions): InfiniteCanvasNavigation;
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { pinchTransform, wheelDeltaMultiplier, wheelDeltaPixels, zoomAroundPoint } from "./infinite-canvas-camera.js";
|
|
2
|
+
//#region src/lib/infinite-canvas-navigation.ts
|
|
3
|
+
const INFINITE_CANVAS_KEY_SHORTCUTS = "ArrowLeft ArrowRight ArrowUp ArrowDown + - 0 1";
|
|
4
|
+
const ARROW_KEY_DIRECTIONS = {
|
|
5
|
+
ArrowLeft: {
|
|
6
|
+
x: -1,
|
|
7
|
+
y: 0
|
|
8
|
+
},
|
|
9
|
+
ArrowRight: {
|
|
10
|
+
x: 1,
|
|
11
|
+
y: 0
|
|
12
|
+
},
|
|
13
|
+
ArrowUp: {
|
|
14
|
+
x: 0,
|
|
15
|
+
y: -1
|
|
16
|
+
},
|
|
17
|
+
ArrowDown: {
|
|
18
|
+
x: 0,
|
|
19
|
+
y: 1
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const KEYBOARD_ZOOM_FACTORS = {
|
|
23
|
+
"+": 1.15,
|
|
24
|
+
"=": 1.15,
|
|
25
|
+
"-": 1 / 1.15
|
|
26
|
+
};
|
|
27
|
+
const KEYBOARD_STEP_PX = 32;
|
|
28
|
+
const WHEEL_ZOOM_SPEED = .004;
|
|
29
|
+
const MAX_WHEEL_ZOOM_DELTA = 50;
|
|
30
|
+
const NOTCHED_WHEEL_DELTA = 40;
|
|
31
|
+
const CLICK_SLOP_PX = 3;
|
|
32
|
+
const MIDDLE_BUTTON = 1;
|
|
33
|
+
const SCROLLABLE_OVERFLOW = /auto|scroll|overlay/;
|
|
34
|
+
const SPACE_CONSUMING_TARGET = [
|
|
35
|
+
"input",
|
|
36
|
+
"textarea",
|
|
37
|
+
"select",
|
|
38
|
+
"button",
|
|
39
|
+
"a[href]",
|
|
40
|
+
"[contenteditable]:not([contenteditable='false'])",
|
|
41
|
+
...[
|
|
42
|
+
"button",
|
|
43
|
+
"checkbox",
|
|
44
|
+
"combobox",
|
|
45
|
+
"menuitem",
|
|
46
|
+
"option",
|
|
47
|
+
"radio",
|
|
48
|
+
"slider",
|
|
49
|
+
"spinbutton",
|
|
50
|
+
"switch",
|
|
51
|
+
"tab",
|
|
52
|
+
"textbox"
|
|
53
|
+
].map((role) => `[role='${role}']`)
|
|
54
|
+
].join(", ");
|
|
55
|
+
function keyboardStep(shiftKey) {
|
|
56
|
+
return shiftKey ? 96 : KEYBOARD_STEP_PX;
|
|
57
|
+
}
|
|
58
|
+
function runInfiniteCanvasKeyCommand(camera, key, shiftKey) {
|
|
59
|
+
const direction = ARROW_KEY_DIRECTIONS[key];
|
|
60
|
+
if (direction) {
|
|
61
|
+
const current = camera.getTransform();
|
|
62
|
+
const step = keyboardStep(shiftKey);
|
|
63
|
+
camera.move({
|
|
64
|
+
...current,
|
|
65
|
+
x: current.x - direction.x * step,
|
|
66
|
+
y: current.y - direction.y * step
|
|
67
|
+
}, "keyboard");
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const zoomFactor = KEYBOARD_ZOOM_FACTORS[key];
|
|
71
|
+
if (zoomFactor) camera.zoomBy(zoomFactor, { reason: "keyboard" });
|
|
72
|
+
else if (key === "0") camera.reset({ reason: "keyboard" });
|
|
73
|
+
else if (key === "1") camera.fit({ reason: "keyboard" });
|
|
74
|
+
else return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function canScrollFurther(element, delta) {
|
|
78
|
+
if (element.scrollHeight <= element.clientHeight && element.scrollWidth <= element.clientWidth) return false;
|
|
79
|
+
const { overflowX, overflowY } = getComputedStyle(element);
|
|
80
|
+
const verticalRoom = delta.y < 0 ? element.scrollTop > 0 : Math.ceil(element.scrollTop + element.clientHeight) < element.scrollHeight;
|
|
81
|
+
const horizontalRoom = delta.x < 0 ? element.scrollLeft > 0 : Math.ceil(element.scrollLeft + element.clientWidth) < element.scrollWidth;
|
|
82
|
+
return delta.y !== 0 && SCROLLABLE_OVERFLOW.test(overflowY) && verticalRoom || delta.x !== 0 && SCROLLABLE_OVERFLOW.test(overflowX) && horizontalRoom;
|
|
83
|
+
}
|
|
84
|
+
function nestedScrollerConsumes(target, root, delta) {
|
|
85
|
+
for (let element = target instanceof Element ? target : null; element && element !== root; element = element.parentElement) if (canScrollFurther(element, delta)) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
function gesturePointerIds(active) {
|
|
89
|
+
return active.kind === "pan" ? [active.pointerId] : active.pointerIds;
|
|
90
|
+
}
|
|
91
|
+
function clickAfterGesture(ended) {
|
|
92
|
+
if (ended.kind === "pinch") return "click";
|
|
93
|
+
if (!(ended.explicit || ended.moved)) return null;
|
|
94
|
+
return ended.button === MIDDLE_BUTTON ? "auxclick" : "click";
|
|
95
|
+
}
|
|
96
|
+
function gestureScale(event) {
|
|
97
|
+
return "scale" in event && typeof event.scale === "number" ? event.scale : null;
|
|
98
|
+
}
|
|
99
|
+
function gestureClientPoint(event) {
|
|
100
|
+
if (!("clientX" in event && "clientY" in event)) return null;
|
|
101
|
+
const { clientX, clientY } = event;
|
|
102
|
+
return typeof clientX === "number" && typeof clientY === "number" ? {
|
|
103
|
+
clientX,
|
|
104
|
+
clientY
|
|
105
|
+
} : null;
|
|
106
|
+
}
|
|
107
|
+
function attachInfiniteCanvasNavigation(root, camera, readOptions) {
|
|
108
|
+
const listeners = new AbortController();
|
|
109
|
+
const { signal } = listeners;
|
|
110
|
+
const touches = /* @__PURE__ */ new Map();
|
|
111
|
+
let gesture = null;
|
|
112
|
+
let spaceHeld = false;
|
|
113
|
+
let safariPinchStartScale = null;
|
|
114
|
+
let suppressedClick = null;
|
|
115
|
+
let suppressedClickTimer;
|
|
116
|
+
let pointerFocus = false;
|
|
117
|
+
let pointerFocusTimer;
|
|
118
|
+
function syncPanReady() {
|
|
119
|
+
root.toggleAttribute("data-pan-ready", spaceHeld || readOptions().panOnDrag === "anywhere");
|
|
120
|
+
}
|
|
121
|
+
function suppressNextClick(type) {
|
|
122
|
+
suppressedClick = type;
|
|
123
|
+
clearTimeout(suppressedClickTimer);
|
|
124
|
+
suppressedClickTimer = setTimeout(() => {
|
|
125
|
+
suppressedClick = null;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function startPan(event, explicit) {
|
|
129
|
+
root.setPointerCapture(event.pointerId);
|
|
130
|
+
root.toggleAttribute("data-panning", true);
|
|
131
|
+
gesture = {
|
|
132
|
+
kind: "pan",
|
|
133
|
+
pointerId: event.pointerId,
|
|
134
|
+
button: event.button,
|
|
135
|
+
explicit,
|
|
136
|
+
moved: false,
|
|
137
|
+
startPointer: {
|
|
138
|
+
x: event.clientX,
|
|
139
|
+
y: event.clientY
|
|
140
|
+
},
|
|
141
|
+
startTransform: camera.getTransform()
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function startPinch() {
|
|
145
|
+
const [first, second] = [...touches.entries()];
|
|
146
|
+
if (!(first && second)) return;
|
|
147
|
+
for (const [pointerId] of [first, second]) if (!root.hasPointerCapture(pointerId)) root.setPointerCapture(pointerId);
|
|
148
|
+
root.toggleAttribute("data-panning", true);
|
|
149
|
+
gesture = {
|
|
150
|
+
kind: "pinch",
|
|
151
|
+
pointerIds: [first[0], second[0]],
|
|
152
|
+
startDistance: Math.max(1, Math.hypot(first[1].x - second[1].x, first[1].y - second[1].y)),
|
|
153
|
+
startMidpoint: camera.toLocalPoint({
|
|
154
|
+
clientX: (first[1].x + second[1].x) / 2,
|
|
155
|
+
clientY: (first[1].y + second[1].y) / 2
|
|
156
|
+
}),
|
|
157
|
+
startTransform: camera.getTransform()
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function involvesPointer(pointerId) {
|
|
161
|
+
return gesture !== null && gesturePointerIds(gesture).includes(pointerId);
|
|
162
|
+
}
|
|
163
|
+
function finishGesture() {
|
|
164
|
+
const ended = gesture;
|
|
165
|
+
if (!ended) return;
|
|
166
|
+
gesture = null;
|
|
167
|
+
root.removeAttribute("data-panning");
|
|
168
|
+
for (const pointerId of gesturePointerIds(ended)) if (root.hasPointerCapture(pointerId)) root.releasePointerCapture(pointerId);
|
|
169
|
+
const click = clickAfterGesture(ended);
|
|
170
|
+
if (click) suppressNextClick(click);
|
|
171
|
+
}
|
|
172
|
+
function claim(event) {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
event.stopPropagation();
|
|
175
|
+
}
|
|
176
|
+
function isNavigableTarget(target) {
|
|
177
|
+
return target === root || target instanceof Node && camera.contentElement()?.contains(target) === true;
|
|
178
|
+
}
|
|
179
|
+
function markPointerFocus() {
|
|
180
|
+
pointerFocus = true;
|
|
181
|
+
clearTimeout(pointerFocusTimer);
|
|
182
|
+
pointerFocusTimer = setTimeout(() => {
|
|
183
|
+
pointerFocus = false;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function claimsTouchPinch(event) {
|
|
187
|
+
if (event.pointerType !== "touch") return false;
|
|
188
|
+
if (event.isPrimary) touches.clear();
|
|
189
|
+
touches.set(event.pointerId, {
|
|
190
|
+
x: event.clientX,
|
|
191
|
+
y: event.clientY
|
|
192
|
+
});
|
|
193
|
+
if (touches.size < 2) return false;
|
|
194
|
+
claim(event);
|
|
195
|
+
if (touches.size === 2) startPinch();
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function isExplicitPan(event) {
|
|
199
|
+
if (!isNavigableTarget(event.target)) return false;
|
|
200
|
+
if (event.button === MIDDLE_BUTTON) return true;
|
|
201
|
+
return event.button === 0 && (spaceHeld || readOptions().panOnDrag === "anywhere");
|
|
202
|
+
}
|
|
203
|
+
function claimNavigationPointer(event) {
|
|
204
|
+
markPointerFocus();
|
|
205
|
+
if (claimsTouchPinch(event) || gesture || !event.isPrimary || !isExplicitPan(event)) return;
|
|
206
|
+
claim(event);
|
|
207
|
+
startPan(event, true);
|
|
208
|
+
}
|
|
209
|
+
function followPointer(event) {
|
|
210
|
+
if (touches.has(event.pointerId)) touches.set(event.pointerId, {
|
|
211
|
+
x: event.clientX,
|
|
212
|
+
y: event.clientY
|
|
213
|
+
});
|
|
214
|
+
if (!(gesture && involvesPointer(event.pointerId))) return;
|
|
215
|
+
if (gesture.kind === "pan") {
|
|
216
|
+
const deltaX = event.clientX - gesture.startPointer.x;
|
|
217
|
+
const deltaY = event.clientY - gesture.startPointer.y;
|
|
218
|
+
if (Math.hypot(deltaX, deltaY) > CLICK_SLOP_PX) gesture.moved = true;
|
|
219
|
+
const { startTransform } = gesture;
|
|
220
|
+
camera.move({
|
|
221
|
+
...startTransform,
|
|
222
|
+
x: startTransform.x + deltaX,
|
|
223
|
+
y: startTransform.y + deltaY
|
|
224
|
+
}, "pointer");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const [first, second] = gesture.pointerIds.map((pointerId) => touches.get(pointerId));
|
|
228
|
+
if (!(first && second)) return;
|
|
229
|
+
const midpoint = camera.toLocalPoint({
|
|
230
|
+
clientX: (first.x + second.x) / 2,
|
|
231
|
+
clientY: (first.y + second.y) / 2
|
|
232
|
+
});
|
|
233
|
+
const ratio = Math.hypot(first.x - second.x, first.y - second.y) / gesture.startDistance;
|
|
234
|
+
const nextScale = camera.limitScale(gesture.startTransform.scale * ratio);
|
|
235
|
+
camera.move(pinchTransform(gesture.startTransform, gesture.startMidpoint, midpoint, nextScale), "pinch");
|
|
236
|
+
}
|
|
237
|
+
function releasePointer(event) {
|
|
238
|
+
touches.delete(event.pointerId);
|
|
239
|
+
if (involvesPointer(event.pointerId)) finishGesture();
|
|
240
|
+
}
|
|
241
|
+
function zoomWithWheel(event) {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
if (safariPinchStartScale !== null) return;
|
|
244
|
+
const current = camera.getTransform();
|
|
245
|
+
const zoomDelta = event.deltaY * wheelDeltaMultiplier(event.deltaMode, root.clientHeight);
|
|
246
|
+
const boundedDelta = Math.min(Math.max(zoomDelta, -50), MAX_WHEEL_ZOOM_DELTA);
|
|
247
|
+
const nextScale = camera.limitScale(current.scale * Math.exp(-boundedDelta * WHEEL_ZOOM_SPEED));
|
|
248
|
+
camera.move(zoomAroundPoint(current, nextScale, camera.toLocalPoint(event)), "wheel", Math.abs(zoomDelta) >= NOTCHED_WHEEL_DELTA);
|
|
249
|
+
}
|
|
250
|
+
function handleWheel(event) {
|
|
251
|
+
readOptions().onWheel?.(event);
|
|
252
|
+
if (event.defaultPrevented) return;
|
|
253
|
+
if (event.ctrlKey || event.metaKey) {
|
|
254
|
+
zoomWithWheel(event);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const delta = wheelDeltaPixels(event, root.clientHeight);
|
|
258
|
+
if (nestedScrollerConsumes(event.target, root, delta)) return;
|
|
259
|
+
event.preventDefault();
|
|
260
|
+
const current = camera.getTransform();
|
|
261
|
+
camera.move({
|
|
262
|
+
...current,
|
|
263
|
+
x: current.x - delta.x,
|
|
264
|
+
y: current.y - delta.y
|
|
265
|
+
}, "wheel");
|
|
266
|
+
}
|
|
267
|
+
function startSafariPinch(event) {
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
safariPinchStartScale = camera.getTransform().scale;
|
|
270
|
+
}
|
|
271
|
+
function followSafariPinch(event) {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
const scale = gestureScale(event);
|
|
274
|
+
if (safariPinchStartScale === null || scale === null || gesture?.kind === "pinch") return;
|
|
275
|
+
const clientPoint = gestureClientPoint(event);
|
|
276
|
+
const origin = clientPoint ? camera.toLocalPoint(clientPoint) : {
|
|
277
|
+
x: root.clientWidth / 2,
|
|
278
|
+
y: root.clientHeight / 2
|
|
279
|
+
};
|
|
280
|
+
camera.move(zoomAroundPoint(camera.getTransform(), camera.limitScale(safariPinchStartScale * scale), origin), "pinch");
|
|
281
|
+
}
|
|
282
|
+
function endSafariPinch(event) {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
safariPinchStartScale = null;
|
|
285
|
+
}
|
|
286
|
+
function armSpacePan(event) {
|
|
287
|
+
if (event.code !== "Space") return;
|
|
288
|
+
if (spaceHeld) {
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (event.defaultPrevented || event.repeat || event.isComposing || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
293
|
+
const { target } = event;
|
|
294
|
+
if (target instanceof HTMLElement && (target.isContentEditable || target.matches(SPACE_CONSUMING_TARGET))) return;
|
|
295
|
+
if (!(root.contains(document.activeElement) || root.matches(":hover"))) return;
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
spaceHeld = true;
|
|
298
|
+
syncPanReady();
|
|
299
|
+
}
|
|
300
|
+
function disarmSpacePan(event) {
|
|
301
|
+
if (event.code !== "Space" || !spaceHeld) return;
|
|
302
|
+
spaceHeld = false;
|
|
303
|
+
syncPanReady();
|
|
304
|
+
}
|
|
305
|
+
function releaseHeldInput() {
|
|
306
|
+
spaceHeld = false;
|
|
307
|
+
safariPinchStartScale = null;
|
|
308
|
+
touches.clear();
|
|
309
|
+
finishGesture();
|
|
310
|
+
syncPanReady();
|
|
311
|
+
}
|
|
312
|
+
function swallowClickAfterPan(event) {
|
|
313
|
+
if (event.type !== suppressedClick) return;
|
|
314
|
+
suppressedClick = null;
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
event.stopPropagation();
|
|
317
|
+
}
|
|
318
|
+
function revealKeyboardFocus(event) {
|
|
319
|
+
if (pointerFocus || !(event.target instanceof Element)) return;
|
|
320
|
+
if (camera.contentElement()?.contains(event.target)) camera.revealElement(event.target);
|
|
321
|
+
}
|
|
322
|
+
function absorbNativeScroll() {
|
|
323
|
+
const { scrollLeft, scrollTop } = root;
|
|
324
|
+
if (scrollLeft === 0 && scrollTop === 0) return;
|
|
325
|
+
root.scrollTo(0, 0);
|
|
326
|
+
const current = camera.getTransform();
|
|
327
|
+
camera.moveNow({
|
|
328
|
+
...current,
|
|
329
|
+
x: current.x - scrollLeft,
|
|
330
|
+
y: current.y - scrollTop
|
|
331
|
+
}, "reveal");
|
|
332
|
+
}
|
|
333
|
+
root.addEventListener("pointerdown", claimNavigationPointer, {
|
|
334
|
+
capture: true,
|
|
335
|
+
signal
|
|
336
|
+
});
|
|
337
|
+
root.addEventListener("pointermove", followPointer, {
|
|
338
|
+
capture: true,
|
|
339
|
+
signal
|
|
340
|
+
});
|
|
341
|
+
root.addEventListener("pointerup", releasePointer, {
|
|
342
|
+
capture: true,
|
|
343
|
+
signal
|
|
344
|
+
});
|
|
345
|
+
root.addEventListener("pointercancel", releasePointer, {
|
|
346
|
+
capture: true,
|
|
347
|
+
signal
|
|
348
|
+
});
|
|
349
|
+
root.addEventListener("lostpointercapture", (event) => {
|
|
350
|
+
if (event.target === root && involvesPointer(event.pointerId)) finishGesture();
|
|
351
|
+
}, { signal });
|
|
352
|
+
root.addEventListener("click", swallowClickAfterPan, {
|
|
353
|
+
capture: true,
|
|
354
|
+
signal
|
|
355
|
+
});
|
|
356
|
+
root.addEventListener("auxclick", swallowClickAfterPan, {
|
|
357
|
+
capture: true,
|
|
358
|
+
signal
|
|
359
|
+
});
|
|
360
|
+
root.addEventListener("mousedown", (event) => {
|
|
361
|
+
if (event.button === MIDDLE_BUTTON && isNavigableTarget(event.target)) event.preventDefault();
|
|
362
|
+
}, { signal });
|
|
363
|
+
root.addEventListener("wheel", handleWheel, {
|
|
364
|
+
passive: false,
|
|
365
|
+
signal
|
|
366
|
+
});
|
|
367
|
+
root.addEventListener("gesturestart", startSafariPinch, { signal });
|
|
368
|
+
root.addEventListener("gesturechange", followSafariPinch, { signal });
|
|
369
|
+
root.addEventListener("gestureend", endSafariPinch, { signal });
|
|
370
|
+
root.addEventListener("focusin", revealKeyboardFocus, { signal });
|
|
371
|
+
root.addEventListener("scroll", absorbNativeScroll, { signal });
|
|
372
|
+
window.addEventListener("keydown", armSpacePan, { signal });
|
|
373
|
+
window.addEventListener("keyup", disarmSpacePan, { signal });
|
|
374
|
+
window.addEventListener("blur", releaseHeldInput, { signal });
|
|
375
|
+
document.addEventListener("visibilitychange", () => {
|
|
376
|
+
if (document.visibilityState === "hidden") releaseHeldInput();
|
|
377
|
+
}, { signal });
|
|
378
|
+
syncPanReady();
|
|
379
|
+
return {
|
|
380
|
+
beginBackgroundPan(event) {
|
|
381
|
+
if (gesture || event.defaultPrevented || event.target !== root || !event.isPrimary || event.button !== 0) return;
|
|
382
|
+
if (readOptions().panOnDrag === "background") startPan(event, false);
|
|
383
|
+
},
|
|
384
|
+
syncPanReady,
|
|
385
|
+
detach() {
|
|
386
|
+
listeners.abort();
|
|
387
|
+
clearTimeout(suppressedClickTimer);
|
|
388
|
+
clearTimeout(pointerFocusTimer);
|
|
389
|
+
releaseHeldInput();
|
|
390
|
+
root.removeAttribute("data-pan-ready");
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
//#endregion
|
|
395
|
+
export { ARROW_KEY_DIRECTIONS, INFINITE_CANVAS_KEY_SHORTCUTS, attachInfiniteCanvasNavigation, keyboardStep, runInfiniteCanvasKeyCommand };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { TooltipProvider } from "../ui/tooltip.js";
|
|
3
|
+
import { useMarkdownEditor } from "./context.js";
|
|
4
|
+
import { Toolbar, ToolbarSeparator } from "../ui/toolbar.js";
|
|
5
|
+
import { MarkdownEditorFormatButton, MarkdownEditorHeading, MarkdownEditorLink, useMarkdownEditorFormatActions } from "./toolbar.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { NodeSelection } from "@tiptap/pm/state";
|
|
8
|
+
import { BubbleMenu } from "@tiptap/react/menus";
|
|
9
|
+
//#region src/markdown-editor/bubble-menu.tsx
|
|
10
|
+
const showForFormattableText = ({ editor, view, state, element }) => {
|
|
11
|
+
const { selection } = state;
|
|
12
|
+
return !(selection instanceof NodeSelection) && !selection.empty && state.doc.textBetween(selection.from, selection.to).trim() !== "" && !editor.isActive("codeBlock") && editor.isEditable && (view.hasFocus() || element.contains(document.activeElement));
|
|
13
|
+
};
|
|
14
|
+
const floatingOptions = {
|
|
15
|
+
placement: "top",
|
|
16
|
+
offset: 8,
|
|
17
|
+
flip: { padding: 8 },
|
|
18
|
+
shift: { padding: 8 }
|
|
19
|
+
};
|
|
20
|
+
function MarkdownEditorBubbleMenu({ children }) {
|
|
21
|
+
const { editor, disabled, source } = useMarkdownEditor();
|
|
22
|
+
const { marks } = useMarkdownEditorFormatActions();
|
|
23
|
+
if (!editor || disabled || source) return null;
|
|
24
|
+
return /* @__PURE__ */ jsx(BubbleMenu, {
|
|
25
|
+
editor,
|
|
26
|
+
shouldShow: showForFormattableText,
|
|
27
|
+
options: floatingOptions,
|
|
28
|
+
"data-control-ui": "markdown-editor",
|
|
29
|
+
"data-control-family": "markdown-editor",
|
|
30
|
+
"data-slot": "bubble-menu",
|
|
31
|
+
children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Toolbar, {
|
|
32
|
+
"aria-label": "Selection formatting",
|
|
33
|
+
variant: "floating",
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ jsx(MarkdownEditorHeading, {}),
|
|
36
|
+
/* @__PURE__ */ jsx(ToolbarSeparator, {}),
|
|
37
|
+
marks.map((action) => /* @__PURE__ */ jsx(MarkdownEditorFormatButton, { action }, action.label)),
|
|
38
|
+
/* @__PURE__ */ jsx(MarkdownEditorLink, {}),
|
|
39
|
+
children
|
|
40
|
+
]
|
|
41
|
+
}) })
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { MarkdownEditorBubbleMenu };
|
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
1
|
+
import { MouseEventHandler, ReactNode } from "react";
|
|
2
|
+
import { BoldIcon } from "lucide-react";
|
|
2
3
|
//#region src/markdown-editor/toolbar.d.ts
|
|
4
|
+
type MarkdownEditorFormatAction = {
|
|
5
|
+
label: string;
|
|
6
|
+
Icon: typeof BoldIcon;
|
|
7
|
+
active: boolean;
|
|
8
|
+
run: () => void;
|
|
9
|
+
};
|
|
10
|
+
export declare function useMarkdownEditorFormatActions(): {
|
|
11
|
+
marks: MarkdownEditorFormatAction[];
|
|
12
|
+
blocks: MarkdownEditorFormatAction[];
|
|
13
|
+
};
|
|
14
|
+
export declare function MarkdownEditorFormatButton({ action, onMouseDown }: {
|
|
15
|
+
action: MarkdownEditorFormatAction;
|
|
16
|
+
onMouseDown?: MouseEventHandler<HTMLButtonElement>;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
3
18
|
export declare function MarkdownEditorToolbar({ children }: {
|
|
4
19
|
children?: ReactNode;
|
|
5
20
|
}): import("react").JSX.Element;
|
|
21
|
+
export declare function MarkdownEditorHeading(): import("react").JSX.Element;
|
|
6
22
|
export declare function MarkdownEditorLink(): import("react").JSX.Element;
|
|
7
23
|
export declare function MarkdownEditorImage(): import("react").JSX.Element;
|
|
8
24
|
export declare function MarkdownEditorUploads(): import("react").JSX.Element;
|