@toclocoinc/lattice-grid 1.47.0 → 1.49.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 +137 -5
- package/docs/api-detail.html +186 -2
- package/lattice-grid.d.ts +165 -3
- package/lattice-grid.esm.min.js +1665 -154
- package/lattice-grid.min.cjs +1665 -154
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +1665 -154
- package/modules/ai.esm.min.js +29 -4
- package/modules/ai.min.cjs +29 -4
- package/modules/ai.min.js +29 -4
- package/modules/angular.esm.min.js +3 -3
- package/modules/angular.min.cjs +3 -3
- package/modules/angular.min.js +3 -3
- package/modules/chart-alluvial.esm.min.js +1 -1
- package/modules/chart-arc.esm.min.js +1 -1
- package/modules/chart-bubblemap.esm.min.js +1 -1
- package/modules/chart-bump.esm.min.js +1 -1
- package/modules/chart-calendar.esm.min.js +1 -1
- package/modules/chart-decomposition.esm.min.js +1 -1
- package/modules/chart-diverging.esm.min.js +1 -1
- package/modules/chart-dumbbell.esm.min.js +1 -1
- package/modules/chart-fan.esm.min.js +1 -1
- package/modules/chart-hexbin.esm.min.js +1 -1
- package/modules/chart-hexmap.esm.min.js +1 -1
- package/modules/chart-icicle.esm.min.js +1 -1
- package/modules/chart-parallel.esm.min.js +1 -1
- package/modules/chart-ridgeline.esm.min.js +1 -1
- package/modules/chart-roc.esm.min.js +1 -1
- package/modules/chart-slope.esm.min.js +1 -1
- package/modules/chart-splom.esm.min.js +1 -1
- package/modules/chart-waffle.esm.min.js +1 -1
- package/modules/charts.esm.min.js +1368 -1237
- package/modules/charts.min.cjs +1368 -1237
- package/modules/charts.min.js +1368 -1237
- package/modules/data-router.esm.min.js +4 -4
- package/modules/data-router.min.cjs +4 -4
- package/modules/data-router.min.js +4 -4
- package/modules/devtools.esm.min.js +2 -2
- package/modules/devtools.min.cjs +2 -2
- package/modules/devtools.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +4 -4
- package/modules/dhtmlx-compat.min.cjs +4 -4
- package/modules/dhtmlx-compat.min.js +4 -4
- package/modules/gantt.esm.min.js +4 -4
- package/modules/gantt.min.cjs +4 -4
- package/modules/gantt.min.js +4 -4
- package/modules/htmx.esm.min.js +1665 -154
- package/modules/htmx.min.cjs +1665 -154
- package/modules/htmx.min.js +1665 -154
- package/modules/kanban.esm.min.js +4 -4
- package/modules/kanban.min.cjs +4 -4
- package/modules/kanban.min.js +4 -4
- package/modules/kpi.esm.min.js +4 -4
- package/modules/kpi.min.cjs +4 -4
- package/modules/kpi.min.js +4 -4
- package/modules/mock-socket.esm.min.js +2 -2
- package/modules/mock-socket.min.cjs +2 -2
- package/modules/mock-socket.min.js +2 -2
- package/modules/react.esm.min.js +3 -3
- package/modules/react.min.cjs +3 -3
- package/modules/react.min.js +3 -3
- package/modules/svelte.esm.min.js +3 -3
- package/modules/svelte.min.cjs +3 -3
- package/modules/svelte.min.js +3 -3
- package/modules/vue.esm.min.js +3 -3
- package/modules/vue.min.cjs +3 -3
- package/modules/vue.min.js +3 -3
- package/modules/webcomponent.esm.min.js +1665 -154
- package/modules/webcomponent.min.cjs +1665 -154
- package/modules/webcomponent.min.js +1665 -154
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
dependencies, no build step required. Optional adapters for React, Vue, Svelte
|
|
5
5
|
and Web Components ship alongside it.
|
|
6
6
|
|
|
7
|
-
Version 1.
|
|
7
|
+
Version 1.49.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/docs/API.html
CHANGED
|
@@ -865,6 +865,7 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
|
|
|
865
865
|
<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>
|
|
866
866
|
<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>
|
|
867
867
|
<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>
|
|
868
|
+
<tr><td class="name">find</td><td class="type">boolean | FindConfig</td><td class="dflt">true</td><td class="desc">The in-grid find bar: <kbd>Ctrl</kbd>+<kbd>F</kbd> (<kbd>Cmd</kbd>+<kbd>F</kbd>) with focus in the grid opens it; typing highlights every matching cell in place without filtering a row away; <kbd>Enter</kbd> / <kbd>Shift</kbd>+<kbd>Enter</kbd> step through the matches. <code>{ shortcut, debounce }</code>: <code>shortcut: false</code> keeps the bar reachable through <code>grid.find.open()</code> only; <code>debounce</code> is the typing quiet period in ms (120). <code>false</code> removes the bar and the binding; <code>grid.find(text)</code> still searches. See <a href="api-detail.html#find">Find</a>.</td></tr>
|
|
868
869
|
<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>
|
|
869
870
|
<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>
|
|
870
871
|
<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>
|
|
@@ -3218,6 +3219,42 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
3218
3219
|
</table>
|
|
3219
3220
|
</div>
|
|
3220
3221
|
|
|
3222
|
+
<p class="section-note">
|
|
3223
|
+
<strong>Typed binding for timestamp and date columns.</strong> A prepared statement binds a
|
|
3224
|
+
filter value with the value's own type, not the column's: the grid sends an instant as an
|
|
3225
|
+
ISO-8601 string, the client binds it as <code>VARCHAR</code>, and DuckDB refuses
|
|
3226
|
+
<code>"ts" >= ?</code> against a <code>TIMESTAMP</code> column (<em>Binder Error: Cannot
|
|
3227
|
+
compare values of type TIMESTAMP and type VARCHAR</em>). The adapter therefore types the
|
|
3228
|
+
<em>placeholder</em>: a comparison or <code>IN</code> member against a <code>TIMESTAMP</code>,
|
|
3229
|
+
<code>TIMESTAMP WITH TIME ZONE</code>, <code>DATE</code>, <code>TIME</code> or
|
|
3230
|
+
<code>TIMESTAMP_S/_MS/_NS</code> column is written <code>CAST(? AS <that type>)</code>,
|
|
3231
|
+
and the value is still bound, never interpolated. The column's type comes from the engine — one
|
|
3232
|
+
<code>DESCRIBE SELECT * FROM <from></code> on the first query, cached for the adapter's
|
|
3233
|
+
life and exposed as <code>adapter.describe()</code> — so an untyped grid column over a
|
|
3234
|
+
timestamp is covered. When the schema does not name the column (a <code>DESCRIBE</code> that
|
|
3235
|
+
failed, said once), the grid column's declared type on the condition is the fallback:
|
|
3236
|
+
<code>timestamp</code>/<code>datetime</code> cast to <code>TIMESTAMP</code>,
|
|
3237
|
+
<code>date</code>/<code>dateString</code> to <code>DATE</code>, <code>time</code> to
|
|
3238
|
+
<code>TIME</code>. The engine's type wins when both are known. A <code>Date</code> or an
|
|
3239
|
+
epoch-milliseconds number is bound as its ISO instant, because DuckDB has no cast from a number
|
|
3240
|
+
to a timestamp. The same schema fixes <code>blank</code>: <code>= ''</code> is a conversion
|
|
3241
|
+
error on any non-text column, so a typed column's blank test is <code>IS NULL</code> alone.
|
|
3242
|
+
Text and numeric comparisons (<code>VARCHAR</code>, <code>BIGINT</code>, <code>DOUBLE</code>,
|
|
3243
|
+
<code>DECIMAL</code>, <code>HUGEINT</code>) are written exactly as before, with no cast.
|
|
3244
|
+
</p>
|
|
3245
|
+
<p class="section-note">
|
|
3246
|
+
<strong>Time zones, honestly.</strong> The cast is the engine's, so its zone rules apply. Against
|
|
3247
|
+
a naive <code>TIMESTAMP</code> column the wall-clock digits of the bound string are compared;
|
|
3248
|
+
an instant ending in <code>Z</code> — which is what the grid's own date filter sends — therefore
|
|
3249
|
+
matches a column that stores UTC wall time, the usual convention for log and event data. A
|
|
3250
|
+
non-zero offset in the string is engine-version dependent (DuckDB 1.1 converts it to UTC, 1.5
|
|
3251
|
+
keeps the digits as written), so send <code>Z</code> instants, not local offsets. Against a
|
|
3252
|
+
<code>TIMESTAMP WITH TIME ZONE</code> column an offset or <code>Z</code> is honoured exactly,
|
|
3253
|
+
and a string with <em>no</em> zone is interpreted in the engine's session
|
|
3254
|
+
<code>TimeZone</code> (UTC in DuckDB-Wasm unless the ICU extension is loaded and the setting
|
|
3255
|
+
changed). Against a <code>DATE</code> column an instant is truncated to its UTC day.
|
|
3256
|
+
</p>
|
|
3257
|
+
|
|
3221
3258
|
<h5 id="dfql-options"><code>dfqlAdapter</code></h5>
|
|
3222
3259
|
<div class="table-wrap">
|
|
3223
3260
|
<table>
|
|
@@ -3857,6 +3894,7 @@ off(); <span class="cmt">// on() returns i
|
|
|
3857
3894
|
<tr><td class="name">history:applied</td><td class="type">{ direction, step }</td><td class="desc">An action was undone or redone. Distinct from <code>history:changed</code>, which also fires when a new action is pushed onto the stacks and so cannot tell you anything was reversed.</td></tr>
|
|
3858
3895
|
<tr><td class="name">state:reset</td><td class="type">{ state }</td><td class="desc">The grid was returned to its baseline.</td></tr>
|
|
3859
3896
|
<tr><td class="name">highlight:changed</td><td class="type">{ highlights }</td><td class="desc">A highlight was added or cleared.</td></tr>
|
|
3897
|
+
<tr><td class="name">find:changed</td><td class="type">{ text, caseSensitive, wholeCell, columns, open, count }</td><td class="desc">The find query, its matches, the current match or the bar's open state changed. <code>count</code> is a <code>FindCount</code>; while the bar's sliced scan is still running <code>count.complete</code> is false and the figure is partial.</td></tr>
|
|
3860
3898
|
<tr><td class="name">redaction:changed</td><td class="type">{ columns }</td><td class="desc">A column was redacted or restored.</td></tr>
|
|
3861
3899
|
<tr><td class="name">header:contextmenu</td><td class="type">{ colId, column, element, x, y }</td><td class="desc">A column heading was right-clicked.</td></tr>
|
|
3862
3900
|
<tr><td class="name">render:done</td><td class="type">{ first, last }</td><td class="desc">The cells are written and stable. Anything decorating them from outside must run after this, the cell layer rewrites each cell's <code>className</code> wholesale and would otherwise erase it.</td></tr>
|
|
@@ -6130,7 +6168,7 @@ spans.addSpan('R0', 'name', 3, 2); <span class="cmt">// rowspan 3, colspan 2</sp
|
|
|
6130
6168
|
<h3 id="nested-config-example">Nested configuration, executed</h3>
|
|
6131
6169
|
<p class="section-note">Thirteen option blocks, each key written where it belongs. Parsed and evaluated on
|
|
6132
6170
|
every build, so a key that was renamed or moved shows up here.</p>
|
|
6133
|
-
<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>
|
|
6171
|
+
<pre data-run="js" data-expect="14" data-covers="config:aboveLimit config:adapter config:ageBy 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:maxAge 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>
|
|
6134
6172
|
<span class="cmt">// the option names: each one below is a documented key, written where it</span>
|
|
6135
6173
|
<span class="cmt">// belongs, so a key that was renamed or moved stops matching its interface.</span>
|
|
6136
6174
|
<span class="kw">const</span> source = {}, other = {}, provider = {}, compute = {}, adapter = {};
|
|
@@ -6171,7 +6209,10 @@ spans.addSpan('R0', 'name', 3, 2); <span class="cmt">// rowspan 3, colspan 2</sp
|
|
|
6171
6209
|
<span class="kw">const</span> pagedSourceConfig = { mode: 'paged', pageSize: 100, maxCachedPages: 5, fetch };
|
|
6172
6210
|
|
|
6173
6211
|
<span class="cmt">// A streaming source — StreamSourceConfig</span>
|
|
6174
|
-
<span class="kw">const</span> streamSourceConfig = { mode: 'stream', open, coalesceMs: 16, maxRows: 1e6, promoteToMemoryBelow: 5e5
|
|
6212
|
+
<span class="kw">const</span> streamSourceConfig = { mode: 'stream', open, coalesceMs: 16, maxRows: 1e6, promoteToMemoryBelow: 5e5,
|
|
6213
|
+
<span class="cmt">// A rolling *time* window beside the count one: keep five minutes, aged by the</span>
|
|
6214
|
+
<span class="cmt">// row's own clock. Omit ageBy and rows age from when they arrived instead.</span>
|
|
6215
|
+
maxAge: 5 * 60 * 1000, ageBy: 'ts' };
|
|
6175
6216
|
|
|
6176
6217
|
<span class="cmt">// A pushdown source — PushdownSourceConfig</span>
|
|
6177
6218
|
<span class="kw">const</span> pushdownSourceConfig = { adapter, compute, pageSize: 200 };
|
|
@@ -6242,7 +6283,7 @@ grid.destroy();
|
|
|
6242
6283
|
<p class="section-note">Each documented event is subscribed to and unsubscribed on every build. A consumer
|
|
6243
6284
|
wiring a handler to a renamed event gets silence, which is indistinguishable from an event that
|
|
6244
6285
|
has not fired yet — so the name is checked rather than left to be discovered.</p>
|
|
6245
|
-
<pre data-run="js" data-expect="
|
|
6286
|
+
<pre data-run="js" data-expect="108" data-covers="event:find:changed event:cell:changed event:cell:clicked event:cell:confirmed event:cell:conflict event:cell:contextmenu event:cell:dblclicked event:cell:edit:end event:cell:edit:start event:cell:pending event:cell:reverted event:clipboard:copy event:column:filter:open event:column:profile:open event:column:grouped event:column:menu:open event:column:pivoted event:column:resized event:columns:changed event:columns:tagged event:comment:added event:comment:deleted event:comment:edited event:comment:failed event:comment:indexLoaded event:comment:resolved event:comment:threadClosed event:comment:threadOpened event:comment:unresolved event:destroy event:detail:toggled event:diff:changed event:diff:swapped event:export:progress event:facet:computed event:facet:expanded event:facet:failed event:facet:filtered event:form:closed event:form:error event:form:opened event:form:saved event:formatting:changed event:group:toggled event:header:contextmenu event:highlight:changed event:history:applied event:history:changed event:licence:changed event:page:changed event:permissions:changed event:presence:failed event:presence:joined event:presence:left event:presence:lockRefused event:presence:published event:presence:updated event:presentation:captured event:presentation:changed event:presentation:ended event:presentation:scale event:presentation:spotlight event:presentation:started event:presentation:view event:range:changed event:ready event:redaction:changed event:render:done event:render:first event:row:clicked event:row:copied event:row:dblclicked event:row:edit:end event:row:edit:start event:row:moved event:row:received event:row:sent event:rows:deferred event:rows:paused event:rows:queued event:rows:resumed event:scroll event:scroll:end event:selection:changed event:size:changed event:source:error event:stream:chunk event:stream:end event:stream:evicted event:timeline:attached event:timeline:detached event:timeline:seek event:timeline:seeking event:toolpanel:focus event:tree:loadAborted event:tree:loadFailed event:tree:loaded event:tree:loading event:view:applied event:view:default event:view:removed event:view:renamed event:view:saved event:views:changed event:row:pending event:row:confirmed event:row:reverted event:row:conflict"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
6246
6287
|
|
|
6247
6288
|
<span class="cmt">// Every documented event name, checked against the bus that would carry it.</span>
|
|
6248
6289
|
<span class="cmt">// Subscribing to a name the grid does not know is the failure this catches:</span>
|
|
@@ -6259,7 +6300,7 @@ grid.destroy();
|
|
|
6259
6300
|
'diff:changed', 'diff:swapped', 'export:progress', 'facet:computed',
|
|
6260
6301
|
'facet:expanded', 'facet:failed', 'facet:filtered', 'form:closed',
|
|
6261
6302
|
'form:error', 'form:opened', 'form:saved', 'formatting:changed',
|
|
6262
|
-
'group:toggled', 'header:contextmenu', 'highlight:changed', 'history:applied',
|
|
6303
|
+
'group:toggled', 'header:contextmenu', 'highlight:changed', 'find:changed', 'history:applied',
|
|
6263
6304
|
'history:changed', 'licence:changed', 'page:changed', 'permissions:changed',
|
|
6264
6305
|
'presence:failed', 'presence:joined', 'presence:left', 'presence:lockRefused',
|
|
6265
6306
|
'presence:published', 'presence:updated', 'presentation:captured', 'presentation:changed',
|
|
@@ -6878,6 +6919,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
6878
6919
|
<tr><td class="name">labels</td><td class="type">boolean</td><td class="desc">Draw the tick labels. <small>(optional)</small></td></tr>
|
|
6879
6920
|
<tr><td class="name">every</td><td class="type">number</td><td class="desc">Show every nth category label, on a crowded category axis. <small>(optional)</small></td></tr>
|
|
6880
6921
|
<tr><td class="name">rotate</td><td class="type">boolean | 'auto'</td><td class="desc">Force the category labels' rotation rather than deciding it. <small>(optional)</small></td></tr>
|
|
6922
|
+
<tr><td class="name">window</td><td class="type">Pick<WindowSpec, 'kind' | 'span'></td><td class="desc">A rolling window for the axis domain (BACKLOG-0001036), in the shipped `WindowSpec` vocabulary that rolling statistics already use. Only `{ kind: 'time', span }` applies to an axis: the domain becomes the last `span` milliseconds ending **now**, so the chart keeps scrolling left while the feed is silent — the thing a count window cannot do, because with no rows arriving nothing changes. Advanced on a low-frequency clock (a quarter of the window, between 50 ms and 1 s), never per frame, and stopped when the chart is destroyed or its document is hidden. Needs a continuous x axis carrying wall-clock times; `{ kind: 'count' }` is the source's `maxRows` and is refused here rather than given a second meaning. <small>(optional)</small></td></tr>
|
|
6881
6923
|
</tbody>
|
|
6882
6924
|
</table>
|
|
6883
6925
|
</div>
|
|
@@ -8085,6 +8127,92 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8085
8127
|
</tbody>
|
|
8086
8128
|
</table>
|
|
8087
8129
|
</div>
|
|
8130
|
+
<h3 id="type-FindApi">FindApi</h3>
|
|
8131
|
+
<p class="section-note">In-grid find (BACKLOG-0001018): locate text and step through where it occurs without filtering anything away. Matches are a visual overlay — no row is reordered, removed or edited — and coexist with the quick filter.</p>
|
|
8132
|
+
<div class="table-wrap">
|
|
8133
|
+
<table>
|
|
8134
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
8135
|
+
<tbody>
|
|
8136
|
+
<tr><td class="name">open</td><td class="type">(text?: string): void</td><td class="desc">Show the bar with focus in its input, optionally seeding the text.</td></tr>
|
|
8137
|
+
<tr><td class="name">close</td><td class="type">(): void</td><td class="desc">Hide the bar and clear every match.</td></tr>
|
|
8138
|
+
<tr><td class="name">clear</td><td class="type">(): void</td><td class="desc">Clear the query and the highlights, leaving the bar as it is.</td></tr>
|
|
8139
|
+
<tr><td class="name">next</td><td class="type">(): FindMatch | null</td><td class="desc">The next match, wrapping from the last to the first, scrolled into view and made the active cell unless an edit is open.</td></tr>
|
|
8140
|
+
<tr><td class="name">prev</td><td class="type">(): FindMatch | null</td><td class="desc">The previous match, wrapping from the first to the last.</td></tr>
|
|
8141
|
+
<tr><td class="name">goTo</td><td class="type">(index: number): FindMatch | null</td><td class="desc">Make the match at a position in `matches()` current.</td></tr>
|
|
8142
|
+
<tr><td class="name">matches</td><td class="type">(): FindMatch[]</td><td class="desc">Every match, in display order: pinned-top rows, then the body, then pinned-bottom rows.</td></tr>
|
|
8143
|
+
<tr><td class="name">count</td><td class="type">(): FindCount</td><td class="desc"></td></tr>
|
|
8144
|
+
<tr><td class="name">current</td><td class="type">(): FindMatch | null</td><td class="desc"></td></tr>
|
|
8145
|
+
<tr><td class="name">state</td><td class="type">(): FindState</td><td class="desc"></td></tr>
|
|
8146
|
+
<tr><td class="name">stateFor</td><td class="type">(key: string, colId: string): 'current' | 'match' | null</td><td class="desc">How a cell is painted: the current match, another match, or nothing.</td></tr>
|
|
8147
|
+
</tbody>
|
|
8148
|
+
</table>
|
|
8149
|
+
</div>
|
|
8150
|
+
<h3 id="type-FindConfig">FindConfig</h3>
|
|
8151
|
+
<p class="section-note">The in-grid find bar's settings (BACKLOG-0001018). `find: true` or an omitted key mounts the bar with these defaults; `find: false` removes the bar and its shortcut while `grid.find` keeps working programmatically.</p>
|
|
8152
|
+
<div class="table-wrap">
|
|
8153
|
+
<table>
|
|
8154
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
8155
|
+
<tbody>
|
|
8156
|
+
<tr><td class="name">shortcut</td><td class="type">boolean</td><td class="desc">Bind Ctrl+F (Cmd+F on a Mac) while focus is in the grid. The browser's own find is untouched while focus is anywhere else on the page. Default true. <small>(optional)</small></td></tr>
|
|
8157
|
+
<tr><td class="name">debounce</td><td class="type">number</td><td class="desc">Milliseconds of typing quiet before the bar searches. Default 120. <small>(optional)</small></td></tr>
|
|
8158
|
+
</tbody>
|
|
8159
|
+
</table>
|
|
8160
|
+
</div>
|
|
8161
|
+
<h3 id="type-FindCount">FindCount</h3>
|
|
8162
|
+
<p class="section-note">How many matches there are and which is current. `windowed` is the honest scope flag: over a paged pushdown source only the loaded rows are searched, so `total` counts matches in `loaded` rows out of the `rows` the source reports for the whole matching set.</p>
|
|
8163
|
+
<div class="table-wrap">
|
|
8164
|
+
<table>
|
|
8165
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
8166
|
+
<tbody>
|
|
8167
|
+
<tr><td class="name">current</td><td class="type">number</td><td class="desc">1-based position of the current match; 0 when there is none.</td></tr>
|
|
8168
|
+
<tr><td class="name">total</td><td class="type">number</td><td class="desc"></td></tr>
|
|
8169
|
+
<tr><td class="name">complete</td><td class="type">boolean</td><td class="desc">False while the bar's sliced scan is still running, so a partial count is never read as final.</td></tr>
|
|
8170
|
+
<tr><td class="name">windowed</td><td class="type">boolean</td><td class="desc"></td></tr>
|
|
8171
|
+
<tr><td class="name">loaded</td><td class="type">number</td><td class="desc">Rows the search actually read; a windowed source's not-yet-fetched placeholders are not counted.</td></tr>
|
|
8172
|
+
<tr><td class="name">rows</td><td class="type">number</td><td class="desc">The rows the source reports for the whole matching set, when it can say.</td></tr>
|
|
8173
|
+
</tbody>
|
|
8174
|
+
</table>
|
|
8175
|
+
</div>
|
|
8176
|
+
<h3 id="type-FindMatch">FindMatch</h3>
|
|
8177
|
+
<p class="section-note">One matching cell.</p>
|
|
8178
|
+
<div class="table-wrap">
|
|
8179
|
+
<table>
|
|
8180
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
8181
|
+
<tbody>
|
|
8182
|
+
<tr><td class="name">key</td><td class="type">string</td><td class="desc"></td></tr>
|
|
8183
|
+
<tr><td class="name">colId</td><td class="type">string</td><td class="desc"></td></tr>
|
|
8184
|
+
<tr><td class="name">index</td><td class="type">number</td><td class="desc">The display index, or -1 for a row pinned to an edge.</td></tr>
|
|
8185
|
+
<tr><td class="name">pinned</td><td class="type">'top' | 'bottom' | null</td><td class="desc">Which sticky strip a pinned row is in; null for a body row.</td></tr>
|
|
8186
|
+
</tbody>
|
|
8187
|
+
</table>
|
|
8188
|
+
</div>
|
|
8189
|
+
<h3 id="type-FindQuery">FindQuery</h3>
|
|
8190
|
+
<p class="section-note">How `grid.find(text, opts)` matches. Defaults: case-insensitive, substring, every visible column, starting from the first row. Find matches the **formatted display text** — what the cell shows, a column `format` included — never a raw value; there is no regular-expression mode.</p>
|
|
8191
|
+
<div class="table-wrap">
|
|
8192
|
+
<table>
|
|
8193
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
8194
|
+
<tbody>
|
|
8195
|
+
<tr><td class="name">caseSensitive</td><td class="type">boolean</td><td class="desc">Match letter case exactly. Default false. <small>(optional)</small></td></tr>
|
|
8196
|
+
<tr><td class="name">wholeCell</td><td class="type">boolean</td><td class="desc">The whole cell text must equal the search text rather than contain it. Default false. <small>(optional)</small></td></tr>
|
|
8197
|
+
<tr><td class="name">columns</td><td class="type">string[] | string | null</td><td class="desc">Search only these column ids. Omitted searches every visible column. <small>(optional)</small></td></tr>
|
|
8198
|
+
<tr><td class="name">from</td><td class="type">number</td><td class="desc">The display index to start from: the first match at or after it becomes current. Default 0. <small>(optional)</small></td></tr>
|
|
8199
|
+
</tbody>
|
|
8200
|
+
</table>
|
|
8201
|
+
</div>
|
|
8202
|
+
<h3 id="type-FindState">FindState</h3>
|
|
8203
|
+
<p class="section-note">The current query and whether the bar is showing.</p>
|
|
8204
|
+
<div class="table-wrap">
|
|
8205
|
+
<table>
|
|
8206
|
+
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
8207
|
+
<tbody>
|
|
8208
|
+
<tr><td class="name">text</td><td class="type">string</td><td class="desc"></td></tr>
|
|
8209
|
+
<tr><td class="name">caseSensitive</td><td class="type">boolean</td><td class="desc"></td></tr>
|
|
8210
|
+
<tr><td class="name">wholeCell</td><td class="type">boolean</td><td class="desc"></td></tr>
|
|
8211
|
+
<tr><td class="name">columns</td><td class="type">string[] | null</td><td class="desc"></td></tr>
|
|
8212
|
+
<tr><td class="name">open</td><td class="type">boolean</td><td class="desc"></td></tr>
|
|
8213
|
+
</tbody>
|
|
8214
|
+
</table>
|
|
8215
|
+
</div>
|
|
8088
8216
|
<h3 id="type-ForecastPoint">ForecastPoint</h3>
|
|
8089
8217
|
<p class="section-note">One forecast step: the point estimate and, where a band applies, its interval.</p>
|
|
8090
8218
|
<div class="table-wrap">
|
|
@@ -8224,6 +8352,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8224
8352
|
<tr><td class="name">licence</td><td class="type">LicenceApi</td><td class="desc">Licence state, and setting a key after construction. <small>(read-only)</small></td></tr>
|
|
8225
8353
|
<tr><td class="name">pagination</td><td class="type">PaginationApi</td><td class="desc">Pages, where the grid is paged rather than scrolled. <small>(read-only)</small></td></tr>
|
|
8226
8354
|
<tr><td class="name">highlight</td><td class="type">HighlightApi</td><td class="desc">Transient emphasis on a row, column or cell. <small>(read-only)</small></td></tr>
|
|
8355
|
+
<tr><td class="name">find</td><td class="type">FindApi</td><td class="desc">In-grid find: locate text without filtering, and step through the matches. <small>(read-only)</small></td></tr>
|
|
8227
8356
|
<tr><td class="name">redaction</td><td class="type">RedactionApi</td><td class="desc">Values hidden from view and from export. <small>(read-only)</small></td></tr>
|
|
8228
8357
|
<tr><td class="name">capture</td><td class="type">(opts?: CaptureOptions): Promise<Blob></td><td class="desc">An image of the grid as drawn, where the module is installed. <small>(optional)</small></td></tr>
|
|
8229
8358
|
<tr><td class="name">annotate</td><td class="type">AnnotationApi</td><td class="desc">Drawing over the grid, where the module is installed. <small>(optional)</small></td></tr>
|
|
@@ -8343,6 +8472,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
8343
8472
|
<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>
|
|
8344
8473
|
<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>
|
|
8345
8474
|
<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>
|
|
8475
|
+
<tr><td class="name">find</td><td class="type">boolean | FindConfig</td><td class="desc">The in-grid find bar (BACKLOG-0001018): Ctrl+F / Cmd+F with focus in the grid opens it; typing highlights every matching cell in place without filtering a row away; Enter and Shift+Enter step through the matches. `false` removes the bar and its shortcut; the `grid.find` API still works. Default true. <small>(optional)</small></td></tr>
|
|
8346
8476
|
<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>
|
|
8347
8477
|
<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>
|
|
8348
8478
|
<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>
|
|
@@ -9545,7 +9675,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
9545
9675
|
<table>
|
|
9546
9676
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
9547
9677
|
<tbody>
|
|
9548
|
-
<tr><td class="name">toRow</td><td class="type">(row: string | number, align?: 'start' | 'center' | 'end' | 'auto'): void</td><td class="desc">A row key, or a display index. A key survives a sort and is usually what a caller holds; resolving one scans the display order, so prefer an index when scrolling a very large grid repeatedly.</td></tr>
|
|
9678
|
+
<tr><td class="name">toRow</td><td class="type">(row: string | number, align?: 'start' | 'center' | 'end' | 'auto'): void</td><td class="desc">A row key, or a display index. A key survives a sort and is usually what a caller holds; resolving one scans the display order, so prefer an index when scrolling a very large grid repeatedly. The row lands fully visible in the part of the body the pinned strips (pinned rows, sticky group headings, a bottom grand total) do not cover: `end` puts it just above the bottom strip, `start` just below the top one.</td></tr>
|
|
9549
9679
|
<tr><td class="name">toColumn</td><td class="type">(id: string): void</td><td class="desc"></td></tr>
|
|
9550
9680
|
<tr><td class="name">toCell</td><td class="type">(row: string | number, colId: string, align?: 'start' | 'center' | 'end' | 'auto'): void</td><td class="desc">Scroll a cell into view, both axes in one call.</td></tr>
|
|
9551
9681
|
<tr><td class="name">position</td><td class="type">(): { top: number; left: number }</td><td class="desc"></td></tr>
|
|
@@ -9774,6 +9904,8 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
|
|
|
9774
9904
|
<tr><td class="name">mode</td><td class="type">'stream'</td><td class="desc"></td></tr>
|
|
9775
9905
|
<tr><td class="name">open</td><td class="type">(req: {</td><td class="desc"></td></tr>
|
|
9776
9906
|
<tr><td class="name">maxRows</td><td class="type">number</td><td class="desc">The most rows to keep. A stream has no end, so an unbounded grid dies overnight; this makes it a sliding window and the oldest rows are dropped. Omit for no limit. Set on the source, not passed to `open`, it bounds what the grid retains rather than what the producer sends. <small>(optional)</small></td></tr>
|
|
9907
|
+
<tr><td class="name">maxAge</td><td class="type">number</td><td class="desc">The longest a row is kept, in milliseconds — a rolling *time* window, sitting beside `maxRows` as a second, independent bound (BACKLOG-0001036). Rows older than the span are evicted through the same path, the same `evicted` counters and the same `stream:evicted` event as the count bound, so an existing readout keeps working. Set both and whichever bites first applies. Eviction continues on a low-frequency timer while the feed is idle, so "the last five minutes" keeps shrinking through a silent period rather than freezing — which is the thing `maxRows` cannot do. Retention is a *bound, not a guillotine*: rows live a little past the span before a block is dropped. Two things add to it. First the eviction slack, ten per cent of the span, exactly as `maxRows` overshoots its count, so the row permutation is rebuilt once per block rather than once per row. Second, when the feed is idle, up to one tick of the eviction timer, which runs at a quarter of the span clamped to between 50 ms and one second. So the real ceiling is roughly `span * 1.1 + tick`, and because the tick has a floor it is proportionally larger the shorter the window: negligible at a five-minute window (about 10%), around 1.25x at ten seconds, and as much as ~1.35x at three. That is the deliberate trade for an idle grid that costs no CPU. Omit for no age limit. <small>(optional)</small></td></tr>
|
|
9908
|
+
<tr><td class="name">ageBy</td><td class="type">string | ((row: unknown) => unknown)</td><td class="desc">Which clock `maxAge` reads: a column id (or dotted path), or a function of the row returning a `Date`, epoch milliseconds, or an ISO string (BACKLOG-0001036). Given, the window follows the **data's own** clock, so it means what the producer means — and inherits the producer's clock skew. Omitted, `maxAge` falls back to **arrival time**: when the row reached this source. Arrival time needs no timestamp column and cannot be skewed, but it is not event time — a row delayed in transit counts as young. A row whose time value cannot be read is never aged out. <small>(optional)</small></td></tr>
|
|
9777
9909
|
<tr><td class="name">promoteToMemoryBelow</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
9778
9910
|
<tr><td class="name">coalesceMs</td><td class="type">number</td><td class="desc"><small>(optional)</small></td></tr>
|
|
9779
9911
|
</tbody>
|
package/docs/api-detail.html
CHANGED
|
@@ -437,7 +437,7 @@
|
|
|
437
437
|
<div class="shell">
|
|
438
438
|
<aside class="rail">
|
|
439
439
|
<p class="rail__brand">Lattice Grid</p>
|
|
440
|
-
<p class="rail__sub">Developer guide · v1.
|
|
440
|
+
<p class="rail__sub">Developer guide · v1.49.0</p>
|
|
441
441
|
<nav>
|
|
442
442
|
<div class="rail__group">
|
|
443
443
|
<span class="rail__label">Start here</span>
|
|
@@ -506,6 +506,7 @@
|
|
|
506
506
|
<a href="#rules-guide">Conditional formatting</a>
|
|
507
507
|
<a href="#formatting-guide">Formatting a user can change</a>
|
|
508
508
|
<a href="#quickfilter-guide">Quick filter</a>
|
|
509
|
+
<a href="#find">Find</a>
|
|
509
510
|
<a href="#charts-guide">In-cell charts</a>
|
|
510
511
|
</div>
|
|
511
512
|
<div class="rail__group">
|
|
@@ -3149,8 +3150,17 @@ createGrid(el, {
|
|
|
3149
3150
|
<p>Column and table names are checked against an identifier pattern rather than escaped, and
|
|
3150
3151
|
a name that fails is refused. Integers past the safe range are kept as strings instead of
|
|
3151
3152
|
being rounded into a plausible lie.</p>
|
|
3153
|
+
<p>A timestamp or date filter is bound through a typed placeholder — <code>"ts" >=
|
|
3154
|
+
CAST(? AS TIMESTAMP)</code> — because a prepared statement binds an ISO string as
|
|
3155
|
+
<code>VARCHAR</code> and DuckDB will not compare that with a <code>TIMESTAMP</code>,
|
|
3156
|
+
<code>TIMESTAMP WITH TIME ZONE</code> or <code>DATE</code> column. The adapter reads the
|
|
3157
|
+
column types once from the engine (<code>DESCRIBE</code>) and falls back to the grid column's
|
|
3158
|
+
declared type, so both a typed and an untyped grid column over a timestamp filter correctly,
|
|
3159
|
+
including a time window. Send instants ending in <code>Z</code>, as the grid's date filter
|
|
3160
|
+
does: the cast is the engine's, and its zone rules apply (see the reference for the
|
|
3161
|
+
<code>TIMESTAMPTZ</code> and naive-string cases).</p>
|
|
3152
3162
|
<p><code>demo/duckdb.html</code> runs this against a Parquet file of several million readings
|
|
3153
|
-
with no server involved.</p>
|
|
3163
|
+
with no server involved, including a time-window filter on its <code>TIMESTAMP</code> column.</p>
|
|
3154
3164
|
</div>
|
|
3155
3165
|
|
|
3156
3166
|
<h3 id="fulldataset">Whole-dataset statistics over a remote source</h3>
|
|
@@ -4460,6 +4470,96 @@ grid.updates.log({ since: Date.now() - 60000 }); <span class="cmt">// what arr
|
|
|
4460
4470
|
left up overnight holds every row it was ever sent. Set <code>source.maxRows</code> and the
|
|
4461
4471
|
stream becomes a sliding window, dropping the oldest as new ones arrive and reporting how
|
|
4462
4472
|
many it let go through <code>evicted</code> on the progress report.</p>
|
|
4473
|
+
</div>
|
|
4474
|
+
|
|
4475
|
+
<h3 id="time-window-guide">A time window, not just a row count</h3>
|
|
4476
|
+
<p class="lead-in">
|
|
4477
|
+
<code>maxRows</code> is a <em>count</em> window. <code>maxAge</code> is a <em>time</em>
|
|
4478
|
+
window. They are different promises, and a live feed usually wants the second one.
|
|
4479
|
+
</p>
|
|
4480
|
+
<div class="example">
|
|
4481
|
+
<p class="example__label">Keep the last five minutes, and show the last five minutes</p>
|
|
4482
|
+
<pre><code>const grid = createGrid(el, {
|
|
4483
|
+
columns,
|
|
4484
|
+
source: {
|
|
4485
|
+
mode: 'stream',
|
|
4486
|
+
open,
|
|
4487
|
+
maxAge: 5 * 60 * 1000, <span class="cmt">// keep five minutes of rows</span>
|
|
4488
|
+
ageBy: 'ts', <span class="cmt">// ...aged by this column; omit for arrival time</span>
|
|
4489
|
+
maxRows: 20000, <span class="cmt">// ...and never more than this many, whichever bites first</span>
|
|
4490
|
+
},
|
|
4491
|
+
});
|
|
4492
|
+
|
|
4493
|
+
createChart({
|
|
4494
|
+
grid, container, type: 'line', x: 'ts', y: { col: 'value', fn: 'avg' },
|
|
4495
|
+
<span class="cmt">// The x domain is the last five minutes ending *now*, so the chart</span>
|
|
4496
|
+
<span class="cmt">// keeps scrolling left even while the feed is silent.</span>
|
|
4497
|
+
axis: { x: { window: { kind: 'time', span: 5 * 60 * 1000 } } },
|
|
4498
|
+
});</code></pre>
|
|
4499
|
+
</div>
|
|
4500
|
+
<div class="why">
|
|
4501
|
+
<p><strong>A count window drifts, and it freezes.</strong> <code>maxRows</code> equals
|
|
4502
|
+
“the last five minutes” only while the feed rate is steady: a burst silently
|
|
4503
|
+
shrinks the window to two minutes, a quiet spell stretches it to twenty, and the x axis
|
|
4504
|
+
changes span under the reader. Worse, when the feed goes quiet nothing is evicted and the
|
|
4505
|
+
chart stops moving, even though time is still passing — and the silence is usually the
|
|
4506
|
+
thing worth seeing. <code>maxAge</code> is a span of wall clock, so it means the same thing
|
|
4507
|
+
whatever the feed is doing.</p>
|
|
4508
|
+
<p><strong>Two bounds, one eviction path.</strong> <code>maxAge</code> and
|
|
4509
|
+
<code>maxRows</code> are independent and compose: both are applied on the same pass and
|
|
4510
|
+
whichever bites first is simply the one that drops rows. Neither is silently ignored.
|
|
4511
|
+
Age eviction reuses the count bound’s machinery outright, so <code>evicted</code> on
|
|
4512
|
+
the progress report and the <code>stream:evicted</code> event carry age evictions exactly as
|
|
4513
|
+
they always carried count evictions — an existing “dropped off the back of the
|
|
4514
|
+
window” readout keeps working with nothing changed.</p>
|
|
4515
|
+
<p><strong>The span is a bound, not a guillotine.</strong> A row lives a little past the span
|
|
4516
|
+
before it goes, and two things add to that. First the eviction slack, ten per cent of the
|
|
4517
|
+
span — exactly the overshoot <code>maxRows</code> already allows on its count — so
|
|
4518
|
+
the row permutation is rebuilt once per block rather than once per arriving row. Second, when
|
|
4519
|
+
the feed is idle, up to one tick of the eviction timer, which runs at a quarter of the span
|
|
4520
|
+
clamped to between 50 ms and one second. The real ceiling is therefore about
|
|
4521
|
+
<code>span × 1.1 + tick</code>, and because the tick has a floor it is
|
|
4522
|
+
<em>proportionally larger the shorter the window</em>: about 10% over at a five-minute
|
|
4523
|
+
window, around 1.25× at ten seconds, and as much as ~1.35× at three. That is the
|
|
4524
|
+
deliberate price of an idle grid that costs no CPU, and it is why the number to reach for is
|
|
4525
|
+
the window you want the reader to see rather than a hard retention limit. The chart’s
|
|
4526
|
+
domain is exact either way — it ends at <em>now</em> — so the extra rows sit off
|
|
4527
|
+
the left edge rather than being drawn.</p>
|
|
4528
|
+
<p><strong>Which clock: <code>ageBy</code>, or arrival.</strong> Given
|
|
4529
|
+
<code>ageBy</code> — a column id, a dotted path, or a function of the row returning a
|
|
4530
|
+
<code>Date</code>, epoch milliseconds or an ISO string — the window follows the
|
|
4531
|
+
<em>data’s own</em> clock, so it means what the producer means. That also inherits the
|
|
4532
|
+
producer’s clock skew: if their clock runs five minutes fast, their rows live five
|
|
4533
|
+
minutes longer than yours. Omit <code>ageBy</code> and rows age from <strong>arrival
|
|
4534
|
+
time</strong>, when the row reached the source. Arrival time needs no timestamp column and
|
|
4535
|
+
cannot be skewed, but it is not event time — a row delayed in transit is treated as
|
|
4536
|
+
young. A synthetic or metric feed usually wants arrival; a log or event feed usually wants
|
|
4537
|
+
<code>ageBy</code>. A row whose time value cannot be read is never aged out: dropping data
|
|
4538
|
+
because a timestamp was malformed is the worse failure.</p>
|
|
4539
|
+
<p><strong>Out of order is handled, not reordered.</strong> With <code>ageBy</code> the
|
|
4540
|
+
row’s clock need not be monotonic in arrival order, so eviction scans the window rather
|
|
4541
|
+
than walking the head; a late row that is already older than the span is dropped on the same
|
|
4542
|
+
pass it arrived on and counted as evicted, rather than being painted and then withdrawn a
|
|
4543
|
+
moment later. Nothing is re-sorted: a row’s <em>position</em> is still arrival order,
|
|
4544
|
+
only its <em>retention</em> is decided by its time.</p>
|
|
4545
|
+
<p><strong>The chart axis rolls independently.</strong> The axis takes the same
|
|
4546
|
+
<code>WindowSpec</code> vocabulary rolling statistics use —
|
|
4547
|
+
<code>window: { kind: 'time', span }</code> — and its domain ends at <em>now</em>
|
|
4548
|
+
rather than at the newest point, which is what makes the chart keep scrolling with zero new
|
|
4549
|
+
rows. It works with or without <code>maxAge</code> on the source; set both to the same span
|
|
4550
|
+
and the retained data and the drawn domain agree. Only <code>kind: 'time'</code> applies to
|
|
4551
|
+
an axis: a count window over a chart is the source’s <code>maxRows</code>, and
|
|
4552
|
+
<code>kind: 'count'</code> is refused with a warning rather than quietly given a second
|
|
4553
|
+
meaning. The x column has to be continuous and carry wall-clock times — a banded or
|
|
4554
|
+
categorical axis has no domain to roll.</p>
|
|
4555
|
+
<p><strong>Idle costs nothing.</strong> Both halves advance on a plain interval — a
|
|
4556
|
+
quarter of the window, clamped to between 50 ms and one second — and never on an
|
|
4557
|
+
animation frame. The source’s wake returns after a single number comparison unless a
|
|
4558
|
+
row is actually due, and the chart’s wake does nothing at all when the document is
|
|
4559
|
+
hidden or the chart is detached. Both timers are cleared on destroy. Measured over a
|
|
4560
|
+
five-minute window holding 50,000 rows with no feed at all, the window’s CPU cost is
|
|
4561
|
+
inside the run-to-run noise of the same source with no bound set: under 0.04% of one core
|
|
4562
|
+
(<code>bench/idle-window.mjs</code>).</p>
|
|
4463
4563
|
<p><strong>The log keeps the raw sequence, not the merged one.</strong> Merging is right for
|
|
4464
4564
|
applying a backlog quickly and wrong for looking at what happened, because the intermediate
|
|
4465
4565
|
states are exactly what a time scrubber would move between. It survives the flush,
|
|
@@ -5084,6 +5184,7 @@ grid.annotate.use(null); <span class="cmt">// hand the grid
|
|
|
5084
5184
|
<tr><td class="name"><kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>H</kbd></td><td class="desc">Move focus to the column header</td></tr>
|
|
5085
5185
|
<tr><td class="name"><kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>P</kbd></td><td class="desc">Move focus to the tool panel</td></tr>
|
|
5086
5186
|
<tr><td class="name"><kbd>Shift</kbd>+<kbd>F10</kbd> <span class="sep">or</span> <kbd>ContextMenu</kbd></td><td class="desc">Open the context menu for the focused cell</td></tr>
|
|
5187
|
+
<tr><td class="name"><kbd>Ctrl</kbd>+<kbd>F</kbd></td><td class="desc">Find in the grid</td></tr>
|
|
5087
5188
|
<tr><td class="name"><kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>ArrowUp</kbd> <span class="sep">or</span> <kbd>Alt</kbd>+<kbd>Shift</kbd>+<kbd>ArrowDown</kbd></td><td class="desc">Move the focused row, when row reorder is enabled</td></tr>
|
|
5088
5189
|
<tr><th colspan="2">On a column heading</th></tr>
|
|
5089
5190
|
<tr><td class="name"><kbd>ArrowLeft</kbd> <span class="sep">or</span> <kbd>ArrowRight</kbd></td><td class="desc">Move between headings</td></tr>
|
|
@@ -5415,6 +5516,7 @@ grid.edit.pasteInto(text); <span class="cmt">// Excel's t
|
|
|
5415
5516
|
<tr><td class="sig">Enter</td><td class="desc">Start editing; commit and step down.</td></tr>
|
|
5416
5517
|
<tr><td class="sig">Tab</td><td class="desc">Commit and step across.</td></tr>
|
|
5417
5518
|
<tr><td class="sig">Escape</td><td class="desc">Cancel the edit; restore a maximised grid once nothing else wants it.</td></tr>
|
|
5519
|
+
<tr><td class="sig">Ctrl/Cmd + F</td><td class="desc">Open the <a href="#find">find bar</a>; in it, Enter / Shift+Enter step through the matches and Escape closes.</td></tr>
|
|
5418
5520
|
<tr><td class="sig">Space</td><td class="desc">Toggle the row's selection.</td></tr>
|
|
5419
5521
|
<tr><td class="sig">Home / End, Page Up / Down</td><td class="desc">Jump; with Ctrl, to the ends of the grid.</td></tr>
|
|
5420
5522
|
</tbody>
|
|
@@ -6065,6 +6167,86 @@ grid.highlight.clear();</code></pre>
|
|
|
6065
6167
|
it. A highlight belongs to the row rather than the element, so it survives scrolling, sorting
|
|
6066
6168
|
and paging.
|
|
6067
6169
|
</p>
|
|
6170
|
+
|
|
6171
|
+
<h2 id="find">Find</h2>
|
|
6172
|
+
<p class="lead-in">
|
|
6173
|
+
The quick filter answers "show me only the rows that contain X". Find answers a different
|
|
6174
|
+
question — "where is X?" — and leaves every other row exactly where it was, so you keep your
|
|
6175
|
+
place and the neighbours that give a value its meaning. Press <kbd>Ctrl</kbd>+<kbd>F</kbd>
|
|
6176
|
+
(<kbd>Cmd</kbd>+<kbd>F</kbd> on a Mac) with focus in the grid: a bar opens above the header
|
|
6177
|
+
with focus in its input, every cell whose <em>displayed</em> text matches lights up in place,
|
|
6178
|
+
the bar reads "N of M", <kbd>Enter</kbd> and <kbd>Shift</kbd>+<kbd>Enter</kbd> step through
|
|
6179
|
+
the matches with wrap, and <kbd>Escape</kbd> closes it and clears the marks.
|
|
6180
|
+
</p>
|
|
6181
|
+
<div class="example">
|
|
6182
|
+
<p class="example__label">The same thing from code</p>
|
|
6183
|
+
<pre data-run="js" data-expect="2 matches, 3 rows shown, current a, events true" data-covers="method:find config:find config:shortcut config:debounce event:find:changed"><code>const { createHeadlessGrid } = await import('../packages/core/src/index.js');
|
|
6184
|
+
const grid = createHeadlessGrid({
|
|
6185
|
+
rowKey: 'id',
|
|
6186
|
+
find: { shortcut: true, debounce: 0 }, // the bar's settings; `find: false` removes the bar
|
|
6187
|
+
columns: [
|
|
6188
|
+
{ field: 'name' },
|
|
6189
|
+
{ field: 'price', format: (p) => `$${p.value.toFixed(2)}` },
|
|
6190
|
+
],
|
|
6191
|
+
rows: [
|
|
6192
|
+
{ id: 'a', name: 'Acme', price: 3.5 },
|
|
6193
|
+
{ id: 'b', name: 'Beta', price: 13.25 },
|
|
6194
|
+
{ id: 'c', name: 'Acme Two', price: 3.75 },
|
|
6195
|
+
],
|
|
6196
|
+
});
|
|
6197
|
+
let events = 0;
|
|
6198
|
+
grid.on('find:changed', () => { events += 1; });
|
|
6199
|
+
|
|
6200
|
+
const count = grid.find('$3.'); // the formatted text: two prices begin "$3."
|
|
6201
|
+
const first = grid.find.current(); // { key: 'a', colId: 'price', index: 0, pinned: null }
|
|
6202
|
+
grid.find.next(); // row c; next() again wraps back to a
|
|
6203
|
+
const rowsStillShown = grid.rows.count(); // 3 — find never removes a row
|
|
6204
|
+
|
|
6205
|
+
grid.destroy();
|
|
6206
|
+
return `${count.total} matches, ${rowsStillShown} rows shown, current ${first.key}, events ${events > 0}`;</code></pre>
|
|
6207
|
+
</div>
|
|
6208
|
+
<p class="lead-in">
|
|
6209
|
+
<code>grid.find(text, opts)</code> searches now and returns a <code>FindCount</code>; it is
|
|
6210
|
+
callable like <code>grid.highlight</code>. The options are a <code>FindQuery</code>:
|
|
6211
|
+
<code>caseSensitive</code>, <code>wholeCell</code>, <code>columns</code> (an id or a list of
|
|
6212
|
+
ids; omitted searches every visible column) and <code>from</code> (the display index the
|
|
6213
|
+
first current match is chosen at or after). Then <code>find.next()</code>,
|
|
6214
|
+
<code>find.prev()</code> and <code>find.goTo(i)</code> move the current match and scroll it
|
|
6215
|
+
into view; <code>find.matches()</code>, <code>find.count()</code>,
|
|
6216
|
+
<code>find.current()</code> and <code>find.state()</code> read the result;
|
|
6217
|
+
<code>find.open(text?)</code>, <code>find.close()</code> and <code>find.clear()</code> drive
|
|
6218
|
+
the bar; <code>find.stateFor(key, colId)</code> is what the painter asks. Every change fires
|
|
6219
|
+
<code>find:changed</code> with the query, the open flag and the count.
|
|
6220
|
+
</p>
|
|
6221
|
+
<div class="table-wrap">
|
|
6222
|
+
<table>
|
|
6223
|
+
<thead><tr><th>Rule</th><th>What it means</th></tr></thead>
|
|
6224
|
+
<tbody>
|
|
6225
|
+
<tr><td class="sig">Display text</td><td class="desc">Find matches what the cell <em>shows</em> — a column <code>format</code>, a unit type, a lookup label — never the raw value. Searching <code>$3.</code> finds prices formatted that way. There is no regular-expression mode; the quick filter has one.</td></tr>
|
|
6226
|
+
<tr><td class="sig">An overlay, not a filter</td><td class="desc">No row is reordered, removed or edited. Matches are painted as <code>.lat-cell--find</code>, the current one also as <code>.lat-cell--find-current</code>, coloured by <code>--lattice-find-match</code> and <code>--lattice-find-current</code>. Find and the quick filter coexist: both may be active, and find re-runs over whatever the filter leaves.</td></tr>
|
|
6227
|
+
<tr><td class="sig">Pinned rows and columns</td><td class="desc">Rows pinned to either edge (and a bottom grand total) are searched and painted like any other; a pinned match has <code>index: -1</code> and <code>pinned: 'top' | 'bottom'</code>. Pinned columns are cells like any other.</td></tr>
|
|
6228
|
+
<tr><td class="sig">Virtualised rows</td><td class="desc">Matches are computed from the row model, not the DOM, so a match five thousand rows down is counted without rendering it; stepping to it scrolls it into view, and the paint follows the render.</td></tr>
|
|
6229
|
+
<tr><td class="sig">The active cell</td><td class="desc">Stepping to a match makes it the active cell, so <kbd>Enter</kbd> in the grid edits it — except while an edit is already open, when the match is scrolled and painted and the editor is left alone.</td></tr>
|
|
6230
|
+
<tr><td class="sig">Windowed sources</td><td class="desc">A paged pushdown source (OData, DuckDB, REST) holds only its loaded rows client-side, so only those are searched. The count says so — "N of M <em>in loaded rows</em>", and <code>FindCount.windowed</code> is true with <code>loaded</code> and <code>rows</code> beside it — rather than presenting a page-one count as the whole. Pushing find to the adapter is a follow-up, not a v1 promise.</td></tr>
|
|
6231
|
+
<tr><td class="sig">Large grids</td><td class="desc">Typing is scanned in per-frame slices from the row at the top of the viewport, so the matches on screen appear after the first slice and the grid stays interactive; the count reads "N of M so far" and <code>count.complete</code> is false until the scan finishes. <code>grid.find(text)</code> scans to completion before returning, so its answer is final.</td></tr>
|
|
6232
|
+
<tr><td class="sig">The browser's find</td><td class="desc"><kbd>Ctrl</kbd>+<kbd>F</kbd> is claimed only while focus is inside the grid and not in a text field, so the page's own find works everywhere else and an open cell editor keeps it. <code>find: { shortcut: false }</code> leaves the binding to the page and keeps the bar reachable through <code>find.open()</code>; <code>find: false</code> removes the bar altogether.</td></tr>
|
|
6233
|
+
<tr><td class="sig">Accessibility</td><td class="desc">The bar is a <code>role="search"</code> landmark; every control is a native input, button or select with a catalogue name, so nothing needs a mouse. The count is announced through a polite <code>role="status"</code> line once per completed search ("3 of 12 matches", "No matches in loaded rows"); the current match becomes the focused cell when no edit is open, so a screen reader reads it. The strings are in every bundled locale.</td></tr>
|
|
6234
|
+
<tr><td class="sig">Keys in the bar</td><td class="desc">Only <kbd>Escape</kbd> and <kbd>Enter</kbd> in the input are consumed by the bar. Everything else — <kbd>Tab</kbd> between its controls, <kbd>Enter</kbd> and <kbd>Space</kbd> on its buttons, a page's own <kbd>Ctrl</kbd>+<kbd>S</kbd> — propagates as it would from any form control, so a host's document-level shortcuts still see it; the grid's own keyboard and range layers stand aside for a key aimed at the bar, which is what keeps Tab from being read as "next cell" and Enter from opening an editor.</td></tr>
|
|
6235
|
+
<tr><td class="sig">Pinned strips</td><td class="desc">Stepping to a match scrolls it fully into the part of the body the pinned strips do not cover — below pinned-top rows and sticky group headings, above pinned-bottom rows and a bottom grand total. That is <code>grid.scroll.toRow</code>'s behaviour for every caller, not only find.</td></tr>
|
|
6236
|
+
</tbody>
|
|
6237
|
+
</table>
|
|
6238
|
+
</div>
|
|
6239
|
+
<div class="example">
|
|
6240
|
+
<p class="example__label">Configuration</p>
|
|
6241
|
+
<pre><code>find: false <span class="cmt">// no bar, no Ctrl+F; grid.find(text) still works</span>
|
|
6242
|
+
find: { shortcut: false } <span class="cmt">// bar via grid.find.open() only</span>
|
|
6243
|
+
find: { debounce: 250 } <span class="cmt">// a slower typist, or a slower grid</span>
|
|
6244
|
+
|
|
6245
|
+
grid.find('acme', { caseSensitive: true, wholeCell: false, columns: ['customer'] });
|
|
6246
|
+
grid.find.count(); <span class="cmt">// { current, total, complete, windowed, loaded, rows }</span>
|
|
6247
|
+
grid.on('find:changed', (e) => status.textContent = `${e.count.current} of ${e.count.total}`);</code></pre>
|
|
6248
|
+
</div>
|
|
6249
|
+
|
|
6068
6250
|
<h2 id="views-guide">Saved views</h2>
|
|
6069
6251
|
<p class="lead-in">
|
|
6070
6252
|
A view is a named grid state: sort, filters, grouping, column order, widths, visibility.
|
|
@@ -6437,6 +6619,7 @@ grid.import.apply(preview);</code></pre>
|
|
|
6437
6619
|
<tr><td class="name">processCell</td><td class="desc">(optional)</td></tr>
|
|
6438
6620
|
<tr><td class="name">promoteToMemoryBelow</td><td class="desc">(optional)</td></tr>
|
|
6439
6621
|
<tr><td class="name">quickFilterText</td><td class="desc">Initial quick-filter term. Equivalent to grid.filters.quick(text).</td></tr>
|
|
6622
|
+
<tr><td class="name">find</td><td class="desc">The in-grid find bar (Ctrl+F): false removes it, { shortcut, debounce } tunes it. See <a href="#find">Find</a>.</td></tr>
|
|
6440
6623
|
<tr><td class="name">quote</td><td class="desc">(optional)</td></tr>
|
|
6441
6624
|
<tr><td class="name">removeMs</td><td class="desc">Silence after which a peer is dropped. (optional)</td></tr>
|
|
6442
6625
|
<tr><td class="name">showHeader</td><td class="desc">Draw the column headings at all. false removes the row, and removes it from the accessibility tree rather than only from view. Distinct from showColumnFunctions, which keeps the headings and drops only their sort, filter and menu controls.</td></tr>
|
|
@@ -6773,6 +6956,7 @@ el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);</code></pre>
|
|
|
6773
6956
|
<tbody>
|
|
6774
6957
|
<tr><td class="name">formatting:changed</td><td class="desc">A conditional formatting rule was added, edited, reordered or restated.</td></tr>
|
|
6775
6958
|
<tr><td class="name">highlight:changed</td><td class="desc">A highlight was added or cleared.</td></tr>
|
|
6959
|
+
<tr><td class="name">find:changed</td><td class="desc">The find query, its matches, the current match or the bar's open state changed; carries the FindCount, partial while the sliced scan runs.</td></tr>
|
|
6776
6960
|
<tr><td class="name">permissions:changed</td><td class="desc">The context moved and every column re-resolved.</td></tr>
|
|
6777
6961
|
<tr><td class="name">presentation:captured</td><td class="desc">A PNG was taken.</td></tr>
|
|
6778
6962
|
<tr><td class="name">presentation:changed</td><td class="desc">The options of a running presentation changed.</td></tr>
|