@nuforge/editor 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +5 -114
- package/dist/index.d.ts +0 -151
- package/dist/index.mjs +5 -114
- package/dist/react.cjs +14 -137
- package/dist/react.d.ts +0 -164
- package/dist/react.mjs +14 -137
- package/package.json +9 -9
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/react.cjs.map +0 -1
- package/dist/react.mjs.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import * as t from '@nuforge/types';
|
|
|
2
2
|
import { Nu, NuExternalsFactory } from '@nuforge/core';
|
|
3
3
|
import { MutationEntry } from '@nuforge/reactivity';
|
|
4
4
|
|
|
5
|
-
/** A control hint for auto-generating inspector fields from a block's props. */
|
|
6
5
|
interface PropSchemaField {
|
|
7
6
|
name: string;
|
|
8
7
|
type: 'string' | 'number' | 'boolean' | 'expression' | 'enum';
|
|
@@ -10,11 +9,6 @@ interface PropSchemaField {
|
|
|
10
9
|
default?: string | number | boolean;
|
|
11
10
|
options?: string[];
|
|
12
11
|
}
|
|
13
|
-
/**
|
|
14
|
-
* A reusable element/block in the palette. `create()` returns a fresh template
|
|
15
|
-
* subtree to insert. `icon` is a name (not a component) so the registry stays
|
|
16
|
-
* framework-agnostic; the UI maps the name to its own icon set.
|
|
17
|
-
*/
|
|
18
12
|
interface Block {
|
|
19
13
|
id: string;
|
|
20
14
|
label: string;
|
|
@@ -32,65 +26,29 @@ declare class BlockRegistry {
|
|
|
32
26
|
all(): Block[];
|
|
33
27
|
byCategory(): Record<string, Block[]>;
|
|
34
28
|
}
|
|
35
|
-
/** The built-in palette — the common HTML primitives. */
|
|
36
29
|
declare const DEFAULT_BLOCKS: Block[];
|
|
37
30
|
|
|
38
|
-
/** A literal expression (`"x"`, `5`, `true`). */
|
|
39
31
|
declare function lit(value: string | number | boolean): t.Literal;
|
|
40
|
-
/** A text node: `<text text={value} />`, rendered as its string value. */
|
|
41
32
|
declare function text(value: string): t.TagTemplate;
|
|
42
|
-
/**
|
|
43
|
-
* Concise element builder — `el('div', { class: lit('box') }, [text('hi')])`
|
|
44
|
-
* instead of the verbose `t.tagTemplate({ tag, props, children })`.
|
|
45
|
-
*/
|
|
46
33
|
declare function el(tag: string, props?: Record<string, t.Expression>, children?: t.Template[]): t.TagTemplate;
|
|
47
34
|
|
|
48
|
-
/** Where a dragged node is dropped relative to a target. */
|
|
49
35
|
type DropPosition = 'before' | 'after' | 'inside';
|
|
50
|
-
/**
|
|
51
|
-
* Compute a drop position from the pointer's Y against a target's bounding rect.
|
|
52
|
-
* When `allowInside` is set, the middle band is `inside` (drop as a child) and
|
|
53
|
-
* the outer bands are `before`/`after`; otherwise it's a simple before/after
|
|
54
|
-
* split at the midpoint. Pure — use it from an `onDragOver`/`onPointerMove`
|
|
55
|
-
* handler to drive a drop indicator and the eventual `moveNode`/`pasteNode`.
|
|
56
|
-
*/
|
|
57
36
|
declare function dropPositionFromPointer(clientY: number, rect: {
|
|
58
37
|
top: number;
|
|
59
38
|
height: number;
|
|
60
39
|
}, opts?: {
|
|
61
40
|
allowInside?: boolean;
|
|
62
41
|
}): DropPosition;
|
|
63
|
-
/** A human label for a template node (tag name, component name, slot, …). */
|
|
64
42
|
declare function templateLabel(node: t.Template): string;
|
|
65
|
-
/** Child templates of a node (empty for leaves / text nodes). */
|
|
66
43
|
declare function templateChildren(node: t.Template): t.Template[];
|
|
67
|
-
/** Is this a text node (`<text text=…>`), rendered as its string value? */
|
|
68
44
|
declare function isTextTemplate(node: t.Template): boolean;
|
|
69
|
-
/** Preview the content of a text node, e.g. for a layers tree. */
|
|
70
45
|
declare function textPreview(node: t.Template): string | null;
|
|
71
|
-
/** Can this node hold appended children? */
|
|
72
46
|
declare function canHaveChildren(node: t.Template | null): boolean;
|
|
73
|
-
/** Is `ancestor` somewhere above `node` in the tree? */
|
|
74
47
|
declare function isAncestorOf(nu: Nu, ancestor: t.Template, node: t.Template): boolean;
|
|
75
48
|
|
|
76
|
-
/**
|
|
77
|
-
* Wire protocol for `RemoteCanvasHost` <-> `createCanvasReceiver`: a genuinely
|
|
78
|
-
* navigated `/canvas/[id]`-style iframe has its own JS realm, so state and
|
|
79
|
-
* interaction can't cross as direct object references the way `IframeCanvas`'s
|
|
80
|
-
* portal allows — everything here is a structured-clone-safe plain message.
|
|
81
|
-
*
|
|
82
|
-
* Deliberately generic: only the shapes the editor primitive itself needs
|
|
83
|
-
* (state sync, ready handshake, the core interaction events) are typed. Host
|
|
84
|
-
* concerns like theme/preview-mode/overrides ride through `CanvasUiMessage`'s
|
|
85
|
-
* opaque `payload` — this package relays it, never interprets it.
|
|
86
|
-
*/
|
|
87
49
|
declare const CANVAS_CHANNEL = "nuforge-canvas";
|
|
88
50
|
declare const CANVAS_PROTOCOL_VERSION = 1;
|
|
89
|
-
/** Namespace the `BroadcastChannel` per canvas instance (derive from the
|
|
90
|
-
* iframe's own `src`, so a host never has to invent/track a separate id). */
|
|
91
51
|
declare function canvasChannelName(src: string): string;
|
|
92
|
-
/** The `{ root, types }` shape from `@nuforge/types`' `flatten` — plain JSON,
|
|
93
|
-
* safe across `postMessage`/`BroadcastChannel`'s structured clone. */
|
|
94
52
|
interface FlattenedState {
|
|
95
53
|
root: {
|
|
96
54
|
$$typeId: string;
|
|
@@ -104,9 +62,6 @@ interface CanvasInitMessage {
|
|
|
104
62
|
channel: typeof CANVAS_CHANNEL;
|
|
105
63
|
v: typeof CANVAS_PROTOCOL_VERSION;
|
|
106
64
|
type: 'init';
|
|
107
|
-
/** Which component the canvas should render (matches `Nu.createFrame`'s
|
|
108
|
-
* `component.name`) — lets the SAME canvas realm switch pages without a
|
|
109
|
-
* full iframe reload; only the rendered Frame changes. */
|
|
110
65
|
activeComponent: string;
|
|
111
66
|
flat: FlattenedState;
|
|
112
67
|
}
|
|
@@ -118,8 +73,6 @@ interface CanvasPatchMessage {
|
|
|
118
73
|
flat: FlattenedState;
|
|
119
74
|
rev: number;
|
|
120
75
|
}
|
|
121
|
-
/** Host-defined payload (theme CSS, preview-mode toggle, hide/lock overrides,
|
|
122
|
-
* ...) — nucode doesn't interpret this, just relays it verbatim. */
|
|
123
76
|
interface CanvasUiMessage {
|
|
124
77
|
channel: typeof CANVAS_CHANNEL;
|
|
125
78
|
v: typeof CANVAS_PROTOCOL_VERSION;
|
|
@@ -131,9 +84,6 @@ interface CanvasReadyMessage {
|
|
|
131
84
|
channel: typeof CANVAS_CHANNEL;
|
|
132
85
|
v: typeof CANVAS_PROTOCOL_VERSION;
|
|
133
86
|
type: 'ready';
|
|
134
|
-
/** Distinguishes a fresh mount from a stale one still catching up (e.g. a
|
|
135
|
-
* rapid remount) — the host ignores a `ready` whose `mountGen` is not newer
|
|
136
|
-
* than the last one it accepted. */
|
|
137
87
|
mountGen: number;
|
|
138
88
|
}
|
|
139
89
|
interface CanvasSelectMessage {
|
|
@@ -174,151 +124,58 @@ interface CanvasLinkClickMessage {
|
|
|
174
124
|
}
|
|
175
125
|
type CanvasClientMessage = CanvasReadyMessage | CanvasSelectMessage | CanvasHoverMessage | CanvasDropMessage | CanvasKeydownMessage | CanvasLinkClickMessage;
|
|
176
126
|
type CanvasMessage = CanvasHostMessage | CanvasClientMessage;
|
|
177
|
-
/** Plain `Omit` collapses a union to only its shared keys (`keyof` over a
|
|
178
|
-
* union intersects, not unions) — this distributes per member instead, so
|
|
179
|
-
* e.g. `DistributiveOmit<CanvasClientMessage, 'channel' | 'v'>` still keeps
|
|
180
|
-
* each variant's own fields (`nodeId`, `targetId`, `key`, ...). */
|
|
181
127
|
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
182
|
-
/** Narrow an arbitrary `BroadcastChannel` payload to a message from this
|
|
183
|
-
* protocol — guards against unrelated traffic on the same channel name and
|
|
184
|
-
* against a version mismatch across a stale cached bundle. */
|
|
185
128
|
declare function isCanvasMessage(data: unknown): data is CanvasMessage;
|
|
186
129
|
|
|
187
130
|
interface CanvasReceiverOpts {
|
|
188
|
-
/** Must match the `src` `RemoteCanvasHost` was given — the channel name is
|
|
189
|
-
* derived from it, so both sides agree without a separate id to track. */
|
|
190
131
|
src: string;
|
|
191
132
|
externals?: NuExternalsFactory;
|
|
192
133
|
}
|
|
193
134
|
interface CanvasReceiver {
|
|
194
|
-
/**
|
|
195
|
-
* A `Nu` instance mirroring the host's, kept in sync one-way (host -> here)
|
|
196
|
-
* via `init`/`patch` messages. It's a genuinely mutable, loaded `Nu` — DSL
|
|
197
|
-
* `onClick` handlers rendered from it run and mutate it locally exactly like
|
|
198
|
-
* any other `Nu` (e.g. a counter's `val inc = () => count = count + 1`, used
|
|
199
|
-
* by an in-editor "preview mode" toggle to stay truly interactive). That
|
|
200
|
-
* local mutation is intentionally NOT relayed back to the host: the next
|
|
201
|
-
* `init`/`patch` from the host simply overwrites/merges over it, so nothing
|
|
202
|
-
* here needs to enforce read-only-ness — only `RemoteCanvasHost`'s own
|
|
203
|
-
* resync-on-transition behavior needs to guarantee a clean return to the
|
|
204
|
-
* host's authoritative state (see its docs).
|
|
205
|
-
*/
|
|
206
135
|
nu: Nu;
|
|
207
|
-
/** Which component `nu` should currently be rendered as (via
|
|
208
|
-
* `nu.createFrame({ component: { name } })`) — reactive, so a component
|
|
209
|
-
* wrapped in `@nuforge/react`'s `observer()` re-renders when it changes. */
|
|
210
136
|
volatile: {
|
|
211
137
|
activeComponent: string | null;
|
|
212
|
-
/**
|
|
213
|
-
* Bumped every time `nu` gets a full `nu.load()` — a bootstrap, a resync
|
|
214
|
-
* (previewMode/activeComponent change), OR a `patch` whose flattened
|
|
215
|
-
* node-id set differs from the current one (nodes added/removed, not
|
|
216
|
-
* just field edits — see `applyState`). Callers building a Frame (e.g.
|
|
217
|
-
* via `useNuFrame`) should key their memo/`key` on this, alongside
|
|
218
|
-
* `activeComponent`, so React remounts and gets a fresh Frame instead of
|
|
219
|
-
* reading a disposed one.
|
|
220
|
-
*
|
|
221
|
-
* Structural patches are folded into the SAME full-reload path as
|
|
222
|
-
* `init`, not merged in place, even though that means a `val`'s live
|
|
223
|
-
* runtime value (e.g. a preview-mode counter's `count` — lives in a
|
|
224
|
-
* Frame's `Environment` bindings, not `nu.state`'s AST) gets reset too.
|
|
225
|
-
* An earlier version tried to preserve that by merging state first and
|
|
226
|
-
* bumping this counter as a second, separate step — that let React
|
|
227
|
-
* render the OLD (soon-stale) Frame against the ALREADY-merged,
|
|
228
|
-
* structurally-different `nu.state` in between the two updates
|
|
229
|
-
* (`Frame`'s template/component caches are keyed by structural PATH, not
|
|
230
|
-
* object identity, and aren't evicted on removal), producing duplicate
|
|
231
|
-
* React keys or a briefly-missing node before the remount caught up.
|
|
232
|
-
* Doing both as one atomic `nu.load()` + bump — the same already-proven
|
|
233
|
-
* resync path — closes that window entirely.
|
|
234
|
-
*/
|
|
235
138
|
loadGeneration: number;
|
|
236
139
|
};
|
|
237
|
-
/** Announce this receiver is mounted and ready for the host's first `init`.
|
|
238
|
-
* Call once, after registering any `onUi`/message handling — the host may
|
|
239
|
-
* reply synchronously-ish (same task queue) once it sees `ready`. */
|
|
240
140
|
postReady(): void;
|
|
241
|
-
/** Register a callback for host->iframe UI payloads (`ui` messages) —
|
|
242
|
-
* interpretation (theme, preview mode, overrides, ...) is entirely up to
|
|
243
|
-
* the caller; this SDK only relays the envelope. Returns an unsubscribe. */
|
|
244
141
|
onUi(cb: (payload: unknown) => void): () => void;
|
|
245
|
-
/** Send an interaction event up to the host (select/hover/drop/keydown/link-click). */
|
|
246
142
|
postEvent(message: DistributiveOmit<CanvasClientMessage, 'channel' | 'v'>): void;
|
|
247
143
|
dispose(): void;
|
|
248
144
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Canvas-side receiver for a genuinely-navigated `/canvas/[id]`-style route:
|
|
251
|
-
* a `Nu` instance kept in sync with the host's one-way, via `BroadcastChannel`
|
|
252
|
-
* + `t.unflatten`/`t.merge` (the same primitives `@nuforge/collaboration`'s
|
|
253
|
-
* `LoroSyncProvider` uses for its state<->doc binding, without the CRDT layer
|
|
254
|
-
* — there's exactly one writer here, so convergence machinery is unneeded).
|
|
255
|
-
* No `createEditor()`: this instance is never meant to originate an edit the
|
|
256
|
-
* host doesn't already know about, so it only needs `Nu`, not the mutation SDK.
|
|
257
|
-
*/
|
|
258
145
|
declare function createCanvasReceiver(opts: CanvasReceiverOpts): CanvasReceiver;
|
|
259
146
|
|
|
260
147
|
interface CreateEditorOptions {
|
|
261
|
-
/** Extra blocks to add to the palette. */
|
|
262
148
|
blocks?: Block[];
|
|
263
|
-
/** Include the built-in DEFAULT_BLOCKS (default true). */
|
|
264
149
|
defaultBlocks?: boolean;
|
|
265
150
|
}
|
|
266
|
-
/**
|
|
267
|
-
* A high-level facade over a {@link Nu} runtime for building visual page
|
|
268
|
-
* editors. Every mutation goes through `nu.change`, so it is transactional and
|
|
269
|
-
* undoable. Reach for these instead of hand-splicing the AST.
|
|
270
|
-
*/
|
|
271
151
|
declare class Editor {
|
|
272
152
|
readonly nu: Nu;
|
|
273
153
|
readonly blocks: BlockRegistry;
|
|
274
154
|
readonly commands: CommandRegistry;
|
|
275
155
|
constructor(nu: Nu, opts?: CreateEditorOptions);
|
|
276
|
-
/** Append (or insert at `index`) `child` into `parent`'s children. */
|
|
277
156
|
insertNode(parent: t.Template, child: t.Template, index?: number): void;
|
|
278
|
-
/** Remove a node from its parent. Returns false if it has no parent. */
|
|
279
157
|
removeNode(node: t.Template): boolean;
|
|
280
|
-
/** Move `dragged` before/after/inside `target`. Refuses to nest into self. */
|
|
281
158
|
moveNode(dragged: t.Template, target: t.Template, position: DropPosition): boolean;
|
|
282
159
|
private clipboardNode;
|
|
283
|
-
/** Put a node on the editor clipboard (a clone is made on paste). */
|
|
284
160
|
copyNode(node: t.Template): void;
|
|
285
|
-
/** Copy a node, then remove it. */
|
|
286
161
|
cutNode(node: t.Template): boolean;
|
|
287
|
-
/** Whether there is something on the clipboard to paste. */
|
|
288
162
|
canPaste(): boolean;
|
|
289
|
-
/**
|
|
290
|
-
* Paste a fresh copy (new ids) of the clipboard node before/after/inside
|
|
291
|
-
* `target`. Returns the inserted node, or null if nothing was pasted.
|
|
292
|
-
*/
|
|
293
163
|
pasteNode(target: t.Template, position?: DropPosition): t.Template | null;
|
|
294
|
-
/** Duplicate a node in place (inserted right after it). Returns the copy. */
|
|
295
164
|
duplicateNode(node: t.Template): t.Template | null;
|
|
296
|
-
/**
|
|
297
|
-
* Insert a detached node before/after/inside a target (undoable). The core of
|
|
298
|
-
* canvas drag-and-drop: a drop computes a target + position, then calls this.
|
|
299
|
-
* Returns the inserted node.
|
|
300
|
-
*/
|
|
301
165
|
insertNodeAt(node: t.Template, target: t.Template, position: DropPosition): t.Template | null;
|
|
302
|
-
/** Create a block and insert it before/after/inside a target. */
|
|
303
166
|
insertBlockAt(blockId: string, target: t.Template, position: DropPosition): t.Template | null;
|
|
304
167
|
private insertCloneRelativeTo;
|
|
305
|
-
/** Set several props at once. */
|
|
306
168
|
updateProps(node: t.Template, props: Record<string, t.Expression>): void;
|
|
307
169
|
setProp(node: t.Template, key: string, expr: t.Expression): void;
|
|
308
170
|
removeProp(node: t.Template, key: string): void;
|
|
309
|
-
/** Parse `source` as a nuforge expression and assign it to `key`. */
|
|
310
171
|
setExpression(node: t.Template, key: string, source: string): boolean;
|
|
311
|
-
/** Update a text node's literal value. */
|
|
312
172
|
setText(node: t.Template, value: string): boolean;
|
|
313
173
|
setTag(node: t.TagTemplate, tag: string): void;
|
|
314
174
|
addClass(node: t.Template, name: string): void;
|
|
315
|
-
/** Parse `source` and set the condition under which class `name` applies. */
|
|
316
175
|
setClassCondition(node: t.Template, name: string, source: string): boolean;
|
|
317
176
|
removeClass(node: t.Template, name: string): void;
|
|
318
|
-
/** Set the `@if` condition from source. */
|
|
319
177
|
setCondition(node: t.Template, source: string): boolean;
|
|
320
178
|
clearCondition(node: t.Template): void;
|
|
321
|
-
/** Repeat a node over `iterator` (a list expression), binding each `alias`. */
|
|
322
179
|
setEach(node: t.Template, iterator: string, alias?: string, indexName?: string): boolean;
|
|
323
180
|
clearEach(node: t.Template): void;
|
|
324
181
|
addState(component: t.NuComponent, name?: string, init?: string): t.Val;
|
|
@@ -327,12 +184,9 @@ declare class Editor {
|
|
|
327
184
|
addComponentProp(component: t.NuComponent, name?: string, init?: string): t.ComponentProp;
|
|
328
185
|
removeComponentProp(component: t.NuComponent, prop: t.ComponentProp): void;
|
|
329
186
|
setComponentPropInit(prop: t.ComponentProp, source: string): boolean;
|
|
330
|
-
/** Create a block's template and insert it into `parent`. */
|
|
331
187
|
insertBlock(blockId: string, parent: t.Template, index?: number): t.Template | null;
|
|
332
188
|
private uniqueName;
|
|
333
|
-
/** Add a new component (a "page"). */
|
|
334
189
|
addComponent(name?: string): t.NuComponent;
|
|
335
|
-
/** Rename a component, updating `<Name/>` references across the program. */
|
|
336
190
|
renameComponent(component: t.NuComponent, name: string): boolean;
|
|
337
191
|
removeComponent(component: t.NuComponent): boolean;
|
|
338
192
|
duplicateComponent(component: t.NuComponent): t.NuComponent;
|
|
@@ -343,20 +197,15 @@ declare class Editor {
|
|
|
343
197
|
getChildren(node: t.Template): t.Template[];
|
|
344
198
|
getSiblings(node: t.Template): t.Template[];
|
|
345
199
|
getNodeIndex(node: t.Template): number;
|
|
346
|
-
/** Full path from the State root to this node (for selections/serialization). */
|
|
347
200
|
getNodePath(node: t.Type): Array<string | number>;
|
|
348
|
-
/** Fires with the raw field-level writes of every change (incl. undo/redo). */
|
|
349
201
|
onChange(cb: (entries: MutationEntry[]) => void): () => void;
|
|
350
|
-
/** Fires only when the tree shape changes (a child array was mutated). */
|
|
351
202
|
onStructureChange(cb: () => void): () => void;
|
|
352
203
|
private spliceFromParent;
|
|
353
204
|
private applyMove;
|
|
354
|
-
/** Insert an already-detached node relative to `target` (no removal). */
|
|
355
205
|
private insertRelativeTo;
|
|
356
206
|
}
|
|
357
207
|
declare function createEditor(nu: Nu, opts?: CreateEditorOptions): Editor;
|
|
358
208
|
|
|
359
|
-
/** A named action a UI can discover and bind to (toolbar buttons, shortcuts). */
|
|
360
209
|
interface Command {
|
|
361
210
|
id: string;
|
|
362
211
|
label: string;
|