@dashfoo/react 0.6.3 → 0.7.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.d.ts +44 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +754 -624
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { canRedo, canUndo, dashfooMachine, dragDockMachine, findTabset, fromJSON, normalize,
|
|
3
|
+
import { canRedo, canUndo, dashfooMachine, decideSnap, dockLocationFor, dragDockMachine, findTabset, fromJSON, normalize, resolveDockTarget, 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
|
-
import { createStore, useStore } from "zustand";
|
|
7
|
-
import { CollisionPriority, CollisionType } from "@dnd-kit/abstract";
|
|
8
6
|
import { useActorRef, useSelector } from "@xstate/react";
|
|
7
|
+
import { CollisionPriority, CollisionType } from "@dnd-kit/abstract";
|
|
9
8
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { createStore, useStore } from "zustand";
|
|
10
10
|
import { Group, Panel as Panel$1, Separator } from "react-resizable-panels";
|
|
11
11
|
|
|
12
12
|
//#region src/lib/topmost-collision.ts
|
|
@@ -63,17 +63,15 @@ const createDragManager = () => new DragDropManager({
|
|
|
63
63
|
plugins: (defaults) => [...defaults.filter((plugin) => plugin !== Accessibility && plugin !== Feedback), Feedback.configure({ dropAnimation: null })],
|
|
64
64
|
sensors: () => [PointerSensor.configure({ preventActivation })]
|
|
65
65
|
});
|
|
66
|
+
const DragRootContext = createContext(null);
|
|
66
67
|
const DragContext = createContext(null);
|
|
67
68
|
const SharedDragManagerContext = createContext(null);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
+
*/
|
|
73
74
|
const sameDropIntent = (a, b) => a === b || a !== null && b !== null && a.index === b.index && a.location === b.location && a.targetId === b.targetId;
|
|
74
|
-
const sameDragSubject = (a, b) => a === b || a !== null && b !== null && a.id === b.id && a.kind === b.kind;
|
|
75
|
-
const nullSubjectStore = createDragSubjectStore();
|
|
76
|
-
const DragSubjectStoreContext = createContext(null);
|
|
77
75
|
const useDraggableHandle = () => {
|
|
78
76
|
const elementRef = useRef(null);
|
|
79
77
|
const draggableRef = useRef(null);
|
|
@@ -194,11 +192,20 @@ const useExternalTabSource = ({ createTab, disabled = false, label = "" }) => {
|
|
|
194
192
|
id: `external-${id}`
|
|
195
193
|
}, disabled, label);
|
|
196
194
|
};
|
|
195
|
+
/**
|
|
196
|
+
* Both drag readers are documented as working anywhere under the provider, which
|
|
197
|
+
* includes a custom overlay mounted beside the layout rather than inside a drag
|
|
198
|
+
* layer, so they read the root's actor. Outside a provider entirely there is no
|
|
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.
|
|
201
|
+
*/
|
|
197
202
|
const useDragSubject = () => {
|
|
198
|
-
|
|
203
|
+
const actorRef = useContext(DragRootContext)?.actorRef;
|
|
204
|
+
return useSelector(actorRef, (snapshot) => snapshot?.context.subject ?? null);
|
|
199
205
|
};
|
|
200
206
|
const useDropIntent = () => {
|
|
201
|
-
|
|
207
|
+
const actorRef = useContext(DragRootContext)?.actorRef;
|
|
208
|
+
return useSelector(actorRef, (snapshot) => snapshot?.context.intent ?? null, sameDropIntent);
|
|
202
209
|
};
|
|
203
210
|
|
|
204
211
|
//#endregion
|
|
@@ -269,7 +276,7 @@ const usePersistence = (config, defaultModel) => {
|
|
|
269
276
|
const loadedKey = useRef(config?.key);
|
|
270
277
|
useEffect(() => {
|
|
271
278
|
if (config !== null && config.key !== loadedKey.current) {
|
|
272
|
-
console.warn("[dashfoo] persist key changed for a mounted layout; the displayed layout still reflects the previous key
|
|
279
|
+
console.warn("[dashfoo] persist key changed for a mounted layout; the displayed layout still reflects the previous key; remount (key={persistKey}) to load the new key");
|
|
273
280
|
loadedKey.current = config.key;
|
|
274
281
|
}
|
|
275
282
|
}, [config]);
|
|
@@ -352,7 +359,7 @@ const useContainerWidth = () => {
|
|
|
352
359
|
observerRef.current?.disconnect();
|
|
353
360
|
if (!element) return;
|
|
354
361
|
const observer = new ResizeObserver((entries) => {
|
|
355
|
-
const entry = entries
|
|
362
|
+
const entry = entries.at(0);
|
|
356
363
|
if (entry) setWidth(entry.contentRect.width);
|
|
357
364
|
});
|
|
358
365
|
observer.observe(element);
|
|
@@ -393,8 +400,18 @@ const useResponsiveModel = ({ breakpoints }) => {
|
|
|
393
400
|
}, [active, containerRef]);
|
|
394
401
|
};
|
|
395
402
|
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/lib/warn-once.ts
|
|
405
|
+
const warned = /* @__PURE__ */ new Set();
|
|
406
|
+
const warnOnce = (key, message) => {
|
|
407
|
+
if (warned.has(key)) return;
|
|
408
|
+
warned.add(key);
|
|
409
|
+
console.warn(`[dashfoo] ${message}`);
|
|
410
|
+
};
|
|
411
|
+
|
|
396
412
|
//#endregion
|
|
397
413
|
//#region src/hooks/store.ts
|
|
414
|
+
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";
|
|
398
415
|
const useDashfooStore = (options) => {
|
|
399
416
|
const { defaultModel, model: controlledModel, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange } = options;
|
|
400
417
|
const initialModel = controlledModel ?? defaultModel;
|
|
@@ -421,32 +438,22 @@ const useDashfooStore = (options) => {
|
|
|
421
438
|
const dispatch = useCallback((action) => {
|
|
422
439
|
const resolved = onAction ? onAction(action) : action;
|
|
423
440
|
if (!resolved) return;
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
});
|
|
431
|
-
return {
|
|
432
|
-
after: actorRef.getSnapshot().context.history.present,
|
|
433
|
-
before
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
after: reducer(controlledModel, resolved),
|
|
438
|
-
before: normalize(controlledModel)
|
|
439
|
-
};
|
|
440
|
-
})();
|
|
441
|
-
notify(transition.before, transition.after, resolved);
|
|
441
|
+
const before = actorRef.getSnapshot().context.history.present;
|
|
442
|
+
actorRef.send({
|
|
443
|
+
action: resolved,
|
|
444
|
+
type: "DISPATCH"
|
|
445
|
+
});
|
|
446
|
+
notify(before, actorRef.getSnapshot().context.history.present, resolved);
|
|
442
447
|
}, [
|
|
443
448
|
actorRef,
|
|
444
|
-
controlledModel,
|
|
445
449
|
notify,
|
|
446
450
|
onAction
|
|
447
451
|
]);
|
|
448
452
|
const undo = useCallback(() => {
|
|
449
|
-
if (controlledModel !== void 0)
|
|
453
|
+
if (controlledModel !== void 0) {
|
|
454
|
+
warnOnce("controlled-undo", CONTROLLED_HISTORY_MESSAGE);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
450
457
|
const before = actorRef.getSnapshot().context.history.present;
|
|
451
458
|
actorRef.send({ type: "UNDO" });
|
|
452
459
|
const after = actorRef.getSnapshot().context.history.present;
|
|
@@ -457,7 +464,10 @@ const useDashfooStore = (options) => {
|
|
|
457
464
|
notify
|
|
458
465
|
]);
|
|
459
466
|
const redo = useCallback(() => {
|
|
460
|
-
if (controlledModel !== void 0)
|
|
467
|
+
if (controlledModel !== void 0) {
|
|
468
|
+
warnOnce("controlled-redo", CONTROLLED_HISTORY_MESSAGE);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
461
471
|
const before = actorRef.getSnapshot().context.history.present;
|
|
462
472
|
actorRef.send({ type: "REDO" });
|
|
463
473
|
const after = actorRef.getSnapshot().context.history.present;
|
|
@@ -474,8 +484,8 @@ const useDashfooStore = (options) => {
|
|
|
474
484
|
});
|
|
475
485
|
}, [actorRef]);
|
|
476
486
|
return useMemo(() => ({
|
|
477
|
-
canRedo: () => canRedo(actorRef.getSnapshot().context.history),
|
|
478
|
-
canUndo: () => canUndo(actorRef.getSnapshot().context.history),
|
|
487
|
+
canRedo: () => controlledModel === void 0 && canRedo(actorRef.getSnapshot().context.history),
|
|
488
|
+
canUndo: () => controlledModel === void 0 && canUndo(actorRef.getSnapshot().context.history),
|
|
479
489
|
dispatch,
|
|
480
490
|
model,
|
|
481
491
|
redo,
|
|
@@ -483,6 +493,7 @@ const useDashfooStore = (options) => {
|
|
|
483
493
|
undo
|
|
484
494
|
}), [
|
|
485
495
|
actorRef,
|
|
496
|
+
controlledModel,
|
|
486
497
|
dispatch,
|
|
487
498
|
model,
|
|
488
499
|
redo,
|
|
@@ -491,6 +502,49 @@ const useDashfooStore = (options) => {
|
|
|
491
502
|
]);
|
|
492
503
|
};
|
|
493
504
|
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/lib/drag-subject.ts
|
|
507
|
+
const isTabFactory = (value) => typeof value === "function";
|
|
508
|
+
/**
|
|
509
|
+
* Reads what a dnd-kit source represents. It lives beside the manager wiring
|
|
510
|
+
* rather than in the layer adapter because `dragstart` fires once per gesture on
|
|
511
|
+
* the shared manager, above any one layer.
|
|
512
|
+
*/
|
|
513
|
+
const subjectFor = (source) => {
|
|
514
|
+
const data = source.data;
|
|
515
|
+
if (data?.type === "tabset") return {
|
|
516
|
+
id: String(data.tabsetId),
|
|
517
|
+
kind: "tabset"
|
|
518
|
+
};
|
|
519
|
+
if (data?.type === "external") {
|
|
520
|
+
if (!isTabFactory(data.createTab)) {
|
|
521
|
+
console.warn("[dashfoo] external drag source is missing its createTab function");
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
let candidate;
|
|
525
|
+
try {
|
|
526
|
+
candidate = data.createTab();
|
|
527
|
+
} catch (error) {
|
|
528
|
+
console.warn("[dashfoo] external drag source createTab threw", error);
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const parsed = tabNodeSchema.safeParse(candidate);
|
|
532
|
+
if (!parsed.success) {
|
|
533
|
+
console.warn("[dashfoo] external drag source returned an invalid tab", parsed.error);
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
id: String(source.id),
|
|
538
|
+
kind: "external",
|
|
539
|
+
tab: parsed.data
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
return {
|
|
543
|
+
id: String(source.id),
|
|
544
|
+
kind: "tab"
|
|
545
|
+
};
|
|
546
|
+
};
|
|
547
|
+
|
|
494
548
|
//#endregion
|
|
495
549
|
//#region src/lib/tab-insertion.ts
|
|
496
550
|
const pointInRect = (point, rect) => point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
@@ -591,7 +645,9 @@ const DragPreviewOverlay = ({ manager }) => {
|
|
|
591
645
|
y: PREVIEW_OFFSET.y + (rect ? point.y - rect.top : 0)
|
|
592
646
|
});
|
|
593
647
|
});
|
|
594
|
-
const offEnd = manager.monitor.addEventListener("dragend", () =>
|
|
648
|
+
const offEnd = manager.monitor.addEventListener("dragend", () => {
|
|
649
|
+
setChip(null);
|
|
650
|
+
});
|
|
595
651
|
return () => {
|
|
596
652
|
offStart();
|
|
597
653
|
offEnd();
|
|
@@ -621,12 +677,107 @@ const DragPreviewOverlay = ({ manager }) => {
|
|
|
621
677
|
//#region src/components/drag-root.tsx
|
|
622
678
|
const DashfooDragProvider = ({ children }) => {
|
|
623
679
|
const [manager] = useState(createDragManager);
|
|
624
|
-
useInsertionEffect(() => () =>
|
|
625
|
-
|
|
680
|
+
useInsertionEffect(() => () => {
|
|
681
|
+
manager.destroy();
|
|
682
|
+
}, [manager]);
|
|
683
|
+
const actorRef = useActorRef(dragDockMachine);
|
|
684
|
+
const [scopes] = useState(() => /* @__PURE__ */ new Map());
|
|
685
|
+
const registerScope = useCallback((scope) => {
|
|
686
|
+
scopes.set(scope.layerId, scope);
|
|
687
|
+
return () => {
|
|
688
|
+
scopes.delete(scope.layerId);
|
|
689
|
+
};
|
|
690
|
+
}, [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
|
+
useEffect(() => {
|
|
697
|
+
const scopedTarget = () => {
|
|
698
|
+
const target = manager.dragOperation.target;
|
|
699
|
+
const data = target?.data;
|
|
700
|
+
const scope = data?.layerId === void 0 ? void 0 : scopes.get(data.layerId);
|
|
701
|
+
const element = target?.element;
|
|
702
|
+
if (!scope || data?.tabsetId === void 0 || !(element instanceof HTMLElement)) return null;
|
|
703
|
+
return {
|
|
704
|
+
element,
|
|
705
|
+
scope,
|
|
706
|
+
tabsetId: data.tabsetId
|
|
707
|
+
};
|
|
708
|
+
};
|
|
709
|
+
let intentLayerId = null;
|
|
710
|
+
const syncIntent = () => {
|
|
711
|
+
const operation = manager.dragOperation;
|
|
712
|
+
const found = scopedTarget();
|
|
713
|
+
const draggedId = operation.source ? String(operation.source.id) : void 0;
|
|
714
|
+
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
|
+
actorRef.send({
|
|
717
|
+
intent,
|
|
718
|
+
type: "OVER"
|
|
719
|
+
});
|
|
720
|
+
};
|
|
721
|
+
const handleStart = (event) => {
|
|
722
|
+
const source = event.operation.source;
|
|
723
|
+
if (!source) return;
|
|
724
|
+
const subject = subjectFor(source);
|
|
725
|
+
if (!subject) {
|
|
726
|
+
manager.actions.stop({ canceled: true });
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
intentLayerId = null;
|
|
730
|
+
actorRef.send({
|
|
731
|
+
subject,
|
|
732
|
+
type: "START"
|
|
733
|
+
});
|
|
734
|
+
};
|
|
735
|
+
const handleMove = () => {
|
|
736
|
+
syncIntent();
|
|
737
|
+
};
|
|
738
|
+
const handleCollision = () => {
|
|
739
|
+
syncIntent();
|
|
740
|
+
};
|
|
741
|
+
const handleEnd = (event) => {
|
|
742
|
+
if (event.canceled) {
|
|
743
|
+
actorRef.send({ type: "CANCEL" });
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
syncIntent();
|
|
747
|
+
actorRef.send({ type: "DROP" });
|
|
748
|
+
};
|
|
749
|
+
const commitSubscription = actorRef.on("COMMIT", (emitted) => {
|
|
750
|
+
(intentLayerId === null ? void 0 : scopes.get(intentLayerId))?.commit(emitted.action);
|
|
751
|
+
});
|
|
752
|
+
const offStart = manager.monitor.addEventListener("dragstart", handleStart);
|
|
753
|
+
const offMove = manager.monitor.addEventListener("dragmove", handleMove);
|
|
754
|
+
const offCollision = manager.monitor.addEventListener("collision", handleCollision);
|
|
755
|
+
const offEnd = manager.monitor.addEventListener("dragend", handleEnd);
|
|
756
|
+
return () => {
|
|
757
|
+
commitSubscription.unsubscribe();
|
|
758
|
+
offStart();
|
|
759
|
+
offMove();
|
|
760
|
+
offCollision();
|
|
761
|
+
offEnd();
|
|
762
|
+
};
|
|
763
|
+
}, [
|
|
764
|
+
actorRef,
|
|
765
|
+
manager,
|
|
766
|
+
scopes
|
|
767
|
+
]);
|
|
768
|
+
const rootValue = useMemo(() => ({
|
|
769
|
+
actorRef,
|
|
770
|
+
manager,
|
|
771
|
+
registerScope
|
|
772
|
+
}), [
|
|
773
|
+
actorRef,
|
|
774
|
+
manager,
|
|
775
|
+
registerScope
|
|
776
|
+
]);
|
|
626
777
|
return /* @__PURE__ */ jsx(SharedDragManagerContext.Provider, {
|
|
627
778
|
value: manager,
|
|
628
|
-
children: /* @__PURE__ */ jsxs(
|
|
629
|
-
value:
|
|
779
|
+
children: /* @__PURE__ */ jsxs(DragRootContext.Provider, {
|
|
780
|
+
value: rootValue,
|
|
630
781
|
children: [children, /* @__PURE__ */ jsx(DragPreviewOverlay, { manager })]
|
|
631
782
|
})
|
|
632
783
|
});
|
|
@@ -642,17 +793,9 @@ const useLayout = (selector) => {
|
|
|
642
793
|
return useStore(store, selector);
|
|
643
794
|
};
|
|
644
795
|
|
|
645
|
-
//#endregion
|
|
646
|
-
//#region src/lib/warn-once.ts
|
|
647
|
-
const warned = /* @__PURE__ */ new Set();
|
|
648
|
-
const warnOnce = (key, message) => {
|
|
649
|
-
if (warned.has(key)) return;
|
|
650
|
-
warned.add(key);
|
|
651
|
-
console.warn(`[dashfoo] ${message}`);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
796
|
//#endregion
|
|
655
797
|
//#region src/components/drag-adapter.tsx
|
|
798
|
+
const MISSING_ROOT = "[dashfoo] DashfooDragProvider unmounted while its layout is still mounted; keep the provider above the layout or remount the layout";
|
|
656
799
|
const tabRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab\"]")].flatMap((tab) => tab.dataset.tabId === excludeId ? [] : [tab.getBoundingClientRect()]);
|
|
657
800
|
const intentForTabset = (id, element, point, draggedId) => {
|
|
658
801
|
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
@@ -661,8 +804,7 @@ const intentForTabset = (id, element, point, draggedId) => {
|
|
|
661
804
|
location: "center",
|
|
662
805
|
targetId: id
|
|
663
806
|
};
|
|
664
|
-
const
|
|
665
|
-
const location = target.kind === "tab" ? "center" : `split-${target.edge}`;
|
|
807
|
+
const location = dockLocationFor(resolveDockTarget(point, element.getBoundingClientRect()));
|
|
666
808
|
if (location === "center" && strip) return {
|
|
667
809
|
index: tabRects(strip, draggedId).length,
|
|
668
810
|
location,
|
|
@@ -673,266 +815,81 @@ const intentForTabset = (id, element, point, draggedId) => {
|
|
|
673
815
|
targetId: id
|
|
674
816
|
};
|
|
675
817
|
};
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (data?.type === "external") {
|
|
684
|
-
if (!isTabFactory(data.createTab)) {
|
|
685
|
-
console.warn("[dashfoo] external drag source is missing its createTab function");
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
let candidate;
|
|
689
|
-
try {
|
|
690
|
-
candidate = data.createTab();
|
|
691
|
-
} catch (error) {
|
|
692
|
-
console.warn("[dashfoo] external drag source createTab threw", error);
|
|
693
|
-
return null;
|
|
694
|
-
}
|
|
695
|
-
const parsed = tabNodeSchema.safeParse(candidate);
|
|
696
|
-
if (!parsed.success) {
|
|
697
|
-
console.warn("[dashfoo] external drag source returned an invalid tab", parsed.error);
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
return {
|
|
701
|
-
id: String(source.id),
|
|
702
|
-
kind: "external",
|
|
703
|
-
tab: parsed.data
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
return {
|
|
707
|
-
id: String(source.id),
|
|
708
|
-
kind: "tab"
|
|
709
|
-
};
|
|
710
|
-
};
|
|
711
|
-
const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
712
|
-
const actorRef = useActorRef(dragDockMachine);
|
|
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
|
+
*/
|
|
823
|
+
const DragScope = ({ children, onCommit, splitDock }) => {
|
|
824
|
+
const root = useContext(DragRootContext);
|
|
713
825
|
const layoutStore = useContext(LayoutStoreContext);
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
useInsertionEffect(() => () => ownManager?.destroy(), [ownManager]);
|
|
717
|
-
const manager = sharedManager ?? ownManager;
|
|
718
|
-
if (manager === null) throw new Error("[dashfoo] DashfooDragProvider unmounted while its layout is still mounted; keep the provider above the layout or remount the layout");
|
|
719
|
-
const inheritedStore = useContext(DragSubjectStoreContext);
|
|
720
|
-
const [subjectStore] = useState(() => inheritedStore ?? createDragSubjectStore());
|
|
721
|
-
const ownerId = useId();
|
|
722
|
-
useEffect(() => {
|
|
723
|
-
const subscription = actorRef.subscribe((snapshot) => {
|
|
724
|
-
const { intent, subject } = snapshot.context;
|
|
725
|
-
const state = subjectStore.getState();
|
|
726
|
-
if (intent !== null) {
|
|
727
|
-
if (!sameDropIntent(state.intent, intent) || state.intentOwner !== ownerId) subjectStore.setState({
|
|
728
|
-
intent,
|
|
729
|
-
intentOwner: ownerId
|
|
730
|
-
});
|
|
731
|
-
} else if (state.intent !== null && state.intentOwner === ownerId) subjectStore.setState({
|
|
732
|
-
intent: null,
|
|
733
|
-
intentOwner: null
|
|
734
|
-
});
|
|
735
|
-
if (!sameDragSubject(subjectStore.getState().subject, subject)) subjectStore.setState({ subject });
|
|
736
|
-
});
|
|
737
|
-
return () => {
|
|
738
|
-
subscription.unsubscribe();
|
|
739
|
-
};
|
|
740
|
-
}, [
|
|
741
|
-
actorRef,
|
|
742
|
-
ownerId,
|
|
743
|
-
subjectStore
|
|
744
|
-
]);
|
|
745
|
-
const onCommitRef = useRef(onCommit);
|
|
746
|
-
const splitDockRef = useRef(splitDock);
|
|
747
|
-
useEffect(() => {
|
|
748
|
-
onCommitRef.current = onCommit;
|
|
749
|
-
splitDockRef.current = splitDock;
|
|
750
|
-
});
|
|
826
|
+
const layerId = useId();
|
|
827
|
+
const manager = root?.manager;
|
|
751
828
|
const getTabsetElement = useCallback((id) => {
|
|
752
|
-
const element = manager
|
|
829
|
+
const element = manager?.registry.droppables.get(`${layerId}:${id}`)?.element;
|
|
753
830
|
return element instanceof HTMLElement ? element : void 0;
|
|
754
|
-
}, [
|
|
755
|
-
|
|
756
|
-
|
|
831
|
+
}, [layerId, manager]);
|
|
832
|
+
const scope = useMemo(() => ({
|
|
833
|
+
commit: (action) => {
|
|
757
834
|
if (layoutStore?.getState().editable === false) return;
|
|
758
|
-
const commit =
|
|
835
|
+
const commit = onCommit ?? layoutStore?.getState().dispatch;
|
|
759
836
|
if (!commit) {
|
|
760
837
|
warnOnce("drag-layer-no-commit", "Layout.DragLayer has neither an onCommit prop nor a <Layout.Root> above it; drops are ignored");
|
|
761
838
|
return;
|
|
762
839
|
}
|
|
763
|
-
commit(
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
};
|
|
768
|
-
}, [actorRef, layoutStore]);
|
|
769
|
-
useEffect(() => {
|
|
770
|
-
const resolveIntent = (targetId, element, point, draggedId) => {
|
|
840
|
+
commit(action);
|
|
841
|
+
},
|
|
842
|
+
layerId,
|
|
843
|
+
resolveIntent: (targetId, element, point, draggedId) => {
|
|
771
844
|
if (layoutStore?.getState().editable === false) return null;
|
|
772
845
|
if (!shouldAllowDrop(draggedId, targetId, [...element.querySelectorAll("[data-dashfoo=\"tab\"]")].map((tab) => tab.dataset.tabId ?? ""))) return null;
|
|
773
846
|
const intent = intentForTabset(targetId, element, point, draggedId);
|
|
774
|
-
if (!(
|
|
847
|
+
if (!(splitDock ?? layoutStore?.getState().splitDock ?? true) && splitEdge(intent.location) !== void 0) return {
|
|
775
848
|
location: "center",
|
|
776
849
|
targetId
|
|
777
850
|
};
|
|
778
851
|
return intent;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (!source) return;
|
|
783
|
-
const subject = subjectFor(source);
|
|
784
|
-
if (!subject) {
|
|
785
|
-
manager.actions.stop({ canceled: true });
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
actorRef.send({
|
|
789
|
-
subject,
|
|
790
|
-
type: "START"
|
|
791
|
-
});
|
|
792
|
-
};
|
|
793
|
-
const syncIntent = () => {
|
|
794
|
-
const operation = manager.dragOperation;
|
|
795
|
-
const point = operation.position.current;
|
|
796
|
-
const target = operation.target;
|
|
797
|
-
const data = target?.data;
|
|
798
|
-
const tabsetId = data?.layerId === ownerId ? data.tabsetId : void 0;
|
|
799
|
-
const element = tabsetId === void 0 ? void 0 : target?.element;
|
|
800
|
-
const draggedId = operation.source ? String(operation.source.id) : void 0;
|
|
801
|
-
actorRef.send({
|
|
802
|
-
intent: tabsetId !== void 0 && element instanceof HTMLElement ? resolveIntent(tabsetId, element, point, draggedId) : null,
|
|
803
|
-
type: "OVER"
|
|
804
|
-
});
|
|
805
|
-
};
|
|
806
|
-
const handleMove = () => syncIntent();
|
|
807
|
-
const handleCollision = () => syncIntent();
|
|
808
|
-
const handleEnd = (event) => {
|
|
809
|
-
if (event.canceled) {
|
|
810
|
-
actorRef.send({ type: "CANCEL" });
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
syncIntent();
|
|
814
|
-
actorRef.send({ type: "DROP" });
|
|
815
|
-
};
|
|
816
|
-
const offStart = manager.monitor.addEventListener("dragstart", handleStart);
|
|
817
|
-
const offMove = manager.monitor.addEventListener("dragmove", handleMove);
|
|
818
|
-
const offCollision = manager.monitor.addEventListener("collision", handleCollision);
|
|
819
|
-
const offEnd = manager.monitor.addEventListener("dragend", handleEnd);
|
|
820
|
-
return () => {
|
|
821
|
-
offStart();
|
|
822
|
-
offMove();
|
|
823
|
-
offCollision();
|
|
824
|
-
offEnd();
|
|
825
|
-
};
|
|
826
|
-
}, [
|
|
827
|
-
actorRef,
|
|
852
|
+
}
|
|
853
|
+
}), [
|
|
854
|
+
layerId,
|
|
828
855
|
layoutStore,
|
|
829
|
-
|
|
830
|
-
|
|
856
|
+
onCommit,
|
|
857
|
+
splitDock
|
|
831
858
|
]);
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
859
|
+
const registerScope = root?.registerScope;
|
|
860
|
+
useEffect(() => registerScope?.(scope), [registerScope, scope]);
|
|
861
|
+
const contextValue = useMemo(() => root === null ? null : {
|
|
862
|
+
actorRef: root.actorRef,
|
|
863
|
+
layerId,
|
|
864
|
+
manager: root.manager
|
|
865
|
+
}, [layerId, root]);
|
|
866
|
+
if (contextValue === null) throw new Error(MISSING_ROOT);
|
|
867
|
+
return /* @__PURE__ */ jsxs(DragContext.Provider, {
|
|
837
868
|
value: contextValue,
|
|
838
|
-
children: /* @__PURE__ */
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
/* @__PURE__ */ jsx(DockIndicator, {
|
|
843
|
-
actorRef,
|
|
844
|
-
getTabsetElement
|
|
845
|
-
}),
|
|
846
|
-
ownManager === null ? null : /* @__PURE__ */ jsx(DragPreviewOverlay, { manager: ownManager })
|
|
847
|
-
]
|
|
848
|
-
})
|
|
869
|
+
children: [children, /* @__PURE__ */ jsx(DockIndicator, {
|
|
870
|
+
actorRef: contextValue.actorRef,
|
|
871
|
+
getTabsetElement
|
|
872
|
+
})]
|
|
849
873
|
});
|
|
850
874
|
};
|
|
875
|
+
/**
|
|
876
|
+
* Mounts its own root when there is none above it, so a hand-composed
|
|
877
|
+
* `Layout.DragLayer` still works standalone. The choice is made on the first
|
|
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.
|
|
880
|
+
*/
|
|
881
|
+
const DragProvider = (props) => {
|
|
882
|
+
const hasRoot = useContext(DragRootContext) !== null;
|
|
883
|
+
const [ownsRoot] = useState(() => !hasRoot);
|
|
884
|
+
const layer = /* @__PURE__ */ jsx(DragScope, { ...props });
|
|
885
|
+
return ownsRoot ? /* @__PURE__ */ jsx(DashfooDragProvider, { children: layer }) : layer;
|
|
886
|
+
};
|
|
851
887
|
|
|
852
888
|
//#endregion
|
|
853
889
|
//#region src/components/float-context.ts
|
|
854
890
|
const FloatLayerContext = createContext(false);
|
|
855
891
|
const useHasFloatLayer = () => useContext(FloatLayerContext);
|
|
856
892
|
|
|
857
|
-
//#endregion
|
|
858
|
-
//#region src/hooks/use-inline-rename.ts
|
|
859
|
-
const useInlineRename = ({ currentName, inputRef, onCommit, onDone }) => {
|
|
860
|
-
const done = useRef(false);
|
|
861
|
-
useEffect(() => {
|
|
862
|
-
inputRef.current?.focus();
|
|
863
|
-
inputRef.current?.select();
|
|
864
|
-
}, [inputRef]);
|
|
865
|
-
const commit = (value) => {
|
|
866
|
-
const trimmed = value.trim();
|
|
867
|
-
if (trimmed && trimmed !== currentName) onCommit(trimmed);
|
|
868
|
-
onDone();
|
|
869
|
-
};
|
|
870
|
-
const handleKeyDown = (event) => {
|
|
871
|
-
if (event.key === "Enter") {
|
|
872
|
-
done.current = true;
|
|
873
|
-
commit(event.currentTarget.value);
|
|
874
|
-
} else if (event.key === "Escape") {
|
|
875
|
-
done.current = true;
|
|
876
|
-
onDone();
|
|
877
|
-
}
|
|
878
|
-
};
|
|
879
|
-
const handleBlur = (event) => {
|
|
880
|
-
if (done.current) return;
|
|
881
|
-
done.current = true;
|
|
882
|
-
commit(event.currentTarget.value);
|
|
883
|
-
};
|
|
884
|
-
return {
|
|
885
|
-
handleBlur,
|
|
886
|
-
handleKeyDown
|
|
887
|
-
};
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
//#endregion
|
|
891
|
-
//#region src/lib/float-geometry.ts
|
|
892
|
-
const MIN_WIDTH = 200;
|
|
893
|
-
const MIN_HEIGHT = 120;
|
|
894
|
-
const DEFAULT_WIDTH = 480;
|
|
895
|
-
const DEFAULT_HEIGHT = 340;
|
|
896
|
-
const LIFT_OFFSET = 16;
|
|
897
|
-
const clampToBounds = (rect, bounds, size = rect) => ({
|
|
898
|
-
height: rect.height,
|
|
899
|
-
left: Math.max(0, Math.min(rect.left, Math.max(0, bounds.width - size.width))),
|
|
900
|
-
top: Math.max(0, Math.min(rect.top, Math.max(0, bounds.height - size.height))),
|
|
901
|
-
width: rect.width
|
|
902
|
-
});
|
|
903
|
-
const measureFloatRect = (element) => {
|
|
904
|
-
const rect = (element.closest("[data-dashfoo=\"tabset\"]") ?? element).getBoundingClientRect();
|
|
905
|
-
const seeded = {
|
|
906
|
-
height: Math.max(MIN_HEIGHT, Math.min(Math.round(rect.height), DEFAULT_HEIGHT)),
|
|
907
|
-
left: Math.round(rect.left) + LIFT_OFFSET,
|
|
908
|
-
top: Math.round(rect.top) + LIFT_OFFSET,
|
|
909
|
-
width: Math.max(MIN_WIDTH, Math.min(Math.round(rect.width), DEFAULT_WIDTH))
|
|
910
|
-
};
|
|
911
|
-
return clampToBounds(seeded, {
|
|
912
|
-
height: window.innerHeight,
|
|
913
|
-
width: window.innerWidth
|
|
914
|
-
});
|
|
915
|
-
};
|
|
916
|
-
const resizeRect = (start, edges, dx, dy) => {
|
|
917
|
-
let { height, left, top, width } = start;
|
|
918
|
-
if (edges.x === 1) width = Math.max(MIN_WIDTH, start.width + dx);
|
|
919
|
-
else if (edges.x === -1) {
|
|
920
|
-
width = Math.max(MIN_WIDTH, start.width - dx);
|
|
921
|
-
left = Math.max(0, start.left + (start.width - width));
|
|
922
|
-
}
|
|
923
|
-
if (edges.y === 1) height = Math.max(MIN_HEIGHT, start.height + dy);
|
|
924
|
-
else if (edges.y === -1) {
|
|
925
|
-
height = Math.max(MIN_HEIGHT, start.height - dy);
|
|
926
|
-
top = Math.max(0, start.top + (start.height - height));
|
|
927
|
-
}
|
|
928
|
-
return {
|
|
929
|
-
height,
|
|
930
|
-
left,
|
|
931
|
-
top,
|
|
932
|
-
width
|
|
933
|
-
};
|
|
934
|
-
};
|
|
935
|
-
|
|
936
893
|
//#endregion
|
|
937
894
|
//#region src/components/float-resize-handles.ts
|
|
938
895
|
const EDGE_THICKNESS = 12;
|
|
@@ -1052,6 +1009,202 @@ const RESIZE_HANDLES = [
|
|
|
1052
1009
|
];
|
|
1053
1010
|
const EDGE_BY_KEY = new Map(RESIZE_HANDLES.map((h) => [h.key, h.edges]));
|
|
1054
1011
|
|
|
1012
|
+
//#endregion
|
|
1013
|
+
//#region src/lib/float-geometry.ts
|
|
1014
|
+
const MIN_WIDTH = 200;
|
|
1015
|
+
const MIN_HEIGHT = 120;
|
|
1016
|
+
const DEFAULT_WIDTH = 480;
|
|
1017
|
+
const DEFAULT_HEIGHT = 340;
|
|
1018
|
+
const LIFT_OFFSET = 16;
|
|
1019
|
+
const clampToBounds = (rect, bounds, size = rect) => ({
|
|
1020
|
+
height: rect.height,
|
|
1021
|
+
left: Math.max(0, Math.min(rect.left, Math.max(0, bounds.width - size.width))),
|
|
1022
|
+
top: Math.max(0, Math.min(rect.top, Math.max(0, bounds.height - size.height))),
|
|
1023
|
+
width: rect.width
|
|
1024
|
+
});
|
|
1025
|
+
const measureFloatRect = (element) => {
|
|
1026
|
+
const rect = (element.closest("[data-dashfoo=\"tabset\"]") ?? element).getBoundingClientRect();
|
|
1027
|
+
const seeded = {
|
|
1028
|
+
height: Math.max(MIN_HEIGHT, Math.min(Math.round(rect.height), DEFAULT_HEIGHT)),
|
|
1029
|
+
left: Math.round(rect.left) + LIFT_OFFSET,
|
|
1030
|
+
top: Math.round(rect.top) + LIFT_OFFSET,
|
|
1031
|
+
width: Math.max(MIN_WIDTH, Math.min(Math.round(rect.width), DEFAULT_WIDTH))
|
|
1032
|
+
};
|
|
1033
|
+
return clampToBounds(seeded, {
|
|
1034
|
+
height: window.innerHeight,
|
|
1035
|
+
width: window.innerWidth
|
|
1036
|
+
});
|
|
1037
|
+
};
|
|
1038
|
+
const resizeRect = (start, edges, dx, dy) => {
|
|
1039
|
+
let { height, left, top, width } = start;
|
|
1040
|
+
if (edges.x === 1) width = Math.max(MIN_WIDTH, start.width + dx);
|
|
1041
|
+
else if (edges.x === -1) {
|
|
1042
|
+
width = Math.max(MIN_WIDTH, start.width - dx);
|
|
1043
|
+
left = Math.max(0, start.left + (start.width - width));
|
|
1044
|
+
}
|
|
1045
|
+
if (edges.y === 1) height = Math.max(MIN_HEIGHT, start.height + dy);
|
|
1046
|
+
else if (edges.y === -1) {
|
|
1047
|
+
height = Math.max(MIN_HEIGHT, start.height - dy);
|
|
1048
|
+
top = Math.max(0, start.top + (start.height - height));
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
height,
|
|
1052
|
+
left,
|
|
1053
|
+
top,
|
|
1054
|
+
width
|
|
1055
|
+
};
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
//#endregion
|
|
1059
|
+
//#region src/hooks/use-float-gesture.ts
|
|
1060
|
+
const CHIP_SIZE = {
|
|
1061
|
+
height: 34,
|
|
1062
|
+
width: 168
|
|
1063
|
+
};
|
|
1064
|
+
const TAP_SLOP = 4;
|
|
1065
|
+
/**
|
|
1066
|
+
* Moving and resizing writes straight to the panel's inline style and only
|
|
1067
|
+
* dispatches on pointer-up: routing every pointermove through the model would
|
|
1068
|
+
* re-render the whole layout tree behind the float.
|
|
1069
|
+
*/
|
|
1070
|
+
const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) => {
|
|
1071
|
+
const panelRef = useRef(null);
|
|
1072
|
+
const gestureRef = useRef(null);
|
|
1073
|
+
const latestRef = useRef(geometry);
|
|
1074
|
+
const movedRef = useRef(false);
|
|
1075
|
+
const setPanel = (element) => {
|
|
1076
|
+
panelRef.current = element;
|
|
1077
|
+
};
|
|
1078
|
+
const handlePointerDown = (event) => {
|
|
1079
|
+
const panel = panelRef.current;
|
|
1080
|
+
if (!panel || !editable) return;
|
|
1081
|
+
const prior = gestureRef.current;
|
|
1082
|
+
if (prior && panel.hasPointerCapture?.(prior.pointerId)) panel.releasePointerCapture(prior.pointerId);
|
|
1083
|
+
movedRef.current = false;
|
|
1084
|
+
const edgeKey = event.currentTarget.dataset.edge;
|
|
1085
|
+
const parent = panel.offsetParent instanceof HTMLElement ? panel.offsetParent : null;
|
|
1086
|
+
const gesture = {
|
|
1087
|
+
bounds: {
|
|
1088
|
+
height: parent?.clientHeight ?? 0,
|
|
1089
|
+
width: parent?.clientWidth ?? 0
|
|
1090
|
+
},
|
|
1091
|
+
edges: edgeKey === void 0 ? null : EDGE_BY_KEY.get(edgeKey) ?? null,
|
|
1092
|
+
pointerId: event.pointerId,
|
|
1093
|
+
start: geometry,
|
|
1094
|
+
startX: event.clientX,
|
|
1095
|
+
startY: event.clientY
|
|
1096
|
+
};
|
|
1097
|
+
gestureRef.current = gesture;
|
|
1098
|
+
latestRef.current = geometry;
|
|
1099
|
+
if (gesture.edges) panel.setPointerCapture(event.pointerId);
|
|
1100
|
+
};
|
|
1101
|
+
const handlePointerUp = (event) => {
|
|
1102
|
+
const gesture = gestureRef.current;
|
|
1103
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
1104
|
+
gestureRef.current = null;
|
|
1105
|
+
const panel = panelRef.current;
|
|
1106
|
+
if (panel?.hasPointerCapture?.(event.pointerId) === true) panel.releasePointerCapture(event.pointerId);
|
|
1107
|
+
if (!movedRef.current) {
|
|
1108
|
+
onTap();
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
onCommit(latestRef.current);
|
|
1112
|
+
};
|
|
1113
|
+
const handlePointerMove = (event) => {
|
|
1114
|
+
const gesture = gestureRef.current;
|
|
1115
|
+
const panel = panelRef.current;
|
|
1116
|
+
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
1117
|
+
if (event.buttons === 0) {
|
|
1118
|
+
handlePointerUp(event);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const dx = event.clientX - gesture.startX;
|
|
1122
|
+
const dy = event.clientY - gesture.startY;
|
|
1123
|
+
if (!movedRef.current) {
|
|
1124
|
+
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
1125
|
+
movedRef.current = true;
|
|
1126
|
+
if (!gesture.edges) panel.setPointerCapture(gesture.pointerId);
|
|
1127
|
+
}
|
|
1128
|
+
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
1129
|
+
...gesture.start,
|
|
1130
|
+
left: gesture.start.left + dx,
|
|
1131
|
+
top: gesture.start.top + dy
|
|
1132
|
+
}, gesture.bounds, minimized ? CHIP_SIZE : void 0);
|
|
1133
|
+
latestRef.current = next;
|
|
1134
|
+
panel.style.left = `${next.left}px`;
|
|
1135
|
+
panel.style.top = `${next.top}px`;
|
|
1136
|
+
if (!minimized) {
|
|
1137
|
+
panel.style.width = `${next.width}px`;
|
|
1138
|
+
panel.style.height = `${next.height}px`;
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const handlePointerCancel = (event) => {
|
|
1142
|
+
const gesture = gestureRef.current;
|
|
1143
|
+
const panel = panelRef.current;
|
|
1144
|
+
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
1145
|
+
gestureRef.current = null;
|
|
1146
|
+
if (panel.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
1147
|
+
if (!movedRef.current) return;
|
|
1148
|
+
panel.style.left = `${gesture.start.left}px`;
|
|
1149
|
+
panel.style.top = `${gesture.start.top}px`;
|
|
1150
|
+
if (!minimized) {
|
|
1151
|
+
panel.style.width = `${gesture.start.width}px`;
|
|
1152
|
+
panel.style.height = `${gesture.start.height}px`;
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
return {
|
|
1156
|
+
handlers: {
|
|
1157
|
+
onPointerCancel: handlePointerCancel,
|
|
1158
|
+
onPointerDown: handlePointerDown,
|
|
1159
|
+
onPointerMove: handlePointerMove,
|
|
1160
|
+
onPointerUp: handlePointerUp
|
|
1161
|
+
},
|
|
1162
|
+
ref: setPanel,
|
|
1163
|
+
style: minimized ? {
|
|
1164
|
+
left: geometry.left,
|
|
1165
|
+
top: geometry.top
|
|
1166
|
+
} : {
|
|
1167
|
+
height: geometry.height,
|
|
1168
|
+
left: geometry.left,
|
|
1169
|
+
top: geometry.top,
|
|
1170
|
+
width: geometry.width
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
//#endregion
|
|
1176
|
+
//#region src/hooks/use-inline-rename.ts
|
|
1177
|
+
const useInlineRename = ({ currentName, inputRef, onCommit, onDone }) => {
|
|
1178
|
+
const done = useRef(false);
|
|
1179
|
+
useEffect(() => {
|
|
1180
|
+
inputRef.current?.focus();
|
|
1181
|
+
inputRef.current?.select();
|
|
1182
|
+
}, [inputRef]);
|
|
1183
|
+
const commit = (value) => {
|
|
1184
|
+
const trimmed = value.trim();
|
|
1185
|
+
if (trimmed && trimmed !== currentName) onCommit(trimmed);
|
|
1186
|
+
onDone();
|
|
1187
|
+
};
|
|
1188
|
+
const handleKeyDown = (event) => {
|
|
1189
|
+
if (event.key === "Enter") {
|
|
1190
|
+
done.current = true;
|
|
1191
|
+
commit(event.currentTarget.value);
|
|
1192
|
+
} else if (event.key === "Escape") {
|
|
1193
|
+
done.current = true;
|
|
1194
|
+
onDone();
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
const handleBlur = (event) => {
|
|
1198
|
+
if (done.current) return;
|
|
1199
|
+
done.current = true;
|
|
1200
|
+
commit(event.currentTarget.value);
|
|
1201
|
+
};
|
|
1202
|
+
return {
|
|
1203
|
+
handleBlur,
|
|
1204
|
+
handleKeyDown
|
|
1205
|
+
};
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1055
1208
|
//#endregion
|
|
1056
1209
|
//#region src/components/layout-root.tsx
|
|
1057
1210
|
const DEFAULT_TABSET_MIN_SIZE = 320;
|
|
@@ -1061,15 +1214,15 @@ const rootStyle = {
|
|
|
1061
1214
|
position: "relative",
|
|
1062
1215
|
width: "100%"
|
|
1063
1216
|
};
|
|
1064
|
-
const LayoutRoot = ({ children, closableTabs = true, dispatch, draggableTabs = true, draggableTabsets = true, editable = true, floatable = false, keepMounted = false, maximizable = true, model, renamableTabs = true, renderTab, renderTabLabel, renderTabsetToolbar, resizableSplits = true, rootRef, snap, style, ...props }) => {
|
|
1217
|
+
const LayoutRoot = ({ children, closableTabs = true, dispatch, draggableTabs = true, draggableTabsets = true, editable = true, floatable = false, keepMounted = false, maximizable = true, model, renamableTabs = true, renderTab, renderTabLabel, renderTabsetToolbar, resizableSplits = true, restructurable = true, rootRef, snap, style, ...props }) => {
|
|
1065
1218
|
const global = model.global;
|
|
1066
1219
|
const snapshot = {
|
|
1067
1220
|
closableTabs: editable && closableTabs && global.tabEnableClose !== false,
|
|
1068
1221
|
dispatch,
|
|
1069
|
-
draggableTabs: editable && draggableTabs && global.tabEnableDrag !== false,
|
|
1070
|
-
draggableTabsets: editable && draggableTabsets,
|
|
1222
|
+
draggableTabs: editable && restructurable && draggableTabs && global.tabEnableDrag !== false,
|
|
1223
|
+
draggableTabsets: editable && restructurable && draggableTabsets,
|
|
1071
1224
|
editable,
|
|
1072
|
-
floatable: editable && floatable,
|
|
1225
|
+
floatable: editable && restructurable && floatable,
|
|
1073
1226
|
keepMounted,
|
|
1074
1227
|
maximizable: maximizable && global.tabSetEnableMaximize !== false,
|
|
1075
1228
|
maximizedTabsetId: model.maximizedTabsetId,
|
|
@@ -1077,7 +1230,7 @@ const LayoutRoot = ({ children, closableTabs = true, dispatch, draggableTabs = t
|
|
|
1077
1230
|
renderTab,
|
|
1078
1231
|
renderTabLabel,
|
|
1079
1232
|
renderTabsetToolbar,
|
|
1080
|
-
resizableSplits: editable && resizableSplits && global.enableSplitResize !== false,
|
|
1233
|
+
resizableSplits: editable && restructurable && resizableSplits && global.enableSplitResize !== false,
|
|
1081
1234
|
snap: snap ?? global.snap ?? null,
|
|
1082
1235
|
splitDock: global.enableSplitDock !== false,
|
|
1083
1236
|
tabLocation: global.tabLocation ?? "top",
|
|
@@ -1099,15 +1252,142 @@ const LayoutRoot = ({ children, closableTabs = true, dispatch, draggableTabs = t
|
|
|
1099
1252
|
return /* @__PURE__ */ jsx(LayoutStoreContext.Provider, {
|
|
1100
1253
|
value: store,
|
|
1101
1254
|
children: /* @__PURE__ */ jsx("div", {
|
|
1102
|
-
...props,
|
|
1255
|
+
...props,
|
|
1256
|
+
"data-dashfoo": "layout",
|
|
1257
|
+
ref: rootRef,
|
|
1258
|
+
style: layoutStyle,
|
|
1259
|
+
children
|
|
1260
|
+
})
|
|
1261
|
+
});
|
|
1262
|
+
};
|
|
1263
|
+
/**
|
|
1264
|
+
* A nested subtree (a float) renders through the same components as the main
|
|
1265
|
+
* layout, minus a few capabilities. It republishes the parent store with the
|
|
1266
|
+
* overrides applied instead of re-deriving every flag from a synthetic model,
|
|
1267
|
+
* so anything the parent changes keeps flowing into the subtree.
|
|
1268
|
+
*/
|
|
1269
|
+
const LayoutOverrides = ({ children, overrides }) => {
|
|
1270
|
+
const snapshot = {
|
|
1271
|
+
...useLayout((state) => state),
|
|
1272
|
+
...overrides
|
|
1273
|
+
};
|
|
1274
|
+
const [store] = useState(() => createLayoutStore(snapshot));
|
|
1275
|
+
useLayoutEffect(() => {
|
|
1276
|
+
store.setState(snapshot);
|
|
1277
|
+
});
|
|
1278
|
+
return /* @__PURE__ */ jsx(LayoutStoreContext.Provider, {
|
|
1279
|
+
value: store,
|
|
1280
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1103
1281
|
"data-dashfoo": "layout",
|
|
1104
|
-
|
|
1105
|
-
style: layoutStyle,
|
|
1282
|
+
style: rootStyle,
|
|
1106
1283
|
children
|
|
1107
1284
|
})
|
|
1108
1285
|
});
|
|
1109
1286
|
};
|
|
1110
1287
|
|
|
1288
|
+
//#endregion
|
|
1289
|
+
//#region src/hooks/use-snap-resize.ts
|
|
1290
|
+
const WEIGHT_EPSILON = .01;
|
|
1291
|
+
const IDLE = { kind: "idle" };
|
|
1292
|
+
const boundaryOf = (state) => state.kind === "dragging" || state.kind === "snapped" ? state.boundary : null;
|
|
1293
|
+
const snappedBoundaryOf = (state) => state.kind === "snapped" ? state.boundary : null;
|
|
1294
|
+
const layoutFromWeights = (children) => {
|
|
1295
|
+
const total = children.reduce((sum, child) => sum + child.weight, 0);
|
|
1296
|
+
return children.reduce((layout, child) => {
|
|
1297
|
+
layout[child.id] = child.weight / total * 100;
|
|
1298
|
+
return layout;
|
|
1299
|
+
}, {});
|
|
1300
|
+
};
|
|
1301
|
+
const orderedFromLayout = (layout, children) => children.map((child) => layout[child.id] ?? 0);
|
|
1302
|
+
const layoutFromOrdered = (sizes, children) => children.reduce((layout, child, index) => {
|
|
1303
|
+
layout[child.id] = sizes[index] ?? 0;
|
|
1304
|
+
return layout;
|
|
1305
|
+
}, {});
|
|
1306
|
+
const layoutsMatch = (current, expected) => {
|
|
1307
|
+
const currentIds = Object.keys(current);
|
|
1308
|
+
const expectedIds = Object.keys(expected);
|
|
1309
|
+
if (currentIds.length !== expectedIds.length) return false;
|
|
1310
|
+
return expectedIds.every((id) => Math.abs((current[id] ?? NaN) - expected[id]) <= WEIGHT_EPSILON);
|
|
1311
|
+
};
|
|
1312
|
+
const useSnapResize = ({ children, onWeightsSettled, resizableSplits, snap }) => {
|
|
1313
|
+
const groupRef = useRef(null);
|
|
1314
|
+
const state = useRef(IDLE);
|
|
1315
|
+
const [snappedBoundary, setSnappedBoundary] = useState(null);
|
|
1316
|
+
const transition = useCallback((next) => {
|
|
1317
|
+
state.current = next;
|
|
1318
|
+
setSnappedBoundary(snappedBoundaryOf(next));
|
|
1319
|
+
}, []);
|
|
1320
|
+
const desiredLayout = useMemo(() => layoutFromWeights(children), [children]);
|
|
1321
|
+
useLayoutEffect(() => {
|
|
1322
|
+
const group = groupRef.current;
|
|
1323
|
+
if (!group) return;
|
|
1324
|
+
const current = group.getLayout();
|
|
1325
|
+
if (!(Object.keys(current).length === Object.keys(desiredLayout).length && Object.keys(desiredLayout).every((id) => id in current)) || layoutsMatch(current, desiredLayout)) return;
|
|
1326
|
+
transition({ kind: "syncing" });
|
|
1327
|
+
group.setLayout(desiredLayout);
|
|
1328
|
+
const frame = requestAnimationFrame(() => {
|
|
1329
|
+
transition(IDLE);
|
|
1330
|
+
});
|
|
1331
|
+
return () => {
|
|
1332
|
+
cancelAnimationFrame(frame);
|
|
1333
|
+
transition(IDLE);
|
|
1334
|
+
};
|
|
1335
|
+
}, [desiredLayout, transition]);
|
|
1336
|
+
const onLayoutChange = (layout) => {
|
|
1337
|
+
if (state.current.kind === "syncing" || !resizableSplits) return;
|
|
1338
|
+
const boundary = boundaryOf(state.current);
|
|
1339
|
+
if (boundary === null) return;
|
|
1340
|
+
const decision = decideSnap(orderedFromLayout(layout, children), boundary, snap);
|
|
1341
|
+
if (decision.kind === "inactive") return;
|
|
1342
|
+
if (decision.kind === "clear") {
|
|
1343
|
+
transition({
|
|
1344
|
+
boundary,
|
|
1345
|
+
kind: "dragging"
|
|
1346
|
+
});
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
transition({
|
|
1350
|
+
boundary,
|
|
1351
|
+
kind: "snapped"
|
|
1352
|
+
});
|
|
1353
|
+
const group = groupRef.current;
|
|
1354
|
+
const target = layoutFromOrdered(decision.sizes, children);
|
|
1355
|
+
if (!group || layoutsMatch(layout, target)) return;
|
|
1356
|
+
transition({ kind: "syncing" });
|
|
1357
|
+
group.setLayout(target);
|
|
1358
|
+
transition({
|
|
1359
|
+
boundary,
|
|
1360
|
+
kind: "snapped"
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1363
|
+
const onLayoutChanged = (layout, meta) => {
|
|
1364
|
+
if (!meta.isUserInteraction) return;
|
|
1365
|
+
if (state.current.kind === "syncing") return;
|
|
1366
|
+
const boundary = boundaryOf(state.current);
|
|
1367
|
+
transition(IDLE);
|
|
1368
|
+
if (!resizableSplits) return;
|
|
1369
|
+
const weights = settleSnap(children.map((child) => layout[child.id] ?? child.weight), boundary, snap);
|
|
1370
|
+
if (!weights.some((weight, index) => {
|
|
1371
|
+
const child = children[index];
|
|
1372
|
+
return child !== void 0 && Math.abs(weight - child.weight) > WEIGHT_EPSILON;
|
|
1373
|
+
})) return;
|
|
1374
|
+
onWeightsSettled(weights);
|
|
1375
|
+
};
|
|
1376
|
+
const beginBoundaryDrag = (boundary) => {
|
|
1377
|
+
transition({
|
|
1378
|
+
boundary,
|
|
1379
|
+
kind: "dragging"
|
|
1380
|
+
});
|
|
1381
|
+
};
|
|
1382
|
+
return {
|
|
1383
|
+
beginBoundaryDrag,
|
|
1384
|
+
groupRef,
|
|
1385
|
+
onLayoutChange,
|
|
1386
|
+
onLayoutChanged,
|
|
1387
|
+
snappedBoundary
|
|
1388
|
+
};
|
|
1389
|
+
};
|
|
1390
|
+
|
|
1111
1391
|
//#endregion
|
|
1112
1392
|
//#region src/lib/tab-ids.ts
|
|
1113
1393
|
const tabDomId = (tabsetId, tabId) => `dashfoo-tab-${tabsetId}-${tabId}`;
|
|
@@ -1118,7 +1398,9 @@ const panelDomId = (tabsetId) => `dashfoo-panel-${tabsetId}`;
|
|
|
1118
1398
|
const tabButtonIn = (scope, tabsetId, tabId) => scope?.querySelector(`#${CSS.escape(tabDomId(tabsetId, tabId))}`) ?? null;
|
|
1119
1399
|
const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
1120
1400
|
...initial,
|
|
1121
|
-
cancelRename: () =>
|
|
1401
|
+
cancelRename: () => {
|
|
1402
|
+
set({ editingTabId: null });
|
|
1403
|
+
},
|
|
1122
1404
|
closeTab: (tabId) => {
|
|
1123
1405
|
set({ restoreFocus: true });
|
|
1124
1406
|
get().dispatch({
|
|
@@ -1137,7 +1419,9 @@ const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
|
1137
1419
|
overflowItems: [],
|
|
1138
1420
|
registerRenameInput: () => {
|
|
1139
1421
|
set((state) => ({ renameInputCount: state.renameInputCount + 1 }));
|
|
1140
|
-
return () =>
|
|
1422
|
+
return () => {
|
|
1423
|
+
set((state) => ({ renameInputCount: state.renameInputCount - 1 }));
|
|
1424
|
+
};
|
|
1141
1425
|
},
|
|
1142
1426
|
registerTablist: (element) => {
|
|
1143
1427
|
if (element) {
|
|
@@ -1149,11 +1433,13 @@ const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
|
1149
1433
|
set({ tablistElement: null });
|
|
1150
1434
|
},
|
|
1151
1435
|
renameInputCount: 0,
|
|
1152
|
-
renameTab: (tabId, name) =>
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1436
|
+
renameTab: (tabId, name) => {
|
|
1437
|
+
get().dispatch({
|
|
1438
|
+
name,
|
|
1439
|
+
tabId,
|
|
1440
|
+
type: "renameTab"
|
|
1441
|
+
});
|
|
1442
|
+
},
|
|
1157
1443
|
restoreFocus: false,
|
|
1158
1444
|
selectOverflowTab: (tabId) => {
|
|
1159
1445
|
const { dispatch, node, tablistElement } = get();
|
|
@@ -1176,7 +1462,7 @@ const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
|
1176
1462
|
tabsetId: node.id,
|
|
1177
1463
|
type: "selectTab"
|
|
1178
1464
|
});
|
|
1179
|
-
if (options?.focus) {
|
|
1465
|
+
if (options?.focus === true) {
|
|
1180
1466
|
const tabId = node.children[index]?.id;
|
|
1181
1467
|
if (tabId) tabButtonIn(tablistElement, node.id, tabId)?.focus();
|
|
1182
1468
|
}
|
|
@@ -1308,7 +1594,7 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1308
1594
|
useEffect(() => {
|
|
1309
1595
|
if (!open) return;
|
|
1310
1596
|
const handlePointerDown = (event) => {
|
|
1311
|
-
if (event.target instanceof Node &&
|
|
1597
|
+
if (event.target instanceof Node && rootRef.current?.contains(event.target) !== true) setOpen(false);
|
|
1312
1598
|
};
|
|
1313
1599
|
document.addEventListener("pointerdown", handlePointerDown);
|
|
1314
1600
|
return () => {
|
|
@@ -1361,7 +1647,7 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1361
1647
|
}
|
|
1362
1648
|
};
|
|
1363
1649
|
const handleFocusOut = (event) => {
|
|
1364
|
-
if (event.relatedTarget instanceof Node && rootRef.current?.contains(event.relatedTarget)) return;
|
|
1650
|
+
if (event.relatedTarget instanceof Node && rootRef.current?.contains(event.relatedTarget) === true) return;
|
|
1365
1651
|
setOpen(false);
|
|
1366
1652
|
};
|
|
1367
1653
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -1387,7 +1673,9 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1387
1673
|
tabIndex: -1,
|
|
1388
1674
|
children: items.map((item, index) => /* @__PURE__ */ jsx("button", {
|
|
1389
1675
|
"data-dashfoo": "tab-overflow-item",
|
|
1390
|
-
onClick: () =>
|
|
1676
|
+
onClick: () => {
|
|
1677
|
+
handleSelect(item.id);
|
|
1678
|
+
},
|
|
1391
1679
|
ref: (node) => {
|
|
1392
1680
|
itemRefs.current[index] = node;
|
|
1393
1681
|
},
|
|
@@ -1418,7 +1706,9 @@ const overflowingIds = (tablist) => {
|
|
|
1418
1706
|
const box = tablist.getBoundingClientRect();
|
|
1419
1707
|
return [...tablist.querySelectorAll("[data-dashfoo=\"tab\"]")].flatMap((tab) => {
|
|
1420
1708
|
const rect = tab.getBoundingClientRect();
|
|
1421
|
-
|
|
1709
|
+
const clipped = rect.right > box.right + 1 || rect.left < box.left - 1;
|
|
1710
|
+
const tabId = tab.dataset.tabId;
|
|
1711
|
+
return clipped && tabId !== void 0 && tabId !== "" ? [tabId] : [];
|
|
1422
1712
|
});
|
|
1423
1713
|
};
|
|
1424
1714
|
const useTabOverflow = (tablist, signature) => {
|
|
@@ -1723,7 +2013,9 @@ const RenameEditor = ({ defaultValue, onBlur, onKeyDown, ref: userRef, ...props
|
|
|
1723
2013
|
const rename = useInlineRename({
|
|
1724
2014
|
currentName: tab.name,
|
|
1725
2015
|
inputRef,
|
|
1726
|
-
onCommit: (name) =>
|
|
2016
|
+
onCommit: (name) => {
|
|
2017
|
+
commitRename(tab.id, name);
|
|
2018
|
+
},
|
|
1727
2019
|
onDone: cancelRename
|
|
1728
2020
|
});
|
|
1729
2021
|
const refCallback = useMemo(() => mergeRefs(inputRef, userRef), [userRef]);
|
|
@@ -1963,22 +2255,6 @@ const groupStyle = {
|
|
|
1963
2255
|
minHeight: 0,
|
|
1964
2256
|
minWidth: 0
|
|
1965
2257
|
};
|
|
1966
|
-
const WEIGHT_EPSILON = .01;
|
|
1967
|
-
const layoutFromWeights = (children, total) => children.reduce((layout, child) => {
|
|
1968
|
-
layout[child.id] = (child.weight ?? 1) / total * 100;
|
|
1969
|
-
return layout;
|
|
1970
|
-
}, {});
|
|
1971
|
-
const orderedFromLayout = (layout, children) => children.map((child) => layout[child.id] ?? 0);
|
|
1972
|
-
const layoutFromOrdered = (sizes, children) => children.reduce((layout, child, index) => {
|
|
1973
|
-
layout[child.id] = sizes[index] ?? 0;
|
|
1974
|
-
return layout;
|
|
1975
|
-
}, {});
|
|
1976
|
-
const layoutsMatch = (current, expected) => {
|
|
1977
|
-
const currentIds = Object.keys(current);
|
|
1978
|
-
const expectedIds = Object.keys(expected);
|
|
1979
|
-
if (currentIds.length !== expectedIds.length) return false;
|
|
1980
|
-
return expectedIds.every((id) => Math.abs((current[id] ?? NaN) - expected[id]) <= WEIGHT_EPSILON);
|
|
1981
|
-
};
|
|
1982
2258
|
const descendantMinSize = (child, axis, tabsetMinSize) => {
|
|
1983
2259
|
const explicit = dimensionToPixels(child.min);
|
|
1984
2260
|
if (explicit !== void 0) return explicit;
|
|
@@ -2001,89 +2277,30 @@ const RowView = ({ node, renderTabset }) => {
|
|
|
2001
2277
|
const effectiveSnap = node.snap ?? globalSnap;
|
|
2002
2278
|
const snapActive = snapEnabled(effectiveSnap);
|
|
2003
2279
|
const orientation = node.orientation === "row" ? "horizontal" : "vertical";
|
|
2004
|
-
const total = node.children.reduce((sum, child) => sum +
|
|
2005
|
-
const
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
}
|
|
2018
|
-
};
|
|
2019
|
-
const setSnapTransition = (active) => {
|
|
2020
|
-
const element = groupElement.current;
|
|
2021
|
-
if (!element) return;
|
|
2022
|
-
if (active) element.dataset.dashfooSnapping = "true";
|
|
2023
|
-
else delete element.dataset.dashfooSnapping;
|
|
2024
|
-
};
|
|
2025
|
-
useLayoutEffect(() => {
|
|
2026
|
-
const group = groupRef.current;
|
|
2027
|
-
if (!group) return;
|
|
2028
|
-
const current = group.getLayout();
|
|
2029
|
-
if (!(Object.keys(current).length === Object.keys(desiredLayout).length && Object.keys(desiredLayout).every((id) => id in current)) || layoutsMatch(current, desiredLayout)) return;
|
|
2030
|
-
syncing.current = true;
|
|
2031
|
-
group.setLayout(desiredLayout);
|
|
2032
|
-
const frame = requestAnimationFrame(() => {
|
|
2033
|
-
syncing.current = false;
|
|
2034
|
-
});
|
|
2035
|
-
return () => {
|
|
2036
|
-
cancelAnimationFrame(frame);
|
|
2037
|
-
syncing.current = false;
|
|
2038
|
-
};
|
|
2039
|
-
}, [desiredLayout]);
|
|
2040
|
-
const handleLayoutChange = (layout) => {
|
|
2041
|
-
if (snapping.current || syncing.current || !resizableSplits) return;
|
|
2042
|
-
if (effectiveSnap === null || !snapActive) return;
|
|
2043
|
-
const boundary = activeBoundary.current;
|
|
2044
|
-
if (boundary === null) return;
|
|
2045
|
-
const { sizes, snapped } = snapSizes(orderedFromLayout(layout, node.children), boundary, effectiveSnap);
|
|
2046
|
-
setHighlight(snapped ? boundary : null);
|
|
2047
|
-
if (!snapped) {
|
|
2048
|
-
setSnapTransition(false);
|
|
2049
|
-
return;
|
|
2050
|
-
}
|
|
2051
|
-
setSnapTransition(true);
|
|
2052
|
-
const group = groupRef.current;
|
|
2053
|
-
const target = layoutFromOrdered(sizes, node.children);
|
|
2054
|
-
if (!group || layoutsMatch(layout, target)) return;
|
|
2055
|
-
snapping.current = true;
|
|
2056
|
-
group.setLayout(target);
|
|
2057
|
-
snapping.current = false;
|
|
2058
|
-
};
|
|
2059
|
-
const handleLayoutChanged = (layout, meta) => {
|
|
2060
|
-
if (!meta.isUserInteraction) return;
|
|
2061
|
-
if (syncing.current) return;
|
|
2062
|
-
const boundary = activeBoundary.current;
|
|
2063
|
-
activeBoundary.current = null;
|
|
2064
|
-
setHighlight(null);
|
|
2065
|
-
setSnapTransition(false);
|
|
2066
|
-
if (!resizableSplits) return;
|
|
2067
|
-
let weights = node.children.map((child) => layout[child.id] ?? child.weight ?? 1);
|
|
2068
|
-
if (boundary !== null && effectiveSnap !== null && snapActive) weights = snapSizes(weights, boundary, effectiveSnap).sizes;
|
|
2069
|
-
if (!weights.some((weight, index) => Math.abs(weight - (node.children[index]?.weight ?? 1)) > WEIGHT_EPSILON)) return;
|
|
2070
|
-
dispatch({
|
|
2071
|
-
rowId: node.id,
|
|
2072
|
-
type: "adjustSplit",
|
|
2073
|
-
weights
|
|
2074
|
-
});
|
|
2075
|
-
};
|
|
2280
|
+
const total = node.children.reduce((sum, child) => sum + child.weight, 0);
|
|
2281
|
+
const { beginBoundaryDrag, groupRef, onLayoutChange, onLayoutChanged, snappedBoundary } = useSnapResize({
|
|
2282
|
+
children: node.children,
|
|
2283
|
+
onWeightsSettled: (weights) => {
|
|
2284
|
+
dispatch({
|
|
2285
|
+
rowId: node.id,
|
|
2286
|
+
type: "adjustSplit",
|
|
2287
|
+
weights
|
|
2288
|
+
});
|
|
2289
|
+
},
|
|
2290
|
+
resizableSplits,
|
|
2291
|
+
snap: effectiveSnap
|
|
2292
|
+
});
|
|
2076
2293
|
return /* @__PURE__ */ jsx(Group, {
|
|
2077
2294
|
"data-dashfoo": "row",
|
|
2295
|
+
"data-dashfoo-snapping": snappedBoundary === null ? void 0 : "true",
|
|
2078
2296
|
disabled: !resizableSplits,
|
|
2079
|
-
elementRef: groupElement,
|
|
2080
2297
|
groupRef,
|
|
2081
|
-
onLayoutChange: snapActive ?
|
|
2082
|
-
onLayoutChanged
|
|
2298
|
+
onLayoutChange: snapActive ? onLayoutChange : void 0,
|
|
2299
|
+
onLayoutChanged,
|
|
2083
2300
|
orientation,
|
|
2084
2301
|
style: groupStyle,
|
|
2085
2302
|
children: node.children.map((child, index) => {
|
|
2086
|
-
const percent =
|
|
2303
|
+
const percent = child.weight / total * 100;
|
|
2087
2304
|
let min = child.min ? dimensionToSize(child.min) : void 0;
|
|
2088
2305
|
if (min === void 0) {
|
|
2089
2306
|
const minimum = descendantMinSize(child, node.orientation, tabsetMinSize);
|
|
@@ -2095,7 +2312,7 @@ const RowView = ({ node, renderTabset }) => {
|
|
|
2095
2312
|
"data-dashfoo-snapped": snappedBoundary === index - 1 ? "true" : void 0,
|
|
2096
2313
|
disabled: !resizableSplits,
|
|
2097
2314
|
onPointerDown: snapActive && resizableSplits ? () => {
|
|
2098
|
-
|
|
2315
|
+
beginBoundaryDrag(index - 1);
|
|
2099
2316
|
} : void 0
|
|
2100
2317
|
}) : null, /* @__PURE__ */ jsx(Panel$1, {
|
|
2101
2318
|
defaultSize: `${percent}%`,
|
|
@@ -2139,21 +2356,24 @@ const bodyStyle = {
|
|
|
2139
2356
|
minHeight: 0,
|
|
2140
2357
|
position: "relative"
|
|
2141
2358
|
};
|
|
2142
|
-
const
|
|
2143
|
-
|
|
2144
|
-
|
|
2359
|
+
const FLOAT_OVERRIDES = {
|
|
2360
|
+
draggableTabsets: false,
|
|
2361
|
+
floatable: false,
|
|
2362
|
+
maximizable: false,
|
|
2363
|
+
maximizedTabsetId: void 0
|
|
2145
2364
|
};
|
|
2146
|
-
const TAP_SLOP = 4;
|
|
2147
2365
|
const FloatTitleEditor = ({ dispatch, node, onDone }) => {
|
|
2148
2366
|
const inputRef = useRef(null);
|
|
2149
2367
|
const { handleBlur, handleKeyDown } = useInlineRename({
|
|
2150
2368
|
currentName: floatTitle(node),
|
|
2151
2369
|
inputRef,
|
|
2152
|
-
onCommit: (name) =>
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2370
|
+
onCommit: (name) => {
|
|
2371
|
+
dispatch({
|
|
2372
|
+
floatId: node.id,
|
|
2373
|
+
name,
|
|
2374
|
+
type: "renameFloat"
|
|
2375
|
+
});
|
|
2376
|
+
},
|
|
2157
2377
|
onDone
|
|
2158
2378
|
});
|
|
2159
2379
|
return /* @__PURE__ */ jsx("input", {
|
|
@@ -2162,7 +2382,9 @@ const FloatTitleEditor = ({ dispatch, node, onDone }) => {
|
|
|
2162
2382
|
defaultValue: floatTitle(node),
|
|
2163
2383
|
onBlur: handleBlur,
|
|
2164
2384
|
onKeyDown: handleKeyDown,
|
|
2165
|
-
onPointerDown: (event) =>
|
|
2385
|
+
onPointerDown: (event) => {
|
|
2386
|
+
event.stopPropagation();
|
|
2387
|
+
},
|
|
2166
2388
|
ref: inputRef,
|
|
2167
2389
|
style: titleStyle,
|
|
2168
2390
|
type: "text"
|
|
@@ -2173,35 +2395,24 @@ const FloatTitle = ({ dispatch, node, renamable }) => {
|
|
|
2173
2395
|
if (renamable && editing) return /* @__PURE__ */ jsx(FloatTitleEditor, {
|
|
2174
2396
|
dispatch,
|
|
2175
2397
|
node,
|
|
2176
|
-
onDone: () =>
|
|
2398
|
+
onDone: () => {
|
|
2399
|
+
setEditing(false);
|
|
2400
|
+
}
|
|
2177
2401
|
});
|
|
2178
2402
|
return /* @__PURE__ */ jsx("span", {
|
|
2179
2403
|
"data-dashfoo": "float-title",
|
|
2180
|
-
onDoubleClick: renamable ? () =>
|
|
2404
|
+
onDoubleClick: renamable ? () => {
|
|
2405
|
+
setEditing(true);
|
|
2406
|
+
} : void 0,
|
|
2181
2407
|
style: titleStyle,
|
|
2182
2408
|
title: renamable ? "Double-click to rename" : void 0,
|
|
2183
2409
|
children: floatTitle(node)
|
|
2184
2410
|
});
|
|
2185
2411
|
};
|
|
2186
|
-
const FloatPanel = ({
|
|
2412
|
+
const FloatPanel = ({ node, onFocus, zIndex }) => {
|
|
2187
2413
|
const dispatch = useLayout((state) => state.dispatch);
|
|
2188
|
-
const closableTabs = useLayout((state) => state.closableTabs);
|
|
2189
|
-
const draggableTabs = useLayout((state) => state.draggableTabs);
|
|
2190
2414
|
const editable = useLayout((state) => state.editable);
|
|
2191
|
-
const
|
|
2192
|
-
const resizableSplits = useLayout((state) => state.resizableSplits);
|
|
2193
|
-
const keepMounted = useLayout((state) => state.keepMounted);
|
|
2194
|
-
const renderTab = useLayout((state) => state.renderTab);
|
|
2195
|
-
const renderTabLabel = useLayout((state) => state.renderTabLabel);
|
|
2196
|
-
const renderTabsetToolbar = useLayout((state) => state.renderTabsetToolbar);
|
|
2197
|
-
const snap = useLayout((state) => state.snap);
|
|
2198
|
-
const panelRef = useRef(null);
|
|
2199
|
-
const gestureRef = useRef(null);
|
|
2200
|
-
const latestRef = useRef(node.geometry);
|
|
2201
|
-
const movedRef = useRef(false);
|
|
2202
|
-
const setPanel = (element) => {
|
|
2203
|
-
panelRef.current = element;
|
|
2204
|
-
};
|
|
2415
|
+
const minimized = node.minimized === true;
|
|
2205
2416
|
const restore = () => {
|
|
2206
2417
|
if (!editable) return;
|
|
2207
2418
|
dispatch({
|
|
@@ -2210,88 +2421,30 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2210
2421
|
type: "setFloatMinimized"
|
|
2211
2422
|
});
|
|
2212
2423
|
};
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
edges: edgeKey ? EDGE_BY_KEY.get(edgeKey) ?? null : null,
|
|
2227
|
-
pointerId: event.pointerId,
|
|
2228
|
-
start: node.geometry,
|
|
2229
|
-
startX: event.clientX,
|
|
2230
|
-
startY: event.clientY
|
|
2231
|
-
};
|
|
2232
|
-
gestureRef.current = gesture;
|
|
2233
|
-
latestRef.current = node.geometry;
|
|
2234
|
-
if (gesture.edges) panel.setPointerCapture(event.pointerId);
|
|
2235
|
-
};
|
|
2236
|
-
const handlePointerUp = (event) => {
|
|
2237
|
-
const gesture = gestureRef.current;
|
|
2238
|
-
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
2239
|
-
gestureRef.current = null;
|
|
2240
|
-
const panel = panelRef.current;
|
|
2241
|
-
if (panel?.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
2242
|
-
if (!movedRef.current) {
|
|
2243
|
-
if (node.minimized) restore();
|
|
2244
|
-
return;
|
|
2245
|
-
}
|
|
2246
|
-
dispatch({
|
|
2247
|
-
floatId: node.id,
|
|
2248
|
-
geometry: latestRef.current,
|
|
2249
|
-
type: "moveFloat"
|
|
2250
|
-
});
|
|
2251
|
-
};
|
|
2252
|
-
const handlePointerMove = (event) => {
|
|
2253
|
-
const gesture = gestureRef.current;
|
|
2254
|
-
const panel = panelRef.current;
|
|
2255
|
-
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2256
|
-
if (event.buttons === 0) {
|
|
2257
|
-
handlePointerUp(event);
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
const dx = event.clientX - gesture.startX;
|
|
2261
|
-
const dy = event.clientY - gesture.startY;
|
|
2262
|
-
if (!movedRef.current) {
|
|
2263
|
-
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
2264
|
-
movedRef.current = true;
|
|
2265
|
-
if (!gesture.edges) panel.setPointerCapture(gesture.pointerId);
|
|
2266
|
-
}
|
|
2267
|
-
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
2268
|
-
...gesture.start,
|
|
2269
|
-
left: gesture.start.left + dx,
|
|
2270
|
-
top: gesture.start.top + dy
|
|
2271
|
-
}, gesture.bounds, node.minimized ? CHIP_SIZE : void 0);
|
|
2272
|
-
latestRef.current = next;
|
|
2273
|
-
panel.style.left = `${next.left}px`;
|
|
2274
|
-
panel.style.top = `${next.top}px`;
|
|
2275
|
-
if (!node.minimized) {
|
|
2276
|
-
panel.style.width = `${next.width}px`;
|
|
2277
|
-
panel.style.height = `${next.height}px`;
|
|
2278
|
-
}
|
|
2279
|
-
};
|
|
2280
|
-
const handlePointerCancel = (event) => {
|
|
2281
|
-
const gesture = gestureRef.current;
|
|
2282
|
-
const panel = panelRef.current;
|
|
2283
|
-
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2284
|
-
gestureRef.current = null;
|
|
2285
|
-
if (panel.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
2286
|
-
if (!movedRef.current) return;
|
|
2287
|
-
panel.style.left = `${gesture.start.left}px`;
|
|
2288
|
-
panel.style.top = `${gesture.start.top}px`;
|
|
2289
|
-
if (!node.minimized) {
|
|
2290
|
-
panel.style.width = `${gesture.start.width}px`;
|
|
2291
|
-
panel.style.height = `${gesture.start.height}px`;
|
|
2424
|
+
const { handlers, ref, style } = useFloatGesture({
|
|
2425
|
+
editable,
|
|
2426
|
+
geometry: node.geometry,
|
|
2427
|
+
minimized,
|
|
2428
|
+
onCommit: (geometry) => {
|
|
2429
|
+
dispatch({
|
|
2430
|
+
floatId: node.id,
|
|
2431
|
+
geometry,
|
|
2432
|
+
type: "moveFloat"
|
|
2433
|
+
});
|
|
2434
|
+
},
|
|
2435
|
+
onTap: () => {
|
|
2436
|
+
if (minimized) restore();
|
|
2292
2437
|
}
|
|
2438
|
+
});
|
|
2439
|
+
const { onPointerDown, ...rootHandlers } = handlers;
|
|
2440
|
+
const frameStyle = {
|
|
2441
|
+
...style,
|
|
2442
|
+
pointerEvents: "auto",
|
|
2443
|
+
position: "absolute",
|
|
2444
|
+
zIndex
|
|
2293
2445
|
};
|
|
2294
|
-
if (
|
|
2446
|
+
if (minimized) return /* @__PURE__ */ jsxs("button", {
|
|
2447
|
+
...rootHandlers,
|
|
2295
2448
|
"aria-label": `Restore ${floatTitle(node)} panel`,
|
|
2296
2449
|
"data-dashfoo": "float-chip",
|
|
2297
2450
|
onKeyDown: (event) => {
|
|
@@ -2300,19 +2453,10 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2300
2453
|
restore();
|
|
2301
2454
|
}
|
|
2302
2455
|
},
|
|
2303
|
-
|
|
2304
|
-
onPointerDown: handlePointerDown,
|
|
2456
|
+
onPointerDown,
|
|
2305
2457
|
onPointerDownCapture: onFocus,
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
ref: setPanel,
|
|
2309
|
-
style: {
|
|
2310
|
-
left: node.geometry.left,
|
|
2311
|
-
pointerEvents: "auto",
|
|
2312
|
-
position: "absolute",
|
|
2313
|
-
top: node.geometry.top,
|
|
2314
|
-
zIndex
|
|
2315
|
-
},
|
|
2458
|
+
ref,
|
|
2459
|
+
style: frameStyle,
|
|
2316
2460
|
title: floatTitle(node),
|
|
2317
2461
|
type: "button",
|
|
2318
2462
|
children: [/* @__PURE__ */ jsx(FloatIcon, {}), /* @__PURE__ */ jsx("span", {
|
|
@@ -2320,31 +2464,16 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2320
2464
|
children: floatTitle(node)
|
|
2321
2465
|
})]
|
|
2322
2466
|
});
|
|
2323
|
-
const floatModel = {
|
|
2324
|
-
global,
|
|
2325
|
-
layout: node.layout,
|
|
2326
|
-
version: 1
|
|
2327
|
-
};
|
|
2328
2467
|
return /* @__PURE__ */ jsxs("div", {
|
|
2468
|
+
...rootHandlers,
|
|
2329
2469
|
"data-dashfoo": "float",
|
|
2330
|
-
onPointerCancel: handlePointerCancel,
|
|
2331
2470
|
onPointerDownCapture: onFocus,
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
ref: setPanel,
|
|
2335
|
-
style: {
|
|
2336
|
-
height: node.geometry.height,
|
|
2337
|
-
left: node.geometry.left,
|
|
2338
|
-
pointerEvents: "auto",
|
|
2339
|
-
position: "absolute",
|
|
2340
|
-
top: node.geometry.top,
|
|
2341
|
-
width: node.geometry.width,
|
|
2342
|
-
zIndex
|
|
2343
|
-
},
|
|
2471
|
+
ref,
|
|
2472
|
+
style: frameStyle,
|
|
2344
2473
|
children: [
|
|
2345
2474
|
/* @__PURE__ */ jsxs("div", {
|
|
2346
2475
|
"data-dashfoo": "float-titlebar",
|
|
2347
|
-
onPointerDown
|
|
2476
|
+
onPointerDown,
|
|
2348
2477
|
style: editable ? titleBarStyle : {
|
|
2349
2478
|
...titleBarStyle,
|
|
2350
2479
|
cursor: "default"
|
|
@@ -2363,12 +2492,16 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2363
2492
|
editable && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
|
|
2364
2493
|
"aria-label": "Minimize panel",
|
|
2365
2494
|
"data-dashfoo": "float-minimize",
|
|
2366
|
-
onClick: () =>
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2495
|
+
onClick: () => {
|
|
2496
|
+
dispatch({
|
|
2497
|
+
floatId: node.id,
|
|
2498
|
+
minimized: true,
|
|
2499
|
+
type: "setFloatMinimized"
|
|
2500
|
+
});
|
|
2501
|
+
},
|
|
2502
|
+
onPointerDown: (event) => {
|
|
2503
|
+
event.stopPropagation();
|
|
2504
|
+
},
|
|
2372
2505
|
style: dockButtonStyle,
|
|
2373
2506
|
title: "Minimize panel",
|
|
2374
2507
|
type: "button",
|
|
@@ -2376,11 +2509,15 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2376
2509
|
}), /* @__PURE__ */ jsx("button", {
|
|
2377
2510
|
"aria-label": "Dock panel back into the main layout",
|
|
2378
2511
|
"data-dashfoo": "float-dock",
|
|
2379
|
-
onClick: () =>
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2512
|
+
onClick: () => {
|
|
2513
|
+
dispatch({
|
|
2514
|
+
floatId: node.id,
|
|
2515
|
+
type: "dockFloat"
|
|
2516
|
+
});
|
|
2517
|
+
},
|
|
2518
|
+
onPointerDown: (event) => {
|
|
2519
|
+
event.stopPropagation();
|
|
2520
|
+
},
|
|
2384
2521
|
style: dockButtonStyle,
|
|
2385
2522
|
title: "Dock panel back",
|
|
2386
2523
|
type: "button",
|
|
@@ -2391,29 +2528,15 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2391
2528
|
/* @__PURE__ */ jsx("div", {
|
|
2392
2529
|
"data-dashfoo": "float-body",
|
|
2393
2530
|
style: bodyStyle,
|
|
2394
|
-
children: /* @__PURE__ */ jsx(
|
|
2395
|
-
|
|
2396
|
-
dispatch,
|
|
2397
|
-
draggableTabs,
|
|
2398
|
-
draggableTabsets: false,
|
|
2399
|
-
editable,
|
|
2400
|
-
floatable: false,
|
|
2401
|
-
keepMounted,
|
|
2402
|
-
maximizable: false,
|
|
2403
|
-
model: floatModel,
|
|
2404
|
-
renamableTabs,
|
|
2405
|
-
renderTab,
|
|
2406
|
-
renderTabLabel,
|
|
2407
|
-
renderTabsetToolbar,
|
|
2408
|
-
resizableSplits,
|
|
2409
|
-
snap: snap ?? void 0,
|
|
2531
|
+
children: /* @__PURE__ */ jsx(LayoutOverrides, {
|
|
2532
|
+
overrides: FLOAT_OVERRIDES,
|
|
2410
2533
|
children: /* @__PURE__ */ jsx(DragProvider, { children: /* @__PURE__ */ jsx(RowView, { node: node.layout }) })
|
|
2411
2534
|
})
|
|
2412
2535
|
}),
|
|
2413
2536
|
editable && RESIZE_HANDLES.map((handle) => /* @__PURE__ */ jsx("div", {
|
|
2414
2537
|
"data-dashfoo": "float-resize",
|
|
2415
2538
|
"data-edge": handle.key,
|
|
2416
|
-
onPointerDown
|
|
2539
|
+
onPointerDown,
|
|
2417
2540
|
style: {
|
|
2418
2541
|
position: "absolute",
|
|
2419
2542
|
touchAction: "none",
|
|
@@ -2433,26 +2556,24 @@ const overlayStyle = {
|
|
|
2433
2556
|
position: "fixed",
|
|
2434
2557
|
zIndex: 1
|
|
2435
2558
|
};
|
|
2436
|
-
const FloatOverlay = ({ floats
|
|
2559
|
+
const FloatOverlay = ({ floats }) => {
|
|
2437
2560
|
const [topId, setTopId] = useState(null);
|
|
2438
2561
|
if (floats.length === 0) return null;
|
|
2439
2562
|
return /* @__PURE__ */ jsx("div", {
|
|
2440
2563
|
"data-dashfoo": "float-overlay",
|
|
2441
2564
|
style: overlayStyle,
|
|
2442
2565
|
children: floats.map((node) => /* @__PURE__ */ jsx(FloatPanel, {
|
|
2443
|
-
global,
|
|
2444
2566
|
node,
|
|
2445
|
-
onFocus: () =>
|
|
2567
|
+
onFocus: () => {
|
|
2568
|
+
setTopId(node.id);
|
|
2569
|
+
},
|
|
2446
2570
|
zIndex: node.id === topId ? 2 : 1
|
|
2447
2571
|
}, node.id))
|
|
2448
2572
|
});
|
|
2449
2573
|
};
|
|
2450
|
-
const FloatLayer = ({ children, floats
|
|
2574
|
+
const FloatLayer = ({ children, floats }) => /* @__PURE__ */ jsxs(FloatLayerContext.Provider, {
|
|
2451
2575
|
value: true,
|
|
2452
|
-
children: [children, /* @__PURE__ */ jsx(FloatOverlay, {
|
|
2453
|
-
floats,
|
|
2454
|
-
global
|
|
2455
|
-
})]
|
|
2576
|
+
children: [children, /* @__PURE__ */ jsx(FloatOverlay, { floats })]
|
|
2456
2577
|
});
|
|
2457
2578
|
|
|
2458
2579
|
//#endregion
|
|
@@ -2499,48 +2620,62 @@ const DashfooLayout = forwardRef((props, ref) => {
|
|
|
2499
2620
|
});
|
|
2500
2621
|
const hasSharedManager = useContext(SharedDragManagerContext) !== null;
|
|
2501
2622
|
useImperativeHandle(ref, () => ({
|
|
2502
|
-
addTab: (tab, target) =>
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2623
|
+
addTab: (tab, target) => {
|
|
2624
|
+
store.dispatch({
|
|
2625
|
+
index: target.index,
|
|
2626
|
+
location: target.location ?? "center",
|
|
2627
|
+
tab,
|
|
2628
|
+
targetId: target.targetId,
|
|
2629
|
+
type: "addNode"
|
|
2630
|
+
});
|
|
2631
|
+
},
|
|
2509
2632
|
canRedo: store.canRedo,
|
|
2510
2633
|
canUndo: store.canUndo,
|
|
2511
|
-
closeTab: (tabId) =>
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2634
|
+
closeTab: (tabId) => {
|
|
2635
|
+
store.dispatch({
|
|
2636
|
+
tabId,
|
|
2637
|
+
type: "deleteTab"
|
|
2638
|
+
});
|
|
2639
|
+
},
|
|
2515
2640
|
dispatch: store.dispatch,
|
|
2516
|
-
dockFloat: (floatId) =>
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2641
|
+
dockFloat: (floatId) => {
|
|
2642
|
+
store.dispatch({
|
|
2643
|
+
floatId,
|
|
2644
|
+
type: "dockFloat"
|
|
2645
|
+
});
|
|
2646
|
+
},
|
|
2647
|
+
floatTab: (tabId) => {
|
|
2648
|
+
store.dispatch({
|
|
2649
|
+
tabId,
|
|
2650
|
+
type: "floatTab"
|
|
2651
|
+
});
|
|
2652
|
+
},
|
|
2524
2653
|
getModel: () => store.model,
|
|
2525
|
-
maximizeTabset: (tabsetId) =>
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2654
|
+
maximizeTabset: (tabsetId) => {
|
|
2655
|
+
store.dispatch({
|
|
2656
|
+
tabsetId,
|
|
2657
|
+
type: "setMaximizedTabset"
|
|
2658
|
+
});
|
|
2659
|
+
},
|
|
2529
2660
|
redo: store.redo,
|
|
2530
|
-
renameTab: (tabId, name) =>
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2661
|
+
renameTab: (tabId, name) => {
|
|
2662
|
+
store.dispatch({
|
|
2663
|
+
name,
|
|
2664
|
+
tabId,
|
|
2665
|
+
type: "renameTab"
|
|
2666
|
+
});
|
|
2667
|
+
},
|
|
2535
2668
|
resetLayout: () => {
|
|
2536
2669
|
persistence.clear();
|
|
2537
2670
|
if (defaultModel !== void 0) store.setModel(defaultModel);
|
|
2538
2671
|
},
|
|
2539
|
-
selectTab: (tabsetId, index) =>
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2672
|
+
selectTab: (tabsetId, index) => {
|
|
2673
|
+
store.dispatch({
|
|
2674
|
+
index,
|
|
2675
|
+
tabsetId,
|
|
2676
|
+
type: "selectTab"
|
|
2677
|
+
});
|
|
2678
|
+
},
|
|
2544
2679
|
undo: store.undo
|
|
2545
2680
|
}), [
|
|
2546
2681
|
defaultModel,
|
|
@@ -2566,24 +2701,18 @@ const DashfooLayout = forwardRef((props, ref) => {
|
|
|
2566
2701
|
responsive?.orientation,
|
|
2567
2702
|
store.model
|
|
2568
2703
|
]);
|
|
2569
|
-
const
|
|
2570
|
-
const dispatch = useCallback((action) => {
|
|
2571
|
-
if (isCompact && action.type === "adjustSplit") return;
|
|
2572
|
-
storeDispatch(action);
|
|
2573
|
-
}, [isCompact, storeDispatch]);
|
|
2574
|
-
const maximized = view.maximizedTabsetId ? findTabset(view, view.maximizedTabsetId) : void 0;
|
|
2704
|
+
const maximized = view.maximizedTabsetId === void 0 ? void 0 : findTabset(view, view.maximizedTabsetId);
|
|
2575
2705
|
const tree = /* @__PURE__ */ jsx(Layout.FloatLayer, {
|
|
2576
|
-
floats: store.model.floats
|
|
2577
|
-
global: store.model.global,
|
|
2706
|
+
floats: store.model.floats,
|
|
2578
2707
|
children: /* @__PURE__ */ jsx(Layout.DragLayer, { children: maximized ? /* @__PURE__ */ jsx(Layout.Tabset, { node: maximized }) : /* @__PURE__ */ jsx(Layout.Rows, { node: view.layout }) })
|
|
2579
2708
|
});
|
|
2580
2709
|
return /* @__PURE__ */ jsx(Layout.Root, {
|
|
2581
2710
|
closableTabs,
|
|
2582
|
-
dispatch,
|
|
2583
|
-
draggableTabs
|
|
2584
|
-
draggableTabsets
|
|
2711
|
+
dispatch: store.dispatch,
|
|
2712
|
+
draggableTabs,
|
|
2713
|
+
draggableTabsets,
|
|
2585
2714
|
editable,
|
|
2586
|
-
floatable
|
|
2715
|
+
floatable,
|
|
2587
2716
|
keepMounted,
|
|
2588
2717
|
maximizable,
|
|
2589
2718
|
model: view,
|
|
@@ -2591,7 +2720,8 @@ const DashfooLayout = forwardRef((props, ref) => {
|
|
|
2591
2720
|
renderTab,
|
|
2592
2721
|
renderTabLabel,
|
|
2593
2722
|
renderTabsetToolbar,
|
|
2594
|
-
resizableSplits
|
|
2723
|
+
resizableSplits,
|
|
2724
|
+
restructurable: !isCompact,
|
|
2595
2725
|
rootRef: containerRef,
|
|
2596
2726
|
snap,
|
|
2597
2727
|
children: hasSharedManager ? tree : /* @__PURE__ */ jsx(DashfooDragProvider, { children: tree })
|