@toclocoinc/lattice-grid 1.63.3 → 1.64.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 (163) hide show
  1. package/README.md +2 -1
  2. package/docs/API.html +285 -24
  3. package/docs/CHART-CODES.md +24 -0
  4. package/docs/api-detail.html +80 -5
  5. package/lattice-grid.d.ts +93 -1
  6. package/lattice-grid.esm.min.js +154 -95
  7. package/lattice-grid.min.cjs +154 -95
  8. package/lattice-grid.min.js +154 -95
  9. package/modules/ai.d.ts +1 -1
  10. package/modules/ai.esm.min.js +3 -6
  11. package/modules/ai.min.cjs +3 -6
  12. package/modules/ai.min.js +3 -6
  13. package/modules/angular.d.ts +1 -1
  14. package/modules/angular.esm.min.js +1 -4
  15. package/modules/angular.min.cjs +1 -4
  16. package/modules/angular.min.js +1 -4
  17. package/modules/chart-alluvial.d.ts +1 -1
  18. package/modules/chart-alluvial.esm.min.js +1 -1
  19. package/modules/chart-alluvial.min.cjs +1 -1
  20. package/modules/chart-alluvial.min.js +1 -1
  21. package/modules/chart-arc.d.ts +1 -1
  22. package/modules/chart-arc.esm.min.js +1 -1
  23. package/modules/chart-arc.min.cjs +1 -1
  24. package/modules/chart-arc.min.js +1 -1
  25. package/modules/chart-bubblemap.d.ts +1 -1
  26. package/modules/chart-bubblemap.esm.min.js +1 -1
  27. package/modules/chart-bubblemap.min.cjs +1 -1
  28. package/modules/chart-bubblemap.min.js +1 -1
  29. package/modules/chart-bump.d.ts +1 -1
  30. package/modules/chart-bump.esm.min.js +1 -1
  31. package/modules/chart-bump.min.cjs +1 -1
  32. package/modules/chart-bump.min.js +1 -1
  33. package/modules/chart-calendar.d.ts +1 -1
  34. package/modules/chart-calendar.esm.min.js +1 -1
  35. package/modules/chart-calendar.min.cjs +1 -1
  36. package/modules/chart-calendar.min.js +1 -1
  37. package/modules/chart-decomposition.d.ts +1 -1
  38. package/modules/chart-decomposition.esm.min.js +1 -1
  39. package/modules/chart-decomposition.min.cjs +1 -1
  40. package/modules/chart-decomposition.min.js +1 -1
  41. package/modules/chart-diverging.d.ts +1 -1
  42. package/modules/chart-diverging.esm.min.js +1 -1
  43. package/modules/chart-diverging.min.cjs +1 -1
  44. package/modules/chart-diverging.min.js +1 -1
  45. package/modules/chart-dumbbell.d.ts +1 -1
  46. package/modules/chart-dumbbell.esm.min.js +1 -1
  47. package/modules/chart-dumbbell.min.cjs +1 -1
  48. package/modules/chart-dumbbell.min.js +1 -1
  49. package/modules/chart-fan.d.ts +1 -1
  50. package/modules/chart-fan.esm.min.js +1 -1
  51. package/modules/chart-fan.min.cjs +1 -1
  52. package/modules/chart-fan.min.js +1 -1
  53. package/modules/chart-hexbin.d.ts +1 -1
  54. package/modules/chart-hexbin.esm.min.js +1 -1
  55. package/modules/chart-hexbin.min.cjs +1 -1
  56. package/modules/chart-hexbin.min.js +1 -1
  57. package/modules/chart-hexmap.d.ts +1 -1
  58. package/modules/chart-hexmap.esm.min.js +1 -1
  59. package/modules/chart-hexmap.min.cjs +1 -1
  60. package/modules/chart-hexmap.min.js +1 -1
  61. package/modules/chart-icicle.d.ts +1 -1
  62. package/modules/chart-icicle.esm.min.js +1 -1
  63. package/modules/chart-icicle.min.cjs +1 -1
  64. package/modules/chart-icicle.min.js +1 -1
  65. package/modules/chart-markermap.d.ts +29 -0
  66. package/modules/chart-markermap.esm.min.js +313 -0
  67. package/modules/chart-markermap.min.cjs +317 -0
  68. package/modules/chart-markermap.min.js +317 -0
  69. package/modules/chart-parallel.d.ts +1 -1
  70. package/modules/chart-parallel.esm.min.js +1 -1
  71. package/modules/chart-parallel.min.cjs +1 -1
  72. package/modules/chart-parallel.min.js +1 -1
  73. package/modules/chart-ridgeline.d.ts +1 -1
  74. package/modules/chart-ridgeline.esm.min.js +1 -1
  75. package/modules/chart-ridgeline.min.cjs +1 -1
  76. package/modules/chart-ridgeline.min.js +1 -1
  77. package/modules/chart-roc.d.ts +1 -1
  78. package/modules/chart-roc.esm.min.js +1 -1
  79. package/modules/chart-roc.min.cjs +1 -1
  80. package/modules/chart-roc.min.js +1 -1
  81. package/modules/chart-slope.d.ts +1 -1
  82. package/modules/chart-slope.esm.min.js +1 -1
  83. package/modules/chart-slope.min.cjs +1 -1
  84. package/modules/chart-slope.min.js +1 -1
  85. package/modules/chart-splom.d.ts +1 -1
  86. package/modules/chart-splom.esm.min.js +1 -1
  87. package/modules/chart-splom.min.cjs +1 -1
  88. package/modules/chart-splom.min.js +1 -1
  89. package/modules/chart-waffle.d.ts +1 -1
  90. package/modules/chart-waffle.esm.min.js +1 -1
  91. package/modules/chart-waffle.min.cjs +1 -1
  92. package/modules/chart-waffle.min.js +1 -1
  93. package/modules/charts.d.ts +1 -1
  94. package/modules/charts.esm.min.js +395 -33
  95. package/modules/charts.min.cjs +395 -33
  96. package/modules/charts.min.js +395 -33
  97. package/modules/data-router.d.ts +1 -1
  98. package/modules/data-router.esm.min.js +3 -6
  99. package/modules/data-router.min.cjs +3 -6
  100. package/modules/data-router.min.js +3 -6
  101. package/modules/devtools.d.ts +1 -1
  102. package/modules/devtools.esm.min.js +1 -4
  103. package/modules/devtools.min.cjs +1 -4
  104. package/modules/devtools.min.js +1 -4
  105. package/modules/dhtmlx-compat.d.ts +1 -1
  106. package/modules/dhtmlx-compat.esm.min.js +3 -6
  107. package/modules/dhtmlx-compat.min.cjs +3 -6
  108. package/modules/dhtmlx-compat.min.js +3 -6
  109. package/modules/gantt.d.ts +1 -1
  110. package/modules/gantt.esm.min.js +3 -6
  111. package/modules/gantt.min.cjs +3 -6
  112. package/modules/gantt.min.js +3 -6
  113. package/modules/geo-europe-nuts.d.ts +1 -1
  114. package/modules/geo-europe-nuts.esm.min.js +1 -1
  115. package/modules/geo-uk.d.ts +1 -1
  116. package/modules/geo-uk.esm.min.js +1 -1
  117. package/modules/geo-us-states.d.ts +1 -1
  118. package/modules/geo-us-states.esm.min.js +1 -1
  119. package/modules/geo-world-110m.d.ts +1 -1
  120. package/modules/geo-world-110m.esm.min.js +1 -1
  121. package/modules/geo-world-50m.d.ts +1 -1
  122. package/modules/geo-world-50m.esm.min.js +1 -1
  123. package/modules/htmx.d.ts +1 -1
  124. package/modules/htmx.esm.min.js +154 -95
  125. package/modules/htmx.min.cjs +154 -95
  126. package/modules/htmx.min.js +154 -95
  127. package/modules/kanban.d.ts +1 -1
  128. package/modules/kanban.esm.min.js +3 -6
  129. package/modules/kanban.min.cjs +3 -6
  130. package/modules/kanban.min.js +3 -6
  131. package/modules/kpi.d.ts +56 -3
  132. package/modules/kpi.esm.min.js +202 -10
  133. package/modules/kpi.min.cjs +202 -10
  134. package/modules/kpi.min.js +202 -10
  135. package/modules/layout.d.ts +1 -1
  136. package/modules/layout.esm.min.js +3 -6
  137. package/modules/layout.min.cjs +3 -6
  138. package/modules/layout.min.js +3 -6
  139. package/modules/mock-socket.d.ts +1 -1
  140. package/modules/mock-socket.esm.min.js +1 -4
  141. package/modules/mock-socket.min.cjs +1 -4
  142. package/modules/mock-socket.min.js +1 -4
  143. package/modules/react.d.ts +1 -1
  144. package/modules/react.esm.min.js +3 -6
  145. package/modules/react.min.cjs +3 -6
  146. package/modules/react.min.js +3 -6
  147. package/modules/svelte.d.ts +1 -1
  148. package/modules/svelte.esm.min.js +1 -4
  149. package/modules/svelte.min.cjs +1 -4
  150. package/modules/svelte.min.js +1 -4
  151. package/modules/tabs.d.ts +1 -1
  152. package/modules/tabs.esm.min.js +3 -6
  153. package/modules/tabs.min.cjs +3 -6
  154. package/modules/tabs.min.js +3 -6
  155. package/modules/vue.d.ts +1 -1
  156. package/modules/vue.esm.min.js +1 -4
  157. package/modules/vue.min.cjs +1 -4
  158. package/modules/vue.min.js +1 -4
  159. package/modules/webcomponent.d.ts +1 -1
  160. package/modules/webcomponent.esm.min.js +154 -95
  161. package/modules/webcomponent.min.cjs +154 -95
  162. package/modules/webcomponent.min.js +154 -95
  163. 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.63.3</p>
440
+ <p class="rail__sub">Developer guide · v1.64.0</p>
441
441
  <nav>
442
442
  <div class="rail__group">
443
443
  <span class="rail__label">Start here</span>
@@ -554,7 +554,7 @@
554
554
  <a href="API.html">reference tables</a> are the shorter version for when you already know.
555
555
  </p>
556
556
  <p class="chips">
557
- <span class="chip">Version 1.63.3</span>
557
+ <span class="chip">Version 1.64.0</span>
558
558
  <span class="chip">Zero dependencies</span>
559
559
  <span class="chip">No build step</span>
560
560
  </p>
@@ -1369,7 +1369,7 @@ off(); <span class="cmt">// every subscrip
1369
1369
  </p>
1370
1370
  <div class="example">
1371
1371
  <p class="example__label">Which version am I running?</p>
1372
- <pre><code>grid.getVersion(); <span class="cmt">// '1.63.3'</span>
1372
+ <pre><code>grid.getVersion(); <span class="cmt">// '1.64.0'</span>
1373
1373
  LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid to hand</span></code></pre>
1374
1374
  </div>
1375
1375
  <p class="lead-in">
@@ -7896,6 +7896,81 @@ grid.diff.before('CIR-100042', 'capacity');</code></pre>
7896
7896
  highlight cannot serve both.
7897
7897
  </p>
7898
7898
 
7899
+ <h3 id="network-map-guide">A network map: icon nodes, links coloured by their value</h3>
7900
+ <p class="lead-in">
7901
+ A <code>network</code> chart draws the rows as a graph &mdash; two columns name the endpoints
7902
+ and a third carries the value on the link. Until 1.64 every node was a plain circle, every
7903
+ edge the same grey, and the layout went wherever the simulation put it, so the picture a
7904
+ network team actually draws &mdash; core on top, regions below, an icon per device, a colour
7905
+ per circuit &mdash; could not be expressed. Three options change that, and every one of them
7906
+ carries a fact that only the host has.
7907
+ </p>
7908
+ <div class="example">
7909
+ <p class="example__label">One <code>nodes</code> list, one rule set, one chart config</p>
7910
+ <pre><code>createGrid(el, {
7911
+ columns: [{ field: 'from' }, { field: 'to' }, { field: 'load', type: 'number' }],
7912
+ rows, <span class="cmt">// one row per circuit</span>
7913
+ selection: 'multiple',
7914
+ <span class="cmt">// The rules live on the column. The cells and the links read the same ones.</span>
7915
+ formatting: {
7916
+ load: [
7917
+ { label: 'Healthy', when: { op: 'lt', value: 40 }, style: { background: '#107c41' } },
7918
+ { label: 'Busy', when: { op: 'lt', value: 80 }, style: { background: '#f0b400' } },
7919
+ { label: 'Saturated', when: { op: 'gte', value: 80 }, style: { background: '#a4262c' } },
7920
+ ],
7921
+ },
7922
+ });
7923
+
7924
+ createChart({
7925
+ grid, container: '#topology', type: 'network',
7926
+ source: 'from', target: 'to', y: { col: 'load', fn: 'sum' },
7927
+ selection: true,
7928
+ nodes: [
7929
+ <span class="cmt">// x/y are fractions of the plot: both given pins the node there.</span>
7930
+ { id: 'core-1', label: 'Core', icon: 'router', x: 0.3, y: 0.15 },
7931
+ { id: 'core-2', label: 'Core', icon: 'router', x: 0.7, y: 0.15 },
7932
+ { id: 'emea', label: 'EMEA', icon: 'hub', x: 0.2, y: 0.8 },
7933
+ { id: 'amer', label: 'AMER', icon: 'hub', x: 0.5, y: 0.8 },
7934
+ { id: 'apac', label: 'APAC', icon: 'hub', x: 0.8, y: 0.8 },
7935
+ ],
7936
+ });</code></pre>
7937
+ </div>
7938
+ <div class="why">
7939
+ <p><strong>The icons come from the grid, not from the chart.</strong> <code>icon</code> is a
7940
+ name in the grid&rsquo;s own sprite registry &mdash; a built-in, or one you registered &mdash;
7941
+ and the chart reads it through <code>grid.icons</code>, the grid it is bound to. That is
7942
+ deliberate and it is the only route that works: the charts module ships as its own bundle, so
7943
+ an <code>import</code> of the registry there would hand the chart a <em>second</em>, empty
7944
+ copy, and a glyph you registered would be invisible to it. One registry, reached through the
7945
+ one object both sides already share. Unknown names warn once and draw a plain disc rather
7946
+ than nothing.</p>
7947
+ <p><strong>Pinning steers the layout without replacing it.</strong> A node with both
7948
+ <code>x</code> and <code>y</code> is a fixed body: it still pushes its neighbours apart and
7949
+ still pulls on its links, and the integration step skips it. Everything unpinned settles
7950
+ around it by the same deterministic relaxation as before &mdash; and pinning one node does
7951
+ not reshuffle the others, because the layout&rsquo;s seeding draws for every node whether it
7952
+ is pinned or not, precisely so that it cannot. Half a position is not a position: a node with
7953
+ only <code>x</code> is laid out.</p>
7954
+ <p><strong>Parallel links are not summed.</strong> Three rows between the same pair are three
7955
+ lines, offset 4&nbsp;px apart, symmetric about the pair&rsquo;s own line, in row order. One
7956
+ line carrying their total would be a number nothing measured &mdash; three circuits at 40% do
7957
+ not make one at 120% &mdash; and the pointer picks out the line you are over rather than the
7958
+ pair, so each one&rsquo;s own value is readable. Links are undirected: no arrowheads, and
7959
+ <code>A,B</code> is the same pair as <code>B,A</code>.</p>
7960
+ <p><strong>There is no chart-level threshold option, on purpose.</strong> The colour comes
7961
+ from <code>grid.formatting.styleFor(col, value)</code> &mdash; <code>background</code>, then
7962
+ <code>backgroundColor</code>, then <code>color</code>; a gradient is not a colour and is not
7963
+ read. A second place to say &ldquo;red above 80&rdquo; is a second place for the chart and the
7964
+ cell to disagree. The legend lists only the rules that fired, with their own labels and
7965
+ swatches, and changing a rule recolours the links on the next frame without the layout
7966
+ re-running, so nothing moves.</p>
7967
+ <p><strong>Clicking acts on rows, because a link is a row.</strong> With
7968
+ <code>selection: true</code>, clicking a link selects its row and clicking a node selects
7969
+ every row it is an end of; the grid&rsquo;s selection then lights those marks and dims the
7970
+ rest. The types whose marks are <em>aggregates</em> are deliberately untouched: selecting the
7971
+ forty rows behind a bar is not what a click on a bar means.</p>
7972
+ </div>
7973
+
7899
7974
  <h3 id="removed-rows">Showing what was deleted</h3>
7900
7975
  <div class="example">
7901
7976
  <p class="example__label">A deletion is a change too</p>
@@ -8078,7 +8153,7 @@ grid.import.apply(preview);</code></pre>
8078
8153
  <tr><td class="name">createKPI</td><td class="desc">A KPI / stat-tile view (module <code>kpi</code>) of a dataset as a panel of aggregate tiles — each tile a <code>sum</code>, <code>avg</code>, <code>min</code>, <code>max</code>, <code>count</code>, <code>countDistinct</code> or a <code>custom</code> reducer over the routed rows, with an optional <code>filter</code> predicate, number <code>format</code> (<code>number</code>/<code>currency</code>/<code>percent</code>/<code>compact</code>), a <code>baseline</code> for a delta, semantic threshold bands (<code>thresholds</code> with two cut points and a direction, or an explicit <code>bands</code> list, giving a <code>good</code>/<code>warn</code>/<code>critical</code> status kept separate from any accent), and an optional <code>sparkline</code> series. It is a dataset viewer like any other: it consumes data through the same keyed-diff <code>rows.apply({ add, update, remove })</code> contract a grid exposes, so a Data Router can <code>attach(value, kpi)</code> and drive a KPI panel beside a grid, a kanban and a chart off one feed. Updates are incremental — a delta adjusts each tile's running accumulator by only the rows it carries (an add contributes, a remove reverses, an update reverses-then-contributes), the two bounded exceptions being a <code>min</code>/<code>max</code> whose current extreme is removed (a rescan of that tile's own value multiset) and a <code>custom</code> reducer (recomputed over the filtered store, an arbitrary function having no inverse). Each tile is a labelled <code>&lt;figure&gt;</code>, focusable and keyboard-activatable, its value announced, the sparkline respecting <code>prefers-reduced-motion</code>; a <code>tile:click</code> event (also from the keyboard) lets a host drill down or filter a routed grid. Pass <code>null</code> as the element for a headless panel that computes the same tile model without a DOM. Not a dashboard layout engine (that is the parked dashboard generator) and no charting beyond the minimal sparkline (that is the charts module).</td></tr>
8079
8154
  <tr><td class="name">createAI</td><td class="desc">Create an AI narrative / insights controller (module <code>ai</code>) over a live grid. It produces a short, plain-language narrative of the grid's <em>computed</em> figures — a per-KPI / per-chart / per-column <code>explain</code>, or an <code>insights</code> panel over the current filtered view. The grid makes no AI call of its own: <code>createAI</code> imports no provider SDK, holds no key, and makes no network request; it calls one host callback, <code>ask({ system, messages, prompt, tools?, schema?, signal })</code> — your model, your key, your privacy decision — the same philosophy as the data adapters. Two grounding paths feed one guard: where the provider offers tool-use the model is given a curated read-only tool set (<code>getSchema</code>, <code>getProfile</code>, <code>getStatistics</code>, <code>getForecast</code>, <code>runQuery</code>) and the grid's own engine computes what it asks for; otherwise the module builds a facts packet from <code>grid.statistics</code>/the profile/forecasts/view counts and passes it in the prompt. Every figure in the narrative is reconciled against the values the engine produced this render — an ungrounded number is stripped before display (the number-reconciliation guard), so a hallucinated figure never reaches the user. The prompt is constrained to narrate-only; the layer is read-only and never mutates data. <code>redact</code> (a column id, a list, or a predicate) and <code>maxRows</code> bound what the module hands <code>ask()</code>, and the module sends nothing itself; an <code>ask()</code> error surfaces a friendly message and the grid stays fully usable, AI being additive rather than load-bearing. Complementary to <code>grid.ai</code> (the intent/plan skill layer): pass no <code>ask</code> and it adopts the grid's configured <code>ai.ask</code>, running the facts-packet path over it. Pass a headless grid for a DOM-free narrative; <code>facts(target)</code> returns the exact grounded packet without calling <code>ask()</code>. UMD global <code>LatticeGridAI</code>.</td></tr>
8080
8155
  <tr><td class="name">createTabs</td><td class="desc">Create a tabbed grid (module <code>tabs</code>): a <code>role="tablist"</code> strip above a stack of <code>role="tabpanel"</code> regions, each hosting its own, independently-configured <code>createGrid</code> instance — "configure each tab as per a normal grid" rather than one grid whose state is swapped (<code>ColumnModel#applyState</code> only repositions/hides/resizes existing columns by id; it carries no field, type or row data, so a state-swap only works when every tab shares one schema). <code>createGrid</code> is injected (<code>createTabs(el, { createGrid, tabs })</code>), the same pattern the React/Vue/Svelte adapters use, so the module imports no engine code and adds nothing to a page that does not load it. A tab that names <code>from: '&lt;tabId&gt;'</code> gets a <code>source: { mode: 'derived', from: &lt;the parent tab’s live grid&gt;, where, group, join, … }</code> wired for it automatically — reusing the shipped derived-source mechanism rather than a new config-inheritance one — and activating a derived tab materialises its whole ancestor chain first; a cyclic <code>from</code> graph is refused (naming the exact cycle) when <code>createTabs</code> is called, not at first click. A tab’s grid mounts on first activation and then stays alive, hidden, so its scroll/selection/filters/sort/grouping/expansion — and an open cell/row editor, left exactly as it was, uncommitted and undiscarded — survive a switch natively; <code>destroy()</code> tears every mounted tab down. The strip is a real tablist with <code>aria-selected</code>, a roving <code>tabindex</code>, and manual-activation keyboard handling (arrows/Home/End move focus, Enter/Space or a click activates). Events: <code>tab:changed</code>, a cancellable <code>beforeTabChange</code> paired with <code>tabChange:cancelled</code>. UMD global <code>LatticeGridTabs</code>.</td></tr>
8081
- <tr><td class="name">createLayout</td><td class="desc">Create a reconfigurable dashboard layout (module <code>layout</code>): a cell grid inside an element, and a set of windows on it that a user can move, resize and close by drag <em>or</em> by keyboard &mdash; the surface a customer would otherwise reach for GridStack to get. It is <strong>payload-agnostic</strong>: a window body is a <code>div</code> with an <code>id</code> that the module creates, sizes and never reads, so it imports no engine code at all (not even <code>createGrid</code>) and its own code is 12,890 bytes gzipped, measured against a 62,206-byte fixed bundle floor. <code>columns</code>/<code>rows</code> divide the element; <code>overflowX</code> and <code>overflowY</code> are <em>independent</em> axes, each <code>'static'</code> (tracks divide the container with <code>minmax(0, 1fr)</code>) or <code>'scroll'</code> (tracks take a fixed <code>columnWidth</code>/<code>rowHeight</code> and the canvas extends past the viewport, so a column keeps the size it asked for &mdash; measured: shrinking a 600px host to 300px leaves a 200px column at 200px). Spacing takes a real CSS length: a number of pixels, or <code>'200px'</code>, <code>'25%'</code>, <code>'1fr'</code>, <code>'2rem'</code>; anything else is refused by name and replaced by the default, because the value reaches an inline style. Windows are placed by 1-based <code>xPos</code>/<code>yPos</code>/<code>xSize</code>/<code>ySize</code>, or auto-placed in the first free cell; <code>chrome</code> defaults on, and <code>closable</code>/<code>movable</code>/<code>resizable</code> all default <em>off</em>, so a fixed dashboard is fixed without opting out. <code>compact: 'vertical'</code> pushes displaced windows down then pulls them up (<code>window:moved</code> carries both <code>to</code> and <code>landed</code>); <code>'none'</code> keeps every window where it is put. <code>closable</code>/<code>movable</code>/<code>resizable</code> each also take a <em>layout-level</em> default of the same name, which a window's own boolean overrides, and <code>setInteractive(true|false|{movable, resizable, closable})</code> changes that default at runtime &mdash; the &ldquo;Edit layout&rdquo; button &mdash; without destroying the layout or any payload in it, with <code>getInteractive()</code> reading it back. Locking always wins and unlocking never overrides an opt-out: <code>setInteractive(false)</code> pins a window that declared <code>movable: true</code>, while <code>setInteractive(true)</code> leaves a window that declared <code>movable: false</code> pinned. The config key and the method are deliberately different: <code>movable: false</code> in the config states the <em>default</em> for windows that declare nothing and takes nothing away from one that opted in, whereas <code>setInteractive(false)</code> is an <em>active lock</em>. <code>getInteractive()</code> is three-valued &mdash; <code>undefined</code> for unset, <code>true</code>, or <code>false</code> for a lock &mdash; and a key carrying <code>undefined</code> is treated as absent, so <code>setInteractive(getInteractive())</code> is a no-op in every state. It moves both halves of the enforcement, the rendered handles <em>and</em> the pointer and keyboard gesture checks, and fires no event because a mode is not an arrangement &mdash; <code>getLayout()</code> neither carries it nor restores it. A locked layout is not a read-only dashboard: what is inside a window is configured with that payload's own settings. <code>maximise(id)</code>, <code>minimise(id)</code> and <code>restore(id)</code> are the display modes, with <code>maximised()</code> and <code>minimised()</code> reading them back: maximise fills the <em>layout host</em> rather than the browser window (no <code>position: fixed</code>, whose containing block is the nearest ancestor with a <code>transform</code> or a <code>contain</code>; no reparenting; nothing that can disturb the page around the dashboard), hides the other windows, runs <strong>no compaction at all</strong> and keeps the payload container as the very same DOM node &mdash; and <kbd>Escape</kbd> restores it from anywhere inside the layout. <code>minimise</code> draws a window as a single row and hides its payload while its chrome stays to carry the way back, so the windows below pull up on screen; in the arrangement nothing moves at all, because the collapse is a projection of the dashboard rather than a change to it, so restoring gives back exactly the arrangement that was there in <em>any</em> order and with any number of other windows still collapsed. A <code>chrome: false</code> window is refused by name. Both controls are opt-in per window (<code>maximisable</code>, <code>minimisable</code>) with a layout-level default of the same name, and <code>setInteractive()</code> deliberately does not touch either: a mode is not an arrangement, so neither appears in <code>getLayout()</code>, which reports the underlying placement in both states. Keyboard parity with the drag: a focusable handle per window running the kanban board's grab/move/drop/cancel model, with a polite live region announcing grabbed, every tentative position, dropped, cancelled and reverted. It owns exactly <strong>one</strong> <code>ResizeObserver</code> for the whole layout, over two targets, and tells payloads their new content box through <code>window:resized</code> &mdash; it never calls into a payload, because it cannot know what one is. Events: <code>window:moved</code>, <code>window:resized</code>, <code>window:closed</code>, <code>layout:changed</code>, the cancellable <code>beforeWindowMove</code>/<code>beforeWindowResize</code>/<code>beforeWindowClose</code> and their <code>*:cancelled</code> pairs; drag progress is not emitted per frame. <code>getLayout()</code>/<code>setLayout()</code> round-trip the arrangement as plain JSON, and <code>getState()</code>/<code>setState()</code> are the versioned pair. Closing a window does <strong>not</strong> destroy its payload &mdash; the container is handed back on <code>window:closed</code> and the host owns that lifecycle. UMD global <code>LatticeGridLayout</code>.</td></tr>
8156
+ <tr><td class="name">createLayout</td><td class="desc">Create a reconfigurable dashboard layout (module <code>layout</code>): a cell grid inside an element, and a set of windows on it that a user can move, resize and close by drag <em>or</em> by keyboard &mdash; the surface a customer would otherwise reach for GridStack to get. It is <strong>payload-agnostic</strong>: a window body is a <code>div</code> with an <code>id</code> that the module creates, sizes and never reads, so it imports no engine code at all (not even <code>createGrid</code>) and the whole module is about 17,500 bytes gzipped on the built bundle, of which roughly 2KB is the shared module runtime. <code>columns</code>/<code>rows</code> divide the element; <code>overflowX</code> and <code>overflowY</code> are <em>independent</em> axes, each <code>'static'</code> (tracks divide the container with <code>minmax(0, 1fr)</code>) or <code>'scroll'</code> (tracks take a fixed <code>columnWidth</code>/<code>rowHeight</code> and the canvas extends past the viewport, so a column keeps the size it asked for &mdash; measured: shrinking a 600px host to 300px leaves a 200px column at 200px). Spacing takes a real CSS length: a number of pixels, or <code>'200px'</code>, <code>'25%'</code>, <code>'1fr'</code>, <code>'2rem'</code>; anything else is refused by name and replaced by the default, because the value reaches an inline style. Windows are placed by 1-based <code>xPos</code>/<code>yPos</code>/<code>xSize</code>/<code>ySize</code>, or auto-placed in the first free cell; <code>chrome</code> defaults on, and <code>closable</code>/<code>movable</code>/<code>resizable</code> all default <em>off</em>, so a fixed dashboard is fixed without opting out. <code>compact: 'vertical'</code> pushes displaced windows down then pulls them up (<code>window:moved</code> carries both <code>to</code> and <code>landed</code>); <code>'none'</code> keeps every window where it is put. <code>closable</code>/<code>movable</code>/<code>resizable</code> each also take a <em>layout-level</em> default of the same name, which a window's own boolean overrides, and <code>setInteractive(true|false|{movable, resizable, closable})</code> changes that default at runtime &mdash; the &ldquo;Edit layout&rdquo; button &mdash; without destroying the layout or any payload in it, with <code>getInteractive()</code> reading it back. Locking always wins and unlocking never overrides an opt-out: <code>setInteractive(false)</code> pins a window that declared <code>movable: true</code>, while <code>setInteractive(true)</code> leaves a window that declared <code>movable: false</code> pinned. The config key and the method are deliberately different: <code>movable: false</code> in the config states the <em>default</em> for windows that declare nothing and takes nothing away from one that opted in, whereas <code>setInteractive(false)</code> is an <em>active lock</em>. <code>getInteractive()</code> is three-valued &mdash; <code>undefined</code> for unset, <code>true</code>, or <code>false</code> for a lock &mdash; and a key carrying <code>undefined</code> is treated as absent, so <code>setInteractive(getInteractive())</code> is a no-op in every state. It moves both halves of the enforcement, the rendered handles <em>and</em> the pointer and keyboard gesture checks, and fires no event because a mode is not an arrangement &mdash; <code>getLayout()</code> neither carries it nor restores it. A locked layout is not a read-only dashboard: what is inside a window is configured with that payload's own settings. <code>maximise(id)</code>, <code>minimise(id)</code> and <code>restore(id)</code> are the display modes, with <code>maximised()</code> and <code>minimised()</code> reading them back: maximise fills the <em>layout host</em> rather than the browser window (no <code>position: fixed</code>, whose containing block is the nearest ancestor with a <code>transform</code> or a <code>contain</code>; no reparenting; nothing that can disturb the page around the dashboard), hides the other windows, runs <strong>no compaction at all</strong> and keeps the payload container as the very same DOM node &mdash; and <kbd>Escape</kbd> restores it from anywhere inside the layout. <code>minimise</code> draws a window as a single row and hides its payload while its chrome stays to carry the way back, so the windows below pull up on screen; in the arrangement nothing moves at all, because the collapse is a projection of the dashboard rather than a change to it, so restoring gives back exactly the arrangement that was there in <em>any</em> order and with any number of other windows still collapsed. A <code>chrome: false</code> window is refused by name. Both controls are opt-in per window (<code>maximisable</code>, <code>minimisable</code>) with a layout-level default of the same name, and <code>setInteractive()</code> deliberately does not touch either: a mode is not an arrangement, so neither appears in <code>getLayout()</code>, which reports the underlying placement in both states. Keyboard parity with the drag: a focusable handle per window running the kanban board's grab/move/drop/cancel model, with a polite live region announcing grabbed, every tentative position, dropped, cancelled and reverted. It owns exactly <strong>one</strong> <code>ResizeObserver</code> for the whole layout, over two targets, and tells payloads their new content box through <code>window:resized</code> &mdash; it never calls into a payload, because it cannot know what one is. Events: <code>window:moved</code>, <code>window:resized</code>, <code>window:closed</code>, <code>layout:changed</code>, the cancellable <code>beforeWindowMove</code>/<code>beforeWindowResize</code>/<code>beforeWindowClose</code> and their <code>*:cancelled</code> pairs; drag progress is not emitted per frame. <code>getLayout()</code>/<code>setLayout()</code> round-trip the arrangement as plain JSON, and <code>getState()</code>/<code>setState()</code> are the versioned pair. Closing a window does <strong>not</strong> destroy its payload &mdash; the container is handed back on <code>window:closed</code> and the host owns that lifecycle. UMD global <code>LatticeGridLayout</code>.</td></tr>
8082
8157
  <tr><td class="name">createDevtools</td><td class="desc">Mount the devtools panel against a grid, including its accessibility checks.</td></tr>
8083
8158
  <tr><td class="name">createGantt</td><td class="desc">Create a project-planning Gantt controller (module <code>gantt</code>) over a task list and a dependency list. A CPM engine (<code>computeSchedule</code>) computes each task's early/late start and finish, its slack and the zero-float critical path, honouring the four link types (<code>LINK_TYPES</code>: FS/SS/FF/SF) with lag, and recomputes on every edit — emitting <code>schedule</code> or, on a dependency cycle or bad input, <code>error</code> (a code from <code>SCHEDULE_ERROR</code>). Milestones are zero-duration points; summary (WBS) tasks are derived from their children (earliest start, latest finish, weighted progress) rather than scheduled; <code>findViolations</code> flags a task placed earlier than its predecessors allow, and <code>toISODate</code> maps an engine day-number back to a calendar date.</td></tr>
8084
8159
  <tr><td class="name">createLatticeGridElement</td><td class="desc">Build the element class without registering it, for a custom registry.</td></tr>
@@ -9408,7 +9483,7 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'trial'<
9408
9483
 
9409
9484
  <footer>
9410
9485
  <p>
9411
- Lattice Grid 1.63.3 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
9486
+ Lattice Grid 1.64.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
9412
9487
  Written against the shipped source. Where this guide and the code disagree, the code wins,
9413
9488
  please <a href="https://www.latticegrid.dev">tell us</a>.
9414
9489
  </p>
package/lattice-grid.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Lattice Grid 1.63.3, type declarations
2
+ * Lattice Grid 1.64.0, type declarations
3
3
  * Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
4
4
  * https://latticegrid.dev
5
5
  */
@@ -5167,6 +5167,21 @@ export interface ColumnsApi {
5167
5167
  totals(ids: string | string[]): void;
5168
5168
  }
5169
5169
 
5170
+ /** One sprite: its view box, its path data, and how it is painted. */
5171
+ export interface IconGlyph {
5172
+ viewBox: string;
5173
+ paths: string[];
5174
+ paint: 'stroke' | 'fill';
5175
+ }
5176
+
5177
+ /** Read access to the grid's icon sprite set (see {@link Grid.icons}). */
5178
+ export interface IconRegistryApi {
5179
+ /** One glyph, as a copy, or null when the name is not registered. */
5180
+ get(name: string): IconGlyph | null;
5181
+ /** Every registered name, in registration order. */
5182
+ names(): string[];
5183
+ }
5184
+
5170
5185
  export interface RowFormApi {
5171
5186
  /** Open the form for a row. False when the form is not configured. */
5172
5187
  open(key: string): boolean;
@@ -5642,6 +5657,13 @@ export interface AnnotationMark {
5642
5657
  fontSize?: number;
5643
5658
  /** An optional backing colour drawn behind a `text` mark's label. */
5644
5659
  background?: string;
5660
+ /**
5661
+ * Which columns the mark belongs to: a pinned region holds still while the
5662
+ * grid scrolls sideways, the centre moves with it. Set from where a stroke
5663
+ * began; omitted (the centre) for every mark that is not over a pinned
5664
+ * column, so a mark saved before this existed reads unchanged.
5665
+ */
5666
+ region?: 'start' | 'centre' | 'end';
5645
5667
  }
5646
5668
 
5647
5669
  export interface AnnotationApi {
@@ -6619,6 +6641,17 @@ export interface Grid {
6619
6641
  /** The row form. Declines when `rowForm` is not configured. */
6620
6642
  readonly form: RowFormApi;
6621
6643
 
6644
+ /**
6645
+ * The grid's icon registry, read-only.
6646
+ *
6647
+ * The same sprite set `registerIcon` writes to and every cell paints from,
6648
+ * reachable from the grid instance so that code outside the grid bundle — an
6649
+ * optional module drawing its own glyph, a network chart putting a `router`
6650
+ * on a node — draws from the one registry rather than a second, empty copy of
6651
+ * it. Register with `registerIcon` or `config.icons`, as before.
6652
+ */
6653
+ readonly icons: IconRegistryApi;
6654
+
6622
6655
  /** The library version. */
6623
6656
  getVersion(): string;
6624
6657
  /** Release everything: listeners, timers, workers and the DOM the grid made. */
@@ -7815,6 +7848,25 @@ export interface ChartSpec {
7815
7848
  */
7816
7849
  shapes?: unknown;
7817
7850
  codeProperty?: string;
7851
+ /**
7852
+ * The longitude column, for the types that place a row by where it is rather
7853
+ * than by a code: `markermap`, `bubblemap` and `hexmap`. Degrees east, -180
7854
+ * to 180; a row outside that, or with no reading, is left off the map and
7855
+ * counted.
7856
+ */
7857
+ lon?: string;
7858
+ /**
7859
+ * The latitude column, beside {@link ChartSpec.lon}. Degrees north, -90 to
7860
+ * 90, on the same terms.
7861
+ */
7862
+ lat?: string;
7863
+ /**
7864
+ * The measure a `markermap` writes beside each dot and colours it by. Its
7865
+ * text is the column's own formatted cell text and its colour is whatever
7866
+ * the column's conditional-formatting rules give that value, so a map and the
7867
+ * table beside it say the same thing about the same number.
7868
+ */
7869
+ value?: string;
7818
7870
  /**
7819
7871
  * Which layer of a multi-layer geometry pack to draw — the UK pack, for
7820
7872
  * instance, ships `regions`, `local-authorities` and `constituencies`
@@ -7882,6 +7934,25 @@ export interface ChartSpec {
7882
7934
  values?: boolean;
7883
7935
  /** Network layouts: how many relaxation passes to run. */
7884
7936
  iterations?: number;
7937
+ /**
7938
+ * The nodes of a `network`, named by the host rather than inferred from the
7939
+ * rows: an icon per device, a label, and a position the layout must honour.
7940
+ * A node listed here that appears in no row is still drawn. A node in the
7941
+ * rows that is not listed here takes the chart's `icon` default and its own
7942
+ * id as its label.
7943
+ */
7944
+ nodes?: ChartNode[];
7945
+ /**
7946
+ * The default glyph for a `network` node that names none of its own: any name
7947
+ * in the grid's icon registry (see {@link Grid.icons}). Unset, a node with no
7948
+ * icon is a plain disc.
7949
+ */
7950
+ icon?: string;
7951
+ /**
7952
+ * A `network` link's stroke width in pixels, fixed. Unset, width follows the
7953
+ * link's value as a share of the heaviest link, as it always has.
7954
+ */
7955
+ linkWidth?: number;
7885
7956
 
7886
7957
  /**
7887
7958
  * Control and capability charts: a tolerance overriding the column's own
@@ -7895,6 +7966,27 @@ export interface ChartSpec {
7895
7966
  confidence?: number;
7896
7967
  }
7897
7968
 
7969
+ /**
7970
+ * One node of a `network` chart, as the host declares it.
7971
+ *
7972
+ * `x` and `y` are fractions of the plot, 0 to 1, measured from its top-left. A
7973
+ * node giving both is **pinned** there and takes no part in the force
7974
+ * simulation; the rest are laid out around it, deterministically. Giving only
7975
+ * one of the two is not a position and the node is laid out.
7976
+ */
7977
+ export interface ChartNode {
7978
+ /** Matches a value in the `source` or `target` column. */
7979
+ id: string;
7980
+ /** Drawn beneath the node. The id is used when this is absent. */
7981
+ label?: string;
7982
+ /** A name in the grid's icon registry, drawn inside the node's disc. */
7983
+ icon?: string;
7984
+ /** Where to pin it, as a fraction of the plot's width. */
7985
+ x?: number;
7986
+ /** Where to pin it, as a fraction of the plot's height. */
7987
+ y?: number;
7988
+ }
7989
+
7898
7990
  /**
7899
7991
  * The events a chart raises.
7900
7992
  *