@toclocoinc/lattice-grid 1.15.0 → 1.17.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 +473 -14
- package/docs/api-detail.html +334 -5
- package/lattice-grid.d.ts +402 -6
- package/lattice-grid.esm.min.js +4080 -890
- package/lattice-grid.min.cjs +4080 -890
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +4080 -890
- package/modules/charts.esm.min.js +89 -4
- package/modules/devtools.esm.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +46 -4
- package/modules/htmx.esm.min.js +4080 -890
- package/modules/htmx.min.cjs +4080 -890
- package/modules/htmx.min.js +4080 -890
- 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 +4080 -890
- 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, dropSourceRows }</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 stop the store retaining them and reconstruct a row on demand — but the source layer and grid config still hold the array, so the resident footprint does not actually fall. <code>dropSourceRows: true</code> closes that gap: it releases the objects from the source layer too, so the packed columns become the only copy and the footprint drops by roughly an order of magnitude at scale. Either way <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,13 +860,15 @@ 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>
|
|
863
867
|
<tr><td class="name">alignedGrids</td><td class="type">Grid[]</td><td class="dflt">, </td><td class="desc">Other grids to stay column-aligned with. Widths, order, visibility, pinning and horizontal scroll are shared; sort, filters, selection and rows stay independent. Declare it on the grid created last. See <a href="api-detail.html#aligned-grids">Aligned grids</a>.</td></tr>
|
|
864
|
-
<tr><td class="name">stickyGroupHeaders</td><td class="type">boolean | number | { depth }</td><td class="dflt">
|
|
868
|
+
<tr><td class="name">stickyGroupHeaders</td><td class="type">boolean | number | { depth }</td><td class="dflt">false</td><td class="desc">Keep the enclosing group headings pinned above the viewport while scrolling inside a group. Off by default; <code>true</code> turns it on and stacks at most two, a number sets the cap, and each costs a row of viewport. See <a href="api-detail.html#sticky-group-headings">Sticky group headings</a>.</td></tr>
|
|
865
869
|
<tr><td class="name">gridLines</td><td class="type">boolean | 'both' | 'horizontal' | 'vertical' | 'none'</td><td class="dflt">'horizontal'</td><td class="desc">Which rules are drawn between cells. Horizontal is what the grid has always drawn; vertical rules are additive. <code>'rows'</code> and <code>'columns'</code> are accepted aliases. Only the rules between data are affected, the header underline and pinned seams are structure.</td></tr>
|
|
866
870
|
<tr><td class="name">cornerRadius</td><td class="type">boolean | number | string</td><td class="dflt">, </td><td class="desc">Round the grid's outer corners. <code>true</code> adopts the theme's radius, a number is pixels, a string is used as written.</td></tr>
|
|
871
|
+
<tr><td class="name">stripedRows</td><td class="type">boolean</td><td class="dflt">false</td><td class="desc">Shade alternate data rows (zebra striping). Strictly opt-in, so an existing grid is unchanged on upgrade. Parity follows each row's logical index, so a stripe survives a scroll; group headings, footers and the grand total are never striped; selection and hover still win. Uses the theme's <code>--lattice-surface-alt</code>, so dark, high-contrast and terminal come for free.</td></tr>
|
|
867
872
|
<tr><td class="name">columnTagFilter</td><td class="type">boolean | { multiple, label }</td><td class="dflt">, </td><td class="desc">A bar above the headings for showing only the columns carrying a chosen tag. Draws nothing unless some column has <code>tags</code>. See <a href="api-detail.html#column-tags">Column tags</a>.</td></tr>
|
|
868
873
|
<tr><td class="name">rowTemplate</td><td class="type">string | { template, cardsPerRow, maxCardWidth, gap, className, role, itemRole }</td><td class="dflt">, </td><td class="desc">Draw each row with a template instead of dividing it into columns, a card list, a feed, a search-result list. Compiles once; binds with <code>{{data.field}}</code>. <code>cardsPerRow</code> or <code>maxCardWidth</code> puts several on a line. The pipeline underneath is unchanged. See <a href="api-detail.html#cards">Cards, lists and feeds</a>.</td></tr>
|
|
869
874
|
<tr><td class="name">responsive</td><td class="type">{ maxWidth, template, rowHeight }</td><td class="dflt">, </td><td class="desc">Collapse to cards when the <em>container</em> is at or below <code>maxWidth</code> (640 by default), and return to a table above it. Sorting, filtering and export keep working. Emits <code>presentation:changed</code>. See <a href="api-detail.html#cards">Cards, lists and feeds</a>.</td></tr>
|
|
@@ -1068,7 +1073,8 @@ grid.overlay.hide();</code></pre>
|
|
|
1068
1073
|
<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
1074
|
<tr><td class="sig">pivot(ids)</td><td class="type">void</td><td class="desc"></td></tr>
|
|
1070
1075
|
<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>
|
|
1076
|
+
<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>
|
|
1077
|
+
<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
1078
|
<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
1079
|
<tr><td class="sig">state()</td><td class="type">ColumnState[]</td><td class="desc">Serialisable column state.</td></tr>
|
|
1074
1080
|
<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 +1141,8 @@ grid.overlay.hide();</code></pre>
|
|
|
1135
1141
|
<tr><td class="sig">undo() / redo()</td><td class="type">void</td><td class="desc">Depth from <code>edit.undoDepth</code>.</td></tr>
|
|
1136
1142
|
<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
1143
|
<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>
|
|
1144
|
+
<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>
|
|
1145
|
+
<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
1146
|
<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
1147
|
<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
1148
|
<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 +1150,38 @@ grid.overlay.hide();</code></pre>
|
|
|
1142
1150
|
</table>
|
|
1143
1151
|
</div>
|
|
1144
1152
|
|
|
1153
|
+
<p class="section-note">
|
|
1154
|
+
<strong>Previewing a bulk paste.</strong> A paste can rewrite dozens of cells at once, and one
|
|
1155
|
+
that lands somewhere unexpected looks exactly like one that worked. Set
|
|
1156
|
+
<code>edit.pastePreview: true</code> and a paste into more than one cell opens a confirm/cancel
|
|
1157
|
+
dialog first, listing every cell that changes (old → new) and every cell a commit
|
|
1158
|
+
would reject. It is off by default, so existing paste behaviour is unchanged.
|
|
1159
|
+
<code>previewPaste</code> computes that same diff without any UI:
|
|
1160
|
+
</p>
|
|
1161
|
+
<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');
|
|
1162
|
+
|
|
1163
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
1164
|
+
<span class="cmt">// A per-row rule: the middle row is locked, so a paste over it is refused.</span>
|
|
1165
|
+
columns: [{ field: 'a', edit: { enabled: (p) => p.row.data.locked !== <span class="kw">true</span> } }],
|
|
1166
|
+
rows: [
|
|
1167
|
+
{ id: '0', a: 'A0', locked: <span class="kw">false</span> },
|
|
1168
|
+
{ id: '1', a: 'A1', locked: <span class="kw">true</span> },
|
|
1169
|
+
{ id: '2', a: 'A2', locked: <span class="kw">false</span> },
|
|
1170
|
+
],
|
|
1171
|
+
rowKey: 'id',
|
|
1172
|
+
<span class="cmt">// Opt in: off by default, so a plain paste is unaffected.</span>
|
|
1173
|
+
edit: { enabled: <span class="kw">true</span>, pastePreview: <span class="kw">true</span> },
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
<span class="cmt">// What *would* happen — nothing is committed yet.</span>
|
|
1177
|
+
<span class="kw">const</span> preview = grid.edit.previewPaste({ key: '0', colId: 'a' }, 'X\nY\nZ');
|
|
1178
|
+
|
|
1179
|
+
<span class="cmt">// Confirming is setCells of the changes — the ordinary paste path.</span>
|
|
1180
|
+
grid.edit.setCells(preview.changes.map((c) => ({ key: c.key, colId: c.colId, value: c.newValue })), 'paste');
|
|
1181
|
+
|
|
1182
|
+
<span class="kw">const</span> changed = preview.changes.filter((c) => c.changed).length;
|
|
1183
|
+
<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>
|
|
1184
|
+
|
|
1145
1185
|
<h2 id="form">grid.form</h2>
|
|
1146
1186
|
<p class="section-note">
|
|
1147
1187
|
The row edit form, a drawer or dialog holding one control per field. Present whether or not
|
|
@@ -1653,6 +1693,38 @@ grid.formatting.restat(); // re-derive every threshold from the
|
|
|
1653
1693
|
<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
1694
|
<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
1695
|
|
|
1696
|
+
<h3>Runtime decorations: data bars and icon sets on demand</h3>
|
|
1697
|
+
<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>
|
|
1698
|
+
<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');
|
|
1699
|
+
|
|
1700
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
1701
|
+
columns: [{ field: 'score' }, { field: 'trend' }],
|
|
1702
|
+
rows: [{ id: '1', score: 72, trend: 8 }],
|
|
1703
|
+
rowKey: 'id',
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
<span class="cmt">// A data bar, sized 0..100, set at runtime.</span>
|
|
1707
|
+
grid.columns.decorate('score', { type: 'bar', min: 0, max: 100 });
|
|
1708
|
+
<span class="kw">const</span> bar = grid.columns.get('score').cell.decoration.type;
|
|
1709
|
+
|
|
1710
|
+
<span class="cmt">// A built-in three-arrow icon set on the trend column, with its own bands.</span>
|
|
1711
|
+
grid.columns.decorate('trend', { type: 'icon', bands: [
|
|
1712
|
+
{ min: 0, icon: 'chevronUp', label: 'increasing', variant: 'success' },
|
|
1713
|
+
{ icon: 'chevronDown', label: 'decreasing', variant: 'danger' },
|
|
1714
|
+
] });
|
|
1715
|
+
<span class="kw">const</span> set = grid.columns.get('trend').cell.decoration.type === 'icon' ? 'arrows' : 'none';
|
|
1716
|
+
<span class="kw">const</span> band = grid.columns.get('trend').cell.decoration.bands[0].label;
|
|
1717
|
+
|
|
1718
|
+
<span class="cmt">// Clearing a decoration returns the column to plain text.</span>
|
|
1719
|
+
grid.columns.decorate('score', <span class="kw">null</span>);
|
|
1720
|
+
<span class="kw">const</span> cleared = grid.columns.get('score').cell.decoration === <span class="kw">undefined</span> ? 'cleared' : 'still-set';
|
|
1721
|
+
|
|
1722
|
+
<span class="cmt">// A runtime colour rule is the durable, view-persisted sibling.</span>
|
|
1723
|
+
grid.formatting.add('score', { when: { op: 'lt', value: 50 }, style: { background: '#fdecea' } });
|
|
1724
|
+
<span class="kw">const</span> painted = grid.formatting.styleFor('score', 20) ? 'painted' : 'plain';
|
|
1725
|
+
|
|
1726
|
+
<span class="kw">return</span> [bar, set, band, cleared, painted].join('|');</code></pre>
|
|
1727
|
+
|
|
1656
1728
|
<h2 id="statistics">grid.statistics</h2>
|
|
1657
1729
|
<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
1730
|
<pre><code>grid.statistics.profile('margin');
|
|
@@ -2730,6 +2802,152 @@ app.get('/api/orders', async (req, res) => {
|
|
|
2730
2802
|
<code>createPushdownSource</code> switches to fetching everything and paging from what it holds.
|
|
2731
2803
|
</p>
|
|
2732
2804
|
|
|
2805
|
+
<h4 id="pushdown-fulldataset">Whole-dataset statistics: <code>fullDataset</code></h4>
|
|
2806
|
+
<p class="section-note">
|
|
2807
|
+
A windowed source computes a total, statistic or group over the <em>loaded window</em> — the
|
|
2808
|
+
rows on screen — not the whole matching set, unless residual work already forced a whole-result
|
|
2809
|
+
fetch. “Median revenue” in the footer becomes the median of ~200 rows, wrong and
|
|
2810
|
+
looking right. <code>fullDataset.enabled</code> makes the whole-result fetch <strong>sticky and
|
|
2811
|
+
explicit</strong>: the entire matching set is held client-side once per query and every window,
|
|
2812
|
+
total and statistic is served from it, so the figures are computed over everything. It reuses
|
|
2813
|
+
the same whole-result path residual work already takes — <code>needsAll</code> — rather than a
|
|
2814
|
+
parallel mechanism. It is off by default and strictly opt-in. For a <code>restAdapter</code>,
|
|
2815
|
+
which cannot compute, it is the only way to get a correct whole-dataset statistic at all.
|
|
2816
|
+
</p>
|
|
2817
|
+
<p class="section-note">
|
|
2818
|
+
<strong>Memory-guarded, refused loudly.</strong> A matching set past <code>maxRows</code> or
|
|
2819
|
+
<code>maxBytesEstimate</code> is <strong>refused</strong> — thrown, surfaced as a
|
|
2820
|
+
<code>source:error</code> with no rows shown — never silently truncated. Presenting a fraction
|
|
2821
|
+
as the whole is the exact failure whole-dataset pull exists to prevent, so it is never the
|
|
2822
|
+
failure mode of the fix itself. Narrow the filter or raise the limit.
|
|
2823
|
+
</p>
|
|
2824
|
+
<div class="table-wrap">
|
|
2825
|
+
<table>
|
|
2826
|
+
<thead><tr><th>Key</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2827
|
+
<tbody>
|
|
2828
|
+
<tr><td class="name">enabled</td><td class="type">boolean</td><td class="desc"><code>false</code></td><td class="desc">Hold the whole matching set client-side and serve every window, total and statistic from it.</td></tr>
|
|
2829
|
+
<tr><td class="name">maxRows</td><td class="type">number</td><td class="desc"><code>1_000_000</code></td><td class="desc">Refuse (visible <code>source:error</code>) when the matching set is larger.</td></tr>
|
|
2830
|
+
<tr><td class="name">maxBytesEstimate</td><td class="type">number</td><td class="desc"><code>512 MB</code></td><td class="desc">Refuse past this estimated heap cost, sampled from a representative row.</td></tr>
|
|
2831
|
+
</tbody>
|
|
2832
|
+
</table>
|
|
2833
|
+
</div>
|
|
2834
|
+
<pre data-run="js" data-expect="49.5" data-covers="config:fullDataset config:enabled config:maxBytesEstimate"><code><span class="cmt">// An adapter that can page but reports the whole matching count. Without</span>
|
|
2835
|
+
<span class="cmt">// fullDataset a stat would see only the window; with it, the whole set.</span>
|
|
2836
|
+
<span class="kw">const</span> { createPushdownSource } = <span class="kw">await</span> import('../packages/core/src/source/pushdown.js');
|
|
2837
|
+
<span class="kw">const</span> all = Array.from({ length: 100 }, (_, i) => ({ id: i, amount: i }));
|
|
2838
|
+
<span class="kw">const</span> adapter = {
|
|
2839
|
+
name: 'demo',
|
|
2840
|
+
capabilities: { range: true, total: true },
|
|
2841
|
+
execute: <span class="kw">async</span> (query) => {
|
|
2842
|
+
<span class="kw">const</span> start = query.range ? query.range.start : 0;
|
|
2843
|
+
<span class="kw">const</span> end = query.range ? query.range.end : all.length;
|
|
2844
|
+
<span class="kw">return</span> { rows: all.slice(start, end), total: all.length };
|
|
2845
|
+
},
|
|
2846
|
+
};
|
|
2847
|
+
<span class="kw">const</span> source = createPushdownSource({ adapter, fullDataset: { enabled: true, maxRows: 1000, maxBytesEstimate: 5_000_000 } });
|
|
2848
|
+
<span class="cmt">// Ask for a 10-row window; fullDataset holds all 100, so the mean is the true one.</span>
|
|
2849
|
+
<span class="kw">const</span> block = <span class="kw">await</span> source.fetch({ range: { start: 0, end: 10 }, filters: null, sort: [], quick: '' });
|
|
2850
|
+
<span class="kw">const</span> held = block.total; <span class="cmt">// 100: the whole set is held, not the 10-row window</span>
|
|
2851
|
+
<span class="kw">return</span> all.reduce((s, r) => s + r.amount, 0) / held; <span class="cmt">// 49.5, the true whole-dataset mean</span></code></pre>
|
|
2852
|
+
|
|
2853
|
+
<h4 id="pushdown-aggregates">Pushing statistics down: <code>aggregates</code></h4>
|
|
2854
|
+
<p class="section-note">
|
|
2855
|
+
A DuckDB-class engine can compute a median or a standard deviation over the whole matching set
|
|
2856
|
+
far faster than pulling every row to do it here. The <code>aggregates</code> config decides, at
|
|
2857
|
+
grid setup, which statistics are computed by the engine and which by the grid. It is a
|
|
2858
|
+
<strong>design-time developer choice</strong> — fixed for the life of the grid, never a runtime
|
|
2859
|
+
toggle, never shown to an end user. Absent, every aggregate is computed client-side, so no
|
|
2860
|
+
existing grid changes behaviour.
|
|
2861
|
+
</p>
|
|
2862
|
+
<p class="section-note">
|
|
2863
|
+
Each statistic is classified <strong>IDENTICAL</strong> (the engine's result equals the grid's
|
|
2864
|
+
own kernel, verified against it) or <strong>MAY-DIFFER</strong> (the engine computes it by a
|
|
2865
|
+
method that can differ from the grid's definition). The classification drives this
|
|
2866
|
+
documentation and build-time provenance, <em>not</em> whether a stat is pushed — that is your
|
|
2867
|
+
choice. Only <code>weightedQuantile</code> is a genuine <strong>fallback</strong>: the engine
|
|
2868
|
+
cannot express the grid's midpoint convention, so it is always computed client-side.
|
|
2869
|
+
</p>
|
|
2870
|
+
<div class="table-wrap">
|
|
2871
|
+
<table>
|
|
2872
|
+
<thead><tr><th>Key</th><th>Type</th><th>Default</th><th>Description</th></tr></thead>
|
|
2873
|
+
<tbody>
|
|
2874
|
+
<tr><td class="name">default</td><td class="type">'engine' | 'client' | 'engine-if-identical'</td><td class="desc"><code>'client'</code></td><td class="desc"><code>engine</code> pushes everything the engine can express (using its method for MAY-DIFFER stats); <code>engine-if-identical</code> pushes only the verified-identical ones and keeps MAY-DIFFER client-side — the recommended setting for a windowed DuckDB source; <code>client</code> computes everything here.</td></tr>
|
|
2875
|
+
<tr><td class="name">overrides</td><td class="type">Record<stat, 'engine' | 'client'></td><td class="desc">—</td><td class="desc">Per-stat overrides that win over <code>default</code>. A stat the engine cannot express is always client-side regardless.</td></tr>
|
|
2876
|
+
</tbody>
|
|
2877
|
+
</table>
|
|
2878
|
+
</div>
|
|
2879
|
+
<p class="section-note">
|
|
2880
|
+
<strong>No mixed provenance.</strong> An engine number and a client number never appear in one
|
|
2881
|
+
result set. Aggregates are pushed only when the filter is <em>fully</em> pushed; a residual
|
|
2882
|
+
filter the engine could not apply forces <em>every</em> aggregate client-side, because an
|
|
2883
|
+
engine figure computed over a superset beside a client figure over the real set would be
|
|
2884
|
+
wrong-but-plausible. <code>lastPlan().aggregates</code> reports, per statistic, whether the
|
|
2885
|
+
engine or the client computed it and the class it was assigned — build-time inspection, not a
|
|
2886
|
+
per-figure runtime marker.
|
|
2887
|
+
</p>
|
|
2888
|
+
<p class="section-note">
|
|
2889
|
+
The classification table below is generated from the single pushdown map
|
|
2890
|
+
(<code>STAT_PUSHDOWN</code>), so it cannot drift from what the adapter actually emits:
|
|
2891
|
+
</p>
|
|
2892
|
+
<div class="table-wrap">
|
|
2893
|
+
<table id="stat-pushdown-table">
|
|
2894
|
+
<thead><tr><th>Statistic</th><th>Class</th><th>DuckDB expression</th><th>Note</th></tr></thead>
|
|
2895
|
+
<tbody data-generated="stat-pushdown">
|
|
2896
|
+
<!-- Generated from STAT_PUSHDOWN by tools/apiref.js; do not hand-edit. -->
|
|
2897
|
+
<tr><td class="name">sum</td><td class="type">IDENTICAL</td><td class="desc"><code>sum(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2898
|
+
<tr><td class="name">avg</td><td class="type">IDENTICAL</td><td class="desc"><code>avg(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2899
|
+
<tr><td class="name">min</td><td class="type">IDENTICAL</td><td class="desc"><code>min(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2900
|
+
<tr><td class="name">max</td><td class="type">IDENTICAL</td><td class="desc"><code>max(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2901
|
+
<tr><td class="name">count</td><td class="type">IDENTICAL</td><td class="desc"><code>count(*)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2902
|
+
<tr><td class="name">countValues</td><td class="type">IDENTICAL</td><td class="desc"><code>count(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2903
|
+
<tr><td class="name">range</td><td class="type">IDENTICAL</td><td class="desc"><code>(max(col) - min(col))</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2904
|
+
<tr><td class="name">variance</td><td class="type">IDENTICAL</td><td class="desc"><code>var_samp(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2905
|
+
<tr><td class="name">varianceP</td><td class="type">IDENTICAL</td><td class="desc"><code>var_pop(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2906
|
+
<tr><td class="name">stddev</td><td class="type">IDENTICAL</td><td class="desc"><code>stddev_samp(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2907
|
+
<tr><td class="name">stddevP</td><td class="type">IDENTICAL</td><td class="desc"><code>stddev_pop(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2908
|
+
<tr><td class="name">sumSquares</td><td class="type">IDENTICAL</td><td class="desc"><code>sum(col * col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2909
|
+
<tr><td class="name">median</td><td class="type">IDENTICAL</td><td class="desc"><code>median(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2910
|
+
<tr><td class="name">p25</td><td class="type">IDENTICAL</td><td class="desc"><code>quantile_cont(col, 0.25)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2911
|
+
<tr><td class="name">p75</td><td class="type">IDENTICAL</td><td class="desc"><code>quantile_cont(col, 0.75)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2912
|
+
<tr><td class="name">p90</td><td class="type">IDENTICAL</td><td class="desc"><code>quantile_cont(col, 0.9)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2913
|
+
<tr><td class="name">p95</td><td class="type">IDENTICAL</td><td class="desc"><code>quantile_cont(col, 0.95)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2914
|
+
<tr><td class="name">p99</td><td class="type">IDENTICAL</td><td class="desc"><code>quantile_cont(col, 0.99)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2915
|
+
<tr><td class="name">iqr</td><td class="type">IDENTICAL</td><td class="desc"><code>(quantile_cont(col, 0.75) - quantile_cont(col, 0.25))</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2916
|
+
<tr><td class="name">mad</td><td class="type">IDENTICAL</td><td class="desc"><code>mad(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2917
|
+
<tr><td class="name">distinct</td><td class="type">IDENTICAL</td><td class="desc"><code>count(DISTINCT col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2918
|
+
<tr><td class="name">skewness</td><td class="type">IDENTICAL</td><td class="desc"><code>skewness(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2919
|
+
<tr><td class="name">kurtosis</td><td class="type">IDENTICAL</td><td class="desc"><code>kurtosis(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2920
|
+
<tr><td class="name">geomean</td><td class="type">IDENTICAL</td><td class="desc"><code>exp(avg(ln(col)))</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2921
|
+
<tr><td class="name">harmean</td><td class="type">IDENTICAL</td><td class="desc"><code>(count(col) / sum(1.0 / col))</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2922
|
+
<tr><td class="name">entropy</td><td class="type">IDENTICAL</td><td class="desc"><code>entropy(col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2923
|
+
<tr><td class="name">correlation</td><td class="type">IDENTICAL</td><td class="desc"><code>corr(weight, col)</code></td><td class="desc">Pushes to DuckDB with the same result.</td></tr>
|
|
2924
|
+
<tr><td class="name">mode</td><td class="type">MAY-DIFFER</td><td class="desc"><code>mode(col)</code></td><td class="desc">DuckDB returns a modal value even for an all-distinct column; the grid returns null. Tie-breaking can also differ, and on a text column an empty string counts as a value. Under pushdown you will see a value.</td></tr>
|
|
2925
|
+
<tr><td class="name">weightedQuantile</td><td class="type">FALLBACK</td><td class="desc">—</td><td class="desc">No SQL equivalent for the grid's weighted-quantile midpoint convention; always computed client-side (needs a full-dataset pull for a correct figure over a remote source).</td></tr>
|
|
2926
|
+
</tbody>
|
|
2927
|
+
</table>
|
|
2928
|
+
</div>
|
|
2929
|
+
<pre data-run="js" data-expect="engine=identical client=fallback" data-covers="config:aggregates config:default config:overrides export:STAT_PUSHDOWN"><code><span class="cmt">// Push the verified-identical stats to the engine; keep the fallback here.</span>
|
|
2930
|
+
<span class="cmt">// STAT_PUSHDOWN is the published map every stat's class and SQL comes from.</span>
|
|
2931
|
+
<span class="kw">const</span> { createPushdownSource, STAT_PUSHDOWN } = <span class="kw">await</span> import('../packages/core/src/source/index.js');
|
|
2932
|
+
<span class="kw">void</span> STAT_PUSHDOWN; <span class="cmt">// the single source of truth for the classification table above</span>
|
|
2933
|
+
<span class="kw">const</span> adapter = {
|
|
2934
|
+
name: 'demo',
|
|
2935
|
+
capabilities: { filter: 'tree', operators: ['eq'] },
|
|
2936
|
+
execute: <span class="kw">async</span> () => ({ rows: [], total: 0 }),
|
|
2937
|
+
<span class="cmt">// A real duckdbAdapter runs SQL; here we just echo which stats arrived.</span>
|
|
2938
|
+
executeAggregates: <span class="kw">async</span> (query, aggs) => Object.fromEntries(aggs.map((a) => [a.id, 1])),
|
|
2939
|
+
};
|
|
2940
|
+
<span class="kw">const</span> source = createPushdownSource({ adapter, aggregates: {
|
|
2941
|
+
<span class="kw">default</span>: 'engine-if-identical', <span class="cmt">// push only the verified-identical stats</span>
|
|
2942
|
+
overrides: { mode: 'client' }, <span class="cmt">// but always keep mode's exact definition</span>
|
|
2943
|
+
} });
|
|
2944
|
+
<span class="kw">const</span> split = <span class="kw">await</span> source.aggregate(
|
|
2945
|
+
{ filters: null, sort: [], range: null },
|
|
2946
|
+
[{ id: 'a', col: 'revenue', fn: 'median' }, { id: 'b', col: 'size', fn: 'weightedQuantile' }],
|
|
2947
|
+
);
|
|
2948
|
+
<span class="cmt">// median is IDENTICAL so it pushes; weightedQuantile is a fallback so it stays here.</span>
|
|
2949
|
+
<span class="kw">return</span> `engine=${split.engine[0].class} client=${split.client[0].class}`;</code></pre>
|
|
2950
|
+
|
|
2733
2951
|
<h3 id="derived-join">Joining two grids</h3>
|
|
2734
2952
|
<p class="section-note">
|
|
2735
2953
|
Two grids each holding their own data, and a third showing where they meet. Orders against
|
|
@@ -2788,7 +3006,32 @@ app.get('/api/orders', async (req, res) => {
|
|
|
2788
3006
|
columns: [{ field: 'rep' }, { field: 'total' }],
|
|
2789
3007
|
});
|
|
2790
3008
|
|
|
2791
|
-
byRep.on('row:
|
|
3009
|
+
byRep.on('row:clicked', (e) => byRep.crossFilter.toggle(e.key));</code></pre>
|
|
3010
|
+
<p class="section-note">
|
|
3011
|
+
The event is <code>row:clicked</code>, not <code>row:click</code>. A handler bound to the wrong
|
|
3012
|
+
name subscribes without error and never fires, so this example is executed on every build to
|
|
3013
|
+
keep the name honest: it wires the same handler to a stand-in grid, emits the event, and checks
|
|
3014
|
+
the click reached the cross-filter.
|
|
3015
|
+
</p>
|
|
3016
|
+
<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
|
|
3017
|
+
// be executed here without a DOM. The names are the product's own.
|
|
3018
|
+
const listeners = {};
|
|
3019
|
+
const filtered = [];
|
|
3020
|
+
const grid = {
|
|
3021
|
+
on: (name, fn) => { (listeners[name] = listeners[name] || []).push(fn); },
|
|
3022
|
+
emit: (name, e) => { for (const fn of listeners[name] || []) fn(e); },
|
|
3023
|
+
crossFilter: { toggle: (key) => { filtered.push(key); } },
|
|
3024
|
+
};
|
|
3025
|
+
|
|
3026
|
+
// The line from the example, verbatim in its event name.
|
|
3027
|
+
grid.on('row:clicked', (e) => { if (e.key) grid.crossFilter.toggle(e.key); });
|
|
3028
|
+
|
|
3029
|
+
// A click on a rep row. The wrong name — 'row:click' — would reach no handler,
|
|
3030
|
+
// and this block would produce '' instead of the key.
|
|
3031
|
+
grid.emit('row:clicked', { key: 'EMEA' });
|
|
3032
|
+
grid.emit('row:click', { key: 'US' });
|
|
3033
|
+
|
|
3034
|
+
return filtered.join(',');</code></pre>
|
|
2792
3035
|
<div class="table-wrap">
|
|
2793
3036
|
<table>
|
|
2794
3037
|
<thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
|
|
@@ -3228,6 +3471,63 @@ const chart = createChart({
|
|
|
3228
3471
|
grid.filters.set({ col: 'region', op: 'eq', value: point.x });
|
|
3229
3472
|
});</code></pre>
|
|
3230
3473
|
|
|
3474
|
+
<h3 id="chart-a-range">Chart a selected range</h3>
|
|
3475
|
+
<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>
|
|
3476
|
+
<ul>
|
|
3477
|
+
<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>
|
|
3478
|
+
<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>
|
|
3479
|
+
<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>
|
|
3480
|
+
<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>
|
|
3481
|
+
</ul>
|
|
3482
|
+
<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>
|
|
3483
|
+
<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');
|
|
3484
|
+
<span class="kw">const</span> { deriveRangeSpec, canChartRange } = <span class="kw">await</span> import('../packages/modules/charts/index.js');
|
|
3485
|
+
|
|
3486
|
+
<span class="kw">let</span> charted = null;
|
|
3487
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
3488
|
+
columns: [
|
|
3489
|
+
{ field: 'region', type: 'text' },
|
|
3490
|
+
{ field: 'revenue', type: 'number', total: 'sum' },
|
|
3491
|
+
{ field: 'cost', type: 'number', total: 'sum' },
|
|
3492
|
+
],
|
|
3493
|
+
rows: [
|
|
3494
|
+
{ id: 1, region: 'EMEA', revenue: 300, cost: 120 },
|
|
3495
|
+
{ id: 2, region: 'AMER', revenue: 500, cost: 240 },
|
|
3496
|
+
{ id: 3, region: 'APAC', revenue: 200, cost: 90 },
|
|
3497
|
+
],
|
|
3498
|
+
rowKey: 'id',
|
|
3499
|
+
<span class="cmt">// Opt in, with the handler that draws — the page's link to modules/charts.</span>
|
|
3500
|
+
<span class="cmt">// The menu and Alt+F1 call it with the grid and the selected range.</span>
|
|
3501
|
+
rangeChart(grid, range) { charted = range; },
|
|
3502
|
+
});
|
|
3503
|
+
|
|
3504
|
+
<span class="cmt">// The rectangle the user dragged: three rows, the text column and both numbers.</span>
|
|
3505
|
+
<span class="kw">const</span> range = { startRow: 0, endRow: 2, columns: ['region', 'revenue', 'cost'] };
|
|
3506
|
+
|
|
3507
|
+
<span class="cmt">// The leading text column is the dimension; the two numeric columns are the</span>
|
|
3508
|
+
<span class="cmt">// measures, so the default is a grouped bar (a combo of bar marks).</span>
|
|
3509
|
+
<span class="kw">const</span> plan = deriveRangeSpec(grid, { range });
|
|
3510
|
+
|
|
3511
|
+
<span class="cmt">// What the menu action does when the reader picks "Chart selection".</span>
|
|
3512
|
+
<span class="kw">const</span> handler = grid.get('rangeChart');
|
|
3513
|
+
<span class="kw">if</span> (canChartRange(grid, { range })) handler(grid, range);
|
|
3514
|
+
|
|
3515
|
+
<span class="kw">return</span> [plan.x === 'region' ? 'Region' : plan.x,
|
|
3516
|
+
plan.measures.join(','),
|
|
3517
|
+
plan.type,
|
|
3518
|
+
charted === range ? 'drawn' : 'no'].join('|');</code></pre>
|
|
3519
|
+
<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>
|
|
3520
|
+
<pre><code>import { chartRange } from '@toclocoinc/lattice-grid/modules/charts';
|
|
3521
|
+
|
|
3522
|
+
createGrid(el, {
|
|
3523
|
+
columns, rows,
|
|
3524
|
+
rangeChart(grid, range) {
|
|
3525
|
+
<span class="cmt">// A grouped bar by default; pass `type` to draw it as something else.</span>
|
|
3526
|
+
const chart = chartRange(grid, { container: '#chart', range });
|
|
3527
|
+
if (chart) chart.update({ scheme: 'colourblind' });
|
|
3528
|
+
},
|
|
3529
|
+
});</code></pre>
|
|
3530
|
+
|
|
3231
3531
|
<h3>Maps</h3>
|
|
3232
3532
|
<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>
|
|
3233
3533
|
<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>
|
|
@@ -3536,7 +3836,7 @@ createGrid(el, {
|
|
|
3536
3836
|
<h3 id="config-example">A configuration, executed</h3>
|
|
3537
3837
|
<p class="section-note">This block runs on every build. If a key here stopped being honoured, or was
|
|
3538
3838
|
renamed, the build would fail rather than the documentation quietly going stale.</p>
|
|
3539
|
-
<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');
|
|
3839
|
+
<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:stripedRows 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');
|
|
3540
3840
|
|
|
3541
3841
|
<span class="cmt">// Every one of these is a documented configuration key, set together so the</span>
|
|
3542
3842
|
<span class="cmt">// example proves they are accepted and honoured rather than merely spelled.</span>
|
|
@@ -3547,11 +3847,12 @@ createGrid(el, {
|
|
|
3547
3847
|
rowHeight: 32, headerHeight: 40, overscan: 8, autoHeight: <span class="kw">false</span>,
|
|
3548
3848
|
showHeader: <span class="kw">true</span>, density: 'compact', theme: 'light',
|
|
3549
3849
|
locale: 'en-GB', timeZone: 'UTC', title: 'Readings',
|
|
3550
|
-
gridLines: 'both', cornerRadius: 4, targetSize: 'default',
|
|
3850
|
+
gridLines: 'both', cornerRadius: 4, stripedRows: <span class="kw">false</span>, targetSize: 'default',
|
|
3551
3851
|
sampleSize: 100, quickFilterText: '', maximise: <span class="kw">false</span>,
|
|
3552
3852
|
shortcuts: <span class="kw">true</span>, rowReorder: <span class="kw">false</span>,
|
|
3553
3853
|
stickyGroupHeaders: <span class="kw">true</span>, groupFooter: <span class="kw">false</span>,
|
|
3554
3854
|
totalFilteredOnly: <span class="kw">false</span>, showTotalInHeader: <span class="kw">false</span>,
|
|
3855
|
+
aggregateChooser: <span class="kw">false</span>,
|
|
3555
3856
|
allowUnsafeTemplates: <span class="kw">false</span>, useWorker: <span class="kw">false</span>,
|
|
3556
3857
|
sharedMemory: <span class="kw">false</span>, workerThreshold: 100000,
|
|
3557
3858
|
columnVirtualisationAbove: 40, showColumnFunctions: <span class="kw">false</span>,
|
|
@@ -3561,6 +3862,64 @@ createGrid(el, {
|
|
|
3561
3862
|
grid.destroy();
|
|
3562
3863
|
<span class="kw">return</span> n;</code></pre>
|
|
3563
3864
|
|
|
3865
|
+
<h3 id="ingest-worker-example">Non-blocking stream ingest, executed</h3>
|
|
3866
|
+
<p class="section-note">A <code>stream</code> source loaded with <code>ingest.useWorker</code> on. In a browser a
|
|
3867
|
+
chunk that clears <code>ingest.workerThreshold</code> is columnized on a Worker so the main thread
|
|
3868
|
+
is not blocked; here in Node there is no Worker, so it columnizes in-process — the same code,
|
|
3869
|
+
the same result, which is exactly what this asserts. With <code>retainSource:false</code> the
|
|
3870
|
+
grid keeps only the packed columns, so <code>rows.data()</code> returns reconstructed objects
|
|
3871
|
+
rather than the caller's own. This makes <strong>stream</strong> (and remote) ingest
|
|
3872
|
+
non-blocking; memory and paged sources still read the caller's objects on the main thread.</p>
|
|
3873
|
+
<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');
|
|
3874
|
+
|
|
3875
|
+
<span class="cmt">// A stream source: the producer pushes chunks of rows as it finds them.</span>
|
|
3876
|
+
<span class="kw">async</span> <span class="kw">function</span>* open() {
|
|
3877
|
+
yield { rows: [{ id: '1', city: 'Oslo', pop: 700000 }, { id: '2', city: 'Bergen', pop: 280000 }] };
|
|
3878
|
+
yield { rows: [{ id: '3', city: 'Tromsø', pop: 77000 }] };
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
3882
|
+
rowKey: 'id',
|
|
3883
|
+
columns: [{ id: 'city', field: 'city' }, { id: 'pop', field: 'pop', type: 'number' }],
|
|
3884
|
+
source: { mode: 'stream', open },
|
|
3885
|
+
<span class="cmt">// Columnize stream chunks off the main thread when a chunk is large enough,</span>
|
|
3886
|
+
<span class="cmt">// and keep only the packed columns rather than the caller's row objects.</span>
|
|
3887
|
+
ingest: { useWorker: <span class="kw">true</span>, workerThreshold: 1, retainSource: <span class="kw">false</span> },
|
|
3888
|
+
});
|
|
3889
|
+
|
|
3890
|
+
<span class="cmt">// The stream loads over async frames; wait for it to finish before counting.</span>
|
|
3891
|
+
<span class="kw">await</span> <span class="kw">new</span> Promise((resolve) => grid.on('stream:end', resolve));
|
|
3892
|
+
<span class="kw">const</span> n = grid.rows.count();
|
|
3893
|
+
grid.destroy();
|
|
3894
|
+
<span class="kw">return</span> n;</code></pre>
|
|
3895
|
+
|
|
3896
|
+
<h3 id="dropsourcerows-example">Source-layer memory reduction, executed</h3>
|
|
3897
|
+
<p class="section-note">A memory grid loaded with <code>ingest.dropSourceRows</code> on. Once the column store
|
|
3898
|
+
is built, the caller's row objects are released from the source layer and the grid config, so the
|
|
3899
|
+
packed columns are the only resident copy — an order-of-magnitude drop at scale. Reads are served
|
|
3900
|
+
by reconstructing a row from the columns, so the <em>values</em> are unchanged; what is gone is
|
|
3901
|
+
object identity, which is why <code>rows.data()</code> returns a fresh object each call rather than
|
|
3902
|
+
the one you supplied.</p>
|
|
3903
|
+
<pre data-run="js" data-expect="true" data-covers="config:dropSourceRows config:ingest"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3904
|
+
|
|
3905
|
+
<span class="kw">const</span> supplied = [{ id: '1', city: 'Oslo', pop: 700000 }, { id: '2', city: 'Bergen', pop: 280000 }];
|
|
3906
|
+
|
|
3907
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
3908
|
+
rowKey: 'id',
|
|
3909
|
+
columns: [{ id: 'city', field: 'city' }, { id: 'pop', field: 'pop', type: 'number' }],
|
|
3910
|
+
source: { mode: 'memory' },
|
|
3911
|
+
rows: supplied,
|
|
3912
|
+
<span class="cmt">// Release the caller's objects; keep only the packed columns.</span>
|
|
3913
|
+
ingest: { dropSourceRows: <span class="kw">true</span> },
|
|
3914
|
+
});
|
|
3915
|
+
|
|
3916
|
+
<span class="kw">const</span> back = grid.rows.data();
|
|
3917
|
+
<span class="cmt">// Same values, reconstructed from the columns — but not the caller's own object.</span>
|
|
3918
|
+
<span class="kw">const</span> valuesMatch = back[0].city === 'Oslo' && back[0].pop === 700000;
|
|
3919
|
+
<span class="kw">const</span> identityDropped = back[0] !== supplied[0];
|
|
3920
|
+
grid.destroy();
|
|
3921
|
+
<span class="kw">return</span> valuesMatch && identityDropped;</code></pre>
|
|
3922
|
+
|
|
3564
3923
|
<h3 id="events-example">Events, executed</h3>
|
|
3565
3924
|
<p class="section-note">Fourteen events raised by ordinary calls, asserted on every build. An event that
|
|
3566
3925
|
stopped firing, or changed name, fails here rather than in a consumer.</p>
|
|
@@ -3693,7 +4052,7 @@ grid.destroy();
|
|
|
3693
4052
|
|
|
3694
4053
|
<h3 id="module-exports-example">Every module export, executed</h3>
|
|
3695
4054
|
<p class="section-note">Nine shipped modules, each export resolved against its own barrel on every build.</p>
|
|
3696
|
-
<pre data-run="js" data-expect="
|
|
4055
|
+
<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>
|
|
3697
4056
|
<span class="cmt">// barrel. A module that stopped exporting something fails here.</span>
|
|
3698
4057
|
<span class="kw">const</span> modules = [
|
|
3699
4058
|
[<span class="kw">await</span> import('../packages/dom/src/index.js'), [
|
|
@@ -3704,7 +4063,8 @@ grid.destroy();
|
|
|
3704
4063
|
]],
|
|
3705
4064
|
[<span class="kw">await</span> import('../packages/modules/charts/index.js'), [
|
|
3706
4065
|
'Chart', 'PALETTE', 'SCHEMES', 'TYPES',
|
|
3707
|
-
'createChart', '
|
|
4066
|
+
'createChart', 'chartRange', 'canChartRange', 'deriveRangeSpec',
|
|
4067
|
+
'registerScheme', 'resolveScheme', 'schemeNames',
|
|
3708
4068
|
'setDefaultScheme',
|
|
3709
4069
|
]],
|
|
3710
4070
|
[<span class="kw">await</span> import('../packages/modules/htmx/index.js'), [
|
|
@@ -3744,7 +4104,7 @@ grid.destroy();
|
|
|
3744
4104
|
<h3 id="nested-config-example">Nested configuration, executed</h3>
|
|
3745
4105
|
<p class="section-note">Thirteen option blocks, each key written where it belongs. Parsed and evaluated on
|
|
3746
4106
|
every build, so a key that was renamed or moved shows up here.</p>
|
|
3747
|
-
<pre data-run="js" data-expect="
|
|
4107
|
+
<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>
|
|
3748
4108
|
<span class="cmt">// the option names: each one below is a documented key, written where it</span>
|
|
3749
4109
|
<span class="cmt">// belongs, so a key that was renamed or moved stops matching its interface.</span>
|
|
3750
4110
|
<span class="kw">const</span> source = {}, other = {}, provider = {}, compute = {}, adapter = {};
|
|
@@ -3803,14 +4163,17 @@ grid.destroy();
|
|
|
3803
4163
|
unit: 'metre', system: 'si', space: true, binary: false, format: 'auto',
|
|
3804
4164
|
display: 'yesNo', label: 'Size', hint: 'in metres' };
|
|
3805
4165
|
|
|
4166
|
+
<span class="cmt">// How rows enter the store — IngestConfig</span>
|
|
4167
|
+
<span class="kw">const</span> ingestConfig = { retainSource: <span class="kw">false</span> };
|
|
4168
|
+
|
|
3806
4169
|
<span class="kw">return</span> [derivedSourceConfig, editConfig, selectionConfig, treeConfig, detailConfig,
|
|
3807
4170
|
presenceConfig, commentConfig, pagedSourceConfig, streamSourceConfig,
|
|
3808
|
-
pushdownSourceConfig, csvExportOptions, columnFacetConfig, numberFormat].length;</code></pre>
|
|
4171
|
+
pushdownSourceConfig, csvExportOptions, columnFacetConfig, numberFormat, ingestConfig].length;</code></pre>
|
|
3809
4172
|
|
|
3810
4173
|
<h3 id="remaining-config-example">The remaining option names, executed</h3>
|
|
3811
4174
|
<p class="section-note">Set on a real grid and checked against its own diagnostics: an unrecognised key
|
|
3812
4175
|
raises <code>config.unknown</code>, so a renamed or dropped option fails here.</p>
|
|
3813
|
-
<pre data-run="js" data-expect="
|
|
4176
|
+
<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');
|
|
3814
4177
|
<span class="kw">const</span> { reportedWarnings } = <span class="kw">await</span> import('../packages/core/src/internal/util.js');
|
|
3815
4178
|
|
|
3816
4179
|
<span class="cmt">// Top-level configuration keys. Each is set on a real grid, and the grid is</span>
|
|
@@ -3821,7 +4184,8 @@ grid.destroy();
|
|
|
3821
4184
|
'columnPresets', 'columnTagFilter', 'comments', 'components', 'context',
|
|
3822
4185
|
'contextMenu', 'dataTypes', 'detail', 'diff', 'edit',
|
|
3823
4186
|
'environment', 'facets', 'formatting', 'formulaFunctions', 'fullWidth',
|
|
3824
|
-
'grandTotalRow', 'highlightOnChange', 'historyBar', 'hostFilter', '
|
|
4187
|
+
'grandTotalRow', 'groupPanel', 'highlightOnChange', 'historyBar', 'hostFilter', 'ingest',
|
|
4188
|
+
'licence',
|
|
3825
4189
|
'pagination', 'permissions', 'pinnedBottomRows', 'pinnedTopRows', 'pipes',
|
|
3826
4190
|
'pivot', 'presence', 'responsive', 'rowClass', 'rowForm',
|
|
3827
4191
|
'rowStyle', 'rowTemplate', 'rowTransfer', 'selection', 'source',
|
|
@@ -4051,6 +4415,35 @@ grid.destroy();
|
|
|
4051
4415
|
<!-- BEGIN GENERATED TYPE REFERENCE -->
|
|
4052
4416
|
<h2 id="type-reference">Type reference</h2>
|
|
4053
4417
|
<p class="section-note">Every interface the library declares, with the type of each member. The sections above describe how the grid is used; this one is the complete surface, generated from the type declarations so that it always matches the release.</p>
|
|
4418
|
+
<h3 id="type-AggregateProvenance">AggregateProvenance</h3>
|
|
4419
|
+
<p class="section-note">How one aggregate was routed, for `lastPlan()` provenance.</p>
|
|
4420
|
+
<div class="table-wrap">
|
|
4421
|
+
<table>
|
|
4422
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
4423
|
+
<tbody>
|
|
4424
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc"></td></tr>
|
|
4425
|
+
<tr><td class="name">col</td><td class="type">string</td><td class="desc"></td></tr>
|
|
4426
|
+
<tr><td class="name">fn</td><td class="type">string</td><td class="desc"></td></tr>
|
|
4427
|
+
<tr><td class="name">class</td><td class="type">'identical' | 'may-differ' | 'fallback'</td><td class="desc">How the engine result relates to the grid kernel.</td></tr>
|
|
4428
|
+
<tr><td class="name">reason</td><td class="type">string</td><td class="desc">Why it is client-side, when it is (config, fallback, or the guard). <small>(optional)</small></td></tr>
|
|
4429
|
+
<tr><td class="name">weight</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4430
|
+
</tbody>
|
|
4431
|
+
</table>
|
|
4432
|
+
</div>
|
|
4433
|
+
<h3 id="type-AggregateRequest">AggregateRequest</h3>
|
|
4434
|
+
<p class="section-note">One aggregate the grid asks the source to compute over the matching set. `params` carries e.g. `{ share: 0.1 }` so an adapter emits the matching SQL; `weight` names the second column for a two-column stat like `correlation`.</p>
|
|
4435
|
+
<div class="table-wrap">
|
|
4436
|
+
<table>
|
|
4437
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
4438
|
+
<tbody>
|
|
4439
|
+
<tr><td class="name">id</td><td class="type">string</td><td class="desc">Keys the result back to the request.</td></tr>
|
|
4440
|
+
<tr><td class="name">col</td><td class="type">string</td><td class="desc">The column to reduce.</td></tr>
|
|
4441
|
+
<tr><td class="name">fn</td><td class="type">string</td><td class="desc">The statistic name, as used in `total: '<name>'`.</td></tr>
|
|
4442
|
+
<tr><td class="name">weight</td><td class="type">string</td><td class="desc">The second column, for a two-column statistic. <small>(optional)</small></td></tr>
|
|
4443
|
+
<tr><td class="name">params</td><td class="type">Record<string, unknown></td><td class="desc">Parameters the statistic takes, e.g. a trim share. <small>(optional)</small></td></tr>
|
|
4444
|
+
</tbody>
|
|
4445
|
+
</table>
|
|
4446
|
+
</div>
|
|
4054
4447
|
<h3 id="type-AiApi">AiApi</h3>
|
|
4055
4448
|
<div class="table-wrap">
|
|
4056
4449
|
<table>
|
|
@@ -4232,6 +4625,7 @@ grid.destroy();
|
|
|
4232
4625
|
<tr><td class="name">x</td><td class="type">string</td><td class="desc">The category column. <small>(optional)</small></td></tr>
|
|
4233
4626
|
<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>
|
|
4234
4627
|
<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>
|
|
4628
|
+
<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>
|
|
4235
4629
|
<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>
|
|
4236
4630
|
<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>
|
|
4237
4631
|
<tr><td class="name">target</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -4521,6 +4915,7 @@ grid.destroy();
|
|
|
4521
4915
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
4522
4916
|
<tbody>
|
|
4523
4917
|
<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>
|
|
4918
|
+
<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>
|
|
4524
4919
|
<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>
|
|
4525
4920
|
<tr><td class="name">get</td><td class="type">(id: string): ResolvedColumn | undefined</td><td class="desc"></td></tr>
|
|
4526
4921
|
<tr><td class="name">all</td><td class="type">(): ResolvedColumn[]</td><td class="desc"></td></tr>
|
|
@@ -4535,6 +4930,7 @@ grid.destroy();
|
|
|
4535
4930
|
<tr><td class="name">move</td><td class="type">(id: string, to: number): void</td><td class="desc"></td></tr>
|
|
4536
4931
|
<tr><td class="name">pin</td><td class="type">(id: string, side: 'start' | 'end' | null): void</td><td class="desc"></td></tr>
|
|
4537
4932
|
<tr><td class="name">resize</td><td class="type">(id: string, px: number): void</td><td class="desc"></td></tr>
|
|
4933
|
+
<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>
|
|
4538
4934
|
<tr><td class="name">autoSize</td><td class="type">(ids?: string | string[]): void</td><td class="desc"></td></tr>
|
|
4539
4935
|
<tr><td class="name">fit</td><td class="type">(): void</td><td class="desc"></td></tr>
|
|
4540
4936
|
<tr><td class="name">group</td><td class="type">(ids: string | string[]): void</td><td class="desc"></td></tr>
|
|
@@ -4798,6 +5194,8 @@ grid.destroy();
|
|
|
4798
5194
|
<tr><td class="name">edge</td><td class="type">boolean</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4799
5195
|
<tr><td class="name">position</td><td class="type">'start' | 'end'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4800
5196
|
<tr><td class="name">name</td><td class="type">string | Record<string, string></td><td class="desc"><small>(optional)</small></td></tr>
|
|
5197
|
+
<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>
|
|
5198
|
+
<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>
|
|
4801
5199
|
<tr><td class="name">min</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4802
5200
|
<tr><td class="name">max</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4803
5201
|
<tr><td class="name">origin</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -4972,6 +5370,8 @@ grid.destroy();
|
|
|
4972
5370
|
<tr><td class="name">redo</td><td class="type">(): void</td><td class="desc"></td></tr>
|
|
4973
5371
|
<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>
|
|
4974
5372
|
<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>
|
|
5373
|
+
<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>
|
|
5374
|
+
<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>
|
|
4975
5375
|
<tr><td class="name">settle</td><td class="type">(id: string, ok: boolean, reason?: string): boolean</td><td class="desc"></td></tr>
|
|
4976
5376
|
<tr><td class="name">pending</td><td class="type">(): OpenWrite[]</td><td class="desc"></td></tr>
|
|
4977
5377
|
<tr><td class="name">status</td><td class="type">(key: string, colId: string): 'pending' | null</td><td class="desc"></td></tr>
|
|
@@ -4991,6 +5391,7 @@ grid.destroy();
|
|
|
4991
5391
|
<tr><td class="name">commit</td><td class="type">(write: PendingWrite) => unknown</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4992
5392
|
<tr><td class="name">confirm</td><td class="type">'auto' | 'manual'</td><td class="desc"><small>(optional)</small></td></tr>
|
|
4993
5393
|
<tr><td class="name">pendingTimeout</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5394
|
+
<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>
|
|
4994
5395
|
</tbody>
|
|
4995
5396
|
</table>
|
|
4996
5397
|
</div>
|
|
@@ -5324,6 +5725,7 @@ grid.destroy();
|
|
|
5324
5725
|
<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>
|
|
5325
5726
|
<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>
|
|
5326
5727
|
<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>
|
|
5728
|
+
<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>
|
|
5327
5729
|
<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>
|
|
5328
5730
|
<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>
|
|
5329
5731
|
<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>
|
|
@@ -5344,6 +5746,7 @@ grid.destroy();
|
|
|
5344
5746
|
<tr><td class="name">density</td><td class="type">Density</td><td class="desc">Row height and padding as a named step, rather than pixel by pixel. <small>(optional)</small></td></tr>
|
|
5345
5747
|
<tr><td class="name">gridLines</td><td class="type">boolean | 'both' | 'horizontal' | 'vertical' | 'none' | 'rows' | 'columns'</td><td class="desc">Which rules are drawn between cells. `'both'` by default. The two axes are separate decisions: horizontal rules help the eye track along a row, vertical ones stop adjacent values running together. `false` or `'none'` draws neither. Only the rules *between data* are affected, the header's underline, the pinned seams and the totals separator are structure, not grid lines. <small>(optional)</small></td></tr>
|
|
5346
5748
|
<tr><td class="name">cornerRadius</td><td class="type">boolean | number | string</td><td class="desc">Round the grid's outer corners. Square by default. `true` adopts the theme's own radius; a number is pixels; a string is used as written, so a host can pass its own token or a relative unit. <small>(optional)</small></td></tr>
|
|
5749
|
+
<tr><td class="name">stripedRows</td><td class="type">boolean</td><td class="desc">Shade alternate data rows (zebra striping). Off by default, and strictly opt-in: an existing grid must look exactly the same on upgrade. When `true`, every other data row takes the theme's `--lattice-surface-alt` background, which every palette already defines, so dark, high-contrast and terminal stripe correctly without extra work. Parity follows the row's *logical* index, not its position in the DOM, so a row keeps its stripe across a scroll even though the rows are recycled. Structural rows — group headings, group footers and the grand total — are never striped, and both selection and hover still win over the stripe. <small>(optional)</small></td></tr>
|
|
5347
5750
|
<tr><td class="name">columnTagFilter</td><td class="type">boolean | { multiple?: boolean; label?: string }</td><td class="desc">Show a bar above the column headings for filtering columns by tag. Off by default, and it draws nothing unless some column carries a `tags` entry. `multiple: true` lets more than one tag be chosen at once. Only tagged columns are ever hidden, so an untagged account or total column stays visible whatever is selected. <small>(optional)</small></td></tr>
|
|
5348
5751
|
<tr><td class="name">typeOptions</td><td class="type">Record<string, {</td><td class="desc">Per-column options a data type reads. `ratio` and `percentRate` use `{ weight }` to name the column their average is weighted by. A unit type reads `{ significantFigures }` to render to a fixed precision rather than a fixed number of decimals. <small>(optional)</small></td></tr>
|
|
5349
5752
|
<tr><td class="name">rowTemplate</td><td class="type">string | {</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -5353,7 +5756,7 @@ grid.destroy();
|
|
|
5353
5756
|
<tr><td class="name">rowHeight</td><td class="type">number | ((row: Row) => number)</td><td class="desc">Row height in pixels, or a function of the row. A function makes the grid measure rather than assume, which costs a pass over what is on screen: worth it for wrapped text, wasteful for a uniform grid. <small>(optional)</small></td></tr>
|
|
5354
5757
|
<tr><td class="name">title</td><td class="type">string</td><td class="desc">A caption for the grid, drawn above the column headings. Inside the grid rather than an element the host places above it: a title outside does not scroll with the grid, is not in the region a screen reader announces, and is left behind by image capture and print. <small>(optional)</small></td></tr>
|
|
5355
5758
|
<tr><td class="name">showHeader</td><td class="type">boolean</td><td class="desc">Draw the column headings at all. `true` by default. `false` removes the row, and removes it from the accessibility tree rather than only from view, a heading a screen reader still announces is invisible, not hidden. What a small dashboard tile wants when its `title` already says what the panel is. Distinct from `showColumnFunctions`, which keeps the headings and drops only the sort, filter and menu controls inside them. <small>(optional)</small></td></tr>
|
|
5356
|
-
<tr><td class="name">headerHeight</td><td class="type">number</td><td class="desc">Header height in pixels. <small>(optional)</small></td></tr>
|
|
5759
|
+
<tr><td class="name">headerHeight</td><td class="type">number</td><td class="desc">Header height in pixels. Omitted, the header takes its height from the density-scaled `--lattice-header-height` token, so `density` sizes the header as it sizes the rows. A number names one explicitly and outranks the token. <small>(optional)</small></td></tr>
|
|
5357
5760
|
<tr><td class="name">overscan</td><td class="type">number</td><td class="desc">How many rows to render beyond the viewport. More costs memory and smooths fast scrolling; fewer is lighter and can show a gap. <small>(optional)</small></td></tr>
|
|
5358
5761
|
<tr><td class="name">autoHeight</td><td class="type">boolean | 'visible'</td><td class="desc">Size rows to their content rather than to the density token. Only rows that are actually rendered are ever measured, in both settings: the grid does not lay out rows you cannot see. The difference is what happens on a large grid: `true` gives up above ten thousand rows and falls back to fixed heights, because a cumulative offset array being patched as you scroll a million rows is not worth the result. `'visible'` keeps measuring at any size, accepting that the scrollbar shifts as rows are measured on the way past. The name is historical and reads as though it were about which rows are measured; it is about whether the ceiling applies. <small>(optional)</small></td></tr>
|
|
5359
5762
|
<tr><td class="name">state</td><td class="type">GridState</td><td class="desc">Sort, filters, grouping, widths and the rest, restored at construction. <small>(optional)</small></td></tr>
|
|
@@ -5380,20 +5783,23 @@ grid.destroy();
|
|
|
5380
5783
|
<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>
|
|
5381
5784
|
<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>
|
|
5382
5785
|
<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>
|
|
5786
|
+
<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>
|
|
5383
5787
|
<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>
|
|
5384
5788
|
<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>
|
|
5385
5789
|
<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>
|
|
5386
5790
|
<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>
|
|
5791
|
+
<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>
|
|
5387
5792
|
<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>
|
|
5388
5793
|
<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>
|
|
5389
5794
|
<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>
|
|
5390
5795
|
<tr><td class="name">alignedGrids</td><td class="type">unknown[]</td><td class="desc">Other grids to stay column-aligned with. Column widths, order, visibility and pinning are shared, and horizontal scrolling moves them together. Sort, filters, selection, grouping and the rows themselves stay independent: sharing those would make one grid with extra steps rather than two aligned ones. Declared on the grid created last, since it is the only one that can name the others; the link is peer-based once made. <small>(optional)</small></td></tr>
|
|
5391
|
-
<tr><td class="name">stickyGroupHeaders</td><td class="type">boolean | number | { depth?: number }</td><td class="desc">Keep the enclosing group headings pinned above the viewport while scrolling inside a group.
|
|
5796
|
+
<tr><td class="name">stickyGroupHeaders</td><td class="type">boolean | number | { depth?: number }</td><td class="desc">Keep the enclosing group headings pinned above the viewport while scrolling inside a group. Off by default — a deliberate product default; sticky group headers are opt-in. `true` turns it on, stacking at most two; a number, or `{ depth }`, sets how many may stack: each costs a row of viewport, so a deep grouping would otherwise spend the screen describing itself. `false` is off, the same as leaving it unset. <small>(optional)</small></td></tr>
|
|
5392
5797
|
<tr><td class="name">highlightOnChange</td><td class="type">boolean | string | {</td><td class="desc">Flash a cell when its value changes. `true` takes the defaults; an object names a colour, a duration in milliseconds, or both. <small>(optional)</small></td></tr>
|
|
5393
5798
|
<tr><td class="name">formatting</td><td class="type">Record<string, FormattingRule[]></td><td class="desc">Conditional formatting rules the grid holds as runtime state, keyed by column id or `'*'` for every column (spec 8.12). Seeds `grid.formatting`, which an end user can then change; the rules travel in saved views and undo like any other change. Config-time `cell.style` is unaffected. <small>(optional)</small></td></tr>
|
|
5394
5799
|
<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>
|
|
5395
5800
|
<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>
|
|
5396
5801
|
<tr><td class="name">toolPanel</td><td class="type">boolean | {</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5802
|
+
<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>
|
|
5397
5803
|
<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>
|
|
5398
5804
|
<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>
|
|
5399
5805
|
<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>
|
|
@@ -5501,6 +5907,32 @@ grid.destroy();
|
|
|
5501
5907
|
</tbody>
|
|
5502
5908
|
</table>
|
|
5503
5909
|
</div>
|
|
5910
|
+
<h3 id="type-IconBand">IconBand</h3>
|
|
5911
|
+
<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>
|
|
5912
|
+
<div class="table-wrap">
|
|
5913
|
+
<table>
|
|
5914
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5915
|
+
<tbody>
|
|
5916
|
+
<tr><td class="name">min</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5917
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc"></td></tr>
|
|
5918
|
+
<tr><td class="name">label</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5919
|
+
<tr><td class="name">variant</td><td class="type">VariantName</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5920
|
+
</tbody>
|
|
5921
|
+
</table>
|
|
5922
|
+
</div>
|
|
5923
|
+
<h3 id="type-IngestConfig">IngestConfig</h3>
|
|
5924
|
+
<p class="section-note">How rows are ingested into the column store.</p>
|
|
5925
|
+
<div class="table-wrap">
|
|
5926
|
+
<table>
|
|
5927
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5928
|
+
<tbody>
|
|
5929
|
+
<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>
|
|
5930
|
+
<tr><td class="name">dropSourceRows</td><td class="type">boolean</td><td class="desc">Release the caller's row objects from the *source layer* once the column store has been built, so the columns become the sole resident copy of the data. Default `false`, which keeps today's behaviour. `retainSource:false` stops the {@link https://en.wikipedia.org/wiki/Column-oriented_DBMS column store} from holding the caller's objects, but the memory source and the grid config still retain the supplied array by reference — so the objects stay alive and the resident footprint does not actually fall. This flag closes that gap: it clears `MemorySource`'s retained array and drops the array from the grid config, leaving nothing on the heap but the packed columns. That is where the large reduction comes from (roughly an order of magnitude at a million rows), not from `retainSource` on its own. Implies `retainSource:false`: dropping the caller's objects while the store still expects to read through them would leave the source with no data at all, so setting this on forces the store to reconstruct rows from columns. Every read is therefore served from the columns — `at()`, `byKey()`, `get()`, `value()`, `values()`, filtering, sorting, grouping, totals and export are all unaffected in their values. What changes is the same three identity behaviours `retainSource:false` documents: `rows.data()` returns freshly reconstructed objects (so `row === sourceObject` no longer holds), a custom renderer reaching for `row.sourceObject` gets a reconstruction, and equality against a row becomes value-based. One consumer cannot be served from the columns: an *impure computed column* (a shadow, or a rank/positional column) is deliberately never materialised into the store, so its handle is built by reading the source objects. Under `dropSourceRows` those objects are gone, so such a column reduces over nothing and warns once rather than returning a silently wrong figure. Do not enable `dropSourceRows` on a grid that sorts, filters, groups or totals on a shadow or a positional column. <small>(optional)</small></td></tr>
|
|
5931
|
+
<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>
|
|
5932
|
+
<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>
|
|
5933
|
+
</tbody>
|
|
5934
|
+
</table>
|
|
5935
|
+
</div>
|
|
5504
5936
|
<h3 id="type-LicenceApi">LicenceApi</h3>
|
|
5505
5937
|
<div class="table-wrap">
|
|
5506
5938
|
<table>
|
|
@@ -5876,6 +6308,17 @@ grid.destroy();
|
|
|
5876
6308
|
</tbody>
|
|
5877
6309
|
</table>
|
|
5878
6310
|
</div>
|
|
6311
|
+
<h3 id="type-PushdownAggregatesConfig">PushdownAggregatesConfig</h3>
|
|
6312
|
+
<p class="section-note">Design-time aggregate-pushdown policy for a pushdown source (BACKLOG-0000730 Part B, ungrouped). The developer chooses, at grid setup before render, whether each statistic is computed by the engine (fast, over the matching set) or client-side (the grid's exact definition, needs a full-dataset pull). It is fixed for the life of the grid, never a runtime toggle, and never surfaced to an end user. Absent, every aggregate is computed client-side — today's behaviour, so no existing caller regresses. `engine-if-identical` is the recommended setting for a windowed DuckDB source: it pushes only the statistics whose engine result is verified identical to the grid kernel, keeping the documented MAY-DIFFER stats (e.g. `mode`) client-side. The engine is used only when the filter is fully pushed; a residual filter forces every aggregate client-side, so an engine figure and a client figure never mix in one result set.</p>
|
|
6313
|
+
<div class="table-wrap">
|
|
6314
|
+
<table>
|
|
6315
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
6316
|
+
<tbody>
|
|
6317
|
+
<tr><td class="name">default</td><td class="type">AggregateMode</td><td class="desc">The default policy for stats the engine can express. `'engine'` pushes everything expressible (using the engine's method for MAY-DIFFER stats); `'engine-if-identical'` pushes only the verified-identical ones; `'client'` computes everything client-side. Default `'client'`. <small>(optional)</small></td></tr>
|
|
6318
|
+
<tr><td class="name">overrides</td><td class="type">Record<string, 'engine' | 'client'></td><td class="desc">Per-stat overrides, winning over `default`. A stat the engine cannot express (`weightedQuantile`) is always client-side regardless. <small>(optional)</small></td></tr>
|
|
6319
|
+
</tbody>
|
|
6320
|
+
</table>
|
|
6321
|
+
</div>
|
|
5879
6322
|
<h3 id="type-PushdownCapabilities">PushdownCapabilities</h3>
|
|
5880
6323
|
<p class="section-note">What a pushdown adapter can answer. Everything is off unless declared.</p>
|
|
5881
6324
|
<div class="table-wrap">
|
|
@@ -5892,6 +6335,18 @@ grid.destroy();
|
|
|
5892
6335
|
</tbody>
|
|
5893
6336
|
</table>
|
|
5894
6337
|
</div>
|
|
6338
|
+
<h3 id="type-PushdownFullDatasetConfig">PushdownFullDatasetConfig</h3>
|
|
6339
|
+
<p class="section-note">Opt-in, sticky full-dataset pull for a pushdown/remote source (BACKLOG-0000730). Off by default. When enabled, the source materialises the entire matching set client-side once per query signature and serves every window, total and statistic from it, so those figures are computed over the whole set rather than the loaded window. A set past either limit is refused with a visible `source:error` — never silently truncated.</p>
|
|
6340
|
+
<div class="table-wrap">
|
|
6341
|
+
<table>
|
|
6342
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
6343
|
+
<tbody>
|
|
6344
|
+
<tr><td class="name">enabled</td><td class="type">boolean</td><td class="desc">Sticky: hold the whole matching set client-side. Default `false`. <small>(optional)</small></td></tr>
|
|
6345
|
+
<tr><td class="name">maxRows</td><td class="type">number</td><td class="desc">Refuse (visible error) past this many rows. Default `1_000_000`. <small>(optional)</small></td></tr>
|
|
6346
|
+
<tr><td class="name">maxBytesEstimate</td><td class="type">number</td><td class="desc">Refuse past this estimated heap cost, in bytes. Default `512 * 1024 * 1024`. <small>(optional)</small></td></tr>
|
|
6347
|
+
</tbody>
|
|
6348
|
+
</table>
|
|
6349
|
+
</div>
|
|
5895
6350
|
<h3 id="type-PushdownPlan">PushdownPlan</h3>
|
|
5896
6351
|
<p class="section-note">How one request was divided between the engine and the grid.</p>
|
|
5897
6352
|
<div class="table-wrap">
|
|
@@ -5902,6 +6357,8 @@ grid.destroy();
|
|
|
5902
6357
|
<tr><td class="name">residual</td><td class="type">{ filters: object | null; sort: SortEntry[] | null; quick: string }</td><td class="desc">What the grid applied afterwards.</td></tr>
|
|
5903
6358
|
<tr><td class="name">needsAll</td><td class="type">boolean</td><td class="desc">Whether the whole result had to be fetched rather than a window.</td></tr>
|
|
5904
6359
|
<tr><td class="name">unpushed</td><td class="type">string[]</td><td class="desc">Which parts could not be pushed: `filter`, `sort`, `quick`.</td></tr>
|
|
6360
|
+
<tr><td class="name">full</td><td class="type">boolean</td><td class="desc">Whether the whole result was fetched because `fullDataset` is on, rather than only because residual work forced it. When true, totals and statistics reduce over the whole matching set and the windowed-stat warning is silent.</td></tr>
|
|
6361
|
+
<tr><td class="name">aggregates</td><td class="type">{</td><td class="desc">Per-aggregate provenance, present only when the last request computed aggregates (BACKLOG-0000730 Part B): which statistics the engine computed and which the client did, with the class the pushdown map assigned each. Under grouping it also carries the `groupBy` the subtotals were computed over. Build-time inspection, not a runtime per-figure marker. <small>(optional)</small></td></tr>
|
|
5905
6362
|
</tbody>
|
|
5906
6363
|
</table>
|
|
5907
6364
|
</div>
|
|
@@ -5913,6 +6370,8 @@ grid.destroy();
|
|
|
5913
6370
|
<tr><td class="name">adapter</td><td class="type">PushdownAdapter</td><td class="desc"></td></tr>
|
|
5914
6371
|
<tr><td class="name">compute</td><td class="type">object</td><td class="desc">The compute barrel, for applying whatever the engine could not. <small>(optional)</small></td></tr>
|
|
5915
6372
|
<tr><td class="name">pageSize</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6373
|
+
<tr><td class="name">fullDataset</td><td class="type">PushdownFullDatasetConfig</td><td class="desc">Opt-in full-dataset pull. Off unless `fullDataset.enabled` is set. See {@link PushdownFullDatasetConfig}. <small>(optional)</small></td></tr>
|
|
6374
|
+
<tr><td class="name">aggregates</td><td class="type">PushdownAggregatesConfig</td><td class="desc">Design-time aggregate-pushdown policy. Absent = client-side (today's behaviour). See {@link PushdownAggregatesConfig}. <small>(optional)</small></td></tr>
|
|
5916
6375
|
</tbody>
|
|
5917
6376
|
</table>
|
|
5918
6377
|
</div>
|