@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.
- package/README.md +12 -3
- package/dist/index.d.ts +1229 -179
- package/dist/index.js +18017 -11231
- package/dist/index.js.map +1 -1
- package/dist/monaco.d.ts +1 -0
- package/dist/monaco.js +4 -0
- package/dist/monaco.js.map +1 -0
- package/dist/styles/index.css +1471 -245
- package/package.json +9 -5
- package/src/BlockPropertiesPopover.tsx +30 -15
- package/src/DocumentSettingsDialog.tsx +13 -21
- package/src/EditorContext.tsx +199 -42
- package/src/EditorShell.tsx +348 -260
- package/src/ImageEditor.tsx +22 -1
- package/src/InlinePreviewGutter.tsx +16 -8
- package/src/MediaBin.tsx +107 -14
- package/src/OutlinePanel.tsx +202 -7
- package/src/PlainHtmlPreview.tsx +40 -1
- package/src/PreviewControls.tsx +309 -69
- package/src/PreviewPanel.tsx +312 -78
- package/src/RawEditor.tsx +13 -2
- package/src/StatusBar.tsx +5 -2
- package/src/TemplateAnnotation.ts +12 -3
- package/src/TemplatePicker.tsx +75 -21
- package/src/ThemeCustomizerPanel.tsx +8 -0
- package/src/ThemePicker.tsx +10 -5
- package/src/TimelineBlockPreview.tsx +1 -1
- package/src/Toolbar.tsx +202 -41
- package/src/TransitionPicker.tsx +36 -4
- package/src/ViewMenuPanel.tsx +12 -14
- package/src/WysiwygEditor.tsx +124 -91
- package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +33 -0
- package/src/__tests__/blockTagActivity.test.ts +183 -0
- package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +75 -0
- package/src/__tests__/buildPreviewDocContent.test.ts +48 -0
- package/src/__tests__/buildPreviewDocTransition.test.ts +21 -1
- package/src/__tests__/canvasSurfaceTextEditing.test.tsx +60 -0
- package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
- package/src/__tests__/documentSettingsDialog.test.tsx +29 -1
- package/src/__tests__/editorScrollHandoff.test.tsx +102 -0
- package/src/__tests__/editorScrollSync.test.ts +65 -0
- package/src/__tests__/editorShellProps.test.tsx +123 -1
- package/src/__tests__/emojiPicker.test.tsx +2 -42
- package/src/__tests__/headingTransition.test.ts +0 -12
- package/src/__tests__/imageEditAffordance.test.tsx +5 -1
- package/src/__tests__/imageEditModalContract.test.tsx +119 -0
- package/src/__tests__/imageEditorShell.test.tsx +15 -1
- package/src/__tests__/jsonEditor.test.tsx +56 -1
- package/src/__tests__/layersPanel.test.tsx +75 -5
- package/src/__tests__/majorApiTypes.test.ts +26 -0
- package/src/__tests__/mediaBinDrop.test.tsx +90 -0
- package/src/__tests__/narrationSave.test.ts +148 -0
- package/src/__tests__/outlinePanel.test.tsx +93 -3
- package/src/__tests__/outlineSource.test.ts +217 -0
- package/src/__tests__/plainHtmlPreview.test.tsx +17 -1
- package/src/__tests__/presentationMode.test.tsx +290 -0
- package/src/__tests__/previewControls.test.tsx +218 -5
- package/src/__tests__/previewPanelAudioFailure.test.tsx +46 -0
- package/src/__tests__/previewPanelPresentation.test.tsx +126 -0
- package/src/__tests__/previewPanelTransforms.test.tsx +117 -0
- package/src/__tests__/rawEditorModelIsolation.test.ts +19 -0
- package/src/__tests__/resolveBlockVisual.test.ts +43 -0
- package/src/__tests__/statusBar.test.tsx +27 -0
- package/src/__tests__/teleprompter.test.tsx +190 -0
- package/src/__tests__/templateAnnotationRoundTrip.test.ts +26 -0
- package/src/__tests__/templatePickerPortal.test.tsx +62 -0
- package/src/__tests__/tiptapBridge.test.ts +23 -0
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
- package/src/__tests__/transformStyleId.test.ts +13 -0
- package/src/__tests__/useImageEditor.test.tsx +67 -0
- package/src/__tests__/useMediaRecorder.test.ts +75 -0
- package/src/__tests__/viewMenuPanel.test.tsx +130 -0
- package/src/__tests__/wysiwygImageUpload.test.ts +69 -0
- package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +56 -0
- package/src/asciiDiagram/AsciiDiagramExtension.ts +343 -0
- package/src/asciiDiagram/AsciiDiagramWidget.tsx +224 -0
- package/src/asciiDiagram/RepairableDiagramExtension.ts +200 -0
- package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +145 -0
- package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +159 -0
- package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +227 -0
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
- package/src/asciiDiagram/asciiDiagramCommands.ts +148 -0
- package/src/asciiDiagram/asciiDiagramData.ts +116 -0
- package/src/asciiDiagram/asciiDiagramOps.ts +219 -0
- package/src/asciiDiagram/asciiPaste.ts +19 -0
- package/src/blockTagActivity.ts +233 -0
- package/src/buildDocumentPreviewMarkdown.ts +168 -0
- package/src/buildPreviewDoc.ts +19 -12
- package/src/customTemplates/CustomLayoutManager.tsx +3 -2
- package/src/customTemplates/CustomTemplateContext.tsx +6 -0
- package/src/customTemplates/TemplateDesigner.tsx +7 -4
- package/src/customTemplates/__tests__/library.test.ts +8 -0
- package/src/customTemplates/designer.css +158 -99
- package/src/customTemplates/library.ts +16 -3
- package/src/customThemes/CustomThemeContext.tsx +6 -0
- package/src/customThemes/CustomThemeDialog.tsx +35 -5
- package/src/customThemes/ImportThemeSection.tsx +178 -0
- package/src/customThemes/__tests__/customThemeLibrary.test.ts +9 -0
- package/src/customThemes/__tests__/importThemeSection.test.tsx +192 -0
- package/src/customThemes/customThemeLibrary.ts +7 -4
- package/src/customThemes/index.ts +7 -1
- package/src/customThemes/themeDraft.ts +23 -7
- package/src/diagram/DiagramCanvas.tsx +9 -4
- package/src/diagram/types.ts +35 -0
- package/src/editorScrollSync.ts +73 -0
- package/src/emojiData.ts +3 -23
- package/src/headingTransition.ts +0 -15
- package/src/imageEditor/CanvasSurface.tsx +29 -23
- package/src/imageEditor/LayersPanel.tsx +78 -3
- package/src/imageEditor/Toolbar.tsx +5 -1
- package/src/imageEditor/icons.tsx +4 -0
- package/src/imageEditor/image-editor.css +75 -0
- package/src/imageEditor/layers/SelectionHandles.tsx +1 -1
- package/src/imageEditor/useImageEditor.ts +71 -14
- package/src/index.ts +191 -19
- package/src/jsonEditor/JsonEditorContext.tsx +12 -6
- package/src/jsonEditor/RenderNode.tsx +24 -3
- package/src/jsonEditor/editors.tsx +86 -19
- package/src/mediaEntries.ts +12 -0
- package/src/monaco.ts +35 -0
- package/src/monacoWorkers.ts +89 -0
- package/src/outlineSource.ts +171 -0
- package/src/presentation/PresentationMode.tsx +596 -0
- package/src/rawEditorIsolation.ts +18 -0
- package/src/recorder/hooks/useMediaRecorder.ts +97 -53
- package/src/resolveBlockVisual.ts +10 -4
- package/src/scene/Scene.tsx +48 -18
- package/src/scene/SceneBlockExtension.ts +17 -10
- package/src/scene/SceneBlockWidget.tsx +5 -1
- package/src/scene/SceneViewport.tsx +7 -3
- package/src/scene/ShapePalette.tsx +17 -2
- package/src/scene/__tests__/DiagramAdapter.test.ts +86 -0
- package/src/scene/__tests__/SceneBlockExtension.test.ts +33 -0
- package/src/scene/__tests__/sceneIsolation.test.tsx +93 -0
- package/src/scene/adapters/DiagramAdapter.ts +12 -101
- package/src/scene/commands/SceneCommand.ts +4 -1
- package/src/scene/index.ts +1 -6
- package/src/scene/layers/DiagramEdges.tsx +21 -7
- package/src/scene/layers/edgeGeometry.ts +9 -1
- package/src/scene/layers/nodeCard.tsx +27 -8
- package/src/scene/scene.css +5 -2
- package/src/scene/text/SceneTextOverlay.tsx +5 -5
- package/src/scene/text/sceneTextChannel.ts +26 -20
- package/src/scene/text/sceneTextConfig.ts +4 -0
- package/src/scene/tools/ConnectTool.ts +13 -4
- package/src/scene/tools/SceneTool.ts +8 -0
- package/src/scene/tools/SelectTool.ts +34 -18
- package/src/scene/tools/ShapeTool.ts +2 -3
- package/src/styles/ascii-diagram.css +79 -0
- package/src/styles/ascii-timeline.css +499 -0
- package/src/styles/editor.css +706 -120
- package/src/styles/index.css +3 -0
- package/src/styles/tree-view.css +139 -0
- package/src/teleprompter/TeleprompterControls.tsx +218 -0
- package/src/teleprompter/TeleprompterSelfView.tsx +22 -0
- package/src/teleprompter/TeleprompterSurface.tsx +267 -0
- package/src/teleprompter/TeleprompterView.tsx +338 -0
- package/src/teleprompter/canvasRenderer.ts +161 -0
- package/src/teleprompter/floatingWindow.ts +352 -0
- package/src/teleprompter/index.ts +61 -0
- package/src/teleprompter/pcmWorklet.ts +62 -0
- package/src/teleprompter/recording/insertPreamble.ts +80 -0
- package/src/teleprompter/recording/narrationSave.ts +134 -0
- package/src/teleprompter/recording/useNarrationRecorder.ts +367 -0
- package/src/teleprompter/scrollModel.ts +85 -0
- package/src/teleprompter/teleprompterTheme.ts +303 -0
- package/src/teleprompter/types.ts +38 -0
- package/src/teleprompter/useFloatingWindow.ts +74 -0
- package/src/teleprompter/useMicAnalysis.ts +211 -0
- package/src/teleprompter/useTeleprompter.ts +421 -0
- package/src/templateContentPreviewResolver.ts +12 -5
- package/src/timeline/TimelineEditorWidget.tsx +702 -0
- package/src/timeline/TimelineViewExtension.ts +252 -0
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +355 -0
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
- package/src/timeline/__tests__/timelineCommands.test.ts +327 -0
- package/src/timeline/__tests__/timelineOps.test.ts +222 -0
- package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
- package/src/timeline/timelineCommands.ts +367 -0
- package/src/timeline/timelineData.ts +54 -0
- package/src/timeline/timelineOps.ts +277 -0
- package/src/timeline/timelinePaste.ts +8 -0
- package/src/tiptapBridge.ts +24 -16
- package/src/transformStyleId.ts +17 -0
- package/src/treeview/TreeOutlineWidget.tsx +240 -0
- package/src/treeview/TreeViewExtension.ts +250 -0
- package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
- package/src/treeview/__tests__/treeOps.test.ts +125 -0
- package/src/treeview/__tests__/treePaste.test.ts +40 -0
- package/src/treeview/__tests__/treeViewCommands.test.ts +163 -0
- package/src/treeview/treeOps.ts +172 -0
- package/src/treeview/treePaste.ts +13 -0
- package/src/treeview/treeViewCommands.ts +94 -0
- package/src/treeview/treeViewData.ts +41 -0
- package/src/useMonacoLoader.ts +11 -34
- package/src/wysiwygImageUpload.ts +113 -0
- package/src/diagram/DiagramExtension.ts +0 -209
- package/src/diagram/DiagramWidget.tsx +0 -272
- package/src/diagram/useDiagramData.ts +0 -126
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* sceneTextChannel —
|
|
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`.
|
|
10
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
23
|
+
export interface SceneTextChannel {
|
|
24
|
+
set(handle: SceneTextHandle | null): void;
|
|
25
|
+
get(): SceneTextHandle | null;
|
|
26
|
+
subscribe(listener: Listener): () => void;
|
|
27
|
+
}
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
6
|
-
*
|
|
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
|
+
}
|