@ctrl-ui/react 0.9.5 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +38 -2
  2. package/dist/action-bar.js +16 -8
  3. package/dist/activity.d.ts +4 -2
  4. package/dist/activity.js +5 -2
  5. package/dist/audio-visualizer.js +1 -1
  6. package/dist/blocks/agent-run-clock.d.ts +5 -0
  7. package/dist/blocks/agent-run-clock.js +52 -0
  8. package/dist/blocks/agent-run-sidebar.d.ts +13 -0
  9. package/dist/blocks/agent-run-sidebar.js +101 -0
  10. package/dist/blocks/agent-run-status.d.ts +32 -0
  11. package/dist/blocks/agent-run-status.js +27 -0
  12. package/dist/blocks/agent-run.d.ts +82 -0
  13. package/dist/blocks/agent-run.js +262 -0
  14. package/dist/blocks/coding-agent.d.ts +1 -1
  15. package/dist/blocks/coding-agent.js +50 -69
  16. package/dist/blocks/design-canvas-data.d.ts +1 -1
  17. package/dist/blocks/design-canvas-inspector.js +1 -1
  18. package/dist/blocks/design-canvas.d.ts +2 -1
  19. package/dist/blocks/design-canvas.js +56 -42
  20. package/dist/blocks/discussion.js +1 -1
  21. package/dist/blocks/file-explorer.js +66 -47
  22. package/dist/blocks/settings.js +37 -41
  23. package/dist/blocks/team-chat.js +2 -1
  24. package/dist/blocks/theme-toggle.d.ts +2 -1
  25. package/dist/blocks/theme-toggle.js +13 -4
  26. package/dist/chat-message.js +33 -39
  27. package/dist/hooks/use-mobile.d.ts +1 -1
  28. package/dist/hooks/use-mobile.js +1 -1
  29. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  30. package/dist/knob-contracts/badge-knobs.js +3 -1
  31. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  32. package/dist/knob-contracts/button-knobs.js +1 -0
  33. package/dist/knob-contracts/calendar-knobs.d.ts +1 -1
  34. package/dist/knob-contracts/calendar-knobs.js +9 -1
  35. package/dist/knob-contracts/card-knobs.d.ts +1 -1
  36. package/dist/knob-contracts/card-knobs.js +5 -1
  37. package/dist/knob-contracts/choice-knobs.d.ts +1 -1
  38. package/dist/knob-contracts/choice-knobs.js +1 -0
  39. package/dist/knob-contracts/collapsible-knobs.d.ts +1 -1
  40. package/dist/knob-contracts/collapsible-knobs.js +5 -1
  41. package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
  42. package/dist/knob-contracts/emoji-picker-knobs.js +3 -1
  43. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  44. package/dist/knob-contracts/field-knobs.js +17 -1
  45. package/dist/knob-contracts/infinite-canvas-knobs.d.ts +1 -1
  46. package/dist/knob-contracts/infinite-canvas-knobs.js +1 -7
  47. package/dist/knob-contracts/item-knobs.d.ts +1 -1
  48. package/dist/knob-contracts/item-knobs.js +5 -1
  49. package/dist/knob-contracts/kbd-knobs.d.ts +1 -1
  50. package/dist/knob-contracts/kbd-knobs.js +4 -1
  51. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  52. package/dist/knob-contracts/page-layout-knobs.js +1 -0
  53. package/dist/knob-contracts/pagination-knobs.d.ts +1 -1
  54. package/dist/knob-contracts/pagination-knobs.js +2 -1
  55. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  56. package/dist/knob-contracts/popup-knobs.js +9 -0
  57. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  58. package/dist/knob-contracts/range-knobs.js +2 -0
  59. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  60. package/dist/knob-contracts/sidebar-knobs.js +2 -0
  61. package/dist/knob-contracts/swap-knobs.d.ts +4 -0
  62. package/dist/knob-contracts/swap-knobs.js +9 -0
  63. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  64. package/dist/knob-contracts/tabs-knobs.js +3 -0
  65. package/dist/knob-contracts/timeline-knobs.d.ts +1 -1
  66. package/dist/knob-contracts/timeline-knobs.js +5 -1
  67. package/dist/knob-contracts/tool-approval-knobs.d.ts +4 -0
  68. package/dist/knob-contracts/tool-approval-knobs.js +32 -0
  69. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  70. package/dist/knob-contracts/tree-knobs.js +3 -1
  71. package/dist/lib/infinite-canvas-camera-store.d.ts +65 -0
  72. package/dist/lib/infinite-canvas-camera-store.js +233 -0
  73. package/dist/lib/infinite-canvas-camera.d.ts +33 -0
  74. package/dist/lib/infinite-canvas-camera.js +98 -0
  75. package/dist/lib/infinite-canvas-navigation.d.ts +19 -0
  76. package/dist/lib/infinite-canvas-navigation.js +395 -0
  77. package/dist/markdown-editor/bubble-menu.d.ts +6 -0
  78. package/dist/markdown-editor/bubble-menu.js +45 -0
  79. package/dist/markdown-editor/toolbar.d.ts +17 -1
  80. package/dist/markdown-editor/toolbar.js +103 -81
  81. package/dist/skin.d.ts +2 -0
  82. package/dist/styles/index.css +11 -10
  83. package/dist/styles/recipes/activity.css +12 -0
  84. package/dist/styles/recipes/avatar.css +1 -1
  85. package/dist/styles/recipes/badge.css +17 -0
  86. package/dist/styles/recipes/button-group.css +12 -6
  87. package/dist/styles/recipes/button.css +20 -3
  88. package/dist/styles/recipes/calendar.css +82 -6
  89. package/dist/styles/recipes/card.css +40 -5
  90. package/dist/styles/recipes/chat-message-edit.css +6 -21
  91. package/dist/styles/recipes/choice.css +9 -2
  92. package/dist/styles/recipes/collapsible.css +8 -1
  93. package/dist/styles/recipes/color-picker-values.css +4 -0
  94. package/dist/styles/recipes/emoji-picker.css +29 -8
  95. package/dist/styles/recipes/field-parts.css +80 -5
  96. package/dist/styles/recipes/field.css +113 -40
  97. package/dist/styles/recipes/infinite-canvas.css +17 -57
  98. package/dist/styles/recipes/item.css +33 -5
  99. package/dist/styles/recipes/kbd.css +26 -6
  100. package/dist/styles/recipes/markdown-editor.css +12 -0
  101. package/dist/styles/recipes/morphing-panel.css +1 -1
  102. package/dist/styles/recipes/page-layout.css +18 -2
  103. package/dist/styles/recipes/pagination.css +8 -0
  104. package/dist/styles/recipes/popup-controls.css +9 -0
  105. package/dist/styles/recipes/popup-drawer.css +1 -0
  106. package/dist/styles/recipes/popup-lists.css +55 -6
  107. package/dist/styles/recipes/popup-motion.css +7 -1
  108. package/dist/styles/recipes/popup.css +10 -0
  109. package/dist/styles/recipes/range.css +16 -2
  110. package/dist/styles/recipes/sidebar-mobile.css +1 -1
  111. package/dist/styles/recipes/sidebar-parts.css +17 -1
  112. package/dist/styles/recipes/sidebar.css +11 -0
  113. package/dist/styles/recipes/swap.css +143 -0
  114. package/dist/styles/recipes/tabs.css +41 -8
  115. package/dist/styles/recipes/timeline.css +36 -2
  116. package/dist/styles/recipes/tool-approval.css +267 -0
  117. package/dist/styles/recipes/track-highlight.css +19 -6
  118. package/dist/styles/recipes/tree.css +33 -0
  119. package/dist/styles/theme.css +17 -6
  120. package/dist/tool-approval.d.ts +49 -0
  121. package/dist/tool-approval.js +260 -0
  122. package/dist/ui/alert-dialog.js +1 -1
  123. package/dist/ui/badge.js +1 -1
  124. package/dist/ui/calendar.js +9 -4
  125. package/dist/ui/card.d.ts +3 -1
  126. package/dist/ui/card.js +2 -1
  127. package/dist/ui/code.js +13 -3
  128. package/dist/ui/command.js +1 -0
  129. package/dist/ui/dialog.js +1 -1
  130. package/dist/ui/dropdown-menu.d.ts +1 -1
  131. package/dist/ui/dropdown-menu.js +23 -6
  132. package/dist/ui/dropzone.js +10 -11
  133. package/dist/ui/field.d.ts +6 -2
  134. package/dist/ui/field.js +4 -2
  135. package/dist/ui/infinite-canvas.d.ts +19 -22
  136. package/dist/ui/infinite-canvas.js +264 -325
  137. package/dist/ui/morphing-panel.d.ts +5 -1
  138. package/dist/ui/morphing-panel.js +4 -2
  139. package/dist/ui/rich-tooltip.d.ts +2 -1
  140. package/dist/ui/rich-tooltip.js +32 -29
  141. package/dist/ui/select.js +2 -2
  142. package/dist/ui/sidebar-mobile.js +1 -1
  143. package/dist/ui/sidebar-provider.d.ts +3 -1
  144. package/dist/ui/sidebar-provider.js +3 -2
  145. package/dist/ui/sidebar-resize-rail.js +1 -1
  146. package/dist/ui/sidebar.d.ts +6 -5
  147. package/dist/ui/sidebar.js +10 -9
  148. package/dist/ui/swap.d.ts +14 -0
  149. package/dist/ui/swap.js +69 -0
  150. package/dist/ui/tabs.d.ts +1 -1
  151. package/dist/ui/tabs.js +2 -13
  152. package/dist/ui/timeline.d.ts +1 -1
  153. package/dist/ui/timeline.js +2 -2
  154. package/dist/ui/typography.d.ts +11 -5
  155. package/dist/ui/typography.js +18 -6
  156. package/dist/user-ask.js +1 -1
  157. package/lint/biome-preset.json +3 -0
  158. package/lint/class-policy.mjs +17 -0
  159. package/lint/eslint-plugin.mjs +87 -0
  160. package/lint/grit-rules.mjs +40 -0
  161. package/lint/no-component-style-classes.grit +16 -0
  162. package/lint/no-raw-type-classes.grit +3 -4
  163. package/lint/write-grit-rules.mjs +19 -0
  164. package/package.json +12 -3
  165. package/dist/styles/type-scale.css +0 -72
@@ -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,6 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/markdown-editor/bubble-menu.d.ts
3
+ export declare function MarkdownEditorBubbleMenu({ children }: {
4
+ children?: ReactNode;
5
+ }): import("react").JSX.Element | null;
6
+ //#endregion
@@ -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;
@@ -17,80 +17,110 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
17
17
  import { BoldIcon, ChevronDownIcon, CodeIcon, ImageIcon, ItalicIcon, LinkIcon, ListChecksIcon, ListIcon, ListOrderedIcon, QuoteIcon, RedoIcon, SquareCodeIcon, StrikethroughIcon, UndoIcon } from "lucide-react";
18
18
  import { useEditorState } from "@tiptap/react";
19
19
  //#region src/markdown-editor/toolbar.tsx
20
+ function useMarkdownEditorFormatActions() {
21
+ const { editor } = useMarkdownEditor();
22
+ const state = useEditorState({
23
+ editor,
24
+ selector: ({ editor: current }) => ({
25
+ bold: Boolean(current?.isActive("bold")),
26
+ italic: Boolean(current?.isActive("italic")),
27
+ strike: Boolean(current?.isActive("strike")),
28
+ code: Boolean(current?.isActive("code")),
29
+ bulletList: Boolean(current?.isActive("bulletList")),
30
+ orderedList: Boolean(current?.isActive("orderedList")),
31
+ taskList: Boolean(current?.isActive("taskList")),
32
+ blockquote: Boolean(current?.isActive("blockquote")),
33
+ codeBlock: Boolean(current?.isActive("codeBlock"))
34
+ })
35
+ });
36
+ const focused = () => editor?.chain().focus();
37
+ return {
38
+ marks: [
39
+ {
40
+ label: "Bold",
41
+ Icon: BoldIcon,
42
+ active: Boolean(state?.bold),
43
+ run: () => focused()?.toggleBold().run()
44
+ },
45
+ {
46
+ label: "Italic",
47
+ Icon: ItalicIcon,
48
+ active: Boolean(state?.italic),
49
+ run: () => focused()?.toggleItalic().run()
50
+ },
51
+ {
52
+ label: "Strikethrough",
53
+ Icon: StrikethroughIcon,
54
+ active: Boolean(state?.strike),
55
+ run: () => focused()?.toggleStrike().run()
56
+ },
57
+ {
58
+ label: "Inline code",
59
+ Icon: CodeIcon,
60
+ active: Boolean(state?.code),
61
+ run: () => focused()?.toggleCode().run()
62
+ }
63
+ ],
64
+ blocks: [
65
+ {
66
+ label: "Bullet list",
67
+ Icon: ListIcon,
68
+ active: Boolean(state?.bulletList),
69
+ run: () => focused()?.toggleBulletList().run()
70
+ },
71
+ {
72
+ label: "Numbered list",
73
+ Icon: ListOrderedIcon,
74
+ active: Boolean(state?.orderedList),
75
+ run: () => focused()?.toggleOrderedList().run()
76
+ },
77
+ {
78
+ label: "Task list",
79
+ Icon: ListChecksIcon,
80
+ active: Boolean(state?.taskList),
81
+ run: () => focused()?.toggleTaskList().run()
82
+ },
83
+ {
84
+ label: "Quote",
85
+ Icon: QuoteIcon,
86
+ active: Boolean(state?.blockquote),
87
+ run: () => focused()?.toggleBlockquote().run()
88
+ },
89
+ {
90
+ label: "Code block",
91
+ Icon: SquareCodeIcon,
92
+ active: Boolean(state?.codeBlock),
93
+ run: () => focused()?.toggleCodeBlock().run()
94
+ }
95
+ ]
96
+ };
97
+ }
98
+ function MarkdownEditorFormatButton({ action, onMouseDown }) {
99
+ const { editor, disabled, source } = useMarkdownEditor();
100
+ const { label, Icon, active, run } = action;
101
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
102
+ render: /* @__PURE__ */ jsx(ToolbarButton, {
103
+ type: "button",
104
+ iconOnly: true,
105
+ "aria-label": label,
106
+ "aria-pressed": active,
107
+ disabled: disabled || source || !editor,
108
+ onMouseDown,
109
+ onClick: run
110
+ }),
111
+ children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" })
112
+ }), /* @__PURE__ */ jsx(TooltipContent, { children: label })] });
113
+ }
20
114
  function MarkdownEditorToolbar({ children }) {
21
115
  const { editor, disabled, source, sourceRequired, setSource, uploads } = useMarkdownEditor();
22
- const state = useEditorState({
116
+ const { marks, blocks } = useMarkdownEditorFormatActions();
117
+ const history = useEditorState({
23
118
  editor,
24
119
  selector: ({ editor: current }) => ({
25
- bold: current?.isActive("bold"),
26
- italic: current?.isActive("italic"),
27
- strike: current?.isActive("strike"),
28
- code: current?.isActive("code"),
29
- bulletList: current?.isActive("bulletList"),
30
- orderedList: current?.isActive("orderedList"),
31
- taskList: current?.isActive("taskList"),
32
- blockquote: current?.isActive("blockquote"),
33
- codeBlock: current?.isActive("codeBlock"),
34
120
  undo: current?.can().undo(),
35
121
  redo: current?.can().redo()
36
122
  })
37
123
  });
38
- const actions = [
39
- {
40
- label: "Bold",
41
- Icon: BoldIcon,
42
- active: state?.bold,
43
- run: () => editor?.chain().focus().toggleBold().run()
44
- },
45
- {
46
- label: "Italic",
47
- Icon: ItalicIcon,
48
- active: state?.italic,
49
- run: () => editor?.chain().focus().toggleItalic().run()
50
- },
51
- {
52
- label: "Strikethrough",
53
- Icon: StrikethroughIcon,
54
- active: state?.strike,
55
- run: () => editor?.chain().focus().toggleStrike().run()
56
- },
57
- {
58
- label: "Inline code",
59
- Icon: CodeIcon,
60
- active: state?.code,
61
- run: () => editor?.chain().focus().toggleCode().run()
62
- },
63
- {
64
- label: "Bullet list",
65
- Icon: ListIcon,
66
- active: state?.bulletList,
67
- run: () => editor?.chain().focus().toggleBulletList().run()
68
- },
69
- {
70
- label: "Numbered list",
71
- Icon: ListOrderedIcon,
72
- active: state?.orderedList,
73
- run: () => editor?.chain().focus().toggleOrderedList().run()
74
- },
75
- {
76
- label: "Task list",
77
- Icon: ListChecksIcon,
78
- active: state?.taskList,
79
- run: () => editor?.chain().focus().toggleTaskList().run()
80
- },
81
- {
82
- label: "Quote",
83
- Icon: QuoteIcon,
84
- active: state?.blockquote,
85
- run: () => editor?.chain().focus().toggleBlockquote().run()
86
- },
87
- {
88
- label: "Code block",
89
- Icon: SquareCodeIcon,
90
- active: state?.codeBlock,
91
- run: () => editor?.chain().focus().toggleCodeBlock().run()
92
- }
93
- ];
94
124
  return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx("div", {
95
125
  "data-control-ui": "markdown-editor",
96
126
  "data-control-family": "markdown-editor",
@@ -106,18 +136,10 @@ function MarkdownEditorToolbar({ children }) {
106
136
  children: [
107
137
  /* @__PURE__ */ jsx(MarkdownEditorHeading, {}),
108
138
  /* @__PURE__ */ jsx(ToolbarSeparator, {}),
109
- actions.map(({ label, Icon, active, run }) => /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
110
- render: /* @__PURE__ */ jsx(ToolbarButton, {
111
- type: "button",
112
- iconOnly: true,
113
- "aria-label": label,
114
- "aria-pressed": Boolean(active),
115
- disabled: disabled || source || !editor,
116
- onMouseDown: (event) => event.preventDefault(),
117
- onClick: run
118
- }),
119
- children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" })
120
- }), /* @__PURE__ */ jsx(TooltipContent, { children: label })] }, label)),
139
+ [...marks, ...blocks].map((action) => /* @__PURE__ */ jsx(MarkdownEditorFormatButton, {
140
+ action,
141
+ onMouseDown: (event) => event.preventDefault()
142
+ }, action.label)),
121
143
  /* @__PURE__ */ jsx(MarkdownEditorLink, {}),
122
144
  /* @__PURE__ */ jsx(MarkdownEditorImage, {}),
123
145
  /* @__PURE__ */ jsx(ToolbarSeparator, {}),
@@ -125,7 +147,7 @@ function MarkdownEditorToolbar({ children }) {
125
147
  type: "button",
126
148
  iconOnly: true,
127
149
  "aria-label": "Undo",
128
- disabled: disabled || source || !state?.undo,
150
+ disabled: disabled || source || !history?.undo,
129
151
  onClick: () => editor?.chain().focus().undo().run(),
130
152
  children: /* @__PURE__ */ jsx(UndoIcon, { "aria-hidden": "true" })
131
153
  }),
@@ -133,7 +155,7 @@ function MarkdownEditorToolbar({ children }) {
133
155
  type: "button",
134
156
  iconOnly: true,
135
157
  "aria-label": "Redo",
136
- disabled: disabled || source || !state?.redo,
158
+ disabled: disabled || source || !history?.redo,
137
159
  onClick: () => editor?.chain().focus().redo().run(),
138
160
  children: /* @__PURE__ */ jsx(RedoIcon, { "aria-hidden": "true" })
139
161
  }),
@@ -395,4 +417,4 @@ function MarkdownEditorUploads() {
395
417
  }) : null] });
396
418
  }
397
419
  //#endregion
398
- export { MarkdownEditorImage, MarkdownEditorLink, MarkdownEditorToolbar, MarkdownEditorUploads };
420
+ export { MarkdownEditorFormatButton, MarkdownEditorHeading, MarkdownEditorImage, MarkdownEditorLink, MarkdownEditorToolbar, MarkdownEditorUploads, useMarkdownEditorFormatActions };