@grafloria/element 0.4.57 → 0.4.59
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,16 +1067,41 @@ 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) => {
|
|
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
|
+
}
|
|
1071
1090
|
for (const [id, el] of ctx.tabStrips) {
|
|
1072
1091
|
if (!ctx.boardGroups.has(id))
|
|
1073
1092
|
continue;
|
|
1074
|
-
const
|
|
1075
|
-
if (
|
|
1076
|
-
return
|
|
1093
|
+
const h = hit(id, el, 0);
|
|
1094
|
+
if (h)
|
|
1095
|
+
return h;
|
|
1077
1096
|
}
|
|
1078
1097
|
return null;
|
|
1079
1098
|
},
|
|
1099
|
+
tabIndexAt: (containerId, cx) => {
|
|
1100
|
+
const el = ctx.tabStrips.get(containerId);
|
|
1101
|
+
if (!el || !ctx.boardGroups.has(containerId))
|
|
1102
|
+
return null;
|
|
1103
|
+
return indexInStrip(el, cx);
|
|
1104
|
+
},
|
|
1080
1105
|
markDrop: markTabDrop,
|
|
1081
1106
|
dropIntoStrip: (widgetId, containerId, index, sourceBoardId, displaced) => moveWidgetToTabCommands(widgetId, containerId, index, sourceBoardId, displaced),
|
|
1082
1107
|
};
|
|
@@ -1211,12 +1236,12 @@ export function createDashboardHandle(ctx) {
|
|
|
1211
1236
|
joinTargets: [...ctx.layoutOf.keys()].filter((id) => { var _a, _b; return id !== containerId && id !== pageId && ((_a = ctx.layoutOf.get(id)) !== null && _a !== void 0 ? _a : (_b = specById.get(id)) === null || _b === void 0 ? void 0 : _b.layout) === 'tabs' && !!model.getGroup(id) && !insidePage(id); }),
|
|
1212
1237
|
stripHeight: (targetId) => tabStripReserve(ctx.tabsOf.get(targetId), Math.max(1, liveCount(targetId))),
|
|
1213
1238
|
ownBoards: [pageId, ...[...ctx.boardGroups.keys()].filter((id) => insidePage(id))],
|
|
1214
|
-
stripIndex: (targetId, cx, cy) => {
|
|
1239
|
+
stripIndex: (targetId, cx, cy, grace = 0) => {
|
|
1215
1240
|
const strip = ctx.tabStrips.get(targetId);
|
|
1216
1241
|
if (!strip)
|
|
1217
1242
|
return null;
|
|
1218
1243
|
const r = strip.getBoundingClientRect();
|
|
1219
|
-
if (r.width === 0 || cx < r.left || cx > r.right || cy < r.top || cy > r.bottom)
|
|
1244
|
+
if (r.width === 0 || cx < r.left - grace || cx > r.right + grace || cy < r.top - grace || cy > r.bottom + grace)
|
|
1220
1245
|
return null;
|
|
1221
1246
|
return indexInStrip(strip, cx);
|
|
1222
1247
|
},
|
|
@@ -558,7 +558,7 @@ export interface TearOutPlan {
|
|
|
558
558
|
/** Paint (or, with null, clear) the insertion mark on `targetId`'s strip. */
|
|
559
559
|
markDrop(targetId: string | null, index: number | null): void;
|
|
560
560
|
/** The tab index a CLIENT point means along `targetId`'s strip — null when the point is not over that strip. */
|
|
561
|
-
stripIndex(targetId: string, clientX: number, clientY: number): number | null;
|
|
561
|
+
stripIndex(targetId: string, clientX: number, clientY: number, grace?: number): number | null;
|
|
562
562
|
/** The strip's height on `targetId`, px — the band above its body. */
|
|
563
563
|
stripHeight(targetId: string): number;
|
|
564
564
|
/** The page itself and every board inside it — never a board the page can land on. */
|
|
@@ -581,13 +581,30 @@ 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
|
-
|
|
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
|
+
}): {
|
|
586
594
|
containerId: string;
|
|
587
595
|
index: number;
|
|
588
596
|
} | null;
|
|
589
597
|
/** Paint (or, with null, clear) the insertion mark on a strip. */
|
|
590
598
|
markDrop(containerId: string | null, index: number | null): void;
|
|
599
|
+
/**
|
|
600
|
+
* Which tab slot a CLIENT x means along one named strip — the x alone, with
|
|
601
|
+
* no hit test. The binder decides WHETHER the hand is on a strip from the
|
|
602
|
+
* container's own frame (the model), because a strip element that is gliding
|
|
603
|
+
* — home after a push, or away under one — reports a box the hand is not in
|
|
604
|
+
* yet. Optional: a host with its own hooks and no such method keeps the
|
|
605
|
+
* live hit test for everything.
|
|
606
|
+
*/
|
|
607
|
+
tabIndexAt?(containerId: string, clientX: number): number | null;
|
|
591
608
|
/** The commands that make `widgetId` a new tab of `containerId` at `index`; `displaced` are this board's survivors. Empty = refused. */
|
|
592
609
|
dropIntoStrip(widgetId: string, containerId: string, index: number, sourceBoardId: string, displaced: Command[]): Command[];
|
|
593
610
|
}
|
|
@@ -680,4 +697,14 @@ export declare function parentPeerOf(container: HTMLElement, groupId: string): B
|
|
|
680
697
|
export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
|
|
681
698
|
export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
|
|
682
699
|
export type { BinderPeer };
|
|
700
|
+
/**
|
|
701
|
+
* THE STRIP KEEPS THE HAND IT HAS (CSS px). A tab strip is ~30 px tall and
|
|
702
|
+
* the zone right under it moves the whole container out of the way, so at the
|
|
703
|
+
* seam a wobble of a pixel or two toggled a 90-px animated push — the user,
|
|
704
|
+
* aiming a widget at the tabs: "it's switching so fast between having it above
|
|
705
|
+
* the entire tab group or inside as a tab". Once a drag is over a strip, that
|
|
706
|
+
* strip's box counts as this much larger until the hand really leaves. A strip
|
|
707
|
+
* the drag has NOT reached gets no extra room: it never grows under the hand.
|
|
708
|
+
*/
|
|
709
|
+
export declare const STRIP_STAY = 9;
|
|
683
710
|
export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
|
|
@@ -149,6 +149,16 @@ export function registerBoardPeer(container, peer) {
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
const DRAG_THRESHOLD = 4;
|
|
152
|
+
/**
|
|
153
|
+
* THE STRIP KEEPS THE HAND IT HAS (CSS px). A tab strip is ~30 px tall and
|
|
154
|
+
* the zone right under it moves the whole container out of the way, so at the
|
|
155
|
+
* seam a wobble of a pixel or two toggled a 90-px animated push — the user,
|
|
156
|
+
* aiming a widget at the tabs: "it's switching so fast between having it above
|
|
157
|
+
* the entire tab group or inside as a tab". Once a drag is over a strip, that
|
|
158
|
+
* strip's box counts as this much larger until the hand really leaves. A strip
|
|
159
|
+
* the drag has NOT reached gets no extra room: it never grows under the hand.
|
|
160
|
+
*/
|
|
161
|
+
export const STRIP_STAY = 9;
|
|
152
162
|
/** The node a gesture holds — node-only paths (the pixel ghost, a resize, a strip drop, a removal) ask through this. */
|
|
153
163
|
const nodeOf = (g) => g.entity;
|
|
154
164
|
let binderSeq = 0;
|
|
@@ -697,6 +707,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
697
707
|
if (!engine.getItem(id))
|
|
698
708
|
return;
|
|
699
709
|
engine.remove(id);
|
|
710
|
+
// THE LIVE BOUND FOLLOWS THE MEMBERS. A board that took rows from its
|
|
711
|
+
// parent to hold an arrival (D4) keeps that bound while the tile is here;
|
|
712
|
+
// when the tile leaves — an undo of the drop, a removal — the bound goes
|
|
713
|
+
// back to what the remaining tiles need, or the board stays a row taller
|
|
714
|
+
// than anything in it.
|
|
715
|
+
if (designRows !== undefined)
|
|
716
|
+
setLiveBound(liveBound(engine.getItems()));
|
|
700
717
|
project();
|
|
701
718
|
api.render();
|
|
702
719
|
};
|
|
@@ -2158,17 +2175,45 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2158
2175
|
};
|
|
2159
2176
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2160
2177
|
const resolveTileZone = (g, ev) => {
|
|
2161
|
-
var _a, _b;
|
|
2178
|
+
var _a, _b, _c, _d, _e;
|
|
2162
2179
|
let strip = null;
|
|
2163
2180
|
if (options.tabDrop && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
2164
2181
|
// A group never becomes a tab: over a container's strip it is over the
|
|
2165
2182
|
// container's margin — a cell on the parent board, pushing with intent.
|
|
2166
2183
|
const crect = api.container.getBoundingClientRect();
|
|
2167
|
-
|
|
2184
|
+
const cx = crect.left + ev.screen.x;
|
|
2185
|
+
const cy = crect.top + ev.screen.y;
|
|
2186
|
+
const stay = g.strip ? STRIP_STAY : 0;
|
|
2187
|
+
// THE HELD CONTAINER'S STRIP IS READ FROM THE MODEL, AT REST — as its
|
|
2188
|
+
// bands are (0.4.49). Two reasons, both measured on the live demo with
|
|
2189
|
+
// a widget carried down the panel's strip: a beside SHIFTS or PUSHES the
|
|
2190
|
+
// container and its strip travels with it, so tested where the DOM says
|
|
2191
|
+
// it is, the tab zone runs away from the hand aiming at it (the tabs
|
|
2192
|
+
// became unreachable at any height); and the DOM box GLIDES, so for a
|
|
2193
|
+
// frame or two after the container is sent home the strip is still in
|
|
2194
|
+
// the air and the hand falls through it.
|
|
2195
|
+
const held = (_c = (_a = beside === null || beside === void 0 ? void 0 : beside.id) !== null && _a !== void 0 ? _a : (_b = g.strip) === null || _b === void 0 ? void 0 : _b.containerId) !== null && _c !== void 0 ? _c : null;
|
|
2196
|
+
if (held && options.tabDrop.tabIndexAt) {
|
|
2197
|
+
const grp = diagram.getGroup(held);
|
|
2198
|
+
const f = beside && beside.id === held ? beside.frame0 : grp ? frameOfGroup(grp) : null;
|
|
2199
|
+
if (f) {
|
|
2200
|
+
const s = clientPerWorld();
|
|
2201
|
+
const tol = stay / (s.y || 1);
|
|
2202
|
+
const inStrip = ev.world.x >= f.x - stay / (s.x || 1) &&
|
|
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);
|
|
2168
2213
|
}
|
|
2169
2214
|
return resolveZone(Object.assign(Object.assign({ x: ev.world.x, y: ev.world.y, roots: zoneRoots(), strip, prev: beside
|
|
2170
2215
|
? { 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()) }
|
|
2171
|
-
: ((
|
|
2216
|
+
: ((_e = (_d = g.leg) === null || _d === void 0 ? void 0 : _d.adopted.besideState()) !== null && _e !== void 0 ? _e : null), maxDepth: nesting, ghostDepth: g.subject === 'group' ? 1 + levelsInside(g.id) : 0 }, (g.subject === 'group' ? { restFrames: restFramesOf(g) } : {})), { ghostSubtree: g.subject === 'group' ? descendantGroups(g.id) : EMPTY_SUBTREE, gap, homeChain: homeChain() }));
|
|
2172
2217
|
};
|
|
2173
2218
|
/** This board's containers as they stood at the press: a group ghost's intent pushes never change what the hand means. */
|
|
2174
2219
|
const restFramesOf = (g) => {
|
|
@@ -2220,6 +2265,14 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2220
2265
|
}
|
|
2221
2266
|
return deepest;
|
|
2222
2267
|
};
|
|
2268
|
+
/** Client pixels per world unit — the camera's scale, measured the way the tear-out measures its bands. */
|
|
2269
|
+
const clientPerWorld = () => {
|
|
2270
|
+
const rect = api.container.getBoundingClientRect();
|
|
2271
|
+
const toWorld = (cx, cy) => { var _a; return ((_a = api.viewport) === null || _a === void 0 ? void 0 : _a.clientToWorld) ? api.viewport.clientToWorld(cx, cy, rect) : { x: cx - rect.left, y: cy - rect.top }; };
|
|
2272
|
+
const o = toWorld(rect.left, rect.top);
|
|
2273
|
+
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
2274
|
+
return { x: 100 / (u.x - o.x || 100), y: 100 / (u.y - o.y || 100) };
|
|
2275
|
+
};
|
|
2223
2276
|
/** The groups this board sits in, all the way up: their bands never apply to a tile of this board. */
|
|
2224
2277
|
const homeChain = () => {
|
|
2225
2278
|
const out = new Set();
|
|
@@ -2394,9 +2447,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2394
2447
|
const squeezeBefore = squeezeRoom;
|
|
2395
2448
|
let entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2396
2449
|
// 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.
|
|
2450
|
+
// full SECTION grows instead (D4, below): its height is a tile of its
|
|
2451
|
+
// parent, and the parent is what has the rows to give.
|
|
2400
2452
|
if (!entered && !parentPeer()) {
|
|
2401
2453
|
const room = elasticRows();
|
|
2402
2454
|
if (room !== undefined && room > ((_a = bound()) !== null && _a !== void 0 ? _a : 0)) {
|
|
@@ -2405,7 +2457,38 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2405
2457
|
entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2406
2458
|
}
|
|
2407
2459
|
}
|
|
2460
|
+
let grown = null;
|
|
2461
|
+
const ungrow = () => {
|
|
2462
|
+
const g = grown;
|
|
2463
|
+
if (!g)
|
|
2464
|
+
return;
|
|
2465
|
+
g.peer.resizeMemberBy(group.id, -g.rows);
|
|
2466
|
+
setLiveBound(Math.max(1, (maxRows !== null && maxRows !== void 0 ? maxRows : 1) - g.rows));
|
|
2467
|
+
grown = null;
|
|
2468
|
+
};
|
|
2469
|
+
if (!entered && escalate) {
|
|
2470
|
+
const parent = parentPeer();
|
|
2471
|
+
let rowsAdded = 0;
|
|
2472
|
+
let firstBefore = null;
|
|
2473
|
+
let lastAfter = null;
|
|
2474
|
+
// At most the tile's own height in rows: past that the board is not
|
|
2475
|
+
// "full", it is smaller than the thing being dropped into it.
|
|
2476
|
+
for (let i = 0; parent && !entered && i < Math.max(1, span.h); i++) {
|
|
2477
|
+
const res = parent.resizeMemberBy(group.id, +1);
|
|
2478
|
+
if (!res.changed || !res.cellBefore || !res.cellAfter || !res.frameBefore || !res.frameAfter)
|
|
2479
|
+
break;
|
|
2480
|
+
rowsAdded += 1;
|
|
2481
|
+
firstBefore = firstBefore !== null && firstBefore !== void 0 ? firstBefore : { cell: res.cellBefore, frame: res.frameBefore };
|
|
2482
|
+
lastAfter = { cell: res.cellAfter, frame: res.frameAfter };
|
|
2483
|
+
setLiveBound((maxRows !== null && maxRows !== void 0 ? maxRows : 0) + 1);
|
|
2484
|
+
entered = engine.add({ id: node.id, x: 0, y: engine.rows(), w: span.w, h: span.h });
|
|
2485
|
+
}
|
|
2486
|
+
if (parent && rowsAdded > 0 && firstBefore && lastAfter) {
|
|
2487
|
+
grown = { peer: parent, rows: rowsAdded, cellBefore: firstBefore.cell, frameBefore: firstBefore.frame, cellAfter: lastAfter.cell, frameAfter: lastAfter.frame };
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2408
2490
|
if (!entered) {
|
|
2491
|
+
ungrow();
|
|
2409
2492
|
setSqueeze(squeezeBefore);
|
|
2410
2493
|
engine.endGesture();
|
|
2411
2494
|
return null; // a bounded, full board refuses the adoption
|
|
@@ -2535,6 +2618,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2535
2618
|
if (engine.getItem(node.id))
|
|
2536
2619
|
engine.remove(node.id);
|
|
2537
2620
|
engine.cancelGesture(); // pre-entry layout, memory cleared
|
|
2621
|
+
ungrow(); // the rows this board took from its parent for the arrival go back (D4)
|
|
2538
2622
|
setSqueeze(squeezeBefore);
|
|
2539
2623
|
adoptedGhostId = null;
|
|
2540
2624
|
disarmGlideSoon();
|
|
@@ -2555,6 +2639,11 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2555
2639
|
// Widgets AND sections this board's adoption displaced, as one list: a
|
|
2556
2640
|
// dock or a push that moved a section commits it with the drop.
|
|
2557
2641
|
const commands = tileCommands(deltasSince(startCells, startGeom, node.id));
|
|
2642
|
+
// …and the rows this board took from its parent to hold the arrival (D4).
|
|
2643
|
+
if (grown) {
|
|
2644
|
+
commands.push(new SetGroupCellCommand(group.id, grown.cellBefore, grown.cellAfter, grown.frameBefore, grown.frameAfter));
|
|
2645
|
+
grown = null;
|
|
2646
|
+
}
|
|
2558
2647
|
engine.endGesture();
|
|
2559
2648
|
pendingDrop = node.id; // the tile stays for the member the commit (or the host's drop-in) adds
|
|
2560
2649
|
if (squeezeRoom !== undefined) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cellToRect, rowHeightFor, sizeToSpan } from './grid-mapping.js';
|
|
2
2
|
import { BESIDE_BAND, resolveTabZone } from './zones.js';
|
|
3
3
|
import { EDGE_GRACE } from './board-ctx.js';
|
|
4
|
+
import { STRIP_STAY } from './grid-binder.js';
|
|
4
5
|
/** A torn-out page never arrives shorter than this: a strip with no room under it is not a group. */
|
|
5
6
|
export const TEAR_OUT_MIN_ROWS = 2;
|
|
6
7
|
export function createTearOut(ctx, deps) {
|
|
@@ -278,8 +279,11 @@ export function createTearOut(ctx, deps) {
|
|
|
278
279
|
x: world.x,
|
|
279
280
|
y: world.y,
|
|
280
281
|
clientInside,
|
|
281
|
-
|
|
282
|
-
|
|
282
|
+
// A page dragged along its own strip, or over another's, keeps that
|
|
283
|
+
// strip through a small overshoot too (STRIP_STAY): the zone under a
|
|
284
|
+
// strip moves the container, and a pixel must not toggle it.
|
|
285
|
+
ownStrip: plan.stripIndex(fromGroupId, cx, cy, zone.kind === 'reorder' ? STRIP_STAY : 0),
|
|
286
|
+
stripOf: (id) => plan.stripIndex(id, cx, cy, zone.kind === 'strip' && zone.target.id === id ? STRIP_STAY : 0),
|
|
283
287
|
root: root && root.kind === 'root' ? { side: root.side } : null,
|
|
284
288
|
target: target
|
|
285
289
|
? {
|