@toclocoinc/lattice-grid 1.54.0 → 1.56.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 +6 -4
- package/docs/API.html +360 -62
- package/docs/api-detail.html +348 -15
- package/lattice-grid.d.ts +287 -27
- package/lattice-grid.esm.min.js +1610 -622
- package/lattice-grid.min.cjs +1610 -622
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +1610 -622
- package/modules/ai.esm.min.js +19 -4
- package/modules/ai.min.cjs +19 -4
- package/modules/ai.min.js +19 -4
- package/modules/angular.esm.min.js +2 -2
- package/modules/angular.min.cjs +2 -2
- package/modules/angular.min.js +2 -2
- package/modules/chart-alluvial.esm.min.js +1 -1
- package/modules/chart-arc.esm.min.js +1 -1
- package/modules/chart-bubblemap.esm.min.js +1 -1
- package/modules/chart-bump.esm.min.js +1 -1
- package/modules/chart-calendar.esm.min.js +1 -1
- package/modules/chart-decomposition.esm.min.js +1 -1
- package/modules/chart-diverging.esm.min.js +1 -1
- package/modules/chart-dumbbell.esm.min.js +1 -1
- package/modules/chart-fan.esm.min.js +1 -1
- package/modules/chart-hexbin.esm.min.js +1 -1
- package/modules/chart-hexmap.esm.min.js +1 -1
- package/modules/chart-icicle.esm.min.js +1 -1
- package/modules/chart-parallel.esm.min.js +1 -1
- package/modules/chart-ridgeline.esm.min.js +1 -1
- package/modules/chart-roc.esm.min.js +1 -1
- package/modules/chart-slope.esm.min.js +1 -1
- package/modules/chart-splom.esm.min.js +1 -1
- package/modules/chart-waffle.esm.min.js +1 -1
- package/modules/charts.esm.min.js +115 -29
- package/modules/charts.min.cjs +115 -29
- package/modules/charts.min.js +115 -29
- package/modules/data-router.esm.min.js +37 -4
- package/modules/data-router.min.cjs +37 -4
- package/modules/data-router.min.js +37 -4
- package/modules/devtools.esm.min.js +2 -2
- package/modules/devtools.min.cjs +2 -2
- package/modules/devtools.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +4 -4
- package/modules/dhtmlx-compat.min.cjs +4 -4
- package/modules/dhtmlx-compat.min.js +4 -4
- package/modules/gantt.esm.min.js +32 -6
- package/modules/gantt.min.cjs +32 -6
- package/modules/gantt.min.js +32 -6
- package/modules/htmx.esm.min.js +1610 -622
- package/modules/htmx.min.cjs +1610 -622
- package/modules/htmx.min.js +1610 -622
- package/modules/kanban.esm.min.js +49 -9
- package/modules/kanban.min.cjs +49 -9
- package/modules/kanban.min.js +49 -9
- package/modules/kpi.esm.min.js +4141 -13
- package/modules/kpi.min.cjs +4141 -13
- package/modules/kpi.min.js +4141 -13
- package/modules/layout.esm.min.js +12 -8
- package/modules/layout.min.cjs +12 -8
- package/modules/layout.min.js +12 -8
- package/modules/mock-socket.esm.min.js +2 -2
- package/modules/mock-socket.min.cjs +2 -2
- package/modules/mock-socket.min.js +2 -2
- package/modules/react.esm.min.js +2 -2
- package/modules/react.min.cjs +2 -2
- package/modules/react.min.js +2 -2
- package/modules/svelte.esm.min.js +2 -2
- package/modules/svelte.min.cjs +2 -2
- package/modules/svelte.min.js +2 -2
- package/modules/tabs.esm.min.js +4 -4
- package/modules/tabs.min.cjs +4 -4
- package/modules/tabs.min.js +4 -4
- package/modules/vue.esm.min.js +2 -2
- package/modules/vue.min.cjs +2 -2
- package/modules/vue.min.js +2 -2
- package/modules/webcomponent.esm.min.js +1610 -622
- package/modules/webcomponent.min.cjs +1610 -622
- package/modules/webcomponent.min.js +1610 -622
- package/package.json +3 -2
package/docs/api-detail.html
CHANGED
|
@@ -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.
|
|
440
|
+
<p class="rail__sub">Developer guide · v1.56.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.
|
|
556
|
+
<span class="chip">Version 1.56.0</span>
|
|
557
557
|
<span class="chip">Zero dependencies</span>
|
|
558
558
|
<span class="chip">No build step</span>
|
|
559
559
|
</p>
|
|
@@ -585,6 +585,10 @@ createGrid(element, { locale: 'fr-FR', messages: FR_FR });</code></pre>
|
|
|
585
585
|
|
|
586
586
|
<p><code>MESSAGE_KEYS</code> lists every key. <code>auditCatalogue(yours)</code> returns what is missing and what is not a real key, which is the quickest way to check a translation before shipping it.</p>
|
|
587
587
|
|
|
588
|
+
<h3>Keys seeded in English, awaiting translation</h3>
|
|
589
|
+
<p>A key added after a catalogue was written ships in British English only until a translator supplies it; <code>Messages</code> merges every catalogue over the default, so the grid says it in English rather than showing the key. The shipped catalogues do not carry an English copy under the guise of a translation, and the build's completeness test lists exactly which keys are in this state. As of 1.55 the most recent additions are the strings that had shipped as template literals, untranslated in every locale (BACKLOG-0001106): the presence live region and roster note, <code>a11y.presence.refused</code>, <code>presence.someoneElse</code> and <code>presence.hidden</code>; the comment panel's changed-since note, <code>comments.valueMoved</code>; the facet band's accessible name, <code>facets.filter</code>; the heading tooltip that names a reduction, <code>header.totalOf</code>; the audit-mode tooltip, <code>diff.before</code> and <code>diff.empty</code>; the kanban list names, <code>kanban.columnCards</code> and <code>kanban.laneCards</code>, both plural objects selected by <code>count</code>; and the gantt lag label, <code>gantt.lag</code> and <code>gantt.lead</code>. Supply any of them in <code>messages</code> to translate it today.</p>
|
|
590
|
+
<p>The kanban board and the gantt view are modules and do not import the catalogue. A board bound to a grid, or a plan created with one, borrows that grid's <code>messages</code>; otherwise pass <code>messages</code> — a grid's own, or any object with <code>t(key, params)</code> — to <code>createKanban</code> or to <code>gantt.mount</code>. With neither they render the English.</p>
|
|
591
|
+
|
|
588
592
|
<div class="why">
|
|
589
593
|
<p><strong>The grid is checked in the other direction too.</strong> <code>auditCatalogue</code> tells you a catalogue is complete: that a translator covered every key. It cannot tell you the grid only ever renders text that came from a catalogue in the first place, and a string written into the source passes every test, because the tests assert on the English the grid happens to produce.</p>
|
|
590
594
|
<p>So the build refuses one. Any literal reaching an element's text, or an announced attribute such as <code>aria-label</code>, <code>title</code> or <code>placeholder</code>, has to come from the catalogue. That is what stops a localised grid drifting back into English one plausible change at a time.</p>
|
|
@@ -668,8 +672,8 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
|
|
|
668
672
|
|
|
669
673
|
<div class="example">
|
|
670
674
|
<p class="example__label">jsDelivr, no npm install, no bundler</p>
|
|
671
|
-
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1
|
|
672
|
-
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1
|
|
675
|
+
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1/lattice-grid.min.css">
|
|
676
|
+
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1/lattice-grid.min.js"></script>
|
|
673
677
|
|
|
674
678
|
<script>
|
|
675
679
|
<span class="kw">const</span> grid = LatticeGrid.createGrid(document.getElementById('grid'), config);
|
|
@@ -694,9 +698,10 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
|
|
|
694
698
|
|
|
695
699
|
<div class="note">
|
|
696
700
|
<p><strong>jsDelivr mirrors every version published to npm</strong> at
|
|
697
|
-
<code>cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid
|
|
698
|
-
|
|
699
|
-
|
|
701
|
+
<code>cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1/<file></code>. <code>@1</code> pins
|
|
702
|
+
the major: a page in production picks up fixes within 1.x and never a breaking release, where
|
|
703
|
+
<code>@latest</code> would. To freeze a page on one exact build, replace <code>@1</code> with
|
|
704
|
+
the full version <code>getVersion()</code> reports. The same convention reaches a
|
|
700
705
|
module: <code>.../modules/htmx.esm.min.js</code>, <code>.../modules/dhtmlx-compat.esm.min.js</code>,
|
|
701
706
|
and so on. Type declarations resolve automatically through npm's own <code>types</code> field;
|
|
702
707
|
for editor tooling against the CDN or a plain script tag, point your <code>tsconfig</code> at
|
|
@@ -1273,7 +1278,7 @@ off(); <span class="cmt">// every subscrip
|
|
|
1273
1278
|
</p>
|
|
1274
1279
|
<div class="example">
|
|
1275
1280
|
<p class="example__label">Which version am I running?</p>
|
|
1276
|
-
<pre><code>grid.getVersion(); <span class="cmt">// '1.
|
|
1281
|
+
<pre><code>grid.getVersion(); <span class="cmt">// '1.56.0'</span>
|
|
1277
1282
|
LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid to hand</span></code></pre>
|
|
1278
1283
|
</div>
|
|
1279
1284
|
<p class="lead-in">
|
|
@@ -1281,6 +1286,46 @@ LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid
|
|
|
1281
1286
|
"the grid on this page", and whoever reads it has a grid rather than the module it was built
|
|
1282
1287
|
from.
|
|
1283
1288
|
</p>
|
|
1289
|
+
|
|
1290
|
+
<h3 id="headless-vs-dom">A headless grid is not a smaller grid, it is a grid without a renderer</h3>
|
|
1291
|
+
<p class="lead-in">
|
|
1292
|
+
<code>createGrid</code> builds this same core and attaches the DOM renderer to it;
|
|
1293
|
+
<code>createHeadlessGrid</code> stops one step earlier. Everything the core owns — data, state,
|
|
1294
|
+
sort, filter, group, total, pivot, formulas and computed columns, editing and optimistic
|
|
1295
|
+
write-back, export, and every event — runs unchanged with no renderer attached, which is why
|
|
1296
|
+
most of the examples in this guide that call <code>createHeadlessGrid</code> are settling real
|
|
1297
|
+
API questions, not toy snippets. What it does not have is the renderer: no layout, no
|
|
1298
|
+
measurement, no scrolling geometry, no focus, and <code>grid.element</code> is <code>null</code>.
|
|
1299
|
+
</p>
|
|
1300
|
+
<div class="why">
|
|
1301
|
+
<p><strong>A grid mounted where it has no rendered box paints only a handful of rows, not the
|
|
1302
|
+
whole dataset — by design, not as a bug.</strong> The visible row window is computed from the
|
|
1303
|
+
container's own height; a container that is detached from the document, or sits under a
|
|
1304
|
+
<code>display: none</code> ancestor, measures zero, and the virtualiser falls back to a small
|
|
1305
|
+
band around the top of the data (the overscan margin, five rows with the default configuration)
|
|
1306
|
+
rather than nothing at all. A harness that builds a grid off in a hidden or unattached container
|
|
1307
|
+
and then asserts on what is visible reads far fewer rows than it loaded and looks broken; giving
|
|
1308
|
+
the container a real, measured box before asserting is what fixes it. Note that this is about
|
|
1309
|
+
the container having <em>no computed size</em>, not about being visually off-screen: a
|
|
1310
|
+
container positioned outside the browser's visible area with real, explicit dimensions (for
|
|
1311
|
+
example <code>position: fixed; left: -9999px</code> with a width and height) still gets a real
|
|
1312
|
+
box and renders fully.</p>
|
|
1313
|
+
<p><strong>The in-repo test DOM (<code>testdom.js</code>) is a stub, not a browser, and says so
|
|
1314
|
+
in its own header comment.</strong> It implements exactly the surface the renderer touches —
|
|
1315
|
+
element creation, attributes, <code>classList</code>, <code>style</code>, children,
|
|
1316
|
+
<code>textContent</code>, event dispatch, injected geometry, and shims for
|
|
1317
|
+
<code>ResizeObserver</code> and <code>requestAnimationFrame</code> — which is enough to drive
|
|
1318
|
+
the renderer's logic in Node for most of the suite. It does not compute a cascade or a layout,
|
|
1319
|
+
so a class or style change that a stylesheet then overrides, or a box that depends on CSS rather
|
|
1320
|
+
than on the geometry a test injected, is invisible to it; those need the real-browser tests
|
|
1321
|
+
(<code>test/*-browser.test.js</code>) that drive an actual headless Chrome instead. It also does
|
|
1322
|
+
not model real focus semantics — its <code>focus()</code> simply records which element is
|
|
1323
|
+
"focused" with no check for visibility, tab order or focusability. Since BACKLOG-0001181,
|
|
1324
|
+
<code>classList.add</code> does throw on a token containing a space, the same
|
|
1325
|
+
<code>InvalidCharacterError</code> a real <code>DOMTokenList</code> throws, closing the specific
|
|
1326
|
+
gap where a two-word class name passed every headless test and then blanked a real page.</p>
|
|
1327
|
+
</div>
|
|
1328
|
+
|
|
1284
1329
|
<h2 id="columns-guide">Defining columns</h2>
|
|
1285
1330
|
<p class="lead-in">
|
|
1286
1331
|
A column is an object with a <code>field</code> (the path to read from your data) and
|
|
@@ -1341,6 +1386,55 @@ LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid
|
|
|
1341
1386
|
A cycle is caught at compile time with the full path named, rather than becoming a stack
|
|
1342
1387
|
overflow at render time.</p>
|
|
1343
1388
|
</div>
|
|
1389
|
+
<p>When a computed value is re-run, stated plainly:</p>
|
|
1390
|
+
<ul>
|
|
1391
|
+
<li>A pure compute (the default) runs at ingest and is cached. It runs again when its row is
|
|
1392
|
+
replaced through <code>rows.apply({ update })</code> or the data through
|
|
1393
|
+
<code>rows.load()</code> — unconditionally, since a value derived from data that is
|
|
1394
|
+
gone is stale by definition; when the grid a derived grid follows changes; and when you ask
|
|
1395
|
+
with <code>rows.refresh({ rows, columns, force: true })</code>. A sort, a filter, a state
|
|
1396
|
+
restore or an edit to a column outside its <code>deps</code> does not re-run it. An in-place
|
|
1397
|
+
cell edit to one of its <code>deps</code> does not currently re-run it either.</li>
|
|
1398
|
+
<li>Naming a column re-runs what depends on it: <code>refresh({ columns: ['p'], force: true })</code>
|
|
1399
|
+
recomputes a <code>q</code> whose <code>deps</code> include <code>p</code>;
|
|
1400
|
+
<code>refresh({ columns: ['q'] })</code> does not recompute <code>p</code>.</li>
|
|
1401
|
+
<li><code>pure: false</code> guarantees the compute is re-evaluated on every read and every
|
|
1402
|
+
paint. It is never served from a cache.</li>
|
|
1403
|
+
<li>Whenever a compute re-runs, <code>rows.text()</code> and the painted cell show the new
|
|
1404
|
+
result, and so do a sort or filter on the column: every cache the grid keeps for that cell
|
|
1405
|
+
— the one behind the text and the paint, and the one sort and filter handles read
|
|
1406
|
+
— is invalidated together.</li>
|
|
1407
|
+
<li>Without <code>force</code>, a targeted <code>refresh({ rows, columns })</code> behaves
|
|
1408
|
+
differently by store mode, and which one you get flips at <code>columnarBelow</code>. On a
|
|
1409
|
+
grid below that row count the named cell is re-run on its next read; on a columnar one the
|
|
1410
|
+
stored value stands until you pass <code>force: true</code>. This is behaviour to plan for,
|
|
1411
|
+
not a tuning detail: the same call on the same data recomputes or does not purely according
|
|
1412
|
+
to how many rows arrived. Pass <code>force: true</code> when you want the same answer
|
|
1413
|
+
whatever the row count.</li>
|
|
1414
|
+
</ul>
|
|
1415
|
+
<div class="example">
|
|
1416
|
+
<p class="example__label">An answer that arrives later</p>
|
|
1417
|
+
<pre><code><span class="cmt">// A lookup the grid cannot see: show a placeholder, fill the table, then</span>
|
|
1418
|
+
<span class="cmt">// tell the grid which cells to recompute. The compute stays pure, so it is</span>
|
|
1419
|
+
<span class="cmt">// not re-run on every paint — only when you say the answer changed.</span>
|
|
1420
|
+
const names = new Map();
|
|
1421
|
+
const column = {
|
|
1422
|
+
id: 'owner', title: 'Owner',
|
|
1423
|
+
value: {
|
|
1424
|
+
deps: ['ownerId'],
|
|
1425
|
+
compute: (deps) => names.get(deps.ownerId) ?? 'Loading…',
|
|
1426
|
+
},
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1429
|
+
const missing = [...new Set(grid.rows.data().map((r) => r.ownerId))].filter((id) => !names.has(id));
|
|
1430
|
+
const resolved = await fetchNames(missing); <span class="cmt">// { id: name }</span>
|
|
1431
|
+
for (const id of missing) names.set(id, resolved[id]);
|
|
1432
|
+
const rows = grid.rows.data().filter((r) => missing.includes(r.ownerId)).map((r) => r.id);
|
|
1433
|
+
grid.rows.refresh({ rows, columns: ['owner'], force: true });
|
|
1434
|
+
|
|
1435
|
+
<span class="cmt">// Or declare the column `pure: false` and it re-reads `names` on every</span>
|
|
1436
|
+
<span class="cmt">// paint; then a plain grid.rows.refresh() after the fetch is enough.</span></code></pre>
|
|
1437
|
+
</div>
|
|
1344
1438
|
|
|
1345
1439
|
<h3>Sizing and pinning</h3>
|
|
1346
1440
|
<div class="example">
|
|
@@ -1354,6 +1448,31 @@ LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid
|
|
|
1354
1448
|
<code>grid.columns.fit()</code> distributes the viewport width across visible columns, and
|
|
1355
1449
|
<code>autoSize</code> measures content.
|
|
1356
1450
|
</p>
|
|
1451
|
+
<p>The width <code>fit()</code> distributes is the space the cells actually occupy: the body
|
|
1452
|
+
viewport’s client width, read when you call it. When the grid has enough rows to
|
|
1453
|
+
scroll vertically, that width excludes the scrollbar, so the columns end flush with it
|
|
1454
|
+
instead of running under it; when there is no vertical scrollbar, it is the full inner
|
|
1455
|
+
width. Rows you passed to <code>createGrid</code> or <code>grid.rows.load()</code> before
|
|
1456
|
+
the call are counted, so calling it straight after either works.</p>
|
|
1457
|
+
<p>Every column the grid draws counts toward that width, not only the ones <code>fit()</code>
|
|
1458
|
+
sizes. It sizes your resizable columns. A column it does not size keeps its width, and that
|
|
1459
|
+
width comes off the target first: a column declared <code>resizable: false</code>, and the
|
|
1460
|
+
grid’s own selection checkbox, detail expander, group and tree columns. Your resizable
|
|
1461
|
+
columns then share what is left in proportion to their current widths, within each
|
|
1462
|
+
<code>min</code> and <code>max</code>; a column held at a bound stays there and the others
|
|
1463
|
+
share the rest, so all the drawn columns together still come to the viewport width
|
|
1464
|
+
exactly. Under a pivot every column drawn is one the grid generates, so <code>fit()</code>
|
|
1465
|
+
has nothing to size and leaves the widths as they are.</p>
|
|
1466
|
+
<p>If what is left is less than those columns’ minimums, which happens when the columns
|
|
1467
|
+
<code>fit()</code> does not size already take the width, each resizable column is set to its
|
|
1468
|
+
<code>min</code> (40px when it declares none). None goes below its minimum, and none is
|
|
1469
|
+
squeezed to nothing: the grid scrolls horizontally instead, and a <code>[lattice]</code>
|
|
1470
|
+
warning in the console names the widths that ran out.</p>
|
|
1471
|
+
<p><code>fit()</code> is one-shot. It sets a fixed width on each column once, including a
|
|
1472
|
+
<code>flex</code> column, and does not follow the grid afterwards. If the width changes
|
|
1473
|
+
later, because the container is resized or because rows that arrive afterwards bring a
|
|
1474
|
+
vertical scrollbar in, call it again. A column that should keep tracking the width by
|
|
1475
|
+
itself wants <code>flex</code> instead of <code>fit()</code>.</p>
|
|
1357
1476
|
<p>Both are also on the column menu: Move left, Move right, Move to start, Move to end, and
|
|
1358
1477
|
a Width submenu, and bound to the keyboard with a heading focused: <kbd>Alt</kbd> with a
|
|
1359
1478
|
left or right arrow resizes, <kbd>Shift</kbd> with one moves the column. Neither operation
|
|
@@ -1969,9 +2088,16 @@ grid.edit.setCells([
|
|
|
1969
2088
|
<span class="cmt">// → the number of cells written</span></code></pre>
|
|
1970
2089
|
</div>
|
|
1971
2090
|
<p class="lead-in">
|
|
1972
|
-
This is the full path, not a shortcut: it validates, emits <code>cell:changed</code
|
|
2091
|
+
This is the full path, not a shortcut: it validates, emits <code>cell:changed</code> per cell,
|
|
1973
2092
|
re-sorts if the column is sorted on, records one undo entry, and returns <code>0</code> for a
|
|
1974
|
-
column the user may not write.
|
|
2093
|
+
column the user may not write. It then announces the whole call <strong>once</strong> as
|
|
2094
|
+
<code>rows:changed</code> (<code>identified: true</code>, <code>edit: true</code>, the
|
|
2095
|
+
<code>updated</code> rows and the <code>columns</code> written), after the per-cell events, so
|
|
2096
|
+
a derived grid, a statistic tile or anything else that follows <code>rows:changed</code>
|
|
2097
|
+
re-reads once for a twenty-cell paste rather than twenty times. An editor commit, a fill, a
|
|
2098
|
+
paste, an undo, a redo and an optimistic rollback each announce themselves the same way, once
|
|
2099
|
+
per batch. Earlier releases announced nothing here, and a derived view of an edited grid went
|
|
2100
|
+
silently stale.
|
|
1975
2101
|
</p>
|
|
1976
2102
|
|
|
1977
2103
|
<h3>High-frequency updates</h3>
|
|
@@ -2068,6 +2194,59 @@ grid.filters.quick(''); <span class="cmt">// clear</span></code></pre>
|
|
|
2068
2194
|
<code>notBlank</code> work everywhere.
|
|
2069
2195
|
</p>
|
|
2070
2196
|
|
|
2197
|
+
<h3 id="where-guide">Filters your application owns: <code>where</code></h3>
|
|
2198
|
+
<p class="lead-in">
|
|
2199
|
+
A condition tree can only test what is in a column. Plenty of real filters cannot be written
|
|
2200
|
+
that way — whether this user may see the row, whether you hold an exchange rate for its
|
|
2201
|
+
currency, whether it came back from your last search call. Those go in as <strong>named
|
|
2202
|
+
predicates</strong>, and they compose with everything above.
|
|
2203
|
+
</p>
|
|
2204
|
+
|
|
2205
|
+
<div class="example">
|
|
2206
|
+
<p class="example__label">A permission filter and a toggle, side by side</p>
|
|
2207
|
+
<pre><code>grid.filters.where('visibleToMe', row => row.owner === me, { pinned: true });
|
|
2208
|
+
grid.filters.where('rateKnown', row => rates.has(row.ccy), { deps: ['ccy'] });
|
|
2209
|
+
|
|
2210
|
+
grid.filters.where(); <span class="cmt">// ['visibleToMe', 'rateKnown']</span>
|
|
2211
|
+
grid.filters.where('rateKnown', null); <span class="cmt">// remove just that one</span>
|
|
2212
|
+
grid.filters.reapply('rateKnown'); <span class="cmt">// the rate table arrived late</span></code></pre>
|
|
2213
|
+
</div>
|
|
2214
|
+
|
|
2215
|
+
<div class="why">
|
|
2216
|
+
<p>There is deliberately no "a filter is present" flag. That flag is a second piece of state
|
|
2217
|
+
describing the first, and the two drift: the classic symptom is a grid that filters while the
|
|
2218
|
+
UI insists it is not, or insists it is filtering while every row passes. Here, registering a
|
|
2219
|
+
predicate is what puts it in force, and removing it is what takes it out.</p>
|
|
2220
|
+
</div>
|
|
2221
|
+
|
|
2222
|
+
<p class="lead-in">
|
|
2223
|
+
Three options shape one. <code>deps</code> names the columns the predicate reads, exactly as
|
|
2224
|
+
<code>value.deps</code> does for a computed column: the verdict is then cached per row and
|
|
2225
|
+
re-run when one of <em>those</em> columns changes on that row, not when an unrelated one does.
|
|
2226
|
+
Leave it off and the predicate is assumed to read the whole row, so it runs every pass and can
|
|
2227
|
+
never be stale. <code>pinned</code> makes a predicate survive
|
|
2228
|
+
<code>filters.clear()</code>, which is what you want for permissions and tenant scoping and
|
|
2229
|
+
not much else. <code>condition</code> gives the predicate a declarative twin that is pushed to
|
|
2230
|
+
the source while the function stays as the residual, so a pushdown engine narrows the fetch
|
|
2231
|
+
instead of your code filtering a page.
|
|
2232
|
+
</p>
|
|
2233
|
+
|
|
2234
|
+
<div class="why">
|
|
2235
|
+
<p><strong>Migrating from AG Grid's external filter:</strong> its three pieces become two.
|
|
2236
|
+
<code>isExternalFilterPresent()</code> goes away, because registration is presence.
|
|
2237
|
+
<code>doesExternalFilterPass(node)</code> becomes the predicate itself.
|
|
2238
|
+
<code>onFilterChanged()</code> becomes <code>deps</code> where the grid can watch the change
|
|
2239
|
+
for you, and <code>reapply(name?)</code> where it cannot.</p>
|
|
2240
|
+
</div>
|
|
2241
|
+
|
|
2242
|
+
<div class="why">
|
|
2243
|
+
<p><strong>What travels in a saved view is the name, not the function.</strong>
|
|
2244
|
+
<code>state.get()</code> carries <code>where: string[]</code>; your predicates are your code
|
|
2245
|
+
and the grid will not pretend it can serialise them. Applying a view that names a predicate
|
|
2246
|
+
you have not registered <em>reports</em> the skip instead of quietly showing a wider row set,
|
|
2247
|
+
and never removes a predicate the view did not mention.</p>
|
|
2248
|
+
</div>
|
|
2249
|
+
|
|
2071
2250
|
<h2 id="grouping">Grouping, totals and pivot</h2>
|
|
2072
2251
|
<div class="example">
|
|
2073
2252
|
<p class="example__label">Group by one or more columns</p>
|
|
@@ -3305,12 +3484,84 @@ createGrid(right, {
|
|
|
3305
3484
|
hundred updates against a two hundred thousand row source cost under 300 ms in total. Set
|
|
3306
3485
|
<code>refresh</code> to <code>live</code>, <code>manual</code> or a number of milliseconds to
|
|
3307
3486
|
change the coalescing; <code>idle</code> is the default and settles to a frame.</p>
|
|
3487
|
+
<p><strong><code>manual</code> means the host says when, and <code>rows.load()</code> is how it
|
|
3488
|
+
says it.</strong> A <code>manual</code> derived grid never re-derives on its own: the source can
|
|
3489
|
+
filter, edit and tick underneath it and the panel keeps showing what it last derived. Call
|
|
3490
|
+
<code>rows.load()</code> <em>on the derived grid</em>, with no argument, and it re-reads its
|
|
3491
|
+
<code>from</code> there and then and replaces its rows; call it again whenever you want the next
|
|
3492
|
+
reading. A derived grid takes its rows from <code>from</code>, so anything passed to
|
|
3493
|
+
<code>load</code> is not used. The derived grid's own sort and filters stay as they were. Press
|
|
3494
|
+
it as often as you like — the source is left exactly as one press leaves it, and
|
|
3495
|
+
destroying the panel leaves nothing of it attached there.</p>
|
|
3308
3496
|
<p><strong>Derived grids are read-only.</strong> There is one copy of the data and it lives in
|
|
3309
|
-
the source. Write there and the derived grid follows
|
|
3497
|
+
the source. Write there and the derived grid follows: an edit through the source's editing
|
|
3498
|
+
API (<code>edit.setCells</code>, an inline commit, a fill, a paste, an undo, a redo or an
|
|
3499
|
+
optimistic rollback) is announced once per batch, after the per-cell
|
|
3500
|
+
<code>cell:changed</code> events, and the derived grid re-derives once for the batch. A
|
|
3501
|
+
grouped derivation patches the groups the edited rows belong to; a derivation with a
|
|
3502
|
+
<code>where</code>, an <code>unnest</code>, a producer, or an edit to a column the source
|
|
3503
|
+
filters on re-reads in full, at the cost measured above. <strong>Use <code>idle</code> when a
|
|
3504
|
+
derived child follows an editable grid.</strong> Under <code>live</code> the derivation runs
|
|
3505
|
+
synchronously inside the edit call, so a keystroke's commit on a 200k-row source pays the
|
|
3506
|
+
whole re-read before the editor closes; <code>idle</code> defers it to the next frame and
|
|
3507
|
+
folds a burst of edits into one.</p>
|
|
3310
3508
|
<p><strong>The key comes for free.</strong> A derived grid keys on <code>__key</code>, which
|
|
3311
3509
|
the source writes onto every row it produces: the group value, the profiled column, or the
|
|
3312
3510
|
source row's own key when nothing is grouped. Set <code>rowKey</code> only to override it.</p>
|
|
3313
3511
|
</div>
|
|
3512
|
+
<div class="example" id="derived-manual-refresh">
|
|
3513
|
+
<p class="example__label">A frozen panel, refreshed on a button press, executed</p>
|
|
3514
|
+
<pre data-run="js" data-expect="20 20 10 10 5; steady; released" data-covers="config:refresh method:rows method:diagnostics"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
3515
|
+
|
|
3516
|
+
<span class="kw">const</span> detail = createHeadlessGrid({
|
|
3517
|
+
rowKey: 'id',
|
|
3518
|
+
columns: [{ field: 'region' }, { field: 'capacity', type: 'number' }],
|
|
3519
|
+
rows: Array.from({ length: <span class="num">20</span> }, (_, i) => ({ id: i, region: i % <span class="num">2</span> ? 'North' : 'South', capacity: i })),
|
|
3520
|
+
});
|
|
3521
|
+
detail.rows.count();
|
|
3522
|
+
|
|
3523
|
+
<span class="cmt">// What the detail grid is listening with before any panel is attached.</span>
|
|
3524
|
+
<span class="kw">const</span> listening = () => Object.values(detail.diagnostics.events()).reduce((a, b) => a + b, <span class="num">0</span>);
|
|
3525
|
+
<span class="kw">const</span> alone = listening();
|
|
3526
|
+
|
|
3527
|
+
<span class="cmt">// A summary that derives once, then waits to be told.</span>
|
|
3528
|
+
<span class="kw">const</span> panel = createHeadlessGrid({
|
|
3529
|
+
columns: [{ field: 'region' }, { field: 'sites', type: 'number' }],
|
|
3530
|
+
source: { mode: 'derived', from: detail, groupBy: 'region',
|
|
3531
|
+
select: { sites: { fn: 'count' } }, refresh: 'manual' },
|
|
3532
|
+
});
|
|
3533
|
+
|
|
3534
|
+
<span class="cmt">// The Refresh button. In a page this is your <button>; here any EventTarget will do.</span>
|
|
3535
|
+
<span class="kw">const</span> button = <span class="kw">new</span> EventTarget();
|
|
3536
|
+
button.addEventListener('click', () => panel.rows.load()); <span class="cmt">// no argument</span>
|
|
3537
|
+
|
|
3538
|
+
<span class="kw">const</span> sites = () => {
|
|
3539
|
+
<span class="kw">let</span> n = <span class="num">0</span>;
|
|
3540
|
+
panel.rows.forEach((row) => { n += panel.rows.value(row.key, 'sites'); });
|
|
3541
|
+
<span class="kw">return</span> n;
|
|
3542
|
+
};
|
|
3543
|
+
<span class="cmt">// Longer than any automatic refresh would take to land, so "frozen" is a finding.</span>
|
|
3544
|
+
<span class="kw">const</span> settle = () => <span class="kw">new</span> Promise((done) => setTimeout(done, <span class="num">50</span>));
|
|
3545
|
+
|
|
3546
|
+
<span class="kw">const</span> attached = listening();
|
|
3547
|
+
<span class="kw">const</span> seen = [sites()]; <span class="cmt">// 20</span>
|
|
3548
|
+
detail.filters.set({ col: 'capacity', op: 'lt', value: <span class="num">10</span> });
|
|
3549
|
+
<span class="kw">await</span> settle(); seen.push(sites()); <span class="cmt">// 20: the source moved, the panel did not</span>
|
|
3550
|
+
button.dispatchEvent(<span class="kw">new</span> Event('click'));
|
|
3551
|
+
seen.push(sites()); <span class="cmt">// 10: re-derived on the press</span>
|
|
3552
|
+
detail.filters.set({ col: 'capacity', op: 'lt', value: <span class="num">5</span> });
|
|
3553
|
+
<span class="kw">await</span> settle(); seen.push(sites()); <span class="cmt">// 10: frozen again</span>
|
|
3554
|
+
button.dispatchEvent(<span class="kw">new</span> Event('click'));
|
|
3555
|
+
seen.push(sites()); <span class="cmt">// 5</span>
|
|
3556
|
+
|
|
3557
|
+
<span class="cmt">// Pressing it any number of times leaves the detail grid as one press does,</span>
|
|
3558
|
+
<span class="cmt">// and destroying the panel leaves nothing of it behind there.</span>
|
|
3559
|
+
<span class="kw">const</span> steady = listening() === attached ? 'steady' : 'grew';
|
|
3560
|
+
panel.destroy();
|
|
3561
|
+
<span class="kw">const</span> released = listening() === alone ? 'released' : 'left behind';
|
|
3562
|
+
detail.destroy();
|
|
3563
|
+
<span class="kw">return</span> `${seen.join(' ')}; ${steady}; ${released}`;</code></pre>
|
|
3564
|
+
</div>
|
|
3314
3565
|
|
|
3315
3566
|
<h3>Other shapes</h3>
|
|
3316
3567
|
<div class="table-wrap">
|
|
@@ -3514,7 +3765,11 @@ createGrid(right, {
|
|
|
3514
3765
|
next frame; a <strong>number</strong> is a debounce in milliseconds; <code>'live'</code>
|
|
3515
3766
|
derives on every change and is the one to avoid for an expensive analysis over a ticking feed;
|
|
3516
3767
|
<code>'manual'</code> stops automatic derivation entirely, leaving the host to drive the
|
|
3517
|
-
source
|
|
3768
|
+
source: call <code>rows.load()</code> on the derived grid, with no argument, whenever the
|
|
3769
|
+
analysis should be brought up to date — from a Refresh button, when its tab is shown, or
|
|
3770
|
+
on a timer of your own. Each call re-derives once, at the cost in the table above;
|
|
3771
|
+
<a href="#derived-manual-refresh">a frozen panel refreshed on a button press</a> is executed
|
|
3772
|
+
above. Below a few tens of thousands of rows none of this matters.</p>
|
|
3518
3773
|
|
|
3519
3774
|
<h2 id="cross-filter">Cross-filtering</h2>
|
|
3520
3775
|
<p class="lead-in">
|
|
@@ -4563,6 +4818,27 @@ grid.selection.extendRange(34, 'cap'); <span class="cmt">// grows the block jus
|
|
|
4563
4818
|
Selected rows carry <code>aria-selected</code> and the class
|
|
4564
4819
|
<code>lat-row--selected</code>, which the theme styles.
|
|
4565
4820
|
</p>
|
|
4821
|
+
<div class="example">
|
|
4822
|
+
<p class="example__label">A row with its own click action</p>
|
|
4823
|
+
<pre><code>selection: { mode: 'multiple', checkbox: <span class="kw">true</span>, checkboxOnly: <span class="kw">true</span> }</code></pre>
|
|
4824
|
+
</div>
|
|
4825
|
+
<p class="lead-in">
|
|
4826
|
+
<code>checkboxOnly: true</code> restricts row selection to the checkbox column:
|
|
4827
|
+
clicking the checkbox selects or deselects the row, and clicking anywhere else in the
|
|
4828
|
+
row does neither. This is for a host that binds its own action — typically opening a
|
|
4829
|
+
record's detail view — to a plain click on the row: without it, that click also
|
|
4830
|
+
selects the row, and a bulk action run afterwards operates on rows the user never
|
|
4831
|
+
chose to select. The same restriction applies to the keyboard: Space still toggles
|
|
4832
|
+
selection while focus is on the checkbox cell, and does nothing elsewhere. Cell ranges
|
|
4833
|
+
and the fill handle are unaffected either way. Off by default, so a plain click still
|
|
4834
|
+
selects a row exactly as it always has.
|
|
4835
|
+
</p>
|
|
4836
|
+
<p class="lead-in">
|
|
4837
|
+
<code>checkboxOnly</code> only narrows which gesture may change selection; it does not
|
|
4838
|
+
grant selection where <code>mode: 'none'</code> has already refused it, and it composes
|
|
4839
|
+
normally with <code>mode: 'single'</code> — the checkbox remains the only way to change
|
|
4840
|
+
which one row is selected.
|
|
4841
|
+
</p>
|
|
4566
4842
|
|
|
4567
4843
|
<div class="why">
|
|
4568
4844
|
<p><strong>Ctrl+Shift+Arrow is the keyboard form of ctrl-dragging.</strong> The first press
|
|
@@ -4696,6 +4972,15 @@ createChart({
|
|
|
4696
4972
|
chart stops moving, even though time is still passing — and the silence is usually the
|
|
4697
4973
|
thing worth seeing. <code>maxAge</code> is a span of wall clock, so it means the same thing
|
|
4698
4974
|
whatever the feed is doing.</p>
|
|
4975
|
+
<p><strong>An empty reduction is a gap, not a zero.</strong> <code>fn: 'avg'</code> above
|
|
4976
|
+
— and <code>sum</code>, <code>mean</code>, <code>min</code>, <code>max</code>,
|
|
4977
|
+
<code>first</code> and <code>last</code> beside it — read as <code>null</code> when a
|
|
4978
|
+
bucket carries no rows to reduce (BACKLOG-0001088), so a producer that has stopped sending is
|
|
4979
|
+
drawn as a break in the line rather than a value dropping to zero, which would read as a real
|
|
4980
|
+
observation nobody made. <code>count</code> and <code>countValues</code> are the deliberate
|
|
4981
|
+
exception: they are already honest at zero, a tally of rows or of values actually present, so
|
|
4982
|
+
reach for <code>countValues</code> when the reading you actually want is “how many
|
|
4983
|
+
arrived” and zero has to be drawn as zero rather than as a gap.</p>
|
|
4699
4984
|
<p><strong>Two bounds, one eviction path.</strong> <code>maxAge</code> and
|
|
4700
4985
|
<code>maxRows</code> are independent and compose: both are applied on the same pass and
|
|
4701
4986
|
whichever bites first is simply the one that drops rows. Neither is silently ignored.
|
|
@@ -4754,6 +5039,16 @@ createChart({
|
|
|
4754
5039
|
that far ahead the chart shows its empty state, with the same warning.
|
|
4755
5040
|
<code>chart.data().windowed</code> counts the readings dropped at either edge of the
|
|
4756
5041
|
window. The fix for the warning is the producer’s clock, not a wider window.</p>
|
|
5042
|
+
<p><strong>The other edge: a producer that has simply stopped.</strong> The case above is a
|
|
5043
|
+
clock running fast; the opposite is a feed that has gone quiet for longer than the window
|
|
5044
|
+
(BACKLOG-0001088) — every reading is older than the span, so every mark would fall to
|
|
5045
|
+
the left of the domain, off the plot, while the axes and legend keep drawing as if the chart
|
|
5046
|
+
were healthy. Rather than draw that, the chart shows its empty state and warns once <strong>per
|
|
5047
|
+
chart instance</strong>, naming the span and how old the newest reading actually is, so a dead
|
|
5048
|
+
feed reads as “no data” rather than as a chart that quietly stopped moving. Two
|
|
5049
|
+
charts bound to the same stale column each get their own warning — the key is scoped to
|
|
5050
|
+
the chart, not just the column, so a dashboard of tiled charts sharing one timestamp column
|
|
5051
|
+
does not lose the second warning to the first.</p>
|
|
4757
5052
|
<p><strong>Idle costs nothing.</strong> Both halves advance on a plain interval — a
|
|
4758
5053
|
quarter of the window, clamped to between 50 ms and one second — and never on an
|
|
4759
5054
|
animation frame. The source’s wake returns after a single number comparison unless a
|
|
@@ -5368,6 +5663,17 @@ grid.annotate.use(null); <span class="cmt">// hand the grid
|
|
|
5368
5663
|
<h3>Keyboard</h3>
|
|
5369
5664
|
<p>Every operation is reachable without a pointer. Resizing and reordering a column were once
|
|
5370
5665
|
drag-only; both now have key bindings and menu items, so nothing depends on dragging.</p>
|
|
5666
|
+
<p><strong>Entering the grid.</strong> The grid is one stop in the page's tab order. Pressing
|
|
5667
|
+
<kbd>Tab</kbd> into a grid that has not been used yet puts focus on the grid itself, and the
|
|
5668
|
+
grid draws a focus ring around its own edge at once, in the theme's focus colour, so a keyboard
|
|
5669
|
+
user can see where focus went before pressing anything else. In Windows High Contrast Mode
|
|
5670
|
+
the ring is drawn in the system text colour. A screen reader announces the grid (or tree grid),
|
|
5671
|
+
its row and column counts and whether it is read-only. The first arrow key, <kbd>Home</kbd>
|
|
5672
|
+
or <kbd>Ctrl</kbd>+<kbd>Home</kbd> moves focus to a cell, and the ring moves with it. From then
|
|
5673
|
+
on the cell you were on is the grid's tab stop: <kbd>Shift</kbd>+<kbd>Tab</kbd> from the first
|
|
5674
|
+
cell leaves the grid, and <kbd>Tab</kbd> back into the grid returns to that cell. The grid takes
|
|
5675
|
+
the tab stop back only when that cell is outside the rendered rows, and then draws its own
|
|
5676
|
+
ring again.</p>
|
|
5371
5677
|
<div class="table-wrap">
|
|
5372
5678
|
<table>
|
|
5373
5679
|
<thead><tr><th>Keys</th><th>Does</th></tr></thead>
|
|
@@ -5717,10 +6023,11 @@ grid.edit.pasteInto(text); <span class="cmt">// Excel's t
|
|
|
5717
6023
|
<tr><td class="sig">Delete / Backspace</td><td class="desc">Clear the selected cells.</td></tr>
|
|
5718
6024
|
<tr><td class="sig">Enter</td><td class="desc">Start editing; commit and step down.</td></tr>
|
|
5719
6025
|
<tr><td class="sig">Tab</td><td class="desc">Commit and step across.</td></tr>
|
|
5720
|
-
<tr><td class="sig">Escape</td><td class="desc">Cancel the edit; restore a maximised grid once nothing else wants it.</td></tr>
|
|
6026
|
+
<tr><td class="sig">Escape</td><td class="desc">Cancel the edit; restore a maximised grid once nothing else wants it. Coming back from full screen, whether by Escape or the rail's restore button, puts focus back on the cell you were on, not on the page.</td></tr>
|
|
5721
6027
|
<tr><td class="sig">Ctrl/Cmd + F</td><td class="desc">Open the <a href="#find">find bar</a>; in it, Enter / Shift+Enter step through the matches and Escape closes.</td></tr>
|
|
5722
6028
|
<tr><td class="sig">Space</td><td class="desc">Toggle the row's selection.</td></tr>
|
|
5723
6029
|
<tr><td class="sig">Home / End, Page Up / Down</td><td class="desc">Jump; with Ctrl, to the ends of the grid.</td></tr>
|
|
6030
|
+
<tr><td class="sig">Ctrl/Cmd + Alt + H</td><td class="desc">Move focus to the column heading. On a heading, ArrowLeft / ArrowRight move between headings, Enter or Space sort by the column (Shift to add it to the sort), Alt + arrows resize, Shift + arrows move the column, Alt + ArrowDown opens the column menu, and ArrowDown or Escape return you to the data. The full list is in the <a href="#accessibility-guide">accessibility guide</a>.</td></tr>
|
|
5724
6031
|
</tbody>
|
|
5725
6032
|
</table>
|
|
5726
6033
|
</div>
|
|
@@ -5728,6 +6035,10 @@ grid.edit.pasteInto(text); <span class="cmt">// Excel's t
|
|
|
5728
6035
|
<p>None of these fire while you are typing into an input, a filter box, an open editor, the
|
|
5729
6036
|
view-name field. The grid checks where the keystroke came from before claiming it, which
|
|
5730
6037
|
sounds obvious and is the sort of thing that is usually wrong.</p>
|
|
6038
|
+
<p>A key a heading handles acts once, on the heading: Enter sorts and does not also open an
|
|
6039
|
+
editor on a body cell, ArrowRight reaches the next heading and not a cell beneath it. A key
|
|
6040
|
+
the heading declines still travels, which is how Escape reaches a maximised grid from a
|
|
6041
|
+
heading.</p>
|
|
5731
6042
|
</div>
|
|
5732
6043
|
|
|
5733
6044
|
<h2 id="rules-guide">Conditional formatting</h2>
|
|
@@ -7224,7 +7535,7 @@ el.grid.sort.set([{ col: 'charge', dir: 'desc' }]);</code></pre>
|
|
|
7224
7535
|
<tr><td class="name">filter:changed</td><td class="desc">The condition tree or the quick filter changed.</td></tr>
|
|
7225
7536
|
<tr><td class="name">page:changed</td><td class="desc">Fired after the rows have moved, whether the page changed by API or by the pager control.</td></tr>
|
|
7226
7537
|
<tr><td class="name">sort:changed</td><td class="desc">The full sort entry list.</td></tr>
|
|
7227
|
-
<tr><td class="name">state:changed</td><td class="desc">report lists anything a restore could not apply.</td></tr>
|
|
7538
|
+
<tr><td class="name">state:changed</td><td class="desc">Every state change, whether a user gesture or a programmatic call, announced exactly once — with one known gap, filters.where (BACKLOG-0001235), which changes the where section and the rows on screen without raising it. cause is 'user', 'apply' or 'reset'; sections names the GridState keys that moved; report lists anything a restore could not apply. A save layer subscribes to this one event and ignores cause 'reset'.</td></tr>
|
|
7228
7539
|
<tr><td class="name">state:reset</td><td class="desc">The grid was returned to its baseline.</td></tr>
|
|
7229
7540
|
<tr><td class="name">timeline:attached</td><td class="desc">A time brush was connected to the grid.</td></tr>
|
|
7230
7541
|
<tr><td class="name">timeline:detached</td><td class="desc">The brush was removed.</td></tr>
|
|
@@ -7471,9 +7782,31 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'trial'<
|
|
|
7471
7782
|
});</code></pre>
|
|
7472
7783
|
</div>
|
|
7473
7784
|
|
|
7785
|
+
<h3 id="wrapping-creategrid-recipe">House-wide defaults, without patching <code>createGrid</code></h3>
|
|
7786
|
+
<p class="lead-in">
|
|
7787
|
+
<code>createGrid</code> is exported through a getter with no setter, so
|
|
7788
|
+
<code>LatticeGrid.createGrid = myWrapper</code> does not replace it — silently in a plain
|
|
7789
|
+
script, with a <code>TypeError</code> in a module (see the <a
|
|
7790
|
+
href="API.html#wrapping-creategrid">reference</a> for the exact descriptor). Own the seam
|
|
7791
|
+
yourself instead: one module that every call site imports from.
|
|
7792
|
+
</p>
|
|
7793
|
+
<div class="example">
|
|
7794
|
+
<pre><code><span class="cmt">// lattice.js</span>
|
|
7795
|
+
<span class="kw">import</span> { createGrid <span class="kw">as</span> baseCreateGrid } <span class="kw">from</span> '@toclocoinc/lattice-grid';
|
|
7796
|
+
|
|
7797
|
+
<span class="kw">export function</span> createGrid(element, config) {
|
|
7798
|
+
<span class="kw">return</span> baseCreateGrid(element, { theme: 'house', locale: 'en-GB', ...config });
|
|
7799
|
+
}</code></pre>
|
|
7800
|
+
</div>
|
|
7801
|
+
<p class="lead-in">
|
|
7802
|
+
There is no shipped <code>defaults()</code> call that does this for you today (a separate card,
|
|
7803
|
+
BACKLOG-0001187, is considering one) — a wrapping module you own and every call site imports is
|
|
7804
|
+
the supported pattern until then.
|
|
7805
|
+
</p>
|
|
7806
|
+
|
|
7474
7807
|
<footer>
|
|
7475
7808
|
<p>
|
|
7476
|
-
Lattice Grid 1.
|
|
7809
|
+
Lattice Grid 1.56.0 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
|
|
7477
7810
|
Written against the shipped source. Where this guide and the code disagree, the code wins,
|
|
7478
7811
|
please <a href="https://www.latticegrid.dev">tell us</a>.
|
|
7479
7812
|
</p>
|