@toclocoinc/lattice-grid 1.57.0 → 1.59.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 (78) hide show
  1. package/README.md +1 -1
  2. package/docs/API.html +227 -17
  3. package/docs/api-detail.html +180 -4
  4. package/lattice-grid.d.ts +316 -13
  5. package/lattice-grid.esm.min.js +809 -128
  6. package/lattice-grid.min.cjs +809 -128
  7. package/lattice-grid.min.css +1 -1
  8. package/lattice-grid.min.js +809 -128
  9. package/modules/ai.esm.min.js +5 -4
  10. package/modules/ai.min.cjs +5 -4
  11. package/modules/ai.min.js +5 -4
  12. package/modules/angular.esm.min.js +3 -2
  13. package/modules/angular.min.cjs +3 -2
  14. package/modules/angular.min.js +3 -2
  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 +4 -4
  34. package/modules/charts.min.cjs +4 -4
  35. package/modules/charts.min.js +4 -4
  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 +109 -33
  46. package/modules/gantt.min.cjs +109 -33
  47. package/modules/gantt.min.js +109 -33
  48. package/modules/htmx.esm.min.js +809 -128
  49. package/modules/htmx.min.cjs +809 -128
  50. package/modules/htmx.min.js +809 -128
  51. package/modules/kanban.esm.min.js +5 -6
  52. package/modules/kanban.min.cjs +5 -6
  53. package/modules/kanban.min.js +5 -6
  54. package/modules/kpi.esm.min.js +41 -8
  55. package/modules/kpi.min.cjs +41 -8
  56. package/modules/kpi.min.js +41 -8
  57. package/modules/layout.esm.min.js +59 -6
  58. package/modules/layout.min.cjs +59 -6
  59. package/modules/layout.min.js +59 -6
  60. package/modules/mock-socket.esm.min.js +2 -2
  61. package/modules/mock-socket.min.cjs +2 -2
  62. package/modules/mock-socket.min.js +2 -2
  63. package/modules/react.esm.min.js +3 -2
  64. package/modules/react.min.cjs +3 -2
  65. package/modules/react.min.js +3 -2
  66. package/modules/svelte.esm.min.js +3 -2
  67. package/modules/svelte.min.cjs +3 -2
  68. package/modules/svelte.min.js +3 -2
  69. package/modules/tabs.esm.min.js +411 -9
  70. package/modules/tabs.min.cjs +411 -9
  71. package/modules/tabs.min.js +411 -9
  72. package/modules/vue.esm.min.js +3 -2
  73. package/modules/vue.min.cjs +3 -2
  74. package/modules/vue.min.js +3 -2
  75. package/modules/webcomponent.esm.min.js +809 -128
  76. package/modules/webcomponent.min.cjs +809 -128
  77. package/modules/webcomponent.min.js +809 -128
  78. package/package.json +1 -1
@@ -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.57.0</p>
440
+ <p class="rail__sub">Developer guide · v1.59.0</p>
441
441
  <nav>
442
442
  <div class="rail__group">
443
443
  <span class="rail__label">Start here</span>
@@ -553,7 +553,7 @@
553
553
  <a href="API.html">reference tables</a> are the shorter version for when you already know.
554
554
  </p>
555
555
  <p class="chips">
556
- <span class="chip">Version 1.57.0</span>
556
+ <span class="chip">Version 1.59.0</span>
557
557
  <span class="chip">Zero dependencies</span>
558
558
  <span class="chip">No build step</span>
559
559
  </p>
@@ -1278,7 +1278,7 @@ off(); <span class="cmt">// every subscrip
1278
1278
  </p>
1279
1279
  <div class="example">
1280
1280
  <p class="example__label">Which version am I running?</p>
1281
- <pre><code>grid.getVersion(); <span class="cmt">// '1.57.0'</span>
1281
+ <pre><code>grid.getVersion(); <span class="cmt">// '1.59.0'</span>
1282
1282
  LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid to hand</span></code></pre>
1283
1283
  </div>
1284
1284
  <p class="lead-in">
@@ -2684,6 +2684,131 @@ grid.setPinnedRows([], { edge: 'top' }); <span class="cmt">// clear</span
2684
2684
  technology as one <code>role="columnheader"</code> cell with an <code>aria-colspan</code>.</p>
2685
2685
  </div>
2686
2686
 
2687
+ <h2 id="group-row-renderer">Group rows you draw yourself</h2>
2688
+ <p class="lead-in">
2689
+ The grid's own group row is an expander, a label and a count. When the heading has to carry
2690
+ more than that &mdash; a sprint section with a chevron, the section name, the points summed
2691
+ across it, a done/total count and a progress bar &mdash; <code>groupRenderer</code> hands you
2692
+ the whole row. It is drawn as one band across every column, over the pinned regions, and no
2693
+ ordinary cells are mounted underneath it.
2694
+ </p>
2695
+
2696
+ <div class="example">
2697
+ <p class="example__label">A section header with a rollup the grid was never told to compute</p>
2698
+ <pre><code>createGrid(element, {
2699
+ columns,
2700
+ rows,
2701
+ groupRenderer: ({ value, leafCount, expanded, leaves }) =&gt; {
2702
+ <span class="cmt">// `leaves()` is the group's own rows. Roll up whatever you like.</span>
2703
+ <span class="kw">const</span> rows = leaves();
2704
+ <span class="kw">const</span> points = rows.reduce((sum, r) =&gt; sum + r.data.points, 0);
2705
+ <span class="kw">const</span> done = rows.filter((r) =&gt; r.data.done).length;
2706
+ <span class="kw">const</span> pct = Math.round((done / leafCount) * 100);
2707
+ <span class="kw">return</span> `&lt;span data-lat-group-toggle&gt;${expanded ? '▾' : '▸'}&lt;/span&gt;
2708
+ &lt;b&gt;${value}&lt;/b&gt; ${points} pts · ${done}/${leafCount}
2709
+ &lt;progress value="${pct}" max="100"&gt;&lt;/progress&gt;`;
2710
+ },
2711
+ });</code></pre>
2712
+ </div>
2713
+
2714
+ <h3 id="group-row-renderer-params">What the renderer is given</h3>
2715
+ <div class="table-wrap">
2716
+ <table>
2717
+ <thead><tr><th>Field</th><th>What it is</th></tr></thead>
2718
+ <tbody>
2719
+ <tr><td class="name">key</td><td class="desc">The group's key &mdash; the same string <code>rows.expand()</code> and <code>rows.collapse()</code> take, and the one <code>group:toggled</code> carries.</td></tr>
2720
+ <tr><td class="name">column</td><td class="desc">The id of the column <em>this level</em> groups on. It is stamped per rebuild from the grouping, not derived from the row's depth, so it stays correct when an outer grouping is removed and this level becomes the outermost.</td></tr>
2721
+ <tr><td class="name">value</td><td class="desc">The value this group stands for.</td></tr>
2722
+ <tr><td class="name">level</td><td class="desc">Depth of the group. Zero is the outermost.</td></tr>
2723
+ <tr><td class="name">expanded</td><td class="desc">Whether the group is open. Draw your chevron from this; the row is re-rendered when it changes.</td></tr>
2724
+ <tr><td class="name">leafCount</td><td class="desc">How many records sit beneath the heading, at any depth. Read this when the size is all you need.</td></tr>
2725
+ <tr><td class="name">totals</td><td class="desc">The group's own reductions by column id &mdash; whatever <code>total</code> asked for. Unaffected by drawing the row.</td></tr>
2726
+ <tr><td class="name">leaves()</td><td class="desc">The rows beneath the heading, computed <em>when you call it</em>. The members the filters left, in display order, so a rollup agrees with the rows drawn below. Also available on its own as <code>grid.rows.leavesOf(key)</code>.</td></tr>
2727
+ <tr><td class="name">toggle()</td><td class="desc">Expand the group if it is closed, collapse it if it is open.</td></tr>
2728
+ <tr><td class="name">grid, row, element</td><td class="desc">The grid, the group row model, and the element to fill if you would rather write into it than return anything.</td></tr>
2729
+ </tbody>
2730
+ </table>
2731
+ </div>
2732
+
2733
+ <div class="why">
2734
+ <p><strong>A string here is markup, and in <code>fullWidth.render</code> it is text.</strong>
2735
+ That difference is deliberate, and it is the difference between the two features. A
2736
+ <a href="#full-width-rows">full-width row</a> renders one of <em>your data</em> rows, and
2737
+ §8.9 keeps host markup behind <code>allowUnsafeTemplates</code> everywhere a data value
2738
+ reaches the page. A group heading has no data row at all &mdash; the grid synthesised it from
2739
+ the grouping &mdash; so its string can only be a template you wrote in your own source. That
2740
+ is the same footing the board's <code>cardRenderer</code> already stands on, and the header
2741
+ this exists for (a chevron, a bar) is unwritable without it. If you interpolate a value that
2742
+ came from outside your application, escape it yourself, or build a node and return that
2743
+ instead.</p>
2744
+ <p><strong>Your chevron is yours to wire.</strong> The grid's own expander binds a click
2745
+ handler to the button it built; a string cannot carry a handler, so that chevron would be
2746
+ dead. Any element in your markup carrying <code>data-lat-group-toggle</code> expands or
2747
+ collapses the group it sits in, and <code>toggle()</code> does the same from a node you built
2748
+ yourself.</p>
2749
+ <p><strong><code>leaves()</code> is a function on purpose.</strong> A group is unbounded and
2750
+ the renderer runs as rows are painted. Handing every group an array of its members would cost
2751
+ a hundred thousand rows on a heading nobody looked at. Call it when you need the rows; read
2752
+ <code>leafCount</code> when you need the size.</p>
2753
+ </div>
2754
+
2755
+ <h3 id="group-default-expanded">Which groups start open</h3>
2756
+ <p class="lead-in">
2757
+ <code>groupDefaultExpanded</code> decides the state of a group <em>before anyone has touched
2758
+ it</em>: <code>true</code> (the default) opens them all, <code>false</code> closes them all, a
2759
+ number opens the first N levels, and a predicate answers per group. Once the user or your own
2760
+ code expands or collapses a group, that decision stands &mdash; the default is not consulted
2761
+ for it again, so a group cannot spring shut under the user on the next rebuild.
2762
+ </p>
2763
+
2764
+ <div class="example">
2765
+ <p class="example__label">The current sprint open, everything else closed, executed</p>
2766
+ <pre data-run="js" data-expect="Backlog 0/1 8pts closed | Sprint 12 1/2 8pts open" data-covers="config:groupRenderer config:groupDefaultExpanded"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
2767
+
2768
+ <span class="cmt">// The renderer the grid calls for each group row. Called here directly too,</span>
2769
+ <span class="cmt">// with the params the grid builds, so this block shows its actual output.</span>
2770
+ <span class="kw">const</span> groupRenderer = (p) =&gt; {
2771
+ <span class="kw">const</span> rows = p.leaves();
2772
+ <span class="kw">const</span> done = rows.filter((r) =&gt; r.data.done).length;
2773
+ <span class="kw">const</span> points = rows.reduce((sum, r) =&gt; sum + r.data.points, 0);
2774
+ <span class="kw">return</span> `${p.value} ${done}/${p.leafCount} ${points}pts ${p.expanded ? 'open' : 'closed'}`;
2775
+ };
2776
+
2777
+ <span class="kw">const</span> grid = createHeadlessGrid({
2778
+ rowKey: 'id',
2779
+ columns: [{ field: 'section' }, { field: 'points', type: 'number', total: 'sum' }, { field: 'done' }],
2780
+ rows: [
2781
+ { id: 1, section: 'Sprint 12', points: 3, done: <span class="kw">true</span> },
2782
+ { id: 2, section: 'Sprint 12', points: 5, done: <span class="kw">false</span> },
2783
+ { id: 3, section: 'Backlog', points: 8, done: <span class="kw">false</span> },
2784
+ ],
2785
+ groupRenderer,
2786
+ <span class="cmt">// Per group, not merely per depth: only the current sprint starts open.</span>
2787
+ groupDefaultExpanded: (group) =&gt; group.value === 'Sprint 12',
2788
+ });
2789
+ grid.columns.group(['section']);
2790
+
2791
+ <span class="kw">const</span> out = [];
2792
+ <span class="kw">for</span> (<span class="kw">let</span> i = 0; i &lt; grid.rows.count(); i++) {
2793
+ <span class="kw">const</span> row = grid.rows.get(i);
2794
+ <span class="kw">if</span> (!row.group) <span class="kw">continue</span>;
2795
+ out.push(groupRenderer({
2796
+ row, key: row.key, column: row.groupColumn, value: row.groupValue,
2797
+ level: row.level, expanded: row.expanded, leafCount: row.leafCount,
2798
+ totals: row.totals, leaves: () =&gt; grid.rows.leavesOf(row.key),
2799
+ toggle: () =&gt; {}, grid, element: <span class="kw">null</span>,
2800
+ }));
2801
+ }
2802
+ grid.destroy();
2803
+ <span class="kw">return</span> out.join(' | ');</code></pre>
2804
+ </div>
2805
+
2806
+ <p class="lead-in">
2807
+ <code>grid.rows.leavesOf(key)</code> is the same reach on its own, for a breadcrumb, a
2808
+ side panel or a rollup computed away from the row: it returns the leaf rows beneath a group
2809
+ heading, which is what <code>leafCount</code> counts.
2810
+ </p>
2811
+
2687
2812
  <h2 id="row-reorder">Row reorder</h2>
2688
2813
  <p class="lead-in">
2689
2814
  <code>rowReorder: true</code> puts a drag handle in the first visible column and lets a user
@@ -4094,6 +4219,55 @@ grid.destroy();
4094
4219
  <span class="kw">return</span> `${a}, ${b}`;</code></pre>
4095
4220
  </div>
4096
4221
 
4222
+ <h2 id="cell-tooltips">Rich cell tooltips</h2>
4223
+ <p class="lead-in">
4224
+ A <code>cell.tooltip</code> string becomes the browser's own <code>title</code>. That is one
4225
+ line of plain text, shown on the browser's schedule, styled by the browser, and unreachable
4226
+ with a keyboard &mdash; it cannot show a related record, a small chart, a list of validation
4227
+ errors or an edit history. The object form of <code>cell.tooltip</code> declares a tooltip the
4228
+ grid draws itself instead (BACKLOG-0001204): <code>{ render, mount, unmount }</code>. The
4229
+ plain-text form is unchanged and still produces a <code>title</code>.
4230
+ </p>
4231
+ <p class="lead-in">
4232
+ <code>render(params)</code> may return an element, a <code>{ title, rows, note }</code> spec the
4233
+ grid renders as text, a <code>{ html }</code> wrapper, or a string. <strong>A bare string is
4234
+ always text, never markup.</strong> That is deliberate and is not a style choice: the most
4235
+ natural tooltip anyone writes is <code>render: (p) =&gt; p.value</code>, and a value is row data
4236
+ &mdash; so if a bare string were markup, a field holding an <code>onerror</code> attribute would
4237
+ execute while the code that rendered it looked harmless. Markup has to be asked for explicitly,
4238
+ in the source, where review can see it; and what goes through <code>{ html }</code> is scrubbed
4239
+ of script the same way <code>allowUnsafeTemplates</code> output is.
4240
+ </p>
4241
+ <p class="lead-in">
4242
+ <code>mount(el, params)</code> and <code>unmount(el)</code> hold live content. The grid core
4243
+ never imports a module, so a sparkline or a KPI tile is mounted by <em>you</em>, inside
4244
+ <code>mount</code>, from a module bundle your page loaded. <code>unmount</code> runs on every
4245
+ close, so nothing is left running behind a hidden tooltip.
4246
+ </p>
4247
+ <p class="lead-in">
4248
+ <code>tooltip: { delay, maxWidth }</code> on the grid carries the defaults. <code>delay</code>
4249
+ is the rest before anything is built &mdash; 400ms by default, which is what stops a pointer
4250
+ sweeping across the grid from mounting a chart per cell &mdash; and <code>maxWidth</code> caps
4251
+ the width (a number is pixels, a string is used as written). Neither switches tooltips on: a
4252
+ column with no <code>cell.tooltip</code> has none.
4253
+ </p>
4254
+ <p class="lead-in">
4255
+ <strong>Keyboard and assistive technology.</strong> Focusing a cell shows the same tooltip after
4256
+ the same delay, and the cell carries <code>aria-describedby</code> pointing at it, so the
4257
+ content is announced rather than merely drawn. Any <code>aria-describedby</code> the cell
4258
+ already had &mdash; a validation message, for instance &mdash; is preserved and restored, not
4259
+ replaced. The tooltip is hoverable and stays open while the pointer rests on it, and Escape
4260
+ dismisses it without moving the pointer (WCAG 2.2 AA, 1.4.13). Escape is consumed only while a
4261
+ tooltip is open, so an editor, a menu or a maximised grid still sees it otherwise.
4262
+ </p>
4263
+ <p class="lead-in">
4264
+ <strong>Pooled rows.</strong> The tooltip closes on scroll, and its content is resolved from the
4265
+ DOM at the moment it opens rather than when the pointer arrived. Both follow from the same
4266
+ fact: rows and cells are recycled as the grid scrolls, so a bubble left open would be anchored
4267
+ to a node that has since been handed to a different row. It can therefore never show one row's
4268
+ content over another's.
4269
+ </p>
4270
+
4097
4271
  <h2 id="scrollbars">Always-visible scrollbars</h2>
4098
4272
  <p class="lead-in">
4099
4273
  Native scrollbars are overlay bars on most platforms now: they fade away when the pointer is
@@ -7582,6 +7756,8 @@ el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);</code></pre>
7582
7756
  <tr><td class="name">cell:conflict</td><td class="desc">The write was accepted but the server row had moved underneath it. Last-write-wins with the divergence surfaced: your value stands and serverRow carries the server's truth.</td></tr>
7583
7757
  <tr><td class="name">cell:contextmenu</td><td class="desc">Right-click on a cell.</td></tr>
7584
7758
  <tr><td class="name">cell:dblclicked</td><td class="desc">A cell was double-clicked. Carries the row, column, value and text.</td></tr>
7759
+ <tr><td class="name">cell:mouseover</td><td class="desc">The pointer entered a cell. Fires once per cell, carries what cell:clicked carries plus the cell element as target, and is delegated on the viewport so it stays correct over pooled rows.</td></tr>
7760
+ <tr><td class="name">cell:mouseout</td><td class="desc">The pointer left a cell. Fires once per cell, including when the pointer left the grid; moving to the next cell fires this first, then cell:mouseover.</td></tr>
7585
7761
  <tr><td class="name">cell:edit:end</td><td class="desc">It closed: committed or cancelled.</td></tr>
7586
7762
  <tr><td class="name">cell:edit:start</td><td class="desc">An edit session opened.</td></tr>
7587
7763
  <tr><td class="name">cell:pending</td><td class="desc">Applied optimistically, not yet durable. Only with edit.commit.</td></tr>
@@ -7901,7 +8077,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'trial'<
7901
8077
 
7902
8078
  <footer>
7903
8079
  <p>
7904
- Lattice Grid 1.57.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
8080
+ Lattice Grid 1.59.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
7905
8081
  Written against the shipped source. Where this guide and the code disagree, the code wins,
7906
8082
  please <a href="https://www.latticegrid.dev">tell us</a>.
7907
8083
  </p>