@dashfoo/react 0.5.1 → 0.6.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 +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +348 -242
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
import { canRedo, canUndo, dashfooMachine, dragDockMachine, findTabset, fromJSON, normalize, reducer, resolveDockTarget, snapEnabled, snapSizes, stackModel, tabNodeSchema, toJSON, zoneRect } from "@dashfoo/core";
|
|
4
4
|
import { Fragment, createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
-
import { Accessibility, DragDropManager, Draggable, Feedback, PointerSensor } from "@dnd-kit/dom";
|
|
5
|
+
import { Accessibility, DragDropManager, Draggable, Droppable, Feedback, PointerSensor } from "@dnd-kit/dom";
|
|
6
6
|
import { createStore, useStore } from "zustand";
|
|
7
|
+
import { CollisionPriority, CollisionType } from "@dnd-kit/abstract";
|
|
7
8
|
import { useActorRef, useSelector } from "@xstate/react";
|
|
8
9
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
import { Group, Panel as Panel$1, Separator } from "react-resizable-panels";
|
|
10
11
|
|
|
12
|
+
//#region src/lib/topmost-collision.ts
|
|
13
|
+
const createTopmostPointerIntersection = (topElementAt = (x, y) => document.elementFromPoint(x, y)) => {
|
|
14
|
+
let cached = null;
|
|
15
|
+
const topAt = (x, y) => {
|
|
16
|
+
if (cached && cached.x === x && cached.y === y) return cached.top;
|
|
17
|
+
const top = topElementAt(x, y);
|
|
18
|
+
cached = {
|
|
19
|
+
top,
|
|
20
|
+
x,
|
|
21
|
+
y
|
|
22
|
+
};
|
|
23
|
+
queueMicrotask(() => {
|
|
24
|
+
cached = null;
|
|
25
|
+
});
|
|
26
|
+
return top;
|
|
27
|
+
};
|
|
28
|
+
return ({ dragOperation, droppable }) => {
|
|
29
|
+
const { element } = droppable;
|
|
30
|
+
if (!element) return null;
|
|
31
|
+
const point = dragOperation.position.current;
|
|
32
|
+
const top = topAt(point.x, point.y);
|
|
33
|
+
if (!top || !element.contains(top)) return null;
|
|
34
|
+
let depth = 0;
|
|
35
|
+
for (let node = element; node; node = node.parentElement) depth += 1;
|
|
36
|
+
return {
|
|
37
|
+
id: droppable.id,
|
|
38
|
+
priority: CollisionPriority.High,
|
|
39
|
+
type: CollisionType.PointerIntersection,
|
|
40
|
+
value: depth
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const topmostPointerIntersection = createTopmostPointerIntersection();
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
11
47
|
//#region src/hooks/drag-hooks.tsx
|
|
12
48
|
const INTERACTIVE_SELECTOR = `
|
|
13
49
|
input:not([disabled]),
|
|
@@ -24,7 +60,7 @@ const preventActivation = (event, source) => {
|
|
|
24
60
|
return interactive !== null && interactive !== source.element;
|
|
25
61
|
};
|
|
26
62
|
const createDragManager = () => new DragDropManager({
|
|
27
|
-
plugins: (defaults) => defaults.filter((plugin) => plugin !== Accessibility && plugin !== Feedback),
|
|
63
|
+
plugins: (defaults) => [...defaults.filter((plugin) => plugin !== Accessibility && plugin !== Feedback), Feedback.configure({ dropAnimation: null })],
|
|
28
64
|
sensors: () => [PointerSensor.configure({ preventActivation })]
|
|
29
65
|
});
|
|
30
66
|
const DragContext = createContext(null);
|
|
@@ -109,9 +145,37 @@ const useTabsetDraggable = (tabsetId, disabled = false, label = "") => {
|
|
|
109
145
|
};
|
|
110
146
|
const useTabsetDroppable = (tabsetId) => {
|
|
111
147
|
const context = useContext(DragContext);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
148
|
+
const elementRef = useRef(null);
|
|
149
|
+
const droppableRef = useRef(null);
|
|
150
|
+
const ref = useCallback((element) => {
|
|
151
|
+
elementRef.current = element;
|
|
152
|
+
if (droppableRef.current) droppableRef.current.element = element ?? void 0;
|
|
153
|
+
}, []);
|
|
154
|
+
const layerId = context?.layerId;
|
|
155
|
+
const manager = context?.manager;
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
if (!manager || layerId === void 0) return;
|
|
158
|
+
const droppable = new Droppable({
|
|
159
|
+
collisionDetector: topmostPointerIntersection,
|
|
160
|
+
data: {
|
|
161
|
+
layerId,
|
|
162
|
+
tabsetId,
|
|
163
|
+
type: "tabset"
|
|
164
|
+
},
|
|
165
|
+
id: `${layerId}:${tabsetId}`
|
|
166
|
+
}, manager);
|
|
167
|
+
droppable.element = elementRef.current ?? void 0;
|
|
168
|
+
droppableRef.current = droppable;
|
|
169
|
+
return () => {
|
|
170
|
+
droppable.destroy();
|
|
171
|
+
droppableRef.current = null;
|
|
172
|
+
};
|
|
173
|
+
}, [
|
|
174
|
+
layerId,
|
|
175
|
+
manager,
|
|
176
|
+
tabsetId
|
|
177
|
+
]);
|
|
178
|
+
return { ref };
|
|
115
179
|
};
|
|
116
180
|
const useExternalTabSource = ({ createTab, disabled = false, label = "" }) => {
|
|
117
181
|
const id = useId();
|
|
@@ -422,31 +486,6 @@ const useDashfooStore = (options) => {
|
|
|
422
486
|
]);
|
|
423
487
|
};
|
|
424
488
|
|
|
425
|
-
//#endregion
|
|
426
|
-
//#region src/components/drag-root.tsx
|
|
427
|
-
const DashfooDragProvider = ({ children }) => {
|
|
428
|
-
const [manager] = useState(createDragManager);
|
|
429
|
-
useInsertionEffect(() => () => manager.destroy(), [manager]);
|
|
430
|
-
const [subjectStore] = useState(createDragSubjectStore);
|
|
431
|
-
return /* @__PURE__ */ jsx(SharedDragManagerContext.Provider, {
|
|
432
|
-
value: manager,
|
|
433
|
-
children: /* @__PURE__ */ jsx(DragSubjectStoreContext.Provider, {
|
|
434
|
-
value: subjectStore,
|
|
435
|
-
children
|
|
436
|
-
})
|
|
437
|
-
});
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
//#endregion
|
|
441
|
-
//#region src/hooks/layout-store.ts
|
|
442
|
-
const createLayoutStore = (initial) => createStore(() => initial);
|
|
443
|
-
const LayoutStoreContext = createContext(null);
|
|
444
|
-
const useLayout = (selector) => {
|
|
445
|
-
const store = useContext(LayoutStoreContext);
|
|
446
|
-
if (store === null) throw new Error("dashfoo components must be rendered inside <DashfooLayout> or <Layout.Root>.");
|
|
447
|
-
return useStore(store, selector);
|
|
448
|
-
};
|
|
449
|
-
|
|
450
489
|
//#endregion
|
|
451
490
|
//#region src/lib/tab-insertion.ts
|
|
452
491
|
const pointInRect = (point, rect) => point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
@@ -472,15 +511,6 @@ const shouldAllowDrop = (draggedId, targetId, tabIds) => {
|
|
|
472
511
|
return true;
|
|
473
512
|
};
|
|
474
513
|
|
|
475
|
-
//#endregion
|
|
476
|
-
//#region src/lib/warn-once.ts
|
|
477
|
-
const warned = /* @__PURE__ */ new Set();
|
|
478
|
-
const warnOnce = (key, message) => {
|
|
479
|
-
if (warned.has(key)) return;
|
|
480
|
-
warned.add(key);
|
|
481
|
-
console.warn(`[dashfoo] ${message}`);
|
|
482
|
-
};
|
|
483
|
-
|
|
484
514
|
//#endregion
|
|
485
515
|
//#region src/components/drag-overlays.tsx
|
|
486
516
|
const overlayBase = {
|
|
@@ -511,37 +541,107 @@ const DockIndicator = ({ actorRef, getTabsetElement }) => {
|
|
|
511
541
|
if (!intent) return null;
|
|
512
542
|
const element = getTabsetElement(intent.targetId);
|
|
513
543
|
if (!element) return null;
|
|
544
|
+
const zIndex = element.closest("[data-dashfoo=\"float\"]") ? overlayBase.zIndex : "auto";
|
|
514
545
|
if (intent.location === "center") {
|
|
515
546
|
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
516
547
|
if (strip) return /* @__PURE__ */ jsx("div", {
|
|
517
548
|
"data-dashfoo": "dock-indicator",
|
|
518
|
-
style:
|
|
549
|
+
style: {
|
|
550
|
+
...lineStyle(insertionLineRect(strip.getBoundingClientRect(), tabItemRects(strip, draggedId), intent.index ?? 0)),
|
|
551
|
+
zIndex
|
|
552
|
+
}
|
|
519
553
|
});
|
|
520
554
|
}
|
|
521
555
|
return /* @__PURE__ */ jsx("div", {
|
|
522
556
|
"data-dashfoo": "dock-indicator",
|
|
523
|
-
style:
|
|
557
|
+
style: {
|
|
558
|
+
...paneStyle(zoneRect(element.getBoundingClientRect(), intent.location)),
|
|
559
|
+
zIndex
|
|
560
|
+
}
|
|
524
561
|
});
|
|
525
562
|
};
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
top: 0,
|
|
530
|
-
zIndex: 9999
|
|
563
|
+
const PREVIEW_OFFSET = {
|
|
564
|
+
x: 12,
|
|
565
|
+
y: 8
|
|
531
566
|
};
|
|
532
|
-
const
|
|
533
|
-
|
|
567
|
+
const labelOf = (source) => {
|
|
568
|
+
const raw = source?.data?.label;
|
|
569
|
+
return typeof raw === "string" ? raw : "";
|
|
570
|
+
};
|
|
571
|
+
const DragPreviewOverlay = ({ manager }) => {
|
|
572
|
+
const [chip, setChip] = useState(null);
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
const offStart = manager.monitor.addEventListener("dragstart", (event) => {
|
|
575
|
+
const source = event.operation.source;
|
|
576
|
+
if (!source) return;
|
|
577
|
+
const rect = source.element?.getBoundingClientRect();
|
|
578
|
+
const point = event.operation.position.current;
|
|
579
|
+
setChip({
|
|
580
|
+
label: labelOf(source),
|
|
581
|
+
x: PREVIEW_OFFSET.x + (rect ? point.x - rect.left : 0),
|
|
582
|
+
y: PREVIEW_OFFSET.y + (rect ? point.y - rect.top : 0)
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
const offEnd = manager.monitor.addEventListener("dragend", () => setChip(null));
|
|
586
|
+
return () => {
|
|
587
|
+
offStart();
|
|
588
|
+
offEnd();
|
|
589
|
+
};
|
|
590
|
+
}, [manager]);
|
|
534
591
|
return /* @__PURE__ */ jsx("div", {
|
|
535
|
-
"data-
|
|
536
|
-
ref:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
592
|
+
"data-dnd-overlay": "",
|
|
593
|
+
ref: useCallback((element) => {
|
|
594
|
+
const feedback = manager.registry.plugins.get(Feedback);
|
|
595
|
+
if (feedback) feedback.overlay = element ?? void 0;
|
|
596
|
+
}, [manager]),
|
|
597
|
+
children: chip === null ? null : /* @__PURE__ */ jsx("div", {
|
|
598
|
+
"data-dashfoo": "drag-preview",
|
|
599
|
+
style: {
|
|
600
|
+
left: 0,
|
|
601
|
+
position: "absolute",
|
|
602
|
+
top: 0,
|
|
603
|
+
transform: `translate(${chip.x}px, ${chip.y}px)`,
|
|
604
|
+
width: "max-content"
|
|
605
|
+
},
|
|
606
|
+
children: chip.label
|
|
607
|
+
})
|
|
542
608
|
});
|
|
543
609
|
};
|
|
544
610
|
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/components/drag-root.tsx
|
|
613
|
+
const DashfooDragProvider = ({ children }) => {
|
|
614
|
+
const [manager] = useState(createDragManager);
|
|
615
|
+
useInsertionEffect(() => () => manager.destroy(), [manager]);
|
|
616
|
+
const [subjectStore] = useState(createDragSubjectStore);
|
|
617
|
+
return /* @__PURE__ */ jsx(SharedDragManagerContext.Provider, {
|
|
618
|
+
value: manager,
|
|
619
|
+
children: /* @__PURE__ */ jsxs(DragSubjectStoreContext.Provider, {
|
|
620
|
+
value: subjectStore,
|
|
621
|
+
children: [children, /* @__PURE__ */ jsx(DragPreviewOverlay, { manager })]
|
|
622
|
+
})
|
|
623
|
+
});
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
//#endregion
|
|
627
|
+
//#region src/hooks/layout-store.ts
|
|
628
|
+
const createLayoutStore = (initial) => createStore(() => initial);
|
|
629
|
+
const LayoutStoreContext = createContext(null);
|
|
630
|
+
const useLayout = (selector) => {
|
|
631
|
+
const store = useContext(LayoutStoreContext);
|
|
632
|
+
if (store === null) throw new Error("dashfoo components must be rendered inside <DashfooLayout> or <Layout.Root>.");
|
|
633
|
+
return useStore(store, selector);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
//#endregion
|
|
637
|
+
//#region src/lib/warn-once.ts
|
|
638
|
+
const warned = /* @__PURE__ */ new Set();
|
|
639
|
+
const warnOnce = (key, message) => {
|
|
640
|
+
if (warned.has(key)) return;
|
|
641
|
+
warned.add(key);
|
|
642
|
+
console.warn(`[dashfoo] ${message}`);
|
|
643
|
+
};
|
|
644
|
+
|
|
545
645
|
//#endregion
|
|
546
646
|
//#region src/components/drag-adapter.tsx
|
|
547
647
|
const tabRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab\"]")].flatMap((tab) => tab.dataset.tabId === excludeId ? [] : [tab.getBoundingClientRect()]);
|
|
@@ -564,14 +664,6 @@ const intentForTabset = (id, element, point, draggedId) => {
|
|
|
564
664
|
targetId: id
|
|
565
665
|
};
|
|
566
666
|
};
|
|
567
|
-
const PREVIEW_OFFSET = {
|
|
568
|
-
x: 12,
|
|
569
|
-
y: 8
|
|
570
|
-
};
|
|
571
|
-
const labelOf = (source) => {
|
|
572
|
-
const raw = source?.data?.label;
|
|
573
|
-
return typeof raw === "string" ? raw : "";
|
|
574
|
-
};
|
|
575
667
|
const isTabFactory = (value) => typeof value === "function";
|
|
576
668
|
const subjectFor = (source) => {
|
|
577
669
|
const data = source.data;
|
|
@@ -610,9 +702,6 @@ const subjectFor = (source) => {
|
|
|
610
702
|
const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
611
703
|
const actorRef = useActorRef(dragDockMachine);
|
|
612
704
|
const layoutStore = useContext(LayoutStoreContext);
|
|
613
|
-
const [tabsets] = useState(() => /* @__PURE__ */ new Map());
|
|
614
|
-
const overlayRef = useRef(null);
|
|
615
|
-
const [preview, setPreview] = useState(null);
|
|
616
705
|
const sharedManager = useContext(SharedDragManagerContext);
|
|
617
706
|
const [ownManager] = useState(() => sharedManager ? null : createDragManager());
|
|
618
707
|
useInsertionEffect(() => () => ownManager?.destroy(), [ownManager]);
|
|
@@ -650,6 +739,10 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
650
739
|
onCommitRef.current = onCommit;
|
|
651
740
|
splitDockRef.current = splitDock;
|
|
652
741
|
});
|
|
742
|
+
const getTabsetElement = useCallback((id) => {
|
|
743
|
+
const element = manager.registry.droppables.get(`${ownerId}:${id}`)?.element;
|
|
744
|
+
return element instanceof HTMLElement ? element : void 0;
|
|
745
|
+
}, [manager, ownerId]);
|
|
653
746
|
useEffect(() => {
|
|
654
747
|
const subscription = actorRef.on("COMMIT", (emitted) => {
|
|
655
748
|
if (layoutStore?.getState().editable === false) return;
|
|
@@ -664,23 +757,7 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
664
757
|
subscription.unsubscribe();
|
|
665
758
|
};
|
|
666
759
|
}, [actorRef, layoutStore]);
|
|
667
|
-
const registerTabset = useCallback((id, element) => {
|
|
668
|
-
if (element) tabsets.set(id, element);
|
|
669
|
-
else tabsets.delete(id);
|
|
670
|
-
}, [tabsets]);
|
|
671
|
-
const getTabsetElement = useCallback((id) => tabsets.get(id), [tabsets]);
|
|
672
|
-
const attachOverlay = useCallback((element) => {
|
|
673
|
-
overlayRef.current = element;
|
|
674
|
-
}, []);
|
|
675
760
|
useEffect(() => {
|
|
676
|
-
const tabsetAt = (point) => {
|
|
677
|
-
const top = document.elementFromPoint(point.x, point.y);
|
|
678
|
-
if (!top) return;
|
|
679
|
-
for (const [id, element] of tabsets) if (element.contains(top)) return {
|
|
680
|
-
element,
|
|
681
|
-
id
|
|
682
|
-
};
|
|
683
|
-
};
|
|
684
761
|
const resolveIntent = (targetId, element, point, draggedId) => {
|
|
685
762
|
if (layoutStore?.getState().editable === false) return null;
|
|
686
763
|
if (!shouldAllowDrop(draggedId, targetId, [...element.querySelectorAll("[data-dashfoo=\"tab\"]")].map((tab) => tab.dataset.tabId ?? ""))) return null;
|
|
@@ -691,71 +768,62 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
691
768
|
};
|
|
692
769
|
return intent;
|
|
693
770
|
};
|
|
694
|
-
const positionOverlay = (point) => {
|
|
695
|
-
const element = overlayRef.current;
|
|
696
|
-
if (element) element.style.transform = `translate(${point.x + PREVIEW_OFFSET.x}px, ${point.y + PREVIEW_OFFSET.y}px)`;
|
|
697
|
-
};
|
|
698
771
|
const handleStart = (event) => {
|
|
699
772
|
const source = event.operation.source;
|
|
700
773
|
if (!source) return;
|
|
701
774
|
const subject = subjectFor(source);
|
|
702
|
-
if (!subject)
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
x: point.x + PREVIEW_OFFSET.x,
|
|
707
|
-
y: point.y + PREVIEW_OFFSET.y
|
|
708
|
-
});
|
|
775
|
+
if (!subject) {
|
|
776
|
+
manager.actions.stop({ canceled: true });
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
709
779
|
actorRef.send({
|
|
710
780
|
subject,
|
|
711
781
|
type: "START"
|
|
712
782
|
});
|
|
713
783
|
};
|
|
714
|
-
const
|
|
715
|
-
const
|
|
716
|
-
const point =
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
const
|
|
784
|
+
const syncIntent = () => {
|
|
785
|
+
const operation = manager.dragOperation;
|
|
786
|
+
const point = operation.position.current;
|
|
787
|
+
const target = operation.target;
|
|
788
|
+
const data = target?.data;
|
|
789
|
+
const tabsetId = data?.layerId === ownerId ? data.tabsetId : void 0;
|
|
790
|
+
const element = tabsetId === void 0 ? void 0 : target?.element;
|
|
791
|
+
const draggedId = operation.source ? String(operation.source.id) : void 0;
|
|
720
792
|
actorRef.send({
|
|
721
|
-
intent:
|
|
793
|
+
intent: tabsetId !== void 0 && element instanceof HTMLElement ? resolveIntent(tabsetId, element, point, draggedId) : null,
|
|
722
794
|
type: "OVER"
|
|
723
795
|
});
|
|
724
796
|
};
|
|
797
|
+
const handleMove = () => syncIntent();
|
|
798
|
+
const handleCollision = () => syncIntent();
|
|
725
799
|
const handleEnd = (event) => {
|
|
726
|
-
setPreview(null);
|
|
727
800
|
if (event.canceled) {
|
|
728
801
|
actorRef.send({ type: "CANCEL" });
|
|
729
802
|
return;
|
|
730
803
|
}
|
|
731
|
-
|
|
732
|
-
const point = op.position.current;
|
|
733
|
-
const draggedId = op.source ? String(op.source.id) : void 0;
|
|
734
|
-
const hit = tabsetAt(point);
|
|
735
|
-
if (hit) actorRef.send({
|
|
736
|
-
intent: resolveIntent(hit.id, hit.element, point, draggedId),
|
|
737
|
-
type: "OVER"
|
|
738
|
-
});
|
|
804
|
+
syncIntent();
|
|
739
805
|
actorRef.send({ type: "DROP" });
|
|
740
806
|
};
|
|
741
807
|
const offStart = manager.monitor.addEventListener("dragstart", handleStart);
|
|
742
808
|
const offMove = manager.monitor.addEventListener("dragmove", handleMove);
|
|
809
|
+
const offCollision = manager.monitor.addEventListener("collision", handleCollision);
|
|
743
810
|
const offEnd = manager.monitor.addEventListener("dragend", handleEnd);
|
|
744
811
|
return () => {
|
|
745
812
|
offStart();
|
|
746
813
|
offMove();
|
|
814
|
+
offCollision();
|
|
747
815
|
offEnd();
|
|
748
816
|
};
|
|
749
817
|
}, [
|
|
750
818
|
actorRef,
|
|
751
819
|
layoutStore,
|
|
752
820
|
manager,
|
|
753
|
-
|
|
821
|
+
ownerId
|
|
754
822
|
]);
|
|
755
823
|
const contextValue = useMemo(() => ({
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}), [manager,
|
|
824
|
+
layerId: ownerId,
|
|
825
|
+
manager
|
|
826
|
+
}), [manager, ownerId]);
|
|
759
827
|
return /* @__PURE__ */ jsx(DragContext.Provider, {
|
|
760
828
|
value: contextValue,
|
|
761
829
|
children: /* @__PURE__ */ jsxs(DragSubjectStoreContext.Provider, {
|
|
@@ -766,10 +834,7 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
766
834
|
actorRef,
|
|
767
835
|
getTabsetElement
|
|
768
836
|
}),
|
|
769
|
-
/* @__PURE__ */ jsx(
|
|
770
|
-
overlayRef: attachOverlay,
|
|
771
|
-
preview
|
|
772
|
-
})
|
|
837
|
+
ownManager === null ? null : /* @__PURE__ */ jsx(DragPreviewOverlay, { manager: ownManager })
|
|
773
838
|
]
|
|
774
839
|
})
|
|
775
840
|
});
|
|
@@ -825,6 +890,125 @@ const resizeRect = (start, edges, dx, dy) => {
|
|
|
825
890
|
};
|
|
826
891
|
};
|
|
827
892
|
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region src/components/float-resize-handles.ts
|
|
895
|
+
const EDGE_THICKNESS = 12;
|
|
896
|
+
const EDGE_OFFSET = -6;
|
|
897
|
+
const CORNER_SIZE = 20;
|
|
898
|
+
const CORNER_OFFSET = -8;
|
|
899
|
+
const EDGE_INSET = 12;
|
|
900
|
+
const RESIZE_HANDLES = [
|
|
901
|
+
{
|
|
902
|
+
edges: {
|
|
903
|
+
x: 0,
|
|
904
|
+
y: -1
|
|
905
|
+
},
|
|
906
|
+
key: "n",
|
|
907
|
+
style: {
|
|
908
|
+
cursor: "ns-resize",
|
|
909
|
+
height: EDGE_THICKNESS,
|
|
910
|
+
insetInline: EDGE_INSET,
|
|
911
|
+
top: EDGE_OFFSET
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
edges: {
|
|
916
|
+
x: 0,
|
|
917
|
+
y: 1
|
|
918
|
+
},
|
|
919
|
+
key: "s",
|
|
920
|
+
style: {
|
|
921
|
+
bottom: EDGE_OFFSET,
|
|
922
|
+
cursor: "ns-resize",
|
|
923
|
+
height: EDGE_THICKNESS,
|
|
924
|
+
insetInline: EDGE_INSET
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
edges: {
|
|
929
|
+
x: 1,
|
|
930
|
+
y: 0
|
|
931
|
+
},
|
|
932
|
+
key: "e",
|
|
933
|
+
style: {
|
|
934
|
+
cursor: "ew-resize",
|
|
935
|
+
insetBlock: EDGE_INSET,
|
|
936
|
+
right: EDGE_OFFSET,
|
|
937
|
+
width: EDGE_THICKNESS
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
edges: {
|
|
942
|
+
x: -1,
|
|
943
|
+
y: 0
|
|
944
|
+
},
|
|
945
|
+
key: "w",
|
|
946
|
+
style: {
|
|
947
|
+
cursor: "ew-resize",
|
|
948
|
+
insetBlock: EDGE_INSET,
|
|
949
|
+
left: EDGE_OFFSET,
|
|
950
|
+
width: EDGE_THICKNESS
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
edges: {
|
|
955
|
+
x: 1,
|
|
956
|
+
y: -1
|
|
957
|
+
},
|
|
958
|
+
key: "ne",
|
|
959
|
+
style: {
|
|
960
|
+
cursor: "nesw-resize",
|
|
961
|
+
height: CORNER_SIZE,
|
|
962
|
+
right: CORNER_OFFSET,
|
|
963
|
+
top: CORNER_OFFSET,
|
|
964
|
+
width: CORNER_SIZE
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
edges: {
|
|
969
|
+
x: -1,
|
|
970
|
+
y: -1
|
|
971
|
+
},
|
|
972
|
+
key: "nw",
|
|
973
|
+
style: {
|
|
974
|
+
cursor: "nwse-resize",
|
|
975
|
+
height: CORNER_SIZE,
|
|
976
|
+
left: CORNER_OFFSET,
|
|
977
|
+
top: CORNER_OFFSET,
|
|
978
|
+
width: CORNER_SIZE
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
edges: {
|
|
983
|
+
x: 1,
|
|
984
|
+
y: 1
|
|
985
|
+
},
|
|
986
|
+
key: "se",
|
|
987
|
+
style: {
|
|
988
|
+
bottom: CORNER_OFFSET,
|
|
989
|
+
cursor: "nwse-resize",
|
|
990
|
+
height: CORNER_SIZE,
|
|
991
|
+
right: CORNER_OFFSET,
|
|
992
|
+
width: CORNER_SIZE
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
edges: {
|
|
997
|
+
x: -1,
|
|
998
|
+
y: 1
|
|
999
|
+
},
|
|
1000
|
+
key: "sw",
|
|
1001
|
+
style: {
|
|
1002
|
+
bottom: CORNER_OFFSET,
|
|
1003
|
+
cursor: "nesw-resize",
|
|
1004
|
+
height: CORNER_SIZE,
|
|
1005
|
+
left: CORNER_OFFSET,
|
|
1006
|
+
width: CORNER_SIZE
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
];
|
|
1010
|
+
const EDGE_BY_KEY = new Map(RESIZE_HANDLES.map((h) => [h.key, h.edges]));
|
|
1011
|
+
|
|
828
1012
|
//#endregion
|
|
829
1013
|
//#region src/components/layout-root.tsx
|
|
830
1014
|
const DEFAULT_TABSET_MIN_SIZE = 320;
|
|
@@ -1148,6 +1332,7 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1148
1332
|
"data-dashfoo": "tab-overflow",
|
|
1149
1333
|
onClick: handleToggle,
|
|
1150
1334
|
ref: triggerRef,
|
|
1335
|
+
title: "More tabs",
|
|
1151
1336
|
type: "button",
|
|
1152
1337
|
children: /* @__PURE__ */ jsx(OverflowIcon, {})
|
|
1153
1338
|
}), open ? /* @__PURE__ */ jsx("div", {
|
|
@@ -1547,6 +1732,7 @@ const TabsetCloseButton = ({ children, onClick, ...props }) => {
|
|
|
1547
1732
|
};
|
|
1548
1733
|
return /* @__PURE__ */ jsx("button", {
|
|
1549
1734
|
"aria-label": `Close ${tab.name}`,
|
|
1735
|
+
title: `Close ${tab.name}`,
|
|
1550
1736
|
...props,
|
|
1551
1737
|
"data-dashfoo": "tab-close",
|
|
1552
1738
|
onClick: handleClick,
|
|
@@ -1638,6 +1824,7 @@ const TabsetGrip = ({ children, ref: userRef, ...props }) => {
|
|
|
1638
1824
|
if (hidden) return null;
|
|
1639
1825
|
return /* @__PURE__ */ jsx("button", {
|
|
1640
1826
|
"aria-label": "Move tabset",
|
|
1827
|
+
title: "Move tabset",
|
|
1641
1828
|
...props,
|
|
1642
1829
|
"data-dashfoo": "tabset-grip",
|
|
1643
1830
|
ref: refCallback,
|
|
@@ -1656,6 +1843,7 @@ const TabsetMaximizeButton = ({ children, onClick, ...props }) => {
|
|
|
1656
1843
|
};
|
|
1657
1844
|
return /* @__PURE__ */ jsx("button", {
|
|
1658
1845
|
"aria-label": isMaximized ? "Restore" : "Maximize",
|
|
1846
|
+
title: isMaximized ? "Restore tabset" : "Maximize tabset",
|
|
1659
1847
|
...props,
|
|
1660
1848
|
"aria-pressed": isMaximized,
|
|
1661
1849
|
"data-dashfoo": "tabset-maximize",
|
|
@@ -1685,6 +1873,7 @@ const TabsetFloatButton = ({ children, onClick, ...props }) => {
|
|
|
1685
1873
|
};
|
|
1686
1874
|
return /* @__PURE__ */ jsx("button", {
|
|
1687
1875
|
"aria-label": "Float panel",
|
|
1876
|
+
title: "Float panel",
|
|
1688
1877
|
...props,
|
|
1689
1878
|
"data-dashfoo": "tabset-float",
|
|
1690
1879
|
onClick: handleClick,
|
|
@@ -1917,117 +2106,6 @@ const bodyStyle = {
|
|
|
1917
2106
|
minHeight: 0,
|
|
1918
2107
|
position: "relative"
|
|
1919
2108
|
};
|
|
1920
|
-
const RESIZE_HANDLES = [
|
|
1921
|
-
{
|
|
1922
|
-
edges: {
|
|
1923
|
-
x: 0,
|
|
1924
|
-
y: -1
|
|
1925
|
-
},
|
|
1926
|
-
key: "n",
|
|
1927
|
-
style: {
|
|
1928
|
-
cursor: "ns-resize",
|
|
1929
|
-
height: 6,
|
|
1930
|
-
insetInline: 10,
|
|
1931
|
-
top: -3
|
|
1932
|
-
}
|
|
1933
|
-
},
|
|
1934
|
-
{
|
|
1935
|
-
edges: {
|
|
1936
|
-
x: 0,
|
|
1937
|
-
y: 1
|
|
1938
|
-
},
|
|
1939
|
-
key: "s",
|
|
1940
|
-
style: {
|
|
1941
|
-
bottom: -3,
|
|
1942
|
-
cursor: "ns-resize",
|
|
1943
|
-
height: 6,
|
|
1944
|
-
insetInline: 10
|
|
1945
|
-
}
|
|
1946
|
-
},
|
|
1947
|
-
{
|
|
1948
|
-
edges: {
|
|
1949
|
-
x: 1,
|
|
1950
|
-
y: 0
|
|
1951
|
-
},
|
|
1952
|
-
key: "e",
|
|
1953
|
-
style: {
|
|
1954
|
-
cursor: "ew-resize",
|
|
1955
|
-
insetBlock: 10,
|
|
1956
|
-
right: -3,
|
|
1957
|
-
width: 6
|
|
1958
|
-
}
|
|
1959
|
-
},
|
|
1960
|
-
{
|
|
1961
|
-
edges: {
|
|
1962
|
-
x: -1,
|
|
1963
|
-
y: 0
|
|
1964
|
-
},
|
|
1965
|
-
key: "w",
|
|
1966
|
-
style: {
|
|
1967
|
-
cursor: "ew-resize",
|
|
1968
|
-
insetBlock: 10,
|
|
1969
|
-
left: -3,
|
|
1970
|
-
width: 6
|
|
1971
|
-
}
|
|
1972
|
-
},
|
|
1973
|
-
{
|
|
1974
|
-
edges: {
|
|
1975
|
-
x: 1,
|
|
1976
|
-
y: -1
|
|
1977
|
-
},
|
|
1978
|
-
key: "ne",
|
|
1979
|
-
style: {
|
|
1980
|
-
cursor: "nesw-resize",
|
|
1981
|
-
height: 14,
|
|
1982
|
-
right: -4,
|
|
1983
|
-
top: -4,
|
|
1984
|
-
width: 14
|
|
1985
|
-
}
|
|
1986
|
-
},
|
|
1987
|
-
{
|
|
1988
|
-
edges: {
|
|
1989
|
-
x: -1,
|
|
1990
|
-
y: -1
|
|
1991
|
-
},
|
|
1992
|
-
key: "nw",
|
|
1993
|
-
style: {
|
|
1994
|
-
cursor: "nwse-resize",
|
|
1995
|
-
height: 14,
|
|
1996
|
-
left: -4,
|
|
1997
|
-
top: -4,
|
|
1998
|
-
width: 14
|
|
1999
|
-
}
|
|
2000
|
-
},
|
|
2001
|
-
{
|
|
2002
|
-
edges: {
|
|
2003
|
-
x: 1,
|
|
2004
|
-
y: 1
|
|
2005
|
-
},
|
|
2006
|
-
key: "se",
|
|
2007
|
-
style: {
|
|
2008
|
-
bottom: -4,
|
|
2009
|
-
cursor: "nwse-resize",
|
|
2010
|
-
height: 14,
|
|
2011
|
-
right: -4,
|
|
2012
|
-
width: 14
|
|
2013
|
-
}
|
|
2014
|
-
},
|
|
2015
|
-
{
|
|
2016
|
-
edges: {
|
|
2017
|
-
x: -1,
|
|
2018
|
-
y: 1
|
|
2019
|
-
},
|
|
2020
|
-
key: "sw",
|
|
2021
|
-
style: {
|
|
2022
|
-
bottom: -4,
|
|
2023
|
-
cursor: "nesw-resize",
|
|
2024
|
-
height: 14,
|
|
2025
|
-
left: -4,
|
|
2026
|
-
width: 14
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
];
|
|
2030
|
-
const EDGE_BY_KEY = new Map(RESIZE_HANDLES.map((h) => [h.key, h.edges]));
|
|
2031
2109
|
const CHIP_SIZE = {
|
|
2032
2110
|
height: 34,
|
|
2033
2111
|
width: 168
|
|
@@ -2120,10 +2198,12 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2120
2198
|
const handlePointerDown = (event) => {
|
|
2121
2199
|
const panel = panelRef.current;
|
|
2122
2200
|
if (!panel || !editable) return;
|
|
2201
|
+
const prior = gestureRef.current;
|
|
2202
|
+
if (prior && panel.hasPointerCapture?.(prior.pointerId)) panel.releasePointerCapture(prior.pointerId);
|
|
2123
2203
|
movedRef.current = false;
|
|
2124
2204
|
const edgeKey = event.currentTarget.dataset.edge;
|
|
2125
2205
|
const parent = panel.offsetParent;
|
|
2126
|
-
|
|
2206
|
+
const gesture = {
|
|
2127
2207
|
bounds: {
|
|
2128
2208
|
height: parent?.clientHeight ?? 0,
|
|
2129
2209
|
width: parent?.clientWidth ?? 0
|
|
@@ -2134,18 +2214,40 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2134
2214
|
startX: event.clientX,
|
|
2135
2215
|
startY: event.clientY
|
|
2136
2216
|
};
|
|
2217
|
+
gestureRef.current = gesture;
|
|
2137
2218
|
latestRef.current = node.geometry;
|
|
2219
|
+
if (gesture.edges) panel.setPointerCapture(event.pointerId);
|
|
2220
|
+
};
|
|
2221
|
+
const handlePointerUp = (event) => {
|
|
2222
|
+
const gesture = gestureRef.current;
|
|
2223
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
2224
|
+
gestureRef.current = null;
|
|
2225
|
+
const panel = panelRef.current;
|
|
2226
|
+
if (panel?.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
2227
|
+
if (!movedRef.current) {
|
|
2228
|
+
if (node.minimized) restore();
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
dispatch({
|
|
2232
|
+
floatId: node.id,
|
|
2233
|
+
geometry: latestRef.current,
|
|
2234
|
+
type: "moveFloat"
|
|
2235
|
+
});
|
|
2138
2236
|
};
|
|
2139
2237
|
const handlePointerMove = (event) => {
|
|
2140
2238
|
const gesture = gestureRef.current;
|
|
2141
2239
|
const panel = panelRef.current;
|
|
2142
2240
|
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2241
|
+
if (event.buttons === 0) {
|
|
2242
|
+
handlePointerUp(event);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2143
2245
|
const dx = event.clientX - gesture.startX;
|
|
2144
2246
|
const dy = event.clientY - gesture.startY;
|
|
2145
2247
|
if (!movedRef.current) {
|
|
2146
2248
|
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
2147
2249
|
movedRef.current = true;
|
|
2148
|
-
panel.setPointerCapture(gesture.pointerId);
|
|
2250
|
+
if (!gesture.edges) panel.setPointerCapture(gesture.pointerId);
|
|
2149
2251
|
}
|
|
2150
2252
|
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
2151
2253
|
...gesture.start,
|
|
@@ -2160,20 +2262,19 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2160
2262
|
panel.style.height = `${next.height}px`;
|
|
2161
2263
|
}
|
|
2162
2264
|
};
|
|
2163
|
-
const
|
|
2265
|
+
const handlePointerCancel = (event) => {
|
|
2164
2266
|
const gesture = gestureRef.current;
|
|
2165
|
-
|
|
2267
|
+
const panel = panelRef.current;
|
|
2268
|
+
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2166
2269
|
gestureRef.current = null;
|
|
2167
|
-
if (
|
|
2168
|
-
|
|
2169
|
-
|
|
2270
|
+
if (panel.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
2271
|
+
if (!movedRef.current) return;
|
|
2272
|
+
panel.style.left = `${gesture.start.left}px`;
|
|
2273
|
+
panel.style.top = `${gesture.start.top}px`;
|
|
2274
|
+
if (!node.minimized) {
|
|
2275
|
+
panel.style.width = `${gesture.start.width}px`;
|
|
2276
|
+
panel.style.height = `${gesture.start.height}px`;
|
|
2170
2277
|
}
|
|
2171
|
-
panelRef.current?.releasePointerCapture(event.pointerId);
|
|
2172
|
-
dispatch({
|
|
2173
|
-
floatId: node.id,
|
|
2174
|
-
geometry: latestRef.current,
|
|
2175
|
-
type: "moveFloat"
|
|
2176
|
-
});
|
|
2177
2278
|
};
|
|
2178
2279
|
if (node.minimized) return /* @__PURE__ */ jsxs("button", {
|
|
2179
2280
|
"aria-label": `Restore ${floatTitle(node)} panel`,
|
|
@@ -2184,6 +2285,7 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2184
2285
|
restore();
|
|
2185
2286
|
}
|
|
2186
2287
|
},
|
|
2288
|
+
onPointerCancel: handlePointerCancel,
|
|
2187
2289
|
onPointerDown: handlePointerDown,
|
|
2188
2290
|
onPointerDownCapture: onFocus,
|
|
2189
2291
|
onPointerMove: handlePointerMove,
|
|
@@ -2210,6 +2312,7 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2210
2312
|
};
|
|
2211
2313
|
return /* @__PURE__ */ jsxs("div", {
|
|
2212
2314
|
"data-dashfoo": "float",
|
|
2315
|
+
onPointerCancel: handlePointerCancel,
|
|
2213
2316
|
onPointerDownCapture: onFocus,
|
|
2214
2317
|
onPointerMove: handlePointerMove,
|
|
2215
2318
|
onPointerUp: handlePointerUp,
|
|
@@ -2252,6 +2355,7 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2252
2355
|
}),
|
|
2253
2356
|
onPointerDown: (event) => event.stopPropagation(),
|
|
2254
2357
|
style: dockButtonStyle,
|
|
2358
|
+
title: "Minimize panel",
|
|
2255
2359
|
type: "button",
|
|
2256
2360
|
children: /* @__PURE__ */ jsx(MinimizeIcon, {})
|
|
2257
2361
|
}), /* @__PURE__ */ jsx("button", {
|
|
@@ -2263,6 +2367,7 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2263
2367
|
}),
|
|
2264
2368
|
onPointerDown: (event) => event.stopPropagation(),
|
|
2265
2369
|
style: dockButtonStyle,
|
|
2370
|
+
title: "Dock panel back",
|
|
2266
2371
|
type: "button",
|
|
2267
2372
|
children: /* @__PURE__ */ jsx(DockIcon, {})
|
|
2268
2373
|
})] })
|
|
@@ -2310,7 +2415,8 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2310
2415
|
const overlayStyle = {
|
|
2311
2416
|
inset: 0,
|
|
2312
2417
|
pointerEvents: "none",
|
|
2313
|
-
position: "fixed"
|
|
2418
|
+
position: "fixed",
|
|
2419
|
+
zIndex: 1
|
|
2314
2420
|
};
|
|
2315
2421
|
const FloatOverlay = ({ floats, global }) => {
|
|
2316
2422
|
const [topId, setTopId] = useState(null);
|