@grafloria/element 0.4.56 → 0.4.58
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/package.json +1 -1
- package/src/lib/dashboard-kit/board-ctx.d.ts +6 -0
- package/src/lib/dashboard-kit/board-ctx.js +6 -1
- package/src/lib/dashboard-kit/grid-binder.d.ts +3 -4
- package/src/lib/dashboard-kit/grid-binder.js +73 -607
- package/src/lib/dashboard-kit/tear-out.d.ts +55 -0
- package/src/lib/dashboard-kit/tear-out.js +613 -0
|
@@ -56,13 +56,16 @@ import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridI
|
|
|
56
56
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
57
57
|
import { captionOfGroup, captionPassThrough, sectionCaptionReserve } from './caption.js';
|
|
58
58
|
import { TAB_STRIP_HEIGHT } from './tabs.js';
|
|
59
|
-
import { BESIDE_BAND, resolve as resolveZone
|
|
59
|
+
import { BESIDE_BAND, resolve as resolveZone } from './zones.js';
|
|
60
60
|
import { SequenceCommand, SetGroupCellCommand, tileCommands } from './commit.js';
|
|
61
|
+
import { EDGE_GRACE } from './board-ctx.js';
|
|
61
62
|
import { createProjection } from './project.js';
|
|
62
63
|
import { cursorFor, edgesNear, NO_EDGES, anyEdge } from './edges.js';
|
|
63
64
|
import { DRAG_HANDLE_CLASS, dragHandleSelector, gripHostOf, normalizeDragHandle, pressOnDragHandle, sameDragHandle } from './grip.js';
|
|
64
65
|
import { createChrome, edgeGripFor, isTabsGroup } from './chrome.js';
|
|
65
66
|
import { createKeyboard, describeCell, liveRegionFor } from './keyboard.js';
|
|
67
|
+
import { createTearOut, TEAR_OUT_MIN_ROWS } from './tear-out.js';
|
|
68
|
+
export { TEAR_OUT_MIN_ROWS } from './tear-out.js';
|
|
66
69
|
export { EDGE_GRIP, anyEdge } from './edges.js';
|
|
67
70
|
export { GRIP_CLASS, DRAG_HANDLE_CLASS, CAPTION_BAND, normalizeDragHandle, dragHandleSelector, gripOf, syncGrip, gripHostOf, pressOnDragHandle } from './grip.js';
|
|
68
71
|
export { SequenceCommand, SetGroupCellCommand } from './commit.js';
|
|
@@ -101,8 +104,6 @@ export function ownsPress(container, diagram, ev, hit) {
|
|
|
101
104
|
}
|
|
102
105
|
return true;
|
|
103
106
|
}
|
|
104
|
-
/** A torn-out page never arrives shorter than this: a strip with no room under it is not a group. */
|
|
105
|
-
export const TEAR_OUT_MIN_ROWS = 2;
|
|
106
107
|
/**
|
|
107
108
|
* Binders on the same canvas find each other here. A WeakMap: a canvas that
|
|
108
109
|
* is gone takes its (empty) peer set with it — the strong Map it replaced kept
|
|
@@ -696,6 +697,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
696
697
|
if (!engine.getItem(id))
|
|
697
698
|
return;
|
|
698
699
|
engine.remove(id);
|
|
700
|
+
// THE LIVE BOUND FOLLOWS THE MEMBERS. A board that took rows from its
|
|
701
|
+
// parent to hold an arrival (D4) keeps that bound while the tile is here;
|
|
702
|
+
// when the tile leaves — an undo of the drop, a removal — the bound goes
|
|
703
|
+
// back to what the remaining tiles need, or the board stays a row taller
|
|
704
|
+
// than anything in it.
|
|
705
|
+
if (designRows !== undefined)
|
|
706
|
+
setLiveBound(liveBound(engine.getItems()));
|
|
699
707
|
project();
|
|
700
708
|
api.render();
|
|
701
709
|
};
|
|
@@ -821,7 +829,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
821
829
|
/** Small overshoots CLAMP onto the board instead of counting as off-board —
|
|
822
830
|
* the plan prototype cannot leave its board at all (cells clamp at the
|
|
823
831
|
* edges), so a 60px slip past the frame must not dim or delete. */
|
|
824
|
-
const EDGE_GRACE = 60;
|
|
825
832
|
/** A point inside a member group's own frame (its strip and its pages). */
|
|
826
833
|
const worldInsideGroup = (g, x, y) => {
|
|
827
834
|
const s = sizeOf(g);
|
|
@@ -2394,9 +2401,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2394
2401
|
const squeezeBefore = squeezeRoom;
|
|
2395
2402
|
let entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2396
2403
|
// Only a board with NO parent tile to grow through squeezes — a page. A
|
|
2397
|
-
// full SECTION
|
|
2398
|
-
//
|
|
2399
|
-
// escalation path's, not a squeeze's, and is not built yet.
|
|
2404
|
+
// full SECTION grows instead (D4, below): its height is a tile of its
|
|
2405
|
+
// parent, and the parent is what has the rows to give.
|
|
2400
2406
|
if (!entered && !parentPeer()) {
|
|
2401
2407
|
const room = elasticRows();
|
|
2402
2408
|
if (room !== undefined && room > ((_a = bound()) !== null && _a !== void 0 ? _a : 0)) {
|
|
@@ -2405,7 +2411,38 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2405
2411
|
entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2406
2412
|
}
|
|
2407
2413
|
}
|
|
2414
|
+
let grown = null;
|
|
2415
|
+
const ungrow = () => {
|
|
2416
|
+
const g = grown;
|
|
2417
|
+
if (!g)
|
|
2418
|
+
return;
|
|
2419
|
+
g.peer.resizeMemberBy(group.id, -g.rows);
|
|
2420
|
+
setLiveBound(Math.max(1, (maxRows !== null && maxRows !== void 0 ? maxRows : 1) - g.rows));
|
|
2421
|
+
grown = null;
|
|
2422
|
+
};
|
|
2423
|
+
if (!entered && escalate) {
|
|
2424
|
+
const parent = parentPeer();
|
|
2425
|
+
let rowsAdded = 0;
|
|
2426
|
+
let firstBefore = null;
|
|
2427
|
+
let lastAfter = null;
|
|
2428
|
+
// At most the tile's own height in rows: past that the board is not
|
|
2429
|
+
// "full", it is smaller than the thing being dropped into it.
|
|
2430
|
+
for (let i = 0; parent && !entered && i < Math.max(1, span.h); i++) {
|
|
2431
|
+
const res = parent.resizeMemberBy(group.id, +1);
|
|
2432
|
+
if (!res.changed || !res.cellBefore || !res.cellAfter || !res.frameBefore || !res.frameAfter)
|
|
2433
|
+
break;
|
|
2434
|
+
rowsAdded += 1;
|
|
2435
|
+
firstBefore = firstBefore !== null && firstBefore !== void 0 ? firstBefore : { cell: res.cellBefore, frame: res.frameBefore };
|
|
2436
|
+
lastAfter = { cell: res.cellAfter, frame: res.frameAfter };
|
|
2437
|
+
setLiveBound((maxRows !== null && maxRows !== void 0 ? maxRows : 0) + 1);
|
|
2438
|
+
entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2439
|
+
}
|
|
2440
|
+
if (parent && rowsAdded > 0 && firstBefore && lastAfter) {
|
|
2441
|
+
grown = { peer: parent, rows: rowsAdded, cellBefore: firstBefore.cell, frameBefore: firstBefore.frame, cellAfter: lastAfter.cell, frameAfter: lastAfter.frame };
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2408
2444
|
if (!entered) {
|
|
2445
|
+
ungrow();
|
|
2409
2446
|
setSqueeze(squeezeBefore);
|
|
2410
2447
|
engine.endGesture();
|
|
2411
2448
|
return null; // a bounded, full board refuses the adoption
|
|
@@ -2535,6 +2572,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2535
2572
|
if (engine.getItem(node.id))
|
|
2536
2573
|
engine.remove(node.id);
|
|
2537
2574
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
2575
|
+
ungrow(); // the rows this board took from its parent for the arrival go back (D4)
|
|
2538
2576
|
setSqueeze(squeezeBefore);
|
|
2539
2577
|
adoptedGhostId = null;
|
|
2540
2578
|
disarmGlideSoon();
|
|
@@ -2555,6 +2593,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2555
2593
|
// Widgets AND sections this board's adoption displaced, as one list: a
|
|
2556
2594
|
// dock or a push that moved a section commits it with the drop.
|
|
2557
2595
|
const commands = tileCommands(deltasSince(startCells, startGeom, node.id));
|
|
2596
|
+
// …and the rows this board took from its parent to hold the arrival (D4).
|
|
2597
|
+
if (grown) {
|
|
2598
|
+
commands.push(new SetGroupCellCommand(group.id, grown.cellBefore, grown.cellAfter, grown.frameBefore, grown.frameAfter));
|
|
2599
|
+
grown = null;
|
|
2600
|
+
}
|
|
2558
2601
|
engine.endGesture();
|
|
2559
2602
|
pendingDrop = node.id; // the tile stays for the member the commit (or the host's drop-in) adds
|
|
2560
2603
|
if (squeezeRoom !== undefined) {
|
|
@@ -2989,606 +3032,29 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2989
3032
|
api.container.addEventListener('focusin', keyboard.onFocusIn);
|
|
2990
3033
|
api.container.addEventListener('keydown', keyboard.onKey);
|
|
2991
3034
|
// -- tab tear-out -----------------------------------------------------------
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
};
|
|
3016
|
-
// The board holds the NEW group, not the bare page: it is adopted under the
|
|
3017
|
-
// group's id, sized for the page plus its strip, taking the room under the
|
|
3018
|
-
// pointer with its top edge at the pointer — the tab is what the pointer
|
|
3019
|
-
// holds, the page hangs below it. NOT at the press: a ghost in the engine
|
|
3020
|
-
// costs rows, and on a fit board every tile squeezes the moment it enters,
|
|
3021
|
-
// so the ghost enters only when the pointer is over free board space, and
|
|
3022
|
-
// leaves whenever it is over a strip, a group or an edge. A full board that
|
|
3023
|
-
// refuses the ghost still lets the page join, reorder or split.
|
|
3024
|
-
let leg = null;
|
|
3025
|
-
/** The board holding the leg: null = this one, else the foreign peer under the pointer. */
|
|
3026
|
-
let legPeer = null;
|
|
3027
|
-
const ensureLeg = (world, peer = null) => {
|
|
3028
|
-
// The page TRAVELS at most half the board tall (the dock's measure): at
|
|
3029
|
-
// its natural height — a page fills its container, eight rows on the
|
|
3030
|
-
// demo — the ghost crossing the KPI row tore the whole dashboard apart
|
|
3031
|
-
// on its way to a cell (identification round, D1 03–08).
|
|
3032
|
-
const capPx = dockSpan.h * (rowHeightFor(geom(), rows()) + gap) - gap;
|
|
3033
|
-
const size = { width: plan.size.width, height: Math.min(plan.size.height, capPx) };
|
|
3034
|
-
// A different board under the pointer takes the leg over, the old one
|
|
3035
|
-
// back to its pre-entry layout first. An INNER tab used to know only
|
|
3036
|
-
// the board owning its container — over the main board the chip dimmed
|
|
3037
|
-
// and the release did nothing (identification round, L2).
|
|
3038
|
-
if (leg && legPeer !== peer) {
|
|
3039
|
-
leg.abort();
|
|
3040
|
-
leg = null;
|
|
3041
|
-
}
|
|
3042
|
-
if (!leg) {
|
|
3043
|
-
leg = peer ? peer.adopt({ id: plan.arrivingId }, world, size, { fit: 'shrink', anchor: 'top' }) : adopt({ id: plan.arrivingId }, world, size, { fit: 'shrink', anchor: 'top' });
|
|
3044
|
-
legPeer = leg ? peer : null;
|
|
3045
|
-
}
|
|
3046
|
-
return leg;
|
|
3047
|
-
};
|
|
3048
|
-
/** The deepest OTHER board under the point that may take the page — never one of the page's own boards. */
|
|
3049
|
-
const foreignAt = (wx, wy) => {
|
|
3050
|
-
let best = null;
|
|
3051
|
-
for (const p of peersOnCanvas()) {
|
|
3052
|
-
if (p === selfPeer || plan.ownBoards.includes(p.group.id))
|
|
3053
|
-
continue;
|
|
3054
|
-
if (!p.containsWorld(wx, wy))
|
|
3055
|
-
continue;
|
|
3056
|
-
if (!best || p.frameArea() < best.frameArea())
|
|
3057
|
-
best = p;
|
|
3058
|
-
}
|
|
3059
|
-
return best;
|
|
3060
|
-
};
|
|
3061
|
-
const naturalSpan = (() => {
|
|
3062
|
-
const sp = sizeToSpan(plan.size.width, plan.size.height, frame(), geom(), rows());
|
|
3063
|
-
return { w: Math.max(1, Math.min(columns, sp.w)), h: Math.max(TEAR_OUT_MIN_ROWS, sp.h) };
|
|
3064
|
-
})();
|
|
3065
|
-
// A DOCKED group takes at most HALF the board — VS Code's edge drop splits
|
|
3066
|
-
// the area in two. A page as tall as the container it left would
|
|
3067
|
-
// otherwise shove the whole dashboard out of view. Half of the board as
|
|
3068
|
-
// it stood at the press, and of what the canvas shows, whichever is less.
|
|
3069
|
-
const dockSpan = (() => {
|
|
3070
|
-
const rows0 = rows();
|
|
3071
|
-
const rect = api.container.getBoundingClientRect();
|
|
3072
|
-
const rh = rowHeightFor(geom(), rows0) + gap;
|
|
3073
|
-
const visible = rect.height > 0 && rh > 0 ? Math.max(1, Math.floor((rect.height + gap) / rh)) : rows0;
|
|
3074
|
-
const halfRows = Math.max(TEAR_OUT_MIN_ROWS, Math.ceil(Math.min(rows0, visible) / 2));
|
|
3075
|
-
const halfCols = Math.max(1, Math.ceil(columns / 2));
|
|
3076
|
-
return { w: Math.min(naturalSpan.w, halfCols), h: Math.min(naturalSpan.h, halfRows) };
|
|
3077
|
-
})();
|
|
3078
|
-
tearing = pageId;
|
|
3079
|
-
const doc = (_a = api.container.ownerDocument) !== null && _a !== void 0 ? _a : document;
|
|
3080
|
-
const chip = doc.createElement('div');
|
|
3081
|
-
chip.className = 'axdb-drag-chip axdb-tab-chip';
|
|
3082
|
-
chip.textContent = plan.label;
|
|
3083
|
-
doc.body.appendChild(chip);
|
|
3084
|
-
const moveChip = (cx, cy) => {
|
|
3085
|
-
chip.style.left = `${cx + 6}px`;
|
|
3086
|
-
chip.style.top = `${cy + 6}px`;
|
|
3087
|
-
};
|
|
3088
|
-
moveChip(ev.clientX, ev.clientY);
|
|
3089
|
-
armGlide();
|
|
3090
|
-
api.render();
|
|
3091
|
-
const layer = htmlLayer();
|
|
3092
|
-
const area = (g) => {
|
|
3093
|
-
const sz = sizeOf(g);
|
|
3094
|
-
return sz.width * sz.height;
|
|
3095
|
-
};
|
|
3096
|
-
const targets = plan.joinTargets
|
|
3097
|
-
.map((id) => diagram.getGroup(id))
|
|
3098
|
-
.filter((g) => !!g && g.id !== fromGroupId)
|
|
3099
|
-
.sort((a, b) => area(a) - area(b));
|
|
3100
|
-
// GEOMETRY MUST NOT MOVE WITH THE GHOST. On a fit board the rows squeeze
|
|
3101
|
-
// while the ghost occupies some and relax when it leaves, so a target's
|
|
3102
|
-
// frame changes with the very decision being made about it: a join makes
|
|
3103
|
-
// the ghost leave, the frame grows, and the same pointer now reads as a
|
|
3104
|
-
// split. The target and its frame are therefore anchored when the pointer
|
|
3105
|
-
// enters it and kept until it leaves; the board frame is the one at press.
|
|
3106
|
-
let anchor = null;
|
|
3107
|
-
const inRect = (r, wx, wy) => wx >= r.x && wx <= r.x + r.width && wy >= r.y && wy <= r.y + r.height;
|
|
3108
|
-
const targetAt = (wx, wy) => {
|
|
3109
|
-
var _a;
|
|
3110
|
-
if (anchor && inRect(anchor.frame, wx, wy))
|
|
3111
|
-
return anchor.g;
|
|
3112
|
-
const t = (_a = targets.find((x) => worldInsideGroup(x, wx, wy))) !== null && _a !== void 0 ? _a : null;
|
|
3113
|
-
if (t)
|
|
3114
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost leaves first, so the frame anchored is the relaxed one
|
|
3115
|
-
anchor = t ? { g: t, frame: frameOfGroup(t), stripH: plan.stripHeight(t.id) } : null;
|
|
3116
|
-
return t;
|
|
3117
|
-
};
|
|
3118
|
-
// THE BANDS ARE ON THE SCREEN. Dockview measures its container edges on
|
|
3119
|
-
// the element, and so must we: the camera moves during the drag (the
|
|
3120
|
-
// board glides, the ghost adds rows a bounded scroll answers to), so a
|
|
3121
|
-
// band fixed in world space at the press drifts away from the edge the
|
|
3122
|
-
// user sees. The frame is mapped to client space through the live
|
|
3123
|
-
// camera and clipped to the canvas, so a scrolled board's bands sit at
|
|
3124
|
-
// its VISIBLE edges.
|
|
3125
|
-
const ROOT_TOP = 20;
|
|
3126
|
-
const ROOT_BOTTOM = 20;
|
|
3127
|
-
const ROOT_SIDE = 40;
|
|
3128
|
-
// THE EDGE BANDS ARE A FIFTH OF THE BODY — Dockview's activation size, not
|
|
3129
|
-
// VS Code's third. Two thirds of every group read as "beside" under the
|
|
3130
|
-
// thirds, and the user, putting a page back into the panel it came from,
|
|
3131
|
-
// never found the "into": "close to the edge means beside it, the content
|
|
3132
|
-
// area means inside, the header means a new tab" (0.4.42).
|
|
3133
|
-
const EDGE_BAND = BESIDE_BAND; // one band for a tab's split and a widget's beside (zones.ts)
|
|
3134
|
-
const visibleFrame = () => {
|
|
3135
|
-
const rect = api.container.getBoundingClientRect();
|
|
3136
|
-
const o = toWorld(rect.left, rect.top);
|
|
3137
|
-
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3138
|
-
const sx = 100 / (u.x - o.x || 100);
|
|
3139
|
-
const sy = 100 / (u.y - o.y || 100);
|
|
3140
|
-
const f = frame();
|
|
3141
|
-
const left = rect.left + (f.x - o.x) * sx;
|
|
3142
|
-
const top = rect.top + (f.y - o.y) * sy;
|
|
3143
|
-
const right = left + f.width * sx;
|
|
3144
|
-
const bottom = top + f.height * sy;
|
|
3145
|
-
if (rect.width <= 0 || rect.height <= 0)
|
|
3146
|
-
return { left, top, right, bottom }; // unlaid-out: nothing to clip to
|
|
3147
|
-
return { left: Math.max(left, rect.left), top: Math.max(top, rect.top), right: Math.min(right, rect.right), bottom: Math.min(bottom, rect.bottom) };
|
|
3148
|
-
};
|
|
3149
|
-
/** Inside the VISIBLE canvas plus the grace band, on the screen — a camera that moved must not turn a pointer outside the canvas into a world point inside the board. */
|
|
3150
|
-
const clientInsideCanvasGrace = (cx, cy) => {
|
|
3151
|
-
// The CANVAS, not this board's frame: a nested page's binder runs the
|
|
3152
|
-
// tear-out of the tabs inside it, and its pages must still land on the
|
|
3153
|
-
// boards around it. An unmeasurable container (no layout) is never off.
|
|
3154
|
-
const rect = api.container.getBoundingClientRect();
|
|
3155
|
-
if (rect.width <= 0 || rect.height <= 0)
|
|
3156
|
-
return true;
|
|
3157
|
-
return cx >= rect.left - EDGE_GRACE && cx <= rect.right + EDGE_GRACE && cy >= rect.top - EDGE_GRACE && cy <= rect.bottom + EDGE_GRACE;
|
|
3158
|
-
};
|
|
3159
|
-
/**
|
|
3160
|
-
* The board's rows for a dock: as they stood BEFORE the ghost entered.
|
|
3161
|
-
* Read live, the ghost's own pushing inflated them on every re-entry —
|
|
3162
|
-
* side docks 14, 21, 104 rows tall, a bottom dock landing at row 29.
|
|
3163
|
-
*/
|
|
3164
|
-
const baseRows = () => {
|
|
3165
|
-
const b = leg === null || leg === void 0 ? void 0 : leg.baseline();
|
|
3166
|
-
if (!b)
|
|
3167
|
-
return rowsWithout(plan.arrivingId);
|
|
3168
|
-
let r = 0;
|
|
3169
|
-
for (const c of b.values())
|
|
3170
|
-
r = Math.max(r, c.y + c.h);
|
|
3171
|
-
return r;
|
|
3172
|
-
};
|
|
3173
|
-
const worldToClient = (wx, wy) => {
|
|
3174
|
-
const rect = api.container.getBoundingClientRect();
|
|
3175
|
-
const o = toWorld(rect.left, rect.top);
|
|
3176
|
-
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
3177
|
-
return { x: rect.left + (wx - o.x) * (100 / (u.x - o.x || 100)), y: rect.top + (wy - o.y) * (100 / (u.y - o.y || 100)) };
|
|
3178
|
-
};
|
|
3179
|
-
/**
|
|
3180
|
-
* Would the page land where the user cannot see it? A cell whose room
|
|
3181
|
-
* lies below the fold (the only cell left under a locked section — D1
|
|
3182
|
-
* hold3) used to take the drop a screen away. Hidden = not one pixel of
|
|
3183
|
-
* the cell inside the visible canvas: a landing whose top edge shows,
|
|
3184
|
-
* the rest below the fold of a small canvas, is a landing the user can
|
|
3185
|
-
* see and a grow board scrolls to (lab L61 dropped at the canvas foot).
|
|
3186
|
-
*/
|
|
3187
|
-
const landingHidden = () => {
|
|
3188
|
-
const r = leg === null || leg === void 0 ? void 0 : leg.rect();
|
|
3189
|
-
const rect = api.container.getBoundingClientRect();
|
|
3190
|
-
if (!r || rect.width <= 0 || rect.height <= 0)
|
|
3191
|
-
return false;
|
|
3192
|
-
const tl = worldToClient(r.x, r.y);
|
|
3193
|
-
const br = worldToClient(r.x + r.width, r.y + r.height);
|
|
3194
|
-
return tl.y >= rect.bottom || br.y <= rect.top || tl.x >= rect.right || br.x <= rect.left;
|
|
3195
|
-
};
|
|
3196
|
-
const rootAt = (cx, cy) => {
|
|
3197
|
-
// The bands reach a little OUTSIDE the frame too: a pointer that
|
|
3198
|
-
// overshoots the board's top edge by a few pixels means "the top".
|
|
3199
|
-
const v = visibleFrame();
|
|
3200
|
-
if (cx < v.left - ROOT_SIDE || cx > v.right + ROOT_SIDE || cy < v.top - ROOT_TOP || cy > v.bottom + ROOT_BOTTOM)
|
|
3201
|
-
return null;
|
|
3202
|
-
const rows = Math.max(TEAR_OUT_MIN_ROWS, baseRows());
|
|
3203
|
-
const w = dockSpan.w;
|
|
3204
|
-
const h = dockSpan.h;
|
|
3205
|
-
if (cy - v.top <= ROOT_TOP)
|
|
3206
|
-
return { kind: 'root', side: 'top', cell: { x: 0, y: 0, w: columns, h } };
|
|
3207
|
-
if (cx - v.left <= ROOT_SIDE)
|
|
3208
|
-
return { kind: 'root', side: 'left', cell: { x: 0, y: 0, w, h: rows } };
|
|
3209
|
-
if (v.right - cx <= ROOT_SIDE)
|
|
3210
|
-
return { kind: 'root', side: 'right', cell: { x: Math.max(0, columns - w), y: 0, w, h: rows } };
|
|
3211
|
-
if (v.bottom - cy <= ROOT_BOTTOM)
|
|
3212
|
-
return { kind: 'root', side: 'bottom', cell: { x: 0, y: rows, w: columns, h } };
|
|
3213
|
-
return null;
|
|
3214
|
-
};
|
|
3215
|
-
const rowsWithout = (id) => {
|
|
3216
|
-
let r = 0;
|
|
3217
|
-
for (const it of engine.getItems())
|
|
3218
|
-
if (it.id !== id)
|
|
3219
|
-
r = Math.max(r, it.y + it.h);
|
|
3220
|
-
return r;
|
|
3221
|
-
};
|
|
3222
|
-
/** A split being previewed: the target shrunk to its half, to be restored when the pointer leaves. */
|
|
3223
|
-
let split = null;
|
|
3224
|
-
const halves = (target, side) => {
|
|
3225
|
-
// The cell to halve is the target's own — not the half it is already
|
|
3226
|
-
// shrunk to while a split is being previewed.
|
|
3227
|
-
const live = engine.getItem(target.id);
|
|
3228
|
-
const it = split && split.id === target.id ? split.before : live;
|
|
3229
|
-
if (!it || !live)
|
|
3230
|
-
return null; // a group on another board: no cell of ours to halve
|
|
3231
|
-
if (side === 'left' || side === 'right') {
|
|
3232
|
-
if (it.w < 2)
|
|
3233
|
-
return null;
|
|
3234
|
-
const a = Math.ceil(it.w / 2);
|
|
3235
|
-
const b = it.w - a;
|
|
3236
|
-
return side === 'right'
|
|
3237
|
-
? { keep: { x: it.x, y: it.y, w: a, h: it.h }, born: { x: it.x + a, y: it.y, w: b, h: it.h } }
|
|
3238
|
-
: { keep: { x: it.x + b, y: it.y, w: a, h: it.h }, born: { x: it.x, y: it.y, w: b, h: it.h } };
|
|
3239
|
-
}
|
|
3240
|
-
if (it.h < 2 * TEAR_OUT_MIN_ROWS)
|
|
3241
|
-
return null;
|
|
3242
|
-
const a = Math.ceil(it.h / 2);
|
|
3243
|
-
const b = it.h - a;
|
|
3244
|
-
return side === 'bottom'
|
|
3245
|
-
? { keep: { x: it.x, y: it.y, w: it.w, h: a }, born: { x: it.x, y: it.y + a, w: it.w, h: b } }
|
|
3246
|
-
: { keep: { x: it.x, y: it.y + b, w: it.w, h: a }, born: { x: it.x, y: it.y, w: it.w, h: b } };
|
|
3247
|
-
};
|
|
3248
|
-
const zoneAt = (cx, cy, world) => {
|
|
3249
|
-
// A pointer OUTSIDE the visible canvas is off, full stop: a camera that
|
|
3250
|
-
// moved can map it to a world point inside a group, and that group
|
|
3251
|
-
// took a join or a split from a release in the page header. The
|
|
3252
|
-
// target is looked up (and anchored) only inside it.
|
|
3253
|
-
const clientInside = clientInsideCanvasGrace(cx, cy);
|
|
3254
|
-
const target = clientInside ? targetAt(world.x, world.y) : null;
|
|
3255
|
-
const root = clientInside ? rootAt(cx, cy) : null;
|
|
3256
|
-
const foreign = foreignAt(world.x, world.y);
|
|
3257
|
-
// The ORDER is zones.ts's, shared with the split board: a strip, the
|
|
3258
|
-
// board's own edges, the target's fifth or middle, home, then a board.
|
|
3259
|
-
const tz = resolveTabZone({
|
|
3260
|
-
x: world.x,
|
|
3261
|
-
y: world.y,
|
|
3262
|
-
clientInside,
|
|
3263
|
-
ownStrip: plan.stripIndex(fromGroupId, cx, cy),
|
|
3264
|
-
stripOf: (id) => plan.stripIndex(id, cx, cy),
|
|
3265
|
-
root: root && root.kind === 'root' ? { side: root.side } : null,
|
|
3266
|
-
target: target
|
|
3267
|
-
? {
|
|
3268
|
-
id: target.id,
|
|
3269
|
-
frame: anchor && anchor.g === target ? anchor.frame : frameOfGroup(target),
|
|
3270
|
-
stripHeight: anchor && anchor.g === target ? anchor.stripH : plan.stripHeight(target.id),
|
|
3271
|
-
}
|
|
3272
|
-
: null,
|
|
3273
|
-
home: worldInsideGroup(from, world.x, world.y) ? frameOfGroup(from) : null,
|
|
3274
|
-
homeBand: 0, // on a grid board the whole source frame is home
|
|
3275
|
-
band: EDGE_BAND,
|
|
3276
|
-
canSplit: (_id, side) => !!target && !!halves(target, side),
|
|
3277
|
-
pane: false,
|
|
3278
|
-
foreign: !!foreign && (!worldInsideBoard(world.x, world.y) || foreign.frameArea() < boardArea()),
|
|
3279
|
-
});
|
|
3280
|
-
switch (tz.kind) {
|
|
3281
|
-
case 'off':
|
|
3282
|
-
return { kind: 'off' };
|
|
3283
|
-
case 'strip':
|
|
3284
|
-
return { kind: 'strip', target: target, index: tz.index };
|
|
3285
|
-
case 'reorder':
|
|
3286
|
-
return { kind: 'reorder', index: tz.index };
|
|
3287
|
-
case 'root':
|
|
3288
|
-
return root;
|
|
3289
|
-
case 'join':
|
|
3290
|
-
return { kind: 'join', target: target };
|
|
3291
|
-
case 'split': {
|
|
3292
|
-
const h = halves(target, tz.side);
|
|
3293
|
-
return h ? Object.assign({ kind: 'split', target: target, side: tz.side }, h) : { kind: 'join', target: target };
|
|
3294
|
-
}
|
|
3295
|
-
case 'home':
|
|
3296
|
-
return { kind: 'home' };
|
|
3297
|
-
default:
|
|
3298
|
-
return tz.kind === 'board' && tz.foreign && foreign ? { kind: 'board', peer: foreign } : { kind: 'board' };
|
|
3299
|
-
}
|
|
3300
|
-
};
|
|
3301
|
-
const zoneKey = (z) => JSON.stringify(z, (k, v) => (k === 'target' ? v.id : k === 'peer' ? v.group.id : v));
|
|
3302
|
-
// A TOP DOCK INSERTS ROWS. The engine's push cascade resolves the band's
|
|
3303
|
-
// collisions one tile at a time and scrambles what stood beneath it (the
|
|
3304
|
-
// demo's KPI row came apart, two of its tiles under the chart). VS Code
|
|
3305
|
-
// shoves the whole area down intact, so after the ghost takes the band
|
|
3306
|
-
// every tile that stood at or below it is put back at its own column,
|
|
3307
|
-
// exactly the band's height lower — a translation of a layout without
|
|
3308
|
-
// overlaps has none — and comes back the same way when the pointer leaves.
|
|
3309
|
-
// The layout translated is the one from BEFORE the ghost entered (the
|
|
3310
|
-
// leg's baseline), not the engine's mid-gesture state: the adoption
|
|
3311
|
-
// itself pushes tiles about, and a snapshot taken after it would carry
|
|
3312
|
-
// that cascade into the insert.
|
|
3313
|
-
let inserted = null;
|
|
3314
|
-
const insertRows = (cell, l) => {
|
|
3315
|
-
if (cell.w < columns)
|
|
3316
|
-
return; // a side dock has no whole rows to insert
|
|
3317
|
-
inserted = l.baseline();
|
|
3318
|
-
for (const [id, c] of inserted) {
|
|
3319
|
-
const it = engine.getItem(id);
|
|
3320
|
-
if (!it)
|
|
3321
|
-
continue;
|
|
3322
|
-
it.x = c.x;
|
|
3323
|
-
it.y = c.y >= cell.y ? c.y + cell.h : c.y;
|
|
3324
|
-
}
|
|
3325
|
-
};
|
|
3326
|
-
const undoInsertRows = () => {
|
|
3327
|
-
if (!inserted)
|
|
3328
|
-
return;
|
|
3329
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost frees its rows first
|
|
3330
|
-
for (const [id, c] of inserted) {
|
|
3331
|
-
const it = engine.getItem(id);
|
|
3332
|
-
if (it) {
|
|
3333
|
-
it.x = c.x;
|
|
3334
|
-
it.y = c.y;
|
|
3335
|
-
}
|
|
3336
|
-
}
|
|
3337
|
-
inserted = null;
|
|
3338
|
-
project();
|
|
3339
|
-
};
|
|
3340
|
-
let joinEl = null;
|
|
3341
|
-
const showOverlay = (r) => {
|
|
3342
|
-
if (!layer)
|
|
3343
|
-
return;
|
|
3344
|
-
if (!joinEl) {
|
|
3345
|
-
joinEl = doc.createElement('div');
|
|
3346
|
-
joinEl.className = 'axdb-join';
|
|
3347
|
-
layer.prepend(joinEl);
|
|
3348
|
-
}
|
|
3349
|
-
joinEl.style.left = `${r.x}px`;
|
|
3350
|
-
joinEl.style.top = `${r.y}px`;
|
|
3351
|
-
joinEl.style.width = `${r.width}px`;
|
|
3352
|
-
joinEl.style.height = `${r.height}px`;
|
|
3353
|
-
};
|
|
3354
|
-
const hideOverlay = () => {
|
|
3355
|
-
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
3356
|
-
joinEl = null;
|
|
3357
|
-
};
|
|
3358
|
-
const undoSplitPreview = () => {
|
|
3359
|
-
if (!split)
|
|
3360
|
-
return;
|
|
3361
|
-
const it = engine.getItem(split.id);
|
|
3362
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the born half must be free before the target grows back into it
|
|
3363
|
-
if (it) {
|
|
3364
|
-
if (it.x !== split.before.x || it.y !== split.before.y)
|
|
3365
|
-
engine.moveCheck(split.id, split.before.x, split.before.y, { gate: false });
|
|
3366
|
-
if (it.w !== split.before.w || it.h !== split.before.h)
|
|
3367
|
-
engine.resizeCheck(split.id, split.before.w, split.before.h);
|
|
3368
|
-
}
|
|
3369
|
-
split = null;
|
|
3370
|
-
project();
|
|
3371
|
-
};
|
|
3372
|
-
const previewSplit = (z, world) => {
|
|
3373
|
-
const it = engine.getItem(z.target.id);
|
|
3374
|
-
if (!it)
|
|
3375
|
-
return false;
|
|
3376
|
-
// The target's cell AT REST is the one the commit restores — read it
|
|
3377
|
-
// BEFORE the leg enters: the arriving ghost is placed under the pointer
|
|
3378
|
-
// first, and a target that is a tile like any other is pushed by it
|
|
3379
|
-
// for a moment (tile first, step 3) until it takes its half below.
|
|
3380
|
-
const before = { x: it.x, y: it.y, w: it.w, h: it.h };
|
|
3381
|
-
const frameBefore = frameOfGroup(z.target);
|
|
3382
|
-
const l = ensureLeg(world, null);
|
|
3383
|
-
if (!l)
|
|
3384
|
-
return false;
|
|
3385
|
-
split = { id: z.target.id, before, frameBefore, keep: z.keep };
|
|
3386
|
-
if (it.w !== z.keep.w || it.h !== z.keep.h)
|
|
3387
|
-
engine.resizeCheck(z.target.id, z.keep.w, z.keep.h);
|
|
3388
|
-
const now = engine.getItem(z.target.id);
|
|
3389
|
-
if (now && (now.x !== z.keep.x || now.y !== z.keep.y))
|
|
3390
|
-
engine.moveCheck(z.target.id, z.keep.x, z.keep.y, { gate: false });
|
|
3391
|
-
const ok = l.place(z.born);
|
|
3392
|
-
project();
|
|
3393
|
-
hidePlaceholder(); // the accent overlay says which half; the grey placeholder under it is noise
|
|
3394
|
-
return ok;
|
|
3395
|
-
};
|
|
3396
|
-
/** The band a dock is promised: the cell it takes — a BOTTOM dock's lies past the last row, so its tint sits on the board's last rows instead. */
|
|
3397
|
-
const dockOverlayRect = (z) => {
|
|
3398
|
-
const cell = z.side === 'bottom' ? Object.assign(Object.assign({}, z.cell), { y: Math.max(0, z.cell.y - z.cell.h) }) : z.cell;
|
|
3399
|
-
return cellToRect(cell, frame(), geom(), rows());
|
|
3400
|
-
};
|
|
3401
|
-
/** The dock, applied for real at the release: the ghost takes the band, every member group is pushed, a top dock inserts rows. */
|
|
3402
|
-
const realizeDock = (z, world) => {
|
|
3403
|
-
const l = ensureLeg(world, null);
|
|
3404
|
-
if (!l)
|
|
3405
|
-
return false;
|
|
3406
|
-
l.place(z.cell, true); // DOCKING PUSHES SECTIONS: everything below the band moves down, solid or not
|
|
3407
|
-
insertRows(z.cell, l);
|
|
3408
|
-
project();
|
|
3409
|
-
hidePlaceholder();
|
|
3410
|
-
return true;
|
|
3411
|
-
};
|
|
3412
|
-
let zone = { kind: 'board' };
|
|
3413
|
-
let key = zoneKey(zone);
|
|
3414
|
-
const applyZone = (z, world) => {
|
|
3415
|
-
var _a, _b, _c;
|
|
3416
|
-
const k = zoneKey(z);
|
|
3417
|
-
const same = k === key;
|
|
3418
|
-
key = k;
|
|
3419
|
-
zone = z;
|
|
3420
|
-
if (same) {
|
|
3421
|
-
if (z.kind === 'board')
|
|
3422
|
-
(_b = ensureLeg(world, (_a = z.peer) !== null && _a !== void 0 ? _a : null)) === null || _b === void 0 ? void 0 : _b.move(world);
|
|
3423
|
-
return;
|
|
3424
|
-
}
|
|
3425
|
-
undoSplitPreview();
|
|
3426
|
-
undoInsertRows();
|
|
3427
|
-
plan.markDrop(null, null);
|
|
3428
|
-
hideOverlay();
|
|
3429
|
-
// A PREVIEW IS AN OVERLAY. A split and a dock used to be applied LIVE
|
|
3430
|
-
// while the pointer hovered — the target halved and moved to its other
|
|
3431
|
-
// half, a top dock shoved every tile down — so the very group the user
|
|
3432
|
-
// was entering jumped away from under the pointer (the fluid demo's
|
|
3433
|
-
// side panel went off the screen in one frame as a page came back to
|
|
3434
|
-
// it). VS Code, Dockview and Golden Layout tint the half or the band and
|
|
3435
|
-
// move nothing until the drop; so does this now: the layout stays where
|
|
3436
|
-
// the user sees it, and the halving or the row insert happens on release.
|
|
3437
|
-
switch (z.kind) {
|
|
3438
|
-
case 'strip':
|
|
3439
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3440
|
-
showOverlay(frameOfGroup(z.target));
|
|
3441
|
-
plan.markDrop(z.target.id, z.index);
|
|
3442
|
-
break;
|
|
3443
|
-
case 'join':
|
|
3444
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3445
|
-
showOverlay(frameOfGroup(z.target));
|
|
3446
|
-
break;
|
|
3447
|
-
case 'split':
|
|
3448
|
-
leg === null || leg === void 0 ? void 0 : leg.leave(); // the ghost leaves first, so the half is measured on the relaxed board
|
|
3449
|
-
showOverlay(cellToRect(z.born, frame(), geom(), rows()));
|
|
3450
|
-
break;
|
|
3451
|
-
case 'root':
|
|
3452
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3453
|
-
showOverlay(dockOverlayRect(z));
|
|
3454
|
-
break;
|
|
3455
|
-
case 'reorder':
|
|
3456
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3457
|
-
plan.markDrop(fromGroupId, z.index);
|
|
3458
|
-
break;
|
|
3459
|
-
case 'home':
|
|
3460
|
-
case 'off':
|
|
3461
|
-
leg === null || leg === void 0 ? void 0 : leg.leave();
|
|
3462
|
-
break;
|
|
3463
|
-
case 'board': {
|
|
3464
|
-
const l = ensureLeg(world, (_c = z.peer) !== null && _c !== void 0 ? _c : null);
|
|
3465
|
-
l === null || l === void 0 ? void 0 : l.enter(world);
|
|
3466
|
-
break;
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
};
|
|
3470
|
-
let last = { x: ev.clientX, y: ev.clientY };
|
|
3471
|
-
const detach = () => {
|
|
3472
|
-
window.removeEventListener('pointermove', onMove, true);
|
|
3473
|
-
window.removeEventListener('pointerup', onUp, true);
|
|
3474
|
-
window.removeEventListener('pointercancel', onCancel, true);
|
|
3475
|
-
window.removeEventListener('keydown', onKey, true);
|
|
3476
|
-
};
|
|
3477
|
-
const onMove = (e) => {
|
|
3478
|
-
if (disposed)
|
|
3479
|
-
return detach();
|
|
3480
|
-
last = { x: e.clientX, y: e.clientY };
|
|
3481
|
-
moveChip(e.clientX, e.clientY);
|
|
3482
|
-
const world = toWorld(e.clientX, e.clientY);
|
|
3483
|
-
const z = zoneAt(e.clientX, e.clientY, world);
|
|
3484
|
-
applyZone(z, world);
|
|
3485
|
-
// Dimmed = a release here does nothing: home, off the board, or a board
|
|
3486
|
-
// that refused the ghost (bounded and full).
|
|
3487
|
-
chip.classList.toggle('axdb-out', zone.kind === 'home' || zone.kind === 'off' || (zone.kind === 'board' && (!leg || landingHidden())));
|
|
3488
|
-
api.render();
|
|
3489
|
-
};
|
|
3490
|
-
const done = (changed, kind) => {
|
|
3491
|
-
var _a;
|
|
3492
|
-
disarmGlideSoon();
|
|
3493
|
-
enforceBoardHeight();
|
|
3494
|
-
persistLayouts();
|
|
3495
|
-
api.renderNow();
|
|
3496
|
-
(_a = options.onGesture) === null || _a === void 0 ? void 0 : _a.call(options, { type: kind, kind: 'move', nodeId: pageId, changed });
|
|
3497
|
-
};
|
|
3498
|
-
const finish = (commit) => {
|
|
3499
|
-
var _a, _b, _c, _d;
|
|
3500
|
-
detach();
|
|
3501
|
-
chip.remove();
|
|
3502
|
-
hideOverlay();
|
|
3503
|
-
plan.markDrop(null, null);
|
|
3504
|
-
tearing = null;
|
|
3505
|
-
if (disposed)
|
|
3506
|
-
return;
|
|
3507
|
-
const world = toWorld(last.x, last.y);
|
|
3508
|
-
const z = commit ? zoneAt(last.x, last.y, world) : { kind: 'home' };
|
|
3509
|
-
if (z.kind !== 'root')
|
|
3510
|
-
undoInsertRows();
|
|
3511
|
-
if (!commit || z.kind === 'home' || z.kind === 'off' || (z.kind === 'root' && !ensureLeg(world, null)) || (z.kind === 'board' && (!ensureLeg(world, (_a = z.peer) !== null && _a !== void 0 ? _a : null) || landingHidden()))) {
|
|
3512
|
-
undoSplitPreview();
|
|
3513
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3514
|
-
done(false, 'cancel');
|
|
3515
|
-
return;
|
|
3516
|
-
}
|
|
3517
|
-
if (z.kind === 'reorder') {
|
|
3518
|
-
undoSplitPreview();
|
|
3519
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3520
|
-
const cmds = plan.reorder(z.index);
|
|
3521
|
-
const changed = cmds.length > 0 ? execute('Reorder tab', cmds) : false;
|
|
3522
|
-
done(changed, changed ? 'commit' : 'cancel');
|
|
3523
|
-
return;
|
|
3524
|
-
}
|
|
3525
|
-
if (z.kind === 'strip' || z.kind === 'join') {
|
|
3526
|
-
// JOIN: no cell on this board — the leg is abandoned (its displaced
|
|
3527
|
-
// tiles are already home) and the page becomes the target's tab.
|
|
3528
|
-
undoSplitPreview();
|
|
3529
|
-
const index = z.kind === 'strip' ? z.index : plan.dropIndex(z.target.id, last.x, last.y);
|
|
3530
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3531
|
-
const planned = plan.join(z.target.id, index, group.id);
|
|
3532
|
-
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
3533
|
-
return;
|
|
3534
|
-
}
|
|
3535
|
-
if (z.kind === 'split') {
|
|
3536
|
-
// SPLIT: the target keeps one half of its cell, the page's new group the
|
|
3537
|
-
// other. The hover only tinted the half; the halving happens NOW.
|
|
3538
|
-
hideOverlay();
|
|
3539
|
-
plan.markDrop(null, null);
|
|
3540
|
-
if (!previewSplit(z, world)) {
|
|
3541
|
-
// The half cannot be taken (a bounded board with no room for the
|
|
3542
|
-
// ghost): the page joins the target instead, as it would have had
|
|
3543
|
-
// the target been too small to halve.
|
|
3544
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3545
|
-
const planned = plan.join(z.target.id, plan.dropIndex(z.target.id, last.x, last.y), group.id);
|
|
3546
|
-
done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
|
|
3547
|
-
return;
|
|
3548
|
-
}
|
|
3549
|
-
const fin = (_b = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _b !== void 0 ? _b : null;
|
|
3550
|
-
const halved = !!split && !!engine.getItem(z.target.id);
|
|
3551
|
-
split = null;
|
|
3552
|
-
if (!fin || !halved) {
|
|
3553
|
-
leg === null || leg === void 0 ? void 0 : leg.abort();
|
|
3554
|
-
done(false, 'cancel');
|
|
3555
|
-
return;
|
|
3556
|
-
}
|
|
3557
|
-
const planned = plan.commands(fin.cell, fin.rect, group.id);
|
|
3558
|
-
// The target's halving is in the leg's own deltas: its cell at rest was
|
|
3559
|
-
// snapshotted when the ghost entered, and its half is where it stands now.
|
|
3560
|
-
const changed = execute('Split group', [...fin.commands, ...planned.move, ...planned.collapse]);
|
|
3561
|
-
done(changed, 'commit');
|
|
3562
|
-
return;
|
|
3563
|
-
}
|
|
3564
|
-
// ROOT DOCK: the hover only tinted the band; the ghost takes it NOW. A
|
|
3565
|
-
// free cell on the board: the leg already sits where it will land.
|
|
3566
|
-
if (z.kind === 'root')
|
|
3567
|
-
realizeDock(z, world);
|
|
3568
|
-
else if (zoneKey(zone) !== zoneKey(z))
|
|
3569
|
-
applyZone(z, world);
|
|
3570
|
-
hideOverlay(); // a re-applied zone repaints its preview; the release is not a preview
|
|
3571
|
-
plan.markDrop(null, null);
|
|
3572
|
-
const fin = (_c = leg === null || leg === void 0 ? void 0 : leg.finalize()) !== null && _c !== void 0 ? _c : null;
|
|
3573
|
-
if (!fin) {
|
|
3574
|
-
done(false, 'cancel');
|
|
3575
|
-
return;
|
|
3576
|
-
}
|
|
3577
|
-
const planned = plan.commands(fin.cell, fin.rect, (_d = leg === null || leg === void 0 ? void 0 : leg.groupId) !== null && _d !== void 0 ? _d : group.id);
|
|
3578
|
-
done(execute(z.kind === 'root' ? 'Dock tab' : 'Move tab out', [...fin.commands, ...planned.move, ...planned.collapse]), 'commit');
|
|
3579
|
-
};
|
|
3580
|
-
const onUp = () => finish(true);
|
|
3581
|
-
const onCancel = () => finish(false);
|
|
3582
|
-
const onKey = (e) => {
|
|
3583
|
-
if (e.key === 'Escape')
|
|
3584
|
-
finish(false);
|
|
3585
|
-
};
|
|
3586
|
-
window.addEventListener('pointermove', onMove, true);
|
|
3587
|
-
window.addEventListener('pointerup', onUp, true);
|
|
3588
|
-
window.addEventListener('pointercancel', onCancel, true);
|
|
3589
|
-
window.addEventListener('keydown', onKey, true);
|
|
3590
|
-
return true;
|
|
3591
|
-
};
|
|
3035
|
+
// -- tab tear-out (tear-out.ts) --------------------------------------------
|
|
3036
|
+
const beginTearOut = createTearOut(ctx, {
|
|
3037
|
+
columns: () => columns,
|
|
3038
|
+
adopt: (node, world, pxSize, opts) => adopt(node, world, pxSize, opts),
|
|
3039
|
+
boardArea,
|
|
3040
|
+
peersOnCanvas,
|
|
3041
|
+
selfPeer: () => selfPeer,
|
|
3042
|
+
worldInsideBoard,
|
|
3043
|
+
worldInsideGroup,
|
|
3044
|
+
frameOfGroup,
|
|
3045
|
+
execute,
|
|
3046
|
+
project,
|
|
3047
|
+
hidePlaceholder,
|
|
3048
|
+
armGlide,
|
|
3049
|
+
disarmGlideSoon,
|
|
3050
|
+
enforceBoardHeight,
|
|
3051
|
+
persistLayouts,
|
|
3052
|
+
busy: () => !!gesture || !!slabGesture,
|
|
3053
|
+
tearing: () => tearing,
|
|
3054
|
+
setTearing: (id) => {
|
|
3055
|
+
tearing = id;
|
|
3056
|
+
},
|
|
3057
|
+
});
|
|
3592
3058
|
// -- palette drag-in --------------------------------------------------------
|
|
3593
3059
|
const beginPaletteDrag = (node, spec, event) => {
|
|
3594
3060
|
var _a, _b;
|