@nuforge/editor 0.3.2 → 0.4.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/index.cjs CHANGED
@@ -96,22 +96,61 @@ const DEFAULT_BLOCKS = [
96
96
  ];
97
97
 
98
98
  const CANVAS_CHANNEL = 'nuforge-canvas';
99
- const CANVAS_PROTOCOL_VERSION = 1;
100
- function canvasChannelName(src) {
101
- return `${CANVAS_CHANNEL}:${src}`;
102
- }
99
+ const CANVAS_PROTOCOL_VERSION = 2;
103
100
  function isCanvasMessage(data) {
104
101
  return (!!data &&
105
102
  typeof data === 'object' &&
106
103
  data.channel === CANVAS_CHANNEL &&
107
104
  data.v === CANVAS_PROTOCOL_VERSION);
108
105
  }
106
+ function canvasNonce() {
107
+ const c = globalThis.crypto;
108
+ if (c?.randomUUID) {
109
+ return c.randomUUID();
110
+ }
111
+ return `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
112
+ }
109
113
 
110
- function createCanvasReceiver(opts) {
114
+ const READY_RETRY_MS = 100;
115
+ const READY_RETRY_MAX_MS = 2000;
116
+ function onNextFrame(cb) {
117
+ const raf = globalThis
118
+ .requestAnimationFrame;
119
+ if (raf) {
120
+ raf(cb);
121
+ }
122
+ else {
123
+ setTimeout(cb, 16);
124
+ }
125
+ }
126
+ function sameRect(a, b) {
127
+ if (!a || !b) {
128
+ return a === b;
129
+ }
130
+ return (a.top === b.top &&
131
+ a.left === b.left &&
132
+ a.width === b.width &&
133
+ a.height === b.height);
134
+ }
135
+ function sameRects(a, b) {
136
+ return (a.selectedLabel === b.selectedLabel &&
137
+ sameRect(a.selected, b.selected) &&
138
+ sameRect(a.hovered, b.hovered));
139
+ }
140
+ function createCanvasReceiver(opts = {}) {
111
141
  const nu = core.Nu.create({ externals: opts.externals });
112
142
  const volatile = reactivity.reactive({ activeComponent: null, loadGeneration: 0 });
113
- const channel = new BroadcastChannel(canvasChannelName(opts.src));
143
+ const view = opts.view ?? globalThis.window ?? null;
144
+ const doc = opts.doc ?? globalThis.document ?? null;
114
145
  const uiListeners = new Set();
146
+ const nonce = canvasNonce();
147
+ let port = null;
148
+ let retryTimer = null;
149
+ let retryDelay = READY_RETRY_MS;
150
+ let disposed = false;
151
+ const send = (message) => {
152
+ port?.postMessage(message);
153
+ };
115
154
  const applyState = (message) => {
116
155
  if (message.type === 'init') {
117
156
  nu.load(t__namespace.unflatten(message.flat));
@@ -122,46 +161,150 @@ function createCanvasReceiver(opts) {
122
161
  }
123
162
  volatile.activeComponent = message.activeComponent;
124
163
  };
125
- const onMessage = (event) => {
164
+ let tracked = {
165
+ selectedId: null,
166
+ hoveredId: null,
167
+ };
168
+ let lastRects = null;
169
+ let measureScheduled = false;
170
+ let resizeObserver = null;
171
+ const elementFor = (id) => id && doc ? doc.querySelector(`[data-nu-id="${id}"]`) : null;
172
+ const rectOf = (el) => {
173
+ if (!el) {
174
+ return null;
175
+ }
176
+ const r = el.getBoundingClientRect();
177
+ return { top: r.top, left: r.left, width: r.width, height: r.height };
178
+ };
179
+ const measure = () => {
180
+ const selectedEl = elementFor(tracked.selectedId);
181
+ const next = {
182
+ type: 'rects',
183
+ selected: rectOf(selectedEl),
184
+ hovered: tracked.hoveredId && tracked.hoveredId !== tracked.selectedId
185
+ ? rectOf(elementFor(tracked.hoveredId))
186
+ : null,
187
+ selectedLabel: selectedEl ? selectedEl.tagName.toLowerCase() : null,
188
+ };
189
+ if (lastRects && sameRects(lastRects, next)) {
190
+ return;
191
+ }
192
+ lastRects = next;
193
+ send({ channel: CANVAS_CHANNEL, v: CANVAS_PROTOCOL_VERSION, ...next });
194
+ };
195
+ const scheduleMeasure = () => {
196
+ if (measureScheduled || !doc) {
197
+ return;
198
+ }
199
+ measureScheduled = true;
200
+ onNextFrame(() => {
201
+ measureScheduled = false;
202
+ measure();
203
+ });
204
+ };
205
+ const onScroll = () => scheduleMeasure();
206
+ if (doc) {
207
+ doc.addEventListener('scroll', onScroll, true);
208
+ const RO = globalThis
209
+ .ResizeObserver;
210
+ if (RO && doc.documentElement) {
211
+ resizeObserver = new RO(scheduleMeasure);
212
+ resizeObserver.observe(doc.documentElement);
213
+ }
214
+ }
215
+ const onPortMessage = (event) => {
126
216
  if (!isCanvasMessage(event.data)) {
127
217
  return;
128
218
  }
129
219
  const message = event.data;
130
220
  if (message.type === 'init' || message.type === 'patch') {
131
221
  applyState(message);
222
+ scheduleMeasure();
132
223
  }
133
224
  else if (message.type === 'ui') {
134
225
  for (const cb of uiListeners) {
135
226
  cb(message.payload);
136
227
  }
228
+ scheduleMeasure();
229
+ }
230
+ else if (message.type === 'track') {
231
+ tracked = { selectedId: message.selectedId, hoveredId: message.hoveredId };
232
+ lastRects = null;
233
+ scheduleMeasure();
234
+ }
235
+ };
236
+ const stopRetrying = () => {
237
+ if (retryTimer !== null) {
238
+ clearTimeout(retryTimer);
239
+ retryTimer = null;
240
+ }
241
+ };
242
+ const announce = () => {
243
+ view?.parent?.postMessage({
244
+ channel: CANVAS_CHANNEL,
245
+ v: CANVAS_PROTOCOL_VERSION,
246
+ type: 'ready',
247
+ nonce,
248
+ }, '*');
249
+ retryDelay = Math.min(retryDelay * 2, READY_RETRY_MAX_MS);
250
+ retryTimer = setTimeout(announce, retryDelay);
251
+ };
252
+ const onWindowMessage = (event) => {
253
+ if (disposed || !isCanvasMessage(event.data)) {
254
+ return;
255
+ }
256
+ const message = event.data;
257
+ if (message.type !== 'connect' || message.nonce !== nonce) {
258
+ return;
137
259
  }
260
+ const incoming = event.ports?.[0];
261
+ if (!incoming) {
262
+ return;
263
+ }
264
+ port?.close();
265
+ port = incoming;
266
+ port.addEventListener('message', onPortMessage);
267
+ port.start();
268
+ stopRetrying();
269
+ send({
270
+ channel: CANVAS_CHANNEL,
271
+ v: CANVAS_PROTOCOL_VERSION,
272
+ type: 'hello',
273
+ nonce,
274
+ });
138
275
  };
139
- channel.addEventListener('message', onMessage);
276
+ view?.addEventListener('message', onWindowMessage);
140
277
  return {
141
278
  nu,
142
279
  volatile,
143
280
  postReady() {
144
- channel.postMessage({
145
- channel: CANVAS_CHANNEL,
146
- v: CANVAS_PROTOCOL_VERSION,
147
- type: 'ready',
148
- mountGen: Math.floor(Math.random() * 1e9),
149
- });
281
+ if (disposed || port || retryTimer !== null) {
282
+ return;
283
+ }
284
+ retryDelay = READY_RETRY_MS;
285
+ announce();
150
286
  },
151
287
  onUi(cb) {
152
288
  uiListeners.add(cb);
153
289
  return () => uiListeners.delete(cb);
154
290
  },
155
291
  postEvent(message) {
156
- channel.postMessage({
292
+ send({
157
293
  channel: CANVAS_CHANNEL,
158
294
  v: CANVAS_PROTOCOL_VERSION,
159
295
  ...message,
160
296
  });
161
297
  },
162
298
  dispose() {
163
- channel.removeEventListener('message', onMessage);
164
- channel.close();
299
+ disposed = true;
300
+ stopRetrying();
301
+ doc?.removeEventListener('scroll', onScroll, true);
302
+ resizeObserver?.disconnect();
303
+ resizeObserver = null;
304
+ view?.removeEventListener('message', onWindowMessage);
305
+ port?.removeEventListener('message', onPortMessage);
306
+ port?.close();
307
+ port = null;
165
308
  uiListeners.clear();
166
309
  },
167
310
  };
@@ -758,7 +901,7 @@ exports.CommandRegistry = CommandRegistry;
758
901
  exports.DEFAULT_BLOCKS = DEFAULT_BLOCKS;
759
902
  exports.Editor = Editor;
760
903
  exports.canHaveChildren = canHaveChildren;
761
- exports.canvasChannelName = canvasChannelName;
904
+ exports.canvasNonce = canvasNonce;
762
905
  exports.createCanvasReceiver = createCanvasReceiver;
763
906
  exports.createEditor = createEditor;
764
907
  exports.defineBlock = defineBlock;
package/dist/index.d.ts CHANGED
@@ -47,8 +47,13 @@ declare function canHaveChildren(node: t.Template | null): boolean;
47
47
  declare function isAncestorOf(nu: Nu, ancestor: t.Template, node: t.Template): boolean;
48
48
 
49
49
  declare const CANVAS_CHANNEL = "nuforge-canvas";
50
- declare const CANVAS_PROTOCOL_VERSION = 1;
51
- declare function canvasChannelName(src: string): string;
50
+ declare const CANVAS_PROTOCOL_VERSION = 2;
51
+ interface Rect {
52
+ top: number;
53
+ left: number;
54
+ width: number;
55
+ height: number;
56
+ }
52
57
  interface FlattenedState {
53
58
  root: {
54
59
  $$typeId: string;
@@ -79,12 +84,31 @@ interface CanvasUiMessage {
79
84
  type: 'ui';
80
85
  payload: unknown;
81
86
  }
82
- type CanvasHostMessage = CanvasInitMessage | CanvasPatchMessage | CanvasUiMessage;
87
+ interface CanvasConnectMessage {
88
+ channel: typeof CANVAS_CHANNEL;
89
+ v: typeof CANVAS_PROTOCOL_VERSION;
90
+ type: 'connect';
91
+ nonce: string;
92
+ }
93
+ interface CanvasTrackMessage {
94
+ channel: typeof CANVAS_CHANNEL;
95
+ v: typeof CANVAS_PROTOCOL_VERSION;
96
+ type: 'track';
97
+ selectedId: string | null;
98
+ hoveredId: string | null;
99
+ }
100
+ type CanvasHostMessage = CanvasInitMessage | CanvasPatchMessage | CanvasUiMessage | CanvasConnectMessage | CanvasTrackMessage;
83
101
  interface CanvasReadyMessage {
84
102
  channel: typeof CANVAS_CHANNEL;
85
103
  v: typeof CANVAS_PROTOCOL_VERSION;
86
104
  type: 'ready';
87
- mountGen: number;
105
+ nonce: string;
106
+ }
107
+ interface CanvasHelloMessage {
108
+ channel: typeof CANVAS_CHANNEL;
109
+ v: typeof CANVAS_PROTOCOL_VERSION;
110
+ type: 'hello';
111
+ nonce: string;
88
112
  }
89
113
  interface CanvasSelectMessage {
90
114
  channel: typeof CANVAS_CHANNEL;
@@ -122,14 +146,42 @@ interface CanvasLinkClickMessage {
122
146
  type: 'link-click';
123
147
  href: string;
124
148
  }
125
- type CanvasClientMessage = CanvasReadyMessage | CanvasSelectMessage | CanvasHoverMessage | CanvasDropMessage | CanvasKeydownMessage | CanvasLinkClickMessage;
149
+ interface CanvasRectsMessage {
150
+ channel: typeof CANVAS_CHANNEL;
151
+ v: typeof CANVAS_PROTOCOL_VERSION;
152
+ type: 'rects';
153
+ selected: Rect | null;
154
+ hovered: Rect | null;
155
+ selectedLabel: string | null;
156
+ }
157
+ type CanvasEventMessage = CanvasSelectMessage | CanvasHoverMessage | CanvasDropMessage | CanvasKeydownMessage | CanvasLinkClickMessage;
158
+ type CanvasClientMessage = CanvasReadyMessage | CanvasHelloMessage | CanvasRectsMessage | CanvasEventMessage;
126
159
  type CanvasMessage = CanvasHostMessage | CanvasClientMessage;
127
160
  type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
128
161
  declare function isCanvasMessage(data: unknown): data is CanvasMessage;
162
+ declare function canvasNonce(): string;
129
163
 
164
+ interface CanvasView {
165
+ parent: {
166
+ postMessage(message: unknown, targetOrigin: string): void;
167
+ } | null;
168
+ addEventListener(type: 'message', cb: (e: MessageEvent) => void): void;
169
+ removeEventListener(type: 'message', cb: (e: MessageEvent) => void): void;
170
+ }
171
+ interface CanvasGeometrySource {
172
+ querySelector(selectors: string): CanvasMeasurable | null;
173
+ addEventListener(type: 'scroll', cb: () => void, capture: boolean): void;
174
+ removeEventListener(type: 'scroll', cb: () => void, capture: boolean): void;
175
+ documentElement?: Element | null;
176
+ }
177
+ interface CanvasMeasurable {
178
+ tagName: string;
179
+ getBoundingClientRect(): Rect;
180
+ }
130
181
  interface CanvasReceiverOpts {
131
- src: string;
132
182
  externals?: NuExternalsFactory;
183
+ view?: CanvasView;
184
+ doc?: CanvasGeometrySource;
133
185
  }
134
186
  interface CanvasReceiver {
135
187
  nu: Nu;
@@ -139,10 +191,10 @@ interface CanvasReceiver {
139
191
  };
140
192
  postReady(): void;
141
193
  onUi(cb: (payload: unknown) => void): () => void;
142
- postEvent(message: DistributiveOmit<CanvasClientMessage, 'channel' | 'v'>): void;
194
+ postEvent(message: DistributiveOmit<CanvasEventMessage, 'channel' | 'v'>): void;
143
195
  dispose(): void;
144
196
  }
145
- declare function createCanvasReceiver(opts: CanvasReceiverOpts): CanvasReceiver;
197
+ declare function createCanvasReceiver(opts?: CanvasReceiverOpts): CanvasReceiver;
146
198
 
147
199
  interface CreateEditorOptions {
148
200
  blocks?: Block[];
@@ -222,5 +274,5 @@ declare class CommandRegistry {
222
274
  run(id: string): void;
223
275
  }
224
276
 
225
- export { BlockRegistry, CANVAS_CHANNEL, CANVAS_PROTOCOL_VERSION, CommandRegistry, DEFAULT_BLOCKS, Editor, canHaveChildren, canvasChannelName, createCanvasReceiver, createEditor, defineBlock, dropPositionFromPointer, el, isAncestorOf, isCanvasMessage, isTextTemplate, lit, templateChildren, templateLabel, text, textPreview };
226
- export type { Block, CanvasClientMessage, CanvasDropMessage, CanvasHostMessage, CanvasHoverMessage, CanvasInitMessage, CanvasKeydownMessage, CanvasLinkClickMessage, CanvasMessage, CanvasPatchMessage, CanvasReadyMessage, CanvasReceiver, CanvasReceiverOpts, CanvasSelectMessage, CanvasUiMessage, Command, CreateEditorOptions, DistributiveOmit, DropPosition, FlattenedState, PropSchemaField };
277
+ export { BlockRegistry, CANVAS_CHANNEL, CANVAS_PROTOCOL_VERSION, CommandRegistry, DEFAULT_BLOCKS, Editor, canHaveChildren, canvasNonce, createCanvasReceiver, createEditor, defineBlock, dropPositionFromPointer, el, isAncestorOf, isCanvasMessage, isTextTemplate, lit, templateChildren, templateLabel, text, textPreview };
278
+ export type { Block, CanvasClientMessage, CanvasConnectMessage, CanvasDropMessage, CanvasEventMessage, CanvasGeometrySource, CanvasHelloMessage, CanvasHostMessage, CanvasHoverMessage, CanvasInitMessage, CanvasKeydownMessage, CanvasLinkClickMessage, CanvasMeasurable, CanvasMessage, CanvasPatchMessage, CanvasReadyMessage, CanvasReceiver, CanvasReceiverOpts, CanvasRectsMessage, CanvasSelectMessage, CanvasTrackMessage, CanvasUiMessage, CanvasView, Command, CreateEditorOptions, DistributiveOmit, DropPosition, FlattenedState, PropSchemaField, Rect };
package/dist/index.mjs CHANGED
@@ -75,22 +75,61 @@ const DEFAULT_BLOCKS = [
75
75
  ];
76
76
 
77
77
  const CANVAS_CHANNEL = 'nuforge-canvas';
78
- const CANVAS_PROTOCOL_VERSION = 1;
79
- function canvasChannelName(src) {
80
- return `${CANVAS_CHANNEL}:${src}`;
81
- }
78
+ const CANVAS_PROTOCOL_VERSION = 2;
82
79
  function isCanvasMessage(data) {
83
80
  return (!!data &&
84
81
  typeof data === 'object' &&
85
82
  data.channel === CANVAS_CHANNEL &&
86
83
  data.v === CANVAS_PROTOCOL_VERSION);
87
84
  }
85
+ function canvasNonce() {
86
+ const c = globalThis.crypto;
87
+ if (c?.randomUUID) {
88
+ return c.randomUUID();
89
+ }
90
+ return `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
91
+ }
88
92
 
89
- function createCanvasReceiver(opts) {
93
+ const READY_RETRY_MS = 100;
94
+ const READY_RETRY_MAX_MS = 2000;
95
+ function onNextFrame(cb) {
96
+ const raf = globalThis
97
+ .requestAnimationFrame;
98
+ if (raf) {
99
+ raf(cb);
100
+ }
101
+ else {
102
+ setTimeout(cb, 16);
103
+ }
104
+ }
105
+ function sameRect(a, b) {
106
+ if (!a || !b) {
107
+ return a === b;
108
+ }
109
+ return (a.top === b.top &&
110
+ a.left === b.left &&
111
+ a.width === b.width &&
112
+ a.height === b.height);
113
+ }
114
+ function sameRects(a, b) {
115
+ return (a.selectedLabel === b.selectedLabel &&
116
+ sameRect(a.selected, b.selected) &&
117
+ sameRect(a.hovered, b.hovered));
118
+ }
119
+ function createCanvasReceiver(opts = {}) {
90
120
  const nu = Nu.create({ externals: opts.externals });
91
121
  const volatile = reactive({ activeComponent: null, loadGeneration: 0 });
92
- const channel = new BroadcastChannel(canvasChannelName(opts.src));
122
+ const view = opts.view ?? globalThis.window ?? null;
123
+ const doc = opts.doc ?? globalThis.document ?? null;
93
124
  const uiListeners = new Set();
125
+ const nonce = canvasNonce();
126
+ let port = null;
127
+ let retryTimer = null;
128
+ let retryDelay = READY_RETRY_MS;
129
+ let disposed = false;
130
+ const send = (message) => {
131
+ port?.postMessage(message);
132
+ };
94
133
  const applyState = (message) => {
95
134
  if (message.type === 'init') {
96
135
  nu.load(t.unflatten(message.flat));
@@ -101,46 +140,150 @@ function createCanvasReceiver(opts) {
101
140
  }
102
141
  volatile.activeComponent = message.activeComponent;
103
142
  };
104
- const onMessage = (event) => {
143
+ let tracked = {
144
+ selectedId: null,
145
+ hoveredId: null,
146
+ };
147
+ let lastRects = null;
148
+ let measureScheduled = false;
149
+ let resizeObserver = null;
150
+ const elementFor = (id) => id && doc ? doc.querySelector(`[data-nu-id="${id}"]`) : null;
151
+ const rectOf = (el) => {
152
+ if (!el) {
153
+ return null;
154
+ }
155
+ const r = el.getBoundingClientRect();
156
+ return { top: r.top, left: r.left, width: r.width, height: r.height };
157
+ };
158
+ const measure = () => {
159
+ const selectedEl = elementFor(tracked.selectedId);
160
+ const next = {
161
+ type: 'rects',
162
+ selected: rectOf(selectedEl),
163
+ hovered: tracked.hoveredId && tracked.hoveredId !== tracked.selectedId
164
+ ? rectOf(elementFor(tracked.hoveredId))
165
+ : null,
166
+ selectedLabel: selectedEl ? selectedEl.tagName.toLowerCase() : null,
167
+ };
168
+ if (lastRects && sameRects(lastRects, next)) {
169
+ return;
170
+ }
171
+ lastRects = next;
172
+ send({ channel: CANVAS_CHANNEL, v: CANVAS_PROTOCOL_VERSION, ...next });
173
+ };
174
+ const scheduleMeasure = () => {
175
+ if (measureScheduled || !doc) {
176
+ return;
177
+ }
178
+ measureScheduled = true;
179
+ onNextFrame(() => {
180
+ measureScheduled = false;
181
+ measure();
182
+ });
183
+ };
184
+ const onScroll = () => scheduleMeasure();
185
+ if (doc) {
186
+ doc.addEventListener('scroll', onScroll, true);
187
+ const RO = globalThis
188
+ .ResizeObserver;
189
+ if (RO && doc.documentElement) {
190
+ resizeObserver = new RO(scheduleMeasure);
191
+ resizeObserver.observe(doc.documentElement);
192
+ }
193
+ }
194
+ const onPortMessage = (event) => {
105
195
  if (!isCanvasMessage(event.data)) {
106
196
  return;
107
197
  }
108
198
  const message = event.data;
109
199
  if (message.type === 'init' || message.type === 'patch') {
110
200
  applyState(message);
201
+ scheduleMeasure();
111
202
  }
112
203
  else if (message.type === 'ui') {
113
204
  for (const cb of uiListeners) {
114
205
  cb(message.payload);
115
206
  }
207
+ scheduleMeasure();
208
+ }
209
+ else if (message.type === 'track') {
210
+ tracked = { selectedId: message.selectedId, hoveredId: message.hoveredId };
211
+ lastRects = null;
212
+ scheduleMeasure();
213
+ }
214
+ };
215
+ const stopRetrying = () => {
216
+ if (retryTimer !== null) {
217
+ clearTimeout(retryTimer);
218
+ retryTimer = null;
219
+ }
220
+ };
221
+ const announce = () => {
222
+ view?.parent?.postMessage({
223
+ channel: CANVAS_CHANNEL,
224
+ v: CANVAS_PROTOCOL_VERSION,
225
+ type: 'ready',
226
+ nonce,
227
+ }, '*');
228
+ retryDelay = Math.min(retryDelay * 2, READY_RETRY_MAX_MS);
229
+ retryTimer = setTimeout(announce, retryDelay);
230
+ };
231
+ const onWindowMessage = (event) => {
232
+ if (disposed || !isCanvasMessage(event.data)) {
233
+ return;
234
+ }
235
+ const message = event.data;
236
+ if (message.type !== 'connect' || message.nonce !== nonce) {
237
+ return;
116
238
  }
239
+ const incoming = event.ports?.[0];
240
+ if (!incoming) {
241
+ return;
242
+ }
243
+ port?.close();
244
+ port = incoming;
245
+ port.addEventListener('message', onPortMessage);
246
+ port.start();
247
+ stopRetrying();
248
+ send({
249
+ channel: CANVAS_CHANNEL,
250
+ v: CANVAS_PROTOCOL_VERSION,
251
+ type: 'hello',
252
+ nonce,
253
+ });
117
254
  };
118
- channel.addEventListener('message', onMessage);
255
+ view?.addEventListener('message', onWindowMessage);
119
256
  return {
120
257
  nu,
121
258
  volatile,
122
259
  postReady() {
123
- channel.postMessage({
124
- channel: CANVAS_CHANNEL,
125
- v: CANVAS_PROTOCOL_VERSION,
126
- type: 'ready',
127
- mountGen: Math.floor(Math.random() * 1e9),
128
- });
260
+ if (disposed || port || retryTimer !== null) {
261
+ return;
262
+ }
263
+ retryDelay = READY_RETRY_MS;
264
+ announce();
129
265
  },
130
266
  onUi(cb) {
131
267
  uiListeners.add(cb);
132
268
  return () => uiListeners.delete(cb);
133
269
  },
134
270
  postEvent(message) {
135
- channel.postMessage({
271
+ send({
136
272
  channel: CANVAS_CHANNEL,
137
273
  v: CANVAS_PROTOCOL_VERSION,
138
274
  ...message,
139
275
  });
140
276
  },
141
277
  dispose() {
142
- channel.removeEventListener('message', onMessage);
143
- channel.close();
278
+ disposed = true;
279
+ stopRetrying();
280
+ doc?.removeEventListener('scroll', onScroll, true);
281
+ resizeObserver?.disconnect();
282
+ resizeObserver = null;
283
+ view?.removeEventListener('message', onWindowMessage);
284
+ port?.removeEventListener('message', onPortMessage);
285
+ port?.close();
286
+ port = null;
144
287
  uiListeners.clear();
145
288
  },
146
289
  };
@@ -730,4 +873,4 @@ function createEditor(nu, opts) {
730
873
  return new Editor(nu, opts);
731
874
  }
732
875
 
733
- export { BlockRegistry, CANVAS_CHANNEL, CANVAS_PROTOCOL_VERSION, CommandRegistry, DEFAULT_BLOCKS, Editor, canHaveChildren, canvasChannelName, createCanvasReceiver, createEditor, defineBlock, dropPositionFromPointer, el, isAncestorOf, isCanvasMessage, isTextTemplate, lit, templateChildren, templateLabel, text, textPreview };
876
+ export { BlockRegistry, CANVAS_CHANNEL, CANVAS_PROTOCOL_VERSION, CommandRegistry, DEFAULT_BLOCKS, Editor, canHaveChildren, canvasNonce, createCanvasReceiver, createEditor, defineBlock, dropPositionFromPointer, el, isAncestorOf, isCanvasMessage, isTextTemplate, lit, templateChildren, templateLabel, text, textPreview };
package/dist/react.cjs CHANGED
@@ -7,10 +7,7 @@ var react = require('react');
7
7
  var reactDom = require('react-dom');
8
8
 
9
9
  const CANVAS_CHANNEL = 'nuforge-canvas';
10
- const CANVAS_PROTOCOL_VERSION = 1;
11
- function canvasChannelName(src) {
12
- return `${CANVAS_CHANNEL}:${src}`;
13
- }
10
+ const CANVAS_PROTOCOL_VERSION = 2;
14
11
  function isCanvasMessage(data) {
15
12
  return (!!data &&
16
13
  typeof data === 'object' &&
@@ -219,13 +216,14 @@ function IframeCanvas({ frame, selectedId = null, hoveredId = null, revision = 0
219
216
  }, renderBox({ selected, hovered, selectedLabel, drop }))
220
217
  : null);
221
218
  }
219
+ const NO_BOXES = {
220
+ selected: null,
221
+ hovered: null,
222
+ selectedLabel: null,
223
+ };
222
224
  function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hoveredId = null, revision = 0, previewMode = false, uiPayload, onSelect, onHover, onDropOnNode, onKeydownRelay, onLinkClick, className, containerClassName, renderBox, }) {
223
225
  const iframeRef = react.useRef(null);
224
- const [doc, setDoc] = react.useState(null);
225
- const [tick, setTick] = react.useState(0);
226
- const [selected, setSelected] = react.useState(null);
227
- const [hovered, setHovered] = react.useState(null);
228
- const [selectedLabel, setSelectedLabel] = react.useState(null);
226
+ const [boxes, setBoxes] = react.useState(NO_BOXES);
229
227
  const props = react.useRef({
230
228
  nu,
231
229
  activeComponent,
@@ -248,42 +246,14 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
248
246
  };
249
247
  const connectionRef = react.useRef(null);
250
248
  react.useEffect(() => {
251
- const iframe = iframeRef.current;
252
- if (!iframe) {
253
- return;
254
- }
255
- const onLoad = () => setDoc(iframe.contentDocument);
256
- iframe.addEventListener('load', onLoad);
257
- return () => iframe.removeEventListener('load', onLoad);
258
- }, [src]);
259
- react.useEffect(() => {
260
- if (!doc) {
261
- return;
262
- }
263
- const onScroll = () => setTick((x) => x + 1);
264
- doc.addEventListener('scroll', onScroll, true);
265
- return () => doc.removeEventListener('scroll', onScroll, true);
266
- }, [doc]);
267
- react.useEffect(() => {
268
- const iframe = iframeRef.current;
269
- if (!iframe) {
270
- return;
271
- }
272
- const observer = new ResizeObserver(() => setTick((x) => x + 1));
273
- observer.observe(iframe);
274
- if (doc?.documentElement) {
275
- observer.observe(doc.documentElement);
276
- }
277
- return () => observer.disconnect();
278
- }, [doc]);
279
- react.useEffect(() => {
280
- const channel = new BroadcastChannel(canvasChannelName(src));
281
- let ready = false;
282
- let mountGen = null;
249
+ let port = null;
250
+ let pendingNonce = null;
251
+ let connectedNonce = null;
283
252
  let rafId = null;
284
253
  let rev = 0;
254
+ const post = (message) => port?.postMessage(message);
285
255
  const sendInit = () => {
286
- channel.postMessage({
256
+ post({
287
257
  channel: CANVAS_CHANNEL,
288
258
  v: CANVAS_PROTOCOL_VERSION,
289
259
  type: 'init',
@@ -292,12 +262,12 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
292
262
  });
293
263
  };
294
264
  const requestPatch = () => {
295
- if (!ready || rafId !== null) {
265
+ if (connectedNonce === null || rafId !== null) {
296
266
  return;
297
267
  }
298
268
  rafId = requestAnimationFrame(() => {
299
269
  rafId = null;
300
- channel.postMessage({
270
+ post({
301
271
  channel: CANVAS_CHANNEL,
302
272
  v: CANVAS_PROTOCOL_VERSION,
303
273
  type: 'patch',
@@ -308,37 +278,51 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
308
278
  });
309
279
  };
310
280
  const postUi = (payload) => {
311
- channel.postMessage({
281
+ post({
312
282
  channel: CANVAS_CHANNEL,
313
283
  v: CANVAS_PROTOCOL_VERSION,
314
284
  type: 'ui',
315
285
  payload,
316
286
  });
317
287
  };
318
- connectionRef.current = { sendInit, requestPatch, postUi };
319
- const onMessage = (event) => {
288
+ const postTrack = (selectedId, hoveredId) => {
289
+ post({
290
+ channel: CANVAS_CHANNEL,
291
+ v: CANVAS_PROTOCOL_VERSION,
292
+ type: 'track',
293
+ selectedId,
294
+ hoveredId,
295
+ });
296
+ };
297
+ connectionRef.current = { sendInit, requestPatch, postUi, postTrack };
298
+ const onPortMessage = (event) => {
320
299
  if (!isCanvasMessage(event.data)) {
321
300
  return;
322
301
  }
323
302
  const message = event.data;
324
303
  const p = props.current;
325
304
  switch (message.type) {
326
- case 'ready':
327
- if (mountGen !== null && message.mountGen <= mountGen) {
305
+ case 'hello':
306
+ if (message.nonce !== pendingNonce) {
328
307
  return;
329
308
  }
330
- mountGen = message.mountGen;
331
- ready = true;
309
+ connectedNonce = message.nonce;
332
310
  sendInit();
311
+ postTrack(trackRef.current.selectedId, trackRef.current.hoveredId);
333
312
  postUi(props.current.uiPayload);
334
313
  break;
314
+ case 'rects':
315
+ setBoxes({
316
+ selected: message.selected,
317
+ hovered: message.hovered,
318
+ selectedLabel: message.selectedLabel,
319
+ });
320
+ break;
335
321
  case 'select':
336
322
  p.onSelect?.(message.nodeId);
337
- setTick((x) => x + 1);
338
323
  break;
339
324
  case 'hover':
340
325
  p.onHover?.(message.nodeId);
341
- setTick((x) => x + 1);
342
326
  break;
343
327
  case 'drop':
344
328
  p.onDropOnNode?.(message.targetId, message.position);
@@ -351,14 +335,39 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
351
335
  break;
352
336
  }
353
337
  };
354
- channel.addEventListener('message', onMessage);
355
- const unlistenChangeset = nu.listenToChangeset(() => {
356
- requestPatch();
357
- setTick((x) => x + 1);
358
- });
338
+ const onWindowMessage = (event) => {
339
+ const frame = iframeRef.current;
340
+ if (!frame || event.source !== frame.contentWindow) {
341
+ return;
342
+ }
343
+ if (!isCanvasMessage(event.data) || event.data.type !== 'ready') {
344
+ return;
345
+ }
346
+ const { nonce } = event.data;
347
+ if (connectedNonce === nonce) {
348
+ return;
349
+ }
350
+ port?.close();
351
+ const channel = new MessageChannel();
352
+ port = channel.port1;
353
+ pendingNonce = nonce;
354
+ connectedNonce = null;
355
+ port.addEventListener('message', onPortMessage);
356
+ port.start();
357
+ event.source.postMessage({
358
+ channel: CANVAS_CHANNEL,
359
+ v: CANVAS_PROTOCOL_VERSION,
360
+ type: 'connect',
361
+ nonce,
362
+ }, '*', [channel.port2]);
363
+ };
364
+ window.addEventListener('message', onWindowMessage);
365
+ const unlistenChangeset = nu.listenToChangeset(requestPatch);
359
366
  return () => {
360
- channel.removeEventListener('message', onMessage);
361
- channel.close();
367
+ window.removeEventListener('message', onWindowMessage);
368
+ port?.removeEventListener('message', onPortMessage);
369
+ port?.close();
370
+ port = null;
362
371
  unlistenChangeset();
363
372
  if (rafId !== null) {
364
373
  cancelAnimationFrame(rafId);
@@ -378,26 +387,20 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
378
387
  react.useEffect(() => {
379
388
  connectionRef.current?.postUi(uiPayload);
380
389
  }, [uiPayload]);
381
- const elementFor = (id) => doc && id ? doc.querySelector(`[data-nu-id="${id}"]`) : null;
382
- const rectOfEl = (el) => {
383
- if (!el) {
384
- return null;
385
- }
386
- const r = el.getBoundingClientRect();
387
- return { top: r.top, left: r.left, width: r.width, height: r.height };
388
- };
389
- react.useLayoutEffect(() => {
390
+ const trackRef = react.useRef({
391
+ selectedId: null,
392
+ hoveredId: null,
393
+ });
394
+ react.useEffect(() => {
395
+ const next = previewMode
396
+ ? { selectedId: null, hoveredId: null }
397
+ : { selectedId, hoveredId };
398
+ trackRef.current = next;
390
399
  if (previewMode) {
391
- setSelected(null);
392
- setHovered(null);
393
- setSelectedLabel(null);
394
- return;
400
+ setBoxes(NO_BOXES);
395
401
  }
396
- const selectedEl = elementFor(selectedId);
397
- setSelected(rectOfEl(selectedEl));
398
- setHovered(hoveredId && hoveredId !== selectedId ? rectOfEl(elementFor(hoveredId)) : null);
399
- setSelectedLabel(selectedEl ? selectedEl.tagName.toLowerCase() : null);
400
- }, [previewMode, selectedId, hoveredId, revision, tick, doc]);
402
+ connectionRef.current?.postTrack(next.selectedId, next.hoveredId);
403
+ }, [previewMode, selectedId, hoveredId, revision]);
401
404
  return react.createElement('div', { className: containerClassName, style: { position: 'relative' } }, react.createElement('iframe', {
402
405
  ref: iframeRef,
403
406
  title: 'nuforge canvas',
@@ -411,7 +414,7 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
411
414
  pointerEvents: 'none',
412
415
  overflow: 'hidden',
413
416
  },
414
- }, renderBox({ selected, hovered, selectedLabel }))
417
+ }, renderBox(boxes))
415
418
  : null);
416
419
  }
417
420
  function useStructureRevision(editor) {
package/dist/react.d.ts CHANGED
@@ -30,7 +30,13 @@ declare class BlockRegistry {
30
30
  type DropPosition = 'before' | 'after' | 'inside';
31
31
 
32
32
  declare const CANVAS_CHANNEL = "nuforge-canvas";
33
- declare const CANVAS_PROTOCOL_VERSION = 1;
33
+ declare const CANVAS_PROTOCOL_VERSION = 2;
34
+ interface Rect {
35
+ top: number;
36
+ left: number;
37
+ width: number;
38
+ height: number;
39
+ }
34
40
  interface CanvasKeydownMessage {
35
41
  channel: typeof CANVAS_CHANNEL;
36
42
  v: typeof CANVAS_PROTOCOL_VERSION;
@@ -120,12 +126,6 @@ declare class Editor {
120
126
  private insertRelativeTo;
121
127
  }
122
128
 
123
- interface Rect {
124
- top: number;
125
- left: number;
126
- width: number;
127
- height: number;
128
- }
129
129
  interface CanvasWindow {
130
130
  doc: Document | null;
131
131
  window: Window | null;
package/dist/react.mjs CHANGED
@@ -5,10 +5,7 @@ import { createContext, memo, createElement, Fragment, useRef, useState, useEffe
5
5
  import { createPortal } from 'react-dom';
6
6
 
7
7
  const CANVAS_CHANNEL = 'nuforge-canvas';
8
- const CANVAS_PROTOCOL_VERSION = 1;
9
- function canvasChannelName(src) {
10
- return `${CANVAS_CHANNEL}:${src}`;
11
- }
8
+ const CANVAS_PROTOCOL_VERSION = 2;
12
9
  function isCanvasMessage(data) {
13
10
  return (!!data &&
14
11
  typeof data === 'object' &&
@@ -217,13 +214,14 @@ function IframeCanvas({ frame, selectedId = null, hoveredId = null, revision = 0
217
214
  }, renderBox({ selected, hovered, selectedLabel, drop }))
218
215
  : null);
219
216
  }
217
+ const NO_BOXES = {
218
+ selected: null,
219
+ hovered: null,
220
+ selectedLabel: null,
221
+ };
220
222
  function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hoveredId = null, revision = 0, previewMode = false, uiPayload, onSelect, onHover, onDropOnNode, onKeydownRelay, onLinkClick, className, containerClassName, renderBox, }) {
221
223
  const iframeRef = useRef(null);
222
- const [doc, setDoc] = useState(null);
223
- const [tick, setTick] = useState(0);
224
- const [selected, setSelected] = useState(null);
225
- const [hovered, setHovered] = useState(null);
226
- const [selectedLabel, setSelectedLabel] = useState(null);
224
+ const [boxes, setBoxes] = useState(NO_BOXES);
227
225
  const props = useRef({
228
226
  nu,
229
227
  activeComponent,
@@ -246,42 +244,14 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
246
244
  };
247
245
  const connectionRef = useRef(null);
248
246
  useEffect(() => {
249
- const iframe = iframeRef.current;
250
- if (!iframe) {
251
- return;
252
- }
253
- const onLoad = () => setDoc(iframe.contentDocument);
254
- iframe.addEventListener('load', onLoad);
255
- return () => iframe.removeEventListener('load', onLoad);
256
- }, [src]);
257
- useEffect(() => {
258
- if (!doc) {
259
- return;
260
- }
261
- const onScroll = () => setTick((x) => x + 1);
262
- doc.addEventListener('scroll', onScroll, true);
263
- return () => doc.removeEventListener('scroll', onScroll, true);
264
- }, [doc]);
265
- useEffect(() => {
266
- const iframe = iframeRef.current;
267
- if (!iframe) {
268
- return;
269
- }
270
- const observer = new ResizeObserver(() => setTick((x) => x + 1));
271
- observer.observe(iframe);
272
- if (doc?.documentElement) {
273
- observer.observe(doc.documentElement);
274
- }
275
- return () => observer.disconnect();
276
- }, [doc]);
277
- useEffect(() => {
278
- const channel = new BroadcastChannel(canvasChannelName(src));
279
- let ready = false;
280
- let mountGen = null;
247
+ let port = null;
248
+ let pendingNonce = null;
249
+ let connectedNonce = null;
281
250
  let rafId = null;
282
251
  let rev = 0;
252
+ const post = (message) => port?.postMessage(message);
283
253
  const sendInit = () => {
284
- channel.postMessage({
254
+ post({
285
255
  channel: CANVAS_CHANNEL,
286
256
  v: CANVAS_PROTOCOL_VERSION,
287
257
  type: 'init',
@@ -290,12 +260,12 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
290
260
  });
291
261
  };
292
262
  const requestPatch = () => {
293
- if (!ready || rafId !== null) {
263
+ if (connectedNonce === null || rafId !== null) {
294
264
  return;
295
265
  }
296
266
  rafId = requestAnimationFrame(() => {
297
267
  rafId = null;
298
- channel.postMessage({
268
+ post({
299
269
  channel: CANVAS_CHANNEL,
300
270
  v: CANVAS_PROTOCOL_VERSION,
301
271
  type: 'patch',
@@ -306,37 +276,51 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
306
276
  });
307
277
  };
308
278
  const postUi = (payload) => {
309
- channel.postMessage({
279
+ post({
310
280
  channel: CANVAS_CHANNEL,
311
281
  v: CANVAS_PROTOCOL_VERSION,
312
282
  type: 'ui',
313
283
  payload,
314
284
  });
315
285
  };
316
- connectionRef.current = { sendInit, requestPatch, postUi };
317
- const onMessage = (event) => {
286
+ const postTrack = (selectedId, hoveredId) => {
287
+ post({
288
+ channel: CANVAS_CHANNEL,
289
+ v: CANVAS_PROTOCOL_VERSION,
290
+ type: 'track',
291
+ selectedId,
292
+ hoveredId,
293
+ });
294
+ };
295
+ connectionRef.current = { sendInit, requestPatch, postUi, postTrack };
296
+ const onPortMessage = (event) => {
318
297
  if (!isCanvasMessage(event.data)) {
319
298
  return;
320
299
  }
321
300
  const message = event.data;
322
301
  const p = props.current;
323
302
  switch (message.type) {
324
- case 'ready':
325
- if (mountGen !== null && message.mountGen <= mountGen) {
303
+ case 'hello':
304
+ if (message.nonce !== pendingNonce) {
326
305
  return;
327
306
  }
328
- mountGen = message.mountGen;
329
- ready = true;
307
+ connectedNonce = message.nonce;
330
308
  sendInit();
309
+ postTrack(trackRef.current.selectedId, trackRef.current.hoveredId);
331
310
  postUi(props.current.uiPayload);
332
311
  break;
312
+ case 'rects':
313
+ setBoxes({
314
+ selected: message.selected,
315
+ hovered: message.hovered,
316
+ selectedLabel: message.selectedLabel,
317
+ });
318
+ break;
333
319
  case 'select':
334
320
  p.onSelect?.(message.nodeId);
335
- setTick((x) => x + 1);
336
321
  break;
337
322
  case 'hover':
338
323
  p.onHover?.(message.nodeId);
339
- setTick((x) => x + 1);
340
324
  break;
341
325
  case 'drop':
342
326
  p.onDropOnNode?.(message.targetId, message.position);
@@ -349,14 +333,39 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
349
333
  break;
350
334
  }
351
335
  };
352
- channel.addEventListener('message', onMessage);
353
- const unlistenChangeset = nu.listenToChangeset(() => {
354
- requestPatch();
355
- setTick((x) => x + 1);
356
- });
336
+ const onWindowMessage = (event) => {
337
+ const frame = iframeRef.current;
338
+ if (!frame || event.source !== frame.contentWindow) {
339
+ return;
340
+ }
341
+ if (!isCanvasMessage(event.data) || event.data.type !== 'ready') {
342
+ return;
343
+ }
344
+ const { nonce } = event.data;
345
+ if (connectedNonce === nonce) {
346
+ return;
347
+ }
348
+ port?.close();
349
+ const channel = new MessageChannel();
350
+ port = channel.port1;
351
+ pendingNonce = nonce;
352
+ connectedNonce = null;
353
+ port.addEventListener('message', onPortMessage);
354
+ port.start();
355
+ event.source.postMessage({
356
+ channel: CANVAS_CHANNEL,
357
+ v: CANVAS_PROTOCOL_VERSION,
358
+ type: 'connect',
359
+ nonce,
360
+ }, '*', [channel.port2]);
361
+ };
362
+ window.addEventListener('message', onWindowMessage);
363
+ const unlistenChangeset = nu.listenToChangeset(requestPatch);
357
364
  return () => {
358
- channel.removeEventListener('message', onMessage);
359
- channel.close();
365
+ window.removeEventListener('message', onWindowMessage);
366
+ port?.removeEventListener('message', onPortMessage);
367
+ port?.close();
368
+ port = null;
360
369
  unlistenChangeset();
361
370
  if (rafId !== null) {
362
371
  cancelAnimationFrame(rafId);
@@ -376,26 +385,20 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
376
385
  useEffect(() => {
377
386
  connectionRef.current?.postUi(uiPayload);
378
387
  }, [uiPayload]);
379
- const elementFor = (id) => doc && id ? doc.querySelector(`[data-nu-id="${id}"]`) : null;
380
- const rectOfEl = (el) => {
381
- if (!el) {
382
- return null;
383
- }
384
- const r = el.getBoundingClientRect();
385
- return { top: r.top, left: r.left, width: r.width, height: r.height };
386
- };
387
- useLayoutEffect(() => {
388
+ const trackRef = useRef({
389
+ selectedId: null,
390
+ hoveredId: null,
391
+ });
392
+ useEffect(() => {
393
+ const next = previewMode
394
+ ? { selectedId: null, hoveredId: null }
395
+ : { selectedId, hoveredId };
396
+ trackRef.current = next;
388
397
  if (previewMode) {
389
- setSelected(null);
390
- setHovered(null);
391
- setSelectedLabel(null);
392
- return;
398
+ setBoxes(NO_BOXES);
393
399
  }
394
- const selectedEl = elementFor(selectedId);
395
- setSelected(rectOfEl(selectedEl));
396
- setHovered(hoveredId && hoveredId !== selectedId ? rectOfEl(elementFor(hoveredId)) : null);
397
- setSelectedLabel(selectedEl ? selectedEl.tagName.toLowerCase() : null);
398
- }, [previewMode, selectedId, hoveredId, revision, tick, doc]);
400
+ connectionRef.current?.postTrack(next.selectedId, next.hoveredId);
401
+ }, [previewMode, selectedId, hoveredId, revision]);
399
402
  return createElement('div', { className: containerClassName, style: { position: 'relative' } }, createElement('iframe', {
400
403
  ref: iframeRef,
401
404
  title: 'nuforge canvas',
@@ -409,7 +412,7 @@ function RemoteCanvasHost({ nu, activeComponent, src, selectedId = null, hovered
409
412
  pointerEvents: 'none',
410
413
  overflow: 'hidden',
411
414
  },
412
- }, renderBox({ selected, hovered, selectedLabel }))
415
+ }, renderBox(boxes))
413
416
  : null);
414
417
  }
415
418
  function useStructureRevision(editor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuforge/editor",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "High-level SDK for building visual page editors on nuforge (mutations, blocks, commands, selection)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,8 +30,8 @@
30
30
  "react": "^18 || ^19",
31
31
  "react-dom": "^18 || ^19",
32
32
  "@nuforge/core": "0.1.5",
33
- "@nuforge/react": "0.1.5",
34
33
  "@nuforge/parser": "0.1.4",
34
+ "@nuforge/react": "0.1.5",
35
35
  "@nuforge/types": "0.1.4"
36
36
  },
37
37
  "peerDependenciesMeta": {
@@ -50,10 +50,10 @@
50
50
  "@types/react-dom": "^19.2.0",
51
51
  "react": "^19.2.0",
52
52
  "react-dom": "^19.2.0",
53
- "@nuforge/core": "0.1.5",
53
+ "@nuforge/types": "0.1.4",
54
54
  "@nuforge/parser": "0.1.4",
55
55
  "@nuforge/react": "0.1.5",
56
- "@nuforge/types": "0.1.4"
56
+ "@nuforge/core": "0.1.5"
57
57
  },
58
58
  "publishConfig": {
59
59
  "access": "public"