@grafloria/element 0.4.23 → 0.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -91,6 +91,37 @@ export declare function captionOfGroup(grp: {
|
|
|
91
91
|
name?: string;
|
|
92
92
|
} | undefined): SectionCaptionOptions | null;
|
|
93
93
|
export declare function pairOf(v: number | [number, number] | undefined, dflt: [number, number]): [number, number];
|
|
94
|
+
/** The shape of the model and groups this module needs — structural, so the kit's
|
|
95
|
+
* caption logic stays free of engine imports. */
|
|
96
|
+
interface CaptionGroup {
|
|
97
|
+
id: string;
|
|
98
|
+
name?: string;
|
|
99
|
+
members?: {
|
|
100
|
+
has(id: string): boolean;
|
|
101
|
+
} | null;
|
|
102
|
+
getMetadata(key: string): unknown;
|
|
103
|
+
}
|
|
104
|
+
interface CaptionDiagram {
|
|
105
|
+
getGroups?(): CaptionGroup[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Does the board that HOLDS this section paint section chrome? Only the grid
|
|
109
|
+
* binder draws the slab overlay the band lives on, so a section inside a
|
|
110
|
+
* SPLIT board has no band — and must not reserve space for one.
|
|
111
|
+
*
|
|
112
|
+
* Read from the parent group's persisted layout rather than from the live
|
|
113
|
+
* binder registry: membership and metadata both exist before either binder is
|
|
114
|
+
* built, so the answer is the same on the first projection as on the hundredth
|
|
115
|
+
* (asking the registry made the reserve depend on which binder registered
|
|
116
|
+
* first, and a child binds BEFORE its parent).
|
|
117
|
+
*/
|
|
118
|
+
export declare function parentPaintsSectionChrome(diagram: CaptionDiagram, group: CaptionGroup): boolean;
|
|
119
|
+
/** A section's own caption reserve: its band's pixels, or 0 when nothing paints one. */
|
|
120
|
+
export declare function sectionCaptionReserve(diagram: CaptionDiagram, group: CaptionGroup & {
|
|
121
|
+
size?: {
|
|
122
|
+
height: number;
|
|
123
|
+
} | null;
|
|
124
|
+
}, isStatic: boolean): number;
|
|
94
125
|
/** Painted at all? `show: 'design'` disappears under static. */
|
|
95
126
|
export declare function captionPainted(c: SectionCaptionOptions | null, isStatic: boolean): boolean;
|
|
96
127
|
/**
|
|
@@ -117,8 +148,12 @@ export declare function captionKey(c: SectionCaptionOptions, ctx: {
|
|
|
117
148
|
rtl: boolean;
|
|
118
149
|
static: boolean;
|
|
119
150
|
}): string;
|
|
120
|
-
/**
|
|
121
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Is the band too short to carry a subtitle, actions or full-size text? Asked
|
|
153
|
+
* of the band's ACTUAL height, so an authored `height` that survives on a
|
|
154
|
+
* short section keeps its content instead of being styled as squeezed.
|
|
155
|
+
*/
|
|
156
|
+
export declare const captionTight: (c: SectionCaptionOptions, sectionH: number) => boolean;
|
|
122
157
|
/** The per-sync geometry of a painted band: height and tier follow the section's live size. */
|
|
123
158
|
export declare function sizeCaptionBand(band: HTMLElement, c: SectionCaptionOptions, sectionH: number): void;
|
|
124
159
|
/**
|
|
@@ -142,3 +177,4 @@ export declare function paintCaptionBand(band: HTMLElement, c: SectionCaptionOpt
|
|
|
142
177
|
render?: (host: HTMLElement) => void;
|
|
143
178
|
onAction?: (actionId: string) => void;
|
|
144
179
|
}): void;
|
|
180
|
+
export {};
|
|
@@ -48,6 +48,35 @@ export function pairOf(v, dflt) {
|
|
|
48
48
|
return dflt;
|
|
49
49
|
return typeof v === 'number' ? [v, v] : [v[0], v[1]];
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Does the board that HOLDS this section paint section chrome? Only the grid
|
|
53
|
+
* binder draws the slab overlay the band lives on, so a section inside a
|
|
54
|
+
* SPLIT board has no band — and must not reserve space for one.
|
|
55
|
+
*
|
|
56
|
+
* Read from the parent group's persisted layout rather than from the live
|
|
57
|
+
* binder registry: membership and metadata both exist before either binder is
|
|
58
|
+
* built, so the answer is the same on the first projection as on the hundredth
|
|
59
|
+
* (asking the registry made the reserve depend on which binder registered
|
|
60
|
+
* first, and a child binds BEFORE its parent).
|
|
61
|
+
*/
|
|
62
|
+
export function parentPaintsSectionChrome(diagram, group) {
|
|
63
|
+
var _a, _b, _c, _d;
|
|
64
|
+
const groups = (_b = (_a = diagram.getGroups) === null || _a === void 0 ? void 0 : _a.call(diagram)) !== null && _b !== void 0 ? _b : [];
|
|
65
|
+
for (const g of groups) {
|
|
66
|
+
if (g === group || !((_c = g.members) === null || _c === void 0 ? void 0 : _c.has(group.id)))
|
|
67
|
+
continue;
|
|
68
|
+
const board = g.getMetadata('dashboardBoard');
|
|
69
|
+
return ((_d = board === null || board === void 0 ? void 0 : board.layout) !== null && _d !== void 0 ? _d : 'grid') !== 'split';
|
|
70
|
+
}
|
|
71
|
+
return false; // not a member of any board: a view, which carries no caption
|
|
72
|
+
}
|
|
73
|
+
/** A section's own caption reserve: its band's pixels, or 0 when nothing paints one. */
|
|
74
|
+
export function sectionCaptionReserve(diagram, group, isStatic) {
|
|
75
|
+
var _a, _b;
|
|
76
|
+
if (!parentPaintsSectionChrome(diagram, group))
|
|
77
|
+
return 0;
|
|
78
|
+
return captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_b = (_a = group.size) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : 0 });
|
|
79
|
+
}
|
|
51
80
|
/** Painted at all? `show: 'design'` disappears under static. */
|
|
52
81
|
export function captionPainted(c, isStatic) {
|
|
53
82
|
if (!c)
|
|
@@ -62,7 +91,10 @@ export function captionPainted(c, isStatic) {
|
|
|
62
91
|
*/
|
|
63
92
|
export function captionBandHeight(c, sectionH) {
|
|
64
93
|
var _a;
|
|
65
|
-
|
|
94
|
+
// An authored `height` WINS over the squeezed tier — the tier is a default
|
|
95
|
+
// for a short section, not an override of what the app asked for. The clamp
|
|
96
|
+
// still protects the children either way.
|
|
97
|
+
const want = (_a = c.height) !== null && _a !== void 0 ? _a : (sectionH > 0 && sectionH < CAPTION_TIGHT_BELOW ? CAPTION_HEIGHT_TIGHT : c.subtitle ? CAPTION_HEIGHT_SUBTITLE : CAPTION_HEIGHT);
|
|
66
98
|
if (sectionH <= 0)
|
|
67
99
|
return want;
|
|
68
100
|
return Math.max(CAPTION_MIN_HEIGHT, Math.min(want, sectionH - CAPTION_MIN_CONTENT));
|
|
@@ -87,8 +119,12 @@ export function captionReserve(c, ctx) {
|
|
|
87
119
|
export function captionKey(c, ctx) {
|
|
88
120
|
return JSON.stringify([c, ctx.rtl, ctx.static]);
|
|
89
121
|
}
|
|
90
|
-
/**
|
|
91
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Is the band too short to carry a subtitle, actions or full-size text? Asked
|
|
124
|
+
* of the band's ACTUAL height, so an authored `height` that survives on a
|
|
125
|
+
* short section keeps its content instead of being styled as squeezed.
|
|
126
|
+
*/
|
|
127
|
+
export const captionTight = (c, sectionH) => captionBandHeight(c, sectionH) <= CAPTION_HEIGHT_TIGHT;
|
|
92
128
|
/** Where the band's box sits horizontally: a tab hugs the LEADING edge, mirrored on RTL. */
|
|
93
129
|
function bandSides(c, mh, rtl) {
|
|
94
130
|
if (c.position !== 'tab')
|
|
@@ -97,7 +133,7 @@ function bandSides(c, mh, rtl) {
|
|
|
97
133
|
}
|
|
98
134
|
/** The per-sync geometry of a painted band: height and tier follow the section's live size. */
|
|
99
135
|
export function sizeCaptionBand(band, c, sectionH) {
|
|
100
|
-
band.classList.toggle('axdb-slab-h--tight', captionTight(sectionH));
|
|
136
|
+
band.classList.toggle('axdb-slab-h--tight', captionTight(c, sectionH));
|
|
101
137
|
band.style.height = `${captionBandHeight(c, sectionH)}px`;
|
|
102
138
|
}
|
|
103
139
|
/**
|
|
@@ -139,7 +175,7 @@ export function paintCaptionBand(band, c, ctx) {
|
|
|
139
175
|
const cls = (name, on) => band.classList.toggle(name, on);
|
|
140
176
|
cls('axdb-slab-h--tab', c.position === 'tab');
|
|
141
177
|
cls('axdb-slab-h--hover', c.show === 'hover');
|
|
142
|
-
cls('axdb-slab-h--tight', captionTight(ctx.sectionH));
|
|
178
|
+
cls('axdb-slab-h--tight', captionTight(c, ctx.sectionH));
|
|
143
179
|
cls('axdb-slab-h--center', c.align === 'center');
|
|
144
180
|
cls('axdb-slab-h--end', c.align === 'end');
|
|
145
181
|
cls('axdb-slab-h--vtop', c.valign === 'top');
|
|
@@ -436,14 +436,6 @@ export interface DashboardGridHandle {
|
|
|
436
436
|
* the feature).
|
|
437
437
|
*/
|
|
438
438
|
interface BinderPeer {
|
|
439
|
-
/**
|
|
440
|
-
* Does this board paint SECTION CHROME (the slab overlay that carries the
|
|
441
|
-
* ring, the corner handle and the caption band) for its member groups? Only
|
|
442
|
-
* the grid binder does. A section reserves its caption's pixels ONLY when
|
|
443
|
-
* its parent paints one — otherwise a captioned section inside a SPLIT
|
|
444
|
-
* board pushed its children down by 44 px under a band nobody drew.
|
|
445
|
-
*/
|
|
446
|
-
paintsCaptions?: boolean;
|
|
447
439
|
group: GroupModel;
|
|
448
440
|
/** True when this board's engine holds `id` as an item (member lookup). */
|
|
449
441
|
hasItem(id: string): boolean;
|
|
@@ -54,7 +54,7 @@ import { AddToGroupCommand, BatchCommand, Command, GridPackEngine, RemoveFromGro
|
|
|
54
54
|
import { LiveRegionController, 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, captionPainted, captionPassThrough, captionKey,
|
|
57
|
+
import { captionOfGroup, captionPainted, captionPassThrough, captionKey, paintCaptionBand, sectionCaptionReserve, sizeCaptionBand } from './caption.js';
|
|
58
58
|
/** The class of the painted grip element (a child of the node host). */
|
|
59
59
|
export const GRIP_CLASS = 'axdb-grip';
|
|
60
60
|
/** The container class that turns the caption strip's grip dots on. */
|
|
@@ -453,12 +453,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
453
453
|
* band is outside `containsWorld`, so a press on it is the PARENT's (it
|
|
454
454
|
* selects the section) rather than an empty press of this board.
|
|
455
455
|
*/
|
|
456
|
-
const ownReserve = () =>
|
|
457
|
-
var _a, _b, _c;
|
|
458
|
-
return ((_a = parentPeer()) === null || _a === void 0 ? void 0 : _a.paintsCaptions) === true
|
|
459
|
-
? captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_c = (_b = group.size) === null || _b === void 0 ? void 0 : _b.height) !== null && _c !== void 0 ? _c : 0 })
|
|
460
|
-
: 0;
|
|
461
|
-
};
|
|
456
|
+
const ownReserve = () => sectionCaptionReserve(diagram, group, isStatic);
|
|
462
457
|
const frame = () => {
|
|
463
458
|
var _a, _b, _c, _d;
|
|
464
459
|
const r = ownReserve();
|
|
@@ -1182,6 +1177,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1182
1177
|
for (const [id, el] of slabEls) {
|
|
1183
1178
|
if (!seen.has(id)) {
|
|
1184
1179
|
el.remove();
|
|
1180
|
+
hoverSlabs.delete(el);
|
|
1185
1181
|
slabEls.delete(id);
|
|
1186
1182
|
}
|
|
1187
1183
|
}
|
|
@@ -1198,9 +1194,18 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1198
1194
|
let band = el.querySelector(':scope > .axdb-slab-h');
|
|
1199
1195
|
if (!cap || !captionPainted(cap, isStatic)) {
|
|
1200
1196
|
band === null || band === void 0 ? void 0 : band.remove();
|
|
1197
|
+
hoverSlabs.delete(el);
|
|
1198
|
+
el.classList.remove('axdb-slab--hot');
|
|
1201
1199
|
el.removeAttribute('aria-label');
|
|
1200
|
+
el.removeAttribute('role');
|
|
1202
1201
|
return;
|
|
1203
1202
|
}
|
|
1203
|
+
if (cap.show === 'hover')
|
|
1204
|
+
hoverSlabs.add(el);
|
|
1205
|
+
else {
|
|
1206
|
+
hoverSlabs.delete(el);
|
|
1207
|
+
el.classList.remove('axdb-slab--hot');
|
|
1208
|
+
}
|
|
1204
1209
|
const ctx = { rtl, static: isStatic, sectionH };
|
|
1205
1210
|
const key = captionKey(cap, ctx);
|
|
1206
1211
|
if (band && band.getAttribute('data-key') === key) {
|
|
@@ -1213,10 +1218,18 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1213
1218
|
const render = options.renderCaption;
|
|
1214
1219
|
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); } }));
|
|
1215
1220
|
band.setAttribute('data-key', key);
|
|
1216
|
-
|
|
1221
|
+
// A named group, so the caption text is the section's name in the
|
|
1222
|
+
// accessibility tree rather than a stray label on an unnamed div. (The
|
|
1223
|
+
// band is not a tab stop yet — the actions are deliberately out of the
|
|
1224
|
+
// tab order so a board keeps exactly ONE stop; see the plan.)
|
|
1225
|
+
if (cap.text) {
|
|
1226
|
+
el.setAttribute('role', 'group');
|
|
1217
1227
|
el.setAttribute('aria-label', cap.text);
|
|
1218
|
-
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
el.removeAttribute('role');
|
|
1219
1231
|
el.removeAttribute('aria-label');
|
|
1232
|
+
}
|
|
1220
1233
|
};
|
|
1221
1234
|
const insideMemberGroupFrame = (x, y) => {
|
|
1222
1235
|
var _a;
|
|
@@ -2191,7 +2204,6 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2191
2204
|
containsWorldExtended: worldInsideBoardExtended,
|
|
2192
2205
|
frameArea: boardArea,
|
|
2193
2206
|
adopt,
|
|
2194
|
-
paintsCaptions: true, // the grid binder owns the slab overlays (syncSlabs)
|
|
2195
2207
|
};
|
|
2196
2208
|
peersOnCanvas().add(selfPeer);
|
|
2197
2209
|
selfPeerRef = selfPeer;
|
|
@@ -2425,20 +2437,19 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2425
2437
|
* in CSS terms. The binder already tracks the pointer; it marks the section
|
|
2426
2438
|
* under it instead.
|
|
2427
2439
|
*/
|
|
2440
|
+
/** Only the sections carrying a `show: 'hover'` band — usually none, so the
|
|
2441
|
+
* pointer handler costs nothing on a board that has no hover caption. */
|
|
2442
|
+
const hoverSlabs = new Set();
|
|
2428
2443
|
const markHotSection = (clientX, clientY) => {
|
|
2429
|
-
if (!
|
|
2444
|
+
if (!hoverSlabs.size)
|
|
2430
2445
|
return;
|
|
2431
|
-
for (const
|
|
2432
|
-
if (!el.querySelector(':scope > .axdb-slab-h--hover'))
|
|
2433
|
-
continue;
|
|
2446
|
+
for (const el of hoverSlabs) {
|
|
2434
2447
|
const r = el.getBoundingClientRect();
|
|
2435
|
-
|
|
2436
|
-
el.classList.toggle('axdb-slab--hot', hot);
|
|
2437
|
-
void id;
|
|
2448
|
+
el.classList.toggle('axdb-slab--hot', clientX >= r.left && clientX <= r.right && clientY >= r.top && clientY <= r.bottom);
|
|
2438
2449
|
}
|
|
2439
2450
|
};
|
|
2440
2451
|
const onHoverLeave = () => {
|
|
2441
|
-
for (const el of
|
|
2452
|
+
for (const el of hoverSlabs)
|
|
2442
2453
|
el.classList.remove('axdb-slab--hot');
|
|
2443
2454
|
};
|
|
2444
2455
|
const onHover = (e) => {
|
|
@@ -3091,6 +3102,7 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
3091
3102
|
for (const el of slabEls.values())
|
|
3092
3103
|
el.remove();
|
|
3093
3104
|
slabEls.clear();
|
|
3105
|
+
hoverSlabs.clear();
|
|
3094
3106
|
// A rebind inside the 60 ms window (a layout switch right after an undo)
|
|
3095
3107
|
// disposed this binder with the timer pending — the host kept its
|
|
3096
3108
|
// lifted ghost for good (visual gate, nested-containers ⑤).
|
|
@@ -29,7 +29,7 @@ import { anyEdge, clearOtherSelections, dragHandleSelector, gripHostOf, gripOf,
|
|
|
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';
|
|
32
|
-
import {
|
|
32
|
+
import { sectionCaptionReserve } from './caption.js';
|
|
33
33
|
/** Group metadata key the tree persists under. */
|
|
34
34
|
export const SPLIT_TREE_KEY = 'dashboardTree';
|
|
35
35
|
/**
|
|
@@ -112,12 +112,7 @@ export function bindDashboardSplit(api, group, options = {}) {
|
|
|
112
112
|
* reserves nothing until the tab-container round gives the split binder its
|
|
113
113
|
* own overlays.
|
|
114
114
|
*/
|
|
115
|
-
const ownReserve = () =>
|
|
116
|
-
var _a, _b, _c;
|
|
117
|
-
return ((_a = parentPeerOf(api.container, group.id)) === null || _a === void 0 ? void 0 : _a.paintsCaptions) === true
|
|
118
|
-
? captionReserve(captionOfGroup(group), { static: isStatic, sectionH: (_c = (_b = group.size) === null || _b === void 0 ? void 0 : _b.height) !== null && _c !== void 0 ? _c : 0 })
|
|
119
|
-
: 0;
|
|
120
|
-
};
|
|
115
|
+
const ownReserve = () => sectionCaptionReserve(diagram, group, isStatic);
|
|
121
116
|
const frame = () => {
|
|
122
117
|
var _a, _b, _c, _d;
|
|
123
118
|
const r = ownReserve();
|