@grafloria/element 0.4.12 → 0.4.13
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/dashboard.d.ts +8 -0
- package/src/lib/dashboard-kit/dashboard.js +25 -2
- package/src/lib/dashboard-kit/grid-binder.d.ts +26 -0
- package/src/lib/dashboard-kit/grid-binder.js +34 -1
- package/src/lib/dashboard-kit/split-binder.js +13 -1
- package/src/lib/dashboard-kit/styles.js +8 -0
package/package.json
CHANGED
|
@@ -282,6 +282,14 @@ export interface DashboardHandle {
|
|
|
282
282
|
* and a quiet ring; a void click clears. False when the id is unknown.
|
|
283
283
|
*/
|
|
284
284
|
focusWidget(id: string): boolean;
|
|
285
|
+
/**
|
|
286
|
+
* SELECT a widget WITHOUT moving keyboard focus — the ring and the grip,
|
|
287
|
+
* nothing else; what a mouse press does. `undefined` clears the selection
|
|
288
|
+
* on every view. False when the id is unknown.
|
|
289
|
+
*/
|
|
290
|
+
selectWidget(id: string | undefined): boolean;
|
|
291
|
+
/** The selected widget, if any (across views: only the on-camera one can be). */
|
|
292
|
+
getSelectedWidget(): string | undefined;
|
|
285
293
|
/** Every widget handle of a view (default: the active one). */
|
|
286
294
|
widgetsOf(viewId?: string): WidgetHandle[];
|
|
287
295
|
/**
|
|
@@ -520,6 +520,24 @@ export function createDashboardHandle(ctx) {
|
|
|
520
520
|
queueMicrotask(() => retry(4));
|
|
521
521
|
return true;
|
|
522
522
|
},
|
|
523
|
+
selectWidget(id) {
|
|
524
|
+
var _a;
|
|
525
|
+
if (id === undefined) {
|
|
526
|
+
for (const b of binders.values())
|
|
527
|
+
b.selectWidget(undefined);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
const b = binders.get((_a = viewOfWidget.get(id)) !== null && _a !== void 0 ? _a : '');
|
|
531
|
+
return !!b && b.selectWidget(id);
|
|
532
|
+
},
|
|
533
|
+
getSelectedWidget() {
|
|
534
|
+
for (const b of binders.values()) {
|
|
535
|
+
const s = b.getSelectedWidget();
|
|
536
|
+
if (s)
|
|
537
|
+
return s;
|
|
538
|
+
}
|
|
539
|
+
return undefined;
|
|
540
|
+
},
|
|
523
541
|
widgetsOf(viewId) {
|
|
524
542
|
var _a;
|
|
525
543
|
const v = views.find((x) => x.id === (viewId !== null && viewId !== void 0 ? viewId : ctx.active));
|
|
@@ -1043,7 +1061,7 @@ export function dashboard(options) {
|
|
|
1043
1061
|
// its tree from exactly those, so any stale tree is cleared first;
|
|
1044
1062
|
// split → grid rebuilds from them, so the column cache goes too.
|
|
1045
1063
|
ctx.rebindView = (viewId, next) => {
|
|
1046
|
-
var _a, _b;
|
|
1064
|
+
var _a, _b, _c;
|
|
1047
1065
|
const v = views.find((x) => x.id === viewId);
|
|
1048
1066
|
const g = groups.get(viewId);
|
|
1049
1067
|
const b = binders.get(viewId);
|
|
@@ -1052,6 +1070,7 @@ export function dashboard(options) {
|
|
|
1052
1070
|
const cells = b.saveLayout().cells;
|
|
1053
1071
|
const live = { rtl: b.getRtl(), static: b.getStatic(), dragHandle: b.getDragHandle() };
|
|
1054
1072
|
const focused = b.getFocusedWidget();
|
|
1073
|
+
const selected = b.getSelectedWidget();
|
|
1055
1074
|
b.dispose();
|
|
1056
1075
|
const write = (fn) => (model.runSystemWrite ? model.runSystemWrite(fn) : fn());
|
|
1057
1076
|
write(() => {
|
|
@@ -1072,9 +1091,13 @@ export function dashboard(options) {
|
|
|
1072
1091
|
binders.set(viewId, bindView(v, g, next, live));
|
|
1073
1092
|
(_a = binders.get(viewId)) === null || _a === void 0 ? void 0 : _a.sync();
|
|
1074
1093
|
// The selected widget (and its grip) survives the switch, as it does in
|
|
1075
|
-
// the DevExpress designer — the host used to have to restate it.
|
|
1094
|
+
// the DevExpress designer — the host used to have to restate it. A
|
|
1095
|
+
// MOUSE selection is not a focus (the press never focuses the host),
|
|
1096
|
+
// so it is carried on its own: the kit lab's L21 lost it (2026-09-08).
|
|
1076
1097
|
if (focused)
|
|
1077
1098
|
(_b = binders.get(viewId)) === null || _b === void 0 ? void 0 : _b.focusWidget(focused);
|
|
1099
|
+
else if (selected)
|
|
1100
|
+
(_c = binders.get(viewId)) === null || _c === void 0 ? void 0 : _c.selectWidget(selected);
|
|
1078
1101
|
};
|
|
1079
1102
|
handle.showView(ctx.active);
|
|
1080
1103
|
ctx.rebindContainer = (id) => {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
* second `bindDashboardGrid` on the section for a nested pack grid.
|
|
51
51
|
*/
|
|
52
52
|
import { Command, type DiagramModel, type GridColumnLayout, type GroupModel, type NodeModel } from '@grafloria/engine';
|
|
53
|
+
import { type ToolPointerEvent } from '@grafloria/renderer';
|
|
53
54
|
import { type CellRect, type WorldRect } from './grid-mapping.js';
|
|
54
55
|
/** The slice of a DiagramInstance the binder needs (structural, test-friendly). */
|
|
55
56
|
export interface DashboardGridApi {
|
|
@@ -261,6 +262,23 @@ export declare const gripOf: (v: DragHandleOption) => DragGripOptions | null;
|
|
|
261
262
|
*/
|
|
262
263
|
export declare function syncGrip(host: HTMLElement, cfg: DragGripOptions | null, movable: boolean): void;
|
|
263
264
|
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
265
|
+
/**
|
|
266
|
+
* Is this press OURS? The renderer's tool registry is PAGE-GLOBAL: every
|
|
267
|
+
* registered tool is asked about every press on every canvas, ties going to
|
|
268
|
+
* the first registered. Two boards on one page sharing widget ids — a designer
|
|
269
|
+
* beside its preview, a page of examples — had the FIRST board's tool claim
|
|
270
|
+
* the second's presses: it selected its own tile and moved nothing (kit lab,
|
|
271
|
+
* 2026-09-08: 16 of 23 boards dead to the mouse). A tool claims only a press
|
|
272
|
+
* whose DOM target sits in its own container and whose hit node is its own
|
|
273
|
+
* diagram's object, not a namesake from another model.
|
|
274
|
+
*/
|
|
275
|
+
export declare function ownsPress(container: HTMLElement, diagram: {
|
|
276
|
+
getNode(id: string): unknown;
|
|
277
|
+
}, ev: ToolPointerEvent, hit: {
|
|
278
|
+
node?: {
|
|
279
|
+
id: string;
|
|
280
|
+
};
|
|
281
|
+
}): boolean;
|
|
264
282
|
export declare function gripHostOf(target: Element | null): HTMLElement | null;
|
|
265
283
|
/**
|
|
266
284
|
* Did a press land on the drag handle? A press INSIDE the handle element
|
|
@@ -313,6 +331,14 @@ export interface DashboardGridHandle {
|
|
|
313
331
|
focusWidget(id: string): boolean;
|
|
314
332
|
/** The member the roving tabindex currently rests on. */
|
|
315
333
|
getFocusedWidget(): string | undefined;
|
|
334
|
+
/**
|
|
335
|
+
* SELECT a member without moving keyboard focus — what a mouse press does
|
|
336
|
+
* (the renderer cancels the press's default, so a click never focuses the
|
|
337
|
+
* host). `undefined` clears. False for a non-member.
|
|
338
|
+
*/
|
|
339
|
+
selectWidget(id: string | undefined): boolean;
|
|
340
|
+
/** The selected member: the one wearing the ring and, in grip mode, the grip. */
|
|
341
|
+
getSelectedWidget(): string | undefined;
|
|
316
342
|
/**
|
|
317
343
|
* The layout to PERSIST — from the engine's LARGEST cached column count, so
|
|
318
344
|
* saving while the board is narrow still saves the wide layout the user
|
|
@@ -92,6 +92,25 @@ export function syncGrip(host, cfg, movable) {
|
|
|
92
92
|
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'}`);
|
|
93
93
|
}
|
|
94
94
|
/** The member host a press on a painted grip belongs to (the grip may sit OUTSIDE the host's box). */
|
|
95
|
+
/**
|
|
96
|
+
* Is this press OURS? The renderer's tool registry is PAGE-GLOBAL: every
|
|
97
|
+
* registered tool is asked about every press on every canvas, ties going to
|
|
98
|
+
* the first registered. Two boards on one page sharing widget ids — a designer
|
|
99
|
+
* beside its preview, a page of examples — had the FIRST board's tool claim
|
|
100
|
+
* the second's presses: it selected its own tile and moved nothing (kit lab,
|
|
101
|
+
* 2026-09-08: 16 of 23 boards dead to the mouse). A tool claims only a press
|
|
102
|
+
* whose DOM target sits in its own container and whose hit node is its own
|
|
103
|
+
* diagram's object, not a namesake from another model.
|
|
104
|
+
*/
|
|
105
|
+
export function ownsPress(container, diagram, ev, hit) {
|
|
106
|
+
var _a;
|
|
107
|
+
const t = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target;
|
|
108
|
+
if (typeof Node !== 'undefined' && t instanceof Node && !container.contains(t))
|
|
109
|
+
return false;
|
|
110
|
+
if (hit.node && diagram.getNode(hit.node.id) !== hit.node)
|
|
111
|
+
return false;
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
95
114
|
export function gripHostOf(target) {
|
|
96
115
|
var _a, _b;
|
|
97
116
|
const grip = (_a = target === null || target === void 0 ? void 0 : target.closest) === null || _a === void 0 ? void 0 : _a.call(target, '.' + GRIP_CLASS);
|
|
@@ -1673,6 +1692,8 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1673
1692
|
return false;
|
|
1674
1693
|
if (gesture)
|
|
1675
1694
|
return true; // own the rest of an in-flight gesture
|
|
1695
|
+
if (!ownsPress(api.container, diagram, ev, hit))
|
|
1696
|
+
return false;
|
|
1676
1697
|
if (hit.node) {
|
|
1677
1698
|
if (((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(hit.node.id))
|
|
1678
1699
|
return true;
|
|
@@ -1825,8 +1846,10 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1825
1846
|
}
|
|
1826
1847
|
}
|
|
1827
1848
|
}
|
|
1828
|
-
if (hoverHost && hoverHost !== host)
|
|
1849
|
+
if (hoverHost && hoverHost !== host) {
|
|
1829
1850
|
hoverHost.style.cursor = '';
|
|
1851
|
+
hoverHost.removeAttribute('data-axdb-edge'); // the affordance follows the pointer off a tile
|
|
1852
|
+
}
|
|
1830
1853
|
hoverHost = host;
|
|
1831
1854
|
if (!host)
|
|
1832
1855
|
return;
|
|
@@ -2262,6 +2285,16 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2262
2285
|
return true;
|
|
2263
2286
|
},
|
|
2264
2287
|
getFocusedWidget: () => focusedId,
|
|
2288
|
+
selectWidget(id) {
|
|
2289
|
+
var _a;
|
|
2290
|
+
if (disposed)
|
|
2291
|
+
return false;
|
|
2292
|
+
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id)))
|
|
2293
|
+
return false;
|
|
2294
|
+
selectWidget(id);
|
|
2295
|
+
return true;
|
|
2296
|
+
},
|
|
2297
|
+
getSelectedWidget: () => selectedId,
|
|
2265
2298
|
setStatic(on) {
|
|
2266
2299
|
if (on === isStatic)
|
|
2267
2300
|
return;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { __awaiter } from "tslib";
|
|
26
26
|
import { Command } from '@grafloria/engine';
|
|
27
27
|
import { LiveRegionController, registerTool } from '@grafloria/renderer';
|
|
28
|
-
import { dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, pressOnDragHandle, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
|
|
28
|
+
import { dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, pressOnDragHandle, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
|
|
29
29
|
import { cellFromGridItem } from './grid-mapping.js';
|
|
30
30
|
import { addSplitLeaf, cellsFromSplit, cloneSplit, dividersOf, groupRectsOf, insertSplitLeaf, moveSplitDivider, normalizeSplit, pathToLeaf, projectSplit, removeSplitLeaf, splitFromCells, splitLeaves, } from './split-layout.js';
|
|
31
31
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
@@ -631,6 +631,8 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
631
631
|
return false;
|
|
632
632
|
if (gesture)
|
|
633
633
|
return true;
|
|
634
|
+
if (!ownsPress(api.container, diagram, ev, hit))
|
|
635
|
+
return false;
|
|
634
636
|
if (hit.node)
|
|
635
637
|
return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(hit.node.id);
|
|
636
638
|
return worldInsideBoard(ev.world.x, ev.world.y);
|
|
@@ -1025,6 +1027,16 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
1025
1027
|
return true;
|
|
1026
1028
|
},
|
|
1027
1029
|
getFocusedWidget: () => focusedId,
|
|
1030
|
+
selectWidget(id) {
|
|
1031
|
+
var _a;
|
|
1032
|
+
if (disposed)
|
|
1033
|
+
return false;
|
|
1034
|
+
if (id !== undefined && (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id)))
|
|
1035
|
+
return false;
|
|
1036
|
+
selectWidget(id);
|
|
1037
|
+
return true;
|
|
1038
|
+
},
|
|
1039
|
+
getSelectedWidget: () => selectedId,
|
|
1028
1040
|
saveLayout() {
|
|
1029
1041
|
return { columns, cells: cellsFromSplit(readTree(), columns, rowsGuess()) };
|
|
1030
1042
|
},
|
|
@@ -244,6 +244,11 @@ const CSS = `
|
|
|
244
244
|
.grafloria-node-host.axdb-gp-inside.axdb-gp-right .axdb-widget > .axdb-widget-h { padding-right: 32px; }
|
|
245
245
|
.grafloria-node-host.axdb-gp-inside.axdb-gp-center .axdb-widget > .axdb-widget-h { padding-top: 12px; }
|
|
246
246
|
.axdb-widget-b { flex: 1; min-height: 0; position: relative; }
|
|
247
|
+
/* A drag across a STATIC board (nothing prevents the press's default there)
|
|
248
|
+
used to select every label on it; kit cards are not prose. Tables stay
|
|
249
|
+
copyable — a figure in a grid is the one thing a viewer selects. */
|
|
250
|
+
.axdb-widget { user-select: none; -webkit-user-select: none; }
|
|
251
|
+
.axdb-widget .axdb-table { user-select: text; -webkit-user-select: text; }
|
|
247
252
|
.axdb-widget-b > svg { display: block; width: 100%; height: 100%; }
|
|
248
253
|
.axdb-widget-b.axdb-scroll { overflow: auto; }
|
|
249
254
|
/* A chart WITH a legend under it: the plot yields height, the legend keeps its
|
|
@@ -332,6 +337,9 @@ const CSS = `
|
|
|
332
337
|
tile gets a bigger ring, not dead card), square, capped so its centre figure
|
|
333
338
|
stays a figure and not a headline. */
|
|
334
339
|
.axdb-widget-b.axdb-donut { display: flex; align-items: center; gap: 10px; }
|
|
340
|
+
/* A legend taller than a SHORT body (a 2-row donut on a squeezed board) is
|
|
341
|
+
clipped at its own foot, not centred over the title above it. */
|
|
342
|
+
.axdb-widget-b.axdb-donut > .axdb-lg--col { max-height: 100%; min-height: 0; overflow: hidden; }
|
|
335
343
|
.axdb-widget-b.axdb-donut > svg {
|
|
336
344
|
flex: 0 0 auto; width: auto; height: 100%; max-height: 260px; max-width: 60%; aspect-ratio: 1 / 1;
|
|
337
345
|
}
|