@grafloria/element 0.4.68 → 0.4.69
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/grid-binder.d.ts +19 -1
- package/src/lib/dashboard-kit/grid-binder.js +87 -114
- package/src/lib/dashboard-kit/split-binder.d.ts +1 -1
- package/src/lib/dashboard-kit/split-binder.js +238 -8
- package/src/lib/dashboard-kit/zones.d.ts +38 -0
- package/src/lib/dashboard-kit/zones.js +39 -0
package/package.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
import { Command, type DiagramModel, type GridColumnLayout, type GroupModel, type NodeModel } from '@grafloria/engine';
|
|
53
53
|
import { type ToolPointerEvent } from '@grafloria/renderer';
|
|
54
54
|
import { type CellRect, type WorldRect } from './grid-mapping.js';
|
|
55
|
-
import { type BesideSide } from './zones.js';
|
|
55
|
+
import { type BesideSide, type ZoneBoard } from './zones.js';
|
|
56
56
|
import { type ResizeEdges } from './edges.js';
|
|
57
57
|
import { type DragHandleOption } from './grip.js';
|
|
58
58
|
export { TEAR_OUT_MIN_ROWS } from './tear-out.js';
|
|
@@ -683,6 +683,24 @@ export declare function parentPeerOf(container: HTMLElement, groupId: string): B
|
|
|
683
683
|
export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
|
|
684
684
|
export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
|
|
685
685
|
export type { BinderPeer };
|
|
686
|
+
/** The peers registered on a canvas (the set is created on first ask): the boards the zone walk sees. */
|
|
687
|
+
export declare function peersOnCanvasOf(container: HTMLElement): Set<BinderPeer>;
|
|
688
|
+
/** Client pixels per world unit on a canvas: a zoomed camera makes a 9 px stay fewer world units. */
|
|
689
|
+
export declare function clientPerWorldOf(api: Pick<DashboardGridApi, 'container' | 'viewport'>): {
|
|
690
|
+
x: number;
|
|
691
|
+
y: number;
|
|
692
|
+
};
|
|
693
|
+
/**
|
|
694
|
+
* The boards of a canvas as the zone walk sees them (tile first, step 2): a
|
|
695
|
+
* ROOT is a board whose group has no parent group (a view); a board's children
|
|
696
|
+
* are its member groups that are containers, each with the board a descent
|
|
697
|
+
* enters — a tab container's ACTIVE page, a section's own board — one level
|
|
698
|
+
* deeper. Built from the peers and the model on every move: a handful of
|
|
699
|
+
* groups, and the frames are live. One builder for the grid AND the split
|
|
700
|
+
* board (0.4.69): what a hand means over a tab group must not depend on which
|
|
701
|
+
* layout the board under it runs.
|
|
702
|
+
*/
|
|
703
|
+
export declare function zoneRootsOf(peers: Iterable<BinderPeer>, diagram: DiagramModel): ZoneBoard[];
|
|
686
704
|
/**
|
|
687
705
|
* THE STRIP KEEPS THE HAND IT HAS (CSS px). A tab strip is ~30 px tall and
|
|
688
706
|
* the zone right under it moves the whole container out of the way, so at the
|
|
@@ -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, stripUnder } from './zones.js';
|
|
59
|
+
import { BESIDE_BAND, resolve as resolveZone, stripCrossing, 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';
|
|
@@ -148,6 +148,84 @@ export function registerBoardPeer(container, peer) {
|
|
|
148
148
|
s.delete(peer);
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
+
/** The peers registered on a canvas (the set is created on first ask): the boards the zone walk sees. */
|
|
152
|
+
export function peersOnCanvasOf(container) {
|
|
153
|
+
let set = BOARD_REGISTRY.get(container);
|
|
154
|
+
if (!set) {
|
|
155
|
+
set = new Set();
|
|
156
|
+
BOARD_REGISTRY.set(container, set);
|
|
157
|
+
}
|
|
158
|
+
return set;
|
|
159
|
+
}
|
|
160
|
+
/** Client pixels per world unit on a canvas: a zoomed camera makes a 9 px stay fewer world units. */
|
|
161
|
+
export function clientPerWorldOf(api) {
|
|
162
|
+
const rect = api.container.getBoundingClientRect();
|
|
163
|
+
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 }; };
|
|
164
|
+
const o = toWorld(rect.left, rect.top);
|
|
165
|
+
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
166
|
+
return { x: 100 / (u.x - o.x || 100), y: 100 / (u.y - o.y || 100) };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The boards of a canvas as the zone walk sees them (tile first, step 2): a
|
|
170
|
+
* ROOT is a board whose group has no parent group (a view); a board's children
|
|
171
|
+
* are its member groups that are containers, each with the board a descent
|
|
172
|
+
* enters — a tab container's ACTIVE page, a section's own board — one level
|
|
173
|
+
* deeper. Built from the peers and the model on every move: a handful of
|
|
174
|
+
* groups, and the frames are live. One builder for the grid AND the split
|
|
175
|
+
* board (0.4.69): what a hand means over a tab group must not depend on which
|
|
176
|
+
* layout the board under it runs.
|
|
177
|
+
*/
|
|
178
|
+
export function zoneRootsOf(peers, diagram) {
|
|
179
|
+
const list = [...peers];
|
|
180
|
+
const byGroup = new Map(list.map((p) => [p.group.id, p]));
|
|
181
|
+
const frameOf = (grp) => { var _a, _b, _c, _d; return ({ x: grp.position.x, y: grp.position.y, width: (_b = (_a = grp.size) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0, height: (_d = (_c = grp.size) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 0 }); };
|
|
182
|
+
const boardRef = (p, depth) => ({
|
|
183
|
+
id: p.group.id,
|
|
184
|
+
depth,
|
|
185
|
+
ref: p,
|
|
186
|
+
contains: (x, y) => p.containsWorld(x, y),
|
|
187
|
+
containsExtended: (x, y) => p.containsWorldExtended(x, y),
|
|
188
|
+
children: () => {
|
|
189
|
+
var _a, _b, _c, _d, _e;
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const id of (_a = p.group.members) !== null && _a !== void 0 ? _a : []) {
|
|
192
|
+
const grp = diagram.getGroup(id);
|
|
193
|
+
if (!grp || diagram.getNode(id))
|
|
194
|
+
continue;
|
|
195
|
+
const cw = ((_b = grp.getMetadata('containerWidget')) !== null && _b !== void 0 ? _b : {});
|
|
196
|
+
const layout = cw.layout === 'tabs' ? 'tabs' : cw.layout === 'split' ? 'split' : 'grid';
|
|
197
|
+
let innerPeer;
|
|
198
|
+
if (layout === 'tabs') {
|
|
199
|
+
const pageId = cw.active && byGroup.has(cw.active) ? cw.active : [...((_c = grp.members) !== null && _c !== void 0 ? _c : [])].find((m) => byGroup.has(m));
|
|
200
|
+
innerPeer = pageId ? byGroup.get(pageId) : undefined;
|
|
201
|
+
}
|
|
202
|
+
else
|
|
203
|
+
innerPeer = byGroup.get(id);
|
|
204
|
+
out.push({
|
|
205
|
+
id,
|
|
206
|
+
layout,
|
|
207
|
+
static: (_e = (_d = innerPeer === null || innerPeer === void 0 ? void 0 : innerPeer.isStatic) === null || _d === void 0 ? void 0 : _d.call(innerPeer)) !== null && _e !== void 0 ? _e : false,
|
|
208
|
+
frame: frameOf(grp),
|
|
209
|
+
stripHeight: layout === 'tabs' ? TAB_STRIP_HEIGHT : 0,
|
|
210
|
+
band: layout === 'tabs' ? BESIDE_BAND : 0, // a section's whole body is "into" (Quantia's Groups page)
|
|
211
|
+
// The top and bottom are a FIXED depth — one strip's worth, under
|
|
212
|
+
// the strip — not a fifth of the body, which grew with the panel
|
|
213
|
+
// until 216 px of the fluid demo's page meant "above the whole
|
|
214
|
+
// panel" (0.4.62). The sides keep the fifth.
|
|
215
|
+
bandY: layout === 'tabs' ? TAB_STRIP_HEIGHT : 0,
|
|
216
|
+
// …and the TOP band hangs ABOVE the frame, where a hand looking
|
|
217
|
+
// for "above this panel" actually goes (0.4.65). A panel holding
|
|
218
|
+
// the board's first row has no row above it to point at, and the
|
|
219
|
+
// lane under its header is the last place anyone would try.
|
|
220
|
+
topOutside: layout === 'tabs' ? TAB_STRIP_HEIGHT : 0,
|
|
221
|
+
inner: innerPeer ? boardRef(innerPeer, depth + 1) : null,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
return list.filter((p) => !p.group.parentGroupId).map((p) => boardRef(p, 0));
|
|
228
|
+
}
|
|
151
229
|
const DRAG_THRESHOLD = 4;
|
|
152
230
|
/**
|
|
153
231
|
* THE STRIP KEEPS THE HAND IT HAS (CSS px). A tab strip is ~30 px tall and
|
|
@@ -2081,72 +2159,9 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2081
2159
|
const f = frame();
|
|
2082
2160
|
return f.width * boardVisualHeight();
|
|
2083
2161
|
};
|
|
2084
|
-
const peersOnCanvas = () =>
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
set = new Set();
|
|
2088
|
-
BOARD_REGISTRY.set(api.container, set);
|
|
2089
|
-
}
|
|
2090
|
-
return set;
|
|
2091
|
-
};
|
|
2092
|
-
/**
|
|
2093
|
-
* The boards of this canvas as the zone walk sees them (tile first, step
|
|
2094
|
-
* 2): a ROOT is a board whose group has no parent group (a view); a board's
|
|
2095
|
-
* children are its member groups that are containers, each with the board a
|
|
2096
|
-
* descent enters — a tab container's ACTIVE page, a section's own board —
|
|
2097
|
-
* one level deeper. Built from the peers and the model on every move: a
|
|
2098
|
-
* handful of groups, and the frames are live.
|
|
2099
|
-
*/
|
|
2100
|
-
const zoneRoots = () => {
|
|
2101
|
-
const peers = [...peersOnCanvas()];
|
|
2102
|
-
const byGroup = new Map(peers.map((p) => [p.group.id, p]));
|
|
2103
|
-
const boardRef = (p, depth) => ({
|
|
2104
|
-
id: p.group.id,
|
|
2105
|
-
depth,
|
|
2106
|
-
ref: p,
|
|
2107
|
-
contains: (x, y) => p.containsWorld(x, y),
|
|
2108
|
-
containsExtended: (x, y) => p.containsWorldExtended(x, y),
|
|
2109
|
-
children: () => {
|
|
2110
|
-
var _a, _b, _c, _d, _e;
|
|
2111
|
-
const out = [];
|
|
2112
|
-
for (const id of (_a = p.group.members) !== null && _a !== void 0 ? _a : []) {
|
|
2113
|
-
const grp = diagram.getGroup(id);
|
|
2114
|
-
if (!grp || diagram.getNode(id))
|
|
2115
|
-
continue;
|
|
2116
|
-
const cw = ((_b = grp.getMetadata('containerWidget')) !== null && _b !== void 0 ? _b : {});
|
|
2117
|
-
const layout = cw.layout === 'tabs' ? 'tabs' : cw.layout === 'split' ? 'split' : 'grid';
|
|
2118
|
-
let innerPeer;
|
|
2119
|
-
if (layout === 'tabs') {
|
|
2120
|
-
const pageId = cw.active && byGroup.has(cw.active) ? cw.active : [...((_c = grp.members) !== null && _c !== void 0 ? _c : [])].find((m) => byGroup.has(m));
|
|
2121
|
-
innerPeer = pageId ? byGroup.get(pageId) : undefined;
|
|
2122
|
-
}
|
|
2123
|
-
else
|
|
2124
|
-
innerPeer = byGroup.get(id);
|
|
2125
|
-
out.push({
|
|
2126
|
-
id,
|
|
2127
|
-
layout,
|
|
2128
|
-
static: (_e = (_d = innerPeer === null || innerPeer === void 0 ? void 0 : innerPeer.isStatic) === null || _d === void 0 ? void 0 : _d.call(innerPeer)) !== null && _e !== void 0 ? _e : false,
|
|
2129
|
-
frame: frameOfGroup(grp),
|
|
2130
|
-
stripHeight: layout === 'tabs' ? TAB_STRIP_HEIGHT : 0,
|
|
2131
|
-
band: layout === 'tabs' ? BESIDE_BAND : 0, // a section's whole body is "into" (Quantia's Groups page)
|
|
2132
|
-
// The top and bottom are a FIXED depth — one strip's worth, under
|
|
2133
|
-
// the strip — not a fifth of the body, which grew with the panel
|
|
2134
|
-
// until 216 px of the fluid demo's page meant "above the whole
|
|
2135
|
-
// panel" (0.4.62). The sides keep the fifth.
|
|
2136
|
-
bandY: layout === 'tabs' ? TAB_STRIP_HEIGHT : 0,
|
|
2137
|
-
// …and the TOP band hangs ABOVE the frame, where a hand looking
|
|
2138
|
-
// for "above this panel" actually goes (0.4.65). A panel holding
|
|
2139
|
-
// the board's first row has no row above it to point at, and the
|
|
2140
|
-
// lane under its header is the last place anyone would try.
|
|
2141
|
-
topOutside: layout === 'tabs' ? TAB_STRIP_HEIGHT : 0,
|
|
2142
|
-
inner: innerPeer ? boardRef(innerPeer, depth + 1) : null,
|
|
2143
|
-
});
|
|
2144
|
-
}
|
|
2145
|
-
return out;
|
|
2146
|
-
},
|
|
2147
|
-
});
|
|
2148
|
-
return peers.filter((p) => !p.group.parentGroupId).map((p) => boardRef(p, 0));
|
|
2149
|
-
};
|
|
2162
|
+
const peersOnCanvas = () => peersOnCanvasOf(api.container);
|
|
2163
|
+
/** The boards of this canvas as the zone walk sees them — see `zoneRootsOf`. */
|
|
2164
|
+
const zoneRoots = () => zoneRootsOf(peersOnCanvas(), diagram);
|
|
2150
2165
|
/** The ghost takes the cell under the hand on THIS board: re-entering at the bottom edge first (collision-free), then gatelessly; a tile already here moves through the gate. */
|
|
2151
2166
|
const placeOnSelf = (g, desired, pushSolid = false) => {
|
|
2152
2167
|
var _a, _b;
|
|
@@ -2198,7 +2213,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2198
2213
|
};
|
|
2199
2214
|
/** What the pointer means for the dragged tile: the resolve over the live tree, with the beside the hand holds. */
|
|
2200
2215
|
const resolveTileZone = (g, ev) => {
|
|
2201
|
-
var _a, _b, _c, _d, _e, _f
|
|
2216
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2202
2217
|
const roots = zoneRoots();
|
|
2203
2218
|
// THE CONTAINERS THIS GESTURE HAS MOVED, at the frames they rest in. A
|
|
2204
2219
|
// drag's own pushes must never change what the hand means — settled for a
|
|
@@ -2212,44 +2227,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2212
2227
|
// A group never becomes a tab: over a container's strip it is over the
|
|
2213
2228
|
// container's margin — a cell on the parent board, pushing with intent.
|
|
2214
2229
|
const scaleY = clientPerWorld().y || 1;
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
// A HAND MOVES FASTER THAN A STRIP IS TALL. (Only for a hand ARRIVING:
|
|
2218
|
-
// one already holding the strip leaves it by the stay, which reaches
|
|
2219
|
-
// down and never up — the band above must stay reachable.) The strip is 30 px and a
|
|
2220
|
-
// hand covers 40 to 80 px between events, so testing only where the
|
|
2221
|
-
// pointer LANDS skips it — the user, coming down from above the panel:
|
|
2222
|
-
// "it's not passing by the tab header, it drops directly to inside or
|
|
2223
|
-
// outside." The segment it travelled is tested too, in steps of half a
|
|
2224
|
-
// strip: crossing the rows and landing within one strip's height of
|
|
2225
|
-
// them means the tabs. Flying far past them does not — a fast drag
|
|
2226
|
-
// into the page must never snag on the header.
|
|
2227
|
-
const dx = ev.world.x - prevWorld.x;
|
|
2228
|
-
const dy = ev.world.y - prevWorld.y;
|
|
2229
|
-
const n = Math.ceil(Math.hypot(dx, dy) / (TAB_STRIP_HEIGHT / 2));
|
|
2230
|
-
let crossed = null;
|
|
2231
|
-
for (let i = 1; i < n && !crossed; i++)
|
|
2232
|
-
crossed = (_e = (_d = stripUnder({ x: prevWorld.x + (dx * i) / n, y: prevWorld.y + (dy * i) / n, roots, held: null, stay: 0, restFrames: rests })) === null || _d === void 0 ? void 0 : _d.containerId) !== null && _e !== void 0 ? _e : null;
|
|
2233
|
-
if (crossed) {
|
|
2234
|
-
const grp = diagram.getGroup(crossed);
|
|
2235
|
-
const f = (_f = rests.get(crossed)) !== null && _f !== void 0 ? _f : (grp ? frameOfGroup(grp) : null);
|
|
2236
|
-
if (f) {
|
|
2237
|
-
const top = f.y;
|
|
2238
|
-
const bottom = f.y + TAB_STRIP_HEIGHT;
|
|
2239
|
-
// A CROSSING is the two events on OPPOSITE sides of the rows — a
|
|
2240
|
-
// hand that swept along the tabs from beside the panel and ended
|
|
2241
|
-
// above it did not cross them, it went past them. And the sides
|
|
2242
|
-
// still take the corners (0.4.47): a hand landing in the outer
|
|
2243
|
-
// fifth meant "after it", whatever it crossed on the way.
|
|
2244
|
-
const through = (prevWorld.y < top && ev.world.y > bottom) || (prevWorld.y > bottom && ev.world.y < top);
|
|
2245
|
-
const away = ev.world.y < top ? top - ev.world.y : ev.world.y > bottom ? ev.world.y - bottom : 0;
|
|
2246
|
-
const rx = (ev.world.x - f.x) / Math.max(1, f.width);
|
|
2247
|
-
const inSideBand = rx < BESIDE_BAND || rx > 1 - BESIDE_BAND;
|
|
2248
|
-
if (through && !inSideBand && ev.world.x >= f.x && ev.world.x <= f.x + f.width && away <= TAB_STRIP_HEIGHT / scaleY)
|
|
2249
|
-
hit = { containerId: crossed };
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2230
|
+
// …and the segment the hand TRAVELLED, for a hand arriving faster than a strip is tall (0.4.67; `stripCrossing`).
|
|
2231
|
+
const hit = (_d = 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 / scaleY, restFrames: rests })) !== null && _d !== void 0 ? _d : (prevWorld && !g.strip ? stripCrossing({ prev: prevWorld, cur: ev.world, roots, restFrames: rests, stripHeight: TAB_STRIP_HEIGHT, band: BESIDE_BAND, reach: TAB_STRIP_HEIGHT / scaleY }) : null);
|
|
2253
2232
|
if (hit) {
|
|
2254
2233
|
// The SLOT is the painted strip's business — its tabs are laid out by
|
|
2255
2234
|
// the browser. A container the gesture shifted sideways is painted
|
|
@@ -2270,7 +2249,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2270
2249
|
strip,
|
|
2271
2250
|
prev: beside
|
|
2272
2251
|
? { 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()) }
|
|
2273
|
-
: ((
|
|
2252
|
+
: ((_f = (_e = g.leg) === null || _e === void 0 ? void 0 : _e.adopted.besideState()) !== null && _f !== void 0 ? _f : null), // a beside another board holds for the ghost, through its leg
|
|
2274
2253
|
maxDepth: nesting,
|
|
2275
2254
|
ghostDepth: g.subject === 'group' ? 1 + levelsInside(g.id) : 0, // a group's widgets sit one board deeper than wherever it lands
|
|
2276
2255
|
restFrames: rests,
|
|
@@ -2330,13 +2309,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2330
2309
|
return deepest;
|
|
2331
2310
|
};
|
|
2332
2311
|
/** Client pixels per world unit — the camera's scale, measured the way the tear-out measures its bands. */
|
|
2333
|
-
const clientPerWorld = () =>
|
|
2334
|
-
const rect = api.container.getBoundingClientRect();
|
|
2335
|
-
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 }; };
|
|
2336
|
-
const o = toWorld(rect.left, rect.top);
|
|
2337
|
-
const u = toWorld(rect.left + 100, rect.top + 100);
|
|
2338
|
-
return { x: 100 / (u.x - o.x || 100), y: 100 / (u.y - o.y || 100) };
|
|
2339
|
-
};
|
|
2312
|
+
const clientPerWorld = () => clientPerWorldOf(api);
|
|
2340
2313
|
/** The groups this board sits in, all the way up: their bands never apply to a tile of this board. */
|
|
2341
2314
|
const homeChain = () => {
|
|
2342
2315
|
const out = new Set();
|
|
@@ -27,7 +27,7 @@ import type { DashboardGridApi, DashboardGridHandle, DashboardGridOptions } from
|
|
|
27
27
|
import { type SplitNode } from './split-layout.js';
|
|
28
28
|
/** Group metadata key the tree persists under. */
|
|
29
29
|
export declare const SPLIT_TREE_KEY = "dashboardTree";
|
|
30
|
-
export interface DashboardSplitOptions extends Pick<DashboardGridOptions, 'columns' | 'gap' | 'padding' | 'rtl' | 'fluid' | 'static' | 'dragHandle' | 'squeeze' | 'designHeight' | 'baseRowHeight' | 'dragOut' | 'removeZone' | 'onRemoveRequest' | 'onDropIn' | 'onGesture' | 'onSelect' | 'renderCaption' | 'onCaptionAction'> {
|
|
30
|
+
export interface DashboardSplitOptions extends Pick<DashboardGridOptions, 'columns' | 'gap' | 'padding' | 'rtl' | 'fluid' | 'static' | 'dragHandle' | 'squeeze' | 'designHeight' | 'baseRowHeight' | 'dragOut' | 'removeZone' | 'onRemoveRequest' | 'onDropIn' | 'tabDrop' | 'onMemberLeaving' | 'nesting' | 'onGesture' | 'onSelect' | 'renderCaption' | 'onCaptionAction'> {
|
|
31
31
|
/** An authored tree. Default: the persisted one, else derived from the members' cells. */
|
|
32
32
|
tree?: SplitNode | null;
|
|
33
33
|
}
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
* the authored size.
|
|
24
24
|
*/
|
|
25
25
|
import { __awaiter } from "tslib";
|
|
26
|
-
import { BESIDE_BAND, resolveTabZone } from './zones.js';
|
|
27
|
-
import { BatchCommand, Command } from '@grafloria/engine';
|
|
26
|
+
import { BESIDE_BAND, resolveTabZone, resolve as resolveZone, stripCrossing, stripUnder } from './zones.js';
|
|
27
|
+
import { AddToGroupCommand, BatchCommand, Command, RemoveFromGroupCommand } from '@grafloria/engine';
|
|
28
28
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
29
|
-
import { anyEdge, clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, parentPeerOf, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS, EDGE_GRIP } from './grid-binder.js';
|
|
30
|
-
import { cellFromGridItem } from './grid-mapping.js';
|
|
29
|
+
import { anyEdge, clearOtherSelections, clientPerWorldOf, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, parentPeerOf, peersOnCanvasOf, pressOnDragHandle, registerBoardPeer, syncGrip, zoneRootsOf, DRAG_HANDLE_CLASS, EDGE_GRIP, STRIP_STAY } from './grid-binder.js';
|
|
30
|
+
import { buildCommitCommands, cellFromGridItem } from './grid-mapping.js';
|
|
31
|
+
import { SequenceCommand } from './commit.js';
|
|
32
|
+
import { TAB_STRIP_HEIGHT } from './tabs.js';
|
|
31
33
|
import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, moveSplitDivider, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
|
|
32
34
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
33
35
|
import { captionKey, captionOfGroup, captionPainted, paintCaptionBand, sectionCaptionReserve, sizeCaptionBand } from './caption.js';
|
|
@@ -509,6 +511,75 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
509
511
|
const f = frame();
|
|
510
512
|
return x >= f.x && x <= f.x + f.width && y >= f.y && y <= f.y + f.height;
|
|
511
513
|
};
|
|
514
|
+
// -- the tab-group zones on a split board (0.4.69) ----------------------------
|
|
515
|
+
const frameOfGroupW = (grp) => { var _a, _b, _c, _d; return ({ x: grp.position.x, y: grp.position.y, width: (_b = (_a = grp.size) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0, height: (_d = (_c = grp.size) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 0 }); };
|
|
516
|
+
const EMPTY_SUBTREE = new Set();
|
|
517
|
+
/** This board's group and its ancestors: a container's band never applies to a widget that lives inside it. */
|
|
518
|
+
const homeChain = () => {
|
|
519
|
+
const out = new Set();
|
|
520
|
+
let cur = group;
|
|
521
|
+
for (let i = 0; cur && i < 32; i++) {
|
|
522
|
+
out.add(cur.id);
|
|
523
|
+
cur = cur.parentGroupId ? diagram.getGroup(cur.parentGroupId) : undefined;
|
|
524
|
+
}
|
|
525
|
+
return out;
|
|
526
|
+
};
|
|
527
|
+
/**
|
|
528
|
+
* The zone under the hand for a widget of this board — the SAME walk the
|
|
529
|
+
* grid board runs (zones.ts), over the same tree of boards: a strip slot
|
|
530
|
+
* (with the crossing rule for a hand arriving faster than a strip is
|
|
531
|
+
* tall), a band beside a container, a plain cell on the deepest board the
|
|
532
|
+
* pointer may enter, or off. Nothing on a split board is pushed, so no
|
|
533
|
+
* container is read at rest.
|
|
534
|
+
*/
|
|
535
|
+
const tileZone = (g, ev, prev) => {
|
|
536
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
537
|
+
const roots = zoneRootsOf(peersOnCanvasOf(api.container), diagram);
|
|
538
|
+
let strip = null;
|
|
539
|
+
if (((_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.tabIndexAt) && !isStatic && g.kind === 'move' && g.node) {
|
|
540
|
+
const scaleY = clientPerWorldOf(api).y || 1;
|
|
541
|
+
const hit = (_d = 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 / scaleY })) !== null && _d !== void 0 ? _d : (prev && !g.strip ? stripCrossing({ prev, cur: ev.world, roots, stripHeight: TAB_STRIP_HEIGHT, band: BESIDE_BAND, reach: TAB_STRIP_HEIGHT / scaleY }) : null);
|
|
542
|
+
if (hit) {
|
|
543
|
+
const idx = options.tabDrop.tabIndexAt(hit.containerId, api.container.getBoundingClientRect().left + ev.screen.x);
|
|
544
|
+
if (idx !== null)
|
|
545
|
+
strip = { containerId: hit.containerId, index: idx };
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return resolveZone({
|
|
549
|
+
x: ev.world.x,
|
|
550
|
+
y: ev.world.y,
|
|
551
|
+
roots,
|
|
552
|
+
strip,
|
|
553
|
+
prev: (_g = (_e = g.beside) !== null && _e !== void 0 ? _e : (_f = g.leg) === null || _f === void 0 ? void 0 : _f.adopted.besideState()) !== null && _g !== void 0 ? _g : null,
|
|
554
|
+
maxDepth: (_h = options.nesting) !== null && _h !== void 0 ? _h : 2,
|
|
555
|
+
ghostDepth: 0,
|
|
556
|
+
ghostSubtree: EMPTY_SUBTREE,
|
|
557
|
+
gap,
|
|
558
|
+
homeChain: homeChain(),
|
|
559
|
+
});
|
|
560
|
+
};
|
|
561
|
+
/** The widget's NATURAL size for a board that adopts it: its authored cell at this board's units, not the pane it was stretched over. */
|
|
562
|
+
const pxSizeOf = (g) => {
|
|
563
|
+
const cell = persistedCell(g.id);
|
|
564
|
+
const f = frame();
|
|
565
|
+
const colW = Math.max(1, (f.width - 2 * padding - (columns - 1) * gap) / columns);
|
|
566
|
+
if (cell)
|
|
567
|
+
return { width: Math.max(1, cell.w * (colW + gap) - gap), height: Math.max(1, cell.h * (baseRowHeight + gap) - gap) };
|
|
568
|
+
return g.node ? { width: g.node.size.width, height: g.node.size.height } : { width: colW, height: baseRowHeight };
|
|
569
|
+
};
|
|
570
|
+
const endStrip = (g) => {
|
|
571
|
+
var _a;
|
|
572
|
+
if (!g.strip)
|
|
573
|
+
return;
|
|
574
|
+
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(null, null);
|
|
575
|
+
g.strip = null;
|
|
576
|
+
};
|
|
577
|
+
const endLeg = (g) => {
|
|
578
|
+
if (!g.leg)
|
|
579
|
+
return;
|
|
580
|
+
g.leg.adopted.abort();
|
|
581
|
+
g.leg = null;
|
|
582
|
+
};
|
|
512
583
|
// -- a11y -------------------------------------------------------------------
|
|
513
584
|
const nameOf = (id) => {
|
|
514
585
|
var _a, _b, _c, _d;
|
|
@@ -543,6 +614,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
543
614
|
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id);
|
|
544
615
|
};
|
|
545
616
|
let selfPeerRef = null;
|
|
617
|
+
/** The previous move event's world point: the segment a hand travelled, for a strip it stepped over. */
|
|
618
|
+
let prevWorld = null;
|
|
546
619
|
// Static boards let content be clicked — see grid-binder's staticGuard.
|
|
547
620
|
const staticGuard = (e) => {
|
|
548
621
|
var _a, _b, _c, _d;
|
|
@@ -679,6 +752,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
679
752
|
const beginMoveVisuals = (g) => {
|
|
680
753
|
var _a;
|
|
681
754
|
g.started = true;
|
|
755
|
+
prevWorld = null;
|
|
682
756
|
g.liveTree = removeSplitLeaf(g.startTree, g.id); // the siblings take the slot at once
|
|
683
757
|
g.hostEl = hostOf(g.id);
|
|
684
758
|
(_a = g.hostEl) === null || _a === void 0 ? void 0 : _a.classList.add('axdb-ghost');
|
|
@@ -686,7 +760,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
686
760
|
api.render();
|
|
687
761
|
};
|
|
688
762
|
const onToolMove = (ev) => {
|
|
689
|
-
var _a, _b;
|
|
763
|
+
var _a, _b, _c, _d, _e;
|
|
690
764
|
const g = gesture;
|
|
691
765
|
if (!g || g.kind === 'palette')
|
|
692
766
|
return;
|
|
@@ -711,24 +785,83 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
711
785
|
api.render();
|
|
712
786
|
return;
|
|
713
787
|
}
|
|
714
|
-
// MOVE: the ghost follows the pointer
|
|
788
|
+
// MOVE: the ghost follows the pointer. What the hand means is the ZONE
|
|
789
|
+
// WALK's answer first (0.4.69, the grid board's since 0.4.50): a tab
|
|
790
|
+
// container's strip makes the widget a tab, its page takes the widget
|
|
791
|
+
// through a leg, its outer fifth means a pane beside it — and only then
|
|
792
|
+
// the split board's own rule, the nearest edge of the pane under the
|
|
793
|
+
// pointer. Measured before this on the fluid demo in split mode: the
|
|
794
|
+
// header was never a tab target, the page never took a widget, and
|
|
795
|
+
// "above" claimed the upper half of the panel's body.
|
|
715
796
|
if (g.node) {
|
|
716
797
|
const x = ev.world.x - g.grab.dx;
|
|
717
798
|
const y = ev.world.y - g.grab.dy;
|
|
718
799
|
diagram.runSystemWrite(() => g.node.setPosition(x, y));
|
|
719
800
|
}
|
|
720
801
|
const inside = worldInsideBoard(ev.world.x, ev.world.y);
|
|
721
|
-
const
|
|
802
|
+
const prev = prevWorld;
|
|
803
|
+
prevWorld = { x: ev.world.x, y: ev.world.y };
|
|
804
|
+
const z = tileZone(g, ev, prev);
|
|
805
|
+
let t = null;
|
|
806
|
+
if (z.kind === 'strip') {
|
|
807
|
+
// -- INTO A STRIP: the strip marks the slot; nothing else is painted.
|
|
808
|
+
endLeg(g);
|
|
809
|
+
g.beside = null;
|
|
810
|
+
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index)
|
|
811
|
+
(_b = options.tabDrop) === null || _b === void 0 ? void 0 : _b.markDrop(z.containerId, z.index);
|
|
812
|
+
g.strip = { containerId: z.containerId, index: z.index };
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
endStrip(g);
|
|
816
|
+
const peer = z.kind === 'plain' ? (_c = z.board.ref) !== null && _c !== void 0 ? _c : null : null;
|
|
817
|
+
if (z.kind === 'beside') {
|
|
818
|
+
// -- BESIDE a container: a pane on that side of it — the split board's own line.
|
|
819
|
+
endLeg(g);
|
|
820
|
+
const grp = diagram.getGroup(z.containerId);
|
|
821
|
+
const rect = (_d = rectsOf(g.liveTree).get(z.containerId)) !== null && _d !== void 0 ? _d : (grp ? frameOfGroupW(grp) : null);
|
|
822
|
+
if (rect) {
|
|
823
|
+
g.beside = z.kept && g.beside ? g.beside : { containerId: z.containerId, side: z.side, frame0: rect };
|
|
824
|
+
t = { id: z.containerId, side: z.side, rect };
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
else if (peer && peer !== selfPeerRef) {
|
|
828
|
+
// -- INTO a nested board (a page, a section) — or out onto the board
|
|
829
|
+
// that holds this one: that board holds the widget through a leg and
|
|
830
|
+
// paints its own placeholder. A board that refuses (full, fit) leaves
|
|
831
|
+
// the split rule to answer.
|
|
832
|
+
g.beside = null;
|
|
833
|
+
if (g.leg && g.leg.peer !== peer)
|
|
834
|
+
endLeg(g);
|
|
835
|
+
if (!g.leg) {
|
|
836
|
+
const adopted = peer.adopt({ id: g.id }, ev.world, pxSizeOf(g), {});
|
|
837
|
+
if (adopted)
|
|
838
|
+
g.leg = { peer, adopted };
|
|
839
|
+
}
|
|
840
|
+
if (g.leg)
|
|
841
|
+
g.leg.adopted.move(ev.world);
|
|
842
|
+
else
|
|
843
|
+
t = inside ? dropTargetAt(g.liveTree, ev.world.x, ev.world.y, g.id) : null;
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
g.beside = null;
|
|
847
|
+
endLeg(g);
|
|
848
|
+
t = inside ? dropTargetAt(g.liveTree, ev.world.x, ev.world.y, g.id) : null;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
722
851
|
g.target = t ? targetOf(t) : null;
|
|
723
852
|
showInsertion(t ? insertionRect(t.rect, t.side) : null);
|
|
724
853
|
const out = !inside &&
|
|
854
|
+
!t &&
|
|
855
|
+
!g.strip &&
|
|
856
|
+
!g.leg &&
|
|
725
857
|
options.dragOut === 'remove' &&
|
|
726
858
|
(!options.removeZone || options.removeZone({ x: ev.screen.x, y: ev.screen.y }, { x: ev.world.x, y: ev.world.y }));
|
|
727
859
|
g.out = out;
|
|
728
|
-
(
|
|
860
|
+
(_e = g.hostEl) === null || _e === void 0 ? void 0 : _e.classList.toggle('axdb-out', out);
|
|
729
861
|
api.render();
|
|
730
862
|
};
|
|
731
863
|
const onToolUp = () => {
|
|
864
|
+
var _a, _b, _c, _d;
|
|
732
865
|
const g = gesture;
|
|
733
866
|
if (!g || g.kind === 'palette')
|
|
734
867
|
return;
|
|
@@ -747,6 +880,89 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
747
880
|
fire({ type: changed ? 'commit' : 'cancel', kind: 'resize', nodeId: g.id, changed });
|
|
748
881
|
return;
|
|
749
882
|
}
|
|
883
|
+
const paint = () => {
|
|
884
|
+
if (disposed)
|
|
885
|
+
return;
|
|
886
|
+
project(readTree());
|
|
887
|
+
api.renderNow();
|
|
888
|
+
};
|
|
889
|
+
const settle = () => {
|
|
890
|
+
paint();
|
|
891
|
+
void Promise.resolve(pendingBatch).then(paint, () => undefined);
|
|
892
|
+
};
|
|
893
|
+
if (g.strip && options.tabDrop) {
|
|
894
|
+
// -- INTO A STRIP: the widget becomes a new tab of that container and
|
|
895
|
+
// its pane leaves this tree — the tree-without-it rides as the source's
|
|
896
|
+
// "displaced", the way a grid's survivors do (0.4.69). One history step.
|
|
897
|
+
const target = g.strip;
|
|
898
|
+
g.strip = null;
|
|
899
|
+
options.tabDrop.markDrop(null, null);
|
|
900
|
+
const without = new SetSplitTreeCommand(group.id, g.startTree, normalizeSplit(g.liveTree));
|
|
901
|
+
const cmds = options.tabDrop.dropIntoStrip(g.id, target.containerId, target.index, group.id, [without]);
|
|
902
|
+
if (cmds.length === 0) {
|
|
903
|
+
project(g.startTree);
|
|
904
|
+
api.renderNow();
|
|
905
|
+
fire({ type: 'cancel', kind: 'move', nodeId: g.id, changed: false });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
execute('Move widget into a new tab', cmds);
|
|
909
|
+
settle();
|
|
910
|
+
live.announce(`${nameOf(g.id)} became a tab of ${nameOf(target.containerId)}`, 'polite', true);
|
|
911
|
+
fire({ type: 'commit', kind: 'move', nodeId: g.id, changed: true });
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (g.leg) {
|
|
915
|
+
// -- ONTO ANOTHER BOARD (a page, a section, the board holding this one):
|
|
916
|
+
// one batch across both — this tree without the pane, the target's
|
|
917
|
+
// displaced tiles, the membership, the widget's cell and frame there.
|
|
918
|
+
const leg = g.leg;
|
|
919
|
+
g.leg = null;
|
|
920
|
+
const fin = leg.adopted.finalize();
|
|
921
|
+
if (!fin) {
|
|
922
|
+
project(g.startTree);
|
|
923
|
+
api.renderNow();
|
|
924
|
+
fire({ type: 'cancel', kind: 'move', nodeId: g.id, changed: false });
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
const node = g.node;
|
|
928
|
+
if (node) {
|
|
929
|
+
diagram.runSystemWrite(() => {
|
|
930
|
+
var _a;
|
|
931
|
+
node.setPosition(fin.rect.x, fin.rect.y);
|
|
932
|
+
node.setSize(fin.rect.width, fin.rect.height, (_a = node.size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
const geom0 = (_a = g.startGeom) !== null && _a !== void 0 ? _a : { pos: { x: fin.rect.x, y: fin.rect.y }, size: { width: fin.rect.width, height: fin.rect.height } };
|
|
936
|
+
const own = buildCommitCommands([
|
|
937
|
+
{
|
|
938
|
+
id: g.id,
|
|
939
|
+
locked: false,
|
|
940
|
+
isGroup: false,
|
|
941
|
+
cellBefore: (_b = persistedCell(g.id)) !== null && _b !== void 0 ? _b : fin.cell,
|
|
942
|
+
cellAfter: fin.cell,
|
|
943
|
+
posBefore: geom0.pos,
|
|
944
|
+
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
945
|
+
sizeBefore: geom0.size,
|
|
946
|
+
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
947
|
+
},
|
|
948
|
+
]);
|
|
949
|
+
const crossing = [
|
|
950
|
+
new SetSplitTreeCommand(group.id, g.startTree, normalizeSplit(g.liveTree)),
|
|
951
|
+
...fin.commands,
|
|
952
|
+
new RemoveFromGroupCommand(group.id, g.id),
|
|
953
|
+
new AddToGroupCommand(leg.adopted.groupId, g.id),
|
|
954
|
+
...own,
|
|
955
|
+
];
|
|
956
|
+
// …and whatever follows a member out of this board: an emptied split
|
|
957
|
+
// page closes — inside one sequence with the move, or the batch could
|
|
958
|
+
// never undo (its group is gone).
|
|
959
|
+
const leaving = (_d = (_c = options.onMemberLeaving) === null || _c === void 0 ? void 0 : _c.call(options, g.id)) !== null && _d !== void 0 ? _d : [];
|
|
960
|
+
execute('Move widget', leaving.length > 0 ? [new SequenceCommand('Move widget', [...crossing, ...leaving])] : crossing);
|
|
961
|
+
settle();
|
|
962
|
+
live.announce(`${nameOf(g.id)} moved into ${nameOf(leg.adopted.groupId)}`, 'polite', true);
|
|
963
|
+
fire({ type: 'commit', kind: 'move', nodeId: g.id, changed: true });
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
750
966
|
if (g.out && options.onRemoveRequest) {
|
|
751
967
|
const without = g.liveTree;
|
|
752
968
|
void options.onRemoveRequest(g.id, [new SetSplitTreeCommand(group.id, g.startTree, normalizeSplit(without))]);
|
|
@@ -779,6 +995,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
779
995
|
return;
|
|
780
996
|
gesture = null;
|
|
781
997
|
teardownGesture(g);
|
|
998
|
+
endStrip(g);
|
|
999
|
+
endLeg(g);
|
|
782
1000
|
if (g.kind === 'palette') {
|
|
783
1001
|
api.renderNow();
|
|
784
1002
|
fire({ type: 'cancel', kind: 'palette', nodeId: g.id, changed: false });
|
|
@@ -835,6 +1053,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
835
1053
|
out: false,
|
|
836
1054
|
chip: null,
|
|
837
1055
|
esc: null,
|
|
1056
|
+
strip: null,
|
|
1057
|
+
leg: null,
|
|
1058
|
+
beside: null,
|
|
1059
|
+
startGeom: null,
|
|
838
1060
|
hostEl: null,
|
|
839
1061
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
840
1062
|
};
|
|
@@ -934,6 +1156,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
934
1156
|
out: false,
|
|
935
1157
|
chip: null,
|
|
936
1158
|
esc: null,
|
|
1159
|
+
strip: null,
|
|
1160
|
+
leg: null,
|
|
1161
|
+
beside: null,
|
|
1162
|
+
startGeom: { pos: { x: node.position.x, y: node.position.y }, size: { width: node.size.width, height: node.size.height } },
|
|
937
1163
|
hostEl: null,
|
|
938
1164
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
939
1165
|
};
|
|
@@ -1314,6 +1540,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1314
1540
|
out: false,
|
|
1315
1541
|
chip,
|
|
1316
1542
|
esc: null,
|
|
1543
|
+
strip: null,
|
|
1544
|
+
leg: null,
|
|
1545
|
+
beside: null,
|
|
1546
|
+
startGeom: null,
|
|
1317
1547
|
hostEl: null,
|
|
1318
1548
|
pointerId: null,
|
|
1319
1549
|
};
|
|
@@ -190,6 +190,44 @@ export interface StripProbe {
|
|
|
190
190
|
export declare function stripUnder(p: StripProbe): {
|
|
191
191
|
containerId: string;
|
|
192
192
|
} | null;
|
|
193
|
+
export interface CrossingProbe {
|
|
194
|
+
/** The previous pointer event's point and this one's, in world units. */
|
|
195
|
+
prev: {
|
|
196
|
+
x: number;
|
|
197
|
+
y: number;
|
|
198
|
+
};
|
|
199
|
+
cur: {
|
|
200
|
+
x: number;
|
|
201
|
+
y: number;
|
|
202
|
+
};
|
|
203
|
+
roots: ZoneBoard[];
|
|
204
|
+
/** Containers the gesture has displaced, at the frames they rest in. */
|
|
205
|
+
restFrames?: ReadonlyMap<string, ZoneRect>;
|
|
206
|
+
/** A strip's rows, in world units. */
|
|
207
|
+
stripHeight: number;
|
|
208
|
+
/** The outer fraction of a container that is its side bands: those keep their corners. Default BESIDE_BAND. */
|
|
209
|
+
band?: number;
|
|
210
|
+
/** How far past the rows a landing may be and still mean the tabs, in world units. Default: one strip's height. */
|
|
211
|
+
reach?: number;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* A HAND MOVES FASTER THAN A STRIP IS TALL. The strip is 30 px and a hand
|
|
215
|
+
* covers 40 to 80 px between pointer events, so testing only where the pointer
|
|
216
|
+
* LANDS skips it — the user, coming down from above the panel: "it's not
|
|
217
|
+
* passing by the tab header, it drops directly to inside or outside." The
|
|
218
|
+
* segment the hand travelled is tested too, in steps of half a strip: a
|
|
219
|
+
* CROSSING — the two events on opposite sides of a strip's rows — that lands
|
|
220
|
+
* within one strip's height of them means the tabs. Flying far past them does
|
|
221
|
+
* not (a fast drag into the page must never snag on the header), a sweep
|
|
222
|
+
* ALONG the rows is not a crossing, and the sides still take the corners
|
|
223
|
+
* (0.4.47): a hand landing in the outer fifth meant "after it", whatever it
|
|
224
|
+
* crossed on the way. Only for a hand ARRIVING: one already holding a strip
|
|
225
|
+
* leaves it by the stay. The grid board had this inline (0.4.67); the split
|
|
226
|
+
* board asks the same question, so it is one helper (0.4.69).
|
|
227
|
+
*/
|
|
228
|
+
export declare function stripCrossing(p: CrossingProbe): {
|
|
229
|
+
containerId: string;
|
|
230
|
+
} | null;
|
|
193
231
|
export declare function resolve(input: ResolveInput): Zone;
|
|
194
232
|
/** A join target as the walk sees it: another tab container, its frame anchored by the binder while the pointer is inside it. */
|
|
195
233
|
export interface TabTarget {
|
|
@@ -125,6 +125,45 @@ export function stripUnder(p) {
|
|
|
125
125
|
visit(r, 0, 0);
|
|
126
126
|
return bestId === null ? null : { containerId: bestId };
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* A HAND MOVES FASTER THAN A STRIP IS TALL. The strip is 30 px and a hand
|
|
130
|
+
* covers 40 to 80 px between pointer events, so testing only where the pointer
|
|
131
|
+
* LANDS skips it — the user, coming down from above the panel: "it's not
|
|
132
|
+
* passing by the tab header, it drops directly to inside or outside." The
|
|
133
|
+
* segment the hand travelled is tested too, in steps of half a strip: a
|
|
134
|
+
* CROSSING — the two events on opposite sides of a strip's rows — that lands
|
|
135
|
+
* within one strip's height of them means the tabs. Flying far past them does
|
|
136
|
+
* not (a fast drag into the page must never snag on the header), a sweep
|
|
137
|
+
* ALONG the rows is not a crossing, and the sides still take the corners
|
|
138
|
+
* (0.4.47): a hand landing in the outer fifth meant "after it", whatever it
|
|
139
|
+
* crossed on the way. Only for a hand ARRIVING: one already holding a strip
|
|
140
|
+
* leaves it by the stay. The grid board had this inline (0.4.67); the split
|
|
141
|
+
* board asks the same question, so it is one helper (0.4.69).
|
|
142
|
+
*/
|
|
143
|
+
export function stripCrossing(p) {
|
|
144
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
145
|
+
const dx = p.cur.x - p.prev.x;
|
|
146
|
+
const dy = p.cur.y - p.prev.y;
|
|
147
|
+
const n = Math.ceil(Math.hypot(dx, dy) / Math.max(1, p.stripHeight / 2));
|
|
148
|
+
let crossed = null;
|
|
149
|
+
for (let i = 1; i < n && !crossed; i++) {
|
|
150
|
+
crossed = (_b = (_a = stripUnder({ x: p.prev.x + (dx * i) / n, y: p.prev.y + (dy * i) / n, roots: p.roots, held: null, stay: 0, restFrames: p.restFrames })) === null || _a === void 0 ? void 0 : _a.containerId) !== null && _b !== void 0 ? _b : null;
|
|
151
|
+
}
|
|
152
|
+
if (!crossed)
|
|
153
|
+
return null;
|
|
154
|
+
const f = (_d = (_c = p.restFrames) === null || _c === void 0 ? void 0 : _c.get(crossed)) !== null && _d !== void 0 ? _d : (_e = containerOf(p.roots, crossed)) === null || _e === void 0 ? void 0 : _e.frame;
|
|
155
|
+
if (!f)
|
|
156
|
+
return null;
|
|
157
|
+
const top = f.y;
|
|
158
|
+
const bottom = f.y + p.stripHeight;
|
|
159
|
+
const through = (p.prev.y < top && p.cur.y > bottom) || (p.prev.y > bottom && p.cur.y < top);
|
|
160
|
+
const away = p.cur.y < top ? top - p.cur.y : p.cur.y > bottom ? p.cur.y - bottom : 0;
|
|
161
|
+
const band = (_f = p.band) !== null && _f !== void 0 ? _f : BESIDE_BAND;
|
|
162
|
+
const rx = (p.cur.x - f.x) / Math.max(1, f.width);
|
|
163
|
+
const inSideBand = rx < band || rx > 1 - band;
|
|
164
|
+
const inside = p.cur.x >= f.x && p.cur.x <= f.x + f.width;
|
|
165
|
+
return through && !inSideBand && inside && away <= ((_g = p.reach) !== null && _g !== void 0 ? _g : p.stripHeight) ? { containerId: crossed } : null;
|
|
166
|
+
}
|
|
128
167
|
/**
|
|
129
168
|
* The band a point is in for a TILE drag: the sides and the bottom as
|
|
130
169
|
* `bandOf` gives them, and "top" ONLY from the band hanging above the frame.
|