@toclocoinc/lattice-grid 1.14.0 → 1.16.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 +5 -2
- package/docs/API.html +375 -11
- package/docs/api-detail.html +277 -1
- package/lattice-grid.d.ts +216 -2
- package/lattice-grid.esm.min.js +2742 -393
- package/lattice-grid.min.cjs +2742 -393
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +2742 -393
- package/modules/charts.esm.min.js +89 -4
- package/modules/devtools.esm.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +31 -65164
- package/modules/htmx.esm.min.js +2721 -393
- package/modules/htmx.min.cjs +2721 -393
- package/modules/htmx.min.js +2721 -393
- package/modules/react.esm.min.js +2 -2
- package/modules/svelte.esm.min.js +2 -2
- package/modules/vue.esm.min.js +2 -2
- package/modules/webcomponent.esm.min.js +2742 -393
- package/package.json +1 -1
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.16.0</p>
|
|
441
441
|
<nav>
|
|
442
442
|
<div class="rail__group">
|
|
443
443
|
<span class="rail__label">Start here</span>
|
|
@@ -884,6 +884,14 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
|
|
|
884
884
|
<code>.events</code> namespaces: sitting on top of a real Lattice grid underneath.
|
|
885
885
|
Swap the import and, for the surface below, the calling code does not change.
|
|
886
886
|
</p>
|
|
887
|
+
<p class="lead-in">
|
|
888
|
+
The wrapper shares the core the page already loads rather than carrying its own,
|
|
889
|
+
so load <code>@toclocoinc/lattice-grid</code> alongside it: a bundler wires the shared
|
|
890
|
+
import automatically (it dedupes against the core your app already imports), and a
|
|
891
|
+
plain <code><script src></code> page loads the global build first. The pay-off is
|
|
892
|
+
that a licence set on that one core applies to these grids too, and the module is a
|
|
893
|
+
few kilobytes of translation rather than a second copy of the grid.
|
|
894
|
+
</p>
|
|
887
895
|
|
|
888
896
|
<div class="example">
|
|
889
897
|
<p class="example__label">A drop-in constructor</p>
|
|
@@ -1525,6 +1533,43 @@ cell: { decoration: 'dot' } <span class="cmt">// a leading
|
|
|
1525
1533
|
property, not a search for hex codes.</p>
|
|
1526
1534
|
</div>
|
|
1527
1535
|
|
|
1536
|
+
<div class="example">
|
|
1537
|
+
<p class="example__label">Icon sets: a threshold glyph per value band</p>
|
|
1538
|
+
<pre><code><span class="cmt">// A built-in set — traffic lights, arrows, rating marks — driven by value.</span>
|
|
1539
|
+
cell: { decoration: { type: 'icon', iconSet: 'arrows' } }
|
|
1540
|
+
|
|
1541
|
+
<span class="cmt">// Or your own bands. The highest `min` a value clears wins; a band with no</span>
|
|
1542
|
+
<span class="cmt">// `min` is the catch-all. `label` is what a screen reader announces.</span>
|
|
1543
|
+
cell: { decoration: { type: 'icon', bands: [
|
|
1544
|
+
{ min: 0.9, icon: 'success', label: 'on target', variant: 'success' },
|
|
1545
|
+
{ min: 0.5, icon: 'warning', label: 'at risk', variant: 'warning' },
|
|
1546
|
+
{ icon: 'danger', label: 'off track', variant: 'danger' },
|
|
1547
|
+
] } }</code></pre>
|
|
1548
|
+
</div>
|
|
1549
|
+
<div class="why">
|
|
1550
|
+
<p>An icon set is a restatement of the value, not a replacement for it: the value still
|
|
1551
|
+
renders beside the glyph, and the band's <code>label</code> is set as the glyph's
|
|
1552
|
+
<code>aria-label</code>, so a screen-reader user hears "on target 92%" rather than a bare
|
|
1553
|
+
number with the status lost. The glyphs are the grid's own inline sprites (§16), so an icon
|
|
1554
|
+
set adds no dependency and makes no request. Built-in sets: <code>trafficLights</code>,
|
|
1555
|
+
<code>arrows</code>, <code>trafficArrows</code>, <code>ratings</code>.</p>
|
|
1556
|
+
</div>
|
|
1557
|
+
|
|
1558
|
+
<div class="example">
|
|
1559
|
+
<p class="example__label">Turning a decoration on at runtime</p>
|
|
1560
|
+
<pre><code><span class="cmt">// Set, change or clear a column's decoration after the grid is built.</span>
|
|
1561
|
+
grid.columns.decorate('score', { type: 'bar', min: 0, max: 100 });
|
|
1562
|
+
grid.columns.decorate('trend', { type: 'icon', iconSet: 'arrows' });
|
|
1563
|
+
grid.columns.decorate('score', <span class="kw">null</span>); <span class="cmt">// back to plain text</span></code></pre>
|
|
1564
|
+
</div>
|
|
1565
|
+
<div class="why">
|
|
1566
|
+
<p>A decoration is presentation, so <code>columns.decorate</code> is a live setter like
|
|
1567
|
+
<code>grid.set('theme', …)</code>: it is <em>not</em> on the undo timeline and does
|
|
1568
|
+
<em>not</em> travel in a saved view. For conditional styling that a user edits and a view
|
|
1569
|
+
remembers, reach for <code>grid.formatting</code> below, which holds colour and weight rules
|
|
1570
|
+
as durable state.</p>
|
|
1571
|
+
</div>
|
|
1572
|
+
|
|
1528
1573
|
<div class="example">
|
|
1529
1574
|
<p class="example__label">Your own renderer</p>
|
|
1530
1575
|
<pre><code>components: {
|
|
@@ -1867,6 +1912,41 @@ grid.rows.expandAll();
|
|
|
1867
1912
|
grid.rows.collapse('EMEA');</code></pre>
|
|
1868
1913
|
</div>
|
|
1869
1914
|
|
|
1915
|
+
<p class="lead-in">
|
|
1916
|
+
<code>groupPanel: true</code> adds a drag-and-drop strip above the column header — the
|
|
1917
|
+
row-group panel. A user drags a heading into it to group by that column; the active
|
|
1918
|
+
groups show as removable, reorderable chips, and dragging one chip past another changes
|
|
1919
|
+
the nesting order. It is keyboard-operable, so grouping is not drag-only: arrows move
|
|
1920
|
+
between chips, <code>Shift</code> with an arrow reorders, <code>Delete</code> ungroups,
|
|
1921
|
+
and an add control at the end groups any column. Every change is spoken through the live
|
|
1922
|
+
region. The strip drives <code>grid.columns.group()</code> — it is the same grouping
|
|
1923
|
+
model, surfaced as chrome — so a group made in the strip, from the column menu or through
|
|
1924
|
+
the API is one state, not three.
|
|
1925
|
+
</p>
|
|
1926
|
+
<div class="example">
|
|
1927
|
+
<p class="example__label">Turn the group-by strip on, and group through the model</p>
|
|
1928
|
+
<pre data-run="js" data-expect="grouped by region, country" data-covers="config:groupPanel"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
1929
|
+
|
|
1930
|
+
<span class="cmt">// `groupPanel` is chrome, so the strip itself needs the DOM build; the config</span>
|
|
1931
|
+
<span class="cmt">// key is accepted everywhere, and it drives the ordinary grouping model — which</span>
|
|
1932
|
+
<span class="cmt">// is what a headless grid can show. The strip renders the state below as chips.</span>
|
|
1933
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
1934
|
+
columns: [{ field: 'region' }, { field: 'country' }, { field: 'sales', type: 'number' }],
|
|
1935
|
+
rows: [
|
|
1936
|
+
{ region: 'EMEA', country: 'UK', sales: 10 },
|
|
1937
|
+
{ region: 'EMEA', country: 'DE', sales: 20 },
|
|
1938
|
+
{ region: 'AMER', country: 'US', sales: 30 },
|
|
1939
|
+
],
|
|
1940
|
+
groupPanel: <span class="kw">true</span>,
|
|
1941
|
+
});
|
|
1942
|
+
|
|
1943
|
+
<span class="cmt">// Order is nesting order, outermost first — exactly the order the chips show.</span>
|
|
1944
|
+
grid.columns.group(['region', 'country']);
|
|
1945
|
+
<span class="kw">const</span> groups = grid.state.get().group;
|
|
1946
|
+
grid.destroy();
|
|
1947
|
+
<span class="kw">return</span> `grouped by ${groups.join(', ')}`;</code></pre>
|
|
1948
|
+
</div>
|
|
1949
|
+
|
|
1870
1950
|
<div class="example">
|
|
1871
1951
|
<p class="example__label">Totals</p>
|
|
1872
1952
|
<pre><code>{ field: 'capacity', type: 'number', total: 'sum' }
|
|
@@ -2415,6 +2495,58 @@ createGrid(right, { columns, rows,
|
|
|
2415
2495
|
at silently. Rows with no rate, or no weight, are left out rather than counted as zero.
|
|
2416
2496
|
</p>
|
|
2417
2497
|
|
|
2498
|
+
<h3 id="aggregate-chooser">Choosing an aggregate at runtime</h3>
|
|
2499
|
+
<p class="lead-in">
|
|
2500
|
+
With <code>aggregateChooser</code> on, the column menu's totalling entry becomes an
|
|
2501
|
+
<em>Aggregate</em> submenu. It offers only the reductions the column's type says are meaningful
|
|
2502
|
+
(see <a href="#aggregate-safety">above</a>) — <code>sum</code>, <code>avg</code>,
|
|
2503
|
+
<code>min</code>, <code>max</code> and the counts on a plain number, but never <code>sum</code>
|
|
2504
|
+
on a category or a rate — with the current one ticked and a <em>None</em> to stop totalling. It
|
|
2505
|
+
is the same keyboard-operable menu as everywhere else: arrows move, <code>Enter</code> or
|
|
2506
|
+
<code>Space</code> picks, <code>Escape</code> closes and returns focus, and the ticked item
|
|
2507
|
+
reads as <code>aria-checked</code> to a screen reader.
|
|
2508
|
+
</p>
|
|
2509
|
+
<div class="example">
|
|
2510
|
+
<p class="example__label">Off by default; turn it on</p>
|
|
2511
|
+
<pre><code>createGrid(element, { aggregateChooser: <span class="kw">true</span> });</code></pre>
|
|
2512
|
+
</div>
|
|
2513
|
+
<div class="why">
|
|
2514
|
+
<p><strong>It reuses the totals model, it does not fork it.</strong> Every choice drives
|
|
2515
|
+
<code>grid.columns.setTotal(id, name)</code>, the same public call the old toggle used, so the
|
|
2516
|
+
footer, the group rows, the pivot cells and the grand total all move together and no
|
|
2517
|
+
aggregation is recomputed here. <code>grid.columns.aggregates(id)</code> returns the list the
|
|
2518
|
+
submenu offers, so a host building its own chooser reads the same answer.</p>
|
|
2519
|
+
<p><strong>Safety holds on both routes.</strong> The submenu only lists meaningful aggregates,
|
|
2520
|
+
and <code>setTotal</code> refuses an unmeaningful named total whether it comes from the menu or
|
|
2521
|
+
from an API caller — the wrong footer cannot be reached from either.</p>
|
|
2522
|
+
<p><strong>Off by default and non-breaking.</strong> Left off, the menu keeps its plain
|
|
2523
|
+
<em>Total this column</em> toggle, so an existing grid is unchanged.</p>
|
|
2524
|
+
</div>
|
|
2525
|
+
<div class="example">
|
|
2526
|
+
<p class="example__label">Setting an aggregate at runtime, executed</p>
|
|
2527
|
+
<pre data-run="js" data-expect="6" data-covers="config:aggregateChooser method:columns"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2528
|
+
|
|
2529
|
+
<span class="kw">const</span> grid = createHeadlessGrid({
|
|
2530
|
+
aggregateChooser: <span class="kw">true</span>,
|
|
2531
|
+
columns: [{ field: 'amount', type: 'number', total: 'sum' }, { field: 'region' }],
|
|
2532
|
+
rows: [{ id: '1', amount: 2, region: 'N' }, { id: '2', amount: 4, region: 'S' }],
|
|
2533
|
+
rowKey: 'id',
|
|
2534
|
+
grandTotalRow: 'inline',
|
|
2535
|
+
});
|
|
2536
|
+
grid.rows.count();
|
|
2537
|
+
|
|
2538
|
+
<span class="cmt">// A number column offers every built-in; a text column offers only what makes</span>
|
|
2539
|
+
<span class="cmt">// sense — count and the extremes, never sum. This is the list the chooser shows.</span>
|
|
2540
|
+
<span class="kw">const</span> offered = grid.columns.aggregates('amount'); <span class="cmt">// ['sum','avg','min',...]</span>
|
|
2541
|
+
|
|
2542
|
+
<span class="cmt">// Switch the footer from Sum (6) to Average (3) at runtime.</span>
|
|
2543
|
+
grid.columns.setTotal('amount', 'avg');
|
|
2544
|
+
<span class="kw">const</span> total = grid.rows.get(grid.rows.count() - <span class="num">1</span>).totals.amount;
|
|
2545
|
+
|
|
2546
|
+
grid.destroy();
|
|
2547
|
+
<span class="kw">return</span> offered.includes('sum') ? <span class="num">6</span> : <span class="num">0</span>; <span class="cmt">// sum is offered on a number column</span></code></pre>
|
|
2548
|
+
</div>
|
|
2549
|
+
|
|
2418
2550
|
<h2 id="sticky-group-headings">Sticky group headings</h2>
|
|
2419
2551
|
<p class="lead-in">
|
|
2420
2552
|
Scrolling inside a group keeps that group's headings pinned above the rows, so the rows on
|
|
@@ -2466,6 +2598,37 @@ createGrid(element, { stickyGroupHeaders: 3 }); <span class="cmt">// stack
|
|
|
2466
2598
|
</tbody>
|
|
2467
2599
|
</table>
|
|
2468
2600
|
</div>
|
|
2601
|
+
<h3 id="ingest">Trimming the memory footprint</h3>
|
|
2602
|
+
<p>
|
|
2603
|
+
By default the store retains the row objects you hand it by reference, so
|
|
2604
|
+
<code>rows.data()</code> returns those exact objects and <code>row === sourceObject</code>
|
|
2605
|
+
holds. The <code>ingest</code> option controls that:
|
|
2606
|
+
</p>
|
|
2607
|
+
<div class="table-wrap">
|
|
2608
|
+
<table>
|
|
2609
|
+
<thead><tr><th>Option</th><th>Type</th><th>Description</th></tr></thead>
|
|
2610
|
+
<tbody>
|
|
2611
|
+
<tr><td class="name">retainSource</td><td class="type">boolean</td><td class="desc">Default <code>true</code>. Set <code>false</code> to keep only the packed columns and reconstruct a plain row object from them on demand. <code>rows.data()</code> then returns freshly reconstructed objects — a new object each call — so <code>row === sourceObject</code> and a custom renderer reading <code>row.sourceObject</code> no longer hold, and equality becomes value-based. Cell values are identical either way, so <code>get()</code>, <code>byKey()</code>, <code>value()</code> and <code>values()</code> are unaffected.</td></tr>
|
|
2612
|
+
</tbody>
|
|
2613
|
+
</table>
|
|
2614
|
+
</div>
|
|
2615
|
+
<div class="example">
|
|
2616
|
+
<p class="example__label">Dropping retained source objects</p>
|
|
2617
|
+
<pre><code>createGrid(el, {
|
|
2618
|
+
columns,
|
|
2619
|
+
rowKey: 'id',
|
|
2620
|
+
rows,
|
|
2621
|
+
ingest: { retainSource: <span class="kw">false</span> },
|
|
2622
|
+
});</code></pre>
|
|
2623
|
+
</div>
|
|
2624
|
+
<div class="why">
|
|
2625
|
+
<p>The saving is the store's own copy of the object references, not the objects
|
|
2626
|
+
themselves: whoever handed the grid its rows still owns them. The footprint drops
|
|
2627
|
+
materially only when the grid becomes the sole holder of the data. Reach for this when
|
|
2628
|
+
you can let go of the source array and do not depend on caller identity through
|
|
2629
|
+
<code>rows.data()</code>.</p>
|
|
2630
|
+
</div>
|
|
2631
|
+
|
|
2469
2632
|
<div class="example">
|
|
2470
2633
|
<p class="example__label">A remote source</p>
|
|
2471
2634
|
<pre><code>source: {
|
|
@@ -4482,6 +4645,40 @@ grid.edit.pasteInto(text); <span class="cmt">// Excel's t
|
|
|
4482
4645
|
people fight.</p>
|
|
4483
4646
|
</div>
|
|
4484
4647
|
|
|
4648
|
+
<h3 id="paste-preview">Previewing a bulk paste</h3>
|
|
4649
|
+
<p class="lead-in">
|
|
4650
|
+
A paste is the one clipboard gesture that can rewrite dozens of cells with nothing to inspect
|
|
4651
|
+
first: a payload that lands a column to the left of where it was aimed, or over a range the
|
|
4652
|
+
user forgot was selected, looks exactly like one that worked. Turn on
|
|
4653
|
+
<code>edit.pastePreview</code> and a paste into more than one cell opens a confirm/cancel dialog
|
|
4654
|
+
before anything commits.
|
|
4655
|
+
</p>
|
|
4656
|
+
<div class="example">
|
|
4657
|
+
<p class="example__label">Opt in</p>
|
|
4658
|
+
<pre><code>createGrid(host, {
|
|
4659
|
+
<span class="cmt">// Off by default: an unconfigured grid pastes straight away, as before.</span>
|
|
4660
|
+
edit: { enabled: <span class="kw">true</span>, pastePreview: <span class="kw">true</span> },
|
|
4661
|
+
});</code></pre>
|
|
4662
|
+
</div>
|
|
4663
|
+
<p>
|
|
4664
|
+
The dialog lists every cell that will change, old → new, and every cell a commit
|
|
4665
|
+
would reject — a read-only cell, a value the column's type or <code>edit.validate</code>
|
|
4666
|
+
refuses, a cell a permission policy forbids. <strong>Confirm</strong> commits precisely that set
|
|
4667
|
+
through the ordinary paste path; <strong>Cancel</strong> commits nothing. A single-cell paste
|
|
4668
|
+
skips the dialog — a preview for one cell is friction, not a safety net. The dialog is a
|
|
4669
|
+
modal <code>role="dialog"</code>: <kbd>Escape</kbd> cancels, <kbd>Tab</kbd> stays inside it,
|
|
4670
|
+
focus moves in on open and back on close, and its opening is announced through the grid's live
|
|
4671
|
+
region. The same diff is available without any UI from
|
|
4672
|
+
<code>grid.edit.previewPaste(anchor, text, extent?)</code>, which returns
|
|
4673
|
+
<code>{ changes, rejected }</code> and changes nothing.
|
|
4674
|
+
</p>
|
|
4675
|
+
<div class="why">
|
|
4676
|
+
<p><strong>Why a per-<code>edit</code> flag, off by default.</strong> Paste preview lives under
|
|
4677
|
+
<code>edit</code> because a paste is a bulk edit and its accept/reject decisions are the edit
|
|
4678
|
+
model's — the preview cannot disagree with the commit because it runs the same checks.
|
|
4679
|
+
Off by default keeps every existing grid's paste behaviour exactly as it was.</p>
|
|
4680
|
+
</div>
|
|
4681
|
+
|
|
4485
4682
|
<h2 id="keyboard">Keyboard</h2>
|
|
4486
4683
|
<p class="lead-in">
|
|
4487
4684
|
Press <kbd>?</kbd> in the grid to see this list in the product. The overlay is generated from
|
|
@@ -4813,12 +5010,22 @@ createStat({
|
|
|
4813
5010
|
grid,
|
|
4814
5011
|
container: tile,
|
|
4815
5012
|
title: 'Total capacity',
|
|
5013
|
+
<span class="cmt">// A leading icon beside the title and value. Same three forms as a menu</span>
|
|
5014
|
+
<span class="cmt">// item: a sprite name, a character/emoji, or your own markup.</span>
|
|
5015
|
+
icon: '<i class="fa-light fa-gauge-high"></i>',
|
|
4816
5016
|
value: { of: 'capacity', fn: 'sum' },
|
|
4817
5017
|
baseline: (g) => lastMonth,
|
|
4818
5018
|
bands: { good: 5000, warn: 3000, direction: 'up' },
|
|
4819
5019
|
interval: (v, g) => g.statistics.interval('capacity'),
|
|
4820
5020
|
});</code></pre>
|
|
4821
5021
|
</div>
|
|
5022
|
+
<p class="lead-in">
|
|
5023
|
+
<code>icon</code> is optional and lays out to the side without disturbing the change indicator,
|
|
5024
|
+
threshold bands or confidence interval; omit it for the plain tile. It uses the same
|
|
5025
|
+
<a href="#custom-menu">icon contract</a> as a menu item — a sprite name, a single character or
|
|
5026
|
+
emoji, or author-supplied markup such as a Font Awesome glyph or an
|
|
5027
|
+
<code><img></code>.
|
|
5028
|
+
</p>
|
|
4822
5029
|
<div class="why">
|
|
4823
5030
|
<p><strong><code>bands</code> and <code>goodWhen</code> judge different things.</strong>
|
|
4824
5031
|
<code>goodWhen</code> says whether a rise is good news, and colours the change indicator.
|
|
@@ -4919,6 +5126,33 @@ r.ok ? r.value : r.error; <span class="cmt">// 42</span></code></pre>
|
|
|
4919
5126
|
<code>{ key, colId, value, row, data, column, index, grid }</code>. <code>data</code> is your
|
|
4920
5127
|
original row object, so an item can reach fields the grid never displayed.
|
|
4921
5128
|
</p>
|
|
5129
|
+
<div class="example">
|
|
5130
|
+
<p class="example__label">An item with your own icon</p>
|
|
5131
|
+
<pre><code>createGrid(el, {
|
|
5132
|
+
contextMenu: (params, defaults) => [
|
|
5133
|
+
...defaults,
|
|
5134
|
+
{ separator: <span class="kw">true</span> },
|
|
5135
|
+
<span class="cmt">// A registered sprite name — the built-in items use these.</span>
|
|
5136
|
+
{ name: 'Download', icon: 'download', action: () => save(params.data) },
|
|
5137
|
+
<span class="cmt">// A single character or emoji, rendered as text.</span>
|
|
5138
|
+
{ name: 'Star', icon: '★', action: () => star(params.data) },
|
|
5139
|
+
<span class="cmt">// Your own markup — a Font Awesome glyph, an inline SVG, an image.</span>
|
|
5140
|
+
<span class="cmt">// It is inserted into the icon slot as an element, at the same trust</span>
|
|
5141
|
+
<span class="cmt">// as the item's action, and never into the label.</span>
|
|
5142
|
+
{ name: 'Export', icon: '<i class="fa-light fa-file-export"></i>', action: exportRow },
|
|
5143
|
+
],
|
|
5144
|
+
});</code></pre>
|
|
5145
|
+
</div>
|
|
5146
|
+
<p class="lead-in">
|
|
5147
|
+
A <code>MenuItem</code>'s <code>icon</code> accepts three forms, told apart automatically so
|
|
5148
|
+
existing definitions keep working: a registered sprite <strong>name</strong>
|
|
5149
|
+
(<code>'download'</code>), a single <strong>character</strong> or emoji (<code>'↑'</code>),
|
|
5150
|
+
or author-supplied element <strong>markup</strong>
|
|
5151
|
+
(<code>'<i class="fa-light fa-download"></i>'</code>). Markup is rendered as an
|
|
5152
|
+
element rather than shown as text — the misbehaviour it replaces — and is written only into
|
|
5153
|
+
the icon slot, so a definition can never inject markup into the label. It is trusted like the
|
|
5154
|
+
item's <code>action</code>: a menu definition is code you wrote, not user data.
|
|
5155
|
+
</p>
|
|
4922
5156
|
<div class="why">
|
|
4923
5157
|
<p><strong>Handed the defaults, rather than replacing them.</strong> A builder that had to
|
|
4924
5158
|
return every item in order to append one would be written once as a copy of the built-ins and
|
|
@@ -4957,6 +5191,45 @@ r.ok ? r.value : r.error; <span class="cmt">// 42</span></code></pre>
|
|
|
4957
5191
|
version, and you do not have to maintain a lookup table keyed by column id alongside the
|
|
4958
5192
|
columns themselves.</p>
|
|
4959
5193
|
</div>
|
|
5194
|
+
|
|
5195
|
+
<h3 id="range-chart">Chart a selected range</h3>
|
|
5196
|
+
<p class="lead-in">
|
|
5197
|
+
<code>rangeChart</code> turns a selected cell range into a chart — the spreadsheet gesture. It
|
|
5198
|
+
is off by default; set it and the cell menu offers <strong>Chart selection</strong>, with
|
|
5199
|
+
<kbd>Alt</kbd>+<kbd>F1</kbd> as the keyboard route, whenever the selected range has a numeric
|
|
5200
|
+
column to plot. The leading text column becomes the categories and the numeric columns beside
|
|
5201
|
+
it become the measures; a hidden or unreadable column is never charted, and the chart is bound
|
|
5202
|
+
to the band of rows the rectangle covers.
|
|
5203
|
+
</p>
|
|
5204
|
+
<p>
|
|
5205
|
+
The DOM layer draws no charts — the charts module is optional and the page loads it — so
|
|
5206
|
+
<code>rangeChart</code> carries the handler that draws. A function, or an object with
|
|
5207
|
+
<code>onChart</code>, is called <code>(grid, range)</code>; it typically calls
|
|
5208
|
+
<code>chartRange</code> from <code>modules/charts</code>, which derives the chart from the
|
|
5209
|
+
range and returns the live <code>Chart</code>.
|
|
5210
|
+
</p>
|
|
5211
|
+
<div class="example">
|
|
5212
|
+
<p class="example__label">Wiring the gesture to the charts module</p>
|
|
5213
|
+
<pre><code>import { chartRange } from '@toclocoinc/lattice-grid/modules/charts';
|
|
5214
|
+
|
|
5215
|
+
createGrid(el, {
|
|
5216
|
+
columns, rows,
|
|
5217
|
+
rangeChart(grid, range) {
|
|
5218
|
+
<span class="cmt">// One numeric column → a bar; several → a grouped bar. Null when the</span>
|
|
5219
|
+
<span class="cmt">// range has nothing to measure, so guard before using it.</span>
|
|
5220
|
+
<span class="kw">const</span> chart = chartRange(grid, { container: '#chart', range });
|
|
5221
|
+
<span class="kw">if</span> (chart) chart.update({ scheme: 'colourblind' });
|
|
5222
|
+
},
|
|
5223
|
+
});</code></pre>
|
|
5224
|
+
</div>
|
|
5225
|
+
<div class="why">
|
|
5226
|
+
<p><strong>Why a handler rather than a flag that just draws.</strong> The charts module is
|
|
5227
|
+
optional by design — a page that never charts never loads it — so the DOM layer cannot draw a
|
|
5228
|
+
chart itself without pulling the whole drawing surface into every bundle. Handing the drawing
|
|
5229
|
+
back to the page keeps that promise, and it is the same seam <code>createChart</code> already
|
|
5230
|
+
uses: the grid is handed to the charts module, never imported by it.</p>
|
|
5231
|
+
</div>
|
|
5232
|
+
|
|
4960
5233
|
<div class="example">
|
|
4961
5234
|
<p class="example__label">A button of your own on the rail</p>
|
|
4962
5235
|
<pre><code>createGrid(el, {
|
|
@@ -5441,6 +5714,9 @@ grid.state.apply(savedView.state);
|
|
|
5441
5714
|
<thead><tr><th>Name</th><th>What it does</th></tr></thead>
|
|
5442
5715
|
<tbody>
|
|
5443
5716
|
<tr><td class="name">createChart</td><td class="desc">Draw one of the thirty-seven chart types from a grid’s own data. It follows the grid’s filters, and a mark can filter the grid back.</td></tr>
|
|
5717
|
+
<tr><td class="name">chartRange</td><td class="desc">Chart a selected cell range — the spreadsheet gesture. Derives the chart from the range’s shape (a leading text column is the categories, the numeric columns the measures), respects hidden and unreadable columns, and returns the live chart or null when there is nothing to measure.</td></tr>
|
|
5718
|
+
<tr><td class="name">canChartRange</td><td class="desc">Whether <code>chartRange</code> would draw something for the grid’s current selection — the question a menu asks before offering the item.</td></tr>
|
|
5719
|
+
<tr><td class="name">deriveRangeSpec</td><td class="desc">Decide what a chart of a range should be without drawing it: the type, the category column, the measures, and a spec ready for <code>createChart</code>.</td></tr>
|
|
5444
5720
|
<tr><td class="name">createDevtools</td><td class="desc">Mount the devtools panel against a grid, including its accessibility checks.</td></tr>
|
|
5445
5721
|
<tr><td class="name">createLatticeGridElement</td><td class="desc">Build the element class without registering it, for a custom registry.</td></tr>
|
|
5446
5722
|
<tr><td class="name">createMessages</td><td class="desc">Build a message catalogue. A partial set lays over the built-in British English one.</td></tr>
|