@toclocoinc/lattice-grid 1.53.0 → 1.54.0
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/README.md +1 -1
- package/docs/API.html +16 -4
- package/docs/api-detail.html +13 -2
- package/lattice-grid.d.ts +87 -8
- package/lattice-grid.esm.min.js +32 -16
- package/lattice-grid.min.cjs +32 -16
- package/lattice-grid.min.js +32 -16
- package/modules/ai.esm.min.js +10 -4
- package/modules/ai.min.cjs +10 -4
- package/modules/ai.min.js +10 -4
- package/modules/angular.esm.min.js +2 -2
- package/modules/angular.min.cjs +2 -2
- package/modules/angular.min.js +2 -2
- package/modules/chart-alluvial.esm.min.js +1 -1
- package/modules/chart-arc.esm.min.js +1 -1
- package/modules/chart-bubblemap.esm.min.js +1 -1
- package/modules/chart-bump.esm.min.js +1 -1
- package/modules/chart-calendar.esm.min.js +1 -1
- package/modules/chart-decomposition.esm.min.js +1 -1
- package/modules/chart-diverging.esm.min.js +1 -1
- package/modules/chart-dumbbell.esm.min.js +1 -1
- package/modules/chart-fan.esm.min.js +1 -1
- package/modules/chart-hexbin.esm.min.js +1 -1
- package/modules/chart-hexmap.esm.min.js +1 -1
- package/modules/chart-icicle.esm.min.js +1 -1
- package/modules/chart-parallel.esm.min.js +1 -1
- package/modules/chart-ridgeline.esm.min.js +1 -1
- package/modules/chart-roc.esm.min.js +1 -1
- package/modules/chart-slope.esm.min.js +1 -1
- package/modules/chart-splom.esm.min.js +1 -1
- package/modules/chart-waffle.esm.min.js +1 -1
- package/modules/charts.esm.min.js +73 -25
- package/modules/charts.min.cjs +73 -25
- package/modules/charts.min.js +73 -25
- package/modules/data-router.esm.min.js +4 -4
- package/modules/data-router.min.cjs +4 -4
- package/modules/data-router.min.js +4 -4
- package/modules/devtools.esm.min.js +2 -2
- package/modules/devtools.min.cjs +2 -2
- package/modules/devtools.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +4 -4
- package/modules/dhtmlx-compat.min.cjs +4 -4
- package/modules/dhtmlx-compat.min.js +4 -4
- package/modules/gantt.esm.min.js +4 -4
- package/modules/gantt.min.cjs +4 -4
- package/modules/gantt.min.js +4 -4
- package/modules/htmx.esm.min.js +32 -16
- package/modules/htmx.min.cjs +32 -16
- package/modules/htmx.min.js +32 -16
- package/modules/kanban.esm.min.js +4 -4
- package/modules/kanban.min.cjs +4 -4
- package/modules/kanban.min.js +4 -4
- package/modules/kpi.esm.min.js +113 -15
- package/modules/kpi.min.cjs +113 -15
- package/modules/kpi.min.js +113 -15
- package/modules/layout.esm.min.js +299 -21
- package/modules/layout.min.cjs +299 -21
- package/modules/layout.min.js +299 -21
- package/modules/mock-socket.esm.min.js +2 -2
- package/modules/mock-socket.min.cjs +2 -2
- package/modules/mock-socket.min.js +2 -2
- package/modules/react.esm.min.js +2 -2
- package/modules/react.min.cjs +2 -2
- package/modules/react.min.js +2 -2
- package/modules/svelte.esm.min.js +2 -2
- package/modules/svelte.min.cjs +2 -2
- package/modules/svelte.min.js +2 -2
- package/modules/tabs.esm.min.js +4 -4
- package/modules/tabs.min.cjs +4 -4
- package/modules/tabs.min.js +4 -4
- package/modules/vue.esm.min.js +2 -2
- package/modules/vue.min.cjs +2 -2
- package/modules/vue.min.js +2 -2
- package/modules/webcomponent.esm.min.js +32 -16
- package/modules/webcomponent.min.cjs +32 -16
- package/modules/webcomponent.min.js +32 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
dependencies, no build step required. Optional adapters for React, Vue, Svelte
|
|
5
5
|
and Web Components ship alongside it.
|
|
6
6
|
|
|
7
|
-
Version 1.
|
|
7
|
+
Version 1.54.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/docs/API.html
CHANGED
|
@@ -5517,6 +5517,7 @@ const board = createKanban(document.querySelector('#board'), {
|
|
|
5517
5517
|
<tr><td class="sig">getState() / setState(snapshot)</td><td class="desc">Serialise and restore the board state — collapsed columns/lanes, column order, quick filter, sprint/epic selection and selection. Also accepted as <code>config.state</code> at construction.</td></tr>
|
|
5518
5518
|
<tr><td class="sig">sla</td><td class="desc">The card-aging / SLA monitor, present only when a <code>sla</code> config is supplied. Read <code>sla.states()</code>, <code>sla.breaches()</code>/<code>sla.warnings()</code> and <code>sla.stateFor(cardOrKey)</code> for each card's age and level; <code>sla.evaluate()</code> re-checks and fires crossings. See the card-aging note below.</td></tr>
|
|
5519
5519
|
<tr><td class="sig">setLoading(bool) / setError(message)</td><td class="desc">A loading state and a host-supplied error banner; empty columns already render their placeholder.</td></tr>
|
|
5520
|
+
<tr><td class="sig">fields</td><td class="desc">Extra columns of the bound <code>grid</code> to project onto the rows a tile <code>filter</code> sees, beyond the fields the tiles declare. A read of a bound column outside the projection still resolves, and warns once naming the tile and the column; a <code>field</code> naming no column at all is refused by name at first read, and that tile reports <code>unknown</code> rather than an aggregation identity.</td></tr>
|
|
5520
5521
|
<tr><td class="sig">setRows(rows) / refresh()</td><td class="desc">Replace the source rows, or recompute and re-render.</td></tr>
|
|
5521
5522
|
<tr><td class="sig">on(name, fn) / off(name, fn)</td><td class="desc">Events: <code>card:click</code>, <code>card:dblclick</code>, <code>card:contextmenu</code>, <code>card:move</code>, <code>card:reverted</code>, <code>card:confirmed</code>, <code>card:sla</code>, <code>selection:changed</code>, <code>drag:start</code>, <code>drag:end</code> (plus the vocabulary the later cycles emit). On a grid-bound board a move fires <code>card:move</code> optimistically; the grid's write-back then settles it with <code>card:confirmed</code> or, if the server rejects, <code>card:reverted</code> (the card re-reads and the flow transition log rolls the optimistic move back).</td></tr>
|
|
5522
5523
|
<tr><td class="sig">readonly(scope)</td><td class="desc">Whether a scope is readonly — the whole board, a <code>{ column }</code> or a <code>{ card }</code>. A readonly card is not draggable; a move into a readonly column is refused.</td></tr>
|
|
@@ -5706,9 +5707,18 @@ const kpi = createKPI(document.querySelector('#kpis'), {
|
|
|
5706
5707
|
onTileClick: ({ tile }) => drillInto(tile.id),
|
|
5707
5708
|
});</code></pre>
|
|
5708
5709
|
<p>Each tile names an <strong>aggregation</strong> (<code>sum</code>, <code>avg</code>, <code>min</code>, <code>max</code>, <code>count</code>, <code>countDistinct</code>, or a <code>custom</code> reducer <code>(rows, tile) => value</code>), the <strong>field</strong> it reads, and an optional <strong>filter</strong> predicate. Formatting (<code>number</code>/<code>currency</code>/<code>percent</code>/<code>compact</code>, with decimals, currency and locale), a <strong>baseline</strong> for a delta, and semantic <strong>threshold bands</strong> (two cut points with a direction, or an explicit <code>bands</code> list) are all per tile; the band is a semantic name (<code>good</code>/<code>warn</code>/<code>critical</code>), separate from any accent colour. An optional <strong>sparkline</strong> plots a <code>{ x, y }</code> series in sequence order. Each tile is a labelled <code><figure></code>, focusable and keyboard-activatable, its value announced; the sparkline transition respects <code>prefers-reduced-motion</code>.</p>
|
|
5709
|
-
<p><strong>A panel with no data says so.</strong> A tile's status is <code>good</code>, <code>warn</code>, <code>critical</code>, <code>null</code> (no thresholds) — or <code>unknown</code>, which means the panel holds <em>no rows at all</em
|
|
5710
|
+
<p><strong>A panel with no data says so.</strong> A tile's status is <code>good</code>, <code>warn</code>, <code>critical</code>, <code>null</code> (no thresholds) — or <code>unknown</code>, which means the tile <em>measured nothing</em>. Two things cause that: the panel holds <em>no rows at all</em>, or the tile's <code>field</code> names no column on the bound grid, so it never read a cell to reduce over. That fourth status is decided from data presence <em>before</em> any threshold is consulted, because an aggregation over nothing returns the identity of its operation (<code>sum</code> and <code>count</code> return 0) and 0 is a number a threshold grades: under <code>lowerIsBetter</code> cut points it grades <code>good</code>, so an empty panel would otherwise read as a healthy one. An <code>unknown</code> tile reports a <code>null</code> value, renders the <code>nullText</code> placeholder rather than a zero, and is marked with a dashed edge and a visible “No data” caption that also forms part of its accessible name — the state is never carried by colour alone. Because <code>unknown</code> is an explicit status rather than a severity, anything rolling tiles up can surface “not measured” instead of inheriting a false green.</p>
|
|
5710
5711
|
<p><strong>A measured zero is still a measurement.</strong> A tile whose <code>filter</code> matches none of the rows the panel <em>does</em> hold is a different thing: no open incidents is genuinely good, so it reads <code>0</code> and is graded on its thresholds exactly as before. Only an empty panel is <code>unknown</code>.</p>
|
|
5711
5712
|
<p><strong>Staleness is a different question, and a time-bounded source answers it.</strong> <code>unknown</code> is about the absence of rows, not their staleness: a panel over an unbounded store keeps showing the last values it was given when its feed goes quiet, because those rows are still there. Give the underlying source a <a href="#sources">rolling time window</a> (<code>maxAge</code> with <code>ageBy</code>) and its rows age out while the feed is silent, so the panel drains and reports <code>unknown</code> the next time it reads them — a grid-bound panel when the host calls <code>refresh()</code>, a routed one as the removals reach <code>rows.apply</code>.</p>
|
|
5713
|
+
<p><strong>A grid-bound filter reads the columns you project, and says so when it cannot.</strong> A panel bound to a <code>grid</code> does not see whole grid rows: it materialises a <em>projection</em> of each row through the grid’s own value pipeline, carrying the row key plus the fields the tiles declare. That is what keeps a refresh over a large grid cheap — and it used to mean a <code>filter</code> reading any <em>other</em> column saw <code>undefined</code>, matched nothing, and reported a confident <code>0</code> beside a grid full of rows that matched. Declare the extra columns with <code>fields</code>:</p>
|
|
5714
|
+
<pre><code>createKPI(el, {
|
|
5715
|
+
grid,
|
|
5716
|
+
fields: ['priority'], <span class="cmt">// project it, so the filter can read it</span>
|
|
5717
|
+
tiles: [
|
|
5718
|
+
{ label: 'P1 jobs', agg: 'count', filter: (r) => r.priority === 'P1' },
|
|
5719
|
+
],
|
|
5720
|
+
});</code></pre>
|
|
5721
|
+
<p>Forget to, and the panel tells you rather than quietly reporting a zero: a read of a column the bound grid <em>has</em> but the projection does not resolves to the real cell <strong>and</strong> warns once — “the ‘P1 jobs’ tile's filter read <code>priority</code>, which is a column on the bound grid but is not projected — add it to <code>fields</code>” — keyed on the tile and the field, so one bad filter over a 100,000-row grid produces one line, not 100,000. <code>undefined</code> on its own is deliberately <em>not</em> the trigger: a blank cell in a column you did project is a legal value and stays silent, because a warning that fires on ordinary sparse data gets muted and then deleted. A tile <code>field</code> naming no column on the grid at all is a different fault, and <strong>that tile reports no data rather than a number</strong>: reducing over a column that does not exist gives <code>sum</code> and <code>count</code> a <code>0</code>, which grades <code>good</code> under any <code>lowerIsBetter</code> threshold, so warning in the console while leaving a confident green zero on the dashboard would document the lie rather than fix it — and the reader of a dashboard is not reading the console. It reports the <code>unknown</code> status above, renders <code>nullText</code>, and contributes an explicit <code>unknown</code> to any roll-up. That is deliberately not the same case as a tile with a real field whose <code>filter</code> simply matches nothing: that tile measured, and its zero is still graded. The refusal is checked at first read rather than at bind time, because a dynamic grid's columns can arrive after the panel does, and the verdict is recomputed at every read, so a tile refused while the grid was still loading is measured again the moment its column lands. A panel over a plain <code>rows</code> array has whole rows already and none of this applies to it.</p>
|
|
5712
5722
|
<p><strong>Incremental, not recomputed.</strong> Each tile keeps a running accumulator, so a routed <code>rows.apply</code> delta adjusts only the rows it carries — an add contributes, a remove reverses, an update reverses the old row and contributes the new one — rather than re-reading the whole dataset per delta. The two bounded exceptions are honest: an extreme (<code>min</code>/<code>max</code>) removed at its current value triggers a rescan of that tile's own value multiset, and a <code>custom</code> reducer is recomputed over the (filtered) store because an arbitrary function has no inverse.</p>
|
|
5713
5723
|
<div class="table-wrap">
|
|
5714
5724
|
<table>
|
|
@@ -5984,7 +5994,7 @@ tabs.destroy();
|
|
|
5984
5994
|
|
|
5985
5995
|
<h2 id="layout">The dashboard layout</h2>
|
|
5986
5996
|
<p><code>modules/layout</code> is an opt-in <strong>reconfigurable dashboard surface</strong>: a cell grid inside an element, and a set of windows placed on it that a user can move, resize and close — by drag <em>or</em> by keyboard. It is the thing a customer would otherwise reach for GridStack or react-grid-layout to get, which means a second dependency, a second sizing model, and a seam where the viewers in it do not resize properly.</p>
|
|
5987
|
-
<p><strong>It is payload-agnostic, and that is the whole design.</strong> A window body is a <code>div</code> with an <code>id</code>. The module creates it, sizes it, and never reads or writes its contents — it does not import <code>createGrid</code>, does not know what a payload is, and never calls into one. What it does instead is emit <code>window:resized</code> with the measured content box, which is the contract. That rule is what keeps its own code to <strong>
|
|
5997
|
+
<p><strong>It is payload-agnostic, and that is the whole design.</strong> A window body is a <code>div</code> with an <code>id</code>. The module creates it, sizes it, and never reads or writes its contents — it does not import <code>createGrid</code>, does not know what a payload is, and never calls into one. What it does instead is emit <code>window:resized</code> with the measured content box, which is the contract. That rule is what keeps its own code to <strong>12,890 bytes gzipped</strong> (measured: a 77,190-byte bundle over a 62,206-byte fixed floor, of which 2,094 bytes are the two shared module helpers) and what makes it usable for a payload we have not written yet.</p>
|
|
5988
5998
|
<pre><code>import { createLayout } from '@toclocoinc/lattice-grid/modules/layout';
|
|
5989
5999
|
|
|
5990
6000
|
const layout = createLayout(document.querySelector('#dash'), {
|
|
@@ -6015,12 +6025,14 @@ createGrid(layout.payload('pipeline'), { rowKey: 'id', rows, columns });</code><
|
|
|
6015
6025
|
<thead><tr><th>Member</th><th>Description</th></tr></thead>
|
|
6016
6026
|
<tbody>
|
|
6017
6027
|
<tr><td class="sig">createLayout(el, config)</td><td class="desc">Create a dashboard layout. <code>columns</code>/<code>rows</code> (default 12/6) divide the element; <code>overflowX</code>/<code>overflowY</code> are each <code>'static'</code> or <code>'scroll'</code>; <code>columnWidth</code>/<code>rowHeight</code> are the fixed track sizes a scrolling axis uses; <code>gap</code> (8px), <code>padding</code> (5px) and <code>compact</code> (<code>'vertical'</code>) complete it. A second mount on the same element is refused by name.</td></tr>
|
|
6018
|
-
<tr><td class="sig">config.windows[]</td><td class="desc">Each window: <code>id</code> (required, unique), <code>xPos</code>/<code>yPos</code>/<code>xSize</code>/<code>ySize</code> in 1-based cells (auto-placed in the first free cell when omitted), <code>title</code>, <code>chrome</code> (default <code>true</code>), and <code>closable</code>/<code>movable</code>/<code>resizable</code> (all default <code>false</code>, so a dashboard the developer wants fixed is fixed without opting out of anything; each also takes a layout-level default of the same name, which a window's own boolean overrides). <code>padding</code> and <code>payloadId</code> (default <code>`${id}-body`</code>) override per window.</td></tr>
|
|
6028
|
+
<tr><td class="sig">config.windows[]</td><td class="desc">Each window: <code>id</code> (required, unique), <code>xPos</code>/<code>yPos</code>/<code>xSize</code>/<code>ySize</code> in 1-based cells (auto-placed in the first free cell when omitted), <code>title</code>, <code>chrome</code> (default <code>true</code>), and <code>closable</code>/<code>movable</code>/<code>resizable</code>/<code>maximisable</code>/<code>minimisable</code> (all default <code>false</code>, so a dashboard the developer wants fixed is fixed without opting out of anything; each also takes a layout-level default of the same name, which a window's own boolean overrides). <code>padding</code> and <code>payloadId</code> (default <code>`${id}-body`</code>) override per window.</td></tr>
|
|
6019
6029
|
<tr><td class="sig">payload(id) / window(id) / windows()</td><td class="desc">The payload container for a window — the <code>div</code> carrying its <code>payloadId</code>, which you fill; a copy of a window's current descriptor; every window id in mount order.</td></tr>
|
|
6020
6030
|
<tr><td class="sig">add(spec) / move(id, to) / close(id)</td><td class="desc">Add a window after mount (returns its payload container); move or resize one through the same before-events the drag uses; close one through <code>beforeWindowClose</code>. <code>move</code> and <code>close</code> return <code>true</code>/<code>false</code> synchronously with no handler registered, or a <code>Promise<boolean></code> when a handler deferred.</td></tr>
|
|
6021
6031
|
<tr><td class="sig">getLayout() / setLayout(snapshot)</td><td class="desc">The full current arrangement as plain JSON (<code>{columns, rows, windows: [{id, xPos, yPos, xSize, ySize}]}</code>), and its restore. <code>setLayout</code> never throws on garbage, and an entry naming a window that does not exist yet is <em>retained</em> and applied when that window is added.</td></tr>
|
|
6022
6032
|
<tr><td class="sig">getState() / setState(snapshot)</td><td class="desc">The versioned persistence pair, following core's and the Gantt's shape: no arguments in, one plain JSON-safe object out, and <code>setState</code> survives whatever is handed to it.</td></tr>
|
|
6023
|
-
<tr><td class="sig">setInteractive(value) / getInteractive()</td><td class="desc">Lock or unlock the whole dashboard at runtime, without destroying it. A boolean sets <code>movable</code>, <code>resizable</code> and <code>closable</code> together; an object sets only the keys it carries, and a key carrying <code>undefined</code> is treated as absent; <code>getInteractive()</code> returns the layout-level values as a copy, three-valued (<code>undefined</code> for unset, <code>true</code>, or <code>false</code> for a lock) so that <code>setInteractive(getInteractive())</code> is a no-op in every state. The config keys of the same name state the <em>default</em>; only this method takes a capability away. Locking always wins and unlocking never overrides an opt-out: <code>setInteractive(false)</code> pins a window that declared <code>movable: true</code>, and <code>setInteractive(true)</code> leaves a window that declared <code>movable: false</code> pinned. No event fires and <code>getLayout()</code> is unchanged — a mode is not an arrangement.</td></tr>
|
|
6033
|
+
<tr><td class="sig">setInteractive(value) / getInteractive()</td><td class="desc">Lock or unlock the whole dashboard at runtime, without destroying it. A boolean sets <code>movable</code>, <code>resizable</code> and <code>closable</code> together; an object sets only the keys it carries, and a key carrying <code>undefined</code> is treated as absent; <code>getInteractive()</code> returns the layout-level values as a copy, three-valued (<code>undefined</code> for unset, <code>true</code>, or <code>false</code> for a lock) so that <code>setInteractive(getInteractive())</code> is a no-op in every state. The config keys of the same name state the <em>default</em>; only this method takes a capability away. Locking always wins and unlocking never overrides an opt-out: <code>setInteractive(false)</code> pins a window that declared <code>movable: true</code>, and <code>setInteractive(true)</code> leaves a window that declared <code>movable: false</code> pinned. No event fires and <code>getLayout()</code> is unchanged — a mode is not an arrangement. It does not touch <code>maximisable</code> or <code>minimisable</code> either, for the same reason.</td></tr>
|
|
6034
|
+
<tr><td class="sig">maximise(id) / minimise(id) / restore(id)</td><td class="desc"><strong>Maximise fills the layout host</strong> — the element you mounted on — not the browser window, and hides every other window for the duration. That is deliberate: filling the viewport means <code>position: fixed</code>, whose containing block is the nearest ancestor carrying a <code>transform</code>, <code>filter</code>, <code>contain</code> or <code>will-change</code>, so the same rule fills the screen on one page and lands in a 300px box on the next; filling the host is a geometry change inside the layout and cannot disturb the page around it. <strong>Nothing moves</strong>: no compaction runs, no placement changes, and the payload container is the same DOM node throughout, so whatever you mounted in it is untouched. <strong>Escape restores it</strong> from anywhere inside the layout, unless something inside has already claimed the key — and a <em>grid</em> payload claims every Escape whether or not it cancelled anything, at <em>two</em> independent sites (a focused body cell and a focused header cell), so from inside a maximised grid the way back is the restore control, which is where focus already is if you pressed it to get there. <code>minimise(id)</code> draws a window as a single row and hides its payload, keeping the chrome that carries the way back — so under <code>compact: 'vertical'</code> the windows below <em>pull up</em> on screen, which is the point of minimising one. <strong>In the arrangement, nothing moves at all:</strong> the collapse is a projection of the dashboard, not a change to it, so <code>restore(id)</code> gives back exactly the arrangement that was there — in <strong>any</strong> order, with any number of other windows still collapsed. All 14,400 minimise/restore orderings of a five-window dashboard are asserted. A window with <code>chrome: false</code> is refused by name: there would be nothing left on screen to restore it with. The controls are opt-in per window (<code>maximisable</code>, <code>minimisable</code>) with a layout-level default of the same name, and <code>setInteractive()</code> does <strong>not</strong> touch them — a display mode neither moves nor resizes a window in the arrangement, so a locked dashboard can still be blown up to read.</td></tr>
|
|
6035
|
+
<tr><td class="sig">maximised() / minimised()</td><td class="desc">The id of the window filling the host (at most one — maximising a second restores the first), or <code>null</code>; and the ids of every minimised window in mount order. Neither state is part of <code>getLayout()</code>: a mode is not an arrangement, so <code>getLayout()</code> reports the <em>underlying</em> placement in both states — where the window will be when restored — and <code>setLayout()</code> never restores anyone into a mode, moving a minimised window <em>under</em> it instead.</td></tr>
|
|
6024
6036
|
<tr><td class="sig">refresh()</td><td class="desc">Re-measure every window and emit <code>window:resized</code> for those that changed. Called automatically; exposed for a host that changed something the module cannot observe, such as revealing an ancestor.</td></tr>
|
|
6025
6037
|
<tr><td class="sig">on(name, fn) / off(name, fn)</td><td class="desc">Events: <code>window:moved</code>, <code>window:resized</code>, <code>window:closed</code>, <code>layout:changed</code>; the cancellable <code>beforeWindowMove</code>, <code>beforeWindowResize</code> and <code>beforeWindowClose</code> (call <code>preventDefault(reason?)</code> or return <code>false</code>), each paired with <code>windowMove:cancelled</code>, <code>windowResize:cancelled</code> and <code>windowClose:cancelled</code>. <code>'*'</code> subscribes to every past-tense event and is deliberately never delivered a before-event. Config sugar for all ten. Drag progress is <strong>not</strong> emitted per frame.</td></tr>
|
|
6026
6038
|
<tr><td class="sig">destroy()</td><td class="desc">Stop observing, drop every listener including any left by a gesture in flight, and remove the DOM the module built. Whatever you mounted in a payload is yours to destroy.</td></tr>
|
package/docs/api-detail.html
CHANGED
|
@@ -437,7 +437,7 @@
|
|
|
437
437
|
<div class="shell">
|
|
438
438
|
<aside class="rail">
|
|
439
439
|
<p class="rail__brand">Lattice Grid</p>
|
|
440
|
-
<p class="rail__sub">Developer guide · v1.
|
|
440
|
+
<p class="rail__sub">Developer guide · v1.54.0</p>
|
|
441
441
|
<nav>
|
|
442
442
|
<div class="rail__group">
|
|
443
443
|
<span class="rail__label">Start here</span>
|
|
@@ -4743,6 +4743,17 @@ createChart({
|
|
|
4743
4743
|
<code>kind: 'count'</code> is refused with a warning rather than quietly given a second
|
|
4744
4744
|
meaning. The x column has to be continuous and carry wall-clock times — a banded or
|
|
4745
4745
|
categorical axis has no domain to roll.</p>
|
|
4746
|
+
<p><strong>A producer whose clock runs ahead.</strong> A reading stamped slightly ahead of the
|
|
4747
|
+
viewer’s clock carries the end of the domain forward with it, so the newest mark is
|
|
4748
|
+
drawn. How far is bounded: <strong>a quarter of the span</strong> (15 s on a
|
|
4749
|
+
60 s window). A reading further ahead than that is treated as a producer whose clock is
|
|
4750
|
+
wrong — it does not move the window, it is not drawn, and the chart warns once, naming
|
|
4751
|
+
the column, how many readings were left out and how far ahead they were. Without the bound,
|
|
4752
|
+
one device two minutes fast moved a one-minute window past every other device’s recent
|
|
4753
|
+
readings and the chart drew a single dot (BACKLOG-0001123). If every reading in the window is
|
|
4754
|
+
that far ahead the chart shows its empty state, with the same warning.
|
|
4755
|
+
<code>chart.data().windowed</code> counts the readings dropped at either edge of the
|
|
4756
|
+
window. The fix for the warning is the producer’s clock, not a wider window.</p>
|
|
4746
4757
|
<p><strong>Idle costs nothing.</strong> Both halves advance on a plain interval — a
|
|
4747
4758
|
quarter of the window, clamped to between 50 ms and one second — and never on an
|
|
4748
4759
|
animation frame. The source’s wake returns after a single number comparison unless a
|
|
@@ -6991,7 +7002,7 @@ grid.import.apply(preview);</code></pre>
|
|
|
6991
7002
|
<tr><td class="name">createKPI</td><td class="desc">A KPI / stat-tile view (module <code>kpi</code>) of a dataset as a panel of aggregate tiles — each tile a <code>sum</code>, <code>avg</code>, <code>min</code>, <code>max</code>, <code>count</code>, <code>countDistinct</code> or a <code>custom</code> reducer over the routed rows, with an optional <code>filter</code> predicate, number <code>format</code> (<code>number</code>/<code>currency</code>/<code>percent</code>/<code>compact</code>), a <code>baseline</code> for a delta, semantic threshold bands (<code>thresholds</code> with two cut points and a direction, or an explicit <code>bands</code> list, giving a <code>good</code>/<code>warn</code>/<code>critical</code> status kept separate from any accent), and an optional <code>sparkline</code> series. It is a dataset viewer like any other: it consumes data through the same keyed-diff <code>rows.apply({ add, update, remove })</code> contract a grid exposes, so a Data Router can <code>attach(value, kpi)</code> and drive a KPI panel beside a grid, a kanban and a chart off one feed. Updates are incremental — a delta adjusts each tile's running accumulator by only the rows it carries (an add contributes, a remove reverses, an update reverses-then-contributes), the two bounded exceptions being a <code>min</code>/<code>max</code> whose current extreme is removed (a rescan of that tile's own value multiset) and a <code>custom</code> reducer (recomputed over the filtered store, an arbitrary function having no inverse). Each tile is a labelled <code><figure></code>, focusable and keyboard-activatable, its value announced, the sparkline respecting <code>prefers-reduced-motion</code>; a <code>tile:click</code> event (also from the keyboard) lets a host drill down or filter a routed grid. Pass <code>null</code> as the element for a headless panel that computes the same tile model without a DOM. Not a dashboard layout engine (that is the parked dashboard generator) and no charting beyond the minimal sparkline (that is the charts module).</td></tr>
|
|
6992
7003
|
<tr><td class="name">createAI</td><td class="desc">Create an AI narrative / insights controller (module <code>ai</code>) over a live grid. It produces a short, plain-language narrative of the grid's <em>computed</em> figures — a per-KPI / per-chart / per-column <code>explain</code>, or an <code>insights</code> panel over the current filtered view. The grid makes no AI call of its own: <code>createAI</code> imports no provider SDK, holds no key, and makes no network request; it calls one host callback, <code>ask({ system, messages, prompt, tools?, schema?, signal })</code> — your model, your key, your privacy decision — the same philosophy as the data adapters. Two grounding paths feed one guard: where the provider offers tool-use the model is given a curated read-only tool set (<code>getSchema</code>, <code>getProfile</code>, <code>getStatistics</code>, <code>getForecast</code>, <code>runQuery</code>) and the grid's own engine computes what it asks for; otherwise the module builds a facts packet from <code>grid.statistics</code>/the profile/forecasts/view counts and passes it in the prompt. Every figure in the narrative is reconciled against the values the engine produced this render — an ungrounded number is stripped before display (the number-reconciliation guard), so a hallucinated figure never reaches the user. The prompt is constrained to narrate-only; the layer is read-only and never mutates data. <code>redact</code> (a column id, a list, or a predicate) and <code>maxRows</code> bound what the module hands <code>ask()</code>, and the module sends nothing itself; an <code>ask()</code> error surfaces a friendly message and the grid stays fully usable, AI being additive rather than load-bearing. Complementary to <code>grid.ai</code> (the intent/plan skill layer): pass no <code>ask</code> and it adopts the grid's configured <code>ai.ask</code>, running the facts-packet path over it. Pass a headless grid for a DOM-free narrative; <code>facts(target)</code> returns the exact grounded packet without calling <code>ask()</code>. UMD global <code>LatticeGridAI</code>.</td></tr>
|
|
6993
7004
|
<tr><td class="name">createTabs</td><td class="desc">Create a tabbed grid (module <code>tabs</code>): a <code>role="tablist"</code> strip above a stack of <code>role="tabpanel"</code> regions, each hosting its own, independently-configured <code>createGrid</code> instance — "configure each tab as per a normal grid" rather than one grid whose state is swapped (<code>ColumnModel#applyState</code> only repositions/hides/resizes existing columns by id; it carries no field, type or row data, so a state-swap only works when every tab shares one schema). <code>createGrid</code> is injected (<code>createTabs(el, { createGrid, tabs })</code>), the same pattern the React/Vue/Svelte adapters use, so the module imports no engine code and adds nothing to a page that does not load it. A tab that names <code>from: '<tabId>'</code> gets a <code>source: { mode: 'derived', from: <the parent tab’s live grid>, where, group, join, … }</code> wired for it automatically — reusing the shipped derived-source mechanism rather than a new config-inheritance one — and activating a derived tab materialises its whole ancestor chain first; a cyclic <code>from</code> graph is refused (naming the exact cycle) when <code>createTabs</code> is called, not at first click. A tab’s grid mounts on first activation and then stays alive, hidden, so its scroll/selection/filters/sort/grouping/expansion — and an open cell/row editor, left exactly as it was, uncommitted and undiscarded — survive a switch natively; <code>destroy()</code> tears every mounted tab down. The strip is a real tablist with <code>aria-selected</code>, a roving <code>tabindex</code>, and manual-activation keyboard handling (arrows/Home/End move focus, Enter/Space or a click activates). Events: <code>tab:changed</code>, a cancellable <code>beforeTabChange</code> paired with <code>tabChange:cancelled</code>. UMD global <code>LatticeGridTabs</code>.</td></tr>
|
|
6994
|
-
<tr><td class="name">createLayout</td><td class="desc">Create a reconfigurable dashboard layout (module <code>layout</code>): a cell grid inside an element, and a set of windows on it that a user can move, resize and close by drag <em>or</em> by keyboard — the surface a customer would otherwise reach for GridStack to get. It is <strong>payload-agnostic</strong>: a window body is a <code>div</code> with an <code>id</code> that the module creates, sizes and never reads, so it imports no engine code at all (not even <code>createGrid</code>) and its own code is
|
|
7005
|
+
<tr><td class="name">createLayout</td><td class="desc">Create a reconfigurable dashboard layout (module <code>layout</code>): a cell grid inside an element, and a set of windows on it that a user can move, resize and close by drag <em>or</em> by keyboard — the surface a customer would otherwise reach for GridStack to get. It is <strong>payload-agnostic</strong>: a window body is a <code>div</code> with an <code>id</code> that the module creates, sizes and never reads, so it imports no engine code at all (not even <code>createGrid</code>) and its own code is 12,890 bytes gzipped, measured against a 62,206-byte fixed bundle floor. <code>columns</code>/<code>rows</code> divide the element; <code>overflowX</code> and <code>overflowY</code> are <em>independent</em> axes, each <code>'static'</code> (tracks divide the container with <code>minmax(0, 1fr)</code>) or <code>'scroll'</code> (tracks take a fixed <code>columnWidth</code>/<code>rowHeight</code> and the canvas extends past the viewport, so a column keeps the size it asked for — measured: shrinking a 600px host to 300px leaves a 200px column at 200px). Spacing takes a real CSS length: a number of pixels, or <code>'200px'</code>, <code>'25%'</code>, <code>'1fr'</code>, <code>'2rem'</code>; anything else is refused by name and replaced by the default, because the value reaches an inline style. Windows are placed by 1-based <code>xPos</code>/<code>yPos</code>/<code>xSize</code>/<code>ySize</code>, or auto-placed in the first free cell; <code>chrome</code> defaults on, and <code>closable</code>/<code>movable</code>/<code>resizable</code> all default <em>off</em>, so a fixed dashboard is fixed without opting out. <code>compact: 'vertical'</code> pushes displaced windows down then pulls them up (<code>window:moved</code> carries both <code>to</code> and <code>landed</code>); <code>'none'</code> keeps every window where it is put. <code>closable</code>/<code>movable</code>/<code>resizable</code> each also take a <em>layout-level</em> default of the same name, which a window's own boolean overrides, and <code>setInteractive(true|false|{movable, resizable, closable})</code> changes that default at runtime — the “Edit layout” button — without destroying the layout or any payload in it, with <code>getInteractive()</code> reading it back. Locking always wins and unlocking never overrides an opt-out: <code>setInteractive(false)</code> pins a window that declared <code>movable: true</code>, while <code>setInteractive(true)</code> leaves a window that declared <code>movable: false</code> pinned. The config key and the method are deliberately different: <code>movable: false</code> in the config states the <em>default</em> for windows that declare nothing and takes nothing away from one that opted in, whereas <code>setInteractive(false)</code> is an <em>active lock</em>. <code>getInteractive()</code> is three-valued — <code>undefined</code> for unset, <code>true</code>, or <code>false</code> for a lock — and a key carrying <code>undefined</code> is treated as absent, so <code>setInteractive(getInteractive())</code> is a no-op in every state. It moves both halves of the enforcement, the rendered handles <em>and</em> the pointer and keyboard gesture checks, and fires no event because a mode is not an arrangement — <code>getLayout()</code> neither carries it nor restores it. A locked layout is not a read-only dashboard: what is inside a window is configured with that payload's own settings. <code>maximise(id)</code>, <code>minimise(id)</code> and <code>restore(id)</code> are the display modes, with <code>maximised()</code> and <code>minimised()</code> reading them back: maximise fills the <em>layout host</em> rather than the browser window (no <code>position: fixed</code>, whose containing block is the nearest ancestor with a <code>transform</code> or a <code>contain</code>; no reparenting; nothing that can disturb the page around the dashboard), hides the other windows, runs <strong>no compaction at all</strong> and keeps the payload container as the very same DOM node — and <kbd>Escape</kbd> restores it from anywhere inside the layout. <code>minimise</code> draws a window as a single row and hides its payload while its chrome stays to carry the way back, so the windows below pull up on screen; in the arrangement nothing moves at all, because the collapse is a projection of the dashboard rather than a change to it, so restoring gives back exactly the arrangement that was there in <em>any</em> order and with any number of other windows still collapsed. A <code>chrome: false</code> window is refused by name. Both controls are opt-in per window (<code>maximisable</code>, <code>minimisable</code>) with a layout-level default of the same name, and <code>setInteractive()</code> deliberately does not touch either: a mode is not an arrangement, so neither appears in <code>getLayout()</code>, which reports the underlying placement in both states. Keyboard parity with the drag: a focusable handle per window running the kanban board's grab/move/drop/cancel model, with a polite live region announcing grabbed, every tentative position, dropped, cancelled and reverted. It owns exactly <strong>one</strong> <code>ResizeObserver</code> for the whole layout, over two targets, and tells payloads their new content box through <code>window:resized</code> — it never calls into a payload, because it cannot know what one is. Events: <code>window:moved</code>, <code>window:resized</code>, <code>window:closed</code>, <code>layout:changed</code>, the cancellable <code>beforeWindowMove</code>/<code>beforeWindowResize</code>/<code>beforeWindowClose</code> and their <code>*:cancelled</code> pairs; drag progress is not emitted per frame. <code>getLayout()</code>/<code>setLayout()</code> round-trip the arrangement as plain JSON, and <code>getState()</code>/<code>setState()</code> are the versioned pair. Closing a window does <strong>not</strong> destroy its payload — the container is handed back on <code>window:closed</code> and the host owns that lifecycle. UMD global <code>LatticeGridLayout</code>.</td></tr>
|
|
6995
7006
|
<tr><td class="name">createDevtools</td><td class="desc">Mount the devtools panel against a grid, including its accessibility checks.</td></tr>
|
|
6996
7007
|
<tr><td class="name">createGantt</td><td class="desc">Create a project-planning Gantt controller (module <code>gantt</code>) over a task list and a dependency list. A CPM engine (<code>computeSchedule</code>) computes each task's early/late start and finish, its slack and the zero-float critical path, honouring the four link types (<code>LINK_TYPES</code>: FS/SS/FF/SF) with lag, and recomputes on every edit — emitting <code>schedule</code> or, on a dependency cycle or bad input, <code>error</code> (a code from <code>SCHEDULE_ERROR</code>). Milestones are zero-duration points; summary (WBS) tasks are derived from their children (earliest start, latest finish, weighted progress) rather than scheduled; <code>findViolations</code> flags a task placed earlier than its predecessors allow, and <code>toISODate</code> maps an engine day-number back to a calendar date.</td></tr>
|
|
6997
7008
|
<tr><td class="name">createLatticeGridElement</td><td class="desc">Build the element class without registering it, for a custom registry.</td></tr>
|
package/lattice-grid.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Lattice Grid 1.
|
|
2
|
+
* Lattice Grid 1.54.0, type declarations
|
|
3
3
|
* Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
|
|
4
4
|
* https://latticegrid.dev
|
|
5
5
|
*/
|
|
@@ -8715,14 +8715,17 @@ declare module 'lattice-grid/modules/kpi' {
|
|
|
8715
8715
|
value: unknown;
|
|
8716
8716
|
formatted: string;
|
|
8717
8717
|
/**
|
|
8718
|
-
* The tile's semantic band, or `unknown` when the
|
|
8719
|
-
*
|
|
8718
|
+
* The tile's semantic band, or `unknown` when the tile measured nothing.
|
|
8719
|
+
* `unknown` is decided from data presence before any threshold is
|
|
8720
8720
|
* consulted: an aggregation over nothing returns the identity of its
|
|
8721
8721
|
* operation (`sum` and `count` return 0), and 0 is a number a threshold
|
|
8722
|
-
* grades, so without it an empty panel would report as a healthy one.
|
|
8723
|
-
*
|
|
8724
|
-
*
|
|
8725
|
-
*
|
|
8722
|
+
* grades, so without it an empty panel would report as a healthy one.
|
|
8723
|
+
*
|
|
8724
|
+
* Two things make a tile `unknown`: the panel holds no rows at all, or the
|
|
8725
|
+
* tile's `field` names no column on the bound grid, so it never read a cell
|
|
8726
|
+
* to reduce over. A tile whose `filter` matches none of the rows the panel
|
|
8727
|
+
* *does* hold is neither — it has measured a real zero and is banded
|
|
8728
|
+
* normally. `null` means the tile has no thresholds or bands configured.
|
|
8726
8729
|
*/
|
|
8727
8730
|
status: 'good' | 'warn' | 'critical' | 'unknown' | null;
|
|
8728
8731
|
target?: number;
|
|
@@ -8746,6 +8749,14 @@ declare module 'lattice-grid/modules/kpi' {
|
|
|
8746
8749
|
rows?: KPIRow[];
|
|
8747
8750
|
grid?: unknown;
|
|
8748
8751
|
rowKey?: string | ((row: KPIRow) => unknown);
|
|
8752
|
+
/**
|
|
8753
|
+
* Extra columns of the bound `grid` to project onto the rows a tile `filter`
|
|
8754
|
+
* sees, beyond the fields the tiles themselves declare. A grid-bound panel
|
|
8755
|
+
* hands a filter a projection, not a whole grid row, so a filter over a
|
|
8756
|
+
* column no tile names would otherwise read `undefined` and report a
|
|
8757
|
+
* confident zero. Ignored on a panel over a plain `rows` array.
|
|
8758
|
+
*/
|
|
8759
|
+
fields?: string[];
|
|
8749
8760
|
tiles?: KPITile[];
|
|
8750
8761
|
columns?: number;
|
|
8751
8762
|
ariaLabel?: string;
|
|
@@ -9348,6 +9359,21 @@ declare module 'lattice-grid/modules/layout' {
|
|
|
9348
9359
|
movable?: boolean;
|
|
9349
9360
|
/** Whether the window can be resized by drag or keyboard (default `false`). */
|
|
9350
9361
|
resizable?: boolean;
|
|
9362
|
+
/**
|
|
9363
|
+
* Whether to offer a maximise control in the chrome (default `false`).
|
|
9364
|
+
*
|
|
9365
|
+
* Maximising fills the **layout host**, not the browser window, and hides
|
|
9366
|
+
* every other window for the duration. Escape restores it, unless a payload
|
|
9367
|
+
* has already claimed the key.
|
|
9368
|
+
*/
|
|
9369
|
+
maximisable?: boolean;
|
|
9370
|
+
/**
|
|
9371
|
+
* Whether to offer a minimise control in the chrome (default `false`).
|
|
9372
|
+
*
|
|
9373
|
+
* A window with `chrome: false` cannot be minimised whatever this says:
|
|
9374
|
+
* there would be nothing left on screen to restore it with.
|
|
9375
|
+
*/
|
|
9376
|
+
minimisable?: boolean;
|
|
9351
9377
|
/** Padding inside the window; the layout's `padding` (default `'5px'`) otherwise. */
|
|
9352
9378
|
padding?: number | string;
|
|
9353
9379
|
/** The `id` given to the payload container (default `` `${id}-body` ``). */
|
|
@@ -9469,6 +9495,16 @@ declare module 'lattice-grid/modules/layout' {
|
|
|
9469
9495
|
resizable?: boolean;
|
|
9470
9496
|
/** The default `closable` for windows that declare none (default `false`); see `movable`. */
|
|
9471
9497
|
closable?: boolean;
|
|
9498
|
+
/**
|
|
9499
|
+
* The default `maximisable` for windows that declare none (default `false`).
|
|
9500
|
+
*
|
|
9501
|
+
* Not touched by `setInteractive()`: a display mode neither moves nor resizes
|
|
9502
|
+
* a window in the arrangement, so a locked dashboard can still be blown up
|
|
9503
|
+
* to read.
|
|
9504
|
+
*/
|
|
9505
|
+
maximisable?: boolean;
|
|
9506
|
+
/** The default `minimisable` for windows that declare none (default `false`); see `maximisable`. */
|
|
9507
|
+
minimisable?: boolean;
|
|
9472
9508
|
/** The windows, in mount order. */
|
|
9473
9509
|
windows?: LayoutWindow[];
|
|
9474
9510
|
/** An arrangement to apply at mount, as produced by `getLayout()`. */
|
|
@@ -9513,7 +9549,50 @@ declare module 'lattice-grid/modules/layout' {
|
|
|
9513
9549
|
move(id: string, to: Partial<LayoutPlacement>): boolean | Promise<boolean>;
|
|
9514
9550
|
/** Close a window through `beforeWindowClose`; the payload is not destroyed. */
|
|
9515
9551
|
close(id: string): boolean | Promise<boolean>;
|
|
9516
|
-
/**
|
|
9552
|
+
/**
|
|
9553
|
+
* Blow one window up to fill the layout host, hiding the rest.
|
|
9554
|
+
*
|
|
9555
|
+
* It fills the **host element**, not the browser window, so there is no
|
|
9556
|
+
* `position: fixed` (whose containing block is the nearest ancestor carrying
|
|
9557
|
+
* a `transform` or a `contain`, which is why the same rule fills the screen
|
|
9558
|
+
* on one page and lands in a 300px box on the next), no reparenting and
|
|
9559
|
+
* nothing that can disturb the page around the dashboard.
|
|
9560
|
+
*
|
|
9561
|
+
* **Nothing moves**: no compaction runs, no placement changes, and the
|
|
9562
|
+
* payload container is the same DOM node throughout. **Escape restores it**,
|
|
9563
|
+
* from anywhere inside the layout, unless a payload has already claimed the
|
|
9564
|
+
* key — a grid marks every Escape as handled, at two independent sites (a
|
|
9565
|
+
* focused body cell and a focused header cell), so from inside a maximised
|
|
9566
|
+
* grid the way back is the restore control. A minimised window is expanded first,
|
|
9567
|
+
* and maximising a second window restores the first.
|
|
9568
|
+
*/
|
|
9569
|
+
maximise(id: string): boolean;
|
|
9570
|
+
/**
|
|
9571
|
+
* Collapse one window to a single row: its payload is hidden and its chrome
|
|
9572
|
+
* stays, carrying the control that brings it back.
|
|
9573
|
+
*
|
|
9574
|
+
* On screen it becomes one row and the windows below pull up into the space
|
|
9575
|
+
* under `compact: 'vertical'`. In the arrangement nothing moves at all — the
|
|
9576
|
+
* collapse is a projection of it — so `restore()` gives back exactly the
|
|
9577
|
+
* arrangement that was there, in **any** order and with any number of other
|
|
9578
|
+
* windows still collapsed.
|
|
9579
|
+
*
|
|
9580
|
+
* A window with `chrome: false` is refused, with a warning naming it.
|
|
9581
|
+
*/
|
|
9582
|
+
minimise(id: string): boolean;
|
|
9583
|
+
/** Leave whichever display mode a window is in; `false` when it was in none. */
|
|
9584
|
+
restore(id: string): boolean;
|
|
9585
|
+
/** The id of the window filling the host, or `null`. At most one. */
|
|
9586
|
+
maximised(): string | null;
|
|
9587
|
+
/** The ids of every currently minimised window, in mount order. */
|
|
9588
|
+
minimised(): string[];
|
|
9589
|
+
/**
|
|
9590
|
+
* The full current arrangement.
|
|
9591
|
+
*
|
|
9592
|
+
* **A mode is not an arrangement**: this reports the *underlying* placement
|
|
9593
|
+
* of a maximised or minimised window — where it will be when restored — never
|
|
9594
|
+
* the geometry it is drawn at.
|
|
9595
|
+
*/
|
|
9517
9596
|
getLayout(): LayoutSnapshot;
|
|
9518
9597
|
/** Restore an arrangement; never throws on garbage. */
|
|
9519
9598
|
setLayout(incoming: LayoutSnapshot | LayoutWindow[]): number;
|