@agent-native/toolkit 0.5.0 → 0.6.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/dist/composer/AgentComposerFrame.d.ts +19 -0
- package/dist/composer/AgentComposerFrame.d.ts.map +1 -0
- package/dist/composer/AgentComposerFrame.js +14 -0
- package/dist/composer/AgentComposerFrame.js.map +1 -0
- package/dist/composer/ComposerPlusMenu.d.ts +17 -0
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -0
- package/dist/composer/ComposerPlusMenu.js +501 -0
- package/dist/composer/ComposerPlusMenu.js.map +1 -0
- package/dist/composer/MentionPopover.d.ts +29 -0
- package/dist/composer/MentionPopover.d.ts.map +1 -0
- package/dist/composer/MentionPopover.js +180 -0
- package/dist/composer/MentionPopover.js.map +1 -0
- package/dist/composer/PastedTextChip.d.ts +9 -0
- package/dist/composer/PastedTextChip.d.ts.map +1 -0
- package/dist/composer/PastedTextChip.js +47 -0
- package/dist/composer/PastedTextChip.js.map +1 -0
- package/dist/composer/PromptComposer.d.ts +114 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -0
- package/dist/composer/PromptComposer.js +302 -0
- package/dist/composer/PromptComposer.js.map +1 -0
- package/dist/composer/PromptComposer.spec.d.ts +2 -0
- package/dist/composer/PromptComposer.spec.d.ts.map +1 -0
- package/dist/composer/PromptComposer.spec.js +64 -0
- package/dist/composer/PromptComposer.spec.js.map +1 -0
- package/dist/composer/RealtimeVoiceMode.d.ts +104 -0
- package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -0
- package/dist/composer/RealtimeVoiceMode.js +245 -0
- package/dist/composer/RealtimeVoiceMode.js.map +1 -0
- package/dist/composer/RealtimeVoiceMode.spec.d.ts +2 -0
- package/dist/composer/RealtimeVoiceMode.spec.d.ts.map +1 -0
- package/dist/composer/RealtimeVoiceMode.spec.js +439 -0
- package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -0
- package/dist/composer/TiptapComposer.d.ts +221 -0
- package/dist/composer/TiptapComposer.d.ts.map +1 -0
- package/dist/composer/TiptapComposer.js +1838 -0
- package/dist/composer/TiptapComposer.js.map +1 -0
- package/dist/composer/TiptapComposer.spec.d.ts +2 -0
- package/dist/composer/TiptapComposer.spec.d.ts.map +1 -0
- package/dist/composer/TiptapComposer.spec.js +277 -0
- package/dist/composer/TiptapComposer.spec.js.map +1 -0
- package/dist/composer/VoiceButton.d.ts +25 -0
- package/dist/composer/VoiceButton.d.ts.map +1 -0
- package/dist/composer/VoiceButton.js +100 -0
- package/dist/composer/VoiceButton.js.map +1 -0
- package/dist/composer/VoiceButton.spec.d.ts +2 -0
- package/dist/composer/VoiceButton.spec.d.ts.map +1 -0
- package/dist/composer/VoiceButton.spec.js +19 -0
- package/dist/composer/VoiceButton.spec.js.map +1 -0
- package/dist/composer/asset-picker-url.d.ts +16 -0
- package/dist/composer/asset-picker-url.d.ts.map +1 -0
- package/dist/composer/asset-picker-url.js +64 -0
- package/dist/composer/asset-picker-url.js.map +1 -0
- package/dist/composer/asset-picker-url.spec.d.ts +2 -0
- package/dist/composer/asset-picker-url.spec.d.ts.map +1 -0
- package/dist/composer/asset-picker-url.spec.js +23 -0
- package/dist/composer/asset-picker-url.spec.js.map +1 -0
- package/dist/composer/attachment-accept.d.ts +9 -0
- package/dist/composer/attachment-accept.d.ts.map +1 -0
- package/dist/composer/attachment-accept.js +65 -0
- package/dist/composer/attachment-accept.js.map +1 -0
- package/dist/composer/attachment-accept.spec.d.ts +2 -0
- package/dist/composer/attachment-accept.spec.d.ts.map +1 -0
- package/dist/composer/attachment-accept.spec.js +15 -0
- package/dist/composer/attachment-accept.spec.js.map +1 -0
- package/dist/composer/draft-key.d.ts +2 -0
- package/dist/composer/draft-key.d.ts.map +1 -0
- package/dist/composer/draft-key.js +8 -0
- package/dist/composer/draft-key.js.map +1 -0
- package/dist/composer/draft-key.spec.d.ts +2 -0
- package/dist/composer/draft-key.spec.d.ts.map +1 -0
- package/dist/composer/draft-key.spec.js +13 -0
- package/dist/composer/draft-key.spec.js.map +1 -0
- package/dist/composer/extensions/FileReference.d.ts +3 -0
- package/dist/composer/extensions/FileReference.d.ts.map +1 -0
- package/dist/composer/extensions/FileReference.js +36 -0
- package/dist/composer/extensions/FileReference.js.map +1 -0
- package/dist/composer/extensions/MentionReference.d.ts +3 -0
- package/dist/composer/extensions/MentionReference.d.ts.map +1 -0
- package/dist/composer/extensions/MentionReference.js +66 -0
- package/dist/composer/extensions/MentionReference.js.map +1 -0
- package/dist/composer/extensions/SkillReference.d.ts +3 -0
- package/dist/composer/extensions/SkillReference.d.ts.map +1 -0
- package/dist/composer/extensions/SkillReference.js +40 -0
- package/dist/composer/extensions/SkillReference.js.map +1 -0
- package/dist/composer/index.d.ts +14 -0
- package/dist/composer/index.d.ts.map +1 -0
- package/dist/composer/index.js +13 -0
- package/dist/composer/index.js.map +1 -0
- package/dist/composer/pasted-text.d.ts +31 -0
- package/dist/composer/pasted-text.d.ts.map +1 -0
- package/dist/composer/pasted-text.js +130 -0
- package/dist/composer/pasted-text.js.map +1 -0
- package/dist/composer/pasted-text.spec.d.ts +2 -0
- package/dist/composer/pasted-text.spec.d.ts.map +1 -0
- package/dist/composer/pasted-text.spec.js +107 -0
- package/dist/composer/pasted-text.spec.js.map +1 -0
- package/dist/composer/prompt-attachments.d.ts +18 -0
- package/dist/composer/prompt-attachments.d.ts.map +1 -0
- package/dist/composer/prompt-attachments.js +65 -0
- package/dist/composer/prompt-attachments.js.map +1 -0
- package/dist/composer/prompt-attachments.spec.d.ts +2 -0
- package/dist/composer/prompt-attachments.spec.d.ts.map +1 -0
- package/dist/composer/prompt-attachments.spec.js +63 -0
- package/dist/composer/prompt-attachments.spec.js.map +1 -0
- package/dist/composer/realtime-voice-audio-level.d.ts +14 -0
- package/dist/composer/realtime-voice-audio-level.d.ts.map +1 -0
- package/dist/composer/realtime-voice-audio-level.js +47 -0
- package/dist/composer/realtime-voice-audio-level.js.map +1 -0
- package/dist/composer/realtime-voice-audio-level.spec.d.ts +2 -0
- package/dist/composer/realtime-voice-audio-level.spec.d.ts.map +1 -0
- package/dist/composer/realtime-voice-audio-level.spec.js +25 -0
- package/dist/composer/realtime-voice-audio-level.spec.js.map +1 -0
- package/dist/composer/realtime-voice-transcript.d.ts +26 -0
- package/dist/composer/realtime-voice-transcript.d.ts.map +1 -0
- package/dist/composer/realtime-voice-transcript.js +103 -0
- package/dist/composer/realtime-voice-transcript.js.map +1 -0
- package/dist/composer/realtime-voice-transcript.spec.d.ts +2 -0
- package/dist/composer/realtime-voice-transcript.spec.d.ts.map +1 -0
- package/dist/composer/realtime-voice-transcript.spec.js +76 -0
- package/dist/composer/realtime-voice-transcript.spec.js.map +1 -0
- package/dist/composer/runtime-adapters.d.ts +163 -0
- package/dist/composer/runtime-adapters.d.ts.map +1 -0
- package/dist/composer/runtime-adapters.js +130 -0
- package/dist/composer/runtime-adapters.js.map +1 -0
- package/dist/composer/types.d.ts +60 -0
- package/dist/composer/types.d.ts.map +1 -0
- package/dist/composer/types.js +2 -0
- package/dist/composer/types.js.map +1 -0
- package/dist/composer/use-file-search.d.ts +6 -0
- package/dist/composer/use-file-search.d.ts.map +1 -0
- package/dist/composer/use-file-search.js +53 -0
- package/dist/composer/use-file-search.js.map +1 -0
- package/dist/composer/use-mention-search.d.ts +6 -0
- package/dist/composer/use-mention-search.d.ts.map +1 -0
- package/dist/composer/use-mention-search.js +75 -0
- package/dist/composer/use-mention-search.js.map +1 -0
- package/dist/composer/use-skills.d.ts +7 -0
- package/dist/composer/use-skills.d.ts.map +1 -0
- package/dist/composer/use-skills.js +40 -0
- package/dist/composer/use-skills.js.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.d.ts +179 -0
- package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.js +1657 -0
- package/dist/composer/useRealtimeVoiceMode.js.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.spec.d.ts +2 -0
- package/dist/composer/useRealtimeVoiceMode.spec.d.ts.map +1 -0
- package/dist/composer/useRealtimeVoiceMode.spec.js +618 -0
- package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -0
- package/dist/composer/useVoiceDictation.d.ts +57 -0
- package/dist/composer/useVoiceDictation.d.ts.map +1 -0
- package/dist/composer/useVoiceDictation.js +1004 -0
- package/dist/composer/useVoiceDictation.js.map +1 -0
- package/dist/composer/useVoiceDictation.spec.d.ts +2 -0
- package/dist/composer/useVoiceDictation.spec.d.ts.map +1 -0
- package/dist/composer/useVoiceDictation.spec.js +32 -0
- package/dist/composer/useVoiceDictation.spec.js.map +1 -0
- package/dist/editor/RegistryBlockContext.d.ts +68 -0
- package/dist/editor/RegistryBlockContext.d.ts.map +1 -0
- package/dist/editor/RegistryBlockContext.js +10 -0
- package/dist/editor/RegistryBlockContext.js.map +1 -0
- package/dist/editor/RegistryBlockNode.d.ts +66 -0
- package/dist/editor/RegistryBlockNode.d.ts.map +1 -0
- package/dist/editor/RegistryBlockNode.js +406 -0
- package/dist/editor/RegistryBlockNode.js.map +1 -0
- package/dist/editor/RegistryBlockNode.spec.d.ts +2 -0
- package/dist/editor/RegistryBlockNode.spec.d.ts.map +1 -0
- package/dist/editor/RegistryBlockNode.spec.js +215 -0
- package/dist/editor/RegistryBlockNode.spec.js.map +1 -0
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +2 -0
- package/dist/editor/index.js.map +1 -1
- package/dist/editor/registrySlashCommands.d.ts +68 -0
- package/dist/editor/registrySlashCommands.d.ts.map +1 -0
- package/dist/editor/registrySlashCommands.js +51 -0
- package/dist/editor/registrySlashCommands.js.map +1 -0
- package/migration-manifest.json +4 -0
- package/package.json +18 -1
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { IconPencil } from "@tabler/icons-react";
|
|
3
|
+
import { NodeSelection, Plugin, PluginKey, } from "@tiptap/pm/state";
|
|
4
|
+
import { Node, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes, } from "@tiptap/react";
|
|
5
|
+
import { useEffect, useMemo, useState, } from "react";
|
|
6
|
+
import { useRegistryBlockData, } from "./RegistryBlockContext.js";
|
|
7
|
+
export { RegistryBlockDataProvider, useRegistryBlockData, } from "./RegistryBlockContext.js";
|
|
8
|
+
/* -------------------------------------------------------------------------- */
|
|
9
|
+
/* The generic registry-block side-map + Tiptap NodeView. */
|
|
10
|
+
/* */
|
|
11
|
+
/* This is the app-agnostic NodeView that renders registered block specs */
|
|
12
|
+
/* inside a `SharedRichEditor` document. Hosts mount the node produced by */
|
|
13
|
+
/* {@link createRegistryBlockNode} as an extra extension and wrap the editor */
|
|
14
|
+
/* in a {@link RegistryBlockDataProvider}, sourcing the typed block `data` */
|
|
15
|
+
/* from their own authoritative store (for example, PlanContent.blocks). The */
|
|
16
|
+
/* node itself carries only lightweight identity attrs (type/id/title/summary) */
|
|
17
|
+
/* plus an optional `__raw` verbatim-MDX attr for byte-stable source */
|
|
18
|
+
/* round-trips; the heavy typed `data` is threaded through the side-map */
|
|
19
|
+
/* context, keeping the doc small and the block data the single source of */
|
|
20
|
+
/* truth. */
|
|
21
|
+
/* -------------------------------------------------------------------------- */
|
|
22
|
+
function clickedInteractiveChild(target) {
|
|
23
|
+
if (target.closest("button,input,textarea,select,a,[role='textbox']")) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
// The block drag-handle grip is a `role="button"` div that lives in the editor
|
|
27
|
+
// wrapper, and nested container blocks (columns/tabs) render their own inner
|
|
28
|
+
// editors — each its own grip. A container block's `onMouseDownCapture`
|
|
29
|
+
// selection handler runs in a SEPARATE React root (Tiptap mounts every node
|
|
30
|
+
// view as its own root), so its `stopPropagation()` halts the NATIVE mousedown
|
|
31
|
+
// right there, before it can reach an inner grip or inner editor. Treat the
|
|
32
|
+
// grip and anything inside a nested editor region as interactive so the outer
|
|
33
|
+
// container never hijacks a mousedown meant for inner machinery — this is what
|
|
34
|
+
// makes dragging a block OUT of / BETWEEN columns (a nested grip) work at all.
|
|
35
|
+
if (target.closest(".drag-handle") ||
|
|
36
|
+
target.closest(".plan-nested-document-editor-region")) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const blockNode = target.closest(".plan-block-node");
|
|
40
|
+
const editable = target.closest("[contenteditable='true']");
|
|
41
|
+
return !!blockNode && !!editable && blockNode.contains(editable);
|
|
42
|
+
}
|
|
43
|
+
/* -------------------------------------------------------------------------- */
|
|
44
|
+
/* B. RegistryBlockNodeView (React) */
|
|
45
|
+
/* -------------------------------------------------------------------------- */
|
|
46
|
+
/**
|
|
47
|
+
* Renders one registry-block atom. The block is non-editable as far as
|
|
48
|
+
* ProseMirror is concerned (`contentEditable={false}`); all interaction happens
|
|
49
|
+
* inside the registry-driven `<BlockView>`. Read vs edit is toggled by
|
|
50
|
+
* `props.selected` (the node is "selected" in the editor) AND the document being
|
|
51
|
+
* editable. `data-plan-interactive` keeps existing host click-guards from
|
|
52
|
+
* treating clicks inside the block as document clicks.
|
|
53
|
+
*/
|
|
54
|
+
export function RegistryBlockNodeView(props) {
|
|
55
|
+
const blockType = String(props.node.attrs.blockType ?? "");
|
|
56
|
+
const blockId = String(props.node.attrs.blockId ?? "");
|
|
57
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
58
|
+
const [shellHovered, setShellHovered] = useState(false);
|
|
59
|
+
const sideMap = useRegistryBlockData();
|
|
60
|
+
// Optimistic edit override. `onBlockDataChange` commits into the host's own
|
|
61
|
+
// store (a ref the side-map context can't observe), so an edit does NOT
|
|
62
|
+
// re-render this node — the new data only reaches the view on the next full
|
|
63
|
+
// document reconcile, which lands after the autosave round-trip (seconds
|
|
64
|
+
// later) and is skipped entirely when the host treats the save as its own
|
|
65
|
+
// echo. That left quick toggles like the callout tone buttons visually frozen
|
|
66
|
+
// until reload. Holding the just-edited data locally re-renders this one node
|
|
67
|
+
// immediately, then releases once the authoritative block catches up to (or
|
|
68
|
+
// moves past) the value the edit was based on.
|
|
69
|
+
const [pendingEdit, setPendingEdit] = useState(null);
|
|
70
|
+
const liveBlock = sideMap?.getBlock(blockId);
|
|
71
|
+
const liveData = liveBlock?.data;
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (pendingEdit && !Object.is(liveData, pendingEdit.base)) {
|
|
74
|
+
setPendingEdit(null);
|
|
75
|
+
}
|
|
76
|
+
}, [liveData, pendingEdit]);
|
|
77
|
+
const block = liveBlock && pendingEdit && Object.is(liveData, pendingEdit.base)
|
|
78
|
+
? { ...liveBlock, data: pendingEdit.data }
|
|
79
|
+
: liveBlock;
|
|
80
|
+
const commitBlockData = (nextData, meta) => {
|
|
81
|
+
setPendingEdit({ data: nextData, base: liveData });
|
|
82
|
+
sideMap?.onBlockDataChange(blockId, nextData, meta);
|
|
83
|
+
};
|
|
84
|
+
const editable = sideMap?.editable ?? false;
|
|
85
|
+
// In Notion-sync mode, flag blocks that have no Notion (NFM) analog so the
|
|
86
|
+
// author sees what won't push. Prose blocks aren't registry-block nodes, so
|
|
87
|
+
// this only ever covers structured blocks.
|
|
88
|
+
const incompatibleWithNotion = (sideMap?.notionSync ?? false) &&
|
|
89
|
+
(sideMap?.isNotionIncompatibleType?.(blockType) ?? false);
|
|
90
|
+
// The block data isn't in the side-map yet (e.g. a freshly inserted node whose
|
|
91
|
+
// store entry hasn't been seeded). Render a graceful placeholder.
|
|
92
|
+
if (!block) {
|
|
93
|
+
return (_jsx(NodeViewWrapper, { className: "plan-block-node", "data-block-id": blockId, children: _jsx("div", { contentEditable: false, "data-plan-interactive": true, className: "plan-block-node__placeholder rounded-md border border-dashed border-border px-3 py-2 text-sm text-muted-foreground", children: blockType ? `Loading ${blockType} block…` : "Loading block…" }) }));
|
|
94
|
+
}
|
|
95
|
+
const selectNode = (event) => {
|
|
96
|
+
if (!editable)
|
|
97
|
+
return;
|
|
98
|
+
const target = event.target;
|
|
99
|
+
if (target instanceof HTMLElement && clickedInteractiveChild(target))
|
|
100
|
+
return;
|
|
101
|
+
const pos = typeof props.getPos === "function" ? props.getPos() : null;
|
|
102
|
+
if (typeof pos !== "number")
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
event.stopPropagation();
|
|
107
|
+
const { view } = props.editor;
|
|
108
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos)));
|
|
109
|
+
view.focus();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Ignore stale positions during React/ProseMirror reconciliation.
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const updateShellHover = (event) => {
|
|
116
|
+
const target = event.target;
|
|
117
|
+
setShellHovered(target instanceof HTMLElement &&
|
|
118
|
+
target.closest(".plan-block-node__shell") === event.currentTarget);
|
|
119
|
+
};
|
|
120
|
+
// Choose how to render the block body:
|
|
121
|
+
// 1. A host registry adapter renders a registered block.
|
|
122
|
+
// 2. No spec, but the side-map provides `renderLegacyBlock` → delegate to the
|
|
123
|
+
// host's dispatcher (decision, legacy visual-questions, image, and any
|
|
124
|
+
// other type rendered by a bespoke component rather than the registry), so
|
|
125
|
+
// EVERY block type renders in the document exactly as it does in the
|
|
126
|
+
// per-block reader — never a bare title fallback.
|
|
127
|
+
// 3. Neither → a small non-crashing fallback.
|
|
128
|
+
let body;
|
|
129
|
+
let editSurface = null;
|
|
130
|
+
const registered = sideMap?.renderRegisteredBlock?.(block, {
|
|
131
|
+
blockType,
|
|
132
|
+
editable,
|
|
133
|
+
selected: props.selected,
|
|
134
|
+
shellHovered,
|
|
135
|
+
panelOpen,
|
|
136
|
+
setPanelOpen,
|
|
137
|
+
onChange: commitBlockData,
|
|
138
|
+
});
|
|
139
|
+
if (registered) {
|
|
140
|
+
body = registered.body;
|
|
141
|
+
editSurface = registered.editSurface ?? null;
|
|
142
|
+
}
|
|
143
|
+
else if (sideMap?.renderLegacyBlock) {
|
|
144
|
+
// Self-editing legacy blocks (e.g. image) render their own edit affordance
|
|
145
|
+
// inside their overlay, so render them in edit mode and add NO separate
|
|
146
|
+
// corner edit surface — the block owns a single, self-contained overlay.
|
|
147
|
+
const selfEdits = editable && Boolean(sideMap.legacyBlockSelfEdits?.(blockType));
|
|
148
|
+
body = sideMap.renderLegacyBlock(block, { editing: selfEdits });
|
|
149
|
+
if (editable && !selfEdits) {
|
|
150
|
+
// Prefer a host-provided schema/custom editor (a real form) over the raw
|
|
151
|
+
// JSON fallback when the host knows how to edit this legacy block type.
|
|
152
|
+
const customEditor = sideMap.renderLegacyBlockEditor?.(block, {
|
|
153
|
+
onChange: (nextData) => commitBlockData(nextData),
|
|
154
|
+
});
|
|
155
|
+
editSurface = (_jsx(LegacyJsonEditSurface, { block: block, blockType: blockType, open: panelOpen, onOpenChange: setPanelOpen, renderEditSurface: sideMap.renderEditSurface, onChange: (nextBlock) => commitBlockData(nextBlock), selected: shellHovered, customEditor: customEditor }));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
body = (_jsx("div", { className: "plan-block-node__fallback rounded-md border border-border px-3 py-2 text-sm text-muted-foreground", children: block.title || blockType || "Unsupported block" }));
|
|
160
|
+
}
|
|
161
|
+
return (_jsx(NodeViewWrapper, { className: "plan-block-node", "data-block-id": blockId, "data-block-type": blockType || undefined, "data-plan-block-selected": props.selected ? "" : undefined, "data-notion-incompatible": incompatibleWithNotion ? "" : undefined, onMouseDownCapture: selectNode, children: _jsxs("div", { contentEditable: false, "data-plan-interactive": true, className: "plan-block-node__shell relative", onMouseEnter: updateShellHover, onMouseMove: updateShellHover, onMouseLeave: () => setShellHovered(false), children: [incompatibleWithNotion && (_jsx("span", { className: "plan-block-notion-badge", title: "This block type has no Notion equivalent and won't sync to Notion.", children: "Won't sync to Notion" })), body, editSurface && (_jsx("div", { className: "plan-block-node__edit absolute right-2 top-2 z-20", children: editSurface }))] }) }));
|
|
162
|
+
}
|
|
163
|
+
export function LegacyJsonEditSurface({ block, blockType, open, onOpenChange, renderEditSurface, onChange, selected, customEditor, }) {
|
|
164
|
+
const serializedBlockData = useMemo(() => JSON.stringify(block.data, null, 2), [block.data]);
|
|
165
|
+
const [draft, setDraft] = useState(serializedBlockData);
|
|
166
|
+
const [parseError, setParseError] = useState(null);
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
setDraft(serializedBlockData);
|
|
169
|
+
setParseError(null);
|
|
170
|
+
}, [block.id, serializedBlockData]);
|
|
171
|
+
const saveDraft = () => {
|
|
172
|
+
try {
|
|
173
|
+
const nextData = JSON.parse(draft);
|
|
174
|
+
setParseError(null);
|
|
175
|
+
onChange(nextData);
|
|
176
|
+
onOpenChange(false);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
setParseError(error instanceof Error ? error.message : "Invalid JSON data.");
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const trigger = (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": `Edit ${block.title ?? "block"}`, onClick: () => onOpenChange(true), className: "an-block-edit-trigger flex size-7 items-center justify-center rounded-md border border-border bg-background text-muted-foreground opacity-0 shadow-sm transition-opacity hover:bg-muted hover:text-foreground focus-visible:opacity-100 data-[visible=true]:opacity-100", "data-visible": selected || open, children: _jsx(IconPencil, { className: "size-4" }) }));
|
|
183
|
+
const jsonEditor = (_jsxs("div", { className: "grid gap-3", children: [_jsx("textarea", { "data-plan-interactive": true, className: "min-h-64 w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs leading-5 text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", value: draft, "aria-invalid": parseError ? true : undefined, onChange: (event) => {
|
|
184
|
+
setDraft(event.target.value);
|
|
185
|
+
if (parseError)
|
|
186
|
+
setParseError(null);
|
|
187
|
+
} }), parseError ? (_jsxs("p", { className: "text-xs text-destructive", role: "alert", children: ["Invalid JSON: ", parseError] })) : null, _jsx("div", { className: "flex justify-end", children: _jsx("button", { type: "button", "data-plan-interactive": true, className: "inline-flex h-8 items-center justify-center rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground", onClick: saveDraft, children: "Save" }) })] }));
|
|
188
|
+
const editor = customEditor ?? jsonEditor;
|
|
189
|
+
if (!renderEditSurface)
|
|
190
|
+
return open ? editor : trigger;
|
|
191
|
+
return renderEditSurface({
|
|
192
|
+
title: block.title ?? "Block",
|
|
193
|
+
open,
|
|
194
|
+
onOpenChange,
|
|
195
|
+
blockId: block.id,
|
|
196
|
+
blockType: blockType ||
|
|
197
|
+
(typeof block.type === "string"
|
|
198
|
+
? (block.type ?? "")
|
|
199
|
+
: "legacy"),
|
|
200
|
+
blockTitle: block.title,
|
|
201
|
+
blockSummary: block.summary,
|
|
202
|
+
blockData: block.data,
|
|
203
|
+
trigger,
|
|
204
|
+
children: editor,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Build the generic registry-block Tiptap atom node. Returns a Tiptap `Node`
|
|
209
|
+
* that:
|
|
210
|
+
* - carries identity attrs `blockType` / `blockId` / `title` / `summary`, a
|
|
211
|
+
* `sourceBlockId` (set when a duplicate is re-minted, so the host can copy the
|
|
212
|
+
* original block's data), and an optional `__raw` verbatim-MDX attr for
|
|
213
|
+
* byte-stable source round-trips;
|
|
214
|
+
* - is an atom + isolating + draggable block that renders through
|
|
215
|
+
* {@link RegistryBlockNodeView} (via `ReactNodeViewRenderer`);
|
|
216
|
+
* - installs a dedupe `appendTransaction` plugin that re-mints any duplicate or
|
|
217
|
+
* empty `blockId` (the classic paste/duplicate case), preserving the original
|
|
218
|
+
* block's id + side-map data and tagging its own transaction so it never
|
|
219
|
+
* loops.
|
|
220
|
+
*/
|
|
221
|
+
export function createRegistryBlockNode(options) {
|
|
222
|
+
const { nodeName, dataTag, mintId, group = "block" } = options;
|
|
223
|
+
const dedupeKey = new PluginKey(`${nodeName}DedupeIds`);
|
|
224
|
+
const keyboardGuardKey = new PluginKey(`${nodeName}KeyboardGuard`);
|
|
225
|
+
/**
|
|
226
|
+
* Collect every `blockId` currently present on this node type in a doc, with
|
|
227
|
+
* the position of each node, so duplicate ids (from paste/duplicate) can be
|
|
228
|
+
* detected and re-minted.
|
|
229
|
+
*/
|
|
230
|
+
function collectEntries(state) {
|
|
231
|
+
const found = [];
|
|
232
|
+
state.doc.descendants((node, pos) => {
|
|
233
|
+
if (node.type.name === nodeName) {
|
|
234
|
+
found.push({
|
|
235
|
+
pos,
|
|
236
|
+
blockType: String(node.attrs.blockType ?? ""),
|
|
237
|
+
blockId: String(node.attrs.blockId ?? ""),
|
|
238
|
+
sourceBlockId: typeof node.attrs.sourceBlockId === "string"
|
|
239
|
+
? node.attrs.sourceBlockId
|
|
240
|
+
: undefined,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
});
|
|
245
|
+
return found;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Build a transaction that re-mints any duplicate / missing ids in `state`, or
|
|
249
|
+
* `null` when nothing needs changing. Only the *later* duplicate (and any node
|
|
250
|
+
* with an empty id) is re-minted, so the original keeps its id and side-map
|
|
251
|
+
* data.
|
|
252
|
+
*/
|
|
253
|
+
function buildDedupeTransaction(state) {
|
|
254
|
+
const entries = collectEntries(state);
|
|
255
|
+
if (entries.length === 0)
|
|
256
|
+
return null;
|
|
257
|
+
const seen = new Set();
|
|
258
|
+
let tr = state.tr;
|
|
259
|
+
let changed = false;
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
const needsNewId = !entry.blockId || seen.has(entry.blockId);
|
|
262
|
+
if (needsNewId) {
|
|
263
|
+
const freshId = mintId(entry.blockType || "block");
|
|
264
|
+
const node = state.doc.nodeAt(entry.pos);
|
|
265
|
+
if (node) {
|
|
266
|
+
tr = tr.setNodeMarkup(entry.pos, undefined, {
|
|
267
|
+
...node.attrs,
|
|
268
|
+
blockId: freshId,
|
|
269
|
+
sourceBlockId: entry.sourceBlockId || entry.blockId || null,
|
|
270
|
+
});
|
|
271
|
+
changed = true;
|
|
272
|
+
}
|
|
273
|
+
seen.add(freshId);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
seen.add(entry.blockId);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return changed ? tr.setMeta(dedupeKey, true) : null;
|
|
280
|
+
}
|
|
281
|
+
const selectedRegistryBlock = (state) => state.selection instanceof NodeSelection &&
|
|
282
|
+
state.selection.node.type.name === nodeName;
|
|
283
|
+
const isMutatingKey = (event) => {
|
|
284
|
+
if (event.altKey || event.ctrlKey || event.metaKey)
|
|
285
|
+
return false;
|
|
286
|
+
if (event.key === "Enter")
|
|
287
|
+
return true;
|
|
288
|
+
return event.key.length === 1;
|
|
289
|
+
};
|
|
290
|
+
return Node.create({
|
|
291
|
+
name: nodeName,
|
|
292
|
+
group,
|
|
293
|
+
atom: true,
|
|
294
|
+
draggable: true,
|
|
295
|
+
selectable: true,
|
|
296
|
+
isolating: true,
|
|
297
|
+
addAttributes() {
|
|
298
|
+
return {
|
|
299
|
+
blockType: { default: "" },
|
|
300
|
+
blockId: { default: "" },
|
|
301
|
+
title: { default: null },
|
|
302
|
+
summary: { default: null },
|
|
303
|
+
sourceBlockId: { default: null },
|
|
304
|
+
// Optional verbatim source for hosts that need byte-identical
|
|
305
|
+
// source-format round-trips without React (server pull, hashing). Plan
|
|
306
|
+
// never sets this.
|
|
307
|
+
__raw: { default: null, rendered: false },
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
parseHTML() {
|
|
311
|
+
return [
|
|
312
|
+
{
|
|
313
|
+
tag: `div[${dataTag}]`,
|
|
314
|
+
getAttrs: (element) => {
|
|
315
|
+
const node = element;
|
|
316
|
+
return {
|
|
317
|
+
blockType: node.getAttribute("data-block-type") || "",
|
|
318
|
+
blockId: node.getAttribute("data-block-id") || "",
|
|
319
|
+
title: node.getAttribute("data-title") || null,
|
|
320
|
+
summary: node.getAttribute("data-summary") || null,
|
|
321
|
+
sourceBlockId: node.getAttribute("data-source-block-id") || null,
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
},
|
|
327
|
+
renderHTML({ HTMLAttributes }) {
|
|
328
|
+
return [
|
|
329
|
+
"div",
|
|
330
|
+
mergeAttributes(HTMLAttributes, {
|
|
331
|
+
[dataTag]: "",
|
|
332
|
+
"data-block-type": HTMLAttributes.blockType ?? "",
|
|
333
|
+
"data-block-id": HTMLAttributes.blockId ?? "",
|
|
334
|
+
"data-title": HTMLAttributes.title ?? undefined,
|
|
335
|
+
"data-summary": HTMLAttributes.summary ?? undefined,
|
|
336
|
+
"data-source-block-id": HTMLAttributes.sourceBlockId ?? undefined,
|
|
337
|
+
}),
|
|
338
|
+
];
|
|
339
|
+
},
|
|
340
|
+
addNodeView() {
|
|
341
|
+
return ReactNodeViewRenderer(RegistryBlockNodeView);
|
|
342
|
+
},
|
|
343
|
+
addProseMirrorPlugins() {
|
|
344
|
+
return [
|
|
345
|
+
new Plugin({
|
|
346
|
+
key: dedupeKey,
|
|
347
|
+
appendTransaction(transactions, _oldState, newState) {
|
|
348
|
+
// Ignore our own re-mint, and skip when nothing changed the doc.
|
|
349
|
+
if (transactions.some((transaction) => transaction.getMeta(dedupeKey)) ||
|
|
350
|
+
!transactions.some((transaction) => transaction.docChanged)) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return buildDedupeTransaction(newState);
|
|
354
|
+
},
|
|
355
|
+
}),
|
|
356
|
+
new Plugin({
|
|
357
|
+
key: keyboardGuardKey,
|
|
358
|
+
props: {
|
|
359
|
+
handleClickOn(view, _pos, node, nodePos, event, direct) {
|
|
360
|
+
if (node.type.name !== nodeName || !direct)
|
|
361
|
+
return false;
|
|
362
|
+
if (event.target instanceof HTMLElement &&
|
|
363
|
+
clickedInteractiveChild(event.target)) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
event.preventDefault();
|
|
367
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
|
|
368
|
+
view.focus();
|
|
369
|
+
return true;
|
|
370
|
+
},
|
|
371
|
+
handleKeyDown(view, event) {
|
|
372
|
+
if (!selectedRegistryBlock(view.state) || !isMutatingKey(event))
|
|
373
|
+
return false;
|
|
374
|
+
event.preventDefault();
|
|
375
|
+
return true;
|
|
376
|
+
},
|
|
377
|
+
handleTextInput(view) {
|
|
378
|
+
return selectedRegistryBlock(view.state);
|
|
379
|
+
},
|
|
380
|
+
handlePaste(view, event) {
|
|
381
|
+
if (!selectedRegistryBlock(view.state))
|
|
382
|
+
return false;
|
|
383
|
+
event.preventDefault();
|
|
384
|
+
return true;
|
|
385
|
+
},
|
|
386
|
+
handleDOMEvents: {
|
|
387
|
+
beforeinput(view, event) {
|
|
388
|
+
if (!selectedRegistryBlock(view.state))
|
|
389
|
+
return false;
|
|
390
|
+
const inputEvent = event;
|
|
391
|
+
if (!inputEvent.inputType ||
|
|
392
|
+
(!inputEvent.inputType.startsWith("insert") &&
|
|
393
|
+
inputEvent.inputType !== "formatSetBlockTextDirection")) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
event.preventDefault();
|
|
397
|
+
return true;
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
}),
|
|
402
|
+
];
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=RegistryBlockNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryBlockNode.js","sourceRoot":"","sources":["../../src/editor/RegistryBlockNode.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RegistryBlockNode.spec.d.ts","sourceRoot":"","sources":["../../src/editor/RegistryBlockNode.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { Editor } from "@tiptap/core";
|
|
3
|
+
import { Slice } from "@tiptap/pm/model";
|
|
4
|
+
import { NodeSelection } from "@tiptap/pm/state";
|
|
5
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
6
|
+
import React, { act } from "react";
|
|
7
|
+
import { createRoot } from "react-dom/client";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { createRegistryBlockNode, LegacyJsonEditSurface, } from "./RegistryBlockNode.js";
|
|
10
|
+
const PlanBlockNode = createRegistryBlockNode({
|
|
11
|
+
nodeName: "planBlock",
|
|
12
|
+
dataTag: "data-plan-block",
|
|
13
|
+
mintId: (blockType) => `${blockType}-fresh`,
|
|
14
|
+
});
|
|
15
|
+
function createEditor() {
|
|
16
|
+
const element = document.createElement("div");
|
|
17
|
+
document.body.appendChild(element);
|
|
18
|
+
return new Editor({
|
|
19
|
+
element,
|
|
20
|
+
extensions: [StarterKit, PlanBlockNode],
|
|
21
|
+
content: {
|
|
22
|
+
type: "doc",
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "paragraph",
|
|
26
|
+
content: [{ type: "text", text: "Before" }],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "planBlock",
|
|
30
|
+
attrs: {
|
|
31
|
+
blockType: "diagram",
|
|
32
|
+
blockId: "diagram-1",
|
|
33
|
+
title: "Architecture diagram",
|
|
34
|
+
summary: null,
|
|
35
|
+
sourceBlockId: null,
|
|
36
|
+
__raw: null,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: "paragraph",
|
|
41
|
+
content: [{ type: "text", text: "After" }],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function findPlanBlockPos(editor) {
|
|
48
|
+
let found = -1;
|
|
49
|
+
editor.state.doc.descendants((node, pos) => {
|
|
50
|
+
if (node.type.name === "planBlock") {
|
|
51
|
+
found = pos;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
if (found < 0)
|
|
57
|
+
throw new Error("Expected planBlock node");
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
function selectPlanBlock(editor) {
|
|
61
|
+
const pos = findPlanBlockPos(editor);
|
|
62
|
+
editor.view.dispatch(editor.state.tr.setSelection(NodeSelection.create(editor.state.doc, pos)));
|
|
63
|
+
}
|
|
64
|
+
function planBlockSnapshot(editor) {
|
|
65
|
+
return editor.state.doc.nodeAt(findPlanBlockPos(editor))?.toJSON();
|
|
66
|
+
}
|
|
67
|
+
function runKeyDown(editor, key) {
|
|
68
|
+
const event = new KeyboardEvent("keydown", {
|
|
69
|
+
key,
|
|
70
|
+
bubbles: true,
|
|
71
|
+
cancelable: true,
|
|
72
|
+
});
|
|
73
|
+
const handled = editor.view.someProp("handleKeyDown", (handler) => handler(editor.view, event));
|
|
74
|
+
expect(handled).toBe(true);
|
|
75
|
+
return event;
|
|
76
|
+
}
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
document.body.innerHTML = "";
|
|
79
|
+
});
|
|
80
|
+
describe("RegistryBlockNode keyboard guard", () => {
|
|
81
|
+
it("keeps selected registry atoms immutable for text entry and preserves undo history", () => {
|
|
82
|
+
const editor = createEditor();
|
|
83
|
+
try {
|
|
84
|
+
editor.commands.setTextSelection(7);
|
|
85
|
+
editor.commands.insertContent("!");
|
|
86
|
+
expect(editor.state.doc.child(0).textContent).toBe("Before!");
|
|
87
|
+
selectPlanBlock(editor);
|
|
88
|
+
const beforeAtom = planBlockSnapshot(editor);
|
|
89
|
+
const keyEvent = runKeyDown(editor, "x");
|
|
90
|
+
expect(keyEvent.defaultPrevented).toBe(true);
|
|
91
|
+
expect(planBlockSnapshot(editor)).toEqual(beforeAtom);
|
|
92
|
+
const enterEvent = runKeyDown(editor, "Enter");
|
|
93
|
+
expect(enterEvent.defaultPrevented).toBe(true);
|
|
94
|
+
expect(planBlockSnapshot(editor)).toEqual(beforeAtom);
|
|
95
|
+
const textInputHandled = editor.view.someProp("handleTextInput", (handler) => handler(editor.view, 0, 0, "typed", () => editor.state.tr));
|
|
96
|
+
expect(textInputHandled).toBe(true);
|
|
97
|
+
expect(planBlockSnapshot(editor)).toEqual(beforeAtom);
|
|
98
|
+
const pasteEvent = new Event("paste", {
|
|
99
|
+
bubbles: true,
|
|
100
|
+
cancelable: true,
|
|
101
|
+
});
|
|
102
|
+
const pasteHandled = editor.view.someProp("handlePaste", (handler) => handler(editor.view, pasteEvent, Slice.empty));
|
|
103
|
+
expect(pasteHandled).toBe(true);
|
|
104
|
+
expect(pasteEvent.defaultPrevented).toBe(true);
|
|
105
|
+
expect(planBlockSnapshot(editor)).toEqual(beforeAtom);
|
|
106
|
+
const beforeInputEvent = new InputEvent("beforeinput", {
|
|
107
|
+
data: "z",
|
|
108
|
+
inputType: "insertText",
|
|
109
|
+
bubbles: true,
|
|
110
|
+
cancelable: true,
|
|
111
|
+
});
|
|
112
|
+
const beforeInputHandled = editor.view.someProp("handleDOMEvents", (handlers) => handlers.beforeinput?.(editor.view, beforeInputEvent));
|
|
113
|
+
expect(beforeInputHandled).toBe(true);
|
|
114
|
+
expect(beforeInputEvent.defaultPrevented).toBe(true);
|
|
115
|
+
expect(planBlockSnapshot(editor)).toEqual(beforeAtom);
|
|
116
|
+
expect(editor.commands.undo()).toBe(true);
|
|
117
|
+
expect(editor.state.doc.child(0).textContent).toBe("Before");
|
|
118
|
+
expect(editor.state.doc.textContent).not.toContain("Module");
|
|
119
|
+
expect(planBlockSnapshot(editor)).toEqual(beforeAtom);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
editor.destroy();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe("LegacyJsonEditSurface", () => {
|
|
127
|
+
let container;
|
|
128
|
+
let root;
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
131
|
+
container = document.createElement("div");
|
|
132
|
+
document.body.appendChild(container);
|
|
133
|
+
root = createRoot(container);
|
|
134
|
+
});
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
act(() => {
|
|
137
|
+
root.unmount();
|
|
138
|
+
});
|
|
139
|
+
container.remove();
|
|
140
|
+
vi.unstubAllGlobals();
|
|
141
|
+
});
|
|
142
|
+
function renderLegacyEditor(block, onChange = vi.fn()) {
|
|
143
|
+
const onOpenChange = vi.fn();
|
|
144
|
+
act(() => {
|
|
145
|
+
root.render(React.createElement(LegacyJsonEditSurface, {
|
|
146
|
+
block,
|
|
147
|
+
open: true,
|
|
148
|
+
onOpenChange,
|
|
149
|
+
onChange,
|
|
150
|
+
selected: false,
|
|
151
|
+
}));
|
|
152
|
+
});
|
|
153
|
+
return { onChange, onOpenChange };
|
|
154
|
+
}
|
|
155
|
+
function textarea() {
|
|
156
|
+
const element = container.querySelector("textarea");
|
|
157
|
+
if (!element)
|
|
158
|
+
throw new Error("Expected textarea");
|
|
159
|
+
return element;
|
|
160
|
+
}
|
|
161
|
+
function saveButton() {
|
|
162
|
+
const element = Array.from(container.querySelectorAll("button")).find((button) => button.textContent === "Save");
|
|
163
|
+
if (!element)
|
|
164
|
+
throw new Error("Expected Save button");
|
|
165
|
+
return element;
|
|
166
|
+
}
|
|
167
|
+
function setTextareaValue(value) {
|
|
168
|
+
const element = textarea();
|
|
169
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
170
|
+
setter?.call(element, value);
|
|
171
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
172
|
+
}
|
|
173
|
+
it("shows an inline error instead of throwing on malformed JSON", () => {
|
|
174
|
+
const onChange = vi.fn();
|
|
175
|
+
const { onOpenChange } = renderLegacyEditor({
|
|
176
|
+
id: "legacy-1",
|
|
177
|
+
data: { ok: true },
|
|
178
|
+
}, onChange);
|
|
179
|
+
act(() => {
|
|
180
|
+
setTextareaValue("{ nope");
|
|
181
|
+
});
|
|
182
|
+
expect(() => {
|
|
183
|
+
act(() => {
|
|
184
|
+
saveButton().dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
185
|
+
});
|
|
186
|
+
}).not.toThrow();
|
|
187
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
188
|
+
expect(onOpenChange).not.toHaveBeenCalledWith(false);
|
|
189
|
+
expect(container.textContent).toContain("Invalid JSON");
|
|
190
|
+
});
|
|
191
|
+
it("resyncs the draft when block data changes before saving", () => {
|
|
192
|
+
const onChange = vi.fn();
|
|
193
|
+
const block = {
|
|
194
|
+
id: "legacy-1",
|
|
195
|
+
data: { value: "initial" },
|
|
196
|
+
};
|
|
197
|
+
renderLegacyEditor(block, onChange);
|
|
198
|
+
expect(textarea().value).toContain("initial");
|
|
199
|
+
act(() => {
|
|
200
|
+
root.render(React.createElement(LegacyJsonEditSurface, {
|
|
201
|
+
block: { ...block, data: { value: "updated" } },
|
|
202
|
+
open: true,
|
|
203
|
+
onOpenChange: vi.fn(),
|
|
204
|
+
onChange,
|
|
205
|
+
selected: false,
|
|
206
|
+
}));
|
|
207
|
+
});
|
|
208
|
+
expect(textarea().value).toContain("updated");
|
|
209
|
+
act(() => {
|
|
210
|
+
saveButton().dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
|
|
211
|
+
});
|
|
212
|
+
expect(onChange).toHaveBeenCalledWith({ value: "updated" });
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
//# sourceMappingURL=RegistryBlockNode.spec.js.map
|