@grafloria/element 0.4.54 → 0.4.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/dashboard-kit/board-ctx.d.ts +62 -0
- package/src/lib/dashboard-kit/board-ctx.js +2 -0
- package/src/lib/dashboard-kit/chrome.d.ts +60 -0
- package/src/lib/dashboard-kit/chrome.js +461 -0
- package/src/lib/dashboard-kit/dashboard.d.ts +2 -0
- package/src/lib/dashboard-kit/dashboard.js +50 -2
- package/src/lib/dashboard-kit/edges.d.ts +15 -0
- package/src/lib/dashboard-kit/edges.js +30 -0
- package/src/lib/dashboard-kit/grid-binder.d.ts +10 -43
- package/src/lib/dashboard-kit/grid-binder.js +284 -951
- package/src/lib/dashboard-kit/grip.d.ts +37 -0
- package/src/lib/dashboard-kit/grip.js +67 -0
- package/src/lib/dashboard-kit/keyboard.d.ts +41 -0
- package/src/lib/dashboard-kit/keyboard.js +145 -0
- package/src/lib/dashboard-kit/project.d.ts +30 -0
- package/src/lib/dashboard-kit/project.js +177 -0
- package/src/lib/dashboard-kit/zones.d.ts +10 -0
- package/src/lib/dashboard-kit/zones.js +13 -3
|
@@ -51,51 +51,21 @@
|
|
|
51
51
|
*/
|
|
52
52
|
import { __awaiter } from "tslib";
|
|
53
53
|
import { AddToGroupCommand, BatchCommand, GridPackEngine, RemoveFromGroupCommand, } from '@grafloria/engine';
|
|
54
|
-
import {
|
|
54
|
+
import { registerTool } from '@grafloria/renderer';
|
|
55
55
|
import { buildCommitCommands, cellFromGridItem, cellToRect, columnUnitFor, gridItemFromCell, pointToCell, rowHeightFor, sizeToSpan, } from './grid-mapping.js';
|
|
56
56
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
57
|
-
import { captionOfGroup,
|
|
57
|
+
import { captionOfGroup, captionPassThrough, sectionCaptionReserve } from './caption.js';
|
|
58
58
|
import { TAB_STRIP_HEIGHT } from './tabs.js';
|
|
59
59
|
import { BESIDE_BAND, resolve as resolveZone, resolveTabZone } from './zones.js';
|
|
60
60
|
import { SequenceCommand, SetGroupCellCommand, tileCommands } from './commit.js';
|
|
61
|
+
import { createProjection } from './project.js';
|
|
62
|
+
import { cursorFor, edgesNear, NO_EDGES, anyEdge } from './edges.js';
|
|
63
|
+
import { DRAG_HANDLE_CLASS, dragHandleSelector, gripHostOf, normalizeDragHandle, pressOnDragHandle, sameDragHandle } from './grip.js';
|
|
64
|
+
import { createChrome, edgeGripFor, isTabsGroup } from './chrome.js';
|
|
65
|
+
import { createKeyboard, describeCell, liveRegionFor } from './keyboard.js';
|
|
66
|
+
export { EDGE_GRIP, anyEdge } from './edges.js';
|
|
67
|
+
export { GRIP_CLASS, DRAG_HANDLE_CLASS, CAPTION_BAND, normalizeDragHandle, dragHandleSelector, gripOf, syncGrip, gripHostOf, pressOnDragHandle } from './grip.js';
|
|
61
68
|
export { SequenceCommand, SetGroupCellCommand } from './commit.js';
|
|
62
|
-
/** The class of the painted grip element (a child of the node host). */
|
|
63
|
-
export const GRIP_CLASS = 'axdb-grip';
|
|
64
|
-
/** The container class that turns the caption strip's grip dots on. */
|
|
65
|
-
export const DRAG_HANDLE_CLASS = 'axdb-drag-handle';
|
|
66
|
-
/** A `dragHandle` value with its defaults filled in — the form the handle reports. */
|
|
67
|
-
export const normalizeDragHandle = (v) => {
|
|
68
|
-
var _a, _b;
|
|
69
|
-
return typeof v === 'object' && v !== null && v.grip
|
|
70
|
-
? { grip: true, position: (_a = v.position) !== null && _a !== void 0 ? _a : 'left', placement: (_b = v.placement) !== null && _b !== void 0 ? _b : 'inside' }
|
|
71
|
-
: v === true ? true : typeof v === 'string' && v.length > 0 ? v : false;
|
|
72
|
-
};
|
|
73
|
-
/** The selector a `dragHandle` value names — `null` when the whole card is the handle. */
|
|
74
|
-
export const dragHandleSelector = (v) => v === true ? '.axdb-widget-h' : typeof v === 'string' ? v : typeof v === 'object' ? '.' + GRIP_CLASS : null;
|
|
75
|
-
/** The grip config of a value, or null when it paints no grip. */
|
|
76
|
-
export const gripOf = (v) => (typeof v === 'object' ? v : null);
|
|
77
|
-
const sameDragHandle = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
78
|
-
/**
|
|
79
|
-
* Paint (or remove) the grip on one host, and stamp the host with the grip's
|
|
80
|
-
* placement so the header can make room for it. `movable` false = no grip.
|
|
81
|
-
*/
|
|
82
|
-
export function syncGrip(host, cfg, movable) {
|
|
83
|
-
var _a, _b, _c, _d, _e;
|
|
84
|
-
const existing = host.querySelector(':scope > .' + GRIP_CLASS);
|
|
85
|
-
host.classList.remove('axdb-gp-inside', 'axdb-gp-outside', 'axdb-gp-left', 'axdb-gp-center', 'axdb-gp-right');
|
|
86
|
-
if (!cfg || !movable) {
|
|
87
|
-
existing === null || existing === void 0 ? void 0 : existing.remove();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const el = (_a = existing) !== null && _a !== void 0 ? _a : host.ownerDocument.createElement('div');
|
|
91
|
-
if (!existing) {
|
|
92
|
-
el.setAttribute('aria-hidden', 'true');
|
|
93
|
-
el.setAttribute('title', 'Drag');
|
|
94
|
-
host.appendChild(el);
|
|
95
|
-
}
|
|
96
|
-
el.className = `${GRIP_CLASS} ${GRIP_CLASS}--${(_b = cfg.position) !== null && _b !== void 0 ? _b : 'left'} ${GRIP_CLASS}--${(_c = cfg.placement) !== null && _c !== void 0 ? _c : 'inside'}`;
|
|
97
|
-
host.classList.add(`axdb-gp-${(_d = cfg.placement) !== null && _d !== void 0 ? _d : 'inside'}`, `axdb-gp-${(_e = cfg.position) !== null && _e !== void 0 ? _e : 'left'}`);
|
|
98
|
-
}
|
|
99
69
|
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
100
70
|
/**
|
|
101
71
|
* Is this press OURS? The renderer's tool registry is PAGE-GLOBAL: every
|
|
@@ -131,33 +101,6 @@ export function ownsPress(container, diagram, ev, hit) {
|
|
|
131
101
|
}
|
|
132
102
|
return true;
|
|
133
103
|
}
|
|
134
|
-
export function gripHostOf(target) {
|
|
135
|
-
var _a, _b;
|
|
136
|
-
const grip = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '.' + GRIP_CLASS);
|
|
137
|
-
return (_b = grip === null || grip === void 0 ? void 0 : grip.closest('.grafloria-node-host')) !== null && _b !== void 0 ? _b : null;
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
141
|
-
* always does. The default handle is a CAPTION BAR the DevExpress way: the
|
|
142
|
-
* strip from the card's top edge down to the header's bottom, padding
|
|
143
|
-
* included — so the pointer need not hit the header's text to grab the tile.
|
|
144
|
-
*/
|
|
145
|
-
export function pressOnDragHandle(sel, target, hostEl, clientX, clientY) {
|
|
146
|
-
var _a, _b;
|
|
147
|
-
const grip = (_b = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, sel)) !== null && _b !== void 0 ? _b : null;
|
|
148
|
-
if (grip && (!hostEl || hostEl.contains(grip)))
|
|
149
|
-
return true;
|
|
150
|
-
if (sel !== '.axdb-widget-h' || !hostEl)
|
|
151
|
-
return false;
|
|
152
|
-
const hr = hostEl.getBoundingClientRect();
|
|
153
|
-
const header = hostEl.querySelector('.axdb-widget-h');
|
|
154
|
-
// A host painting its own content has no kit header: its caption is the top
|
|
155
|
-
// band of the card, so `dragHandle: true` still means something there.
|
|
156
|
-
const bottom = header ? header.getBoundingClientRect().bottom : hr.top + CAPTION_BAND;
|
|
157
|
-
return clientX >= hr.left && clientX <= hr.right && clientY >= hr.top && clientY <= bottom;
|
|
158
|
-
}
|
|
159
|
-
/** The caption strip of a host with no kit header, px from the card's top. */
|
|
160
|
-
export const CAPTION_BAND = 28;
|
|
161
104
|
/** A torn-out page never arrives shorter than this: a strip with no room under it is not a group. */
|
|
162
105
|
export const TEAR_OUT_MIN_ROWS = 2;
|
|
163
106
|
/**
|
|
@@ -204,58 +147,9 @@ export function registerBoardPeer(container, peer) {
|
|
|
204
147
|
s.delete(peer);
|
|
205
148
|
};
|
|
206
149
|
}
|
|
207
|
-
/**
|
|
208
|
-
* ONE aria-live region per canvas, shared by every board on it — the
|
|
209
|
-
* renderer's own controller (coalescing, de-duplicating), so a dashboard
|
|
210
|
-
* announces through the same channel a diagram does. WeakMap: the region
|
|
211
|
-
* follows the container out of memory.
|
|
212
|
-
*/
|
|
213
|
-
const LIVE_REGIONS = new WeakMap();
|
|
214
|
-
function liveRegionFor(container) {
|
|
215
|
-
let live = LIVE_REGIONS.get(container);
|
|
216
|
-
if (!live) {
|
|
217
|
-
live = new LiveRegionController(container);
|
|
218
|
-
LIVE_REGIONS.set(container, live);
|
|
219
|
-
}
|
|
220
|
-
return live;
|
|
221
|
-
}
|
|
222
|
-
function directionName(dx, dy) {
|
|
223
|
-
return dx < 0 ? 'left' : dx > 0 ? 'right' : dy < 0 ? 'up' : 'down';
|
|
224
|
-
}
|
|
225
|
-
/** "column 4, row 2, 3 by 1" — the cell as a person hears it (1-based). */
|
|
226
|
-
function describeCell(c) {
|
|
227
|
-
return `column ${c.x + 1}, row ${c.y + 1}, ${c.w} by ${c.h}`;
|
|
228
|
-
}
|
|
229
150
|
const DRAG_THRESHOLD = 4;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
export const EDGE_GRIP = 7;
|
|
233
|
-
const NO_EDGES = { n: false, e: false, s: false, w: false };
|
|
234
|
-
/** Which of a host's edges a client point is within EDGE_GRIP of (none when outside). */
|
|
235
|
-
function edgesNear(host, cx, cy) {
|
|
236
|
-
const r = host.getBoundingClientRect();
|
|
237
|
-
if (cx < r.left - 2 || cx > r.right + 2 || cy < r.top - 2 || cy > r.bottom + 2)
|
|
238
|
-
return NO_EDGES;
|
|
239
|
-
return {
|
|
240
|
-
n: cy - r.top <= EDGE_GRIP,
|
|
241
|
-
s: r.bottom - cy <= EDGE_GRIP,
|
|
242
|
-
w: cx - r.left <= EDGE_GRIP,
|
|
243
|
-
e: r.right - cx <= EDGE_GRIP,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
export const anyEdge = (E) => E.n || E.e || E.s || E.w;
|
|
247
|
-
/** The resize cursor for a set of edges ('' when none). */
|
|
248
|
-
function cursorFor(E) {
|
|
249
|
-
const v = E.n || E.s;
|
|
250
|
-
const h = E.e || E.w;
|
|
251
|
-
if (v && h)
|
|
252
|
-
return (E.n && E.w) || (E.s && E.e) ? 'nwse-resize' : 'nesw-resize';
|
|
253
|
-
if (v)
|
|
254
|
-
return 'ns-resize';
|
|
255
|
-
if (h)
|
|
256
|
-
return 'ew-resize';
|
|
257
|
-
return '';
|
|
258
|
-
}
|
|
151
|
+
/** The node a gesture holds — node-only paths (the pixel ghost, a resize, a strip drop, a removal) ask through this. */
|
|
152
|
+
const nodeOf = (g) => g.entity;
|
|
259
153
|
let binderSeq = 0;
|
|
260
154
|
export function bindDashboardGrid(api, group, options = {}) {
|
|
261
155
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
@@ -442,13 +336,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
442
336
|
let disposed = false;
|
|
443
337
|
/** Reentrancy guard: our own derived frame writes must not re-project. */
|
|
444
338
|
let writing = false;
|
|
445
|
-
let placeholder = null;
|
|
446
339
|
/** Foreign tile currently adopted from another binder's gesture. */
|
|
447
340
|
let adoptedGhostId = null;
|
|
448
341
|
/** The tab page this board is currently tearing out of a container, if any. */
|
|
449
342
|
let tearing = null;
|
|
450
|
-
let glideTimer = null;
|
|
451
|
-
let ghostTimer = null;
|
|
452
343
|
/**
|
|
453
344
|
* OUR OWN CAPTION RESERVE: a section carrying a caption gives the band's
|
|
454
345
|
* pixels up at the top of its frame, so no child may take them — and the
|
|
@@ -574,172 +465,57 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
574
465
|
grp.setMetadata('gridItem', gridItemFromCell(cell));
|
|
575
466
|
}
|
|
576
467
|
};
|
|
577
|
-
// --
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
605
|
-
};
|
|
606
|
-
/** Enforce the board-frame height the sizing mode implies. */
|
|
607
|
-
const enforceBoardHeight = () => {
|
|
608
|
-
if (designH <= 0)
|
|
609
|
-
return;
|
|
610
|
-
const r = rows();
|
|
611
|
-
// FIT keeps its design height, full stop — the user's rule: "the board
|
|
612
|
-
// stays the same and the widgets change size so all of them fit". Only
|
|
613
|
-
// overflow:'scroll' lets the frame EXTEND to hold the rows at the floor
|
|
614
|
-
// height (and the canvas pan). Grow extends at the base row height.
|
|
615
|
-
const target = sizing === 'fit'
|
|
616
|
-
? overflow === 'scroll'
|
|
617
|
-
? Math.max(designH, 2 * padding + r * minRowHeight + (r - 1) * gap)
|
|
618
|
-
: designH
|
|
619
|
-
: Math.max(designH, 2 * padding + r * baseRowHeight + (r - 1) * gap);
|
|
620
|
-
const f = frame();
|
|
621
|
-
if (Math.abs(f.height - target) > 0.5) {
|
|
468
|
+
// -- the board, as its modules see it (tile first, step 4b-i) --------------
|
|
469
|
+
const ctx = {
|
|
470
|
+
api,
|
|
471
|
+
group,
|
|
472
|
+
diagram,
|
|
473
|
+
options,
|
|
474
|
+
gap,
|
|
475
|
+
padding,
|
|
476
|
+
baseRowHeight,
|
|
477
|
+
minRowHeight,
|
|
478
|
+
overflow,
|
|
479
|
+
engine: () => engine,
|
|
480
|
+
frame,
|
|
481
|
+
geom,
|
|
482
|
+
rows,
|
|
483
|
+
sizing: () => sizing,
|
|
484
|
+
designH: () => designH,
|
|
485
|
+
rtl: () => rtl,
|
|
486
|
+
isStatic: () => isStatic,
|
|
487
|
+
disposed: () => disposed,
|
|
488
|
+
htmlLayer,
|
|
489
|
+
hostOf,
|
|
490
|
+
memberEntity,
|
|
491
|
+
sizeOf,
|
|
492
|
+
// A NODE ghost follows the hand by pixels and gets the placeholder; a GROUP ghost (a container adopted here) is projected to its cell like any tile, carried.
|
|
493
|
+
ghostId: () => (adoptedGhostId && !diagram.getGroup(adoptedGhostId) ? adoptedGhostId : (gesture === null || gesture === void 0 ? void 0 : gesture.started) && gesture.subject === 'node' ? gesture.id : null),
|
|
494
|
+
write: (fn) => {
|
|
622
495
|
writing = true;
|
|
623
496
|
try {
|
|
624
|
-
|
|
497
|
+
fn();
|
|
625
498
|
}
|
|
626
499
|
finally {
|
|
627
500
|
writing = false;
|
|
628
501
|
}
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
/** Project every member from its engine cells (the ghost is exempt). */
|
|
632
|
-
const project = () => {
|
|
633
|
-
enforceBoardHeight();
|
|
634
|
-
writing = true;
|
|
635
|
-
try {
|
|
636
|
-
const f = frame();
|
|
637
|
-
const g = geom();
|
|
638
|
-
const r = rows();
|
|
639
|
-
for (const item of engine.getItems()) {
|
|
640
|
-
if ((gesture === null || gesture === void 0 ? void 0 : gesture.started) && item.id === gesture.id)
|
|
641
|
-
continue; // the ghost
|
|
642
|
-
if (item.id === adoptedGhostId)
|
|
643
|
-
continue; // a ghost adopted from another binder
|
|
644
|
-
writeRect(item.id, cellToRect(item, f, g, r));
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
finally {
|
|
648
|
-
writing = false;
|
|
649
|
-
}
|
|
650
|
-
syncPlaceholder();
|
|
651
|
-
// The section overlays are projected chrome like the placeholder: they
|
|
652
|
-
// follow every frame write, not only a rebuild. Painted at bind time only,
|
|
653
|
-
// a section two levels down kept the geometry of its parent's placeholder
|
|
654
|
-
// frame (100 × 34) after the view board had laid the parent out (the kit
|
|
655
|
-
// lab's L47, 2026-09-08).
|
|
656
|
-
syncSlabs();
|
|
657
|
-
};
|
|
658
|
-
// -- placeholder / ghost chrome --------------------------------------------
|
|
659
|
-
/** The placeholder exists ONLY while a gesture is live — so at any moment
|
|
660
|
-
* the DOM holds at most one `.axdb-ph` per active gesture, not one idle
|
|
661
|
-
* div per bound board. */
|
|
662
|
-
const syncPlaceholder = () => {
|
|
663
|
-
const ghostId = adoptedGhostId !== null && adoptedGhostId !== void 0 ? adoptedGhostId : ((gesture === null || gesture === void 0 ? void 0 : gesture.started) && !gesture.removedFromBoard ? gesture.id : null);
|
|
664
|
-
const item = ghostId ? engine.getItem(ghostId) : undefined;
|
|
665
|
-
const live = !!item;
|
|
666
|
-
if (!live || !item) {
|
|
667
|
-
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
|
|
668
|
-
placeholder = null;
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
const layer = htmlLayer();
|
|
672
|
-
if (!layer)
|
|
673
|
-
return;
|
|
674
|
-
if (!placeholder || placeholder.parentElement !== layer) {
|
|
675
|
-
placeholder === null || placeholder === void 0 ? void 0 : placeholder.remove();
|
|
676
|
-
placeholder = document.createElement('div');
|
|
677
|
-
placeholder.className = 'axdb-ph';
|
|
678
|
-
layer.prepend(placeholder);
|
|
679
|
-
}
|
|
680
|
-
const r = cellToRect(item, frame(), geom(), rows());
|
|
681
|
-
placeholder.style.display = 'block';
|
|
682
|
-
placeholder.style.left = `${r.x}px`;
|
|
683
|
-
placeholder.style.top = `${r.y}px`;
|
|
684
|
-
placeholder.style.width = `${r.width}px`;
|
|
685
|
-
placeholder.style.height = `${r.height}px`;
|
|
686
|
-
};
|
|
687
|
-
const armGlide = () => {
|
|
688
|
-
var _a;
|
|
689
|
-
(_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.add('axdb-glide');
|
|
690
|
-
if (glideTimer)
|
|
691
|
-
clearTimeout(glideTimer);
|
|
692
|
-
};
|
|
693
|
-
const disarmGlideSoon = () => {
|
|
694
|
-
if (glideTimer)
|
|
695
|
-
clearTimeout(glideTimer);
|
|
696
|
-
glideTimer = setTimeout(() => { var _a; return (_a = htmlLayer()) === null || _a === void 0 ? void 0 : _a.classList.remove('axdb-glide'); }, GLIDE_OFF_DELAY);
|
|
697
|
-
};
|
|
698
|
-
/** The host whose ghost class the pending timer will lift. */
|
|
699
|
-
let ghostHost = null;
|
|
700
|
-
/**
|
|
701
|
-
* Lift a pending ghost NOW. One timer serves every host, so superseding it
|
|
702
|
-
* (a new gesture within 60 ms of the last drop — ③ then ④ in the
|
|
703
|
-
* nested-containers checks — or a dispose on a rebind) used to clear the
|
|
704
|
-
* timer and leave the previous tile lifted for good: a permanent drop
|
|
705
|
-
* shadow the visual gate finally caught.
|
|
706
|
-
*/
|
|
707
|
-
const flushGhost = () => {
|
|
708
|
-
if (ghostTimer)
|
|
709
|
-
clearTimeout(ghostTimer);
|
|
710
|
-
ghostTimer = null;
|
|
711
|
-
ghostHost === null || ghostHost === void 0 ? void 0 : ghostHost.classList.remove('axdb-ghost', 'axdb-out');
|
|
712
|
-
ghostHost = null;
|
|
713
|
-
};
|
|
714
|
-
const setGhost = (id, on) => {
|
|
715
|
-
const host = hostOf(id);
|
|
716
|
-
if (!host)
|
|
717
|
-
return;
|
|
718
|
-
if (ghostHost && ghostHost !== host)
|
|
719
|
-
flushGhost();
|
|
720
|
-
if (on) {
|
|
721
|
-
if (ghostTimer)
|
|
722
|
-
clearTimeout(ghostTimer);
|
|
723
|
-
ghostTimer = null;
|
|
724
|
-
host.classList.add('axdb-ghost');
|
|
725
|
-
host.classList.remove('axdb-out');
|
|
726
|
-
ghostHost = host;
|
|
727
|
-
}
|
|
728
|
-
else {
|
|
729
|
-
host.classList.remove('axdb-out');
|
|
730
|
-
// Keep transition-exemption through the drop write so the snap into the
|
|
731
|
-
// placeholder is INSTANT (gridstack-style), then let glides resume.
|
|
732
|
-
if (ghostTimer)
|
|
733
|
-
clearTimeout(ghostTimer);
|
|
734
|
-
ghostHost = host;
|
|
735
|
-
ghostTimer = setTimeout(() => {
|
|
736
|
-
host.classList.remove('axdb-ghost');
|
|
737
|
-
ghostTimer = null;
|
|
738
|
-
if (ghostHost === host)
|
|
739
|
-
ghostHost = null;
|
|
740
|
-
}, 60);
|
|
741
|
-
}
|
|
502
|
+
},
|
|
742
503
|
};
|
|
504
|
+
// -- projection: cells -> pixels (project.ts) ------------------------------
|
|
505
|
+
const projection = createProjection(ctx, { afterProject: () => syncSlabs() });
|
|
506
|
+
const { writeRect, enforceBoardHeight, project, syncPlaceholder, hidePlaceholder, armGlide, disarmGlideSoon, flushGhost, setGhost } = projection;
|
|
507
|
+
// -- chrome: slabs, frames, captions, handles, cursors (chrome.ts) ---------
|
|
508
|
+
const chrome = createChrome(ctx, {
|
|
509
|
+
selectedId: () => selectedId,
|
|
510
|
+
syncA11y: (only) => syncA11y(only),
|
|
511
|
+
grabbing: () => !!slabGesture,
|
|
512
|
+
gestureRunning: () => !!gesture,
|
|
513
|
+
memberGroupAt: (x, y) => memberGroupAt(x, y),
|
|
514
|
+
slabEdgesNear: (grp, x, y) => slabEdgesNear(grp, x, y),
|
|
515
|
+
dragHandle: () => dragHandle,
|
|
516
|
+
wantHandles,
|
|
517
|
+
});
|
|
518
|
+
const { syncSlabs, syncHandles, setCarried, flushCarried, showRefusal, ensureStaticGuard } = chrome;
|
|
743
519
|
// -- resize handles ---------------------------------------------------------
|
|
744
520
|
/** The accessible name of a widget: its title, else its kind, else its id. */
|
|
745
521
|
const nameOf = (node) => {
|
|
@@ -813,82 +589,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
813
589
|
host.classList.toggle('axdb-selected', id === selectedId);
|
|
814
590
|
}
|
|
815
591
|
};
|
|
816
|
-
/**
|
|
817
|
-
* The chrome on every member host: the painted grip (or none) and the corner
|
|
818
|
-
* resize handle — ONE host lookup per member, which the host observer's
|
|
819
|
-
* budget counts (a repaint of one host must cost that host's lookup, not a
|
|
820
|
-
* second pass).
|
|
821
|
-
*/
|
|
822
|
-
const syncHandles = (only) => {
|
|
823
|
-
var _a, _b, _c, _d, _e;
|
|
824
|
-
syncA11y(only);
|
|
825
|
-
if (disposed)
|
|
826
|
-
return;
|
|
827
|
-
ensureStaticGuard();
|
|
828
|
-
syncSlabs();
|
|
829
|
-
const grip = gripOf(dragHandle);
|
|
830
|
-
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
831
|
-
if (only && !only.has(id))
|
|
832
|
-
continue;
|
|
833
|
-
const node = diagram.getNode(id);
|
|
834
|
-
if (!node)
|
|
835
|
-
continue;
|
|
836
|
-
const host = hostOf(id);
|
|
837
|
-
if (!host)
|
|
838
|
-
continue;
|
|
839
|
-
syncGrip(host, grip, ((_b = node.state) === null || _b === void 0 ? void 0 : _b.locked) !== true && !isStatic && ((_c = node.getMetadata) === null || _c === void 0 ? void 0 : _c.call(node, 'widgetMovable')) !== false);
|
|
840
|
-
if (!wantHandles)
|
|
841
|
-
continue;
|
|
842
|
-
const existing = host.querySelector(':scope > .axdb-rs');
|
|
843
|
-
if (((_d = node.state) === null || _d === void 0 ? void 0 : _d.locked) === true || isStatic || ((_e = node.getMetadata) === null || _e === void 0 ? void 0 : _e.call(node, 'widgetResizable')) === false) {
|
|
844
|
-
existing === null || existing === void 0 ? void 0 : existing.remove();
|
|
845
|
-
continue;
|
|
846
|
-
}
|
|
847
|
-
const rs = existing !== null && existing !== void 0 ? existing : document.createElement('div');
|
|
848
|
-
if (!existing) {
|
|
849
|
-
rs.className = 'axdb-rs';
|
|
850
|
-
rs.setAttribute('title', 'Resize');
|
|
851
|
-
host.appendChild(rs);
|
|
852
|
-
}
|
|
853
|
-
// The grab corner mirrors with the board: bottom-right LTR, bottom-left
|
|
854
|
-
// RTL — the same corner the tile actually grows from in each direction.
|
|
855
|
-
rs.classList.toggle('axdb-rs--rtl', rtl);
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
/**
|
|
859
|
-
* Only a HOST-LEVEL change matters: a host arriving (a mount) or a host's
|
|
860
|
-
* own children changing (a repaint that wiped the injected handle). A
|
|
861
|
-
* chart's internal churn — most of what a live dashboard mutates — targets
|
|
862
|
-
* deeper nodes and is ignored, and the hosts the records DO name are the
|
|
863
|
-
* only ones re-synced. This was members × repaints `querySelector` calls
|
|
864
|
-
* per wave (9,216 at 96 widgets, review D10); it is now proportional to the
|
|
865
|
-
* hosts that actually changed.
|
|
866
|
-
*/
|
|
867
|
-
const hostObserver = new MutationObserver((records) => {
|
|
868
|
-
const touched = new Set();
|
|
869
|
-
const noteHost = (el) => {
|
|
870
|
-
var _a;
|
|
871
|
-
const e = el;
|
|
872
|
-
if ((_a = e === null || e === void 0 ? void 0 : e.classList) === null || _a === void 0 ? void 0 : _a.contains('grafloria-node-host')) {
|
|
873
|
-
const id = e.getAttribute('data-node-id');
|
|
874
|
-
if (id)
|
|
875
|
-
touched.add(id);
|
|
876
|
-
}
|
|
877
|
-
};
|
|
878
|
-
for (const r of records) {
|
|
879
|
-
// Our own re-injected handle arriving is not a change to answer — it
|
|
880
|
-
// would echo one more pass per repaint.
|
|
881
|
-
const ownEcho = r.removedNodes.length === 0 &&
|
|
882
|
-
r.addedNodes.length > 0 &&
|
|
883
|
-
Array.from(r.addedNodes).every((n) => { var _a; return (_a = n.classList) === null || _a === void 0 ? void 0 : _a.contains('axdb-rs'); });
|
|
884
|
-
if (ownEcho)
|
|
885
|
-
continue;
|
|
886
|
-
noteHost(r.target);
|
|
887
|
-
r.addedNodes.forEach((n) => noteHost(n));
|
|
888
|
-
}
|
|
889
|
-
if (touched.size)
|
|
890
|
-
syncHandles(touched);
|
|
891
|
-
});
|
|
892
592
|
// -- gesture snapshot / commit ---------------------------------------------
|
|
893
593
|
const snapshotAll = () => {
|
|
894
594
|
const cells = new Map();
|
|
@@ -1165,54 +865,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1165
865
|
const s = sizeOf(grp);
|
|
1166
866
|
return { n: y - p.y <= grip, s: p.y + s.height - y <= grip, w: x - p.x <= grip, e: p.x + s.width - x <= grip };
|
|
1167
867
|
};
|
|
1168
|
-
/**
|
|
1169
|
-
* SECTION CHROME. A section (member group) paints no card of its own, so it
|
|
1170
|
-
* had nothing to press: a click on its empty band cleared the selection and
|
|
1171
|
-
* its frame had no handle and no edge — a section with many children could
|
|
1172
|
-
* not be selected at all, and could only be resized by pulling a child
|
|
1173
|
-
* (Quantia, Groups page). Every section gets a pointer-transparent overlay
|
|
1174
|
-
* in the HTML layer that wears the selection ring and, while selected, the
|
|
1175
|
-
* corner handle; its frame edges answer the resize cursor.
|
|
1176
|
-
*/
|
|
1177
|
-
const slabEls = new Map();
|
|
1178
|
-
/**
|
|
1179
|
-
* GROUP FRAME (0.4.43): a TAB CONTAINER wears a frame by default — its slab
|
|
1180
|
-
* is bordered and a tinted surface lies under its pages, first in the layer
|
|
1181
|
-
* so the tiles paint over it. A page torn out with two widgets under its tab
|
|
1182
|
-
* read as a strip floating over two loose cards: nothing said the second
|
|
1183
|
-
* card was the tab's. A plain section keeps its invisible slab.
|
|
1184
|
-
*/
|
|
1185
|
-
const groupBgs = new Map();
|
|
1186
|
-
const syncGroupBg = (layer, id, on, x, y, w, h) => {
|
|
1187
|
-
var _a;
|
|
1188
|
-
let bg = (_a = groupBgs.get(id)) !== null && _a !== void 0 ? _a : null;
|
|
1189
|
-
if (!on) {
|
|
1190
|
-
bg === null || bg === void 0 ? void 0 : bg.remove();
|
|
1191
|
-
groupBgs.delete(id);
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
if (!bg || bg.parentElement !== layer) {
|
|
1195
|
-
bg === null || bg === void 0 ? void 0 : bg.remove();
|
|
1196
|
-
bg = document.createElement('div');
|
|
1197
|
-
bg.className = 'axdb-group-bg';
|
|
1198
|
-
bg.setAttribute('data-group-bg', id);
|
|
1199
|
-
layer.prepend(bg);
|
|
1200
|
-
groupBgs.set(id, bg);
|
|
1201
|
-
}
|
|
1202
|
-
bg.style.left = `${x}px`;
|
|
1203
|
-
bg.style.top = `${y}px`;
|
|
1204
|
-
bg.style.width = `${w}px`;
|
|
1205
|
-
bg.style.height = `${h}px`;
|
|
1206
|
-
};
|
|
1207
|
-
const isTabsGroup = (grp) => { var _a; return ((_a = grp.getMetadata('containerWidget')) === null || _a === void 0 ? void 0 : _a.layout) === 'tabs'; };
|
|
1208
|
-
/**
|
|
1209
|
-
* A TAB CONTAINER's frame is its drag handle (0.4.43): its 8-px margin —
|
|
1210
|
-
* under the strip, beside the pages — moves the group, so the edge-resize
|
|
1211
|
-
* zone shrinks to 3 px there (the corner handle still resizes). A section's
|
|
1212
|
-
* edges keep the full grip: its empty band is a drop target, not a handle.
|
|
1213
|
-
*/
|
|
1214
|
-
const TAB_FRAME_GRIP = 3;
|
|
1215
|
-
const edgeGripFor = (grp) => (isTabsGroup(grp) ? TAB_FRAME_GRIP : EDGE_GRIP);
|
|
1216
868
|
/**
|
|
1217
869
|
* BESIDE (0.4.45): a WIDGET dragged onto a tab container's outer band — a
|
|
1218
870
|
* fifth of its body, the same bands a tab's split uses — lands next to it
|
|
@@ -1298,6 +950,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1298
950
|
g.leg.adopted.abort();
|
|
1299
951
|
g.leg = null;
|
|
1300
952
|
}
|
|
953
|
+
if (g.subject === 'group' && !g.removedFromBoard && !beside) {
|
|
954
|
+
// A group ghost pushed its way here with intent: the containers it moved
|
|
955
|
+
// come home (the engine's memory) before the beside is measured on them.
|
|
956
|
+
engine.remove(g.id);
|
|
957
|
+
g.removedFromBoard = true;
|
|
958
|
+
}
|
|
1301
959
|
if (g.removedFromBoard) {
|
|
1302
960
|
g.removedFromBoard = false;
|
|
1303
961
|
setDim(g, false);
|
|
@@ -1305,180 +963,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1305
963
|
}
|
|
1306
964
|
besideOn(g.id, g.spans, z, row);
|
|
1307
965
|
};
|
|
1308
|
-
/**
|
|
1309
|
-
* CARRIED (0.4.43): a group dragged by its strip or band moves as ONE thing.
|
|
1310
|
-
* The held TILE is transition-exempt (the ghost), but a group has no host of
|
|
1311
|
-
* its own: its strip jumped to the pointer while its pages' tiles GLIDED
|
|
1312
|
-
* after it — on the live demo the content trailed the strip by up to 140 px
|
|
1313
|
-
* at every step. Everything in the group's subtree — tiles, strips, slabs,
|
|
1314
|
-
* the surface — is exempt for the gesture and, like the ghost, through the
|
|
1315
|
-
* drop write; then the glides resume.
|
|
1316
|
-
*/
|
|
1317
|
-
const carriedEls = new Set();
|
|
1318
|
-
let carriedTimer = null;
|
|
1319
|
-
const subtreeIds = (id) => {
|
|
1320
|
-
var _a;
|
|
1321
|
-
const groups = [];
|
|
1322
|
-
const nodes = [];
|
|
1323
|
-
const queue = [id];
|
|
1324
|
-
const seen = new Set();
|
|
1325
|
-
while (queue.length) {
|
|
1326
|
-
const cur = queue.shift();
|
|
1327
|
-
if (seen.has(cur))
|
|
1328
|
-
continue;
|
|
1329
|
-
seen.add(cur);
|
|
1330
|
-
const grp = diagram.getGroup(cur);
|
|
1331
|
-
if (grp) {
|
|
1332
|
-
groups.push(cur);
|
|
1333
|
-
for (const m of (_a = grp.members) !== null && _a !== void 0 ? _a : [])
|
|
1334
|
-
queue.push(m);
|
|
1335
|
-
}
|
|
1336
|
-
else if (diagram.getNode(cur))
|
|
1337
|
-
nodes.push(cur);
|
|
1338
|
-
}
|
|
1339
|
-
return { groups, nodes };
|
|
1340
|
-
};
|
|
1341
|
-
const cssId = (id) => (typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/"/g, '\\"'));
|
|
1342
|
-
const setCarried = (id, on) => {
|
|
1343
|
-
const layer = htmlLayer();
|
|
1344
|
-
if (!layer)
|
|
1345
|
-
return;
|
|
1346
|
-
if (carriedTimer) {
|
|
1347
|
-
clearTimeout(carriedTimer);
|
|
1348
|
-
carriedTimer = null;
|
|
1349
|
-
}
|
|
1350
|
-
if (on) {
|
|
1351
|
-
const { groups, nodes } = subtreeIds(id);
|
|
1352
|
-
const els = [];
|
|
1353
|
-
for (const n of nodes) {
|
|
1354
|
-
const h = hostOf(n);
|
|
1355
|
-
if (h)
|
|
1356
|
-
els.push(h);
|
|
1357
|
-
}
|
|
1358
|
-
for (const g of groups) {
|
|
1359
|
-
els.push(...Array.from(layer.querySelectorAll(`:scope > .axdb-tabs[data-tabs-id="${cssId(g)}"], :scope > .axdb-slab[data-slab-id="${cssId(g)}"], :scope > .axdb-group-bg[data-group-bg="${cssId(g)}"]`)));
|
|
1360
|
-
}
|
|
1361
|
-
for (const el of els) {
|
|
1362
|
-
el.classList.add('axdb-carried');
|
|
1363
|
-
carriedEls.add(el);
|
|
1364
|
-
}
|
|
1365
|
-
return;
|
|
1366
|
-
}
|
|
1367
|
-
carriedTimer = setTimeout(() => {
|
|
1368
|
-
for (const el of carriedEls)
|
|
1369
|
-
el.classList.remove('axdb-carried');
|
|
1370
|
-
carriedEls.clear();
|
|
1371
|
-
carriedTimer = null;
|
|
1372
|
-
}, 60);
|
|
1373
|
-
};
|
|
1374
|
-
const flushCarried = () => {
|
|
1375
|
-
if (carriedTimer)
|
|
1376
|
-
clearTimeout(carriedTimer);
|
|
1377
|
-
carriedTimer = null;
|
|
1378
|
-
for (const el of carriedEls)
|
|
1379
|
-
el.classList.remove('axdb-carried');
|
|
1380
|
-
carriedEls.clear();
|
|
1381
|
-
};
|
|
1382
|
-
let slabLayer = null;
|
|
1383
|
-
const syncSlabs = () => {
|
|
1384
|
-
var _a, _b, _c;
|
|
1385
|
-
if (disposed)
|
|
1386
|
-
return;
|
|
1387
|
-
const layer = (slabLayer === null || slabLayer === void 0 ? void 0 : slabLayer.isConnected) ? slabLayer : (slabLayer = htmlLayer());
|
|
1388
|
-
if (!layer)
|
|
1389
|
-
return;
|
|
1390
|
-
const seen = new Set();
|
|
1391
|
-
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
1392
|
-
const grp = diagram.getGroup(id);
|
|
1393
|
-
if (!grp || diagram.getNode(id))
|
|
1394
|
-
continue;
|
|
1395
|
-
seen.add(id);
|
|
1396
|
-
let el = slabEls.get(id);
|
|
1397
|
-
if (!el || el.parentElement !== layer) {
|
|
1398
|
-
el === null || el === void 0 ? void 0 : el.remove();
|
|
1399
|
-
el = document.createElement('div');
|
|
1400
|
-
el.className = 'axdb-slab';
|
|
1401
|
-
el.setAttribute('data-slab-id', id);
|
|
1402
|
-
const rs = document.createElement('div');
|
|
1403
|
-
rs.className = 'axdb-rs';
|
|
1404
|
-
rs.setAttribute('title', 'Resize section');
|
|
1405
|
-
el.appendChild(rs);
|
|
1406
|
-
layer.appendChild(el);
|
|
1407
|
-
slabEls.set(id, el);
|
|
1408
|
-
}
|
|
1409
|
-
const p = grp.position;
|
|
1410
|
-
const sz = sizeOf(grp);
|
|
1411
|
-
el.style.left = `${p.x}px`;
|
|
1412
|
-
el.style.top = `${p.y}px`;
|
|
1413
|
-
el.style.width = `${sz.width}px`;
|
|
1414
|
-
el.style.height = `${sz.height}px`;
|
|
1415
|
-
el.classList.toggle('axdb-slab--selected', selectedId === id);
|
|
1416
|
-
el.classList.toggle('axdb-slab--static', isStatic);
|
|
1417
|
-
(_b = el.querySelector(':scope > .axdb-rs')) === null || _b === void 0 ? void 0 : _b.classList.toggle('axdb-rs--rtl', rtl);
|
|
1418
|
-
const tabs = isTabsGroup(grp);
|
|
1419
|
-
el.classList.toggle('axdb-slab--tabs', tabs);
|
|
1420
|
-
syncGroupBg(layer, id, tabs, p.x, p.y, sz.width, sz.height);
|
|
1421
|
-
syncCaption(el, id, grp, sz.height);
|
|
1422
|
-
}
|
|
1423
|
-
for (const [id, el] of slabEls) {
|
|
1424
|
-
if (!seen.has(id)) {
|
|
1425
|
-
el.remove();
|
|
1426
|
-
hoverSlabs.delete(el);
|
|
1427
|
-
slabEls.delete(id);
|
|
1428
|
-
(_c = groupBgs.get(id)) === null || _c === void 0 ? void 0 : _c.remove();
|
|
1429
|
-
groupBgs.delete(id);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
};
|
|
1433
|
-
/**
|
|
1434
|
-
* THE CAPTION BAND of a section, on its slab overlay. Painted from the
|
|
1435
|
-
* group's persisted caption; repainted only when its identity changes (the
|
|
1436
|
-
* options, RTL, static, the tier) so a custom `renderCaption` is not run
|
|
1437
|
-
* per frame. The band takes the pointer (the slab itself does not): a press
|
|
1438
|
-
* on it selects the section, an action fires, pass-through reaches content.
|
|
1439
|
-
*/
|
|
1440
|
-
const syncCaption = (el, id, grp, sectionH) => {
|
|
1441
|
-
const cap = captionOfGroup(grp);
|
|
1442
|
-
let band = el.querySelector(':scope > .axdb-slab-h');
|
|
1443
|
-
if (!cap || !captionPainted(cap, isStatic)) {
|
|
1444
|
-
band === null || band === void 0 ? void 0 : band.remove();
|
|
1445
|
-
hoverSlabs.delete(el);
|
|
1446
|
-
el.classList.remove('axdb-slab--hot');
|
|
1447
|
-
el.removeAttribute('aria-label');
|
|
1448
|
-
el.removeAttribute('role');
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
if (cap.show === 'hover')
|
|
1452
|
-
hoverSlabs.add(el);
|
|
1453
|
-
else {
|
|
1454
|
-
hoverSlabs.delete(el);
|
|
1455
|
-
el.classList.remove('axdb-slab--hot');
|
|
1456
|
-
}
|
|
1457
|
-
const ctx = { rtl, static: isStatic, sectionH };
|
|
1458
|
-
const key = captionKey(cap, ctx);
|
|
1459
|
-
if (band && band.getAttribute('data-key') === key) {
|
|
1460
|
-
sizeCaptionBand(band, cap, sectionH); // the tier follows the live size
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
band === null || band === void 0 ? void 0 : band.remove();
|
|
1464
|
-
band = document.createElement('div');
|
|
1465
|
-
el.prepend(band);
|
|
1466
|
-
const render = options.renderCaption;
|
|
1467
|
-
paintCaptionBand(band, cap, Object.assign(Object.assign(Object.assign({}, ctx), (render ? { render: (host) => render(id, host) } : {})), { onAction: (actionId) => { var _a; return (_a = options.onCaptionAction) === null || _a === void 0 ? void 0 : _a.call(options, id, actionId); } }));
|
|
1468
|
-
band.setAttribute('data-key', key);
|
|
1469
|
-
// A named group, so the caption text is the section's name in the
|
|
1470
|
-
// accessibility tree rather than a stray label on an unnamed div. (The
|
|
1471
|
-
// band is not a tab stop yet — the actions are deliberately out of the
|
|
1472
|
-
// tab order so a board keeps exactly ONE stop; see the plan.)
|
|
1473
|
-
if (cap.text) {
|
|
1474
|
-
el.setAttribute('role', 'group');
|
|
1475
|
-
el.setAttribute('aria-label', cap.text);
|
|
1476
|
-
}
|
|
1477
|
-
else {
|
|
1478
|
-
el.removeAttribute('role');
|
|
1479
|
-
el.removeAttribute('aria-label');
|
|
1480
|
-
}
|
|
1481
|
-
};
|
|
1482
966
|
const insideMemberGroupFrame = (x, y) => {
|
|
1483
967
|
var _a;
|
|
1484
968
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : []) {
|
|
@@ -1493,8 +977,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1493
977
|
return false;
|
|
1494
978
|
};
|
|
1495
979
|
let slabGesture = null;
|
|
1496
|
-
/** The
|
|
1497
|
-
const
|
|
980
|
+
/** The gesture as it is NOW, for the same reason. */
|
|
981
|
+
const currentGesture = () => gesture;
|
|
1498
982
|
/** The PARENT running a resize of OUR section from a press this tool claimed. */
|
|
1499
983
|
let forwardSlab = null;
|
|
1500
984
|
const frameOfGroup = (grp) => ({ x: grp.position.x, y: grp.position.y, width: sizeOf(grp).width, height: sizeOf(grp).height });
|
|
@@ -1542,48 +1026,45 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1542
1026
|
const it = engine.getItem(id);
|
|
1543
1027
|
if (!grp || !it || gesture || slabGesture || isStatic)
|
|
1544
1028
|
return;
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1029
|
+
// A GROUP MOVE IS A GESTURE OF THE ONE MACHINE (tile first, 4b-ii): the
|
|
1030
|
+
// same threshold, snapshot, zone walk, legs, beside and commit a widget
|
|
1031
|
+
// gets — the group being the subject. The frame follows its cell.
|
|
1032
|
+
//
|
|
1033
|
+
// THE PRESS ARMS NOTHING. `beginGestureVisuals` arms the glide when the
|
|
1034
|
+
// threshold is crossed, as it does for a widget: arming it here left a
|
|
1035
|
+
// plain CLICK on a caption band gliding for 400 ms, and everything the
|
|
1036
|
+
// page did next — a caption change, a layout switch — animated instead of
|
|
1037
|
+
// landing (the gallery's fluid-board: "and the rows are back" measured a
|
|
1038
|
+
// tile still travelling).
|
|
1039
|
+
gesture = {
|
|
1040
|
+
kind: 'move',
|
|
1548
1041
|
id,
|
|
1549
|
-
|
|
1042
|
+
subject: 'group',
|
|
1043
|
+
entity: grp,
|
|
1550
1044
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent ? ev.source.pointerId : null,
|
|
1551
1045
|
started: false,
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1046
|
+
downClient: { x: ev.screen.x, y: ev.screen.y },
|
|
1047
|
+
downWorld: { x: ev.world.x, y: ev.world.y },
|
|
1048
|
+
grab: { dx: ev.world.x - grp.position.x, dy: ev.world.y - grp.position.y },
|
|
1049
|
+
startCells: new Map(),
|
|
1050
|
+
startGeom: new Map(),
|
|
1051
|
+
startSize: { width: sizeOf(grp).width, height: sizeOf(grp).height },
|
|
1052
|
+
startPos: { x: grp.position.x, y: grp.position.y },
|
|
1053
|
+
edges: NO_EDGES,
|
|
1054
|
+
spans: { w: it.w, h: it.h },
|
|
1055
|
+
removedFromBoard: false,
|
|
1056
|
+
leg: null,
|
|
1057
|
+
strip: null,
|
|
1058
|
+
lastWorld: null,
|
|
1059
|
+
lastScreen: null,
|
|
1060
|
+
hostEl: null,
|
|
1061
|
+
esc: null,
|
|
1062
|
+
chip: null,
|
|
1559
1063
|
};
|
|
1560
|
-
capturePointer(slabGesture.pointerId);
|
|
1561
|
-
api.container.style.cursor = 'grabbing';
|
|
1562
|
-
};
|
|
1563
|
-
/** The cell a slab move asked for and could not have — painted so the refusal is visible; null clears it. */
|
|
1564
|
-
let refusal = null;
|
|
1565
|
-
const showRefusal = (cell, w, h) => {
|
|
1566
|
-
const layer = htmlLayer();
|
|
1567
|
-
if (!cell || !layer) {
|
|
1568
|
-
refusal === null || refusal === void 0 ? void 0 : refusal.remove();
|
|
1569
|
-
refusal = null;
|
|
1570
|
-
api.container.style.cursor = slabGesture ? 'grabbing' : '';
|
|
1571
|
-
return;
|
|
1572
|
-
}
|
|
1573
|
-
if (!refusal || refusal.parentElement !== layer) {
|
|
1574
|
-
refusal === null || refusal === void 0 ? void 0 : refusal.remove();
|
|
1575
|
-
refusal = document.createElement('div');
|
|
1576
|
-
refusal.className = 'axdb-ph axdb-ph--no';
|
|
1577
|
-
layer.prepend(refusal);
|
|
1578
|
-
}
|
|
1579
|
-
const r = cellToRect({ x: cell.x, y: cell.y, w, h }, frame(), geom(), rows());
|
|
1580
|
-
refusal.style.left = `${r.x}px`;
|
|
1581
|
-
refusal.style.top = `${r.y}px`;
|
|
1582
|
-
refusal.style.width = `${r.width}px`;
|
|
1583
|
-
refusal.style.height = `${r.height}px`;
|
|
1584
|
-
api.container.style.cursor = 'not-allowed';
|
|
1585
1064
|
};
|
|
1586
1065
|
const slabMove = (ev) => {
|
|
1066
|
+
if ((gesture === null || gesture === void 0 ? void 0 : gesture.subject) === 'group')
|
|
1067
|
+
return onToolMove(ev);
|
|
1587
1068
|
const g = slabGesture;
|
|
1588
1069
|
if (!g)
|
|
1589
1070
|
return;
|
|
@@ -1668,6 +1149,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1668
1149
|
};
|
|
1669
1150
|
const slabUp = () => {
|
|
1670
1151
|
var _a;
|
|
1152
|
+
if ((gesture === null || gesture === void 0 ? void 0 : gesture.subject) === 'group')
|
|
1153
|
+
return onToolUp();
|
|
1671
1154
|
const g = slabGesture;
|
|
1672
1155
|
if (!g)
|
|
1673
1156
|
return;
|
|
@@ -1694,6 +1177,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1694
1177
|
};
|
|
1695
1178
|
const slabCancel = () => {
|
|
1696
1179
|
var _a;
|
|
1180
|
+
if ((gesture === null || gesture === void 0 ? void 0 : gesture.subject) === 'group')
|
|
1181
|
+
return cancelActiveGesture();
|
|
1697
1182
|
const g = slabGesture;
|
|
1698
1183
|
if (!g)
|
|
1699
1184
|
return;
|
|
@@ -1741,8 +1226,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1741
1226
|
g.started = true;
|
|
1742
1227
|
armGlide();
|
|
1743
1228
|
if (g.kind !== 'palette') {
|
|
1744
|
-
|
|
1745
|
-
|
|
1229
|
+
if (g.subject === 'node') {
|
|
1230
|
+
setGhost(g.id, true);
|
|
1231
|
+
g.hostEl = hostOf(g.id);
|
|
1232
|
+
}
|
|
1233
|
+
else
|
|
1234
|
+
setCarried(g.id, true); // a group moves as one thing: its whole subtree is exempt from the glide
|
|
1746
1235
|
capturePointer(g.pointerId);
|
|
1747
1236
|
api.container.style.cursor = g.kind === 'resize' ? 'nwse-resize' : 'grabbing';
|
|
1748
1237
|
}
|
|
@@ -1778,14 +1267,19 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1778
1267
|
};
|
|
1779
1268
|
const cleanupGestureVisuals = (g) => {
|
|
1780
1269
|
var _a;
|
|
1781
|
-
if (g.kind !== 'palette')
|
|
1782
|
-
|
|
1270
|
+
if (g.kind !== 'palette') {
|
|
1271
|
+
if (g.subject === 'node')
|
|
1272
|
+
setGhost(g.id, false);
|
|
1273
|
+
else {
|
|
1274
|
+
setCarried(g.id, false); // exempt through the drop write, then the glides resume
|
|
1275
|
+
showRefusal(null, 0, 0);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1783
1278
|
disarmGlideSoon();
|
|
1784
1279
|
releasePointer(g.pointerId);
|
|
1785
1280
|
api.container.style.cursor = '';
|
|
1786
1281
|
(_a = g.chip) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1787
|
-
|
|
1788
|
-
placeholder = null;
|
|
1282
|
+
hidePlaceholder();
|
|
1789
1283
|
};
|
|
1790
1284
|
const commitGesture = (g) => {
|
|
1791
1285
|
var _a;
|
|
@@ -1815,8 +1309,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1815
1309
|
persistLayouts(); // an edit at a narrow count propagated into the wide cache
|
|
1816
1310
|
if (changed) {
|
|
1817
1311
|
const it = engine.getItem(g.id);
|
|
1818
|
-
if (it)
|
|
1819
|
-
live.announce(`${nameOf(g
|
|
1312
|
+
if (it && g.subject === 'node')
|
|
1313
|
+
live.announce(`${nameOf(nodeOf(g))} ${g.kind === 'resize' ? 'resized' : 'moved'} to ${describeCell(it)}`);
|
|
1820
1314
|
}
|
|
1821
1315
|
syncA11y();
|
|
1822
1316
|
api.renderNow();
|
|
@@ -1916,14 +1410,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1916
1410
|
const moveGhost = (g, ev) => {
|
|
1917
1411
|
var _a;
|
|
1918
1412
|
const desired = { x: ev.world.x - g.grab.dx, y: ev.world.y - g.grab.dy };
|
|
1919
|
-
g.node
|
|
1920
|
-
|
|
1413
|
+
if (g.subject === 'node') {
|
|
1414
|
+
// The pixel ghost follows the hand; a group's frame is projected from its cell, carried.
|
|
1415
|
+
nodeOf(g).setPosition(desired.x, desired.y);
|
|
1416
|
+
ghostStyleFastPath(g, desired);
|
|
1417
|
+
}
|
|
1921
1418
|
g.lastWorld = { x: ev.world.x, y: ev.world.y };
|
|
1922
1419
|
g.lastScreen = { x: ev.screen.x, y: ev.screen.y };
|
|
1923
1420
|
/** The ghost's pixel size on THIS board — a palette chip has spans, not a size. */
|
|
1924
1421
|
const pxSize = () => {
|
|
1925
1422
|
if (g.kind !== 'palette')
|
|
1926
|
-
return
|
|
1423
|
+
return sizeOf(g.entity);
|
|
1927
1424
|
const f = frame();
|
|
1928
1425
|
const gg = geom();
|
|
1929
1426
|
return { width: g.spans.w * (columnUnitFor(gg, f.width) + gap) - gap, height: g.spans.h * (rowHeightFor(gg, rows()) + gap) - gap };
|
|
@@ -1952,7 +1449,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1952
1449
|
project();
|
|
1953
1450
|
}
|
|
1954
1451
|
if (!g.leg) {
|
|
1955
|
-
const adopted = peer.adopt(g.
|
|
1452
|
+
const adopted = peer.adopt(g.entity, ev.world, pxSize(), opts);
|
|
1956
1453
|
if (!adopted)
|
|
1957
1454
|
return false;
|
|
1958
1455
|
g.leg = { peer, adopted };
|
|
@@ -1965,7 +1462,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1965
1462
|
// BEFORE any engine is asked anything. The strip still wins over every
|
|
1966
1463
|
// board; a band's stickiness and the vacated cell still hold a beside.
|
|
1967
1464
|
const z = resolveTileZone(g, ev);
|
|
1968
|
-
if (z.kind === 'strip' && options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
1465
|
+
if (z.kind === 'strip' && options.tabDrop && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
1969
1466
|
// -- INTO A STRIP: the widget becomes a new tab there, so it leaves
|
|
1970
1467
|
// this board (survivors settle home) and the strip marks the slot.
|
|
1971
1468
|
endBeside(true); // a band's shift gives way to the strip: the container comes back
|
|
@@ -2063,7 +1560,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2063
1560
|
g.leg = null;
|
|
2064
1561
|
}
|
|
2065
1562
|
setDim(g, false);
|
|
2066
|
-
|
|
1563
|
+
// A group moved by hand takes the cell under the hand and PUSHES what
|
|
1564
|
+
// stands in its way (0.4.44) — and the zone walk reads the containers
|
|
1565
|
+
// it pushed at REST, so the hand still finds their zones where they were.
|
|
1566
|
+
placeOnSelf(g, desired, g.subject === 'group');
|
|
2067
1567
|
}
|
|
2068
1568
|
else {
|
|
2069
1569
|
// -- outside every board ------------------------------------------
|
|
@@ -2071,6 +1571,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2071
1571
|
setDim(g, true);
|
|
2072
1572
|
}
|
|
2073
1573
|
syncPlaceholder();
|
|
1574
|
+
// A carried group's slab and frame are this board's chrome, but its frame
|
|
1575
|
+
// is written by whichever board holds it now: re-sync so they follow.
|
|
1576
|
+
if (g.subject === 'group')
|
|
1577
|
+
syncSlabs();
|
|
2074
1578
|
};
|
|
2075
1579
|
const onToolMove = (ev) => {
|
|
2076
1580
|
var _a, _b, _c;
|
|
@@ -2355,8 +1859,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2355
1859
|
const anchorBottom = liveRect ? liveRect.y + liveRect.height : bottom;
|
|
2356
1860
|
const px = E.w ? anchorRight - w : anchorLeft;
|
|
2357
1861
|
const py = E.n ? anchorBottom - h : anchorTop;
|
|
2358
|
-
g.
|
|
2359
|
-
g.
|
|
1862
|
+
nodeOf(g).setSize(w, h, (_c = nodeOf(g).size.depth) !== null && _c !== void 0 ? _c : 0);
|
|
1863
|
+
nodeOf(g).setPosition(px, py);
|
|
2360
1864
|
ghostStyleFastPath(g, { x: px, y: py, width: w, height: h });
|
|
2361
1865
|
const spanF = bound() !== undefined ? frame() : f;
|
|
2362
1866
|
const spanG = bound() !== undefined ? geom() : gg;
|
|
@@ -2396,7 +1900,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2396
1900
|
syncPlaceholder();
|
|
2397
1901
|
};
|
|
2398
1902
|
const onToolUp = () => {
|
|
2399
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1903
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2400
1904
|
const g = gesture;
|
|
2401
1905
|
if (!g || g.kind === 'palette')
|
|
2402
1906
|
return;
|
|
@@ -2416,8 +1920,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2416
1920
|
const displaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2417
1921
|
const snap = g.startGeom.get(g.id);
|
|
2418
1922
|
if (snap) {
|
|
2419
|
-
g.
|
|
2420
|
-
g.
|
|
1923
|
+
nodeOf(g).setPosition(snap.pos.x, snap.pos.y);
|
|
1924
|
+
nodeOf(g).setSize(snap.size.width, snap.size.height, (_a = snap.size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
2421
1925
|
}
|
|
2422
1926
|
const cmds = options.tabDrop.dropIntoStrip(g.id, target.containerId, target.index, group.id, displaced);
|
|
2423
1927
|
if (cmds.length === 0) {
|
|
@@ -2447,8 +1951,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2447
1951
|
try {
|
|
2448
1952
|
diagram.runSystemWrite(() => {
|
|
2449
1953
|
var _a;
|
|
2450
|
-
g.node
|
|
2451
|
-
|
|
1954
|
+
if (g.subject === 'node') {
|
|
1955
|
+
nodeOf(g).setPosition(fin.rect.x, fin.rect.y);
|
|
1956
|
+
nodeOf(g).setSize(fin.rect.width, fin.rect.height, (_a = nodeOf(g).size.depth) !== null && _a !== void 0 ? _a : 0);
|
|
1957
|
+
}
|
|
1958
|
+
else
|
|
1959
|
+
g.entity.setFrame(Object.assign({}, fin.rect));
|
|
2452
1960
|
});
|
|
2453
1961
|
}
|
|
2454
1962
|
finally {
|
|
@@ -2457,6 +1965,27 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2457
1965
|
const sourceDisplaced = tileCommands(deltasSince(g.startCells, g.startGeom, g.id));
|
|
2458
1966
|
const before = g.startCells.get(g.id);
|
|
2459
1967
|
const geomBefore = g.startGeom.get(g.id);
|
|
1968
|
+
// A GROUP crossing boards: its cell-and-frame command, and the host's
|
|
1969
|
+
// bookkeeping for a container that changed boards (the spec entry, the
|
|
1970
|
+
// registry) rides along through `onMemberMoving` (tile first, 4b-ii).
|
|
1971
|
+
const own = g.subject === 'group'
|
|
1972
|
+
? [
|
|
1973
|
+
new SetGroupCellCommand(g.id, before !== null && before !== void 0 ? before : fin.cell, fin.cell, geomBefore ? { x: geomBefore.pos.x, y: geomBefore.pos.y, width: geomBefore.size.width, height: geomBefore.size.height } : fin.rect, fin.rect),
|
|
1974
|
+
...((_d = (_c = options.onMemberMoving) === null || _c === void 0 ? void 0 : _c.call(options, g.id, group.id, targetGroupId)) !== null && _d !== void 0 ? _d : []),
|
|
1975
|
+
]
|
|
1976
|
+
: buildCommitCommands([
|
|
1977
|
+
{
|
|
1978
|
+
id: g.id,
|
|
1979
|
+
locked: false,
|
|
1980
|
+
isGroup: false,
|
|
1981
|
+
cellBefore: before !== null && before !== void 0 ? before : fin.cell,
|
|
1982
|
+
cellAfter: fin.cell,
|
|
1983
|
+
posBefore: (_e = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _e !== void 0 ? _e : { x: fin.rect.x, y: fin.rect.y },
|
|
1984
|
+
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
1985
|
+
sizeBefore: (_f = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _f !== void 0 ? _f : { width: fin.rect.width, height: fin.rect.height },
|
|
1986
|
+
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
1987
|
+
},
|
|
1988
|
+
]);
|
|
2460
1989
|
const crossing = [
|
|
2461
1990
|
...sourceDisplaced,
|
|
2462
1991
|
// …and the TARGET board's own displaced tiles. Dropping onto an
|
|
@@ -2469,24 +1998,12 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2469
1998
|
...fin.commands,
|
|
2470
1999
|
new RemoveFromGroupCommand(group.id, g.id),
|
|
2471
2000
|
new AddToGroupCommand(targetGroupId, g.id),
|
|
2472
|
-
...
|
|
2473
|
-
{
|
|
2474
|
-
id: g.id,
|
|
2475
|
-
locked: false,
|
|
2476
|
-
isGroup: false,
|
|
2477
|
-
cellBefore: before !== null && before !== void 0 ? before : fin.cell,
|
|
2478
|
-
cellAfter: fin.cell,
|
|
2479
|
-
posBefore: (_c = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.pos) !== null && _c !== void 0 ? _c : { x: fin.rect.x, y: fin.rect.y },
|
|
2480
|
-
posAfter: { x: fin.rect.x, y: fin.rect.y },
|
|
2481
|
-
sizeBefore: (_d = geomBefore === null || geomBefore === void 0 ? void 0 : geomBefore.size) !== null && _d !== void 0 ? _d : { width: fin.rect.width, height: fin.rect.height },
|
|
2482
|
-
sizeAfter: { width: fin.rect.width, height: fin.rect.height },
|
|
2483
|
-
},
|
|
2484
|
-
]),
|
|
2001
|
+
...own,
|
|
2485
2002
|
];
|
|
2486
2003
|
// …and whatever follows a member out of this board: an emptied page
|
|
2487
2004
|
// closes — in which case the membership commands ride INSIDE one
|
|
2488
2005
|
// sequence with it, or the batch could never undo (its group is gone).
|
|
2489
|
-
const leaving = (
|
|
2006
|
+
const leaving = (_h = (_g = options.onMemberLeaving) === null || _g === void 0 ? void 0 : _g.call(options, g.id)) !== null && _h !== void 0 ? _h : [];
|
|
2490
2007
|
execute('Move widget', leaving.length > 0 ? [new SequenceCommand('Move widget', [...crossing, ...leaving])] : crossing);
|
|
2491
2008
|
engine.endGesture();
|
|
2492
2009
|
cleanupGestureVisuals(g);
|
|
@@ -2494,10 +2011,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2494
2011
|
enforceBoardHeight();
|
|
2495
2012
|
persistLayouts();
|
|
2496
2013
|
api.renderNow();
|
|
2497
|
-
(
|
|
2014
|
+
(_j = options.onGesture) === null || _j === void 0 ? void 0 : _j.call(options, { type: 'commit', kind: g.kind, nodeId: g.id, changed: true });
|
|
2498
2015
|
return;
|
|
2499
2016
|
}
|
|
2500
|
-
if (g.removedFromBoard && dragOut === 'cancel') {
|
|
2017
|
+
if (g.removedFromBoard && (dragOut === 'cancel' || g.subject === 'group')) {
|
|
2501
2018
|
// Released outside every board on a snap-home board: full restore,
|
|
2502
2019
|
// nothing committed (the parked-outside release).
|
|
2503
2020
|
cancelActiveGesture();
|
|
@@ -2524,16 +2041,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2524
2041
|
if (snap) {
|
|
2525
2042
|
// Park the node on its start rect so the page's RemoveNodeCommand
|
|
2526
2043
|
// captures sane geometry for undo.
|
|
2527
|
-
g.
|
|
2528
|
-
g.
|
|
2044
|
+
nodeOf(g).setPosition(snap.pos.x, snap.pos.y);
|
|
2045
|
+
nodeOf(g).setSize(snap.size.width, snap.size.height, (_k = snap.size.depth) !== null && _k !== void 0 ? _k : 0);
|
|
2529
2046
|
}
|
|
2530
2047
|
engine.endGesture();
|
|
2531
2048
|
cleanupGestureVisuals(g);
|
|
2532
2049
|
gesture = null;
|
|
2533
2050
|
enforceBoardHeight();
|
|
2534
2051
|
api.renderNow();
|
|
2535
|
-
void ((
|
|
2536
|
-
(
|
|
2052
|
+
void ((_l = options.onRemoveRequest) === null || _l === void 0 ? void 0 : _l.call(options, g.id, displaced));
|
|
2053
|
+
(_m = options.onGesture) === null || _m === void 0 ? void 0 : _m.call(options, { type: 'remove', kind: g.kind, nodeId: g.id, changed: true });
|
|
2537
2054
|
return;
|
|
2538
2055
|
}
|
|
2539
2056
|
commitGesture(g);
|
|
@@ -2611,6 +2128,27 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2611
2128
|
placeNear(g.id, cell.x, cell.y, g.spans.w, pushSolid);
|
|
2612
2129
|
project();
|
|
2613
2130
|
}
|
|
2131
|
+
else if (g.subject === 'group') {
|
|
2132
|
+
// The cell under the pointer, else the nearest legal one ALONG ITS ROW
|
|
2133
|
+
// (a section dragged left that wandered down its column read as the
|
|
2134
|
+
// wrong tile moving); when even that fails the wanted cell is painted
|
|
2135
|
+
// as refused (identification round, D6/D7).
|
|
2136
|
+
const it = engine.getItem(g.id);
|
|
2137
|
+
if (!it)
|
|
2138
|
+
return;
|
|
2139
|
+
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), it.w);
|
|
2140
|
+
if (cell.x === it.x && cell.y === it.y)
|
|
2141
|
+
return;
|
|
2142
|
+
const was = { x: it.x, y: it.y };
|
|
2143
|
+
const moved = engine.moveCheck(g.id, cell.x, cell.y, { gate: false, pushSolid: true }).changed || placeOnRow(g.id, cell.x, cell.y, it.w, true);
|
|
2144
|
+
if (moved) {
|
|
2145
|
+
project();
|
|
2146
|
+
setCarried(g.id, true); // chrome repainted by the projection is carried too
|
|
2147
|
+
}
|
|
2148
|
+
const now = engine.getItem(g.id);
|
|
2149
|
+
const stuck = !!now && now.x === was.x && now.y === was.y;
|
|
2150
|
+
showRefusal(stuck ? cell : null, it.w, it.h);
|
|
2151
|
+
}
|
|
2614
2152
|
else {
|
|
2615
2153
|
const spanW = (_b = (_a = engine.getItem(g.id)) === null || _a === void 0 ? void 0 : _a.w) !== null && _b !== void 0 ? _b : g.spans.w;
|
|
2616
2154
|
const cell = pointToCell(desired.x, desired.y, frame(), geom(), rows(), spanW);
|
|
@@ -2622,24 +2160,65 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2622
2160
|
const resolveTileZone = (g, ev) => {
|
|
2623
2161
|
var _a, _b;
|
|
2624
2162
|
let strip = null;
|
|
2625
|
-
if (options.tabDrop && !isStatic && g.kind !== 'palette') {
|
|
2163
|
+
if (options.tabDrop && !isStatic && g.kind !== 'palette' && g.subject === 'node') {
|
|
2164
|
+
// A group never becomes a tab: over a container's strip it is over the
|
|
2165
|
+
// container's margin — a cell on the parent board, pushing with intent.
|
|
2626
2166
|
const crect = api.container.getBoundingClientRect();
|
|
2627
2167
|
strip = options.tabDrop.stripAt(crect.left + ev.screen.x, crect.top + ev.screen.y);
|
|
2628
2168
|
}
|
|
2629
|
-
return resolveZone({
|
|
2630
|
-
x: ev.world.x,
|
|
2631
|
-
y: ev.world.y,
|
|
2632
|
-
roots: zoneRoots(),
|
|
2633
|
-
strip,
|
|
2634
|
-
prev: beside
|
|
2169
|
+
return resolveZone(Object.assign(Object.assign({ x: ev.world.x, y: ev.world.y, roots: zoneRoots(), strip, prev: beside
|
|
2635
2170
|
? { 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()) }
|
|
2636
|
-
: ((_b = (_a = g.leg) === null || _a === void 0 ? void 0 : _a.adopted.besideState()) !== null && _b !== void 0 ? _b : null),
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2171
|
+
: ((_b = (_a = g.leg) === null || _a === void 0 ? void 0 : _a.adopted.besideState()) !== null && _b !== void 0 ? _b : null), maxDepth: nesting, ghostDepth: g.subject === 'group' ? 1 + levelsInside(g.id) : 0 }, (g.subject === 'group' ? { restFrames: restFramesOf(g) } : {})), { ghostSubtree: g.subject === 'group' ? descendantGroups(g.id) : EMPTY_SUBTREE, gap, homeChain: homeChain() }));
|
|
2172
|
+
};
|
|
2173
|
+
/** This board's containers as they stood at the press: a group ghost's intent pushes never change what the hand means. */
|
|
2174
|
+
const restFramesOf = (g) => {
|
|
2175
|
+
const out = new Map();
|
|
2176
|
+
for (const [id, snap] of g.startGeom) {
|
|
2177
|
+
if (id === g.id || !isGroupMember(id))
|
|
2178
|
+
continue;
|
|
2179
|
+
out.set(id, { x: snap.pos.x, y: snap.pos.y, width: snap.size.width, height: snap.size.height });
|
|
2180
|
+
}
|
|
2181
|
+
return out;
|
|
2182
|
+
};
|
|
2183
|
+
/** Every group under `id`, itself included — the boards a group ghost may never enter. */
|
|
2184
|
+
const descendantGroups = (id) => {
|
|
2185
|
+
var _a;
|
|
2186
|
+
const out = new Set();
|
|
2187
|
+
const queue = [id];
|
|
2188
|
+
while (queue.length) {
|
|
2189
|
+
const cur = queue.shift();
|
|
2190
|
+
if (out.has(cur))
|
|
2191
|
+
continue;
|
|
2192
|
+
const grp = diagram.getGroup(cur);
|
|
2193
|
+
if (!grp)
|
|
2194
|
+
continue;
|
|
2195
|
+
out.add(cur);
|
|
2196
|
+
for (const m of (_a = grp.members) !== null && _a !== void 0 ? _a : [])
|
|
2197
|
+
if (diagram.getGroup(m))
|
|
2198
|
+
queue.push(m);
|
|
2199
|
+
}
|
|
2200
|
+
return out;
|
|
2201
|
+
};
|
|
2202
|
+
/**
|
|
2203
|
+
* The container LEVELS a group carries INSIDE it: 0 for a section of
|
|
2204
|
+
* widgets or a tab container of plain pages (a container and its page are
|
|
2205
|
+
* one level), 1 for a section holding a section, and so on. The group's own
|
|
2206
|
+
* level is the +1 above: its widgets sit one board deeper than the board it
|
|
2207
|
+
* lands on. The nesting policy counts both.
|
|
2208
|
+
*/
|
|
2209
|
+
const levelsInside = (id) => {
|
|
2210
|
+
var _a;
|
|
2211
|
+
const grp = diagram.getGroup(id);
|
|
2212
|
+
if (!grp)
|
|
2213
|
+
return 0;
|
|
2214
|
+
const tabs = isTabsGroup(grp);
|
|
2215
|
+
let deepest = 0;
|
|
2216
|
+
for (const m of (_a = grp.members) !== null && _a !== void 0 ? _a : []) {
|
|
2217
|
+
if (!diagram.getGroup(m))
|
|
2218
|
+
continue;
|
|
2219
|
+
deepest = Math.max(deepest, tabs ? levelsInside(m) : 1 + levelsInside(m));
|
|
2220
|
+
}
|
|
2221
|
+
return deepest;
|
|
2643
2222
|
};
|
|
2644
2223
|
/** The groups this board sits in, all the way up: their bands never apply to a tile of this board. */
|
|
2645
2224
|
const homeChain = () => {
|
|
@@ -3021,7 +2600,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3021
2600
|
if (isStatic)
|
|
3022
2601
|
return false;
|
|
3023
2602
|
beginSlabMove(id, ev);
|
|
3024
|
-
return (
|
|
2603
|
+
return (gesture === null || gesture === void 0 ? void 0 : gesture.id) === id;
|
|
3025
2604
|
},
|
|
3026
2605
|
dragMember: (id, ev) => {
|
|
3027
2606
|
var _a;
|
|
@@ -3034,7 +2613,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3034
2613
|
return { world, screen: { x: e.clientX - rect.left, y: e.clientY - rect.top }, source: e };
|
|
3035
2614
|
};
|
|
3036
2615
|
beginSlabMove(id, toTool(ev));
|
|
3037
|
-
if (((_a =
|
|
2616
|
+
if (((_a = currentGesture()) === null || _a === void 0 ? void 0 : _a.id) !== id)
|
|
3038
2617
|
return false;
|
|
3039
2618
|
const detachAll = () => {
|
|
3040
2619
|
window.removeEventListener('pointermove', onMove, true);
|
|
@@ -3044,7 +2623,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3044
2623
|
};
|
|
3045
2624
|
const onMove = (e) => {
|
|
3046
2625
|
var _a;
|
|
3047
|
-
if (disposed || ((_a =
|
|
2626
|
+
if (disposed || ((_a = currentGesture()) === null || _a === void 0 ? void 0 : _a.id) !== id)
|
|
3048
2627
|
return detachAll();
|
|
3049
2628
|
slabMove(toTool(e));
|
|
3050
2629
|
api.render();
|
|
@@ -3314,7 +2893,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3314
2893
|
gesture = {
|
|
3315
2894
|
kind: isResize ? 'resize' : 'move',
|
|
3316
2895
|
id: node.id,
|
|
3317
|
-
node,
|
|
2896
|
+
subject: 'node',
|
|
2897
|
+
entity: node,
|
|
3318
2898
|
pointerId: typeof PointerEvent !== 'undefined' && ev.source instanceof PointerEvent
|
|
3319
2899
|
? ev.source.pointerId
|
|
3320
2900
|
: null,
|
|
@@ -3390,253 +2970,24 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3390
2970
|
onToolUp();
|
|
3391
2971
|
};
|
|
3392
2972
|
const unregisterTool = registerTool(tool);
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
return;
|
|
3412
|
-
for (const el of hoverSlabs) {
|
|
3413
|
-
const r = el.getBoundingClientRect();
|
|
3414
|
-
el.classList.toggle('axdb-slab--hot', clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom);
|
|
3415
|
-
}
|
|
3416
|
-
};
|
|
3417
|
-
const onHoverLeave = () => {
|
|
3418
|
-
for (const el of hoverSlabs)
|
|
3419
|
-
el.classList.remove('axdb-slab--hot');
|
|
3420
|
-
};
|
|
3421
|
-
const onHover = (e) => {
|
|
3422
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3423
|
-
if (disposed || gesture)
|
|
3424
|
-
return;
|
|
3425
|
-
markHotSection(e.clientX, e.clientY);
|
|
3426
|
-
// The event may target the host, its content, or (when a host's content
|
|
3427
|
-
// is pointer-transparent) the canvas under it — find the member host by
|
|
3428
|
-
// the pointer's position in that case.
|
|
3429
|
-
let host = (_b = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest) === null || _b === void 0 ? void 0 : _b.call(_a, '.grafloria-node-host');
|
|
3430
|
-
if (!host) {
|
|
3431
|
-
for (const id of (_c = group.members) !== null && _c !== void 0 ? _c : []) {
|
|
3432
|
-
const h = hostOf(id);
|
|
3433
|
-
if (!h)
|
|
3434
|
-
continue;
|
|
3435
|
-
const r = h.getBoundingClientRect();
|
|
3436
|
-
if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
|
|
3437
|
-
host = h;
|
|
3438
|
-
break;
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
if (hoverHost && hoverHost !== host) {
|
|
3443
|
-
hoverHost.style.cursor = '';
|
|
3444
|
-
hoverHost.removeAttribute('data-axdb-edge'); // the affordance follows the pointer off a tile
|
|
3445
|
-
}
|
|
3446
|
-
hoverHost = host;
|
|
3447
|
-
if (!host) {
|
|
3448
|
-
// No tile under the pointer: a SECTION's frame edge still says resize.
|
|
3449
|
-
const wpt = ((_d = api.viewport) === null || _d === void 0 ? void 0 : _d.clientToWorld) ? api.viewport.clientToWorld(e.clientX, e.clientY, api.container.getBoundingClientRect()) : null;
|
|
3450
|
-
const sid = wpt ? memberGroupAt(wpt.x, wpt.y) : null;
|
|
3451
|
-
const grp = sid ? diagram.getGroup(sid) : undefined;
|
|
3452
|
-
const c = grp && wpt && !isStatic ? cursorFor(slabEdgesNear(grp, wpt.x, wpt.y)) : '';
|
|
3453
|
-
if (!slabGesture)
|
|
3454
|
-
api.container.style.cursor = c;
|
|
3455
|
-
return;
|
|
3456
|
-
}
|
|
3457
|
-
if (!slabGesture && api.container.style.cursor)
|
|
3458
|
-
api.container.style.cursor = '';
|
|
3459
|
-
const id = (_e = host.getAttribute('data-node-id')) !== null && _e !== void 0 ? _e : '';
|
|
3460
|
-
if (!((_f = group.members) !== null && _f !== void 0 ? _f : new Set()).has(id))
|
|
3461
|
-
return;
|
|
3462
|
-
const node = diagram.getNode(id);
|
|
3463
|
-
const resizable = !!node && !isStatic && ((_g = node.state) === null || _g === void 0 ? void 0 : _g.locked) !== true && ((_h = node.getMetadata) === null || _h === void 0 ? void 0 : _h.call(node, 'widgetResizable')) !== false;
|
|
3464
|
-
const cursor = resizable ? cursorFor(edgesNear(host, e.clientX, e.clientY)) : '';
|
|
3465
|
-
// The affordance rides on an ATTRIBUTE, not the inline style: a repaint
|
|
3466
|
-
// rewrites the host's style and used to clear the cursor mid-hover, and
|
|
3467
|
-
// content that sets its own cursor (a chart canvas) hid it — the
|
|
3468
|
-
// stylesheet applies the attribute's cursor to the host and everything in it.
|
|
3469
|
-
if (cursor)
|
|
3470
|
-
host.setAttribute('data-axdb-edge', cursor);
|
|
3471
|
-
else
|
|
3472
|
-
host.removeAttribute('data-axdb-edge');
|
|
3473
|
-
};
|
|
3474
|
-
api.container.addEventListener('pointermove', onHover, { passive: true });
|
|
3475
|
-
api.container.addEventListener('pointerleave', onHoverLeave, { passive: true });
|
|
3476
|
-
/**
|
|
3477
|
-
* STATIC BOARDS LET CONTENT BE CLICKED. The renderer prevents the default of
|
|
3478
|
-
* every press a tool claims, which cancels the compatibility mouse events —
|
|
3479
|
-
* a chart inside a read-only board could not be clicked. So under `static`
|
|
3480
|
-
* a press inside a member's CONTENT (not on kit chrome) is stopped on the
|
|
3481
|
-
* HTML layer, in the bubble phase: the content has already received it, the
|
|
3482
|
-
* renderer never does, nothing is prevented.
|
|
3483
|
-
*/
|
|
3484
|
-
const staticGuard = (e) => {
|
|
3485
|
-
var _a, _b, _c, _d;
|
|
3486
|
-
if (!isStatic || disposed)
|
|
3487
|
-
return;
|
|
3488
|
-
const t = e.target;
|
|
3489
|
-
const host = (_a = t === null || t === void 0 ? void 0 : t.closest) === null || _a === void 0 ? void 0 : _a.call(t, '.grafloria-node-host');
|
|
3490
|
-
if (!host || !((_b = group.members) !== null && _b !== void 0 ? _b : new Set()).has((_c = host.getAttribute('data-node-id')) !== null && _c !== void 0 ? _c : ''))
|
|
3491
|
-
return;
|
|
3492
|
-
if ((_d = t === null || t === void 0 ? void 0 : t.closest) === null || _d === void 0 ? void 0 : _d.call(t, '.axdb-rs, .axdb-grip, .axdb-div'))
|
|
3493
|
-
return;
|
|
3494
|
-
e.stopPropagation();
|
|
3495
|
-
};
|
|
3496
|
-
let guardedLayer = null;
|
|
3497
|
-
const ensureStaticGuard = () => {
|
|
3498
|
-
// One lookup, ever: the host observer budgets container lookups per
|
|
3499
|
-
// repaint, and the layer element lives as long as the instance.
|
|
3500
|
-
if (guardedLayer === null || guardedLayer === void 0 ? void 0 : guardedLayer.isConnected)
|
|
3501
|
-
return;
|
|
3502
|
-
const layer = htmlLayer();
|
|
3503
|
-
if (!layer)
|
|
3504
|
-
return;
|
|
3505
|
-
guardedLayer === null || guardedLayer === void 0 ? void 0 : guardedLayer.removeEventListener('pointerdown', staticGuard);
|
|
3506
|
-
guardedLayer = layer;
|
|
3507
|
-
layer.addEventListener('pointerdown', staticGuard);
|
|
3508
|
-
};
|
|
3509
|
-
/**
|
|
3510
|
-
* KEYBOARD OPERATION (WCAG 2.1.1, and the non-drag alternative 2.5.7 asks
|
|
3511
|
-
* for): on a focused member, arrows move it one cell, Shift+arrows resize it
|
|
3512
|
-
* one cell, Home/End jump to the first/last member. Every move and resize is
|
|
3513
|
-
* the same programmatic gesture the API uses — one undoable step, reported
|
|
3514
|
-
* through onLayoutChange — and every outcome is spoken: the tile's new
|
|
3515
|
-
* cell, each neighbour it displaced, or why it was refused. Handled keys
|
|
3516
|
-
* stop here so the renderer's own pixel nudge never fights the grid.
|
|
3517
|
-
*/
|
|
3518
|
-
const memberHostAt = (target) => {
|
|
3519
|
-
var _a, _b, _c;
|
|
3520
|
-
const host = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '.grafloria-node-host');
|
|
3521
|
-
if (!host)
|
|
3522
|
-
return null;
|
|
3523
|
-
const id = (_b = host.getAttribute('data-node-id')) !== null && _b !== void 0 ? _b : '';
|
|
3524
|
-
if (!((_c = group.members) !== null && _c !== void 0 ? _c : new Set()).has(id) || !diagram.getNode(id))
|
|
3525
|
-
return null;
|
|
3526
|
-
return { id, host };
|
|
3527
|
-
};
|
|
3528
|
-
const onFocusIn = (e) => {
|
|
3529
|
-
const hit = memberHostAt(e.target);
|
|
3530
|
-
if (!hit || disposed)
|
|
3531
|
-
return;
|
|
3532
|
-
if (focusedId !== hit.id || selectedId !== hit.id) {
|
|
3533
|
-
focusedId = hit.id;
|
|
3534
|
-
selectWidget(hit.id);
|
|
3535
|
-
syncA11y();
|
|
3536
|
-
}
|
|
3537
|
-
};
|
|
3538
|
-
const onKey = (e) => {
|
|
3539
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
3540
|
-
if (disposed || gesture)
|
|
3541
|
-
return;
|
|
3542
|
-
const hit = memberHostAt(e.target);
|
|
3543
|
-
if (!hit) {
|
|
3544
|
-
// Tab reaches the diagram's own root (the svg) before any widget. An
|
|
3545
|
-
// arrow or Enter there hands focus to the board's tab stop, so a
|
|
3546
|
-
// keyboard user is never parked on "Diagram, 8 nodes" with nowhere to go.
|
|
3547
|
-
const el = e.target;
|
|
3548
|
-
const onRoot = !!el && ((_a = el.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'svg' && ((_b = el.classList) === null || _b === void 0 ? void 0 : _b.contains('grafloria-diagram'));
|
|
3549
|
-
if (onRoot && (e.key.startsWith('Arrow') || e.key === 'Enter' || e.key === ' ')) {
|
|
3550
|
-
const members = [...((_c = group.members) !== null && _c !== void 0 ? _c : [])].filter((id) => !!diagram.getNode(id) && !!hostOf(id));
|
|
3551
|
-
const target = focusedId && members.includes(focusedId) ? focusedId : members[0];
|
|
3552
|
-
if (target && handle.focusWidget(target)) {
|
|
3553
|
-
e.preventDefault();
|
|
3554
|
-
e.stopPropagation();
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
return;
|
|
3558
|
-
}
|
|
3559
|
-
const members = [...((_d = group.members) !== null && _d !== void 0 ? _d : [])].filter((id) => !!diagram.getNode(id) && !!hostOf(id));
|
|
3560
|
-
if (e.key === 'Home' || e.key === 'End') {
|
|
3561
|
-
const id = e.key === 'Home' ? members[0] : members[members.length - 1];
|
|
3562
|
-
if (id)
|
|
3563
|
-
handle.focusWidget(id);
|
|
3564
|
-
e.preventDefault();
|
|
3565
|
-
e.stopPropagation();
|
|
3566
|
-
return;
|
|
3567
|
-
}
|
|
3568
|
-
const arrow = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1] }[e.key];
|
|
3569
|
-
if (!arrow)
|
|
3570
|
-
return;
|
|
3571
|
-
e.preventDefault();
|
|
3572
|
-
e.stopPropagation();
|
|
3573
|
-
if (isStatic)
|
|
3574
|
-
return; // readable, not editable
|
|
3575
|
-
const node = diagram.getNode(hit.id);
|
|
3576
|
-
const item = engine.getItem(hit.id);
|
|
3577
|
-
if (!node || !item)
|
|
3578
|
-
return;
|
|
3579
|
-
const name = nameOf(node);
|
|
3580
|
-
if (((_e = node.state) === null || _e === void 0 ? void 0 : _e.locked) === true) {
|
|
3581
|
-
live.announceError(`${name} is pinned`);
|
|
3582
|
-
return;
|
|
3583
|
-
}
|
|
3584
|
-
const [dx, dy] = arrow;
|
|
3585
|
-
const before = new Map(engine.getItems().map((i) => [i.id, { x: i.x, y: i.y, w: i.w, h: i.h }]));
|
|
3586
|
-
const resize = e.shiftKey;
|
|
3587
|
-
if (resize && ((_f = node.getMetadata) === null || _f === void 0 ? void 0 : _f.call(node, 'widgetResizable')) === false) {
|
|
3588
|
-
live.announceError(`${name} cannot be resized`);
|
|
3589
|
-
return;
|
|
3590
|
-
}
|
|
3591
|
-
if (!resize && ((_g = node.getMetadata) === null || _g === void 0 ? void 0 : _g.call(node, 'widgetMovable')) === false) {
|
|
3592
|
-
live.announceError(`${name} cannot be moved`);
|
|
3593
|
-
return;
|
|
3594
|
-
}
|
|
3595
|
-
// A one-cell keyboard step onto a neighbour would be refused by the
|
|
3596
|
-
// pointer's anti-jitter gate (a 3-wide covering a third of its neighbour
|
|
3597
|
-
// is not "more than half"). A key press is deliberate, so when the step
|
|
3598
|
-
// is refused and a neighbour sits there, aim at the neighbour's far edge —
|
|
3599
|
-
// the same swap a full drag lands on.
|
|
3600
|
-
const stepOrSwap = () => __awaiter(this, void 0, void 0, function* () {
|
|
3601
|
-
if (yield handle.moveTo(hit.id, item.x + dx, item.y + dy))
|
|
3602
|
-
return true;
|
|
3603
|
-
const probe = { x: item.x + dx, y: item.y + dy, w: item.w, h: item.h };
|
|
3604
|
-
const c = engine
|
|
3605
|
-
.getItems()
|
|
3606
|
-
.find((o) => o.id !== hit.id && probe.x < o.x + o.w && o.x < probe.x + probe.w && probe.y < o.y + o.h && o.y < probe.y + probe.h);
|
|
3607
|
-
if (!c)
|
|
3608
|
-
return false;
|
|
3609
|
-
const tx = dx > 0 ? c.x + c.w - item.w : dx < 0 ? c.x : item.x;
|
|
3610
|
-
const ty = dy > 0 ? c.y + c.h - item.h : dy < 0 ? c.y : item.y;
|
|
3611
|
-
return handle.moveTo(hit.id, tx, ty);
|
|
3612
|
-
});
|
|
3613
|
-
const op = resize ? handle.resizeTo(hit.id, item.w + dx, item.h + dy) : stepOrSwap();
|
|
3614
|
-
void op.then((ok) => {
|
|
3615
|
-
var _a, _b;
|
|
3616
|
-
if (disposed)
|
|
3617
|
-
return;
|
|
3618
|
-
const after = engine.getItem(hit.id);
|
|
3619
|
-
if (!ok || !after) {
|
|
3620
|
-
live.announceError(resize ? `Cannot resize ${name} that way` : `Cannot move ${name} ${directionName(dx, dy)}`);
|
|
3621
|
-
return;
|
|
3622
|
-
}
|
|
3623
|
-
const parts = [`${name} ${resize ? 'resized' : 'moved'} to ${describeCell(after)}`];
|
|
3624
|
-
for (const other of engine.getItems()) {
|
|
3625
|
-
if (other.id === hit.id)
|
|
3626
|
-
continue;
|
|
3627
|
-
const was = before.get(other.id);
|
|
3628
|
-
if (!was || (was.x === other.x && was.y === other.y))
|
|
3629
|
-
continue;
|
|
3630
|
-
const o = diagram.getNode(other.id);
|
|
3631
|
-
parts.push(`${o ? nameOf(o) : other.id} moved to ${describeCell(other)}`);
|
|
3632
|
-
}
|
|
3633
|
-
live.announce(parts.join('. '), 'polite', true);
|
|
3634
|
-
syncA11y();
|
|
3635
|
-
(_b = (_a = hostOf(hit.id)) === null || _a === void 0 ? void 0 : _a.focus) === null || _b === void 0 ? void 0 : _b.call(_a, { preventScroll: true });
|
|
3636
|
-
});
|
|
3637
|
-
};
|
|
3638
|
-
api.container.addEventListener('focusin', onFocusIn);
|
|
3639
|
-
api.container.addEventListener('keydown', onKey);
|
|
2973
|
+
api.container.addEventListener('pointermove', chrome.onHover, { passive: true });
|
|
2974
|
+
api.container.addEventListener('pointerleave', chrome.onHoverLeave, { passive: true });
|
|
2975
|
+
// -- keyboard operation (keyboard.ts) --------------------------------------
|
|
2976
|
+
const keyboard = createKeyboard(ctx, {
|
|
2977
|
+
handle: () => handle,
|
|
2978
|
+
live,
|
|
2979
|
+
nameOf,
|
|
2980
|
+
focusedId: () => focusedId,
|
|
2981
|
+
setFocusedId: (id) => {
|
|
2982
|
+
focusedId = id;
|
|
2983
|
+
},
|
|
2984
|
+
selectedId: () => selectedId,
|
|
2985
|
+
selectWidget,
|
|
2986
|
+
syncA11y: () => syncA11y(),
|
|
2987
|
+
gestureRunning: () => !!gesture,
|
|
2988
|
+
});
|
|
2989
|
+
api.container.addEventListener('focusin', keyboard.onFocusIn);
|
|
2990
|
+
api.container.addEventListener('keydown', keyboard.onKey);
|
|
3640
2991
|
// -- tab tear-out -----------------------------------------------------------
|
|
3641
2992
|
/**
|
|
3642
2993
|
* A press that TRAVELLED on a tab drags the whole page onto this board, the
|
|
@@ -4039,8 +3390,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4039
3390
|
engine.moveCheck(z.target.id, z.keep.x, z.keep.y, { gate: false });
|
|
4040
3391
|
const ok = l.place(z.born);
|
|
4041
3392
|
project();
|
|
4042
|
-
|
|
4043
|
-
placeholder = null;
|
|
3393
|
+
hidePlaceholder(); // the accent overlay says which half; the grey placeholder under it is noise
|
|
4044
3394
|
return ok;
|
|
4045
3395
|
};
|
|
4046
3396
|
/** The band a dock is promised: the cell it takes — a BOTTOM dock's lies past the last row, so its tint sits on the board's last rows instead. */
|
|
@@ -4056,8 +3406,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4056
3406
|
l.place(z.cell, true); // DOCKING PUSHES SECTIONS: everything below the band moves down, solid or not
|
|
4057
3407
|
insertRows(z.cell, l);
|
|
4058
3408
|
project();
|
|
4059
|
-
|
|
4060
|
-
placeholder = null;
|
|
3409
|
+
hidePlaceholder();
|
|
4061
3410
|
return true;
|
|
4062
3411
|
};
|
|
4063
3412
|
let zone = { kind: 'board' };
|
|
@@ -4255,7 +3604,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4255
3604
|
const g = {
|
|
4256
3605
|
kind: 'palette',
|
|
4257
3606
|
id: node.id,
|
|
4258
|
-
node,
|
|
3607
|
+
subject: 'node',
|
|
3608
|
+
entity: node,
|
|
4259
3609
|
pointerId: (_b = event.pointerId) !== null && _b !== void 0 ? _b : null,
|
|
4260
3610
|
started: false,
|
|
4261
3611
|
downClient: { x: event.clientX, y: event.clientY },
|
|
@@ -4667,19 +4017,17 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4667
4017
|
},
|
|
4668
4018
|
beginPaletteDrag,
|
|
4669
4019
|
dispose() {
|
|
4670
|
-
var _a, _b, _c
|
|
4020
|
+
var _a, _b, _c;
|
|
4671
4021
|
if (disposed)
|
|
4672
4022
|
return;
|
|
4673
4023
|
cancelActiveGesture(false);
|
|
4674
4024
|
disposed = true;
|
|
4675
4025
|
peersOnCanvas().delete(selfPeer);
|
|
4676
4026
|
unregisterTool();
|
|
4677
|
-
api.container.removeEventListener('pointermove', onHover);
|
|
4678
|
-
api.container.removeEventListener('pointerleave', onHoverLeave);
|
|
4679
|
-
|
|
4680
|
-
api.container.removeEventListener('
|
|
4681
|
-
api.container.removeEventListener('keydown', onKey);
|
|
4682
|
-
hostObserver.disconnect();
|
|
4027
|
+
api.container.removeEventListener('pointermove', chrome.onHover);
|
|
4028
|
+
api.container.removeEventListener('pointerleave', chrome.onHoverLeave);
|
|
4029
|
+
api.container.removeEventListener('focusin', keyboard.onFocusIn);
|
|
4030
|
+
api.container.removeEventListener('keydown', keyboard.onKey);
|
|
4683
4031
|
containerObserver === null || containerObserver === void 0 ? void 0 : containerObserver.disconnect();
|
|
4684
4032
|
tearing = null;
|
|
4685
4033
|
for (const off of subs)
|
|
@@ -4688,23 +4036,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4688
4036
|
// split layout must not keep showing a resize corner it cannot act on.
|
|
4689
4037
|
for (const id of (_a = group.members) !== null && _a !== void 0 ? _a : [])
|
|
4690
4038
|
(_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.querySelector(':scope > .axdb-rs')) === null || _c === void 0 ? void 0 : _c.remove();
|
|
4691
|
-
|
|
4692
|
-
placeholder
|
|
4693
|
-
if (glideTimer)
|
|
4694
|
-
clearTimeout(glideTimer);
|
|
4695
|
-
for (const el of slabEls.values())
|
|
4696
|
-
el.remove();
|
|
4697
|
-
slabEls.clear();
|
|
4698
|
-
hoverSlabs.clear();
|
|
4699
|
-
for (const bg of groupBgs.values())
|
|
4700
|
-
bg.remove();
|
|
4701
|
-
groupBgs.clear();
|
|
4702
|
-
// A rebind inside the 60 ms window (a layout switch right after an undo)
|
|
4703
|
-
// disposed this binder with the timer pending — the host kept its
|
|
4704
|
-
// lifted ghost for good (visual gate, nested-containers ⑤).
|
|
4705
|
-
flushGhost();
|
|
4706
|
-
flushCarried();
|
|
4707
|
-
(_d = htmlLayer()) === null || _d === void 0 ? void 0 : _d.classList.remove('axdb-glide');
|
|
4039
|
+
chrome.dispose(); // slabs, frames, the refused cell, the carried subtree, the host observer, the static guard
|
|
4040
|
+
projection.dispose(); // the placeholder, the glide timer, a pending ghost
|
|
4708
4041
|
api.container.style.cursor = '';
|
|
4709
4042
|
},
|
|
4710
4043
|
};
|
|
@@ -4716,7 +4049,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
4716
4049
|
rebuild(true);
|
|
4717
4050
|
const layer = htmlLayer();
|
|
4718
4051
|
if (layer)
|
|
4719
|
-
|
|
4052
|
+
chrome.observe(layer);
|
|
4720
4053
|
containerObserver === null || containerObserver === void 0 ? void 0 : containerObserver.observe(api.container);
|
|
4721
4054
|
return handle;
|
|
4722
4055
|
}
|