@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
package/src/TemplatePicker.tsx
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and a one-sentence description so authors can quickly find the right layout.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
9
|
+
import { useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
10
10
|
import { createPortal } from 'react-dom';
|
|
11
11
|
import type { CustomTemplateDefinition } from '@bendyline/squisq/schemas';
|
|
12
12
|
import { TEMPLATE_METADATA, resolveTemplateName } from '@bendyline/squisq/doc';
|
|
@@ -36,14 +36,20 @@ interface TemplateEntry {
|
|
|
36
36
|
icon: JSX.Element;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
* picker inside their own popovers (e.g. the toolbar overflow menu) can treat
|
|
42
|
-
* clicks inside the gallery as "inside" in their outside-click handling.
|
|
43
|
-
*/
|
|
44
|
-
export const TEMPLATE_GALLERY_PORTAL_ID = 'squisq-template-gallery-portal';
|
|
39
|
+
/** Matches every gallery when multiple editor instances are mounted. */
|
|
40
|
+
export const TEMPLATE_GALLERY_PORTAL_SELECTOR = '[data-squisq-template-gallery-portal]';
|
|
45
41
|
|
|
46
42
|
const TEMPLATE_GALLERY_DIALOG_ID = 'squisq-template-gallery-dialog';
|
|
43
|
+
const TEMPLATE_GALLERY_PORTAL_ID_PREFIX = 'squisq-template-gallery-portal';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Give each portaled gallery a stable, collision-free id. Hosts should match
|
|
47
|
+
* galleries through {@link TEMPLATE_GALLERY_PORTAL_SELECTOR}; a process-wide
|
|
48
|
+
* singleton id cannot identify the right editor when several are mounted.
|
|
49
|
+
*/
|
|
50
|
+
function useTemplateGalleryPortalId(): string {
|
|
51
|
+
return `${TEMPLATE_GALLERY_PORTAL_ID_PREFIX}-${useId().replace(/:/g, '')}`;
|
|
52
|
+
}
|
|
47
53
|
|
|
48
54
|
const W = 56;
|
|
49
55
|
const H = 40;
|
|
@@ -419,28 +425,62 @@ export const TEMPLATE_ENTRIES: TemplateEntry[] = [
|
|
|
419
425
|
<rect x={4} y={6} width={16} height={10} rx={2} fill={FA} opacity={0.85} />
|
|
420
426
|
<rect x={36} y={6} width={16} height={10} rx={2} fill={F2} opacity={0.8} />
|
|
421
427
|
<rect x={20} y={24} width={16} height={10} rx={2} fill={F2} opacity={0.8} />
|
|
428
|
+
<path d="M 20 11 L 36 11" stroke={FA} strokeWidth={1.5} fill="none" opacity={0.7} />
|
|
429
|
+
<path d="M 36 11 L 32 8.5 L 32 13.5 Z" fill={FA} opacity={0.7} />
|
|
422
430
|
<path
|
|
423
|
-
d="M
|
|
431
|
+
d="M 12 16 C 12 22, 24 22, 24 24"
|
|
424
432
|
stroke={FA}
|
|
425
433
|
strokeWidth={1.5}
|
|
426
434
|
fill="none"
|
|
427
435
|
opacity={0.7}
|
|
428
|
-
markerEnd="url(#tp-diagram-arrow)"
|
|
429
436
|
/>
|
|
430
437
|
<path
|
|
431
|
-
d="M
|
|
438
|
+
d="M 44 16 C 44 22, 32 22, 32 24"
|
|
432
439
|
stroke={FA}
|
|
433
440
|
strokeWidth={1.5}
|
|
434
441
|
fill="none"
|
|
435
442
|
opacity={0.7}
|
|
436
443
|
/>
|
|
444
|
+
</TemplateIcon>
|
|
445
|
+
),
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: 'tree',
|
|
449
|
+
label: 'Tree',
|
|
450
|
+
description:
|
|
451
|
+
'Renders an ASCII file-tree / outline fence as a filesystem-style treeview — edit as an outline.',
|
|
452
|
+
icon: (
|
|
453
|
+
<TemplateIcon>
|
|
454
|
+
<rect x={6} y={5} width={16} height={7} rx={1.5} fill={FA} opacity={0.85} />
|
|
437
455
|
<path
|
|
438
|
-
d="M
|
|
439
|
-
stroke={
|
|
456
|
+
d="M 12 12 L 12 34 M 12 20 L 22 20 M 12 34 L 22 34"
|
|
457
|
+
stroke={F1}
|
|
440
458
|
strokeWidth={1.5}
|
|
441
459
|
fill="none"
|
|
442
460
|
opacity={0.7}
|
|
443
461
|
/>
|
|
462
|
+
<rect x={24} y={16} width={14} height={7} rx={1.5} fill={F2} opacity={0.8} />
|
|
463
|
+
<rect x={24} y={30} width={14} height={7} rx={1.5} fill={F2} opacity={0.8} />
|
|
464
|
+
<rect x={26} y={2} width={14} height={7} rx={1.5} fill={F2} opacity={0.6} />
|
|
465
|
+
</TemplateIcon>
|
|
466
|
+
),
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: 'timeline',
|
|
470
|
+
label: 'Timeline',
|
|
471
|
+
description:
|
|
472
|
+
'Plots events across one or more horizontal tracks with callouts and optional branching links.',
|
|
473
|
+
icon: (
|
|
474
|
+
<TemplateIcon>
|
|
475
|
+
<path d="M 5 21 L 51 21" stroke={F1} strokeWidth={1.8} fill="none" />
|
|
476
|
+
<path d="M 47 18 L 52 21 L 47 24" stroke={F1} strokeWidth={1.5} fill="none" />
|
|
477
|
+
<circle cx={13} cy={21} r={3} fill={FA} />
|
|
478
|
+
<circle cx={29} cy={21} r={3} fill={F2} />
|
|
479
|
+
<circle cx={44} cy={21} r={3} fill={FA} />
|
|
480
|
+
<path d="M 13 18 L 13 10 M 29 24 L 29 32 M 44 18 L 44 8" stroke={F1} strokeWidth={1} />
|
|
481
|
+
<rect x={7} y={5} width={12} height={3} rx={1} fill={F2} opacity={0.8} />
|
|
482
|
+
<rect x={23} y={33} width={12} height={3} rx={1} fill={F2} opacity={0.8} />
|
|
483
|
+
<rect x={38} y={3} width={12} height={3} rx={1} fill={F2} opacity={0.8} />
|
|
444
484
|
</TemplateIcon>
|
|
445
485
|
),
|
|
446
486
|
},
|
|
@@ -566,6 +606,8 @@ export function TemplatePicker({
|
|
|
566
606
|
const [dialogStyle, setDialogStyle] = useState<React.CSSProperties>({});
|
|
567
607
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
568
608
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
609
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
610
|
+
const dialogId = `${TEMPLATE_GALLERY_DIALOG_ID}-${useId().replace(/:/g, '')}`;
|
|
569
611
|
|
|
570
612
|
const updateDialogBounds = () => {
|
|
571
613
|
if (!triggerRef.current) return;
|
|
@@ -583,7 +625,7 @@ export function TemplatePicker({
|
|
|
583
625
|
const handler = (e: MouseEvent) => {
|
|
584
626
|
const target = e.target as Node;
|
|
585
627
|
const inTrigger = triggerRef.current?.contains(target);
|
|
586
|
-
const inDialog =
|
|
628
|
+
const inDialog = dialogRef.current?.contains(target);
|
|
587
629
|
if (!inTrigger && !inDialog) {
|
|
588
630
|
setOpen(false);
|
|
589
631
|
}
|
|
@@ -646,6 +688,8 @@ export function TemplatePicker({
|
|
|
646
688
|
const gallery = open
|
|
647
689
|
? createPortal(
|
|
648
690
|
<TemplateGalleryDialog
|
|
691
|
+
dialogRef={dialogRef}
|
|
692
|
+
dialogId={dialogId}
|
|
649
693
|
title={dialogTitle}
|
|
650
694
|
colorScheme={colorScheme}
|
|
651
695
|
style={dialogStyle}
|
|
@@ -673,6 +717,7 @@ export function TemplatePicker({
|
|
|
673
717
|
onClick={handleOpen}
|
|
674
718
|
aria-haspopup="dialog"
|
|
675
719
|
aria-expanded={open}
|
|
720
|
+
aria-controls={open ? dialogId : undefined}
|
|
676
721
|
title="Choose block type"
|
|
677
722
|
>
|
|
678
723
|
<span className="squisq-template-picker-trigger-label">Block:</span>
|
|
@@ -707,12 +752,16 @@ export function TemplatePicker({
|
|
|
707
752
|
// ── Reusable gallery body ──────────────────────────────────────────
|
|
708
753
|
|
|
709
754
|
function TemplateGalleryDialog({
|
|
755
|
+
dialogRef,
|
|
756
|
+
dialogId,
|
|
710
757
|
children,
|
|
711
758
|
title,
|
|
712
759
|
colorScheme,
|
|
713
760
|
style,
|
|
714
761
|
onClose,
|
|
715
762
|
}: {
|
|
763
|
+
dialogRef: React.RefObject<HTMLDivElement>;
|
|
764
|
+
dialogId: string;
|
|
716
765
|
children: React.ReactNode;
|
|
717
766
|
title: string;
|
|
718
767
|
colorScheme: 'light' | 'dark';
|
|
@@ -721,7 +770,8 @@ function TemplateGalleryDialog({
|
|
|
721
770
|
}) {
|
|
722
771
|
return (
|
|
723
772
|
<div
|
|
724
|
-
|
|
773
|
+
ref={dialogRef}
|
|
774
|
+
id={dialogId}
|
|
725
775
|
className="squisq-template-gallery-dialog"
|
|
726
776
|
data-theme={colorScheme}
|
|
727
777
|
style={style}
|
|
@@ -833,6 +883,7 @@ function TemplateGalleryBody({
|
|
|
833
883
|
onOpenDesigner?: () => void;
|
|
834
884
|
previewSource?: TemplatePreviewSource;
|
|
835
885
|
}) {
|
|
886
|
+
const galleryId = useTemplateGalleryPortalId();
|
|
836
887
|
const [query, setQuery] = useState('');
|
|
837
888
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
838
889
|
// Pull merged doc + library templates from the surrounding context.
|
|
@@ -894,7 +945,8 @@ function TemplateGalleryBody({
|
|
|
894
945
|
|
|
895
946
|
return (
|
|
896
947
|
<div
|
|
897
|
-
id={
|
|
948
|
+
id={galleryId}
|
|
949
|
+
data-squisq-template-gallery-portal=""
|
|
898
950
|
className={`squisq-template-gallery${grouped ? ' squisq-template-gallery--segmented' : ''}`}
|
|
899
951
|
role="listbox"
|
|
900
952
|
aria-label="Block types"
|
|
@@ -1154,10 +1206,13 @@ export function TemplateBadgePopover({
|
|
|
1154
1206
|
previewSource,
|
|
1155
1207
|
}: TemplateBadgePopoverProps) {
|
|
1156
1208
|
const [style, setStyle] = useState<React.CSSProperties>(() => computeDialogStyle(anchorRect));
|
|
1209
|
+
const dialogRef = useRef<HTMLDivElement>(null);
|
|
1210
|
+
const dialogId = `${TEMPLATE_GALLERY_DIALOG_ID}-${useId().replace(/:/g, '')}`;
|
|
1157
1211
|
|
|
1158
1212
|
// Reposition once after mount so the dialog covers the current editor shell.
|
|
1159
1213
|
useEffect(() => {
|
|
1160
|
-
requestAnimationFrame(() => setStyle(computeDialogStyle(anchorRect)));
|
|
1214
|
+
const frame = requestAnimationFrame(() => setStyle(computeDialogStyle(anchorRect)));
|
|
1215
|
+
return () => cancelAnimationFrame(frame);
|
|
1161
1216
|
}, [anchorRect]);
|
|
1162
1217
|
|
|
1163
1218
|
// Outside click + Escape close
|
|
@@ -1167,7 +1222,7 @@ export function TemplateBadgePopover({
|
|
|
1167
1222
|
};
|
|
1168
1223
|
const onMouse = (e: MouseEvent) => {
|
|
1169
1224
|
const target = e.target as Node;
|
|
1170
|
-
const inDialog =
|
|
1225
|
+
const inDialog = dialogRef.current?.contains(target);
|
|
1171
1226
|
if (!inDialog) onClose();
|
|
1172
1227
|
};
|
|
1173
1228
|
// Defer the mousedown listener by one frame so the click that opened
|
|
@@ -1192,6 +1247,8 @@ export function TemplateBadgePopover({
|
|
|
1192
1247
|
|
|
1193
1248
|
return createPortal(
|
|
1194
1249
|
<TemplateGalleryDialog
|
|
1250
|
+
dialogRef={dialogRef}
|
|
1251
|
+
dialogId={dialogId}
|
|
1195
1252
|
title={dialogTitle}
|
|
1196
1253
|
colorScheme={colorScheme}
|
|
1197
1254
|
style={style}
|
|
@@ -1211,7 +1268,7 @@ export function TemplateBadgePopover({
|
|
|
1211
1268
|
}
|
|
1212
1269
|
|
|
1213
1270
|
function templateDialogTitle(previewSource?: TemplatePreviewSource): string {
|
|
1214
|
-
return `Block
|
|
1271
|
+
return `Block Type for ${templateDialogBlockTitle(previewSource)}`;
|
|
1215
1272
|
}
|
|
1216
1273
|
|
|
1217
1274
|
function templateDialogBlockTitle(previewSource?: TemplatePreviewSource): string {
|
|
@@ -1253,8 +1310,5 @@ function findEditorShellRect(anchor: Element | DOMRect): DOMRect {
|
|
|
1253
1310
|
}
|
|
1254
1311
|
}
|
|
1255
1312
|
|
|
1256
|
-
const fallback = document.querySelector('.squisq-editor-shell');
|
|
1257
|
-
if (fallback) return fallback.getBoundingClientRect();
|
|
1258
|
-
|
|
1259
1313
|
return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
|
|
1260
1314
|
}
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type ImageTreatmentPreset,
|
|
41
41
|
} from './customThemes/themeDraft';
|
|
42
42
|
import { Section, SeedColorRow, FontPicker, PresetRow } from './customThemes/themeControls';
|
|
43
|
+
import { ImportThemeSection } from './customThemes/ImportThemeSection';
|
|
43
44
|
|
|
44
45
|
// ── Component ───────────────────────────────────────────────────────
|
|
45
46
|
|
|
@@ -188,6 +189,13 @@ export function ThemeCustomizerPanel({
|
|
|
188
189
|
/>
|
|
189
190
|
</Section>
|
|
190
191
|
|
|
192
|
+
<Section
|
|
193
|
+
title="Import from file"
|
|
194
|
+
hint="Pull colors and fonts from a Word, PowerPoint, or Excel file."
|
|
195
|
+
>
|
|
196
|
+
<ImportThemeSection onImported={(patch) => updateDraft(patch)} />
|
|
197
|
+
</Section>
|
|
198
|
+
|
|
191
199
|
<Section title="Colors" hint="Pick seed colors. The rest is derived.">
|
|
192
200
|
<SeedColorRow
|
|
193
201
|
label="Primary"
|
package/src/ThemePicker.tsx
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* preview-on-hover style listing rather than a wall of names.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import { createPortal } from 'react-dom';
|
|
14
14
|
import {
|
|
15
15
|
getThemeSummaries,
|
|
@@ -95,6 +95,7 @@ function ThemeNameChip({
|
|
|
95
95
|
return (
|
|
96
96
|
<span
|
|
97
97
|
className={`squisq-theme-picker-name-chip${className ? ` ${className}` : ''}`}
|
|
98
|
+
data-theme-id={theme.id}
|
|
98
99
|
style={{
|
|
99
100
|
background: theme.colors.background,
|
|
100
101
|
color: theme.colors.text,
|
|
@@ -102,7 +103,7 @@ function ThemeNameChip({
|
|
|
102
103
|
borderColor: theme.colors.backgroundLight,
|
|
103
104
|
}}
|
|
104
105
|
>
|
|
105
|
-
{label}
|
|
106
|
+
<span className="squisq-theme-picker-name-chip-label">{label}</span>
|
|
106
107
|
</span>
|
|
107
108
|
);
|
|
108
109
|
}
|
|
@@ -133,6 +134,8 @@ export function ThemePicker({
|
|
|
133
134
|
const [open, setOpen] = useState(false);
|
|
134
135
|
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
135
136
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
137
|
+
const popoverRef = useRef<HTMLDivElement>(null);
|
|
138
|
+
const popoverId = `squisq-theme-picker-popover-${useId().replace(/:/g, '')}`;
|
|
136
139
|
|
|
137
140
|
// Custom entries are computed per-render (built-ins stay static at module
|
|
138
141
|
// load). A custom theme is already a full Theme, so it becomes an entry
|
|
@@ -189,7 +192,7 @@ export function ThemePicker({
|
|
|
189
192
|
// trigger as-is. On the first open it doesn't exist yet — we'll
|
|
190
193
|
// re-run after mount via the requestAnimationFrame in the open
|
|
191
194
|
// effect, so estimate generously for paint #1.
|
|
192
|
-
const popoverEl =
|
|
195
|
+
const popoverEl = popoverRef.current;
|
|
193
196
|
const measured = popoverEl?.scrollHeight ?? 520;
|
|
194
197
|
const spaceBelow = vh - rect.bottom - margin - gap;
|
|
195
198
|
const spaceAbove = rect.top - margin - gap;
|
|
@@ -229,7 +232,7 @@ export function ThemePicker({
|
|
|
229
232
|
const onDown = (e: MouseEvent) => {
|
|
230
233
|
const target = e.target as Node;
|
|
231
234
|
if (triggerRef.current?.contains(target)) return;
|
|
232
|
-
const popoverEl =
|
|
235
|
+
const popoverEl = popoverRef.current;
|
|
233
236
|
if (popoverEl?.contains(target)) return;
|
|
234
237
|
setOpen(false);
|
|
235
238
|
};
|
|
@@ -258,7 +261,8 @@ export function ThemePicker({
|
|
|
258
261
|
const popover = open
|
|
259
262
|
? createPortal(
|
|
260
263
|
<div
|
|
261
|
-
|
|
264
|
+
ref={popoverRef}
|
|
265
|
+
id={popoverId}
|
|
262
266
|
className="squisq-theme-picker-popover"
|
|
263
267
|
role="listbox"
|
|
264
268
|
aria-label={ariaLabel}
|
|
@@ -392,6 +396,7 @@ export function ThemePicker({
|
|
|
392
396
|
onClick={handleToggle}
|
|
393
397
|
aria-haspopup="listbox"
|
|
394
398
|
aria-expanded={open}
|
|
399
|
+
aria-controls={open ? popoverId : undefined}
|
|
395
400
|
aria-label={ariaLabel}
|
|
396
401
|
>
|
|
397
402
|
{previewEntry && (
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Renders a tiny slideshow-style thumbnail of a single block, used to fill each
|
|
5
5
|
* bar on the timeline. Reuses the same rendering path as the inline preview
|
|
6
|
-
* gutter and the slideshow: `buildPreviewDoc` → `
|
|
6
|
+
* gutter and the slideshow: `buildPreviewDoc` → `materializeBlockLayers` → `BlockRenderer`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { memo } from 'react';
|