@dashfoo/react 0.6.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +57 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1099 -976
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { canRedo, canUndo, dashfooMachine, dragDockMachine, findTabset, fromJSON, normalize, reducer, resolveDockTarget, snapEnabled,
|
|
3
|
+
import { canRedo, canUndo, dashfooMachine, decideSnap, dragDockMachine, findTabset, fromJSON, normalize, reducer, resolveDockTarget, resolveSnapGrid, settleSnap, snapEnabled, splitEdge, stackModel, tabNodeSchema, toJSON, zoneRect } from "@dashfoo/core";
|
|
4
4
|
import { Fragment, createContext, createElement, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Accessibility, DragDropManager, Draggable, Droppable, Feedback, PointerSensor } from "@dnd-kit/dom";
|
|
6
|
-
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,11 @@ 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
|
-
intent: null,
|
|
70
|
-
intentOwner: null,
|
|
71
|
-
subject: null
|
|
72
|
-
}));
|
|
69
|
+
/** The intent is rebuilt every pointer move, so readers compare by value. */
|
|
73
70
|
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
71
|
const useDraggableHandle = () => {
|
|
78
72
|
const elementRef = useRef(null);
|
|
79
73
|
const draggableRef = useRef(null);
|
|
@@ -194,11 +188,24 @@ const useExternalTabSource = ({ createTab, disabled = false, label = "" }) => {
|
|
|
194
188
|
id: `external-${id}`
|
|
195
189
|
}, disabled, label);
|
|
196
190
|
};
|
|
191
|
+
/**
|
|
192
|
+
* Reads the root's actor, so it works from an overlay mounted beside the layout
|
|
193
|
+
* rather than inside a drag layer. Outside a provider it reports the empty state
|
|
194
|
+
* instead of throwing.
|
|
195
|
+
*/
|
|
197
196
|
const useDragSubject = () => {
|
|
198
|
-
|
|
197
|
+
const actorRef = useContext(DragRootContext)?.actorRef;
|
|
198
|
+
return useSelector(actorRef, (snapshot) => {
|
|
199
|
+
const drag = snapshot?.context.drag;
|
|
200
|
+
return drag?.kind === "dragging" ? drag.subject : null;
|
|
201
|
+
});
|
|
199
202
|
};
|
|
200
203
|
const useDropIntent = () => {
|
|
201
|
-
|
|
204
|
+
const actorRef = useContext(DragRootContext)?.actorRef;
|
|
205
|
+
return useSelector(actorRef, (snapshot) => {
|
|
206
|
+
const drag = snapshot?.context.drag;
|
|
207
|
+
return drag?.kind === "dragging" ? drag.drop?.intent ?? null : null;
|
|
208
|
+
}, sameDropIntent);
|
|
202
209
|
};
|
|
203
210
|
|
|
204
211
|
//#endregion
|
|
@@ -244,28 +251,29 @@ const usePersistence = (config, defaultModel) => {
|
|
|
244
251
|
useEffect(() => {
|
|
245
252
|
configRef.current = config;
|
|
246
253
|
});
|
|
247
|
-
const [
|
|
248
|
-
if (config === null
|
|
254
|
+
const [load] = useState(() => {
|
|
255
|
+
if (config === null) return { kind: "absent" };
|
|
249
256
|
const raw = config.storage.getItem(config.key);
|
|
250
|
-
if (raw === null) return
|
|
257
|
+
if (raw === null) return { kind: "absent" };
|
|
251
258
|
try {
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
259
|
+
return {
|
|
260
|
+
kind: "loaded",
|
|
261
|
+
model: fromJSON(raw)
|
|
262
|
+
};
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
error,
|
|
266
|
+
kind: "corrupt"
|
|
267
|
+
};
|
|
255
268
|
}
|
|
256
269
|
});
|
|
270
|
+
const initialModel = load.kind === "loaded" && defaultModel !== void 0 ? load.model : defaultModel;
|
|
257
271
|
useEffect(() => {
|
|
258
272
|
const current = configRef.current;
|
|
259
|
-
if (current === null) return;
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
fromJSON(raw);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.warn("[dashfoo] discarding unreadable persisted layout", error);
|
|
266
|
-
current.storage.removeItem(current.key);
|
|
267
|
-
}
|
|
268
|
-
}, []);
|
|
273
|
+
if (current === null || load.kind !== "corrupt") return;
|
|
274
|
+
console.warn("[dashfoo] discarding unreadable persisted layout", load.error);
|
|
275
|
+
current.storage.removeItem(current.key);
|
|
276
|
+
}, [load]);
|
|
269
277
|
const loadedKey = useRef(config?.key);
|
|
270
278
|
useEffect(() => {
|
|
271
279
|
if (config !== null && config.key !== loadedKey.current) {
|
|
@@ -393,21 +401,33 @@ const useResponsiveModel = ({ breakpoints }) => {
|
|
|
393
401
|
}, [active, containerRef]);
|
|
394
402
|
};
|
|
395
403
|
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/lib/warn-once.ts
|
|
406
|
+
const warned = /* @__PURE__ */ new Set();
|
|
407
|
+
const warnOnce = (key, message) => {
|
|
408
|
+
if (warned.has(key)) return;
|
|
409
|
+
warned.add(key);
|
|
410
|
+
console.warn(`[dashfoo] ${message}`);
|
|
411
|
+
};
|
|
412
|
+
|
|
396
413
|
//#endregion
|
|
397
414
|
//#region src/hooks/store.ts
|
|
415
|
+
const CONTROLLED_HISTORY_MESSAGE = "undo/redo are unavailable while `model` is controlled; keep the history in the state you own and drive it through onModelChange";
|
|
416
|
+
const CONTROLLED_SET_MODEL_MESSAGE = "setModel (and resetLayout) do nothing while `model` is controlled; set the model you own instead";
|
|
417
|
+
const CONTROLLED_MODE_SWITCH_MESSAGE = "`model` was added or removed after mount; a layout is controlled or uncontrolled for its whole life, so the first render wins. Remount (key=…) to switch";
|
|
418
|
+
const CONTROLLED_WITH_DEFAULT_MESSAGE = "`model` and `defaultModel` were both passed; `model` wins and `defaultModel` (and `persist`) are ignored";
|
|
419
|
+
/** The mode is latched on the first render; running both sources leaves the
|
|
420
|
+
* unread one describing a document nobody rendered. */
|
|
398
421
|
const useDashfooStore = (options) => {
|
|
399
422
|
const { defaultModel, model: controlledModel, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange } = options;
|
|
400
423
|
const initialModel = controlledModel ?? defaultModel;
|
|
401
424
|
if (initialModel === void 0) throw new Error("useDashfooStore requires either a `model` or a `defaultModel`.");
|
|
425
|
+
const [controlled] = useState(() => controlledModel !== void 0);
|
|
426
|
+
if (controlled !== (controlledModel !== void 0)) warnOnce("controlled-mode-switch", CONTROLLED_MODE_SWITCH_MESSAGE);
|
|
427
|
+
if (controlled && defaultModel !== void 0) warnOnce("controlled-with-default", CONTROLLED_WITH_DEFAULT_MESSAGE);
|
|
402
428
|
const actorRef = useActorRef(dashfooMachine, { input: { model: normalize(initialModel) } });
|
|
403
429
|
const history = useSelector(actorRef, (snapshot) => snapshot.context.history);
|
|
404
|
-
|
|
405
|
-
if (controlledModel !== void 0) actorRef.send({
|
|
406
|
-
model: normalize(controlledModel),
|
|
407
|
-
type: "SET_MODEL"
|
|
408
|
-
});
|
|
409
|
-
}, [actorRef, controlledModel]);
|
|
410
|
-
const model = controlledModel ?? history.present;
|
|
430
|
+
const model = useMemo(() => controlledModel === void 0 ? history.present : normalize(controlledModel), [controlledModel, history.present]);
|
|
411
431
|
const notify = useCallback((before, after, action) => {
|
|
412
432
|
if (Object.is(before, after)) return;
|
|
413
433
|
onModelChange?.(after, action);
|
|
@@ -421,61 +441,62 @@ const useDashfooStore = (options) => {
|
|
|
421
441
|
const dispatch = useCallback((action) => {
|
|
422
442
|
const resolved = onAction ? onAction(action) : action;
|
|
423
443
|
if (!resolved) return;
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
after: reducer(controlledModel, resolved),
|
|
438
|
-
before: normalize(controlledModel)
|
|
439
|
-
};
|
|
440
|
-
})();
|
|
441
|
-
notify(transition.before, transition.after, resolved);
|
|
444
|
+
if (controlled) {
|
|
445
|
+
notify(model, reducer(model, resolved), resolved);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const before = actorRef.getSnapshot().context.history.present;
|
|
449
|
+
actorRef.send({
|
|
450
|
+
action: resolved,
|
|
451
|
+
type: "DISPATCH"
|
|
452
|
+
});
|
|
453
|
+
notify(before, actorRef.getSnapshot().context.history.present, resolved);
|
|
442
454
|
}, [
|
|
443
455
|
actorRef,
|
|
444
|
-
|
|
456
|
+
controlled,
|
|
457
|
+
model,
|
|
445
458
|
notify,
|
|
446
459
|
onAction
|
|
447
460
|
]);
|
|
448
461
|
const undo = useCallback(() => {
|
|
449
|
-
if (
|
|
462
|
+
if (controlled) {
|
|
463
|
+
warnOnce("controlled-undo", CONTROLLED_HISTORY_MESSAGE);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
450
466
|
const before = actorRef.getSnapshot().context.history.present;
|
|
451
467
|
actorRef.send({ type: "UNDO" });
|
|
452
|
-
|
|
453
|
-
notify(before, after);
|
|
468
|
+
notify(before, actorRef.getSnapshot().context.history.present);
|
|
454
469
|
}, [
|
|
455
470
|
actorRef,
|
|
456
|
-
|
|
471
|
+
controlled,
|
|
457
472
|
notify
|
|
458
473
|
]);
|
|
459
474
|
const redo = useCallback(() => {
|
|
460
|
-
if (
|
|
475
|
+
if (controlled) {
|
|
476
|
+
warnOnce("controlled-redo", CONTROLLED_HISTORY_MESSAGE);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
461
479
|
const before = actorRef.getSnapshot().context.history.present;
|
|
462
480
|
actorRef.send({ type: "REDO" });
|
|
463
|
-
|
|
464
|
-
notify(before, after);
|
|
481
|
+
notify(before, actorRef.getSnapshot().context.history.present);
|
|
465
482
|
}, [
|
|
466
483
|
actorRef,
|
|
467
|
-
|
|
484
|
+
controlled,
|
|
468
485
|
notify
|
|
469
486
|
]);
|
|
470
487
|
const setModel = useCallback((next) => {
|
|
488
|
+
if (controlled) {
|
|
489
|
+
warnOnce("controlled-set-model", CONTROLLED_SET_MODEL_MESSAGE);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
471
492
|
actorRef.send({
|
|
472
493
|
model: normalize(next),
|
|
473
494
|
type: "SET_MODEL"
|
|
474
495
|
});
|
|
475
|
-
}, [actorRef]);
|
|
496
|
+
}, [actorRef, controlled]);
|
|
476
497
|
return useMemo(() => ({
|
|
477
|
-
canRedo: () => canRedo(actorRef.getSnapshot().context.history),
|
|
478
|
-
canUndo: () => canUndo(actorRef.getSnapshot().context.history),
|
|
498
|
+
canRedo: () => !controlled && canRedo(actorRef.getSnapshot().context.history),
|
|
499
|
+
canUndo: () => !controlled && canUndo(actorRef.getSnapshot().context.history),
|
|
479
500
|
dispatch,
|
|
480
501
|
model,
|
|
481
502
|
redo,
|
|
@@ -483,6 +504,7 @@ const useDashfooStore = (options) => {
|
|
|
483
504
|
undo
|
|
484
505
|
}), [
|
|
485
506
|
actorRef,
|
|
507
|
+
controlled,
|
|
486
508
|
dispatch,
|
|
487
509
|
model,
|
|
488
510
|
redo,
|
|
@@ -491,6 +513,46 @@ const useDashfooStore = (options) => {
|
|
|
491
513
|
]);
|
|
492
514
|
};
|
|
493
515
|
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/lib/drag-subject.ts
|
|
518
|
+
const isTabFactory = (value) => typeof value === "function";
|
|
519
|
+
/** Beside the manager wiring, not the layer adapter: `dragstart` fires once per
|
|
520
|
+
* gesture on the shared manager, above any one layer. */
|
|
521
|
+
const subjectFor = (source) => {
|
|
522
|
+
const data = source.data;
|
|
523
|
+
if (data?.type === "tabset") return {
|
|
524
|
+
id: String(data.tabsetId),
|
|
525
|
+
kind: "tabset"
|
|
526
|
+
};
|
|
527
|
+
if (data?.type === "external") {
|
|
528
|
+
if (!isTabFactory(data.createTab)) {
|
|
529
|
+
console.warn("[dashfoo] external drag source is missing its createTab function");
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
let candidate;
|
|
533
|
+
try {
|
|
534
|
+
candidate = data.createTab();
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.warn("[dashfoo] external drag source createTab threw", error);
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
const parsed = tabNodeSchema.safeParse(candidate);
|
|
540
|
+
if (!parsed.success) {
|
|
541
|
+
console.warn("[dashfoo] external drag source returned an invalid tab", parsed.error);
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
return {
|
|
545
|
+
id: String(source.id),
|
|
546
|
+
kind: "external",
|
|
547
|
+
tab: parsed.data
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
id: String(source.id),
|
|
552
|
+
kind: "tab"
|
|
553
|
+
};
|
|
554
|
+
};
|
|
555
|
+
|
|
494
556
|
//#endregion
|
|
495
557
|
//#region src/lib/tab-insertion.ts
|
|
496
558
|
const pointInRect = (point, rect) => point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
@@ -517,7 +579,7 @@ const shouldAllowDrop = (draggedId, targetId, tabIds) => {
|
|
|
517
579
|
};
|
|
518
580
|
|
|
519
581
|
//#endregion
|
|
520
|
-
//#region src/components/drag-
|
|
582
|
+
//#region src/components/drag-preview-overlay.tsx
|
|
521
583
|
const overlayBase = {
|
|
522
584
|
boxSizing: "border-box",
|
|
523
585
|
pointerEvents: "none",
|
|
@@ -541,9 +603,10 @@ const lineStyle = (zone) => ({
|
|
|
541
603
|
});
|
|
542
604
|
const tabItemRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab-item\"]")].flatMap((item) => item.querySelector("[data-dashfoo=\"tab\"]")?.dataset.tabId === excludeId ? [] : [item.getBoundingClientRect()]);
|
|
543
605
|
const DockIndicator = ({ actorRef, getTabsetElement }) => {
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
606
|
+
const drag = useSelector(actorRef, (snapshot) => snapshot.context.drag);
|
|
607
|
+
if (drag.kind !== "dragging" || drag.drop === null) return null;
|
|
608
|
+
const { intent } = drag.drop;
|
|
609
|
+
const draggedId = drag.subject.id;
|
|
547
610
|
const element = getTabsetElement(intent.targetId);
|
|
548
611
|
if (!element) return null;
|
|
549
612
|
const zIndex = element.closest("[data-dashfoo=\"float\"]") ? overlayBase.zIndex : "auto";
|
|
@@ -599,12 +662,13 @@ const DragPreviewOverlay = ({ manager }) => {
|
|
|
599
662
|
offEnd();
|
|
600
663
|
};
|
|
601
664
|
}, [manager]);
|
|
665
|
+
const attachOverlay = useCallback((element) => {
|
|
666
|
+
const feedback = manager.registry.plugins.get(Feedback);
|
|
667
|
+
if (feedback) feedback.overlay = element ?? void 0;
|
|
668
|
+
}, [manager]);
|
|
602
669
|
return /* @__PURE__ */ jsx("div", {
|
|
603
670
|
"data-dnd-overlay": "",
|
|
604
|
-
ref:
|
|
605
|
-
const feedback = manager.registry.plugins.get(Feedback);
|
|
606
|
-
if (feedback) feedback.overlay = element ?? void 0;
|
|
607
|
-
}, [manager]),
|
|
671
|
+
ref: attachOverlay,
|
|
608
672
|
children: chip === null ? null : /* @__PURE__ */ jsx("div", {
|
|
609
673
|
"data-dashfoo": "drag-preview",
|
|
610
674
|
style: {
|
|
@@ -620,166 +684,45 @@ const DragPreviewOverlay = ({ manager }) => {
|
|
|
620
684
|
};
|
|
621
685
|
|
|
622
686
|
//#endregion
|
|
623
|
-
//#region src/components/drag-
|
|
687
|
+
//#region src/components/dashfoo-drag-provider.tsx
|
|
624
688
|
const DashfooDragProvider = ({ children }) => {
|
|
625
689
|
const [manager] = useState(createDragManager);
|
|
626
690
|
useInsertionEffect(() => () => {
|
|
627
691
|
manager.destroy();
|
|
628
692
|
}, [manager]);
|
|
629
|
-
const [subjectStore] = useState(createDragSubjectStore);
|
|
630
|
-
return /* @__PURE__ */ jsx(SharedDragManagerContext.Provider, {
|
|
631
|
-
value: manager,
|
|
632
|
-
children: /* @__PURE__ */ jsxs(DragSubjectStoreContext.Provider, {
|
|
633
|
-
value: subjectStore,
|
|
634
|
-
children: [children, /* @__PURE__ */ jsx(DragPreviewOverlay, { manager })]
|
|
635
|
-
})
|
|
636
|
-
});
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
//#endregion
|
|
640
|
-
//#region src/hooks/layout-store.ts
|
|
641
|
-
const createLayoutStore = (initial) => createStore(() => initial);
|
|
642
|
-
const LayoutStoreContext = createContext(null);
|
|
643
|
-
const useLayout = (selector) => {
|
|
644
|
-
const store = useContext(LayoutStoreContext);
|
|
645
|
-
if (store === null) throw new Error("dashfoo components must be rendered inside <DashfooLayout> or <Layout.Root>.");
|
|
646
|
-
return useStore(store, selector);
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
//#endregion
|
|
650
|
-
//#region src/lib/warn-once.ts
|
|
651
|
-
const warned = /* @__PURE__ */ new Set();
|
|
652
|
-
const warnOnce = (key, message) => {
|
|
653
|
-
if (warned.has(key)) return;
|
|
654
|
-
warned.add(key);
|
|
655
|
-
console.warn(`[dashfoo] ${message}`);
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
//#endregion
|
|
659
|
-
//#region src/components/drag-adapter.tsx
|
|
660
|
-
const tabRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab\"]")].flatMap((tab) => tab.dataset.tabId === excludeId ? [] : [tab.getBoundingClientRect()]);
|
|
661
|
-
const intentForTabset = (id, element, point, draggedId) => {
|
|
662
|
-
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
663
|
-
if (strip && pointInRect(point, strip.getBoundingClientRect())) return {
|
|
664
|
-
index: insertionIndex(tabRects(strip, draggedId), point.x),
|
|
665
|
-
location: "center",
|
|
666
|
-
targetId: id
|
|
667
|
-
};
|
|
668
|
-
const target = resolveDockTarget(point, element.getBoundingClientRect());
|
|
669
|
-
const location = target.kind === "tab" ? "center" : `split-${target.edge}`;
|
|
670
|
-
if (location === "center" && strip) return {
|
|
671
|
-
index: tabRects(strip, draggedId).length,
|
|
672
|
-
location,
|
|
673
|
-
targetId: id
|
|
674
|
-
};
|
|
675
|
-
return {
|
|
676
|
-
location,
|
|
677
|
-
targetId: id
|
|
678
|
-
};
|
|
679
|
-
};
|
|
680
|
-
const isTabFactory = (value) => typeof value === "function";
|
|
681
|
-
const subjectFor = (source) => {
|
|
682
|
-
const data = source.data;
|
|
683
|
-
if (data?.type === "tabset") return {
|
|
684
|
-
id: String(data.tabsetId),
|
|
685
|
-
kind: "tabset"
|
|
686
|
-
};
|
|
687
|
-
if (data?.type === "external") {
|
|
688
|
-
if (!isTabFactory(data.createTab)) {
|
|
689
|
-
console.warn("[dashfoo] external drag source is missing its createTab function");
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
let candidate;
|
|
693
|
-
try {
|
|
694
|
-
candidate = data.createTab();
|
|
695
|
-
} catch (error) {
|
|
696
|
-
console.warn("[dashfoo] external drag source createTab threw", error);
|
|
697
|
-
return null;
|
|
698
|
-
}
|
|
699
|
-
const parsed = tabNodeSchema.safeParse(candidate);
|
|
700
|
-
if (!parsed.success) {
|
|
701
|
-
console.warn("[dashfoo] external drag source returned an invalid tab", parsed.error);
|
|
702
|
-
return null;
|
|
703
|
-
}
|
|
704
|
-
return {
|
|
705
|
-
id: String(source.id),
|
|
706
|
-
kind: "external",
|
|
707
|
-
tab: parsed.data
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
return {
|
|
711
|
-
id: String(source.id),
|
|
712
|
-
kind: "tab"
|
|
713
|
-
};
|
|
714
|
-
};
|
|
715
|
-
const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
716
693
|
const actorRef = useActorRef(dragDockMachine);
|
|
717
|
-
const
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
useInsertionEffect(() => () => ownManager?.destroy(), [ownManager]);
|
|
721
|
-
const manager = sharedManager ?? ownManager;
|
|
722
|
-
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");
|
|
723
|
-
const inheritedStore = useContext(DragSubjectStoreContext);
|
|
724
|
-
const [subjectStore] = useState(() => inheritedStore ?? createDragSubjectStore());
|
|
725
|
-
const ownerId = useId();
|
|
726
|
-
useEffect(() => {
|
|
727
|
-
const subscription = actorRef.subscribe((snapshot) => {
|
|
728
|
-
const { intent, subject } = snapshot.context;
|
|
729
|
-
const state = subjectStore.getState();
|
|
730
|
-
if (intent !== null) {
|
|
731
|
-
if (!sameDropIntent(state.intent, intent) || state.intentOwner !== ownerId) subjectStore.setState({
|
|
732
|
-
intent,
|
|
733
|
-
intentOwner: ownerId
|
|
734
|
-
});
|
|
735
|
-
} else if (state.intent !== null && state.intentOwner === ownerId) subjectStore.setState({
|
|
736
|
-
intent: null,
|
|
737
|
-
intentOwner: null
|
|
738
|
-
});
|
|
739
|
-
if (!sameDragSubject(subjectStore.getState().subject, subject)) subjectStore.setState({ subject });
|
|
740
|
-
});
|
|
741
|
-
return () => {
|
|
742
|
-
subscription.unsubscribe();
|
|
743
|
-
};
|
|
744
|
-
}, [
|
|
745
|
-
actorRef,
|
|
746
|
-
ownerId,
|
|
747
|
-
subjectStore
|
|
748
|
-
]);
|
|
749
|
-
const onCommitRef = useRef(onCommit);
|
|
750
|
-
const splitDockRef = useRef(splitDock);
|
|
751
|
-
useEffect(() => {
|
|
752
|
-
onCommitRef.current = onCommit;
|
|
753
|
-
splitDockRef.current = splitDock;
|
|
754
|
-
});
|
|
755
|
-
const getTabsetElement = useCallback((id) => {
|
|
756
|
-
const element = manager.registry.droppables.get(`${ownerId}:${id}`)?.element;
|
|
757
|
-
return element instanceof HTMLElement ? element : void 0;
|
|
758
|
-
}, [manager, ownerId]);
|
|
759
|
-
useEffect(() => {
|
|
760
|
-
const subscription = actorRef.on("COMMIT", (emitted) => {
|
|
761
|
-
if (layoutStore?.getState().editable === false) return;
|
|
762
|
-
const commit = onCommitRef.current ?? layoutStore?.getState().dispatch;
|
|
763
|
-
if (!commit) {
|
|
764
|
-
warnOnce("drag-layer-no-commit", "Layout.DragLayer has neither an onCommit prop nor a <Layout.Root> above it; drops are ignored");
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
commit(emitted.action);
|
|
768
|
-
});
|
|
694
|
+
const [scopes] = useState(() => /* @__PURE__ */ new Map());
|
|
695
|
+
const registerScope = useCallback((scope) => {
|
|
696
|
+
scopes.set(scope.layerId, scope);
|
|
769
697
|
return () => {
|
|
770
|
-
|
|
698
|
+
scopes.delete(scope.layerId);
|
|
771
699
|
};
|
|
772
|
-
}, [
|
|
700
|
+
}, [scopes]);
|
|
773
701
|
useEffect(() => {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
702
|
+
const scopedTarget = () => {
|
|
703
|
+
const target = manager.dragOperation.target;
|
|
704
|
+
const data = target?.data;
|
|
705
|
+
const scope = data?.layerId === void 0 ? void 0 : scopes.get(data.layerId);
|
|
706
|
+
const element = target?.element;
|
|
707
|
+
if (!scope || data?.tabsetId === void 0 || !(element instanceof HTMLElement)) return null;
|
|
708
|
+
return {
|
|
709
|
+
element,
|
|
710
|
+
scope,
|
|
711
|
+
tabsetId: data.tabsetId
|
|
781
712
|
};
|
|
782
|
-
|
|
713
|
+
};
|
|
714
|
+
const syncIntent = () => {
|
|
715
|
+
const operation = manager.dragOperation;
|
|
716
|
+
const found = scopedTarget();
|
|
717
|
+
const draggedId = operation.source ? String(operation.source.id) : void 0;
|
|
718
|
+
const intent = found === null ? null : found.scope.resolveIntent(found.tabsetId, found.element, operation.position.current, draggedId);
|
|
719
|
+
actorRef.send({
|
|
720
|
+
drop: intent === null || found === null ? null : {
|
|
721
|
+
intent,
|
|
722
|
+
scope: found.scope.layerId
|
|
723
|
+
},
|
|
724
|
+
type: "OVER"
|
|
725
|
+
});
|
|
783
726
|
};
|
|
784
727
|
const handleStart = (event) => {
|
|
785
728
|
const source = event.operation.source;
|
|
@@ -794,26 +737,22 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
794
737
|
type: "START"
|
|
795
738
|
});
|
|
796
739
|
};
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
const element = tabsetId === void 0 ? void 0 : target?.element;
|
|
804
|
-
const draggedId = operation.source ? String(operation.source.id) : void 0;
|
|
805
|
-
actorRef.send({
|
|
806
|
-
intent: tabsetId !== void 0 && element instanceof HTMLElement ? resolveIntent(tabsetId, element, point, draggedId) : null,
|
|
807
|
-
type: "OVER"
|
|
740
|
+
let frame = null;
|
|
741
|
+
const scheduleResolve = () => {
|
|
742
|
+
if (frame !== null) return;
|
|
743
|
+
frame = requestAnimationFrame(() => {
|
|
744
|
+
frame = null;
|
|
745
|
+
syncIntent();
|
|
808
746
|
});
|
|
809
747
|
};
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
748
|
+
const cancelPendingResolve = () => {
|
|
749
|
+
if (frame !== null) {
|
|
750
|
+
cancelAnimationFrame(frame);
|
|
751
|
+
frame = null;
|
|
752
|
+
}
|
|
815
753
|
};
|
|
816
754
|
const handleEnd = (event) => {
|
|
755
|
+
cancelPendingResolve();
|
|
817
756
|
if (event.canceled) {
|
|
818
757
|
actorRef.send({ type: "CANCEL" });
|
|
819
758
|
return;
|
|
@@ -821,11 +760,16 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
821
760
|
syncIntent();
|
|
822
761
|
actorRef.send({ type: "DROP" });
|
|
823
762
|
};
|
|
763
|
+
const commitSubscription = actorRef.on("COMMIT", (emitted) => {
|
|
764
|
+
scopes.get(emitted.scope)?.commit(emitted.action);
|
|
765
|
+
});
|
|
824
766
|
const offStart = manager.monitor.addEventListener("dragstart", handleStart);
|
|
825
|
-
const offMove = manager.monitor.addEventListener("dragmove",
|
|
826
|
-
const offCollision = manager.monitor.addEventListener("collision",
|
|
767
|
+
const offMove = manager.monitor.addEventListener("dragmove", scheduleResolve);
|
|
768
|
+
const offCollision = manager.monitor.addEventListener("collision", scheduleResolve);
|
|
827
769
|
const offEnd = manager.monitor.addEventListener("dragend", handleEnd);
|
|
828
770
|
return () => {
|
|
771
|
+
cancelPendingResolve();
|
|
772
|
+
commitSubscription.unsubscribe();
|
|
829
773
|
offStart();
|
|
830
774
|
offMove();
|
|
831
775
|
offCollision();
|
|
@@ -833,114 +777,144 @@ const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
|
833
777
|
};
|
|
834
778
|
}, [
|
|
835
779
|
actorRef,
|
|
836
|
-
layoutStore,
|
|
837
780
|
manager,
|
|
838
|
-
|
|
781
|
+
scopes
|
|
839
782
|
]);
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
manager
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
ownManager === null ? null : /* @__PURE__ */ jsx(DragPreviewOverlay, { manager: ownManager })
|
|
855
|
-
]
|
|
783
|
+
const rootValue = useMemo(() => ({
|
|
784
|
+
actorRef,
|
|
785
|
+
manager,
|
|
786
|
+
registerScope
|
|
787
|
+
}), [
|
|
788
|
+
actorRef,
|
|
789
|
+
manager,
|
|
790
|
+
registerScope
|
|
791
|
+
]);
|
|
792
|
+
return /* @__PURE__ */ jsx(SharedDragManagerContext.Provider, {
|
|
793
|
+
value: manager,
|
|
794
|
+
children: /* @__PURE__ */ jsxs(DragRootContext.Provider, {
|
|
795
|
+
value: rootValue,
|
|
796
|
+
children: [children, /* @__PURE__ */ jsx(DragPreviewOverlay, { manager })]
|
|
856
797
|
})
|
|
857
798
|
});
|
|
858
799
|
};
|
|
859
800
|
|
|
860
801
|
//#endregion
|
|
861
|
-
//#region src/
|
|
862
|
-
const
|
|
863
|
-
const
|
|
802
|
+
//#region src/hooks/layout-store.ts
|
|
803
|
+
const createLayoutStore = (initial) => createStore(() => initial);
|
|
804
|
+
const LayoutStoreContext = createContext(null);
|
|
805
|
+
const useLayout = (selector) => {
|
|
806
|
+
const store = useContext(LayoutStoreContext);
|
|
807
|
+
if (store === null) throw new Error("dashfoo components must be rendered inside <DashfooLayout> or <Layout.Root>.");
|
|
808
|
+
return useStore(store, selector);
|
|
809
|
+
};
|
|
864
810
|
|
|
865
811
|
//#endregion
|
|
866
|
-
//#region src/
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
812
|
+
//#region src/components/drag-provider.tsx
|
|
813
|
+
const MISSING_ROOT = "[dashfoo] DashfooDragProvider unmounted while its layout is still mounted; keep the provider above the layout or remount the layout";
|
|
814
|
+
/** One DOM pass per pointer move, where resolving a drop used to run three. */
|
|
815
|
+
const measureStrip = (element, excludeId) => {
|
|
816
|
+
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
817
|
+
if (!strip) return null;
|
|
818
|
+
const tabIds = [];
|
|
819
|
+
const tabRects = [];
|
|
820
|
+
for (const tab of strip.querySelectorAll("[data-dashfoo=\"tab\"]")) {
|
|
821
|
+
const tabId = tab.dataset.tabId ?? "";
|
|
822
|
+
tabIds.push(tabId);
|
|
823
|
+
if (tabId !== excludeId) tabRects.push(tab.getBoundingClientRect());
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
rect: strip.getBoundingClientRect(),
|
|
827
|
+
tabIds,
|
|
828
|
+
tabRects
|
|
877
829
|
};
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
onDone();
|
|
885
|
-
}
|
|
830
|
+
};
|
|
831
|
+
const intentForTabset = (id, element, point, strip) => {
|
|
832
|
+
if (strip && pointInRect(point, strip.rect)) return {
|
|
833
|
+
index: insertionIndex(strip.tabRects, point.x),
|
|
834
|
+
location: "center",
|
|
835
|
+
targetId: id
|
|
886
836
|
};
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
837
|
+
const location = resolveDockTarget(point, element.getBoundingClientRect());
|
|
838
|
+
if (location === "center" && strip) return {
|
|
839
|
+
index: strip.tabRects.length,
|
|
840
|
+
location,
|
|
841
|
+
targetId: id
|
|
891
842
|
};
|
|
892
843
|
return {
|
|
893
|
-
|
|
894
|
-
|
|
844
|
+
location,
|
|
845
|
+
targetId: id
|
|
895
846
|
};
|
|
896
847
|
};
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
const
|
|
903
|
-
const
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
848
|
+
/** One drag layer (the main tree, or one float). Holds no drag state of its own. */
|
|
849
|
+
const DragScope = ({ children, onCommit, splitDock }) => {
|
|
850
|
+
const root = useContext(DragRootContext);
|
|
851
|
+
const layoutStore = useContext(LayoutStoreContext);
|
|
852
|
+
const layerId = useId();
|
|
853
|
+
const manager = root?.manager;
|
|
854
|
+
const getTabsetElement = useCallback((id) => {
|
|
855
|
+
const element = manager?.registry.droppables.get(`${layerId}:${id}`)?.element;
|
|
856
|
+
return element instanceof HTMLElement ? element : void 0;
|
|
857
|
+
}, [layerId, manager]);
|
|
858
|
+
const scope = useMemo(() => ({
|
|
859
|
+
commit: (action) => {
|
|
860
|
+
if (layoutStore?.getState().editable === false) return;
|
|
861
|
+
const commit = onCommit ?? layoutStore?.getState().dispatch;
|
|
862
|
+
if (!commit) {
|
|
863
|
+
warnOnce("drag-layer-no-commit", "Layout.DragLayer has neither an onCommit prop nor a <Layout.Root> above it; drops are ignored");
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
commit(action);
|
|
867
|
+
},
|
|
868
|
+
layerId,
|
|
869
|
+
resolveIntent: (targetId, element, point, draggedId) => {
|
|
870
|
+
if (layoutStore?.getState().editable === false) return null;
|
|
871
|
+
const strip = measureStrip(element, draggedId);
|
|
872
|
+
if (!shouldAllowDrop(draggedId, targetId, strip?.tabIds ?? [])) return null;
|
|
873
|
+
const intent = intentForTabset(targetId, element, point, strip);
|
|
874
|
+
if (!(splitDock ?? layoutStore?.getState().splitDock ?? true) && splitEdge(intent.location) !== void 0) return {
|
|
875
|
+
location: "center",
|
|
876
|
+
targetId
|
|
877
|
+
};
|
|
878
|
+
return intent;
|
|
879
|
+
}
|
|
880
|
+
}), [
|
|
881
|
+
layerId,
|
|
882
|
+
layoutStore,
|
|
883
|
+
onCommit,
|
|
884
|
+
splitDock
|
|
885
|
+
]);
|
|
886
|
+
const registerScope = root?.registerScope;
|
|
887
|
+
useEffect(() => registerScope?.(scope), [registerScope, scope]);
|
|
888
|
+
const contextValue = useMemo(() => root === null ? null : {
|
|
889
|
+
actorRef: root.actorRef,
|
|
890
|
+
layerId,
|
|
891
|
+
manager: root.manager
|
|
892
|
+
}, [layerId, root]);
|
|
893
|
+
if (contextValue === null) throw new Error(MISSING_ROOT);
|
|
894
|
+
return /* @__PURE__ */ jsxs(DragContext.Provider, {
|
|
895
|
+
value: contextValue,
|
|
896
|
+
children: [children, /* @__PURE__ */ jsx(DockIndicator, {
|
|
897
|
+
actorRef: contextValue.actorRef,
|
|
898
|
+
getTabsetElement
|
|
899
|
+
})]
|
|
922
900
|
});
|
|
923
901
|
};
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
height = Math.max(MIN_HEIGHT, start.height - dy);
|
|
934
|
-
top = Math.max(0, start.top + (start.height - height));
|
|
935
|
-
}
|
|
936
|
-
return {
|
|
937
|
-
height,
|
|
938
|
-
left,
|
|
939
|
-
top,
|
|
940
|
-
width
|
|
941
|
-
};
|
|
902
|
+
/**
|
|
903
|
+
* Mounts its own root when there is none above it. Decided on the first render:
|
|
904
|
+
* a root that disappears later means a torn-down tree, which `DragScope` reports.
|
|
905
|
+
*/
|
|
906
|
+
const DragProvider = (props) => {
|
|
907
|
+
const hasRoot = useContext(DragRootContext) !== null;
|
|
908
|
+
const [ownsRoot] = useState(() => !hasRoot);
|
|
909
|
+
const layer = /* @__PURE__ */ jsx(DragScope, { ...props });
|
|
910
|
+
return ownsRoot ? /* @__PURE__ */ jsx(DashfooDragProvider, { children: layer }) : layer;
|
|
942
911
|
};
|
|
943
912
|
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/components/float-context.ts
|
|
915
|
+
const FloatLayerContext = createContext(false);
|
|
916
|
+
const useHasFloatLayer = () => useContext(FloatLayerContext);
|
|
917
|
+
|
|
944
918
|
//#endregion
|
|
945
919
|
//#region src/components/float-resize-handles.ts
|
|
946
920
|
const EDGE_THICKNESS = 12;
|
|
@@ -1061,122 +1035,527 @@ const RESIZE_HANDLES = [
|
|
|
1061
1035
|
const EDGE_BY_KEY = new Map(RESIZE_HANDLES.map((h) => [h.key, h.edges]));
|
|
1062
1036
|
|
|
1063
1037
|
//#endregion
|
|
1064
|
-
//#region src/
|
|
1065
|
-
const
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
maximizedTabsetId: model.maximizedTabsetId,
|
|
1084
|
-
renamableTabs: editable && renamableTabs && global.tabEnableRename !== false,
|
|
1085
|
-
renderTab,
|
|
1086
|
-
renderTabLabel,
|
|
1087
|
-
renderTabsetToolbar,
|
|
1088
|
-
resizableSplits: editable && resizableSplits && global.enableSplitResize !== false,
|
|
1089
|
-
snap: snap ?? global.snap ?? null,
|
|
1090
|
-
splitDock: global.enableSplitDock !== false,
|
|
1091
|
-
tabLocation: global.tabLocation ?? "top",
|
|
1092
|
-
tabsetMinSize: global.tabSetMinSize ?? DEFAULT_TABSET_MIN_SIZE,
|
|
1093
|
-
tabStripEnabled: global.tabSetEnableTabStrip !== false
|
|
1038
|
+
//#region src/lib/float-geometry.ts
|
|
1039
|
+
const MIN_WIDTH = 200;
|
|
1040
|
+
const MIN_HEIGHT = 120;
|
|
1041
|
+
const DEFAULT_WIDTH = 480;
|
|
1042
|
+
const DEFAULT_HEIGHT = 340;
|
|
1043
|
+
const LIFT_OFFSET = 16;
|
|
1044
|
+
const clampToBounds = (rect, bounds, size = rect) => ({
|
|
1045
|
+
height: rect.height,
|
|
1046
|
+
left: Math.max(0, Math.min(rect.left, Math.max(0, bounds.width - size.width))),
|
|
1047
|
+
top: Math.max(0, Math.min(rect.top, Math.max(0, bounds.height - size.height))),
|
|
1048
|
+
width: rect.width
|
|
1049
|
+
});
|
|
1050
|
+
const measureFloatRect = (element) => {
|
|
1051
|
+
const rect = (element.closest("[data-dashfoo=\"tabset\"]") ?? element).getBoundingClientRect();
|
|
1052
|
+
const seeded = {
|
|
1053
|
+
height: Math.max(MIN_HEIGHT, Math.min(Math.round(rect.height), DEFAULT_HEIGHT)),
|
|
1054
|
+
left: Math.round(rect.left) + LIFT_OFFSET,
|
|
1055
|
+
top: Math.round(rect.top) + LIFT_OFFSET,
|
|
1056
|
+
width: Math.max(MIN_WIDTH, Math.min(Math.round(rect.width), DEFAULT_WIDTH))
|
|
1094
1057
|
};
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1058
|
+
return clampToBounds(seeded, {
|
|
1059
|
+
height: window.innerHeight,
|
|
1060
|
+
width: window.innerWidth
|
|
1098
1061
|
});
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1062
|
+
};
|
|
1063
|
+
const resizeRect = (start, edges, dx, dy) => {
|
|
1064
|
+
let { height, left, top, width } = start;
|
|
1065
|
+
if (edges.x === 1) width = Math.max(MIN_WIDTH, start.width + dx);
|
|
1066
|
+
else if (edges.x === -1) {
|
|
1067
|
+
width = Math.max(MIN_WIDTH, start.width - dx);
|
|
1068
|
+
left = Math.max(0, start.left + (start.width - width));
|
|
1069
|
+
}
|
|
1070
|
+
if (edges.y === 1) height = Math.max(MIN_HEIGHT, start.height + dy);
|
|
1071
|
+
else if (edges.y === -1) {
|
|
1072
|
+
height = Math.max(MIN_HEIGHT, start.height - dy);
|
|
1073
|
+
top = Math.max(0, start.top + (start.height - height));
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
height,
|
|
1077
|
+
left,
|
|
1078
|
+
top,
|
|
1079
|
+
width
|
|
1106
1080
|
};
|
|
1107
|
-
return /* @__PURE__ */ jsx(LayoutStoreContext.Provider, {
|
|
1108
|
-
value: store,
|
|
1109
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
1110
|
-
...props,
|
|
1111
|
-
"data-dashfoo": "layout",
|
|
1112
|
-
ref: rootRef,
|
|
1113
|
-
style: layoutStyle,
|
|
1114
|
-
children
|
|
1115
|
-
})
|
|
1116
|
-
});
|
|
1117
1081
|
};
|
|
1118
1082
|
|
|
1119
1083
|
//#endregion
|
|
1120
|
-
//#region src/
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1084
|
+
//#region src/hooks/use-float-gesture.ts
|
|
1085
|
+
const CHIP_SIZE = {
|
|
1086
|
+
height: 34,
|
|
1087
|
+
width: 168
|
|
1088
|
+
};
|
|
1089
|
+
const TAP_SLOP = 4;
|
|
1090
|
+
/** Writes inline style and dispatches only on pointer-up: routing every move
|
|
1091
|
+
* through the model would re-render the whole tree behind the float. */
|
|
1092
|
+
const useFloatGesture = ({ editable, geometry, minimized, onCommit, onTap }) => {
|
|
1093
|
+
const panelRef = useRef(null);
|
|
1094
|
+
const gestureRef = useRef(null);
|
|
1095
|
+
const setPanel = (element) => {
|
|
1096
|
+
panelRef.current = element;
|
|
1097
|
+
};
|
|
1098
|
+
const handlePointerDown = (event) => {
|
|
1099
|
+
const panel = panelRef.current;
|
|
1100
|
+
if (!panel || !editable) return;
|
|
1101
|
+
const prior = gestureRef.current;
|
|
1102
|
+
if (prior && panel.hasPointerCapture?.(prior.pointerId)) panel.releasePointerCapture(prior.pointerId);
|
|
1103
|
+
const edgeKey = event.currentTarget.dataset.edge;
|
|
1104
|
+
const parent = panel.offsetParent instanceof HTMLElement ? panel.offsetParent : null;
|
|
1105
|
+
const gesture = {
|
|
1106
|
+
bounds: {
|
|
1107
|
+
height: parent?.clientHeight ?? 0,
|
|
1108
|
+
width: parent?.clientWidth ?? 0
|
|
1109
|
+
},
|
|
1110
|
+
edges: edgeKey === void 0 ? null : EDGE_BY_KEY.get(edgeKey) ?? null,
|
|
1111
|
+
latest: geometry,
|
|
1112
|
+
moved: false,
|
|
1113
|
+
pointerId: event.pointerId,
|
|
1114
|
+
start: geometry,
|
|
1115
|
+
startX: event.clientX,
|
|
1116
|
+
startY: event.clientY
|
|
1152
1117
|
};
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1118
|
+
gestureRef.current = gesture;
|
|
1119
|
+
if (gesture.edges) panel.setPointerCapture(event.pointerId);
|
|
1120
|
+
};
|
|
1121
|
+
const handlePointerUp = (event) => {
|
|
1122
|
+
const gesture = gestureRef.current;
|
|
1123
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
1124
|
+
gestureRef.current = null;
|
|
1125
|
+
const panel = panelRef.current;
|
|
1126
|
+
if (panel?.hasPointerCapture?.(event.pointerId) === true) panel.releasePointerCapture(event.pointerId);
|
|
1127
|
+
if (!gesture.moved) {
|
|
1128
|
+
onTap();
|
|
1159
1129
|
return;
|
|
1160
1130
|
}
|
|
1161
|
-
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1131
|
+
onCommit(gesture.latest);
|
|
1132
|
+
};
|
|
1133
|
+
const handlePointerMove = (event) => {
|
|
1134
|
+
const gesture = gestureRef.current;
|
|
1135
|
+
const panel = panelRef.current;
|
|
1136
|
+
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
1137
|
+
if (event.buttons === 0) {
|
|
1138
|
+
handlePointerUp(event);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const dx = event.clientX - gesture.startX;
|
|
1142
|
+
const dy = event.clientY - gesture.startY;
|
|
1143
|
+
if (!gesture.moved) {
|
|
1144
|
+
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
1145
|
+
gesture.moved = true;
|
|
1146
|
+
if (!gesture.edges) panel.setPointerCapture(gesture.pointerId);
|
|
1147
|
+
}
|
|
1148
|
+
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
1149
|
+
...gesture.start,
|
|
1150
|
+
left: gesture.start.left + dx,
|
|
1151
|
+
top: gesture.start.top + dy
|
|
1152
|
+
}, gesture.bounds, minimized ? CHIP_SIZE : void 0);
|
|
1153
|
+
gesture.latest = next;
|
|
1154
|
+
panel.style.left = `${next.left}px`;
|
|
1155
|
+
panel.style.top = `${next.top}px`;
|
|
1156
|
+
if (!minimized) {
|
|
1157
|
+
panel.style.width = `${next.width}px`;
|
|
1158
|
+
panel.style.height = `${next.height}px`;
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
const handlePointerCancel = (event) => {
|
|
1162
|
+
const gesture = gestureRef.current;
|
|
1163
|
+
const panel = panelRef.current;
|
|
1164
|
+
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
1165
|
+
gestureRef.current = null;
|
|
1166
|
+
if (panel.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
1167
|
+
if (!gesture.moved) return;
|
|
1168
|
+
panel.style.left = `${gesture.start.left}px`;
|
|
1169
|
+
panel.style.top = `${gesture.start.top}px`;
|
|
1170
|
+
if (!minimized) {
|
|
1171
|
+
panel.style.width = `${gesture.start.width}px`;
|
|
1172
|
+
panel.style.height = `${gesture.start.height}px`;
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
return {
|
|
1176
|
+
handlers: {
|
|
1177
|
+
onPointerCancel: handlePointerCancel,
|
|
1178
|
+
onPointerDown: handlePointerDown,
|
|
1179
|
+
onPointerMove: handlePointerMove,
|
|
1180
|
+
onPointerUp: handlePointerUp
|
|
1181
|
+
},
|
|
1182
|
+
ref: setPanel,
|
|
1183
|
+
style: minimized ? {
|
|
1184
|
+
left: geometry.left,
|
|
1185
|
+
top: geometry.top
|
|
1186
|
+
} : {
|
|
1187
|
+
height: geometry.height,
|
|
1188
|
+
left: geometry.left,
|
|
1189
|
+
top: geometry.top,
|
|
1190
|
+
width: geometry.width
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
//#endregion
|
|
1196
|
+
//#region src/hooks/use-inline-rename.ts
|
|
1197
|
+
const useInlineRename = ({ currentName, inputRef, onCommit, onDone }) => {
|
|
1198
|
+
const done = useRef(false);
|
|
1199
|
+
useEffect(() => {
|
|
1200
|
+
inputRef.current?.focus();
|
|
1201
|
+
inputRef.current?.select();
|
|
1202
|
+
}, [inputRef]);
|
|
1203
|
+
const commit = (value) => {
|
|
1204
|
+
const trimmed = value.trim();
|
|
1205
|
+
if (trimmed && trimmed !== currentName) onCommit(trimmed);
|
|
1206
|
+
onDone();
|
|
1207
|
+
};
|
|
1208
|
+
const handleKeyDown = (event) => {
|
|
1209
|
+
if (event.key === "Enter") {
|
|
1210
|
+
done.current = true;
|
|
1211
|
+
commit(event.currentTarget.value);
|
|
1212
|
+
} else if (event.key === "Escape") {
|
|
1213
|
+
done.current = true;
|
|
1214
|
+
onDone();
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
const handleBlur = (event) => {
|
|
1218
|
+
if (done.current) return;
|
|
1219
|
+
done.current = true;
|
|
1220
|
+
commit(event.currentTarget.value);
|
|
1221
|
+
};
|
|
1222
|
+
return {
|
|
1223
|
+
handleBlur,
|
|
1224
|
+
handleKeyDown
|
|
1225
|
+
};
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
//#endregion
|
|
1229
|
+
//#region src/components/close-icon.tsx
|
|
1230
|
+
const iconStyle = { pointerEvents: "none" };
|
|
1231
|
+
const Icon = ({ children }) => /* @__PURE__ */ jsx("svg", {
|
|
1232
|
+
"aria-hidden": "true",
|
|
1233
|
+
fill: "none",
|
|
1234
|
+
height: "12",
|
|
1235
|
+
stroke: "currentColor",
|
|
1236
|
+
strokeLinecap: "round",
|
|
1237
|
+
strokeLinejoin: "round",
|
|
1238
|
+
strokeWidth: "2",
|
|
1239
|
+
style: iconStyle,
|
|
1240
|
+
viewBox: "0 0 24 24",
|
|
1241
|
+
width: "12",
|
|
1242
|
+
children
|
|
1243
|
+
});
|
|
1244
|
+
const CloseIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] });
|
|
1245
|
+
const GripIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1246
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1247
|
+
cx: "12",
|
|
1248
|
+
cy: "5",
|
|
1249
|
+
r: "1"
|
|
1250
|
+
}),
|
|
1251
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1252
|
+
cx: "19",
|
|
1253
|
+
cy: "5",
|
|
1254
|
+
r: "1"
|
|
1255
|
+
}),
|
|
1256
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1257
|
+
cx: "5",
|
|
1258
|
+
cy: "5",
|
|
1259
|
+
r: "1"
|
|
1260
|
+
}),
|
|
1261
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1262
|
+
cx: "12",
|
|
1263
|
+
cy: "12",
|
|
1264
|
+
r: "1"
|
|
1265
|
+
}),
|
|
1266
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1267
|
+
cx: "19",
|
|
1268
|
+
cy: "12",
|
|
1269
|
+
r: "1"
|
|
1270
|
+
}),
|
|
1271
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1272
|
+
cx: "5",
|
|
1273
|
+
cy: "12",
|
|
1274
|
+
r: "1"
|
|
1275
|
+
}),
|
|
1276
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1277
|
+
cx: "12",
|
|
1278
|
+
cy: "19",
|
|
1279
|
+
r: "1"
|
|
1280
|
+
}),
|
|
1281
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1282
|
+
cx: "19",
|
|
1283
|
+
cy: "19",
|
|
1284
|
+
r: "1"
|
|
1285
|
+
}),
|
|
1286
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1287
|
+
cx: "5",
|
|
1288
|
+
cy: "19",
|
|
1289
|
+
r: "1"
|
|
1290
|
+
})
|
|
1291
|
+
] });
|
|
1292
|
+
const MaximizeIcon = ({ maximized }) => maximized ? /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1293
|
+
/* @__PURE__ */ jsx("path", { d: "m14 10 7-7" }),
|
|
1294
|
+
/* @__PURE__ */ jsx("path", { d: "M20 10h-6V4" }),
|
|
1295
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1296
|
+
/* @__PURE__ */ jsx("path", { d: "M4 14h6v6" })
|
|
1297
|
+
] }) : /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1298
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
1299
|
+
/* @__PURE__ */ jsx("path", { d: "m21 3-7 7" }),
|
|
1300
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1301
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" })
|
|
1302
|
+
] });
|
|
1303
|
+
const FloatIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1304
|
+
height: "14",
|
|
1305
|
+
rx: "2",
|
|
1306
|
+
width: "18",
|
|
1307
|
+
x: "3",
|
|
1308
|
+
y: "6"
|
|
1309
|
+
}), /* @__PURE__ */ jsx("path", { d: "M3 10h18" })] });
|
|
1310
|
+
const MinimizeIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
|
|
1311
|
+
const DockIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1312
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2" }),
|
|
1313
|
+
/* @__PURE__ */ jsx("path", { d: "M11 8 7 12l4 4" }),
|
|
1314
|
+
/* @__PURE__ */ jsx("path", { d: "M7 12h10" })
|
|
1315
|
+
] });
|
|
1316
|
+
|
|
1317
|
+
//#endregion
|
|
1318
|
+
//#region src/components/layout-root.tsx
|
|
1319
|
+
const DEFAULT_TABSET_MIN_SIZE = 320;
|
|
1320
|
+
const rootStyle = {
|
|
1321
|
+
display: "flex",
|
|
1322
|
+
height: "100%",
|
|
1323
|
+
position: "relative",
|
|
1324
|
+
width: "100%"
|
|
1325
|
+
};
|
|
1326
|
+
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 }) => {
|
|
1327
|
+
const global = model.global;
|
|
1328
|
+
const snapshot = {
|
|
1329
|
+
closableTabs: editable && closableTabs && global.tabEnableClose !== false,
|
|
1330
|
+
dispatch,
|
|
1331
|
+
draggableTabs: editable && restructurable && draggableTabs && global.tabEnableDrag !== false,
|
|
1332
|
+
draggableTabsets: editable && restructurable && draggableTabsets,
|
|
1333
|
+
editable,
|
|
1334
|
+
floatable: editable && restructurable && floatable,
|
|
1335
|
+
keepMounted,
|
|
1336
|
+
maximizable: maximizable && global.tabSetEnableMaximize !== false,
|
|
1337
|
+
maximizedTabsetId: model.maximizedTabsetId,
|
|
1338
|
+
renamableTabs: editable && renamableTabs && global.tabEnableRename !== false,
|
|
1339
|
+
renderTab,
|
|
1340
|
+
renderTabLabel,
|
|
1341
|
+
renderTabsetToolbar,
|
|
1342
|
+
resizableSplits: editable && restructurable && resizableSplits && global.enableSplitResize !== false,
|
|
1343
|
+
snap: snap ?? global.snap ?? null,
|
|
1344
|
+
splitDock: global.enableSplitDock !== false,
|
|
1345
|
+
tabLocation: global.tabLocation ?? "top",
|
|
1346
|
+
tabsetMinSize: global.tabSetMinSize ?? DEFAULT_TABSET_MIN_SIZE,
|
|
1347
|
+
tabStripEnabled: global.tabSetEnableTabStrip !== false
|
|
1348
|
+
};
|
|
1349
|
+
const [store] = useState(() => createLayoutStore(snapshot));
|
|
1350
|
+
useLayoutEffect(() => {
|
|
1351
|
+
store.setState(snapshot);
|
|
1352
|
+
});
|
|
1353
|
+
const layoutStyle = global.splitterSize === void 0 ? {
|
|
1354
|
+
...rootStyle,
|
|
1355
|
+
...style
|
|
1356
|
+
} : {
|
|
1357
|
+
...rootStyle,
|
|
1358
|
+
"--dashfoo-splitter-size": `${global.splitterSize}px`,
|
|
1359
|
+
...style
|
|
1360
|
+
};
|
|
1361
|
+
return /* @__PURE__ */ jsx(LayoutStoreContext.Provider, {
|
|
1362
|
+
value: store,
|
|
1363
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1364
|
+
...props,
|
|
1365
|
+
"data-dashfoo": "layout",
|
|
1366
|
+
ref: rootRef,
|
|
1367
|
+
style: layoutStyle,
|
|
1368
|
+
children
|
|
1369
|
+
})
|
|
1370
|
+
});
|
|
1371
|
+
};
|
|
1372
|
+
/**
|
|
1373
|
+
* Republishes the parent store with overrides applied rather than re-deriving
|
|
1374
|
+
* flags, so anything the parent changes keeps flowing into the subtree.
|
|
1375
|
+
*/
|
|
1376
|
+
const LayoutOverrides = ({ children, overrides }) => {
|
|
1377
|
+
const snapshot = {
|
|
1378
|
+
...useLayout((state) => state),
|
|
1379
|
+
...overrides
|
|
1380
|
+
};
|
|
1381
|
+
const [store] = useState(() => createLayoutStore(snapshot));
|
|
1382
|
+
useLayoutEffect(() => {
|
|
1383
|
+
store.setState(snapshot);
|
|
1384
|
+
});
|
|
1385
|
+
return /* @__PURE__ */ jsx(LayoutStoreContext.Provider, {
|
|
1386
|
+
value: store,
|
|
1387
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1388
|
+
"data-dashfoo": "layout",
|
|
1389
|
+
style: rootStyle,
|
|
1390
|
+
children
|
|
1391
|
+
})
|
|
1392
|
+
});
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
//#endregion
|
|
1396
|
+
//#region src/hooks/use-snap-resize.ts
|
|
1397
|
+
const WEIGHT_EPSILON = .01;
|
|
1398
|
+
const IDLE = { kind: "idle" };
|
|
1399
|
+
const boundaryOf = (state) => state.kind === "dragging" || state.kind === "snapped" ? state.boundary : null;
|
|
1400
|
+
const snappedBoundaryOf = (state) => state.kind === "snapped" ? state.boundary : null;
|
|
1401
|
+
const layoutFromWeights = (children) => {
|
|
1402
|
+
const total = children.reduce((sum, child) => sum + child.weight, 0);
|
|
1403
|
+
return children.reduce((layout, child) => {
|
|
1404
|
+
layout[child.id] = child.weight / total * 100;
|
|
1405
|
+
return layout;
|
|
1406
|
+
}, {});
|
|
1407
|
+
};
|
|
1408
|
+
const orderedFromLayout = (layout, children) => children.map((child) => layout[child.id] ?? 0);
|
|
1409
|
+
const layoutFromOrdered = (sizes, children) => children.reduce((layout, child, index) => {
|
|
1410
|
+
layout[child.id] = sizes[index] ?? 0;
|
|
1411
|
+
return layout;
|
|
1412
|
+
}, {});
|
|
1413
|
+
const layoutsMatch = (current, expected) => {
|
|
1414
|
+
const currentIds = Object.keys(current);
|
|
1415
|
+
const expectedIds = Object.keys(expected);
|
|
1416
|
+
if (currentIds.length !== expectedIds.length) return false;
|
|
1417
|
+
return expectedIds.every((id) => Math.abs((current[id] ?? NaN) - expected[id]) <= WEIGHT_EPSILON);
|
|
1418
|
+
};
|
|
1419
|
+
const useSnapResize = ({ children, onWeightsSettled, resizableSplits, snap }) => {
|
|
1420
|
+
const groupRef = useRef(null);
|
|
1421
|
+
const state = useRef(IDLE);
|
|
1422
|
+
const [snappedBoundary, setSnappedBoundary] = useState(null);
|
|
1423
|
+
const transition = useCallback((next) => {
|
|
1424
|
+
state.current = next;
|
|
1425
|
+
setSnappedBoundary(snappedBoundaryOf(next));
|
|
1426
|
+
}, []);
|
|
1427
|
+
const desiredLayout = useMemo(() => layoutFromWeights(children), [children]);
|
|
1428
|
+
useLayoutEffect(() => {
|
|
1429
|
+
const group = groupRef.current;
|
|
1430
|
+
if (!group) return;
|
|
1431
|
+
const current = group.getLayout();
|
|
1432
|
+
if (!(Object.keys(current).length === Object.keys(desiredLayout).length && Object.keys(desiredLayout).every((id) => id in current)) || layoutsMatch(current, desiredLayout)) return;
|
|
1433
|
+
transition({ kind: "syncing" });
|
|
1434
|
+
group.setLayout(desiredLayout);
|
|
1435
|
+
const frame = requestAnimationFrame(() => {
|
|
1436
|
+
transition(IDLE);
|
|
1437
|
+
});
|
|
1438
|
+
return () => {
|
|
1439
|
+
cancelAnimationFrame(frame);
|
|
1440
|
+
transition(IDLE);
|
|
1441
|
+
};
|
|
1442
|
+
}, [desiredLayout, transition]);
|
|
1443
|
+
const onLayoutChange = (layout) => {
|
|
1444
|
+
if (state.current.kind === "syncing" || !resizableSplits) return;
|
|
1445
|
+
const boundary = boundaryOf(state.current);
|
|
1446
|
+
if (boundary === null) return;
|
|
1447
|
+
const decision = decideSnap(orderedFromLayout(layout, children), boundary, snap);
|
|
1448
|
+
if (decision.kind === "inactive") return;
|
|
1449
|
+
if (decision.kind === "clear") {
|
|
1450
|
+
transition({
|
|
1451
|
+
boundary,
|
|
1452
|
+
kind: "dragging"
|
|
1453
|
+
});
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
transition({
|
|
1457
|
+
boundary,
|
|
1458
|
+
kind: "snapped"
|
|
1459
|
+
});
|
|
1460
|
+
const group = groupRef.current;
|
|
1461
|
+
const target = layoutFromOrdered(decision.sizes, children);
|
|
1462
|
+
if (!group || layoutsMatch(layout, target)) return;
|
|
1463
|
+
transition({ kind: "syncing" });
|
|
1464
|
+
group.setLayout(target);
|
|
1465
|
+
transition({
|
|
1466
|
+
boundary,
|
|
1467
|
+
kind: "snapped"
|
|
1468
|
+
});
|
|
1469
|
+
};
|
|
1470
|
+
const onLayoutChanged = (layout, meta) => {
|
|
1471
|
+
if (!meta.isUserInteraction) return;
|
|
1472
|
+
if (state.current.kind === "syncing") return;
|
|
1473
|
+
const boundary = boundaryOf(state.current);
|
|
1474
|
+
transition(IDLE);
|
|
1475
|
+
if (!resizableSplits) return;
|
|
1476
|
+
const weights = settleSnap(children.map((child) => layout[child.id] ?? child.weight), boundary, snap);
|
|
1477
|
+
if (!weights.some((weight, index) => {
|
|
1478
|
+
const child = children[index];
|
|
1479
|
+
return child !== void 0 && Math.abs(weight - child.weight) > WEIGHT_EPSILON;
|
|
1480
|
+
})) return;
|
|
1481
|
+
onWeightsSettled(weights);
|
|
1482
|
+
};
|
|
1483
|
+
const beginBoundaryDrag = (boundary) => {
|
|
1484
|
+
transition({
|
|
1485
|
+
boundary,
|
|
1486
|
+
kind: "dragging"
|
|
1487
|
+
});
|
|
1488
|
+
};
|
|
1489
|
+
return {
|
|
1490
|
+
beginBoundaryDrag,
|
|
1491
|
+
groupRef,
|
|
1492
|
+
onLayoutChange,
|
|
1493
|
+
onLayoutChanged,
|
|
1494
|
+
snappedBoundary
|
|
1495
|
+
};
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
//#endregion
|
|
1499
|
+
//#region src/lib/tab-ids.ts
|
|
1500
|
+
const tabDomId = (tabsetId, tabId) => `dashfoo-tab-${tabsetId}-${tabId}`;
|
|
1501
|
+
const panelDomId = (tabsetId) => `dashfoo-panel-${tabsetId}`;
|
|
1502
|
+
|
|
1503
|
+
//#endregion
|
|
1504
|
+
//#region src/components/tabset/tabset-store.ts
|
|
1505
|
+
const tabButtonIn = (scope, tabsetId, tabId) => scope?.querySelector(`#${CSS.escape(tabDomId(tabsetId, tabId))}`) ?? null;
|
|
1506
|
+
const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
1507
|
+
...initial,
|
|
1508
|
+
cancelRename: () => {
|
|
1509
|
+
set({ editingTabId: null });
|
|
1510
|
+
},
|
|
1511
|
+
closeTab: (tabId) => {
|
|
1512
|
+
set({ pendingCloseTabId: tabId });
|
|
1513
|
+
get().dispatch({
|
|
1514
|
+
tabId,
|
|
1515
|
+
type: "deleteTab"
|
|
1516
|
+
});
|
|
1517
|
+
},
|
|
1518
|
+
commitRename: (tabId, value) => {
|
|
1519
|
+
set({ editingTabId: null });
|
|
1520
|
+
const { node, renameTab } = get();
|
|
1521
|
+
const trimmed = value.trim();
|
|
1522
|
+
const current = node.children.find((tab) => tab.id === tabId)?.name;
|
|
1523
|
+
if (trimmed && trimmed !== current) renameTab(tabId, trimmed);
|
|
1524
|
+
},
|
|
1525
|
+
editingTabId: null,
|
|
1526
|
+
overflowItems: [],
|
|
1527
|
+
pendingCloseTabId: null,
|
|
1528
|
+
registerRenameInput: () => {
|
|
1529
|
+
set((state) => ({ renameInputCount: state.renameInputCount + 1 }));
|
|
1530
|
+
return () => {
|
|
1531
|
+
set((state) => ({ renameInputCount: state.renameInputCount - 1 }));
|
|
1532
|
+
};
|
|
1533
|
+
},
|
|
1534
|
+
registerTablist: (element) => {
|
|
1535
|
+
if (element) {
|
|
1536
|
+
const current = get().tablistElement;
|
|
1537
|
+
if (current && current !== element) warnOnce(`duplicate-tablist-${get().node.id}`, "more than one <Tabset.Tablist> under the same <Tabset.Root>; the last one wins for keyboard focus and overflow");
|
|
1538
|
+
set({ tablistElement: element });
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
set({ tablistElement: null });
|
|
1542
|
+
},
|
|
1543
|
+
renameInputCount: 0,
|
|
1544
|
+
renameTab: (tabId, name) => {
|
|
1545
|
+
get().dispatch({
|
|
1546
|
+
name,
|
|
1547
|
+
tabId,
|
|
1548
|
+
type: "renameTab"
|
|
1549
|
+
});
|
|
1550
|
+
},
|
|
1551
|
+
selectOverflowTab: (tabId) => {
|
|
1552
|
+
const { dispatch, node, tablistElement } = get();
|
|
1553
|
+
const index = node.children.findIndex((tab) => tab.id === tabId);
|
|
1554
|
+
if (index === -1) return;
|
|
1555
|
+
dispatch({
|
|
1556
|
+
index,
|
|
1557
|
+
tabsetId: node.id,
|
|
1558
|
+
type: "selectTab"
|
|
1180
1559
|
});
|
|
1181
1560
|
tabButtonIn(tablistElement, node.id, tabId)?.scrollIntoView({
|
|
1182
1561
|
block: "nearest",
|
|
@@ -1282,7 +1661,7 @@ const TabsetContent = ({ children, style, ...props }) => {
|
|
|
1282
1661
|
};
|
|
1283
1662
|
|
|
1284
1663
|
//#endregion
|
|
1285
|
-
//#region src/components/tab-overflow.tsx
|
|
1664
|
+
//#region src/components/tab-overflow-menu.tsx
|
|
1286
1665
|
const OverflowIcon = () => /* @__PURE__ */ jsxs("svg", {
|
|
1287
1666
|
"aria-hidden": "true",
|
|
1288
1667
|
fill: "currentColor",
|
|
@@ -1370,8 +1749,6 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1370
1749
|
case "Escape":
|
|
1371
1750
|
event.preventDefault();
|
|
1372
1751
|
closeAndReturnFocus();
|
|
1373
|
-
break;
|
|
1374
|
-
default: break;
|
|
1375
1752
|
}
|
|
1376
1753
|
};
|
|
1377
1754
|
const handleFocusOut = (event) => {
|
|
@@ -1415,16 +1792,93 @@ const TabOverflowMenu = ({ items, onSelect }) => {
|
|
|
1415
1792
|
}) : null]
|
|
1416
1793
|
});
|
|
1417
1794
|
};
|
|
1418
|
-
|
|
1419
|
-
//#endregion
|
|
1420
|
-
//#region src/components/tabset/tabset-overflow.tsx
|
|
1421
|
-
const TabsetOverflowMenu = () => {
|
|
1422
|
-
const overflowItems = useTabset((state) => state.overflowItems);
|
|
1423
|
-
const selectOverflowTab = useTabset((state) => state.selectOverflowTab);
|
|
1424
|
-
if (overflowItems.length === 0) return null;
|
|
1425
|
-
return /* @__PURE__ */ jsx(TabOverflowMenu, {
|
|
1426
|
-
items: overflowItems,
|
|
1427
|
-
onSelect: selectOverflowTab
|
|
1795
|
+
|
|
1796
|
+
//#endregion
|
|
1797
|
+
//#region src/components/tabset/tabset-overflow-menu.tsx
|
|
1798
|
+
const TabsetOverflowMenu = () => {
|
|
1799
|
+
const overflowItems = useTabset((state) => state.overflowItems);
|
|
1800
|
+
const selectOverflowTab = useTabset((state) => state.selectOverflowTab);
|
|
1801
|
+
if (overflowItems.length === 0) return null;
|
|
1802
|
+
return /* @__PURE__ */ jsx(TabOverflowMenu, {
|
|
1803
|
+
items: overflowItems,
|
|
1804
|
+
onSelect: selectOverflowTab
|
|
1805
|
+
});
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1808
|
+
//#endregion
|
|
1809
|
+
//#region src/lib/merge-refs.ts
|
|
1810
|
+
const applyRef = (ref, value) => {
|
|
1811
|
+
if (typeof ref === "function") ref(value);
|
|
1812
|
+
else if (ref) ref.current = value;
|
|
1813
|
+
};
|
|
1814
|
+
const mergeRefs = (...refs) => (value) => {
|
|
1815
|
+
for (const ref of refs) applyRef(ref, value);
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
//#endregion
|
|
1819
|
+
//#region src/components/tabset/tabset-rename-input.tsx
|
|
1820
|
+
const RenameEditor = ({ defaultValue, onBlur, onKeyDown, ref: userRef, ...props }) => {
|
|
1821
|
+
const { tab } = useTab();
|
|
1822
|
+
const cancelRename = useTabset((state) => state.cancelRename);
|
|
1823
|
+
const commitRename = useTabset((state) => state.commitRename);
|
|
1824
|
+
const inputRef = useRef(null);
|
|
1825
|
+
const rename = useInlineRename({
|
|
1826
|
+
currentName: tab.name,
|
|
1827
|
+
inputRef,
|
|
1828
|
+
onCommit: (name) => {
|
|
1829
|
+
commitRename(tab.id, name);
|
|
1830
|
+
},
|
|
1831
|
+
onDone: cancelRename
|
|
1832
|
+
});
|
|
1833
|
+
const refCallback = useMemo(() => mergeRefs(inputRef, userRef), [userRef]);
|
|
1834
|
+
const handleKeyDown = (event) => {
|
|
1835
|
+
onKeyDown?.(event);
|
|
1836
|
+
rename.handleKeyDown(event);
|
|
1837
|
+
};
|
|
1838
|
+
const handleCommitOnBlur = (event) => {
|
|
1839
|
+
onBlur?.(event);
|
|
1840
|
+
rename.handleBlur(event);
|
|
1841
|
+
};
|
|
1842
|
+
return /* @__PURE__ */ jsx("input", {
|
|
1843
|
+
"aria-label": `Rename ${tab.name}`,
|
|
1844
|
+
...props,
|
|
1845
|
+
"data-dashfoo": "tab-rename",
|
|
1846
|
+
defaultValue: defaultValue ?? tab.name,
|
|
1847
|
+
onBlur: handleCommitOnBlur,
|
|
1848
|
+
onKeyDown: handleKeyDown,
|
|
1849
|
+
ref: refCallback,
|
|
1850
|
+
type: "text"
|
|
1851
|
+
});
|
|
1852
|
+
};
|
|
1853
|
+
const TabsetRenameInput = (props) => {
|
|
1854
|
+
const { tab } = useTab();
|
|
1855
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1856
|
+
const registerRenameInput = useTabset((state) => state.registerRenameInput);
|
|
1857
|
+
useEffect(() => registerRenameInput(), [registerRenameInput]);
|
|
1858
|
+
if (!editing) return null;
|
|
1859
|
+
return /* @__PURE__ */ jsx(RenameEditor, { ...props });
|
|
1860
|
+
};
|
|
1861
|
+
const TabsetCloseButton = ({ children, onClick, ...props }) => {
|
|
1862
|
+
const { index, tab } = useTab();
|
|
1863
|
+
const closeTab = useTabset((state) => state.closeTab);
|
|
1864
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1865
|
+
const tabsClosable = useTabset((state) => state.tabsClosable);
|
|
1866
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1867
|
+
if (!(tabsClosable && tab.enableClose !== false) || editing) return null;
|
|
1868
|
+
const handleClick = (event) => {
|
|
1869
|
+
onClick?.(event);
|
|
1870
|
+
event.stopPropagation();
|
|
1871
|
+
closeTab(tab.id);
|
|
1872
|
+
};
|
|
1873
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1874
|
+
"aria-label": `Close ${tab.name}`,
|
|
1875
|
+
title: `Close ${tab.name}`,
|
|
1876
|
+
...props,
|
|
1877
|
+
"data-dashfoo": "tab-close",
|
|
1878
|
+
onClick: handleClick,
|
|
1879
|
+
tabIndex: index === visualSelected ? 0 : -1,
|
|
1880
|
+
type: "button",
|
|
1881
|
+
children: children ?? /* @__PURE__ */ jsx(CloseIcon, {})
|
|
1428
1882
|
});
|
|
1429
1883
|
};
|
|
1430
1884
|
|
|
@@ -1439,13 +1893,14 @@ const overflowingIds = (tablist) => {
|
|
|
1439
1893
|
return clipped && tabId !== void 0 && tabId !== "" ? [tabId] : [];
|
|
1440
1894
|
});
|
|
1441
1895
|
};
|
|
1896
|
+
const sameIds = (a, b) => a.length === b.length && a.every((id, index) => id === b[index]);
|
|
1442
1897
|
const useTabOverflow = (tablist, signature) => {
|
|
1443
1898
|
const [overflow, setOverflow] = useState([]);
|
|
1444
1899
|
useEffect(() => {
|
|
1445
1900
|
const recompute = () => {
|
|
1446
1901
|
setOverflow((previous) => {
|
|
1447
1902
|
const next = tablist ? overflowingIds(tablist) : [];
|
|
1448
|
-
return
|
|
1903
|
+
return sameIds(previous, next) ? previous : next;
|
|
1449
1904
|
});
|
|
1450
1905
|
};
|
|
1451
1906
|
recompute();
|
|
@@ -1507,7 +1962,9 @@ const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
|
1507
1962
|
useLayoutEffect(() => {
|
|
1508
1963
|
store.setState(snapshot);
|
|
1509
1964
|
});
|
|
1510
|
-
const
|
|
1965
|
+
const tablistElement = useStore(store, (state) => state.tablistElement);
|
|
1966
|
+
const overflowSignature = node.children.map((tab) => `${tab.id}:${tab.name}`).join("|");
|
|
1967
|
+
const overflow = useTabOverflow(tablistElement, overflowSignature);
|
|
1511
1968
|
const overflowItems = useMemo(() => {
|
|
1512
1969
|
const names = new Map(node.children.map((tab) => [tab.id, tab.name]));
|
|
1513
1970
|
return overflow.map((id) => ({
|
|
@@ -1519,8 +1976,10 @@ const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
|
1519
1976
|
store.setState({ overflowItems });
|
|
1520
1977
|
}, [overflowItems, store]);
|
|
1521
1978
|
useEffect(() => {
|
|
1522
|
-
|
|
1523
|
-
|
|
1979
|
+
const { pendingCloseTabId } = store.getState();
|
|
1980
|
+
if (pendingCloseTabId === null) return;
|
|
1981
|
+
store.setState({ pendingCloseTabId: null });
|
|
1982
|
+
if (node.children.some((tab) => tab.id === pendingCloseTabId)) return;
|
|
1524
1983
|
const activeTabId = node.children[node.selected]?.id;
|
|
1525
1984
|
if (activeTabId) tabsetRef.current?.querySelector(`#${CSS.escape(tabDomId(node.id, activeTabId))}`)?.focus();
|
|
1526
1985
|
else tabsetRef.current?.focus();
|
|
@@ -1548,253 +2007,87 @@ const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
|
1548
2007
|
...style
|
|
1549
2008
|
},
|
|
1550
2009
|
tabIndex: -1,
|
|
1551
|
-
children
|
|
1552
|
-
})
|
|
1553
|
-
});
|
|
1554
|
-
};
|
|
1555
|
-
|
|
1556
|
-
//#endregion
|
|
1557
|
-
//#region src/lib/merge-refs.ts
|
|
1558
|
-
const applyRef = (ref, value) => {
|
|
1559
|
-
if (typeof ref === "function") ref(value);
|
|
1560
|
-
else if (ref) ref.current = value;
|
|
1561
|
-
};
|
|
1562
|
-
const mergeRefs = (...refs) => (value) => {
|
|
1563
|
-
for (const ref of refs) applyRef(ref, value);
|
|
1564
|
-
};
|
|
1565
|
-
|
|
1566
|
-
//#endregion
|
|
1567
|
-
//#region src/components/tabset/tabset-tab.tsx
|
|
1568
|
-
const TabsetTab = ({ children, ref: userRef, tab, ...props }) => {
|
|
1569
|
-
const store = useContext(TabsetStoreContext);
|
|
1570
|
-
const node = useTabset((state) => state.node);
|
|
1571
|
-
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1572
|
-
const dragSubject = useDragSubject();
|
|
1573
|
-
const isDragging = dragSubject?.kind === "tab" && dragSubject.id === tab.id;
|
|
1574
|
-
const itemRef = useRef(null);
|
|
1575
|
-
const wasEditing = useRef(false);
|
|
1576
|
-
const index = node.children.findIndex((child) => child.id === tab.id);
|
|
1577
|
-
useEffect(() => {
|
|
1578
|
-
if (store?.getState().node.children.some((child) => child.id === tab.id) === false) warnOnce(`orphan-tab-${tab.id}`, `<Tabset.Tab> for "${tab.id}" is not a child of its Tabset.Root's node; it renders but can never be selected`);
|
|
1579
|
-
}, [
|
|
1580
|
-
index === -1,
|
|
1581
|
-
store,
|
|
1582
|
-
tab.id
|
|
1583
|
-
]);
|
|
1584
|
-
useEffect(() => {
|
|
1585
|
-
if (wasEditing.current && !editing) itemRef.current?.querySelector("[data-dashfoo=\"tab\"]")?.focus();
|
|
1586
|
-
wasEditing.current = editing;
|
|
1587
|
-
}, [editing]);
|
|
1588
|
-
const contextValue = useMemo(() => ({
|
|
1589
|
-
index,
|
|
1590
|
-
tab
|
|
1591
|
-
}), [index, tab]);
|
|
1592
|
-
const refCallback = useMemo(() => mergeRefs(itemRef, userRef), [userRef]);
|
|
1593
|
-
return /* @__PURE__ */ jsx(TabContext.Provider, {
|
|
1594
|
-
value: contextValue,
|
|
1595
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
1596
|
-
...props,
|
|
1597
|
-
"data-dashfoo": "tab-item",
|
|
1598
|
-
"data-dragging": isDragging || void 0,
|
|
1599
|
-
ref: refCallback,
|
|
1600
|
-
children
|
|
1601
|
-
})
|
|
1602
|
-
});
|
|
1603
|
-
};
|
|
1604
|
-
const TabsetTrigger = ({ children, onClick, onDoubleClick, ref: userRef, ...props }) => {
|
|
1605
|
-
const { index, tab } = useTab();
|
|
1606
|
-
const node = useTabset((state) => state.node);
|
|
1607
|
-
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1608
|
-
const selectTab = useTabset((state) => state.selectTab);
|
|
1609
|
-
const startEditing = useTabset((state) => state.startEditing);
|
|
1610
|
-
const tabsRenamable = useTabset((state) => state.tabsRenamable);
|
|
1611
|
-
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1612
|
-
const draggableTabs = useLayout((state) => state.draggableTabs);
|
|
1613
|
-
const renderTabLabel = useLayout((state) => state.renderTabLabel);
|
|
1614
|
-
const { ref } = useTabDraggable(tab.id, !draggableTabs || tab.enableDrag === false, tab.name);
|
|
1615
|
-
const refCallback = useMemo(() => mergeRefs(ref, userRef), [ref, userRef]);
|
|
1616
|
-
if (editing) return null;
|
|
1617
|
-
const selected = index === visualSelected;
|
|
1618
|
-
const renamable = tabsRenamable && tab.enableRename !== false;
|
|
1619
|
-
const handleClick = (event) => {
|
|
1620
|
-
onClick?.(event);
|
|
1621
|
-
selectTab(index);
|
|
1622
|
-
};
|
|
1623
|
-
const handleDoubleClick = (event) => {
|
|
1624
|
-
onDoubleClick?.(event);
|
|
1625
|
-
if (renamable) startEditing(tab.id);
|
|
1626
|
-
};
|
|
1627
|
-
return /* @__PURE__ */ jsx("button", {
|
|
1628
|
-
"aria-label": renderTabLabel ? tab.name : void 0,
|
|
1629
|
-
...props,
|
|
1630
|
-
"aria-controls": panelDomId(node.id),
|
|
1631
|
-
"aria-selected": selected,
|
|
1632
|
-
"data-dashfoo": "tab",
|
|
1633
|
-
"data-tab-id": tab.id,
|
|
1634
|
-
id: tabDomId(node.id, tab.id),
|
|
1635
|
-
onClick: handleClick,
|
|
1636
|
-
onDoubleClick: handleDoubleClick,
|
|
1637
|
-
ref: refCallback,
|
|
1638
|
-
role: "tab",
|
|
1639
|
-
tabIndex: selected ? 0 : -1,
|
|
1640
|
-
type: "button",
|
|
1641
|
-
children: children ?? (renderTabLabel ? renderTabLabel(tab) : tab.name)
|
|
1642
|
-
});
|
|
1643
|
-
};
|
|
1644
|
-
|
|
1645
|
-
//#endregion
|
|
1646
|
-
//#region src/components/tabset-icons.tsx
|
|
1647
|
-
const iconStyle = { pointerEvents: "none" };
|
|
1648
|
-
const Icon = ({ children }) => /* @__PURE__ */ jsx("svg", {
|
|
1649
|
-
"aria-hidden": "true",
|
|
1650
|
-
fill: "none",
|
|
1651
|
-
height: "12",
|
|
1652
|
-
stroke: "currentColor",
|
|
1653
|
-
strokeLinecap: "round",
|
|
1654
|
-
strokeLinejoin: "round",
|
|
1655
|
-
strokeWidth: "2",
|
|
1656
|
-
style: iconStyle,
|
|
1657
|
-
viewBox: "0 0 24 24",
|
|
1658
|
-
width: "12",
|
|
1659
|
-
children
|
|
1660
|
-
});
|
|
1661
|
-
const CloseIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] });
|
|
1662
|
-
const GripIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1663
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1664
|
-
cx: "12",
|
|
1665
|
-
cy: "5",
|
|
1666
|
-
r: "1"
|
|
1667
|
-
}),
|
|
1668
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1669
|
-
cx: "19",
|
|
1670
|
-
cy: "5",
|
|
1671
|
-
r: "1"
|
|
1672
|
-
}),
|
|
1673
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1674
|
-
cx: "5",
|
|
1675
|
-
cy: "5",
|
|
1676
|
-
r: "1"
|
|
1677
|
-
}),
|
|
1678
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1679
|
-
cx: "12",
|
|
1680
|
-
cy: "12",
|
|
1681
|
-
r: "1"
|
|
1682
|
-
}),
|
|
1683
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1684
|
-
cx: "19",
|
|
1685
|
-
cy: "12",
|
|
1686
|
-
r: "1"
|
|
1687
|
-
}),
|
|
1688
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1689
|
-
cx: "5",
|
|
1690
|
-
cy: "12",
|
|
1691
|
-
r: "1"
|
|
1692
|
-
}),
|
|
1693
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1694
|
-
cx: "12",
|
|
1695
|
-
cy: "19",
|
|
1696
|
-
r: "1"
|
|
1697
|
-
}),
|
|
1698
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1699
|
-
cx: "19",
|
|
1700
|
-
cy: "19",
|
|
1701
|
-
r: "1"
|
|
1702
|
-
}),
|
|
1703
|
-
/* @__PURE__ */ jsx("circle", {
|
|
1704
|
-
cx: "5",
|
|
1705
|
-
cy: "19",
|
|
1706
|
-
r: "1"
|
|
1707
|
-
})
|
|
1708
|
-
] });
|
|
1709
|
-
const MaximizeIcon = ({ maximized }) => maximized ? /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1710
|
-
/* @__PURE__ */ jsx("path", { d: "m14 10 7-7" }),
|
|
1711
|
-
/* @__PURE__ */ jsx("path", { d: "M20 10h-6V4" }),
|
|
1712
|
-
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1713
|
-
/* @__PURE__ */ jsx("path", { d: "M4 14h6v6" })
|
|
1714
|
-
] }) : /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1715
|
-
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
1716
|
-
/* @__PURE__ */ jsx("path", { d: "m21 3-7 7" }),
|
|
1717
|
-
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1718
|
-
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" })
|
|
1719
|
-
] });
|
|
1720
|
-
const FloatIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1721
|
-
height: "14",
|
|
1722
|
-
rx: "2",
|
|
1723
|
-
width: "18",
|
|
1724
|
-
x: "3",
|
|
1725
|
-
y: "6"
|
|
1726
|
-
}), /* @__PURE__ */ jsx("path", { d: "M3 10h18" })] });
|
|
1727
|
-
const MinimizeIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
|
|
1728
|
-
const DockIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1729
|
-
/* @__PURE__ */ jsx("path", { d: "M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2" }),
|
|
1730
|
-
/* @__PURE__ */ jsx("path", { d: "M11 8 7 12l4 4" }),
|
|
1731
|
-
/* @__PURE__ */ jsx("path", { d: "M7 12h10" })
|
|
1732
|
-
] });
|
|
1733
|
-
|
|
1734
|
-
//#endregion
|
|
1735
|
-
//#region src/components/tabset/tabset-tab-controls.tsx
|
|
1736
|
-
const RenameEditor = ({ defaultValue, onBlur, onKeyDown, ref: userRef, ...props }) => {
|
|
1737
|
-
const { tab } = useTab();
|
|
1738
|
-
const cancelRename = useTabset((state) => state.cancelRename);
|
|
1739
|
-
const commitRename = useTabset((state) => state.commitRename);
|
|
1740
|
-
const inputRef = useRef(null);
|
|
1741
|
-
const rename = useInlineRename({
|
|
1742
|
-
currentName: tab.name,
|
|
1743
|
-
inputRef,
|
|
1744
|
-
onCommit: (name) => {
|
|
1745
|
-
commitRename(tab.id, name);
|
|
1746
|
-
},
|
|
1747
|
-
onDone: cancelRename
|
|
1748
|
-
});
|
|
1749
|
-
const refCallback = useMemo(() => mergeRefs(inputRef, userRef), [userRef]);
|
|
1750
|
-
const handleKeyDown = (event) => {
|
|
1751
|
-
onKeyDown?.(event);
|
|
1752
|
-
rename.handleKeyDown(event);
|
|
1753
|
-
};
|
|
1754
|
-
const handleCommitOnBlur = (event) => {
|
|
1755
|
-
onBlur?.(event);
|
|
1756
|
-
rename.handleBlur(event);
|
|
1757
|
-
};
|
|
1758
|
-
return /* @__PURE__ */ jsx("input", {
|
|
1759
|
-
"aria-label": `Rename ${tab.name}`,
|
|
1760
|
-
...props,
|
|
1761
|
-
"data-dashfoo": "tab-rename",
|
|
1762
|
-
defaultValue: defaultValue ?? tab.name,
|
|
1763
|
-
onBlur: handleCommitOnBlur,
|
|
1764
|
-
onKeyDown: handleKeyDown,
|
|
1765
|
-
ref: refCallback,
|
|
1766
|
-
type: "text"
|
|
2010
|
+
children
|
|
2011
|
+
})
|
|
1767
2012
|
});
|
|
1768
2013
|
};
|
|
1769
|
-
|
|
1770
|
-
|
|
2014
|
+
|
|
2015
|
+
//#endregion
|
|
2016
|
+
//#region src/components/tabset/tabset-tab.tsx
|
|
2017
|
+
const TabsetTab = ({ children, ref: userRef, tab, ...props }) => {
|
|
2018
|
+
const store = useContext(TabsetStoreContext);
|
|
2019
|
+
const node = useTabset((state) => state.node);
|
|
1771
2020
|
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
2021
|
+
const dragSubject = useDragSubject();
|
|
2022
|
+
const isDragging = dragSubject?.kind === "tab" && dragSubject.id === tab.id;
|
|
2023
|
+
const itemRef = useRef(null);
|
|
2024
|
+
const wasEditing = useRef(false);
|
|
2025
|
+
const index = node.children.findIndex((child) => child.id === tab.id);
|
|
2026
|
+
useEffect(() => {
|
|
2027
|
+
if (store?.getState().node.children.some((child) => child.id === tab.id) === false) warnOnce(`orphan-tab-${tab.id}`, `<Tabset.Tab> for "${tab.id}" is not a child of its Tabset.Root's node; it renders but can never be selected`);
|
|
2028
|
+
}, [
|
|
2029
|
+
index === -1,
|
|
2030
|
+
store,
|
|
2031
|
+
tab.id
|
|
2032
|
+
]);
|
|
2033
|
+
useEffect(() => {
|
|
2034
|
+
if (wasEditing.current && !editing) itemRef.current?.querySelector("[data-dashfoo=\"tab\"]")?.focus();
|
|
2035
|
+
wasEditing.current = editing;
|
|
2036
|
+
}, [editing]);
|
|
2037
|
+
const contextValue = useMemo(() => ({
|
|
2038
|
+
index,
|
|
2039
|
+
tab
|
|
2040
|
+
}), [index, tab]);
|
|
2041
|
+
const refCallback = useMemo(() => mergeRefs(itemRef, userRef), [userRef]);
|
|
2042
|
+
return /* @__PURE__ */ jsx(TabContext.Provider, {
|
|
2043
|
+
value: contextValue,
|
|
2044
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2045
|
+
...props,
|
|
2046
|
+
"data-dashfoo": "tab-item",
|
|
2047
|
+
"data-dragging": isDragging || void 0,
|
|
2048
|
+
ref: refCallback,
|
|
2049
|
+
children
|
|
2050
|
+
})
|
|
2051
|
+
});
|
|
1776
2052
|
};
|
|
1777
|
-
const
|
|
2053
|
+
const TabsetTrigger = ({ children, onClick, onDoubleClick, ref: userRef, ...props }) => {
|
|
1778
2054
|
const { index, tab } = useTab();
|
|
1779
|
-
const
|
|
2055
|
+
const node = useTabset((state) => state.node);
|
|
1780
2056
|
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1781
|
-
const
|
|
2057
|
+
const selectTab = useTabset((state) => state.selectTab);
|
|
2058
|
+
const startEditing = useTabset((state) => state.startEditing);
|
|
2059
|
+
const tabsRenamable = useTabset((state) => state.tabsRenamable);
|
|
1782
2060
|
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1783
|
-
|
|
2061
|
+
const draggableTabs = useLayout((state) => state.draggableTabs);
|
|
2062
|
+
const renderTabLabel = useLayout((state) => state.renderTabLabel);
|
|
2063
|
+
const { ref } = useTabDraggable(tab.id, !draggableTabs || tab.enableDrag === false, tab.name);
|
|
2064
|
+
const refCallback = useMemo(() => mergeRefs(ref, userRef), [ref, userRef]);
|
|
2065
|
+
if (editing) return null;
|
|
2066
|
+
const selected = index === visualSelected;
|
|
2067
|
+
const renamable = tabsRenamable && tab.enableRename !== false;
|
|
1784
2068
|
const handleClick = (event) => {
|
|
1785
2069
|
onClick?.(event);
|
|
1786
|
-
|
|
1787
|
-
|
|
2070
|
+
selectTab(index);
|
|
2071
|
+
};
|
|
2072
|
+
const handleDoubleClick = (event) => {
|
|
2073
|
+
onDoubleClick?.(event);
|
|
2074
|
+
if (renamable) startEditing(tab.id);
|
|
1788
2075
|
};
|
|
1789
2076
|
return /* @__PURE__ */ jsx("button", {
|
|
1790
|
-
"aria-label":
|
|
1791
|
-
title: `Close ${tab.name}`,
|
|
2077
|
+
"aria-label": renderTabLabel ? tab.name : void 0,
|
|
1792
2078
|
...props,
|
|
1793
|
-
"
|
|
2079
|
+
"aria-controls": panelDomId(node.id),
|
|
2080
|
+
"aria-selected": selected,
|
|
2081
|
+
"data-dashfoo": "tab",
|
|
2082
|
+
"data-tab-id": tab.id,
|
|
2083
|
+
id: tabDomId(node.id, tab.id),
|
|
1794
2084
|
onClick: handleClick,
|
|
1795
|
-
|
|
2085
|
+
onDoubleClick: handleDoubleClick,
|
|
2086
|
+
ref: refCallback,
|
|
2087
|
+
role: "tab",
|
|
2088
|
+
tabIndex: selected ? 0 : -1,
|
|
1796
2089
|
type: "button",
|
|
1797
|
-
children: children ??
|
|
2090
|
+
children: children ?? (renderTabLabel ? renderTabLabel(tab) : tab.name)
|
|
1798
2091
|
});
|
|
1799
2092
|
};
|
|
1800
2093
|
|
|
@@ -1821,15 +2114,17 @@ const TabsetTablist = ({ onKeyDown, ref: userRef, style, ...props }) => {
|
|
|
1821
2114
|
const node = useTabset((state) => state.node);
|
|
1822
2115
|
const registerTablist = useTabset((state) => state.registerTablist);
|
|
1823
2116
|
const selectTab = useTabset((state) => state.selectTab);
|
|
2117
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1824
2118
|
const handleKeyDown = (event) => {
|
|
1825
2119
|
onKeyDown?.(event);
|
|
1826
2120
|
if (event.defaultPrevented) return;
|
|
1827
2121
|
if (!(event.target instanceof HTMLElement) || !event.target.closest("[role=\"tab\"]")) return;
|
|
1828
2122
|
const count = node.children.length;
|
|
1829
2123
|
if (count === 0) return;
|
|
2124
|
+
const from = Math.max(visualSelected, 0);
|
|
1830
2125
|
const next = {
|
|
1831
|
-
ArrowLeft: (
|
|
1832
|
-
ArrowRight: (
|
|
2126
|
+
ArrowLeft: (from - 1 + count) % count,
|
|
2127
|
+
ArrowRight: (from + 1) % count,
|
|
1833
2128
|
End: count - 1,
|
|
1834
2129
|
Home: 0
|
|
1835
2130
|
}[event.key];
|
|
@@ -1870,14 +2165,32 @@ const TabsetToolbar = ({ style, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
|
1870
2165
|
...style
|
|
1871
2166
|
}
|
|
1872
2167
|
});
|
|
2168
|
+
/**
|
|
2169
|
+
* One answer per control, shared by the controls and by the layout deciding
|
|
2170
|
+
* whether to render a toolbar at all, so the two cannot disagree.
|
|
2171
|
+
*/
|
|
2172
|
+
const useTabsetChrome = () => {
|
|
2173
|
+
const draggableTabsets = useLayout((state) => state.draggableTabsets);
|
|
2174
|
+
const floatable = useLayout((state) => state.floatable);
|
|
2175
|
+
const isMaximized = useTabset((state) => state.isMaximized);
|
|
2176
|
+
const overflowCount = useTabset((state) => state.overflowItems.length);
|
|
2177
|
+
const showMaximize = useTabset((state) => state.showMaximize);
|
|
2178
|
+
const hasFloatLayer = useHasFloatLayer();
|
|
2179
|
+
if (floatable && !isMaximized && !hasFloatLayer) warnOnce("float-no-layer", "Tabset.FloatButton needs a <Layout.FloatLayer> around the layout (DashfooLayout adds one); the float control is hidden");
|
|
2180
|
+
return {
|
|
2181
|
+
float: floatable && !isMaximized && hasFloatLayer,
|
|
2182
|
+
grip: draggableTabsets && !isMaximized,
|
|
2183
|
+
maximize: showMaximize,
|
|
2184
|
+
overflow: overflowCount > 0
|
|
2185
|
+
};
|
|
2186
|
+
};
|
|
1873
2187
|
const TabsetGrip = ({ children, ref: userRef, ...props }) => {
|
|
1874
2188
|
const activeTabName = useTabset((state) => state.activeTab?.name ?? "");
|
|
1875
|
-
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1876
2189
|
const node = useTabset((state) => state.node);
|
|
1877
|
-
const
|
|
1878
|
-
const { ref } = useTabsetDraggable(node.id,
|
|
2190
|
+
const { grip } = useTabsetChrome();
|
|
2191
|
+
const { ref } = useTabsetDraggable(node.id, !grip, activeTabName);
|
|
1879
2192
|
const refCallback = useMemo(() => mergeRefs(ref, userRef), [ref, userRef]);
|
|
1880
|
-
if (
|
|
2193
|
+
if (!grip) return null;
|
|
1881
2194
|
return /* @__PURE__ */ jsx("button", {
|
|
1882
2195
|
"aria-label": "Move tabset",
|
|
1883
2196
|
title: "Move tabset",
|
|
@@ -1890,9 +2203,9 @@ const TabsetGrip = ({ children, ref: userRef, ...props }) => {
|
|
|
1890
2203
|
};
|
|
1891
2204
|
const TabsetMaximizeButton = ({ children, onClick, ...props }) => {
|
|
1892
2205
|
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1893
|
-
const showMaximize = useTabset((state) => state.showMaximize);
|
|
1894
2206
|
const toggleMaximize = useTabset((state) => state.toggleMaximize);
|
|
1895
|
-
|
|
2207
|
+
const { maximize } = useTabsetChrome();
|
|
2208
|
+
if (!maximize) return null;
|
|
1896
2209
|
const handleClick = (event) => {
|
|
1897
2210
|
onClick?.(event);
|
|
1898
2211
|
toggleMaximize();
|
|
@@ -1909,16 +2222,10 @@ const TabsetMaximizeButton = ({ children, onClick, ...props }) => {
|
|
|
1909
2222
|
});
|
|
1910
2223
|
};
|
|
1911
2224
|
const TabsetFloatButton = ({ children, onClick, ...props }) => {
|
|
1912
|
-
const floatable = useLayout((state) => state.floatable);
|
|
1913
2225
|
const dispatch = useLayout((state) => state.dispatch);
|
|
1914
|
-
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1915
2226
|
const node = useTabset((state) => state.node);
|
|
1916
|
-
const
|
|
1917
|
-
if (!
|
|
1918
|
-
if (!hasFloatLayer) {
|
|
1919
|
-
warnOnce("float-no-layer", "Tabset.FloatButton needs a <Layout.FloatLayer> around the layout (DashfooLayout adds one); the float control is hidden");
|
|
1920
|
-
return null;
|
|
1921
|
-
}
|
|
2227
|
+
const { float } = useTabsetChrome();
|
|
2228
|
+
if (!float) return null;
|
|
1922
2229
|
const handleClick = (event) => {
|
|
1923
2230
|
onClick?.(event);
|
|
1924
2231
|
const geometry = measureFloatRect(event.currentTarget);
|
|
@@ -1943,15 +2250,12 @@ const TabsetFloatButton = ({ children, onClick, ...props }) => {
|
|
|
1943
2250
|
//#region src/components/tabset/tabset-view.tsx
|
|
1944
2251
|
const tabsetContent = /* @__PURE__ */ jsx(TabsetContent, {});
|
|
1945
2252
|
const DefaultTabsetLayout = () => {
|
|
1946
|
-
const draggableTabsets = useLayout((state) => state.draggableTabsets);
|
|
1947
|
-
const floatable = useLayout((state) => state.floatable);
|
|
1948
2253
|
const renderTabsetToolbar = useLayout((state) => state.renderTabsetToolbar);
|
|
1949
2254
|
const tabLocation = useLayout((state) => state.tabLocation);
|
|
1950
2255
|
const tabStripEnabled = useLayout((state) => state.tabStripEnabled);
|
|
1951
|
-
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1952
2256
|
const node = useTabset((state) => state.node);
|
|
1953
|
-
const
|
|
1954
|
-
const showToolbar =
|
|
2257
|
+
const chrome = useTabsetChrome();
|
|
2258
|
+
const showToolbar = renderTabsetToolbar !== void 0 || Object.values(chrome).some(Boolean);
|
|
1955
2259
|
const strip = tabStripEnabled ? /* @__PURE__ */ jsxs(TabsetTabStrip, { children: [/* @__PURE__ */ jsx(TabsetTablist, { children: node.children.map((tab) => /* @__PURE__ */ jsxs(TabsetTab, {
|
|
1956
2260
|
tab,
|
|
1957
2261
|
children: [
|
|
@@ -1983,22 +2287,6 @@ const groupStyle = {
|
|
|
1983
2287
|
minHeight: 0,
|
|
1984
2288
|
minWidth: 0
|
|
1985
2289
|
};
|
|
1986
|
-
const WEIGHT_EPSILON = .01;
|
|
1987
|
-
const layoutFromWeights = (children, total) => children.reduce((layout, child) => {
|
|
1988
|
-
layout[child.id] = (child.weight ?? 1) / total * 100;
|
|
1989
|
-
return layout;
|
|
1990
|
-
}, {});
|
|
1991
|
-
const orderedFromLayout = (layout, children) => children.map((child) => layout[child.id] ?? 0);
|
|
1992
|
-
const layoutFromOrdered = (sizes, children) => children.reduce((layout, child, index) => {
|
|
1993
|
-
layout[child.id] = sizes[index] ?? 0;
|
|
1994
|
-
return layout;
|
|
1995
|
-
}, {});
|
|
1996
|
-
const layoutsMatch = (current, expected) => {
|
|
1997
|
-
const currentIds = Object.keys(current);
|
|
1998
|
-
const expectedIds = Object.keys(expected);
|
|
1999
|
-
if (currentIds.length !== expectedIds.length) return false;
|
|
2000
|
-
return expectedIds.every((id) => Math.abs((current[id] ?? NaN) - expected[id]) <= WEIGHT_EPSILON);
|
|
2001
|
-
};
|
|
2002
2290
|
const descendantMinSize = (child, axis, tabsetMinSize) => {
|
|
2003
2291
|
const explicit = dimensionToPixels(child.min);
|
|
2004
2292
|
if (explicit !== void 0) return explicit;
|
|
@@ -2019,91 +2307,33 @@ const RowView = ({ node, renderTabset }) => {
|
|
|
2019
2307
|
const tabsetMinSize = useLayout((state) => state.tabsetMinSize);
|
|
2020
2308
|
const globalSnap = useLayout((state) => state.snap);
|
|
2021
2309
|
const effectiveSnap = node.snap ?? globalSnap;
|
|
2022
|
-
const
|
|
2310
|
+
const snapGrid = useMemo(() => resolveSnapGrid(effectiveSnap, node.children.length), [effectiveSnap, node.children.length]);
|
|
2311
|
+
const snapActive = snapEnabled(snapGrid);
|
|
2023
2312
|
const orientation = node.orientation === "row" ? "horizontal" : "vertical";
|
|
2024
|
-
const total = node.children.reduce((sum, child) => sum +
|
|
2025
|
-
const
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
}
|
|
2038
|
-
};
|
|
2039
|
-
const setSnapTransition = (active) => {
|
|
2040
|
-
const element = groupElement.current;
|
|
2041
|
-
if (!element) return;
|
|
2042
|
-
if (active) element.dataset.dashfooSnapping = "true";
|
|
2043
|
-
else delete element.dataset.dashfooSnapping;
|
|
2044
|
-
};
|
|
2045
|
-
useLayoutEffect(() => {
|
|
2046
|
-
const group = groupRef.current;
|
|
2047
|
-
if (!group) return;
|
|
2048
|
-
const current = group.getLayout();
|
|
2049
|
-
if (!(Object.keys(current).length === Object.keys(desiredLayout).length && Object.keys(desiredLayout).every((id) => id in current)) || layoutsMatch(current, desiredLayout)) return;
|
|
2050
|
-
syncing.current = true;
|
|
2051
|
-
group.setLayout(desiredLayout);
|
|
2052
|
-
const frame = requestAnimationFrame(() => {
|
|
2053
|
-
syncing.current = false;
|
|
2054
|
-
});
|
|
2055
|
-
return () => {
|
|
2056
|
-
cancelAnimationFrame(frame);
|
|
2057
|
-
syncing.current = false;
|
|
2058
|
-
};
|
|
2059
|
-
}, [desiredLayout]);
|
|
2060
|
-
const handleLayoutChange = (layout) => {
|
|
2061
|
-
if (snapping.current || syncing.current || !resizableSplits) return;
|
|
2062
|
-
if (effectiveSnap === null || !snapActive) return;
|
|
2063
|
-
const boundary = activeBoundary.current;
|
|
2064
|
-
if (boundary === null) return;
|
|
2065
|
-
const { sizes, snapped } = snapSizes(orderedFromLayout(layout, node.children), boundary, effectiveSnap);
|
|
2066
|
-
setHighlight(snapped ? boundary : null);
|
|
2067
|
-
if (!snapped) {
|
|
2068
|
-
setSnapTransition(false);
|
|
2069
|
-
return;
|
|
2070
|
-
}
|
|
2071
|
-
setSnapTransition(true);
|
|
2072
|
-
const group = groupRef.current;
|
|
2073
|
-
const target = layoutFromOrdered(sizes, node.children);
|
|
2074
|
-
if (!group || layoutsMatch(layout, target)) return;
|
|
2075
|
-
snapping.current = true;
|
|
2076
|
-
group.setLayout(target);
|
|
2077
|
-
snapping.current = false;
|
|
2078
|
-
};
|
|
2079
|
-
const handleLayoutChanged = (layout, meta) => {
|
|
2080
|
-
if (!meta.isUserInteraction) return;
|
|
2081
|
-
if (syncing.current) return;
|
|
2082
|
-
const boundary = activeBoundary.current;
|
|
2083
|
-
activeBoundary.current = null;
|
|
2084
|
-
setHighlight(null);
|
|
2085
|
-
setSnapTransition(false);
|
|
2086
|
-
if (!resizableSplits) return;
|
|
2087
|
-
let weights = node.children.map((child) => layout[child.id] ?? child.weight ?? 1);
|
|
2088
|
-
if (boundary !== null && effectiveSnap !== null && snapActive) weights = snapSizes(weights, boundary, effectiveSnap).sizes;
|
|
2089
|
-
if (!weights.some((weight, index) => Math.abs(weight - (node.children[index]?.weight ?? 1)) > WEIGHT_EPSILON)) return;
|
|
2090
|
-
dispatch({
|
|
2091
|
-
rowId: node.id,
|
|
2092
|
-
type: "adjustSplit",
|
|
2093
|
-
weights
|
|
2094
|
-
});
|
|
2095
|
-
};
|
|
2313
|
+
const total = node.children.reduce((sum, child) => sum + child.weight, 0);
|
|
2314
|
+
const { beginBoundaryDrag, groupRef, onLayoutChange, onLayoutChanged, snappedBoundary } = useSnapResize({
|
|
2315
|
+
children: node.children,
|
|
2316
|
+
onWeightsSettled: (weights) => {
|
|
2317
|
+
dispatch({
|
|
2318
|
+
rowId: node.id,
|
|
2319
|
+
type: "adjustSplit",
|
|
2320
|
+
weights
|
|
2321
|
+
});
|
|
2322
|
+
},
|
|
2323
|
+
resizableSplits,
|
|
2324
|
+
snap: snapGrid
|
|
2325
|
+
});
|
|
2096
2326
|
return /* @__PURE__ */ jsx(Group, {
|
|
2097
2327
|
"data-dashfoo": "row",
|
|
2328
|
+
"data-dashfoo-snapping": snappedBoundary === null ? void 0 : "true",
|
|
2098
2329
|
disabled: !resizableSplits,
|
|
2099
|
-
elementRef: groupElement,
|
|
2100
2330
|
groupRef,
|
|
2101
|
-
onLayoutChange: snapActive ?
|
|
2102
|
-
onLayoutChanged
|
|
2331
|
+
onLayoutChange: snapActive ? onLayoutChange : void 0,
|
|
2332
|
+
onLayoutChanged,
|
|
2103
2333
|
orientation,
|
|
2104
2334
|
style: groupStyle,
|
|
2105
2335
|
children: node.children.map((child, index) => {
|
|
2106
|
-
const percent =
|
|
2336
|
+
const percent = child.weight / total * 100;
|
|
2107
2337
|
let min = child.min ? dimensionToSize(child.min) : void 0;
|
|
2108
2338
|
if (min === void 0) {
|
|
2109
2339
|
const minimum = descendantMinSize(child, node.orientation, tabsetMinSize);
|
|
@@ -2115,7 +2345,7 @@ const RowView = ({ node, renderTabset }) => {
|
|
|
2115
2345
|
"data-dashfoo-snapped": snappedBoundary === index - 1 ? "true" : void 0,
|
|
2116
2346
|
disabled: !resizableSplits,
|
|
2117
2347
|
onPointerDown: snapActive && resizableSplits ? () => {
|
|
2118
|
-
|
|
2348
|
+
beginBoundaryDrag(index - 1);
|
|
2119
2349
|
} : void 0
|
|
2120
2350
|
}) : null, /* @__PURE__ */ jsx(Panel$1, {
|
|
2121
2351
|
defaultSize: `${percent}%`,
|
|
@@ -2159,11 +2389,12 @@ const bodyStyle = {
|
|
|
2159
2389
|
minHeight: 0,
|
|
2160
2390
|
position: "relative"
|
|
2161
2391
|
};
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2392
|
+
const FLOAT_OVERRIDES = {
|
|
2393
|
+
draggableTabsets: false,
|
|
2394
|
+
floatable: false,
|
|
2395
|
+
maximizable: false,
|
|
2396
|
+
maximizedTabsetId: void 0
|
|
2165
2397
|
};
|
|
2166
|
-
const TAP_SLOP = 4;
|
|
2167
2398
|
const FloatTitleEditor = ({ dispatch, node, onDone }) => {
|
|
2168
2399
|
const inputRef = useRef(null);
|
|
2169
2400
|
const { handleBlur, handleKeyDown } = useInlineRename({
|
|
@@ -2211,25 +2442,10 @@ const FloatTitle = ({ dispatch, node, renamable }) => {
|
|
|
2211
2442
|
children: floatTitle(node)
|
|
2212
2443
|
});
|
|
2213
2444
|
};
|
|
2214
|
-
const FloatPanel = ({
|
|
2445
|
+
const FloatPanel = ({ node, onFocus, zIndex }) => {
|
|
2215
2446
|
const dispatch = useLayout((state) => state.dispatch);
|
|
2216
|
-
const closableTabs = useLayout((state) => state.closableTabs);
|
|
2217
|
-
const draggableTabs = useLayout((state) => state.draggableTabs);
|
|
2218
2447
|
const editable = useLayout((state) => state.editable);
|
|
2219
|
-
const
|
|
2220
|
-
const resizableSplits = useLayout((state) => state.resizableSplits);
|
|
2221
|
-
const keepMounted = useLayout((state) => state.keepMounted);
|
|
2222
|
-
const renderTab = useLayout((state) => state.renderTab);
|
|
2223
|
-
const renderTabLabel = useLayout((state) => state.renderTabLabel);
|
|
2224
|
-
const renderTabsetToolbar = useLayout((state) => state.renderTabsetToolbar);
|
|
2225
|
-
const snap = useLayout((state) => state.snap);
|
|
2226
|
-
const panelRef = useRef(null);
|
|
2227
|
-
const gestureRef = useRef(null);
|
|
2228
|
-
const latestRef = useRef(node.geometry);
|
|
2229
|
-
const movedRef = useRef(false);
|
|
2230
|
-
const setPanel = (element) => {
|
|
2231
|
-
panelRef.current = element;
|
|
2232
|
-
};
|
|
2448
|
+
const minimized = node.minimized === true;
|
|
2233
2449
|
const restore = () => {
|
|
2234
2450
|
if (!editable) return;
|
|
2235
2451
|
dispatch({
|
|
@@ -2238,88 +2454,30 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2238
2454
|
type: "setFloatMinimized"
|
|
2239
2455
|
});
|
|
2240
2456
|
};
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
edges: edgeKey === void 0 ? null : EDGE_BY_KEY.get(edgeKey) ?? null,
|
|
2255
|
-
pointerId: event.pointerId,
|
|
2256
|
-
start: node.geometry,
|
|
2257
|
-
startX: event.clientX,
|
|
2258
|
-
startY: event.clientY
|
|
2259
|
-
};
|
|
2260
|
-
gestureRef.current = gesture;
|
|
2261
|
-
latestRef.current = node.geometry;
|
|
2262
|
-
if (gesture.edges) panel.setPointerCapture(event.pointerId);
|
|
2263
|
-
};
|
|
2264
|
-
const handlePointerUp = (event) => {
|
|
2265
|
-
const gesture = gestureRef.current;
|
|
2266
|
-
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
2267
|
-
gestureRef.current = null;
|
|
2268
|
-
const panel = panelRef.current;
|
|
2269
|
-
if (panel?.hasPointerCapture?.(event.pointerId) === true) panel.releasePointerCapture(event.pointerId);
|
|
2270
|
-
if (!movedRef.current) {
|
|
2271
|
-
if (node.minimized === true) restore();
|
|
2272
|
-
return;
|
|
2273
|
-
}
|
|
2274
|
-
dispatch({
|
|
2275
|
-
floatId: node.id,
|
|
2276
|
-
geometry: latestRef.current,
|
|
2277
|
-
type: "moveFloat"
|
|
2278
|
-
});
|
|
2279
|
-
};
|
|
2280
|
-
const handlePointerMove = (event) => {
|
|
2281
|
-
const gesture = gestureRef.current;
|
|
2282
|
-
const panel = panelRef.current;
|
|
2283
|
-
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2284
|
-
if (event.buttons === 0) {
|
|
2285
|
-
handlePointerUp(event);
|
|
2286
|
-
return;
|
|
2287
|
-
}
|
|
2288
|
-
const dx = event.clientX - gesture.startX;
|
|
2289
|
-
const dy = event.clientY - gesture.startY;
|
|
2290
|
-
if (!movedRef.current) {
|
|
2291
|
-
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
2292
|
-
movedRef.current = true;
|
|
2293
|
-
if (!gesture.edges) panel.setPointerCapture(gesture.pointerId);
|
|
2294
|
-
}
|
|
2295
|
-
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
2296
|
-
...gesture.start,
|
|
2297
|
-
left: gesture.start.left + dx,
|
|
2298
|
-
top: gesture.start.top + dy
|
|
2299
|
-
}, gesture.bounds, node.minimized === true ? CHIP_SIZE : void 0);
|
|
2300
|
-
latestRef.current = next;
|
|
2301
|
-
panel.style.left = `${next.left}px`;
|
|
2302
|
-
panel.style.top = `${next.top}px`;
|
|
2303
|
-
if (node.minimized !== true) {
|
|
2304
|
-
panel.style.width = `${next.width}px`;
|
|
2305
|
-
panel.style.height = `${next.height}px`;
|
|
2306
|
-
}
|
|
2307
|
-
};
|
|
2308
|
-
const handlePointerCancel = (event) => {
|
|
2309
|
-
const gesture = gestureRef.current;
|
|
2310
|
-
const panel = panelRef.current;
|
|
2311
|
-
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2312
|
-
gestureRef.current = null;
|
|
2313
|
-
if (panel.hasPointerCapture?.(event.pointerId)) panel.releasePointerCapture(event.pointerId);
|
|
2314
|
-
if (!movedRef.current) return;
|
|
2315
|
-
panel.style.left = `${gesture.start.left}px`;
|
|
2316
|
-
panel.style.top = `${gesture.start.top}px`;
|
|
2317
|
-
if (node.minimized !== true) {
|
|
2318
|
-
panel.style.width = `${gesture.start.width}px`;
|
|
2319
|
-
panel.style.height = `${gesture.start.height}px`;
|
|
2457
|
+
const { handlers, ref, style } = useFloatGesture({
|
|
2458
|
+
editable,
|
|
2459
|
+
geometry: node.geometry,
|
|
2460
|
+
minimized,
|
|
2461
|
+
onCommit: (geometry) => {
|
|
2462
|
+
dispatch({
|
|
2463
|
+
floatId: node.id,
|
|
2464
|
+
geometry,
|
|
2465
|
+
type: "moveFloat"
|
|
2466
|
+
});
|
|
2467
|
+
},
|
|
2468
|
+
onTap: () => {
|
|
2469
|
+
if (minimized) restore();
|
|
2320
2470
|
}
|
|
2471
|
+
});
|
|
2472
|
+
const { onPointerDown, ...rootHandlers } = handlers;
|
|
2473
|
+
const frameStyle = {
|
|
2474
|
+
...style,
|
|
2475
|
+
pointerEvents: "auto",
|
|
2476
|
+
position: "absolute",
|
|
2477
|
+
zIndex
|
|
2321
2478
|
};
|
|
2322
|
-
if (
|
|
2479
|
+
if (minimized) return /* @__PURE__ */ jsxs("button", {
|
|
2480
|
+
...rootHandlers,
|
|
2323
2481
|
"aria-label": `Restore ${floatTitle(node)} panel`,
|
|
2324
2482
|
"data-dashfoo": "float-chip",
|
|
2325
2483
|
onKeyDown: (event) => {
|
|
@@ -2328,19 +2486,10 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2328
2486
|
restore();
|
|
2329
2487
|
}
|
|
2330
2488
|
},
|
|
2331
|
-
|
|
2332
|
-
onPointerDown: handlePointerDown,
|
|
2489
|
+
onPointerDown,
|
|
2333
2490
|
onPointerDownCapture: onFocus,
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
ref: setPanel,
|
|
2337
|
-
style: {
|
|
2338
|
-
left: node.geometry.left,
|
|
2339
|
-
pointerEvents: "auto",
|
|
2340
|
-
position: "absolute",
|
|
2341
|
-
top: node.geometry.top,
|
|
2342
|
-
zIndex
|
|
2343
|
-
},
|
|
2491
|
+
ref,
|
|
2492
|
+
style: frameStyle,
|
|
2344
2493
|
title: floatTitle(node),
|
|
2345
2494
|
type: "button",
|
|
2346
2495
|
children: [/* @__PURE__ */ jsx(FloatIcon, {}), /* @__PURE__ */ jsx("span", {
|
|
@@ -2348,31 +2497,16 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2348
2497
|
children: floatTitle(node)
|
|
2349
2498
|
})]
|
|
2350
2499
|
});
|
|
2351
|
-
const floatModel = {
|
|
2352
|
-
global,
|
|
2353
|
-
layout: node.layout,
|
|
2354
|
-
version: 1
|
|
2355
|
-
};
|
|
2356
2500
|
return /* @__PURE__ */ jsxs("div", {
|
|
2501
|
+
...rootHandlers,
|
|
2357
2502
|
"data-dashfoo": "float",
|
|
2358
|
-
onPointerCancel: handlePointerCancel,
|
|
2359
2503
|
onPointerDownCapture: onFocus,
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
ref: setPanel,
|
|
2363
|
-
style: {
|
|
2364
|
-
height: node.geometry.height,
|
|
2365
|
-
left: node.geometry.left,
|
|
2366
|
-
pointerEvents: "auto",
|
|
2367
|
-
position: "absolute",
|
|
2368
|
-
top: node.geometry.top,
|
|
2369
|
-
width: node.geometry.width,
|
|
2370
|
-
zIndex
|
|
2371
|
-
},
|
|
2504
|
+
ref,
|
|
2505
|
+
style: frameStyle,
|
|
2372
2506
|
children: [
|
|
2373
2507
|
/* @__PURE__ */ jsxs("div", {
|
|
2374
2508
|
"data-dashfoo": "float-titlebar",
|
|
2375
|
-
onPointerDown
|
|
2509
|
+
onPointerDown,
|
|
2376
2510
|
style: editable ? titleBarStyle : {
|
|
2377
2511
|
...titleBarStyle,
|
|
2378
2512
|
cursor: "default"
|
|
@@ -2427,29 +2561,15 @@ const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
|
2427
2561
|
/* @__PURE__ */ jsx("div", {
|
|
2428
2562
|
"data-dashfoo": "float-body",
|
|
2429
2563
|
style: bodyStyle,
|
|
2430
|
-
children: /* @__PURE__ */ jsx(
|
|
2431
|
-
|
|
2432
|
-
dispatch,
|
|
2433
|
-
draggableTabs,
|
|
2434
|
-
draggableTabsets: false,
|
|
2435
|
-
editable,
|
|
2436
|
-
floatable: false,
|
|
2437
|
-
keepMounted,
|
|
2438
|
-
maximizable: false,
|
|
2439
|
-
model: floatModel,
|
|
2440
|
-
renamableTabs,
|
|
2441
|
-
renderTab,
|
|
2442
|
-
renderTabLabel,
|
|
2443
|
-
renderTabsetToolbar,
|
|
2444
|
-
resizableSplits,
|
|
2445
|
-
snap: snap ?? void 0,
|
|
2564
|
+
children: /* @__PURE__ */ jsx(LayoutOverrides, {
|
|
2565
|
+
overrides: FLOAT_OVERRIDES,
|
|
2446
2566
|
children: /* @__PURE__ */ jsx(DragProvider, { children: /* @__PURE__ */ jsx(RowView, { node: node.layout }) })
|
|
2447
2567
|
})
|
|
2448
2568
|
}),
|
|
2449
2569
|
editable && RESIZE_HANDLES.map((handle) => /* @__PURE__ */ jsx("div", {
|
|
2450
2570
|
"data-dashfoo": "float-resize",
|
|
2451
2571
|
"data-edge": handle.key,
|
|
2452
|
-
onPointerDown
|
|
2572
|
+
onPointerDown,
|
|
2453
2573
|
style: {
|
|
2454
2574
|
position: "absolute",
|
|
2455
2575
|
touchAction: "none",
|
|
@@ -2469,14 +2589,13 @@ const overlayStyle = {
|
|
|
2469
2589
|
position: "fixed",
|
|
2470
2590
|
zIndex: 1
|
|
2471
2591
|
};
|
|
2472
|
-
const FloatOverlay = ({ floats
|
|
2592
|
+
const FloatOverlay = ({ floats }) => {
|
|
2473
2593
|
const [topId, setTopId] = useState(null);
|
|
2474
2594
|
if (floats.length === 0) return null;
|
|
2475
2595
|
return /* @__PURE__ */ jsx("div", {
|
|
2476
2596
|
"data-dashfoo": "float-overlay",
|
|
2477
2597
|
style: overlayStyle,
|
|
2478
2598
|
children: floats.map((node) => /* @__PURE__ */ jsx(FloatPanel, {
|
|
2479
|
-
global,
|
|
2480
2599
|
node,
|
|
2481
2600
|
onFocus: () => {
|
|
2482
2601
|
setTopId(node.id);
|
|
@@ -2485,12 +2604,9 @@ const FloatOverlay = ({ floats, global }) => {
|
|
|
2485
2604
|
}, node.id))
|
|
2486
2605
|
});
|
|
2487
2606
|
};
|
|
2488
|
-
const FloatLayer = ({ children, floats
|
|
2607
|
+
const FloatLayer = ({ children, floats }) => /* @__PURE__ */ jsxs(FloatLayerContext.Provider, {
|
|
2489
2608
|
value: true,
|
|
2490
|
-
children: [children, /* @__PURE__ */ jsx(FloatOverlay, {
|
|
2491
|
-
floats,
|
|
2492
|
-
global
|
|
2493
|
-
})]
|
|
2609
|
+
children: [children, /* @__PURE__ */ jsx(FloatOverlay, { floats })]
|
|
2494
2610
|
});
|
|
2495
2611
|
|
|
2496
2612
|
//#endregion
|
|
@@ -2506,8 +2622,8 @@ const Layout = {
|
|
|
2506
2622
|
//#endregion
|
|
2507
2623
|
//#region src/components/dashfoo-layout.tsx
|
|
2508
2624
|
const DEFAULT_PERSIST_DEBOUNCE_MS = 300;
|
|
2509
|
-
const resolvePersist = (persist
|
|
2510
|
-
if (persist === void 0
|
|
2625
|
+
const resolvePersist = (persist) => {
|
|
2626
|
+
if (persist === void 0) return null;
|
|
2511
2627
|
if (typeof persist === "string") return {
|
|
2512
2628
|
debounceMs: DEFAULT_PERSIST_DEBOUNCE_MS,
|
|
2513
2629
|
key: persist,
|
|
@@ -2522,7 +2638,8 @@ const resolvePersist = (persist, hasDefaultModel) => {
|
|
|
2522
2638
|
const warnedComponents = /* @__PURE__ */ new Set();
|
|
2523
2639
|
const DashfooLayout = forwardRef((props, ref) => {
|
|
2524
2640
|
const { closableTabs = true, components, defaultModel, draggableTabs = true, draggableTabsets = true, editable = true, factory, floatable = false, keepMounted = false, maximizable = true, model, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange, persist, renamableTabs = true, renderTabLabel, renderTabsetToolbar, resizableSplits = true, responsive, snap } = props;
|
|
2525
|
-
const
|
|
2641
|
+
const persistConfig = useMemo(() => resolvePersist(persist), [persist]);
|
|
2642
|
+
const persistence = usePersistence(persistConfig, defaultModel);
|
|
2526
2643
|
const handleModelChange = useCallback((next, action) => {
|
|
2527
2644
|
persistence.save(next);
|
|
2528
2645
|
onModelChange?.(next, action);
|
|
@@ -2613,37 +2730,43 @@ const DashfooLayout = forwardRef((props, ref) => {
|
|
|
2613
2730
|
}, [components, factory]);
|
|
2614
2731
|
const [containerRef, width] = useContainerWidth();
|
|
2615
2732
|
const isCompact = responsive !== void 0 && width <= responsive.maxWidth;
|
|
2616
|
-
|
|
2733
|
+
/** Everything the compact breakpoint changes, decided once. */
|
|
2734
|
+
const presentation = useMemo(() => isCompact ? {
|
|
2735
|
+
maximizable: false,
|
|
2736
|
+
model: stackModel(store.model, responsive?.orientation),
|
|
2737
|
+
restructurable: false
|
|
2738
|
+
} : {
|
|
2739
|
+
maximizable,
|
|
2740
|
+
model: store.model,
|
|
2741
|
+
restructurable: true
|
|
2742
|
+
}, [
|
|
2617
2743
|
isCompact,
|
|
2744
|
+
maximizable,
|
|
2618
2745
|
responsive?.orientation,
|
|
2619
2746
|
store.model
|
|
2620
2747
|
]);
|
|
2621
|
-
const
|
|
2622
|
-
const dispatch = useCallback((action) => {
|
|
2623
|
-
if (isCompact && action.type === "adjustSplit") return;
|
|
2624
|
-
storeDispatch(action);
|
|
2625
|
-
}, [isCompact, storeDispatch]);
|
|
2748
|
+
const view = presentation.model;
|
|
2626
2749
|
const maximized = view.maximizedTabsetId === void 0 ? void 0 : findTabset(view, view.maximizedTabsetId);
|
|
2627
2750
|
const tree = /* @__PURE__ */ jsx(Layout.FloatLayer, {
|
|
2628
|
-
floats:
|
|
2629
|
-
global: store.model.global,
|
|
2751
|
+
floats: view.floats,
|
|
2630
2752
|
children: /* @__PURE__ */ jsx(Layout.DragLayer, { children: maximized ? /* @__PURE__ */ jsx(Layout.Tabset, { node: maximized }) : /* @__PURE__ */ jsx(Layout.Rows, { node: view.layout }) })
|
|
2631
2753
|
});
|
|
2632
2754
|
return /* @__PURE__ */ jsx(Layout.Root, {
|
|
2633
2755
|
closableTabs,
|
|
2634
|
-
dispatch,
|
|
2635
|
-
draggableTabs
|
|
2636
|
-
draggableTabsets
|
|
2756
|
+
dispatch: store.dispatch,
|
|
2757
|
+
draggableTabs,
|
|
2758
|
+
draggableTabsets,
|
|
2637
2759
|
editable,
|
|
2638
|
-
floatable
|
|
2760
|
+
floatable,
|
|
2639
2761
|
keepMounted,
|
|
2640
|
-
maximizable,
|
|
2762
|
+
maximizable: presentation.maximizable,
|
|
2641
2763
|
model: view,
|
|
2642
2764
|
renamableTabs,
|
|
2643
2765
|
renderTab,
|
|
2644
2766
|
renderTabLabel,
|
|
2645
2767
|
renderTabsetToolbar,
|
|
2646
|
-
resizableSplits
|
|
2768
|
+
resizableSplits,
|
|
2769
|
+
restructurable: presentation.restructurable,
|
|
2647
2770
|
rootRef: containerRef,
|
|
2648
2771
|
snap,
|
|
2649
2772
|
children: hasSharedManager ? tree : /* @__PURE__ */ jsx(DashfooDragProvider, { children: tree })
|