@grafloria/element 0.4.68 → 0.4.70
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 +20 -2
- 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 +371 -12
- 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';
|
|
@@ -631,7 +631,7 @@ export interface AdoptedLeg {
|
|
|
631
631
|
containerId: string;
|
|
632
632
|
side: BesideSide;
|
|
633
633
|
frame0: WorldRect;
|
|
634
|
-
vacated
|
|
634
|
+
vacated?: WorldRect;
|
|
635
635
|
} | null;
|
|
636
636
|
/**
|
|
637
637
|
* Take the tile OFF the board while the pointer is somewhere the board is
|
|
@@ -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,224 @@ 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
|
+
};
|
|
583
|
+
/**
|
|
584
|
+
* What the zone means for the pane under the hand — the one step the widget
|
|
585
|
+
* drag and the palette drag share (0.4.70): a strip marks its slot (a
|
|
586
|
+
* widget's, never a chip's — the walk answers no strip for a chip); a band
|
|
587
|
+
* beside a container is a pane on that side, this board's line; a plain
|
|
588
|
+
* cell on a NESTED board, or on the board holding this one, hands the tile
|
|
589
|
+
* to that board through a leg (its own placeholder), a board that refuses
|
|
590
|
+
* leaving the split rule to answer; anything else is the nearest edge of
|
|
591
|
+
* the pane under the pointer. Answers the insertion target, or null.
|
|
592
|
+
*/
|
|
593
|
+
const zoneTarget = (g, z, world, inside, px) => {
|
|
594
|
+
var _a, _b, _c;
|
|
595
|
+
if (z.kind === 'strip') {
|
|
596
|
+
endLeg(g);
|
|
597
|
+
g.beside = null;
|
|
598
|
+
if (!g.strip || g.strip.containerId !== z.containerId || g.strip.index !== z.index)
|
|
599
|
+
(_a = options.tabDrop) === null || _a === void 0 ? void 0 : _a.markDrop(z.containerId, z.index);
|
|
600
|
+
g.strip = { containerId: z.containerId, index: z.index };
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
endStrip(g);
|
|
604
|
+
const peer = z.kind === 'plain' ? ((_b = z.board.ref) !== null && _b !== void 0 ? _b : null) : null;
|
|
605
|
+
if (z.kind === 'beside') {
|
|
606
|
+
endLeg(g);
|
|
607
|
+
const grp = diagram.getGroup(z.containerId);
|
|
608
|
+
const rect = (_c = rectsOf(g.liveTree).get(z.containerId)) !== null && _c !== void 0 ? _c : (grp ? frameOfGroupW(grp) : null);
|
|
609
|
+
if (!rect)
|
|
610
|
+
return null;
|
|
611
|
+
g.beside = z.kept && g.beside ? g.beside : { containerId: z.containerId, side: z.side, frame0: rect };
|
|
612
|
+
return { id: z.containerId, side: z.side, rect };
|
|
613
|
+
}
|
|
614
|
+
g.beside = null;
|
|
615
|
+
if (peer && peer !== selfPeerRef) {
|
|
616
|
+
if (g.leg && g.leg.peer !== peer)
|
|
617
|
+
endLeg(g);
|
|
618
|
+
if (!g.leg) {
|
|
619
|
+
const adopted = peer.adopt({ id: g.id }, world, px, {});
|
|
620
|
+
if (adopted)
|
|
621
|
+
g.leg = { peer, adopted };
|
|
622
|
+
}
|
|
623
|
+
if (g.leg) {
|
|
624
|
+
g.leg.adopted.move(world);
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else
|
|
629
|
+
endLeg(g);
|
|
630
|
+
return inside ? dropTargetAt(g.liveTree, world.x, world.y, g.id) : null;
|
|
631
|
+
};
|
|
632
|
+
// -- the split board as an adoption TARGET (0.4.70) ----------------------------
|
|
633
|
+
/**
|
|
634
|
+
* A tile from ANOTHER board — a widget leaving a page or a section, a
|
|
635
|
+
* palette chip — held over this board through a leg. Measured live on
|
|
636
|
+
* 0.4.69: a widget dragged out of the Filters page in split mode got no
|
|
637
|
+
* line and no placeholder anywhere, the ghost dimmed, every release
|
|
638
|
+
* snapped home — this peer's `adopt` answered null. The leg's promise is
|
|
639
|
+
* the insertion line on the nearest edge of the pane under the pointer;
|
|
640
|
+
* nothing moves until the release (the split model's rule); `finalize`
|
|
641
|
+
* answers the tree with the new pane, so the source's commit carries it. A
|
|
642
|
+
* beside on a container of this board is a pane on that side of it. An
|
|
643
|
+
* empty board takes the tile as its whole.
|
|
644
|
+
*/
|
|
645
|
+
const adoptPane = (node, world, opts = {}) => {
|
|
646
|
+
if (disposed || isStatic)
|
|
647
|
+
return null;
|
|
648
|
+
const tree = readTree();
|
|
649
|
+
if (tree && splitLeaves(tree).includes(node.id))
|
|
650
|
+
return null; // a pane of this board never adopts itself
|
|
651
|
+
const whole = { path: [], side: 'top', rect: frame() };
|
|
652
|
+
const state = { target: null };
|
|
653
|
+
const targetAt = (w) => (!worldInsideBoard(w.x, w.y) ? null : tree ? dropTargetAt(tree, w.x, w.y) : whole);
|
|
654
|
+
const paint = () => {
|
|
655
|
+
showInsertion(state.target ? insertionRect(state.target.rect, state.target.side) : null);
|
|
656
|
+
api.render();
|
|
657
|
+
};
|
|
658
|
+
const after = () => {
|
|
659
|
+
if (!state.target)
|
|
660
|
+
return null;
|
|
661
|
+
if (!tree)
|
|
662
|
+
return normalizeSplit(addSplitLeaf(null, node.id, frame(), gap, padding));
|
|
663
|
+
const t = state.target;
|
|
664
|
+
const side = rtl && (t.side === 'left' || t.side === 'right') ? (t.side === 'left' ? 'right' : 'left') : t.side;
|
|
665
|
+
return normalizeSplit(insertSplitLeaf(tree, node.id, targetRef(t), side));
|
|
666
|
+
};
|
|
667
|
+
const cellOf = () => {
|
|
668
|
+
var _a;
|
|
669
|
+
const t = after();
|
|
670
|
+
return t ? ((_a = cellsFromSplit(t, columns, rowsGuess()).get(node.id)) !== null && _a !== void 0 ? _a : null) : null;
|
|
671
|
+
};
|
|
672
|
+
const rectOf = () => {
|
|
673
|
+
var _a;
|
|
674
|
+
const t = after();
|
|
675
|
+
return t ? ((_a = rectsOf(t).get(node.id)) !== null && _a !== void 0 ? _a : null) : null;
|
|
676
|
+
};
|
|
677
|
+
const besideOn = (containerId, side) => {
|
|
678
|
+
const rect = rectsOf(tree).get(containerId);
|
|
679
|
+
if (!rect)
|
|
680
|
+
return;
|
|
681
|
+
state.target = { id: containerId, side, rect };
|
|
682
|
+
paint();
|
|
683
|
+
};
|
|
684
|
+
if (opts.beside)
|
|
685
|
+
besideOn(opts.beside.containerId, opts.beside.side);
|
|
686
|
+
else {
|
|
687
|
+
state.target = targetAt(world);
|
|
688
|
+
paint();
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
groupId: group.id,
|
|
692
|
+
move: (w) => {
|
|
693
|
+
state.target = targetAt(w);
|
|
694
|
+
paint();
|
|
695
|
+
},
|
|
696
|
+
beside: (containerId, side) => besideOn(containerId, side),
|
|
697
|
+
besideState: () => {
|
|
698
|
+
const t = state.target;
|
|
699
|
+
if (!t || !('id' in t) || !diagram.getGroup(t.id))
|
|
700
|
+
return null;
|
|
701
|
+
return { containerId: t.id, side: t.side, frame0: t.rect };
|
|
702
|
+
},
|
|
703
|
+
leave: () => {
|
|
704
|
+
state.target = null;
|
|
705
|
+
paint();
|
|
706
|
+
},
|
|
707
|
+
enter: (w) => {
|
|
708
|
+
state.target = targetAt(w);
|
|
709
|
+
paint();
|
|
710
|
+
},
|
|
711
|
+
place: () => false, // a pane is the tree's to size: no prescribed cell here
|
|
712
|
+
baseline: () => (tree ? cellsFromSplit(tree, columns, rowsGuess()) : new Map()),
|
|
713
|
+
cell: () => cellOf(),
|
|
714
|
+
rect: () => rectOf(),
|
|
715
|
+
abort: () => {
|
|
716
|
+
state.target = null;
|
|
717
|
+
showInsertion(null);
|
|
718
|
+
api.render();
|
|
719
|
+
},
|
|
720
|
+
finalize: () => {
|
|
721
|
+
const t = after();
|
|
722
|
+
const cell = cellOf();
|
|
723
|
+
const rect = rectOf();
|
|
724
|
+
state.target = null;
|
|
725
|
+
showInsertion(null);
|
|
726
|
+
if (!t || !cell || !rect)
|
|
727
|
+
return null;
|
|
728
|
+
return { commands: [new SetSplitTreeCommand(group.id, tree, t)], cell, rect };
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
};
|
|
512
732
|
// -- a11y -------------------------------------------------------------------
|
|
513
733
|
const nameOf = (id) => {
|
|
514
734
|
var _a, _b, _c, _d;
|
|
@@ -543,6 +763,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
543
763
|
(_a = options.onSelect) === null || _a === void 0 ? void 0 : _a.call(options, id);
|
|
544
764
|
};
|
|
545
765
|
let selfPeerRef = null;
|
|
766
|
+
/** The previous move event's world point: the segment a hand travelled, for a strip it stepped over. */
|
|
767
|
+
let prevWorld = null;
|
|
546
768
|
// Static boards let content be clicked — see grid-binder's staticGuard.
|
|
547
769
|
const staticGuard = (e) => {
|
|
548
770
|
var _a, _b, _c, _d;
|
|
@@ -679,6 +901,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
679
901
|
const beginMoveVisuals = (g) => {
|
|
680
902
|
var _a;
|
|
681
903
|
g.started = true;
|
|
904
|
+
prevWorld = null;
|
|
682
905
|
g.liveTree = removeSplitLeaf(g.startTree, g.id); // the siblings take the slot at once
|
|
683
906
|
g.hostEl = hostOf(g.id);
|
|
684
907
|
(_a = g.hostEl) === null || _a === void 0 ? void 0 : _a.classList.add('axdb-ghost');
|
|
@@ -711,17 +934,29 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
711
934
|
api.render();
|
|
712
935
|
return;
|
|
713
936
|
}
|
|
714
|
-
// MOVE: the ghost follows the pointer
|
|
937
|
+
// MOVE: the ghost follows the pointer. What the hand means is the ZONE
|
|
938
|
+
// WALK's answer first (0.4.69, the grid board's since 0.4.50): a tab
|
|
939
|
+
// container's strip makes the widget a tab, its page takes the widget
|
|
940
|
+
// through a leg, its outer fifth means a pane beside it — and only then
|
|
941
|
+
// the split board's own rule, the nearest edge of the pane under the
|
|
942
|
+
// pointer. Measured before this on the fluid demo in split mode: the
|
|
943
|
+
// header was never a tab target, the page never took a widget, and
|
|
944
|
+
// "above" claimed the upper half of the panel's body.
|
|
715
945
|
if (g.node) {
|
|
716
946
|
const x = ev.world.x - g.grab.dx;
|
|
717
947
|
const y = ev.world.y - g.grab.dy;
|
|
718
948
|
diagram.runSystemWrite(() => g.node.setPosition(x, y));
|
|
719
949
|
}
|
|
720
950
|
const inside = worldInsideBoard(ev.world.x, ev.world.y);
|
|
721
|
-
const
|
|
951
|
+
const prev = prevWorld;
|
|
952
|
+
prevWorld = { x: ev.world.x, y: ev.world.y };
|
|
953
|
+
const t = zoneTarget(g, tileZone(g, ev, prev), ev.world, inside, pxSizeOf(g));
|
|
722
954
|
g.target = t ? targetOf(t) : null;
|
|
723
955
|
showInsertion(t ? insertionRect(t.rect, t.side) : null);
|
|
724
956
|
const out = !inside &&
|
|
957
|
+
!t &&
|
|
958
|
+
!g.strip &&
|
|
959
|
+
!g.leg &&
|
|
725
960
|
options.dragOut === 'remove' &&
|
|
726
961
|
(!options.removeZone || options.removeZone({ x: ev.screen.x, y: ev.screen.y }, { x: ev.world.x, y: ev.world.y }));
|
|
727
962
|
g.out = out;
|
|
@@ -729,6 +964,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
729
964
|
api.render();
|
|
730
965
|
};
|
|
731
966
|
const onToolUp = () => {
|
|
967
|
+
var _a, _b, _c, _d;
|
|
732
968
|
const g = gesture;
|
|
733
969
|
if (!g || g.kind === 'palette')
|
|
734
970
|
return;
|
|
@@ -747,6 +983,89 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
747
983
|
fire({ type: changed ? 'commit' : 'cancel', kind: 'resize', nodeId: g.id, changed });
|
|
748
984
|
return;
|
|
749
985
|
}
|
|
986
|
+
const paint = () => {
|
|
987
|
+
if (disposed)
|
|
988
|
+
return;
|
|
989
|
+
project(readTree());
|
|
990
|
+
api.renderNow();
|
|
991
|
+
};
|
|
992
|
+
const settle = () => {
|
|
993
|
+
paint();
|
|
994
|
+
void Promise.resolve(pendingBatch).then(paint, () => undefined);
|
|
995
|
+
};
|
|
996
|
+
if (g.strip && options.tabDrop) {
|
|
997
|
+
// -- INTO A STRIP: the widget becomes a new tab of that container and
|
|
998
|
+
// its pane leaves this tree — the tree-without-it rides as the source's
|
|
999
|
+
// "displaced", the way a grid's survivors do (0.4.69). One history step.
|
|
1000
|
+
const target = g.strip;
|
|
1001
|
+
g.strip = null;
|
|
1002
|
+
options.tabDrop.markDrop(null, null);
|
|
1003
|
+
const without = new SetSplitTreeCommand(group.id, g.startTree, normalizeSplit(g.liveTree));
|
|
1004
|
+
const cmds = options.tabDrop.dropIntoStrip(g.id, target.containerId, target.index, group.id, [without]);
|
|
1005
|
+
if (cmds.length === 0) {
|
|
1006
|
+
project(g.startTree);
|
|
1007
|
+
api.renderNow();
|
|
1008
|
+
fire({ type: 'cancel', kind: 'move', nodeId: g.id, changed: false });
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
execute('Move widget into a new tab', cmds);
|
|
1012
|
+
settle();
|
|
1013
|
+
live.announce(`${nameOf(g.id)} became a tab of ${nameOf(target.containerId)}`, 'polite', true);
|
|
1014
|
+
fire({ type: 'commit', kind: 'move', nodeId: g.id, changed: true });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (g.leg) {
|
|
1018
|
+
// -- ONTO ANOTHER BOARD (a page, a section, the board holding this one):
|
|
1019
|
+
// one batch across both — this tree without the pane, the target's
|
|
1020
|
+
// displaced tiles, the membership, the widget's cell and frame there.
|
|
1021
|
+
const leg = g.leg;
|
|
1022
|
+
g.leg = null;
|
|
1023
|
+
const fin = leg.adopted.finalize();
|
|
1024
|
+
if (!fin) {
|
|
1025
|
+
project(g.startTree);
|
|
1026
|
+
api.renderNow();
|
|
1027
|
+
fire({ type: 'cancel', kind: 'move', nodeId: g.id, changed: false });
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const node = g.node;
|
|
1031
|
+
if (node) {
|
|
1032
|
+
diagram.runSystemWrite(() => {
|
|
1033
|
+
var _a;
|
|
1034
|
+
node.setPosition(fin.rect.x, fin.rect.y);
|
|
1035
|
+
node.setSize(fin.rect.width, fin.rect.height, (_a = node.size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
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 } };
|
|
1039
|
+
const own = buildCommitCommands([
|
|
1040
|
+
{
|
|
1041
|
+
id: g.id,
|
|
1042
|
+
locked: false,
|
|
1043
|
+
isGroup: false,
|
|
1044
|
+
cellBefore: (_b = persistedCell(g.id)) !== null && _b !== void 0 ? _b : fin.cell,
|
|
1045
|
+
cellAfter: fin.cell,
|
|
1046
|
+
posBefore: geom0.pos,
|
|
1047
|
+
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
1048
|
+
sizeBefore: geom0.size,
|
|
1049
|
+
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
1050
|
+
},
|
|
1051
|
+
]);
|
|
1052
|
+
const crossing = [
|
|
1053
|
+
new SetSplitTreeCommand(group.id, g.startTree, normalizeSplit(g.liveTree)),
|
|
1054
|
+
...fin.commands,
|
|
1055
|
+
new RemoveFromGroupCommand(group.id, g.id),
|
|
1056
|
+
new AddToGroupCommand(leg.adopted.groupId, g.id),
|
|
1057
|
+
...own,
|
|
1058
|
+
];
|
|
1059
|
+
// …and whatever follows a member out of this board: an emptied split
|
|
1060
|
+
// page closes — inside one sequence with the move, or the batch could
|
|
1061
|
+
// never undo (its group is gone).
|
|
1062
|
+
const leaving = (_d = (_c = options.onMemberLeaving) === null || _c === void 0 ? void 0 : _c.call(options, g.id)) !== null && _d !== void 0 ? _d : [];
|
|
1063
|
+
execute('Move widget', leaving.length > 0 ? [new SequenceCommand('Move widget', [...crossing, ...leaving])] : crossing);
|
|
1064
|
+
settle();
|
|
1065
|
+
live.announce(`${nameOf(g.id)} moved into ${nameOf(leg.adopted.groupId)}`, 'polite', true);
|
|
1066
|
+
fire({ type: 'commit', kind: 'move', nodeId: g.id, changed: true });
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
750
1069
|
if (g.out && options.onRemoveRequest) {
|
|
751
1070
|
const without = g.liveTree;
|
|
752
1071
|
void options.onRemoveRequest(g.id, [new SetSplitTreeCommand(group.id, g.startTree, normalizeSplit(without))]);
|
|
@@ -779,6 +1098,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
779
1098
|
return;
|
|
780
1099
|
gesture = null;
|
|
781
1100
|
teardownGesture(g);
|
|
1101
|
+
endStrip(g);
|
|
1102
|
+
endLeg(g);
|
|
782
1103
|
if (g.kind === 'palette') {
|
|
783
1104
|
api.renderNow();
|
|
784
1105
|
fire({ type: 'cancel', kind: 'palette', nodeId: g.id, changed: false });
|
|
@@ -835,6 +1156,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
835
1156
|
out: false,
|
|
836
1157
|
chip: null,
|
|
837
1158
|
esc: null,
|
|
1159
|
+
strip: null,
|
|
1160
|
+
leg: null,
|
|
1161
|
+
beside: null,
|
|
1162
|
+
startGeom: null,
|
|
838
1163
|
hostEl: null,
|
|
839
1164
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
840
1165
|
};
|
|
@@ -934,6 +1259,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
934
1259
|
out: false,
|
|
935
1260
|
chip: null,
|
|
936
1261
|
esc: null,
|
|
1262
|
+
strip: null,
|
|
1263
|
+
leg: null,
|
|
1264
|
+
beside: null,
|
|
1265
|
+
startGeom: { pos: { x: node.position.x, y: node.position.y }, size: { width: node.size.width, height: node.size.height } },
|
|
937
1266
|
hostEl: null,
|
|
938
1267
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
939
1268
|
};
|
|
@@ -1314,6 +1643,10 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1314
1643
|
out: false,
|
|
1315
1644
|
chip,
|
|
1316
1645
|
esc: null,
|
|
1646
|
+
strip: null,
|
|
1647
|
+
leg: null,
|
|
1648
|
+
beside: null,
|
|
1649
|
+
startGeom: null,
|
|
1317
1650
|
hostEl: null,
|
|
1318
1651
|
pointerId: null,
|
|
1319
1652
|
};
|
|
@@ -1323,6 +1656,11 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1323
1656
|
window.removeEventListener('pointermove', onMove, true);
|
|
1324
1657
|
window.removeEventListener('pointerup', onUp, true);
|
|
1325
1658
|
};
|
|
1659
|
+
// The chip's size in this board's units — what a page or a section that
|
|
1660
|
+
// adopts it makes its tile from (the grid board does the same, 0.4.53).
|
|
1661
|
+
const f0 = frame();
|
|
1662
|
+
const colW0 = Math.max(1, (f0.width - 2 * padding - (columns - 1) * gap) / columns);
|
|
1663
|
+
const chipPx = { width: Math.max(1, spec.w * (colW0 + gap) - gap), height: Math.max(1, spec.h * (baseRowHeight + gap) - gap) };
|
|
1326
1664
|
const onMove = (e) => {
|
|
1327
1665
|
if (gesture !== g)
|
|
1328
1666
|
return detach();
|
|
@@ -1331,19 +1669,39 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1331
1669
|
chip.style.top = `${e.clientY + 6}px`;
|
|
1332
1670
|
}
|
|
1333
1671
|
const w = toWorld(e.clientX, e.clientY);
|
|
1334
|
-
const
|
|
1672
|
+
const inside = worldInsideBoard(w.x, w.y);
|
|
1673
|
+
// Through the zones (0.4.70): a page or a section takes the chip through
|
|
1674
|
+
// a leg and shows its own placeholder; a container's band means a pane
|
|
1675
|
+
// beside it; a chip never becomes a tab (the grid's rule too).
|
|
1676
|
+
const ev = { type: 'move', world: w, screen: { x: e.clientX, y: e.clientY }, modifiers: { shift: false, ctrl: false, alt: false, meta: false } };
|
|
1677
|
+
const t = zoneTarget(g, tileZone(g, ev, null), w, inside, chipPx);
|
|
1335
1678
|
g.target = t ? targetOf(t) : null;
|
|
1336
1679
|
showInsertion(t ? insertionRect(t.rect, t.side) : null);
|
|
1337
|
-
chip === null || chip === void 0 ? void 0 : chip.classList.toggle('axdb-out', !t && !!g.liveTree);
|
|
1680
|
+
chip === null || chip === void 0 ? void 0 : chip.classList.toggle('axdb-out', !t && !g.leg && !!g.liveTree);
|
|
1338
1681
|
api.render();
|
|
1339
1682
|
};
|
|
1340
1683
|
const onUp = () => {
|
|
1341
|
-
var _a, _b;
|
|
1684
|
+
var _a, _b, _c;
|
|
1342
1685
|
detach();
|
|
1343
1686
|
if (gesture !== g)
|
|
1344
1687
|
return;
|
|
1345
1688
|
gesture = null;
|
|
1346
1689
|
teardownGesture(g);
|
|
1690
|
+
if (g.leg) {
|
|
1691
|
+
// Dropped into another board (a page, a section): its leg closes with
|
|
1692
|
+
// that board's displaced tiles; this tree is untouched.
|
|
1693
|
+
const leg = g.leg;
|
|
1694
|
+
g.leg = null;
|
|
1695
|
+
const fin = leg.adopted.finalize();
|
|
1696
|
+
if (!fin) {
|
|
1697
|
+
api.renderNow();
|
|
1698
|
+
fire({ type: 'cancel', kind: 'palette', nodeId: node.id, changed: false });
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
void ((_a = options.onDropIn) === null || _a === void 0 ? void 0 : _a.call(options, node, fin.cell, fin.commands, { boardId: leg.adopted.groupId }));
|
|
1702
|
+
fire({ type: 'drop-in', kind: 'palette', nodeId: node.id, changed: true });
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1347
1705
|
const tree = g.liveTree;
|
|
1348
1706
|
const after = g.target
|
|
1349
1707
|
? insertSplitLeaf(tree, node.id, targetRef(g.target), g.target.side)
|
|
@@ -1356,9 +1714,9 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1356
1714
|
return;
|
|
1357
1715
|
}
|
|
1358
1716
|
const cells = cellsFromSplit(after, columns, rowsGuess());
|
|
1359
|
-
const cell = (
|
|
1717
|
+
const cell = (_b = cells.get(node.id)) !== null && _b !== void 0 ? _b : { x: 0, y: 0, w: Math.max(1, spec.w), h: Math.max(1, spec.h) };
|
|
1360
1718
|
const displaced = [new SetSplitTreeCommand(group.id, tree, normalizeSplit(after))];
|
|
1361
|
-
void ((
|
|
1719
|
+
void ((_c = options.onDropIn) === null || _c === void 0 ? void 0 : _c.call(options, node, cell, displaced, { boardId: group.id }));
|
|
1362
1720
|
fire({ type: 'drop-in', kind: 'palette', nodeId: node.id, changed: true });
|
|
1363
1721
|
};
|
|
1364
1722
|
window.addEventListener('pointermove', onMove, true);
|
|
@@ -1779,7 +2137,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1779
2137
|
const f = frame();
|
|
1780
2138
|
return f.width * f.height;
|
|
1781
2139
|
},
|
|
1782
|
-
|
|
2140
|
+
// A tile from another board lands as a PANE where the line shows (0.4.70) — see adoptPane.
|
|
2141
|
+
adopt: (node, world, _px, opts) => adoptPane(node, world, opts),
|
|
1783
2142
|
};
|
|
1784
2143
|
const unregisterPeer = registerBoardPeer(api.container, selfPeer);
|
|
1785
2144
|
selfPeerRef = selfPeer;
|
|
@@ -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.
|