@ai-matrx/canvas 0.0.0-bootstrap.0 → 0.1.1

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/react.cjs ADDED
@@ -0,0 +1,1477 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ var __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (let key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(to, key) && key !== except)
18
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
23
+ // If the importer is in node compatibility mode or this is not an ESM
24
+ // file that has been converted to a CommonJS file using a Babel-
25
+ // compatible transform (i.e. "__esModule" has not been set), then set
26
+ // "default" to the CommonJS "module.exports" for node compatibility.
27
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
28
+ mod
29
+ ));
30
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
+
32
+ // src/react.ts
33
+ var react_exports = {};
34
+ __export(react_exports, {
35
+ CanvasColumn: () => CanvasColumn,
36
+ CanvasFrame: () => CanvasFrame,
37
+ CanvasPaneView: () => CanvasPaneView,
38
+ CanvasProvider: () => CanvasProvider,
39
+ CanvasToggle: () => CanvasToggle,
40
+ defineCanvasKind: () => defineCanvasKind,
41
+ getCanvasKind: () => getCanvasKind,
42
+ itemTitle: () => itemTitle,
43
+ listCanvasKinds: () => listCanvasKinds,
44
+ registerCanvasKind: () => registerCanvasKind,
45
+ registerCanvasKinds: () => registerCanvasKinds,
46
+ useCanvas: () => useCanvas,
47
+ useCanvasColumnWidth: () => useCanvasColumnWidth,
48
+ useCanvasHostPorts: () => useCanvasHostPorts,
49
+ useCanvasIsPresented: () => useCanvasIsPresented,
50
+ useCanvasKind: () => useCanvasKind,
51
+ useCanvasKinds: () => useCanvasKinds,
52
+ useCanvasState: () => useCanvasState,
53
+ useOptionalCanvas: () => useOptionalCanvas,
54
+ useOptionalCanvasState: () => useOptionalCanvasState
55
+ });
56
+ module.exports = __toCommonJS(react_exports);
57
+
58
+ // src/react/provider.tsx
59
+ var import_react = require("react");
60
+
61
+ // src/core/types.ts
62
+ var CANVAS_MIN_WIDTH = 360;
63
+ var CANVAS_DEFAULT_WIDTH = 640;
64
+ var CANVAS_MIN_SPLIT_FRACTION = 0.12;
65
+
66
+ // src/core/ids.ts
67
+ var SEP = "::";
68
+ function canvasItemId(kind, key) {
69
+ return `${kind}${SEP}${key}`;
70
+ }
71
+ function paneIdFromSeq(seq) {
72
+ return `pane-${seq}`;
73
+ }
74
+ function splitIdFromSeq(seq) {
75
+ return `split-${seq}`;
76
+ }
77
+ function findNonJson(value, path = "data") {
78
+ if (value === null) return null;
79
+ switch (typeof value) {
80
+ case "string":
81
+ case "boolean":
82
+ return null;
83
+ case "number":
84
+ return Number.isFinite(value) ? null : path;
85
+ case "object": {
86
+ if (Array.isArray(value)) {
87
+ for (let i = 0; i < value.length; i++) {
88
+ const bad = findNonJson(value[i], `${path}[${i}]`);
89
+ if (bad) return bad;
90
+ }
91
+ return null;
92
+ }
93
+ const proto = Object.getPrototypeOf(value);
94
+ if (proto !== Object.prototype && proto !== null) return path;
95
+ for (const [k, v] of Object.entries(value)) {
96
+ if (v === void 0) continue;
97
+ const bad = findNonJson(v, `${path}.${k}`);
98
+ if (bad) return bad;
99
+ }
100
+ return null;
101
+ }
102
+ default:
103
+ return path;
104
+ }
105
+ }
106
+
107
+ // src/core/layout.ts
108
+ function listPaneIds(node) {
109
+ if (node.type === "pane") return [node.paneId];
110
+ return node.children.flatMap(listPaneIds);
111
+ }
112
+ function evenSizes(count) {
113
+ return Array.from({ length: count }, () => 1 / count);
114
+ }
115
+ function normalizeSizes(sizes, count) {
116
+ if (sizes.length !== count || sizes.some((s) => !Number.isFinite(s) || s <= 0)) {
117
+ return evenSizes(count);
118
+ }
119
+ const total = sizes.reduce((sum, s) => sum + s, 0);
120
+ return sizes.map((s) => s / total);
121
+ }
122
+ function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
123
+ if (node.type === "pane") {
124
+ if (node.paneId !== paneId) return node;
125
+ return {
126
+ type: "split",
127
+ id: newSplitId,
128
+ orientation,
129
+ children: [node, { type: "pane", paneId: newPaneId }],
130
+ sizes: [0.5, 0.5]
131
+ };
132
+ }
133
+ const index = node.children.findIndex(
134
+ (child) => child.type === "pane" && child.paneId === paneId
135
+ );
136
+ if (index >= 0 && node.orientation === orientation) {
137
+ const share = node.sizes[index] ?? 1 / node.children.length;
138
+ const children = [...node.children];
139
+ children.splice(index + 1, 0, { type: "pane", paneId: newPaneId });
140
+ const sizes = [...node.sizes];
141
+ sizes.splice(index, 1, share / 2, share / 2);
142
+ return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
143
+ }
144
+ return {
145
+ ...node,
146
+ children: node.children.map(
147
+ (child) => splitPane(child, paneId, newPaneId, orientation, newSplitId)
148
+ )
149
+ };
150
+ }
151
+ function removePane(node, paneId) {
152
+ if (node.type === "pane") return node.paneId === paneId ? null : node;
153
+ const children = [];
154
+ const sizes = [];
155
+ node.children.forEach((child, i) => {
156
+ const next = removePane(child, paneId);
157
+ if (next) {
158
+ children.push(next);
159
+ sizes.push(node.sizes[i] ?? 1 / node.children.length);
160
+ }
161
+ });
162
+ if (children.length === 0) return null;
163
+ if (children.length === 1) return children[0] ?? null;
164
+ return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
165
+ }
166
+ function resizeSplit(node, splitId, sizes) {
167
+ if (node.type === "pane") return node;
168
+ if (node.id === splitId) {
169
+ const clamped = sizes.map((s) => Math.max(CANVAS_MIN_SPLIT_FRACTION, s));
170
+ return { ...node, sizes: normalizeSizes(clamped, node.children.length) };
171
+ }
172
+ return {
173
+ ...node,
174
+ children: node.children.map((child) => resizeSplit(child, splitId, sizes))
175
+ };
176
+ }
177
+ function isValidLayout(node, knownPanes, seen = /* @__PURE__ */ new Set()) {
178
+ if (!node || typeof node !== "object") return false;
179
+ const n = node;
180
+ if (n.type === "pane") {
181
+ const id = n.paneId;
182
+ if (typeof id !== "string" || !knownPanes.has(id) || seen.has(id)) return false;
183
+ seen.add(id);
184
+ return true;
185
+ }
186
+ if (n.type !== "split") return false;
187
+ if (typeof n.id !== "string") return false;
188
+ if (n.orientation !== "horizontal" && n.orientation !== "vertical") return false;
189
+ if (!Array.isArray(n.children) || n.children.length < 2) return false;
190
+ if (!Array.isArray(n.sizes) || n.sizes.length !== n.children.length) return false;
191
+ return n.children.every((child) => isValidLayout(child, knownPanes, seen));
192
+ }
193
+
194
+ // src/core/reducer.ts
195
+ var P = "matrxCanvas/";
196
+ var canvasActions = {
197
+ open: (input) => ({ type: `${P}open`, payload: { ...input, now: Date.now() } }),
198
+ update: (itemId, patch) => ({
199
+ type: `${P}update`,
200
+ payload: { itemId, ...patch, now: Date.now() }
201
+ }),
202
+ /** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
203
+ rekey: (itemId, key) => ({ type: `${P}rekey`, payload: { itemId, key } }),
204
+ closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
205
+ closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
206
+ activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
207
+ focusPane: (paneId) => ({ type: `${P}focusPane`, payload: { paneId } }),
208
+ moveItem: (itemId, toPaneId, index) => ({
209
+ type: `${P}moveItem`,
210
+ payload: { itemId, toPaneId, index }
211
+ }),
212
+ splitPane: (paneId, orientation, moveItemId) => ({
213
+ type: `${P}splitPane`,
214
+ payload: { paneId, orientation, moveItemId }
215
+ }),
216
+ closePane: (paneId) => ({ type: `${P}closePane`, payload: { paneId } }),
217
+ resizeSplit: (splitId, sizes) => ({
218
+ type: `${P}resizeSplit`,
219
+ payload: { splitId, sizes }
220
+ }),
221
+ setOpen: (open) => ({ type: `${P}setOpen`, payload: { open } }),
222
+ toggle: () => ({ type: `${P}toggle` }),
223
+ setFullscreen: (fullscreen) => ({ type: `${P}setFullscreen`, payload: { fullscreen } }),
224
+ setWidth: (width) => ({ type: `${P}setWidth`, payload: { width } }),
225
+ hydrate: (snapshot) => ({ type: `${P}hydrate`, payload: { snapshot } }),
226
+ reset: () => ({ type: `${P}reset` })
227
+ };
228
+ function isCanvasAction(action) {
229
+ return !!action && typeof action === "object" && typeof action.type === "string" && action.type.startsWith(P);
230
+ }
231
+ function createInitialCanvasState() {
232
+ const paneId = paneIdFromSeq(1);
233
+ return {
234
+ version: 1,
235
+ isOpen: false,
236
+ isFullscreen: false,
237
+ width: CANVAS_DEFAULT_WIDTH,
238
+ layout: { type: "pane", paneId },
239
+ panes: { [paneId]: { id: paneId, itemIds: [], activeItemId: null } },
240
+ items: {},
241
+ focusedPaneId: paneId,
242
+ seq: 1,
243
+ hydrated: false
244
+ };
245
+ }
246
+ function paneOf(state, itemId) {
247
+ for (const pane of Object.values(state.panes)) {
248
+ if (pane.itemIds.includes(itemId)) return pane;
249
+ }
250
+ return null;
251
+ }
252
+ function withPane(state, pane) {
253
+ return { ...state, panes: { ...state.panes, [pane.id]: pane } };
254
+ }
255
+ function nextActive(itemIds, removed, current) {
256
+ if (current !== removed) return current;
257
+ const index = itemIds.indexOf(removed);
258
+ const rest = itemIds.filter((id) => id !== removed);
259
+ return rest[Math.min(index, rest.length - 1)] ?? null;
260
+ }
261
+ function detachItem(state, itemId) {
262
+ const pane = paneOf(state, itemId);
263
+ if (!pane) return state;
264
+ return withPane(state, {
265
+ ...pane,
266
+ itemIds: pane.itemIds.filter((id) => id !== itemId),
267
+ activeItemId: nextActive(pane.itemIds, itemId, pane.activeItemId)
268
+ });
269
+ }
270
+ function dropPane(state, paneId) {
271
+ const layout = removePane(state.layout, paneId);
272
+ if (!layout) return state;
273
+ const panes = { ...state.panes };
274
+ delete panes[paneId];
275
+ const focusedPaneId = state.focusedPaneId === paneId ? listPaneIds(layout)[0] ?? state.focusedPaneId : state.focusedPaneId;
276
+ return { ...state, layout, panes, focusedPaneId };
277
+ }
278
+ function addSplit(state, paneId, orientation) {
279
+ const seq = state.seq + 1;
280
+ const newPaneId = paneIdFromSeq(seq);
281
+ const layout = splitPane(state.layout, paneId, newPaneId, orientation, splitIdFromSeq(seq));
282
+ return {
283
+ newPaneId,
284
+ state: {
285
+ ...state,
286
+ seq,
287
+ layout,
288
+ panes: { ...state.panes, [newPaneId]: { id: newPaneId, itemIds: [], activeItemId: null } },
289
+ focusedPaneId: newPaneId
290
+ }
291
+ };
292
+ }
293
+ function insertInto(state, paneId, itemId, activate, index) {
294
+ const pane = state.panes[paneId];
295
+ if (!pane) return state;
296
+ const itemIds = [...pane.itemIds.filter((id) => id !== itemId)];
297
+ const at = index === void 0 ? afterActive(itemIds, pane.activeItemId) : Math.max(0, Math.min(index, itemIds.length));
298
+ itemIds.splice(at, 0, itemId);
299
+ return withPane(state, {
300
+ ...pane,
301
+ itemIds,
302
+ activeItemId: activate || pane.activeItemId === null ? itemId : pane.activeItemId
303
+ });
304
+ }
305
+ function afterActive(itemIds, active) {
306
+ const index = active ? itemIds.indexOf(active) : -1;
307
+ return index < 0 ? itemIds.length : index + 1;
308
+ }
309
+ function clampWidth(width) {
310
+ return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : CANVAS_DEFAULT_WIDTH;
311
+ }
312
+ function sanitizeCanvasSnapshot(raw) {
313
+ if (!raw || typeof raw !== "object") return null;
314
+ const s = raw;
315
+ if (s.version !== 1 || !s.panes || !s.items || !s.layout) return null;
316
+ const paneIds = new Set(Object.keys(s.panes));
317
+ if (!isValidLayout(s.layout, paneIds)) return null;
318
+ const layoutPanes = new Set(listPaneIds(s.layout));
319
+ if (layoutPanes.size !== paneIds.size) return null;
320
+ const items = {};
321
+ for (const [id, item] of Object.entries(s.items)) {
322
+ if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
323
+ items[id] = item;
324
+ }
325
+ }
326
+ const panes = {};
327
+ for (const [id, pane] of Object.entries(s.panes)) {
328
+ const itemIds = (Array.isArray(pane.itemIds) ? pane.itemIds : []).filter((itemId) => itemId in items);
329
+ const activeItemId = pane.activeItemId && itemIds.includes(pane.activeItemId) ? pane.activeItemId : itemIds[0] ?? null;
330
+ panes[id] = { id, itemIds, activeItemId };
331
+ }
332
+ const focused = s.focusedPaneId && paneIds.has(s.focusedPaneId) ? s.focusedPaneId : listPaneIds(s.layout)[0];
333
+ if (!focused) return null;
334
+ const layout = normalizeLayout(s.layout);
335
+ const seq = Math.max(typeof s.seq === "number" && Number.isFinite(s.seq) ? s.seq : 0, highestIdNumber(layout));
336
+ return {
337
+ version: 1,
338
+ isOpen: s.isOpen === true,
339
+ // Full screen is a moment, not a preference: a reload never starts there.
340
+ isFullscreen: false,
341
+ width: clampWidth(typeof s.width === "number" ? s.width : CANVAS_DEFAULT_WIDTH),
342
+ layout,
343
+ panes,
344
+ items,
345
+ focusedPaneId: focused,
346
+ seq,
347
+ hydrated: true
348
+ };
349
+ }
350
+ function normalizeLayout(node) {
351
+ if (node.type === "pane") return node;
352
+ return { ...node, sizes: normalizeSizes(node.sizes, node.children.length), children: node.children.map(normalizeLayout) };
353
+ }
354
+ function highestIdNumber(node) {
355
+ const own = Number((node.type === "pane" ? node.paneId : node.id).split("-").pop());
356
+ const mine = Number.isFinite(own) ? own : 0;
357
+ return node.type === "pane" ? mine : Math.max(mine, ...node.children.map(highestIdNumber));
358
+ }
359
+ function canvasReducer(state = createInitialCanvasState(), action) {
360
+ if (!isCanvasAction(action)) return state;
361
+ switch (action.type) {
362
+ case `${P}open`: {
363
+ const { kind, key, title, data, target = "focused", reveal = true, activate = true, now } = action.payload;
364
+ const itemId = canvasItemId(kind, key);
365
+ const existing = state.items[itemId];
366
+ let next = state;
367
+ if (existing) {
368
+ const item = {
369
+ ...existing,
370
+ ...data !== void 0 ? { data } : {},
371
+ ...title !== void 0 ? { title } : {},
372
+ updatedAt: now
373
+ };
374
+ next = { ...next, items: { ...next.items, [itemId]: item } };
375
+ const pane = paneOf(next, itemId);
376
+ if (pane && activate) next = { ...withPane(next, { ...pane, activeItemId: itemId }), focusedPaneId: pane.id };
377
+ if (!pane) next = insertInto(next, next.focusedPaneId, itemId, true);
378
+ } else {
379
+ const item = { id: itemId, kind, key, title: title ?? null, data: data ?? null, openedAt: now, updatedAt: now };
380
+ next = { ...next, items: { ...next.items, [itemId]: item } };
381
+ let paneId = next.focusedPaneId;
382
+ if (typeof target === "object" && next.panes[target.paneId]) {
383
+ paneId = target.paneId;
384
+ } else if (target === "split-right" || target === "split-down") {
385
+ const split = addSplit(next, next.focusedPaneId, target === "split-right" ? "horizontal" : "vertical");
386
+ next = split.state;
387
+ paneId = split.newPaneId;
388
+ }
389
+ next = insertInto(next, paneId, itemId, activate);
390
+ if (activate) next = { ...next, focusedPaneId: paneId };
391
+ }
392
+ return reveal ? { ...next, isOpen: true } : next;
393
+ }
394
+ case `${P}update`: {
395
+ const { itemId, data, title, now } = action.payload;
396
+ const item = state.items[itemId];
397
+ if (!item) return state;
398
+ return {
399
+ ...state,
400
+ items: {
401
+ ...state.items,
402
+ [itemId]: { ...item, ...data !== void 0 ? { data } : {}, ...title !== void 0 ? { title } : {}, updatedAt: now }
403
+ }
404
+ };
405
+ }
406
+ case `${P}rekey`: {
407
+ const { itemId, key } = action.payload;
408
+ const item = state.items[itemId];
409
+ if (!item || item.key === key) return state;
410
+ const nextId = canvasItemId(item.kind, key);
411
+ if (state.items[nextId]) return canvasReducer(canvasReducer(state, canvasActions.closeItem(itemId)), canvasActions.activate(nextId));
412
+ const items = { ...state.items, [nextId]: { ...item, id: nextId, key } };
413
+ delete items[itemId];
414
+ const panes = {};
415
+ for (const pane of Object.values(state.panes)) {
416
+ panes[pane.id] = pane.itemIds.includes(itemId) ? {
417
+ ...pane,
418
+ itemIds: pane.itemIds.map((id) => id === itemId ? nextId : id),
419
+ activeItemId: pane.activeItemId === itemId ? nextId : pane.activeItemId
420
+ } : pane;
421
+ }
422
+ return { ...state, items, panes };
423
+ }
424
+ case `${P}closeItem`: {
425
+ const { itemId } = action.payload;
426
+ if (!state.items[itemId]) return state;
427
+ const pane = paneOf(state, itemId);
428
+ let next = detachItem(state, itemId);
429
+ const items = { ...next.items };
430
+ delete items[itemId];
431
+ next = { ...next, items };
432
+ if (pane && next.panes[pane.id]?.itemIds.length === 0) next = dropPane(next, pane.id);
433
+ return next;
434
+ }
435
+ case `${P}closeOthers`: {
436
+ const { itemId } = action.payload;
437
+ const pane = paneOf(state, itemId);
438
+ if (!pane) return state;
439
+ const items = { ...state.items };
440
+ for (const id of pane.itemIds) if (id !== itemId) delete items[id];
441
+ return { ...withPane(state, { ...pane, itemIds: [itemId], activeItemId: itemId }), items };
442
+ }
443
+ case `${P}activate`: {
444
+ const pane = paneOf(state, action.payload.itemId);
445
+ if (!pane) return state;
446
+ return { ...withPane(state, { ...pane, activeItemId: action.payload.itemId }), focusedPaneId: pane.id };
447
+ }
448
+ case `${P}focusPane`:
449
+ return state.panes[action.payload.paneId] ? { ...state, focusedPaneId: action.payload.paneId } : state;
450
+ case `${P}moveItem`: {
451
+ const { itemId, toPaneId, index } = action.payload;
452
+ const from = paneOf(state, itemId);
453
+ if (!from || !state.panes[toPaneId]) return state;
454
+ if (from.id === toPaneId && index === void 0) return state.focusedPaneId === toPaneId ? state : { ...state, focusedPaneId: toPaneId };
455
+ let next = from.id === toPaneId ? state : detachItem(state, itemId);
456
+ next = insertInto(next, toPaneId, itemId, true, index);
457
+ next = { ...next, focusedPaneId: toPaneId };
458
+ if (from.id !== toPaneId && next.panes[from.id]?.itemIds.length === 0) next = dropPane(next, from.id);
459
+ return next;
460
+ }
461
+ case `${P}splitPane`: {
462
+ const { paneId, orientation, moveItemId } = action.payload;
463
+ const pane = state.panes[paneId];
464
+ if (!pane) return state;
465
+ const split = addSplit(state, paneId, orientation);
466
+ let next = split.state;
467
+ if (moveItemId && pane.itemIds.includes(moveItemId) && pane.itemIds.length > 1) {
468
+ next = detachItem(next, moveItemId);
469
+ next = insertInto(next, split.newPaneId, moveItemId, true);
470
+ }
471
+ return next;
472
+ }
473
+ case `${P}closePane`: {
474
+ const pane = state.panes[action.payload.paneId];
475
+ if (!pane) return state;
476
+ const onlyPane = listPaneIds(state.layout).length === 1;
477
+ if (onlyPane) return { ...state, isOpen: false, isFullscreen: false };
478
+ const items = { ...state.items };
479
+ for (const id of pane.itemIds) delete items[id];
480
+ return dropPane({ ...state, items }, pane.id);
481
+ }
482
+ case `${P}resizeSplit`:
483
+ return { ...state, layout: resizeSplit(state.layout, action.payload.splitId, action.payload.sizes) };
484
+ case `${P}setOpen`:
485
+ return action.payload.open === state.isOpen ? state : { ...state, isOpen: action.payload.open, isFullscreen: action.payload.open ? state.isFullscreen : false };
486
+ case `${P}toggle`:
487
+ return { ...state, isOpen: !state.isOpen, isFullscreen: state.isOpen ? false : state.isFullscreen };
488
+ case `${P}setFullscreen`:
489
+ return { ...state, isFullscreen: action.payload.fullscreen, isOpen: action.payload.fullscreen ? true : state.isOpen };
490
+ case `${P}setWidth`:
491
+ return { ...state, width: clampWidth(action.payload.width) };
492
+ case `${P}hydrate`: {
493
+ const snapshot = action.payload.snapshot ? sanitizeCanvasSnapshot(action.payload.snapshot) : null;
494
+ if (!snapshot) return { ...state, hydrated: true };
495
+ let next = snapshot;
496
+ for (const item of Object.values(state.items)) {
497
+ const pane = paneOf(state, item.id);
498
+ next = { ...next, items: { ...next.items, [item.id]: item } };
499
+ if (!paneOf(next, item.id)) next = insertInto(next, next.focusedPaneId, item.id, pane?.activeItemId === item.id);
500
+ }
501
+ return { ...next, isOpen: next.isOpen || state.isOpen, hydrated: true };
502
+ }
503
+ case `${P}reset`:
504
+ return { ...createInitialCanvasState(), hydrated: true };
505
+ default:
506
+ return state;
507
+ }
508
+ }
509
+
510
+ // src/core/store.ts
511
+ function createCanvasStore(initial) {
512
+ let state = initial ?? createInitialCanvasState();
513
+ const listeners = /* @__PURE__ */ new Set();
514
+ return {
515
+ getState: () => state,
516
+ dispatch(action) {
517
+ const next = canvasReducer(state, action);
518
+ if (next === state) return;
519
+ state = next;
520
+ for (const listener of [...listeners]) listener();
521
+ },
522
+ subscribe(listener) {
523
+ listeners.add(listener);
524
+ return () => listeners.delete(listener);
525
+ }
526
+ };
527
+ }
528
+
529
+ // src/core/persistence.ts
530
+ var CANVAS_STORAGE_KEY = "ai-matrx.canvas.v1";
531
+ function createLocalStorageCanvasPersistence(key = CANVAS_STORAGE_KEY, storage = typeof window === "undefined" ? null : safeLocalStorage()) {
532
+ return {
533
+ load() {
534
+ if (!storage) throw new Error("Canvas storage is unavailable; allow browser storage or provide a persistence port.");
535
+ const raw = storage.getItem(key);
536
+ if (!raw) return null;
537
+ return JSON.parse(raw);
538
+ },
539
+ save(snapshot) {
540
+ if (!storage) throw new Error("Canvas storage is unavailable; allow browser storage or provide a persistence port.");
541
+ storage.setItem(key, JSON.stringify(snapshot));
542
+ }
543
+ };
544
+ }
545
+ function safeLocalStorage() {
546
+ try {
547
+ const probe = "__mxc_probe__";
548
+ window.localStorage.setItem(probe, "1");
549
+ window.localStorage.removeItem(probe);
550
+ return window.localStorage;
551
+ } catch {
552
+ return null;
553
+ }
554
+ }
555
+ function toPersistableSnapshot(state, isRestorable) {
556
+ const keep = new Set(
557
+ Object.values(state.items).filter((item) => isRestorable(item.kind)).map((item) => item.id)
558
+ );
559
+ if (keep.size === Object.keys(state.items).length) return state;
560
+ const items = Object.fromEntries(Object.entries(state.items).filter(([id]) => keep.has(id)));
561
+ let layout = state.layout;
562
+ const panes = {};
563
+ for (const pane of Object.values(state.panes)) {
564
+ const itemIds = pane.itemIds.filter((id) => keep.has(id));
565
+ if (itemIds.length === 0 && pane.itemIds.length > 0) {
566
+ const pruned = removePane(layout, pane.id);
567
+ if (pruned) {
568
+ layout = pruned;
569
+ continue;
570
+ }
571
+ }
572
+ panes[pane.id] = {
573
+ ...pane,
574
+ itemIds,
575
+ activeItemId: pane.activeItemId && keep.has(pane.activeItemId) ? pane.activeItemId : itemIds[0] ?? null
576
+ };
577
+ }
578
+ const focusedPaneId = panes[state.focusedPaneId] ? state.focusedPaneId : Object.values(panes)[0]?.id ?? state.focusedPaneId;
579
+ return { ...state, items, panes, layout, focusedPaneId };
580
+ }
581
+
582
+ // src/core/controller.ts
583
+ var consoleCanvasErrorSink = (report) => {
584
+ console.error(`[@ai-matrx/canvas] ${report.code}: ${report.message}`, report.detail ?? "");
585
+ };
586
+ function createCanvasController(options) {
587
+ const { store, persistence = null } = options;
588
+ const onError = options.onError ?? consoleCanvasErrorSink;
589
+ const isRestorable = options.isRestorable ?? (() => true);
590
+ const dispatch = (action) => store.dispatch(action);
591
+ let presentations = 0;
592
+ const presentationListeners = /* @__PURE__ */ new Set();
593
+ const notifyPresentation = () => {
594
+ for (const listener of [...presentationListeners]) listener();
595
+ };
596
+ const controller = {
597
+ store,
598
+ getState: () => store.getState(),
599
+ open(input) {
600
+ if (options.isKnownKind && !options.isKnownKind(input.kind)) {
601
+ onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
602
+ return null;
603
+ }
604
+ if (input.data !== void 0) {
605
+ const bad = findNonJson(input.data);
606
+ if (bad) {
607
+ onError({
608
+ code: "non-json-data",
609
+ message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
610
+ detail: { kind: input.kind, key: input.key }
611
+ });
612
+ return null;
613
+ }
614
+ }
615
+ dispatch(canvasActions.open(input));
616
+ return canvasItemId(input.kind, input.key);
617
+ },
618
+ update(itemId, patch) {
619
+ if (patch.data !== void 0) {
620
+ const bad = findNonJson(patch.data);
621
+ if (bad) {
622
+ onError({ code: "non-json-data", message: `Canvas update for ${itemId} must be plain JSON; ${bad} is not.` });
623
+ return false;
624
+ }
625
+ }
626
+ if (!store.getState().items[itemId]) return false;
627
+ dispatch(canvasActions.update(itemId, patch));
628
+ return true;
629
+ },
630
+ rekey(itemId, key) {
631
+ const item = store.getState().items[itemId];
632
+ if (!item) return null;
633
+ dispatch(canvasActions.rekey(itemId, key));
634
+ return canvasItemId(item.kind, key);
635
+ },
636
+ close: (itemId) => dispatch(canvasActions.closeItem(itemId)),
637
+ closeOthers: (itemId) => dispatch(canvasActions.closeOthers(itemId)),
638
+ activate: (itemId) => dispatch(canvasActions.activate(itemId)),
639
+ focusPane: (paneId) => dispatch(canvasActions.focusPane(paneId)),
640
+ moveItem: (itemId, toPaneId, index) => dispatch(canvasActions.moveItem(itemId, toPaneId, index)),
641
+ splitPane: (paneId, orientation, moveItemId) => dispatch(canvasActions.splitPane(paneId, orientation, moveItemId)),
642
+ closePane: (paneId) => dispatch(canvasActions.closePane(paneId)),
643
+ resizeSplit: (splitId, sizes) => dispatch(canvasActions.resizeSplit(splitId, sizes)),
644
+ show: () => dispatch(canvasActions.setOpen(true)),
645
+ hide: () => dispatch(canvasActions.setOpen(false)),
646
+ toggle: () => dispatch(canvasActions.toggle()),
647
+ setFullscreen: (fullscreen) => dispatch(canvasActions.setFullscreen(fullscreen)),
648
+ setWidth: (width) => dispatch(canvasActions.setWidth(width)),
649
+ has: (kind, key) => canvasItemId(kind, key) in store.getState().items,
650
+ isPresented: () => presentations > 0,
651
+ registerPresentation() {
652
+ presentations += 1;
653
+ notifyPresentation();
654
+ let done = false;
655
+ return () => {
656
+ if (done) return;
657
+ done = true;
658
+ presentations -= 1;
659
+ notifyPresentation();
660
+ };
661
+ },
662
+ subscribePresentation(listener) {
663
+ presentationListeners.add(listener);
664
+ return () => presentationListeners.delete(listener);
665
+ },
666
+ start() {
667
+ let disposed = false;
668
+ let timer = null;
669
+ let lastSaved = null;
670
+ const save = () => {
671
+ timer = null;
672
+ const state = store.getState();
673
+ if (!persistence || !state.hydrated || state === lastSaved) return;
674
+ lastSaved = state;
675
+ try {
676
+ void Promise.resolve(persistence.save(toPersistableSnapshot(state, isRestorable))).catch(
677
+ (error) => onError({ code: "persistence-save", message: "Saving the canvas layout failed.", detail: error })
678
+ );
679
+ } catch (error) {
680
+ onError({ code: "persistence-save", message: "Saving the canvas layout failed.", detail: error });
681
+ }
682
+ };
683
+ let seen = store.getState();
684
+ const unsubscribe = store.subscribe(() => {
685
+ if (!persistence || disposed) return;
686
+ const current = store.getState();
687
+ if (current === seen) return;
688
+ seen = current;
689
+ if (timer) clearTimeout(timer);
690
+ timer = setTimeout(save, options.saveDelayMs ?? 250);
691
+ });
692
+ if (store.getState().hydrated) {
693
+ } else if (!persistence) {
694
+ dispatch(canvasActions.hydrate(null));
695
+ } else {
696
+ Promise.resolve().then(() => persistence.load()).then(
697
+ (snapshot) => {
698
+ if (disposed) return;
699
+ const openedThisSession = store.getState().isOpen;
700
+ const validSnapshot = snapshot === null ? null : sanitizeCanvasSnapshot(snapshot);
701
+ if (snapshot !== null && validSnapshot === null) {
702
+ onError({ code: "persistence-load", message: "The saved canvas layout is invalid. Clear the saved layout or repair the persistence port." });
703
+ }
704
+ dispatch(canvasActions.hydrate(validSnapshot));
705
+ if (!openedThisSession && options.mayRestoreOpen && !options.mayRestoreOpen() && store.getState().isOpen) {
706
+ dispatch(canvasActions.setOpen(false));
707
+ }
708
+ },
709
+ (error) => {
710
+ onError({ code: "persistence-load", message: "The saved canvas layout could not be read.", detail: error });
711
+ if (!disposed) dispatch(canvasActions.hydrate(null));
712
+ }
713
+ );
714
+ }
715
+ return () => {
716
+ disposed = true;
717
+ unsubscribe();
718
+ if (timer) {
719
+ clearTimeout(timer);
720
+ save();
721
+ }
722
+ };
723
+ }
724
+ };
725
+ return controller;
726
+ }
727
+
728
+ // src/core/selectors.ts
729
+ var selectCanvasIsOpen = (s) => s.isOpen;
730
+ var selectCanvasIsFullscreen = (s) => s.isFullscreen;
731
+ var selectCanvasWidth = (s) => s.width;
732
+ var selectCanvasLayout = (s) => s.layout;
733
+ var selectCanvasItemCount = (s) => Object.keys(s.items).length;
734
+ function selectCanvasPaneCount(s) {
735
+ return listPaneIds(s.layout).length;
736
+ }
737
+
738
+ // src/react/registry.ts
739
+ function defineCanvasKind(kind) {
740
+ if (!kind.component && !kind.load) {
741
+ throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" needs a component or a load().`);
742
+ }
743
+ return kind;
744
+ }
745
+ var REGISTRY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.kinds");
746
+ function registry() {
747
+ const g = globalThis;
748
+ g[REGISTRY] ??= { kinds: /* @__PURE__ */ new Map(), listeners: /* @__PURE__ */ new Set(), version: 0 };
749
+ return g[REGISTRY];
750
+ }
751
+ function notify(state) {
752
+ state.version += 1;
753
+ for (const listener of [...state.listeners]) listener();
754
+ }
755
+ function registerCanvasKind(kind) {
756
+ const state = registry();
757
+ state.kinds.set(kind.id, kind);
758
+ notify(state);
759
+ return () => {
760
+ if (state.kinds.get(kind.id) === kind) {
761
+ state.kinds.delete(kind.id);
762
+ notify(state);
763
+ }
764
+ };
765
+ }
766
+ function registerCanvasKinds(kinds) {
767
+ const undo = kinds.map(registerCanvasKind);
768
+ return () => undo.forEach((fn) => fn());
769
+ }
770
+ function getCanvasKind(id) {
771
+ return registry().kinds.get(id);
772
+ }
773
+ function listCanvasKinds() {
774
+ return [...registry().kinds.values()];
775
+ }
776
+ function subscribeCanvasKinds(listener) {
777
+ const state = registry();
778
+ state.listeners.add(listener);
779
+ return () => state.listeners.delete(listener);
780
+ }
781
+ function getCanvasKindsVersion() {
782
+ return registry().version;
783
+ }
784
+
785
+ // src/react/provider.tsx
786
+ var import_jsx_runtime = require("react/jsx-runtime");
787
+ var PHONE_MAX_WIDTH = 767;
788
+ var isPhoneViewport = () => typeof window !== "undefined" && window.innerWidth <= PHONE_MAX_WIDTH;
789
+ function escapeBelongsElsewhere(event) {
790
+ if (event.defaultPrevented) return true;
791
+ const active = typeof document === "undefined" ? null : document.activeElement;
792
+ return !!active?.closest?.('[role="menu"], [role="dialog"], [role="alertdialog"], [role="listbox"]');
793
+ }
794
+ var CanvasContext = (0, import_react.createContext)(null);
795
+ function CanvasProvider({
796
+ children,
797
+ store,
798
+ persistence,
799
+ onError,
800
+ hotkeys = true,
801
+ itemActions,
802
+ popOut
803
+ }) {
804
+ const [controller] = (0, import_react.useState)(
805
+ () => createCanvasController({
806
+ store: store ?? createCanvasStore(),
807
+ persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
808
+ isRestorable: (kind) => getCanvasKind(kind)?.restore !== false,
809
+ mayRestoreOpen: () => !isPhoneViewport(),
810
+ onError
811
+ })
812
+ );
813
+ (0, import_react.useEffect)(() => controller.start(), [controller]);
814
+ (0, import_react.useEffect)(() => {
815
+ if (!hotkeys) return;
816
+ const onKey = (event) => {
817
+ if (!controller.isPresented()) return;
818
+ if (event.key === "\\" && (event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey) {
819
+ event.preventDefault();
820
+ controller.toggle();
821
+ } else if (event.key === "Escape" && controller.getState().isFullscreen && !escapeBelongsElsewhere(event)) {
822
+ controller.setFullscreen(false);
823
+ }
824
+ };
825
+ window.addEventListener("keydown", onKey);
826
+ return () => window.removeEventListener("keydown", onKey);
827
+ }, [controller, hotkeys]);
828
+ const ports = {
829
+ ...itemActions ? { itemActions } : {},
830
+ ...popOut ? { popOut } : {}
831
+ };
832
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CanvasContext.Provider, { value: { controller, ports }, children });
833
+ }
834
+ function useCanvasContext() {
835
+ const value = (0, import_react.useContext)(CanvasContext);
836
+ if (!value) throw new Error("[@ai-matrx/canvas] useCanvas* must be used inside <CanvasProvider>.");
837
+ return value;
838
+ }
839
+ function useCanvas() {
840
+ return useCanvasContext().controller;
841
+ }
842
+ function useOptionalCanvas() {
843
+ return (0, import_react.useContext)(CanvasContext)?.controller ?? null;
844
+ }
845
+ function useCanvasHostPorts() {
846
+ return useCanvasContext().ports;
847
+ }
848
+ function useRegisterCanvasPresence() {
849
+ const controller = (0, import_react.useContext)(CanvasContext)?.controller;
850
+ (0, import_react.useEffect)(() => controller?.registerPresentation(), [controller]);
851
+ }
852
+ var noPresence = () => () => void 0;
853
+ function useCanvasIsPresented() {
854
+ const controller = (0, import_react.useContext)(CanvasContext)?.controller;
855
+ return (0, import_react.useSyncExternalStore)(
856
+ controller ? controller.subscribePresentation : noPresence,
857
+ () => controller?.isPresented() ?? false,
858
+ () => false
859
+ );
860
+ }
861
+ function useCanvasState(selector) {
862
+ const { store } = useCanvasContext().controller;
863
+ return (0, import_react.useSyncExternalStore)(
864
+ store.subscribe,
865
+ () => selector(store.getState()),
866
+ () => selector(store.getState())
867
+ );
868
+ }
869
+ var NO_SUBSCRIPTION = () => () => void 0;
870
+ function useOptionalCanvasState(selector, fallback) {
871
+ const store = (0, import_react.useContext)(CanvasContext)?.controller.store;
872
+ const read = () => store ? selector(store.getState()) : fallback;
873
+ return (0, import_react.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, read, read);
874
+ }
875
+ function useCanvasKinds() {
876
+ (0, import_react.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
877
+ return listCanvasKinds();
878
+ }
879
+ function useCanvasKind(id) {
880
+ (0, import_react.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
881
+ return getCanvasKind(id);
882
+ }
883
+
884
+ // src/react/CanvasColumn.tsx
885
+ var import_react3 = require("react");
886
+ var import_tap_target2 = require("@ai-matrx/tap-target");
887
+
888
+ // src/react/CanvasPane.tsx
889
+ var import_react2 = require("react");
890
+ var import_tap_target = require("@ai-matrx/tap-target");
891
+ var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
892
+
893
+ // src/react/icons.tsx
894
+ var import_jsx_runtime2 = require("react/jsx-runtime");
895
+ function Glyph({ children, ...props }) {
896
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
897
+ "svg",
898
+ {
899
+ xmlns: "http://www.w3.org/2000/svg",
900
+ width: "16",
901
+ height: "16",
902
+ viewBox: "0 0 24 24",
903
+ fill: "none",
904
+ stroke: "currentColor",
905
+ strokeWidth: 2,
906
+ strokeLinecap: "round",
907
+ strokeLinejoin: "round",
908
+ "aria-hidden": "true",
909
+ ...props,
910
+ children
911
+ }
912
+ );
913
+ }
914
+ var CloseIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
915
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 6 6 18" }),
916
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 6 12 12" })
917
+ ] });
918
+ var MaximizeIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
919
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 3h6v6" }),
920
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21 3-7 7" }),
921
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m3 21 7-7" }),
922
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M9 21H3v-6" })
923
+ ] });
924
+ var MinimizeIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
925
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m14 10 7-7" }),
926
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20 10h-6V4" }),
927
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m3 21 7-7" }),
928
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 14h6v6" })
929
+ ] });
930
+ var MoreIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
931
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
932
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "5", r: "1" }),
933
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "19", r: "1" })
934
+ ] });
935
+ var SplitRightIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
936
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 3v18" })
938
+ ] });
939
+ var SplitDownIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
940
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
941
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 12h18" })
942
+ ] });
943
+ var PanelRightIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
944
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
945
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 3v18" })
946
+ ] });
947
+ var PopOutIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
948
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 3h6v6" }),
949
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 14 21 3" }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
951
+ ] });
952
+ var AlertIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Glyph, { ...p, children: [
953
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
954
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 8v4" }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 16h.01" })
956
+ ] });
957
+
958
+ // src/react/CanvasPane.tsx
959
+ var import_jsx_runtime3 = require("react/jsx-runtime");
960
+ var DRAG_MIME = "application/x-matrx-canvas-item";
961
+ var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
962
+ function kindComponent(kind) {
963
+ if (kind.component) return kind.component;
964
+ const holder = kind;
965
+ if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react2.lazy)(kind.load);
966
+ const resolved = holder[LAZY];
967
+ if (!resolved) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
968
+ return resolved;
969
+ }
970
+ function resetKindComponent(kind) {
971
+ if (kind) delete kind[LAZY];
972
+ }
973
+ function itemTitle(item, kind) {
974
+ if (item.title) return item.title;
975
+ if (kind?.title) {
976
+ try {
977
+ const title = kind.title(item.data, item);
978
+ if (title) return title;
979
+ } catch {
980
+ }
981
+ }
982
+ return kind?.label ?? item.kind;
983
+ }
984
+ function CanvasPaneView({ paneId }) {
985
+ const canvas = useCanvas();
986
+ const pane = useCanvasState((s) => s.panes[paneId]);
987
+ const items = useCanvasState((s) => s.items);
988
+ const focused = useCanvasState((s) => s.focusedPaneId === paneId);
989
+ const [dropActive, setDropActive] = (0, import_react2.useState)(false);
990
+ if (!pane) return null;
991
+ const active = pane.activeItemId ? items[pane.activeItemId] : void 0;
992
+ const onDragOver = (event) => {
993
+ if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
994
+ event.preventDefault();
995
+ event.dataTransfer.dropEffect = "move";
996
+ setDropActive(true);
997
+ };
998
+ const onDrop = (event) => {
999
+ setDropActive(false);
1000
+ const itemId = event.dataTransfer.getData(DRAG_MIME);
1001
+ if (!itemId) return;
1002
+ event.preventDefault();
1003
+ canvas.moveItem(itemId, paneId);
1004
+ };
1005
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1006
+ "section",
1007
+ {
1008
+ className: "mxc-pane",
1009
+ "data-focused": focused ? "" : void 0,
1010
+ "data-drop": dropActive ? "" : void 0,
1011
+ onPointerDownCapture: () => {
1012
+ if (!focused) canvas.focusPane(paneId);
1013
+ },
1014
+ onDragOver,
1015
+ onDragLeave: () => setDropActive(false),
1016
+ onDrop,
1017
+ children: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaneHeader, { paneId, itemIds: pane.itemIds, activeItemId: pane.activeItemId, activeItem: active }),
1019
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mxc-pane-body", children: pane.itemIds.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Launcher, { paneId }) : pane.itemIds.map((itemId) => {
1020
+ const item = items[itemId];
1021
+ if (!item) return null;
1022
+ const isActive = itemId === pane.activeItemId;
1023
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ItemBody, { item, paneId, isActive, isFocused: focused && isActive }, itemId);
1024
+ }) })
1025
+ ]
1026
+ }
1027
+ );
1028
+ }
1029
+ function PaneHeader({
1030
+ paneId,
1031
+ itemIds,
1032
+ activeItemId,
1033
+ activeItem
1034
+ }) {
1035
+ const canvas = useCanvas();
1036
+ const isFullscreen = useCanvasState((s) => s.isFullscreen);
1037
+ const paneCount = useCanvasState(selectCanvasPaneCount);
1038
+ const kind = useCanvasKind(activeItem?.kind ?? "");
1039
+ const kindProps = activeItem && kind ? { item: activeItem, data: activeItem.data, paneId, isFocused: true, canvas } : null;
1040
+ const HeaderAction = kind?.HeaderAction;
1041
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "mxc-pane-header", children: [
1042
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mxc-tabs", role: "tablist", "aria-label": "Canvas tabs", children: itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Tab, { itemId, paneId, index, active: itemId === activeItemId }, itemId)) }),
1043
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mxc-pane-actions", children: [
1044
+ HeaderAction && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HeaderAction, { ...kindProps }) : null,
1045
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaneMenu, { paneId, activeItem, kind, kindProps, canSplitMove: itemIds.length > 1 }),
1046
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1047
+ import_tap_target.TapTargetButtonTransparent,
1048
+ {
1049
+ className: "mxc-desktop-only",
1050
+ ariaLabel: isFullscreen ? "Exit full screen" : "Full screen",
1051
+ icon: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MinimizeIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MaximizeIcon, {}),
1052
+ onClick: () => canvas.setFullscreen(!isFullscreen)
1053
+ }
1054
+ ),
1055
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1056
+ import_tap_target.TapTargetButtonTransparent,
1057
+ {
1058
+ className: "mxc-phone-only",
1059
+ ariaLabel: "Hide canvas",
1060
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PanelRightIcon, {}),
1061
+ onClick: () => canvas.hide()
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1065
+ import_tap_target.TapTargetButtonTransparent,
1066
+ {
1067
+ ariaLabel: paneCount > 1 ? "Close pane" : "Close canvas",
1068
+ icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, {}),
1069
+ onClick: () => canvas.closePane(paneId)
1070
+ }
1071
+ )
1072
+ ] })
1073
+ ] });
1074
+ }
1075
+ function Tab({
1076
+ itemId,
1077
+ paneId,
1078
+ index,
1079
+ active
1080
+ }) {
1081
+ const canvas = useCanvas();
1082
+ const item = useCanvasState((s) => s.items[itemId]);
1083
+ const kind = useCanvasKind(item?.kind ?? "");
1084
+ const ref = (0, import_react2.useRef)(null);
1085
+ (0, import_react2.useEffect)(() => {
1086
+ if (active) ref.current?.scrollIntoView({ block: "nearest", inline: "nearest" });
1087
+ }, [active]);
1088
+ if (!item) return null;
1089
+ const Icon = kind?.icon;
1090
+ const title = itemTitle(item, kind);
1091
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1092
+ "div",
1093
+ {
1094
+ ref,
1095
+ className: "mxc-tab",
1096
+ role: "tab",
1097
+ "aria-selected": active,
1098
+ "data-active": active ? "" : void 0,
1099
+ tabIndex: active ? 0 : -1,
1100
+ title,
1101
+ draggable: true,
1102
+ onDragStart: (event) => {
1103
+ event.dataTransfer.setData(DRAG_MIME, itemId);
1104
+ event.dataTransfer.effectAllowed = "move";
1105
+ },
1106
+ onDragOver: (event) => {
1107
+ if (event.dataTransfer.types.includes(DRAG_MIME)) event.preventDefault();
1108
+ },
1109
+ onDrop: (event) => {
1110
+ const dragged = event.dataTransfer.getData(DRAG_MIME);
1111
+ if (!dragged) return;
1112
+ event.preventDefault();
1113
+ event.stopPropagation();
1114
+ if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
1115
+ },
1116
+ onClick: () => canvas.activate(itemId),
1117
+ onAuxClick: (event) => {
1118
+ if (event.button === 1) canvas.close(itemId);
1119
+ },
1120
+ onKeyDown: (event) => {
1121
+ if (event.key === "Enter" || event.key === " ") canvas.activate(itemId);
1122
+ },
1123
+ children: [
1124
+ Icon ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "mxc-tab-icon" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, { className: "mxc-tab-icon" }),
1125
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mxc-tab-title", children: title }),
1126
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1127
+ "button",
1128
+ {
1129
+ type: "button",
1130
+ className: "mxc-tab-close",
1131
+ "aria-label": `Close ${title}`,
1132
+ onClick: (event) => {
1133
+ event.stopPropagation();
1134
+ canvas.close(itemId);
1135
+ },
1136
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 12, height: 12 })
1137
+ }
1138
+ )
1139
+ ]
1140
+ }
1141
+ );
1142
+ }
1143
+ function PaneMenu({
1144
+ paneId,
1145
+ activeItem,
1146
+ kind,
1147
+ kindProps,
1148
+ canSplitMove
1149
+ }) {
1150
+ const canvas = useCanvas();
1151
+ const ports = useCanvasHostPorts();
1152
+ const moveId = canSplitMove ? activeItem?.id : void 0;
1153
+ const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
1154
+ const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
1155
+ const itemEntries = [...kindItems, ...hostItems];
1156
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Menu.Root, { children: [
1157
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_tap_target.TapTargetButtonTransparent, { ariaLabel: "More", icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, {}) }) }),
1158
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: [
1159
+ itemEntries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1160
+ Menu.Item,
1161
+ {
1162
+ onSelect: entry.onSelect,
1163
+ className: "mxc-menu-item",
1164
+ "data-destructive": entry.destructive ? "" : void 0,
1165
+ children: [
1166
+ entry.icon,
1167
+ entry.label
1168
+ ]
1169
+ },
1170
+ entry.id
1171
+ )),
1172
+ itemEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Menu.Separator, { className: "mxc-menu-separator" }) : null,
1173
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Menu.Item, { className: "mxc-menu-item", onSelect: () => canvas.splitPane(paneId, "horizontal", moveId), children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SplitRightIcon, {}),
1175
+ "Split right"
1176
+ ] }),
1177
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Menu.Item, { className: "mxc-menu-item", onSelect: () => canvas.splitPane(paneId, "vertical", moveId), children: [
1178
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SplitDownIcon, {}),
1179
+ "Split down"
1180
+ ] }),
1181
+ activeItem && ports.popOut ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Menu.Item, { className: "mxc-menu-item", onSelect: () => ports.popOut?.(activeItem), children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PopOutIcon, {}),
1183
+ "Pop out"
1184
+ ] }) : null,
1185
+ activeItem && canSplitMove ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
1187
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Menu.Item, { className: "mxc-menu-item", onSelect: () => canvas.closeOthers(activeItem.id), children: "Close other tabs" })
1188
+ ] }) : null
1189
+ ] }) })
1190
+ ] });
1191
+ }
1192
+ function safeMenu(build) {
1193
+ try {
1194
+ return build() ?? [];
1195
+ } catch (error) {
1196
+ console.error("[@ai-matrx/canvas] a menu builder threw", error);
1197
+ return [];
1198
+ }
1199
+ }
1200
+ function ItemBody({
1201
+ item,
1202
+ paneId,
1203
+ isActive,
1204
+ isFocused
1205
+ }) {
1206
+ const canvas = useCanvas();
1207
+ const kind = useCanvasKind(item.kind);
1208
+ if (!isActive && !kind?.keepAlive) return null;
1209
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: kind ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react2.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(KindRender, { kind, props: { item, data: item.data, paneId, isFocused, canvas } }) }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) }) });
1210
+ }
1211
+ function KindRender({ kind, props }) {
1212
+ const Render = kindComponent(kind);
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Render, { ...props });
1214
+ }
1215
+ function ItemProblem({ message, onClose, onRetry }) {
1216
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
1217
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertIcon, {}),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: message }),
1219
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mxc-problem-actions", children: [
1220
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
1221
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
1222
+ ] })
1223
+ ] });
1224
+ }
1225
+ var ItemBoundary = class extends import_react2.Component {
1226
+ state = { error: null };
1227
+ static getDerivedStateFromError(error) {
1228
+ return { error };
1229
+ }
1230
+ componentDidCatch(error, info) {
1231
+ console.error(`[@ai-matrx/canvas] "${this.props.item.kind}" failed to render`, error, info.componentStack);
1232
+ }
1233
+ render() {
1234
+ if (this.state.error) {
1235
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1236
+ ItemProblem,
1237
+ {
1238
+ message: "This item failed to load.",
1239
+ onClose: this.props.onClose,
1240
+ onRetry: () => {
1241
+ this.props.onRetry();
1242
+ this.setState({ error: null });
1243
+ }
1244
+ }
1245
+ );
1246
+ }
1247
+ return this.props.children;
1248
+ }
1249
+ };
1250
+ function Launcher({ paneId }) {
1251
+ const canvas = useCanvas();
1252
+ const kinds = useCanvasKinds().filter((kind) => kind.launcher);
1253
+ if (kinds.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mxc-empty", children: "Nothing open" });
1254
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mxc-launcher", children: kinds.map((kind) => {
1255
+ const Icon = kind.icon;
1256
+ const launcher = kind.launcher;
1257
+ if (!launcher) return null;
1258
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1259
+ "button",
1260
+ {
1261
+ type: "button",
1262
+ className: "mxc-launcher-item",
1263
+ onClick: () => canvas.open({
1264
+ kind: kind.id,
1265
+ key: launcher.key,
1266
+ data: launcher.data,
1267
+ title: launcher.title ?? null,
1268
+ target: { paneId }
1269
+ }),
1270
+ children: [
1271
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "mxc-launcher-icon" }),
1272
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: launcher.title ?? kind.label })
1273
+ ]
1274
+ },
1275
+ kind.id
1276
+ );
1277
+ }) });
1278
+ }
1279
+
1280
+ // src/react/CanvasColumn.tsx
1281
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1282
+ var MIN_MAIN_WIDTH = 420;
1283
+ function subscribeViewport(listener) {
1284
+ window.addEventListener("resize", listener);
1285
+ return () => window.removeEventListener("resize", listener);
1286
+ }
1287
+ function useViewportWidth() {
1288
+ return (0, import_react3.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
1289
+ }
1290
+ function fitWidth(width, viewport) {
1291
+ if (viewport <= 0) return width;
1292
+ return Math.max(CANVAS_MIN_WIDTH, Math.min(width, viewport - MIN_MAIN_WIDTH));
1293
+ }
1294
+ function useCanvasColumnWidth() {
1295
+ const isOpen = useCanvasState(selectCanvasIsOpen);
1296
+ const isFullscreen = useCanvasState(selectCanvasIsFullscreen);
1297
+ const width = useCanvasState(selectCanvasWidth);
1298
+ const viewport = useViewportWidth();
1299
+ if (!isOpen) return 0;
1300
+ if (isFullscreen) return null;
1301
+ return fitWidth(width, viewport);
1302
+ }
1303
+ function CanvasColumn({ className, style, onLiveWidth }) {
1304
+ const isOpen = useCanvasState(selectCanvasIsOpen);
1305
+ const isFullscreen = useCanvasState(selectCanvasIsFullscreen);
1306
+ const width = useCanvasState(selectCanvasWidth);
1307
+ const layout = useCanvasState(selectCanvasLayout);
1308
+ const viewport = useViewportWidth();
1309
+ const [dragWidth, setDragWidth] = (0, import_react3.useState)(null);
1310
+ useRegisterCanvasPresence();
1311
+ if (!isOpen) return null;
1312
+ const fitted = fitWidth(width, viewport);
1313
+ const shown = dragWidth ?? fitted;
1314
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1315
+ "aside",
1316
+ {
1317
+ className: ["mxc-column", className].filter(Boolean).join(" "),
1318
+ "data-fullscreen": isFullscreen ? "" : void 0,
1319
+ "aria-label": "Canvas",
1320
+ style: { ...style, ["--mxc-width"]: `${shown}px` },
1321
+ children: [
1322
+ !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1323
+ WidthHandle,
1324
+ {
1325
+ width: fitted,
1326
+ onDrag: (next) => {
1327
+ setDragWidth(next);
1328
+ onLiveWidth?.(next);
1329
+ }
1330
+ }
1331
+ ) : null,
1332
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LayoutNodeView, { node: layout }) })
1333
+ ]
1334
+ }
1335
+ );
1336
+ }
1337
+ function WidthHandle({ width, onDrag }) {
1338
+ const canvas = useCanvas();
1339
+ const start = (0, import_react3.useRef)(null);
1340
+ const clamp = (next) => {
1341
+ const max = Math.max(CANVAS_MIN_WIDTH, window.innerWidth - MIN_MAIN_WIDTH);
1342
+ return Math.min(max, Math.max(CANVAS_MIN_WIDTH, next));
1343
+ };
1344
+ const onPointerDown = (event) => {
1345
+ event.preventDefault();
1346
+ event.currentTarget.setPointerCapture(event.pointerId);
1347
+ start.current = { x: event.clientX, width };
1348
+ document.documentElement.dataset.mxcResizing = "";
1349
+ };
1350
+ const onPointerMove = (event) => {
1351
+ if (!start.current) return;
1352
+ onDrag(clamp(start.current.width + (start.current.x - event.clientX)));
1353
+ };
1354
+ const finish = (event) => {
1355
+ if (!start.current) return;
1356
+ const next = clamp(start.current.width + (start.current.x - event.clientX));
1357
+ start.current = null;
1358
+ delete document.documentElement.dataset.mxcResizing;
1359
+ onDrag(null);
1360
+ canvas.setWidth(next);
1361
+ };
1362
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1363
+ "div",
1364
+ {
1365
+ className: "mxc-width-handle",
1366
+ role: "separator",
1367
+ "aria-orientation": "vertical",
1368
+ "aria-label": "Resize canvas",
1369
+ "aria-valuenow": width,
1370
+ tabIndex: 0,
1371
+ onPointerDown,
1372
+ onPointerMove,
1373
+ onPointerUp: finish,
1374
+ onPointerCancel: finish,
1375
+ onDoubleClick: () => canvas.setWidth(Math.round(window.innerWidth / 2)),
1376
+ onKeyDown: (event) => {
1377
+ if (event.key === "ArrowLeft") canvas.setWidth(clamp(width + 32));
1378
+ if (event.key === "ArrowRight") canvas.setWidth(clamp(width - 32));
1379
+ }
1380
+ }
1381
+ );
1382
+ }
1383
+ function LayoutNodeView({ node }) {
1384
+ if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasPaneView, { paneId: node.paneId });
1385
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SplitView, { node });
1386
+ }
1387
+ function SplitView({ node }) {
1388
+ const canvas = useCanvas();
1389
+ const ref = (0, import_react3.useRef)(null);
1390
+ const [liveSizes, setLiveSizes] = (0, import_react3.useState)(null);
1391
+ const sizes = liveSizes ?? node.sizes;
1392
+ const horizontal = node.orientation === "horizontal";
1393
+ const beginDrag = (index) => (event) => {
1394
+ const el = ref.current;
1395
+ if (!el) return;
1396
+ event.preventDefault();
1397
+ event.currentTarget.setPointerCapture(event.pointerId);
1398
+ const rect = el.getBoundingClientRect();
1399
+ const total = horizontal ? rect.width : rect.height;
1400
+ const origin = horizontal ? event.clientX : event.clientY;
1401
+ const base = [...node.sizes];
1402
+ const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
1403
+ const target = event.currentTarget;
1404
+ document.documentElement.dataset.mxcResizing = "";
1405
+ const compute = (pos) => {
1406
+ const delta = (pos - origin) / total;
1407
+ const min = 0.12;
1408
+ const first = Math.min(pair - min, Math.max(min, (base[index] ?? 0) + delta));
1409
+ const next = [...base];
1410
+ next[index] = first;
1411
+ next[index + 1] = pair - first;
1412
+ return next;
1413
+ };
1414
+ const move = (e) => setLiveSizes(compute(horizontal ? e.clientX : e.clientY));
1415
+ const up = (e) => {
1416
+ target.removeEventListener("pointermove", move);
1417
+ target.removeEventListener("pointerup", up);
1418
+ target.removeEventListener("pointercancel", up);
1419
+ delete document.documentElement.dataset.mxcResizing;
1420
+ setLiveSizes(null);
1421
+ canvas.resizeSplit(node.id, compute(horizontal ? e.clientX : e.clientY));
1422
+ };
1423
+ target.addEventListener("pointermove", move);
1424
+ target.addEventListener("pointerup", up);
1425
+ target.addEventListener("pointercancel", up);
1426
+ };
1427
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: "mxc-split", "data-orientation": node.orientation, children: node.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1428
+ SplitChild,
1429
+ {
1430
+ size: sizes[i] ?? 1 / node.children.length,
1431
+ handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1432
+ "div",
1433
+ {
1434
+ className: "mxc-split-handle",
1435
+ role: "separator",
1436
+ "aria-orientation": horizontal ? "vertical" : "horizontal",
1437
+ onPointerDown: beginDrag(i),
1438
+ onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
1439
+ }
1440
+ ) : null,
1441
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LayoutNodeView, { node: child })
1442
+ },
1443
+ child.type === "pane" ? child.paneId : child.id
1444
+ )) });
1445
+ }
1446
+ function SplitChild({ size, handle, children }) {
1447
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1448
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
1449
+ handle
1450
+ ] });
1451
+ }
1452
+ function CanvasToggle({ className }) {
1453
+ const canvas = useCanvas();
1454
+ const isOpen = useCanvasState(selectCanvasIsOpen);
1455
+ const count = useCanvasState(selectCanvasItemCount);
1456
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
1457
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1458
+ import_tap_target2.TapTargetButtonTransparent,
1459
+ {
1460
+ ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
1461
+ tooltip: isOpen ? "Hide canvas (\u2318\\)" : "Show canvas (\u2318\\)",
1462
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelRightIcon, {}),
1463
+ onClick: () => canvas.toggle()
1464
+ }
1465
+ ),
1466
+ count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
1467
+ ] });
1468
+ }
1469
+ function CanvasFrame({ children, className }) {
1470
+ const isFullscreen = useCanvasState(selectCanvasIsFullscreen);
1471
+ const isOpen = useCanvasState(selectCanvasIsOpen);
1472
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
1473
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
1474
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasColumn, {})
1475
+ ] });
1476
+ }
1477
+ //# sourceMappingURL=react.cjs.map