@bendyline/squisq-editor-react 1.6.0 → 1.6.2

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 (68) hide show
  1. package/README.md +57 -10
  2. package/dist/index.d.ts +476 -105
  3. package/dist/index.js +8703 -6741
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles/fa-brands-400-AHOAZHCU.woff2 +0 -0
  6. package/dist/styles/fa-regular-400-VRZYIBIZ.woff2 +0 -0
  7. package/dist/styles/fa-solid-900-MDEYK55F.woff2 +0 -0
  8. package/dist/styles/fa-v4compatibility-ETEVP6IB.woff2 +0 -0
  9. package/dist/styles/index.css +14617 -0
  10. package/package.json +15 -7
  11. package/src/BlockPropertiesPopover.tsx +23 -7
  12. package/src/EditorContext.tsx +22 -16
  13. package/src/EditorShell.tsx +121 -35
  14. package/src/MediaBin.tsx +171 -28
  15. package/src/OutlinePanel.tsx +26 -4
  16. package/src/PreviewControls.tsx +475 -145
  17. package/src/PreviewPanel.tsx +17 -9
  18. package/src/RawEditor.tsx +10 -4
  19. package/src/TemplateAnnotation.ts +22 -7
  20. package/src/TemplateContentPreview.tsx +56 -0
  21. package/src/TemplatePicker.tsx +295 -128
  22. package/src/ThemeCustomizerPanel.tsx +22 -15
  23. package/src/Toolbar.tsx +547 -217
  24. package/src/TransitionPicker.tsx +8 -1
  25. package/src/VersionHistoryPanel.tsx +2 -2
  26. package/src/ViewSwitcher.tsx +4 -4
  27. package/src/WysiwygEditor.tsx +45 -3
  28. package/src/__tests__/buildPreviewDocTransition.test.ts +1 -2
  29. package/src/__tests__/codeContextSectionView.test.tsx +95 -0
  30. package/src/__tests__/codeContextZoneManager.test.ts +127 -0
  31. package/src/__tests__/diffContextSections.test.ts +39 -0
  32. package/src/__tests__/editorShellCodeContext.test.tsx +86 -0
  33. package/src/__tests__/editorShellProps.test.tsx +363 -0
  34. package/src/__tests__/headingTransition.test.ts +59 -9
  35. package/src/__tests__/imageEditorShell.test.tsx +23 -0
  36. package/src/__tests__/mediaReferences.test.ts +82 -0
  37. package/src/__tests__/previewControls.test.tsx +163 -0
  38. package/src/__tests__/templateAnnotationRoundTrip.test.ts +23 -2
  39. package/src/__tests__/templateContentPreview.test.ts +101 -0
  40. package/src/__tests__/tiptapBridge.test.ts +47 -0
  41. package/src/__tests__/useJsonEditorTokens.test.ts +59 -0
  42. package/src/__tests__/useMediaRecorder.test.ts +17 -0
  43. package/src/codeContext/CodeContextSectionView.tsx +124 -0
  44. package/src/codeContext/CodeContextZoneManager.ts +149 -0
  45. package/src/codeContext/CodeContextZones.tsx +121 -0
  46. package/src/codeContext/diffContextSections.ts +38 -0
  47. package/src/codeContext/types.ts +75 -0
  48. package/src/diagram/DiagramWidget.tsx +6 -4
  49. package/src/headingTransition.ts +96 -21
  50. package/src/index.ts +34 -2
  51. package/src/jsonEditor/useJsonEditorTokens.ts +13 -43
  52. package/src/mediaReferences.ts +299 -0
  53. package/src/recorder/hooks/useMediaRecorder.ts +9 -10
  54. package/src/scene/Scene.tsx +53 -7
  55. package/src/scene/SceneBlockWidget.tsx +6 -3
  56. package/src/scene/SceneSelection.tsx +19 -15
  57. package/src/scene/SceneSideToolbar.tsx +89 -0
  58. package/src/scene/layers/DiagramEdges.tsx +4 -3
  59. package/src/scene/layers/edgeGeometry.ts +23 -4
  60. package/src/scene/scene.css +142 -2
  61. package/src/scene/tools/ConnectTool.ts +113 -24
  62. package/src/scene/tools/DrawingConnectTool.ts +86 -16
  63. package/src/scene/tools/SceneTool.ts +2 -0
  64. package/src/styles/code-context.css +155 -0
  65. package/src/styles/editor.css +991 -84
  66. package/src/styles/index.css +1 -0
  67. package/src/templateContentPreviewResolver.ts +353 -0
  68. package/src/tiptapBridge.ts +60 -33
@@ -16,6 +16,12 @@
16
16
  import { createElement, type JSX } from 'react';
17
17
  import type { SceneTool, SceneToolContext } from './SceneTool';
18
18
  import { shapeIdFromLayerId, isPrimaryShapeLayer } from '../layers/shapeLayers';
19
+ import {
20
+ edgeEndpoints,
21
+ snapPointToward,
22
+ straightPath,
23
+ type EdgeNodeBox,
24
+ } from '../layers/edgeGeometry';
19
25
 
20
26
  /** A grab on the selected connector's endpoint handle. */
21
27
  export interface EndpointHit {
@@ -37,8 +43,9 @@ type DragState =
37
43
  | {
38
44
  mode: 'new';
39
45
  sourceId: string;
40
- anchor: { x: number; y: number };
41
- current: { x: number; y: number };
46
+ sourceBox: EdgeNodeBox;
47
+ start: { x: number; y: number };
48
+ end: { x: number; y: number };
42
49
  hovered: string | null;
43
50
  }
44
51
  | {
@@ -46,8 +53,9 @@ type DragState =
46
53
  connectorId: string;
47
54
  end: 'from' | 'to';
48
55
  fixedShapeId: string;
49
- anchor: { x: number; y: number };
50
- current: { x: number; y: number };
56
+ fixedBox: EdgeNodeBox;
57
+ start: { x: number; y: number };
58
+ endPoint: { x: number; y: number };
51
59
  hovered: string | null;
52
60
  };
53
61
 
@@ -59,10 +67,16 @@ export function createDrawingConnectTool(options: DrawingConnectOptions = {}): S
59
67
  return { sx: e.clientX - rect.left, sy: e.clientY - rect.top };
60
68
  }
61
69
 
62
- function shapeCenter(ctx: SceneToolContext, shapeId: string): { x: number; y: number } | null {
70
+ function shapeBox(ctx: SceneToolContext, shapeId: string): EdgeNodeBox | null {
63
71
  const it = ctx.hitItems.find((h) => h.id === `dshape-${shapeId}`);
64
72
  if (!it) return null;
65
- return { x: it.bounds.x + it.bounds.width / 2, y: it.bounds.y + it.bounds.height / 2 };
73
+ return {
74
+ id: shapeId,
75
+ x: it.bounds.x,
76
+ y: it.bounds.y,
77
+ width: it.bounds.width,
78
+ height: it.bounds.height,
79
+ };
66
80
  }
67
81
 
68
82
  function shapeAt(ctx: SceneToolContext, v: { x: number; y: number }): string | null {
@@ -71,6 +85,45 @@ export function createDrawingConnectTool(options: DrawingConnectOptions = {}): S
71
85
  return shapeIdFromLayerId(hitId);
72
86
  }
73
87
 
88
+ function previewNew(
89
+ sourceBox: EdgeNodeBox,
90
+ current: { x: number; y: number },
91
+ targetBox: EdgeNodeBox | null,
92
+ ): { start: { x: number; y: number }; end: { x: number; y: number } } {
93
+ if (targetBox) {
94
+ return (
95
+ edgeEndpoints([sourceBox, targetBox], sourceBox.id, targetBox.id) ?? {
96
+ start: current,
97
+ end: current,
98
+ }
99
+ );
100
+ }
101
+ return {
102
+ start: snapPointToward([sourceBox], sourceBox.id, current) ?? current,
103
+ end: current,
104
+ };
105
+ }
106
+
107
+ function previewRetarget(
108
+ fixedBox: EdgeNodeBox,
109
+ movedEnd: 'from' | 'to',
110
+ current: { x: number; y: number },
111
+ targetBox: EdgeNodeBox | null,
112
+ ): { start: { x: number; y: number }; end: { x: number; y: number } } {
113
+ if (targetBox) {
114
+ const source = movedEnd === 'from' ? targetBox : fixedBox;
115
+ const target = movedEnd === 'from' ? fixedBox : targetBox;
116
+ return (
117
+ edgeEndpoints([source, target], source.id, target.id) ?? {
118
+ start: current,
119
+ end: current,
120
+ }
121
+ );
122
+ }
123
+ const fixed = snapPointToward([fixedBox], fixedBox.id, current) ?? current;
124
+ return movedEnd === 'from' ? { start: current, end: fixed } : { start: fixed, end: current };
125
+ }
126
+
74
127
  return {
75
128
  id: 'connect',
76
129
  label: 'Connect',
@@ -85,14 +138,17 @@ export function createDrawingConnectTool(options: DrawingConnectOptions = {}): S
85
138
  // Endpoint handle of the selected connector → re-target gesture.
86
139
  const ep = options.endpointAt?.(v);
87
140
  if (ep) {
88
- const anchor = shapeCenter(ctx, ep.fixedShapeId) ?? v;
141
+ const fixedBox = shapeBox(ctx, ep.fixedShapeId);
142
+ if (!fixedBox) return;
143
+ const preview = previewRetarget(fixedBox, ep.end, v, null);
89
144
  drag = {
90
145
  mode: 'retarget',
91
146
  connectorId: ep.connectorId,
92
147
  end: ep.end,
93
148
  fixedShapeId: ep.fixedShapeId,
94
- anchor,
95
- current: v,
149
+ fixedBox,
150
+ start: preview.start,
151
+ endPoint: preview.end,
96
152
  hovered: null,
97
153
  };
98
154
  (e.currentTarget as Element).setPointerCapture?.(e.pointerId);
@@ -103,11 +159,15 @@ export function createDrawingConnectTool(options: DrawingConnectOptions = {}): S
103
159
  // Otherwise start a new connection from the shape under the pointer.
104
160
  const src = shapeAt(ctx, v);
105
161
  if (!src) return;
162
+ const sourceBox = shapeBox(ctx, src);
163
+ if (!sourceBox) return;
164
+ const preview = previewNew(sourceBox, v, null);
106
165
  drag = {
107
166
  mode: 'new',
108
167
  sourceId: src,
109
- anchor: shapeCenter(ctx, src) ?? v,
110
- current: v,
168
+ sourceBox,
169
+ start: preview.start,
170
+ end: preview.end,
111
171
  hovered: null,
112
172
  };
113
173
  (e.currentTarget as Element).setPointerCapture?.(e.pointerId);
@@ -118,10 +178,20 @@ export function createDrawingConnectTool(options: DrawingConnectOptions = {}): S
118
178
  if (!drag) return;
119
179
  const { sx, sy } = pointer(e);
120
180
  const v = ctx.screenToViewport(sx, sy);
121
- drag.current = v;
122
181
  const tgt = shapeAt(ctx, v);
123
182
  const exclude = drag.mode === 'new' ? drag.sourceId : drag.fixedShapeId;
124
- drag.hovered = tgt && tgt !== exclude ? tgt : null;
183
+ const targetId = tgt && tgt !== exclude ? tgt : null;
184
+ const targetBox = targetId ? shapeBox(ctx, targetId) : null;
185
+ if (drag.mode === 'new') {
186
+ const preview = previewNew(drag.sourceBox, v, targetBox);
187
+ drag.start = preview.start;
188
+ drag.end = preview.end;
189
+ } else {
190
+ const preview = previewRetarget(drag.fixedBox, drag.end, v, targetBox);
191
+ drag.start = preview.start;
192
+ drag.endPoint = preview.end;
193
+ }
194
+ drag.hovered = targetBox?.id ?? null;
125
195
  },
126
196
 
127
197
  onPointerUp(e, ctx) {
@@ -139,13 +209,13 @@ export function createDrawingConnectTool(options: DrawingConnectOptions = {}): S
139
209
 
140
210
  renderOverlay(): JSX.Element | null {
141
211
  if (!drag) return null;
142
- const a = drag.anchor;
143
- const b = drag.current;
212
+ const a = drag.start;
213
+ const b = drag.mode === 'new' ? drag.end : drag.endPoint;
144
214
  return createElement(
145
215
  'g',
146
216
  { key: 'drawing-connect-preview' },
147
217
  createElement('path', {
148
- d: `M ${a.x} ${a.y} L ${b.x} ${b.y}`,
218
+ d: straightPath(a, b),
149
219
  className: 'squisq-scene-connect-preview',
150
220
  fill: 'none',
151
221
  }),
@@ -55,6 +55,8 @@ export interface SceneTool {
55
55
  cursor: string;
56
56
  /** Optional keyboard accelerator (single-character, lowercase). */
57
57
  shortcut?: string;
58
+ /** Hide resize handles on selected layers while this tool is active. */
59
+ hideSelectionHandles?: boolean;
58
60
  /**
59
61
  * Optional icon — JSX rendered inside a fixed-size toolbar button. Keep
60
62
  * it light (a single SVG path is ideal); the toolbar wrapper handles
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Code-context sections — host-supplied markdown blurbs rendered inside
3
+ * Monaco view zones above anchor lines (see src/codeContext/).
4
+ *
5
+ * The zone dom node lives in Monaco's `.view-zones` layer, which sits below
6
+ * `.view-lines` in hit-test order. Raising the zone's z-index inside that
7
+ * shared stacking context is what makes section content clickable; Monaco's
8
+ * own `suppressMouseDown` keeps the cursor from jumping.
9
+ */
10
+
11
+ .squisq-ccx-zone {
12
+ z-index: 10;
13
+ pointer-events: auto;
14
+ overflow: hidden;
15
+ }
16
+
17
+ .squisq-ccx-section {
18
+ --ccx-fg: #57606a;
19
+ --ccx-fg-strong: #24292f;
20
+ --ccx-bg: rgba(175, 184, 193, 0.12);
21
+ --ccx-bg-hover: rgba(175, 184, 193, 0.22);
22
+ --ccx-border: rgba(140, 149, 159, 0.35);
23
+ --ccx-accent: #0969da;
24
+ font-family: var(--squisq-ux-font, system-ui, -apple-system, sans-serif);
25
+ font-size: 12px;
26
+ line-height: 1.45;
27
+ color: var(--ccx-fg);
28
+ border-left: 2px solid var(--ccx-border);
29
+ background: var(--ccx-bg);
30
+ margin: 2px 8px 2px 4px;
31
+ border-radius: 4px;
32
+ }
33
+
34
+ .squisq-editor-shell[data-theme='dark'] .squisq-ccx-section {
35
+ --ccx-fg: #9198a1;
36
+ --ccx-fg-strong: #e6edf3;
37
+ --ccx-bg: rgba(110, 118, 129, 0.14);
38
+ --ccx-bg-hover: rgba(110, 118, 129, 0.26);
39
+ --ccx-border: rgba(110, 118, 129, 0.45);
40
+ --ccx-accent: #4493f8;
41
+ }
42
+
43
+ /* ── Collapsed strip ─────────────────────────────────────────────── */
44
+
45
+ .squisq-ccx-strip {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 6px;
49
+ width: 100%;
50
+ padding: 3px 10px;
51
+ border: none;
52
+ background: transparent;
53
+ color: inherit;
54
+ font: inherit;
55
+ text-align: left;
56
+ cursor: pointer;
57
+ border-radius: 4px;
58
+ }
59
+
60
+ .squisq-ccx-strip:hover {
61
+ background: var(--ccx-bg-hover);
62
+ }
63
+
64
+ .squisq-ccx-chevron {
65
+ flex: 0 0 auto;
66
+ font-size: 9px;
67
+ opacity: 0.7;
68
+ }
69
+
70
+ .squisq-ccx-strip-text {
71
+ flex: 1 1 auto;
72
+ min-width: 0;
73
+ white-space: nowrap;
74
+ overflow: hidden;
75
+ text-overflow: ellipsis;
76
+ }
77
+
78
+ /* Flatten the strip's markdown to a single inline line. */
79
+ .squisq-ccx-strip-text .squisq-md,
80
+ .squisq-ccx-strip-text .squisq-md-p {
81
+ display: inline;
82
+ margin: 0;
83
+ }
84
+
85
+ .squisq-ccx-strip-text .squisq-md-strong {
86
+ color: var(--ccx-fg-strong);
87
+ }
88
+
89
+ /* ── Expanded body ───────────────────────────────────────────────── */
90
+
91
+ .squisq-ccx-body {
92
+ padding: 2px 12px 8px 24px;
93
+ font-size: 12px;
94
+ user-select: text;
95
+ }
96
+
97
+ .squisq-ccx-body--loading {
98
+ opacity: 0.6;
99
+ font-style: italic;
100
+ }
101
+
102
+ .squisq-ccx-body .squisq-md-p {
103
+ margin: 4px 0;
104
+ }
105
+
106
+ .squisq-ccx-body .squisq-md-strong {
107
+ color: var(--ccx-fg-strong);
108
+ font-size: 11px;
109
+ text-transform: none;
110
+ }
111
+
112
+ .squisq-ccx-body .squisq-md-list {
113
+ margin: 2px 0 6px;
114
+ padding-left: 18px;
115
+ }
116
+
117
+ .squisq-ccx-body .squisq-md-li {
118
+ margin: 1px 0;
119
+ }
120
+
121
+ .squisq-ccx-body .squisq-md-link {
122
+ color: var(--ccx-accent);
123
+ text-decoration: none;
124
+ }
125
+
126
+ .squisq-ccx-body .squisq-md-link:hover {
127
+ text-decoration: underline;
128
+ }
129
+
130
+ .squisq-ccx-body .squisq-md-inline-code,
131
+ .squisq-ccx-body .squisq-md-link .squisq-md-inline-code {
132
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
133
+ font-size: 11px;
134
+ padding: 0 3px;
135
+ border-radius: 3px;
136
+ background: rgba(140, 149, 159, 0.18);
137
+ }
138
+
139
+ .squisq-ccx-body .squisq-md-link .squisq-md-inline-code {
140
+ color: var(--ccx-accent);
141
+ }
142
+
143
+ .squisq-ccx-body .squisq-md-code-block {
144
+ margin: 4px 0;
145
+ padding: 6px 8px;
146
+ border-radius: 4px;
147
+ background: rgba(140, 149, 159, 0.14);
148
+ overflow-x: auto;
149
+ }
150
+
151
+ .squisq-ccx-body .squisq-md-code-block code {
152
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
153
+ font-size: 11px;
154
+ white-space: pre;
155
+ }