@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/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ export type {
|
|
|
45
45
|
DocumentLinkProvider,
|
|
46
46
|
ViewPreferences,
|
|
47
47
|
ThemeInheritance,
|
|
48
|
+
BlockTagVisibility,
|
|
48
49
|
LayoutMode,
|
|
49
50
|
} from './EditorContext.js';
|
|
50
51
|
|
|
@@ -99,11 +100,12 @@ export { DocumentSettingsDialog } from './DocumentSettingsDialog.js';
|
|
|
99
100
|
export type { DocumentSettingsDialogProps } from './DocumentSettingsDialog.js';
|
|
100
101
|
export { ThemePicker } from './ThemePicker.js';
|
|
101
102
|
export type { ThemePickerProps } from './ThemePicker.js';
|
|
102
|
-
export {
|
|
103
|
-
export type {
|
|
103
|
+
export { PICKER_CATEGORIES, ALL_PICKER_ENTRIES, searchPickerEntries } from './emojiData.js';
|
|
104
|
+
export type { PickerEntry, PickerCategory } from './emojiData.js';
|
|
104
105
|
export {
|
|
105
106
|
PreviewSettingsProvider,
|
|
106
107
|
PreviewToolbarControls,
|
|
108
|
+
PreviewModeMenu,
|
|
107
109
|
PreviewModeSwitch,
|
|
108
110
|
PreviewFormatSwitch,
|
|
109
111
|
usePreviewSettings,
|
|
@@ -142,7 +144,6 @@ export {
|
|
|
142
144
|
setHeadingLineTransition,
|
|
143
145
|
readBlockAttrsTransition,
|
|
144
146
|
setHeadingAttrsTransition,
|
|
145
|
-
setBlockAttrsTransition,
|
|
146
147
|
EMPTY_TRANSITION,
|
|
147
148
|
} from './headingTransition.js';
|
|
148
149
|
export type { HeadingTransitionAttrs, TransitionFields } from './headingTransition.js';
|
|
@@ -190,12 +191,26 @@ export {
|
|
|
190
191
|
export { useMonacoLoader } from './useMonacoLoader.js';
|
|
191
192
|
export type { UseMonacoLoaderResult } from './useMonacoLoader.js';
|
|
192
193
|
|
|
194
|
+
// Monaco language-service worker wiring — the host supplies the `?worker`
|
|
195
|
+
// constructors, this owns the label→worker mapping. See monacoWorkers.ts.
|
|
196
|
+
export { configureMonacoWorkers } from './monacoWorkers.js';
|
|
197
|
+
export type { MonacoWorkerConstructor, MonacoWorkerConstructors } from './monacoWorkers.js';
|
|
198
|
+
|
|
193
199
|
// Custom themes — provider stack (doc frontmatter + browser-local library)
|
|
194
|
-
export {
|
|
200
|
+
export {
|
|
201
|
+
CustomThemeProvider,
|
|
202
|
+
useCustomThemes,
|
|
203
|
+
useDocCustomThemes,
|
|
204
|
+
ImportThemeSection,
|
|
205
|
+
draftPatchFromImportedTheme,
|
|
206
|
+
} from './customThemes/index.js';
|
|
195
207
|
export type {
|
|
196
208
|
CustomThemeContextValue,
|
|
197
209
|
CustomThemeProviderProps,
|
|
198
210
|
DocCustomThemes,
|
|
211
|
+
ImportThemeSectionProps,
|
|
212
|
+
ImportedThemeResult,
|
|
213
|
+
ThemeSaveExtras,
|
|
199
214
|
} from './customThemes/index.js';
|
|
200
215
|
|
|
201
216
|
// Custom templates — provider stack (doc frontmatter + browser-local library)
|
|
@@ -219,24 +234,128 @@ export { buildPreviewDoc } from './buildPreviewDoc.js';
|
|
|
219
234
|
// Tiptap extension: Heading with template annotation support
|
|
220
235
|
export { HeadingWithTemplate } from './TemplateAnnotation.js';
|
|
221
236
|
|
|
222
|
-
// Diagram editor —
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
237
|
+
// Diagram editor — ASCII fences are the authored diagram format. The
|
|
238
|
+
// AsciiDiagramExtension turns code fences containing box-and-line art
|
|
239
|
+
// into interactive canvases; edits re-render the art (the fence stays the
|
|
240
|
+
// source of truth). Legacy `{[diagram]}` heading sections still RENDER in
|
|
241
|
+
// preview/player via core, but heading-based canvas editing was removed
|
|
242
|
+
// (BREAKING: DiagramExtension / DiagramWidget / useDiagramData / the
|
|
243
|
+
// heading command helpers are gone).
|
|
226
244
|
export { DiagramCanvas } from './diagram/DiagramCanvas.js';
|
|
227
245
|
export type { DiagramCommand } from './diagram/DiagramCanvas.js';
|
|
228
|
-
export {
|
|
229
|
-
export {
|
|
230
|
-
|
|
246
|
+
export type { DiagramData, DiagramNode, DiagramEdge } from './diagram/types.js';
|
|
247
|
+
export {
|
|
248
|
+
AsciiDiagramExtension,
|
|
249
|
+
findAsciiDiagramBlockPos,
|
|
250
|
+
isAsciiSourceVisible,
|
|
251
|
+
toggleAsciiSource,
|
|
252
|
+
} from './asciiDiagram/AsciiDiagramExtension.js';
|
|
253
|
+
export type {
|
|
254
|
+
AsciiDiagramBlockEntry,
|
|
255
|
+
AsciiDiagramExtensionOptions,
|
|
256
|
+
AsciiDiagramPluginState,
|
|
257
|
+
} from './asciiDiagram/AsciiDiagramExtension.js';
|
|
258
|
+
export { AsciiDiagramWidget } from './asciiDiagram/AsciiDiagramWidget.js';
|
|
259
|
+
export { useAsciiDiagramData, asciiDiagramToCanvas } from './asciiDiagram/asciiDiagramData.js';
|
|
260
|
+
export type { AsciiDiagramView } from './asciiDiagram/asciiDiagramData.js';
|
|
261
|
+
export {
|
|
262
|
+
applyAsciiDiagramCommand,
|
|
263
|
+
applyRepairCommand,
|
|
264
|
+
replaceAsciiFenceText,
|
|
265
|
+
} from './asciiDiagram/asciiDiagramCommands.js';
|
|
266
|
+
// RepairableDiagramExtension mounts an inline "Repair as diagram" button on
|
|
267
|
+
// code fences holding BROKEN box art — art too misaligned for clean detection
|
|
268
|
+
// (so it renders as a faithful code block). One click reconstructs it into
|
|
269
|
+
// clean `diagram`-tagged art that the AsciiDiagramExtension turns into a
|
|
270
|
+
// canvas. Peer to the diagram/tree editors; claims the fences neither does.
|
|
271
|
+
export {
|
|
272
|
+
RepairableDiagramExtension,
|
|
273
|
+
findRepairableBlockPos,
|
|
274
|
+
isRepairableFence,
|
|
275
|
+
REPAIRABLE_KEY,
|
|
276
|
+
} from './asciiDiagram/RepairableDiagramExtension.js';
|
|
277
|
+
export type {
|
|
278
|
+
RepairableBlockEntry,
|
|
279
|
+
RepairableDiagramExtensionOptions,
|
|
280
|
+
RepairablePluginState,
|
|
281
|
+
} from './asciiDiagram/RepairableDiagramExtension.js';
|
|
282
|
+
export {
|
|
283
|
+
addEdgeOp,
|
|
284
|
+
addNodeOp,
|
|
285
|
+
moveNodeOp,
|
|
286
|
+
removeEdgeOp,
|
|
287
|
+
removeNodeOp,
|
|
288
|
+
renameNodeOp,
|
|
289
|
+
resizeNodeOp,
|
|
290
|
+
sanitizeAsciiLabel,
|
|
291
|
+
} from './asciiDiagram/asciiDiagramOps.js';
|
|
292
|
+
export { shouldPasteAsAsciiFence } from './asciiDiagram/asciiPaste.js';
|
|
293
|
+
|
|
294
|
+
// Treeview editor — ASCII file-tree / outline fences get an interactive
|
|
295
|
+
// outline widget; edits re-render the tree art (the fence stays the source
|
|
296
|
+
// of truth). Peer to the ASCII diagram editor; mutually exclusive with it
|
|
297
|
+
// (a fence with boxes is a diagram, not a tree).
|
|
298
|
+
export { TreeViewExtension, findTreeBlockPos } from './treeview/TreeViewExtension.js';
|
|
299
|
+
export type {
|
|
300
|
+
TreeBlockEntry,
|
|
301
|
+
TreeViewExtensionOptions,
|
|
302
|
+
TreeViewPluginState,
|
|
303
|
+
} from './treeview/TreeViewExtension.js';
|
|
304
|
+
export { TreeOutlineWidget } from './treeview/TreeOutlineWidget.js';
|
|
305
|
+
export { useTreeViewData } from './treeview/treeViewData.js';
|
|
306
|
+
export type { TreeViewData } from './treeview/treeViewData.js';
|
|
307
|
+
export { applyTreeCommand, replaceTreeFenceText } from './treeview/treeViewCommands.js';
|
|
308
|
+
export type { TreeCommand } from './treeview/treeViewCommands.js';
|
|
309
|
+
export {
|
|
310
|
+
addItemOp,
|
|
311
|
+
indentItemOp,
|
|
312
|
+
moveItemDownOp,
|
|
313
|
+
moveItemUpOp,
|
|
314
|
+
outdentItemOp,
|
|
315
|
+
removeItemOp,
|
|
316
|
+
renameItemOp,
|
|
317
|
+
sanitizeTreeLabel,
|
|
318
|
+
toggleDirOp,
|
|
319
|
+
} from './treeview/treeOps.js';
|
|
320
|
+
export { shouldPasteAsTreeFence } from './treeview/treePaste.js';
|
|
321
|
+
|
|
322
|
+
// Timeline editor — authored marker-rail fences become an accessible canvas
|
|
323
|
+
// with add-point affordances and a selected-point inspector. Semantic edits
|
|
324
|
+
// re-render the canonical `timeline` fence in one undoable transaction.
|
|
325
|
+
export {
|
|
326
|
+
TimelineViewExtension,
|
|
327
|
+
TIMELINE_VIEW_KEY,
|
|
328
|
+
findTimelineBlockPos,
|
|
329
|
+
getTimelineForNode,
|
|
330
|
+
parseTimelineForNode,
|
|
331
|
+
} from './timeline/TimelineViewExtension.js';
|
|
332
|
+
export type {
|
|
333
|
+
TimelineBlockEntry,
|
|
334
|
+
TimelineViewExtensionOptions,
|
|
335
|
+
TimelineViewPluginState,
|
|
336
|
+
} from './timeline/TimelineViewExtension.js';
|
|
337
|
+
export { TimelineEditorWidget } from './timeline/TimelineEditorWidget.js';
|
|
338
|
+
export type { TimelineEditorWidgetProps } from './timeline/TimelineEditorWidget.js';
|
|
339
|
+
export { useTimelineData } from './timeline/timelineData.js';
|
|
340
|
+
export type { TimelineViewData } from './timeline/timelineData.js';
|
|
231
341
|
export {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
342
|
+
applyTimelineCommand,
|
|
343
|
+
isTimelineSourceSafeForSemanticEdit,
|
|
344
|
+
} from './timeline/timelineCommands.js';
|
|
345
|
+
export type { TimelineCommand, TimelineCommandResult } from './timeline/timelineCommands.js';
|
|
346
|
+
export {
|
|
347
|
+
addTimelineEventOp,
|
|
348
|
+
nextTimelineEventId,
|
|
349
|
+
removeTimelineEventOp,
|
|
350
|
+
sanitizeTimelineText,
|
|
351
|
+
updateTimelineEventOp,
|
|
352
|
+
} from './timeline/timelineOps.js';
|
|
353
|
+
export type {
|
|
354
|
+
AddTimelineEventOptions,
|
|
355
|
+
AddTimelineEventResult,
|
|
356
|
+
TimelineEventPatch,
|
|
357
|
+
} from './timeline/timelineOps.js';
|
|
358
|
+
export { shouldPasteAsTimelineFence } from './timeline/timelinePaste.js';
|
|
240
359
|
|
|
241
360
|
// JSON Form — editable component
|
|
242
361
|
export { JsonEditor } from './jsonEditor/index.js';
|
|
@@ -277,6 +396,59 @@ export type { CaptureKind, ResolvedFormat } from './recorder/formats.js';
|
|
|
277
396
|
export { buildTimingJson, encodeTimingJson, timingPathFor } from './recorder/timingJson.js';
|
|
278
397
|
export type { TimingJson, RecordedBookmark } from './recorder/timingJson.js';
|
|
279
398
|
|
|
399
|
+
// Teleprompter — the Narrate display mode under the Use tab: voice-paced
|
|
400
|
+
// prompter surface, floating-window tiers, and the mic analysis pipeline.
|
|
401
|
+
export {
|
|
402
|
+
TeleprompterView,
|
|
403
|
+
TeleprompterSurface,
|
|
404
|
+
TeleprompterControls,
|
|
405
|
+
useTeleprompter,
|
|
406
|
+
useNarrationRecorder,
|
|
407
|
+
buildNarrationSavePlan,
|
|
408
|
+
executeNarrationSave,
|
|
409
|
+
insertNarrationPreamble,
|
|
410
|
+
narrationAnnotationLine,
|
|
411
|
+
cameraVideoLine,
|
|
412
|
+
vadConfigForSensitivity,
|
|
413
|
+
useMicAnalysis,
|
|
414
|
+
PCM_WORKLET_SOURCE,
|
|
415
|
+
PCM_WORKLET_NAME,
|
|
416
|
+
registerPcmWorklet,
|
|
417
|
+
createFloatingWindowManager,
|
|
418
|
+
detectFloatTiers,
|
|
419
|
+
useFloatingWindow,
|
|
420
|
+
measureTokenLines,
|
|
421
|
+
targetOffsetFor,
|
|
422
|
+
stepScroll,
|
|
423
|
+
EYE_LINE_FRACTION,
|
|
424
|
+
TELEPROMPTER_CSS,
|
|
425
|
+
ensureTeleprompterStyles,
|
|
426
|
+
prompterVarsFromTheme,
|
|
427
|
+
DEFAULT_TELEPROMPTER_PREFS,
|
|
428
|
+
} from './teleprompter/index.js';
|
|
429
|
+
export type {
|
|
430
|
+
TeleprompterViewProps,
|
|
431
|
+
TeleprompterSurfaceProps,
|
|
432
|
+
TeleprompterControlsProps,
|
|
433
|
+
TeleprompterController,
|
|
434
|
+
TeleprompterRecordingDeps,
|
|
435
|
+
NarrationRecorderController,
|
|
436
|
+
NarrationRecorderState,
|
|
437
|
+
NarrationTake,
|
|
438
|
+
NarrationSavePlan,
|
|
439
|
+
NarrationSaveResult,
|
|
440
|
+
MicAnalysisHandle,
|
|
441
|
+
MicAnalysisStatus,
|
|
442
|
+
FloatingWindowManager,
|
|
443
|
+
FloatOpenOptions,
|
|
444
|
+
CanvasSink,
|
|
445
|
+
FloatingWindowHandle,
|
|
446
|
+
TokenLineMap,
|
|
447
|
+
TeleprompterPrefs,
|
|
448
|
+
PrompterTransport,
|
|
449
|
+
FloatTier,
|
|
450
|
+
} from './teleprompter/index.js';
|
|
451
|
+
|
|
280
452
|
// Image editor — layered, sidecar-persisted raster authoring surface.
|
|
281
453
|
// Pairs with `ImageViewer` and the `<basename>_files/` sidecar convention.
|
|
282
454
|
export { ImageEditor } from './ImageEditor.js';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* to the host's `onChange`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createContext, useContext, useMemo, type ReactNode } from 'react';
|
|
8
|
+
import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react';
|
|
9
9
|
import type {
|
|
10
10
|
JsonFormValidationError,
|
|
11
11
|
JsonFormValidator,
|
|
@@ -38,18 +38,24 @@ export interface JsonEditorProviderProps {
|
|
|
38
38
|
export function JsonEditorProvider(props: JsonEditorProviderProps) {
|
|
39
39
|
const { rootSchema, rootData, onRootChange, density, validate, onValidate, children } = props;
|
|
40
40
|
|
|
41
|
+
const validationErrors = useMemo(
|
|
42
|
+
() => (validate ? validate(rootData, rootSchema) : []),
|
|
43
|
+
[validate, rootData, rootSchema],
|
|
44
|
+
);
|
|
41
45
|
const errors = useMemo(() => {
|
|
42
|
-
if (!validate) return new Map<string, JsonFormValidationError[]>();
|
|
43
|
-
const list = validate(rootData, rootSchema);
|
|
44
|
-
if (onValidate) onValidate(list);
|
|
45
46
|
const grouped = new Map<string, JsonFormValidationError[]>();
|
|
46
|
-
for (const e of
|
|
47
|
+
for (const e of validationErrors) {
|
|
47
48
|
const arr = grouped.get(e.path) ?? [];
|
|
48
49
|
arr.push(e);
|
|
49
50
|
grouped.set(e.path, arr);
|
|
50
51
|
}
|
|
51
52
|
return grouped;
|
|
52
|
-
}, [
|
|
53
|
+
}, [validationErrors]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!onValidate) return;
|
|
57
|
+
onValidate(validationErrors);
|
|
58
|
+
}, [validationErrors, onValidate]);
|
|
53
59
|
|
|
54
60
|
const setAtPath = useMemo(
|
|
55
61
|
() => (pointer: string, value: unknown) => {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveRef,
|
|
12
12
|
type SquisqAnnotatedSchema,
|
|
13
13
|
} from '@bendyline/squisq/jsonForm';
|
|
14
|
+
import { useId } from 'react';
|
|
14
15
|
import { useJsonEditor } from './JsonEditorContext';
|
|
15
16
|
import { EDITORS, isCompositeControl } from './editors';
|
|
16
17
|
|
|
@@ -23,6 +24,7 @@ export interface RenderNodeProps {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
|
|
27
|
+
const generatedId = useId().replace(/:/g, '');
|
|
26
28
|
const { rootSchema, rootData, errors } = useJsonEditor();
|
|
27
29
|
const resolved = resolveRef(props.schema, rootSchema) ?? props.schema;
|
|
28
30
|
|
|
@@ -35,6 +37,11 @@ export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
|
|
|
35
37
|
const label = resolved.squisq?.label ?? resolved.title;
|
|
36
38
|
const help = resolved.squisq?.help ?? resolved.description;
|
|
37
39
|
const fieldErrors = errors.get(props.pointer) ?? [];
|
|
40
|
+
const controlId = `squisq-json-control-${generatedId}`;
|
|
41
|
+
const labelId = label ? `${controlId}-label` : undefined;
|
|
42
|
+
const helpId = help ? `${controlId}-help` : undefined;
|
|
43
|
+
const errorId = fieldErrors.length > 0 ? `${controlId}-error` : undefined;
|
|
44
|
+
const describedBy = [helpId, errorId].filter(Boolean).join(' ') || undefined;
|
|
38
45
|
|
|
39
46
|
const editorEl = (
|
|
40
47
|
<Editor
|
|
@@ -42,6 +49,10 @@ export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
|
|
|
42
49
|
schema={resolved}
|
|
43
50
|
pointer={props.pointer}
|
|
44
51
|
disabled={disabled}
|
|
52
|
+
controlId={controlId}
|
|
53
|
+
labelledBy={labelId}
|
|
54
|
+
describedBy={describedBy}
|
|
55
|
+
invalid={fieldErrors.length > 0}
|
|
45
56
|
// GroupEditor honors this — other editors ignore the extra prop.
|
|
46
57
|
// @ts-expect-error optional renderer-specific prop
|
|
47
58
|
suppressTitle={props.suppressTopGroupTitle}
|
|
@@ -55,11 +66,21 @@ export function RenderNode(props: RenderNodeProps): React.ReactElement | null {
|
|
|
55
66
|
|
|
56
67
|
return (
|
|
57
68
|
<div className={`squisq-jf-field${disabled ? ' squisq-jf-field--disabled' : ''}`}>
|
|
58
|
-
{label ?
|
|
69
|
+
{label ? (
|
|
70
|
+
<label id={labelId} htmlFor={controlId} className="squisq-jf-field__label">
|
|
71
|
+
{label}
|
|
72
|
+
</label>
|
|
73
|
+
) : null}
|
|
59
74
|
{editorEl}
|
|
60
|
-
{help ?
|
|
75
|
+
{help ? (
|
|
76
|
+
<p id={helpId} className="squisq-jf-field__help">
|
|
77
|
+
{help}
|
|
78
|
+
</p>
|
|
79
|
+
) : null}
|
|
61
80
|
{fieldErrors.length > 0 ? (
|
|
62
|
-
<p className="squisq-jf-field__error">
|
|
81
|
+
<p id={errorId} className="squisq-jf-field__error" role="alert">
|
|
82
|
+
{fieldErrors[0].message}
|
|
83
|
+
</p>
|
|
63
84
|
) : null}
|
|
64
85
|
</div>
|
|
65
86
|
);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { useEffect, useId, useRef, useState, type ChangeEvent } from 'react';
|
|
10
10
|
import {
|
|
11
|
+
appendPointer,
|
|
11
12
|
arrayItemKind,
|
|
12
13
|
type ControlKind,
|
|
13
14
|
type SquisqAnnotatedSchema,
|
|
@@ -21,6 +22,19 @@ export interface EditorProps {
|
|
|
21
22
|
schema: SquisqAnnotatedSchema;
|
|
22
23
|
pointer: string;
|
|
23
24
|
disabled: boolean;
|
|
25
|
+
controlId?: string;
|
|
26
|
+
labelledBy?: string;
|
|
27
|
+
describedBy?: string;
|
|
28
|
+
invalid?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function accessibilityProps(props: EditorProps) {
|
|
32
|
+
return {
|
|
33
|
+
id: props.controlId,
|
|
34
|
+
'aria-labelledby': props.labelledBy,
|
|
35
|
+
'aria-describedby': props.describedBy,
|
|
36
|
+
'aria-invalid': props.invalid || undefined,
|
|
37
|
+
};
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
// ── Helpers ───────────────────────────────────────────────────────
|
|
@@ -38,10 +52,12 @@ function asNumber(v: unknown): number | '' {
|
|
|
38
52
|
|
|
39
53
|
// ── Primitive editors ────────────────────────────────────────────
|
|
40
54
|
|
|
41
|
-
export function TextEditor(
|
|
55
|
+
export function TextEditor(props: EditorProps) {
|
|
56
|
+
const { value, schema, pointer, disabled } = props;
|
|
42
57
|
const { setAtPath } = useJsonEditor();
|
|
43
58
|
return (
|
|
44
59
|
<input
|
|
60
|
+
{...accessibilityProps(props)}
|
|
45
61
|
type="text"
|
|
46
62
|
className="squisq-jf-input"
|
|
47
63
|
value={asString(value)}
|
|
@@ -52,7 +68,8 @@ export function TextEditor({ value, schema, pointer, disabled }: EditorProps) {
|
|
|
52
68
|
);
|
|
53
69
|
}
|
|
54
70
|
|
|
55
|
-
export function MultilineEditor(
|
|
71
|
+
export function MultilineEditor(props: EditorProps) {
|
|
72
|
+
const { value, schema, pointer, disabled } = props;
|
|
56
73
|
const { setAtPath } = useJsonEditor();
|
|
57
74
|
const ref = useRef<HTMLTextAreaElement>(null);
|
|
58
75
|
// Auto-grow vertically.
|
|
@@ -64,6 +81,7 @@ export function MultilineEditor({ value, schema, pointer, disabled }: EditorProp
|
|
|
64
81
|
}, [value]);
|
|
65
82
|
return (
|
|
66
83
|
<textarea
|
|
84
|
+
{...accessibilityProps(props)}
|
|
67
85
|
ref={ref}
|
|
68
86
|
className="squisq-jf-textarea"
|
|
69
87
|
value={asString(value)}
|
|
@@ -86,7 +104,8 @@ export function RichTextEditor({ value, schema, pointer, disabled }: EditorProps
|
|
|
86
104
|
);
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
export function NumberStepperEditor(
|
|
107
|
+
export function NumberStepperEditor(props: EditorProps) {
|
|
108
|
+
const { value, schema, pointer, disabled } = props;
|
|
90
109
|
const { setAtPath } = useJsonEditor();
|
|
91
110
|
const isInt = schema.type === 'integer';
|
|
92
111
|
const step = schema.squisq?.step ?? schema.multipleOf ?? (isInt ? 1 : 0.1);
|
|
@@ -111,6 +130,7 @@ export function NumberStepperEditor({ value, schema, pointer, disabled }: Editor
|
|
|
111
130
|
−
|
|
112
131
|
</button>
|
|
113
132
|
<input
|
|
133
|
+
{...accessibilityProps(props)}
|
|
114
134
|
className="squisq-jf-stepper__input"
|
|
115
135
|
type="number"
|
|
116
136
|
value={num === '' ? '' : num}
|
|
@@ -138,7 +158,8 @@ export function NumberStepperEditor({ value, schema, pointer, disabled }: Editor
|
|
|
138
158
|
);
|
|
139
159
|
}
|
|
140
160
|
|
|
141
|
-
export function SliderEditor(
|
|
161
|
+
export function SliderEditor(props: EditorProps) {
|
|
162
|
+
const { value, schema, pointer, disabled } = props;
|
|
142
163
|
const { setAtPath } = useJsonEditor();
|
|
143
164
|
const min = (schema.minimum ?? schema.exclusiveMinimum ?? 0) as number;
|
|
144
165
|
const max = (schema.maximum ?? schema.exclusiveMaximum ?? 100) as number;
|
|
@@ -148,6 +169,7 @@ export function SliderEditor({ value, schema, pointer, disabled }: EditorProps)
|
|
|
148
169
|
return (
|
|
149
170
|
<span className="squisq-jf-slider-row">
|
|
150
171
|
<input
|
|
172
|
+
{...accessibilityProps(props)}
|
|
151
173
|
type="range"
|
|
152
174
|
className="squisq-jf-slider"
|
|
153
175
|
min={min}
|
|
@@ -162,11 +184,13 @@ export function SliderEditor({ value, schema, pointer, disabled }: EditorProps)
|
|
|
162
184
|
);
|
|
163
185
|
}
|
|
164
186
|
|
|
165
|
-
export function ToggleEditor(
|
|
187
|
+
export function ToggleEditor(props: EditorProps) {
|
|
188
|
+
const { value, schema, pointer, disabled } = props;
|
|
166
189
|
const { setAtPath } = useJsonEditor();
|
|
167
190
|
const on = Boolean(value);
|
|
168
191
|
return (
|
|
169
192
|
<button
|
|
193
|
+
{...accessibilityProps(props)}
|
|
170
194
|
type="button"
|
|
171
195
|
className={`squisq-jf-toggle${on ? ' squisq-jf-toggle--on' : ''}`}
|
|
172
196
|
disabled={disabled}
|
|
@@ -189,12 +213,17 @@ export function ToggleEditor({ value, schema, pointer, disabled }: EditorProps)
|
|
|
189
213
|
);
|
|
190
214
|
}
|
|
191
215
|
|
|
192
|
-
export function CheckboxEditor(
|
|
216
|
+
export function CheckboxEditor(props: EditorProps) {
|
|
217
|
+
const { value, schema, pointer, disabled } = props;
|
|
193
218
|
const { setAtPath } = useJsonEditor();
|
|
194
|
-
const
|
|
219
|
+
const generatedId = useId();
|
|
220
|
+
const id = props.controlId ?? generatedId;
|
|
195
221
|
return (
|
|
196
222
|
<label htmlFor={id} className="squisq-jf-toggle">
|
|
197
223
|
<input
|
|
224
|
+
aria-labelledby={props.labelledBy}
|
|
225
|
+
aria-describedby={props.describedBy}
|
|
226
|
+
aria-invalid={props.invalid || undefined}
|
|
198
227
|
id={id}
|
|
199
228
|
type="checkbox"
|
|
200
229
|
checked={Boolean(value)}
|
|
@@ -214,11 +243,12 @@ function enumOptions(schema: SquisqAnnotatedSchema): { value: unknown; label: st
|
|
|
214
243
|
}));
|
|
215
244
|
}
|
|
216
245
|
|
|
217
|
-
export function SegmentedEditor(
|
|
246
|
+
export function SegmentedEditor(props: EditorProps) {
|
|
247
|
+
const { value, schema, pointer, disabled } = props;
|
|
218
248
|
const { setAtPath } = useJsonEditor();
|
|
219
249
|
const options = enumOptions(schema);
|
|
220
250
|
return (
|
|
221
|
-
<span className="squisq-jf-segmented">
|
|
251
|
+
<span {...accessibilityProps(props)} className="squisq-jf-segmented" role="group">
|
|
222
252
|
{options.map((opt, i) => {
|
|
223
253
|
const active = value === opt.value;
|
|
224
254
|
return (
|
|
@@ -228,6 +258,7 @@ export function SegmentedEditor({ value, schema, pointer, disabled }: EditorProp
|
|
|
228
258
|
className={`squisq-jf-segmented__btn${active ? ' squisq-jf-segmented__btn--active' : ''}`}
|
|
229
259
|
disabled={disabled}
|
|
230
260
|
onClick={() => setAtPath(pointer, opt.value)}
|
|
261
|
+
aria-pressed={active}
|
|
231
262
|
>
|
|
232
263
|
{opt.label}
|
|
233
264
|
</button>
|
|
@@ -237,12 +268,13 @@ export function SegmentedEditor({ value, schema, pointer, disabled }: EditorProp
|
|
|
237
268
|
);
|
|
238
269
|
}
|
|
239
270
|
|
|
240
|
-
export function RadioEditor(
|
|
271
|
+
export function RadioEditor(props: EditorProps) {
|
|
272
|
+
const { value, schema, pointer, disabled } = props;
|
|
241
273
|
const { setAtPath } = useJsonEditor();
|
|
242
274
|
const options = enumOptions(schema);
|
|
243
275
|
const name = useId();
|
|
244
276
|
return (
|
|
245
|
-
<div className="squisq-jf-radio">
|
|
277
|
+
<div {...accessibilityProps(props)} className="squisq-jf-radio" role="radiogroup">
|
|
246
278
|
{options.map((opt, i) => {
|
|
247
279
|
const active = value === opt.value;
|
|
248
280
|
return (
|
|
@@ -262,11 +294,13 @@ export function RadioEditor({ value, schema, pointer, disabled }: EditorProps) {
|
|
|
262
294
|
);
|
|
263
295
|
}
|
|
264
296
|
|
|
265
|
-
export function ComboboxEditor(
|
|
297
|
+
export function ComboboxEditor(props: EditorProps) {
|
|
298
|
+
const { value, schema, pointer, disabled } = props;
|
|
266
299
|
const { setAtPath } = useJsonEditor();
|
|
267
300
|
const options = enumOptions(schema);
|
|
268
301
|
return (
|
|
269
302
|
<select
|
|
303
|
+
{...accessibilityProps(props)}
|
|
270
304
|
className="squisq-jf-select"
|
|
271
305
|
value={asString(value)}
|
|
272
306
|
disabled={disabled}
|
|
@@ -285,12 +319,14 @@ export function ComboboxEditor({ value, schema, pointer, disabled }: EditorProps
|
|
|
285
319
|
);
|
|
286
320
|
}
|
|
287
321
|
|
|
288
|
-
export function ColorEditor(
|
|
322
|
+
export function ColorEditor(props: EditorProps) {
|
|
323
|
+
const { value, pointer, disabled } = props;
|
|
289
324
|
const { setAtPath } = useJsonEditor();
|
|
290
325
|
const hex = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value) ? value : '#000000';
|
|
291
326
|
return (
|
|
292
327
|
<span className="squisq-jf-color">
|
|
293
328
|
<input
|
|
329
|
+
{...accessibilityProps(props)}
|
|
294
330
|
type="color"
|
|
295
331
|
className="squisq-jf-color__input"
|
|
296
332
|
value={hex}
|
|
@@ -308,7 +344,8 @@ export function ColorEditor({ value, pointer, disabled }: EditorProps) {
|
|
|
308
344
|
);
|
|
309
345
|
}
|
|
310
346
|
|
|
311
|
-
export function DateEditor(
|
|
347
|
+
export function DateEditor(props: EditorProps) {
|
|
348
|
+
const { value, schema, pointer, disabled } = props;
|
|
312
349
|
const { setAtPath } = useJsonEditor();
|
|
313
350
|
const fmt = schema.format;
|
|
314
351
|
const inputType = fmt === 'time' ? 'time' : fmt === 'date' ? 'date' : 'datetime-local';
|
|
@@ -321,6 +358,7 @@ export function DateEditor({ value, schema, pointer, disabled }: EditorProps) {
|
|
|
321
358
|
: asString(value);
|
|
322
359
|
return (
|
|
323
360
|
<input
|
|
361
|
+
{...accessibilityProps(props)}
|
|
324
362
|
type={inputType}
|
|
325
363
|
className="squisq-jf-input"
|
|
326
364
|
value={display}
|
|
@@ -348,7 +386,7 @@ export function GroupEditor(props: EditorProps & { suppressTitle?: boolean }) {
|
|
|
348
386
|
key={key}
|
|
349
387
|
value={obj[key]}
|
|
350
388
|
schema={propSchema}
|
|
351
|
-
pointer={
|
|
389
|
+
pointer={appendPointer(pointer, key)}
|
|
352
390
|
parentDisabled={disabled}
|
|
353
391
|
/>
|
|
354
392
|
))}
|
|
@@ -581,7 +619,9 @@ function defaultForSchema(schema: SquisqAnnotatedSchema): unknown {
|
|
|
581
619
|
}
|
|
582
620
|
}
|
|
583
621
|
|
|
584
|
-
export function TabsEditor(
|
|
622
|
+
export function TabsEditor(props: EditorProps) {
|
|
623
|
+
const { value, schema, pointer, disabled } = props;
|
|
624
|
+
const tabsId = useId().replace(/:/g, '');
|
|
585
625
|
const branches = (schema.oneOf ?? schema.anyOf ?? []) as readonly SquisqAnnotatedSchema[];
|
|
586
626
|
const initial = pickMatchingBranch(branches, value);
|
|
587
627
|
const [active, setActive] = useState(initial);
|
|
@@ -593,9 +633,14 @@ export function TabsEditor({ value, schema, pointer, disabled }: EditorProps) {
|
|
|
593
633
|
|
|
594
634
|
const branch = branches[active];
|
|
595
635
|
if (!branch) return null;
|
|
636
|
+
const activateTab = (index: number) => {
|
|
637
|
+
const next = (index + branches.length) % branches.length;
|
|
638
|
+
setActive(next);
|
|
639
|
+
requestAnimationFrame(() => document.getElementById(`${tabsId}-tab-${next}`)?.focus());
|
|
640
|
+
};
|
|
596
641
|
return (
|
|
597
|
-
<div>
|
|
598
|
-
<div className="squisq-jf-tabs__strip">
|
|
642
|
+
<div {...accessibilityProps(props)}>
|
|
643
|
+
<div className="squisq-jf-tabs__strip" role="tablist">
|
|
599
644
|
{branches.map((b, i) => (
|
|
600
645
|
<button
|
|
601
646
|
key={i}
|
|
@@ -603,12 +648,34 @@ export function TabsEditor({ value, schema, pointer, disabled }: EditorProps) {
|
|
|
603
648
|
className={`squisq-jf-tabs__tab${i === active ? ' squisq-jf-tabs__tab--active' : ''}`}
|
|
604
649
|
disabled={disabled}
|
|
605
650
|
onClick={() => setActive(i)}
|
|
651
|
+
onKeyDown={(event) => {
|
|
652
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
653
|
+
event.preventDefault();
|
|
654
|
+
activateTab(i + 1);
|
|
655
|
+
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
656
|
+
event.preventDefault();
|
|
657
|
+
activateTab(i - 1);
|
|
658
|
+
} else if (event.key === 'Home') {
|
|
659
|
+
event.preventDefault();
|
|
660
|
+
activateTab(0);
|
|
661
|
+
} else if (event.key === 'End') {
|
|
662
|
+
event.preventDefault();
|
|
663
|
+
activateTab(branches.length - 1);
|
|
664
|
+
}
|
|
665
|
+
}}
|
|
666
|
+
role="tab"
|
|
667
|
+
id={`${tabsId}-tab-${i}`}
|
|
668
|
+
aria-selected={i === active}
|
|
669
|
+
aria-controls={`${tabsId}-panel`}
|
|
670
|
+
tabIndex={i === active ? 0 : -1}
|
|
606
671
|
>
|
|
607
672
|
{b.squisq?.label ?? b.title ?? `Option ${i + 1}`}
|
|
608
673
|
</button>
|
|
609
674
|
))}
|
|
610
675
|
</div>
|
|
611
|
-
<
|
|
676
|
+
<div id={`${tabsId}-panel`} role="tabpanel" aria-labelledby={`${tabsId}-tab-${active}`}>
|
|
677
|
+
<RenderNode value={value} schema={branch} pointer={pointer} parentDisabled={disabled} />
|
|
678
|
+
</div>
|
|
612
679
|
</div>
|
|
613
680
|
);
|
|
614
681
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MediaEntry } from '@bendyline/squisq/schemas';
|
|
2
|
+
|
|
3
|
+
/** Files managed by the container but intentionally omitted from the Files panel. */
|
|
4
|
+
export function isVisibleMediaEntry(entry: MediaEntry): boolean {
|
|
5
|
+
const basename = entry.name.slice(entry.name.lastIndexOf('/') + 1);
|
|
6
|
+
return basename.toLowerCase() !== '.gitignore';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Return the entries that should participate in the Files panel and its count. */
|
|
10
|
+
export function filterVisibleMediaEntries(entries: readonly MediaEntry[]): MediaEntry[] {
|
|
11
|
+
return entries.filter(isVisibleMediaEntry);
|
|
12
|
+
}
|