@bendyline/squisq-editor-react 1.6.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +12 -3
  2. package/dist/index.d.ts +1229 -179
  3. package/dist/index.js +18017 -11231
  4. package/dist/index.js.map +1 -1
  5. package/dist/monaco.d.ts +1 -0
  6. package/dist/monaco.js +4 -0
  7. package/dist/monaco.js.map +1 -0
  8. package/dist/styles/index.css +1471 -245
  9. package/package.json +9 -5
  10. package/src/BlockPropertiesPopover.tsx +30 -15
  11. package/src/DocumentSettingsDialog.tsx +13 -21
  12. package/src/EditorContext.tsx +199 -42
  13. package/src/EditorShell.tsx +348 -260
  14. package/src/ImageEditor.tsx +22 -1
  15. package/src/InlinePreviewGutter.tsx +16 -8
  16. package/src/MediaBin.tsx +107 -14
  17. package/src/OutlinePanel.tsx +202 -7
  18. package/src/PlainHtmlPreview.tsx +40 -1
  19. package/src/PreviewControls.tsx +309 -69
  20. package/src/PreviewPanel.tsx +312 -78
  21. package/src/RawEditor.tsx +13 -2
  22. package/src/StatusBar.tsx +5 -2
  23. package/src/TemplateAnnotation.ts +12 -3
  24. package/src/TemplatePicker.tsx +75 -21
  25. package/src/ThemeCustomizerPanel.tsx +8 -0
  26. package/src/ThemePicker.tsx +10 -5
  27. package/src/TimelineBlockPreview.tsx +1 -1
  28. package/src/Toolbar.tsx +202 -41
  29. package/src/TransitionPicker.tsx +36 -4
  30. package/src/ViewMenuPanel.tsx +12 -14
  31. package/src/WysiwygEditor.tsx +124 -91
  32. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +33 -0
  33. package/src/__tests__/blockTagActivity.test.ts +183 -0
  34. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +75 -0
  35. package/src/__tests__/buildPreviewDocContent.test.ts +48 -0
  36. package/src/__tests__/buildPreviewDocTransition.test.ts +21 -1
  37. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +60 -0
  38. package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
  39. package/src/__tests__/documentSettingsDialog.test.tsx +29 -1
  40. package/src/__tests__/editorScrollHandoff.test.tsx +102 -0
  41. package/src/__tests__/editorScrollSync.test.ts +65 -0
  42. package/src/__tests__/editorShellProps.test.tsx +123 -1
  43. package/src/__tests__/emojiPicker.test.tsx +2 -42
  44. package/src/__tests__/headingTransition.test.ts +0 -12
  45. package/src/__tests__/imageEditAffordance.test.tsx +5 -1
  46. package/src/__tests__/imageEditModalContract.test.tsx +119 -0
  47. package/src/__tests__/imageEditorShell.test.tsx +15 -1
  48. package/src/__tests__/jsonEditor.test.tsx +56 -1
  49. package/src/__tests__/layersPanel.test.tsx +75 -5
  50. package/src/__tests__/majorApiTypes.test.ts +26 -0
  51. package/src/__tests__/mediaBinDrop.test.tsx +90 -0
  52. package/src/__tests__/narrationSave.test.ts +148 -0
  53. package/src/__tests__/outlinePanel.test.tsx +93 -3
  54. package/src/__tests__/outlineSource.test.ts +217 -0
  55. package/src/__tests__/plainHtmlPreview.test.tsx +17 -1
  56. package/src/__tests__/presentationMode.test.tsx +290 -0
  57. package/src/__tests__/previewControls.test.tsx +218 -5
  58. package/src/__tests__/previewPanelAudioFailure.test.tsx +46 -0
  59. package/src/__tests__/previewPanelPresentation.test.tsx +126 -0
  60. package/src/__tests__/previewPanelTransforms.test.tsx +117 -0
  61. package/src/__tests__/rawEditorModelIsolation.test.ts +19 -0
  62. package/src/__tests__/resolveBlockVisual.test.ts +43 -0
  63. package/src/__tests__/statusBar.test.tsx +27 -0
  64. package/src/__tests__/teleprompter.test.tsx +190 -0
  65. package/src/__tests__/templateAnnotationRoundTrip.test.ts +26 -0
  66. package/src/__tests__/templatePickerPortal.test.tsx +62 -0
  67. package/src/__tests__/tiptapBridge.test.ts +23 -0
  68. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
  69. package/src/__tests__/transformStyleId.test.ts +13 -0
  70. package/src/__tests__/useImageEditor.test.tsx +67 -0
  71. package/src/__tests__/useMediaRecorder.test.ts +75 -0
  72. package/src/__tests__/viewMenuPanel.test.tsx +130 -0
  73. package/src/__tests__/wysiwygImageUpload.test.ts +69 -0
  74. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +56 -0
  75. package/src/asciiDiagram/AsciiDiagramExtension.ts +343 -0
  76. package/src/asciiDiagram/AsciiDiagramWidget.tsx +224 -0
  77. package/src/asciiDiagram/RepairableDiagramExtension.ts +200 -0
  78. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +145 -0
  79. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +159 -0
  80. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +227 -0
  81. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
  82. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
  83. package/src/asciiDiagram/asciiDiagramCommands.ts +148 -0
  84. package/src/asciiDiagram/asciiDiagramData.ts +116 -0
  85. package/src/asciiDiagram/asciiDiagramOps.ts +219 -0
  86. package/src/asciiDiagram/asciiPaste.ts +19 -0
  87. package/src/blockTagActivity.ts +233 -0
  88. package/src/buildDocumentPreviewMarkdown.ts +168 -0
  89. package/src/buildPreviewDoc.ts +19 -12
  90. package/src/customTemplates/CustomLayoutManager.tsx +3 -2
  91. package/src/customTemplates/CustomTemplateContext.tsx +6 -0
  92. package/src/customTemplates/TemplateDesigner.tsx +7 -4
  93. package/src/customTemplates/__tests__/library.test.ts +8 -0
  94. package/src/customTemplates/designer.css +158 -99
  95. package/src/customTemplates/library.ts +16 -3
  96. package/src/customThemes/CustomThemeContext.tsx +6 -0
  97. package/src/customThemes/CustomThemeDialog.tsx +35 -5
  98. package/src/customThemes/ImportThemeSection.tsx +178 -0
  99. package/src/customThemes/__tests__/customThemeLibrary.test.ts +9 -0
  100. package/src/customThemes/__tests__/importThemeSection.test.tsx +192 -0
  101. package/src/customThemes/customThemeLibrary.ts +7 -4
  102. package/src/customThemes/index.ts +7 -1
  103. package/src/customThemes/themeDraft.ts +23 -7
  104. package/src/diagram/DiagramCanvas.tsx +9 -4
  105. package/src/diagram/types.ts +35 -0
  106. package/src/editorScrollSync.ts +73 -0
  107. package/src/emojiData.ts +3 -23
  108. package/src/headingTransition.ts +0 -15
  109. package/src/imageEditor/CanvasSurface.tsx +29 -23
  110. package/src/imageEditor/LayersPanel.tsx +78 -3
  111. package/src/imageEditor/Toolbar.tsx +5 -1
  112. package/src/imageEditor/icons.tsx +4 -0
  113. package/src/imageEditor/image-editor.css +75 -0
  114. package/src/imageEditor/layers/SelectionHandles.tsx +1 -1
  115. package/src/imageEditor/useImageEditor.ts +71 -14
  116. package/src/index.ts +191 -19
  117. package/src/jsonEditor/JsonEditorContext.tsx +12 -6
  118. package/src/jsonEditor/RenderNode.tsx +24 -3
  119. package/src/jsonEditor/editors.tsx +86 -19
  120. package/src/mediaEntries.ts +12 -0
  121. package/src/monaco.ts +35 -0
  122. package/src/monacoWorkers.ts +89 -0
  123. package/src/outlineSource.ts +171 -0
  124. package/src/presentation/PresentationMode.tsx +596 -0
  125. package/src/rawEditorIsolation.ts +18 -0
  126. package/src/recorder/hooks/useMediaRecorder.ts +97 -53
  127. package/src/resolveBlockVisual.ts +10 -4
  128. package/src/scene/Scene.tsx +48 -18
  129. package/src/scene/SceneBlockExtension.ts +17 -10
  130. package/src/scene/SceneBlockWidget.tsx +5 -1
  131. package/src/scene/SceneViewport.tsx +7 -3
  132. package/src/scene/ShapePalette.tsx +17 -2
  133. package/src/scene/__tests__/DiagramAdapter.test.ts +86 -0
  134. package/src/scene/__tests__/SceneBlockExtension.test.ts +33 -0
  135. package/src/scene/__tests__/sceneIsolation.test.tsx +93 -0
  136. package/src/scene/adapters/DiagramAdapter.ts +12 -101
  137. package/src/scene/commands/SceneCommand.ts +4 -1
  138. package/src/scene/index.ts +1 -6
  139. package/src/scene/layers/DiagramEdges.tsx +21 -7
  140. package/src/scene/layers/edgeGeometry.ts +9 -1
  141. package/src/scene/layers/nodeCard.tsx +27 -8
  142. package/src/scene/scene.css +5 -2
  143. package/src/scene/text/SceneTextOverlay.tsx +5 -5
  144. package/src/scene/text/sceneTextChannel.ts +26 -20
  145. package/src/scene/text/sceneTextConfig.ts +4 -0
  146. package/src/scene/tools/ConnectTool.ts +13 -4
  147. package/src/scene/tools/SceneTool.ts +8 -0
  148. package/src/scene/tools/SelectTool.ts +34 -18
  149. package/src/scene/tools/ShapeTool.ts +2 -3
  150. package/src/styles/ascii-diagram.css +79 -0
  151. package/src/styles/ascii-timeline.css +499 -0
  152. package/src/styles/editor.css +706 -120
  153. package/src/styles/index.css +3 -0
  154. package/src/styles/tree-view.css +139 -0
  155. package/src/teleprompter/TeleprompterControls.tsx +218 -0
  156. package/src/teleprompter/TeleprompterSelfView.tsx +22 -0
  157. package/src/teleprompter/TeleprompterSurface.tsx +267 -0
  158. package/src/teleprompter/TeleprompterView.tsx +338 -0
  159. package/src/teleprompter/canvasRenderer.ts +161 -0
  160. package/src/teleprompter/floatingWindow.ts +352 -0
  161. package/src/teleprompter/index.ts +61 -0
  162. package/src/teleprompter/pcmWorklet.ts +62 -0
  163. package/src/teleprompter/recording/insertPreamble.ts +80 -0
  164. package/src/teleprompter/recording/narrationSave.ts +134 -0
  165. package/src/teleprompter/recording/useNarrationRecorder.ts +367 -0
  166. package/src/teleprompter/scrollModel.ts +85 -0
  167. package/src/teleprompter/teleprompterTheme.ts +303 -0
  168. package/src/teleprompter/types.ts +38 -0
  169. package/src/teleprompter/useFloatingWindow.ts +74 -0
  170. package/src/teleprompter/useMicAnalysis.ts +211 -0
  171. package/src/teleprompter/useTeleprompter.ts +421 -0
  172. package/src/templateContentPreviewResolver.ts +12 -5
  173. package/src/timeline/TimelineEditorWidget.tsx +702 -0
  174. package/src/timeline/TimelineViewExtension.ts +252 -0
  175. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +355 -0
  176. package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
  177. package/src/timeline/__tests__/timelineCommands.test.ts +327 -0
  178. package/src/timeline/__tests__/timelineOps.test.ts +222 -0
  179. package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
  180. package/src/timeline/timelineCommands.ts +367 -0
  181. package/src/timeline/timelineData.ts +54 -0
  182. package/src/timeline/timelineOps.ts +277 -0
  183. package/src/timeline/timelinePaste.ts +8 -0
  184. package/src/tiptapBridge.ts +24 -16
  185. package/src/transformStyleId.ts +17 -0
  186. package/src/treeview/TreeOutlineWidget.tsx +240 -0
  187. package/src/treeview/TreeViewExtension.ts +250 -0
  188. package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
  189. package/src/treeview/__tests__/treeOps.test.ts +125 -0
  190. package/src/treeview/__tests__/treePaste.test.ts +40 -0
  191. package/src/treeview/__tests__/treeViewCommands.test.ts +163 -0
  192. package/src/treeview/treeOps.ts +172 -0
  193. package/src/treeview/treePaste.ts +13 -0
  194. package/src/treeview/treeViewCommands.ts +94 -0
  195. package/src/treeview/treeViewData.ts +41 -0
  196. package/src/useMonacoLoader.ts +11 -34
  197. package/src/wysiwygImageUpload.ts +113 -0
  198. package/src/diagram/DiagramExtension.ts +0 -209
  199. package/src/diagram/DiagramWidget.tsx +0 -272
  200. package/src/diagram/useDiagramData.ts +0 -126
@@ -1,13 +1,13 @@
1
1
  /**
2
- * sceneTextChannel — a module singleton that bridges the canvas's inline
2
+ * sceneTextChannel — an editor-owned channel that bridges the canvas's inline
3
3
  * text editor (which renders in a **detached React root** created by the
4
4
  * Diagram/SceneBlock ProseMirror extensions, outside `<EditorProvider>`)
5
5
  * to the provider, so the top formatting toolbar can target it.
6
6
  *
7
7
  * The active textbox's `SceneTextOverlay` publishes its Tiptap editor here
8
8
  * on focus and clears it on blur/unmount; `EditorProvider` subscribes and
9
- * mirrors the handle into `activeSceneText`. Singleton because only one
10
- * canvas textbox can be focused at a time.
9
+ * mirrors the handle into `activeSceneText`. Each EditorProvider creates its
10
+ * own channel, preventing focus in one editor from mutating another editor.
11
11
  */
12
12
 
13
13
  import type { Editor as TiptapEditor } from '@tiptap/core';
@@ -20,21 +20,27 @@ export interface SceneTextHandle {
20
20
 
21
21
  type Listener = (handle: SceneTextHandle | null) => void;
22
22
 
23
- let current: SceneTextHandle | null = null;
24
- const listeners = new Set<Listener>();
23
+ export interface SceneTextChannel {
24
+ set(handle: SceneTextHandle | null): void;
25
+ get(): SceneTextHandle | null;
26
+ subscribe(listener: Listener): () => void;
27
+ }
25
28
 
26
- export const sceneTextChannel = {
27
- /** Publish (or clear with `null`) the focused canvas textbox editor. */
28
- set(handle: SceneTextHandle | null): void {
29
- current = handle;
30
- for (const listener of listeners) listener(handle);
31
- },
32
- get(): SceneTextHandle | null {
33
- return current;
34
- },
35
- /** Subscribe to handle changes. Returns an unsubscribe function. */
36
- subscribe(listener: Listener): () => void {
37
- listeners.add(listener);
38
- return () => listeners.delete(listener);
39
- },
40
- };
29
+ /** Create a channel owned by one EditorProvider; no cross-editor singleton. */
30
+ export function createSceneTextChannel(): SceneTextChannel {
31
+ let current: SceneTextHandle | null = null;
32
+ const listeners = new Set<Listener>();
33
+ return {
34
+ set(handle) {
35
+ current = handle;
36
+ for (const listener of listeners) listener(handle);
37
+ },
38
+ get() {
39
+ return current;
40
+ },
41
+ subscribe(listener) {
42
+ listeners.add(listener);
43
+ return () => listeners.delete(listener);
44
+ },
45
+ };
46
+ }
@@ -5,9 +5,13 @@
5
5
  * the host owns persistence (markdown heading vs. layout JSON blob).
6
6
  */
7
7
 
8
+ import type { SceneTextChannel } from './sceneTextChannel';
9
+
8
10
  export type SceneTextLevel = 'inline' | 'block' | 'rich';
9
11
 
10
12
  export interface SceneTextEditConfig {
13
+ /** Per-editor bridge to the formatting toolbar. */
14
+ channel?: SceneTextChannel;
11
15
  /**
12
16
  * Map a hit layer id (what the pointer landed on) to the layer id whose
13
17
  * on-screen box the editor should cover, or `null` when that layer isn't
@@ -32,7 +32,13 @@ interface ConnectState {
32
32
  hoveredTargetId: string | null;
33
33
  }
34
34
 
35
- let state: ConnectState | null = null;
35
+ function getState(ctx: SceneToolContext): ConnectState | null {
36
+ return (ctx.interaction.connect as ConnectState | undefined) ?? null;
37
+ }
38
+
39
+ function setState(ctx: SceneToolContext, state: ConnectState | null): void {
40
+ ctx.interaction.connect = state ?? undefined;
41
+ }
36
42
 
37
43
  function pointerFromEvent(e: React.PointerEvent): { sx: number; sy: number } {
38
44
  const rect = (e.currentTarget as Element).getBoundingClientRect();
@@ -126,18 +132,19 @@ export const ConnectTool: SceneTool = {
126
132
  const sourceBox = nodeBoxFromCard(ctx, nodeId);
127
133
  if (!sourceBox) return;
128
134
  const preview = updatePreview(sourceBox, v, null);
129
- state = {
135
+ setState(ctx, {
130
136
  sourceNodeId: nodeId,
131
137
  sourceBox,
132
138
  start: preview.start,
133
139
  end: preview.end,
134
140
  hoveredTargetId: null,
135
- };
141
+ });
136
142
  (e.currentTarget as Element).setPointerCapture?.(e.pointerId);
137
143
  e.stopPropagation();
138
144
  },
139
145
 
140
146
  onPointerMove(e, ctx) {
147
+ const state = getState(ctx);
141
148
  if (!state) return;
142
149
  const { sx, sy } = pointerFromEvent(e);
143
150
  const v = ctx.screenToViewport(sx, sy);
@@ -152,16 +159,18 @@ export const ConnectTool: SceneTool = {
152
159
  },
153
160
 
154
161
  onPointerUp(e, ctx) {
162
+ const state = getState(ctx);
155
163
  if (!state) return;
156
164
  const target = state.hoveredTargetId;
157
165
  if (target) {
158
166
  ctx.dispatch({ kind: 'addEdge', source: state.sourceNodeId, target });
159
167
  }
160
168
  (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);
161
- state = null;
169
+ setState(ctx, null);
162
170
  },
163
171
 
164
172
  renderOverlay(ctx): JSX.Element | null {
173
+ const state = getState(ctx);
165
174
  const points = nodeBoxes(ctx).flatMap((box) =>
166
175
  connectionPoints(box).map((point, index) =>
167
176
  createElement('circle', {
@@ -14,7 +14,15 @@ import type { SceneCommand, SceneEdge } from '../commands/SceneCommand';
14
14
  import type { HitTestable } from '../hooks/useSceneHitTest';
15
15
  import type { SceneTransform } from '../hooks/useScenePanZoom';
16
16
 
17
+ /** Mutable gesture storage owned by one Scene instance. */
18
+ export interface SceneInteractionState {
19
+ selectDrag?: unknown;
20
+ connect?: unknown;
21
+ }
22
+
17
23
  export interface SceneToolContext {
24
+ /** Gesture state private to the owning Scene instance. */
25
+ interaction: SceneInteractionState;
18
26
  /** Current viewport size in viewport units. */
19
27
  viewport: { width: number; height: number };
20
28
  /** Current pan/zoom transform. */
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { createElement, type JSX } from 'react';
21
- import type { SceneTool, SceneToolContext } from './SceneTool';
21
+ import type { SceneInteractionState, SceneTool, SceneToolContext } from './SceneTool';
22
22
  import type { SceneCommand } from '../commands/SceneCommand';
23
23
  import type { ResizeCorner } from '../SceneSelection';
24
24
 
@@ -53,7 +53,13 @@ type DragState = MoveDragState | MarqueeDragState | ResizeDragState;
53
53
  const DRAG_THRESHOLD_PX = 3;
54
54
  const MIN_LAYER_SIZE = 8;
55
55
 
56
- let dragState: DragState | null = null;
56
+ function getDragState(interaction: SceneInteractionState): DragState | null {
57
+ return (interaction.selectDrag as DragState | undefined) ?? null;
58
+ }
59
+
60
+ function setDragState(interaction: SceneInteractionState, state: DragState | null): void {
61
+ interaction.selectDrag = state ?? undefined;
62
+ }
57
63
 
58
64
  function pointerCoords(e: React.PointerEvent): { sx: number; sy: number } {
59
65
  const rect = (e.currentTarget as Element).getBoundingClientRect();
@@ -90,20 +96,23 @@ function applyResize(
90
96
  }
91
97
 
92
98
  /** Begin a handle-initiated resize drag. Called by the Scene when a corner is pressed. */
93
- export function beginHandleDrag(args: {
94
- layerId: string;
95
- corner: ResizeCorner;
96
- startV: { x: number; y: number };
97
- startBounds: { x: number; y: number; width: number; height: number };
98
- }): void {
99
- dragState = {
99
+ export function beginHandleDrag(
100
+ args: {
101
+ layerId: string;
102
+ corner: ResizeCorner;
103
+ startV: { x: number; y: number };
104
+ startBounds: { x: number; y: number; width: number; height: number };
105
+ },
106
+ interaction: SceneInteractionState,
107
+ ): void {
108
+ setDragState(interaction, {
100
109
  kind: 'resize',
101
110
  layerId: args.layerId,
102
111
  corner: args.corner,
103
112
  startV: args.startV,
104
113
  startBounds: args.startBounds,
105
114
  currentBounds: args.startBounds,
106
- };
115
+ });
107
116
  }
108
117
 
109
118
  export const SelectTool: SceneTool = {
@@ -135,29 +144,30 @@ export const SelectTool: SceneTool = {
135
144
  const origin = getLayerOrigin(ctx, id);
136
145
  if (origin) layerStarts.set(id, origin);
137
146
  }
138
- dragState = {
147
+ setDragState(ctx.interaction, {
139
148
  kind: 'move',
140
149
  startV: v,
141
150
  startS: { x: sx, y: sy },
142
151
  layerStarts,
143
152
  currentV: v,
144
153
  movedPastThreshold: false,
145
- };
154
+ });
146
155
  } else {
147
156
  if (!e.shiftKey) ctx.setSelection([]);
148
- dragState = {
157
+ setDragState(ctx.interaction, {
149
158
  kind: 'marquee',
150
159
  startV: v,
151
160
  startS: { x: sx, y: sy },
152
161
  currentV: v,
153
162
  movedPastThreshold: false,
154
- };
163
+ });
155
164
  }
156
165
 
157
166
  (e.currentTarget as Element).setPointerCapture?.(e.pointerId);
158
167
  },
159
168
 
160
169
  onPointerMove(e, ctx) {
170
+ const dragState = getDragState(ctx.interaction);
161
171
  if (!dragState) return;
162
172
  const { sx, sy } = pointerCoords(e);
163
173
  const v = ctx.screenToViewport(sx, sy);
@@ -182,6 +192,7 @@ export const SelectTool: SceneTool = {
182
192
  },
183
193
 
184
194
  onPointerUp(e, ctx) {
195
+ const dragState = getDragState(ctx.interaction);
185
196
  if (!dragState) return;
186
197
  if (
187
198
  dragState.kind === 'move' &&
@@ -208,7 +219,7 @@ export const SelectTool: SceneTool = {
208
219
  ctx.dispatch({ kind: 'resizeLayer', id: dragState.layerId, width, height });
209
220
  }
210
221
  (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);
211
- dragState = null;
222
+ setDragState(ctx.interaction, null);
212
223
  },
213
224
 
214
225
  onDoubleClick(e, ctx) {
@@ -232,7 +243,8 @@ export const SelectTool: SceneTool = {
232
243
  }
233
244
  },
234
245
 
235
- renderOverlay(): JSX.Element | null {
246
+ renderOverlay(ctx): JSX.Element | null {
247
+ const dragState = getDragState(ctx.interaction);
236
248
  if (!dragState || dragState.kind !== 'marquee' || !dragState.movedPastThreshold) return null;
237
249
  const box = boxFromCorners(dragState.startV, dragState.currentV);
238
250
  return createElement('rect', {
@@ -247,7 +259,10 @@ export const SelectTool: SceneTool = {
247
259
  };
248
260
 
249
261
  /** Return the active move-drag offset (viewport units). Null if no drag, or if the drag is a marquee/resize. */
250
- export function getActiveMoveOffset(): { dx: number; dy: number } | null {
262
+ export function getActiveMoveOffset(
263
+ interaction: SceneInteractionState,
264
+ ): { dx: number; dy: number } | null {
265
+ const dragState = getDragState(interaction);
251
266
  if (!dragState || dragState.kind !== 'move' || !dragState.movedPastThreshold) return null;
252
267
  return {
253
268
  dx: dragState.currentV.x - dragState.startV.x,
@@ -256,10 +271,11 @@ export function getActiveMoveOffset(): { dx: number; dy: number } | null {
256
271
  }
257
272
 
258
273
  /** Return the live bounds for the layer currently being resized. */
259
- export function getActiveResize(): {
274
+ export function getActiveResize(interaction: SceneInteractionState): {
260
275
  layerId: string;
261
276
  bounds: { x: number; y: number; width: number; height: number };
262
277
  } | null {
278
+ const dragState = getDragState(interaction);
263
279
  if (!dragState || dragState.kind !== 'resize') return null;
264
280
  return { layerId: dragState.layerId, bounds: dragState.currentBounds };
265
281
  }
@@ -2,9 +2,8 @@
2
2
  * ShapeTool — drag to draw a rectangle or circle as a new ShapeLayer.
3
3
  *
4
4
  * Exposed as a factory so a host can pre-bind the shape kind and
5
- * stroke/fill defaults. Returns a fresh SceneTool instance the
6
- * factory pattern lets multiple Scene instances coexist without
7
- * sharing module-level drag state.
5
+ * stroke/fill defaults. Returns a fresh SceneTool instance so hosts can bind
6
+ * independent tool identities and defaults; gesture state belongs to Scene.
8
7
  */
9
8
 
10
9
  import { createElement, type JSX } from 'react';
@@ -0,0 +1,79 @@
1
+ /* ASCII diagram fences in the WYSIWYG editor.
2
+ *
3
+ * A qualifying code fence is hidden behind its interactive canvas
4
+ * (AsciiDiagramWidget); toggling "Source" on the widget swaps the hide
5
+ * class for a monospace, non-wrapping view of the raw art. */
6
+
7
+ .squisq-wysiwyg-editor pre.squisq-ascii-fence-hidden {
8
+ display: none;
9
+ }
10
+
11
+ .squisq-wysiwyg-editor pre.squisq-ascii-fence-source {
12
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
13
+ /* prosemirror.css sets `.ProseMirror pre { white-space: pre-wrap }`,
14
+ which soft-wraps and visually scrambles column-aligned art. */
15
+ white-space: pre;
16
+ overflow-x: auto;
17
+ line-height: 1.2;
18
+ tab-size: 4;
19
+ }
20
+
21
+ .squisq-ascii-diagram-widget-host {
22
+ margin: 0.75rem 0 1.25rem;
23
+ }
24
+
25
+ .squisq-ascii-diagram-warnings {
26
+ margin-top: 4px;
27
+ font-size: 12px;
28
+ color: var(--squisq-theme-text-muted, #6b7280);
29
+ }
30
+
31
+ .squisq-ascii-diagram-warnings summary {
32
+ cursor: pointer;
33
+ user-select: none;
34
+ }
35
+
36
+ .squisq-ascii-diagram-warnings ul {
37
+ margin: 4px 0 0;
38
+ padding-left: 18px;
39
+ }
40
+
41
+ /* "Repair as diagram" affordance on broken box-art fences. The fence stays
42
+ * visible above this banner; clicking rewrites it to clean diagram art. */
43
+ .squisq-repair-banner {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 10px;
47
+ flex-wrap: wrap;
48
+ margin: 4px 0 1rem;
49
+ padding: 8px 12px;
50
+ border: 1px dashed var(--squisq-border, #d1d5db);
51
+ border-radius: 8px;
52
+ background: var(--squisq-input-bg, #f9fafb);
53
+ font-size: 13px;
54
+ }
55
+
56
+ .squisq-repair-banner-text {
57
+ color: var(--squisq-text-muted, #6b7280);
58
+ }
59
+
60
+ .squisq-repair-banner-btn {
61
+ cursor: pointer;
62
+ border: 1px solid var(--squisq-accent, #6366f1);
63
+ border-radius: 6px;
64
+ background: var(--squisq-accent, #6366f1);
65
+ color: #fff;
66
+ padding: 4px 12px;
67
+ font-size: 13px;
68
+ font-weight: 500;
69
+ line-height: 1.4;
70
+ white-space: nowrap;
71
+ }
72
+
73
+ .squisq-repair-banner-btn:hover {
74
+ filter: brightness(1.05);
75
+ }
76
+
77
+ .squisq-repair-banner-btn:active {
78
+ filter: brightness(0.95);
79
+ }