@toclocoinc/lattice-grid 1.47.0 → 1.48.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.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/docs/API.html +129 -3
  3. package/docs/api-detail.html +96 -2
  4. package/lattice-grid.d.ts +117 -3
  5. package/lattice-grid.esm.min.js +1544 -139
  6. package/lattice-grid.min.cjs +1544 -139
  7. package/lattice-grid.min.css +1 -1
  8. package/lattice-grid.min.js +1544 -139
  9. package/modules/ai.esm.min.js +29 -4
  10. package/modules/ai.min.cjs +29 -4
  11. package/modules/ai.min.js +29 -4
  12. package/modules/angular.esm.min.js +3 -3
  13. package/modules/angular.min.cjs +3 -3
  14. package/modules/angular.min.js +3 -3
  15. package/modules/chart-alluvial.esm.min.js +1 -1
  16. package/modules/chart-arc.esm.min.js +1 -1
  17. package/modules/chart-bubblemap.esm.min.js +1 -1
  18. package/modules/chart-bump.esm.min.js +1 -1
  19. package/modules/chart-calendar.esm.min.js +1 -1
  20. package/modules/chart-decomposition.esm.min.js +1 -1
  21. package/modules/chart-diverging.esm.min.js +1 -1
  22. package/modules/chart-dumbbell.esm.min.js +1 -1
  23. package/modules/chart-fan.esm.min.js +1 -1
  24. package/modules/chart-hexbin.esm.min.js +1 -1
  25. package/modules/chart-hexmap.esm.min.js +1 -1
  26. package/modules/chart-icicle.esm.min.js +1 -1
  27. package/modules/chart-parallel.esm.min.js +1 -1
  28. package/modules/chart-ridgeline.esm.min.js +1 -1
  29. package/modules/chart-roc.esm.min.js +1 -1
  30. package/modules/chart-slope.esm.min.js +1 -1
  31. package/modules/chart-splom.esm.min.js +1 -1
  32. package/modules/chart-waffle.esm.min.js +1 -1
  33. package/modules/charts.esm.min.js +73 -10
  34. package/modules/charts.min.cjs +73 -10
  35. package/modules/charts.min.js +73 -10
  36. package/modules/data-router.esm.min.js +4 -4
  37. package/modules/data-router.min.cjs +4 -4
  38. package/modules/data-router.min.js +4 -4
  39. package/modules/devtools.esm.min.js +2 -2
  40. package/modules/devtools.min.cjs +2 -2
  41. package/modules/devtools.min.js +2 -2
  42. package/modules/dhtmlx-compat.esm.min.js +4 -4
  43. package/modules/dhtmlx-compat.min.cjs +4 -4
  44. package/modules/dhtmlx-compat.min.js +4 -4
  45. package/modules/gantt.esm.min.js +4 -4
  46. package/modules/gantt.min.cjs +4 -4
  47. package/modules/gantt.min.js +4 -4
  48. package/modules/htmx.esm.min.js +1544 -139
  49. package/modules/htmx.min.cjs +1544 -139
  50. package/modules/htmx.min.js +1544 -139
  51. package/modules/kanban.esm.min.js +4 -4
  52. package/modules/kanban.min.cjs +4 -4
  53. package/modules/kanban.min.js +4 -4
  54. package/modules/kpi.esm.min.js +4 -4
  55. package/modules/kpi.min.cjs +4 -4
  56. package/modules/kpi.min.js +4 -4
  57. package/modules/mock-socket.esm.min.js +2 -2
  58. package/modules/mock-socket.min.cjs +2 -2
  59. package/modules/mock-socket.min.js +2 -2
  60. package/modules/react.esm.min.js +3 -3
  61. package/modules/react.min.cjs +3 -3
  62. package/modules/react.min.js +3 -3
  63. package/modules/svelte.esm.min.js +3 -3
  64. package/modules/svelte.min.cjs +3 -3
  65. package/modules/svelte.min.js +3 -3
  66. package/modules/vue.esm.min.js +3 -3
  67. package/modules/vue.min.cjs +3 -3
  68. package/modules/vue.min.js +3 -3
  69. package/modules/webcomponent.esm.min.js +1544 -139
  70. package/modules/webcomponent.min.cjs +1544 -139
  71. package/modules/webcomponent.min.js +1544 -139
  72. 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.47.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
7
+ Version 1.48.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) =&gt; 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) =&gt; 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" &gt;= ?</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 &lt;that type&gt;)</code>,
3231
+ and the value is still bound, never interpolated. The column's type comes from the engine — one
3232
+ <code>DESCRIBE SELECT * FROM &lt;from&gt;</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>
@@ -6242,7 +6280,7 @@ grid.destroy();
6242
6280
  <p class="section-note">Each documented event is subscribed to and unsubscribed on every build. A consumer
6243
6281
  wiring a handler to a renamed event gets silence, which is indistinguishable from an event that
6244
6282
  has not fired yet — so the name is checked rather than left to be discovered.</p>
6245
- <pre data-run="js" data-expect="107" data-covers="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');
6283
+ <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
6284
 
6247
6285
  <span class="cmt">// Every documented event name, checked against the bus that would carry it.</span>
6248
6286
  <span class="cmt">// Subscribing to a name the grid does not know is the failure this catches:</span>
@@ -6259,7 +6297,7 @@ grid.destroy();
6259
6297
  'diff:changed', 'diff:swapped', 'export:progress', 'facet:computed',
6260
6298
  'facet:expanded', 'facet:failed', 'facet:filtered', 'form:closed',
6261
6299
  'form:error', 'form:opened', 'form:saved', 'formatting:changed',
6262
- 'group:toggled', 'header:contextmenu', 'highlight:changed', 'history:applied',
6300
+ 'group:toggled', 'header:contextmenu', 'highlight:changed', 'find:changed', 'history:applied',
6263
6301
  'history:changed', 'licence:changed', 'page:changed', 'permissions:changed',
6264
6302
  'presence:failed', 'presence:joined', 'presence:left', 'presence:lockRefused',
6265
6303
  'presence:published', 'presence:updated', 'presentation:captured', 'presentation:changed',
@@ -8085,6 +8123,92 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
8085
8123
  </tbody>
8086
8124
  </table>
8087
8125
  </div>
8126
+ <h3 id="type-FindApi">FindApi</h3>
8127
+ <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>
8128
+ <div class="table-wrap">
8129
+ <table>
8130
+ <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
8131
+ <tbody>
8132
+ <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>
8133
+ <tr><td class="name">close</td><td class="type">(): void</td><td class="desc">Hide the bar and clear every match.</td></tr>
8134
+ <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>
8135
+ <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>
8136
+ <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>
8137
+ <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>
8138
+ <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>
8139
+ <tr><td class="name">count</td><td class="type">(): FindCount</td><td class="desc"></td></tr>
8140
+ <tr><td class="name">current</td><td class="type">(): FindMatch | null</td><td class="desc"></td></tr>
8141
+ <tr><td class="name">state</td><td class="type">(): FindState</td><td class="desc"></td></tr>
8142
+ <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>
8143
+ </tbody>
8144
+ </table>
8145
+ </div>
8146
+ <h3 id="type-FindConfig">FindConfig</h3>
8147
+ <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>
8148
+ <div class="table-wrap">
8149
+ <table>
8150
+ <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
8151
+ <tbody>
8152
+ <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>
8153
+ <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>
8154
+ </tbody>
8155
+ </table>
8156
+ </div>
8157
+ <h3 id="type-FindCount">FindCount</h3>
8158
+ <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>
8159
+ <div class="table-wrap">
8160
+ <table>
8161
+ <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
8162
+ <tbody>
8163
+ <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>
8164
+ <tr><td class="name">total</td><td class="type">number</td><td class="desc"></td></tr>
8165
+ <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>
8166
+ <tr><td class="name">windowed</td><td class="type">boolean</td><td class="desc"></td></tr>
8167
+ <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>
8168
+ <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>
8169
+ </tbody>
8170
+ </table>
8171
+ </div>
8172
+ <h3 id="type-FindMatch">FindMatch</h3>
8173
+ <p class="section-note">One matching cell.</p>
8174
+ <div class="table-wrap">
8175
+ <table>
8176
+ <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
8177
+ <tbody>
8178
+ <tr><td class="name">key</td><td class="type">string</td><td class="desc"></td></tr>
8179
+ <tr><td class="name">colId</td><td class="type">string</td><td class="desc"></td></tr>
8180
+ <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>
8181
+ <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>
8182
+ </tbody>
8183
+ </table>
8184
+ </div>
8185
+ <h3 id="type-FindQuery">FindQuery</h3>
8186
+ <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>
8187
+ <div class="table-wrap">
8188
+ <table>
8189
+ <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
8190
+ <tbody>
8191
+ <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>
8192
+ <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>
8193
+ <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>
8194
+ <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>
8195
+ </tbody>
8196
+ </table>
8197
+ </div>
8198
+ <h3 id="type-FindState">FindState</h3>
8199
+ <p class="section-note">The current query and whether the bar is showing.</p>
8200
+ <div class="table-wrap">
8201
+ <table>
8202
+ <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
8203
+ <tbody>
8204
+ <tr><td class="name">text</td><td class="type">string</td><td class="desc"></td></tr>
8205
+ <tr><td class="name">caseSensitive</td><td class="type">boolean</td><td class="desc"></td></tr>
8206
+ <tr><td class="name">wholeCell</td><td class="type">boolean</td><td class="desc"></td></tr>
8207
+ <tr><td class="name">columns</td><td class="type">string[] | null</td><td class="desc"></td></tr>
8208
+ <tr><td class="name">open</td><td class="type">boolean</td><td class="desc"></td></tr>
8209
+ </tbody>
8210
+ </table>
8211
+ </div>
8088
8212
  <h3 id="type-ForecastPoint">ForecastPoint</h3>
8089
8213
  <p class="section-note">One forecast step: the point estimate and, where a band applies, its interval.</p>
8090
8214
  <div class="table-wrap">
@@ -8224,6 +8348,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
8224
8348
  <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
8349
  <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
8350
  <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>
8351
+ <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
8352
  <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
8353
  <tr><td class="name">capture</td><td class="type">(opts?: CaptureOptions): Promise&lt;Blob&gt;</td><td class="desc">An image of the grid as drawn, where the module is installed. <small>(optional)</small></td></tr>
8229
8354
  <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 +8468,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
8343
8468
  <tr><td class="name">columnMenu</td><td class="type">boolean | ((p: ColumnMenuParams, defaults: MenuItem[]) =&gt; 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
8469
  <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
8470
  <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>
8471
+ <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
8472
  <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
8473
  <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
8474
  <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 +9671,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
9545
9671
  <table>
9546
9672
  <thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
9547
9673
  <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>
9674
+ <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
9675
  <tr><td class="name">toColumn</td><td class="type">(id: string): void</td><td class="desc"></td></tr>
9550
9676
  <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
9677
  <tr><td class="name">position</td><td class="type">(): { top: number; left: number }</td><td class="desc"></td></tr>
@@ -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.47.0</p>
440
+ <p class="rail__sub">Developer guide · v1.48.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" &gt;=
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>
@@ -5084,6 +5094,7 @@ grid.annotate.use(null); <span class="cmt">// hand the grid
5084
5094
  <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
5095
  <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
5096
  <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>
5097
+ <tr><td class="name"><kbd>Ctrl</kbd>+<kbd>F</kbd></td><td class="desc">Find in the grid</td></tr>
5087
5098
  <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
5099
  <tr><th colspan="2">On a column heading</th></tr>
5089
5100
  <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 +5426,7 @@ grid.edit.pasteInto(text); <span class="cmt">// Excel's t
5415
5426
  <tr><td class="sig">Enter</td><td class="desc">Start editing; commit and step down.</td></tr>
5416
5427
  <tr><td class="sig">Tab</td><td class="desc">Commit and step across.</td></tr>
5417
5428
  <tr><td class="sig">Escape</td><td class="desc">Cancel the edit; restore a maximised grid once nothing else wants it.</td></tr>
5429
+ <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
5430
  <tr><td class="sig">Space</td><td class="desc">Toggle the row's selection.</td></tr>
5419
5431
  <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
5432
  </tbody>
@@ -6065,6 +6077,86 @@ grid.highlight.clear();</code></pre>
6065
6077
  it. A highlight belongs to the row rather than the element, so it survives scrolling, sorting
6066
6078
  and paging.
6067
6079
  </p>
6080
+
6081
+ <h2 id="find">Find</h2>
6082
+ <p class="lead-in">
6083
+ The quick filter answers "show me only the rows that contain X". Find answers a different
6084
+ question — "where is X?" — and leaves every other row exactly where it was, so you keep your
6085
+ place and the neighbours that give a value its meaning. Press <kbd>Ctrl</kbd>+<kbd>F</kbd>
6086
+ (<kbd>Cmd</kbd>+<kbd>F</kbd> on a Mac) with focus in the grid: a bar opens above the header
6087
+ with focus in its input, every cell whose <em>displayed</em> text matches lights up in place,
6088
+ the bar reads "N of M", <kbd>Enter</kbd> and <kbd>Shift</kbd>+<kbd>Enter</kbd> step through
6089
+ the matches with wrap, and <kbd>Escape</kbd> closes it and clears the marks.
6090
+ </p>
6091
+ <div class="example">
6092
+ <p class="example__label">The same thing from code</p>
6093
+ <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');
6094
+ const grid = createHeadlessGrid({
6095
+ rowKey: 'id',
6096
+ find: { shortcut: true, debounce: 0 }, // the bar's settings; `find: false` removes the bar
6097
+ columns: [
6098
+ { field: 'name' },
6099
+ { field: 'price', format: (p) =&gt; `$${p.value.toFixed(2)}` },
6100
+ ],
6101
+ rows: [
6102
+ { id: 'a', name: 'Acme', price: 3.5 },
6103
+ { id: 'b', name: 'Beta', price: 13.25 },
6104
+ { id: 'c', name: 'Acme Two', price: 3.75 },
6105
+ ],
6106
+ });
6107
+ let events = 0;
6108
+ grid.on('find:changed', () =&gt; { events += 1; });
6109
+
6110
+ const count = grid.find('$3.'); // the formatted text: two prices begin "$3."
6111
+ const first = grid.find.current(); // { key: 'a', colId: 'price', index: 0, pinned: null }
6112
+ grid.find.next(); // row c; next() again wraps back to a
6113
+ const rowsStillShown = grid.rows.count(); // 3 — find never removes a row
6114
+
6115
+ grid.destroy();
6116
+ return `${count.total} matches, ${rowsStillShown} rows shown, current ${first.key}, events ${events &gt; 0}`;</code></pre>
6117
+ </div>
6118
+ <p class="lead-in">
6119
+ <code>grid.find(text, opts)</code> searches now and returns a <code>FindCount</code>; it is
6120
+ callable like <code>grid.highlight</code>. The options are a <code>FindQuery</code>:
6121
+ <code>caseSensitive</code>, <code>wholeCell</code>, <code>columns</code> (an id or a list of
6122
+ ids; omitted searches every visible column) and <code>from</code> (the display index the
6123
+ first current match is chosen at or after). Then <code>find.next()</code>,
6124
+ <code>find.prev()</code> and <code>find.goTo(i)</code> move the current match and scroll it
6125
+ into view; <code>find.matches()</code>, <code>find.count()</code>,
6126
+ <code>find.current()</code> and <code>find.state()</code> read the result;
6127
+ <code>find.open(text?)</code>, <code>find.close()</code> and <code>find.clear()</code> drive
6128
+ the bar; <code>find.stateFor(key, colId)</code> is what the painter asks. Every change fires
6129
+ <code>find:changed</code> with the query, the open flag and the count.
6130
+ </p>
6131
+ <div class="table-wrap">
6132
+ <table>
6133
+ <thead><tr><th>Rule</th><th>What it means</th></tr></thead>
6134
+ <tbody>
6135
+ <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>
6136
+ <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>
6137
+ <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>
6138
+ <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>
6139
+ <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>
6140
+ <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>
6141
+ <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>
6142
+ <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>
6143
+ <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>
6144
+ <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>
6145
+ <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>
6146
+ </tbody>
6147
+ </table>
6148
+ </div>
6149
+ <div class="example">
6150
+ <p class="example__label">Configuration</p>
6151
+ <pre><code>find: false <span class="cmt">// no bar, no Ctrl+F; grid.find(text) still works</span>
6152
+ find: { shortcut: false } <span class="cmt">// bar via grid.find.open() only</span>
6153
+ find: { debounce: 250 } <span class="cmt">// a slower typist, or a slower grid</span>
6154
+
6155
+ grid.find('acme', { caseSensitive: true, wholeCell: false, columns: ['customer'] });
6156
+ grid.find.count(); <span class="cmt">// { current, total, complete, windowed, loaded, rows }</span>
6157
+ grid.on('find:changed', (e) =&gt; status.textContent = `${e.count.current} of ${e.count.total}`);</code></pre>
6158
+ </div>
6159
+
6068
6160
  <h2 id="views-guide">Saved views</h2>
6069
6161
  <p class="lead-in">
6070
6162
  A view is a named grid state: sort, filters, grouping, column order, widths, visibility.
@@ -6437,6 +6529,7 @@ grid.import.apply(preview);</code></pre>
6437
6529
  <tr><td class="name">processCell</td><td class="desc">(optional)</td></tr>
6438
6530
  <tr><td class="name">promoteToMemoryBelow</td><td class="desc">(optional)</td></tr>
6439
6531
  <tr><td class="name">quickFilterText</td><td class="desc">Initial quick-filter term. Equivalent to grid.filters.quick(text).</td></tr>
6532
+ <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
6533
  <tr><td class="name">quote</td><td class="desc">(optional)</td></tr>
6441
6534
  <tr><td class="name">removeMs</td><td class="desc">Silence after which a peer is dropped. (optional)</td></tr>
6442
6535
  <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 +6866,7 @@ el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);</code></pre>
6773
6866
  <tbody>
6774
6867
  <tr><td class="name">formatting:changed</td><td class="desc">A conditional formatting rule was added, edited, reordered or restated.</td></tr>
6775
6868
  <tr><td class="name">highlight:changed</td><td class="desc">A highlight was added or cleared.</td></tr>
6869
+ <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
6870
  <tr><td class="name">permissions:changed</td><td class="desc">The context moved and every column re-resolved.</td></tr>
6777
6871
  <tr><td class="name">presentation:captured</td><td class="desc">A PNG was taken.</td></tr>
6778
6872
  <tr><td class="name">presentation:changed</td><td class="desc">The options of a running presentation changed.</td></tr>
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.47.0, type declarations
2
+ * Lattice Grid 1.48.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -2072,6 +2072,15 @@ export interface GridConfig {
2072
2072
  */
2073
2073
  shortcuts?: boolean;
2074
2074
 
2075
+ /**
2076
+ * The in-grid find bar (BACKLOG-0001018): Ctrl+F / Cmd+F with focus in the
2077
+ * grid opens it; typing highlights every matching cell in place without
2078
+ * filtering a row away; Enter and Shift+Enter step through the matches.
2079
+ * `false` removes the bar and its shortcut; the `grid.find` API still works.
2080
+ * Default true.
2081
+ */
2082
+ find?: boolean | FindConfig;
2083
+
2075
2084
  /**
2076
2085
  * Let a user reorder rows by dragging a handle, or with
2077
2086
  * Alt+Shift+Up/Down.
@@ -3815,7 +3824,7 @@ export type EventName =
3815
3824
  /* Selection and view */
3816
3825
  | 'selection:changed' | 'range:changed' | 'clipboard:copy'
3817
3826
  | 'page:changed' | 'scroll' | 'scroll:end' | 'size:changed'
3818
- | 'detail:toggled' | 'toolpanel:focus' | 'highlight:changed'
3827
+ | 'detail:toggled' | 'toolpanel:focus' | 'highlight:changed' | 'find:changed'
3819
3828
  /* Tree data */
3820
3829
  | 'tree:loading' | 'tree:loaded' | 'tree:loadFailed' | 'tree:loadAborted'
3821
3830
  /* State, history and views */
@@ -4322,7 +4331,10 @@ export interface ScrollApi {
4322
4331
  /**
4323
4332
  * A row key, or a display index. A key survives a sort and is usually what a
4324
4333
  * caller holds; resolving one scans the display order, so prefer an index
4325
- * when scrolling a very large grid repeatedly.
4334
+ * when scrolling a very large grid repeatedly. The row lands fully visible in
4335
+ * the part of the body the pinned strips (pinned rows, sticky group
4336
+ * headings, a bottom grand total) do not cover: `end` puts it just above the
4337
+ * bottom strip, `start` just below the top one.
4326
4338
  */
4327
4339
  toRow(row: string | number, align?: 'start' | 'center' | 'end' | 'auto'): void;
4328
4340
  toColumn(id: string): void;
@@ -4960,6 +4972,106 @@ export interface HighlightApi {
4960
4972
  colourFor(key: string, colId: string): string | null;
4961
4973
  }
4962
4974
 
4975
+ /**
4976
+ * The in-grid find bar's settings (BACKLOG-0001018). `find: true` or an
4977
+ * omitted key mounts the bar with these defaults; `find: false` removes the
4978
+ * bar and its shortcut while `grid.find` keeps working programmatically.
4979
+ */
4980
+ export interface FindConfig {
4981
+ /**
4982
+ * Bind Ctrl+F (Cmd+F on a Mac) while focus is in the grid. The browser's
4983
+ * own find is untouched while focus is anywhere else on the page. Default
4984
+ * true.
4985
+ */
4986
+ shortcut?: boolean;
4987
+ /** Milliseconds of typing quiet before the bar searches. Default 120. */
4988
+ debounce?: number;
4989
+ }
4990
+
4991
+ /**
4992
+ * How `grid.find(text, opts)` matches. Defaults: case-insensitive, substring,
4993
+ * every visible column, starting from the first row. Find matches the
4994
+ * **formatted display text** — what the cell shows, a column `format`
4995
+ * included — never a raw value; there is no regular-expression mode.
4996
+ */
4997
+ export interface FindQuery {
4998
+ /** Match letter case exactly. Default false. */
4999
+ caseSensitive?: boolean;
5000
+ /** The whole cell text must equal the search text rather than contain it. Default false. */
5001
+ wholeCell?: boolean;
5002
+ /** Search only these column ids. Omitted searches every visible column. */
5003
+ columns?: string[] | string | null;
5004
+ /** The display index to start from: the first match at or after it becomes current. Default 0. */
5005
+ from?: number;
5006
+ }
5007
+
5008
+ /** One matching cell. */
5009
+ export interface FindMatch {
5010
+ key: string;
5011
+ colId: string;
5012
+ /** The display index, or -1 for a row pinned to an edge. */
5013
+ index: number;
5014
+ /** Which sticky strip a pinned row is in; null for a body row. */
5015
+ pinned: 'top' | 'bottom' | null;
5016
+ }
5017
+
5018
+ /**
5019
+ * How many matches there are and which is current. `windowed` is the honest
5020
+ * scope flag: over a paged pushdown source only the loaded rows are searched,
5021
+ * so `total` counts matches in `loaded` rows out of the `rows` the source
5022
+ * reports for the whole matching set.
5023
+ */
5024
+ export interface FindCount {
5025
+ /** 1-based position of the current match; 0 when there is none. */
5026
+ current: number;
5027
+ total: number;
5028
+ /** False while the bar's sliced scan is still running, so a partial count is never read as final. */
5029
+ complete: boolean;
5030
+ windowed: boolean;
5031
+ /** Rows the search actually read; a windowed source's not-yet-fetched placeholders are not counted. */
5032
+ loaded: number;
5033
+ /** The rows the source reports for the whole matching set, when it can say. */
5034
+ rows: number;
5035
+ }
5036
+
5037
+ /** The current query and whether the bar is showing. */
5038
+ export interface FindState {
5039
+ text: string;
5040
+ caseSensitive: boolean;
5041
+ wholeCell: boolean;
5042
+ columns: string[] | null;
5043
+ open: boolean;
5044
+ }
5045
+
5046
+ /**
5047
+ * In-grid find (BACKLOG-0001018): locate text and step through where it
5048
+ * occurs without filtering anything away. Matches are a visual overlay — no
5049
+ * row is reordered, removed or edited — and coexist with the quick filter.
5050
+ */
5051
+ export interface FindApi {
5052
+ /** Search now, scanning every loaded row before returning; an empty text clears. */
5053
+ (text: string, opts?: FindQuery): FindCount;
5054
+ /** Show the bar with focus in its input, optionally seeding the text. */
5055
+ open(text?: string): void;
5056
+ /** Hide the bar and clear every match. */
5057
+ close(): void;
5058
+ /** Clear the query and the highlights, leaving the bar as it is. */
5059
+ clear(): void;
5060
+ /** The next match, wrapping from the last to the first, scrolled into view and made the active cell unless an edit is open. */
5061
+ next(): FindMatch | null;
5062
+ /** The previous match, wrapping from the first to the last. */
5063
+ prev(): FindMatch | null;
5064
+ /** Make the match at a position in `matches()` current. */
5065
+ goTo(index: number): FindMatch | null;
5066
+ /** Every match, in display order: pinned-top rows, then the body, then pinned-bottom rows. */
5067
+ matches(): FindMatch[];
5068
+ count(): FindCount;
5069
+ current(): FindMatch | null;
5070
+ state(): FindState;
5071
+ /** How a cell is painted: the current match, another match, or nothing. */
5072
+ stateFor(key: string, colId: string): 'current' | 'match' | null;
5073
+ }
5074
+
4963
5075
  export interface StateApi {
4964
5076
  get(): GridState;
4965
5077
  apply(state: GridState, opts?: { skip?: (keyof GridState)[] }): StateApplyReport;
@@ -5245,6 +5357,8 @@ export interface Grid {
5245
5357
  readonly pagination: PaginationApi;
5246
5358
  /** Transient emphasis on a row, column or cell. */
5247
5359
  readonly highlight: HighlightApi;
5360
+ /** In-grid find: locate text without filtering, and step through the matches. */
5361
+ readonly find: FindApi;
5248
5362
  /** Values hidden from view and from export. */
5249
5363
  readonly redaction: RedactionApi;
5250
5364
  /** An image of the grid as drawn, where the module is installed. */