@ai-matrx/canvas 0.1.1 → 0.2.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/CHANGELOG.md +34 -0
- package/README.md +114 -4
- package/dist/controller-Dv6J6XZm.d.cts +576 -0
- package/dist/controller-Dv6J6XZm.d.ts +576 -0
- package/dist/index.cjs +743 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -11
- package/dist/index.d.ts +118 -11
- package/dist/index.js +743 -104
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1476 -406
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +242 -22
- package/dist/react.d.ts +242 -22
- package/dist/react.js +1486 -404
- package/dist/react.js.map +1 -1
- package/dist/styles.css +347 -50
- package/dist/tokens.css +58 -36
- package/package.json +1 -1
- package/dist/controller-oY84BN2c.d.cts +0 -345
- package/dist/controller-oY84BN2c.d.ts +0 -345
package/dist/react.cjs
CHANGED
|
@@ -32,11 +32,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
32
32
|
// src/react.ts
|
|
33
33
|
var react_exports = {};
|
|
34
34
|
__export(react_exports, {
|
|
35
|
+
BrowserBar: () => BrowserBar,
|
|
36
|
+
BrowserTabs: () => BrowserTabs,
|
|
35
37
|
CanvasColumn: () => CanvasColumn,
|
|
36
38
|
CanvasFrame: () => CanvasFrame,
|
|
39
|
+
CanvasHeaderButton: () => CanvasHeaderButton,
|
|
40
|
+
CanvasMenuTrigger: () => CanvasMenuTrigger,
|
|
37
41
|
CanvasPaneView: () => CanvasPaneView,
|
|
42
|
+
CanvasPanel: () => CanvasPanel,
|
|
43
|
+
CanvasPanelEmpty: () => CanvasPanelEmpty,
|
|
38
44
|
CanvasProvider: () => CanvasProvider,
|
|
45
|
+
CanvasTab: () => CanvasTab,
|
|
39
46
|
CanvasToggle: () => CanvasToggle,
|
|
47
|
+
CanvasWindowScope: () => CanvasWindowScope,
|
|
48
|
+
canvasSessionKey: () => canvasSessionKey,
|
|
40
49
|
defineCanvasKind: () => defineCanvasKind,
|
|
41
50
|
getCanvasKind: () => getCanvasKind,
|
|
42
51
|
itemTitle: () => itemTitle,
|
|
@@ -49,16 +58,19 @@ __export(react_exports, {
|
|
|
49
58
|
useCanvasIsPresented: () => useCanvasIsPresented,
|
|
50
59
|
useCanvasKind: () => useCanvasKind,
|
|
51
60
|
useCanvasKinds: () => useCanvasKinds,
|
|
61
|
+
useCanvasNativeBodies: () => useCanvasNativeBodies,
|
|
52
62
|
useCanvasState: () => useCanvasState,
|
|
63
|
+
useCanvasWindowId: () => useCanvasWindowId,
|
|
53
64
|
useOptionalCanvas: () => useOptionalCanvas,
|
|
54
65
|
useOptionalCanvasState: () => useOptionalCanvasState
|
|
55
66
|
});
|
|
56
67
|
module.exports = __toCommonJS(react_exports);
|
|
57
68
|
|
|
58
69
|
// src/react/provider.tsx
|
|
59
|
-
var
|
|
70
|
+
var import_react2 = require("react");
|
|
60
71
|
|
|
61
72
|
// src/core/types.ts
|
|
73
|
+
var CANVAS_MAIN_WINDOW = "main";
|
|
62
74
|
var CANVAS_MIN_WIDTH = 360;
|
|
63
75
|
var CANVAS_DEFAULT_WIDTH = 640;
|
|
64
76
|
var CANVAS_MIN_SPLIT_FRACTION = 0.12;
|
|
@@ -103,12 +115,19 @@ function findNonJson(value, path = "data") {
|
|
|
103
115
|
return path;
|
|
104
116
|
}
|
|
105
117
|
}
|
|
118
|
+
function canvasWindowId(id) {
|
|
119
|
+
return id;
|
|
120
|
+
}
|
|
106
121
|
|
|
107
122
|
// src/core/layout.ts
|
|
108
123
|
function listPaneIds(node) {
|
|
109
124
|
if (node.type === "pane") return [node.paneId];
|
|
110
125
|
return node.children.flatMap(listPaneIds);
|
|
111
126
|
}
|
|
127
|
+
function containsPane(node, paneId) {
|
|
128
|
+
if (node.type === "pane") return node.paneId === paneId;
|
|
129
|
+
return node.children.some((child) => containsPane(child, paneId));
|
|
130
|
+
}
|
|
112
131
|
function evenSizes(count) {
|
|
113
132
|
return Array.from({ length: count }, () => 1 / count);
|
|
114
133
|
}
|
|
@@ -119,7 +138,8 @@ function normalizeSizes(sizes, count) {
|
|
|
119
138
|
const total = sizes.reduce((sum, s) => sum + s, 0);
|
|
120
139
|
return sizes.map((s) => s / total);
|
|
121
140
|
}
|
|
122
|
-
function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
|
|
141
|
+
function splitPane(node, paneId, newPaneId, orientation, newSplitId, firstShare = 0.5) {
|
|
142
|
+
const first = Number.isFinite(firstShare) ? Math.min(1 - CANVAS_MIN_SPLIT_FRACTION, Math.max(CANVAS_MIN_SPLIT_FRACTION, firstShare)) : 0.5;
|
|
123
143
|
if (node.type === "pane") {
|
|
124
144
|
if (node.paneId !== paneId) return node;
|
|
125
145
|
return {
|
|
@@ -127,7 +147,7 @@ function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
|
|
|
127
147
|
id: newSplitId,
|
|
128
148
|
orientation,
|
|
129
149
|
children: [node, { type: "pane", paneId: newPaneId }],
|
|
130
|
-
sizes: [
|
|
150
|
+
sizes: [first, 1 - first]
|
|
131
151
|
};
|
|
132
152
|
}
|
|
133
153
|
const index = node.children.findIndex(
|
|
@@ -138,13 +158,13 @@ function splitPane(node, paneId, newPaneId, orientation, newSplitId) {
|
|
|
138
158
|
const children = [...node.children];
|
|
139
159
|
children.splice(index + 1, 0, { type: "pane", paneId: newPaneId });
|
|
140
160
|
const sizes = [...node.sizes];
|
|
141
|
-
sizes.splice(index, 1, share
|
|
161
|
+
sizes.splice(index, 1, share * first, share * (1 - first));
|
|
142
162
|
return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
|
|
143
163
|
}
|
|
144
164
|
return {
|
|
145
165
|
...node,
|
|
146
166
|
children: node.children.map(
|
|
147
|
-
(child) => splitPane(child, paneId, newPaneId, orientation, newSplitId)
|
|
167
|
+
(child) => splitPane(child, paneId, newPaneId, orientation, newSplitId, first)
|
|
148
168
|
)
|
|
149
169
|
};
|
|
150
170
|
}
|
|
@@ -163,6 +183,10 @@ function removePane(node, paneId) {
|
|
|
163
183
|
if (children.length === 1) return children[0] ?? null;
|
|
164
184
|
return { ...node, children, sizes: normalizeSizes(sizes, children.length) };
|
|
165
185
|
}
|
|
186
|
+
function containsSplit(node, splitId) {
|
|
187
|
+
if (node.type === "pane") return false;
|
|
188
|
+
return node.id === splitId || node.children.some((child) => containsSplit(child, splitId));
|
|
189
|
+
}
|
|
166
190
|
function resizeSplit(node, splitId, sizes) {
|
|
167
191
|
if (node.type === "pane") return node;
|
|
168
192
|
if (node.id === splitId) {
|
|
@@ -193,8 +217,12 @@ function isValidLayout(node, knownPanes, seen = /* @__PURE__ */ new Set()) {
|
|
|
193
217
|
|
|
194
218
|
// src/core/reducer.ts
|
|
195
219
|
var P = "matrxCanvas/";
|
|
220
|
+
var win = (windowId) => windowId && windowId !== CANVAS_MAIN_WINDOW ? { windowId } : {};
|
|
196
221
|
var canvasActions = {
|
|
197
|
-
open: (input) => ({
|
|
222
|
+
open: (input, options) => ({
|
|
223
|
+
type: `${P}open`,
|
|
224
|
+
payload: { ...input, now: Date.now(), ...options?.splitShare !== void 0 ? { splitShare: options.splitShare } : {} }
|
|
225
|
+
}),
|
|
198
226
|
update: (itemId, patch) => ({
|
|
199
227
|
type: `${P}update`,
|
|
200
228
|
payload: { itemId, ...patch, now: Date.now() }
|
|
@@ -209,19 +237,32 @@ var canvasActions = {
|
|
|
209
237
|
type: `${P}moveItem`,
|
|
210
238
|
payload: { itemId, toPaneId, index }
|
|
211
239
|
}),
|
|
212
|
-
|
|
240
|
+
/** `share` = the existing pane's fraction of the space (layout rules' stackSplit / sideSplit). */
|
|
241
|
+
splitPane: (paneId, orientation, moveItemId, share) => ({
|
|
213
242
|
type: `${P}splitPane`,
|
|
214
|
-
payload: { paneId, orientation, moveItemId }
|
|
243
|
+
payload: { paneId, orientation, moveItemId, ...share !== void 0 ? { share } : {} }
|
|
215
244
|
}),
|
|
216
245
|
closePane: (paneId) => ({ type: `${P}closePane`, payload: { paneId } }),
|
|
217
246
|
resizeSplit: (splitId, sizes) => ({
|
|
218
247
|
type: `${P}resizeSplit`,
|
|
219
248
|
payload: { splitId, sizes }
|
|
220
249
|
}),
|
|
221
|
-
setOpen: (open) => ({ type: `${P}setOpen`, payload: { open } }),
|
|
222
|
-
toggle: () =>
|
|
223
|
-
setFullscreen: (fullscreen) => ({
|
|
224
|
-
|
|
250
|
+
setOpen: (open, windowId) => ({ type: `${P}setOpen`, payload: { open, ...win(windowId) } }),
|
|
251
|
+
toggle: (windowId) => windowId && windowId !== CANVAS_MAIN_WINDOW ? { type: `${P}toggle`, payload: { windowId } } : { type: `${P}toggle` },
|
|
252
|
+
setFullscreen: (fullscreen, windowId) => ({
|
|
253
|
+
type: `${P}setFullscreen`,
|
|
254
|
+
payload: { fullscreen, ...win(windowId) }
|
|
255
|
+
}),
|
|
256
|
+
setWidth: (width, windowId) => ({ type: `${P}setWidth`, payload: { width, ...win(windowId) } }),
|
|
257
|
+
/** Moves a tab into its own pop-out window `windowId` (made by the host's popOut port). */
|
|
258
|
+
popOut: (itemId, windowId) => ({ type: `${P}popOut`, payload: { itemId, windowId } }),
|
|
259
|
+
/** Returns a popped-out tab to the window it came from. */
|
|
260
|
+
dockBack: (itemId) => ({ type: `${P}dockBack`, payload: { itemId } }),
|
|
261
|
+
/** A host window closed: its tabs dock home (default) or close with it. The main window never closes. */
|
|
262
|
+
closeWindow: (windowId, mode = "dock") => ({
|
|
263
|
+
type: `${P}closeWindow`,
|
|
264
|
+
payload: { windowId, mode }
|
|
265
|
+
}),
|
|
225
266
|
hydrate: (snapshot) => ({ type: `${P}hydrate`, payload: { snapshot } }),
|
|
226
267
|
reset: () => ({ type: `${P}reset` })
|
|
227
268
|
};
|
|
@@ -234,15 +275,80 @@ function createInitialCanvasState() {
|
|
|
234
275
|
version: 1,
|
|
235
276
|
isOpen: false,
|
|
236
277
|
isFullscreen: false,
|
|
237
|
-
width:
|
|
278
|
+
width: null,
|
|
238
279
|
layout: { type: "pane", paneId },
|
|
280
|
+
focusedPaneId: paneId,
|
|
281
|
+
windows: {},
|
|
239
282
|
panes: { [paneId]: { id: paneId, itemIds: [], activeItemId: null } },
|
|
240
283
|
items: {},
|
|
241
|
-
focusedPaneId: paneId,
|
|
242
284
|
seq: 1,
|
|
243
285
|
hydrated: false
|
|
244
286
|
};
|
|
245
287
|
}
|
|
288
|
+
function listCanvasWindowIds(state) {
|
|
289
|
+
return [CANVAS_MAIN_WINDOW, ...Object.keys(state.windows ?? {})];
|
|
290
|
+
}
|
|
291
|
+
function canvasWindowFrame(state, windowId = CANVAS_MAIN_WINDOW) {
|
|
292
|
+
return windowId === CANVAS_MAIN_WINDOW ? state : state.windows?.[windowId] ?? null;
|
|
293
|
+
}
|
|
294
|
+
function canvasWindowOfPane(state, paneId) {
|
|
295
|
+
for (const id of listCanvasWindowIds(state)) {
|
|
296
|
+
const frame2 = canvasWindowFrame(state, id);
|
|
297
|
+
if (frame2 && containsPane(frame2.layout, paneId)) return id;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
function canvasWindowOfItem(state, itemId) {
|
|
302
|
+
const pane = paneOf(state, itemId);
|
|
303
|
+
return pane ? canvasWindowOfPane(state, pane.id) : null;
|
|
304
|
+
}
|
|
305
|
+
function withFrame(state, windowId, patch) {
|
|
306
|
+
if (windowId === CANVAS_MAIN_WINDOW) return { ...state, ...patch };
|
|
307
|
+
const current = state.windows[windowId];
|
|
308
|
+
if (!current) return state;
|
|
309
|
+
return { ...state, windows: { ...state.windows, [windowId]: { ...current, ...patch } } };
|
|
310
|
+
}
|
|
311
|
+
function ensureWindow(state, windowId, role = "window", home = CANVAS_MAIN_WINDOW) {
|
|
312
|
+
if (windowId === CANVAS_MAIN_WINDOW || state.windows[windowId]) return state;
|
|
313
|
+
const seq = state.seq + 1;
|
|
314
|
+
const paneId = paneIdFromSeq(seq);
|
|
315
|
+
const frame2 = {
|
|
316
|
+
id: windowId,
|
|
317
|
+
role,
|
|
318
|
+
home,
|
|
319
|
+
isOpen: false,
|
|
320
|
+
isFullscreen: false,
|
|
321
|
+
width: null,
|
|
322
|
+
layout: { type: "pane", paneId },
|
|
323
|
+
focusedPaneId: paneId
|
|
324
|
+
};
|
|
325
|
+
return {
|
|
326
|
+
...state,
|
|
327
|
+
seq,
|
|
328
|
+
panes: { ...state.panes, [paneId]: { id: paneId, itemIds: [], activeItemId: null } },
|
|
329
|
+
windows: { ...state.windows, [windowId]: frame2 }
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function removeWindow(state, windowId) {
|
|
333
|
+
const frame2 = state.windows[windowId];
|
|
334
|
+
if (!frame2) return state;
|
|
335
|
+
const panes = { ...state.panes };
|
|
336
|
+
for (const paneId of listPaneIds(frame2.layout)) delete panes[paneId];
|
|
337
|
+
const windows = { ...state.windows };
|
|
338
|
+
delete windows[windowId];
|
|
339
|
+
return { ...state, panes, windows };
|
|
340
|
+
}
|
|
341
|
+
function windowItemIds(state, windowId) {
|
|
342
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
343
|
+
if (!frame2) return [];
|
|
344
|
+
return listPaneIds(frame2.layout).flatMap((paneId) => state.panes[paneId]?.itemIds ?? []);
|
|
345
|
+
}
|
|
346
|
+
function dropEmptyPopOut(state, windowId) {
|
|
347
|
+
if (!windowId || windowId === CANVAS_MAIN_WINDOW) return state;
|
|
348
|
+
const frame2 = state.windows[windowId];
|
|
349
|
+
if (!frame2 || frame2.role !== "pop-out" || windowItemIds(state, windowId).length > 0) return state;
|
|
350
|
+
return removeWindow(state, windowId);
|
|
351
|
+
}
|
|
246
352
|
function paneOf(state, itemId) {
|
|
247
353
|
for (const pane of Object.values(state.panes)) {
|
|
248
354
|
if (pane.itemIds.includes(itemId)) return pane;
|
|
@@ -252,6 +358,10 @@ function paneOf(state, itemId) {
|
|
|
252
358
|
function withPane(state, pane) {
|
|
253
359
|
return { ...state, panes: { ...state.panes, [pane.id]: pane } };
|
|
254
360
|
}
|
|
361
|
+
function focus(state, paneId) {
|
|
362
|
+
const windowId = canvasWindowOfPane(state, paneId);
|
|
363
|
+
return windowId ? withFrame(state, windowId, { focusedPaneId: paneId }) : state;
|
|
364
|
+
}
|
|
255
365
|
function nextActive(itemIds, removed, current) {
|
|
256
366
|
if (current !== removed) return current;
|
|
257
367
|
const index = itemIds.indexOf(removed);
|
|
@@ -268,27 +378,29 @@ function detachItem(state, itemId) {
|
|
|
268
378
|
});
|
|
269
379
|
}
|
|
270
380
|
function dropPane(state, paneId) {
|
|
271
|
-
const
|
|
381
|
+
const windowId = canvasWindowOfPane(state, paneId);
|
|
382
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
383
|
+
if (!windowId || !frame2) return state;
|
|
384
|
+
const layout = removePane(frame2.layout, paneId);
|
|
272
385
|
if (!layout) return state;
|
|
273
386
|
const panes = { ...state.panes };
|
|
274
387
|
delete panes[paneId];
|
|
275
|
-
const focusedPaneId =
|
|
276
|
-
return { ...state,
|
|
388
|
+
const focusedPaneId = frame2.focusedPaneId === paneId ? listPaneIds(layout)[0] ?? frame2.focusedPaneId : frame2.focusedPaneId;
|
|
389
|
+
return withFrame({ ...state, panes }, windowId, { layout, focusedPaneId });
|
|
277
390
|
}
|
|
278
|
-
function addSplit(state, paneId, orientation) {
|
|
391
|
+
function addSplit(state, paneId, orientation, share) {
|
|
392
|
+
const windowId = canvasWindowOfPane(state, paneId);
|
|
393
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
394
|
+
if (!windowId || !frame2) return null;
|
|
279
395
|
const seq = state.seq + 1;
|
|
280
396
|
const newPaneId = paneIdFromSeq(seq);
|
|
281
|
-
const layout = splitPane(
|
|
282
|
-
|
|
283
|
-
newPaneId,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
panes: { ...state.panes, [newPaneId]: { id: newPaneId, itemIds: [], activeItemId: null } },
|
|
289
|
-
focusedPaneId: newPaneId
|
|
290
|
-
}
|
|
291
|
-
};
|
|
397
|
+
const layout = splitPane(frame2.layout, paneId, newPaneId, orientation, splitIdFromSeq(seq), share ?? 0.5);
|
|
398
|
+
const next = withFrame(
|
|
399
|
+
{ ...state, seq, panes: { ...state.panes, [newPaneId]: { id: newPaneId, itemIds: [], activeItemId: null } } },
|
|
400
|
+
windowId,
|
|
401
|
+
{ layout, focusedPaneId: newPaneId }
|
|
402
|
+
);
|
|
403
|
+
return { newPaneId, state: next };
|
|
292
404
|
}
|
|
293
405
|
function insertInto(state, paneId, itemId, activate, index) {
|
|
294
406
|
const pane = state.panes[paneId];
|
|
@@ -306,46 +418,96 @@ function afterActive(itemIds, active) {
|
|
|
306
418
|
const index = active ? itemIds.indexOf(active) : -1;
|
|
307
419
|
return index < 0 ? itemIds.length : index + 1;
|
|
308
420
|
}
|
|
421
|
+
function sendHome(state, itemId, home, activate) {
|
|
422
|
+
const homeFrame = canvasWindowFrame(state, home) ?? state;
|
|
423
|
+
const target = canvasWindowFrame(state, home) ? home : CANVAS_MAIN_WINDOW;
|
|
424
|
+
let next = detachItem(state, itemId);
|
|
425
|
+
next = insertInto(next, homeFrame.focusedPaneId, itemId, activate);
|
|
426
|
+
return activate ? withFrame(next, target, { focusedPaneId: homeFrame.focusedPaneId }) : next;
|
|
427
|
+
}
|
|
309
428
|
function clampWidth(width) {
|
|
310
|
-
return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) :
|
|
429
|
+
return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : null;
|
|
430
|
+
}
|
|
431
|
+
function foldCanvasWindows(state) {
|
|
432
|
+
const extra = Object.keys(state.windows ?? {});
|
|
433
|
+
if (extra.length === 0) return state.windows ? state : { ...state, windows: {} };
|
|
434
|
+
let next = state;
|
|
435
|
+
for (const windowId of extra) {
|
|
436
|
+
for (const itemId of windowItemIds(next, windowId)) next = sendHome(next, itemId, CANVAS_MAIN_WINDOW, false);
|
|
437
|
+
next = removeWindow(next, windowId);
|
|
438
|
+
}
|
|
439
|
+
return next;
|
|
311
440
|
}
|
|
312
441
|
function sanitizeCanvasSnapshot(raw) {
|
|
313
442
|
if (!raw || typeof raw !== "object") return null;
|
|
314
443
|
const s = raw;
|
|
315
|
-
if (s.version !== 1 || !s.panes || !s.items || !s.layout) return null;
|
|
444
|
+
if (s.version !== 1 || !s.panes || typeof s.panes !== "object" || !s.items || typeof s.items !== "object" || !s.layout) return null;
|
|
316
445
|
const paneIds = new Set(Object.keys(s.panes));
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
446
|
+
const seen = /* @__PURE__ */ new Set();
|
|
447
|
+
if (!isValidLayout(s.layout, paneIds, seen)) return null;
|
|
448
|
+
const windows = {};
|
|
449
|
+
if (s.windows && typeof s.windows === "object") {
|
|
450
|
+
for (const [id, value] of Object.entries(s.windows)) {
|
|
451
|
+
if (!value || typeof value !== "object" || id === CANVAS_MAIN_WINDOW) continue;
|
|
452
|
+
const w = value;
|
|
453
|
+
const claimed = new Set(seen);
|
|
454
|
+
if (!isValidLayout(w.layout, paneIds, claimed)) continue;
|
|
455
|
+
for (const pane of claimed) seen.add(pane);
|
|
456
|
+
const layoutPanes = listPaneIds(w.layout);
|
|
457
|
+
const focused2 = w.focusedPaneId && layoutPanes.includes(w.focusedPaneId) ? w.focusedPaneId : layoutPanes[0];
|
|
458
|
+
if (!focused2) continue;
|
|
459
|
+
windows[id] = {
|
|
460
|
+
id,
|
|
461
|
+
role: w.role === "pop-out" ? "pop-out" : "window",
|
|
462
|
+
home: typeof w.home === "string" ? w.home : CANVAS_MAIN_WINDOW,
|
|
463
|
+
isOpen: w.isOpen === true,
|
|
464
|
+
isFullscreen: false,
|
|
465
|
+
width: typeof w.width === "number" ? clampWidth(w.width) : null,
|
|
466
|
+
layout: normalizeLayout(w.layout),
|
|
467
|
+
focusedPaneId: focused2
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (seen.size !== paneIds.size) return null;
|
|
320
472
|
const items = {};
|
|
321
473
|
for (const [id, item] of Object.entries(s.items)) {
|
|
322
474
|
if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
|
|
323
475
|
items[id] = item;
|
|
324
476
|
}
|
|
325
477
|
}
|
|
478
|
+
const placed = /* @__PURE__ */ new Set();
|
|
326
479
|
const panes = {};
|
|
327
480
|
for (const [id, pane] of Object.entries(s.panes)) {
|
|
328
|
-
const itemIds = (Array.isArray(pane.itemIds) ? pane.itemIds : []).filter((itemId) => itemId in items);
|
|
481
|
+
const itemIds = (Array.isArray(pane.itemIds) ? pane.itemIds : []).filter((itemId) => itemId in items && !placed.has(itemId));
|
|
482
|
+
for (const itemId of itemIds) placed.add(itemId);
|
|
329
483
|
const activeItemId = pane.activeItemId && itemIds.includes(pane.activeItemId) ? pane.activeItemId : itemIds[0] ?? null;
|
|
330
484
|
panes[id] = { id, itemIds, activeItemId };
|
|
331
485
|
}
|
|
332
|
-
const
|
|
486
|
+
const mainPanes = listPaneIds(s.layout);
|
|
487
|
+
const focused = s.focusedPaneId && mainPanes.includes(s.focusedPaneId) ? s.focusedPaneId : mainPanes[0];
|
|
333
488
|
if (!focused) return null;
|
|
334
489
|
const layout = normalizeLayout(s.layout);
|
|
335
|
-
const
|
|
336
|
-
|
|
490
|
+
const highest = Math.max(highestIdNumber(layout), ...Object.values(windows).map((w) => highestIdNumber(w.layout)));
|
|
491
|
+
const seq = Math.max(typeof s.seq === "number" && Number.isFinite(s.seq) ? s.seq : 0, highest);
|
|
492
|
+
let state = {
|
|
337
493
|
version: 1,
|
|
338
494
|
isOpen: s.isOpen === true,
|
|
339
495
|
// Full screen is a moment, not a preference: a reload never starts there.
|
|
340
496
|
isFullscreen: false,
|
|
341
|
-
width:
|
|
497
|
+
width: typeof s.width === "number" ? clampWidth(s.width) : null,
|
|
342
498
|
layout,
|
|
499
|
+
focusedPaneId: focused,
|
|
500
|
+
windows,
|
|
343
501
|
panes,
|
|
344
502
|
items,
|
|
345
|
-
focusedPaneId: focused,
|
|
346
503
|
seq,
|
|
347
504
|
hydrated: true
|
|
348
505
|
};
|
|
506
|
+
state = foldCanvasWindows(state);
|
|
507
|
+
for (const id of Object.keys(items)) {
|
|
508
|
+
if (!placed.has(id)) state = insertInto(state, state.focusedPaneId, id, false);
|
|
509
|
+
}
|
|
510
|
+
return state;
|
|
349
511
|
}
|
|
350
512
|
function normalizeLayout(node) {
|
|
351
513
|
if (node.type === "pane") return node;
|
|
@@ -358,12 +520,14 @@ function highestIdNumber(node) {
|
|
|
358
520
|
}
|
|
359
521
|
function canvasReducer(state = createInitialCanvasState(), action) {
|
|
360
522
|
if (!isCanvasAction(action)) return state;
|
|
523
|
+
if (!state.windows) state = { ...state, windows: {} };
|
|
361
524
|
switch (action.type) {
|
|
362
525
|
case `${P}open`: {
|
|
363
|
-
const { kind, key, title, data, target = "focused", reveal = true, activate = true, now } = action.payload;
|
|
526
|
+
const { kind, key, title, data, target = "focused", reveal = true, activate = true, now, splitShare } = action.payload;
|
|
364
527
|
const itemId = canvasItemId(kind, key);
|
|
365
528
|
const existing = state.items[itemId];
|
|
366
529
|
let next = state;
|
|
530
|
+
let revealIn = action.payload.windowId ?? CANVAS_MAIN_WINDOW;
|
|
367
531
|
if (existing) {
|
|
368
532
|
const item = {
|
|
369
533
|
...existing,
|
|
@@ -373,23 +537,33 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
373
537
|
};
|
|
374
538
|
next = { ...next, items: { ...next.items, [itemId]: item } };
|
|
375
539
|
const pane = paneOf(next, itemId);
|
|
376
|
-
if (pane
|
|
377
|
-
|
|
540
|
+
if (pane) {
|
|
541
|
+
revealIn = canvasWindowOfPane(next, pane.id) ?? CANVAS_MAIN_WINDOW;
|
|
542
|
+
if (activate) next = focus(withPane(next, { ...pane, activeItemId: itemId }), pane.id);
|
|
543
|
+
} else {
|
|
544
|
+
next = ensureWindow(next, revealIn);
|
|
545
|
+
const frame2 = canvasWindowFrame(next, revealIn) ?? next;
|
|
546
|
+
next = insertInto(next, frame2.focusedPaneId, itemId, true);
|
|
547
|
+
}
|
|
378
548
|
} else {
|
|
379
549
|
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
|
-
|
|
550
|
+
next = ensureWindow({ ...next, items: { ...next.items, [itemId]: item } }, revealIn);
|
|
551
|
+
const frame2 = canvasWindowFrame(next, revealIn) ?? next;
|
|
552
|
+
let paneId = frame2.focusedPaneId;
|
|
382
553
|
if (typeof target === "object" && next.panes[target.paneId]) {
|
|
383
554
|
paneId = target.paneId;
|
|
555
|
+
revealIn = canvasWindowOfPane(next, paneId) ?? revealIn;
|
|
384
556
|
} else if (target === "split-right" || target === "split-down") {
|
|
385
|
-
const split = addSplit(next,
|
|
386
|
-
|
|
387
|
-
|
|
557
|
+
const split = addSplit(next, frame2.focusedPaneId, target === "split-right" ? "horizontal" : "vertical", splitShare);
|
|
558
|
+
if (split) {
|
|
559
|
+
next = split.state;
|
|
560
|
+
paneId = split.newPaneId;
|
|
561
|
+
}
|
|
388
562
|
}
|
|
389
563
|
next = insertInto(next, paneId, itemId, activate);
|
|
390
|
-
if (activate) next =
|
|
564
|
+
if (activate) next = focus(next, paneId);
|
|
391
565
|
}
|
|
392
|
-
return reveal ?
|
|
566
|
+
return reveal ? withFrame(next, revealIn, { isOpen: true }) : next;
|
|
393
567
|
}
|
|
394
568
|
case `${P}update`: {
|
|
395
569
|
const { itemId, data, title, now } = action.payload;
|
|
@@ -425,12 +599,13 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
425
599
|
const { itemId } = action.payload;
|
|
426
600
|
if (!state.items[itemId]) return state;
|
|
427
601
|
const pane = paneOf(state, itemId);
|
|
602
|
+
const windowId = pane ? canvasWindowOfPane(state, pane.id) : null;
|
|
428
603
|
let next = detachItem(state, itemId);
|
|
429
604
|
const items = { ...next.items };
|
|
430
605
|
delete items[itemId];
|
|
431
606
|
next = { ...next, items };
|
|
432
607
|
if (pane && next.panes[pane.id]?.itemIds.length === 0) next = dropPane(next, pane.id);
|
|
433
|
-
return next;
|
|
608
|
+
return dropEmptyPopOut(next, windowId);
|
|
434
609
|
}
|
|
435
610
|
case `${P}closeOthers`: {
|
|
436
611
|
const { itemId } = action.payload;
|
|
@@ -443,26 +618,31 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
443
618
|
case `${P}activate`: {
|
|
444
619
|
const pane = paneOf(state, action.payload.itemId);
|
|
445
620
|
if (!pane) return state;
|
|
446
|
-
return
|
|
621
|
+
return focus(withPane(state, { ...pane, activeItemId: action.payload.itemId }), pane.id);
|
|
622
|
+
}
|
|
623
|
+
case `${P}focusPane`: {
|
|
624
|
+
const windowId = canvasWindowOfPane(state, action.payload.paneId);
|
|
625
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
626
|
+
if (!windowId || !frame2 || frame2.focusedPaneId === action.payload.paneId) return state;
|
|
627
|
+
return withFrame(state, windowId, { focusedPaneId: action.payload.paneId });
|
|
447
628
|
}
|
|
448
|
-
case `${P}focusPane`:
|
|
449
|
-
return state.panes[action.payload.paneId] ? { ...state, focusedPaneId: action.payload.paneId } : state;
|
|
450
629
|
case `${P}moveItem`: {
|
|
451
630
|
const { itemId, toPaneId, index } = action.payload;
|
|
452
631
|
const from = paneOf(state, itemId);
|
|
453
632
|
if (!from || !state.panes[toPaneId]) return state;
|
|
454
|
-
if (from.id === toPaneId && index === void 0) return state
|
|
633
|
+
if (from.id === toPaneId && index === void 0) return canvasReducer(state, canvasActions.focusPane(toPaneId));
|
|
634
|
+
const fromWindow = canvasWindowOfPane(state, from.id);
|
|
455
635
|
let next = from.id === toPaneId ? state : detachItem(state, itemId);
|
|
456
|
-
next = insertInto(next, toPaneId, itemId, true, index);
|
|
457
|
-
next = { ...next, focusedPaneId: toPaneId };
|
|
636
|
+
next = focus(insertInto(next, toPaneId, itemId, true, index), toPaneId);
|
|
458
637
|
if (from.id !== toPaneId && next.panes[from.id]?.itemIds.length === 0) next = dropPane(next, from.id);
|
|
459
|
-
return next;
|
|
638
|
+
return dropEmptyPopOut(next, fromWindow);
|
|
460
639
|
}
|
|
461
640
|
case `${P}splitPane`: {
|
|
462
|
-
const { paneId, orientation, moveItemId } = action.payload;
|
|
641
|
+
const { paneId, orientation, moveItemId, share } = action.payload;
|
|
463
642
|
const pane = state.panes[paneId];
|
|
464
643
|
if (!pane) return state;
|
|
465
|
-
const split = addSplit(state, paneId, orientation);
|
|
644
|
+
const split = addSplit(state, paneId, orientation, share);
|
|
645
|
+
if (!split) return state;
|
|
466
646
|
let next = split.state;
|
|
467
647
|
if (moveItemId && pane.itemIds.includes(moveItemId) && pane.itemIds.length > 1) {
|
|
468
648
|
next = detachItem(next, moveItemId);
|
|
@@ -472,23 +652,106 @@ function canvasReducer(state = createInitialCanvasState(), action) {
|
|
|
472
652
|
}
|
|
473
653
|
case `${P}closePane`: {
|
|
474
654
|
const pane = state.panes[action.payload.paneId];
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
655
|
+
const windowId = pane ? canvasWindowOfPane(state, pane.id) : null;
|
|
656
|
+
const frame2 = windowId ? canvasWindowFrame(state, windowId) : null;
|
|
657
|
+
if (!pane || !windowId || !frame2) return state;
|
|
658
|
+
const onlyPane = listPaneIds(frame2.layout).length === 1;
|
|
659
|
+
if (onlyPane) {
|
|
660
|
+
if (windowId !== CANVAS_MAIN_WINDOW && state.windows[windowId]?.role === "pop-out") {
|
|
661
|
+
return canvasReducer(state, canvasActions.closeWindow(windowId, "dock"));
|
|
662
|
+
}
|
|
663
|
+
return withFrame(state, windowId, { isOpen: false, isFullscreen: false });
|
|
664
|
+
}
|
|
478
665
|
const items = { ...state.items };
|
|
479
666
|
for (const id of pane.itemIds) delete items[id];
|
|
480
667
|
return dropPane({ ...state, items }, pane.id);
|
|
481
668
|
}
|
|
482
|
-
case `${P}resizeSplit`:
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
669
|
+
case `${P}resizeSplit`: {
|
|
670
|
+
const { splitId, sizes } = action.payload;
|
|
671
|
+
for (const windowId of listCanvasWindowIds(state)) {
|
|
672
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
673
|
+
if (frame2 && containsSplit(frame2.layout, splitId)) {
|
|
674
|
+
return withFrame(state, windowId, { layout: resizeSplit(frame2.layout, splitId, sizes) });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return state;
|
|
678
|
+
}
|
|
679
|
+
case `${P}setOpen`: {
|
|
680
|
+
const { open, windowId = CANVAS_MAIN_WINDOW } = action.payload;
|
|
681
|
+
const next = open ? ensureWindow(state, windowId) : state;
|
|
682
|
+
const frame2 = canvasWindowFrame(next, windowId);
|
|
683
|
+
if (!frame2 || frame2.isOpen === open) return next;
|
|
684
|
+
return withFrame(next, windowId, { isOpen: open, isFullscreen: open ? frame2.isFullscreen : false });
|
|
685
|
+
}
|
|
686
|
+
case `${P}toggle`: {
|
|
687
|
+
const windowId = action.payload?.windowId ?? CANVAS_MAIN_WINDOW;
|
|
688
|
+
const next = ensureWindow(state, windowId);
|
|
689
|
+
const frame2 = canvasWindowFrame(next, windowId);
|
|
690
|
+
if (!frame2) return state;
|
|
691
|
+
return withFrame(next, windowId, { isOpen: !frame2.isOpen, isFullscreen: frame2.isOpen ? false : frame2.isFullscreen });
|
|
692
|
+
}
|
|
693
|
+
case `${P}setFullscreen`: {
|
|
694
|
+
const { fullscreen, windowId = CANVAS_MAIN_WINDOW } = action.payload;
|
|
695
|
+
const next = fullscreen ? ensureWindow(state, windowId) : state;
|
|
696
|
+
const frame2 = canvasWindowFrame(next, windowId);
|
|
697
|
+
if (!frame2) return state;
|
|
698
|
+
return withFrame(next, windowId, { isFullscreen: fullscreen, isOpen: fullscreen ? true : frame2.isOpen });
|
|
699
|
+
}
|
|
700
|
+
case `${P}setWidth`: {
|
|
701
|
+
const { width, windowId = CANVAS_MAIN_WINDOW } = action.payload;
|
|
702
|
+
const next = ensureWindow(state, windowId);
|
|
703
|
+
return withFrame(next, windowId, { width: clampWidth(width) });
|
|
704
|
+
}
|
|
705
|
+
case `${P}popOut`: {
|
|
706
|
+
const { itemId, windowId } = action.payload;
|
|
707
|
+
const from = paneOf(state, itemId);
|
|
708
|
+
if (!from || windowId === CANVAS_MAIN_WINDOW) return state;
|
|
709
|
+
const fromWindow = canvasWindowOfPane(state, from.id) ?? CANVAS_MAIN_WINDOW;
|
|
710
|
+
if (fromWindow === windowId) return state;
|
|
711
|
+
let next = ensureWindow(state, windowId, "pop-out", fromWindow);
|
|
712
|
+
const target = canvasWindowFrame(next, windowId);
|
|
713
|
+
if (!target) return state;
|
|
714
|
+
next = detachItem(next, itemId);
|
|
715
|
+
next = insertInto(next, target.focusedPaneId, itemId, true);
|
|
716
|
+
next = withFrame(next, windowId, { isOpen: true, focusedPaneId: target.focusedPaneId });
|
|
717
|
+
if (next.panes[from.id]?.itemIds.length === 0) next = dropPane(next, from.id);
|
|
718
|
+
next = dropEmptyPopOut(next, fromWindow);
|
|
719
|
+
if (canvasWindowFrame(next, fromWindow) && windowItemIds(next, fromWindow).length === 0) {
|
|
720
|
+
next = withFrame(next, fromWindow, { isOpen: false, isFullscreen: false });
|
|
721
|
+
}
|
|
722
|
+
return next;
|
|
723
|
+
}
|
|
724
|
+
case `${P}dockBack`: {
|
|
725
|
+
const { itemId } = action.payload;
|
|
726
|
+
const windowId = canvasWindowOfItem(state, itemId);
|
|
727
|
+
if (!windowId || windowId === CANVAS_MAIN_WINDOW) return state;
|
|
728
|
+
const frame2 = state.windows[windowId];
|
|
729
|
+
if (!frame2) return state;
|
|
730
|
+
const home = canvasWindowFrame(state, frame2.home) ? frame2.home : CANVAS_MAIN_WINDOW;
|
|
731
|
+
let next = sendHome(state, itemId, home, true);
|
|
732
|
+
next = withFrame(next, home, { isOpen: true });
|
|
733
|
+
const left = paneOf(state, itemId);
|
|
734
|
+
if (left && next.panes[left.id]?.itemIds.length === 0) next = dropPane(next, left.id);
|
|
735
|
+
return dropEmptyPopOut(next, windowId);
|
|
736
|
+
}
|
|
737
|
+
case `${P}closeWindow`: {
|
|
738
|
+
const { windowId, mode } = action.payload;
|
|
739
|
+
const frame2 = state.windows[windowId];
|
|
740
|
+
if (!frame2) return state;
|
|
741
|
+
const itemIds = windowItemIds(state, windowId);
|
|
742
|
+
let next = state;
|
|
743
|
+
if (mode === "close") {
|
|
744
|
+
const items = { ...next.items };
|
|
745
|
+
for (const id of itemIds) delete items[id];
|
|
746
|
+
next = { ...next, items };
|
|
747
|
+
} else if (itemIds.length > 0) {
|
|
748
|
+
const home = canvasWindowFrame(state, frame2.home) ? frame2.home : CANVAS_MAIN_WINDOW;
|
|
749
|
+
const active = state.panes[frame2.focusedPaneId]?.activeItemId ?? null;
|
|
750
|
+
for (const id of itemIds) next = sendHome(next, id, home, id === active);
|
|
751
|
+
next = withFrame(next, home, { isOpen: true });
|
|
752
|
+
}
|
|
753
|
+
return removeWindow(next, windowId);
|
|
754
|
+
}
|
|
492
755
|
case `${P}hydrate`: {
|
|
493
756
|
const snapshot = action.payload.snapshot ? sanitizeCanvasSnapshot(action.payload.snapshot) : null;
|
|
494
757
|
if (!snapshot) return { ...state, hydrated: true };
|
|
@@ -553,6 +816,7 @@ function safeLocalStorage() {
|
|
|
553
816
|
}
|
|
554
817
|
}
|
|
555
818
|
function toPersistableSnapshot(state, isRestorable) {
|
|
819
|
+
state = foldCanvasWindows(state);
|
|
556
820
|
const keep = new Set(
|
|
557
821
|
Object.values(state.items).filter((item) => isRestorable(item.kind)).map((item) => item.id)
|
|
558
822
|
);
|
|
@@ -579,22 +843,156 @@ function toPersistableSnapshot(state, isRestorable) {
|
|
|
579
843
|
return { ...state, items, panes, layout, focusedPaneId };
|
|
580
844
|
}
|
|
581
845
|
|
|
846
|
+
// src/core/rules.ts
|
|
847
|
+
var CANVAS_WEB_LAYOUT_RULES = {
|
|
848
|
+
defaultWidth: CANVAS_DEFAULT_WIDTH,
|
|
849
|
+
minWidth: CANVAS_MIN_WIDTH,
|
|
850
|
+
centreMinWidth: 420,
|
|
851
|
+
stackSplit: 0.5,
|
|
852
|
+
sideSplit: 0.5,
|
|
853
|
+
paneMinWidth: 0
|
|
854
|
+
};
|
|
855
|
+
var clampFraction = (value, fallback) => Number.isFinite(value) ? Math.min(0.88, Math.max(0.12, value)) : fallback;
|
|
856
|
+
function resolveCanvasLayoutRules(rules) {
|
|
857
|
+
const merged = { ...CANVAS_WEB_LAYOUT_RULES, ...rules ?? {} };
|
|
858
|
+
const positive = (value, fallback) => Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
859
|
+
return {
|
|
860
|
+
defaultWidth: typeof merged.defaultWidth === "number" ? positive(merged.defaultWidth, CANVAS_DEFAULT_WIDTH) : { fraction: Math.min(1, Math.max(0.05, Number.isFinite(merged.defaultWidth.fraction) ? merged.defaultWidth.fraction : 0.36)) },
|
|
861
|
+
minWidth: positive(merged.minWidth, CANVAS_MIN_WIDTH),
|
|
862
|
+
centreMinWidth: positive(merged.centreMinWidth, CANVAS_WEB_LAYOUT_RULES.centreMinWidth),
|
|
863
|
+
stackSplit: clampFraction(merged.stackSplit, 0.5),
|
|
864
|
+
sideSplit: clampFraction(merged.sideSplit, 0.5),
|
|
865
|
+
paneMinWidth: positive(merged.paneMinWidth, 0)
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function defaultCanvasWidth(rules, region) {
|
|
869
|
+
if (typeof rules.defaultWidth === "number") return rules.defaultWidth;
|
|
870
|
+
return region > 0 ? Math.round(region * rules.defaultWidth.fraction) : CANVAS_DEFAULT_WIDTH;
|
|
871
|
+
}
|
|
872
|
+
function fitCanvasWidth(stored, region, rules) {
|
|
873
|
+
const wanted = stored ?? defaultCanvasWidth(rules, region);
|
|
874
|
+
if (region <= 0) return Math.max(rules.minWidth, wanted);
|
|
875
|
+
return Math.max(rules.minWidth, Math.min(wanted, region - rules.centreMinWidth));
|
|
876
|
+
}
|
|
877
|
+
function clampDraggedCanvasWidth(next, region, rules) {
|
|
878
|
+
const max = Math.max(rules.minWidth, region - rules.centreMinWidth);
|
|
879
|
+
return Math.min(max, Math.max(rules.minWidth, Math.round(next)));
|
|
880
|
+
}
|
|
881
|
+
function shouldStackSplit(width, children, rules) {
|
|
882
|
+
return rules.paneMinWidth > 0 && width > 0 && width < rules.paneMinWidth * children;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/core/commands.ts
|
|
886
|
+
var CANVAS_SHORTCUTS = [
|
|
887
|
+
{ id: "canvas.toggle", accelerator: "CmdOrCtrl+\\", when: "presented", label: "Show or hide canvas", web: true },
|
|
888
|
+
{ id: "canvas.exitFullscreen", accelerator: "Escape", when: "fullscreen", label: "Restore canvas", web: true },
|
|
889
|
+
{ id: "canvas.toggleFullscreen", accelerator: "CmdOrCtrl+Shift+Enter", when: "canvas-focus", label: "Expand canvas", web: true },
|
|
890
|
+
{ id: "canvas.splitRight", accelerator: "CmdOrCtrl+Alt+\\", when: "canvas-focus", label: "Split right", web: true },
|
|
891
|
+
{ id: "canvas.splitDown", accelerator: "CmdOrCtrl+Alt+Shift+\\", when: "canvas-focus", label: "Split down", web: true },
|
|
892
|
+
{ id: "canvas.closeTab", accelerator: "CmdOrCtrl+W", when: "canvas-focus", label: "Close tab", web: false },
|
|
893
|
+
{ id: "canvas.nextTab", accelerator: "Ctrl+Tab", when: "canvas-focus", label: "Next tab", web: false },
|
|
894
|
+
{ id: "canvas.previousTab", accelerator: "Ctrl+Shift+Tab", when: "canvas-focus", label: "Previous tab", web: false }
|
|
895
|
+
];
|
|
896
|
+
function canvasShortcutHint(id, isMac3) {
|
|
897
|
+
const shortcut = CANVAS_SHORTCUTS.find((entry) => entry.id === id);
|
|
898
|
+
if (!shortcut) return null;
|
|
899
|
+
return shortcut.accelerator.split("+").map((part) => {
|
|
900
|
+
if (part === "CmdOrCtrl") return isMac3 ? "\u2318" : "Ctrl+";
|
|
901
|
+
if (part === "Ctrl") return isMac3 ? "\u2303" : "Ctrl+";
|
|
902
|
+
if (part === "Alt") return isMac3 ? "\u2325" : "Alt+";
|
|
903
|
+
if (part === "Shift") return isMac3 ? "\u21E7" : "Shift+";
|
|
904
|
+
if (part === "Escape") return "Esc";
|
|
905
|
+
return part;
|
|
906
|
+
}).join("");
|
|
907
|
+
}
|
|
908
|
+
function runCanvasCommand(controller, id, windowId = controller.windowId) {
|
|
909
|
+
const state = controller.getState();
|
|
910
|
+
const frame2 = canvasWindowFrame(state, windowId);
|
|
911
|
+
const pane = frame2 ? state.panes[frame2.focusedPaneId] : void 0;
|
|
912
|
+
switch (id) {
|
|
913
|
+
case "canvas.toggle":
|
|
914
|
+
controller.toggle(windowId);
|
|
915
|
+
return true;
|
|
916
|
+
case "canvas.toggleFullscreen":
|
|
917
|
+
controller.setFullscreen(!(frame2?.isFullscreen ?? false), windowId);
|
|
918
|
+
return true;
|
|
919
|
+
case "canvas.exitFullscreen":
|
|
920
|
+
if (!frame2?.isFullscreen) return false;
|
|
921
|
+
controller.setFullscreen(false, windowId);
|
|
922
|
+
return true;
|
|
923
|
+
case "canvas.closeTab":
|
|
924
|
+
if (!frame2?.isOpen || !pane?.activeItemId) return false;
|
|
925
|
+
controller.close(pane.activeItemId);
|
|
926
|
+
return true;
|
|
927
|
+
case "canvas.nextTab":
|
|
928
|
+
case "canvas.previousTab": {
|
|
929
|
+
if (!frame2?.isOpen || !pane?.activeItemId || pane.itemIds.length < 2) return false;
|
|
930
|
+
const at = pane.itemIds.indexOf(pane.activeItemId);
|
|
931
|
+
const step = id === "canvas.nextTab" ? 1 : -1;
|
|
932
|
+
const next = pane.itemIds[(at + step + pane.itemIds.length) % pane.itemIds.length];
|
|
933
|
+
if (next) controller.activate(next);
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
case "canvas.splitRight":
|
|
937
|
+
case "canvas.splitDown":
|
|
938
|
+
if (!frame2?.isOpen || !pane) return false;
|
|
939
|
+
controller.splitPane(pane.id, id === "canvas.splitRight" ? "horizontal" : "vertical", pane.itemIds.length > 1 ? pane.activeItemId ?? void 0 : void 0);
|
|
940
|
+
return true;
|
|
941
|
+
default:
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
var CODE_FOR = { "\\": "Backslash", Tab: "Tab", Escape: "Escape", Enter: "Enter" };
|
|
946
|
+
function matchesCanvasAccelerator(input, accelerator, isMac3) {
|
|
947
|
+
const parts = accelerator.split("+");
|
|
948
|
+
const key = parts.pop() ?? "";
|
|
949
|
+
const want = { meta: false, ctrl: false, alt: false, shift: false };
|
|
950
|
+
for (const part of parts) {
|
|
951
|
+
if (part === "CmdOrCtrl") want[isMac3 ? "meta" : "ctrl"] = true;
|
|
952
|
+
else if (part === "Cmd" || part === "Meta") want.meta = true;
|
|
953
|
+
else if (part === "Ctrl") want.ctrl = true;
|
|
954
|
+
else if (part === "Alt") want.alt = true;
|
|
955
|
+
else if (part === "Shift") want.shift = true;
|
|
956
|
+
}
|
|
957
|
+
if (input.metaKey !== want.meta || input.ctrlKey !== want.ctrl || input.altKey !== want.alt || input.shiftKey !== want.shift) return false;
|
|
958
|
+
const code = CODE_FOR[key] ?? (/^[A-Z]$/i.test(key) ? `Key${key.toUpperCase()}` : void 0);
|
|
959
|
+
if (code && input.code === code) return true;
|
|
960
|
+
return input.key.toLowerCase() === key.toLowerCase();
|
|
961
|
+
}
|
|
962
|
+
|
|
582
963
|
// src/core/controller.ts
|
|
583
964
|
var consoleCanvasErrorSink = (report) => {
|
|
584
965
|
console.error(`[@ai-matrx/canvas] ${report.code}: ${report.message}`, report.detail ?? "");
|
|
585
966
|
};
|
|
586
967
|
function createCanvasController(options) {
|
|
968
|
+
const shared = {
|
|
969
|
+
options,
|
|
970
|
+
onError: options.onError ?? consoleCanvasErrorSink,
|
|
971
|
+
rules: resolveCanvasLayoutRules(options.layoutRules),
|
|
972
|
+
presentations: /* @__PURE__ */ new Map(),
|
|
973
|
+
presentationListeners: /* @__PURE__ */ new Set(),
|
|
974
|
+
views: /* @__PURE__ */ new Map(),
|
|
975
|
+
started: /* @__PURE__ */ new Map(),
|
|
976
|
+
sessionStop: null
|
|
977
|
+
};
|
|
978
|
+
return view(shared, canvasWindowId(options.windowId ?? CANVAS_MAIN_WINDOW));
|
|
979
|
+
}
|
|
980
|
+
function view(shared, windowId) {
|
|
981
|
+
const cached = shared.views.get(windowId);
|
|
982
|
+
if (cached) return cached;
|
|
983
|
+
const { options, onError, rules } = shared;
|
|
587
984
|
const { store, persistence = null } = options;
|
|
588
|
-
const onError = options.onError ?? consoleCanvasErrorSink;
|
|
589
985
|
const isRestorable = options.isRestorable ?? (() => true);
|
|
590
986
|
const dispatch = (action) => store.dispatch(action);
|
|
591
|
-
let presentations = 0;
|
|
592
|
-
const presentationListeners = /* @__PURE__ */ new Set();
|
|
593
987
|
const notifyPresentation = () => {
|
|
594
|
-
for (const listener of [...presentationListeners]) listener();
|
|
988
|
+
for (const listener of [...shared.presentationListeners]) listener();
|
|
595
989
|
};
|
|
990
|
+
const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
|
|
596
991
|
const controller = {
|
|
597
992
|
store,
|
|
993
|
+
windowId,
|
|
994
|
+
rules,
|
|
995
|
+
forWindow: (id) => view(shared, canvasWindowId(id)),
|
|
598
996
|
getState: () => store.getState(),
|
|
599
997
|
open(input) {
|
|
600
998
|
if (options.isKnownKind && !options.isKnownKind(input.kind)) {
|
|
@@ -612,7 +1010,10 @@ function createCanvasController(options) {
|
|
|
612
1010
|
return null;
|
|
613
1011
|
}
|
|
614
1012
|
}
|
|
615
|
-
|
|
1013
|
+
const target = input.target ?? "focused";
|
|
1014
|
+
const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
|
|
1015
|
+
const resolved = input.windowId || windowId === CANVAS_MAIN_WINDOW ? input : { ...input, windowId };
|
|
1016
|
+
dispatch(canvasActions.open(resolved, splitShare === void 0 ? void 0 : { splitShare }));
|
|
616
1017
|
return canvasItemId(input.kind, input.key);
|
|
617
1018
|
},
|
|
618
1019
|
update(itemId, patch) {
|
|
@@ -638,42 +1039,76 @@ function createCanvasController(options) {
|
|
|
638
1039
|
activate: (itemId) => dispatch(canvasActions.activate(itemId)),
|
|
639
1040
|
focusPane: (paneId) => dispatch(canvasActions.focusPane(paneId)),
|
|
640
1041
|
moveItem: (itemId, toPaneId, index) => dispatch(canvasActions.moveItem(itemId, toPaneId, index)),
|
|
641
|
-
splitPane: (paneId, orientation, moveItemId) => dispatch(canvasActions.splitPane(paneId, orientation, moveItemId)),
|
|
1042
|
+
splitPane: (paneId, orientation, moveItemId) => dispatch(canvasActions.splitPane(paneId, orientation, moveItemId, shareFor(orientation))),
|
|
642
1043
|
closePane: (paneId) => dispatch(canvasActions.closePane(paneId)),
|
|
643
1044
|
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)),
|
|
1045
|
+
show: (w = windowId) => dispatch(canvasActions.setOpen(true, w)),
|
|
1046
|
+
hide: (w = windowId) => dispatch(canvasActions.setOpen(false, w)),
|
|
1047
|
+
toggle: (w = windowId) => dispatch(canvasActions.toggle(w)),
|
|
1048
|
+
setFullscreen: (fullscreen, w = windowId) => dispatch(canvasActions.setFullscreen(fullscreen, w)),
|
|
1049
|
+
setWidth: (width, w = windowId) => dispatch(canvasActions.setWidth(width, w)),
|
|
1050
|
+
async popOut(itemId) {
|
|
1051
|
+
const item = store.getState().items[itemId];
|
|
1052
|
+
if (!item) return null;
|
|
1053
|
+
const port = options.windowPorts?.().popOut;
|
|
1054
|
+
if (!port) {
|
|
1055
|
+
onError({ code: "pop-out-unavailable", message: "This app cannot pop canvas tabs out into their own window.", detail: { itemId } });
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
let result;
|
|
1059
|
+
try {
|
|
1060
|
+
result = await port(item);
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
onError({ code: "pop-out-failed", message: "The window for this tab could not be opened.", detail: error });
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
if (!result) {
|
|
1066
|
+
onError({ code: "pop-out-refused", message: "The app declined to pop this tab out.", detail: { itemId } });
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
const target = canvasWindowId(result.windowId);
|
|
1070
|
+
dispatch(canvasActions.popOut(itemId, target));
|
|
1071
|
+
return target;
|
|
1072
|
+
},
|
|
1073
|
+
dockBack(itemId) {
|
|
1074
|
+
const state = store.getState();
|
|
1075
|
+
const from = canvasWindowOfItem(state, itemId);
|
|
1076
|
+
if (!from || from === CANVAS_MAIN_WINDOW || state.windows[from]?.role !== "pop-out") return false;
|
|
1077
|
+
dispatch(canvasActions.dockBack(itemId));
|
|
1078
|
+
options.windowPorts?.().dockBack?.(itemId, from);
|
|
1079
|
+
return true;
|
|
1080
|
+
},
|
|
1081
|
+
closeWindow: (w, mode = "dock") => dispatch(canvasActions.closeWindow(w, mode)),
|
|
649
1082
|
has: (kind, key) => canvasItemId(kind, key) in store.getState().items,
|
|
650
|
-
isPresented: () => presentations > 0,
|
|
651
|
-
registerPresentation() {
|
|
652
|
-
presentations
|
|
1083
|
+
isPresented: (w = windowId) => (shared.presentations.get(w) ?? 0) > 0,
|
|
1084
|
+
registerPresentation(w = windowId) {
|
|
1085
|
+
shared.presentations.set(w, (shared.presentations.get(w) ?? 0) + 1);
|
|
653
1086
|
notifyPresentation();
|
|
654
1087
|
let done = false;
|
|
655
1088
|
return () => {
|
|
656
1089
|
if (done) return;
|
|
657
1090
|
done = true;
|
|
658
|
-
presentations
|
|
1091
|
+
shared.presentations.set(w, Math.max(0, (shared.presentations.get(w) ?? 1) - 1));
|
|
659
1092
|
notifyPresentation();
|
|
660
1093
|
};
|
|
661
1094
|
},
|
|
662
1095
|
subscribePresentation(listener) {
|
|
663
|
-
presentationListeners.add(listener);
|
|
664
|
-
return () => presentationListeners.delete(listener);
|
|
1096
|
+
shared.presentationListeners.add(listener);
|
|
1097
|
+
return () => shared.presentationListeners.delete(listener);
|
|
665
1098
|
},
|
|
666
1099
|
start() {
|
|
667
1100
|
let disposed = false;
|
|
668
1101
|
let timer = null;
|
|
669
1102
|
let lastSaved = null;
|
|
1103
|
+
const owns = store.authority !== "remote";
|
|
1104
|
+
const memory = owns ? persistence : null;
|
|
670
1105
|
const save = () => {
|
|
671
1106
|
timer = null;
|
|
672
1107
|
const state = store.getState();
|
|
673
|
-
if (!
|
|
1108
|
+
if (!memory || !state.hydrated || state === lastSaved) return;
|
|
674
1109
|
lastSaved = state;
|
|
675
1110
|
try {
|
|
676
|
-
void Promise.resolve(
|
|
1111
|
+
void Promise.resolve(memory.save(toPersistableSnapshot(state, isRestorable))).catch(
|
|
677
1112
|
(error) => onError({ code: "persistence-save", message: "Saving the canvas layout failed.", detail: error })
|
|
678
1113
|
);
|
|
679
1114
|
} catch (error) {
|
|
@@ -682,18 +1117,18 @@ function createCanvasController(options) {
|
|
|
682
1117
|
};
|
|
683
1118
|
let seen = store.getState();
|
|
684
1119
|
const unsubscribe = store.subscribe(() => {
|
|
685
|
-
if (!
|
|
1120
|
+
if (!memory || disposed) return;
|
|
686
1121
|
const current = store.getState();
|
|
687
1122
|
if (current === seen) return;
|
|
688
1123
|
seen = current;
|
|
689
1124
|
if (timer) clearTimeout(timer);
|
|
690
1125
|
timer = setTimeout(save, options.saveDelayMs ?? 250);
|
|
691
1126
|
});
|
|
692
|
-
if (store.getState().hydrated) {
|
|
693
|
-
} else if (!
|
|
1127
|
+
if (!owns || store.getState().hydrated) {
|
|
1128
|
+
} else if (!memory) {
|
|
694
1129
|
dispatch(canvasActions.hydrate(null));
|
|
695
1130
|
} else {
|
|
696
|
-
Promise.resolve().then(() =>
|
|
1131
|
+
Promise.resolve().then(() => memory.load()).then(
|
|
697
1132
|
(snapshot) => {
|
|
698
1133
|
if (disposed) return;
|
|
699
1134
|
const openedThisSession = store.getState().isOpen;
|
|
@@ -712,9 +1147,11 @@ function createCanvasController(options) {
|
|
|
712
1147
|
}
|
|
713
1148
|
);
|
|
714
1149
|
}
|
|
1150
|
+
const stopSessions = startSessions(shared, windowId);
|
|
715
1151
|
return () => {
|
|
716
1152
|
disposed = true;
|
|
717
1153
|
unsubscribe();
|
|
1154
|
+
stopSessions();
|
|
718
1155
|
if (timer) {
|
|
719
1156
|
clearTimeout(timer);
|
|
720
1157
|
save();
|
|
@@ -722,26 +1159,245 @@ function createCanvasController(options) {
|
|
|
722
1159
|
};
|
|
723
1160
|
}
|
|
724
1161
|
};
|
|
1162
|
+
shared.views.set(windowId, controller);
|
|
725
1163
|
return controller;
|
|
726
1164
|
}
|
|
1165
|
+
function startSessions(shared, windowId) {
|
|
1166
|
+
const { options, onError } = shared;
|
|
1167
|
+
const sessions = options.sessions;
|
|
1168
|
+
if (!sessions) return () => void 0;
|
|
1169
|
+
shared.started.set(windowId, (shared.started.get(windowId) ?? 0) + 1);
|
|
1170
|
+
let stopped = false;
|
|
1171
|
+
if (!shared.sessionStop) {
|
|
1172
|
+
const store = options.store;
|
|
1173
|
+
const local = store.authority !== "remote";
|
|
1174
|
+
let tracked = null;
|
|
1175
|
+
const call = (fn) => {
|
|
1176
|
+
try {
|
|
1177
|
+
fn();
|
|
1178
|
+
} catch (error) {
|
|
1179
|
+
onError({ code: "session-hook", message: "A canvas session hook threw.", detail: error });
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
const read = (fn) => {
|
|
1183
|
+
try {
|
|
1184
|
+
return fn();
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
onError({ code: "session-hook", message: "A canvas kind's sessionKey threw.", detail: error });
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
let prevItems = /* @__PURE__ */ new Set();
|
|
1191
|
+
const snapshot = (state) => {
|
|
1192
|
+
const out = /* @__PURE__ */ new Map();
|
|
1193
|
+
for (const w of listCanvasWindowIds(state)) {
|
|
1194
|
+
if (!local && !shared.started.has(w)) continue;
|
|
1195
|
+
const frame2 = canvasWindowFrame(state, w);
|
|
1196
|
+
if (!frame2) continue;
|
|
1197
|
+
for (const paneId of listPaneIds(frame2.layout)) {
|
|
1198
|
+
for (const itemId of state.panes[paneId]?.itemIds ?? []) {
|
|
1199
|
+
const item = state.items[itemId];
|
|
1200
|
+
const hooks = item ? sessions(item.kind) : void 0;
|
|
1201
|
+
if (!item || !hooks) continue;
|
|
1202
|
+
const key = read(() => hooks.sessionKey(item));
|
|
1203
|
+
if (key) out.set(itemId, { item, key, windowId: w });
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return out;
|
|
1208
|
+
};
|
|
1209
|
+
const sync = () => {
|
|
1210
|
+
const state = options.store.getState();
|
|
1211
|
+
if (!state.hydrated) return;
|
|
1212
|
+
const next = snapshot(state);
|
|
1213
|
+
const first = tracked === null;
|
|
1214
|
+
const prev = tracked ?? /* @__PURE__ */ new Map();
|
|
1215
|
+
for (const [id, entry] of prev) {
|
|
1216
|
+
const now = next.get(id);
|
|
1217
|
+
if (now && now.key === entry.key) continue;
|
|
1218
|
+
const hooks = sessions(entry.item.kind);
|
|
1219
|
+
const reason = state.items[id] && !now ? "leave" : "close";
|
|
1220
|
+
call(() => hooks?.onDetach?.({ item: entry.item, sessionKey: entry.key, windowId: entry.windowId, reason }));
|
|
1221
|
+
}
|
|
1222
|
+
for (const [id, entry] of next) {
|
|
1223
|
+
const before = prev.get(id);
|
|
1224
|
+
if (before && before.key === entry.key) continue;
|
|
1225
|
+
const hooks = sessions(entry.item.kind);
|
|
1226
|
+
const reason = first ? "restore" : prevItems.has(id) ? "arrive" : "open";
|
|
1227
|
+
call(() => hooks?.onAttach?.({ item: entry.item, sessionKey: entry.key, windowId: entry.windowId, reason }));
|
|
1228
|
+
}
|
|
1229
|
+
tracked = next;
|
|
1230
|
+
prevItems = new Set(Object.keys(state.items));
|
|
1231
|
+
};
|
|
1232
|
+
let lastState = null;
|
|
1233
|
+
const unsubscribe = store.subscribe(() => {
|
|
1234
|
+
const state = store.getState();
|
|
1235
|
+
if (state === lastState) return;
|
|
1236
|
+
lastState = state;
|
|
1237
|
+
sync();
|
|
1238
|
+
});
|
|
1239
|
+
sync();
|
|
1240
|
+
shared.sessionStop = () => {
|
|
1241
|
+
unsubscribe();
|
|
1242
|
+
for (const entry of tracked?.values() ?? []) {
|
|
1243
|
+
const hooks = sessions(entry.item.kind);
|
|
1244
|
+
call(() => hooks?.onDetach?.({ item: entry.item, sessionKey: entry.key, windowId: entry.windowId, reason: "unmount" }));
|
|
1245
|
+
}
|
|
1246
|
+
tracked = null;
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
return () => {
|
|
1250
|
+
if (stopped) return;
|
|
1251
|
+
stopped = true;
|
|
1252
|
+
const left = (shared.started.get(windowId) ?? 1) - 1;
|
|
1253
|
+
if (left > 0) shared.started.set(windowId, left);
|
|
1254
|
+
else shared.started.delete(windowId);
|
|
1255
|
+
if (shared.started.size === 0 && shared.sessionStop) {
|
|
1256
|
+
const stop = shared.sessionStop;
|
|
1257
|
+
shared.sessionStop = null;
|
|
1258
|
+
stop();
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
727
1262
|
|
|
728
1263
|
// src/core/selectors.ts
|
|
729
|
-
var
|
|
730
|
-
var
|
|
731
|
-
var
|
|
732
|
-
var
|
|
733
|
-
var
|
|
734
|
-
|
|
735
|
-
|
|
1264
|
+
var frame = (s, w) => canvasWindowFrame(s, w ?? CANVAS_MAIN_WINDOW);
|
|
1265
|
+
var selectCanvasIsOpen = (s, windowId) => frame(s, windowId)?.isOpen ?? false;
|
|
1266
|
+
var selectCanvasIsFullscreen = (s, windowId) => frame(s, windowId)?.isFullscreen ?? false;
|
|
1267
|
+
var selectCanvasWidth = (s, windowId) => frame(s, windowId)?.width ?? null;
|
|
1268
|
+
var selectCanvasLayout = (s, windowId) => frame(s, windowId)?.layout ?? null;
|
|
1269
|
+
var selectCanvasFocusedPaneId = (s, windowId) => frame(s, windowId)?.focusedPaneId ?? null;
|
|
1270
|
+
function selectCanvasPaneCount(s, windowId) {
|
|
1271
|
+
const layout = frame(s, windowId)?.layout;
|
|
1272
|
+
return layout ? listPaneIds(layout).length : 0;
|
|
1273
|
+
}
|
|
1274
|
+
function selectCanvasWindowItemCount(s, windowId) {
|
|
1275
|
+
const layout = frame(s, windowId)?.layout;
|
|
1276
|
+
if (!layout) return 0;
|
|
1277
|
+
return listPaneIds(layout).reduce((sum, paneId) => sum + (s.panes[paneId]?.itemIds.length ?? 0), 0);
|
|
1278
|
+
}
|
|
1279
|
+
var NO_WINDOWS = Object.freeze({});
|
|
1280
|
+
|
|
1281
|
+
// src/react/layer.tsx
|
|
1282
|
+
var import_react = require("react");
|
|
1283
|
+
var import_react_dom = require("react-dom");
|
|
1284
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1285
|
+
function createCanvasLayer(getPorts) {
|
|
1286
|
+
let elements = [];
|
|
1287
|
+
let nextId = 1;
|
|
1288
|
+
const domListeners = /* @__PURE__ */ new Set();
|
|
1289
|
+
const emitDom = () => {
|
|
1290
|
+
for (const listener of [...domListeners]) listener();
|
|
1291
|
+
};
|
|
1292
|
+
let blocking = 0;
|
|
1293
|
+
const measures = /* @__PURE__ */ new Set();
|
|
1294
|
+
const last = /* @__PURE__ */ new Map();
|
|
1295
|
+
const remeasure = () => {
|
|
1296
|
+
for (const measure of [...measures]) measure();
|
|
1297
|
+
};
|
|
1298
|
+
return {
|
|
1299
|
+
show(element) {
|
|
1300
|
+
const host = getPorts().overlay;
|
|
1301
|
+
if (host) return host.show(element);
|
|
1302
|
+
const id = nextId++;
|
|
1303
|
+
elements = [...elements, [id, element]];
|
|
1304
|
+
emitDom();
|
|
1305
|
+
let done = false;
|
|
1306
|
+
return () => {
|
|
1307
|
+
if (done) return;
|
|
1308
|
+
done = true;
|
|
1309
|
+
elements = elements.filter(([key]) => key !== id);
|
|
1310
|
+
emitDom();
|
|
1311
|
+
};
|
|
1312
|
+
},
|
|
1313
|
+
beginBlocking() {
|
|
1314
|
+
blocking += 1;
|
|
1315
|
+
remeasure();
|
|
1316
|
+
let done = false;
|
|
1317
|
+
return () => {
|
|
1318
|
+
if (done) return;
|
|
1319
|
+
done = true;
|
|
1320
|
+
blocking -= 1;
|
|
1321
|
+
remeasure();
|
|
1322
|
+
};
|
|
1323
|
+
},
|
|
1324
|
+
isBlocked: () => blocking > 0,
|
|
1325
|
+
dom: {
|
|
1326
|
+
list: () => elements,
|
|
1327
|
+
subscribe(listener) {
|
|
1328
|
+
domListeners.add(listener);
|
|
1329
|
+
return () => domListeners.delete(listener);
|
|
1330
|
+
}
|
|
1331
|
+
},
|
|
1332
|
+
native: {
|
|
1333
|
+
register(measure) {
|
|
1334
|
+
measures.add(measure);
|
|
1335
|
+
return () => measures.delete(measure);
|
|
1336
|
+
},
|
|
1337
|
+
report(itemId, rect, info) {
|
|
1338
|
+
const key = rect ? `${rect.x},${rect.y},${rect.width},${rect.height},${rect.devicePixelRatio},${info.visible},${info.windowId}` : `null,${info.windowId}`;
|
|
1339
|
+
if (last.get(itemId) === key) return;
|
|
1340
|
+
if (rect) last.set(itemId, key);
|
|
1341
|
+
else last.delete(itemId);
|
|
1342
|
+
getPorts().onNativeBodyFrame?.(itemId, rect, info);
|
|
1343
|
+
},
|
|
1344
|
+
remeasure
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function CanvasDomOverlay({ layer }) {
|
|
1349
|
+
const elements = (0, import_react.useSyncExternalStore)(layer.dom.subscribe, layer.dom.list, layer.dom.list);
|
|
1350
|
+
const [mounted, setMounted] = (0, import_react.useState)(false);
|
|
1351
|
+
(0, import_react.useEffect)(() => setMounted(true), []);
|
|
1352
|
+
if (!mounted || elements.length === 0 || typeof document === "undefined") return null;
|
|
1353
|
+
return (0, import_react_dom.createPortal)(
|
|
1354
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mxc-overlay", "aria-hidden": "true", children: elements.map(([id, element]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FloatingView, { element }, id)) }),
|
|
1355
|
+
document.body
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
function FloatingView({ element }) {
|
|
1359
|
+
if (element.type === "drop-indicator") {
|
|
1360
|
+
const { x, y, width, height } = element.rect;
|
|
1361
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mxc-drop-indicator", style: { left: x, top: y, width, height } });
|
|
1362
|
+
}
|
|
1363
|
+
if (element.type === "tooltip") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tooltip, { element });
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
function Tooltip({ element }) {
|
|
1367
|
+
const ref = (0, import_react.useRef)(null);
|
|
1368
|
+
const { anchor, side, text } = element;
|
|
1369
|
+
const top = side === "bottom" ? anchor.y + anchor.height + 6 : anchor.y - 6;
|
|
1370
|
+
const centre = anchor.x + anchor.width / 2;
|
|
1371
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
1372
|
+
const el = ref.current;
|
|
1373
|
+
if (!el) return;
|
|
1374
|
+
const width = el.offsetWidth;
|
|
1375
|
+
const left = Math.min(Math.max(4, centre - width / 2), Math.max(4, window.innerWidth - width - 4));
|
|
1376
|
+
el.style.left = `${left}px`;
|
|
1377
|
+
el.style.visibility = "visible";
|
|
1378
|
+
}, [centre, text]);
|
|
1379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, className: "mxc-tooltip", "data-side": side, role: "tooltip", style: { top, left: centre, visibility: "hidden" }, children: text });
|
|
1380
|
+
}
|
|
1381
|
+
function toCanvasRect(rect) {
|
|
1382
|
+
return { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
|
|
736
1383
|
}
|
|
737
1384
|
|
|
738
1385
|
// src/react/registry.ts
|
|
739
1386
|
function defineCanvasKind(kind) {
|
|
740
|
-
if (!kind.component && !kind.load) {
|
|
741
|
-
throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" needs a component
|
|
1387
|
+
if (!kind.component && !kind.load && kind.body !== "native" && !kind.unavailable) {
|
|
1388
|
+
throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" needs a component, a load(), a native body or an unavailable() state.`);
|
|
742
1389
|
}
|
|
743
1390
|
return kind;
|
|
744
1391
|
}
|
|
1392
|
+
function canvasSessionKey(kind, item) {
|
|
1393
|
+
if (kind.sessionKey) return kind.sessionKey(item.data, item);
|
|
1394
|
+
const data = item.data;
|
|
1395
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1396
|
+
const key = data.sessionKey;
|
|
1397
|
+
return typeof key === "string" && key ? key : null;
|
|
1398
|
+
}
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
745
1401
|
var REGISTRY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.kinds");
|
|
746
1402
|
function registry() {
|
|
747
1403
|
const g = globalThis;
|
|
@@ -783,76 +1439,125 @@ function getCanvasKindsVersion() {
|
|
|
783
1439
|
}
|
|
784
1440
|
|
|
785
1441
|
// src/react/provider.tsx
|
|
786
|
-
var
|
|
1442
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
787
1443
|
var PHONE_MAX_WIDTH = 767;
|
|
788
1444
|
var isPhoneViewport = () => typeof window !== "undefined" && window.innerWidth <= PHONE_MAX_WIDTH;
|
|
1445
|
+
var isMacPlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.userAgentData?.platform ?? navigator.platform ?? "");
|
|
789
1446
|
function escapeBelongsElsewhere(event) {
|
|
790
1447
|
if (event.defaultPrevented) return true;
|
|
791
1448
|
const active = typeof document === "undefined" ? null : document.activeElement;
|
|
792
1449
|
return !!active?.closest?.('[role="menu"], [role="dialog"], [role="alertdialog"], [role="listbox"]');
|
|
793
1450
|
}
|
|
794
|
-
var CanvasContext = (0,
|
|
1451
|
+
var CanvasContext = (0, import_react2.createContext)(null);
|
|
1452
|
+
function sessionHooksFor(kindId) {
|
|
1453
|
+
const kind = getCanvasKind(kindId);
|
|
1454
|
+
if (!kind || kind.restore !== "session") return void 0;
|
|
1455
|
+
return {
|
|
1456
|
+
sessionKey: (item) => canvasSessionKey(kind, item),
|
|
1457
|
+
onAttach: kind.onAttach,
|
|
1458
|
+
onDetach: kind.onDetach
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
795
1461
|
function CanvasProvider({
|
|
796
1462
|
children,
|
|
797
1463
|
store,
|
|
798
1464
|
persistence,
|
|
799
1465
|
onError,
|
|
800
1466
|
hotkeys = true,
|
|
801
|
-
|
|
802
|
-
|
|
1467
|
+
windowId: windowIdProp,
|
|
1468
|
+
layoutRules,
|
|
1469
|
+
regionWidth = null,
|
|
1470
|
+
tabStrip = "auto",
|
|
1471
|
+
...hostPorts
|
|
803
1472
|
}) {
|
|
804
|
-
const
|
|
805
|
-
|
|
1473
|
+
const portsRef = (0, import_react2.useRef)(hostPorts);
|
|
1474
|
+
portsRef.current = hostPorts;
|
|
1475
|
+
const [{ controller, layer }] = (0, import_react2.useState)(() => ({
|
|
1476
|
+
controller: createCanvasController({
|
|
806
1477
|
store: store ?? createCanvasStore(),
|
|
807
1478
|
persistence: persistence === void 0 ? createLocalStorageCanvasPersistence() : persistence,
|
|
808
1479
|
isRestorable: (kind) => getCanvasKind(kind)?.restore !== false,
|
|
809
1480
|
mayRestoreOpen: () => !isPhoneViewport(),
|
|
810
|
-
onError
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1481
|
+
onError,
|
|
1482
|
+
windowId: windowIdProp ?? CANVAS_MAIN_WINDOW,
|
|
1483
|
+
layoutRules,
|
|
1484
|
+
windowPorts: () => portsRef.current,
|
|
1485
|
+
sessions: sessionHooksFor
|
|
1486
|
+
}),
|
|
1487
|
+
layer: createCanvasLayer(() => portsRef.current)
|
|
1488
|
+
}));
|
|
1489
|
+
const windowId = controller.windowId;
|
|
1490
|
+
(0, import_react2.useEffect)(() => controller.start(), [controller]);
|
|
1491
|
+
(0, import_react2.useEffect)(() => {
|
|
815
1492
|
if (!hotkeys) return;
|
|
1493
|
+
const isMac3 = isMacPlatform();
|
|
1494
|
+
const applies = (when, event) => {
|
|
1495
|
+
if (!controller.isPresented()) return false;
|
|
1496
|
+
if (when === "presented") return true;
|
|
1497
|
+
if (when === "fullscreen") return !!canvasWindowFrame(controller.getState(), windowId)?.isFullscreen && !escapeBelongsElsewhere(event);
|
|
1498
|
+
const column = document.activeElement?.closest?.("[data-mxc-window]");
|
|
1499
|
+
return column?.getAttribute("data-mxc-window") === windowId;
|
|
1500
|
+
};
|
|
816
1501
|
const onKey = (event) => {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
event
|
|
820
|
-
controller.
|
|
821
|
-
|
|
822
|
-
controller.setFullscreen(false);
|
|
1502
|
+
for (const shortcut of CANVAS_SHORTCUTS) {
|
|
1503
|
+
if (!shortcut.web || !matchesCanvasAccelerator(event, shortcut.accelerator, isMac3)) continue;
|
|
1504
|
+
if (!applies(shortcut.when, event)) return;
|
|
1505
|
+
if (runCanvasCommand(controller, shortcut.id)) event.preventDefault();
|
|
1506
|
+
return;
|
|
823
1507
|
}
|
|
824
1508
|
};
|
|
825
1509
|
window.addEventListener("keydown", onKey);
|
|
826
1510
|
return () => window.removeEventListener("keydown", onKey);
|
|
827
|
-
}, [controller, hotkeys]);
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1511
|
+
}, [controller, hotkeys, windowId]);
|
|
1512
|
+
const value = { controller, windowId, ports: hostPorts, layer, regionWidth, tabStrip };
|
|
1513
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(CanvasContext.Provider, { value, children: [
|
|
1514
|
+
children,
|
|
1515
|
+
hostPorts.overlay ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasDomOverlay, { layer })
|
|
1516
|
+
] });
|
|
1517
|
+
}
|
|
1518
|
+
function CanvasWindowScope({ windowId, children }) {
|
|
1519
|
+
const parent = useCanvasContext();
|
|
1520
|
+
const id = canvasWindowId(windowId);
|
|
1521
|
+
const value = { ...parent, windowId: id, controller: parent.controller.forWindow(id) };
|
|
1522
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CanvasContext.Provider, { value, children });
|
|
833
1523
|
}
|
|
834
1524
|
function useCanvasContext() {
|
|
835
|
-
const value = (0,
|
|
1525
|
+
const value = (0, import_react2.useContext)(CanvasContext);
|
|
836
1526
|
if (!value) throw new Error("[@ai-matrx/canvas] useCanvas* must be used inside <CanvasProvider>.");
|
|
837
1527
|
return value;
|
|
838
1528
|
}
|
|
1529
|
+
function useCanvasInternals() {
|
|
1530
|
+
return useCanvasContext();
|
|
1531
|
+
}
|
|
1532
|
+
function useOptionalCanvasInternals() {
|
|
1533
|
+
return (0, import_react2.useContext)(CanvasContext);
|
|
1534
|
+
}
|
|
839
1535
|
function useCanvas() {
|
|
840
1536
|
return useCanvasContext().controller;
|
|
841
1537
|
}
|
|
842
1538
|
function useOptionalCanvas() {
|
|
843
|
-
return (0,
|
|
1539
|
+
return (0, import_react2.useContext)(CanvasContext)?.controller ?? null;
|
|
1540
|
+
}
|
|
1541
|
+
function useCanvasWindowId() {
|
|
1542
|
+
return (0, import_react2.useContext)(CanvasContext)?.windowId ?? CANVAS_MAIN_WINDOW;
|
|
844
1543
|
}
|
|
845
1544
|
function useCanvasHostPorts() {
|
|
846
1545
|
return useCanvasContext().ports;
|
|
847
1546
|
}
|
|
1547
|
+
function useCanvasNativeBodies() {
|
|
1548
|
+
return useCanvasContext().layer.native;
|
|
1549
|
+
}
|
|
848
1550
|
function useRegisterCanvasPresence() {
|
|
849
|
-
const
|
|
850
|
-
|
|
1551
|
+
const context = (0, import_react2.useContext)(CanvasContext);
|
|
1552
|
+
const controller = context?.controller;
|
|
1553
|
+
const windowId = context?.windowId;
|
|
1554
|
+
(0, import_react2.useEffect)(() => controller?.registerPresentation(windowId), [controller, windowId]);
|
|
851
1555
|
}
|
|
852
1556
|
var noPresence = () => () => void 0;
|
|
853
1557
|
function useCanvasIsPresented() {
|
|
854
|
-
const
|
|
855
|
-
|
|
1558
|
+
const context = (0, import_react2.useContext)(CanvasContext);
|
|
1559
|
+
const controller = context?.controller;
|
|
1560
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
856
1561
|
controller ? controller.subscribePresentation : noPresence,
|
|
857
1562
|
() => controller?.isPresented() ?? false,
|
|
858
1563
|
() => false
|
|
@@ -860,7 +1565,7 @@ function useCanvasIsPresented() {
|
|
|
860
1565
|
}
|
|
861
1566
|
function useCanvasState(selector) {
|
|
862
1567
|
const { store } = useCanvasContext().controller;
|
|
863
|
-
return (0,
|
|
1568
|
+
return (0, import_react2.useSyncExternalStore)(
|
|
864
1569
|
store.subscribe,
|
|
865
1570
|
() => selector(store.getState()),
|
|
866
1571
|
() => selector(store.getState())
|
|
@@ -868,32 +1573,34 @@ function useCanvasState(selector) {
|
|
|
868
1573
|
}
|
|
869
1574
|
var NO_SUBSCRIPTION = () => () => void 0;
|
|
870
1575
|
function useOptionalCanvasState(selector, fallback) {
|
|
871
|
-
const store = (0,
|
|
1576
|
+
const store = (0, import_react2.useContext)(CanvasContext)?.controller.store;
|
|
872
1577
|
const read = () => store ? selector(store.getState()) : fallback;
|
|
873
|
-
return (0,
|
|
1578
|
+
return (0, import_react2.useSyncExternalStore)(store ? store.subscribe : NO_SUBSCRIPTION, read, read);
|
|
874
1579
|
}
|
|
875
1580
|
function useCanvasKinds() {
|
|
876
|
-
(0,
|
|
1581
|
+
(0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
|
|
877
1582
|
return listCanvasKinds();
|
|
878
1583
|
}
|
|
879
1584
|
function useCanvasKind(id) {
|
|
880
|
-
(0,
|
|
1585
|
+
(0, import_react2.useSyncExternalStore)(subscribeCanvasKinds, getCanvasKindsVersion, getCanvasKindsVersion);
|
|
881
1586
|
return getCanvasKind(id);
|
|
882
1587
|
}
|
|
883
1588
|
|
|
884
1589
|
// src/react/CanvasColumn.tsx
|
|
885
|
-
var
|
|
886
|
-
var
|
|
1590
|
+
var import_react5 = require("react");
|
|
1591
|
+
var import_tap_target = require("@ai-matrx/tap-target");
|
|
887
1592
|
|
|
888
1593
|
// src/react/CanvasPane.tsx
|
|
889
|
-
var
|
|
890
|
-
var import_tap_target = require("@ai-matrx/tap-target");
|
|
1594
|
+
var import_react4 = require("react");
|
|
891
1595
|
var Menu = __toESM(require("@radix-ui/react-dropdown-menu"), 1);
|
|
892
1596
|
|
|
1597
|
+
// src/react/chrome.tsx
|
|
1598
|
+
var import_react3 = require("react");
|
|
1599
|
+
|
|
893
1600
|
// src/react/icons.tsx
|
|
894
|
-
var
|
|
1601
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
895
1602
|
function Glyph({ children, ...props }) {
|
|
896
|
-
return /* @__PURE__ */ (0,
|
|
1603
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
897
1604
|
"svg",
|
|
898
1605
|
{
|
|
899
1606
|
xmlns: "http://www.w3.org/2000/svg",
|
|
@@ -911,61 +1618,335 @@ function Glyph({ children, ...props }) {
|
|
|
911
1618
|
}
|
|
912
1619
|
);
|
|
913
1620
|
}
|
|
914
|
-
var CloseIcon = (p) => /* @__PURE__ */ (0,
|
|
915
|
-
/* @__PURE__ */ (0,
|
|
916
|
-
/* @__PURE__ */ (0,
|
|
1621
|
+
var CloseIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1622
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 6 6 18" }),
|
|
1623
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m6 6 12 12" })
|
|
1624
|
+
] });
|
|
1625
|
+
var MaximizeIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1626
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15 3h6v6" }),
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m21 3-7 7" }),
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m3 21 7-7" }),
|
|
1629
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 21H3v-6" })
|
|
917
1630
|
] });
|
|
918
|
-
var
|
|
919
|
-
/* @__PURE__ */ (0,
|
|
920
|
-
/* @__PURE__ */ (0,
|
|
921
|
-
/* @__PURE__ */ (0,
|
|
922
|
-
/* @__PURE__ */ (0,
|
|
1631
|
+
var MinimizeIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m14 10 7-7" }),
|
|
1633
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M20 10h-6V4" }),
|
|
1634
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m3 21 7-7" }),
|
|
1635
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 14h6v6" })
|
|
923
1636
|
] });
|
|
924
|
-
var
|
|
925
|
-
/* @__PURE__ */ (0,
|
|
926
|
-
/* @__PURE__ */ (0,
|
|
927
|
-
/* @__PURE__ */ (0,
|
|
928
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 14h6v6" })
|
|
1637
|
+
var MoreIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1638
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
|
|
1639
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "5", r: "1" }),
|
|
1640
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "19", r: "1" })
|
|
929
1641
|
] });
|
|
930
|
-
var
|
|
931
|
-
/* @__PURE__ */ (0,
|
|
932
|
-
/* @__PURE__ */ (0,
|
|
933
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "19", r: "1" })
|
|
1642
|
+
var SplitRightIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1643
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
1644
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 3v18" })
|
|
934
1645
|
] });
|
|
935
|
-
var
|
|
936
|
-
/* @__PURE__ */ (0,
|
|
937
|
-
/* @__PURE__ */ (0,
|
|
1646
|
+
var SplitDownIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1647
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
1648
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3 12h18" })
|
|
938
1649
|
] });
|
|
939
|
-
var
|
|
940
|
-
/* @__PURE__ */ (0,
|
|
941
|
-
/* @__PURE__ */ (0,
|
|
1650
|
+
var PanelRightIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1651
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
|
|
1652
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15 3v18" })
|
|
942
1653
|
] });
|
|
943
|
-
var
|
|
944
|
-
/* @__PURE__ */ (0,
|
|
945
|
-
/* @__PURE__ */ (0,
|
|
1654
|
+
var PopOutIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1655
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15 3h6v6" }),
|
|
1656
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10 14 21 3" }),
|
|
1657
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
|
|
946
1658
|
] });
|
|
947
|
-
var
|
|
948
|
-
/* @__PURE__ */ (0,
|
|
949
|
-
/* @__PURE__ */ (0,
|
|
950
|
-
/* @__PURE__ */ (0,
|
|
1659
|
+
var AlertIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1660
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1661
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 8v4" }),
|
|
1662
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 16h.01" })
|
|
951
1663
|
] });
|
|
952
|
-
var
|
|
953
|
-
/* @__PURE__ */ (0,
|
|
954
|
-
/* @__PURE__ */ (0,
|
|
955
|
-
|
|
1664
|
+
var ArrowLeftIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m12 19-7-7 7-7" }),
|
|
1666
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M19 12H5" })
|
|
1667
|
+
] });
|
|
1668
|
+
var ArrowRightIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1669
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5 12h14" }),
|
|
1670
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m12 5 7 7-7 7" })
|
|
1671
|
+
] });
|
|
1672
|
+
var RotateCwIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1673
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
|
|
1674
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3v5h-5" })
|
|
1675
|
+
] });
|
|
1676
|
+
var GlobeIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1677
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1678
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }),
|
|
1679
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2 12h20" })
|
|
1680
|
+
] });
|
|
1681
|
+
var PlusIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1682
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5 12h14" }),
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 5v14" })
|
|
1684
|
+
] });
|
|
1685
|
+
var DockIcon = (p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Glyph, { ...p, children: [
|
|
1686
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h6" }),
|
|
1687
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 3 12 12" }),
|
|
1688
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M12 6v6h6" })
|
|
956
1689
|
] });
|
|
957
1690
|
|
|
1691
|
+
// src/react/chrome.tsx
|
|
1692
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1693
|
+
var cx = (...names) => names.filter(Boolean).join(" ");
|
|
1694
|
+
function useTooltip(text) {
|
|
1695
|
+
const internals = useOptionalCanvasInternals();
|
|
1696
|
+
const timer = (0, import_react3.useRef)(null);
|
|
1697
|
+
const hide = (0, import_react3.useRef)(null);
|
|
1698
|
+
const close = () => {
|
|
1699
|
+
if (timer.current) clearTimeout(timer.current);
|
|
1700
|
+
timer.current = null;
|
|
1701
|
+
hide.current?.();
|
|
1702
|
+
hide.current = null;
|
|
1703
|
+
};
|
|
1704
|
+
(0, import_react3.useEffect)(() => close, []);
|
|
1705
|
+
if (!text) return { close, handlers: {} };
|
|
1706
|
+
if (!internals) return { close, handlers: { title: text } };
|
|
1707
|
+
const open = (element, delay) => {
|
|
1708
|
+
close();
|
|
1709
|
+
timer.current = setTimeout(() => {
|
|
1710
|
+
timer.current = null;
|
|
1711
|
+
if (!element.isConnected) return;
|
|
1712
|
+
hide.current = internals.layer.show({ type: "tooltip", text, anchor: toCanvasRect(element.getBoundingClientRect()), side: "bottom" });
|
|
1713
|
+
}, delay);
|
|
1714
|
+
};
|
|
1715
|
+
return {
|
|
1716
|
+
close,
|
|
1717
|
+
handlers: {
|
|
1718
|
+
onPointerEnter: (event) => {
|
|
1719
|
+
if (event.pointerType === "mouse") open(event.currentTarget, 450);
|
|
1720
|
+
},
|
|
1721
|
+
onPointerLeave: close,
|
|
1722
|
+
onPointerDown: close,
|
|
1723
|
+
onFocus: (event) => {
|
|
1724
|
+
if (event.currentTarget.matches(":focus-visible")) open(event.currentTarget, 0);
|
|
1725
|
+
},
|
|
1726
|
+
onBlur: close
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
var CanvasHeaderButton = (0, import_react3.forwardRef)(function CanvasHeaderButton2({ label, shortcut, available = true, size = "sm", pressed, className, children, onClick, onPointerEnter, onPointerLeave, onPointerDown, onFocus, onBlur, ...rest }, ref) {
|
|
1731
|
+
const tooltip = useTooltip(shortcut ? `${label} (${shortcut})` : label);
|
|
1732
|
+
const own = tooltip.handlers;
|
|
1733
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1734
|
+
"button",
|
|
1735
|
+
{
|
|
1736
|
+
ref,
|
|
1737
|
+
type: "button",
|
|
1738
|
+
"aria-label": label,
|
|
1739
|
+
"aria-pressed": pressed,
|
|
1740
|
+
"data-slot": "canvas-panel-action",
|
|
1741
|
+
"data-size": size,
|
|
1742
|
+
"data-available": available ? void 0 : "false",
|
|
1743
|
+
className: cx("mxc-icon-button", className),
|
|
1744
|
+
...rest,
|
|
1745
|
+
...own.title ? { title: own.title } : {},
|
|
1746
|
+
"aria-disabled": available ? rest["aria-disabled"] : true,
|
|
1747
|
+
onClick: (event) => {
|
|
1748
|
+
tooltip.close();
|
|
1749
|
+
if (available) onClick?.(event);
|
|
1750
|
+
},
|
|
1751
|
+
onPointerEnter: (event) => {
|
|
1752
|
+
onPointerEnter?.(event);
|
|
1753
|
+
own.onPointerEnter?.(event);
|
|
1754
|
+
},
|
|
1755
|
+
onPointerLeave: (event) => {
|
|
1756
|
+
onPointerLeave?.(event);
|
|
1757
|
+
own.onPointerLeave?.();
|
|
1758
|
+
},
|
|
1759
|
+
onPointerDown: (event) => {
|
|
1760
|
+
onPointerDown?.(event);
|
|
1761
|
+
own.onPointerDown?.();
|
|
1762
|
+
},
|
|
1763
|
+
onFocus: (event) => {
|
|
1764
|
+
onFocus?.(event);
|
|
1765
|
+
own.onFocus?.(event);
|
|
1766
|
+
},
|
|
1767
|
+
onBlur: (event) => {
|
|
1768
|
+
onBlur?.(event);
|
|
1769
|
+
own.onBlur?.();
|
|
1770
|
+
},
|
|
1771
|
+
children
|
|
1772
|
+
}
|
|
1773
|
+
);
|
|
1774
|
+
});
|
|
1775
|
+
var CanvasMenuTrigger = (0, import_react3.forwardRef)(
|
|
1776
|
+
function CanvasMenuTrigger2({ label = "More", ...props }, ref) {
|
|
1777
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { ref, label, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MoreIcon, {}) });
|
|
1778
|
+
}
|
|
1779
|
+
);
|
|
1780
|
+
var CanvasPanel = (0, import_react3.forwardRef)(function CanvasPanel2({
|
|
1781
|
+
panelId,
|
|
1782
|
+
title,
|
|
1783
|
+
leading,
|
|
1784
|
+
header,
|
|
1785
|
+
actions,
|
|
1786
|
+
renderMenu,
|
|
1787
|
+
expanded = false,
|
|
1788
|
+
onToggleExpand,
|
|
1789
|
+
expandShortcut,
|
|
1790
|
+
onHide,
|
|
1791
|
+
onClose,
|
|
1792
|
+
closeLabel = "Close",
|
|
1793
|
+
focused,
|
|
1794
|
+
bodyProps,
|
|
1795
|
+
className,
|
|
1796
|
+
children,
|
|
1797
|
+
...props
|
|
1798
|
+
}, ref) {
|
|
1799
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1800
|
+
"section",
|
|
1801
|
+
{
|
|
1802
|
+
ref,
|
|
1803
|
+
"data-slot": "canvas-panel",
|
|
1804
|
+
"data-panel": panelId,
|
|
1805
|
+
"data-expanded": expanded ? "" : void 0,
|
|
1806
|
+
"data-focused": focused ? "" : void 0,
|
|
1807
|
+
"aria-label": typeof title === "string" ? title : void 0,
|
|
1808
|
+
className: cx("mxc-panel", className),
|
|
1809
|
+
...props,
|
|
1810
|
+
children: [
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { "data-slot": "canvas-panel-header", className: "mxc-pane-header", children: [
|
|
1812
|
+
header ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-panel-title", children: title }),
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "mxc-pane-leading", children: leading }),
|
|
1814
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "mxc-pane-actions", children: [
|
|
1815
|
+
actions,
|
|
1816
|
+
renderMenu ? renderMenu(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasMenuTrigger, {})) : null,
|
|
1817
|
+
onToggleExpand ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { className: "mxc-desktop-only", label: expanded ? "Restore" : "Expand", shortcut: expandShortcut, onClick: onToggleExpand, children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MinimizeIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MaximizeIcon, {}) }) : null,
|
|
1818
|
+
onHide ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { className: "mxc-phone-only", label: "Hide canvas", onClick: onHide, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelRightIcon, {}) }) : null,
|
|
1819
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { label: closeLabel, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseIcon, {}) }) : null
|
|
1820
|
+
] })
|
|
1821
|
+
] }),
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-slot": "canvas-panel-body", ...bodyProps, className: cx("mxc-pane-body", bodyProps?.className), children })
|
|
1823
|
+
]
|
|
1824
|
+
}
|
|
1825
|
+
);
|
|
1826
|
+
});
|
|
1827
|
+
function CanvasPanelEmpty({ children, className }) {
|
|
1828
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-slot": "canvas-panel-empty", className: cx("mxc-empty-line", className), children });
|
|
1829
|
+
}
|
|
1830
|
+
var CanvasTab = (0, import_react3.forwardRef)(function CanvasTab2({ title, icon, note, active, onSelect, onClose, className, onKeyDown, onClick, ...rest }, ref) {
|
|
1831
|
+
const shown = title || "New tab";
|
|
1832
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1833
|
+
"div",
|
|
1834
|
+
{
|
|
1835
|
+
ref,
|
|
1836
|
+
role: "tab",
|
|
1837
|
+
"aria-selected": active,
|
|
1838
|
+
tabIndex: active ? 0 : -1,
|
|
1839
|
+
"data-slot": "browser-tab",
|
|
1840
|
+
"data-active": active ? "" : void 0,
|
|
1841
|
+
title: shown,
|
|
1842
|
+
className: cx("mxc-tab", className),
|
|
1843
|
+
...rest,
|
|
1844
|
+
onClick: (event) => {
|
|
1845
|
+
onClick?.(event);
|
|
1846
|
+
onSelect();
|
|
1847
|
+
},
|
|
1848
|
+
onKeyDown: (event) => {
|
|
1849
|
+
onKeyDown?.(event);
|
|
1850
|
+
if (event.defaultPrevented) return;
|
|
1851
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1852
|
+
event.preventDefault();
|
|
1853
|
+
onSelect();
|
|
1854
|
+
}
|
|
1855
|
+
},
|
|
1856
|
+
children: [
|
|
1857
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-tab-icon", children: icon }),
|
|
1858
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-tab-title", children: shown }),
|
|
1859
|
+
note ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "mxc-tab-note", "data-slot": "browser-tab-note", children: note }) : null,
|
|
1860
|
+
onClose ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1861
|
+
"button",
|
|
1862
|
+
{
|
|
1863
|
+
type: "button",
|
|
1864
|
+
className: "mxc-tab-close",
|
|
1865
|
+
"data-slot": "browser-tab-close",
|
|
1866
|
+
"aria-label": `Close ${shown}`,
|
|
1867
|
+
onClick: (event) => {
|
|
1868
|
+
event.stopPropagation();
|
|
1869
|
+
onClose();
|
|
1870
|
+
},
|
|
1871
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CloseIcon, {})
|
|
1872
|
+
}
|
|
1873
|
+
) : null
|
|
1874
|
+
]
|
|
1875
|
+
}
|
|
1876
|
+
);
|
|
1877
|
+
});
|
|
1878
|
+
function BrowserTabs({ tabs, activeId, onSelect, onClose, onNew, newLabel = "New tab", label = "Tabs", className }) {
|
|
1879
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-slot": "browser-tabs", role: "tablist", "aria-label": label, className: cx("mxc-tabs", className), children: [
|
|
1880
|
+
tabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1881
|
+
CanvasTab,
|
|
1882
|
+
{
|
|
1883
|
+
"data-tab": tab.id,
|
|
1884
|
+
title: tab.title,
|
|
1885
|
+
note: tab.note,
|
|
1886
|
+
active: tab.id === activeId,
|
|
1887
|
+
icon: tab.glyph ?? (tab.icon ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: tab.icon, alt: "", className: "mxc-favicon" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(GlobeIcon, {})),
|
|
1888
|
+
onSelect: () => onSelect(tab.id),
|
|
1889
|
+
onClose: () => onClose(tab.id)
|
|
1890
|
+
},
|
|
1891
|
+
tab.id
|
|
1892
|
+
)),
|
|
1893
|
+
onNew ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { label: newLabel, "data-slot": "browser-tab-new", onClick: onNew, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlusIcon, {}) }) : null
|
|
1894
|
+
] });
|
|
1895
|
+
}
|
|
1896
|
+
function BrowserBar({ url, canGoBack, canGoForward, onBack, onForward, onReload, onNavigate, trailing, className }) {
|
|
1897
|
+
const [draft, setDraft] = (0, import_react3.useState)(url);
|
|
1898
|
+
const [editing, setEditing] = (0, import_react3.useState)(false);
|
|
1899
|
+
(0, import_react3.useEffect)(() => {
|
|
1900
|
+
if (!editing) setDraft(url);
|
|
1901
|
+
}, [url, editing]);
|
|
1902
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-slot": "browser-bar", className: cx("mxc-browser-bar", className), children: [
|
|
1903
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { size: "md", label: "Back", available: canGoBack, onClick: onBack, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ArrowLeftIcon, {}) }),
|
|
1904
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { size: "md", label: "Forward", available: canGoForward, onClick: onForward, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ArrowRightIcon, {}) }),
|
|
1905
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CanvasHeaderButton, { size: "md", label: "Reload", onClick: onReload, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RotateCwIcon, {}) }),
|
|
1906
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1907
|
+
"form",
|
|
1908
|
+
{
|
|
1909
|
+
className: "mxc-address-form",
|
|
1910
|
+
onSubmit: (event) => {
|
|
1911
|
+
event.preventDefault();
|
|
1912
|
+
setEditing(false);
|
|
1913
|
+
onNavigate(draft.trim());
|
|
1914
|
+
event.currentTarget.elements.namedItem("address")?.blur();
|
|
1915
|
+
},
|
|
1916
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1917
|
+
"input",
|
|
1918
|
+
{
|
|
1919
|
+
name: "address",
|
|
1920
|
+
"aria-label": "Address",
|
|
1921
|
+
"data-slot": "browser-address",
|
|
1922
|
+
className: "mxc-address",
|
|
1923
|
+
value: draft,
|
|
1924
|
+
placeholder: "Search or enter an address",
|
|
1925
|
+
spellCheck: false,
|
|
1926
|
+
onFocus: (event) => {
|
|
1927
|
+
setEditing(true);
|
|
1928
|
+
event.currentTarget.select();
|
|
1929
|
+
},
|
|
1930
|
+
onBlur: () => setEditing(false),
|
|
1931
|
+
onChange: (event) => setDraft(event.target.value)
|
|
1932
|
+
}
|
|
1933
|
+
)
|
|
1934
|
+
}
|
|
1935
|
+
),
|
|
1936
|
+
trailing ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-slot": "browser-bar-trailing", className: "mxc-browser-bar-trailing", children: trailing }) : null
|
|
1937
|
+
] });
|
|
1938
|
+
}
|
|
1939
|
+
|
|
958
1940
|
// src/react/CanvasPane.tsx
|
|
959
|
-
var
|
|
1941
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
960
1942
|
var DRAG_MIME = "application/x-matrx-canvas-item";
|
|
1943
|
+
var useIsoLayoutEffect = typeof window === "undefined" ? import_react4.useEffect : import_react4.useLayoutEffect;
|
|
961
1944
|
var LAZY = /* @__PURE__ */ Symbol.for("ai-matrx.canvas.lazy");
|
|
962
1945
|
function kindComponent(kind) {
|
|
963
1946
|
if (kind.component) return kind.component;
|
|
964
1947
|
const holder = kind;
|
|
965
|
-
if (!holder[LAZY] && kind.load) holder[LAZY] = (0,
|
|
966
|
-
|
|
967
|
-
if (!resolved) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
968
|
-
return resolved;
|
|
1948
|
+
if (!holder[LAZY] && kind.load) holder[LAZY] = (0, import_react4.lazy)(kind.load);
|
|
1949
|
+
return holder[LAZY] ?? null;
|
|
969
1950
|
}
|
|
970
1951
|
function resetKindComponent(kind) {
|
|
971
1952
|
if (kind) delete kind[LAZY];
|
|
@@ -981,128 +1962,134 @@ function itemTitle(item, kind) {
|
|
|
981
1962
|
}
|
|
982
1963
|
return kind?.label ?? item.kind;
|
|
983
1964
|
}
|
|
1965
|
+
var isMac = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
|
|
1966
|
+
function useTabDrag(itemId, title) {
|
|
1967
|
+
const { layer } = useCanvasInternals();
|
|
1968
|
+
const active = (0, import_react4.useRef)([]);
|
|
1969
|
+
const end = () => {
|
|
1970
|
+
for (const dispose of active.current.splice(0)) dispose();
|
|
1971
|
+
};
|
|
1972
|
+
(0, import_react4.useEffect)(() => end, []);
|
|
1973
|
+
return {
|
|
1974
|
+
draggable: true,
|
|
1975
|
+
onDragStart: (event) => {
|
|
1976
|
+
event.dataTransfer.setData(DRAG_MIME, itemId);
|
|
1977
|
+
event.dataTransfer.effectAllowed = "move";
|
|
1978
|
+
end();
|
|
1979
|
+
active.current.push(layer.beginBlocking(), layer.show({ type: "tab-drag", itemId, title }));
|
|
1980
|
+
},
|
|
1981
|
+
onDragEnd: end
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
984
1984
|
function CanvasPaneView({ paneId }) {
|
|
985
1985
|
const canvas = useCanvas();
|
|
1986
|
+
const { windowId, layer, ports, tabStrip } = useCanvasInternals();
|
|
986
1987
|
const pane = useCanvasState((s) => s.panes[paneId]);
|
|
987
1988
|
const items = useCanvasState((s) => s.items);
|
|
988
|
-
const
|
|
989
|
-
const
|
|
1989
|
+
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
1990
|
+
const focused = useCanvasState((s) => selectCanvasFocusedPaneId(s, windowId) === paneId);
|
|
1991
|
+
const paneCount = useCanvasState((s) => selectCanvasPaneCount(s, windowId));
|
|
1992
|
+
const role = useCanvasState((s) => windowId === CANVAS_MAIN_WINDOW ? "main" : s.windows[windowId]?.role ?? "window");
|
|
1993
|
+
const active = pane?.activeItemId ? items[pane.activeItemId] : void 0;
|
|
1994
|
+
const kind = useCanvasKind(active?.kind ?? "");
|
|
1995
|
+
const drop = (0, import_react4.useRef)(null);
|
|
1996
|
+
const sectionRef = (0, import_react4.useRef)(null);
|
|
1997
|
+
(0, import_react4.useEffect)(() => () => drop.current?.(), []);
|
|
990
1998
|
if (!pane) return null;
|
|
991
|
-
const
|
|
1999
|
+
const kindProps = active && kind ? { item: active, data: active.data, paneId, isFocused: focused, canvas, windowId } : null;
|
|
2000
|
+
const Leading = kind?.HeaderLeading;
|
|
2001
|
+
const Action = kind?.HeaderAction;
|
|
2002
|
+
const single = tabStrip === "auto" && pane.itemIds.length === 1 && active;
|
|
2003
|
+
const hideDrop = () => {
|
|
2004
|
+
drop.current?.();
|
|
2005
|
+
drop.current = null;
|
|
2006
|
+
};
|
|
992
2007
|
const onDragOver = (event) => {
|
|
993
2008
|
if (!event.dataTransfer.types.includes(DRAG_MIME)) return;
|
|
994
2009
|
event.preventDefault();
|
|
995
2010
|
event.dataTransfer.dropEffect = "move";
|
|
996
|
-
|
|
2011
|
+
if (!drop.current && sectionRef.current) {
|
|
2012
|
+
drop.current = layer.show({ type: "drop-indicator", rect: toCanvasRect(sectionRef.current.getBoundingClientRect()), paneId });
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
const onDragLeave = (event) => {
|
|
2016
|
+
const next = event.relatedTarget;
|
|
2017
|
+
if (next && sectionRef.current?.contains(next)) return;
|
|
2018
|
+
hideDrop();
|
|
997
2019
|
};
|
|
998
2020
|
const onDrop = (event) => {
|
|
999
|
-
|
|
2021
|
+
hideDrop();
|
|
1000
2022
|
const itemId = event.dataTransfer.getData(DRAG_MIME);
|
|
1001
2023
|
if (!itemId) return;
|
|
1002
2024
|
event.preventDefault();
|
|
1003
2025
|
canvas.moveItem(itemId, paneId);
|
|
1004
2026
|
};
|
|
1005
|
-
|
|
1006
|
-
|
|
2027
|
+
const closeLabel = role === "pop-out" && paneCount === 1 ? "Close window" : paneCount > 1 ? "Close pane" : "Close canvas";
|
|
2028
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2029
|
+
CanvasPanel,
|
|
1007
2030
|
{
|
|
2031
|
+
ref: sectionRef,
|
|
2032
|
+
panelId: paneId,
|
|
1008
2033
|
className: "mxc-pane",
|
|
1009
|
-
|
|
1010
|
-
|
|
2034
|
+
title: active ? itemTitle(active, kind) : "Canvas",
|
|
2035
|
+
focused,
|
|
2036
|
+
"data-mxc-pane": paneId,
|
|
2037
|
+
header: single && active ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SingleTitle, { item: active, kind }) : pane.itemIds.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-tabs", role: "tablist", "aria-label": "Canvas tabs", children: pane.itemIds.map((itemId, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemTab, { itemId, paneId, index, active: itemId === pane.activeItemId }, itemId)) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mxc-panel-title", children: "New tab" }),
|
|
2038
|
+
leading: Leading && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Leading, { ...kindProps }) : null,
|
|
2039
|
+
actions: Action && kindProps ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Action, { ...kindProps }) : null,
|
|
2040
|
+
renderMenu: () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaneMenu, { paneId, windowId, activeItem: active, kind, kindProps, tabCount: pane.itemIds.length, role }),
|
|
2041
|
+
expanded: isFullscreen,
|
|
2042
|
+
onToggleExpand: () => canvas.setFullscreen(!isFullscreen),
|
|
2043
|
+
expandShortcut: canvasShortcutHint("canvas.toggleFullscreen", isMac()),
|
|
2044
|
+
onHide: windowId === CANVAS_MAIN_WINDOW ? () => canvas.hide() : void 0,
|
|
2045
|
+
onClose: () => canvas.closePane(paneId),
|
|
2046
|
+
closeLabel,
|
|
1011
2047
|
onPointerDownCapture: () => {
|
|
1012
2048
|
if (!focused) canvas.focusPane(paneId);
|
|
1013
2049
|
},
|
|
1014
2050
|
onDragOver,
|
|
1015
|
-
onDragLeave
|
|
2051
|
+
onDragLeave,
|
|
1016
2052
|
onDrop,
|
|
1017
|
-
children:
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ItemBody, { item, paneId, isActive, isFocused: focused && isActive }, itemId);
|
|
1024
|
-
}) })
|
|
1025
|
-
]
|
|
2053
|
+
children: pane.itemIds.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Launcher, { paneId }) : pane.itemIds.map((itemId) => {
|
|
2054
|
+
const item = items[itemId];
|
|
2055
|
+
if (!item) return null;
|
|
2056
|
+
const isActive = itemId === pane.activeItemId;
|
|
2057
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBody, { item, paneId, windowId, isActive, isFocused: focused && isActive, hasNativePort: !!ports.onNativeBodyFrame }, itemId);
|
|
2058
|
+
})
|
|
1026
2059
|
}
|
|
1027
2060
|
);
|
|
1028
2061
|
}
|
|
1029
|
-
function
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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
|
-
] })
|
|
2062
|
+
function SingleTitle({ item, kind }) {
|
|
2063
|
+
const title = itemTitle(item, kind);
|
|
2064
|
+
const drag = useTabDrag(item.id, title);
|
|
2065
|
+
const Icon = kind?.icon ?? AlertIcon;
|
|
2066
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mxc-panel-title", "data-draggable": "", title, ...drag, children: [
|
|
2067
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-title-icon" }),
|
|
2068
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mxc-tab-title", children: title })
|
|
1073
2069
|
] });
|
|
1074
2070
|
}
|
|
1075
|
-
function
|
|
1076
|
-
itemId,
|
|
1077
|
-
paneId,
|
|
1078
|
-
index,
|
|
1079
|
-
active
|
|
1080
|
-
}) {
|
|
2071
|
+
function ItemTab({ itemId, paneId, index, active }) {
|
|
1081
2072
|
const canvas = useCanvas();
|
|
1082
2073
|
const item = useCanvasState((s) => s.items[itemId]);
|
|
1083
2074
|
const kind = useCanvasKind(item?.kind ?? "");
|
|
1084
|
-
const ref = (0,
|
|
1085
|
-
(
|
|
1086
|
-
|
|
2075
|
+
const ref = (0, import_react4.useRef)(null);
|
|
2076
|
+
const title = item ? itemTitle(item, kind) : "";
|
|
2077
|
+
const drag = useTabDrag(itemId, title);
|
|
2078
|
+
(0, import_react4.useEffect)(() => {
|
|
2079
|
+
if (active) ref.current?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
1087
2080
|
}, [active]);
|
|
1088
2081
|
if (!item) return null;
|
|
1089
|
-
const Icon = kind?.icon;
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
"div",
|
|
2082
|
+
const Icon = kind?.icon ?? AlertIcon;
|
|
2083
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2084
|
+
CanvasTab,
|
|
1093
2085
|
{
|
|
1094
2086
|
ref,
|
|
1095
|
-
className: "mxc-tab",
|
|
1096
|
-
role: "tab",
|
|
1097
|
-
"aria-selected": active,
|
|
1098
|
-
"data-active": active ? "" : void 0,
|
|
1099
|
-
tabIndex: active ? 0 : -1,
|
|
1100
2087
|
title,
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
2088
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, {}),
|
|
2089
|
+
active,
|
|
2090
|
+
onSelect: () => canvas.activate(itemId),
|
|
2091
|
+
onClose: () => canvas.close(itemId),
|
|
2092
|
+
...drag,
|
|
1106
2093
|
onDragOver: (event) => {
|
|
1107
2094
|
if (event.dataTransfer.types.includes(DRAG_MIME)) event.preventDefault();
|
|
1108
2095
|
},
|
|
@@ -1110,83 +2097,89 @@ function Tab({
|
|
|
1110
2097
|
const dragged = event.dataTransfer.getData(DRAG_MIME);
|
|
1111
2098
|
if (!dragged) return;
|
|
1112
2099
|
event.preventDefault();
|
|
1113
|
-
event.stopPropagation();
|
|
1114
2100
|
if (dragged !== itemId) canvas.moveItem(dragged, paneId, index);
|
|
1115
2101
|
},
|
|
1116
|
-
onClick: () => canvas.activate(itemId),
|
|
1117
2102
|
onAuxClick: (event) => {
|
|
1118
2103
|
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
|
-
]
|
|
2104
|
+
}
|
|
1140
2105
|
}
|
|
1141
2106
|
);
|
|
1142
2107
|
}
|
|
1143
2108
|
function PaneMenu({
|
|
1144
2109
|
paneId,
|
|
2110
|
+
windowId,
|
|
1145
2111
|
activeItem,
|
|
1146
2112
|
kind,
|
|
1147
2113
|
kindProps,
|
|
1148
|
-
|
|
2114
|
+
tabCount,
|
|
2115
|
+
role
|
|
1149
2116
|
}) {
|
|
1150
2117
|
const canvas = useCanvas();
|
|
1151
|
-
const ports =
|
|
1152
|
-
const moveId =
|
|
2118
|
+
const { ports, layer } = useCanvasInternals();
|
|
2119
|
+
const moveId = tabCount > 1 ? activeItem?.id : void 0;
|
|
2120
|
+
const blocking = (0, import_react4.useRef)(null);
|
|
2121
|
+
(0, import_react4.useEffect)(() => () => blocking.current?.(), []);
|
|
1153
2122
|
const kindItems = kind?.menuItems && kindProps ? safeMenu(() => kind.menuItems?.(kindProps)) : [];
|
|
1154
2123
|
const hostItems = activeItem && ports.itemActions ? safeMenu(() => ports.itemActions?.(activeItem, kind)) : [];
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
2124
|
+
const sections = [
|
|
2125
|
+
[...kindItems, ...hostItems].map((entry) => ({ ...entry, id: `item:${entry.id}` })),
|
|
2126
|
+
[
|
|
2127
|
+
{ id: "split-right", label: "Split right", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SplitRightIcon, {}), onSelect: () => canvas.splitPane(paneId, "horizontal", moveId) },
|
|
2128
|
+
{ id: "split-down", label: "Split down", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SplitDownIcon, {}), onSelect: () => canvas.splitPane(paneId, "vertical", moveId) },
|
|
2129
|
+
...activeItem && role === "pop-out" ? [{ id: "dock-back", label: "Dock back", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(DockIcon, {}), onSelect: () => void canvas.dockBack(activeItem.id) }] : activeItem && ports.popOut ? [{ id: "pop-out", label: "Pop out", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PopOutIcon, {}), onSelect: () => void canvas.popOut(activeItem.id) }] : []
|
|
2130
|
+
],
|
|
2131
|
+
activeItem ? [
|
|
2132
|
+
{ id: "close-tab", label: "Close tab", onSelect: () => canvas.close(activeItem.id) },
|
|
2133
|
+
...tabCount > 1 ? [{ id: "close-others", label: "Close other tabs", onSelect: () => canvas.closeOthers(activeItem.id) }] : []
|
|
2134
|
+
] : []
|
|
2135
|
+
].filter((section) => section.length > 0);
|
|
2136
|
+
const showMenu = ports.showMenu;
|
|
2137
|
+
if (showMenu) {
|
|
2138
|
+
const entries = [];
|
|
2139
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
2140
|
+
sections.forEach((section, i) => {
|
|
2141
|
+
if (i > 0) entries.push({ type: "separator" });
|
|
2142
|
+
for (const action of section) {
|
|
2143
|
+
entries.push({ type: "item", id: action.id, label: action.label, ...action.destructive ? { destructive: true } : {} });
|
|
2144
|
+
handlers.set(action.id, action.onSelect);
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2148
|
+
CanvasMenuTrigger,
|
|
2149
|
+
{
|
|
2150
|
+
onClick: (event) => {
|
|
2151
|
+
const anchor = toCanvasRect(event.currentTarget.getBoundingClientRect());
|
|
2152
|
+
void showMenu(entries, anchor).then(
|
|
2153
|
+
(chosen) => {
|
|
2154
|
+
if (chosen) handlers.get(chosen)?.();
|
|
2155
|
+
},
|
|
2156
|
+
(error) => console.error("[@ai-matrx/canvas] the host menu failed", error)
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
2163
|
+
Menu.Root,
|
|
2164
|
+
{
|
|
2165
|
+
onOpenChange: (open) => {
|
|
2166
|
+
blocking.current?.();
|
|
2167
|
+
blocking.current = open ? layer.beginBlocking() : null;
|
|
2168
|
+
},
|
|
2169
|
+
children: [
|
|
2170
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasMenuTrigger, {}) }),
|
|
2171
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Content, { align: "end", sideOffset: 4, className: "mxc-menu", children: sections.map((section, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MenuSection, { first: i === 0, actions: section }, i)) }) })
|
|
2172
|
+
]
|
|
2173
|
+
}
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
function MenuSection({ first, actions }) {
|
|
2177
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
2178
|
+
first ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Menu.Separator, { className: "mxc-menu-separator" }),
|
|
2179
|
+
actions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Menu.Item, { onSelect: action.onSelect, className: "mxc-menu-item", "data-destructive": action.destructive ? "" : void 0, children: [
|
|
2180
|
+
action.icon,
|
|
2181
|
+
action.label
|
|
2182
|
+
] }, action.id))
|
|
1190
2183
|
] });
|
|
1191
2184
|
}
|
|
1192
2185
|
function safeMenu(build) {
|
|
@@ -1200,29 +2193,84 @@ function safeMenu(build) {
|
|
|
1200
2193
|
function ItemBody({
|
|
1201
2194
|
item,
|
|
1202
2195
|
paneId,
|
|
2196
|
+
windowId,
|
|
1203
2197
|
isActive,
|
|
1204
|
-
isFocused
|
|
2198
|
+
isFocused,
|
|
2199
|
+
hasNativePort
|
|
1205
2200
|
}) {
|
|
1206
2201
|
const canvas = useCanvas();
|
|
1207
2202
|
const kind = useCanvasKind(item.kind);
|
|
1208
2203
|
if (!isActive && !kind?.keepAlive) return null;
|
|
1209
|
-
|
|
2204
|
+
const props = { item, data: item.data, paneId, isFocused, canvas, windowId };
|
|
2205
|
+
let unavailable = null;
|
|
2206
|
+
try {
|
|
2207
|
+
unavailable = kind?.unavailable?.(item.data, item) ?? null;
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
console.error(`[@ai-matrx/canvas] "${item.kind}" unavailable() threw`, error);
|
|
2210
|
+
}
|
|
2211
|
+
let body;
|
|
2212
|
+
if (!kind) {
|
|
2213
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemProblem, { message: "This item can no longer be shown.", onClose: () => canvas.close(item.id) });
|
|
2214
|
+
} else if (unavailable) {
|
|
2215
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: unavailable });
|
|
2216
|
+
} else if (kind.body === "native") {
|
|
2217
|
+
body = hasNativePort ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NativeBody, { itemId: item.id, windowId, children: kindComponent(kind) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props }) : null }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Opens in the desktop app" });
|
|
2218
|
+
} else {
|
|
2219
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Rendered, { kind, item, props });
|
|
2220
|
+
}
|
|
2221
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-item", hidden: !isActive, "data-kind": item.kind, children: body });
|
|
2222
|
+
}
|
|
2223
|
+
function Rendered({ kind, item, props }) {
|
|
2224
|
+
const canvas = useCanvas();
|
|
2225
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ItemBoundary, { item, onClose: () => canvas.close(item.id), onRetry: () => resetKindComponent(kind), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.Suspense, { fallback: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-loading", "aria-busy": "true" }), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KindRender, { kind, props }) }) });
|
|
1210
2226
|
}
|
|
1211
2227
|
function KindRender({ kind, props }) {
|
|
1212
2228
|
const Render = kindComponent(kind);
|
|
1213
|
-
|
|
2229
|
+
if (!Render) throw new Error(`[@ai-matrx/canvas] kind "${kind.id}" has nothing to render.`);
|
|
2230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Render, { ...props });
|
|
2231
|
+
}
|
|
2232
|
+
function NativeBody({ itemId, windowId, children }) {
|
|
2233
|
+
const { layer } = useCanvasInternals();
|
|
2234
|
+
const ref = (0, import_react4.useRef)(null);
|
|
2235
|
+
const measure = (0, import_react4.useRef)(() => void 0);
|
|
2236
|
+
measure.current = () => {
|
|
2237
|
+
const el = ref.current;
|
|
2238
|
+
const box = el?.isConnected ? el.getBoundingClientRect() : null;
|
|
2239
|
+
if (!box || box.width <= 0 || box.height <= 0) {
|
|
2240
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
const rect = { ...toCanvasRect(box), devicePixelRatio: window.devicePixelRatio || 1 };
|
|
2244
|
+
layer.native.report(itemId, rect, { visible: !layer.isBlocked(), windowId });
|
|
2245
|
+
};
|
|
2246
|
+
useIsoLayoutEffect(() => measure.current());
|
|
2247
|
+
useIsoLayoutEffect(() => {
|
|
2248
|
+
const el = ref.current;
|
|
2249
|
+
const run = () => measure.current();
|
|
2250
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(run);
|
|
2251
|
+
if (el) observer?.observe(el);
|
|
2252
|
+
const unregister = layer.native.register(run);
|
|
2253
|
+
window.addEventListener("resize", run);
|
|
2254
|
+
return () => {
|
|
2255
|
+
observer?.disconnect();
|
|
2256
|
+
unregister();
|
|
2257
|
+
window.removeEventListener("resize", run);
|
|
2258
|
+
layer.native.report(itemId, null, { visible: false, windowId });
|
|
2259
|
+
};
|
|
2260
|
+
}, [itemId, windowId, layer]);
|
|
2261
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref, className: "mxc-native-body", "data-native-body": itemId, children });
|
|
1214
2262
|
}
|
|
1215
2263
|
function ItemProblem({ message, onClose, onRetry }) {
|
|
1216
|
-
return /* @__PURE__ */ (0,
|
|
1217
|
-
/* @__PURE__ */ (0,
|
|
1218
|
-
/* @__PURE__ */ (0,
|
|
1219
|
-
/* @__PURE__ */ (0,
|
|
1220
|
-
onRetry ? /* @__PURE__ */ (0,
|
|
1221
|
-
/* @__PURE__ */ (0,
|
|
2264
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem", role: "alert", children: [
|
|
2265
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlertIcon, {}),
|
|
2266
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: message }),
|
|
2267
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mxc-problem-actions", children: [
|
|
2268
|
+
onRetry ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onRetry, children: "Try again" }) : null,
|
|
2269
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mxc-text-button", onClick: onClose, children: "Close tab" })
|
|
1222
2270
|
] })
|
|
1223
2271
|
] });
|
|
1224
2272
|
}
|
|
1225
|
-
var ItemBoundary = class extends
|
|
2273
|
+
var ItemBoundary = class extends import_react4.Component {
|
|
1226
2274
|
state = { error: null };
|
|
1227
2275
|
static getDerivedStateFromError(error) {
|
|
1228
2276
|
return { error };
|
|
@@ -1232,7 +2280,7 @@ var ItemBoundary = class extends import_react2.Component {
|
|
|
1232
2280
|
}
|
|
1233
2281
|
render() {
|
|
1234
2282
|
if (this.state.error) {
|
|
1235
|
-
return /* @__PURE__ */ (0,
|
|
2283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1236
2284
|
ItemProblem,
|
|
1237
2285
|
{
|
|
1238
2286
|
message: "This item failed to load.",
|
|
@@ -1250,12 +2298,12 @@ var ItemBoundary = class extends import_react2.Component {
|
|
|
1250
2298
|
function Launcher({ paneId }) {
|
|
1251
2299
|
const canvas = useCanvas();
|
|
1252
2300
|
const kinds = useCanvasKinds().filter((kind) => kind.launcher);
|
|
1253
|
-
if (kinds.length === 0) return /* @__PURE__ */ (0,
|
|
1254
|
-
return /* @__PURE__ */ (0,
|
|
2301
|
+
if (kinds.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CanvasPanelEmpty, { children: "Nothing open" });
|
|
2302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mxc-launcher", children: kinds.map((kind) => {
|
|
1255
2303
|
const Icon = kind.icon;
|
|
1256
2304
|
const launcher = kind.launcher;
|
|
1257
2305
|
if (!launcher) return null;
|
|
1258
|
-
return /* @__PURE__ */ (0,
|
|
2306
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1259
2307
|
"button",
|
|
1260
2308
|
{
|
|
1261
2309
|
type: "button",
|
|
@@ -1268,8 +2316,8 @@ function Launcher({ paneId }) {
|
|
|
1268
2316
|
target: { paneId }
|
|
1269
2317
|
}),
|
|
1270
2318
|
children: [
|
|
1271
|
-
/* @__PURE__ */ (0,
|
|
1272
|
-
/* @__PURE__ */ (0,
|
|
2319
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "mxc-launcher-icon" }),
|
|
2320
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: launcher.title ?? kind.label })
|
|
1273
2321
|
]
|
|
1274
2322
|
},
|
|
1275
2323
|
kind.id
|
|
@@ -1278,69 +2326,72 @@ function Launcher({ paneId }) {
|
|
|
1278
2326
|
}
|
|
1279
2327
|
|
|
1280
2328
|
// src/react/CanvasColumn.tsx
|
|
1281
|
-
var
|
|
1282
|
-
var
|
|
2329
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2330
|
+
var useIsoLayoutEffect2 = typeof window === "undefined" ? import_react5.useEffect : import_react5.useLayoutEffect;
|
|
1283
2331
|
function subscribeViewport(listener) {
|
|
1284
2332
|
window.addEventListener("resize", listener);
|
|
1285
2333
|
return () => window.removeEventListener("resize", listener);
|
|
1286
2334
|
}
|
|
1287
2335
|
function useViewportWidth() {
|
|
1288
|
-
return (0,
|
|
2336
|
+
return (0, import_react5.useSyncExternalStore)(subscribeViewport, () => window.innerWidth, () => 0);
|
|
1289
2337
|
}
|
|
1290
|
-
function
|
|
1291
|
-
|
|
1292
|
-
|
|
2338
|
+
function useRegionWidth() {
|
|
2339
|
+
const viewport = useViewportWidth();
|
|
2340
|
+
const { regionWidth } = useCanvasInternals();
|
|
2341
|
+
return regionWidth ?? viewport;
|
|
1293
2342
|
}
|
|
1294
2343
|
function useCanvasColumnWidth() {
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
const
|
|
2344
|
+
const { controller, windowId } = useCanvasInternals();
|
|
2345
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2346
|
+
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2347
|
+
const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
|
|
2348
|
+
const region = useRegionWidth();
|
|
1299
2349
|
if (!isOpen) return 0;
|
|
1300
2350
|
if (isFullscreen) return null;
|
|
1301
|
-
return
|
|
1302
|
-
}
|
|
1303
|
-
function CanvasColumn({ className, style, onLiveWidth }) {
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
const
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
2351
|
+
return fitCanvasWidth(width, region, controller.rules);
|
|
2352
|
+
}
|
|
2353
|
+
function CanvasColumn({ className, style, onLiveWidth, placement = "docked" }) {
|
|
2354
|
+
const { controller, windowId } = useCanvasInternals();
|
|
2355
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2356
|
+
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2357
|
+
const width = useCanvasState((s) => selectCanvasWidth(s, windowId));
|
|
2358
|
+
const layout = useCanvasState((s) => selectCanvasLayout(s, windowId));
|
|
2359
|
+
const region = useRegionWidth();
|
|
2360
|
+
const [dragWidth, setDragWidth] = (0, import_react5.useState)(null);
|
|
1310
2361
|
useRegisterCanvasPresence();
|
|
1311
|
-
if (!isOpen) return null;
|
|
1312
|
-
const fitted =
|
|
2362
|
+
if (!isOpen || !layout) return null;
|
|
2363
|
+
const fitted = fitCanvasWidth(width, region, controller.rules);
|
|
1313
2364
|
const shown = dragWidth ?? fitted;
|
|
1314
|
-
return /* @__PURE__ */ (0,
|
|
2365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1315
2366
|
"aside",
|
|
1316
2367
|
{
|
|
1317
2368
|
className: ["mxc-column", className].filter(Boolean).join(" "),
|
|
1318
2369
|
"data-fullscreen": isFullscreen ? "" : void 0,
|
|
2370
|
+
"data-placement": placement,
|
|
2371
|
+
"data-mxc-window": windowId,
|
|
1319
2372
|
"aria-label": "Canvas",
|
|
1320
2373
|
style: { ...style, ["--mxc-width"]: `${shown}px` },
|
|
1321
2374
|
children: [
|
|
1322
|
-
!isFullscreen ? /* @__PURE__ */ (0,
|
|
2375
|
+
placement === "docked" && !isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1323
2376
|
WidthHandle,
|
|
1324
2377
|
{
|
|
1325
2378
|
width: fitted,
|
|
2379
|
+
region,
|
|
1326
2380
|
onDrag: (next) => {
|
|
1327
2381
|
setDragWidth(next);
|
|
1328
2382
|
onLiveWidth?.(next);
|
|
1329
2383
|
}
|
|
1330
2384
|
}
|
|
1331
2385
|
) : null,
|
|
1332
|
-
/* @__PURE__ */ (0,
|
|
2386
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: layout }) })
|
|
1333
2387
|
]
|
|
1334
2388
|
}
|
|
1335
2389
|
);
|
|
1336
2390
|
}
|
|
1337
|
-
function WidthHandle({ width, onDrag }) {
|
|
2391
|
+
function WidthHandle({ width, region, onDrag }) {
|
|
1338
2392
|
const canvas = useCanvas();
|
|
1339
|
-
const start = (0,
|
|
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
|
-
};
|
|
2393
|
+
const start = (0, import_react5.useRef)(null);
|
|
2394
|
+
const clamp = (next) => clampDraggedCanvasWidth(next, region, canvas.rules);
|
|
1344
2395
|
const onPointerDown = (event) => {
|
|
1345
2396
|
event.preventDefault();
|
|
1346
2397
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
@@ -1359,7 +2410,7 @@ function WidthHandle({ width, onDrag }) {
|
|
|
1359
2410
|
onDrag(null);
|
|
1360
2411
|
canvas.setWidth(next);
|
|
1361
2412
|
};
|
|
1362
|
-
return /* @__PURE__ */ (0,
|
|
2413
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1363
2414
|
"div",
|
|
1364
2415
|
{
|
|
1365
2416
|
className: "mxc-width-handle",
|
|
@@ -1372,7 +2423,7 @@ function WidthHandle({ width, onDrag }) {
|
|
|
1372
2423
|
onPointerMove,
|
|
1373
2424
|
onPointerUp: finish,
|
|
1374
2425
|
onPointerCancel: finish,
|
|
1375
|
-
onDoubleClick: () => canvas.setWidth(Math.round(
|
|
2426
|
+
onDoubleClick: () => canvas.setWidth(clamp(Math.round(region / 2))),
|
|
1376
2427
|
onKeyDown: (event) => {
|
|
1377
2428
|
if (event.key === "ArrowLeft") canvas.setWidth(clamp(width + 32));
|
|
1378
2429
|
if (event.key === "ArrowRight") canvas.setWidth(clamp(width - 32));
|
|
@@ -1381,15 +2432,31 @@ function WidthHandle({ width, onDrag }) {
|
|
|
1381
2432
|
);
|
|
1382
2433
|
}
|
|
1383
2434
|
function LayoutNodeView({ node }) {
|
|
1384
|
-
if (node.type === "pane") return /* @__PURE__ */ (0,
|
|
1385
|
-
return /* @__PURE__ */ (0,
|
|
2435
|
+
if (node.type === "pane") return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasPaneView, { paneId: node.paneId });
|
|
2436
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SplitView, { node });
|
|
2437
|
+
}
|
|
2438
|
+
function useMeasuredWidth(ref, enabled) {
|
|
2439
|
+
const [width, setWidth] = (0, import_react5.useState)(0);
|
|
2440
|
+
useIsoLayoutEffect2(() => {
|
|
2441
|
+
const el = ref.current;
|
|
2442
|
+
if (!enabled || !el) return;
|
|
2443
|
+
setWidth(el.getBoundingClientRect().width);
|
|
2444
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
2445
|
+
const observer = new ResizeObserver(() => setWidth(el.getBoundingClientRect().width));
|
|
2446
|
+
observer.observe(el);
|
|
2447
|
+
return () => observer.disconnect();
|
|
2448
|
+
}, [enabled]);
|
|
2449
|
+
return width;
|
|
1386
2450
|
}
|
|
1387
2451
|
function SplitView({ node }) {
|
|
1388
2452
|
const canvas = useCanvas();
|
|
1389
|
-
const ref = (0,
|
|
1390
|
-
const [liveSizes, setLiveSizes] = (0,
|
|
2453
|
+
const ref = (0, import_react5.useRef)(null);
|
|
2454
|
+
const [liveSizes, setLiveSizes] = (0, import_react5.useState)(null);
|
|
1391
2455
|
const sizes = liveSizes ?? node.sizes;
|
|
1392
|
-
const
|
|
2456
|
+
const measuredWidth = useMeasuredWidth(ref, node.orientation === "horizontal" && canvas.rules.paneMinWidth > 0);
|
|
2457
|
+
const stacked = node.orientation === "horizontal" && shouldStackSplit(measuredWidth, node.children.length, canvas.rules);
|
|
2458
|
+
const orientation = stacked ? "vertical" : node.orientation;
|
|
2459
|
+
const horizontal = orientation === "horizontal";
|
|
1393
2460
|
const beginDrag = (index) => (event) => {
|
|
1394
2461
|
const el = ref.current;
|
|
1395
2462
|
if (!el) return;
|
|
@@ -1401,10 +2468,10 @@ function SplitView({ node }) {
|
|
|
1401
2468
|
const base = [...node.sizes];
|
|
1402
2469
|
const pair = (base[index] ?? 0) + (base[index + 1] ?? 0);
|
|
1403
2470
|
const target = event.currentTarget;
|
|
1404
|
-
document.documentElement.dataset.mxcResizing = "";
|
|
2471
|
+
document.documentElement.dataset.mxcResizing = horizontal ? "col" : "row";
|
|
1405
2472
|
const compute = (pos) => {
|
|
1406
2473
|
const delta = (pos - origin) / total;
|
|
1407
|
-
const min =
|
|
2474
|
+
const min = CANVAS_MIN_SPLIT_FRACTION;
|
|
1408
2475
|
const first = Math.min(pair - min, Math.max(min, (base[index] ?? 0) + delta));
|
|
1409
2476
|
const next = [...base];
|
|
1410
2477
|
next[index] = first;
|
|
@@ -1424,11 +2491,11 @@ function SplitView({ node }) {
|
|
|
1424
2491
|
target.addEventListener("pointerup", up);
|
|
1425
2492
|
target.addEventListener("pointercancel", up);
|
|
1426
2493
|
};
|
|
1427
|
-
return /* @__PURE__ */ (0,
|
|
2494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref, className: "mxc-split", "data-orientation": orientation, "data-stacked": stacked ? "" : void 0, children: node.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1428
2495
|
SplitChild,
|
|
1429
2496
|
{
|
|
1430
2497
|
size: sizes[i] ?? 1 / node.children.length,
|
|
1431
|
-
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0,
|
|
2498
|
+
handle: i < node.children.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1432
2499
|
"div",
|
|
1433
2500
|
{
|
|
1434
2501
|
className: "mxc-split-handle",
|
|
@@ -1438,40 +2505,43 @@ function SplitView({ node }) {
|
|
|
1438
2505
|
onDoubleClick: () => canvas.resizeSplit(node.id, node.children.map(() => 1 / node.children.length))
|
|
1439
2506
|
}
|
|
1440
2507
|
) : null,
|
|
1441
|
-
children: /* @__PURE__ */ (0,
|
|
2508
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LayoutNodeView, { node: child })
|
|
1442
2509
|
},
|
|
1443
2510
|
child.type === "pane" ? child.paneId : child.id
|
|
1444
2511
|
)) });
|
|
1445
2512
|
}
|
|
1446
2513
|
function SplitChild({ size, handle, children }) {
|
|
1447
|
-
return /* @__PURE__ */ (0,
|
|
1448
|
-
/* @__PURE__ */ (0,
|
|
2514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
2515
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-split-child", style: { flexGrow: size, flexBasis: 0 }, children }),
|
|
1449
2516
|
handle
|
|
1450
2517
|
] });
|
|
1451
2518
|
}
|
|
2519
|
+
var isMac2 = () => typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
|
|
1452
2520
|
function CanvasToggle({ className }) {
|
|
1453
|
-
const
|
|
1454
|
-
const isOpen = useCanvasState(selectCanvasIsOpen);
|
|
1455
|
-
const count = useCanvasState(
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
2521
|
+
const { controller, windowId } = useCanvasInternals();
|
|
2522
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2523
|
+
const count = useCanvasState((s) => selectCanvasWindowItemCount(s, windowId));
|
|
2524
|
+
const hint = canvasShortcutHint("canvas.toggle", isMac2());
|
|
2525
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: ["mxc-toggle", className].filter(Boolean).join(" "), "data-open": isOpen ? "" : void 0, children: [
|
|
2526
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2527
|
+
import_tap_target.TapTargetButtonTransparent,
|
|
1459
2528
|
{
|
|
1460
2529
|
ariaLabel: isOpen ? "Hide canvas" : "Show canvas",
|
|
1461
|
-
tooltip: isOpen ? "Hide canvas
|
|
1462
|
-
icon: /* @__PURE__ */ (0,
|
|
1463
|
-
onClick: () =>
|
|
2530
|
+
tooltip: `${isOpen ? "Hide canvas" : "Show canvas"}${hint ? ` (${hint})` : ""}`,
|
|
2531
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PanelRightIcon, {}),
|
|
2532
|
+
onClick: () => controller.toggle()
|
|
1464
2533
|
}
|
|
1465
2534
|
),
|
|
1466
|
-
count > 0 && !isOpen ? /* @__PURE__ */ (0,
|
|
2535
|
+
count > 0 && !isOpen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mxc-toggle-badge", children: count }) : null
|
|
1467
2536
|
] });
|
|
1468
2537
|
}
|
|
1469
2538
|
function CanvasFrame({ children, className }) {
|
|
1470
|
-
const
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
/* @__PURE__ */ (0,
|
|
2539
|
+
const { windowId } = useCanvasInternals();
|
|
2540
|
+
const isFullscreen = useCanvasState((s) => selectCanvasIsFullscreen(s, windowId));
|
|
2541
|
+
const isOpen = useCanvasState((s) => selectCanvasIsOpen(s, windowId));
|
|
2542
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: ["mxc-frame", className].filter(Boolean).join(" "), "data-canvas-open": isOpen ? "" : void 0, children: [
|
|
2543
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mxc-frame-main", hidden: isOpen && isFullscreen, children }),
|
|
2544
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CanvasColumn, {})
|
|
1475
2545
|
] });
|
|
1476
2546
|
}
|
|
1477
2547
|
//# sourceMappingURL=react.cjs.map
|