@toclocoinc/lattice-grid 1.6.1 → 1.7.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 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.6.1 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
7
+ Version 1.7.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
8
8
 
9
9
  ---
10
10
 
package/docs/API.html CHANGED
@@ -360,7 +360,7 @@
360
360
  <div class="shell">
361
361
  <aside class="rail">
362
362
  <p class="rail__brand">Lattice Grid</p>
363
- <p class="rail__sub">API reference · v1.6.1</p>
363
+ <p class="rail__sub">API reference · v1.7.0</p>
364
364
  <nav>
365
365
  <div class="rail__group">
366
366
  <span class="rail__label">Start</span>
@@ -437,15 +437,15 @@
437
437
  </header>
438
438
 
439
439
  <p class="chips">
440
- <span class="chip">Version 1.6.1</span>
440
+ <span class="chip">Version 1.7.0</span>
441
441
  <span class="chip">Zero dependencies</span>
442
442
  <span class="chip"><a href="api-detail.html">Developer guide &rarr;</a></span>
443
443
  </p>
444
444
 
445
445
  <h2 id="construction">Construction</h2>
446
- <p class="section-note">Two files are all you need: a stylesheet and a script. Nothing is fetched at runtime — no CDN, no font, no icon sprite.</p>
446
+ <p class="section-note">Two files are all you need: a stylesheet and a script. Nothing is fetched at runtime — no CDN, no font, no icon sprite — however the two files themselves got there.</p>
447
447
 
448
- <pre><code><span class="cmt">&lt;!-- Script tag. Everything is on one global. --&gt;</span>
448
+ <pre><code><span class="cmt">&lt;!-- Script tag, from your own build. Everything is on one global. --&gt;</span>
449
449
  &lt;link rel="stylesheet" href="dist/lattice-grid.min.css"&gt;
450
450
  &lt;script src="dist/lattice-grid.min.js"&gt;&lt;/script&gt;
451
451
 
@@ -453,10 +453,21 @@
453
453
  <span class="kw">const</span> grid = LatticeGrid.createGrid(document.getElementById('grid'), config);
454
454
  &lt;/script&gt;</code></pre>
455
455
 
456
- <p>Or as modules, importing the bundle by path:</p>
456
+ <p>Or straight from jsDelivr — no npm install, no bundler, no local copy at all:</p>
457
457
 
458
- <pre><code><span class="cmt">// With a renderer, in a browser.</span>
458
+ <pre><code>&lt;link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.0/lattice-grid.min.css"&gt;
459
+ &lt;script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.0/lattice-grid.min.js"&gt;&lt;/script&gt;
460
+
461
+ &lt;script&gt;
462
+ <span class="kw">const</span> grid = LatticeGrid.createGrid(document.getElementById('grid'), config);
463
+ &lt;/script&gt;</code></pre>
464
+
465
+ <p>Or as modules, importing the bundle by path — your own build, npm, or jsDelivr:</p>
466
+
467
+ <pre><code><span class="cmt">// With a renderer, in a browser. Any of:</span>
459
468
  <span class="kw">import</span> { createGrid } <span class="kw">from</span> './dist/lattice-grid.esm.js';
469
+ <span class="cmt">// import { createGrid } from '@toclocoinc/lattice-grid';</span>
470
+ <span class="cmt">// import { createGrid } from 'https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.0/lattice-grid.esm.min.js';</span>
460
471
  <span class="kw">const</span> grid = createGrid(document.getElementById('grid'), config);
461
472
 
462
473
  <span class="cmt">// Headless: the same API without a renderer. Data, filters, sort,</span>
@@ -466,6 +477,12 @@
466
477
  <span class="kw">import</span> { createHeadlessGrid } <span class="kw">from</span> './dist/lattice-core.esm.js';
467
478
  <span class="kw">const</span> grid = createHeadlessGrid(config);</code></pre>
468
479
 
480
+ <div class="note"><p>jsDelivr mirrors every version published to npm at
481
+ <code>cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@&lt;version&gt;/&lt;file&gt;</code>. Pin an exact
482
+ version for anything shipped — <code>@1.7.0</code>, not <code>@latest</code> — so a release does
483
+ not change what a page already in production loads. The same convention reaches any module:
484
+ <code>.../modules/htmx.esm.min.js</code>, <code>.../modules/react.esm.min.js</code>, and so on.</p></div>
485
+
469
486
  <h2 id="adapters">Framework adapters</h2>
470
487
  <p class="section-note">The web component carries the grid inside it, so use it <em>or</em> <code>createGrid</code> in a page, not both &mdash; two copies keep separate registries, and a renderer registered through one is invisible to the other. One optional bundle per framework. The framework and <code>createGrid</code> are passed in rather than imported, so the adapters add no dependency and carry no second copy of the grid.</p>
471
488
 
@@ -517,11 +534,13 @@
517
534
  </div>
518
535
 
519
536
  <div class="note">
520
- <p><strong>There is no npm package.</strong> Lattice Grid is <code>private: true</code> and is not
521
- published to any registry, so <code>import from '@latticegrid/dom'</code> will not resolve.
522
- Import the built file by path, or use the script tag. The same is true of the type
523
- declarations: point your <code>tsconfig</code> at <code>dist/lattice-grid.d.ts</code> rather
524
- than expecting <code>@types</code> to find them.</p>
537
+ <p><strong>Published as <code>@toclocoinc/lattice-grid</code></strong> <code>npm install
538
+ @toclocoinc/lattice-grid</code>, then <code>import { createLatticeGrid } from
539
+ '@toclocoinc/lattice-grid/modules/react'</code> (swap the module name for Vue or Svelte)
540
+ resolves like any other package. Type declarations resolve automatically through the
541
+ package's own <code>types</code> field — no <code>@types</code> package to install. Importing
542
+ the built file by path, or the script tag, both still work for a project with no npm install
543
+ step at all.</p>
525
544
  </div>
526
545
 
527
546
  <div class="table-wrap">
@@ -598,6 +617,76 @@
598
617
 
599
618
  <p class="section-note">Events are re-dispatched as <code>CustomEvent</code>s named <code>lattice-</code> plus the grid name with colons hyphenated: <code>cell:changed</code> becomes <code>lattice-cell-changed</code>. The payload is on <code>event.detail</code>. The prefix avoids colliding with platform events — the grid emits one called <code>scroll</code>.</p>
600
619
 
620
+ <h2 id="dhtmlx-compat">dhtmlx Grid compatibility wrapper</h2>
621
+ <p class="section-note">A <code>Grid</code> class shaped like dhtmlx's own <code>dhx.Grid</code> (Suite 5+), backed by a real Lattice grid. Covers column definitions, <code>.data</code>, <code>.selection</code>, <code>.history</code>, <code>.export.csv</code>/<code>.xlsx</code>, and a name-mapped subset of <code>.events</code> — see <a href="api-detail.html#dhtmlx-guide">the guide</a> for exactly what is and is not covered. Not the classic pre-Suite-5 <code>dhtmlXGridObject</code>.</p>
622
+
623
+ <pre><code><span class="kw">import</span> { Grid } <span class="kw">from</span> './dist/modules/dhtmlx-compat.esm.min.js';
624
+
625
+ <span class="kw">const</span> grid = <span class="kw">new</span> Grid(container, {
626
+ columns: [{ id: 'name', header: [{ text: 'Name' }], sortable: <span class="kw">true</span> }],
627
+ data: rows,
628
+ });
629
+ grid.data.serialize(); <span class="cmt">// every row's data, in source order</span></code></pre>
630
+
631
+ <div class="table-wrap">
632
+ <table>
633
+ <thead><tr><th>Namespace</th><th>Covers</th></tr></thead>
634
+ <tbody>
635
+ <tr><td class="sig">.data</td><td class="desc"><code>add</code>, <code>update</code>, <code>remove</code>, <code>removeAll</code>, <code>parse</code>, <code>load</code>, <code>find</code>, <code>findAll</code>, <code>exists</code>, <code>getItem</code>, <code>getId</code>, <code>getIndex</code>, <code>getLength</code>, <code>forEach</code>, <code>serialize</code>, <code>sort</code>, <code>filter</code>, <code>resetFilter</code>. Index means current display order throughout.</td></tr>
636
+ <tr><td class="sig">.selection</td><td class="desc"><code>setCell(rowId, colId, ctrlUp?, shiftUp?)</code>, <code>getCell</code>, <code>getCells</code>, <code>isSelectedCell</code>, <code>removeCell</code>. <code>row</code>/<code>column</code> carry <code>.id</code>; a row's own fields sit alongside it, matching dhtmlx's own <code>IRow</code>.</td></tr>
637
+ <tr><td class="sig">.history</td><td class="desc"><code>undo</code>, <code>redo</code>, <code>canUndo</code>, <code>canRedo</code>, <code>clear</code>, <code>getHistory</code>.</td></tr>
638
+ <tr><td class="sig">.export</td><td class="desc"><code>csv</code>, <code>xlsx</code>. <code>pdf</code>/<code>png</code> throw — no raster export to translate to.</td></tr>
639
+ <tr><td class="sig">.events</td><td class="desc"><code>cellClick</code>/<code>cellDblClick</code>/<code>cellRightClick</code>/<code>afterEditStart</code>/<code>afterEditEnd</code>/<code>afterSort</code> call your handler with dhtmlx's own positional arguments. <code>afterRowDrop</code> fires <code>(data, event)</code> from a same-grid reorder settling or a row landing from another grid. Every other mapped name passes Lattice's own event object. Every <code>before*</code>/<code>can*</code>/<code>cancel*</code> event, and row/column drag <em>negotiation</em> (a handler refusing or steering a drop mid-gesture), is unmapped — logged once per name if subscribed to.</td></tr>
640
+ <tr><td class="sig">.rangeSelection</td><td class="desc">Best-effort only — its range shape is this wrapper's own design, not dhtmlx's genuine <code>RangeSelection</code> module.</td></tr>
641
+ </tbody>
642
+ </table>
643
+ </div>
644
+
645
+ <div class="table-wrap">
646
+ <table>
647
+ <thead><tr><th>Config key</th><th>Becomes</th></tr></thead>
648
+ <tbody>
649
+ <tr><td class="sig">dragItem: 'row'</td><td class="desc"><code>rowReorder: true</code> — same-grid drag-to-reorder.</td></tr>
650
+ <tr><td class="sig">rowTransfer</td><td class="desc">Passed straight through, unlike every other key — dhtmlx allows any two <code>dragItem: 'row'</code> grids on a page to exchange rows by default; Lattice's <code>rowTransfer</code> is deliberately opt-in per pair, so there is nothing to derive it from.</td></tr>
651
+ </tbody>
652
+ </table>
653
+ </div>
654
+
655
+ <h2 id="declarative-init">Declarative init, hydration &amp; state</h2>
656
+ <p class="section-note">Core-level primitives, usable with or without any framework adapter or the htmx module below. An element built by <code>createGrid</code> is discoverable from itself: <code>element.__lattice</code> holds the live instance, cleared on <code>destroy()</code>.</p>
657
+
658
+ <div class="table-wrap">
659
+ <table>
660
+ <thead><tr><th>Export</th><th>Does</th></tr></thead>
661
+ <tbody>
662
+ <tr><td class="sig">autoInit(root)</td><td class="desc">Builds a grid on every <code>[data-lattice-grid]</code> element under <code>root</code> not already built — idempotent, safe to call again after new content arrives. Config comes from a sibling <code>&lt;script type="application/json" data-lattice-config&gt;</code>; without one, a <code>&lt;table&gt;</code> element is hydrated instead.</td></tr>
663
+ <tr><td class="sig">hydrateTable(table, config?)</td><td class="desc">Reads a <code>&lt;table&gt;</code>'s header row for column definitions and body rows for data (type-inferred per cell), then replaces the table with the grid. <code>config</code> — columns, rows, anything else — always wins over what was inferred.</td></tr>
664
+ <tr><td class="sig">serialiseState(grid) / restoreState(grid, encoded)</td><td class="desc">A compact, URL-safe encoding of everything <code>grid.state</code> covers — sort, filters, column order and widths, scroll position, selection — diffed against the grid's own defaults first, so an untouched grid encodes to a handful of characters.</td></tr>
665
+ <tr><td class="sig">&lt;meta name="lattice-license" content="…"&gt;</td><td class="desc">Read automatically when no <code>licence</code> is passed to <code>createGrid</code> — no imperative call required.</td></tr>
666
+ </tbody>
667
+ </table>
668
+ </div>
669
+
670
+ <h2 id="htmx-integration">htmx integration</h2>
671
+ <p class="section-note">One file. <code>modules/htmx</code> re-exports <code>createGrid</code>, <code>autoInit</code>, <code>hydrateTable</code>, <code>readTable</code>, <code>serialiseState</code> and <code>restoreState</code> alongside its own exports below, so a page using htmx integration never also loads the base bundle — that would mean two independent copies of the whole engine on one page. Registers on import: builds grids from <code>[data-lattice-grid]</code> elements or server-rendered <code>&lt;table&gt;</code>s, tears them down before htmx detaches a swapped-out subtree, and rebuilds them in newly-loaded content. <code>driveServerMode</code>/<code>driveInfiniteScroll</code> drive sort, filter and infinite scroll over plain htmx requests; <code>driveOobUpdates</code> applies out-of-band row updates in place. See <a href="api-detail.html#htmx-guide">the guide</a> for the full request-lifecycle wiring and why infinite scroll uses two triggers.</p>
672
+
673
+ <pre><code><span class="kw">import</span> { autoInit, driveServerMode, driveInfiniteScroll } <span class="kw">from</span> './dist/modules/htmx.esm.min.js';
674
+
675
+ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under it</span></code></pre>
676
+
677
+ <div class="table-wrap">
678
+ <table>
679
+ <thead><tr><th>Export</th><th>Does</th></tr></thead>
680
+ <tbody>
681
+ <tr><td class="sig">createGrid, autoInit, hydrateTable, readTable, serialiseState, restoreState</td><td class="desc">The same functions documented above, re-exported — this is the one import a page using htmx integration needs for both grid construction and htmx wiring.</td></tr>
682
+ <tr><td class="sig">driveServerMode(grid, trigger, opts?)</td><td class="desc">A sort or filter change fires a request on <code>trigger</code> carrying <code>offset</code>/<code>limit</code>/<code>sort</code>/<code>filters</code>; the response replaces the grid's rows. <code>opts.columns</code> for the HTML-fragment ingest path.</td></tr>
683
+ <tr><td class="sig">driveInfiniteScroll(grid, sentinel, opts?)</td><td class="desc">Appends rows as the grid's own visible window nears the end of what's loaded. <code>sentinel</code>'s own <code>hx-trigger</code> names <code>revealed, lattice:scroll-near-end</code> — the first fires the initial chunk, the second every chunk after. <code>opts.threshold</code> (default 20) sets how many rows from the end counts as near.</td></tr>
684
+ <tr><td class="sig">driveOobUpdates(grid, opts?)</td><td class="desc">Applies an out-of-band swap landing on <code>[data-lattice-row="&lt;key&gt;"]</code> to that row, in place — scroll, selection and filter state untouched.</td></tr>
685
+ <tr><td class="sig">Browser history</td><td class="desc"><code>serialiseState</code>/<code>restoreState</code> above, wired automatically to <code>htmx:beforeHistorySave</code>/<code>htmx:historyRestore</code> once this module is imported — browser back restores the prior sort, filter and scroll position.</td></tr>
686
+ </tbody>
687
+ </table>
688
+ </div>
689
+
601
690
  <h2 id="config">Configuration properties</h2>
602
691
  <p class="section-note">
603
692
  Every key is settable at runtime through <code>grid.set(key, value)</code>. Keys marked
@@ -841,7 +930,7 @@
841
930
  <table>
842
931
  <thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
843
932
  <tbody>
844
- <tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.6.1'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
933
+ <tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.7.0'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
845
934
  <tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
846
935
  <tr><td class="sig">set(key, value)</td><td class="type">void</td><td class="desc">Write one key. Every key is live; nothing needs a rebuild.</td></tr>
847
936
  <tr><td class="sig">setAll(values)</td><td class="type">void</td><td class="desc">Write several in one pass. Emits one <code>config:changed</code> for the batch, not one per key.</td></tr>
@@ -1505,8 +1594,9 @@ grid.highlight.clear(); // everythin
1505
1594
  saved: [{ id: 'escalations', name: 'Escalations', description: 'Worst SLA first',
1506
1595
  state: { filters: { col: 'statusId', op: 'eq', value: 4 },
1507
1596
  sort: [{ col: 'utilisation', dir: 'desc' }] } }],
1508
- allowSave: true, // false removes the save form entirely
1509
- storage: { read, write }, // synchronous mirror; see the note below
1597
+ allowSave: true, // false removes the save form entirely
1598
+ local: true, // saved views live in this browser's localStorage no backend
1599
+ // storage: { read, write }, // or bring your own backend; see the note below
1510
1600
  },
1511
1601
  });</code></pre>
1512
1602
  <div class="table-wrap">
@@ -1526,7 +1616,8 @@ grid.highlight.clear(); // everythin
1526
1616
  </tbody>
1527
1617
  </table>
1528
1618
  </div>
1529
- <div class="note"><p><strong>The grid makes no network calls.</strong> <code>storage.write</code> is for a synchronous mirror such as <code>localStorage</code>. To persist to a server, listen for <code>view:saved</code>, <code>view:renamed</code>, <code>view:removed</code> and <code>view:default</code> — each carries the one view that moved, so you can send a single record rather than diffing two lists. Because the grid does not track whether your write landed, a failed request leaves the view visible locally: catch it and call <code>views.reload()</code>.</p></div>
1619
+ <div class="note"><p><strong>The grid makes no network calls.</strong> <code>storage.write</code> is a synchronous mirror. To persist to a server, listen for <code>view:saved</code>, <code>view:renamed</code>, <code>view:removed</code> and <code>view:default</code> — each carries the one view that moved, so you can send a single record rather than diffing two lists. Because the grid does not track whether your write landed, a failed request leaves the view visible locally: catch it and call <code>views.reload()</code>.</p></div>
1620
+ <div class="note"><p><strong><code>views.local</code></strong> is the no-backend option: <code>true</code> stores views under a default <code>localStorage</code> key, shared by every grid on the origin; <code>{ key: '…' }</code> picks a key of your own, for more than one grid whose views should stay apart. Given alongside an explicit <code>storage</code>, <code>storage</code> wins and <code>local</code> is ignored, with a console warning — the two are never merged. Built on <code>createLocalViewStorage</code>, exported for direct use (a custom key, or a different <code>Storage</code>-shaped backing such as <code>sessionStorage</code>) without the <code>local</code> shorthand.</p></div>
1530
1621
 
1531
1622
  <h2 id="diff">grid.diff</h2>
1532
1623
  <p>Audit mode. Give it a prior snapshot and every row reports whether it was added, removed or changed, and which cells moved.</p>
@@ -1718,7 +1809,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
1718
1809
  <tbody>
1719
1810
  <tr><td class="sig">set(key)</td><td class="type">object</td><td class="desc">Install a key for the process. Returns the provisional verdict; the check is asynchronous and <code>licence:changed</code> fires when it settles.</td></tr>
1720
1811
  <tr><td class="sig">state()</td><td class="type">string</td><td class="desc"><code>'licensed'</code>, <code>'localhost'</code> or <code>'trial'</code>.</td></tr>
1721
- <tr><td class="sig">info()</td><td class="type">object</td><td class="desc"><code>{ valid, reason, issuedTo, expires, product }</code>.</td></tr>
1812
+ <tr><td class="sig">info()</td><td class="type">object</td><td class="desc"><code>{ valid, reason, issuedTo, expires, product }</code>. <code>expires</code> is <code>undefined</code> for a perpetual key — the default — and an ISO date only for one deliberately issued with a term.</td></tr>
1722
1813
  <tr><td class="sig">watermark()</td><td class="type">boolean</td><td class="desc">Whether the trial mark is showing.</td></tr>
1723
1814
  <tr><td class="sig">ready</td><td class="type">Promise</td><td class="desc">Settles when the licence check finishes.</td></tr>
1724
1815
  </tbody>
@@ -1748,10 +1839,13 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
1748
1839
  <p>Keys are issued from <a href="https://www.latticegrid.dev">latticegrid.dev</a>. A key is
1749
1840
  issued per deployment rather than per developer or per seat: name the domains the grid will run
1750
1841
  on and one key covers every developer, every build and every user on them.</p>
1842
+ <p><strong>Keys are perpetual by default.</strong> A key carries no expiry unless one was
1843
+ deliberately issued — a trial, a time-boxed pilot — so the ordinary key is valid until the
1844
+ domains it names change, not one that quietly lapses on a date nobody is tracking.</p>
1751
1845
  <p>Checking a key needs no network. There is no licence server, no call home, and nothing that
1752
- can fail at three in the morning — a key carries its own answer and the grid reads it locally.
1753
- An expired key, a key for the wrong domain, or a key that will not read all do the same thing:
1754
- log one console warning and show the watermark.</p>
1846
+ can fail at three in the morning — a key carries its own answer and the grid reads it locally,
1847
+ fresh on every load. A key for the wrong domain, or a key that will not read at all, does the
1848
+ same thing a genuinely expired trial key does: log one console warning and show the watermark.</p>
1755
1849
  <div class="note"><p>Install the key before creating a grid. Setting one later still works —
1756
1850
  <code>licence:changed</code> fires and the watermark is removed — but the first frames of the
1757
1851
  grid will carry it.</p></div>
@@ -2238,7 +2332,7 @@ createGrid(el, {
2238
2332
  </div>
2239
2333
 
2240
2334
  <footer>
2241
- Lattice Grid 1.6.1 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
2335
+ Lattice Grid 1.7.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
2242
2336
  This document describes the behaviour of the shipped library. Where this guide and the code
2243
2337
  disagree, the code wins — please <a href="https://www.latticegrid.dev">tell us</a>.
2244
2338
  </footer>