@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/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  // workspace.tsx
2
2
  import {
3
- useEffect as useEffect2,
3
+ useEffect as useEffect4,
4
4
  useId as useId2,
5
- useLayoutEffect as useLayoutEffect4,
6
- useRef as useRef5,
7
- useState as useState4
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((key) => [
56
- key,
57
- from[key] + (to[key] - from[key]) * amount
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: (key) => `${prefix}-tab-${encodeURIComponent(key)}`,
71
- panel: (key) => `${prefix}-panel-${encodeURIComponent(key)}`
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
- (key) => keys.includes(key) && !seen.has(key) && !!seen.add(key)
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((key) => !seen.has(key));
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 read(value2, depth = 0) {
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: read(n.first, depth + 1),
138
- second: read(n.second, depth + 1)
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((key) => typeof key !== "string"))
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(read(JSON.parse(value ?? "null")), keys);
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, key, target, edge, index) {
160
- const leaves = panes(layout), source = leaves.find((p) => p.tabs.includes(key)), destination = leaves.find((p) => p.id === target);
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(key), tabs = pane.tabs.filter((k) => k !== key);
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 === key ? tabs[Math.min(index2, tabs.length - 1)] : 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, key);
176
- return { ...pane, tabs, active: key };
188
+ tabs.splice(index ?? tabs.length, 0, key2);
189
+ return { ...pane, tabs, active: key2 };
177
190
  }
178
- const added = { id: newId(), tabs: [key], active: key };
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
- key
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: (key) => {
315
- const index = leaves.findIndex((p) => p.pane.tabs.includes(key));
334
+ next: (key2) => {
335
+ const index = leaves.findIndex((p) => p.pane.tabs.includes(key2));
316
336
  change(
317
- dockTab(layout, key, leaves[(index + 1) % leaves.length].pane.id)
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(key));
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(key) || !!edge && target.pane.tabs.length > 1);
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: (key, edge) => {
387
- const target = leaves.find((p) => p.pane.tabs.includes(key));
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, key, target.pane.id, edge));
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((key) => key !== lifted);
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
- (key = pane.active) => board.current?.querySelector(
449
- key ? `[id="${CSS.escape(ids.panel(key))}"]` : `[data-pane-sheet="${CSS.escape(pane.id)}"]:not([hidden]):not([data-drag-content])`
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 }) => [key, label]);
795
- const tabOrder = JSON.stringify(items.map(({ key }) => 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 = (key) => navRef.current?.querySelector(
827
- `a[data-tab="${CSS.escape(key)}"]`
848
+ const tabElement = (key2) => navRef.current?.querySelector(
849
+ `a[data-tab="${CSS.escape(key2)}"]`
828
850
  )?.parentElement;
829
- const reveal = (key = active) => {
830
- const nav = navRef.current, tab = tabElement(key);
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 = (key) => requestAnimationFrame(() => {
934
+ const settle = (key2) => requestAnimationFrame(() => {
913
935
  if (drag.current) return;
914
- reveal(key);
915
- const ghost2 = ghostRef.current, slot = tabElement(key);
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(([key]) => key), index = order.indexOf(to);
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 focusTab = (key) => requestAnimationFrame(() => document.getElementById(ids.tab(key))?.focus());
956
- const split = (key, edge) => {
977
+ const split = (key2, edge) => {
957
978
  announce(
958
- docking.split(key, edge) ? `${labels.get(key)} split ${edge}` : "Open another tab or make this pane larger to split it"
979
+ docking.split(key2, edge) ? `${labels.get(key2)} split ${edge}` : "Open another tab or make this pane larger to split it"
959
980
  );
960
- focusTab(key);
981
+ tabLinks.focus(key2);
961
982
  };
962
- const keyDown = (event, key, index) => {
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(key, edge);
993
+ if (event.shiftKey) split(key2, edge);
972
994
  else if (edge !== "bottom")
973
- move(key, items[index + (edge === "left" ? -1 : 1)]?.key ?? key);
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
- document.getElementById(ids.tab(items[target].key))?.focus();
986
- else if (event.key === "Delete") onClose(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 key = element.querySelector("a")?.dataset.tab;
1037
- if (!key) continue;
1038
- const left = element.offsetLeft, from = previous.tabs.get(key);
1039
- if (previous.order !== order && key !== dragging && from !== void 0 && from !== left)
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(key, left);
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(([key, text], index) => {
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(key);
1199
+ menuFocus.current = onDuplicate(key2);
1179
1200
  }
1180
1201
  }
1181
1202
  ] : [],
1182
1203
  {
1183
1204
  label: "Move left",
1184
- onSelect: () => move(key, items[index - 1].key),
1205
+ onSelect: () => move(key2, items[index - 1].key),
1185
1206
  disabled: !index
1186
1207
  },
1187
1208
  {
1188
1209
  label: "Move right",
1189
- onSelect: () => move(key, items[index + 1].key),
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(key),
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(key, edge),
1221
+ onSelect: () => split(key2, edge),
1201
1222
  disabled: items.length < 2
1202
1223
  })),
1203
1224
  null,
1204
- { label: "Close tab", onSelect: () => onClose(key) }
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 === key || void 0,
1232
+ "data-dragging": dragging === key2 || void 0,
1212
1233
  style: {
1213
1234
  paddingTop: height - contentHeight,
1214
- marginRight: dragging === key && drag.current?.detached ? -tabWidth : 0,
1235
+ marginRight: dragging === key2 && drag.current?.detached ? -tabWidth : 0,
1215
1236
  marginLeft: docking.insertion === index ? tabWidth : 0,
1216
- zIndex: active === key ? links.length + 1 : links.length - index
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 === key,
1244
+ selected: active === key2,
1224
1245
  keyboardFocus: !pointerFocus
1225
1246
  }
1226
1247
  ),
1227
1248
  /* @__PURE__ */ jsxs3(
1228
1249
  "a",
1229
1250
  {
1230
- id: ids.tab(key),
1251
+ ref: tabLinks.register(key2),
1252
+ id: ids.tab(key2),
1231
1253
  role: "tab",
1232
- tabIndex: active === key ? 0 : -1,
1233
- "aria-selected": active === key,
1234
- "aria-controls": ids.panel(key),
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": key,
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(key);
1264
+ onSelect(key2);
1243
1265
  },
1244
- onFocus: () => onSelect(key),
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, key, index),
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(key) })
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
- focusTab(menuFocus.current ?? key);
1305
+ tabLinks.focus(menuFocus.current ?? key2);
1284
1306
  menuFocus.current = void 0;
1285
1307
  },
1286
- key
1287
- ) : target }, key);
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
- return /* @__PURE__ */ jsx6(WorkspaceId.Provider, { value: id, children: /* @__PURE__ */ jsx6(WorkspaceDensity.Provider, { value: props.density ?? 100, children: /* @__PURE__ */ jsx6(WorkspaceContent, { ...props }) }) });
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 ids = useWorkspaceIds();
1687
+ const links = useTabLinks();
1450
1688
  const { height, contentHeight, logoSize } = useTabSize();
1451
- const corner2 = useRef5(null);
1452
- const [reserve, setReserve] = useState4(0);
1689
+ const corner2 = useRef9(null);
1690
+ const [reserve, setReserve] = useState5(0);
1453
1691
  const hasTrailing = !!trailing;
1454
- useLayoutEffect4(() => {
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 = useRef5(null);
1462
- const [inset, setInset] = useState4(0);
1463
- useLayoutEffect4(() => {
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 = useRef5(void 0);
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
- useLayoutEffect4(() => {
1715
+ useLayoutEffect8(() => {
1478
1716
  if (visibleLayout !== layout) change(visibleLayout);
1479
1717
  }, [visibleLayout, layout]);
1480
- const dirty = useRef5(/* @__PURE__ */ new Map());
1481
- useEffect2(() => {
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(key) {
1489
- const pane = panes(visibleLayout).find((p) => p.tabs.includes(key));
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 !== key);
1492
- const opener = tabs.find((tab) => tab.key === key)?.opener;
1493
- const neighbor = remaining.find((key2) => key2 === opener) ?? remaining[Math.min(pane.tabs.indexOf(key), remaining.length - 1)] ?? tabs.find((t) => t.key !== key)?.key;
1494
- onClose(key, neighbor);
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
- () => (document.getElementById(
1497
- ids.tab(key === pane.active ? neighbor ?? "" : pane.active ?? "")
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
- const focus = (key) => requestAnimationFrame(() => document.getElementById(ids.tab(key))?.focus());
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((key) => tabs.find((t) => t.key === key)),
1821
+ items: pane.tabs.map((key2) => tabs.find((t) => t.key === key2)),
1562
1822
  active: pane.active,
1563
1823
  onSelect,
1564
- onDuplicate: onDuplicate ? (key) => {
1565
- const copy = onDuplicate(key), index = pane.tabs.indexOf(key) + 1;
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: (key) => {
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: tab.key !== region.pane.active && tab.key !== lifted,
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
- UnsavedChanges.Provider,
1867
+ TabScope,
1618
1868
  {
1619
- value: (source, changed) => changed ? dirty.current.set(source, tab.key) : dirty.current.delete(source),
1620
- children: children(tab, paneOf(region.pane))
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 = useRef5(null);
1656
- useLayoutEffect4(() => {
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 useLayoutEffect5,
1700
- useRef as useRef6,
1701
- useState as useState5
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 = useRef6(null);
1710
- const drag = useRef6(void 0);
1711
- const [available, setAvailable] = useState5(0);
1712
- const [draft, setDraft] = useState5();
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
- useLayoutEffect5(() => {
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 useEffect3,
1807
- useLayoutEffect as useLayoutEffect6,
1808
- useRef as useRef7,
1809
- useState as useState6
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] = useState6(() => isFullscreen(host));
1824
- useEffect3(() => {
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
- useEffect3(() => {
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 key = (event) => {
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", key);
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", key);
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 = useRef7(null);
2178
+ const spacer = useRef11(null);
1910
2179
  const shown = host.platform === "macos" && !full;
1911
- useLayoutEffect6(() => {
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] = useState6(maximized);
1939
- const [top, setTop] = useState6(0);
1940
- const spacer = useRef7(null);
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
- useLayoutEffect6(measure);
1947
- useEffect3(() => {
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
  };