@grafloria/element 0.4.17 → 0.4.18

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafloria/element",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -429,6 +429,13 @@ interface BinderPeer {
429
429
  group: GroupModel;
430
430
  /** True when this board's engine holds `id` as an item (member lookup). */
431
431
  hasItem(id: string): boolean;
432
+ /**
433
+ * Drop this board's selection: a press that selects a tile on ONE board of
434
+ * a canvas clears the others, so a section and its parent never show two
435
+ * rings at once (Quantia Groups page: three tiles selected across three
436
+ * boards after three clicks).
437
+ */
438
+ clearSelection?(): void;
432
439
  /** The member's current cell in this board (undefined when absent). */
433
440
  memberCell(id: string): CellRect | undefined;
434
441
  /**
@@ -490,6 +497,8 @@ interface AdoptedLeg {
490
497
  * unregister. A split board adopts nothing and grows no slab: its `adopt`
491
498
  * answers null and `resizeMemberBy` answers unchanged.
492
499
  */
500
+ /** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
501
+ export declare function clearOtherSelections(container: HTMLElement, self: BinderPeer | null): void;
493
502
  export declare function registerBoardPeer(container: HTMLElement, peer: BinderPeer): () => void;
494
503
  export type { BinderPeer };
495
504
  export declare function bindDashboardGrid(api: DashboardGridApi, group: GroupModel, options?: DashboardGridOptions): DashboardGridHandle;
@@ -153,6 +153,13 @@ const BOARD_REGISTRY = new WeakMap();
153
153
  * unregister. A split board adopts nothing and grows no slab: its `adopt`
154
154
  * answers null and `resizeMemberBy` answers unchanged.
155
155
  */
156
+ /** Clear the selection on every OTHER board of the canvas — one selection per canvas. */
157
+ export function clearOtherSelections(container, self) {
158
+ var _a, _b;
159
+ for (const p of (_a = BOARD_REGISTRY.get(container)) !== null && _a !== void 0 ? _a : [])
160
+ if (p !== self)
161
+ (_b = p.clearSelection) === null || _b === void 0 ? void 0 : _b.call(p);
162
+ }
156
163
  export function registerBoardPeer(container, peer) {
157
164
  let set = BOARD_REGISTRY.get(container);
158
165
  if (!set) {
@@ -701,11 +708,15 @@ export function bindDashboardGrid(api, group, options = {}) {
701
708
  */
702
709
  let selectedId;
703
710
  const selectWidget = (id) => {
711
+ if (id !== undefined)
712
+ clearOtherSelections(api.container, selfPeerRef);
704
713
  if (id === selectedId)
705
714
  return;
706
715
  selectedId = id;
707
716
  syncA11y();
708
717
  };
718
+ /** Set once the peer object exists (below); `selectWidget` runs before that only on boot. */
719
+ let selfPeerRef = null;
709
720
  const syncA11y = (only) => {
710
721
  var _a, _b;
711
722
  if (disposed)
@@ -1790,6 +1801,12 @@ export function bindDashboardGrid(api, group, options = {}) {
1790
1801
  };
1791
1802
  const selfPeer = {
1792
1803
  group,
1804
+ clearSelection: () => {
1805
+ if (selectedId === undefined)
1806
+ return;
1807
+ selectedId = undefined;
1808
+ syncA11y();
1809
+ },
1793
1810
  hasItem: (id) => !!engine.getItem(id),
1794
1811
  memberCell: (id) => {
1795
1812
  const it = engine.getItem(id);
@@ -1826,6 +1843,9 @@ export function bindDashboardGrid(api, group, options = {}) {
1826
1843
  adopt,
1827
1844
  };
1828
1845
  peersOnCanvas().add(selfPeer);
1846
+ selfPeerRef = selfPeer;
1847
+ // The board's gap, for chrome that must fit BETWEEN tiles (the outside grip tab).
1848
+ api.container.style.setProperty('--axdb-gap', `${gap}px`);
1829
1849
  const tool = {
1830
1850
  id: `dashboard-grid:${group.id}:${++binderSeq}`,
1831
1851
  priority: 2, // point-specific claim — outranks mode-style tools (see ext/tools.ts)
@@ -2070,7 +2090,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2070
2090
  return;
2071
2091
  if (focusedId !== hit.id || selectedId !== hit.id) {
2072
2092
  focusedId = hit.id;
2073
- selectedId = hit.id;
2093
+ selectWidget(hit.id);
2074
2094
  syncA11y();
2075
2095
  }
2076
2096
  };
@@ -2422,7 +2442,7 @@ export function bindDashboardGrid(api, group, options = {}) {
2422
2442
  if (disposed || !((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
2423
2443
  return false;
2424
2444
  focusedId = id;
2425
- selectedId = id;
2445
+ selectWidget(id);
2426
2446
  syncA11y();
2427
2447
  (_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b, { preventScroll: true });
2428
2448
  return true;
@@ -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, ownsPress, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS } from './grid-binder.js';
28
+ import { clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, pressOnDragHandle, registerBoardPeer, 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';
@@ -372,11 +372,14 @@ export function bindDashboardSplit(api, group, options = {}) {
372
372
  /** The selected widget (see grid-binder): stamped `axdb-selected`, shows the grip. */
373
373
  let selectedId;
374
374
  const selectWidget = (id) => {
375
+ if (id !== undefined)
376
+ clearOtherSelections(api.container, selfPeerRef);
375
377
  if (id === selectedId)
376
378
  return;
377
379
  selectedId = id;
378
380
  syncA11y();
379
381
  };
382
+ let selfPeerRef = null;
380
383
  // Static boards let content be clicked — see grid-binder's staticGuard.
381
384
  const staticGuard = (e) => {
382
385
  var _a, _b, _c, _d;
@@ -823,7 +826,7 @@ export function bindDashboardSplit(api, group, options = {}) {
823
826
  return;
824
827
  if (focusedId !== hit.id || selectedId !== hit.id) {
825
828
  focusedId = hit.id;
826
- selectedId = hit.id;
829
+ selectWidget(hit.id);
827
830
  syncA11y();
828
831
  }
829
832
  };
@@ -967,7 +970,26 @@ export function bindDashboardSplit(api, group, options = {}) {
967
970
  if (layerEl && hostObserver)
968
971
  hostObserver.observe(layerEl, { childList: true });
969
972
  // -- the handle -------------------------------------------------------------
970
- const rowsGuess = () => Math.max(1, Math.round(frame().height / (baseRowHeight + gap)));
973
+ /**
974
+ * The row count the tree's cells are written in. A grid-authored board (or
975
+ * one switched from the grid) carries its members' cells: their extent IS
976
+ * the row count, and switching back must return the same cells — the guess
977
+ * below quantised three 14-row sections on a 758-px fit board to 5 rows
978
+ * (base row height 130), and the grid came back with 14-row inner grids
979
+ * inside 5-row slabs (Quantia, Groups page, Split → Grid). The guess stays
980
+ * for a tree-authored board that never had cells.
981
+ */
982
+ const rowsAtBind = (() => {
983
+ let max = 0;
984
+ for (const id of members()) {
985
+ const c = persistedCell(id);
986
+ if (!c)
987
+ return undefined;
988
+ max = Math.max(max, c.y + c.h);
989
+ }
990
+ return max > 0 ? max : undefined;
991
+ })();
992
+ const rowsGuess = () => rowsAtBind !== null && rowsAtBind !== void 0 ? rowsAtBind : Math.max(1, Math.round(frame().height / (baseRowHeight + gap)));
971
993
  const handle = {
972
994
  sync() {
973
995
  if (disposed)
@@ -1021,7 +1043,7 @@ export function bindDashboardSplit(api, group, options = {}) {
1021
1043
  if (!((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id) || !diagram.getNode(id))
1022
1044
  return false;
1023
1045
  focusedId = id;
1024
- selectedId = id;
1046
+ selectWidget(id);
1025
1047
  syncA11y();
1026
1048
  (_c = (_b = hostOf(id)) === null || _b === void 0 ? void 0 : _b.focus) === null || _c === void 0 ? void 0 : _c.call(_b, { preventScroll: true });
1027
1049
  return true;
@@ -1168,8 +1190,14 @@ export function bindDashboardSplit(api, group, options = {}) {
1168
1190
  api.renderNow();
1169
1191
  // A split board on a CONTAINER (item 7) sits inside a parent grid: register
1170
1192
  // as a peer so the parent's hitTest hands presses on our tiles to us.
1171
- const unregisterPeer = registerBoardPeer(api.container, {
1193
+ const selfPeer = {
1172
1194
  group,
1195
+ clearSelection: () => {
1196
+ if (selectedId === undefined)
1197
+ return;
1198
+ selectedId = undefined;
1199
+ syncA11y();
1200
+ },
1173
1201
  hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
1174
1202
  memberCell: (id) => handle.cellOf(id),
1175
1203
  resizeMemberBy: () => ({ changed: false }),
@@ -1180,7 +1208,10 @@ export function bindDashboardSplit(api, group, options = {}) {
1180
1208
  return f.width * f.height;
1181
1209
  },
1182
1210
  adopt: () => null,
1183
- });
1211
+ };
1212
+ const unregisterPeer = registerBoardPeer(api.container, selfPeer);
1213
+ selfPeerRef = selfPeer;
1214
+ api.container.style.setProperty('--axdb-gap', `${gap}px`);
1184
1215
  const disposeHandle = handle.dispose.bind(handle);
1185
1216
  handle.dispose = () => {
1186
1217
  unregisterPeer();
@@ -235,7 +235,10 @@ const CSS = `
235
235
  .grafloria-node-host > .axdb-grip--inside { top: 2px; }
236
236
  }
237
237
  /* OUTSIDE: a tab on the card's top edge, its corners in line with the card's. */
238
- .grafloria-node-host > .axdb-grip--outside { top: -11px; height: 11px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; box-shadow: 0 -1px 2px rgba(16, 24, 40, .08); }
238
+ /* The OUTSIDE tab lives in the gap between tiles: never taller than the gap
239
+ less a pixel (an 11-px tab in a 10-px gap sat on the tile above), never
240
+ shorter than 6 px so the dots still read. */
241
+ .grafloria-node-host > .axdb-grip--outside { top: calc(-1 * clamp(6px, var(--axdb-gap, 11px) - 1px, 11px)); height: clamp(6px, calc(var(--axdb-gap, 11px) - 1px), 11px); border-bottom-left-radius: 0; border-bottom-right-radius: 0; border-bottom: 0; box-shadow: 0 -1px 2px rgba(16, 24, 40, .08); }
239
242
  .grafloria-node-host > .axdb-grip--outside.axdb-grip--left { left: 10px; }
240
243
  .grafloria-node-host > .axdb-grip--outside.axdb-grip--right { right: 10px; }
241
244
  .grafloria-node-host > .axdb-grip--center { left: 50%; transform: translateX(-50%); }