@dashfoo/react 0.7.0 → 0.8.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 +2 -2
- package/dist/index.d.ts +23 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +366 -321
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { canRedo, canUndo, dashfooMachine, decideSnap,
|
|
3
|
+
import { canRedo, canUndo, dashfooMachine, decideSnap, dragDockMachine, findTabset, fromJSON, normalize, reducer, resolveDockTarget, resolveSnapGrid, settleSnap, snapEnabled, splitEdge, stackModel, tabNodeSchema, toJSON, zoneRect } from "@dashfoo/core";
|
|
4
4
|
import { Fragment, createContext, createElement, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Accessibility, DragDropManager, Draggable, Droppable, Feedback, PointerSensor } from "@dnd-kit/dom";
|
|
6
6
|
import { useActorRef, useSelector } from "@xstate/react";
|
|
@@ -66,11 +66,7 @@ const createDragManager = () => new DragDropManager({
|
|
|
66
66
|
const DragRootContext = createContext(null);
|
|
67
67
|
const DragContext = createContext(null);
|
|
68
68
|
const SharedDragManagerContext = createContext(null);
|
|
69
|
-
/**
|
|
70
|
-
* The actor assigns a freshly built intent on every pointer move, so selecting
|
|
71
|
-
* `context.intent` by reference would re-render every reader of `useDropIntent`
|
|
72
|
-
* for each move that resolved to the same slot.
|
|
73
|
-
*/
|
|
69
|
+
/** The intent is rebuilt every pointer move, so readers compare by value. */
|
|
74
70
|
const sameDropIntent = (a, b) => a === b || a !== null && b !== null && a.index === b.index && a.location === b.location && a.targetId === b.targetId;
|
|
75
71
|
const useDraggableHandle = () => {
|
|
76
72
|
const elementRef = useRef(null);
|
|
@@ -193,19 +189,23 @@ const useExternalTabSource = ({ createTab, disabled = false, label = "" }) => {
|
|
|
193
189
|
}, disabled, label);
|
|
194
190
|
};
|
|
195
191
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* actor to read and they report the empty state instead of throwing: a component
|
|
200
|
-
* that renders both inside and outside a layout should not crash in one of them.
|
|
192
|
+
* Reads the root's actor, so it works from an overlay mounted beside the layout
|
|
193
|
+
* rather than inside a drag layer. Outside a provider it reports the empty state
|
|
194
|
+
* instead of throwing.
|
|
201
195
|
*/
|
|
202
196
|
const useDragSubject = () => {
|
|
203
197
|
const actorRef = useContext(DragRootContext)?.actorRef;
|
|
204
|
-
return useSelector(actorRef, (snapshot) =>
|
|
198
|
+
return useSelector(actorRef, (snapshot) => {
|
|
199
|
+
const drag = snapshot?.context.drag;
|
|
200
|
+
return drag?.kind === "dragging" ? drag.subject : null;
|
|
201
|
+
});
|
|
205
202
|
};
|
|
206
203
|
const useDropIntent = () => {
|
|
207
204
|
const actorRef = useContext(DragRootContext)?.actorRef;
|
|
208
|
-
return useSelector(actorRef, (snapshot) =>
|
|
205
|
+
return useSelector(actorRef, (snapshot) => {
|
|
206
|
+
const drag = snapshot?.context.drag;
|
|
207
|
+
return drag?.kind === "dragging" ? drag.drop?.intent ?? null : null;
|
|
208
|
+
}, sameDropIntent);
|
|
209
209
|
};
|
|
210
210
|
|
|
211
211
|
//#endregion
|
|
@@ -251,28 +251,29 @@ const usePersistence = (config, defaultModel) => {
|
|
|
251
251
|
useEffect(() => {
|
|
252
252
|
configRef.current = config;
|
|
253
253
|
});
|
|
254
|
-
const [
|
|
255
|
-
if (config === null
|
|
254
|
+
const [load] = useState(() => {
|
|
255
|
+
if (config === null) return { kind: "absent" };
|
|
256
256
|
const raw = config.storage.getItem(config.key);
|
|
257
|
-
if (raw === null) return
|
|
257
|
+
if (raw === null) return { kind: "absent" };
|
|
258
258
|
try {
|
|
259
|
-
return
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
return {
|
|
260
|
+
kind: "loaded",
|
|
261
|
+
model: fromJSON(raw)
|
|
262
|
+
};
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
error,
|
|
266
|
+
kind: "corrupt"
|
|
267
|
+
};
|
|
262
268
|
}
|
|
263
269
|
});
|
|
270
|
+
const initialModel = load.kind === "loaded" && defaultModel !== void 0 ? load.model : defaultModel;
|
|
264
271
|
useEffect(() => {
|
|
265
272
|
const current = configRef.current;
|
|
266
|
-
if (current === null) return;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
fromJSON(raw);
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.warn("[dashfoo] discarding unreadable persisted layout", error);
|
|
273
|
-
current.storage.removeItem(current.key);
|
|
274
|
-
}
|
|
275
|
-
}, []);
|
|
273
|
+
if (current === null || load.kind !== "corrupt") return;
|
|
274
|
+
console.warn("[dashfoo] discarding unreadable persisted layout", load.error);
|
|
275
|
+
current.storage.removeItem(current.key);
|
|
276
|
+
}, [load]);
|
|
276
277
|
const loadedKey = useRef(config?.key);
|
|
277
278
|
useEffect(() => {
|
|
278
279
|
if (config !== null && config.key !== loadedKey.current) {
|
|
@@ -412,19 +413,21 @@ const warnOnce = (key, message) => {
|
|
|
412
413
|
//#endregion
|
|
413
414
|
//#region src/hooks/store.ts
|
|
414
415
|
const CONTROLLED_HISTORY_MESSAGE = "undo/redo are unavailable while `model` is controlled; keep the history in the state you own and drive it through onModelChange";
|
|
416
|
+
const CONTROLLED_SET_MODEL_MESSAGE = "setModel (and resetLayout) do nothing while `model` is controlled; set the model you own instead";
|
|
417
|
+
const CONTROLLED_MODE_SWITCH_MESSAGE = "`model` was added or removed after mount; a layout is controlled or uncontrolled for its whole life, so the first render wins. Remount (key=…) to switch";
|
|
418
|
+
const CONTROLLED_WITH_DEFAULT_MESSAGE = "`model` and `defaultModel` were both passed; `model` wins and `defaultModel` (and `persist`) are ignored";
|
|
419
|
+
/** The mode is latched on the first render; running both sources leaves the
|
|
420
|
+
* unread one describing a document nobody rendered. */
|
|
415
421
|
const useDashfooStore = (options) => {
|
|
416
422
|
const { defaultModel, model: controlledModel, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange } = options;
|
|
417
423
|
const initialModel = controlledModel ?? defaultModel;
|
|
418
424
|
if (initialModel === void 0) throw new Error("useDashfooStore requires either a `model` or a `defaultModel`.");
|
|
425
|
+
const [controlled] = useState(() => controlledModel !== void 0);
|
|
426
|
+
if (controlled !== (controlledModel !== void 0)) warnOnce("controlled-mode-switch", CONTROLLED_MODE_SWITCH_MESSAGE);
|
|
427
|
+
if (controlled && defaultModel !== void 0) warnOnce("controlled-with-default", CONTROLLED_WITH_DEFAULT_MESSAGE);
|
|
419
428
|
const actorRef = useActorRef(dashfooMachine, { input: { model: normalize(initialModel) } });
|
|
420
429
|
const history = useSelector(actorRef, (snapshot) => snapshot.context.history);
|
|
421
|
-
|
|
422
|
-
if (controlledModel !== void 0) actorRef.send({
|
|
423
|
-
model: normalize(controlledModel),
|
|
424
|
-
type: "SET_MODEL"
|
|
425
|
-
});
|
|
426
|
-
}, [actorRef, controlledModel]);
|
|
427
|
-
const model = controlledModel ?? history.present;
|
|
430
|
+
const model = useMemo(() => controlledModel === void 0 ? history.present : normalize(controlledModel), [controlledModel, history.present]);
|
|
428
431
|
const notify = useCallback((before, after, action) => {
|
|
429
432
|
if (Object.is(before, after)) return;
|
|
430
433
|
onModelChange?.(after, action);
|
|
@@ -438,6 +441,10 @@ const useDashfooStore = (options) => {
|
|
|
438
441
|
const dispatch = useCallback((action) => {
|
|
439
442
|
const resolved = onAction ? onAction(action) : action;
|
|
440
443
|
if (!resolved) return;
|
|
444
|
+
if (controlled) {
|
|
445
|
+
notify(model, reducer(model, resolved), resolved);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
441
448
|
const before = actorRef.getSnapshot().context.history.present;
|
|
442
449
|
actorRef.send({
|
|
443
450
|
action: resolved,
|
|
@@ -446,46 +453,50 @@ const useDashfooStore = (options) => {
|
|
|
446
453
|
notify(before, actorRef.getSnapshot().context.history.present, resolved);
|
|
447
454
|
}, [
|
|
448
455
|
actorRef,
|
|
456
|
+
controlled,
|
|
457
|
+
model,
|
|
449
458
|
notify,
|
|
450
459
|
onAction
|
|
451
460
|
]);
|
|
452
461
|
const undo = useCallback(() => {
|
|
453
|
-
if (
|
|
462
|
+
if (controlled) {
|
|
454
463
|
warnOnce("controlled-undo", CONTROLLED_HISTORY_MESSAGE);
|
|
455
464
|
return;
|
|
456
465
|
}
|
|
457
466
|
const before = actorRef.getSnapshot().context.history.present;
|
|
458
467
|
actorRef.send({ type: "UNDO" });
|
|
459
|
-
|
|
460
|
-
notify(before, after);
|
|
468
|
+
notify(before, actorRef.getSnapshot().context.history.present);
|
|
461
469
|
}, [
|
|
462
470
|
actorRef,
|
|
463
|
-
|
|
471
|
+
controlled,
|
|
464
472
|
notify
|
|
465
473
|
]);
|
|
466
474
|
const redo = useCallback(() => {
|
|
467
|
-
if (
|
|
475
|
+
if (controlled) {
|
|
468
476
|
warnOnce("controlled-redo", CONTROLLED_HISTORY_MESSAGE);
|
|
469
477
|
return;
|
|
470
478
|
}
|
|
471
479
|
const before = actorRef.getSnapshot().context.history.present;
|
|
472
480
|
actorRef.send({ type: "REDO" });
|
|
473
|
-
|
|
474
|
-
notify(before, after);
|
|
481
|
+
notify(before, actorRef.getSnapshot().context.history.present);
|
|
475
482
|
}, [
|
|
476
483
|
actorRef,
|
|
477
|
-
|
|
484
|
+
controlled,
|
|
478
485
|
notify
|
|
479
486
|
]);
|
|
480
487
|
const setModel = useCallback((next) => {
|
|
488
|
+
if (controlled) {
|
|
489
|
+
warnOnce("controlled-set-model", CONTROLLED_SET_MODEL_MESSAGE);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
481
492
|
actorRef.send({
|
|
482
493
|
model: normalize(next),
|
|
483
494
|
type: "SET_MODEL"
|
|
484
495
|
});
|
|
485
|
-
}, [actorRef]);
|
|
496
|
+
}, [actorRef, controlled]);
|
|
486
497
|
return useMemo(() => ({
|
|
487
|
-
canRedo: () =>
|
|
488
|
-
canUndo: () =>
|
|
498
|
+
canRedo: () => !controlled && canRedo(actorRef.getSnapshot().context.history),
|
|
499
|
+
canUndo: () => !controlled && canUndo(actorRef.getSnapshot().context.history),
|
|
489
500
|
dispatch,
|
|
490
501
|
model,
|
|
491
502
|
redo,
|
|
@@ -493,7 +504,7 @@ const useDashfooStore = (options) => {
|
|
|
493
504
|
undo
|
|
494
505
|
}), [
|
|
495
506
|
actorRef,
|
|
496
|
-
|
|
507
|
+
controlled,
|
|
497
508
|
dispatch,
|
|
498
509
|
model,
|
|
499
510
|
redo,
|
|
@@ -505,11 +516,8 @@ const useDashfooStore = (options) => {
|
|
|
505
516
|
//#endregion
|
|
506
517
|
//#region src/lib/drag-subject.ts
|
|
507
518
|
const isTabFactory = (value) => typeof value === "function";
|
|
508
|
-
/**
|
|
509
|
-
*
|
|
510
|
-
* rather than in the layer adapter because `dragstart` fires once per gesture on
|
|
511
|
-
* the shared manager, above any one layer.
|
|
512
|
-
*/
|
|
519
|
+
/** Beside the manager wiring, not the layer adapter: `dragstart` fires once per
|
|
520
|
+
* gesture on the shared manager, above any one layer. */
|
|
513
521
|
const subjectFor = (source) => {
|
|
514
522
|
const data = source.data;
|
|
515
523
|
if (data?.type === "tabset") return {
|
|
@@ -571,7 +579,7 @@ const shouldAllowDrop = (draggedId, targetId, tabIds) => {
|
|
|
571
579
|
};
|
|
572
580
|
|
|
573
581
|
//#endregion
|
|
574
|
-
//#region src/components/drag-
|
|
582
|
+
//#region src/components/drag-preview-overlay.tsx
|
|
575
583
|
const overlayBase = {
|
|
576
584
|
boxSizing: "border-box",
|
|
577
585
|
pointerEvents: "none",
|
|
@@ -595,9 +603,10 @@ const lineStyle = (zone) => ({
|
|
|
595
603
|
});
|
|
596
604
|
const tabItemRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab-item\"]")].flatMap((item) => item.querySelector("[data-dashfoo=\"tab\"]")?.dataset.tabId === excludeId ? [] : [item.getBoundingClientRect()]);
|
|
597
605
|
const DockIndicator = ({ actorRef, getTabsetElement }) => {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
606
|
+
const drag = useSelector(actorRef, (snapshot) => snapshot.context.drag);
|
|
607
|
+
if (drag.kind !== "dragging" || drag.drop === null) return null;
|
|
608
|
+
const { intent } = drag.drop;
|
|
609
|
+
const draggedId = drag.subject.id;
|
|
601
610
|
const element = getTabsetElement(intent.targetId);
|
|
602
611
|
if (!element) return null;
|
|
603
612
|
const zIndex = element.closest("[data-dashfoo=\"float\"]") ? overlayBase.zIndex : "auto";
|
|
@@ -653,12 +662,13 @@ const DragPreviewOverlay = ({ manager }) => {
|
|
|
653
662
|
offEnd();
|
|
654
663
|
};
|
|
655
664
|
}, [manager]);
|
|
665
|
+
const attachOverlay = useCallback((element) => {
|
|
666
|
+
const feedback = manager.registry.plugins.get(Feedback);
|
|
667
|
+
if (feedback) feedback.overlay = element ?? void 0;
|
|
668
|
+
}, [manager]);
|
|
656
669
|
return /* @__PURE__ */ jsx("div", {
|
|
657
670
|
"data-dnd-overlay": "",
|
|
658
|
-
ref:
|
|
659
|
-
const feedback = manager.registry.plugins.get(Feedback);
|
|
660
|
-
if (feedback) feedback.overlay = element ?? void 0;
|
|
661
|
-
}, [manager]),
|
|
671
|
+
ref: attachOverlay,
|
|
662
672
|
children: chip === null ? null : /* @__PURE__ */ jsx("div", {
|
|
663
673
|
"data-dashfoo": "drag-preview",
|
|
664
674
|
style: {
|
|
@@ -674,7 +684,7 @@ const DragPreviewOverlay = ({ manager }) => {
|
|
|
674
684
|
};
|
|
675
685
|
|
|
676
686
|
//#endregion
|
|
677
|
-
//#region src/components/drag-
|
|
687
|
+
//#region src/components/dashfoo-drag-provider.tsx
|
|
678
688
|
const DashfooDragProvider = ({ children }) => {
|
|
679
689
|
const [manager] = useState(createDragManager);
|
|
680
690
|
useInsertionEffect(() => () => {
|
|
@@ -688,11 +698,6 @@ const DashfooDragProvider = ({ children }) => {
|
|
|
688
698
|
scopes.delete(scope.layerId);
|
|
689
699
|
};
|
|
690
700
|
}, [scopes]);
|
|
691
|
-
/**
|
|
692
|
-
* One listener set and one actor for the whole tree. Attached per layer, a
|
|
693
|
-
* single pointer move fanned out to every float's actor, and each one that did
|
|
694
|
-
* not own the target resolved `intent: null` and then swallowed the drop.
|
|
695
|
-
*/
|
|
696
701
|
useEffect(() => {
|
|
697
702
|
const scopedTarget = () => {
|
|
698
703
|
const target = manager.dragOperation.target;
|
|
@@ -706,15 +711,16 @@ const DashfooDragProvider = ({ children }) => {
|
|
|
706
711
|
tabsetId: data.tabsetId
|
|
707
712
|
};
|
|
708
713
|
};
|
|
709
|
-
let intentLayerId = null;
|
|
710
714
|
const syncIntent = () => {
|
|
711
715
|
const operation = manager.dragOperation;
|
|
712
716
|
const found = scopedTarget();
|
|
713
717
|
const draggedId = operation.source ? String(operation.source.id) : void 0;
|
|
714
718
|
const intent = found === null ? null : found.scope.resolveIntent(found.tabsetId, found.element, operation.position.current, draggedId);
|
|
715
|
-
intentLayerId = intent === null || found === null ? null : found.scope.layerId;
|
|
716
719
|
actorRef.send({
|
|
717
|
-
intent
|
|
720
|
+
drop: intent === null || found === null ? null : {
|
|
721
|
+
intent,
|
|
722
|
+
scope: found.scope.layerId
|
|
723
|
+
},
|
|
718
724
|
type: "OVER"
|
|
719
725
|
});
|
|
720
726
|
};
|
|
@@ -726,19 +732,27 @@ const DashfooDragProvider = ({ children }) => {
|
|
|
726
732
|
manager.actions.stop({ canceled: true });
|
|
727
733
|
return;
|
|
728
734
|
}
|
|
729
|
-
intentLayerId = null;
|
|
730
735
|
actorRef.send({
|
|
731
736
|
subject,
|
|
732
737
|
type: "START"
|
|
733
738
|
});
|
|
734
739
|
};
|
|
735
|
-
|
|
736
|
-
|
|
740
|
+
let frame = null;
|
|
741
|
+
const scheduleResolve = () => {
|
|
742
|
+
if (frame !== null) return;
|
|
743
|
+
frame = requestAnimationFrame(() => {
|
|
744
|
+
frame = null;
|
|
745
|
+
syncIntent();
|
|
746
|
+
});
|
|
737
747
|
};
|
|
738
|
-
const
|
|
739
|
-
|
|
748
|
+
const cancelPendingResolve = () => {
|
|
749
|
+
if (frame !== null) {
|
|
750
|
+
cancelAnimationFrame(frame);
|
|
751
|
+
frame = null;
|
|
752
|
+
}
|
|
740
753
|
};
|
|
741
754
|
const handleEnd = (event) => {
|
|
755
|
+
cancelPendingResolve();
|
|
742
756
|
if (event.canceled) {
|
|
743
757
|
actorRef.send({ type: "CANCEL" });
|
|
744
758
|
return;
|
|
@@ -747,13 +761,14 @@ const DashfooDragProvider = ({ children }) => {
|
|
|
747
761
|
actorRef.send({ type: "DROP" });
|
|
748
762
|
};
|
|
749
763
|
const commitSubscription = actorRef.on("COMMIT", (emitted) => {
|
|
750
|
-
|
|
764
|
+
scopes.get(emitted.scope)?.commit(emitted.action);
|
|
751
765
|
});
|
|
752
766
|
const offStart = manager.monitor.addEventListener("dragstart", handleStart);
|
|
753
|
-
const offMove = manager.monitor.addEventListener("dragmove",
|
|
754
|
-
const offCollision = manager.monitor.addEventListener("collision",
|
|
767
|
+
const offMove = manager.monitor.addEventListener("dragmove", scheduleResolve);
|
|
768
|
+
const offCollision = manager.monitor.addEventListener("collision", scheduleResolve);
|
|
755
769
|
const offEnd = manager.monitor.addEventListener("dragend", handleEnd);
|
|
756
770
|
return () => {
|
|
771
|
+
cancelPendingResolve();
|
|
757
772
|
commitSubscription.unsubscribe();
|
|
758
773
|
offStart();
|
|
759
774
|
offMove();
|
|
@@ -794,19 +809,34 @@ const useLayout = (selector) => {
|
|
|
794
809
|
};
|
|
795
810
|
|
|
796
811
|
//#endregion
|
|
797
|
-
//#region src/components/drag-
|
|
812
|
+
//#region src/components/drag-provider.tsx
|
|
798
813
|
const MISSING_ROOT = "[dashfoo] DashfooDragProvider unmounted while its layout is still mounted; keep the provider above the layout or remount the layout";
|
|
799
|
-
|
|
800
|
-
const
|
|
814
|
+
/** One DOM pass per pointer move, where resolving a drop used to run three. */
|
|
815
|
+
const measureStrip = (element, excludeId) => {
|
|
801
816
|
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
802
|
-
if (strip
|
|
803
|
-
|
|
817
|
+
if (!strip) return null;
|
|
818
|
+
const tabIds = [];
|
|
819
|
+
const tabRects = [];
|
|
820
|
+
for (const tab of strip.querySelectorAll("[data-dashfoo=\"tab\"]")) {
|
|
821
|
+
const tabId = tab.dataset.tabId ?? "";
|
|
822
|
+
tabIds.push(tabId);
|
|
823
|
+
if (tabId !== excludeId) tabRects.push(tab.getBoundingClientRect());
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
rect: strip.getBoundingClientRect(),
|
|
827
|
+
tabIds,
|
|
828
|
+
tabRects
|
|
829
|
+
};
|
|
830
|
+
};
|
|
831
|
+
const intentForTabset = (id, element, point, strip) => {
|
|
832
|
+
if (strip && pointInRect(point, strip.rect)) return {
|
|
833
|
+
index: insertionIndex(strip.tabRects, point.x),
|
|
804
834
|
location: "center",
|
|
805
835
|
targetId: id
|
|
806
836
|
};
|
|
807
|
-
const location =
|
|
837
|
+
const location = resolveDockTarget(point, element.getBoundingClientRect());
|
|
808
838
|
if (location === "center" && strip) return {
|
|
809
|
-
index:
|
|
839
|
+
index: strip.tabRects.length,
|
|
810
840
|
location,
|
|
811
841
|
targetId: id
|
|
812
842
|
};
|
|
@@ -815,11 +845,7 @@ const intentForTabset = (id, element, point, draggedId) => {
|
|
|
815
845
|
targetId: id
|
|
816
846
|
};
|
|
817
847
|
};
|
|
818
|
-
/**
|
|
819
|
-
* One drag layer: the main tree, or one float. It holds no drag state of its
|
|
820
|
-
* own. It registers how its own tabsets turn a pointer into an intent and where
|
|
821
|
-
* a committed action goes, then draws the indicator over its own droppables.
|
|
822
|
-
*/
|
|
848
|
+
/** One drag layer (the main tree, or one float). Holds no drag state of its own. */
|
|
823
849
|
const DragScope = ({ children, onCommit, splitDock }) => {
|
|
824
850
|
const root = useContext(DragRootContext);
|
|
825
851
|
const layoutStore = useContext(LayoutStoreContext);
|
|
@@ -842,8 +868,9 @@ const DragScope = ({ children, onCommit, splitDock }) => {
|
|
|
842
868
|
layerId,
|
|
843
869
|
resolveIntent: (targetId, element, point, draggedId) => {
|
|
844
870
|
if (layoutStore?.getState().editable === false) return null;
|
|
845
|
-
|
|
846
|
-
|
|
871
|
+
const strip = measureStrip(element, draggedId);
|
|
872
|
+
if (!shouldAllowDrop(draggedId, targetId, strip?.tabIds ?? [])) return null;
|
|
873
|
+
const intent = intentForTabset(targetId, element, point, strip);
|
|
847
874
|
if (!(splitDock ?? layoutStore?.getState().splitDock ?? true) && splitEdge(intent.location) !== void 0) return {
|
|
848
875
|
location: "center",
|
|
849
876
|
targetId
|
|
@@ -873,10 +900,8 @@ const DragScope = ({ children, onCommit, splitDock }) => {
|
|
|
873
900
|
});
|
|
874
901
|
};
|
|
875
902
|
/**
|
|
876
|
-
* Mounts its own root when there is none above it
|
|
877
|
-
*
|
|
878
|
-
* render, as it was for the manager this used to create itself: a root that
|
|
879
|
-
* disappears later means a torn-down tree, which `DragScope` reports.
|
|
903
|
+
* Mounts its own root when there is none above it. Decided on the first render:
|
|
904
|
+
* a root that disappears later means a torn-down tree, which `DragScope` reports.
|
|
880
905
|
*/
|
|
881
906
|
const DragProvider = (props) => {
|
|
882
907
|
const hasRoot = useContext(DragRootContext) !== null;
|
|
@@ -1062,16 +1087,11 @@ const CHIP_SIZE = {
|
|
|
1062
1087
|
width: 168
|
|
1063
1088
|
};
|
|
1064
1089
|
const TAP_SLOP = 4;
|
|
1065
|
-
/**
|
|
1066
|
-
*
|
|
1067
|
-
* dispatches on pointer-up: routing every pointermove through the model would
|
|
1068
|
-
* re-render the whole layout tree behind the float.
|
|
1069
|
-
*/
|
|
1090
|
+
/** Writes inline style and dispatches only on pointer-up: routing every move
|
|
1091
|
+
* through the model would re-render the whole tree behind the float. */
|
|
1070
1092
|
const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) => {
|
|
1071
1093
|
const panelRef = useRef(null);
|
|
1072
1094
|
const gestureRef = useRef(null);
|
|
1073
|
-
const latestRef = useRef(geometry);
|
|
1074
|
-
const movedRef = useRef(false);
|
|
1075
1095
|
const setPanel = (element) => {
|
|
1076
1096
|
panelRef.current = element;
|
|
1077
1097
|
};
|
|
@@ -1080,7 +1100,6 @@ const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) =>
|
|
|
1080
1100
|
if (!panel || !editable) return;
|
|
1081
1101
|
const prior = gestureRef.current;
|
|
1082
1102
|
if (prior && panel.hasPointerCapture?.(prior.pointerId)) panel.releasePointerCapture(prior.pointerId);
|
|
1083
|
-
movedRef.current = false;
|
|
1084
1103
|
const edgeKey = event.currentTarget.dataset.edge;
|
|
1085
1104
|
const parent = panel.offsetParent instanceof HTMLElement ? panel.offsetParent : null;
|
|
1086
1105
|
const gesture = {
|
|
@@ -1089,13 +1108,14 @@ const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) =>
|
|
|
1089
1108
|
width: parent?.clientWidth ?? 0
|
|
1090
1109
|
},
|
|
1091
1110
|
edges: edgeKey === void 0 ? null : EDGE_BY_KEY.get(edgeKey) ?? null,
|
|
1111
|
+
latest: geometry,
|
|
1112
|
+
moved: false,
|
|
1092
1113
|
pointerId: event.pointerId,
|
|
1093
1114
|
start: geometry,
|
|
1094
1115
|
startX: event.clientX,
|
|
1095
1116
|
startY: event.clientY
|
|
1096
1117
|
};
|
|
1097
1118
|
gestureRef.current = gesture;
|
|
1098
|
-
latestRef.current = geometry;
|
|
1099
1119
|
if (gesture.edges) panel.setPointerCapture(event.pointerId);
|
|
1100
1120
|
};
|
|
1101
1121
|
const handlePointerUp = (event) => {
|
|
@@ -1104,11 +1124,11 @@ const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) =>
|
|
|
1104
1124
|
gestureRef.current = null;
|
|
1105
1125
|
const panel = panelRef.current;
|
|
1106
1126
|
if (panel?.hasPointerCapture?.(event.pointerId) === true) panel.releasePointerCapture(event.pointerId);
|
|
1107
|
-
if (!
|
|
1127
|
+
if (!gesture.moved) {
|
|
1108
1128
|
onTap();
|
|
1109
1129
|
return;
|
|
1110
1130
|
}
|
|
1111
|
-
onCommit(
|
|
1131
|
+
onCommit(gesture.latest);
|
|
1112
1132
|
};
|
|
1113
1133
|
const handlePointerMove = (event) => {
|
|
1114
1134
|
const gesture = gestureRef.current;
|
|
@@ -1120,9 +1140,9 @@ const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) =>
|
|
|
1120
1140
|
}
|
|
1121
1141
|
const dx = event.clientX - gesture.startX;
|
|
1122
1142
|
const dy = event.clientY - gesture.startY;
|
|
1123
|
-
if (!
|
|
1143
|
+
if (!gesture.moved) {
|
|
1124
1144
|
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
1125
|
-
|
|
1145
|
+
gesture.moved = true;
|
|
1126
1146
|
if (!gesture.edges) panel.setPointerCapture(gesture.pointerId);
|
|
1127
1147
|
}
|
|
1128
1148
|
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
@@ -1130,7 +1150,7 @@ const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) =>
|
|
|
1130
1150
|
left: gesture.start.left + dx,
|
|
1131
1151
|
top: gesture.start.top + dy
|
|
1132
1152
|
}, gesture.bounds, minimized ? CHIP_SIZE : void 0);
|
|
1133
|
-
|
|
1153
|
+
gesture.latest = next;
|
|
1134
1154
|
panel.style.left = `${next.left}px`;
|
|
1135
1155
|
panel.style.top = `${next.top}px`;
|
|
1136
1156
|
if (!minimized) {
|
|
@@ -1144,7 +1164,7 @@ const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) =>
|
|
|
1144
1164
|
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
1145
1165
|
gestureRef.current = null;
|
|
1146
1166
|
if (panel.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
1147
|
-
if (!
|
|
1167
|
+
if (!gesture.moved) return;
|
|
1148
1168
|
panel.style.left = `${gesture.start.left}px`;
|
|
1149
1169
|
panel.style.top = `${gesture.start.top}px`;
|
|
1150
1170
|
if (!minimized) {
|
|
@@ -1205,6 +1225,95 @@ const useInlineRename = ({ currentName, inputRef, onCommit, onDone }) => {
|
|
|
1205
1225
|
};
|
|
1206
1226
|
};
|
|
1207
1227
|
|
|
1228
|
+
//#endregion
|
|
1229
|
+
//#region src/components/close-icon.tsx
|
|
1230
|
+
const iconStyle = { pointerEvents: "none" };
|
|
1231
|
+
const Icon = ({ children }) => /* @__PURE__ */ jsx("svg", {
|
|
1232
|
+
"aria-hidden": "true",
|
|
1233
|
+
fill: "none",
|
|
1234
|
+
height: "12",
|
|
1235
|
+
stroke: "currentColor",
|
|
1236
|
+
strokeLinecap: "round",
|
|
1237
|
+
strokeLinejoin: "round",
|
|
1238
|
+
strokeWidth: "2",
|
|
1239
|
+
style: iconStyle,
|
|
1240
|
+
viewBox: "0 0 24 24",
|
|
1241
|
+
width: "12",
|
|
1242
|
+
children
|
|
1243
|
+
});
|
|
1244
|
+
const CloseIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] });
|
|
1245
|
+
const GripIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1246
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1247
|
+
cx: "12",
|
|
1248
|
+
cy: "5",
|
|
1249
|
+
r: "1"
|
|
1250
|
+
}),
|
|
1251
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1252
|
+
cx: "19",
|
|
1253
|
+
cy: "5",
|
|
1254
|
+
r: "1"
|
|
1255
|
+
}),
|
|
1256
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1257
|
+
cx: "5",
|
|
1258
|
+
cy: "5",
|
|
1259
|
+
r: "1"
|
|
1260
|
+
}),
|
|
1261
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1262
|
+
cx: "12",
|
|
1263
|
+
cy: "12",
|
|
1264
|
+
r: "1"
|
|
1265
|
+
}),
|
|
1266
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1267
|
+
cx: "19",
|
|
1268
|
+
cy: "12",
|
|
1269
|
+
r: "1"
|
|
1270
|
+
}),
|
|
1271
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1272
|
+
cx: "5",
|
|
1273
|
+
cy: "12",
|
|
1274
|
+
r: "1"
|
|
1275
|
+
}),
|
|
1276
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1277
|
+
cx: "12",
|
|
1278
|
+
cy: "19",
|
|
1279
|
+
r: "1"
|
|
1280
|
+
}),
|
|
1281
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1282
|
+
cx: "19",
|
|
1283
|
+
cy: "19",
|
|
1284
|
+
r: "1"
|
|
1285
|
+
}),
|
|
1286
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1287
|
+
cx: "5",
|
|
1288
|
+
cy: "19",
|
|
1289
|
+
r: "1"
|
|
1290
|
+
})
|
|
1291
|
+
] });
|
|
1292
|
+
const MaximizeIcon = ({ maximized }) => maximized ? /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1293
|
+
/* @__PURE__ */ jsx("path", { d: "m14 10 7-7" }),
|
|
1294
|
+
/* @__PURE__ */ jsx("path", { d: "M20 10h-6V4" }),
|
|
1295
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1296
|
+
/* @__PURE__ */ jsx("path", { d: "M4 14h6v6" })
|
|
1297
|
+
] }) : /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1298
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
1299
|
+
/* @__PURE__ */ jsx("path", { d: "m21 3-7 7" }),
|
|
1300
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1301
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" })
|
|
1302
|
+
] });
|
|
1303
|
+
const FloatIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1304
|
+
height: "14",
|
|
1305
|
+
rx: "2",
|
|
1306
|
+
width: "18",
|
|
1307
|
+
x: "3",
|
|
1308
|
+
y: "6"
|
|
1309
|
+
}), /* @__PURE__ */ jsx("path", { d: "M3 10h18" })] });
|
|
1310
|
+
const MinimizeIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
|
|
1311
|
+
const DockIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1312
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2" }),
|
|
1313
|
+
/* @__PURE__ */ jsx("path", { d: "M11 8 7 12l4 4" }),
|
|
1314
|
+
/* @__PURE__ */ jsx("path", { d: "M7 12h10" })
|
|
1315
|
+
] });
|
|
1316
|
+
|
|
1208
1317
|
//#endregion
|
|
1209
1318
|
//#region src/components/layout-root.tsx
|
|
1210
1319
|
const DEFAULT_TABSET_MIN_SIZE = 320;
|
|
@@ -1261,10 +1370,8 @@ const LayoutRoot = ({ children, closableTabs = true, dispatch, draggableTabs = t
|
|
|
1261
1370
|
});
|
|
1262
1371
|
};
|
|
1263
1372
|
/**
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1266
|
-
* overrides applied instead of re-deriving every flag from a synthetic model,
|
|
1267
|
-
* so anything the parent changes keeps flowing into the subtree.
|
|
1373
|
+
* Republishes the parent store with overrides applied rather than re-deriving
|
|
1374
|
+
* flags, so anything the parent changes keeps flowing into the subtree.
|
|
1268
1375
|
*/
|
|
1269
1376
|
const LayoutOverrides = ({ children, overrides }) => {
|
|
1270
1377
|
const snapshot = {
|
|
@@ -1402,7 +1509,7 @@ const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
|
1402
1509
|
set({ editingTabId: null });
|
|
1403
1510
|
},
|
|
1404
1511
|
closeTab: (tabId) => {
|
|
1405
|
-
set({
|
|
1512
|
+
set({ pendingCloseTabId: tabId });
|
|
1406
1513
|
get().dispatch({
|
|
1407
1514
|
tabId,
|
|
1408
1515
|
type: "deleteTab"
|
|
@@ -1417,6 +1524,7 @@ const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
|
1417
1524
|
},
|
|
1418
1525
|
editingTabId: null,
|
|
1419
1526
|
overflowItems: [],
|
|
1527
|
+
pendingCloseTabId: null,
|
|
1420
1528
|
registerRenameInput: () => {
|
|
1421
1529
|
set((state) => ({ renameInputCount: state.renameInputCount + 1 }));
|
|
1422
1530
|
return () => {
|
|
@@ -1440,7 +1548,6 @@ const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
|
1440
1548
|
type: "renameTab"
|
|
1441
1549
|
});
|
|
1442
1550
|
},
|
|
1443
|
-
restoreFocus: false,
|
|
1444
1551
|
selectOverflowTab: (tabId) => {
|
|
1445
1552
|
const { dispatch, node, tablistElement } = get();
|
|
1446
1553
|
const index = node.children.findIndex((tab) => tab.id === tabId);
|
|
@@ -1554,7 +1661,7 @@ const TabsetContent = ({ children, style, ...props }) => {
|
|
|
1554
1661
|
};
|
|
1555
1662
|
|
|
1556
1663
|
//#endregion
|
|
1557
|
-
//#region src/components/tab-overflow.tsx
|
|
1664
|
+
//#region src/components/tab-overflow-menu.tsx
|
|
1558
1665
|
const OverflowIcon = () => /* @__PURE__ */ jsxs("svg", {
|
|
1559
1666
|
"aria-hidden": "true",
|
|
1560
1667
|
fill: "currentColor",
|
|
@@ -1642,8 +1749,6 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1642
1749
|
case "Escape":
|
|
1643
1750
|
event.preventDefault();
|
|
1644
1751
|
closeAndReturnFocus();
|
|
1645
|
-
break;
|
|
1646
|
-
default: break;
|
|
1647
1752
|
}
|
|
1648
1753
|
};
|
|
1649
1754
|
const handleFocusOut = (event) => {
|
|
@@ -1689,7 +1794,7 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1689
1794
|
};
|
|
1690
1795
|
|
|
1691
1796
|
//#endregion
|
|
1692
|
-
//#region src/components/tabset/tabset-overflow.tsx
|
|
1797
|
+
//#region src/components/tabset/tabset-overflow-menu.tsx
|
|
1693
1798
|
const TabsetOverflowMenu = () => {
|
|
1694
1799
|
const overflowItems = useTabset((state) => state.overflowItems);
|
|
1695
1800
|
const selectOverflowTab = useTabset((state) => state.selectOverflowTab);
|
|
@@ -1700,6 +1805,83 @@ const TabsetOverflowMenu = () => {
|
|
|
1700
1805
|
});
|
|
1701
1806
|
};
|
|
1702
1807
|
|
|
1808
|
+
//#endregion
|
|
1809
|
+
//#region src/lib/merge-refs.ts
|
|
1810
|
+
const applyRef = (ref, value) => {
|
|
1811
|
+
if (typeof ref === "function") ref(value);
|
|
1812
|
+
else if (ref) ref.current = value;
|
|
1813
|
+
};
|
|
1814
|
+
const mergeRefs = (...refs) => (value) => {
|
|
1815
|
+
for (const ref of refs) applyRef(ref, value);
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
//#endregion
|
|
1819
|
+
//#region src/components/tabset/tabset-rename-input.tsx
|
|
1820
|
+
const RenameEditor = ({ defaultValue, onBlur, onKeyDown, ref: userRef, ...props }) => {
|
|
1821
|
+
const { tab } = useTab();
|
|
1822
|
+
const cancelRename = useTabset((state) => state.cancelRename);
|
|
1823
|
+
const commitRename = useTabset((state) => state.commitRename);
|
|
1824
|
+
const inputRef = useRef(null);
|
|
1825
|
+
const rename = useInlineRename({
|
|
1826
|
+
currentName: tab.name,
|
|
1827
|
+
inputRef,
|
|
1828
|
+
onCommit: (name) => {
|
|
1829
|
+
commitRename(tab.id, name);
|
|
1830
|
+
},
|
|
1831
|
+
onDone: cancelRename
|
|
1832
|
+
});
|
|
1833
|
+
const refCallback = useMemo(() => mergeRefs(inputRef, userRef), [userRef]);
|
|
1834
|
+
const handleKeyDown = (event) => {
|
|
1835
|
+
onKeyDown?.(event);
|
|
1836
|
+
rename.handleKeyDown(event);
|
|
1837
|
+
};
|
|
1838
|
+
const handleCommitOnBlur = (event) => {
|
|
1839
|
+
onBlur?.(event);
|
|
1840
|
+
rename.handleBlur(event);
|
|
1841
|
+
};
|
|
1842
|
+
return /* @__PURE__ */ jsx("input", {
|
|
1843
|
+
"aria-label": `Rename ${tab.name}`,
|
|
1844
|
+
...props,
|
|
1845
|
+
"data-dashfoo": "tab-rename",
|
|
1846
|
+
defaultValue: defaultValue ?? tab.name,
|
|
1847
|
+
onBlur: handleCommitOnBlur,
|
|
1848
|
+
onKeyDown: handleKeyDown,
|
|
1849
|
+
ref: refCallback,
|
|
1850
|
+
type: "text"
|
|
1851
|
+
});
|
|
1852
|
+
};
|
|
1853
|
+
const TabsetRenameInput = (props) => {
|
|
1854
|
+
const { tab } = useTab();
|
|
1855
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1856
|
+
const registerRenameInput = useTabset((state) => state.registerRenameInput);
|
|
1857
|
+
useEffect(() => registerRenameInput(), [registerRenameInput]);
|
|
1858
|
+
if (!editing) return null;
|
|
1859
|
+
return /* @__PURE__ */ jsx(RenameEditor, { ...props });
|
|
1860
|
+
};
|
|
1861
|
+
const TabsetCloseButton = ({ children, onClick, ...props }) => {
|
|
1862
|
+
const { index, tab } = useTab();
|
|
1863
|
+
const closeTab = useTabset((state) => state.closeTab);
|
|
1864
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1865
|
+
const tabsClosable = useTabset((state) => state.tabsClosable);
|
|
1866
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1867
|
+
if (!(tabsClosable && tab.enableClose !== false) || editing) return null;
|
|
1868
|
+
const handleClick = (event) => {
|
|
1869
|
+
onClick?.(event);
|
|
1870
|
+
event.stopPropagation();
|
|
1871
|
+
closeTab(tab.id);
|
|
1872
|
+
};
|
|
1873
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1874
|
+
"aria-label": `Close ${tab.name}`,
|
|
1875
|
+
title: `Close ${tab.name}`,
|
|
1876
|
+
...props,
|
|
1877
|
+
"data-dashfoo": "tab-close",
|
|
1878
|
+
onClick: handleClick,
|
|
1879
|
+
tabIndex: index === visualSelected ? 0 : -1,
|
|
1880
|
+
type: "button",
|
|
1881
|
+
children: children ?? /* @__PURE__ */ jsx(CloseIcon, {})
|
|
1882
|
+
});
|
|
1883
|
+
};
|
|
1884
|
+
|
|
1703
1885
|
//#endregion
|
|
1704
1886
|
//#region src/hooks/use-tab-overflow.ts
|
|
1705
1887
|
const overflowingIds = (tablist) => {
|
|
@@ -1711,13 +1893,14 @@ const overflowingIds = (tablist) => {
|
|
|
1711
1893
|
return clipped && tabId !== void 0 && tabId !== "" ? [tabId] : [];
|
|
1712
1894
|
});
|
|
1713
1895
|
};
|
|
1896
|
+
const sameIds = (a, b) => a.length === b.length && a.every((id, index) => id === b[index]);
|
|
1714
1897
|
const useTabOverflow = (tablist, signature) => {
|
|
1715
1898
|
const [overflow, setOverflow] = useState([]);
|
|
1716
1899
|
useEffect(() => {
|
|
1717
1900
|
const recompute = () => {
|
|
1718
1901
|
setOverflow((previous) => {
|
|
1719
1902
|
const next = tablist ? overflowingIds(tablist) : [];
|
|
1720
|
-
return
|
|
1903
|
+
return sameIds(previous, next) ? previous : next;
|
|
1721
1904
|
});
|
|
1722
1905
|
};
|
|
1723
1906
|
recompute();
|
|
@@ -1779,7 +1962,9 @@ const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
|
1779
1962
|
useLayoutEffect(() => {
|
|
1780
1963
|
store.setState(snapshot);
|
|
1781
1964
|
});
|
|
1782
|
-
const
|
|
1965
|
+
const tablistElement = useStore(store, (state) => state.tablistElement);
|
|
1966
|
+
const overflowSignature = node.children.map((tab) => `${tab.id}:${tab.name}`).join("|");
|
|
1967
|
+
const overflow = useTabOverflow(tablistElement, overflowSignature);
|
|
1783
1968
|
const overflowItems = useMemo(() => {
|
|
1784
1969
|
const names = new Map(node.children.map((tab) => [tab.id, tab.name]));
|
|
1785
1970
|
return overflow.map((id) => ({
|
|
@@ -1791,8 +1976,10 @@ const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
|
1791
1976
|
store.setState({ overflowItems });
|
|
1792
1977
|
}, [overflowItems, store]);
|
|
1793
1978
|
useEffect(() => {
|
|
1794
|
-
|
|
1795
|
-
|
|
1979
|
+
const { pendingCloseTabId } = store.getState();
|
|
1980
|
+
if (pendingCloseTabId === null) return;
|
|
1981
|
+
store.setState({ pendingCloseTabId: null });
|
|
1982
|
+
if (node.children.some((tab) => tab.id === pendingCloseTabId)) return;
|
|
1796
1983
|
const activeTabId = node.children[node.selected]?.id;
|
|
1797
1984
|
if (activeTabId) tabsetRef.current?.querySelector(`#${CSS.escape(tabDomId(node.id, activeTabId))}`)?.focus();
|
|
1798
1985
|
else tabsetRef.current?.focus();
|
|
@@ -1825,16 +2012,6 @@ const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
|
1825
2012
|
});
|
|
1826
2013
|
};
|
|
1827
2014
|
|
|
1828
|
-
//#endregion
|
|
1829
|
-
//#region src/lib/merge-refs.ts
|
|
1830
|
-
const applyRef = (ref, value) => {
|
|
1831
|
-
if (typeof ref === "function") ref(value);
|
|
1832
|
-
else if (ref) ref.current = value;
|
|
1833
|
-
};
|
|
1834
|
-
const mergeRefs = (...refs) => (value) => {
|
|
1835
|
-
for (const ref of refs) applyRef(ref, value);
|
|
1836
|
-
};
|
|
1837
|
-
|
|
1838
2015
|
//#endregion
|
|
1839
2016
|
//#region src/components/tabset/tabset-tab.tsx
|
|
1840
2017
|
const TabsetTab = ({ children, ref: userRef, tab, ...props }) => {
|
|
@@ -1914,162 +2091,6 @@ const TabsetTrigger = ({ children, onClick, onDoubleClick, ref: userRef, ...prop
|
|
|
1914
2091
|
});
|
|
1915
2092
|
};
|
|
1916
2093
|
|
|
1917
|
-
//#endregion
|
|
1918
|
-
//#region src/components/tabset-icons.tsx
|
|
1919
|
-
const iconStyle = { pointerEvents: "none" };
|
|
1920
|
-
const Icon = ({ children }) => /* @__PURE__ */ jsx("svg", {
|
|
1921
|
-
"aria-hidden": "true",
|
|
1922
|
-
fill: "none",
|
|
1923
|
-
height: "12",
|
|
1924
|
-
stroke: "currentColor",
|
|
1925
|
-
strokeLinecap: "round",
|
|
1926
|
-
strokeLinejoin: "round",
|
|
1927
|
-
strokeWidth: "2",
|
|
1928
|
-
style: iconStyle,
|
|
1929
|
-
viewBox: "0 0 24 24",
|
|
1930
|
-
width: "12",
|
|
1931
|
-
children
|
|
1932
|
-
});
|
|
1933
|
-
const CloseIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] });
|
|
1934
|
-
const GripIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1935
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1936
|
-
cx: "12",
|
|
1937
|
-
cy: "5",
|
|
1938
|
-
r: "1"
|
|
1939
|
-
}),
|
|
1940
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1941
|
-
cx: "19",
|
|
1942
|
-
cy: "5",
|
|
1943
|
-
r: "1"
|
|
1944
|
-
}),
|
|
1945
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1946
|
-
cx: "5",
|
|
1947
|
-
cy: "5",
|
|
1948
|
-
r: "1"
|
|
1949
|
-
}),
|
|
1950
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1951
|
-
cx: "12",
|
|
1952
|
-
cy: "12",
|
|
1953
|
-
r: "1"
|
|
1954
|
-
}),
|
|
1955
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1956
|
-
cx: "19",
|
|
1957
|
-
cy: "12",
|
|
1958
|
-
r: "1"
|
|
1959
|
-
}),
|
|
1960
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1961
|
-
cx: "5",
|
|
1962
|
-
cy: "12",
|
|
1963
|
-
r: "1"
|
|
1964
|
-
}),
|
|
1965
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1966
|
-
cx: "12",
|
|
1967
|
-
cy: "19",
|
|
1968
|
-
r: "1"
|
|
1969
|
-
}),
|
|
1970
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1971
|
-
cx: "19",
|
|
1972
|
-
cy: "19",
|
|
1973
|
-
r: "1"
|
|
1974
|
-
}),
|
|
1975
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1976
|
-
cx: "5",
|
|
1977
|
-
cy: "19",
|
|
1978
|
-
r: "1"
|
|
1979
|
-
})
|
|
1980
|
-
] });
|
|
1981
|
-
const MaximizeIcon = ({ maximized }) => maximized ? /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1982
|
-
/* @__PURE__ */ jsx("path", { d: "m14 10 7-7" }),
|
|
1983
|
-
/* @__PURE__ */ jsx("path", { d: "M20 10h-6V4" }),
|
|
1984
|
-
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1985
|
-
/* @__PURE__ */ jsx("path", { d: "M4 14h6v6" })
|
|
1986
|
-
] }) : /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1987
|
-
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
1988
|
-
/* @__PURE__ */ jsx("path", { d: "m21 3-7 7" }),
|
|
1989
|
-
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1990
|
-
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" })
|
|
1991
|
-
] });
|
|
1992
|
-
const FloatIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1993
|
-
height: "14",
|
|
1994
|
-
rx: "2",
|
|
1995
|
-
width: "18",
|
|
1996
|
-
x: "3",
|
|
1997
|
-
y: "6"
|
|
1998
|
-
}), /* @__PURE__ */ jsx("path", { d: "M3 10h18" })] });
|
|
1999
|
-
const MinimizeIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
|
|
2000
|
-
const DockIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
2001
|
-
/* @__PURE__ */ jsx("path", { d: "M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2" }),
|
|
2002
|
-
/* @__PURE__ */ jsx("path", { d: "M11 8 7 12l4 4" }),
|
|
2003
|
-
/* @__PURE__ */ jsx("path", { d: "M7 12h10" })
|
|
2004
|
-
] });
|
|
2005
|
-
|
|
2006
|
-
//#endregion
|
|
2007
|
-
//#region src/components/tabset/tabset-tab-controls.tsx
|
|
2008
|
-
const RenameEditor = ({ defaultValue, onBlur, onKeyDown, ref: userRef, ...props }) => {
|
|
2009
|
-
const { tab } = useTab();
|
|
2010
|
-
const cancelRename = useTabset((state) => state.cancelRename);
|
|
2011
|
-
const commitRename = useTabset((state) => state.commitRename);
|
|
2012
|
-
const inputRef = useRef(null);
|
|
2013
|
-
const rename = useInlineRename({
|
|
2014
|
-
currentName: tab.name,
|
|
2015
|
-
inputRef,
|
|
2016
|
-
onCommit: (name) => {
|
|
2017
|
-
commitRename(tab.id, name);
|
|
2018
|
-
},
|
|
2019
|
-
onDone: cancelRename
|
|
2020
|
-
});
|
|
2021
|
-
const refCallback = useMemo(() => mergeRefs(inputRef, userRef), [userRef]);
|
|
2022
|
-
const handleKeyDown = (event) => {
|
|
2023
|
-
onKeyDown?.(event);
|
|
2024
|
-
rename.handleKeyDown(event);
|
|
2025
|
-
};
|
|
2026
|
-
const handleCommitOnBlur = (event) => {
|
|
2027
|
-
onBlur?.(event);
|
|
2028
|
-
rename.handleBlur(event);
|
|
2029
|
-
};
|
|
2030
|
-
return /* @__PURE__ */ jsx("input", {
|
|
2031
|
-
"aria-label": `Rename ${tab.name}`,
|
|
2032
|
-
...props,
|
|
2033
|
-
"data-dashfoo": "tab-rename",
|
|
2034
|
-
defaultValue: defaultValue ?? tab.name,
|
|
2035
|
-
onBlur: handleCommitOnBlur,
|
|
2036
|
-
onKeyDown: handleKeyDown,
|
|
2037
|
-
ref: refCallback,
|
|
2038
|
-
type: "text"
|
|
2039
|
-
});
|
|
2040
|
-
};
|
|
2041
|
-
const TabsetRenameInput = (props) => {
|
|
2042
|
-
const { tab } = useTab();
|
|
2043
|
-
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
2044
|
-
const registerRenameInput = useTabset((state) => state.registerRenameInput);
|
|
2045
|
-
useEffect(() => registerRenameInput(), [registerRenameInput]);
|
|
2046
|
-
if (!editing) return null;
|
|
2047
|
-
return /* @__PURE__ */ jsx(RenameEditor, { ...props });
|
|
2048
|
-
};
|
|
2049
|
-
const TabsetCloseButton = ({ children, onClick, ...props }) => {
|
|
2050
|
-
const { index, tab } = useTab();
|
|
2051
|
-
const closeTab = useTabset((state) => state.closeTab);
|
|
2052
|
-
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
2053
|
-
const tabsClosable = useTabset((state) => state.tabsClosable);
|
|
2054
|
-
const visualSelected = useTabset((state) => state.visualSelected);
|
|
2055
|
-
if (!(tabsClosable && tab.enableClose !== false) || editing) return null;
|
|
2056
|
-
const handleClick = (event) => {
|
|
2057
|
-
onClick?.(event);
|
|
2058
|
-
event.stopPropagation();
|
|
2059
|
-
closeTab(tab.id);
|
|
2060
|
-
};
|
|
2061
|
-
return /* @__PURE__ */ jsx("button", {
|
|
2062
|
-
"aria-label": `Close ${tab.name}`,
|
|
2063
|
-
title: `Close ${tab.name}`,
|
|
2064
|
-
...props,
|
|
2065
|
-
"data-dashfoo": "tab-close",
|
|
2066
|
-
onClick: handleClick,
|
|
2067
|
-
tabIndex: index === visualSelected ? 0 : -1,
|
|
2068
|
-
type: "button",
|
|
2069
|
-
children: children ?? /* @__PURE__ */ jsx(CloseIcon, {})
|
|
2070
|
-
});
|
|
2071
|
-
};
|
|
2072
|
-
|
|
2073
2094
|
//#endregion
|
|
2074
2095
|
//#region src/components/tabset/tabset-tablist.tsx
|
|
2075
2096
|
const stripStyle = {
|
|
@@ -2093,15 +2114,17 @@ const TabsetTablist = ({ onKeyDown, ref: userRef, style, ...props }) => {
|
|
|
2093
2114
|
const node = useTabset((state) => state.node);
|
|
2094
2115
|
const registerTablist = useTabset((state) => state.registerTablist);
|
|
2095
2116
|
const selectTab = useTabset((state) => state.selectTab);
|
|
2117
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
2096
2118
|
const handleKeyDown = (event) => {
|
|
2097
2119
|
onKeyDown?.(event);
|
|
2098
2120
|
if (event.defaultPrevented) return;
|
|
2099
2121
|
if (!(event.target instanceof HTMLElement) || !event.target.closest("[role=\"tab\"]")) return;
|
|
2100
2122
|
const count = node.children.length;
|
|
2101
2123
|
if (count === 0) return;
|
|
2124
|
+
const from = Math.max(visualSelected, 0);
|
|
2102
2125
|
const next = {
|
|
2103
|
-
ArrowLeft: (
|
|
2104
|
-
ArrowRight: (
|
|
2126
|
+
ArrowLeft: (from - 1 + count) % count,
|
|
2127
|
+
ArrowRight: (from + 1) % count,
|
|
2105
2128
|
End: count - 1,
|
|
2106
2129
|
Home: 0
|
|
2107
2130
|
}[event.key];
|
|
@@ -2142,14 +2165,32 @@ const TabsetToolbar = ({ style, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
|
2142
2165
|
...style
|
|
2143
2166
|
}
|
|
2144
2167
|
});
|
|
2168
|
+
/**
|
|
2169
|
+
* One answer per control, shared by the controls and by the layout deciding
|
|
2170
|
+
* whether to render a toolbar at all, so the two cannot disagree.
|
|
2171
|
+
*/
|
|
2172
|
+
const useTabsetChrome = () => {
|
|
2173
|
+
const draggableTabsets = useLayout((state) => state.draggableTabsets);
|
|
2174
|
+
const floatable = useLayout((state) => state.floatable);
|
|
2175
|
+
const isMaximized = useTabset((state) => state.isMaximized);
|
|
2176
|
+
const overflowCount = useTabset((state) => state.overflowItems.length);
|
|
2177
|
+
const showMaximize = useTabset((state) => state.showMaximize);
|
|
2178
|
+
const hasFloatLayer = useHasFloatLayer();
|
|
2179
|
+
if (floatable && !isMaximized && !hasFloatLayer) warnOnce("float-no-layer", "Tabset.FloatButton needs a <Layout.FloatLayer> around the layout (DashfooLayout adds one); the float control is hidden");
|
|
2180
|
+
return {
|
|
2181
|
+
float: floatable && !isMaximized && hasFloatLayer,
|
|
2182
|
+
grip: draggableTabsets && !isMaximized,
|
|
2183
|
+
maximize: showMaximize,
|
|
2184
|
+
overflow: overflowCount > 0
|
|
2185
|
+
};
|
|
2186
|
+
};
|
|
2145
2187
|
const TabsetGrip = ({ children, ref: userRef, ...props }) => {
|
|
2146
2188
|
const activeTabName = useTabset((state) => state.activeTab?.name ?? "");
|
|
2147
|
-
const isMaximized = useTabset((state) => state.isMaximized);
|
|
2148
2189
|
const node = useTabset((state) => state.node);
|
|
2149
|
-
const
|
|
2150
|
-
const { ref } = useTabsetDraggable(node.id,
|
|
2190
|
+
const { grip } = useTabsetChrome();
|
|
2191
|
+
const { ref } = useTabsetDraggable(node.id, !grip, activeTabName);
|
|
2151
2192
|
const refCallback = useMemo(() => mergeRefs(ref, userRef), [ref, userRef]);
|
|
2152
|
-
if (
|
|
2193
|
+
if (!grip) return null;
|
|
2153
2194
|
return /* @__PURE__ */ jsx("button", {
|
|
2154
2195
|
"aria-label": "Move tabset",
|
|
2155
2196
|
title: "Move tabset",
|
|
@@ -2162,9 +2203,9 @@ const TabsetGrip = ({ children, ref: userRef, ...props }) => {
|
|
|
2162
2203
|
};
|
|
2163
2204
|
const TabsetMaximizeButton = ({ children, onClick, ...props }) => {
|
|
2164
2205
|
const isMaximized = useTabset((state) => state.isMaximized);
|
|
2165
|
-
const showMaximize = useTabset((state) => state.showMaximize);
|
|
2166
2206
|
const toggleMaximize = useTabset((state) => state.toggleMaximize);
|
|
2167
|
-
|
|
2207
|
+
const { maximize } = useTabsetChrome();
|
|
2208
|
+
if (!maximize) return null;
|
|
2168
2209
|
const handleClick = (event) => {
|
|
2169
2210
|
onClick?.(event);
|
|
2170
2211
|
toggleMaximize();
|
|
@@ -2181,16 +2222,10 @@ const TabsetMaximizeButton = ({ children, onClick, ...props }) => {
|
|
|
2181
2222
|
});
|
|
2182
2223
|
};
|
|
2183
2224
|
const TabsetFloatButton = ({ children, onClick, ...props }) => {
|
|
2184
|
-
const floatable = useLayout((state) => state.floatable);
|
|
2185
2225
|
const dispatch = useLayout((state) => state.dispatch);
|
|
2186
|
-
const isMaximized = useTabset((state) => state.isMaximized);
|
|
2187
2226
|
const node = useTabset((state) => state.node);
|
|
2188
|
-
const
|
|
2189
|
-
if (!
|
|
2190
|
-
if (!hasFloatLayer) {
|
|
2191
|
-
warnOnce("float-no-layer", "Tabset.FloatButton needs a <Layout.FloatLayer> around the layout (DashfooLayout adds one); the float control is hidden");
|
|
2192
|
-
return null;
|
|
2193
|
-
}
|
|
2227
|
+
const { float } = useTabsetChrome();
|
|
2228
|
+
if (!float) return null;
|
|
2194
2229
|
const handleClick = (event) => {
|
|
2195
2230
|
onClick?.(event);
|
|
2196
2231
|
const geometry = measureFloatRect(event.currentTarget);
|
|
@@ -2215,15 +2250,12 @@ const TabsetFloatButton = ({ children, onClick, ...props }) => {
|
|
|
2215
2250
|
//#region src/components/tabset/tabset-view.tsx
|
|
2216
2251
|
const tabsetContent = /* @__PURE__ */ jsx(TabsetContent, {});
|
|
2217
2252
|
const DefaultTabsetLayout = () => {
|
|
2218
|
-
const draggableTabsets = useLayout((state) => state.draggableTabsets);
|
|
2219
|
-
const floatable = useLayout((state) => state.floatable);
|
|
2220
2253
|
const renderTabsetToolbar = useLayout((state) => state.renderTabsetToolbar);
|
|
2221
2254
|
const tabLocation = useLayout((state) => state.tabLocation);
|
|
2222
2255
|
const tabStripEnabled = useLayout((state) => state.tabStripEnabled);
|
|
2223
|
-
const isMaximized = useTabset((state) => state.isMaximized);
|
|
2224
2256
|
const node = useTabset((state) => state.node);
|
|
2225
|
-
const
|
|
2226
|
-
const showToolbar =
|
|
2257
|
+
const chrome = useTabsetChrome();
|
|
2258
|
+
const showToolbar = renderTabsetToolbar !== void 0 || Object.values(chrome).some(Boolean);
|
|
2227
2259
|
const strip = tabStripEnabled ? /* @__PURE__ */ jsxs(TabsetTabStrip, { children: [/* @__PURE__ */ jsx(TabsetTablist, { children: node.children.map((tab) => /* @__PURE__ */ jsxs(TabsetTab, {
|
|
2228
2260
|
tab,
|
|
2229
2261
|
children: [
|
|
@@ -2275,7 +2307,8 @@ const RowView = ({ node, renderTabset }) => {
|
|
|
2275
2307
|
const tabsetMinSize = useLayout((state) => state.tabsetMinSize);
|
|
2276
2308
|
const globalSnap = useLayout((state) => state.snap);
|
|
2277
2309
|
const effectiveSnap = node.snap ?? globalSnap;
|
|
2278
|
-
const
|
|
2310
|
+
const snapGrid = useMemo(() => resolveSnapGrid(effectiveSnap, node.children.length), [effectiveSnap, node.children.length]);
|
|
2311
|
+
const snapActive = snapEnabled(snapGrid);
|
|
2279
2312
|
const orientation = node.orientation === "row" ? "horizontal" : "vertical";
|
|
2280
2313
|
const total = node.children.reduce((sum, child) => sum + child.weight, 0);
|
|
2281
2314
|
const { beginBoundaryDrag, groupRef, onLayoutChange, onLayoutChanged, snappedBoundary } = useSnapResize({
|
|
@@ -2288,7 +2321,7 @@ const RowView = ({ node, renderTabset }) => {
|
|
|
2288
2321
|
});
|
|
2289
2322
|
},
|
|
2290
2323
|
resizableSplits,
|
|
2291
|
-
snap:
|
|
2324
|
+
snap: snapGrid
|
|
2292
2325
|
});
|
|
2293
2326
|
return /* @__PURE__ */ jsx(Group, {
|
|
2294
2327
|
"data-dashfoo": "row",
|
|
@@ -2589,8 +2622,8 @@ const Layout = {
|
|
|
2589
2622
|
//#endregion
|
|
2590
2623
|
//#region src/components/dashfoo-layout.tsx
|
|
2591
2624
|
const DEFAULT_PERSIST_DEBOUNCE_MS = 300;
|
|
2592
|
-
const resolvePersist = (persist
|
|
2593
|
-
if (persist === void 0
|
|
2625
|
+
const resolvePersist = (persist) => {
|
|
2626
|
+
if (persist === void 0) return null;
|
|
2594
2627
|
if (typeof persist === "string") return {
|
|
2595
2628
|
debounceMs: DEFAULT_PERSIST_DEBOUNCE_MS,
|
|
2596
2629
|
key: persist,
|
|
@@ -2605,7 +2638,8 @@ const resolvePersist = (persist, hasDefaultModel) => {
|
|
|
2605
2638
|
const warnedComponents = /* @__PURE__ */ new Set();
|
|
2606
2639
|
const DashfooLayout = forwardRef((props, ref) => {
|
|
2607
2640
|
const { closableTabs = true, components, defaultModel, draggableTabs = true, draggableTabsets = true, editable = true, factory, floatable = false, keepMounted = false, maximizable = true, model, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange, persist, renamableTabs = true, renderTabLabel, renderTabsetToolbar, resizableSplits = true, responsive, snap } = props;
|
|
2608
|
-
const
|
|
2641
|
+
const persistConfig = useMemo(() => resolvePersist(persist), [persist]);
|
|
2642
|
+
const persistence = usePersistence(persistConfig, defaultModel);
|
|
2609
2643
|
const handleModelChange = useCallback((next, action) => {
|
|
2610
2644
|
persistence.save(next);
|
|
2611
2645
|
onModelChange?.(next, action);
|
|
@@ -2696,14 +2730,25 @@ const DashfooLayout = forwardRef((props, ref) => {
|
|
|
2696
2730
|
}, [components, factory]);
|
|
2697
2731
|
const [containerRef, width] = useContainerWidth();
|
|
2698
2732
|
const isCompact = responsive !== void 0 && width <= responsive.maxWidth;
|
|
2699
|
-
|
|
2733
|
+
/** Everything the compact breakpoint changes, decided once. */
|
|
2734
|
+
const presentation = useMemo(() => isCompact ? {
|
|
2735
|
+
maximizable: false,
|
|
2736
|
+
model: stackModel(store.model, responsive?.orientation),
|
|
2737
|
+
restructurable: false
|
|
2738
|
+
} : {
|
|
2739
|
+
maximizable,
|
|
2740
|
+
model: store.model,
|
|
2741
|
+
restructurable: true
|
|
2742
|
+
}, [
|
|
2700
2743
|
isCompact,
|
|
2744
|
+
maximizable,
|
|
2701
2745
|
responsive?.orientation,
|
|
2702
2746
|
store.model
|
|
2703
2747
|
]);
|
|
2748
|
+
const view = presentation.model;
|
|
2704
2749
|
const maximized = view.maximizedTabsetId === void 0 ? void 0 : findTabset(view, view.maximizedTabsetId);
|
|
2705
2750
|
const tree = /* @__PURE__ */ jsx(Layout.FloatLayer, {
|
|
2706
|
-
floats:
|
|
2751
|
+
floats: view.floats,
|
|
2707
2752
|
children: /* @__PURE__ */ jsx(Layout.DragLayer, { children: maximized ? /* @__PURE__ */ jsx(Layout.Tabset, { node: maximized }) : /* @__PURE__ */ jsx(Layout.Rows, { node: view.layout }) })
|
|
2708
2753
|
});
|
|
2709
2754
|
return /* @__PURE__ */ jsx(Layout.Root, {
|
|
@@ -2714,14 +2759,14 @@ const DashfooLayout = forwardRef((props, ref) => {
|
|
|
2714
2759
|
editable,
|
|
2715
2760
|
floatable,
|
|
2716
2761
|
keepMounted,
|
|
2717
|
-
maximizable,
|
|
2762
|
+
maximizable: presentation.maximizable,
|
|
2718
2763
|
model: view,
|
|
2719
2764
|
renamableTabs,
|
|
2720
2765
|
renderTab,
|
|
2721
2766
|
renderTabLabel,
|
|
2722
2767
|
renderTabsetToolbar,
|
|
2723
2768
|
resizableSplits,
|
|
2724
|
-
restructurable:
|
|
2769
|
+
restructurable: presentation.restructurable,
|
|
2725
2770
|
rootRef: containerRef,
|
|
2726
2771
|
snap,
|
|
2727
2772
|
children: hasSharedManager ? tree : /* @__PURE__ */ jsx(DashfooDragProvider, { children: tree })
|