@toclocoinc/lattice-grid 1.14.0 → 1.16.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 +5 -2
- package/docs/API.html +375 -11
- package/docs/api-detail.html +277 -1
- package/lattice-grid.d.ts +216 -2
- package/lattice-grid.esm.min.js +2742 -393
- package/lattice-grid.min.cjs +2742 -393
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +2742 -393
- package/modules/charts.esm.min.js +89 -4
- package/modules/devtools.esm.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +31 -65164
- package/modules/htmx.esm.min.js +2721 -393
- package/modules/htmx.min.cjs +2721 -393
- package/modules/htmx.min.js +2721 -393
- package/modules/react.esm.min.js +2 -2
- package/modules/svelte.esm.min.js +2 -2
- package/modules/vue.esm.min.js +2 -2
- package/modules/webcomponent.esm.min.js +2742 -393
- package/package.json +1 -1
package/docs/API.html
CHANGED
|
@@ -754,6 +754,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
754
754
|
<tr><td class="name">rows</td><td class="type">unknown[]</td><td class="dflt">, </td><td class="desc">Row objects. Held by reference; not copied.</td></tr>
|
|
755
755
|
<tr><td class="name">rowKey</td><td class="type">string | (row) => string</td><td class="dflt">, </td><td class="desc">Stable row identity. Without it the grid assigns a key per row object and warns: enough for sorting, filtering, selection and copying within a session, but change tracking, streaming dedupe, selection persistence and remote reload all switch off, because new objects are new rows.</td></tr>
|
|
756
756
|
<tr><td class="name">source</td><td class="type">SourceConfig</td><td class="dflt">memory</td><td class="desc">Where rows come from: <code>memory</code>, <code>paged</code>, <code>remote</code> or <code>stream</code>. See <a href="#sources">Sources</a>.</td></tr>
|
|
757
|
+
<tr><td class="name">ingest</td><td class="type">IngestConfig</td><td class="dflt">, </td><td class="desc"><code>{ retainSource }</code>. How rows enter the column store. <code>retainSource</code> defaults to <code>true</code>: the caller's row objects are held by reference so <code>rows.data()</code> returns them unchanged and <code>row === sourceObject</code> holds. Set it <code>false</code> to keep only the packed columns and reconstruct a row on demand — a smaller footprint, but <code>rows.data()</code> then returns freshly reconstructed objects, so identity checks and <code>row.sourceObject</code> no longer hold and equality becomes value-based. Cell values are unchanged.</td></tr>
|
|
757
758
|
<tr><td class="name">tree</td><td class="type">TreeConfig</td><td class="dflt">, </td><td class="desc"><code>{ path }</code> or <code>{ parentKey }</code>, plus <code>label</code>, <code>orphans</code>. Rows form a hierarchy. See <a href="api-detail.html#tree-data">Tree data</a>.</td></tr>
|
|
758
759
|
<tr><td class="name">detail</td><td class="type">DetailConfig</td><td class="dflt">, </td><td class="desc"><code>{ rows, config, render, isMaster, height, cacheLimit, target }</code>. A master row expands into a nested grid, inline or into an element you supply. See <a href="api-detail.html#master-detail">Master-detail</a>.</td></tr>
|
|
759
760
|
<tr><td class="name">context</td><td class="type">unknown</td><td class="dflt">, </td><td class="desc">Arbitrary value passed to every callback, so formatters and renderers need no closures over app state.</td></tr>
|
|
@@ -785,7 +786,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
785
786
|
<thead><tr><th>Property</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
786
787
|
<tbody>
|
|
787
788
|
<tr><td class="name">selection</td><td class="type">SelectionConfig | 'single' | 'multiple' | 'none'</td><td class="dflt">, </td><td class="desc">Object form adds <code>checkbox</code> (a pinned column of row checkboxes), <code>headerCheckbox</code> (tri-state select-all in its heading), <code>groupSelectsChildren</code>, <code>ranges</code>, <code>fillHandle</code>, <code>fill</code>. See <a href="api-detail.html#selection-checkbox">Selection and ranges</a>.</td></tr>
|
|
788
|
-
<tr><td class="name">edit</td><td class="type">EditConfig | boolean</td><td class="dflt">, </td><td class="desc"><code>{ enabled, mode: 'cell' | 'row', start: 'single' | 'double' | 'key', enterMovesDown, undoDepth, commit, confirm, pendingTimeout }</code>.
|
|
789
|
+
<tr><td class="name">edit</td><td class="type">EditConfig | boolean</td><td class="dflt">, </td><td class="desc"><code>{ enabled, mode: 'cell' | 'row', start: 'single' | 'double' | 'key', enterMovesDown, undoDepth, commit, confirm, pendingTimeout, pastePreview }</code>. <code>commit</code>/<code>confirm</code>/<code>pendingTimeout</code> turn on optimistic writes; <code>pastePreview</code> (default off) shows a confirm/cancel diff before a bulk paste commits.</td></tr>
|
|
789
790
|
<tr><td class="name">pagination</td><td class="type">PaginationConfig | boolean</td><td class="dflt">, </td><td class="desc">Local or remote paging.</td></tr>
|
|
790
791
|
<tr><td class="name">quickFilterText</td><td class="type">string</td><td class="dflt">, </td><td class="desc">Initial quick-filter term. Equivalent to <code>grid.filters.quick(text)</code>.</td></tr>
|
|
791
792
|
<tr><td class="name">hostFilter</td><td class="type">{ active(), passes(row) }</td><td class="dflt">, </td><td class="desc">An application-level predicate composed with the grid's own filters.</td></tr>
|
|
@@ -798,6 +799,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
798
799
|
<tr><td class="name">totalFilteredOnly</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc">Totals reduce the filtered set. <code>false</code> totals the whole dataset, group totals included. See <a href="api-detail.html#total-filtered-only">Grouping, totals and pivot</a>.</td></tr>
|
|
799
800
|
<tr><td class="name">totalOnlyChangedColumns</td><td class="type">boolean</td><td class="dflt">false</td><td class="desc">Reduce only the totalled columns an edit actually changed. Off by default, it asserts that each total depends on nothing but its own column. See <a href="api-detail.html#grouping">Grouping, totals and pivot</a>.</td></tr>
|
|
800
801
|
<tr><td class="name">showTotalInHeader</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc">Under grouping or pivot, a totalled column's heading names its reduction on a line above the column name. See <a href="api-detail.html#show-total-in-header">Grouping, totals and pivot</a>.</td></tr>
|
|
802
|
+
<tr><td class="name">aggregateChooser</td><td class="type">boolean</td><td class="dflt">false</td><td class="desc">Let the user pick a column's reduction from the column menu. On, the totalling entry becomes an <em>Aggregate</em> submenu offering only the aggregates the column's type says are meaningful (sum, average, min, max, count and so on — never <code>sum</code> on a category column), with the current one ticked and a <em>None</em> to stop totalling; it is keyboard-operable through the standard menu and drives <code>grid.columns.setTotal()</code>, reusing the existing reduction model. Off by default and non-breaking: the menu keeps its plain <em>Total this column</em> toggle. See <a href="api-detail.html#aggregate-chooser">Grouping, totals and pivot</a>.</td></tr>
|
|
801
803
|
<tr><td class="name">allowUnsafeTemplates</td><td class="type">boolean</td><td class="dflt">false</td><td class="desc">Off by default. Templates are escaped unless this is explicitly set. When set, an interpolated value may contain presentational markup, but script is still removed from it: <code><script></code>, <code><iframe></code> and the other executable tags, <code>on*</code> handler attributes, and <code>javascript:</code> URLs. The flag permits markup, not code.</td></tr>
|
|
802
804
|
<tr><td class="name">licence</td><td class="type">string</td><td class="dflt">, </td><td class="desc">Signed licence key. Removes the trial watermark; unlocks nothing, because nothing is locked.</td></tr>
|
|
803
805
|
</tbody>
|
|
@@ -847,6 +849,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
847
849
|
<tr><td class="name">statusBar</td><td class="type">boolean | { panels }</td><td class="desc">Composable panels along the bottom. Default set: <code>rowCount</code>, <code>selectedCount</code>, <code>aggregation</code>, <code>comments</code>, <code>updates</code>, <code>progress</code>. Each is silent when it has nothing to report.</td></tr>
|
|
848
850
|
<tr><td class="name">maximise</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc"><code>false</code> removes the rail button and <code>grid.maximise</code>, for an application with its own full-screen mode.</td></tr>
|
|
849
851
|
<tr><td class="name">toolPanel</td><td class="type">boolean | object</td><td class="desc">Side dock. <code>panels</code>: <code>columns</code>, <code>filters</code>, <code>views</code>, <code>quick</code>, <code>formatting</code>, <code>statistics</code>. <code>side: 'left'</code> makes it the icon rail, which also turns on <code>actions</code> (<code>undo</code>, <code>redo</code>, <code>pause</code>, <code>restore</code>, <code>maximise</code>, then the export group: <code>export</code>, <code>excel</code>, <code>clipboard</code>, <code>print</code>: nine in all, and an array takes these names rather than the button labels) and <code>icons</code>. An explicit array <em>replaces</em> that list rather than extending it; a bare <code>'-'</code> in it renders a divider between groups. <code>exportName</code> names the CSV.</td></tr>
|
|
852
|
+
<tr><td class="name">groupPanel</td><td class="type">boolean | object</td><td class="desc">A drag-and-drop group-by strip above the column header — the row-group panel. Drag a heading into it to group by that column; the active groups show as removable, reorderable chips, and reordering the chips changes the nesting order. It is keyboard-operable — arrows move between chips, <code>Shift</code> with an arrow reorders, <code>Delete</code> ungroups, and an add control groups any column — and every change is announced through the live region. Off by default and non-breaking; it drives the same model as <code>grid.columns.group()</code> and reimplements nothing. The object form takes <code>hint</code>, the placeholder shown while nothing is grouped.</td></tr>
|
|
850
853
|
<tr><td class="name">timeZone</td><td class="type">string</td><td class="desc">An IANA zone every date column formats and parses in, so a grid shows one zone whatever the viewer's machine says. Individual columns may override it.</td></tr>
|
|
851
854
|
<tr><td class="name">formulaFunctions</td><td class="type">object</td><td class="desc">Your own functions, added to the formula language by name. The built-in list is closed on purpose; this is the one way in, and a function you add is called exactly as a built-in is.</td></tr>
|
|
852
855
|
<tr><td class="name">formatting</td><td class="type">object</td><td class="desc">Conditional formatting rules to seed, keyed by column id or <code>'*'</code>. The same shape <code>grid.formatting.all()</code> returns, so a saved view can be handed straight back.</td></tr>
|
|
@@ -857,6 +860,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
857
860
|
<tr><td class="name">environment</td><td class="type">function</td><td class="desc">Extra fields for the diagnostics bundle: build number, tenant, region. Called when a bundle is taken, never on the render path.</td></tr>
|
|
858
861
|
<tr><td class="name">contextMenu</td><td class="type">boolean | (p) => MenuItem[]</td><td class="desc">Right-click menu. The function form is <code>(params, defaults) => items</code>: see <a href="#custom-menu">custom items</a>. <code>false</code> suppresses it: what a read-only grid wants, since the default menu offers Paste, Clear and Fill down.</td></tr>
|
|
859
862
|
<tr><td class="name">columnMenu</td><td class="type">boolean | (p) => MenuItem[]</td><td class="desc">The header's 3-dot menu, and a right-click on a column heading. The function form is <code>(params, defaults) => items</code>, with <code>params</code> carrying <code>colId</code>, <code>column</code> and <code>grid</code>: see <a href="#custom-menu">custom items</a>. <code>false</code> suppresses it.</td></tr>
|
|
863
|
+
<tr><td class="name">rangeChart</td><td class="type">fn | { onChart } | boolean</td><td class="desc">Off by default. Offers <strong>Chart selection</strong> in the cell menu and binds <kbd>Alt</kbd>+<kbd>F1</kbd> when a selected range has a number to plot. The DOM layer draws no charts, so the handler you give — a function, or <code>{ onChart }</code>, called <code>(grid, range)</code> — is where the page wires in <code>chartRange</code> from <a href="#chart-a-range">the charts module</a>.</td></tr>
|
|
860
864
|
<tr><td class="name">shortcuts</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc">The <kbd>?</kbd> keyboard shortcut overlay. <code>false</code> suppresses it, for a host that wants <kbd>?</kbd> for itself. See <a href="api-detail.html#keyboard">Keyboard</a>.</td></tr>
|
|
861
865
|
<tr><td class="name">rowReorder</td><td class="type">boolean | { column }</td><td class="dflt">, </td><td class="desc">Let a user reorder rows by dragging a handle or with <kbd>Alt</kbd>+<kbd>Shift</kbd>+arrows. The handle goes in the first visible column unless <code>column</code> names another. Refused, with a reason announced, while a sort, filter or grouping is active. See <a href="api-detail.html#row-reorder">Row reorder</a>.</td></tr>
|
|
862
866
|
<tr><td class="name">rowTransfer</td><td class="type">boolean | { send, receive, mode, group }</td><td class="dflt">, </td><td class="desc">Let rows be dragged between grids. Off by default. <code>send</code> and <code>receive</code> are both on when present, so one-way is <code>{ receive: false }</code> or <code>{ send: false }</code>. <code>mode: 'copy'</code> leaves the row behind; <code>group</code> restricts which grids may exchange. See <a href="api-detail.html#row-transfer">Moving rows between grids</a>.</td></tr>
|
|
@@ -1068,7 +1072,8 @@ grid.overlay.hide();</code></pre>
|
|
|
1068
1072
|
<tr><td class="sig">group(ids)</td><td class="type">void</td><td class="desc">Set the row-group columns, in order.</td></tr>
|
|
1069
1073
|
<tr><td class="sig">pivot(ids)</td><td class="type">void</td><td class="desc"></td></tr>
|
|
1070
1074
|
<tr><td class="sig">totals(ids)</td><td class="type">void</td><td class="desc">Which columns carry an aggregation.</td></tr>
|
|
1071
|
-
<tr><td class="sig">setTotal(id, fn)</td><td class="type">void</td><td class="desc">Change one column's aggregation. <code>null</code> stops totalling it.</td></tr>
|
|
1075
|
+
<tr><td class="sig">setTotal(id, fn)</td><td class="type">void</td><td class="desc">Change one column's aggregation. <code>null</code> stops totalling it. A named total the column's type says is meaningless is refused (§9.4), the same way it is at configuration.</td></tr>
|
|
1076
|
+
<tr><td class="sig">aggregates(id)</td><td class="type">TotalName[]</td><td class="desc">The aggregate names meaningful for a column, honouring its type's declaration — what the aggregate chooser offers.</td></tr>
|
|
1072
1077
|
<tr><td class="sig">distinct(id)</td><td class="type">unknown[]</td><td class="desc">Distinct values, read from the dictionary rather than by scanning rows.</td></tr>
|
|
1073
1078
|
<tr><td class="sig">state()</td><td class="type">ColumnState[]</td><td class="desc">Serialisable column state.</td></tr>
|
|
1074
1079
|
<tr><td class="sig">apply(state)</td><td class="type">StateApplyReport</td><td class="desc">Restore it. Never throws and never refuses: a saved view written against an older column set applies as much of itself as still makes sense, and the returned <code>{ applied, skipped }</code> names what it could not use and why. Columns added since the view was saved appear in their declared state, after the ones it names. See <a href="api-detail.html#views-guide">Saved views</a>.</td></tr>
|
|
@@ -1135,6 +1140,8 @@ grid.overlay.hide();</code></pre>
|
|
|
1135
1140
|
<tr><td class="sig">undo() / redo()</td><td class="type">void</td><td class="desc">Depth from <code>edit.undoDepth</code>.</td></tr>
|
|
1136
1141
|
<tr><td class="sig">setCells(writes, type?)</td><td class="type">number</td><td class="desc">Write many cells as one undoable step. Returns how many landed.</td></tr>
|
|
1137
1142
|
<tr><td class="sig">pasteInto(anchor, text, extent?)</td><td class="type">number</td><td class="desc">Paste tab-separated text, using Excel's tiling rules.</td></tr>
|
|
1143
|
+
<tr><td class="sig">previewPaste(anchor, text, extent?)</td><td class="type">object</td><td class="desc">Compute what a paste <em>would</em> change without committing: <code>{ changes, rejected }</code>. The engine behind <code>edit.pastePreview</code>.</td></tr>
|
|
1144
|
+
<tr><td class="sig">pastePreview</td><td class="type">boolean</td><td class="desc">Whether a bulk paste is previewed before it commits (<code>edit.pastePreview</code>).</td></tr>
|
|
1138
1145
|
<tr><td class="sig">settle(id, ok, reason?)</td><td class="type">boolean</td><td class="desc">Report the outcome of an optimistic write. Only needed with <code>edit.confirm: 'manual'</code>; the id arrives on <code>cell:pending</code>.</td></tr>
|
|
1139
1146
|
<tr><td class="sig">pending()</td><td class="type">OpenWrite[]</td><td class="desc">Writes still awaiting an outcome. Empty unless <code>edit.commit</code> is set.</td></tr>
|
|
1140
1147
|
<tr><td class="sig">status(key, colId)</td><td class="type">'pending' | null</td><td class="desc">Whether a cell has a write in flight.</td></tr>
|
|
@@ -1142,6 +1149,38 @@ grid.overlay.hide();</code></pre>
|
|
|
1142
1149
|
</table>
|
|
1143
1150
|
</div>
|
|
1144
1151
|
|
|
1152
|
+
<p class="section-note">
|
|
1153
|
+
<strong>Previewing a bulk paste.</strong> A paste can rewrite dozens of cells at once, and one
|
|
1154
|
+
that lands somewhere unexpected looks exactly like one that worked. Set
|
|
1155
|
+
<code>edit.pastePreview: true</code> and a paste into more than one cell opens a confirm/cancel
|
|
1156
|
+
dialog first, listing every cell that changes (old → new) and every cell a commit
|
|
1157
|
+
would reject. It is off by default, so existing paste behaviour is unchanged.
|
|
1158
|
+
<code>previewPaste</code> computes that same diff without any UI:
|
|
1159
|
+
</p>
|
|
1160
|
+
<pre data-run="js" data-expect="2 change, 1 rejected" data-covers="config:pastePreview"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
1161
|
+
|
|
1162
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
1163
|
+
<span class="cmt">// A per-row rule: the middle row is locked, so a paste over it is refused.</span>
|
|
1164
|
+
columns: [{ field: 'a', edit: { enabled: (p) => p.row.data.locked !== <span class="kw">true</span> } }],
|
|
1165
|
+
rows: [
|
|
1166
|
+
{ id: '0', a: 'A0', locked: <span class="kw">false</span> },
|
|
1167
|
+
{ id: '1', a: 'A1', locked: <span class="kw">true</span> },
|
|
1168
|
+
{ id: '2', a: 'A2', locked: <span class="kw">false</span> },
|
|
1169
|
+
],
|
|
1170
|
+
rowKey: 'id',
|
|
1171
|
+
<span class="cmt">// Opt in: off by default, so a plain paste is unaffected.</span>
|
|
1172
|
+
edit: { enabled: <span class="kw">true</span>, pastePreview: <span class="kw">true</span> },
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
<span class="cmt">// What *would* happen — nothing is committed yet.</span>
|
|
1176
|
+
<span class="kw">const</span> preview = grid.edit.previewPaste({ key: '0', colId: 'a' }, 'X\nY\nZ');
|
|
1177
|
+
|
|
1178
|
+
<span class="cmt">// Confirming is setCells of the changes — the ordinary paste path.</span>
|
|
1179
|
+
grid.edit.setCells(preview.changes.map((c) => ({ key: c.key, colId: c.colId, value: c.newValue })), 'paste');
|
|
1180
|
+
|
|
1181
|
+
<span class="kw">const</span> changed = preview.changes.filter((c) => c.changed).length;
|
|
1182
|
+
<span class="kw">return</span> `${changed} change, ${preview.rejected.length} rejected`; <span class="cmt">// rows 0 and 2 change; row 1 refused</span></code></pre>
|
|
1183
|
+
|
|
1145
1184
|
<h2 id="form">grid.form</h2>
|
|
1146
1185
|
<p class="section-note">
|
|
1147
1186
|
The row edit form, a drawer or dialog holding one control per field. Present whether or not
|
|
@@ -1653,6 +1692,38 @@ grid.formatting.restat(); // re-derive every threshold from the
|
|
|
1653
1692
|
<p>A colour scale can take its bounds the same way, with <code>from</code> in place of <code>min</code> and <code>max</code>: <code>'minmax'</code> spans the data, <code>'quantile'</code> spans <code>low</code> to <code>high</code> (5th to 95th percentile by default), <code>'stddev'</code> spans <code>deviations</code> either side of the mean. The quantile form is the better default on real data, one mistyped order of magnitude otherwise compresses every real value into the first swatch.</p>
|
|
1654
1693
|
<div class="note"><p>Thresholds are <strong>pinned</strong> when the rules compile and do not move on their own. That is deliberate: a boundary that re-derived itself as rows were filtered would repaint cells whose values had not changed, and nobody comparing two screenshots could tell which of the two things had moved. <code>grid.formatting.restat()</code> is how you move it, and a "recalculate" control is the natural place to put it.</p></div>
|
|
1655
1694
|
|
|
1695
|
+
<h3>Runtime decorations: data bars and icon sets on demand</h3>
|
|
1696
|
+
<p>Where a colour rule paints the cell's background, a <em>decoration</em> changes the shape the cell renders as, a data bar sized by value, or a threshold icon set. <code>grid.columns.decorate(id, spec)</code> turns one on, changes it, or clears it with <code>null</code>, after the grid is built. It is presentation config rather than query state: unlike a formatting rule it is not on the undo timeline and does not travel in a saved view. Icon sets carry an <code>aria-label</code> per band and keep the value beside the glyph, so the meaning is announced, never only shown.</p>
|
|
1697
|
+
<pre data-run="js" data-expect="bar|arrows|increasing|cleared|painted" data-covers="method:columns method:formatting"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
1698
|
+
|
|
1699
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
1700
|
+
columns: [{ field: 'score' }, { field: 'trend' }],
|
|
1701
|
+
rows: [{ id: '1', score: 72, trend: 8 }],
|
|
1702
|
+
rowKey: 'id',
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
<span class="cmt">// A data bar, sized 0..100, set at runtime.</span>
|
|
1706
|
+
grid.columns.decorate('score', { type: 'bar', min: 0, max: 100 });
|
|
1707
|
+
<span class="kw">const</span> bar = grid.columns.get('score').cell.decoration.type;
|
|
1708
|
+
|
|
1709
|
+
<span class="cmt">// A built-in three-arrow icon set on the trend column, with its own bands.</span>
|
|
1710
|
+
grid.columns.decorate('trend', { type: 'icon', bands: [
|
|
1711
|
+
{ min: 0, icon: 'chevronUp', label: 'increasing', variant: 'success' },
|
|
1712
|
+
{ icon: 'chevronDown', label: 'decreasing', variant: 'danger' },
|
|
1713
|
+
] });
|
|
1714
|
+
<span class="kw">const</span> set = grid.columns.get('trend').cell.decoration.type === 'icon' ? 'arrows' : 'none';
|
|
1715
|
+
<span class="kw">const</span> band = grid.columns.get('trend').cell.decoration.bands[0].label;
|
|
1716
|
+
|
|
1717
|
+
<span class="cmt">// Clearing a decoration returns the column to plain text.</span>
|
|
1718
|
+
grid.columns.decorate('score', <span class="kw">null</span>);
|
|
1719
|
+
<span class="kw">const</span> cleared = grid.columns.get('score').cell.decoration === <span class="kw">undefined</span> ? 'cleared' : 'still-set';
|
|
1720
|
+
|
|
1721
|
+
<span class="cmt">// A runtime colour rule is the durable, view-persisted sibling.</span>
|
|
1722
|
+
grid.formatting.add('score', { when: { op: 'lt', value: 50 }, style: { background: '#fdecea' } });
|
|
1723
|
+
<span class="kw">const</span> painted = grid.formatting.styleFor('score', 20) ? 'painted' : 'plain';
|
|
1724
|
+
|
|
1725
|
+
<span class="kw">return</span> [bar, set, band, cleared, painted].join('|');</code></pre>
|
|
1726
|
+
|
|
1656
1727
|
<h2 id="statistics">grid.statistics</h2>
|
|
1657
1728
|
<p>What the grid knows about its own numbers, and about how they have changed since the page loaded. Every figure is computed over the <em>filtered</em> rows, through the same column handles the totals row uses, so a median here and a median in the footer are the same number, by the same definition (R type 7).</p>
|
|
1658
1729
|
<pre><code>grid.statistics.profile('margin');
|
|
@@ -2494,6 +2565,142 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
2494
2565
|
</table>
|
|
2495
2566
|
</div>
|
|
2496
2567
|
|
|
2568
|
+
<h4 id="adapter-options">What each adapter takes</h4>
|
|
2569
|
+
<p class="section-note">
|
|
2570
|
+
Every adapter is a function of one options object. The tables below list what each accepts, the
|
|
2571
|
+
type, the default where it is not obvious, and what it means. The defaults are the load-bearing
|
|
2572
|
+
part: an adapter is designed to work when handed almost nothing, so most of what you can set is
|
|
2573
|
+
about telling it what your endpoint <em>cannot</em> do rather than switching features on.
|
|
2574
|
+
</p>
|
|
2575
|
+
|
|
2576
|
+
<h5 id="odata-options"><code>odataAdapter</code></h5>
|
|
2577
|
+
<div class="table-wrap">
|
|
2578
|
+
<table>
|
|
2579
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2580
|
+
<tbody>
|
|
2581
|
+
<tr><td class="name">url</td><td class="type">string</td><td class="type">—</td><td class="desc">The entity-set endpoint, e.g. <code>https://api.example.com/Orders</code>. Required.</td></tr>
|
|
2582
|
+
<tr><td class="name">headers</td><td class="type">Record<string, string></td><td class="type">{}</td><td class="desc">Sent on every request, merged over <code>Accept: application/json</code>. This is where a fixed bearer token or an API key goes. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2583
|
+
<tr><td class="name">fetch</td><td class="type">typeof fetch</td><td class="type">the global <code>fetch</code></td><td class="desc">Your own fetch, for a token that expires, a proxy, or a non-browser runtime. The adapter bundles no HTTP client. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2584
|
+
<tr><td class="name">count</td><td class="type">boolean</td><td class="type">true</td><td class="desc">Whether to ask for <code>$count=true</code> and read <code>@odata.count</code>. On by default because the grid sizes its scrollbar from the total; set <code>false</code> for a server that does not support it.</td></tr>
|
|
2585
|
+
<tr><td class="name">search</td><td class="type">boolean</td><td class="type">false</td><td class="desc">Whether the server implements <code>$search</code>. Off by default, so quick-filter text stays with the grid until you confirm the endpoint honours it; <code>true</code> pushes it as <code>$search</code>.</td></tr>
|
|
2586
|
+
</tbody>
|
|
2587
|
+
</table>
|
|
2588
|
+
</div>
|
|
2589
|
+
|
|
2590
|
+
<h5 id="rest-options"><code>restAdapter</code></h5>
|
|
2591
|
+
<p class="section-note">
|
|
2592
|
+
<strong>The parameter names are yours, and the defaults are not zero.</strong> Paging and
|
|
2593
|
+
sorting are assumed present; filtering is assumed <em>absent</em> until you declare
|
|
2594
|
+
<code>operators</code>, because an adapter that claims to filter when the endpoint ignores it
|
|
2595
|
+
returns the wrong rows silently. The query-string names default to
|
|
2596
|
+
<code>offset</code>, <code>limit</code>, <code>sort</code>, <code>order</code>,
|
|
2597
|
+
<code>filter</code> and <code>q</code> (search); <code>params</code> overrides any of them.
|
|
2598
|
+
</p>
|
|
2599
|
+
<div class="table-wrap">
|
|
2600
|
+
<table>
|
|
2601
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2602
|
+
<tbody>
|
|
2603
|
+
<tr><td class="name">url</td><td class="type">string</td><td class="type">—</td><td class="desc">The endpoint, e.g. <code>/api/orders</code>. Required.</td></tr>
|
|
2604
|
+
<tr><td class="name">headers</td><td class="type">Record<string, string></td><td class="type">{}</td><td class="desc">Sent on every request, merged over <code>Accept: application/json</code>. Where a fixed token or key goes. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2605
|
+
<tr><td class="name">fetch</td><td class="type">typeof fetch</td><td class="type">the global <code>fetch</code></td><td class="desc">Your own fetch, for an expiring token, a proxy or a non-browser runtime. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2606
|
+
<tr><td class="name">params</td><td class="type">Partial<Record<'offset'|'limit'|'sort'|'order'|'filter'|'search', string>></td><td class="type">{ offset:'offset', limit:'limit', sort:'sort', order:'order', filter:'filter', search:'q' }</td><td class="desc">Renames the query-string keys to whatever your endpoint already reads. Only the keys you name change; the rest keep the defaults above.</td></tr>
|
|
2607
|
+
<tr><td class="name">capabilities</td><td class="type">PushdownCapabilities</td><td class="type">{ range:true, total:true, sort:'multi', filter:false, quick:false }</td><td class="desc">What the endpoint can answer, merged over the defaults. Declaring <code>operators</code> is the usual way to turn filtering on; reach for this to switch off paging or sorting an endpoint cannot do.</td></tr>
|
|
2608
|
+
<tr><td class="name">operators</td><td class="type">string[]</td><td class="type">— (filtering off)</td><td class="desc">The comparisons the endpoint genuinely applies, e.g. <code>['eq','gt','lt','contains']</code>. Setting it turns filtering on as a <code>tree</code>; a condition using any other operator stays with the grid.</td></tr>
|
|
2609
|
+
<tr><td class="name">encodeFilter</td><td class="type">(filters: object) => string</td><td class="type"><code>JSON.stringify</code></td><td class="desc">How the pushed condition tree becomes the <code>filter</code> parameter's value. Override it to emit whatever query language your service parses instead of JSON.</td></tr>
|
|
2610
|
+
<tr><td class="name">rows</td><td class="type">(body: unknown) => unknown[]</td><td class="type">body itself if an array, else <code>body.rows</code> then <code>body.data</code></td><td class="desc">Pulls the row array out of the response body, for an envelope that nests it somewhere else.</td></tr>
|
|
2611
|
+
<tr><td class="name">total</td><td class="type">(body: unknown, rows: unknown[]) => number</td><td class="type"><code>body.total</code> then <code>body.count</code>, else the page length</td><td class="desc">Reads the count of <em>all</em> matching rows, not the page. The grid sizes its scrollbar from it, so a page-sized total makes a large result look like one page.</td></tr>
|
|
2612
|
+
</tbody>
|
|
2613
|
+
</table>
|
|
2614
|
+
</div>
|
|
2615
|
+
|
|
2616
|
+
<h5 id="duckdb-options"><code>duckdbAdapter</code></h5>
|
|
2617
|
+
<div class="table-wrap">
|
|
2618
|
+
<table>
|
|
2619
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2620
|
+
<tbody>
|
|
2621
|
+
<tr><td class="name">connection</td><td class="type">object</td><td class="type">—</td><td class="desc">A live connection exposing <code>query</code>, and ideally <code>prepare</code>. Required. A connection without <code>prepare</code> is used only for unfiltered queries, because interpolating a user's filter into SQL is worse than not filtering.</td></tr>
|
|
2622
|
+
<tr><td class="name">from</td><td class="type">string</td><td class="type">—</td><td class="desc">A table, a view, or any FROM expression. Required. <code>read_parquet('s3://bucket/*.parquet')</code> is as valid as a table name.</td></tr>
|
|
2623
|
+
<tr><td class="name">fields</td><td class="type">string[]</td><td class="type">everything (<code>SELECT *</code>)</td><td class="desc">The columns to select. Name them to narrow the projection when the grid shows a subset of a wide table.</td></tr>
|
|
2624
|
+
</tbody>
|
|
2625
|
+
</table>
|
|
2626
|
+
</div>
|
|
2627
|
+
|
|
2628
|
+
<h5 id="dfql-options"><code>dfqlAdapter</code></h5>
|
|
2629
|
+
<div class="table-wrap">
|
|
2630
|
+
<table>
|
|
2631
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2632
|
+
<tbody>
|
|
2633
|
+
<tr><td class="name">entity</td><td class="type">string</td><td class="type">—</td><td class="desc">The DemandFlow entity to query. Required.</td></tr>
|
|
2634
|
+
<tr><td class="name">token</td><td class="type">string</td><td class="type">—</td><td class="desc">A personal access token, sent as the bearer credential. Required. Never commit one; read it from configuration at runtime.</td></tr>
|
|
2635
|
+
<tr><td class="name">url</td><td class="type">string</td><td class="type"><code>https://rest.demandflow.com</code></td><td class="desc">The API base, for a non-default region or a self-hosted deployment.</td></tr>
|
|
2636
|
+
<tr><td class="name">comboKey</td><td class="type">'comboKey' | 'comboKey2' | 'comboKey3'</td><td class="type"><code>comboKey</code></td><td class="desc">The name of the key attribute to match on. <code>comboKey</code> is the standard hierarchy.</td></tr>
|
|
2637
|
+
<tr><td class="name">query</td><td class="type">string</td><td class="type"><code>SUB</code></td><td class="desc">The prefix matched against the key attribute. <code>SUB</code> alone means every record of the entity in the tenant.</td></tr>
|
|
2638
|
+
<tr><td class="name">load</td><td class="type">string[]</td><td class="type">everything</td><td class="desc">Fields to project, which saves bandwidth but not query cost.</td></tr>
|
|
2639
|
+
<tr><td class="name">limit</td><td class="type">number</td><td class="type">server default</td><td class="desc">Caps rows <em>scanned</em>, not matched — which is why every request also sends <code>countOnly</code> to reveal the true match count.</td></tr>
|
|
2640
|
+
<tr><td class="name">headers</td><td class="type">Record<string, string></td><td class="type">{}</td><td class="desc">Extra headers merged over the bearer token, for a gateway that needs its own.</td></tr>
|
|
2641
|
+
<tr><td class="name">fetch</td><td class="type">typeof fetch</td><td class="type">the global <code>fetch</code></td><td class="desc">Your own fetch, for a proxy or a non-browser runtime.</td></tr>
|
|
2642
|
+
</tbody>
|
|
2643
|
+
</table>
|
|
2644
|
+
</div>
|
|
2645
|
+
|
|
2646
|
+
<h4 id="adapter-auth">Authenticating a remote adapter</h4>
|
|
2647
|
+
<p class="section-note">
|
|
2648
|
+
Two shapes cover almost every endpoint. A <strong>fixed credential</strong> — an API key or a
|
|
2649
|
+
long-lived token — goes in <code>headers</code>, which <code>odataAdapter</code> and
|
|
2650
|
+
<code>restAdapter</code> send on every request. A credential that <strong>expires</strong> —
|
|
2651
|
+
a short-lived bearer token you refresh — goes in a custom <code>fetch</code>, which is the one
|
|
2652
|
+
place that can mint a fresh value per request. <code>dfqlAdapter</code> takes its
|
|
2653
|
+
<code>token</code> directly, and <code>headers</code> for anything a gateway adds on top.
|
|
2654
|
+
</p>
|
|
2655
|
+
<p class="section-note">
|
|
2656
|
+
<strong>A fixed token in <code>headers</code>.</strong> The map is sent on every request, so an
|
|
2657
|
+
<code>Authorization</code> header authenticates the whole grid. Below, a custom
|
|
2658
|
+
<code>fetch</code> stands in for the network only so the example can prove the header arrived:
|
|
2659
|
+
</p>
|
|
2660
|
+
<pre data-run="js" data-expect="Bearer static-token-123" data-covers="export:odataAdapter"><code><span class="kw">const</span> { odataAdapter } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2661
|
+
|
|
2662
|
+
<span class="kw">let</span> seen;
|
|
2663
|
+
<span class="kw">const</span> adapter = odataAdapter({
|
|
2664
|
+
url: 'https://api.example.com/Orders',
|
|
2665
|
+
<span class="cmt">// A fixed credential authenticates every request.</span>
|
|
2666
|
+
headers: { Authorization: 'Bearer static-token-123' },
|
|
2667
|
+
<span class="cmt">// Only here to capture what the adapter sent; in a browser, omit it.</span>
|
|
2668
|
+
fetch: <span class="kw">async</span> (url, init) => {
|
|
2669
|
+
seen = init.headers.Authorization;
|
|
2670
|
+
<span class="kw">return</span> { ok: <span class="kw">true</span>, json: <span class="kw">async</span> () => ({ value: [], '@odata.count': 0 }) };
|
|
2671
|
+
},
|
|
2672
|
+
});
|
|
2673
|
+
|
|
2674
|
+
<span class="kw">await</span> adapter.execute({ range: { start: 0, end: 20 } }, {});
|
|
2675
|
+
<span class="kw">return</span> seen; <span class="cmt">// the header reached the request</span></code></pre>
|
|
2676
|
+
|
|
2677
|
+
<p class="section-note">
|
|
2678
|
+
<strong>An expiring token in a custom <code>fetch</code>.</strong> A token with a lifetime cannot
|
|
2679
|
+
sit in a fixed map, because the map is read once and the token outlives no request that matters.
|
|
2680
|
+
A custom <code>fetch</code> is called afresh for every request, so it is where you refresh the
|
|
2681
|
+
credential and set the header on the outgoing call:
|
|
2682
|
+
</p>
|
|
2683
|
+
<pre data-run="js" data-expect="Bearer token-2" data-covers="export:restAdapter"><code><span class="kw">const</span> { restAdapter } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2684
|
+
|
|
2685
|
+
<span class="cmt">// Stands in for a token service that hands out a new value each time.</span>
|
|
2686
|
+
<span class="kw">let</span> issued = 0;
|
|
2687
|
+
<span class="kw">const</span> freshToken = <span class="kw">async</span> () => `token-${++issued}`;
|
|
2688
|
+
|
|
2689
|
+
<span class="kw">let</span> lastAuth;
|
|
2690
|
+
<span class="kw">const</span> adapter = restAdapter({
|
|
2691
|
+
url: '/api/orders',
|
|
2692
|
+
fetch: <span class="kw">async</span> (url, init) => {
|
|
2693
|
+
<span class="cmt">// Refreshed per request, then merged over whatever headers the adapter set.</span>
|
|
2694
|
+
<span class="kw">const</span> headers = { ...init.headers, Authorization: `Bearer ${<span class="kw">await</span> freshToken()}` };
|
|
2695
|
+
lastAuth = headers.Authorization;
|
|
2696
|
+
<span class="kw">return</span> { ok: <span class="kw">true</span>, json: <span class="kw">async</span> () => ({ rows: [], total: 0 }) };
|
|
2697
|
+
},
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
<span class="kw">await</span> adapter.execute({ range: { start: 0, end: 20 } }, {}); <span class="cmt">// token-1</span>
|
|
2701
|
+
<span class="kw">await</span> adapter.execute({ range: { start: 20, end: 40 } }, {}); <span class="cmt">// token-2</span>
|
|
2702
|
+
<span class="kw">return</span> lastAuth; <span class="cmt">// a fresh token on the second request</span></code></pre>
|
|
2703
|
+
|
|
2497
2704
|
<h4 id="pushdown-cookbook">Wiring it to the API you already have</h4>
|
|
2498
2705
|
<p class="section-note">
|
|
2499
2706
|
Most data sits behind a service someone on your team wrote. The adapter below sends four
|
|
@@ -2652,7 +2859,32 @@ app.get('/api/orders', async (req, res) => {
|
|
|
2652
2859
|
columns: [{ field: 'rep' }, { field: 'total' }],
|
|
2653
2860
|
});
|
|
2654
2861
|
|
|
2655
|
-
byRep.on('row:
|
|
2862
|
+
byRep.on('row:clicked', (e) => byRep.crossFilter.toggle(e.key));</code></pre>
|
|
2863
|
+
<p class="section-note">
|
|
2864
|
+
The event is <code>row:clicked</code>, not <code>row:click</code>. A handler bound to the wrong
|
|
2865
|
+
name subscribes without error and never fires, so this example is executed on every build to
|
|
2866
|
+
keep the name honest: it wires the same handler to a stand-in grid, emits the event, and checks
|
|
2867
|
+
the click reached the cross-filter.
|
|
2868
|
+
</p>
|
|
2869
|
+
<pre data-run="js" data-covers="event:row:clicked" data-expect="EMEA"><code>// A stand-in for the grid's event bus and cross-filter, so the wiring above can
|
|
2870
|
+
// be executed here without a DOM. The names are the product's own.
|
|
2871
|
+
const listeners = {};
|
|
2872
|
+
const filtered = [];
|
|
2873
|
+
const grid = {
|
|
2874
|
+
on: (name, fn) => { (listeners[name] = listeners[name] || []).push(fn); },
|
|
2875
|
+
emit: (name, e) => { for (const fn of listeners[name] || []) fn(e); },
|
|
2876
|
+
crossFilter: { toggle: (key) => { filtered.push(key); } },
|
|
2877
|
+
};
|
|
2878
|
+
|
|
2879
|
+
// The line from the example, verbatim in its event name.
|
|
2880
|
+
grid.on('row:clicked', (e) => { if (e.key) grid.crossFilter.toggle(e.key); });
|
|
2881
|
+
|
|
2882
|
+
// A click on a rep row. The wrong name — 'row:click' — would reach no handler,
|
|
2883
|
+
// and this block would produce '' instead of the key.
|
|
2884
|
+
grid.emit('row:clicked', { key: 'EMEA' });
|
|
2885
|
+
grid.emit('row:click', { key: 'US' });
|
|
2886
|
+
|
|
2887
|
+
return filtered.join(',');</code></pre>
|
|
2656
2888
|
<div class="table-wrap">
|
|
2657
2889
|
<table>
|
|
2658
2890
|
<thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
|
|
@@ -3092,6 +3324,63 @@ const chart = createChart({
|
|
|
3092
3324
|
grid.filters.set({ col: 'region', op: 'eq', value: point.x });
|
|
3093
3325
|
});</code></pre>
|
|
3094
3326
|
|
|
3327
|
+
<h3 id="chart-a-range">Chart a selected range</h3>
|
|
3328
|
+
<p>A user who drags out a block of cells — a text column and the numbers beside it — is asking a question a spreadsheet answers with one gesture: chart this. <code>chartRange</code> is that gesture. It reads the selected range, derives the chart from its shape, and returns the same live <code>Chart</code> <code>createChart</code> does, so nothing about it is a second kind of chart.</p>
|
|
3329
|
+
<ul>
|
|
3330
|
+
<li><strong>The leading text column becomes the categories</strong>, and the numeric columns beside it become the measures. One number is a bar chart; several are a grouped bar. A block of pure numbers charts against the row position.</li>
|
|
3331
|
+
<li><strong>Hidden and unreadable columns are never charted.</strong> The range is resolved against the columns the reader can actually see, so a chart never carries a value the grid itself would not show.</li>
|
|
3332
|
+
<li><strong>It is bound to the range's own rows</strong>, filtered as the grid is filtered — the band the rectangle covers, not the whole sheet.</li>
|
|
3333
|
+
<li><strong>The type is a sensible default you can change:</strong> <code>chart.update({ type: 'line' })</code>, or pass <code>type</code> up front.</li>
|
|
3334
|
+
</ul>
|
|
3335
|
+
<p>The derivation is pure, so a menu can ask what a range <em>would</em> chart as — the type, the dimension, the measures — before anyone draws it. <code>deriveRangeSpec</code> answers that, and <code>chartRange</code> then draws exactly it. This block proves the shape rule on a real grid; drawing needs a container, shown below it.</p>
|
|
3336
|
+
<pre data-run="js" data-expect="Region|revenue,cost|combo|drawn" data-covers="export:deriveRangeSpec export:canChartRange config:rangeChart"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3337
|
+
<span class="kw">const</span> { deriveRangeSpec, canChartRange } = <span class="kw">await</span> import('../packages/modules/charts/index.js');
|
|
3338
|
+
|
|
3339
|
+
<span class="kw">let</span> charted = null;
|
|
3340
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
3341
|
+
columns: [
|
|
3342
|
+
{ field: 'region', type: 'text' },
|
|
3343
|
+
{ field: 'revenue', type: 'number', total: 'sum' },
|
|
3344
|
+
{ field: 'cost', type: 'number', total: 'sum' },
|
|
3345
|
+
],
|
|
3346
|
+
rows: [
|
|
3347
|
+
{ id: 1, region: 'EMEA', revenue: 300, cost: 120 },
|
|
3348
|
+
{ id: 2, region: 'AMER', revenue: 500, cost: 240 },
|
|
3349
|
+
{ id: 3, region: 'APAC', revenue: 200, cost: 90 },
|
|
3350
|
+
],
|
|
3351
|
+
rowKey: 'id',
|
|
3352
|
+
<span class="cmt">// Opt in, with the handler that draws — the page's link to modules/charts.</span>
|
|
3353
|
+
<span class="cmt">// The menu and Alt+F1 call it with the grid and the selected range.</span>
|
|
3354
|
+
rangeChart(grid, range) { charted = range; },
|
|
3355
|
+
});
|
|
3356
|
+
|
|
3357
|
+
<span class="cmt">// The rectangle the user dragged: three rows, the text column and both numbers.</span>
|
|
3358
|
+
<span class="kw">const</span> range = { startRow: 0, endRow: 2, columns: ['region', 'revenue', 'cost'] };
|
|
3359
|
+
|
|
3360
|
+
<span class="cmt">// The leading text column is the dimension; the two numeric columns are the</span>
|
|
3361
|
+
<span class="cmt">// measures, so the default is a grouped bar (a combo of bar marks).</span>
|
|
3362
|
+
<span class="kw">const</span> plan = deriveRangeSpec(grid, { range });
|
|
3363
|
+
|
|
3364
|
+
<span class="cmt">// What the menu action does when the reader picks "Chart selection".</span>
|
|
3365
|
+
<span class="kw">const</span> handler = grid.get('rangeChart');
|
|
3366
|
+
<span class="kw">if</span> (canChartRange(grid, { range })) handler(grid, range);
|
|
3367
|
+
|
|
3368
|
+
<span class="kw">return</span> [plan.x === 'region' ? 'Region' : plan.x,
|
|
3369
|
+
plan.measures.join(','),
|
|
3370
|
+
plan.type,
|
|
3371
|
+
charted === range ? 'drawn' : 'no'].join('|');</code></pre>
|
|
3372
|
+
<p>Drawing is one more call. <code>chartRange</code> takes the container, derives the spec and returns the live chart — or null when the range has no number to plot. The cell menu offers <strong>Chart selection</strong>, and <strong>Alt+F1</strong> triggers it from the keyboard, when the grid is configured with <code>rangeChart</code>. Because the charts module is optional and the grid draws no charts itself, the config carries the handler — a function, or <code>{ onChart }</code>, called <code>(grid, range)</code> — which is where a page wires the two together:</p>
|
|
3373
|
+
<pre><code>import { chartRange } from '@toclocoinc/lattice-grid/modules/charts';
|
|
3374
|
+
|
|
3375
|
+
createGrid(el, {
|
|
3376
|
+
columns, rows,
|
|
3377
|
+
rangeChart(grid, range) {
|
|
3378
|
+
<span class="cmt">// A grouped bar by default; pass `type` to draw it as something else.</span>
|
|
3379
|
+
const chart = chartRange(grid, { container: '#chart', range });
|
|
3380
|
+
if (chart) chart.update({ scheme: 'colourblind' });
|
|
3381
|
+
},
|
|
3382
|
+
});</code></pre>
|
|
3383
|
+
|
|
3095
3384
|
<h3>Maps</h3>
|
|
3096
3385
|
<p>A <code>geomap</code> takes an ISO code from one column and a value from another. Alpha-2, alpha-3 and numeric codes are all accepted, and continent codes draw a continent map without any outline data. Country outlines are yours to supply through <code>shapes</code>, because a world atlas is larger than the whole library and this package fetches nothing at runtime.</p>
|
|
3097
3386
|
<p>Codes that match nothing are counted and reported on the chart rather than dropped, a map missing half its data looks exactly like a map of a world where half the data is zero. The full code tables are in <a href="CHART-CODES.md">CHART-CODES.md</a>.</p>
|
|
@@ -3400,7 +3689,7 @@ createGrid(el, {
|
|
|
3400
3689
|
<h3 id="config-example">A configuration, executed</h3>
|
|
3401
3690
|
<p class="section-note">This block runs on every build. If a key here stopped being honoured, or was
|
|
3402
3691
|
renamed, the build would fail rather than the documentation quietly going stale.</p>
|
|
3403
|
-
<pre data-run="js" data-expect="2" data-covers="config:rowKey config:columns config:rows config:rowHeight config:headerHeight config:overscan config:autoHeight config:showHeader config:density config:theme config:locale config:timeZone config:title config:gridLines config:cornerRadius config:targetSize config:sampleSize config:quickFilterText config:maximise config:shortcuts config:rowReorder config:stickyGroupHeaders config:groupFooter config:totalFilteredOnly config:showTotalInHeader config:allowUnsafeTemplates config:useWorker config:sharedMemory config:workerThreshold config:columnVirtualisationAbove config:showColumnFunctions export:createHeadlessGrid"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3692
|
+
<pre data-run="js" data-expect="2" data-covers="config:rowKey config:columns config:rows config:rowHeight config:headerHeight config:overscan config:autoHeight config:showHeader config:density config:theme config:locale config:timeZone config:title config:gridLines config:cornerRadius config:targetSize config:sampleSize config:quickFilterText config:maximise config:shortcuts config:rowReorder config:stickyGroupHeaders config:groupFooter config:totalFilteredOnly config:showTotalInHeader config:aggregateChooser config:allowUnsafeTemplates config:useWorker config:sharedMemory config:workerThreshold config:columnVirtualisationAbove config:showColumnFunctions export:createHeadlessGrid"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3404
3693
|
|
|
3405
3694
|
<span class="cmt">// Every one of these is a documented configuration key, set together so the</span>
|
|
3406
3695
|
<span class="cmt">// example proves they are accepted and honoured rather than merely spelled.</span>
|
|
@@ -3416,11 +3705,43 @@ createGrid(el, {
|
|
|
3416
3705
|
shortcuts: <span class="kw">true</span>, rowReorder: <span class="kw">false</span>,
|
|
3417
3706
|
stickyGroupHeaders: <span class="kw">true</span>, groupFooter: <span class="kw">false</span>,
|
|
3418
3707
|
totalFilteredOnly: <span class="kw">false</span>, showTotalInHeader: <span class="kw">false</span>,
|
|
3708
|
+
aggregateChooser: <span class="kw">false</span>,
|
|
3419
3709
|
allowUnsafeTemplates: <span class="kw">false</span>, useWorker: <span class="kw">false</span>,
|
|
3420
3710
|
sharedMemory: <span class="kw">false</span>, workerThreshold: 100000,
|
|
3421
3711
|
columnVirtualisationAbove: 40, showColumnFunctions: <span class="kw">false</span>,
|
|
3422
3712
|
});
|
|
3423
3713
|
|
|
3714
|
+
<span class="kw">const</span> n = grid.rows.count();
|
|
3715
|
+
grid.destroy();
|
|
3716
|
+
<span class="kw">return</span> n;</code></pre>
|
|
3717
|
+
|
|
3718
|
+
<h3 id="ingest-worker-example">Non-blocking stream ingest, executed</h3>
|
|
3719
|
+
<p class="section-note">A <code>stream</code> source loaded with <code>ingest.useWorker</code> on. In a browser a
|
|
3720
|
+
chunk that clears <code>ingest.workerThreshold</code> is columnized on a Worker so the main thread
|
|
3721
|
+
is not blocked; here in Node there is no Worker, so it columnizes in-process — the same code,
|
|
3722
|
+
the same result, which is exactly what this asserts. With <code>retainSource:false</code> the
|
|
3723
|
+
grid keeps only the packed columns, so <code>rows.data()</code> returns reconstructed objects
|
|
3724
|
+
rather than the caller's own. This makes <strong>stream</strong> (and remote) ingest
|
|
3725
|
+
non-blocking; memory and paged sources still read the caller's objects on the main thread.</p>
|
|
3726
|
+
<pre data-run="js" data-expect="3" data-covers="config:ingest config:useWorker config:workerThreshold config:retainSource config:rowKey config:columns"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3727
|
+
|
|
3728
|
+
<span class="cmt">// A stream source: the producer pushes chunks of rows as it finds them.</span>
|
|
3729
|
+
<span class="kw">async</span> <span class="kw">function</span>* open() {
|
|
3730
|
+
yield { rows: [{ id: '1', city: 'Oslo', pop: 700000 }, { id: '2', city: 'Bergen', pop: 280000 }] };
|
|
3731
|
+
yield { rows: [{ id: '3', city: 'Tromsø', pop: 77000 }] };
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
3735
|
+
rowKey: 'id',
|
|
3736
|
+
columns: [{ id: 'city', field: 'city' }, { id: 'pop', field: 'pop', type: 'number' }],
|
|
3737
|
+
source: { mode: 'stream', open },
|
|
3738
|
+
<span class="cmt">// Columnize stream chunks off the main thread when a chunk is large enough,</span>
|
|
3739
|
+
<span class="cmt">// and keep only the packed columns rather than the caller's row objects.</span>
|
|
3740
|
+
ingest: { useWorker: <span class="kw">true</span>, workerThreshold: 1, retainSource: <span class="kw">false</span> },
|
|
3741
|
+
});
|
|
3742
|
+
|
|
3743
|
+
<span class="cmt">// The stream loads over async frames; wait for it to finish before counting.</span>
|
|
3744
|
+
<span class="kw">await</span> <span class="kw">new</span> Promise((resolve) => grid.on('stream:end', resolve));
|
|
3424
3745
|
<span class="kw">const</span> n = grid.rows.count();
|
|
3425
3746
|
grid.destroy();
|
|
3426
3747
|
<span class="kw">return</span> n;</code></pre>
|
|
@@ -3557,7 +3878,7 @@ grid.destroy();
|
|
|
3557
3878
|
|
|
3558
3879
|
<h3 id="module-exports-example">Every module export, executed</h3>
|
|
3559
3880
|
<p class="section-note">Nine shipped modules, each export resolved against its own barrel on every build.</p>
|
|
3560
|
-
<pre data-run="js" data-expect="
|
|
3881
|
+
<pre data-run="js" data-expect="60" data-covers="export:ContextMenu export:Messages export:Registry export:autoInit export:createGrid export:createLocalViewStorage export:createMessages export:createStat export:deltaOf export:gridElementsWithin export:hydrateTable export:mountPanel export:readTable export:toneOf export:Chart export:PALETTE export:SCHEMES export:TYPES export:createChart export:chartRange export:canChartRange export:deriveRangeSpec export:registerScheme export:resolveScheme export:schemeNames export:setDefaultScheme export:HTML_ROW_WARNING_THRESHOLD export:QUERY_CHANGED_EVENT export:SCROLL_NEAR_END_EVENT export:attach export:destroyWithin export:driveInfiniteScroll export:driveOobUpdates export:driveServerMode export:ingestResponse export:initWithin export:queryParams export:restoreStateWithin export:rowsFromFragment export:rowsFromJson export:saveStateWithin export:ATTRIBUTE_CONFIG export:EVENT_PREFIX export:GridElementController export:LatticeGrid export:TAG_NAME export:createLatticeGridElement export:defineLatticeGrid export:domEventName export:observedAttributeNames export:CONSOLE_ACTIVATION export:createDevtools export:expose export:EVENT_NAMES export:handlerName export:createLatticeGrid export:dashedName export:createLatticeAction export:Grid export:warnIfLargeHtmlPayload"><code><span class="cmt">// Every declared export of every shipped module, resolved against its own</span>
|
|
3561
3882
|
<span class="cmt">// barrel. A module that stopped exporting something fails here.</span>
|
|
3562
3883
|
<span class="kw">const</span> modules = [
|
|
3563
3884
|
[<span class="kw">await</span> import('../packages/dom/src/index.js'), [
|
|
@@ -3568,7 +3889,8 @@ grid.destroy();
|
|
|
3568
3889
|
]],
|
|
3569
3890
|
[<span class="kw">await</span> import('../packages/modules/charts/index.js'), [
|
|
3570
3891
|
'Chart', 'PALETTE', 'SCHEMES', 'TYPES',
|
|
3571
|
-
'createChart', '
|
|
3892
|
+
'createChart', 'chartRange', 'canChartRange', 'deriveRangeSpec',
|
|
3893
|
+
'registerScheme', 'resolveScheme', 'schemeNames',
|
|
3572
3894
|
'setDefaultScheme',
|
|
3573
3895
|
]],
|
|
3574
3896
|
[<span class="kw">await</span> import('../packages/modules/htmx/index.js'), [
|
|
@@ -3608,7 +3930,7 @@ grid.destroy();
|
|
|
3608
3930
|
<h3 id="nested-config-example">Nested configuration, executed</h3>
|
|
3609
3931
|
<p class="section-note">Thirteen option blocks, each key written where it belongs. Parsed and evaluated on
|
|
3610
3932
|
every build, so a key that was renamed or moved shows up here.</p>
|
|
3611
|
-
<pre data-run="js" data-expect="
|
|
3933
|
+
<pre data-run="js" data-expect="14" data-covers="config:aboveLimit config:adapter config:binary config:bucket config:bucketFn config:buckets config:cacheLimit config:cardinalityLimit config:checkbox config:coalesceMs config:commit config:confirm config:cumulative config:debounce config:decimals config:delimiter config:display config:download config:enabled config:enterMovesDown config:fetch config:fileName config:fill config:fillHandle config:follow config:format config:from config:granularity config:groupBy config:hasChildren config:headerCheckbox config:headers config:hint config:idleMs config:indexLimit config:isMaster config:join config:label config:limit config:limitPer config:lineEnding config:loadChildren config:lock config:lockMs config:markdown config:maxCachedPages config:maxDecimals config:maxRows config:me config:minDecimals config:mode config:onCreate config:open config:orient config:orphans config:pageSize config:palette config:parentKey config:path config:pendingTimeout config:placement config:processCell config:profile config:promoteToMemoryBelow config:provider config:quote config:ranges config:removeMs config:retainSource config:roster config:scale config:select config:space config:start config:strategy config:system config:target config:throttleMs config:undoDepth config:unit config:unnest config:where"><code><span class="cmt">// Placeholders for the things a real page supplies. The point of this block is</span>
|
|
3612
3934
|
<span class="cmt">// the option names: each one below is a documented key, written where it</span>
|
|
3613
3935
|
<span class="cmt">// belongs, so a key that was renamed or moved stops matching its interface.</span>
|
|
3614
3936
|
<span class="kw">const</span> source = {}, other = {}, provider = {}, compute = {}, adapter = {};
|
|
@@ -3667,14 +3989,17 @@ grid.destroy();
|
|
|
3667
3989
|
unit: 'metre', system: 'si', space: true, binary: false, format: 'auto',
|
|
3668
3990
|
display: 'yesNo', label: 'Size', hint: 'in metres' };
|
|
3669
3991
|
|
|
3992
|
+
<span class="cmt">// How rows enter the store — IngestConfig</span>
|
|
3993
|
+
<span class="kw">const</span> ingestConfig = { retainSource: <span class="kw">false</span> };
|
|
3994
|
+
|
|
3670
3995
|
<span class="kw">return</span> [derivedSourceConfig, editConfig, selectionConfig, treeConfig, detailConfig,
|
|
3671
3996
|
presenceConfig, commentConfig, pagedSourceConfig, streamSourceConfig,
|
|
3672
|
-
pushdownSourceConfig, csvExportOptions, columnFacetConfig, numberFormat].length;</code></pre>
|
|
3997
|
+
pushdownSourceConfig, csvExportOptions, columnFacetConfig, numberFormat, ingestConfig].length;</code></pre>
|
|
3673
3998
|
|
|
3674
3999
|
<h3 id="remaining-config-example">The remaining option names, executed</h3>
|
|
3675
4000
|
<p class="section-note">Set on a real grid and checked against its own diagnostics: an unrecognised key
|
|
3676
4001
|
raises <code>config.unknown</code>, so a renamed or dropped option fails here.</p>
|
|
3677
|
-
<pre data-run="js" data-expect="
|
|
4002
|
+
<pre data-run="js" data-expect="53" data-covers="config:ai config:alignedGrids config:columnDefaults config:columnGroups config:columnMenu config:columnPresets config:columnTagFilter config:comments config:components config:context config:contextMenu config:dataTypes config:detail config:diff config:edit config:environment config:facets config:formatting config:formulaFunctions config:fullWidth config:grandTotalRow config:groupPanel config:highlightOnChange config:historyBar config:hostFilter config:ingest config:licence config:pagination config:permissions config:pinnedBottomRows config:pinnedTopRows config:pipes config:pivot config:presence config:responsive config:rowClass config:rowForm config:rowStyle config:rowTemplate config:rowTransfer config:selection config:source config:state config:statusBar config:toolPanel config:totalFns config:totalOnlyChangedColumns config:tree config:typeOptions config:updates config:variants config:views config:workerUrl config:announce config:apply config:at config:background config:byKey config:compute config:config config:count config:crossFilter config:destroy config:group config:groupSelectsChildren config:groupSelectsFiltered config:height config:loaded config:order config:pageSizes config:refresh config:reload config:render config:rowLabel config:sort"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3678
4003
|
<span class="kw">const</span> { reportedWarnings } = <span class="kw">await</span> import('../packages/core/src/internal/util.js');
|
|
3679
4004
|
|
|
3680
4005
|
<span class="cmt">// Top-level configuration keys. Each is set on a real grid, and the grid is</span>
|
|
@@ -3685,7 +4010,8 @@ grid.destroy();
|
|
|
3685
4010
|
'columnPresets', 'columnTagFilter', 'comments', 'components', 'context',
|
|
3686
4011
|
'contextMenu', 'dataTypes', 'detail', 'diff', 'edit',
|
|
3687
4012
|
'environment', 'facets', 'formatting', 'formulaFunctions', 'fullWidth',
|
|
3688
|
-
'grandTotalRow', 'highlightOnChange', 'historyBar', 'hostFilter', '
|
|
4013
|
+
'grandTotalRow', 'groupPanel', 'highlightOnChange', 'historyBar', 'hostFilter', 'ingest',
|
|
4014
|
+
'licence',
|
|
3689
4015
|
'pagination', 'permissions', 'pinnedBottomRows', 'pinnedTopRows', 'pipes',
|
|
3690
4016
|
'pivot', 'presence', 'responsive', 'rowClass', 'rowForm',
|
|
3691
4017
|
'rowStyle', 'rowTemplate', 'rowTransfer', 'selection', 'source',
|
|
@@ -4096,6 +4422,7 @@ grid.destroy();
|
|
|
4096
4422
|
<tr><td class="name">x</td><td class="type">string</td><td class="desc">The category column. <small>(optional)</small></td></tr>
|
|
4097
4423
|
<tr><td class="name">y</td><td class="type">string</td><td class="desc">The measure column, for the types that take one. <small>(optional)</small></td></tr>
|
|
4098
4424
|
<tr><td class="name">series</td><td class="type">string</td><td class="desc">Splits the measure into one series per distinct value. <small>(optional)</small></td></tr>
|
|
4425
|
+
<tr><td class="name">rows</td><td class="type">object[] | ((grid: Grid) => object[])</td><td class="desc">The exact rows to chart, overriding the grid's own walk — an array, or a function returning one at draw time. `chartRange` uses it to bind a chart to the band of rows a selected range covers rather than the whole grid. <small>(optional)</small></td></tr>
|
|
4099
4426
|
<tr><td class="name">measures</td><td class="type">ChartMeasure[]</td><td class="desc">Several measures at once, for combo and candlestick. <small>(optional)</small></td></tr>
|
|
4100
4427
|
<tr><td class="name">source</td><td class="type">string</td><td class="desc">Endpoints, for sankey, chord and network. <small>(optional)</small></td></tr>
|
|
4101
4428
|
<tr><td class="name">target</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -4385,6 +4712,7 @@ grid.destroy();
|
|
|
4385
4712
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
4386
4713
|
<tbody>
|
|
4387
4714
|
<tr><td class="name">setTotal</td><td class="type">(id: string, fn: TotalName | TotalFn | null): void</td><td class="desc">Set or clear a column's totals-row reduction.</td></tr>
|
|
4715
|
+
<tr><td class="name">aggregates</td><td class="type">(id: string): TotalName[]</td><td class="desc">The aggregate names meaningful for a column, honouring its type's `totals.supported` declaration (§9.4). What the aggregate chooser offers.</td></tr>
|
|
4388
4716
|
<tr><td class="name">distinct</td><td class="type">(id: string): unknown[]</td><td class="desc">Every distinct value in a column, from the dictionary where there is one.</td></tr>
|
|
4389
4717
|
<tr><td class="name">get</td><td class="type">(id: string): ResolvedColumn | undefined</td><td class="desc"></td></tr>
|
|
4390
4718
|
<tr><td class="name">all</td><td class="type">(): ResolvedColumn[]</td><td class="desc"></td></tr>
|
|
@@ -4399,6 +4727,7 @@ grid.destroy();
|
|
|
4399
4727
|
<tr><td class="name">move</td><td class="type">(id: string, to: number): void</td><td class="desc"></td></tr>
|
|
4400
4728
|
<tr><td class="name">pin</td><td class="type">(id: string, side: 'start' | 'end' | null): void</td><td class="desc"></td></tr>
|
|
4401
4729
|
<tr><td class="name">resize</td><td class="type">(id: string, px: number): void</td><td class="desc"></td></tr>
|
|
4730
|
+
<tr><td class="name">decorate</td><td class="type">(id: string, decoration: DecorationName | DecorationSpec | null, opts?: { variant?: VariantSpec }): void</td><td class="desc">Set, change or clear a column's decoration at runtime (§8.7). Pass `null` to clear it back to plain text. Presentation config: it is not on the undo timeline and is not carried in a saved view — use `grid.formatting` for durable, view-persisted conditional styling.</td></tr>
|
|
4402
4731
|
<tr><td class="name">autoSize</td><td class="type">(ids?: string | string[]): void</td><td class="desc"></td></tr>
|
|
4403
4732
|
<tr><td class="name">fit</td><td class="type">(): void</td><td class="desc"></td></tr>
|
|
4404
4733
|
<tr><td class="name">group</td><td class="type">(ids: string | string[]): void</td><td class="desc"></td></tr>
|
|
@@ -4662,6 +4991,8 @@ grid.destroy();
|
|
|
4662
4991
|
<tr><td class="name">edge</td><td class="type">boolean</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4663
4992
|
<tr><td class="name">position</td><td class="type">'start' | 'end'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4664
4993
|
<tr><td class="name">name</td><td class="type">string | Record<string, string></td><td class="desc"><small>(optional)</small></td></tr>
|
|
4994
|
+
<tr><td class="name">iconSet</td><td class="type">IconSetName</td><td class="desc">icon only: a built-in threshold icon set, expanded to `bands`. <small>(optional)</small></td></tr>
|
|
4995
|
+
<tr><td class="name">bands</td><td class="type">IconBand[]</td><td class="desc">icon only: value bands mapped to glyphs, first match by descending `min`. <small>(optional)</small></td></tr>
|
|
4665
4996
|
<tr><td class="name">min</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4666
4997
|
<tr><td class="name">max</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4667
4998
|
<tr><td class="name">origin</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -4836,6 +5167,8 @@ grid.destroy();
|
|
|
4836
5167
|
<tr><td class="name">redo</td><td class="type">(): void</td><td class="desc"></td></tr>
|
|
4837
5168
|
<tr><td class="name">setCells</td><td class="type">(writes: { key: string; colId: string; value: unknown }[], type?: 'cell' | 'fill' | 'paste'): number</td><td class="desc"></td></tr>
|
|
4838
5169
|
<tr><td class="name">pasteInto</td><td class="type">(anchor: { key: string; colId: string }, text: string, extent?: { rows?: number; columns?: number }): number</td><td class="desc"></td></tr>
|
|
5170
|
+
<tr><td class="name">pastePreview</td><td class="type">boolean</td><td class="desc">Whether a bulk paste is previewed before it commits (`edit.pastePreview`, §12). <small>(read-only)</small></td></tr>
|
|
5171
|
+
<tr><td class="name">previewPaste</td><td class="type">(anchor: { key: string; colId: string }, text: string, extent?: { rows?: number; columns?: number }): {</td><td class="desc">Compute what a paste would change, without committing (§12). The engine behind `edit.pastePreview`: `changes` are the accepted writes with their old and new values (and whether each actually differs), `rejected` are the cells a commit would refuse, each with a reason.</td></tr>
|
|
4839
5172
|
<tr><td class="name">settle</td><td class="type">(id: string, ok: boolean, reason?: string): boolean</td><td class="desc"></td></tr>
|
|
4840
5173
|
<tr><td class="name">pending</td><td class="type">(): OpenWrite[]</td><td class="desc"></td></tr>
|
|
4841
5174
|
<tr><td class="name">status</td><td class="type">(key: string, colId: string): 'pending' | null</td><td class="desc"></td></tr>
|
|
@@ -4855,6 +5188,7 @@ grid.destroy();
|
|
|
4855
5188
|
<tr><td class="name">commit</td><td class="type">(write: PendingWrite) => unknown</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4856
5189
|
<tr><td class="name">confirm</td><td class="type">'auto' | 'manual'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4857
5190
|
<tr><td class="name">pendingTimeout</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5191
|
+
<tr><td class="name">pastePreview</td><td class="type">boolean</td><td class="desc">Show a preview of what a bulk paste will change before it commits (§12), with confirm/cancel. Off by default: a paste commits straight away, exactly as it always has. When on, a paste into more than one cell first opens a dialog listing every cell that changes (old → new) and every cell that would be rejected (permission, data-type, read-only); confirm commits precisely that set through the ordinary edit path, cancel commits nothing. <small>(optional)</small></td></tr>
|
|
4858
5192
|
</tbody>
|
|
4859
5193
|
</table>
|
|
4860
5194
|
</div>
|
|
@@ -5188,6 +5522,7 @@ grid.destroy();
|
|
|
5188
5522
|
<tr><td class="name">rows</td><td class="type">unknown[]</td><td class="desc">The data, for a memory grid. Use `source` for anything fetched. <small>(optional)</small></td></tr>
|
|
5189
5523
|
<tr><td class="name">rowKey</td><td class="type">string | ((row: unknown) => string)</td><td class="desc">What identifies a row. Everything that survives a refresh (selection, expansion, and edits in flight) is keyed on it, so it must be stable and unique. A derived grid defaults to its own derived key. <small>(optional)</small></td></tr>
|
|
5190
5524
|
<tr><td class="name">source</td><td class="type">SourceConfig</td><td class="desc">Where rows come from: memory, paged, remote, stream or derived. <small>(optional)</small></td></tr>
|
|
5525
|
+
<tr><td class="name">ingest</td><td class="type">IngestConfig</td><td class="desc">How rows are ingested into the column store. <small>(optional)</small></td></tr>
|
|
5191
5526
|
<tr><td class="name">columnDefaults</td><td class="type">Column</td><td class="desc">Applied to every column before its own settings. <small>(optional)</small></td></tr>
|
|
5192
5527
|
<tr><td class="name">columnPresets</td><td class="type">Record<string, Column></td><td class="desc">Named bundles of column settings, referenced by a column's `preset`. <small>(optional)</small></td></tr>
|
|
5193
5528
|
<tr><td class="name">dataTypes</td><td class="type">Record<string, DataType></td><td class="desc">Your own data types, alongside the built-in catalogue. <small>(optional)</small></td></tr>
|
|
@@ -5244,10 +5579,12 @@ grid.destroy();
|
|
|
5244
5579
|
<tr><td class="name">totalFilteredOnly</td><td class="type">boolean</td><td class="desc">Total what the filters left rather than the whole set. <small>(optional)</small></td></tr>
|
|
5245
5580
|
<tr><td class="name">totalOnlyChangedColumns</td><td class="type">boolean</td><td class="desc">On a change, recompute only the totals whose column moved. <small>(optional)</small></td></tr>
|
|
5246
5581
|
<tr><td class="name">showTotalInHeader</td><td class="type">boolean</td><td class="desc">Put the total in the header rather than a footer row. <small>(optional)</small></td></tr>
|
|
5582
|
+
<tr><td class="name">aggregateChooser</td><td class="type">boolean</td><td class="desc">Let the user pick a column's reduction from the column menu. On, the totalling entry becomes an "Aggregate" submenu offering the aggregates the column's type says are meaningful (§9.4); off, the menu keeps its plain "Total this column" toggle. Off by default, so an existing grid is unchanged. <small>(optional)</small></td></tr>
|
|
5247
5583
|
<tr><td class="name">columnVirtualisationAbove</td><td class="type">number</td><td class="desc">Render only the visible columns once there are more than this many. <small>(optional)</small></td></tr>
|
|
5248
5584
|
<tr><td class="name">statusBar</td><td class="type">boolean | { panels?: string[] }</td><td class="desc">The bar beneath the grid, and which panels it carries. <small>(optional)</small></td></tr>
|
|
5249
5585
|
<tr><td class="name">contextMenu</td><td class="type">boolean | ((p: CellMenuParams, defaults: MenuItem[]) => MenuItem[] | void)</td><td class="desc">The cell right-click menu. A function supplies custom items; `false` suppresses it entirely, which is what a read-only grid wants, the default menu offers Paste, Clear and Fill down. <small>(optional)</small></td></tr>
|
|
5250
5586
|
<tr><td class="name">columnMenu</td><td class="type">boolean | ((p: ColumnMenuParams, defaults: MenuItem[]) => MenuItem[] | void)</td><td class="desc">The header's 3-dot menu, and the right-click menu on a column heading. `false` suppresses both. A function supplies custom items, receiving the grid's own so it can add to them rather than reproduce them. Default true. <small>(optional)</small></td></tr>
|
|
5587
|
+
<tr><td class="name">rangeChart</td><td class="type"></td><td class="desc">Chart a selected cell range — the spreadsheet "chart this selection" gesture. Off by default, so a grid opts in. The DOM layer draws no charts itself — the charts module is optional and loaded by the host — so this is where the host wires the two together: a function, or an object carrying `onChart`, is called with the grid and the selected range when the reader chooses "Chart selection" from the cell menu. The handler typically calls `chartRange` from `lattice-grid/modules/charts`. `true` offers the item and emits nothing extra; supply a handler to have it actually draw. <small>(optional)</small></td></tr>
|
|
5251
5588
|
<tr><td class="name">shortcuts</td><td class="type">boolean</td><td class="desc">The `?` keyboard shortcut overlay. `false` suppresses it, for a host that wants `?` for itself. Default true. <small>(optional)</small></td></tr>
|
|
5252
5589
|
<tr><td class="name">rowReorder</td><td class="type">boolean | { column?: string }</td><td class="desc">Let a user reorder rows by dragging a handle, or with Alt+Shift+Up/Down. `true` puts the handle in the first visible column; `{ column }` names a different one. The move reorders your data and emits `row:moved`; persisting it is yours, and `rows.data()` afterwards is the new order. Refused, with a reason announced, while a sort, filter or grouping is active, the position a row is dropped at has no single meaning in the underlying order then. <small>(optional)</small></td></tr>
|
|
5253
5590
|
<tr><td class="name">rowTransfer</td><td class="type">boolean | {</td><td class="desc">Let rows be dragged out of this grid, into it, or both. Off by default: rows leaving a grid is a data change a host has to want, and a mis-drag that silently removed one has no gesture a user would think to undo. `send` and `receive` are both on when the option is present, so one-way is expressed by turning off the direction you do not want, a source grid is `{ receive: false }` and a target is `{ send: false }`. `mode: 'copy'` leaves the row where it was. `group` restricts exchange to grids sharing the same name, so two unrelated grids on a page do not accept each other's rows. The source needs `rowReorder` as well, since that is what draws the handle a drag starts from. <small>(optional)</small></td></tr>
|
|
@@ -5258,6 +5595,7 @@ grid.destroy();
|
|
|
5258
5595
|
<tr><td class="name">rowClass</td><td class="type">string | string[] | ((p: RowStyleParams) => string | string[])</td><td class="desc">A class, or classes, for every row. Re-evaluated on each repaint. <small>(optional)</small></td></tr>
|
|
5259
5596
|
<tr><td class="name">rowStyle</td><td class="type">CellStyle | ((p: RowStyleParams) => CellStyle)</td><td class="desc">Inline styles for every row. Camel-case or hyphenated property names. <small>(optional)</small></td></tr>
|
|
5260
5597
|
<tr><td class="name">toolPanel</td><td class="type">boolean | {</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5598
|
+
<tr><td class="name">groupPanel</td><td class="type">boolean | {</td><td class="desc">A drag-and-drop group-by strip above the column header — the pattern AG Grid calls the row-group panel. Drag a column heading into it to group by that column; the active groups show as removable, reorderable chips, and reordering the chips changes the nesting order. It is keyboard-operable (arrows navigate, Shift+arrow reorders, Delete ungroups, and an add control groups any column), and every change is announced through the live region, which is why it also addresses the drag-only complaint of BACKLOG-0000429. Off by default and non-breaking, matching `toolPanel`. It drives the same grouping model as `grid.columns.group()`; it reimplements nothing. <small>(optional)</small></td></tr>
|
|
5261
5599
|
<tr><td class="name">quickFilterText</td><td class="type">string</td><td class="desc">The quick filter's initial text. <small>(optional)</small></td></tr>
|
|
5262
5600
|
<tr><td class="name">permissions</td><td class="type">PermissionPolicy</td><td class="desc">Per-column read/write/hidden policy. A usability control, not a security boundary: hidden data is still resident in the store. Enforce the same policy server-side with `permittedColumns` / `permittedExport`. <small>(optional)</small></td></tr>
|
|
5263
5601
|
<tr><td class="name">diff</td><td class="type">{</td><td class="desc">Prior state for diff and audit mode. <small>(optional)</small></td></tr>
|
|
@@ -5365,6 +5703,31 @@ grid.destroy();
|
|
|
5365
5703
|
</tbody>
|
|
5366
5704
|
</table>
|
|
5367
5705
|
</div>
|
|
5706
|
+
<h3 id="type-IconBand">IconBand</h3>
|
|
5707
|
+
<p class="section-note">One band of a threshold icon set. A value clears a band when it is at least `min`; the highest band it clears wins. Omit `min` on the last band to make it the catch-all. `label` is what assistive technology announces for the glyph, so a screen-reader user hears the band's meaning, not only the value.</p>
|
|
5708
|
+
<div class="table-wrap">
|
|
5709
|
+
<table>
|
|
5710
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5711
|
+
<tbody>
|
|
5712
|
+
<tr><td class="name">min</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5713
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc"></td></tr>
|
|
5714
|
+
<tr><td class="name">label</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5715
|
+
<tr><td class="name">variant</td><td class="type">VariantName</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5716
|
+
</tbody>
|
|
5717
|
+
</table>
|
|
5718
|
+
</div>
|
|
5719
|
+
<h3 id="type-IngestConfig">IngestConfig</h3>
|
|
5720
|
+
<p class="section-note">How rows are ingested into the column store.</p>
|
|
5721
|
+
<div class="table-wrap">
|
|
5722
|
+
<table>
|
|
5723
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5724
|
+
<tbody>
|
|
5725
|
+
<tr><td class="name">retainSource</td><td class="type">boolean</td><td class="desc">Retain the caller's row objects by reference so identity round-trips. Default `true`, the historical behaviour: `rows.data()` returns the exact objects you supplied, `row === sourceObject` holds, and a custom renderer reading `row.sourceObject` works. Set `false` to keep only the packed columns and reconstruct a plain row object from them on demand. This drops roughly half the resident footprint, but changes three behaviours: `rows.data()` returns freshly reconstructed objects (new object each call, so `row === sourceObject` no longer holds), a custom renderer that reaches for `row.sourceObject` gets a reconstruction rather than the original, and equality against a row becomes value-based. The stored values are unchanged, so `get()`, `byKey()`, `value()` and `values()` are unaffected. <small>(optional)</small></td></tr>
|
|
5726
|
+
<tr><td class="name">useWorker</td><td class="type">boolean</td><td class="desc">Columnize `stream`-source ingest on a Worker so a large load does not block the main thread. Default `false`. When on, an arriving chunk that clears {@link IngestConfig.workerThreshold} is packed into typed column buffers on the Worker; the main thread merges the finished buffers into the store and renders, without running the per-field extraction pass that otherwise dominates ingest. This makes **stream** ingest non-blocking (remote sources already are). Memory and paged sources cannot be made non-blocking this way — the main thread must read the caller's own row objects — and are unaffected. The effect composes with `retainSource: false`: with it off the source keeps no caller-object array on the main thread at all, so the load is both non-blocking and lighter on memory. A column that reads through a closure — a `date` column's storage conversion, or a computed column — cannot cross the Worker boundary, so a grid with any such column columnizes on the main thread and says so once. Falls back silently to the main thread wherever a Worker cannot be created. <small>(optional)</small></td></tr>
|
|
5727
|
+
<tr><td class="name">workerThreshold</td><td class="type">number</td><td class="desc">Row count in a single stream chunk at or above which columnization is offloaded to the Worker when {@link IngestConfig.useWorker} is on. Default `10000`. A smaller first chunk is packed on the main thread, where the cost is trivial and the postMessage round trip would only add latency to time-to-first-row. <small>(optional)</small></td></tr>
|
|
5728
|
+
</tbody>
|
|
5729
|
+
</table>
|
|
5730
|
+
</div>
|
|
5368
5731
|
<h3 id="type-LicenceApi">LicenceApi</h3>
|
|
5369
5732
|
<div class="table-wrap">
|
|
5370
5733
|
<table>
|
|
@@ -5427,7 +5790,7 @@ grid.destroy();
|
|
|
5427
5790
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5428
5791
|
<tbody>
|
|
5429
5792
|
<tr><td class="name">name</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5430
|
-
<tr><td class="name">icon</td><td class="type">string</td><td class="desc"
|
|
5793
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">An icon shown in the slot before the label. Three forms, told apart without a second option so existing definitions keep working: a registered sprite name (`'download'`), a single character or emoji (`'↑'`), or author-trusted element markup (`'<i class="fa-light fa-download"></i>'`), which is rendered as an element rather than shown as text. Markup is inserted into the icon slot only — never the label — at the same trust as `action`. <small>(optional)</small></td></tr>
|
|
5431
5794
|
<tr><td class="name">shortcut</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5432
5795
|
<tr><td class="name">action</td><td class="type">() => void</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5433
5796
|
<tr><td class="name">disabled</td><td class="type">boolean</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -6205,6 +6568,7 @@ grid.destroy();
|
|
|
6205
6568
|
<tr><td class="name">grid</td><td class="type">Grid</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6206
6569
|
<tr><td class="name">container</td><td class="type">HTMLElement | string</td><td class="desc">An element, or a CSS selector resolved against the grid's document.</td></tr>
|
|
6207
6570
|
<tr><td class="name">title</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6571
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">An optional leading icon beside the title and value, using the same value contract as a menu item: a registered sprite name, a single character or emoji, or author-trusted element markup (`'<i class="fa-light fa-bolt"> </i>'`, an `<img>`). It lays out to the side without disturbing the change indicator, threshold bands or confidence interval; omit it for the plain tile layout. <small>(optional)</small></td></tr>
|
|
6208
6572
|
<tr><td class="name">value</td><td class="type">unknown | StatValueSpec | ((grid: Grid) => unknown)</td><td class="desc">A literal value, a spec to reduce, or a function of the grid. <small>(optional)</small></td></tr>
|
|
6209
6573
|
<tr><td class="name">footer</td><td class="type">string | ((value: unknown, grid: Grid) => string)</td><td class="desc">Text under the value, or a function of it. <small>(optional)</small></td></tr>
|
|
6210
6574
|
<tr><td class="name">baseline</td><td class="type">number | ((grid: Grid) => number)</td><td class="desc">What the value is compared against, for the change indicator. <small>(optional)</small></td></tr>
|