@grafloria/element 0.4.59 → 0.4.61
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
CHANGED
|
@@ -1067,35 +1067,6 @@ export function createDashboardHandle(ctx) {
|
|
|
1067
1067
|
return [new RegisterWidgetCommand({ register: () => apply(after), unregister: () => apply(before) }, 'register')];
|
|
1068
1068
|
};
|
|
1069
1069
|
ctx.tabDrop = {
|
|
1070
|
-
stripAt: (cx, cy, grace) => {
|
|
1071
|
-
// The strip the drag already holds is tested with its box widened by
|
|
1072
|
-
// `grace.px` (see STRIP_STAY): a small overshoot still means the tabs.
|
|
1073
|
-
// Its own hit is checked FIRST, so a neighbour never steals it back.
|
|
1074
|
-
const hit = (id, el, pad) => {
|
|
1075
|
-
const r = el.getBoundingClientRect();
|
|
1076
|
-
if (r.width <= 0)
|
|
1077
|
-
return null;
|
|
1078
|
-
return cx >= r.left - pad && cx <= r.right + pad && cy >= r.top - pad && cy <= r.bottom + pad
|
|
1079
|
-
? { containerId: id, index: indexInStrip(el, cx) }
|
|
1080
|
-
: null;
|
|
1081
|
-
};
|
|
1082
|
-
if (grace && grace.px > 0) {
|
|
1083
|
-
const el = ctx.tabStrips.get(grace.containerId);
|
|
1084
|
-
if (el && ctx.boardGroups.has(grace.containerId)) {
|
|
1085
|
-
const held = hit(grace.containerId, el, grace.px);
|
|
1086
|
-
if (held)
|
|
1087
|
-
return held;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
for (const [id, el] of ctx.tabStrips) {
|
|
1091
|
-
if (!ctx.boardGroups.has(id))
|
|
1092
|
-
continue;
|
|
1093
|
-
const h = hit(id, el, 0);
|
|
1094
|
-
if (h)
|
|
1095
|
-
return h;
|
|
1096
|
-
}
|
|
1097
|
-
return null;
|
|
1098
|
-
},
|
|
1099
1070
|
tabIndexAt: (containerId, cx) => {
|
|
1100
1071
|
const el = ctx.tabStrips.get(containerId);
|
|
1101
1072
|
if (!el || !ctx.boardGroups.has(containerId))
|
|
@@ -581,28 +581,14 @@ export interface TearOutPlan {
|
|
|
581
581
|
* handle owns the strips and the registry; the binder owns the gesture.
|
|
582
582
|
*/
|
|
583
583
|
export interface TabDropHooks {
|
|
584
|
-
/**
|
|
585
|
-
* The strip under a CLIENT point, and the tab index that point means along
|
|
586
|
-
* it. `grace` widens ONE strip's box by that many pixels on every side —
|
|
587
|
-
* the strip a drag is already aiming at keeps the hand through a small
|
|
588
|
-
* overshoot, instead of handing it to the zone underneath on one pixel.
|
|
589
|
-
*/
|
|
590
|
-
stripAt(clientX: number, clientY: number, grace?: {
|
|
591
|
-
containerId: string;
|
|
592
|
-
px: number;
|
|
593
|
-
}): {
|
|
594
|
-
containerId: string;
|
|
595
|
-
index: number;
|
|
596
|
-
} | null;
|
|
597
584
|
/** Paint (or, with null, clear) the insertion mark on a strip. */
|
|
598
585
|
markDrop(containerId: string | null, index: number | null): void;
|
|
599
586
|
/**
|
|
600
587
|
* Which tab slot a CLIENT x means along one named strip — the x alone, with
|
|
601
|
-
* no hit test.
|
|
602
|
-
* container's
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
* live hit test for everything.
|
|
588
|
+
* no hit test. WHETHER the hand is on a strip at all is the zone walk's
|
|
589
|
+
* answer, from the container's frame in the model at rest: a strip element
|
|
590
|
+
* reports the box it is painted in, and a strip that has been pushed — or
|
|
591
|
+
* is gliding home from a push — is a box the hand never asked for.
|
|
606
592
|
*/
|
|
607
593
|
tabIndexAt?(containerId: string, clientX: number): number | null;
|
|
608
594
|
/** The commands that make `widgetId` a new tab of `containerId` at `index`; `displaced` are this board's survivors. Empty = refused. */
|
|
@@ -56,7 +56,7 @@ 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 } from './zones.js';
|
|
59
|
+
import { BESIDE_BAND, resolve as resolveZone, stripUnder } from './zones.js';
|
|
60
60
|
import { SequenceCommand, SetGroupCellCommand, tileCommands } from './commit.js';
|
|
61
61
|
import { EDGE_GRACE } from './board-ctx.js';
|
|
62
62
|
import { createProjection } from './project.js';
|
|
@@ -910,6 +910,52 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
910
910
|
* come back by themselves when the widget leaves (S2).
|
|
911
911
|
*/
|
|
912
912
|
let beside = null;
|
|
913
|
+
/**
|
|
914
|
+
* A VERTICAL BESIDE, HELD BUT NOT APPLIED (0.4.61). A container's top or
|
|
915
|
+
* bottom band pushes it a whole row along its own column — and the zone walk
|
|
916
|
+
* reads the container where it RESTS (0.4.60, which is what stopped the
|
|
917
|
+
* strip flickering), so the moment the push lands, the painted tabs sit a
|
|
918
|
+
* row below the only place that accepts them, and a hand following them down
|
|
919
|
+
* pushes them again. The user met it as "it pushes the entire tab group down
|
|
920
|
+
* and then I cannot go up to the tab header".
|
|
921
|
+
*
|
|
922
|
+
* So a vertical band moves NOTHING while the hand is held: an overlay marks
|
|
923
|
+
* the cell the widget will take and the container gives way on release — the
|
|
924
|
+
* rule a tab's dock and split previews have followed since 0.4.42. The LEFT
|
|
925
|
+
* and RIGHT bands keep their live slide (0.4.48): sideways, a full-width
|
|
926
|
+
* strip never leaves the hand, and the user missed that slide the one time
|
|
927
|
+
* it was frozen.
|
|
928
|
+
*/
|
|
929
|
+
let pendingBeside = null;
|
|
930
|
+
let joinEl = null;
|
|
931
|
+
const showJoin = (r) => {
|
|
932
|
+
const layer = htmlLayer();
|
|
933
|
+
if (!layer)
|
|
934
|
+
return;
|
|
935
|
+
if (!joinEl || joinEl.parentElement !== layer) {
|
|
936
|
+
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
937
|
+
joinEl = document.createElement('div');
|
|
938
|
+
joinEl.className = 'axdb-join';
|
|
939
|
+
layer.prepend(joinEl);
|
|
940
|
+
}
|
|
941
|
+
joinEl.style.left = `${r.x}px`;
|
|
942
|
+
joinEl.style.top = `${r.y}px`;
|
|
943
|
+
joinEl.style.width = `${r.width}px`;
|
|
944
|
+
joinEl.style.height = `${r.height}px`;
|
|
945
|
+
};
|
|
946
|
+
const endPendingBeside = () => {
|
|
947
|
+
pendingBeside = null;
|
|
948
|
+
joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
|
|
949
|
+
joinEl = null;
|
|
950
|
+
};
|
|
951
|
+
/** The cell a vertical beside would take: the container's own, or the row under it. Never the pointer's row — the container is not moving. */
|
|
952
|
+
const pendingCellOf = (containerId, side, spans) => {
|
|
953
|
+
const it = engine.getItem(containerId);
|
|
954
|
+
if (!it)
|
|
955
|
+
return null;
|
|
956
|
+
const x = Math.max(0, Math.min(engine.columns - spans.w, it.x));
|
|
957
|
+
return { x, y: side === 'top' ? it.y : it.y + it.h };
|
|
958
|
+
};
|
|
913
959
|
const endBeside = (restore) => {
|
|
914
960
|
if (!beside)
|
|
915
961
|
return;
|
|
@@ -1292,6 +1338,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1292
1338
|
showRefusal(null, 0, 0);
|
|
1293
1339
|
}
|
|
1294
1340
|
}
|
|
1341
|
+
endPendingBeside();
|
|
1295
1342
|
disarmGlideSoon();
|
|
1296
1343
|
releasePointer(g.pointerId);
|
|
1297
1344
|
api.container.style.cursor = '';
|
|
@@ -1335,6 +1382,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1335
1382
|
};
|
|
1336
1383
|
const cancelActiveGesture = (notify = true) => {
|
|
1337
1384
|
var _a, _b, _c;
|
|
1385
|
+
endPendingBeside();
|
|
1338
1386
|
if (gesture === null || gesture === void 0 ? void 0 : gesture.strip) {
|
|
1339
1387
|
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
1340
1388
|
gesture.strip = null;
|
|
@@ -1483,6 +1531,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1483
1531
|
// -- INTO A STRIP: the widget becomes a new tab there, so it leaves
|
|
1484
1532
|
// this board (survivors settle home) and the strip marks the slot.
|
|
1485
1533
|
endBeside(true); // a band's shift gives way to the strip: the container comes back
|
|
1534
|
+
endPendingBeside();
|
|
1486
1535
|
leaveSelf();
|
|
1487
1536
|
setDim(g, false);
|
|
1488
1537
|
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index) {
|
|
@@ -1501,6 +1550,22 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1501
1550
|
// — at the board's edge the container shifts over to make room, live
|
|
1502
1551
|
// and gliding, like any widget gives way (0.4.48). On THIS board the
|
|
1503
1552
|
// engine is driven directly; on another board its leg drives it (4a).
|
|
1553
|
+
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer && g.subject === 'node' && (z.side === 'top' || z.side === 'bottom')) {
|
|
1554
|
+
// -- A VERTICAL BAND: marked, not applied. Nothing moves until release.
|
|
1555
|
+
if (beside)
|
|
1556
|
+
endBeside(true);
|
|
1557
|
+
const cell = pendingCellOf(z.containerId, z.side, g.spans);
|
|
1558
|
+
if (cell) {
|
|
1559
|
+
leaveSelf();
|
|
1560
|
+
setDim(g, false);
|
|
1561
|
+
pendingBeside = { id: z.containerId, side: z.side, cell, spans: Object.assign({}, g.spans) };
|
|
1562
|
+
showJoin(cellToRect({ x: cell.x, y: cell.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows()));
|
|
1563
|
+
syncPlaceholder();
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
if (pendingBeside)
|
|
1568
|
+
endPendingBeside(); // the hand left the band: the mark goes
|
|
1504
1569
|
if (z.kind === 'beside' && !isStatic && z.board.ref === selfPeer) {
|
|
1505
1570
|
const row = rowOfPoint(ev.world.y);
|
|
1506
1571
|
if (!z.kept || !beside || beside.row !== row)
|
|
@@ -1955,6 +2020,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1955
2020
|
(_b = options.onGesture) === null || _b === void 0 ? void 0 : _b.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
|
|
1956
2021
|
return;
|
|
1957
2022
|
}
|
|
2023
|
+
if (pendingBeside) {
|
|
2024
|
+
// -- REALIZED ON RELEASE: the container held still while the hand chose;
|
|
2025
|
+
// now it gives way, and the commit below reads the settled layout.
|
|
2026
|
+
const p = pendingBeside;
|
|
2027
|
+
endPendingBeside();
|
|
2028
|
+
applyBeside(g, { id: p.id, side: p.side }, p.cell.y);
|
|
2029
|
+
}
|
|
1958
2030
|
if (g.leg) {
|
|
1959
2031
|
// -- CROSS-CONTAINER COMMIT: one batch across both boards -----------
|
|
1960
2032
|
const fin = g.leg.adopted.finalize();
|
|
@@ -2176,46 +2248,57 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2176
2248
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2177
2249
|
const resolveTileZone = (g, ev) => {
|
|
2178
2250
|
var _a, _b, _c, _d, _e;
|
|
2251
|
+
const roots = zoneRoots();
|
|
2252
|
+
// THE CONTAINERS THIS GESTURE HAS MOVED, at the frames they rest in. A
|
|
2253
|
+
// drag's own pushes must never change what the hand means — settled for a
|
|
2254
|
+
// group ghost in 0.4.44, and true of a widget for the same reason: the
|
|
2255
|
+
// beside that a band previews SHOVES the container out from under the
|
|
2256
|
+
// pointer, so read live it would answer about a container that is only
|
|
2257
|
+
// there because of the answer.
|
|
2258
|
+
const rests = restFramesOf(g);
|
|
2179
2259
|
let strip = null;
|
|
2180
|
-
if (options.tabDrop && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
2260
|
+
if (((_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.tabIndexAt) && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
2181
2261
|
// A group never becomes a tab: over a container's strip it is over the
|
|
2182
2262
|
// container's margin — a cell on the parent board, pushing with intent.
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
ev.world.x <= f.x + f.width + stay / (s.x || 1) &&
|
|
2204
|
-
ev.world.y >= f.y - tol &&
|
|
2205
|
-
ev.world.y <= f.y + TAB_STRIP_HEIGHT + tol;
|
|
2206
|
-
const idx = inStrip ? options.tabDrop.tabIndexAt(held, cx) : null;
|
|
2207
|
-
if (idx !== null)
|
|
2208
|
-
strip = { containerId: held, index: idx };
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
if (!strip)
|
|
2212
|
-
strip = options.tabDrop.stripAt(cx, cy, g.strip ? { containerId: g.strip.containerId, px: stay } : undefined);
|
|
2213
|
-
}
|
|
2214
|
-
return resolveZone(Object.assign(Object.assign({ x: ev.world.x, y: ev.world.y, roots: zoneRoots(), strip, prev: beside
|
|
2263
|
+
const hit = stripUnder({ x: ev.world.x, y: ev.world.y, roots, held: (_c = (_b = g.strip) === null || _b === void 0 ? void 0 : _b.containerId) !== null && _c !== void 0 ? _c : null, stay: STRIP_STAY / (clientPerWorld().y || 1), restFrames: rests });
|
|
2264
|
+
if (hit) {
|
|
2265
|
+
// The SLOT is the painted strip's business — its tabs are laid out by
|
|
2266
|
+
// the browser. A container the gesture shifted sideways is painted
|
|
2267
|
+
// that far from where it was measured, so the client x is carried
|
|
2268
|
+
// across with it.
|
|
2269
|
+
const grp = diagram.getGroup(hit.containerId);
|
|
2270
|
+
const rest = rests.get(hit.containerId);
|
|
2271
|
+
const dx = grp && rest ? (grp.position.x - rest.x) * clientPerWorld().x : 0;
|
|
2272
|
+
const idx = options.tabDrop.tabIndexAt(hit.containerId, api.container.getBoundingClientRect().left + ev.screen.x + dx);
|
|
2273
|
+
if (idx !== null)
|
|
2274
|
+
strip = { containerId: hit.containerId, index: idx };
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return resolveZone({
|
|
2278
|
+
x: ev.world.x,
|
|
2279
|
+
y: ev.world.y,
|
|
2280
|
+
roots,
|
|
2281
|
+
strip,
|
|
2282
|
+
prev: beside
|
|
2215
2283
|
? { containerId: beside.id, side: beside.side, frame0: beside.frame0, vacated: cellToRect({ x: beside.vacated.x, y: beside.vacated.y, w: g.spans.w, h: g.spans.h }, frame(), geom(), rows()) }
|
|
2216
|
-
:
|
|
2284
|
+
: pendingBeside
|
|
2285
|
+
? // a VERTICAL band the hand holds: the container never moved, so its own frame is the sticky one
|
|
2286
|
+
{
|
|
2287
|
+
containerId: pendingBeside.id,
|
|
2288
|
+
side: pendingBeside.side,
|
|
2289
|
+
frame0: (() => { const grp = diagram.getGroup(pendingBeside.id); return grp ? frameOfGroup(grp) : cellToRect({ x: pendingBeside.cell.x, y: pendingBeside.cell.y, w: pendingBeside.spans.w, h: pendingBeside.spans.h }, frame(), geom(), rows()); })(),
|
|
2290
|
+
vacated: cellToRect({ x: pendingBeside.cell.x, y: pendingBeside.cell.y, w: pendingBeside.spans.w, h: pendingBeside.spans.h }, frame(), geom(), rows()),
|
|
2291
|
+
}
|
|
2292
|
+
: ((_e = (_d = g.leg) === null || _d === void 0 ? void 0 : _d.adopted.besideState()) !== null && _e !== void 0 ? _e : null), // a beside another board holds for the ghost, through its leg
|
|
2293
|
+
maxDepth: nesting,
|
|
2294
|
+
ghostDepth: g.subject === 'group' ? 1 + levelsInside(g.id) : 0, // a group's widgets sit one board deeper than wherever it lands
|
|
2295
|
+
restFrames: rests,
|
|
2296
|
+
ghostSubtree: g.subject === 'group' ? descendantGroups(g.id) : EMPTY_SUBTREE,
|
|
2297
|
+
gap,
|
|
2298
|
+
homeChain: homeChain(),
|
|
2299
|
+
});
|
|
2217
2300
|
};
|
|
2218
|
-
/** This board's containers as they stood at the press: a
|
|
2301
|
+
/** This board's containers as they stood at the press: a gesture's own pushes never change what the hand means. */
|
|
2219
2302
|
const restFramesOf = (g) => {
|
|
2220
2303
|
const out = new Map();
|
|
2221
2304
|
for (const [id, snap] of g.startGeom) {
|
|
@@ -3203,6 +3286,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3203
3286
|
chip === null || chip === void 0 ? void 0 : chip.remove();
|
|
3204
3287
|
return;
|
|
3205
3288
|
}
|
|
3289
|
+
if (commit && pendingBeside) {
|
|
3290
|
+
const p = pendingBeside;
|
|
3291
|
+
endPendingBeside();
|
|
3292
|
+
applyBeside(g, { id: p.id, side: p.side }, p.cell.y);
|
|
3293
|
+
}
|
|
3294
|
+
else
|
|
3295
|
+
endPendingBeside();
|
|
3206
3296
|
if (commit && g.leg) {
|
|
3207
3297
|
// Dropped into another board (a page, a section): its leg closes with
|
|
3208
3298
|
// that board's displaced tiles; this board displaced nothing.
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* descendant). The strip rows of a tab container are nobody's band, and its
|
|
22
22
|
* margin (inside the frame, outside the page) is a plain cell on the parent.
|
|
23
23
|
*
|
|
24
|
+
* `stripUnder` walks the same tree for the strip itself, so a tab slot and a
|
|
25
|
+
* band are decided on one set of frames — the ones the containers REST in,
|
|
26
|
+
* never the ones a preview pushed them to.
|
|
27
|
+
*
|
|
24
28
|
* This module is pure geometry: no DOM, no engine, no model. The binder
|
|
25
29
|
* builds the tree for each move from its peers and the model, and acts on
|
|
26
30
|
* the answer.
|
|
@@ -89,13 +93,13 @@ export interface ResolveInput {
|
|
|
89
93
|
*/
|
|
90
94
|
homeChain: ReadonlySet<string>;
|
|
91
95
|
/**
|
|
92
|
-
* Containers of the source board as they stood when the gesture began
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* leaves the board.
|
|
96
|
+
* Containers of the source board as they stood when the gesture began. The
|
|
97
|
+
* hand over a pushed container's rest frame still means that container —
|
|
98
|
+
* its zones, its page tested at the displacement — or the target flees the
|
|
99
|
+
* hand that is aiming at it: a group could never enter a container its own
|
|
100
|
+
* body reaches first (0.4.44), and a widget aimed at a tab strip chased a
|
|
101
|
+
* strip its own preview kept shoving away (0.4.60). The engine's memory
|
|
102
|
+
* brings the pushed container home when the ghost leaves the board.
|
|
99
103
|
*/
|
|
100
104
|
restFrames?: ReadonlyMap<string, ZoneRect>;
|
|
101
105
|
}
|
|
@@ -127,6 +131,37 @@ export declare const BESIDE_STAY = 0.05;
|
|
|
127
131
|
* to its far right means "after it", not "above it".
|
|
128
132
|
*/
|
|
129
133
|
export declare function bandOf(f: ZoneRect, stripHeight: number, x: number, y: number, band?: number): BesideSide | null;
|
|
134
|
+
export interface StripProbe {
|
|
135
|
+
x: number;
|
|
136
|
+
y: number;
|
|
137
|
+
roots: ZoneBoard[];
|
|
138
|
+
/** The container whose strip the gesture already holds: its rows are widened by `stay`, and it wins over a neighbour's. */
|
|
139
|
+
held: string | null;
|
|
140
|
+
/** The stickiness around the held strip, in world units. */
|
|
141
|
+
stay: number;
|
|
142
|
+
/** Containers the gesture has displaced, at the frames they rest in. */
|
|
143
|
+
restFrames?: ReadonlyMap<string, ZoneRect>;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The tab strip under a pointer — WHERE THE CONTAINER RESTS, read from the
|
|
147
|
+
* same tree at the same frames as `resolve`.
|
|
148
|
+
*
|
|
149
|
+
* A strip is painted at the top of its container, so it travels with it: a
|
|
150
|
+
* beside shifts the container, a refusal pushes it, and it GLIDES home
|
|
151
|
+
* afterwards. Hit-testing the painted box therefore makes the strip a target
|
|
152
|
+
* at wherever the preview happened to put it — and a box in the air sweeps
|
|
153
|
+
* under a hand that is not moving at all and steals the zone. That is a
|
|
154
|
+
* closed loop: the tab claims the hand, the container comes home, the band
|
|
155
|
+
* under the hand claims it back, the container is pushed away, and the strip
|
|
156
|
+
* flies through the pointer again. The user met it as "it flickers between a
|
|
157
|
+
* tab and above the group; I have to go very slowly".
|
|
158
|
+
*
|
|
159
|
+
* So the rows are the container's own, at rest, on the model's frames. What
|
|
160
|
+
* a hand means never depends on what the preview did with it.
|
|
161
|
+
*/
|
|
162
|
+
export declare function stripUnder(p: StripProbe): {
|
|
163
|
+
containerId: string;
|
|
164
|
+
} | null;
|
|
130
165
|
export declare function resolve(input: ResolveInput): Zone;
|
|
131
166
|
/** A join target as the walk sees it: another tab container, its frame anchored by the binder while the pointer is inside it. */
|
|
132
167
|
export interface TabTarget {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* descendant). The strip rows of a tab container are nobody's band, and its
|
|
22
22
|
* margin (inside the frame, outside the page) is a plain cell on the parent.
|
|
23
23
|
*
|
|
24
|
+
* `stripUnder` walks the same tree for the strip itself, so a tab slot and a
|
|
25
|
+
* band are decided on one set of frames — the ones the containers REST in,
|
|
26
|
+
* never the ones a preview pushed them to.
|
|
27
|
+
*
|
|
24
28
|
* This module is pure geometry: no DOM, no engine, no model. The binder
|
|
25
29
|
* builds the tree for each move from its peers and the model, and acts on
|
|
26
30
|
* the answer.
|
|
@@ -55,6 +59,55 @@ export function bandOf(f, stripHeight, x, y, band = BESIDE_BAND) {
|
|
|
55
59
|
return 'bottom';
|
|
56
60
|
return null;
|
|
57
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The tab strip under a pointer — WHERE THE CONTAINER RESTS, read from the
|
|
64
|
+
* same tree at the same frames as `resolve`.
|
|
65
|
+
*
|
|
66
|
+
* A strip is painted at the top of its container, so it travels with it: a
|
|
67
|
+
* beside shifts the container, a refusal pushes it, and it GLIDES home
|
|
68
|
+
* afterwards. Hit-testing the painted box therefore makes the strip a target
|
|
69
|
+
* at wherever the preview happened to put it — and a box in the air sweeps
|
|
70
|
+
* under a hand that is not moving at all and steals the zone. That is a
|
|
71
|
+
* closed loop: the tab claims the hand, the container comes home, the band
|
|
72
|
+
* under the hand claims it back, the container is pushed away, and the strip
|
|
73
|
+
* flies through the pointer again. The user met it as "it flickers between a
|
|
74
|
+
* tab and above the group; I have to go very slowly".
|
|
75
|
+
*
|
|
76
|
+
* So the rows are the container's own, at rest, on the model's frames. What
|
|
77
|
+
* a hand means never depends on what the preview did with it.
|
|
78
|
+
*/
|
|
79
|
+
export function stripUnder(p) {
|
|
80
|
+
const { x, y } = p;
|
|
81
|
+
// Plain locals, not one object: a `let` assigned only inside the closure
|
|
82
|
+
// narrows to `never` at the return.
|
|
83
|
+
let bestId = null;
|
|
84
|
+
let bestDepth = -1;
|
|
85
|
+
let bestHeld = false;
|
|
86
|
+
const visit = (b, dx, dy) => {
|
|
87
|
+
var _a;
|
|
88
|
+
for (const c of b.children()) {
|
|
89
|
+
const rest = (_a = p.restFrames) === null || _a === void 0 ? void 0 : _a.get(c.id);
|
|
90
|
+
const atRest = rest && inRect(rest, x, y) ? rest : null;
|
|
91
|
+
const frame = atRest !== null && atRest !== void 0 ? atRest : c.frame;
|
|
92
|
+
const tx = atRest ? x : x + dx;
|
|
93
|
+
const ty = atRest ? y : y + dy;
|
|
94
|
+
const held = c.id === p.held;
|
|
95
|
+
const pad = held ? p.stay : 0;
|
|
96
|
+
if (c.stripHeight > 0 && inRect({ x: frame.x, y: frame.y, width: frame.width, height: c.stripHeight }, tx, ty, pad)) {
|
|
97
|
+
if (bestId === null || held || (!bestHeld && b.depth >= bestDepth)) {
|
|
98
|
+
bestId = c.id;
|
|
99
|
+
bestDepth = b.depth;
|
|
100
|
+
bestHeld = held;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (c.inner && inRect(frame, tx, ty, pad))
|
|
104
|
+
visit(c.inner, atRest ? c.frame.x - atRest.x : dx, atRest ? c.frame.y - atRest.y : dy);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
for (const r of p.roots)
|
|
108
|
+
visit(r, 0, 0);
|
|
109
|
+
return bestId === null ? null : { containerId: bestId };
|
|
110
|
+
}
|
|
58
111
|
/** The board a container sits on, found by id through the tree. */
|
|
59
112
|
function boardOf(roots, containerId) {
|
|
60
113
|
const visit = (b) => {
|