@bendyline/squisq-editor-react 1.6.2 → 2.0.1
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 +1275 -183
- package/dist/index.js +20345 -12777
- 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 +1607 -241
- package/package.json +9 -5
- package/src/BlockPropertiesPopover.tsx +30 -15
- package/src/DocumentSettingsDialog.tsx +39 -33
- package/src/EditorContext.tsx +199 -42
- package/src/EditorShell.tsx +349 -261
- 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 +362 -89
- package/src/PreviewPanel.tsx +312 -78
- package/src/RawEditor.tsx +13 -2
- package/src/RecorderEntry.tsx +2 -0
- 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 +357 -51
- 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__/codeContextSectionView.test.tsx +8 -6
- package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
- package/src/__tests__/documentSettingsDialog.test.tsx +51 -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__/mediaAttachmentFlow.test.ts +2 -2
- 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 +382 -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__/recorderTheme.test.tsx +42 -0
- package/src/__tests__/resolveBlockVisual.test.ts +43 -0
- package/src/__tests__/selectionConversions.test.ts +80 -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 +71 -7
- package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
- package/src/__tests__/tiptapImageRoundTrip.test.ts +1 -1
- package/src/__tests__/toolbarSelectionConversion.test.tsx +164 -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 +254 -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 +284 -0
- package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
- package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
- package/src/asciiDiagram/asciiDiagramCommands.ts +174 -0
- package/src/asciiDiagram/asciiDiagramData.ts +149 -0
- package/src/asciiDiagram/asciiDiagramOps.ts +238 -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/codeContext/types.ts +1 -1
- 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/__tests__/useMemoryLayerAdapter.test.ts +13 -0
- package/src/customTemplates/designer.css +158 -99
- package/src/customTemplates/library.ts +16 -3
- package/src/customTemplates/useMemoryLayerAdapter.ts +7 -1
- 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 +25 -6
- package/src/diagram/types.ts +35 -0
- package/src/editorScrollSync.ts +73 -0
- package/src/emojiData.ts +3 -23
- package/src/frontmatterSettings.ts +23 -0
- 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 +198 -20
- 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/RecorderButton.tsx +9 -1
- package/src/recorder/RecorderModal.tsx +84 -41
- package/src/recorder/RecorderPanel.tsx +9 -1
- 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 +119 -0
- package/src/scene/adapters/DiagramAdapter.ts +12 -101
- package/src/scene/adapters/DrawingAdapter.ts +6 -1
- package/src/scene/adapters/LayoutAdapter.ts +3 -0
- package/src/scene/commands/SceneCommand.ts +17 -3
- 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 +39 -23
- package/src/scene/tools/ShapeTool.ts +2 -3
- package/src/selectionConversions.ts +155 -0
- package/src/styles/ascii-diagram.css +79 -0
- package/src/styles/ascii-timeline.css +596 -0
- package/src/styles/editor.css +736 -120
- package/src/styles/index.css +3 -0
- package/src/styles/tree-view.css +189 -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 +861 -0
- package/src/timeline/TimelineViewExtension.ts +252 -0
- package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +414 -0
- package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
- package/src/timeline/__tests__/timelineCommands.test.ts +359 -0
- package/src/timeline/__tests__/timelineOps.test.ts +277 -0
- package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
- package/src/timeline/timelineCommands.ts +421 -0
- package/src/timeline/timelineData.ts +54 -0
- package/src/timeline/timelineOps.ts +381 -0
- package/src/timeline/timelinePaste.ts +8 -0
- package/src/tiptapBridge.ts +47 -21
- package/src/transformStyleId.ts +17 -0
- package/src/treeview/TreeOutlineWidget.tsx +390 -0
- package/src/treeview/TreeViewExtension.ts +250 -0
- package/src/treeview/__tests__/TreeOutlineWidget.test.tsx +156 -0
- package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
- package/src/treeview/__tests__/treeOps.test.ts +177 -0
- package/src/treeview/__tests__/treePaste.test.ts +40 -0
- package/src/treeview/__tests__/treeViewCommands.test.ts +179 -0
- package/src/treeview/treeOps.ts +231 -0
- package/src/treeview/treePaste.ts +13 -0
- package/src/treeview/treeViewCommands.ts +99 -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
package/src/Toolbar.tsx
CHANGED
|
@@ -29,7 +29,7 @@ import { ViewMenuPanel } from './ViewMenuPanel';
|
|
|
29
29
|
import {
|
|
30
30
|
TemplatePicker,
|
|
31
31
|
TEMPLATE_NAMES,
|
|
32
|
-
|
|
32
|
+
TEMPLATE_GALLERY_PORTAL_SELECTOR,
|
|
33
33
|
templateLabel,
|
|
34
34
|
} from './TemplatePicker';
|
|
35
35
|
import { TransitionPicker, TRANSITION_FLYOUT_PORTAL_ID } from './TransitionPicker';
|
|
@@ -50,7 +50,15 @@ import { CustomLayoutManager } from './customTemplates/CustomLayoutManager';
|
|
|
50
50
|
import { Icon } from './Icon';
|
|
51
51
|
import type { PickerEntry } from './emojiData';
|
|
52
52
|
import { createPortal } from 'react-dom';
|
|
53
|
-
import { usePreviewSettingsOptional } from './PreviewControls';
|
|
53
|
+
import { PreviewModeMenu, displayModeLabel, usePreviewSettingsOptional } from './PreviewControls';
|
|
54
|
+
import { filterVisibleMediaEntries } from './mediaEntries';
|
|
55
|
+
import {
|
|
56
|
+
selectionToTable,
|
|
57
|
+
selectionToTableMarkdown,
|
|
58
|
+
selectionToTaskItems,
|
|
59
|
+
selectionToTaskListMarkdown,
|
|
60
|
+
type SelectionTaskItem,
|
|
61
|
+
} from './selectionConversions';
|
|
54
62
|
|
|
55
63
|
const VIEWS: { id: EditorView; label: string; shortLabel?: string; shortcut: string }[] = [
|
|
56
64
|
{ id: 'wysiwyg', label: 'Write', shortcut: '⌘1' },
|
|
@@ -220,6 +228,22 @@ const BUTTONS: ToolbarButton[] = [
|
|
|
220
228
|
group: 'media',
|
|
221
229
|
faIcon: 'fa-solid fa-diagram-project',
|
|
222
230
|
},
|
|
231
|
+
{
|
|
232
|
+
id: 'tree',
|
|
233
|
+
label: 'tree',
|
|
234
|
+
icon: '',
|
|
235
|
+
title: 'Insert tree',
|
|
236
|
+
group: 'media',
|
|
237
|
+
faIcon: 'fa-solid fa-folder-tree',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: 'timeline',
|
|
241
|
+
label: 'timeline',
|
|
242
|
+
icon: '',
|
|
243
|
+
title: 'Insert timeline',
|
|
244
|
+
group: 'media',
|
|
245
|
+
faIcon: 'fa-solid fa-timeline',
|
|
246
|
+
},
|
|
223
247
|
{
|
|
224
248
|
id: 'drawing',
|
|
225
249
|
label: 'drawing',
|
|
@@ -256,6 +280,7 @@ const BUTTONS: ToolbarButton[] = [
|
|
|
256
280
|
|
|
257
281
|
const FIRST_MEDIA_INDEX = BUTTONS.findIndex((b) => b.group === 'media');
|
|
258
282
|
const MEDIA_BUTTONS = BUTTONS.filter((b) => b.group === 'media');
|
|
283
|
+
const CONVERT_BUTTONS = MEDIA_BUTTONS.filter((b) => b.id === 'table' || b.id === 'tasklist');
|
|
259
284
|
const INSERT_MENU_WIDTH = 200;
|
|
260
285
|
const TASK_LIST_ITEMS = ['Task 1', 'Task 2', 'Task 3'] as const;
|
|
261
286
|
const TASK_LIST_MARKDOWN = TASK_LIST_ITEMS.map((item) => `- [ ] ${item}`).join('\n');
|
|
@@ -280,18 +305,35 @@ function fileCountBadge(count: number): string {
|
|
|
280
305
|
return count > 99 ? '99+' : String(count);
|
|
281
306
|
}
|
|
282
307
|
|
|
283
|
-
function
|
|
308
|
+
function paragraphContent(text: string): JSONContent {
|
|
309
|
+
return text ? { type: 'paragraph', content: [{ type: 'text', text }] } : { type: 'paragraph' };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function tableContent(rows: string[][]): JSONContent {
|
|
313
|
+
return {
|
|
314
|
+
type: 'table',
|
|
315
|
+
content: rows.map((row, rowIndex) => ({
|
|
316
|
+
type: 'tableRow',
|
|
317
|
+
content: row.map((cell) => ({
|
|
318
|
+
type: rowIndex === 0 ? 'tableHeader' : 'tableCell',
|
|
319
|
+
content: [paragraphContent(cell)],
|
|
320
|
+
})),
|
|
321
|
+
})),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function taskListContent(
|
|
326
|
+
items: readonly SelectionTaskItem[] = TASK_LIST_ITEMS.map((text) => ({
|
|
327
|
+
checked: false,
|
|
328
|
+
text,
|
|
329
|
+
})),
|
|
330
|
+
): JSONContent {
|
|
284
331
|
return {
|
|
285
332
|
type: 'taskList',
|
|
286
|
-
content:
|
|
333
|
+
content: items.map((item) => ({
|
|
287
334
|
type: 'taskItem',
|
|
288
|
-
attrs: { checked:
|
|
289
|
-
content: [
|
|
290
|
-
{
|
|
291
|
-
type: 'paragraph',
|
|
292
|
-
content: [{ type: 'text', text: item }],
|
|
293
|
-
},
|
|
294
|
-
],
|
|
335
|
+
attrs: { checked: item.checked },
|
|
336
|
+
content: [paragraphContent(item.text)],
|
|
295
337
|
})),
|
|
296
338
|
};
|
|
297
339
|
}
|
|
@@ -302,6 +344,26 @@ function insertTaskList(editor: TiptapEditor): void {
|
|
|
302
344
|
editor.chain().focus().insertContent(content).run();
|
|
303
345
|
}
|
|
304
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Expand a full-line Tiptap text selection to the surrounding top-level node
|
|
349
|
+
* boundaries. Inserting a list/table at an in-paragraph range makes
|
|
350
|
+
* ProseMirror preserve the emptied paragraph before or after the new block.
|
|
351
|
+
* Partial-line selections deliberately retain their exact range.
|
|
352
|
+
*/
|
|
353
|
+
function blockConversionRange(editor: TiptapEditor): { from: number; to: number } {
|
|
354
|
+
const { from, to, $from, $to } = editor.state.selection;
|
|
355
|
+
return {
|
|
356
|
+
from:
|
|
357
|
+
$from.depth === 1 && $from.parent.isTextblock && $from.parentOffset === 0
|
|
358
|
+
? $from.before(1)
|
|
359
|
+
: from,
|
|
360
|
+
to:
|
|
361
|
+
$to.depth === 1 && $to.parent.isTextblock && $to.parentOffset === $to.parent.content.size
|
|
362
|
+
? $to.after(1)
|
|
363
|
+
: to,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
305
367
|
// ─── Tiptap active-state map ────────────────────────────
|
|
306
368
|
|
|
307
369
|
/** Returns true if the given button id is currently active in Tiptap */
|
|
@@ -355,6 +417,85 @@ const LAYOUT_STARTER_TEXT_PARAMS =
|
|
|
355
417
|
/** Multi-line markdown for a new layout (raw / code views). */
|
|
356
418
|
const LAYOUT_STARTER_MARKDOWN = `\n## Layout {[layout]}\n\n### {#text-1} {[text ${LAYOUT_STARTER_TEXT_PARAMS}]}\n\nLayout\n`;
|
|
357
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Starter art for a new diagram — diagrams are authored as ASCII-art code
|
|
422
|
+
* fences (the fence is the source of truth; `AsciiDiagramExtension` mounts
|
|
423
|
+
* the interactive canvas over it). This exact art is the codec's own
|
|
424
|
+
* canonical rendering of a two-node flow, so it re-parses and re-renders
|
|
425
|
+
* byte-stably. The leading rows/columns are deliberate: ASCII has no
|
|
426
|
+
* separate canvas-origin metadata, so this persisted gutter gives the first
|
|
427
|
+
* node real grid space to move north/west. Without it, content-fit makes the
|
|
428
|
+
* node look centered while its authored coordinate is still (0, 0), and a
|
|
429
|
+
* drag into the apparent margin clamps straight back to that corner.
|
|
430
|
+
*/
|
|
431
|
+
const DIAGRAM_STARTER_ART = [
|
|
432
|
+
'',
|
|
433
|
+
'',
|
|
434
|
+
' ┌─────────┐',
|
|
435
|
+
' │ Start │',
|
|
436
|
+
' └────┬────┘',
|
|
437
|
+
' │',
|
|
438
|
+
' ▼',
|
|
439
|
+
' ┌────┴────┐',
|
|
440
|
+
' │ Next │',
|
|
441
|
+
' └─────────┘',
|
|
442
|
+
].join('\n');
|
|
443
|
+
/**
|
|
444
|
+
* Fenced form for raw / code views — tagged with the explicit `diagram`
|
|
445
|
+
* language so the block's identity survives markdown ↔ Tiptap round-trips
|
|
446
|
+
* (the language class round-trips; fence meta does not).
|
|
447
|
+
*/
|
|
448
|
+
const DIAGRAM_STARTER_MARKDOWN = '\n```diagram\n' + DIAGRAM_STARTER_ART + '\n```\n';
|
|
449
|
+
|
|
450
|
+
/** Insert a starter ASCII-diagram code fence after the current top-level block. */
|
|
451
|
+
function insertAsciiDiagramBlock(editor: TiptapEditor): void {
|
|
452
|
+
insertFenceBlock(editor, DIAGRAM_STARTER_ART, 'diagram');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Starter art for a new file tree — an ASCII tree fence (the fence is the
|
|
457
|
+
* source of truth; `TreeViewExtension` mounts the interactive outline).
|
|
458
|
+
*/
|
|
459
|
+
const TREE_STARTER_ART = ['src/', '├── index.ts', '└── utils/', ' └── helpers.ts'].join('\n');
|
|
460
|
+
const TREE_STARTER_MARKDOWN = '\n```tree\n' + TREE_STARTER_ART + '\n```\n';
|
|
461
|
+
|
|
462
|
+
/** Insert a starter ASCII tree code fence after the current top-level block. */
|
|
463
|
+
function insertTreeBlock(editor: TiptapEditor): void {
|
|
464
|
+
insertFenceBlock(editor, TREE_STARTER_ART, 'tree');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Starter multi-event rail for the authored timeline view. */
|
|
468
|
+
const TIMELINE_STARTER_ART =
|
|
469
|
+
'Milestones: ● Start {#start} ─────● Review {#review} ─────● Ship {#ship} ───►';
|
|
470
|
+
const TIMELINE_STARTER_MARKDOWN = '\n```timeline\n' + TIMELINE_STARTER_ART + '\n```\n';
|
|
471
|
+
|
|
472
|
+
/** Insert a starter ASCII timeline code fence after the current top-level block. */
|
|
473
|
+
function insertTimelineBlock(editor: TiptapEditor): void {
|
|
474
|
+
insertFenceBlock(editor, TIMELINE_STARTER_ART, 'timeline');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Insert a code fence after the current top-level block. `lang` tags the
|
|
479
|
+
* fence's `language` attribute — pass the explicit authored-view tag so its
|
|
480
|
+
* identity round-trips through markdown and Tiptap.
|
|
481
|
+
*/
|
|
482
|
+
function insertFenceBlock(editor: TiptapEditor, art: string, lang?: string): void {
|
|
483
|
+
editor
|
|
484
|
+
.chain()
|
|
485
|
+
.focus()
|
|
486
|
+
.command(({ tr, state, dispatch }) => {
|
|
487
|
+
const codeBlockType = state.schema.nodes.codeBlock;
|
|
488
|
+
if (!codeBlockType) return false;
|
|
489
|
+
const attrs = lang ? { language: lang } : null;
|
|
490
|
+
const block = codeBlockType.create(attrs, state.schema.text(art));
|
|
491
|
+
const { $from } = state.selection;
|
|
492
|
+
const insertPos = $from.depth > 0 ? $from.after(1) : state.doc.content.size;
|
|
493
|
+
if (dispatch) tr.insert(insertPos, block);
|
|
494
|
+
return true;
|
|
495
|
+
})
|
|
496
|
+
.run();
|
|
497
|
+
}
|
|
498
|
+
|
|
358
499
|
/**
|
|
359
500
|
* Insert a block-level heading carrying a Scene template (`diagram` /
|
|
360
501
|
* `drawing` / `layout`) at the top level, after the block the caret sits
|
|
@@ -506,6 +647,7 @@ export function Toolbar({
|
|
|
506
647
|
bumpMediaRevision,
|
|
507
648
|
} = useEditorContext();
|
|
508
649
|
const previewSettings = usePreviewSettingsOptional();
|
|
650
|
+
const [useModeMenuRequest, requestUseModeMenu] = useReducer((count: number) => count + 1, 0);
|
|
509
651
|
// When a canvas textbox is being edited, its Tiptap instance takes over
|
|
510
652
|
// the formatting buttons; otherwise they drive the document editor. The
|
|
511
653
|
// `level` gates which buttons apply (inline labels vs. rich textboxes).
|
|
@@ -533,7 +675,7 @@ export function Toolbar({
|
|
|
533
675
|
let cancelled = false;
|
|
534
676
|
mediaProvider.listMedia().then(
|
|
535
677
|
(entries) => {
|
|
536
|
-
if (!cancelled) setScannedFileCount(entries.length);
|
|
678
|
+
if (!cancelled) setScannedFileCount(filterVisibleMediaEntries(entries).length);
|
|
537
679
|
},
|
|
538
680
|
() => {
|
|
539
681
|
if (!cancelled) setScannedFileCount(0);
|
|
@@ -682,23 +824,25 @@ export function Toolbar({
|
|
|
682
824
|
// with the cursor context and change width with their value, all without
|
|
683
825
|
// resizing the flex:1 container itself — a container-only observer would
|
|
684
826
|
// go stale.
|
|
685
|
-
const ro = new ResizeObserver(measure);
|
|
827
|
+
const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);
|
|
686
828
|
const observeAll = () => {
|
|
687
|
-
ro
|
|
688
|
-
ro
|
|
829
|
+
ro?.disconnect();
|
|
830
|
+
ro?.observe(container);
|
|
689
831
|
container
|
|
690
832
|
.querySelectorAll<HTMLElement>(':scope > .squisq-toolbar-group')
|
|
691
|
-
.forEach((group) => ro
|
|
833
|
+
.forEach((group) => ro?.observe(group));
|
|
692
834
|
};
|
|
693
835
|
const mo = new MutationObserver(() => {
|
|
694
836
|
observeAll();
|
|
695
837
|
measure();
|
|
696
838
|
});
|
|
697
839
|
mo.observe(container, { childList: true });
|
|
840
|
+
if (!ro) window.addEventListener('resize', measure);
|
|
698
841
|
observeAll();
|
|
699
842
|
measure();
|
|
700
843
|
return () => {
|
|
701
|
-
ro
|
|
844
|
+
ro?.disconnect();
|
|
845
|
+
if (!ro) window.removeEventListener('resize', measure);
|
|
702
846
|
mo.disconnect();
|
|
703
847
|
};
|
|
704
848
|
}, [activeView]);
|
|
@@ -714,7 +858,7 @@ export function Toolbar({
|
|
|
714
858
|
if (overflowRef.current?.contains(target)) return;
|
|
715
859
|
if (
|
|
716
860
|
target instanceof Element &&
|
|
717
|
-
target.closest(
|
|
861
|
+
target.closest(`${TEMPLATE_GALLERY_PORTAL_SELECTOR}, #${TRANSITION_FLYOUT_PORTAL_ID}`)
|
|
718
862
|
) {
|
|
719
863
|
return;
|
|
720
864
|
}
|
|
@@ -856,9 +1000,19 @@ export function Toolbar({
|
|
|
856
1000
|
insertTaskList(tiptapEditor);
|
|
857
1001
|
break;
|
|
858
1002
|
case 'diagram':
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
|
|
1003
|
+
// Diagrams are ASCII-art code fences; the AsciiDiagramExtension
|
|
1004
|
+
// mounts the interactive canvas over the inserted starter.
|
|
1005
|
+
insertAsciiDiagramBlock(tiptapEditor);
|
|
1006
|
+
break;
|
|
1007
|
+
case 'tree':
|
|
1008
|
+
// File trees are ASCII tree fences; TreeViewExtension mounts the
|
|
1009
|
+
// interactive outline over the inserted starter.
|
|
1010
|
+
insertTreeBlock(tiptapEditor);
|
|
1011
|
+
break;
|
|
1012
|
+
case 'timeline':
|
|
1013
|
+
// The authored fence is immediately replaced by the WYSIWYG rail
|
|
1014
|
+
// canvas; Preview uses the same events as vector layers.
|
|
1015
|
+
insertTimelineBlock(tiptapEditor);
|
|
862
1016
|
break;
|
|
863
1017
|
case 'drawing':
|
|
864
1018
|
// Empty drawing is usable via the canvas's "Shapes ▾" palette;
|
|
@@ -1015,9 +1169,19 @@ export function Toolbar({
|
|
|
1015
1169
|
break;
|
|
1016
1170
|
}
|
|
1017
1171
|
case 'diagram': {
|
|
1018
|
-
// A diagram is
|
|
1019
|
-
//
|
|
1020
|
-
replacement =
|
|
1172
|
+
// A diagram is an ASCII-art code fence; the WYSIWYG view
|
|
1173
|
+
// renders its editable canvas, preview renders it as a slide.
|
|
1174
|
+
replacement = DIAGRAM_STARTER_MARKDOWN;
|
|
1175
|
+
newCursorOffset = replacement.length;
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
case 'tree': {
|
|
1179
|
+
replacement = TREE_STARTER_MARKDOWN;
|
|
1180
|
+
newCursorOffset = replacement.length;
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
case 'timeline': {
|
|
1184
|
+
replacement = TIMELINE_STARTER_MARKDOWN;
|
|
1021
1185
|
newCursorOffset = replacement.length;
|
|
1022
1186
|
break;
|
|
1023
1187
|
}
|
|
@@ -1099,7 +1263,13 @@ export function Toolbar({
|
|
|
1099
1263
|
insertion = `\n${TASK_LIST_MARKDOWN}\n`;
|
|
1100
1264
|
break;
|
|
1101
1265
|
case 'diagram':
|
|
1102
|
-
insertion =
|
|
1266
|
+
insertion = DIAGRAM_STARTER_MARKDOWN;
|
|
1267
|
+
break;
|
|
1268
|
+
case 'tree':
|
|
1269
|
+
insertion = TREE_STARTER_MARKDOWN;
|
|
1270
|
+
break;
|
|
1271
|
+
case 'timeline':
|
|
1272
|
+
insertion = TIMELINE_STARTER_MARKDOWN;
|
|
1103
1273
|
break;
|
|
1104
1274
|
case 'drawing':
|
|
1105
1275
|
insertion = '\n## Drawing {[drawing]}\n';
|
|
@@ -1116,6 +1286,68 @@ export function Toolbar({
|
|
|
1116
1286
|
[monacoEditor, markdownSource, setMarkdownSource],
|
|
1117
1287
|
);
|
|
1118
1288
|
|
|
1289
|
+
// ── Selected-text conversion handlers ─────────────────
|
|
1290
|
+
const readSelectedText = useCallback((): string => {
|
|
1291
|
+
// Canvas text overlays intentionally have a smaller schema without tables
|
|
1292
|
+
// or task lists. Ignore their selection instead of surfacing actions that
|
|
1293
|
+
// cannot be represented by those layers.
|
|
1294
|
+
if (activeSceneText) return '';
|
|
1295
|
+
|
|
1296
|
+
if (activeView === 'wysiwyg' && tiptapEditor) {
|
|
1297
|
+
const { from, to, empty } = tiptapEditor.state.selection;
|
|
1298
|
+
return empty ? '' : tiptapEditor.state.doc.textBetween(from, to, '\n');
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (activeView === 'raw' && monacoEditor) {
|
|
1302
|
+
const selection = monacoEditor.getSelection();
|
|
1303
|
+
const model = monacoEditor.getModel();
|
|
1304
|
+
return selection && model ? model.getValueInRange(selection) : '';
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return '';
|
|
1308
|
+
}, [activeSceneText, activeView, monacoEditor, tiptapEditor]);
|
|
1309
|
+
|
|
1310
|
+
const handleConvertSelection = useCallback(
|
|
1311
|
+
(target: 'table' | 'tasklist') => {
|
|
1312
|
+
const selectedText = readSelectedText();
|
|
1313
|
+
if (!selectedText.trim()) return;
|
|
1314
|
+
|
|
1315
|
+
if (activeView === 'wysiwyg' && tiptapEditor) {
|
|
1316
|
+
const content =
|
|
1317
|
+
target === 'table'
|
|
1318
|
+
? tableContent(selectionToTable(selectedText).rows)
|
|
1319
|
+
: taskListContent(selectionToTaskItems(selectedText));
|
|
1320
|
+
tiptapEditor
|
|
1321
|
+
.chain()
|
|
1322
|
+
.focus()
|
|
1323
|
+
.insertContentAt(blockConversionRange(tiptapEditor), content)
|
|
1324
|
+
.run();
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (activeView === 'raw' && monacoEditor) {
|
|
1329
|
+
const selection = monacoEditor.getSelection();
|
|
1330
|
+
if (!selection) return;
|
|
1331
|
+
const converted =
|
|
1332
|
+
target === 'table'
|
|
1333
|
+
? selectionToTableMarkdown(selectedText)
|
|
1334
|
+
: selectionToTaskListMarkdown(selectedText);
|
|
1335
|
+
if (!converted) return;
|
|
1336
|
+
|
|
1337
|
+
// Monaco line selections commonly include the newline after the last
|
|
1338
|
+
// selected line. Retain selected edge newlines so the replacement
|
|
1339
|
+
// block never runs into the surrounding Markdown.
|
|
1340
|
+
const leadingNewline = /^(?:\r?\n)/.test(selectedText) ? '\n' : '';
|
|
1341
|
+
const trailingNewline = /(?:\r?\n)$/.test(selectedText) ? '\n' : '';
|
|
1342
|
+
monacoEditor.executeEdits('toolbar-convert-selection', [
|
|
1343
|
+
{ range: selection, text: leadingNewline + converted + trailingNewline },
|
|
1344
|
+
]);
|
|
1345
|
+
monacoEditor.focus();
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
[activeView, monacoEditor, readSelectedText, tiptapEditor],
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1119
1351
|
// ── Image upload handler ───────────────────────────────
|
|
1120
1352
|
const handleImageFile = useCallback(
|
|
1121
1353
|
async (file: File) => {
|
|
@@ -1352,6 +1584,10 @@ export function Toolbar({
|
|
|
1352
1584
|
if (sceneTextLevel === 'block') return BLOCK_SCENE_BUTTONS.has(id);
|
|
1353
1585
|
return INLINE_SCENE_BUTTONS.has(id);
|
|
1354
1586
|
};
|
|
1587
|
+
const showConvertActions =
|
|
1588
|
+
insertMenuAnchor !== null &&
|
|
1589
|
+
readSelectedText().trim().length > 0 &&
|
|
1590
|
+
CONVERT_BUTTONS.some((button) => buttonAllowed(button.id));
|
|
1355
1591
|
|
|
1356
1592
|
// ── Progressive heading disclosure ───────────────────
|
|
1357
1593
|
// H1\u2013H3 are always visible. H4 appears once the document already
|
|
@@ -1540,10 +1776,12 @@ export function Toolbar({
|
|
|
1540
1776
|
viewport: previewSettings?.activeViewport ?? VIEWPORT_PRESETS.landscape,
|
|
1541
1777
|
basePath: '/',
|
|
1542
1778
|
mediaProvider,
|
|
1779
|
+
customTemplates: doc?.customTemplates,
|
|
1543
1780
|
}
|
|
1544
1781
|
: undefined,
|
|
1545
1782
|
[
|
|
1546
1783
|
mediaProvider,
|
|
1784
|
+
doc?.customTemplates,
|
|
1547
1785
|
previewSettings?.activeTheme,
|
|
1548
1786
|
previewSettings?.activeViewport,
|
|
1549
1787
|
templatePreviewBlock,
|
|
@@ -1692,35 +1930,65 @@ export function Toolbar({
|
|
|
1692
1930
|
{/* View tabs — hidden when only one view is available (e.g. code mode). */}
|
|
1693
1931
|
{showViewTabs && (
|
|
1694
1932
|
<div className="squisq-toolbar-view-tabs" role="tablist" aria-label="Editor view">
|
|
1695
|
-
{visibleViews.map((view) =>
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1933
|
+
{visibleViews.map((view) => {
|
|
1934
|
+
const viewLabel =
|
|
1935
|
+
view.id === 'preview' && previewSettings
|
|
1936
|
+
? displayModeLabel(previewSettings.activeDisplayMode)
|
|
1937
|
+
: view.label;
|
|
1938
|
+
const tab = (
|
|
1939
|
+
<button
|
|
1940
|
+
role="tab"
|
|
1941
|
+
data-view={view.id}
|
|
1942
|
+
aria-selected={activeView === view.id}
|
|
1943
|
+
className={`squisq-toolbar-view-tab${activeView === view.id ? ' squisq-toolbar-view-tab--active' : ''}`}
|
|
1944
|
+
onClick={() => {
|
|
1945
|
+
if (view.id === 'preview' && activeView === 'preview' && previewSettings) {
|
|
1946
|
+
requestUseModeMenu();
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
setActiveView(view.id);
|
|
1950
|
+
}}
|
|
1951
|
+
data-tooltip={`${viewLabel} (${view.shortcut})`}
|
|
1708
1952
|
>
|
|
1709
|
-
{view.label}
|
|
1710
|
-
</span>
|
|
1711
|
-
{view.shortLabel && view.shortLabel !== view.label && (
|
|
1712
1953
|
<span
|
|
1713
|
-
className="squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--
|
|
1714
|
-
data-label={
|
|
1954
|
+
className="squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--long"
|
|
1955
|
+
data-label={viewLabel}
|
|
1715
1956
|
>
|
|
1716
|
-
{
|
|
1957
|
+
{viewLabel}
|
|
1717
1958
|
</span>
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1959
|
+
{view.shortLabel && view.shortLabel !== view.label && (
|
|
1960
|
+
<span
|
|
1961
|
+
className="squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--short"
|
|
1962
|
+
data-label={view.shortLabel}
|
|
1963
|
+
>
|
|
1964
|
+
{view.shortLabel}
|
|
1965
|
+
</span>
|
|
1966
|
+
)}
|
|
1967
|
+
</button>
|
|
1968
|
+
);
|
|
1969
|
+
|
|
1970
|
+
if (view.id !== 'preview' || !previewSettings) {
|
|
1971
|
+
return (
|
|
1972
|
+
<div key={view.id} className="squisq-toolbar-view-tab-wrap" role="presentation">
|
|
1973
|
+
{tab}
|
|
1974
|
+
</div>
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
return (
|
|
1979
|
+
<div
|
|
1980
|
+
key={view.id}
|
|
1981
|
+
className={`squisq-toolbar-view-tab-wrap squisq-toolbar-use-tab${activeView === 'preview' ? ' squisq-toolbar-use-tab--active' : ''}`}
|
|
1982
|
+
role="presentation"
|
|
1983
|
+
>
|
|
1984
|
+
{tab}
|
|
1985
|
+
<PreviewModeMenu openRequest={useModeMenuRequest} />
|
|
1986
|
+
</div>
|
|
1987
|
+
);
|
|
1988
|
+
})}
|
|
1721
1989
|
</div>
|
|
1722
1990
|
)}
|
|
1723
|
-
{/* After-tabs slot — left side, before formatting
|
|
1991
|
+
{/* After-tabs slot — left side, before formatting or preview controls. */}
|
|
1724
1992
|
{slotAfterTabs}
|
|
1725
1993
|
{/* Formatting buttons — hidden in preview mode and code mode */}
|
|
1726
1994
|
{!isPreview && !isCodeMode && (
|
|
@@ -1803,7 +2071,12 @@ export function Toolbar({
|
|
|
1803
2071
|
data-contextual="transition"
|
|
1804
2072
|
>
|
|
1805
2073
|
<div className="squisq-toolbar-separator" />
|
|
1806
|
-
<TransitionPicker
|
|
2074
|
+
<TransitionPicker
|
|
2075
|
+
value={currentTransition}
|
|
2076
|
+
onChange={handleTransitionPick}
|
|
2077
|
+
colorScheme={colorScheme}
|
|
2078
|
+
accentColor={previewSettings?.activeTheme.colors.primary}
|
|
2079
|
+
/>
|
|
1807
2080
|
</div>
|
|
1808
2081
|
)}
|
|
1809
2082
|
|
|
@@ -2074,7 +2347,12 @@ export function Toolbar({
|
|
|
2074
2347
|
the flyout carries follow-up controls (direction/duration). */}
|
|
2075
2348
|
{showTransitionInOverflow && (
|
|
2076
2349
|
<div className="squisq-toolbar-overflow-item squisq-toolbar-overflow-template">
|
|
2077
|
-
<TransitionPicker
|
|
2350
|
+
<TransitionPicker
|
|
2351
|
+
value={currentTransition!}
|
|
2352
|
+
onChange={handleTransitionPick}
|
|
2353
|
+
colorScheme={colorScheme}
|
|
2354
|
+
accentColor={previewSettings?.activeTheme.colors.primary}
|
|
2355
|
+
/>
|
|
2078
2356
|
</div>
|
|
2079
2357
|
)}
|
|
2080
2358
|
|
|
@@ -2231,7 +2509,35 @@ export function Toolbar({
|
|
|
2231
2509
|
style={{ position: 'fixed', top: insertMenuAnchor.top, left: insertMenuAnchor.left }}
|
|
2232
2510
|
role="menu"
|
|
2233
2511
|
>
|
|
2234
|
-
|
|
2512
|
+
{showConvertActions && (
|
|
2513
|
+
<>
|
|
2514
|
+
<div className="squisq-insert-menu-header">Convert</div>
|
|
2515
|
+
{CONVERT_BUTTONS.map((btn) => {
|
|
2516
|
+
const label = btn.id === 'table' ? 'Table' : 'Task List';
|
|
2517
|
+
return (
|
|
2518
|
+
<button
|
|
2519
|
+
key={`convert-${btn.id}`}
|
|
2520
|
+
className="squisq-toolbar-overflow-item"
|
|
2521
|
+
disabled={!buttonAllowed(btn.id)}
|
|
2522
|
+
onClick={() => {
|
|
2523
|
+
handleConvertSelection(btn.id as 'table' | 'tasklist');
|
|
2524
|
+
closeInsertMenu();
|
|
2525
|
+
}}
|
|
2526
|
+
role="menuitem"
|
|
2527
|
+
aria-label={`Convert selection to ${label}`}
|
|
2528
|
+
>
|
|
2529
|
+
<span className="squisq-toolbar-overflow-icon">{buttonIcon(btn)}</span>
|
|
2530
|
+
<span>{label}</span>
|
|
2531
|
+
</button>
|
|
2532
|
+
);
|
|
2533
|
+
})}
|
|
2534
|
+
</>
|
|
2535
|
+
)}
|
|
2536
|
+
<div
|
|
2537
|
+
className={`squisq-insert-menu-header${showConvertActions ? ' squisq-insert-menu-header--separated' : ''}`}
|
|
2538
|
+
>
|
|
2539
|
+
Insert
|
|
2540
|
+
</div>
|
|
2235
2541
|
{MEDIA_BUTTONS.filter((b) => isButtonVisible(b.id)).map((btn) => {
|
|
2236
2542
|
const disabled = (btn.id === 'image' && !mediaProvider) || !buttonAllowed(btn.id);
|
|
2237
2543
|
const stripped = btn.title.replace(/^Insert\s+/i, '');
|
package/src/TransitionPicker.tsx
CHANGED
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
export interface TransitionPickerProps {
|
|
31
31
|
value: TransitionFields;
|
|
32
32
|
onChange: (next: TransitionFields) => void;
|
|
33
|
+
/** Surface scheme copied onto the portaled flyout so it can style independently of the shell. */
|
|
34
|
+
colorScheme?: 'light' | 'dark';
|
|
35
|
+
/** Active document-theme accent used for open and selected states. */
|
|
36
|
+
accentColor?: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
@@ -41,7 +45,12 @@ export const TRANSITION_FLYOUT_PORTAL_ID = 'squisq-transition-flyout-portal';
|
|
|
41
45
|
|
|
42
46
|
const FLYOUT_ID = TRANSITION_FLYOUT_PORTAL_ID;
|
|
43
47
|
|
|
44
|
-
export function TransitionPicker({
|
|
48
|
+
export function TransitionPicker({
|
|
49
|
+
value,
|
|
50
|
+
onChange,
|
|
51
|
+
colorScheme = 'light',
|
|
52
|
+
accentColor,
|
|
53
|
+
}: TransitionPickerProps) {
|
|
45
54
|
const [open, setOpen] = useState(false);
|
|
46
55
|
const [style, setStyle] = useState<React.CSSProperties>({});
|
|
47
56
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
@@ -119,13 +128,22 @@ export function TransitionPicker({ value, onChange }: TransitionPickerProps) {
|
|
|
119
128
|
|
|
120
129
|
const flyout = open
|
|
121
130
|
? createPortal(
|
|
122
|
-
<TransitionFlyout
|
|
131
|
+
<TransitionFlyout
|
|
132
|
+
style={{ ...style, ...blockAccentStyle(accentColor) }}
|
|
133
|
+
value={value}
|
|
134
|
+
onChange={onChange}
|
|
135
|
+
colorScheme={colorScheme}
|
|
136
|
+
/>,
|
|
123
137
|
document.body,
|
|
124
138
|
)
|
|
125
139
|
: null;
|
|
126
140
|
|
|
127
141
|
return (
|
|
128
|
-
<div
|
|
142
|
+
<div
|
|
143
|
+
className="squisq-transition-picker"
|
|
144
|
+
data-theme={colorScheme}
|
|
145
|
+
style={blockAccentStyle(accentColor)}
|
|
146
|
+
>
|
|
129
147
|
<button
|
|
130
148
|
ref={triggerRef}
|
|
131
149
|
type="button"
|
|
@@ -158,10 +176,12 @@ function TransitionFlyout({
|
|
|
158
176
|
style,
|
|
159
177
|
value,
|
|
160
178
|
onChange,
|
|
179
|
+
colorScheme,
|
|
161
180
|
}: {
|
|
162
181
|
style: React.CSSProperties;
|
|
163
182
|
value: TransitionFields;
|
|
164
183
|
onChange: (next: TransitionFields) => void;
|
|
184
|
+
colorScheme: 'light' | 'dark';
|
|
165
185
|
}) {
|
|
166
186
|
const [query, setQuery] = useState('');
|
|
167
187
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
@@ -211,7 +231,13 @@ function TransitionFlyout({
|
|
|
211
231
|
};
|
|
212
232
|
|
|
213
233
|
return (
|
|
214
|
-
<div
|
|
234
|
+
<div
|
|
235
|
+
id={FLYOUT_ID}
|
|
236
|
+
className="squisq-transition-flyout"
|
|
237
|
+
data-theme={colorScheme}
|
|
238
|
+
role="menu"
|
|
239
|
+
style={style}
|
|
240
|
+
>
|
|
215
241
|
<div className="squisq-transition-flyout-search">
|
|
216
242
|
<input
|
|
217
243
|
ref={searchRef}
|
|
@@ -313,6 +339,12 @@ function TransitionFlyout({
|
|
|
313
339
|
);
|
|
314
340
|
}
|
|
315
341
|
|
|
342
|
+
function blockAccentStyle(accentColor: string | undefined): React.CSSProperties {
|
|
343
|
+
return accentColor
|
|
344
|
+
? ({ ['--squisq-block-props-accent' as string]: accentColor } as React.CSSProperties)
|
|
345
|
+
: {};
|
|
346
|
+
}
|
|
347
|
+
|
|
316
348
|
function TransitionOption({
|
|
317
349
|
entry,
|
|
318
350
|
selected,
|