@grafloria/element 0.4.36 → 0.4.37

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.36",
3
+ "version": "0.4.37",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -79,7 +79,7 @@ export type { JoinColumn, JoinEnd, MatchTier, JoinGuidanceApi, JoinGuidanceOptio
79
79
  export type { ErColumn, ErEntitySpec, ErRelationshipSpec, ErCardinality, ErSide, ErDiagramOptions, UmlClassSpec, UmlRelationshipSpec, UmlRelationKind, UmlSide, UmlDiagramOptions, ErEntityDelta, UmlClassDelta, CardEditingHandle, } from './lib/diagram-kit/index.js';
80
80
  export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField, ErColumnList, UmlClass, UmlMemberList, } from './lib/diagram-kit/index.js';
81
81
  export type { HandleApi } from './lib/diagram-kit/index.js';
82
- export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, dashboard, defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, } from './lib/dashboard-kit/index.js';
82
+ export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, DASHBOARD_KIT_CSS, dashboard, defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, } from './lib/dashboard-kit/index.js';
83
83
  export type { DashboardGridApi, DashboardGridOptions, DashboardGridHandle, CellRect, WorldRect, DashboardGridGeometry, TileDelta, DashboardOptions, DashboardSpec, DashboardSnapshot, DragHandleOption, DragGripOptions, DashboardHandle, DashboardViewSpec, DashboardWidgetSpec, SectionCaption, SectionCaptionOptions, TabsOptions, TabPage, SectionCaptionAction, SectionCaptionFont, WidgetHandle, WidgetRenderer, KpiWidgetData, LineWidgetData, LineSeries, BarWidgetData, DonutWidgetData, FunnelWidgetData, TableWidgetData, } from './lib/dashboard-kit/index.js';
84
84
  export { bindStencilPalette, bindShapeDataPanel, ensureStencilKitStyles } from './lib/stencil-kit/index.js';
85
85
  export type { StencilPaletteApi, StencilPaletteOptions, StencilPaletteHandle, ShapeDataPanelApi, ShapeDataPanelOptions, ShapeDataPanelHandle, } from './lib/stencil-kit/index.js';
package/src/index.js CHANGED
@@ -230,7 +230,7 @@ export { erTable, umlClass, erTables, umlClasses, CardHandle, ErTable, ErField,
230
230
  // GridItemConfig. Element-local code, so the Phase-0 wholesale re-export
231
231
  // below does NOT cover it — this curated block is its only door.
232
232
  // ===========================================================================
233
- export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID,
233
+ export { bindDashboardGrid, paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, gridItemFromCell, cellFromGridItem, buildCommitCommands, ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, DASHBOARD_KIT_CSS,
234
234
  // The DATA-FIRST authoring API — the erDiagram()/umlDiagram() equivalent.
235
235
  dashboard,
236
236
  // …and the built-in renderers behind `kind`, so a dashboard is useful with
@@ -2,7 +2,7 @@ export { bindDashboardGrid, type DashboardGridApi, type DashboardGridOptions, ty
2
2
  export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, type DashboardSplitOptions, type DashboardSplitHandle, } from './split-binder.js';
3
3
  export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, type SplitNode, type SplitGroup, type SplitLeaf, type SplitDir, type SplitSide, type SplitDivider, } from './split-layout.js';
4
4
  export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, type CellRect, type WorldRect, type DashboardGridGeometry, type TileDelta, } from './grid-mapping.js';
5
- export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
5
+ export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, DASHBOARD_KIT_CSS } from './styles.js';
6
6
  export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD, type TabsOptions, type TabPage } from './tabs.js';
7
7
  export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, type SectionCaption, type SectionCaptionOptions, type SectionCaptionAction, type SectionCaptionFont, } from './caption.js';
8
8
  export { dashboard, type DashboardOptions, type DashboardSpec, type DashboardSnapshot, type DashboardHandle, type DashboardViewSpec, type DashboardWidgetSpec, type WidgetHandle, } from './dashboard.js';
@@ -2,7 +2,7 @@ export { bindDashboardGrid, normalizeDragHandle, } from './grid-binder.js';
2
2
  export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, } from './split-binder.js';
3
3
  export { projectSplit, dividersOf, addSplitLeaf, removeSplitLeaf, insertSplitLeaf, moveSplitDivider, splitFromCells, cellsFromSplit, splitLeaves, } from './split-layout.js';
4
4
  export { rowHeightFor, boardHeightFor, columnUnitFor, cellToRect, pointToCell, sizeToSpan, spanWidthPx, gridItemFromCell, cellFromGridItem, buildCommitCommands, } from './grid-mapping.js';
5
- export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
5
+ export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID, DASHBOARD_KIT_CSS } from './styles.js';
6
6
  export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, TAB_DRAG_THRESHOLD } from './tabs.js';
7
7
  export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, } from './caption.js';
8
8
  // The DATA-FIRST authoring API (the erDiagram/umlDiagram equivalent).
@@ -23,7 +23,7 @@
23
23
  * the authored size.
24
24
  */
25
25
  import { __awaiter } from "tslib";
26
- import { Command } from '@grafloria/engine';
26
+ import { BatchCommand, Command } from '@grafloria/engine';
27
27
  import { LiveRegionController, registerTool } from '@grafloria/renderer';
28
28
  import { anyEdge, clearOtherSelections, dragHandleSelector, gripHostOf, gripOf, normalizeDragHandle, ownsPress, parentPeerOf, pressOnDragHandle, registerBoardPeer, syncGrip, DRAG_HANDLE_CLASS, EDGE_GRIP } from './grid-binder.js';
29
29
  import { cellFromGridItem } from './grid-mapping.js';
@@ -779,6 +779,192 @@ export function bindDashboardSplit(api, group, options = {}) {
779
779
  },
780
780
  };
781
781
  const unregisterTool = registerTool(tool);
782
+ // -- a tab torn out of a container on THIS board (0.4.37) --------------------
783
+ // The split board REFUSED every tear-out ("no cells to drop a page into"),
784
+ // so on a split board a tab press was a dead click — no chip, no reorder,
785
+ // no join. The board has a drop model of its own: a widget dropped on a
786
+ // pane's edge inserts there. A torn-out page uses it — the page becomes a
787
+ // one-tab group that IS a pane — and keeps the strip rules of the grid
788
+ // board: its own strip reorders, another container's strip or the centre of
789
+ // its body joins, the centre of its own body is home, off the canvas is
790
+ // nothing. One history step, like every other drop.
791
+ let tearing = false;
792
+ const EDGE_GRACE = 60;
793
+ const execute = (name, commands) => {
794
+ if (commands.length === 0)
795
+ return false;
796
+ void execCommand(new BatchCommand(name, commands));
797
+ return true;
798
+ };
799
+ const beginTearOut = (pageId, fromGroupId, ev, plan) => {
800
+ var _a;
801
+ if (disposed || isStatic || gesture || tearing)
802
+ return false;
803
+ const from = diagram.getGroup(fromGroupId);
804
+ if (!from)
805
+ return false;
806
+ const tree0 = readTree();
807
+ tearing = true;
808
+ const doc = (_a = api.container.ownerDocument) !== null && _a !== void 0 ? _a : document;
809
+ const chip = doc.createElement('div');
810
+ chip.className = 'axdb-drag-chip axdb-tab-chip';
811
+ chip.textContent = plan.label;
812
+ doc.body.appendChild(chip);
813
+ const moveChip = (cx, cy) => {
814
+ chip.style.left = `${cx + 6}px`;
815
+ chip.style.top = `${cy + 6}px`;
816
+ };
817
+ moveChip(ev.clientX, ev.clientY);
818
+ const layer = htmlLayer();
819
+ let joinEl = null;
820
+ const showJoin = (r) => {
821
+ if (!layer)
822
+ return;
823
+ if (!joinEl) {
824
+ joinEl = doc.createElement('div');
825
+ joinEl.className = 'axdb-join';
826
+ layer.prepend(joinEl);
827
+ }
828
+ joinEl.style.left = `${r.x}px`;
829
+ joinEl.style.top = `${r.y}px`;
830
+ joinEl.style.width = `${r.width}px`;
831
+ joinEl.style.height = `${r.height}px`;
832
+ };
833
+ const hideJoin = () => {
834
+ joinEl === null || joinEl === void 0 ? void 0 : joinEl.remove();
835
+ joinEl = null;
836
+ };
837
+ const frameOfGroup = (g) => { var _a, _b, _c, _d; return ({ x: g.position.x, y: g.position.y, width: (_b = (_a = g.size) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0, height: (_d = (_c = g.size) === null || _c === void 0 ? void 0 : _c.height) !== null && _d !== void 0 ? _d : 0 }); };
838
+ const inRect = (r, x, y) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height;
839
+ const targets = plan.joinTargets
840
+ .map((id) => diagram.getGroup(id))
841
+ .filter((g) => !!g && g.id !== fromGroupId && !plan.ownBoards.includes(g.id));
842
+ /** Inside the visible canvas plus a grace band; an unmeasurable container (no layout) is never off. */
843
+ const clientInsideCanvasGrace = (cx, cy) => {
844
+ const rect = api.container.getBoundingClientRect();
845
+ if (rect.width <= 0 || rect.height <= 0)
846
+ return true;
847
+ return cx >= rect.left - EDGE_GRACE && cx <= rect.right + EDGE_GRACE && cy >= rect.top - EDGE_GRACE && cy <= rect.bottom + EDGE_GRACE;
848
+ };
849
+ /** The centre third of a container's BODY (below its strip), both axes — the grid board's join zone. */
850
+ const centreThird = (g, w) => {
851
+ const f = frameOfGroup(g);
852
+ if (!inRect(f, w.x, w.y))
853
+ return false;
854
+ const top = f.y + plan.stripHeight(g.id);
855
+ const h = Math.max(0, f.y + f.height - top);
856
+ return w.x >= f.x + f.width / 3 && w.x <= f.x + (2 * f.width) / 3 && w.y >= top + h / 3 && w.y <= top + (2 * h) / 3;
857
+ };
858
+ const zoneAt = (cx, cy, w) => {
859
+ if (!clientInsideCanvasGrace(cx, cy))
860
+ return { kind: 'off' };
861
+ const own = plan.stripIndex(fromGroupId, cx, cy);
862
+ if (own !== null)
863
+ return { kind: 'reorder', index: own };
864
+ for (const t of targets) {
865
+ const i = plan.stripIndex(t.id, cx, cy);
866
+ if (i !== null)
867
+ return { kind: 'strip', targetId: t.id, index: i };
868
+ }
869
+ for (const t of targets)
870
+ if (centreThird(t, w))
871
+ return { kind: 'join', targetId: t.id };
872
+ if (centreThird(from, w))
873
+ return { kind: 'home' };
874
+ if (worldInsideBoard(w.x, w.y)) {
875
+ const t = dropTargetAt(tree0, w.x, w.y);
876
+ if (t)
877
+ return { kind: 'pane', target: t };
878
+ }
879
+ return { kind: 'home' };
880
+ };
881
+ const apply = (z) => {
882
+ plan.markDrop(z.kind === 'reorder' ? fromGroupId : z.kind === 'strip' ? z.targetId : null, z.kind === 'reorder' || z.kind === 'strip' ? z.index : null);
883
+ const jt = z.kind === 'join' ? diagram.getGroup(z.targetId) : undefined;
884
+ if (jt)
885
+ showJoin(frameOfGroup(jt));
886
+ else
887
+ hideJoin();
888
+ showInsertion(z.kind === 'pane' ? insertionRect(z.target.rect, z.target.side) : null);
889
+ chip.classList.toggle('axdb-out', z.kind === 'home' || z.kind === 'off');
890
+ };
891
+ let last = { x: ev.clientX, y: ev.clientY };
892
+ const detach = () => {
893
+ window.removeEventListener('pointermove', onMove, true);
894
+ window.removeEventListener('pointerup', onUp, true);
895
+ window.removeEventListener('pointercancel', onCancel, true);
896
+ window.removeEventListener('keydown', onKey, true);
897
+ };
898
+ const done = (changed, kind) => {
899
+ tearing = false;
900
+ project(readTree());
901
+ api.renderNow();
902
+ fire({ type: kind, kind: 'move', nodeId: pageId, changed });
903
+ };
904
+ const finish = (commit) => {
905
+ detach();
906
+ chip.remove();
907
+ hideJoin();
908
+ showInsertion(null);
909
+ plan.markDrop(null, null);
910
+ if (disposed) {
911
+ tearing = false;
912
+ return;
913
+ }
914
+ const z = commit ? zoneAt(last.x, last.y, toWorld(last.x, last.y)) : { kind: 'home' };
915
+ if (z.kind === 'home' || z.kind === 'off') {
916
+ done(false, 'cancel');
917
+ return;
918
+ }
919
+ if (z.kind === 'reorder') {
920
+ const cmds = plan.reorder(z.index);
921
+ const changed = cmds.length > 0 && execute('Reorder tab', cmds);
922
+ done(changed, changed ? 'commit' : 'cancel');
923
+ return;
924
+ }
925
+ if (z.kind === 'strip' || z.kind === 'join') {
926
+ const index = z.kind === 'strip' ? z.index : plan.dropIndex(z.targetId, last.x, last.y);
927
+ const planned = plan.join(z.targetId, index, group.id);
928
+ done(execute('Move tab', [...planned.move, ...planned.collapse]), 'commit');
929
+ return;
930
+ }
931
+ // A PANE: the page's new group takes the named side of the target — the
932
+ // widget drop's own rule — and the tree swap rides in the same step as
933
+ // the group's birth, so one undo removes both.
934
+ const side = rtl && (z.target.side === 'left' || z.target.side === 'right') ? (z.target.side === 'left' ? 'right' : 'left') : z.target.side;
935
+ const after = normalizeSplit(insertSplitLeaf(tree0, plan.arrivingId, targetRef(z.target), side));
936
+ const rect = rectsOf(after).get(plan.arrivingId);
937
+ const cell = cellsFromSplit(after, columns, rowsGuess()).get(plan.arrivingId);
938
+ if (!rect || !cell) {
939
+ done(false, 'cancel');
940
+ return;
941
+ }
942
+ const planned = plan.commands(cell, rect, group.id);
943
+ done(execute('Move tab out', [...planned.move, new SetSplitTreeCommand(group.id, tree0, after), ...planned.collapse]), 'commit');
944
+ live.announce(`${plan.label} is a pane ${side === 'left' || side === 'top' ? 'before' : 'after'} ${targetName(z.target)}`, 'polite', true);
945
+ };
946
+ const onMove = (e) => {
947
+ if (disposed)
948
+ return finish(false);
949
+ last = { x: e.clientX, y: e.clientY };
950
+ moveChip(e.clientX, e.clientY);
951
+ apply(zoneAt(e.clientX, e.clientY, toWorld(e.clientX, e.clientY)));
952
+ api.render();
953
+ };
954
+ const onUp = () => finish(true);
955
+ const onCancel = () => finish(false);
956
+ const onKey = (e) => {
957
+ if (e.key === 'Escape')
958
+ finish(false);
959
+ };
960
+ window.addEventListener('pointermove', onMove, true);
961
+ window.addEventListener('pointerup', onUp, true);
962
+ window.addEventListener('pointercancel', onCancel, true);
963
+ window.addEventListener('keydown', onKey, true);
964
+ apply(zoneAt(ev.clientX, ev.clientY, toWorld(ev.clientX, ev.clientY)));
965
+ api.render();
966
+ return true;
967
+ };
782
968
  // -- palette drag-in --------------------------------------------------------
783
969
  const beginPaletteDrag = (node, spec, event) => {
784
970
  var _a;
@@ -1251,9 +1437,8 @@ export function bindDashboardSplit(api, group, options = {}) {
1251
1437
  hasItem: (id) => { var _a; return ((_a = group.members) !== null && _a !== void 0 ? _a : new Set()).has(id); },
1252
1438
  memberCell: (id) => handle.cellOf(id),
1253
1439
  resizeMemberBy: () => ({ changed: false }),
1254
- // A split board has no cells to drop a torn-out page into: it refuses,
1255
- // and the press stays a plain tab click.
1256
- tearOutMember: (_pageId, _fromGroupId, _ev, _plan) => false,
1440
+ // A torn-out page becomes a PANE (0.4.37) see beginTearOut.
1441
+ tearOutMember: (pageId, fromGroupId, ev, plan) => beginTearOut(pageId, fromGroupId, ev, plan),
1257
1442
  // …and it has no cells to move a section across: a strip press stays a selection.
1258
1443
  dragMember: () => false,
1259
1444
  containsWorld: (x, y) => worldInsideBoard(x, y),