@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.
@@ -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.6.1</p>
440
+ <p class="rail__sub">Developer guide · v1.7.0</p>
441
441
  <nav>
442
442
  <div class="rail__group">
443
443
  <span class="rail__label">Start here</span>
@@ -445,6 +445,8 @@
445
445
  <a href="#install">Install</a>
446
446
  <a href="#first-grid">Your first grid</a>
447
447
  <a href="#adapters-guide">React, Vue, Svelte</a>
448
+ <a href="#dhtmlx-guide">Coming from dhtmlx Grid</a>
449
+ <a href="#htmx-guide">Using with htmx</a>
448
450
  <a href="#concepts">How it works</a>
449
451
  </div>
450
452
  <div class="rail__group">
@@ -535,7 +537,7 @@
535
537
  <a href="API.html">reference tables</a> are the shorter version for when you already know.
536
538
  </p>
537
539
  <p class="chips">
538
- <span class="chip">Version 1.6.1</span>
540
+ <span class="chip">Version 1.7.0</span>
539
541
  <span class="chip">Zero dependencies</span>
540
542
  <span class="chip">No build step</span>
541
543
  </p>
@@ -638,10 +640,28 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
638
640
  </div>
639
641
 
640
642
  <h2 id="install">Install</h2>
641
- <p class="lead-in">Two files. Nothing is fetched at runtime — no CDN, no font, no sprite sheet.</p>
643
+ <p class="lead-in">Two files. Nothing is fetched at runtime — no CDN, no font, no sprite sheet — however the two files themselves got onto the page. Four equally valid ways to get them there:</p>
642
644
 
643
645
  <div class="example">
644
- <p class="example__label">Script tag</p>
646
+ <p class="example__label">npm</p>
647
+ <pre><code>npm install @toclocoinc/lattice-grid</code></pre>
648
+ <pre><code><span class="kw">import</span> { createGrid } <span class="kw">from</span> '@toclocoinc/lattice-grid';
649
+ <span class="kw">import</span> '@toclocoinc/lattice-grid/css';
650
+ <span class="kw">const</span> grid = createGrid(document.getElementById('grid'), config);</code></pre>
651
+ </div>
652
+
653
+ <div class="example">
654
+ <p class="example__label">jsDelivr — no npm install, no bundler</p>
655
+ <pre><code>&lt;link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.0/lattice-grid.min.css"&gt;
656
+ &lt;script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.7.0/lattice-grid.min.js"&gt;&lt;/script&gt;
657
+
658
+ &lt;script&gt;
659
+ <span class="kw">const</span> grid = LatticeGrid.createGrid(document.getElementById('grid'), config);
660
+ &lt;/script&gt;</code></pre>
661
+ </div>
662
+
663
+ <div class="example">
664
+ <p class="example__label">Script tag, your own build</p>
645
665
  <pre><code>&lt;link rel="stylesheet" href="lattice-grid.min.css"&gt;
646
666
  &lt;script src="lattice-grid.min.js"&gt;&lt;/script&gt;
647
667
 
@@ -651,16 +671,20 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
651
671
  </div>
652
672
 
653
673
  <div class="example">
654
- <p class="example__label">ES modules</p>
674
+ <p class="example__label">ES modules, your own build</p>
655
675
  <pre><code><span class="kw">import</span> { createGrid } <span class="kw">from</span> './lattice-grid.esm.min.js';
656
676
  <span class="kw">const</span> grid = createGrid(document.getElementById('grid'), config);</code></pre>
657
677
  </div>
658
678
 
659
679
  <div class="note">
660
- <p><strong>There is no npm package.</strong> Lattice Grid is not published to any registry, so
661
- <code>import … from '@latticegrid/dom'</code> will not resolve. Import the built file by path,
662
- or use the script tag. For editor tooling, point your <code>tsconfig</code> at
663
- <code>lattice-grid.d.ts</code>.</p>
680
+ <p><strong>jsDelivr mirrors every version published to npm</strong> at
681
+ <code>cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@&lt;version&gt;/&lt;file&gt;</code> pin an
682
+ exact version, e.g. <code>@1.7.0</code> rather than <code>@latest</code>, so a later release
683
+ does not change what a page already in production loads. The same convention reaches a
684
+ module: <code>.../modules/htmx.esm.min.js</code>, <code>.../modules/dhtmlx-compat.esm.min.js</code>,
685
+ and so on. Type declarations resolve automatically through npm's own <code>types</code> field;
686
+ for editor tooling against the CDN or a plain script tag, point your <code>tsconfig</code> at
687
+ <code>lattice-grid.d.ts</code> directly.</p>
664
688
  </div>
665
689
 
666
690
  <p class="lead-in">Everything else in the distribution is an alternative packaging or a
@@ -838,6 +862,226 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
838
862
  freely.</p>
839
863
  </div>
840
864
 
865
+ <h2 id="dhtmlx-guide">Coming from dhtmlx Grid</h2>
866
+ <p class="lead-in">
867
+ <code>lattice-grid/modules/dhtmlx-compat</code> exposes a <code>Grid</code> class shaped
868
+ like dhtmlx's own <code>dhx.Grid</code> &mdash; the same constructor call, the same
869
+ <code>.data</code>, <code>.selection</code>, <code>.history</code>, <code>.export</code> and
870
+ <code>.events</code> namespaces &mdash; sitting on top of a real Lattice grid underneath.
871
+ Swap the import and, for the surface below, the calling code does not change.
872
+ </p>
873
+
874
+ <div class="example">
875
+ <p class="example__label">A drop-in constructor</p>
876
+ <pre><code><span class="kw">import</span> { Grid } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/dhtmlx-compat';
877
+
878
+ <span class="kw">const</span> grid = <span class="kw">new</span> Grid(container, {
879
+ columns: [
880
+ { id: 'name', header: [{ text: 'Name' }], width: 200, sortable: <span class="kw">true</span> },
881
+ { id: 'qty', header: [{ text: 'Qty' }], type: 'number', editable: <span class="kw">true</span>, editorType: 'input' },
882
+ ],
883
+ data: rows,
884
+ });
885
+
886
+ grid.events.on('cellClick', (row, column, event) =&gt; {
887
+ <span class="cmt">// row and column carry .id, row's own fields sit alongside it &mdash;</span>
888
+ <span class="cmt">// the same shape dhtmlx's own IRow/ICol declare.</span>
889
+ });</code></pre>
890
+ </div>
891
+
892
+ <div class="why">
893
+ <p><strong>What is covered:</strong> column definitions (<code>header</code>, <code>width</code>,
894
+ <code>sortable</code>, <code>resizable</code>, <code>hidden</code>, <code>editorType</code>,
895
+ <code>editorConfig</code>, <code>options</code>, <code>template</code>, <code>summary</code>);
896
+ <code>.data</code>'s <code>add</code>/<code>update</code>/<code>remove</code>/
897
+ <code>removeAll</code>/<code>parse</code>/<code>load</code>/<code>find</code>/
898
+ <code>findAll</code>/<code>exists</code>/<code>getItem</code>/<code>getId</code>/
899
+ <code>getIndex</code>/<code>getLength</code>/<code>forEach</code>/<code>serialize</code>/
900
+ <code>sort</code>/<code>filter</code>/<code>resetFilter</code>; <code>.selection</code>'s
901
+ <code>setCell</code>/<code>getCell</code>/<code>getCells</code>/<code>isSelectedCell</code>/
902
+ <code>removeCell</code>; <code>.history</code>'s <code>undo</code>/<code>redo</code>/
903
+ <code>canUndo</code>/<code>canRedo</code>/<code>clear</code>/<code>getHistory</code>;
904
+ <code>.export.csv</code>/<code>.xlsx</code>; and the events <code>cellClick</code>,
905
+ <code>cellDblClick</code>, <code>cellRightClick</code>, <code>afterEditStart</code>,
906
+ <code>afterEditEnd</code>, <code>afterSort</code>, <code>filterChange</code>,
907
+ <code>afterColumnDrop</code>, <code>resize</code>, <code>afterResizeEnd</code>,
908
+ <code>afterColumnHide</code>, <code>afterColumnShow</code>, <code>afterExpand</code>,
909
+ <code>afterCollapse</code>, <code>afterSelect</code>, <code>afterUnSelect</code>,
910
+ <code>afterCopy</code>, <code>afterRowDrop</code> and <code>scroll</code>. Grid-level
911
+ <code>dragItem: 'row'</code> becomes <code>rowReorder: true</code> &mdash; same-grid
912
+ drag-to-reorder.</p>
913
+ <p><code>cellClick</code>, <code>cellDblClick</code>, <code>cellRightClick</code>,
914
+ <code>afterEditStart</code>, <code>afterEditEnd</code> and <code>afterSort</code> call your
915
+ handler with dhtmlx's own positional arguments &mdash; <code>(row, column, event)</code>,
916
+ not Lattice's own event object &mdash; because that is dhtmlx's own documented signature for
917
+ them. <code>afterRowDrop</code> calls your handler with <code>(data, event)</code>, firing
918
+ from either a same-grid reorder settling or a row landing here from another grid &mdash;
919
+ dhtmlx has one event name for what Lattice models as two. Every other mapped event calls your
920
+ handler with Lattice's own event object, under Lattice's own field names, since a wrong guess
921
+ at a fabricated positional shape is worse than an honest one.</p>
922
+ <p><strong>Index means current display order</strong> &mdash; after sort, filter and
923
+ grouping &mdash; everywhere <code>.data</code> takes or returns one. dhtmlx is not fully
924
+ consistent about this across its own methods; this is the one meaning, held everywhere.</p>
925
+ <p><strong>Cross-grid dragging needs an explicit <code>rowTransfer</code>.</strong> dhtmlx
926
+ lets any two grids with <code>dragItem: 'row'</code> on the same page exchange rows by
927
+ default; Lattice's <code>rowTransfer</code> is deliberately opt-in per pair, with no
928
+ dhtmlx property to derive it from, so a caller wanting that behaviour passes
929
+ <code>rowTransfer</code> straight through as a bonus config key.</p>
930
+ </div>
931
+
932
+ <div class="why">
933
+ <p><strong>What is not.</strong> Every <code>before*</code>/<code>can*</code>/<code>cancel*</code>
934
+ event is unmapped: dhtmlx's convention lets a handler return <code>false</code> to cancel the
935
+ action, and Lattice has no cancelable-event model to honour that with &mdash; approximating one
936
+ would silently ignore a <code>return false</code> a caller depends on. Row and column
937
+ drag-<em>negotiation</em> events &mdash; <code>beforeRowDrag</code>, <code>dragRowOut</code>,
938
+ <code>canRowDrop</code>, <code>cancelRowDrop</code>, <code>beforeRowDrop</code> and their
939
+ column equivalents &mdash; are unmapped for the same reason: each can refuse or steer a drag
940
+ mid-gesture, which Lattice has no live protocol to offer. <code>export.pdf()</code> and
941
+ <code>export.png()</code> throw &mdash; there is no raster export to translate to.
942
+ <code>.rangeSelection</code> is offered on a best-effort basis and its range shape is this
943
+ wrapper's own design, not dhtmlx's — a genuine <code>RangeSelection</code> module is a
944
+ separate part of dhtmlx's own product, and this wrapper does not know its exact shape.
945
+ Classic <code>dhtmlXGridObject</code> (pre-Suite 5, string-configured,
946
+ index-addressed) is a different product in every respect that matters here and is not covered
947
+ at all.</p>
948
+ <p>Calling an unmapped event name does not fail silently: the first subscription logs which
949
+ name has no Lattice equivalent, so a caller relying on it finds out in development rather
950
+ than in production.</p>
951
+ </div>
952
+
953
+ <h2 id="htmx-guide">Using with htmx</h2>
954
+ <p class="lead-in">
955
+ <code>lattice-grid/modules/htmx</code> lets a grid survive htmx's own DOM swaps,
956
+ hydrate from a server-rendered <code>&lt;table&gt;</code>, and drive sort, filter and
957
+ infinite scroll over plain htmx requests &mdash; the server owns pagination and the
958
+ request lifecycle; this module only wires the grid's own state to it. Importing it
959
+ is enough for the lifecycle half: it registers itself against
960
+ <code>document</code> on load.
961
+ </p>
962
+
963
+ <div class="example">
964
+ <p class="example__label">Declarative init</p>
965
+ <pre><code>&lt;script src="https://unpkg.com/htmx.org@2"&gt;&lt;/script&gt;
966
+ &lt;script type="module" src="dist/modules/htmx.esm.min.js"&gt;&lt;/script&gt;
967
+
968
+ &lt;div id="grid" data-lattice-grid&gt;&lt;/div&gt;
969
+ &lt;script type="application/json" data-lattice-config&gt;
970
+ { "columns": [{ "field": "name" }, { "field": "qty", "type": "number" }] }
971
+ &lt;/script&gt;
972
+
973
+ <span class="cmt">// Anywhere on the page, once:</span>
974
+ <span class="kw">import</span> { autoInit } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/htmx';
975
+ autoInit(document);</code></pre>
976
+ </div>
977
+
978
+ <div class="why">
979
+ <p><strong>One file, not two.</strong> <code>autoInit</code> comes from
980
+ <code>modules/htmx</code> itself here, not from the base package — this module
981
+ already carries a complete, independently-bundled copy of <code>createGrid</code> and
982
+ everything it depends on, the same way every module built this way does (a bundle
983
+ inlines what it imports; it has no way to reach across to a copy some other
984
+ <code>&lt;script&gt;</code> tag happens to have loaded). Left unexported, a page using
985
+ htmx integration would load that engine twice: once for <code>modules/htmx</code>,
986
+ again for the base package's own <code>createGrid</code>. This module re-exports
987
+ <code>createGrid</code>, <code>autoInit</code>, <code>hydrateTable</code>,
988
+ <code>readTable</code>, <code>serialiseState</code> and <code>restoreState</code>
989
+ precisely so a page never has to choose between the two — it is the complete package
990
+ for anything touching htmx, not an add-on alongside the base one.</p>
991
+ <p><strong>Surviving a swap.</strong> <code>autoInit(root)</code> builds a grid on
992
+ every <code>[data-lattice-grid]</code> element under <code>root</code> it has not
993
+ already built one for &mdash; idempotent, so calling it again after a swap only picks
994
+ up what is new. A sibling <code>&lt;script type="application/json"
995
+ data-lattice-config&gt;</code> supplies columns and options; without one, a
996
+ <code>&lt;table&gt;</code> element is hydrated instead, reading its header row for
997
+ columns and its body rows for data, then replacing itself with the grid. Once
998
+ imported, this module listens for htmx's own <code>htmx:beforeCleanupElement</code>
999
+ and <code>htmx:load</code> and calls <code>grid.destroy()</code> / <code>autoInit</code>
1000
+ at the right moments automatically &mdash; a grid inside a swapped-out subtree is torn
1001
+ down before htmx detaches it; a grid inside newly-loaded content is built without
1002
+ re-scanning the whole page. A page with JavaScript disabled sees the plain
1003
+ <code>&lt;table&gt;</code>, still readable, since it is only ever replaced once the
1004
+ grid has actually mounted.</p>
1005
+ <p><strong>Finding a live grid from its element</strong> works everywhere in this
1006
+ library, not just through <code>autoInit</code>: <code>element.__lattice</code> holds
1007
+ the instance for any element <code>createGrid</code> was called on, and is cleared
1008
+ when the grid is destroyed.</p>
1009
+ </div>
1010
+
1011
+ <div class="example">
1012
+ <p class="example__label">Server-driven sort, filter and infinite scroll</p>
1013
+ <pre><code><span class="kw">import</span> { createGrid, driveServerMode, driveInfiniteScroll } <span class="kw">from</span> '@toclocoinc/lattice-grid/modules/htmx';
1014
+
1015
+ <span class="kw">const</span> grid = createGrid(host, { columns, rows: [], rowKey: 'id' });
1016
+ <span class="kw">const</span> COLUMNS = columns.map(c =&gt; ({ field: c.field }));
1017
+
1018
+ <span class="cmt">// Replaces the view outright &mdash; fires whenever sort or filter changes.</span>
1019
+ driveServerMode(grid, document.getElementById('query-trigger'), { columns: COLUMNS });
1020
+
1021
+ <span class="cmt">// Appends the next chunk &mdash; fires as the grid's own visible rows near the end.</span>
1022
+ driveInfiniteScroll(grid, document.getElementById('sentinel'), { columns: COLUMNS });</code></pre>
1023
+ <pre><code>&lt;div id="query-trigger" hx-get="/rows" hx-trigger="lattice:query-changed" hx-swap="none" hidden&gt;&lt;/div&gt;
1024
+ &lt;div id="sentinel" hx-get="/rows" hx-trigger="revealed, lattice:scroll-near-end" hx-swap="none" hidden&gt;&lt;/div&gt;</code></pre>
1025
+ </div>
1026
+
1027
+ <div class="why">
1028
+ <p><strong>Two triggers, two elements, deliberately.</strong> A sort or filter
1029
+ change and a scroll asking for more rows are different operations &mdash; one
1030
+ replaces every loaded row, the other appends to them &mdash; and there is no way to
1031
+ tell the two apart once a response has landed if they share a trigger. Each function
1032
+ configures the request (<code>offset</code>, <code>limit</code>, <code>sort</code>,
1033
+ <code>filters</code> &mdash; a small, stable convention any server-side language can
1034
+ read with a JSON parser and a slice) and reads the response back into the grid
1035
+ itself, so <code>hx-swap="none"</code> is required on both: htmx sends the request
1036
+ and nothing else, since a grid is not an HTML swap target.</p>
1037
+ <p><strong>The sentinel's own trigger names two events for a reason.</strong>
1038
+ <code>revealed</code> is htmx's own once-per-element mechanism and gets the very
1039
+ first chunk, firing the moment htmx has processed the element &mdash; reliable
1040
+ because it needs nothing from this module's own timing. Every chunk after that fires
1041
+ through <code>lattice:scroll-near-end</code>, which <code>driveInfiniteScroll</code>
1042
+ dispatches once the grid's own visible row window comes within
1043
+ <code>opts.threshold</code> rows (default 20) of what is loaded. That split matters
1044
+ for a fixed-height, virtualised grid specifically: nothing about it ever leaves the
1045
+ page's own viewport once first revealed &mdash; new rows land inside the grid's own
1046
+ scroll area, not the page's &mdash; so a page-scroll-only trigger would fire exactly
1047
+ once and then go silent. Reading the grid's own render position instead is what
1048
+ makes every later chunk fire on genuine scroll, not on the repaint a successful load
1049
+ causes by itself.</p>
1050
+ <p><strong>Out-of-band updates.</strong> <code>driveOobUpdates(grid, opts)</code>
1051
+ watches for htmx's own out-of-band swaps landing on an element carrying
1052
+ <code>data-lattice-row="&lt;key&gt;"</code>, reads the swapped fragment as that row's
1053
+ cells, and applies it to the grid in place &mdash; scroll position, selection and
1054
+ filter state are untouched, since nothing about the view is reloaded.</p>
1055
+ <p><strong>Browser history.</strong> On <code>htmx:beforeHistorySave</code>, every
1056
+ live grid's state (<code>serialiseState</code> &mdash; sort, filters, column order and
1057
+ widths, scroll position and selection, base64url-encoded and diffed against defaults
1058
+ so an untouched grid costs almost nothing) is written onto its element as
1059
+ <code>data-lattice-state</code>, which rides along in htmx's own history snapshot. On
1060
+ <code>htmx:historyRestore</code>, it is read back and applied &mdash; browser back
1061
+ returns a visitor to the sort, filter and scroll position they had, not a blank
1062
+ slate. A cache miss (the page was re-fetched from the server) restores nothing, since
1063
+ a fresh response is already the truth.</p>
1064
+ <p><strong>A failed request leaves the grid alone.</strong> <code>driveServerMode</code>
1065
+ and <code>driveInfiniteScroll</code> both listen for
1066
+ <code>htmx:responseError</code>/<code>htmx:sendError</code>; the grid's rows are
1067
+ never touched by a failed request, and a recoverable message is shown through
1068
+ <code>grid.overlay</code> rather than the grid going blank.</p>
1069
+ </div>
1070
+
1071
+ <div class="why">
1072
+ <p><strong>What this does not do.</strong> It does not call <code>fetch</code> or
1073
+ <code>htmx.ajax()</code> anywhere &mdash; htmx owns every request end to end; this
1074
+ only supplies the moment and the parameters, and reads the response back in. It does
1075
+ not import htmx: every htmx-specific call goes through <code>globalThis.htmx</code>,
1076
+ read at call time, so loading this module never requires htmx to already be on the
1077
+ page, only to be present by the time a driven request actually fires. It ships as
1078
+ ESM and as a plain <code>&lt;script src&gt;</code> build with no bundler required,
1079
+ with zero runtime dependencies beyond the grid itself and, at call time, htmx &mdash;
1080
+ but because it references the grid's own internals directly rather than the copy
1081
+ already on the page, the bundle carries a full copy of the grid core alongside its
1082
+ own code, the same trade-off the web component and dhtmlx wrappers already make.</p>
1083
+ </div>
1084
+
841
1085
  <h2 id="concepts">How it works</h2>
842
1086
  <p class="lead-in">
843
1087
  Four ideas explain most of the API. If you read nothing else, read this section — the rest of
@@ -924,7 +1168,7 @@ off(); <span class="cmt">// every subscrip
924
1168
  </p>
925
1169
  <div class="example">
926
1170
  <p class="example__label">Which version am I running?</p>
927
- <pre><code>grid.getVersion(); <span class="cmt">// '1.6.1'</span>
1171
+ <pre><code>grid.getVersion(); <span class="cmt">// '1.7.0'</span>
928
1172
  LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid to hand</span></code></pre>
929
1173
  </div>
930
1174
  <p class="lead-in">
@@ -1981,6 +2225,14 @@ createGrid(right, { columns, rows,
1981
2225
  is announced rather than left silent.
1982
2226
  </p>
1983
2227
 
2228
+ <p class="lead-in">
2229
+ <strong>Picking up a row shows it, wherever the pointer goes.</strong> The row being dragged
2230
+ dims in its own grid, and a small label naming it follows the pointer for as long as the drag
2231
+ is held — over the gap between two grids, over one that is about to refuse the drop, anywhere
2232
+ the row's own dimming cannot reach. Both clear on release, and a handle press never also starts
2233
+ a range selection underneath it.
2234
+ </p>
2235
+
1984
2236
  <h2 id="column-tags">Column tags</h2>
1985
2237
  <p class="lead-in">
1986
2238
  Tag columns, then let a user show only the ones carrying a chosen tag. Sixty columns of
@@ -4466,11 +4718,33 @@ grid.on('view:default', e =&gt; api.patch(`/views/${e.view.id}`, { isDefault: tr
4466
4718
  </div>
4467
4719
  <p class="lead-in">
4468
4720
  Each event carries the one view that moved, so you send a single record rather than diffing
4469
- two lists. For a synchronous mirror <code>localStorage</code> use
4470
- <code>views.storage</code> instead. Since the grid does not track whether your write landed,
4471
- catch the failure and call <code>grid.views.reload()</code>.
4721
+ two lists. Since the grid does not track whether your write landed, catch the failure and
4722
+ call <code>grid.views.reload()</code>.
4472
4723
  </p>
4473
4724
 
4725
+ <div class="example">
4726
+ <p class="example__label">With no backend at all</p>
4727
+ <pre><code>createGrid(el, {
4728
+ views: { local: <span class="kw">true</span>, allowSave: <span class="kw">true</span> },
4729
+ });</code></pre>
4730
+ </div>
4731
+
4732
+ <div class="why">
4733
+ <p><strong>The other half of the same seam.</strong> <code>views.storage</code> above is where
4734
+ a developer plugs in their own backend — a real server, reached over the network. Not every
4735
+ grid has one to plug in, and a picker offering "Save" that quietly does nothing until a backend
4736
+ exists is worse than not offering it. <code>views.local: true</code> is the no-backend answer:
4737
+ saved views live in this browser's own <code>localStorage</code>, under a default key shared by
4738
+ every grid on the origin unless you pass one of your own —
4739
+ <code>views: { local: { key: 'orders-grid-views' } }</code> — to keep two grids' views apart.
4740
+ Given alongside an explicit <code>storage</code>, the explicit adapter always wins and
4741
+ <code>local</code> is silently — well, not silently: it warns once — ignored, so a page cannot
4742
+ end up writing to both without meaning to. The adapter itself is exported as
4743
+ <code>createLocalViewStorage(opts)</code>, for anyone who wants it directly — a custom key
4744
+ without the shorthand, or a different <code>Storage</code>-shaped backing such as
4745
+ <code>sessionStorage</code> for views scoped to one tab rather than persisted across visits.</p>
4746
+ </div>
4747
+
4474
4748
  <h2 id="history-guide">Undo</h2>
4475
4749
  <p class="lead-in">
4476
4750
  Undo covers the whole grid, not only edits. Sorts, filters, column moves, grouping, an applied
@@ -4850,7 +5124,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'trial'<
4850
5124
 
4851
5125
  <footer>
4852
5126
  <p>
4853
- Lattice Grid 1.6.1 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
5127
+ Lattice Grid 1.7.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
4854
5128
  Written against the shipped source. Where this guide and the code disagree, the code wins —
4855
5129
  please <a href="https://www.latticegrid.dev">tell us</a>.
4856
5130
  </p>
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.6.1 — type declarations
2
+ * Lattice Grid 1.7.0 — type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */