@labpane/os 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.js +432 -156
- package/dist/index.js.map +4 -4
- package/dist/os/browser-history.d.ts +6 -0
- package/dist/os/context.d.ts +6 -0
- package/dist/os/index.d.ts +4 -0
- package/dist/os/leave-tab.d.ts +4 -0
- package/dist/os/overlay.d.ts +1 -0
- package/dist/os/tab-history.d.ts +17 -0
- package/dist/os/tab-map.d.ts +2 -0
- package/dist/os/tab-state.d.ts +10 -0
- package/dist/os/workspace.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// workspace.tsx
|
|
2
2
|
import {
|
|
3
|
-
useEffect as
|
|
3
|
+
useEffect as useEffect4,
|
|
4
4
|
useId as useId2,
|
|
5
|
-
useLayoutEffect as
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
useLayoutEffect as useLayoutEffect8,
|
|
6
|
+
useMemo as useMemo2,
|
|
7
|
+
useRef as useRef9,
|
|
8
|
+
useState as useState5
|
|
8
9
|
} from "react";
|
|
9
10
|
|
|
10
11
|
// context.ts
|
|
@@ -52,9 +53,9 @@ function densitySize(density) {
|
|
|
52
53
|
const [from, to] = value < compact.value ? [dense, compact] : [compact, comfortable];
|
|
53
54
|
const amount = (value - from.value) / (to.value - from.value);
|
|
54
55
|
return Object.fromEntries(
|
|
55
|
-
Object.keys(from).map((
|
|
56
|
-
|
|
57
|
-
from[
|
|
56
|
+
Object.keys(from).map((key2) => [
|
|
57
|
+
key2,
|
|
58
|
+
from[key2] + (to[key2] - from[key2]) * amount
|
|
58
59
|
])
|
|
59
60
|
);
|
|
60
61
|
}
|
|
@@ -67,8 +68,20 @@ function useTabSize() {
|
|
|
67
68
|
function useWorkspaceIds() {
|
|
68
69
|
const prefix = useContext(WorkspaceId);
|
|
69
70
|
return {
|
|
70
|
-
tab: (
|
|
71
|
-
panel: (
|
|
71
|
+
tab: (key2) => `${prefix}-tab-${encodeURIComponent(key2)}`,
|
|
72
|
+
panel: (key2) => `${prefix}-panel-${encodeURIComponent(key2)}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
var TabLinks = createContext(/* @__PURE__ */ new Map());
|
|
76
|
+
function useTabLinks() {
|
|
77
|
+
const links = useContext(TabLinks);
|
|
78
|
+
return {
|
|
79
|
+
register: (key2) => (element) => {
|
|
80
|
+
if (element) links.set(key2, element);
|
|
81
|
+
else links.delete(key2);
|
|
82
|
+
},
|
|
83
|
+
get: (key2) => links.get(key2),
|
|
84
|
+
focus: (key2) => requestAnimationFrame(() => links.get(key2)?.focus())
|
|
72
85
|
};
|
|
73
86
|
}
|
|
74
87
|
|
|
@@ -102,14 +115,14 @@ function reconcileLayout(node, keys, active, preferred) {
|
|
|
102
115
|
return first && second ? first === node2.first && second === node2.second ? node2 : { ...node2, first, second } : first ?? second;
|
|
103
116
|
}
|
|
104
117
|
const tabs = node2.tabs.filter(
|
|
105
|
-
(
|
|
118
|
+
(key2) => keys.includes(key2) && !seen.has(key2) && !!seen.add(key2)
|
|
106
119
|
);
|
|
107
120
|
if (!tabs.length) return void 0;
|
|
108
121
|
const selected = tabs.includes(active ?? "") ? active : tabs.includes(node2.active ?? "") ? node2.active : tabs[0];
|
|
109
122
|
return tabs.length === node2.tabs.length && selected === node2.active ? node2 : { ...node2, tabs, active: selected };
|
|
110
123
|
};
|
|
111
124
|
let next = clean(node) ?? { id: "primary", tabs: [] };
|
|
112
|
-
const missing = keys.filter((
|
|
125
|
+
const missing = keys.filter((key2) => !seen.has(key2));
|
|
113
126
|
if (missing.length) {
|
|
114
127
|
const leaves = panes(next), target = leaves.find((p) => p.id === preferred) ?? leaves[0];
|
|
115
128
|
next = updateLayout(next, target.id, (node2) => ({
|
|
@@ -122,7 +135,7 @@ function reconcileLayout(node, keys, active, preferred) {
|
|
|
122
135
|
}
|
|
123
136
|
function restoreLayout(value, keys) {
|
|
124
137
|
const ids = /* @__PURE__ */ new Set();
|
|
125
|
-
function
|
|
138
|
+
function read2(value2, depth = 0) {
|
|
126
139
|
const n = value2;
|
|
127
140
|
if (!n || depth > 12 || typeof n.id !== "string" || !n.id || ids.has(n.id))
|
|
128
141
|
throw Error("Invalid layout");
|
|
@@ -134,11 +147,11 @@ function restoreLayout(value, keys) {
|
|
|
134
147
|
id: n.id,
|
|
135
148
|
axis: n.axis,
|
|
136
149
|
ratio: clamp(n.ratio, 0.05, 0.95),
|
|
137
|
-
first:
|
|
138
|
-
second:
|
|
150
|
+
first: read2(n.first, depth + 1),
|
|
151
|
+
second: read2(n.second, depth + 1)
|
|
139
152
|
};
|
|
140
153
|
}
|
|
141
|
-
if (!Array.isArray(n.tabs) || n.tabs.some((
|
|
154
|
+
if (!Array.isArray(n.tabs) || n.tabs.some((key2) => typeof key2 !== "string"))
|
|
142
155
|
throw Error("Invalid tabs");
|
|
143
156
|
return {
|
|
144
157
|
id: n.id,
|
|
@@ -147,7 +160,7 @@ function restoreLayout(value, keys) {
|
|
|
147
160
|
};
|
|
148
161
|
}
|
|
149
162
|
try {
|
|
150
|
-
return reconcileLayout(
|
|
163
|
+
return reconcileLayout(read2(JSON.parse(value ?? "null")), keys);
|
|
151
164
|
} catch {
|
|
152
165
|
return { id: "primary", tabs: keys, active: keys[0] };
|
|
153
166
|
}
|
|
@@ -156,26 +169,26 @@ var newId = () => crypto.randomUUID?.() ?? Array.from(
|
|
|
156
169
|
crypto.getRandomValues(new Uint8Array(16)),
|
|
157
170
|
(byte) => byte.toString(16).padStart(2, "0")
|
|
158
171
|
).join("");
|
|
159
|
-
function dockTab(layout,
|
|
160
|
-
const leaves = panes(layout), source = leaves.find((p) => p.tabs.includes(
|
|
172
|
+
function dockTab(layout, key2, target, edge, index) {
|
|
173
|
+
const leaves = panes(layout), source = leaves.find((p) => p.tabs.includes(key2)), destination = leaves.find((p) => p.id === target);
|
|
161
174
|
if (!source || !destination || source === destination && (!edge || source.tabs.length === 1))
|
|
162
175
|
return layout;
|
|
163
176
|
let next = updateLayout(layout, source.id, (node) => {
|
|
164
|
-
const pane = node, index2 = pane.tabs.indexOf(
|
|
177
|
+
const pane = node, index2 = pane.tabs.indexOf(key2), tabs = pane.tabs.filter((k) => k !== key2);
|
|
165
178
|
return {
|
|
166
179
|
...pane,
|
|
167
180
|
tabs,
|
|
168
|
-
active: pane.active ===
|
|
181
|
+
active: pane.active === key2 ? tabs[Math.min(index2, tabs.length - 1)] : pane.active
|
|
169
182
|
};
|
|
170
183
|
});
|
|
171
184
|
next = updateLayout(next, target, (node) => {
|
|
172
185
|
const pane = node;
|
|
173
186
|
if (!edge) {
|
|
174
187
|
const tabs = [...pane.tabs];
|
|
175
|
-
tabs.splice(index ?? tabs.length, 0,
|
|
176
|
-
return { ...pane, tabs, active:
|
|
188
|
+
tabs.splice(index ?? tabs.length, 0, key2);
|
|
189
|
+
return { ...pane, tabs, active: key2 };
|
|
177
190
|
}
|
|
178
|
-
const added = { id: newId(), tabs: [
|
|
191
|
+
const added = { id: newId(), tabs: [key2], active: key2 };
|
|
179
192
|
return {
|
|
180
193
|
id: newId(),
|
|
181
194
|
axis: edge === "bottom" ? "y" : "x",
|
|
@@ -187,7 +200,7 @@ function dockTab(layout, key, target, edge, index) {
|
|
|
187
200
|
return reconcileLayout(
|
|
188
201
|
next,
|
|
189
202
|
leaves.flatMap((p) => p.tabs),
|
|
190
|
-
|
|
203
|
+
key2
|
|
191
204
|
);
|
|
192
205
|
}
|
|
193
206
|
function minimumSize(node, gap = gutter) {
|
|
@@ -248,6 +261,13 @@ function dropRegion(box, edge, gap = gutter) {
|
|
|
248
261
|
return box;
|
|
249
262
|
}
|
|
250
263
|
|
|
264
|
+
// overlay.ts
|
|
265
|
+
var overlays = ["dialog", "alertdialog", "menu", "listbox"].map((role) => `[role="${role}"]`).concat("[data-labpane-overlay]").join(", ");
|
|
266
|
+
function hasOpenOverlay() {
|
|
267
|
+
const full = document.fullscreenElement;
|
|
268
|
+
return !!full && full !== document.documentElement || !!document.querySelector(overlays);
|
|
269
|
+
}
|
|
270
|
+
|
|
251
271
|
// surface.tsx
|
|
252
272
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
253
273
|
var position = (box) => ({
|
|
@@ -311,18 +331,18 @@ function WorkspaceSurface({
|
|
|
311
331
|
const canSplit = (box, edge) => edge === "bottom" ? box.height >= minimum.y * 2 + rowGap : box.width >= minimum.x * 2 + columnGap;
|
|
312
332
|
const docking = {
|
|
313
333
|
paneCount: leaves.length,
|
|
314
|
-
next: (
|
|
315
|
-
const index = leaves.findIndex((p) => p.pane.tabs.includes(
|
|
334
|
+
next: (key2) => {
|
|
335
|
+
const index = leaves.findIndex((p) => p.pane.tabs.includes(key2));
|
|
316
336
|
change(
|
|
317
|
-
dockTab(layout,
|
|
337
|
+
dockTab(layout, key2, leaves[(index + 1) % leaves.length].pane.id)
|
|
318
338
|
);
|
|
319
339
|
},
|
|
320
|
-
move: ({ key, x, y, detached: wasDetached }) => {
|
|
340
|
+
move: ({ key: key2, x, y, detached: wasDetached }) => {
|
|
321
341
|
const bounds = board.current.getBoundingClientRect(), localX = x - bounds.left, localY = y - bounds.top;
|
|
322
342
|
const target = leaves.find(
|
|
323
343
|
(p) => localX >= p.x && localX <= p.x + p.width && localY >= p.y && localY <= p.y + p.height
|
|
324
344
|
);
|
|
325
|
-
const source = leaves.find((p) => p.pane.tabs.includes(
|
|
345
|
+
const source = leaves.find((p) => p.pane.tabs.includes(key2));
|
|
326
346
|
const margin = wasDetached ? 8 : 24;
|
|
327
347
|
const detached = localY > source.y + tabHeight + margin || localY < source.y - margin || !!target && (localX < source.x - margin || localX > source.x + source.width + margin);
|
|
328
348
|
if (!detached) {
|
|
@@ -335,7 +355,7 @@ function WorkspaceSurface({
|
|
|
335
355
|
else if (localX < target.x + target.width * 0.28) edge = "left";
|
|
336
356
|
else if (localX > target.x + target.width * 0.72) edge = "right";
|
|
337
357
|
}
|
|
338
|
-
const valid = target && (!edge || canSplit(target, edge)) && (!target.pane.tabs.includes(
|
|
358
|
+
const valid = target && (!edge || canSplit(target, edge)) && (!target.pane.tabs.includes(key2) || !!edge && target.pane.tabs.length > 1);
|
|
339
359
|
let index;
|
|
340
360
|
if (valid && !edge) {
|
|
341
361
|
const nav = board.current.querySelector(
|
|
@@ -370,7 +390,7 @@ function WorkspaceSurface({
|
|
|
370
390
|
);
|
|
371
391
|
}
|
|
372
392
|
}
|
|
373
|
-
show(valid ? { pane: target.pane.id, key, edge, index } : { key });
|
|
393
|
+
show(valid ? { pane: target.pane.id, key: key2, edge, index } : { key: key2 });
|
|
374
394
|
return true;
|
|
375
395
|
},
|
|
376
396
|
drop: () => {
|
|
@@ -383,18 +403,18 @@ function WorkspaceSurface({
|
|
|
383
403
|
return true;
|
|
384
404
|
},
|
|
385
405
|
cancel: () => show(),
|
|
386
|
-
split: (
|
|
387
|
-
const target = leaves.find((p) => p.pane.tabs.includes(
|
|
406
|
+
split: (key2, edge) => {
|
|
407
|
+
const target = leaves.find((p) => p.pane.tabs.includes(key2));
|
|
388
408
|
if (!target || target.pane.tabs.length < 2 || !canSplit(target, edge))
|
|
389
409
|
return false;
|
|
390
|
-
change(dockTab(layout,
|
|
410
|
+
change(dockTab(layout, key2, target.pane.id, edge));
|
|
391
411
|
return true;
|
|
392
412
|
}
|
|
393
413
|
};
|
|
394
414
|
const lifted = preview?.key;
|
|
395
415
|
const regions = leaves.map(({ pane, ...box }) => {
|
|
396
416
|
if (lifted && pane.tabs.includes(lifted)) {
|
|
397
|
-
const remaining = pane.tabs.filter((
|
|
417
|
+
const remaining = pane.tabs.filter((key2) => key2 !== lifted);
|
|
398
418
|
pane = {
|
|
399
419
|
...pane,
|
|
400
420
|
active: remaining[Math.min(pane.tabs.indexOf(lifted), remaining.length - 1)]
|
|
@@ -445,8 +465,8 @@ function WorkspaceSurface({
|
|
|
445
465
|
...docking,
|
|
446
466
|
insertion: preview?.pane === pane.id ? preview.index : void 0
|
|
447
467
|
},
|
|
448
|
-
(
|
|
449
|
-
|
|
468
|
+
(key2 = pane.active) => board.current?.querySelector(
|
|
469
|
+
key2 ? `[id="${CSS.escape(ids.panel(key2))}"]` : `[data-pane-sheet="${CSS.escape(pane.id)}"]:not([hidden]):not([data-drag-content])`
|
|
450
470
|
) ?? null
|
|
451
471
|
)
|
|
452
472
|
},
|
|
@@ -500,6 +520,7 @@ function WorkspaceSurface({
|
|
|
500
520
|
},
|
|
501
521
|
onLostPointerCapture: () => setResizing(false),
|
|
502
522
|
onKeyDown: (event) => {
|
|
523
|
+
if (hasOpenOverlay()) return;
|
|
503
524
|
const step = event.shiftKey ? 0.1 : 0.02;
|
|
504
525
|
const values = {
|
|
505
526
|
[horizontal ? "ArrowLeft" : "ArrowUp"]: ratio - step,
|
|
@@ -773,6 +794,7 @@ function WorkspaceTabs({
|
|
|
773
794
|
menu
|
|
774
795
|
}) {
|
|
775
796
|
const ids = useWorkspaceIds();
|
|
797
|
+
const tabLinks = useTabLinks();
|
|
776
798
|
const content = useDragContent();
|
|
777
799
|
const density = useTabSize();
|
|
778
800
|
const [metrics, setMetrics] = React.useState({
|
|
@@ -791,8 +813,8 @@ function WorkspaceTabs({
|
|
|
791
813
|
items.length + (docking.insertion === void 0 ? 0 : 1)
|
|
792
814
|
)
|
|
793
815
|
}, { width: tabWidth, height, contentHeight } = size, width = tabWidth + 16;
|
|
794
|
-
const links = items.map(({ key, label }) => [
|
|
795
|
-
const tabOrder = JSON.stringify(items.map(({ key }) =>
|
|
816
|
+
const links = items.map(({ key: key2, label }) => [key2, label]);
|
|
817
|
+
const tabOrder = JSON.stringify(items.map(({ key: key2 }) => key2));
|
|
796
818
|
const labels = new Map(links);
|
|
797
819
|
const navRef = React.useRef(null);
|
|
798
820
|
const stripRef = React.useRef(null);
|
|
@@ -823,11 +845,11 @@ function WorkspaceTabs({
|
|
|
823
845
|
});
|
|
824
846
|
const frame = React.useRef(0);
|
|
825
847
|
const suppressClick = React.useRef(false);
|
|
826
|
-
const tabElement = (
|
|
827
|
-
`a[data-tab="${CSS.escape(
|
|
848
|
+
const tabElement = (key2) => navRef.current?.querySelector(
|
|
849
|
+
`a[data-tab="${CSS.escape(key2)}"]`
|
|
828
850
|
)?.parentElement;
|
|
829
|
-
const reveal = (
|
|
830
|
-
const nav = navRef.current, tab = tabElement(
|
|
851
|
+
const reveal = (key2 = active) => {
|
|
852
|
+
const nav = navRef.current, tab = tabElement(key2);
|
|
831
853
|
if (!nav || !tab || drag.current?.moved || docking.insertion !== void 0)
|
|
832
854
|
return;
|
|
833
855
|
nav.scrollLeft = Math.max(
|
|
@@ -909,10 +931,10 @@ function WorkspaceTabs({
|
|
|
909
931
|
motion
|
|
910
932
|
);
|
|
911
933
|
};
|
|
912
|
-
const settle = (
|
|
934
|
+
const settle = (key2) => requestAnimationFrame(() => {
|
|
913
935
|
if (drag.current) return;
|
|
914
|
-
reveal(
|
|
915
|
-
const ghost2 = ghostRef.current, slot = tabElement(
|
|
936
|
+
reveal(key2);
|
|
937
|
+
const ghost2 = ghostRef.current, slot = tabElement(key2);
|
|
916
938
|
if (!ghost2 || !slot || matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
917
939
|
setDragging(void 0);
|
|
918
940
|
return;
|
|
@@ -945,21 +967,21 @@ function WorkspaceTabs({
|
|
|
945
967
|
else setDragging(void 0);
|
|
946
968
|
};
|
|
947
969
|
const move = (from, to) => {
|
|
948
|
-
const order = links.map(([
|
|
970
|
+
const order = links.map(([key2]) => key2), index = order.indexOf(to);
|
|
949
971
|
if (from === to || index < 0) return;
|
|
950
972
|
order.splice(order.indexOf(from), 1);
|
|
951
973
|
order.splice(index, 0, from);
|
|
952
974
|
onReorder(order);
|
|
953
975
|
announce(`${labels.get(from)}: position ${index + 1} of ${order.length}`);
|
|
954
976
|
};
|
|
955
|
-
const
|
|
956
|
-
const split = (key, edge) => {
|
|
977
|
+
const split = (key2, edge) => {
|
|
957
978
|
announce(
|
|
958
|
-
docking.split(
|
|
979
|
+
docking.split(key2, edge) ? `${labels.get(key2)} split ${edge}` : "Open another tab or make this pane larger to split it"
|
|
959
980
|
);
|
|
960
|
-
|
|
981
|
+
tabLinks.focus(key2);
|
|
961
982
|
};
|
|
962
|
-
const keyDown = (event,
|
|
983
|
+
const keyDown = (event, key2, index) => {
|
|
984
|
+
if (hasOpenOverlay()) return;
|
|
963
985
|
if (event.key === "Escape") return finish(true);
|
|
964
986
|
const edges = {
|
|
965
987
|
ArrowLeft: "left",
|
|
@@ -968,9 +990,9 @@ function WorkspaceTabs({
|
|
|
968
990
|
};
|
|
969
991
|
const edge = edges[event.key];
|
|
970
992
|
if (event.altKey && edge) {
|
|
971
|
-
if (event.shiftKey) split(
|
|
993
|
+
if (event.shiftKey) split(key2, edge);
|
|
972
994
|
else if (edge !== "bottom")
|
|
973
|
-
move(
|
|
995
|
+
move(key2, items[index + (edge === "left" ? -1 : 1)]?.key ?? key2);
|
|
974
996
|
else return;
|
|
975
997
|
} else {
|
|
976
998
|
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
@@ -981,10 +1003,9 @@ function WorkspaceTabs({
|
|
|
981
1003
|
ArrowRight: (index + 1) % items.length
|
|
982
1004
|
};
|
|
983
1005
|
const target = targets[event.key];
|
|
984
|
-
if (target !== void 0)
|
|
985
|
-
|
|
986
|
-
else if (event.key === "
|
|
987
|
-
else if (event.key === " ") onSelect(key);
|
|
1006
|
+
if (target !== void 0) tabLinks.get(items[target].key)?.focus();
|
|
1007
|
+
else if (event.key === "Delete") onClose(key2);
|
|
1008
|
+
else if (event.key === " ") onSelect(key2);
|
|
988
1009
|
else return;
|
|
989
1010
|
}
|
|
990
1011
|
event.preventDefault();
|
|
@@ -1033,12 +1054,12 @@ function WorkspaceTabs({
|
|
|
1033
1054
|
const previous = positions.current;
|
|
1034
1055
|
positions.current = { order, tabs: /* @__PURE__ */ new Map() };
|
|
1035
1056
|
for (const element of navRef.current.children) {
|
|
1036
|
-
const
|
|
1037
|
-
if (!
|
|
1038
|
-
const left = element.offsetLeft, from = previous.tabs.get(
|
|
1039
|
-
if (previous.order !== order &&
|
|
1057
|
+
const key2 = element.querySelector("a")?.dataset.tab;
|
|
1058
|
+
if (!key2) continue;
|
|
1059
|
+
const left = element.offsetLeft, from = previous.tabs.get(key2);
|
|
1060
|
+
if (previous.order !== order && key2 !== dragging && from !== void 0 && from !== left)
|
|
1040
1061
|
animate(element, from - left);
|
|
1041
|
-
positions.current.tabs.set(
|
|
1062
|
+
positions.current.tabs.set(key2, left);
|
|
1042
1063
|
}
|
|
1043
1064
|
cancelAnimationFrame(frame.current);
|
|
1044
1065
|
draw();
|
|
@@ -1169,83 +1190,84 @@ function WorkspaceTabs({
|
|
|
1169
1190
|
onPointerCancel: () => finish(true),
|
|
1170
1191
|
className: "lab-tablist",
|
|
1171
1192
|
children: [
|
|
1172
|
-
links.map(([
|
|
1193
|
+
links.map(([key2, text], index) => {
|
|
1173
1194
|
const actions = [
|
|
1174
1195
|
...onDuplicate ? [
|
|
1175
1196
|
{
|
|
1176
1197
|
label: "Duplicate tab",
|
|
1177
1198
|
onSelect: () => {
|
|
1178
|
-
menuFocus.current = onDuplicate(
|
|
1199
|
+
menuFocus.current = onDuplicate(key2);
|
|
1179
1200
|
}
|
|
1180
1201
|
}
|
|
1181
1202
|
] : [],
|
|
1182
1203
|
{
|
|
1183
1204
|
label: "Move left",
|
|
1184
|
-
onSelect: () => move(
|
|
1205
|
+
onSelect: () => move(key2, items[index - 1].key),
|
|
1185
1206
|
disabled: !index
|
|
1186
1207
|
},
|
|
1187
1208
|
{
|
|
1188
1209
|
label: "Move right",
|
|
1189
|
-
onSelect: () => move(
|
|
1210
|
+
onSelect: () => move(key2, items[index + 1].key),
|
|
1190
1211
|
disabled: index === items.length - 1
|
|
1191
1212
|
},
|
|
1192
1213
|
{
|
|
1193
1214
|
label: "Move to next pane",
|
|
1194
|
-
onSelect: () => docking.next(
|
|
1215
|
+
onSelect: () => docking.next(key2),
|
|
1195
1216
|
disabled: docking.paneCount < 2
|
|
1196
1217
|
},
|
|
1197
1218
|
null,
|
|
1198
1219
|
...["left", "right", "bottom"].map((edge) => ({
|
|
1199
1220
|
label: `Split ${edge === "bottom" ? "below" : edge}`,
|
|
1200
|
-
onSelect: () => split(
|
|
1221
|
+
onSelect: () => split(key2, edge),
|
|
1201
1222
|
disabled: items.length < 2
|
|
1202
1223
|
})),
|
|
1203
1224
|
null,
|
|
1204
|
-
{ label: "Close tab", onSelect: () => onClose(
|
|
1225
|
+
{ label: "Close tab", onSelect: () => onClose(key2) }
|
|
1205
1226
|
];
|
|
1206
1227
|
const target = /* @__PURE__ */ jsxs3(
|
|
1207
1228
|
"div",
|
|
1208
1229
|
{
|
|
1209
1230
|
role: "presentation",
|
|
1210
1231
|
className: "lab-tab",
|
|
1211
|
-
"data-dragging": dragging ===
|
|
1232
|
+
"data-dragging": dragging === key2 || void 0,
|
|
1212
1233
|
style: {
|
|
1213
1234
|
paddingTop: height - contentHeight,
|
|
1214
|
-
marginRight: dragging ===
|
|
1235
|
+
marginRight: dragging === key2 && drag.current?.detached ? -tabWidth : 0,
|
|
1215
1236
|
marginLeft: docking.insertion === index ? tabWidth : 0,
|
|
1216
|
-
zIndex: active ===
|
|
1237
|
+
zIndex: active === key2 ? links.length + 1 : links.length - index
|
|
1217
1238
|
},
|
|
1218
1239
|
children: [
|
|
1219
1240
|
/* @__PURE__ */ jsx4(
|
|
1220
1241
|
TabSurface,
|
|
1221
1242
|
{
|
|
1222
1243
|
width: tabWidth,
|
|
1223
|
-
selected: active ===
|
|
1244
|
+
selected: active === key2,
|
|
1224
1245
|
keyboardFocus: !pointerFocus
|
|
1225
1246
|
}
|
|
1226
1247
|
),
|
|
1227
1248
|
/* @__PURE__ */ jsxs3(
|
|
1228
1249
|
"a",
|
|
1229
1250
|
{
|
|
1230
|
-
|
|
1251
|
+
ref: tabLinks.register(key2),
|
|
1252
|
+
id: ids.tab(key2),
|
|
1231
1253
|
role: "tab",
|
|
1232
|
-
tabIndex: active ===
|
|
1233
|
-
"aria-selected": active ===
|
|
1234
|
-
"aria-controls": ids.panel(
|
|
1254
|
+
tabIndex: active === key2 ? 0 : -1,
|
|
1255
|
+
"aria-selected": active === key2,
|
|
1256
|
+
"aria-controls": ids.panel(key2),
|
|
1235
1257
|
href: items[index].href,
|
|
1236
|
-
"data-tab":
|
|
1258
|
+
"data-tab": key2,
|
|
1237
1259
|
style: { width: tabWidth, height: contentHeight },
|
|
1238
1260
|
onClick: (event) => {
|
|
1239
1261
|
const { metaKey, ctrlKey, shiftKey, altKey } = event;
|
|
1240
1262
|
if (metaKey || ctrlKey || shiftKey || altKey) return;
|
|
1241
1263
|
event.preventDefault();
|
|
1242
|
-
onSelect(
|
|
1264
|
+
onSelect(key2);
|
|
1243
1265
|
},
|
|
1244
|
-
onFocus: () => onSelect(
|
|
1266
|
+
onFocus: () => onSelect(key2),
|
|
1245
1267
|
draggable: false,
|
|
1246
1268
|
title: text,
|
|
1247
1269
|
"aria-keyshortcuts": "ArrowLeft ArrowRight Home End Delete Alt+ArrowLeft Alt+ArrowRight Alt+Shift+ArrowLeft Alt+Shift+ArrowRight Alt+Shift+ArrowDown",
|
|
1248
|
-
onKeyDown: (event) => keyDown(event,
|
|
1270
|
+
onKeyDown: (event) => keyDown(event, key2, index),
|
|
1249
1271
|
onPointerDown: (event) => {
|
|
1250
1272
|
if (event.button !== 0 || !event.isPrimary) return;
|
|
1251
1273
|
if (event.pointerType === "mouse") event.preventDefault();
|
|
@@ -1254,7 +1276,7 @@ function WorkspaceTabs({
|
|
|
1254
1276
|
setPointerFocus(true);
|
|
1255
1277
|
const box = event.currentTarget.parentElement.getBoundingClientRect();
|
|
1256
1278
|
drag.current = {
|
|
1257
|
-
key,
|
|
1279
|
+
key: key2,
|
|
1258
1280
|
offset: event.clientX - box.left,
|
|
1259
1281
|
offsetY: event.clientY - box.top,
|
|
1260
1282
|
sheet: surface().getBoundingClientRect(),
|
|
@@ -1272,7 +1294,7 @@ function WorkspaceTabs({
|
|
|
1272
1294
|
]
|
|
1273
1295
|
}
|
|
1274
1296
|
),
|
|
1275
|
-
/* @__PURE__ */ jsx4(CloseTab, { label: text, onClick: () => onClose(
|
|
1297
|
+
/* @__PURE__ */ jsx4(CloseTab, { label: text, onClick: () => onClose(key2) })
|
|
1276
1298
|
]
|
|
1277
1299
|
}
|
|
1278
1300
|
);
|
|
@@ -1280,11 +1302,11 @@ function WorkspaceTabs({
|
|
|
1280
1302
|
target,
|
|
1281
1303
|
actions,
|
|
1282
1304
|
() => {
|
|
1283
|
-
|
|
1305
|
+
tabLinks.focus(menuFocus.current ?? key2);
|
|
1284
1306
|
menuFocus.current = void 0;
|
|
1285
1307
|
},
|
|
1286
|
-
|
|
1287
|
-
) : target },
|
|
1308
|
+
key2
|
|
1309
|
+
) : target }, key2);
|
|
1288
1310
|
}),
|
|
1289
1311
|
docking.insertion === items.length && /* @__PURE__ */ jsx4(
|
|
1290
1312
|
"div",
|
|
@@ -1421,11 +1443,226 @@ function useUnsavedChanges(dirty) {
|
|
|
1421
1443
|
}, [dirty, report]);
|
|
1422
1444
|
}
|
|
1423
1445
|
|
|
1446
|
+
// tab-state.ts
|
|
1447
|
+
import * as React3 from "react";
|
|
1448
|
+
var key = /* @__PURE__ */ Symbol.for("labpane.tab-state");
|
|
1449
|
+
var registry = globalThis;
|
|
1450
|
+
var TabStateContext = registry[key] ??= React3.createContext(
|
|
1451
|
+
{ active: true, visible: true }
|
|
1452
|
+
);
|
|
1453
|
+
function useTabActive() {
|
|
1454
|
+
return React3.useContext(TabStateContext).active;
|
|
1455
|
+
}
|
|
1456
|
+
function useTabVisible() {
|
|
1457
|
+
return React3.useContext(TabStateContext).visible;
|
|
1458
|
+
}
|
|
1459
|
+
function useTabVisibleRef() {
|
|
1460
|
+
const visible = useTabVisible();
|
|
1461
|
+
const ref = React3.useRef(visible);
|
|
1462
|
+
React3.useLayoutEffect(() => {
|
|
1463
|
+
ref.current = visible;
|
|
1464
|
+
}, [visible]);
|
|
1465
|
+
return ref;
|
|
1466
|
+
}
|
|
1467
|
+
function useShownOnce(visible) {
|
|
1468
|
+
const [shown, setShown] = React3.useState(visible);
|
|
1469
|
+
React3.useEffect(() => {
|
|
1470
|
+
if (visible) setShown(true);
|
|
1471
|
+
}, [visible]);
|
|
1472
|
+
return shown;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// leave-tab.ts
|
|
1476
|
+
import { createContext as createContext4, useContext as useContext4, useLayoutEffect as useLayoutEffect6, useRef as useRef7 } from "react";
|
|
1477
|
+
|
|
1478
|
+
// tab-map.ts
|
|
1479
|
+
import { useLayoutEffect as useLayoutEffect5, useRef as useRef6 } from "react";
|
|
1480
|
+
function useTabMap(tabs) {
|
|
1481
|
+
const map = useRef6(/* @__PURE__ */ new Map());
|
|
1482
|
+
useLayoutEffect5(() => {
|
|
1483
|
+
for (const key2 of map.current.keys())
|
|
1484
|
+
if (!tabs.some((tab) => tab.key === key2)) map.current.delete(key2);
|
|
1485
|
+
}, [tabs]);
|
|
1486
|
+
return map.current;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// leave-tab.ts
|
|
1490
|
+
function useOpeners(tabs, front) {
|
|
1491
|
+
const openers = useTabMap(tabs);
|
|
1492
|
+
const previous = useRef7({
|
|
1493
|
+
keys: /* @__PURE__ */ new Set()
|
|
1494
|
+
});
|
|
1495
|
+
useLayoutEffect6(() => {
|
|
1496
|
+
const keys = new Set(tabs.map((tab) => tab.key));
|
|
1497
|
+
const { keys: known, front: before } = previous.current;
|
|
1498
|
+
previous.current = { keys, front };
|
|
1499
|
+
if (before === void 0 || !keys.has(before)) return;
|
|
1500
|
+
for (const key2 of keys)
|
|
1501
|
+
if (key2 !== before && (!known.has(key2) || key2 === front))
|
|
1502
|
+
openers.set(key2, before);
|
|
1503
|
+
}, [tabs, front, openers]);
|
|
1504
|
+
return openers;
|
|
1505
|
+
}
|
|
1506
|
+
var LeaveTab = createContext4(() => {
|
|
1507
|
+
});
|
|
1508
|
+
function useLeaveTab() {
|
|
1509
|
+
return useContext4(LeaveTab);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// tab-history.ts
|
|
1513
|
+
import { createContext as createContext5, useContext as useContext5, useSyncExternalStore } from "react";
|
|
1514
|
+
function createTabHistory(initial) {
|
|
1515
|
+
const paths = [initial];
|
|
1516
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1517
|
+
let index = 0;
|
|
1518
|
+
const go = (to) => {
|
|
1519
|
+
index = to;
|
|
1520
|
+
current = snapshot();
|
|
1521
|
+
listeners.forEach((listener) => listener());
|
|
1522
|
+
};
|
|
1523
|
+
const navigate = (path) => {
|
|
1524
|
+
if (path === paths[index]) return;
|
|
1525
|
+
paths.splice(index + 1, Infinity, path);
|
|
1526
|
+
go(index + 1);
|
|
1527
|
+
};
|
|
1528
|
+
const back = () => {
|
|
1529
|
+
if (index > 0) go(index - 1);
|
|
1530
|
+
};
|
|
1531
|
+
const forward = () => {
|
|
1532
|
+
if (index < paths.length - 1) go(index + 1);
|
|
1533
|
+
};
|
|
1534
|
+
const snapshot = () => ({
|
|
1535
|
+
path: paths[index],
|
|
1536
|
+
canGoBack: index > 0,
|
|
1537
|
+
canGoForward: index < paths.length - 1,
|
|
1538
|
+
navigate,
|
|
1539
|
+
back,
|
|
1540
|
+
forward
|
|
1541
|
+
});
|
|
1542
|
+
let current = snapshot();
|
|
1543
|
+
return {
|
|
1544
|
+
subscribe: (listener) => {
|
|
1545
|
+
listeners.add(listener);
|
|
1546
|
+
return () => {
|
|
1547
|
+
listeners.delete(listener);
|
|
1548
|
+
};
|
|
1549
|
+
},
|
|
1550
|
+
get: () => current
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
var TabHistoryContext = createContext5(createTabHistory(""));
|
|
1554
|
+
function useTabHistory() {
|
|
1555
|
+
const store = useContext5(TabHistoryContext);
|
|
1556
|
+
return useSyncExternalStore(store.subscribe, store.get, store.get);
|
|
1557
|
+
}
|
|
1558
|
+
function useTabHistories(tabs) {
|
|
1559
|
+
const stores = useTabMap(tabs);
|
|
1560
|
+
return (tab) => {
|
|
1561
|
+
let store = stores.get(tab.key);
|
|
1562
|
+
if (!store) stores.set(tab.key, store = createTabHistory(tab.href ?? ""));
|
|
1563
|
+
return store;
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// browser-history.ts
|
|
1568
|
+
import { useEffect as useEffect3, useLayoutEffect as useLayoutEffect7, useRef as useRef8 } from "react";
|
|
1569
|
+
var read = (state) => state?.labpane;
|
|
1570
|
+
function createBrowserHistory(front) {
|
|
1571
|
+
const session = Date.now();
|
|
1572
|
+
let serial = 0;
|
|
1573
|
+
let position2 = 0;
|
|
1574
|
+
let below = 0;
|
|
1575
|
+
let above = 0;
|
|
1576
|
+
let hopping = false;
|
|
1577
|
+
let queued = false;
|
|
1578
|
+
const mark = (serial2) => ({ labpane: { session, serial: serial2 } });
|
|
1579
|
+
const push = (path) => history.pushState(mark(++serial), "", path);
|
|
1580
|
+
const hop = () => {
|
|
1581
|
+
hopping = true;
|
|
1582
|
+
history.back();
|
|
1583
|
+
};
|
|
1584
|
+
const settle = () => {
|
|
1585
|
+
hopping = false;
|
|
1586
|
+
if (!queued) return;
|
|
1587
|
+
queued = false;
|
|
1588
|
+
sync();
|
|
1589
|
+
};
|
|
1590
|
+
const move = (to, direction) => {
|
|
1591
|
+
position2 = to;
|
|
1592
|
+
below = Math.max(0, below + direction);
|
|
1593
|
+
above = Math.max(0, above - direction);
|
|
1594
|
+
};
|
|
1595
|
+
function sync() {
|
|
1596
|
+
const tab = front();
|
|
1597
|
+
if (!tab) return;
|
|
1598
|
+
if (hopping) {
|
|
1599
|
+
queued = true;
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
history.replaceState(mark(position2), "", tab.path);
|
|
1603
|
+
if (tab.canGoBack && !below || !tab.canGoForward && above) {
|
|
1604
|
+
push(tab.path);
|
|
1605
|
+
position2 = serial;
|
|
1606
|
+
below += 1;
|
|
1607
|
+
above = 0;
|
|
1608
|
+
}
|
|
1609
|
+
if (tab.canGoForward && !above) {
|
|
1610
|
+
push(tab.path);
|
|
1611
|
+
above = 1;
|
|
1612
|
+
hop();
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
function pop(event) {
|
|
1616
|
+
const landed = read(event.state);
|
|
1617
|
+
if (!landed) return;
|
|
1618
|
+
if (landed.session !== session) return history.back();
|
|
1619
|
+
if (landed.serial === position2) return settle();
|
|
1620
|
+
const tab = front();
|
|
1621
|
+
if (landed.serial > position2) {
|
|
1622
|
+
if (!tab?.canGoForward) {
|
|
1623
|
+
above += 1;
|
|
1624
|
+
queued = true;
|
|
1625
|
+
return hop();
|
|
1626
|
+
}
|
|
1627
|
+
move(landed.serial, 1);
|
|
1628
|
+
tab.forward();
|
|
1629
|
+
} else {
|
|
1630
|
+
move(landed.serial, -1);
|
|
1631
|
+
if (tab?.canGoBack) tab.back();
|
|
1632
|
+
else history.back();
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
return { sync, pop };
|
|
1636
|
+
}
|
|
1637
|
+
function useBrowserHistory(front) {
|
|
1638
|
+
const latest = useRef8(front);
|
|
1639
|
+
useLayoutEffect7(() => {
|
|
1640
|
+
latest.current = front;
|
|
1641
|
+
});
|
|
1642
|
+
const browser = useRef8(
|
|
1643
|
+
void 0
|
|
1644
|
+
);
|
|
1645
|
+
useEffect3(() => {
|
|
1646
|
+
if (!front) return;
|
|
1647
|
+
const { sync, pop } = browser.current ??= createBrowserHistory(
|
|
1648
|
+
() => latest.current?.get()
|
|
1649
|
+
);
|
|
1650
|
+
addEventListener("popstate", pop);
|
|
1651
|
+
sync();
|
|
1652
|
+
const unsubscribe = front.subscribe(sync);
|
|
1653
|
+
return () => {
|
|
1654
|
+
unsubscribe();
|
|
1655
|
+
removeEventListener("popstate", pop);
|
|
1656
|
+
};
|
|
1657
|
+
}, [front]);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1424
1660
|
// workspace.tsx
|
|
1425
1661
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1426
1662
|
function Workspace(props) {
|
|
1427
1663
|
const id = useId2();
|
|
1428
|
-
|
|
1664
|
+
const [links] = useState5(() => /* @__PURE__ */ new Map());
|
|
1665
|
+
return /* @__PURE__ */ jsx6(WorkspaceId.Provider, { value: id, children: /* @__PURE__ */ jsx6(TabLinks.Provider, { value: links, children: /* @__PURE__ */ jsx6(WorkspaceDensity.Provider, { value: props.density ?? 100, children: /* @__PURE__ */ jsx6(WorkspaceContent, { ...props }) }) }) });
|
|
1429
1666
|
}
|
|
1430
1667
|
function WorkspaceContent({
|
|
1431
1668
|
density = 100,
|
|
@@ -1442,70 +1679,93 @@ function WorkspaceContent({
|
|
|
1442
1679
|
confirmClose,
|
|
1443
1680
|
layout,
|
|
1444
1681
|
onLayoutChange: change,
|
|
1682
|
+
history: history2,
|
|
1445
1683
|
className,
|
|
1446
1684
|
children,
|
|
1447
1685
|
empty
|
|
1448
1686
|
}) {
|
|
1449
|
-
const
|
|
1687
|
+
const links = useTabLinks();
|
|
1450
1688
|
const { height, contentHeight, logoSize } = useTabSize();
|
|
1451
|
-
const corner2 =
|
|
1452
|
-
const [reserve, setReserve] =
|
|
1689
|
+
const corner2 = useRef9(null);
|
|
1690
|
+
const [reserve, setReserve] = useState5(0);
|
|
1453
1691
|
const hasTrailing = !!trailing;
|
|
1454
|
-
|
|
1692
|
+
useLayoutEffect8(() => {
|
|
1455
1693
|
const element = corner2.current;
|
|
1456
1694
|
if (!element) return setReserve(0);
|
|
1457
1695
|
const observer = new ResizeObserver(() => setReserve(element.offsetWidth));
|
|
1458
1696
|
observer.observe(element);
|
|
1459
1697
|
return () => observer.disconnect();
|
|
1460
1698
|
}, [hasTrailing]);
|
|
1461
|
-
const root =
|
|
1462
|
-
const [inset, setInset] =
|
|
1463
|
-
|
|
1699
|
+
const root = useRef9(null);
|
|
1700
|
+
const [inset, setInset] = useState5(0);
|
|
1701
|
+
useLayoutEffect8(() => {
|
|
1464
1702
|
if (!titleBar) return setInset(0);
|
|
1465
1703
|
const measure = () => setInset(root.current.getBoundingClientRect().top);
|
|
1466
1704
|
measure();
|
|
1467
1705
|
addEventListener("resize", measure);
|
|
1468
1706
|
return () => removeEventListener("resize", measure);
|
|
1469
1707
|
}, [titleBar]);
|
|
1470
|
-
const preferredPane =
|
|
1708
|
+
const preferredPane = useRef9(void 0);
|
|
1471
1709
|
const visibleLayout = reconcileLayout(
|
|
1472
1710
|
layout,
|
|
1473
1711
|
tabs.map((t) => t.key),
|
|
1474
1712
|
active,
|
|
1475
1713
|
preferredPane.current
|
|
1476
1714
|
);
|
|
1477
|
-
|
|
1715
|
+
useLayoutEffect8(() => {
|
|
1478
1716
|
if (visibleLayout !== layout) change(visibleLayout);
|
|
1479
1717
|
}, [visibleLayout, layout]);
|
|
1480
|
-
const
|
|
1481
|
-
|
|
1718
|
+
const front = active ?? panes(visibleLayout)[0]?.active;
|
|
1719
|
+
const openers = useOpeners(tabs, front);
|
|
1720
|
+
const historyOf = useTabHistories(tabs);
|
|
1721
|
+
const frontTab = tabs.find((tab) => tab.key === front);
|
|
1722
|
+
useBrowserHistory(history2 && frontTab ? historyOf(frontTab) : void 0);
|
|
1723
|
+
const dirty = useRef9(/* @__PURE__ */ new Map());
|
|
1724
|
+
useEffect4(() => {
|
|
1482
1725
|
const warn = (event) => {
|
|
1483
1726
|
if (dirty.current.size) event.preventDefault();
|
|
1484
1727
|
};
|
|
1485
1728
|
addEventListener("beforeunload", warn);
|
|
1486
1729
|
return () => removeEventListener("beforeunload", warn);
|
|
1487
1730
|
}, []);
|
|
1488
|
-
function close(
|
|
1489
|
-
const pane = panes(visibleLayout).find((p) => p.tabs.includes(
|
|
1731
|
+
function close(key2, next) {
|
|
1732
|
+
const pane = panes(visibleLayout).find((p) => p.tabs.includes(key2));
|
|
1490
1733
|
if (!pane) return;
|
|
1491
|
-
const remaining = pane.tabs.filter((k) => k !==
|
|
1492
|
-
const opener = tabs.find((tab) => tab.key ===
|
|
1493
|
-
const neighbor = remaining.find((
|
|
1494
|
-
onClose(
|
|
1734
|
+
const remaining = pane.tabs.filter((k) => k !== key2);
|
|
1735
|
+
const opener = tabs.find((tab) => tab.key === key2)?.opener;
|
|
1736
|
+
const neighbor = next ?? remaining.find((key3) => key3 === opener) ?? remaining[Math.min(pane.tabs.indexOf(key2), remaining.length - 1)] ?? tabs.find((t) => t.key !== key2)?.key;
|
|
1737
|
+
onClose(key2, neighbor);
|
|
1495
1738
|
requestAnimationFrame(
|
|
1496
|
-
() => (
|
|
1497
|
-
|
|
1739
|
+
() => (links.get(
|
|
1740
|
+
key2 === pane.active ? neighbor ?? "" : pane.active ?? ""
|
|
1498
1741
|
) ?? root.current?.querySelector(
|
|
1499
1742
|
".lab-toolbar-end button, [data-pane-sheet]"
|
|
1500
1743
|
))?.focus({ preventScroll: true })
|
|
1501
1744
|
);
|
|
1502
1745
|
}
|
|
1503
|
-
|
|
1746
|
+
function requestClose(key2, next) {
|
|
1747
|
+
if (![...dirty.current.values()].includes(key2)) return close(key2, next);
|
|
1748
|
+
const tab = tabs.find((tab2) => tab2.key === key2);
|
|
1749
|
+
const element = links.get(key2);
|
|
1750
|
+
Promise.resolve(
|
|
1751
|
+
confirmClose ? confirmClose(tab) : confirm(`Discard changes in ${tab.label}?`)
|
|
1752
|
+
).then((discard) => {
|
|
1753
|
+
if (discard) close(key2, next);
|
|
1754
|
+
else requestAnimationFrame(() => element?.focus());
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
function leave(key2) {
|
|
1758
|
+
const opener = openers.get(key2) ?? tabs.find((tab) => tab.key === key2)?.opener;
|
|
1759
|
+
requestClose(
|
|
1760
|
+
key2,
|
|
1761
|
+
tabs.some((tab) => tab.key === opener) ? opener : void 0
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1504
1764
|
const paneOf = (pane) => ({
|
|
1505
1765
|
id: pane.id,
|
|
1506
1766
|
tabs: pane.tabs,
|
|
1507
1767
|
active: pane.active,
|
|
1508
|
-
focus
|
|
1768
|
+
focus: links.focus
|
|
1509
1769
|
});
|
|
1510
1770
|
return /* @__PURE__ */ jsx6(Fragment3, { children: /* @__PURE__ */ jsxs5(
|
|
1511
1771
|
"div",
|
|
@@ -1523,7 +1783,7 @@ function WorkspaceContent({
|
|
|
1523
1783
|
},
|
|
1524
1784
|
className: className ? `lab-workspace ${className}` : "lab-workspace",
|
|
1525
1785
|
onKeyDown: (event) => {
|
|
1526
|
-
if (event.key !== "F6") return;
|
|
1786
|
+
if (event.key !== "F6" || hasOpenOverlay()) return;
|
|
1527
1787
|
event.preventDefault();
|
|
1528
1788
|
const selected = Array.from(
|
|
1529
1789
|
event.currentTarget.querySelectorAll(
|
|
@@ -1558,11 +1818,11 @@ function WorkspaceContent({
|
|
|
1558
1818
|
},
|
|
1559
1819
|
docking,
|
|
1560
1820
|
surface,
|
|
1561
|
-
items: pane.tabs.map((
|
|
1821
|
+
items: pane.tabs.map((key2) => tabs.find((t) => t.key === key2)),
|
|
1562
1822
|
active: pane.active,
|
|
1563
1823
|
onSelect,
|
|
1564
|
-
onDuplicate: onDuplicate ? (
|
|
1565
|
-
const copy = onDuplicate(
|
|
1824
|
+
onDuplicate: onDuplicate ? (key2) => {
|
|
1825
|
+
const copy = onDuplicate(key2), index = pane.tabs.indexOf(key2) + 1;
|
|
1566
1826
|
change(
|
|
1567
1827
|
updateLayout(visibleLayout, pane.id, () => ({
|
|
1568
1828
|
...pane,
|
|
@@ -1576,18 +1836,7 @@ function WorkspaceContent({
|
|
|
1576
1836
|
);
|
|
1577
1837
|
return copy;
|
|
1578
1838
|
} : void 0,
|
|
1579
|
-
onClose:
|
|
1580
|
-
if (![...dirty.current.values()].includes(key))
|
|
1581
|
-
return close(key);
|
|
1582
|
-
const tab = tabs.find((tab2) => tab2.key === key);
|
|
1583
|
-
const element = document.getElementById(ids.tab(key));
|
|
1584
|
-
Promise.resolve(
|
|
1585
|
-
confirmClose ? confirmClose(tab) : confirm(`Discard changes in ${tab.label}?`)
|
|
1586
|
-
).then((discard) => {
|
|
1587
|
-
if (discard) close(key);
|
|
1588
|
-
else requestAnimationFrame(() => element?.focus());
|
|
1589
|
-
});
|
|
1590
|
-
},
|
|
1839
|
+
onClose: requestClose,
|
|
1591
1840
|
onReorder: (keys) => change(
|
|
1592
1841
|
updateLayout(visibleLayout, pane.id, (node) => ({
|
|
1593
1842
|
...node,
|
|
@@ -1601,11 +1850,12 @@ function WorkspaceContent({
|
|
|
1601
1850
|
const region = regions.find(
|
|
1602
1851
|
(r) => r.pane.tabs.includes(tab.key)
|
|
1603
1852
|
);
|
|
1853
|
+
const hidden = tab.key !== region.pane.active && tab.key !== lifted;
|
|
1604
1854
|
return /* @__PURE__ */ jsx6(
|
|
1605
1855
|
TabPanel,
|
|
1606
1856
|
{
|
|
1607
1857
|
tab,
|
|
1608
|
-
hidden
|
|
1858
|
+
hidden,
|
|
1609
1859
|
pane: region.pane.id,
|
|
1610
1860
|
box: region.box,
|
|
1611
1861
|
transition,
|
|
@@ -1614,10 +1864,19 @@ function WorkspaceContent({
|
|
|
1614
1864
|
if (active !== tab.key) onSelect(tab.key);
|
|
1615
1865
|
},
|
|
1616
1866
|
children: /* @__PURE__ */ jsx6(
|
|
1617
|
-
|
|
1867
|
+
TabScope,
|
|
1618
1868
|
{
|
|
1619
|
-
|
|
1620
|
-
|
|
1869
|
+
active: tab.key === front,
|
|
1870
|
+
visible: !hidden,
|
|
1871
|
+
leave: () => leave(tab.key),
|
|
1872
|
+
history: historyOf(tab),
|
|
1873
|
+
children: /* @__PURE__ */ jsx6(
|
|
1874
|
+
UnsavedChanges.Provider,
|
|
1875
|
+
{
|
|
1876
|
+
value: (source, changed) => changed ? dirty.current.set(source, tab.key) : dirty.current.delete(source),
|
|
1877
|
+
children: children(tab, paneOf(region.pane))
|
|
1878
|
+
}
|
|
1879
|
+
)
|
|
1621
1880
|
}
|
|
1622
1881
|
)
|
|
1623
1882
|
},
|
|
@@ -1641,6 +1900,16 @@ function WorkspaceContent({
|
|
|
1641
1900
|
}
|
|
1642
1901
|
) });
|
|
1643
1902
|
}
|
|
1903
|
+
function TabScope({
|
|
1904
|
+
active,
|
|
1905
|
+
visible,
|
|
1906
|
+
leave,
|
|
1907
|
+
history: history2,
|
|
1908
|
+
children
|
|
1909
|
+
}) {
|
|
1910
|
+
const state = useMemo2(() => ({ active, visible }), [active, visible]);
|
|
1911
|
+
return /* @__PURE__ */ jsx6(TabStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx6(LeaveTab.Provider, { value: leave, children: /* @__PURE__ */ jsx6(TabHistoryContext.Provider, { value: history2, children }) }) });
|
|
1912
|
+
}
|
|
1644
1913
|
function TabPanel({
|
|
1645
1914
|
tab,
|
|
1646
1915
|
hidden,
|
|
@@ -1652,8 +1921,8 @@ function TabPanel({
|
|
|
1652
1921
|
}) {
|
|
1653
1922
|
const ids = useWorkspaceIds();
|
|
1654
1923
|
const { height, borderWidth } = useTabSize();
|
|
1655
|
-
const viewport =
|
|
1656
|
-
|
|
1924
|
+
const viewport = useRef9(null);
|
|
1925
|
+
useLayoutEffect8(() => {
|
|
1657
1926
|
viewport.current?.scrollTo(0, 0);
|
|
1658
1927
|
}, [tab?.scrollKey]);
|
|
1659
1928
|
return (
|
|
@@ -1696,9 +1965,9 @@ function TabPanel({
|
|
|
1696
1965
|
|
|
1697
1966
|
// workspace-frame.tsx
|
|
1698
1967
|
import {
|
|
1699
|
-
useLayoutEffect as
|
|
1700
|
-
useRef as
|
|
1701
|
-
useState as
|
|
1968
|
+
useLayoutEffect as useLayoutEffect9,
|
|
1969
|
+
useRef as useRef10,
|
|
1970
|
+
useState as useState6
|
|
1702
1971
|
} from "react";
|
|
1703
1972
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1704
1973
|
function WorkspaceFrame({
|
|
@@ -1706,17 +1975,17 @@ function WorkspaceFrame({
|
|
|
1706
1975
|
change,
|
|
1707
1976
|
children
|
|
1708
1977
|
}) {
|
|
1709
|
-
const viewport =
|
|
1710
|
-
const drag =
|
|
1711
|
-
const [available, setAvailable] =
|
|
1712
|
-
const [draft, setDraft] =
|
|
1978
|
+
const viewport = useRef10(null);
|
|
1979
|
+
const drag = useRef10(void 0);
|
|
1980
|
+
const [available, setAvailable] = useState6(0);
|
|
1981
|
+
const [draft, setDraft] = useState6();
|
|
1713
1982
|
const compact = Math.min(1600, available);
|
|
1714
1983
|
const width = draft ?? (expanded ? available : compact);
|
|
1715
1984
|
const cancel = () => {
|
|
1716
1985
|
drag.current = void 0;
|
|
1717
1986
|
setDraft(void 0);
|
|
1718
1987
|
};
|
|
1719
|
-
|
|
1988
|
+
useLayoutEffect9(() => {
|
|
1720
1989
|
const observer = new ResizeObserver(() => {
|
|
1721
1990
|
setAvailable(viewport.current.clientWidth);
|
|
1722
1991
|
cancel();
|
|
@@ -1803,10 +2072,10 @@ function WorkspaceFrame({
|
|
|
1803
2072
|
|
|
1804
2073
|
// window.tsx
|
|
1805
2074
|
import {
|
|
1806
|
-
useEffect as
|
|
1807
|
-
useLayoutEffect as
|
|
1808
|
-
useRef as
|
|
1809
|
-
useState as
|
|
2075
|
+
useEffect as useEffect5,
|
|
2076
|
+
useLayoutEffect as useLayoutEffect10,
|
|
2077
|
+
useRef as useRef11,
|
|
2078
|
+
useState as useState7
|
|
1810
2079
|
} from "react";
|
|
1811
2080
|
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1812
2081
|
var client = typeof window !== "undefined";
|
|
@@ -1820,8 +2089,8 @@ var setFullscreen = (host, on) => {
|
|
|
1820
2089
|
var interactive = "a, button, input, select, textarea, label, summary, [contenteditable], [role=button], [role=tab], [role=separator], [role=menuitem], .lab-tab";
|
|
1821
2090
|
var draggable = (target) => target instanceof Element && !target.closest(interactive) && (!!target.closest(".lab-toolbar, [data-window-drag]") || target.matches(".lab-workspace") || !!target.querySelector(".lab-workspace"));
|
|
1822
2091
|
function useFullscreen(host) {
|
|
1823
|
-
const [full, setFull] =
|
|
1824
|
-
|
|
2092
|
+
const [full, setFull] = useState7(() => isFullscreen(host));
|
|
2093
|
+
useEffect5(() => {
|
|
1825
2094
|
const update = () => setFull(isFullscreen(host));
|
|
1826
2095
|
update();
|
|
1827
2096
|
window.addEventListener("resize", update);
|
|
@@ -1834,7 +2103,7 @@ function useFullscreen(host) {
|
|
|
1834
2103
|
return [full, (on) => setFullscreen(host, on)];
|
|
1835
2104
|
}
|
|
1836
2105
|
function useWindowFrame(host) {
|
|
1837
|
-
|
|
2106
|
+
useEffect5(() => {
|
|
1838
2107
|
if (!host) return;
|
|
1839
2108
|
const root = document.documentElement;
|
|
1840
2109
|
let armed = false;
|
|
@@ -1870,7 +2139,7 @@ function useWindowFrame(host) {
|
|
|
1870
2139
|
const dblclick = (event) => {
|
|
1871
2140
|
if (draggable(event.target)) host.toggleMaximize();
|
|
1872
2141
|
};
|
|
1873
|
-
const
|
|
2142
|
+
const key2 = (event) => {
|
|
1874
2143
|
const shortcut = host.platform === "macos" ? event.ctrlKey && event.metaKey && event.code === "KeyF" : event.key === "F11";
|
|
1875
2144
|
if (!host.fullscreen || !shortcut) return;
|
|
1876
2145
|
event.preventDefault();
|
|
@@ -1880,13 +2149,13 @@ function useWindowFrame(host) {
|
|
|
1880
2149
|
window.addEventListener("mousedown", down);
|
|
1881
2150
|
window.addEventListener("mouseup", up);
|
|
1882
2151
|
window.addEventListener("dblclick", dblclick);
|
|
1883
|
-
window.addEventListener("keydown",
|
|
2152
|
+
window.addEventListener("keydown", key2);
|
|
1884
2153
|
return () => {
|
|
1885
2154
|
window.removeEventListener("mousemove", move);
|
|
1886
2155
|
window.removeEventListener("mousedown", down);
|
|
1887
2156
|
window.removeEventListener("mouseup", up);
|
|
1888
2157
|
window.removeEventListener("dblclick", dblclick);
|
|
1889
|
-
window.removeEventListener("keydown",
|
|
2158
|
+
window.removeEventListener("keydown", key2);
|
|
1890
2159
|
if (cursor) root.style.cursor = "";
|
|
1891
2160
|
};
|
|
1892
2161
|
}, [host]);
|
|
@@ -1906,9 +2175,9 @@ var glyph = (children) => /* @__PURE__ */ jsx8(
|
|
|
1906
2175
|
);
|
|
1907
2176
|
function TrafficLights({ host }) {
|
|
1908
2177
|
const [full] = useFullscreen(host);
|
|
1909
|
-
const spacer =
|
|
2178
|
+
const spacer = useRef11(null);
|
|
1910
2179
|
const shown = host.platform === "macos" && !full;
|
|
1911
|
-
|
|
2180
|
+
useLayoutEffect10(() => {
|
|
1912
2181
|
const element = spacer.current;
|
|
1913
2182
|
const toolbar = element?.closest(".lab-toolbar");
|
|
1914
2183
|
if (!element || !toolbar || !host.trafficLights) return;
|
|
@@ -1935,16 +2204,16 @@ function desktopSlots(host, menu) {
|
|
|
1935
2204
|
}
|
|
1936
2205
|
function WindowControls({ host }) {
|
|
1937
2206
|
const [full] = useFullscreen(host);
|
|
1938
|
-
const [max, setMax] =
|
|
1939
|
-
const [top, setTop] =
|
|
1940
|
-
const spacer =
|
|
2207
|
+
const [max, setMax] = useState7(maximized);
|
|
2208
|
+
const [top, setTop] = useState7(0);
|
|
2209
|
+
const spacer = useRef11(null);
|
|
1941
2210
|
const hidden = host.platform === "macos" || full;
|
|
1942
2211
|
const measure = () => {
|
|
1943
2212
|
setMax(maximized());
|
|
1944
2213
|
if (spacer.current) setTop(spacer.current.getBoundingClientRect().top);
|
|
1945
2214
|
};
|
|
1946
|
-
|
|
1947
|
-
|
|
2215
|
+
useLayoutEffect10(measure);
|
|
2216
|
+
useEffect5(() => {
|
|
1948
2217
|
window.addEventListener("resize", measure);
|
|
1949
2218
|
return () => window.removeEventListener("resize", measure);
|
|
1950
2219
|
}, []);
|
|
@@ -2002,10 +2271,17 @@ export {
|
|
|
2002
2271
|
densitySize,
|
|
2003
2272
|
desktopSlots,
|
|
2004
2273
|
dockTab,
|
|
2274
|
+
hasOpenOverlay,
|
|
2005
2275
|
panes,
|
|
2006
2276
|
reconcileLayout,
|
|
2007
2277
|
restoreLayout,
|
|
2008
2278
|
useFullscreen,
|
|
2279
|
+
useLeaveTab,
|
|
2280
|
+
useShownOnce,
|
|
2281
|
+
useTabActive,
|
|
2282
|
+
useTabHistory,
|
|
2283
|
+
useTabVisible,
|
|
2284
|
+
useTabVisibleRef,
|
|
2009
2285
|
useUnsavedChanges,
|
|
2010
2286
|
useWindowFrame
|
|
2011
2287
|
};
|