@grafloria/element 0.4.25 → 0.4.27
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/index.d.ts +1 -1
- package/src/lib/dashboard-kit/dashboard.d.ts +36 -3
- package/src/lib/dashboard-kit/dashboard.js +177 -7
- package/src/lib/dashboard-kit/grid-binder.js +50 -24
- package/src/lib/dashboard-kit/index.d.ts +1 -0
- package/src/lib/dashboard-kit/index.js +1 -0
- package/src/lib/dashboard-kit/styles.js +37 -0
- package/src/lib/dashboard-kit/tabs.d.ts +36 -0
- package/src/lib/dashboard-kit/tabs.js +84 -0
- package/src/lib/load.js +13 -3
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export type { ErColumn, ErEntitySpec, ErRelationshipSpec, ErCardinality, ErSide,
|
|
|
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
82
|
export { bindDashboardGrid, 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';
|
|
83
|
-
export type { DashboardGridApi, DashboardGridOptions, DashboardGridHandle, CellRect, WorldRect, DashboardGridGeometry, TileDelta, DashboardOptions, DashboardSpec, DashboardSnapshot, DragHandleOption, DragGripOptions, DashboardHandle, DashboardViewSpec, DashboardWidgetSpec, SectionCaption, SectionCaptionOptions, SectionCaptionAction, SectionCaptionFont, WidgetHandle, WidgetRenderer, KpiWidgetData, LineWidgetData, LineSeries, BarWidgetData, DonutWidgetData, FunnelWidgetData, TableWidgetData, } from './lib/dashboard-kit/index.js';
|
|
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';
|
|
86
86
|
export * from '@grafloria/engine';
|
|
@@ -47,6 +47,7 @@ import { type DashboardGridHandle, type DashboardGridOptions, type DashboardResp
|
|
|
47
47
|
import type { DragHandleOption } from './grid-binder.js';
|
|
48
48
|
import type { SplitNode } from './split-layout.js';
|
|
49
49
|
import type { SectionCaption } from './caption.js';
|
|
50
|
+
import { type TabsOptions } from './tabs.js';
|
|
50
51
|
/** A widget, declared as data. */
|
|
51
52
|
export interface DashboardWidgetSpec {
|
|
52
53
|
id: string;
|
|
@@ -113,7 +114,16 @@ export interface DashboardWidgetSpec {
|
|
|
113
114
|
* always covers the pane. Switch live with `setLayout(mode, containerId)`;
|
|
114
115
|
* `toJSON()` writes it per container.
|
|
115
116
|
*/
|
|
116
|
-
layout?: 'grid' | 'split';
|
|
117
|
+
layout?: 'grid' | 'split' | 'tabs';
|
|
118
|
+
/**
|
|
119
|
+
* TAB CONTAINER (`layout: 'tabs'`). Every child that carries `widgets` is a
|
|
120
|
+
* PAGE: one is visible at a time, and a strip of tabs across the top
|
|
121
|
+
* switches between them (DevExpress' Tab Container; VS Code's editor area
|
|
122
|
+
* is a split of these). `active` is the page showing, persisted by
|
|
123
|
+
* `toJSON()`; `tabs` styles the strip.
|
|
124
|
+
*/
|
|
125
|
+
active?: string;
|
|
126
|
+
tabs?: TabsOptions;
|
|
117
127
|
/**
|
|
118
128
|
* Container only: a CAPTION painted by the kit on the section's slab —
|
|
119
129
|
* `true` for the title, a string, or the full options (subtitle,
|
|
@@ -276,6 +286,8 @@ export interface DashboardOptions {
|
|
|
276
286
|
renderCaption?: (widget: DashboardWidgetSpec, host: HTMLElement) => void;
|
|
277
287
|
/** A press on a caption action (`caption.actions`): the section, the action id, the view. */
|
|
278
288
|
onCaptionAction?: (sectionId: string, actionId: string, viewId: string) => void;
|
|
289
|
+
/** A tab container switched pages: the container, the page now showing, the view. */
|
|
290
|
+
onTabChange?: (containerId: string, pageId: string, viewId: string) => void;
|
|
279
291
|
/** Fires after any committed gesture, with the view whose layout changed. */
|
|
280
292
|
onLayoutChange?: (viewId: string, widgets: DashboardWidgetSpec[]) => void;
|
|
281
293
|
/** Extra binder options, merged last (escape hatch to the layer below). */
|
|
@@ -342,7 +354,8 @@ export interface DashboardHandle {
|
|
|
342
354
|
* saved document reopens in the layout it was left in.
|
|
343
355
|
*/
|
|
344
356
|
setLayout(layout: 'grid' | 'split', viewId?: string): void;
|
|
345
|
-
|
|
357
|
+
/** A container built with `layout: 'tabs'` reports 'tabs'; views never do. */
|
|
358
|
+
getLayout(viewId?: string): 'grid' | 'split' | 'tabs';
|
|
346
359
|
/**
|
|
347
360
|
* Set a SECTION's caption live — `false` removes it, `true` is the title, a
|
|
348
361
|
* string or the options. Repaints the band, gives the reserve back or takes
|
|
@@ -352,6 +365,14 @@ export interface DashboardHandle {
|
|
|
352
365
|
setCaption(id: string, caption: SectionCaption): boolean;
|
|
353
366
|
/** The caption as authored or last set; undefined when the section has none. */
|
|
354
367
|
getCaption(id: string): SectionCaption | undefined;
|
|
368
|
+
/**
|
|
369
|
+
* Show a PAGE of a tab container (`layout: 'tabs'`). False when the
|
|
370
|
+
* container or the page is not one. Persisted, so a saved board reopens on
|
|
371
|
+
* the page it was left on.
|
|
372
|
+
*/
|
|
373
|
+
activateTab(containerId: string, pageId: string): boolean;
|
|
374
|
+
/** The page showing in a tab container. */
|
|
375
|
+
getActiveTab(containerId: string): string | undefined;
|
|
355
376
|
/** Live sizing/float switches — the two prototype toggles. */
|
|
356
377
|
setSizing(mode: 'fit' | 'grow'): void;
|
|
357
378
|
getSizing(): 'fit' | 'grow';
|
|
@@ -527,6 +548,10 @@ export interface DashboardApiRef {
|
|
|
527
548
|
onChange?(listener: (state: unknown) => void): () => void;
|
|
528
549
|
};
|
|
529
550
|
}
|
|
551
|
+
export declare function attachTabsRuntime(ctx: DashboardHandleContext, model: {
|
|
552
|
+
getGroup(id: string): GroupModel | undefined;
|
|
553
|
+
runSystemWrite?(fn: () => void): void;
|
|
554
|
+
}, container: HTMLElement | null, handle: DashboardHandle): void;
|
|
530
555
|
export interface DashboardHandleContext {
|
|
531
556
|
/** The views — MUTATED in place by addWidget (push) and remove (filter). */
|
|
532
557
|
views: DashboardViewSpec[];
|
|
@@ -555,7 +580,15 @@ export interface DashboardHandleContext {
|
|
|
555
580
|
mode: 'fluid' | 'fixed';
|
|
556
581
|
overflow: 'bounded' | 'scroll';
|
|
557
582
|
/** See DashboardOptions.layout — per view. */
|
|
558
|
-
layoutOf: Map<string, 'grid' | 'split'>;
|
|
583
|
+
layoutOf: Map<string, 'grid' | 'split' | 'tabs'>;
|
|
584
|
+
/** Tab containers: the page showing, the strip options, the strip element. */
|
|
585
|
+
activeTab: Map<string, string>;
|
|
586
|
+
tabsOf: Map<string, TabsOptions>;
|
|
587
|
+
tabStrips: Map<string, HTMLElement>;
|
|
588
|
+
/** Set by finalize: re-lay a tab container's pages and repaint its strip. */
|
|
589
|
+
syncTabs?: (containerId: string) => void;
|
|
590
|
+
/** Set by finalize: the user's `onTabChange`, so the handle can fire it. */
|
|
591
|
+
onTabChange?: (containerId: string, pageId: string, viewId: string) => void;
|
|
559
592
|
/** Set by finalize: re-bind a VIEW's board under the given layout (setLayout). */
|
|
560
593
|
rebindView?: (viewId: string, layout: 'grid' | 'split') => void;
|
|
561
594
|
/**
|
|
@@ -48,6 +48,7 @@ import { bindDashboardGrid, } from './grid-binder.js';
|
|
|
48
48
|
import { bindDashboardSplit, SPLIT_TREE_KEY } from './split-binder.js';
|
|
49
49
|
import { gridItemFromCell } from './grid-mapping.js';
|
|
50
50
|
import { ensureDashboardKitStyles } from './styles.js';
|
|
51
|
+
import { paintTabStrip, tabStripKey, tabStripReserve } from './tabs.js';
|
|
51
52
|
import { defaultWidgetRenderer } from './widgets.js';
|
|
52
53
|
/**
|
|
53
54
|
* Add a widget node AND its board membership as ONE undoable step.
|
|
@@ -302,6 +303,117 @@ function assignCells(widgets, columns) {
|
|
|
302
303
|
* reads and writes is the whole reason a second handle implementation, which
|
|
303
304
|
* would silently drift, is not needed.
|
|
304
305
|
*/
|
|
306
|
+
/**
|
|
307
|
+
* TAB CONTAINER RUNTIME, shared by `dashboard()` and `fromDocument()`.
|
|
308
|
+
*
|
|
309
|
+
* A tab container binds no grid of its own: it positions its PAGES itself —
|
|
310
|
+
* one into its frame under the strip, the rest parked off-canvas, exactly as
|
|
311
|
+
* `showView` parks a view one level up — and paints the strip. Each page is an
|
|
312
|
+
* ordinary container with an ordinary binder, so a page can be a grid or a
|
|
313
|
+
* split, and the parent board still sees ONE member group to place and resize.
|
|
314
|
+
*
|
|
315
|
+
* Both entry points call this, so a board reloaded from a document behaves
|
|
316
|
+
* like one built from a literal (the first version lived inside `dashboard()`
|
|
317
|
+
* and `fromDocument` silently produced a tab container that never switched).
|
|
318
|
+
*/
|
|
319
|
+
const cssEscape = (v) => typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(v) : v.replace(/"/g, '\\"');
|
|
320
|
+
export function attachTabsRuntime(ctx, model, container, handle) {
|
|
321
|
+
var _a, _b, _c;
|
|
322
|
+
const pagesOf = (id) => {
|
|
323
|
+
var _a;
|
|
324
|
+
const w = ctx.specById.get(id);
|
|
325
|
+
return ((_a = w === null || w === void 0 ? void 0 : w.widgets) !== null && _a !== void 0 ? _a : []).filter((c) => !!c.widgets).map((p) => { var _a; return ({ id: p.id, label: (_a = p.title) !== null && _a !== void 0 ? _a : p.id }); });
|
|
326
|
+
};
|
|
327
|
+
const isTabs = (id) => { var _a, _b; return ((_a = ctx.layoutOf.get(id)) !== null && _a !== void 0 ? _a : (_b = ctx.specById.get(id)) === null || _b === void 0 ? void 0 : _b.layout) === 'tabs' && pagesOf(id).length > 0; };
|
|
328
|
+
const paintStrip = (id, f, h, pages, active) => {
|
|
329
|
+
var _a, _b, _c;
|
|
330
|
+
const layer = container === null || container === void 0 ? void 0 : container.querySelector('.grafloria-html-layer');
|
|
331
|
+
if (!layer)
|
|
332
|
+
return;
|
|
333
|
+
let el = ctx.tabStrips.get(id);
|
|
334
|
+
if (!el || el.parentElement !== layer) {
|
|
335
|
+
el === null || el === void 0 ? void 0 : el.remove();
|
|
336
|
+
el = document.createElement('div');
|
|
337
|
+
el.setAttribute('data-tabs-id', id);
|
|
338
|
+
layer.appendChild(el);
|
|
339
|
+
ctx.tabStrips.set(id, el);
|
|
340
|
+
}
|
|
341
|
+
el.style.position = 'absolute';
|
|
342
|
+
el.style.left = `${f.x}px`;
|
|
343
|
+
el.style.top = `${f.y}px`;
|
|
344
|
+
el.style.width = `${f.width}px`;
|
|
345
|
+
el.style.height = `${h}px`;
|
|
346
|
+
const rtl = (_c = (_b = ctx.binders.get((_a = ctx.viewOfBoard.get(id)) !== null && _a !== void 0 ? _a : ctx.active)) === null || _b === void 0 ? void 0 : _b.getRtl()) !== null && _c !== void 0 ? _c : false;
|
|
347
|
+
const key = tabStripKey(pages, active, ctx.tabsOf.get(id), rtl);
|
|
348
|
+
if (el.getAttribute('data-key') === key)
|
|
349
|
+
return;
|
|
350
|
+
paintTabStrip(el, pages, active, ctx.tabsOf.get(id), rtl, (pid) => handle.activateTab(id, pid), () => handle.selectWidget(id));
|
|
351
|
+
el.setAttribute('data-key', key);
|
|
352
|
+
};
|
|
353
|
+
const sync = (id) => {
|
|
354
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
355
|
+
const cg = (_a = ctx.boardGroups.get(id)) !== null && _a !== void 0 ? _a : model.getGroup(id);
|
|
356
|
+
if (!cg || !isTabs(id))
|
|
357
|
+
return;
|
|
358
|
+
const pages = pagesOf(id);
|
|
359
|
+
const active = (_b = ctx.activeTab.get(id)) !== null && _b !== void 0 ? _b : pages[0].id;
|
|
360
|
+
const strip = tabStripReserve(ctx.tabsOf.get(id), pages.length);
|
|
361
|
+
const f = { x: cg.position.x, y: cg.position.y, width: (_d = (_c = cg.size) === null || _c === void 0 ? void 0 : _c.width) !== null && _d !== void 0 ? _d : 0, height: (_f = (_e = cg.size) === null || _e === void 0 ? void 0 : _e.height) !== null && _f !== void 0 ? _f : 0 };
|
|
362
|
+
const inner = { width: f.width, height: Math.max(0, f.height - strip) };
|
|
363
|
+
const write = (fn) => (model.runSystemWrite ? model.runSystemWrite(fn) : fn());
|
|
364
|
+
write(() => {
|
|
365
|
+
for (const p of pages) {
|
|
366
|
+
const pg = model.getGroup(p.id);
|
|
367
|
+
if (!pg)
|
|
368
|
+
continue;
|
|
369
|
+
// A PARKED page keeps its size, so its own board keeps its layout and
|
|
370
|
+
// comes back exactly as it was; only its x leaves the canvas.
|
|
371
|
+
pg.setFrame({ x: p.id === active ? f.x : OFFSCREEN_X, y: f.y + strip, width: inner.width, height: inner.height });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
for (const p of pages)
|
|
375
|
+
(_g = ctx.binders.get(p.id)) === null || _g === void 0 ? void 0 : _g.sync();
|
|
376
|
+
// A PARKED page is off-canvas but still in the DOM: take its widgets out
|
|
377
|
+
// of the tab order and hide them from assistive tech, or a keyboard user
|
|
378
|
+
// tabs through pages nobody can see (the accessibility scenario counted
|
|
379
|
+
// five tab stops on a board with three of them hidden).
|
|
380
|
+
for (const p of pages) {
|
|
381
|
+
const pg = model.getGroup(p.id);
|
|
382
|
+
const parked = p.id !== active;
|
|
383
|
+
for (const m of (_h = pg === null || pg === void 0 ? void 0 : pg.members) !== null && _h !== void 0 ? _h : []) {
|
|
384
|
+
const host = container === null || container === void 0 ? void 0 : container.querySelector(`.grafloria-node-host[data-node-id="${cssEscape(m)}"]`);
|
|
385
|
+
if (!host)
|
|
386
|
+
continue;
|
|
387
|
+
if (parked) {
|
|
388
|
+
host.setAttribute('aria-hidden', 'true');
|
|
389
|
+
host.tabIndex = -1;
|
|
390
|
+
}
|
|
391
|
+
else
|
|
392
|
+
host.removeAttribute('aria-hidden');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
paintStrip(id, f, strip, pages, active);
|
|
396
|
+
};
|
|
397
|
+
ctx.syncTabs = sync;
|
|
398
|
+
ctx.subscriptions = (_a = ctx.subscriptions) !== null && _a !== void 0 ? _a : [];
|
|
399
|
+
for (const [id, cg] of ctx.boardGroups) {
|
|
400
|
+
if (!isTabs(id))
|
|
401
|
+
continue;
|
|
402
|
+
const pages = pagesOf(id);
|
|
403
|
+
if (!ctx.activeTab.has(id)) {
|
|
404
|
+
const meta = cg.getMetadata('containerWidget');
|
|
405
|
+
const want = (_c = (_b = ctx.specById.get(id)) === null || _b === void 0 ? void 0 : _b.active) !== null && _c !== void 0 ? _c : meta === null || meta === void 0 ? void 0 : meta.active;
|
|
406
|
+
ctx.activeTab.set(id, want && pages.some((p) => p.id === want) ? want : pages[0].id);
|
|
407
|
+
if ((meta === null || meta === void 0 ? void 0 : meta.tabs) && !ctx.tabsOf.has(id))
|
|
408
|
+
ctx.tabsOf.set(id, meta.tabs);
|
|
409
|
+
}
|
|
410
|
+
// Re-lay the pages whenever the parent moves or resizes the container.
|
|
411
|
+
const off = cg.on('bounds:changed', (() => sync(id)));
|
|
412
|
+
if (typeof off === 'function')
|
|
413
|
+
ctx.subscriptions.push(off);
|
|
414
|
+
sync(id);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
305
417
|
/** `setCaption` as one history step: execute re-applies the value, undo the previous one. */
|
|
306
418
|
class SetCaptionCommand extends Command {
|
|
307
419
|
constructor(sectionId, before, after, apply) {
|
|
@@ -484,16 +596,27 @@ export function createDashboardHandle(ctx) {
|
|
|
484
596
|
for (const v of handle.toJSON().views)
|
|
485
597
|
lastReported.set(v.id, JSON.stringify(v.widgets));
|
|
486
598
|
const onHistory = () => {
|
|
487
|
-
var _a;
|
|
599
|
+
var _a, _b;
|
|
488
600
|
if (!ctx.apiRef)
|
|
489
601
|
return;
|
|
490
602
|
const model = ctx.apiRef.getModel();
|
|
491
603
|
for (const id of [...ctx.boardGroups.keys()]) {
|
|
604
|
+
// A TAB CONTAINER deliberately has no binder — it places its pages
|
|
605
|
+
// itself. "No binder" must not read as "needs rebinding", or every
|
|
606
|
+
// history event bound a GRID on it and laid the three pages out as
|
|
607
|
+
// cells side by side (measured: 2×2 of 216×200 after one resize).
|
|
608
|
+
if (ctx.layoutOf.get(id) === 'tabs')
|
|
609
|
+
continue;
|
|
492
610
|
if (!binders.has(id) && model.getGroup(id))
|
|
493
611
|
(_a = ctx.rebindContainer) === null || _a === void 0 ? void 0 : _a.call(ctx, id);
|
|
494
612
|
}
|
|
495
613
|
for (const b of binders.values())
|
|
496
614
|
b.sync();
|
|
615
|
+
// …and undo/redo of a section resize must re-place the pages under the
|
|
616
|
+
// strip, which only the tabs runtime knows how to do.
|
|
617
|
+
for (const id of ctx.layoutOf.keys())
|
|
618
|
+
if (ctx.layoutOf.get(id) === 'tabs')
|
|
619
|
+
(_b = ctx.syncTabs) === null || _b === void 0 ? void 0 : _b.call(ctx, id);
|
|
497
620
|
clampCamera();
|
|
498
621
|
ctx.apiRef.renderNow();
|
|
499
622
|
reportChanged();
|
|
@@ -630,6 +753,27 @@ export function createDashboardHandle(ctx) {
|
|
|
630
753
|
return true;
|
|
631
754
|
},
|
|
632
755
|
getCaption: (id) => { var _a; return (_a = specById.get(id)) === null || _a === void 0 ? void 0 : _a.caption; },
|
|
756
|
+
activateTab(containerId, pageId) {
|
|
757
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
758
|
+
const cg = ctx.boardGroups.get(containerId);
|
|
759
|
+
const w = specById.get(containerId);
|
|
760
|
+
if (!cg || !w || ((_a = ctx.layoutOf.get(containerId)) !== null && _a !== void 0 ? _a : w.layout) !== 'tabs')
|
|
761
|
+
return false;
|
|
762
|
+
if (!((_b = w.widgets) !== null && _b !== void 0 ? _b : []).some((p) => p.id === pageId && p.widgets))
|
|
763
|
+
return false;
|
|
764
|
+
if (ctx.activeTab.get(containerId) === pageId)
|
|
765
|
+
return true;
|
|
766
|
+
ctx.activeTab.set(containerId, pageId);
|
|
767
|
+
w.active = pageId;
|
|
768
|
+
const cw = (_c = cg.getMetadata('containerWidget')) !== null && _c !== void 0 ? _c : {};
|
|
769
|
+
cg.setMetadata('containerWidget', Object.assign(Object.assign({}, cw), { active: pageId }));
|
|
770
|
+
(_d = ctx.syncTabs) === null || _d === void 0 ? void 0 : _d.call(ctx, containerId);
|
|
771
|
+
(_e = ctx.apiRef) === null || _e === void 0 ? void 0 : _e.renderNow();
|
|
772
|
+
(_f = ctx.onTabChange) === null || _f === void 0 ? void 0 : _f.call(ctx, containerId, pageId, (_g = ctx.viewOfBoard.get(containerId)) !== null && _g !== void 0 ? _g : ctx.active);
|
|
773
|
+
reportChanged();
|
|
774
|
+
return true;
|
|
775
|
+
},
|
|
776
|
+
getActiveTab: (containerId) => ctx.activeTab.get(containerId),
|
|
633
777
|
setSizing(mode) {
|
|
634
778
|
var _a;
|
|
635
779
|
for (const b of binders.values())
|
|
@@ -1061,6 +1205,9 @@ export function dashboard(options) {
|
|
|
1061
1205
|
boardH,
|
|
1062
1206
|
mode,
|
|
1063
1207
|
overflow,
|
|
1208
|
+
activeTab: new Map(),
|
|
1209
|
+
tabsOf: new Map(),
|
|
1210
|
+
tabStrips: new Map(),
|
|
1064
1211
|
layoutOf: new Map(views.map((v) => { var _a; return [v.id, (_a = v.layout) !== null && _a !== void 0 ? _a : layout]; })),
|
|
1065
1212
|
optionsBase: options,
|
|
1066
1213
|
active: (_p = (_o = views[0]) === null || _o === void 0 ? void 0 : _o.id) !== null && _p !== void 0 ? _p : 'main',
|
|
@@ -1079,7 +1226,7 @@ export function dashboard(options) {
|
|
|
1079
1226
|
}, get handle() {
|
|
1080
1227
|
return handle;
|
|
1081
1228
|
}, finalize: (api) => {
|
|
1082
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1229
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1083
1230
|
const a = api;
|
|
1084
1231
|
if (!a)
|
|
1085
1232
|
return;
|
|
@@ -1179,13 +1326,15 @@ export function dashboard(options) {
|
|
|
1179
1326
|
};
|
|
1180
1327
|
handle.showView(ctx.active);
|
|
1181
1328
|
ctx.rebindContainer = (id) => {
|
|
1182
|
-
var _a;
|
|
1329
|
+
var _a, _b;
|
|
1183
1330
|
const g = model.getGroup(id);
|
|
1184
1331
|
const w = specById.get(id);
|
|
1185
1332
|
if (!g || !w || !w.widgets)
|
|
1186
1333
|
return;
|
|
1334
|
+
if (((_a = ctx.layoutOf.get(id)) !== null && _a !== void 0 ? _a : w.layout) === 'tabs')
|
|
1335
|
+
return; // places its own pages
|
|
1187
1336
|
ctx.boardGroups.set(id, g);
|
|
1188
|
-
bindContainer(g, w, (
|
|
1337
|
+
bindContainer(g, w, (_b = ctx.viewOfBoard.get(id)) !== null && _b !== void 0 ? _b : ctx.active);
|
|
1189
1338
|
};
|
|
1190
1339
|
/**
|
|
1191
1340
|
* LIVE LAYOUT SWITCH ON A CONTAINER (item 7) — the view's contract one
|
|
@@ -1236,7 +1385,13 @@ export function dashboard(options) {
|
|
|
1236
1385
|
else if (selected)
|
|
1237
1386
|
(_f = binders.get(id)) === null || _f === void 0 ? void 0 : _f.selectWidget(selected);
|
|
1238
1387
|
}
|
|
1239
|
-
(
|
|
1388
|
+
// (Statements must live BEFORE this return — everything below it is a
|
|
1389
|
+
// hoisted function declaration, so an assignment placed there never
|
|
1390
|
+
// runs. That cost me a debugging round: `ctx.syncTabs` stayed undefined
|
|
1391
|
+
// and every tab switch silently did nothing.)
|
|
1392
|
+
ctx.onTabChange = options.onTabChange;
|
|
1393
|
+
attachTabsRuntime(ctx, model, (_m = a.container) !== null && _m !== void 0 ? _m : null, handle);
|
|
1394
|
+
(_o = ctx.attachHistory) === null || _o === void 0 ? void 0 : _o.call(ctx);
|
|
1240
1395
|
return;
|
|
1241
1396
|
/**
|
|
1242
1397
|
* Mount one board's widgets into its group — and recurse for CONTAINERS.
|
|
@@ -1248,7 +1403,7 @@ export function dashboard(options) {
|
|
|
1248
1403
|
* hit-testing and the height-escalation ratchet all apply unchanged.
|
|
1249
1404
|
*/
|
|
1250
1405
|
function mountBoard(boardId, viewId, widgets, boardGroup) {
|
|
1251
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1406
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1252
1407
|
for (const w of widgets) {
|
|
1253
1408
|
if (w.widgets) {
|
|
1254
1409
|
const innerColumns = innerColumnsOf(w);
|
|
@@ -1285,6 +1440,21 @@ export function dashboard(options) {
|
|
|
1285
1440
|
boardGroup.addMember(w.id);
|
|
1286
1441
|
ctx.boardGroups.set(w.id, cg);
|
|
1287
1442
|
mountBoard(w.id, viewId, w.widgets, cg);
|
|
1443
|
+
if (w.layout === 'tabs') {
|
|
1444
|
+
// A TAB CONTAINER lays its pages out itself — one visible, the
|
|
1445
|
+
// rest parked — so it binds no grid of its own. Its pages are
|
|
1446
|
+
// ordinary containers with ordinary binders. The runtime that
|
|
1447
|
+
// positions them is attached once, after every board is mounted.
|
|
1448
|
+
const pages = ((_f = w.widgets) !== null && _f !== void 0 ? _f : []).filter((c) => !!c.widgets);
|
|
1449
|
+
const active = w.active && pages.some((p) => p.id === w.active) ? w.active : (_g = pages[0]) === null || _g === void 0 ? void 0 : _g.id;
|
|
1450
|
+
if (active) {
|
|
1451
|
+
ctx.activeTab.set(w.id, active);
|
|
1452
|
+
w.active = active;
|
|
1453
|
+
}
|
|
1454
|
+
cg.setMetadata('containerWidget', Object.assign(Object.assign(Object.assign(Object.assign({}, cg.getMetadata('containerWidget')), { layout: 'tabs' }), (active ? { active } : {})), (w.tabs ? { tabs: w.tabs } : {})));
|
|
1455
|
+
ctx.tabsOf.set(w.id, (_h = w.tabs) !== null && _h !== void 0 ? _h : {});
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1288
1458
|
bindContainer(cg, w, viewId);
|
|
1289
1459
|
continue;
|
|
1290
1460
|
}
|
|
@@ -1299,7 +1469,7 @@ export function dashboard(options) {
|
|
|
1299
1469
|
// and made toJSON() → dashboard() NOT round-trip (a saved layout
|
|
1300
1470
|
// rebuilt back into its declaration order rather than its cells).
|
|
1301
1471
|
if (w.x !== undefined && w.y !== undefined) {
|
|
1302
|
-
n.setGridItem(gridItemFromCell({ x: w.x, y: w.y, w: (
|
|
1472
|
+
n.setGridItem(gridItemFromCell({ x: w.x, y: w.y, w: (_j = w.span) !== null && _j !== void 0 ? _j : 3, h: (_k = w.rows) !== null && _k !== void 0 ? _k : 1 }));
|
|
1303
1473
|
}
|
|
1304
1474
|
if (w.pinned)
|
|
1305
1475
|
n.setState({ locked: true });
|
|
@@ -114,6 +114,9 @@ export function ownsPress(container, diagram, ev, hit) {
|
|
|
114
114
|
// anything the caption's `passThrough` names) is the content's, not any
|
|
115
115
|
// tool's: no selection, no drag, no resize — the DOM handles it.
|
|
116
116
|
if (typeof Element !== 'undefined' && t instanceof Element) {
|
|
117
|
+
// A TAB STRIP is content, never a board press: its tabs are real buttons.
|
|
118
|
+
if (t.closest('.axdb-tabs'))
|
|
119
|
+
return false;
|
|
117
120
|
const band = t.closest('.axdb-slab > .axdb-slab-h');
|
|
118
121
|
const sid = (_b = band === null || band === void 0 ? void 0 : band.parentElement) === null || _b === void 0 ? void 0 : _b.getAttribute('data-slab-id');
|
|
119
122
|
if (band && sid) {
|
|
@@ -1733,10 +1736,13 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1733
1736
|
// commit and for Escape.
|
|
1734
1737
|
// NESTED ESCALATION — a section is a board one level down, and a pull
|
|
1735
1738
|
// inside it can need rows the section does not hold. Two shapes:
|
|
1736
|
-
// · a tile spanning the section's FULL HEIGHT (
|
|
1737
|
-
//
|
|
1738
|
-
//
|
|
1739
|
-
//
|
|
1739
|
+
// · a tile spanning the section's FULL HEIGHT (any tile of a one-row KPI
|
|
1740
|
+
// strip): the section gains a row and THE DRAGGED TILE takes it; its
|
|
1741
|
+
// siblings keep their own cells, and the row goes back when the tile is
|
|
1742
|
+
// pulled up again, never below the design. (Until 0.4.26 every
|
|
1743
|
+
// full-height tile grew together — "a row stays a row" — but that
|
|
1744
|
+
// resized the widget NEXT to the one under the pointer, which is not
|
|
1745
|
+
// what a grid resize means.)
|
|
1740
1746
|
// · a PARTIAL tile (a one-row control in a 14-row panel): it pushes what
|
|
1741
1747
|
// is below it; a row the pushed layout needs and the section does not
|
|
1742
1748
|
// hold is asked of the board, one per pointer step, and rows the
|
|
@@ -1801,22 +1807,25 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1801
1807
|
const effWant = fullHeight ? stripRows : wantRows;
|
|
1802
1808
|
const wantsMore = effWant > pulled.h;
|
|
1803
1809
|
const wantsLess = effWant < pulled.h;
|
|
1804
|
-
const fullOnes = engine.getItems().filter((i) => i.y === 0 && i.h >= inner).map((i) => i.id);
|
|
1805
|
-
const resizeAll = (ids, rowsTo) => {
|
|
1806
|
-
for (const id of ids) {
|
|
1807
|
-
const it = engine.getItem(id);
|
|
1808
|
-
if (it)
|
|
1809
|
-
engine.resizeCheck(id, it.w, rowsTo);
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
1810
|
let touched = false;
|
|
1813
1811
|
if (wantsMore) {
|
|
1814
|
-
if (fullHeight) {
|
|
1812
|
+
if (fullHeight && !E.s) {
|
|
1813
|
+
// A tile that already starts at row 0 has nothing above it, so a
|
|
1814
|
+
// TOP-edge pull has nowhere to go: refuse it rather than growing
|
|
1815
|
+
// the tile downwards, away from the edge under the pointer. Same
|
|
1816
|
+
// rule the partial path follows (0.4.20).
|
|
1817
|
+
}
|
|
1818
|
+
else if (fullHeight) {
|
|
1815
1819
|
const res = parent.resizeMemberBy(group.id, +1);
|
|
1816
1820
|
if (res.changed) {
|
|
1817
1821
|
record(res, +1);
|
|
1818
1822
|
setInnerRows(inner + 1);
|
|
1819
|
-
|
|
1823
|
+
// ONLY THE TILE UNDER THE POINTER CHANGES. The section grows to
|
|
1824
|
+
// hold it and its siblings keep their own cells — a grid resize
|
|
1825
|
+
// adjusts what you grabbed, nothing else. (This board used to
|
|
1826
|
+
// grow EVERY full-height tile together, so pulling one KPI of a
|
|
1827
|
+
// strip silently resized its neighbour too.)
|
|
1828
|
+
engine.resizeCheck(g.id, pulled.w, pulled.h + 1);
|
|
1820
1829
|
touched = true;
|
|
1821
1830
|
}
|
|
1822
1831
|
}
|
|
@@ -1840,14 +1849,21 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
1840
1849
|
else if (wantsLess) {
|
|
1841
1850
|
if (fullHeight) {
|
|
1842
1851
|
if (slabRows > designRows && inner > 1) {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1852
|
+
// Shrink the dragged tile, then hand back only the rows the
|
|
1853
|
+
// section no longer needs — a sibling still using them keeps
|
|
1854
|
+
// them (the extent floor below).
|
|
1855
|
+
engine.resizeCheck(g.id, pulled.w, Math.max(1, effWant));
|
|
1856
|
+
const floor = Math.max(designRows, extentOf(engine.getItems()));
|
|
1857
|
+
let slab = slabRows;
|
|
1858
|
+
let bound = inner;
|
|
1859
|
+
while (slab > floor) {
|
|
1860
|
+
const res = parent.resizeMemberBy(group.id, -1);
|
|
1861
|
+
if (!res.changed)
|
|
1862
|
+
break;
|
|
1846
1863
|
record(res, -1);
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
resizeAll(fullOnes, inner);
|
|
1864
|
+
slab -= 1;
|
|
1865
|
+
bound -= 1;
|
|
1866
|
+
setInnerRows(bound);
|
|
1851
1867
|
}
|
|
1852
1868
|
touched = true;
|
|
1853
1869
|
}
|
|
@@ -2261,10 +2277,20 @@ export function bindDashboardGrid(api, group, options = {}) {
|
|
|
2261
2277
|
// A press on one of OUR sections' caption bands is ours by the DOM: a
|
|
2262
2278
|
// 'tab' band sits above the frame, over the gap or the tile above,
|
|
2263
2279
|
// where the geometry says otherwise.
|
|
2264
|
-
const
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2280
|
+
const chrome = (_c = (_b = (_a = ev.source) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.closest) === null || _c === void 0 ? void 0 : _c.call(_b, '.axdb-slab > .axdb-slab-h, .axdb-slab > .axdb-rs');
|
|
2281
|
+
const chromeId = (_d = chrome === null || chrome === void 0 ? void 0 : chrome.parentElement) === null || _d === void 0 ? void 0 : _d.getAttribute('data-slab-id');
|
|
2282
|
+
const mine = !!chromeId && ((_e = group.members) !== null && _e !== void 0 ? _e : new Set()).has(chromeId);
|
|
2283
|
+
if (mine)
|
|
2267
2284
|
return true;
|
|
2285
|
+
// Someone else's SECTION HANDLE is never ours to claim. A tab
|
|
2286
|
+
// container's pages register before the parent board, so a page's tool
|
|
2287
|
+
// won the tie for the CONTAINER's corner handle and, finding no peer to
|
|
2288
|
+
// hand it to, just cleared the selection — the container could not be
|
|
2289
|
+
// resized by hand at all while the API resized it fine. (Only the
|
|
2290
|
+
// handle: declining every foreign band broke the press that reaches the
|
|
2291
|
+
// content under a hidden `show: 'hover'` caption.)
|
|
2292
|
+
if (chromeId && (chrome === null || chrome === void 0 ? void 0 : chrome.classList.contains('axdb-rs')))
|
|
2293
|
+
return false;
|
|
2268
2294
|
if (hit.node) {
|
|
2269
2295
|
if (((_f = group.members) !== null && _f !== void 0 ? _f : new Set()).has(hit.node.id))
|
|
2270
2296
|
return true;
|
|
@@ -3,6 +3,7 @@ export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, type Dashboard
|
|
|
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
5
|
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
|
|
6
|
+
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT, type TabsOptions, type TabPage } from './tabs.js';
|
|
6
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';
|
|
7
8
|
export { dashboard, type DashboardOptions, type DashboardSpec, type DashboardSnapshot, type DashboardHandle, type DashboardViewSpec, type DashboardWidgetSpec, type WidgetHandle, } from './dashboard.js';
|
|
8
9
|
export { defaultWidgetRenderer, renderKpiWidget, renderLineWidget, renderBarWidget, renderDonutWidget, renderFunnelWidget, renderTableWidget, BUILT_IN_WIDGET_KINDS, type WidgetRenderer, type KpiWidgetData, type LineWidgetData, type LineSeries, type BarWidgetData, type DonutWidgetData, type FunnelWidgetData, type TableWidgetData, } from './widgets.js';
|
|
@@ -3,6 +3,7 @@ export { bindDashboardSplit, SetSplitTreeCommand, SPLIT_TREE_KEY, } from './spli
|
|
|
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
5
|
export { ensureDashboardKitStyles, DASHBOARD_KIT_STYLE_ID } from './styles.js';
|
|
6
|
+
export { paintTabStrip, tabStripReserve, tabStripKey, TAB_STRIP_HEIGHT } from './tabs.js';
|
|
6
7
|
export { normalizeCaption, captionReserve, captionBandHeight, paintCaptionBand, CAPTION_HEIGHT, CAPTION_HEIGHT_SUBTITLE, CAPTION_HEIGHT_TIGHT, CAPTION_PASS_THROUGH, } from './caption.js';
|
|
7
8
|
// The DATA-FIRST authoring API (the erDiagram/umlDiagram equivalent).
|
|
8
9
|
export { dashboard, } from './dashboard.js';
|
|
@@ -442,6 +442,43 @@ const CSS = `
|
|
|
442
442
|
.axdb-slab-h-action:hover { background: rgba(236, 238, 244, .1); }
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
/* TAB CONTAINER (0.4.27): a strip of pages across the container's top. The
|
|
446
|
+
strip takes the pointer (the tabs are real buttons); the pages below it are
|
|
447
|
+
ordinary boards. */
|
|
448
|
+
.grafloria-html-layer > .axdb-tabs {
|
|
449
|
+
position: absolute; box-sizing: border-box; pointer-events: auto; z-index: 5;
|
|
450
|
+
display: flex; align-items: flex-end; gap: 2px; padding: 0 6px; overflow-x: auto; overflow-y: hidden;
|
|
451
|
+
background: var(--axdb-tabs-bg, rgba(31, 36, 48, .05));
|
|
452
|
+
border-bottom: 1px solid var(--axdb-tabs-line, rgba(31, 36, 48, .12));
|
|
453
|
+
border-radius: var(--axdb-rs-radius, 3px) var(--axdb-rs-radius, 3px) 0 0;
|
|
454
|
+
scrollbar-width: thin;
|
|
455
|
+
}
|
|
456
|
+
.axdb-tabs--center { justify-content: center; }
|
|
457
|
+
.axdb-tabs--end { justify-content: flex-end; }
|
|
458
|
+
.axdb-tabs--stretch > .axdb-tab { flex: 1 1 0; }
|
|
459
|
+
.axdb-tab {
|
|
460
|
+
all: unset; box-sizing: border-box; flex: 0 0 auto; max-width: 200px;
|
|
461
|
+
padding: 0 12px; height: calc(100% - 4px); display: inline-flex; align-items: center;
|
|
462
|
+
font: 600 12px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
463
|
+
color: var(--axdb-tabs-fg, #5a6478); cursor: pointer;
|
|
464
|
+
border-radius: var(--axdb-rs-radius, 3px) var(--axdb-rs-radius, 3px) 0 0;
|
|
465
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
466
|
+
}
|
|
467
|
+
.axdb-tab:hover { background: rgba(31, 36, 48, .06); }
|
|
468
|
+
.axdb-tab:focus-visible { outline: 2px solid var(--axdb-accent-ring, rgba(59, 82, 217, .55)); outline-offset: -2px; }
|
|
469
|
+
/* The active tab reads as the front page: the card's own ground, lifted. */
|
|
470
|
+
.axdb-tab.axdb-tab--on {
|
|
471
|
+
background: var(--axdb-tabs-on-bg, #fff);
|
|
472
|
+
color: var(--axdb-tabs-on-fg, #1f2430);
|
|
473
|
+
box-shadow: 0 -1px 0 var(--axdb-accent, #3b52d9) inset, 0 0 0 1px rgba(31, 36, 48, .1);
|
|
474
|
+
}
|
|
475
|
+
@media (prefers-color-scheme: dark) {
|
|
476
|
+
.grafloria-html-layer > .axdb-tabs { background: var(--axdb-tabs-bg, rgba(236, 238, 244, .06)); border-bottom-color: var(--axdb-tabs-line, rgba(236, 238, 244, .14)); }
|
|
477
|
+
.axdb-tab { color: var(--axdb-tabs-fg, #98a1b4); }
|
|
478
|
+
.axdb-tab:hover { background: rgba(236, 238, 244, .08); }
|
|
479
|
+
.axdb-tab.axdb-tab--on { background: var(--axdb-tabs-on-bg, #1a1d25); color: var(--axdb-tabs-on-fg, #eceef4); box-shadow: 0 -1px 0 var(--axdb-accent, #7d8ff0) inset, 0 0 0 1px rgba(236, 238, 244, .14); }
|
|
480
|
+
}
|
|
481
|
+
|
|
445
482
|
/* legend chips, shared by line and donut */
|
|
446
483
|
.axdb-lg { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; }
|
|
447
484
|
.axdb-lg--col { flex-direction: column; flex-wrap: nowrap; gap: 6px; margin-top: 0; }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAB CONTAINERS — a container whose children are PAGES, one visible at a
|
|
3
|
+
* time, with a strip of tabs across its top. DevExpress ships this as its Tab
|
|
4
|
+
* Container; VS Code's editor area is a split of them.
|
|
5
|
+
*
|
|
6
|
+
* This module owns the strip: its height, its geometry and its DOM. Who is
|
|
7
|
+
* active, where the pages sit and what a press means are the container's
|
|
8
|
+
* business (dashboard.ts), exactly as the caption band's press routing is the
|
|
9
|
+
* binder's.
|
|
10
|
+
*/
|
|
11
|
+
export interface TabsOptions {
|
|
12
|
+
/** Where the strip sits. Only 'top' today; 'bottom' | 'start' | 'end' follow. */
|
|
13
|
+
position?: 'top';
|
|
14
|
+
/** Along the strip (default 'start', mirrored on RTL). */
|
|
15
|
+
align?: 'start' | 'center' | 'end';
|
|
16
|
+
/** Tabs share the strip's width equally. */
|
|
17
|
+
stretch?: boolean;
|
|
18
|
+
/** Strip height, px (default 30). */
|
|
19
|
+
height?: number;
|
|
20
|
+
/** Your class on the strip, for per-container theming. */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface TabPage {
|
|
24
|
+
id: string;
|
|
25
|
+
label: string;
|
|
26
|
+
}
|
|
27
|
+
export declare const TAB_STRIP_HEIGHT = 30;
|
|
28
|
+
/** Pixels the pages give up at the top of the container. */
|
|
29
|
+
export declare function tabStripReserve(o: TabsOptions | undefined, pageCount: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* Paint the strip. Repainted only when its identity changes (the pages, the
|
|
32
|
+
* active one, the options, RTL) so a page switch is a class toggle, not a
|
|
33
|
+
* rebuild — and so a `click` listener survives between switches.
|
|
34
|
+
*/
|
|
35
|
+
export declare function tabStripKey(pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean): string;
|
|
36
|
+
export declare function paintTabStrip(strip: HTMLElement, pages: TabPage[], activeId: string, o: TabsOptions | undefined, rtl: boolean, onPick: (id: string) => void, onSelectContainer?: () => void): void;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAB CONTAINERS — a container whose children are PAGES, one visible at a
|
|
3
|
+
* time, with a strip of tabs across its top. DevExpress ships this as its Tab
|
|
4
|
+
* Container; VS Code's editor area is a split of them.
|
|
5
|
+
*
|
|
6
|
+
* This module owns the strip: its height, its geometry and its DOM. Who is
|
|
7
|
+
* active, where the pages sit and what a press means are the container's
|
|
8
|
+
* business (dashboard.ts), exactly as the caption band's press routing is the
|
|
9
|
+
* binder's.
|
|
10
|
+
*/
|
|
11
|
+
export const TAB_STRIP_HEIGHT = 30;
|
|
12
|
+
/** Pixels the pages give up at the top of the container. */
|
|
13
|
+
export function tabStripReserve(o, pageCount) {
|
|
14
|
+
var _a;
|
|
15
|
+
if (pageCount <= 0)
|
|
16
|
+
return 0;
|
|
17
|
+
return Math.max(18, (_a = o === null || o === void 0 ? void 0 : o.height) !== null && _a !== void 0 ? _a : TAB_STRIP_HEIGHT);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Paint the strip. Repainted only when its identity changes (the pages, the
|
|
21
|
+
* active one, the options, RTL) so a page switch is a class toggle, not a
|
|
22
|
+
* rebuild — and so a `click` listener survives between switches.
|
|
23
|
+
*/
|
|
24
|
+
export function tabStripKey(pages, activeId, o, rtl) {
|
|
25
|
+
return JSON.stringify([pages, activeId, o !== null && o !== void 0 ? o : null, rtl]);
|
|
26
|
+
}
|
|
27
|
+
export function paintTabStrip(strip, pages, activeId, o, rtl, onPick, onSelectContainer) {
|
|
28
|
+
const doc = strip.ownerDocument;
|
|
29
|
+
strip.className = 'axdb-tabs';
|
|
30
|
+
if (o === null || o === void 0 ? void 0 : o.className)
|
|
31
|
+
for (const c of o.className.split(/\s+/).filter(Boolean))
|
|
32
|
+
strip.classList.add(c);
|
|
33
|
+
strip.classList.toggle('axdb-tabs--center', (o === null || o === void 0 ? void 0 : o.align) === 'center');
|
|
34
|
+
strip.classList.toggle('axdb-tabs--end', (o === null || o === void 0 ? void 0 : o.align) === 'end');
|
|
35
|
+
strip.classList.toggle('axdb-tabs--stretch', (o === null || o === void 0 ? void 0 : o.stretch) === true);
|
|
36
|
+
strip.setAttribute('dir', rtl ? 'rtl' : 'ltr');
|
|
37
|
+
strip.setAttribute('role', 'tablist');
|
|
38
|
+
strip.textContent = '';
|
|
39
|
+
// The strip's EMPTY space selects the container. A tab container's pages
|
|
40
|
+
// cover it completely, so without this there is nowhere to press to select
|
|
41
|
+
// it — and an unselected section shows no corner handle, which made the
|
|
42
|
+
// container impossible to resize by hand.
|
|
43
|
+
strip.onpointerdown = (e) => {
|
|
44
|
+
var _a;
|
|
45
|
+
if ((_a = e.target) === null || _a === void 0 ? void 0 : _a.closest('.axdb-tab'))
|
|
46
|
+
return;
|
|
47
|
+
onSelectContainer === null || onSelectContainer === void 0 ? void 0 : onSelectContainer();
|
|
48
|
+
};
|
|
49
|
+
for (const p of pages) {
|
|
50
|
+
const b = doc.createElement('button');
|
|
51
|
+
b.type = 'button';
|
|
52
|
+
b.className = 'axdb-tab';
|
|
53
|
+
b.setAttribute('role', 'tab');
|
|
54
|
+
b.setAttribute('data-tab-id', p.id);
|
|
55
|
+
b.setAttribute('aria-selected', String(p.id === activeId));
|
|
56
|
+
b.classList.toggle('axdb-tab--on', p.id === activeId);
|
|
57
|
+
// ONE tab stop for the whole strip, arrows move between tabs — the
|
|
58
|
+
// pattern every tablist uses, and it keeps the board's single tab stop
|
|
59
|
+
// rule intact.
|
|
60
|
+
b.tabIndex = p.id === activeId ? 0 : -1;
|
|
61
|
+
b.textContent = p.label;
|
|
62
|
+
b.title = p.label;
|
|
63
|
+
b.addEventListener('click', (e) => {
|
|
64
|
+
e.stopPropagation();
|
|
65
|
+
onPick(p.id);
|
|
66
|
+
});
|
|
67
|
+
b.addEventListener('keydown', (e) => {
|
|
68
|
+
var _a;
|
|
69
|
+
const k = e.key;
|
|
70
|
+
const step = k === 'ArrowRight' ? 1 : k === 'ArrowLeft' ? -1 : k === 'Home' ? -pages.length : k === 'End' ? pages.length : 0;
|
|
71
|
+
if (!step)
|
|
72
|
+
return;
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
const i = pages.findIndex((x) => x.id === p.id);
|
|
76
|
+
const dir = rtl && (k === 'ArrowRight' || k === 'ArrowLeft') ? -step : step;
|
|
77
|
+
const next = Math.max(0, Math.min(pages.length - 1, i + dir));
|
|
78
|
+
onPick(pages[next].id);
|
|
79
|
+
(_a = strip.querySelector(`[data-tab-id="${pages[next].id}"]`)) === null || _a === void 0 ? void 0 : _a.focus();
|
|
80
|
+
});
|
|
81
|
+
strip.appendChild(b);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=tabs.js.map
|
package/src/lib/load.js
CHANGED
|
@@ -72,7 +72,7 @@ import { gridItemFromCell } from './dashboard-kit/grid-mapping.js';
|
|
|
72
72
|
import { cellFromGridItem } from './dashboard-kit/grid-mapping.js';
|
|
73
73
|
import { ensureDashboardKitStyles } from './dashboard-kit/styles.js';
|
|
74
74
|
import { defaultWidgetRenderer } from './dashboard-kit/widgets.js';
|
|
75
|
-
import { createDashboardHandle, } from './dashboard-kit/dashboard.js';
|
|
75
|
+
import { createDashboardHandle, attachTabsRuntime } from './dashboard-kit/dashboard.js';
|
|
76
76
|
/** True when the node is an ER entity card or a UML class card. */
|
|
77
77
|
function isDiagramKitCard(node) {
|
|
78
78
|
return node.getMetadata('kitEntity') !== undefined || node.getMetadata('kitClass') !== undefined;
|
|
@@ -206,6 +206,9 @@ export function fromDocument(document, options = {}) {
|
|
|
206
206
|
// a fixed world — it stays one. Fluid is only what was saved fluid.
|
|
207
207
|
mode: (firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.fluid) === true ? 'fluid' : 'fixed',
|
|
208
208
|
overflow: (_p = firstBoard === null || firstBoard === void 0 ? void 0 : firstBoard.overflow) !== null && _p !== void 0 ? _p : 'bounded',
|
|
209
|
+
activeTab: new Map(),
|
|
210
|
+
tabsOf: new Map(),
|
|
211
|
+
tabStrips: new Map(),
|
|
209
212
|
layoutOf: new Map(dashGroups.map((g) => { var _a, _b; return [g.id, ((_b = (_a = g.getMetadata('dashboardBoard')) === null || _a === void 0 ? void 0 : _a.layout) !== null && _b !== void 0 ? _b : 'grid')]; })),
|
|
210
213
|
// responsive is NOT in the document (a runtime seam), so it is deliberately
|
|
211
214
|
// absent from the round-trip; width/height/columns/gap/sizing/float/rtl are.
|
|
@@ -244,7 +247,7 @@ export function fromDocument(document, options = {}) {
|
|
|
244
247
|
(_b = getNodeType(node.type)) === null || _b === void 0 ? void 0 : _b(node, host);
|
|
245
248
|
};
|
|
246
249
|
const finalize = (api) => {
|
|
247
|
-
var _a, _b, _c, _d, _e;
|
|
250
|
+
var _a, _b, _c, _d, _e, _f;
|
|
248
251
|
const a = api;
|
|
249
252
|
if (!a)
|
|
250
253
|
return;
|
|
@@ -289,8 +292,15 @@ export function fromDocument(document, options = {}) {
|
|
|
289
292
|
const board = group.getMetadata('dashboardBoard');
|
|
290
293
|
if (!board)
|
|
291
294
|
continue;
|
|
295
|
+
// A TAB CONTAINER binds no board of its own — the runtime below places
|
|
296
|
+
// its pages and paints its strip.
|
|
297
|
+
if (board.layout === 'tabs')
|
|
298
|
+
continue;
|
|
292
299
|
boards.set(group.id, bindBoard(group, board));
|
|
293
300
|
}
|
|
301
|
+
// Tab containers, with the same runtime dashboard() uses — a reloaded
|
|
302
|
+
// document must switch pages exactly like a board built from a literal.
|
|
303
|
+
attachTabsRuntime(ctx, model, (_e = a.container) !== null && _e !== void 0 ? _e : null, handle);
|
|
294
304
|
// LIVE LAYOUT SWITCH on a loaded document — the same contract dashboard()
|
|
295
305
|
// finalize offers: cells persisted where the grid reads them, any tree and
|
|
296
306
|
// column cache cleared, the board's `layout` flag flipped, a fresh binder.
|
|
@@ -337,7 +347,7 @@ export function fromDocument(document, options = {}) {
|
|
|
337
347
|
}
|
|
338
348
|
// A loaded board follows the history exactly as an authored one does: an
|
|
339
349
|
// undo re-syncs every binder without the consumer calling refresh().
|
|
340
|
-
(
|
|
350
|
+
(_f = ctx.attachHistory) === null || _f === void 0 ? void 0 : _f.call(ctx);
|
|
341
351
|
};
|
|
342
352
|
return Object.assign({ nodes, edges: model.getLinks(), renderCustomNode,
|
|
343
353
|
finalize,
|