@toclocoinc/lattice-grid 1.14.0 → 1.15.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 +138 -1
- package/docs/api-detail.html +46 -1
- package/lattice-grid.d.ts +28 -2
- package/lattice-grid.esm.min.js +78 -4
- package/lattice-grid.min.cjs +78 -4
- package/lattice-grid.min.css +1 -1
- package/lattice-grid.min.js +78 -4
- package/modules/charts.esm.min.js +2 -2
- package/modules/devtools.esm.min.js +2 -2
- package/modules/dhtmlx-compat.esm.min.js +31 -65164
- package/modules/htmx.esm.min.js +57 -4
- package/modules/htmx.min.cjs +57 -4
- package/modules/htmx.min.js +57 -4
- 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 +78 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
dependencies, no build step required. Optional adapters for React, Vue, Svelte
|
|
5
5
|
and Web Components ship alongside it.
|
|
6
6
|
|
|
7
|
-
Version 1.
|
|
7
|
+
Version 1.15.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -250,7 +250,10 @@ imports this and never the base package as well.
|
|
|
250
250
|
|
|
251
251
|
**dhtmlx.** `modules/dhtmlx-compat` exposes a dhtmlx Grid-shaped API over
|
|
252
252
|
Lattice, for moving an existing integration across a piece at a time rather
|
|
253
|
-
than rewriting it in one go.
|
|
253
|
+
than rewriting it in one go. It shares the core the page already loads rather
|
|
254
|
+
than carrying its own, so load `lattice-grid` alongside it — a bundler wires
|
|
255
|
+
the shared import for you, and a `<script src>` page loads the global build
|
|
256
|
+
first — and a licence set on that core applies to these grids too.
|
|
254
257
|
|
|
255
258
|
### TypeScript
|
|
256
259
|
|
package/docs/API.html
CHANGED
|
@@ -2494,6 +2494,142 @@ createGrid(host, { source, columns: [...] });</code></pre>
|
|
|
2494
2494
|
</table>
|
|
2495
2495
|
</div>
|
|
2496
2496
|
|
|
2497
|
+
<h4 id="adapter-options">What each adapter takes</h4>
|
|
2498
|
+
<p class="section-note">
|
|
2499
|
+
Every adapter is a function of one options object. The tables below list what each accepts, the
|
|
2500
|
+
type, the default where it is not obvious, and what it means. The defaults are the load-bearing
|
|
2501
|
+
part: an adapter is designed to work when handed almost nothing, so most of what you can set is
|
|
2502
|
+
about telling it what your endpoint <em>cannot</em> do rather than switching features on.
|
|
2503
|
+
</p>
|
|
2504
|
+
|
|
2505
|
+
<h5 id="odata-options"><code>odataAdapter</code></h5>
|
|
2506
|
+
<div class="table-wrap">
|
|
2507
|
+
<table>
|
|
2508
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2509
|
+
<tbody>
|
|
2510
|
+
<tr><td class="name">url</td><td class="type">string</td><td class="type">—</td><td class="desc">The entity-set endpoint, e.g. <code>https://api.example.com/Orders</code>. Required.</td></tr>
|
|
2511
|
+
<tr><td class="name">headers</td><td class="type">Record<string, string></td><td class="type">{}</td><td class="desc">Sent on every request, merged over <code>Accept: application/json</code>. This is where a fixed bearer token or an API key goes. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2512
|
+
<tr><td class="name">fetch</td><td class="type">typeof fetch</td><td class="type">the global <code>fetch</code></td><td class="desc">Your own fetch, for a token that expires, a proxy, or a non-browser runtime. The adapter bundles no HTTP client. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2513
|
+
<tr><td class="name">count</td><td class="type">boolean</td><td class="type">true</td><td class="desc">Whether to ask for <code>$count=true</code> and read <code>@odata.count</code>. On by default because the grid sizes its scrollbar from the total; set <code>false</code> for a server that does not support it.</td></tr>
|
|
2514
|
+
<tr><td class="name">search</td><td class="type">boolean</td><td class="type">false</td><td class="desc">Whether the server implements <code>$search</code>. Off by default, so quick-filter text stays with the grid until you confirm the endpoint honours it; <code>true</code> pushes it as <code>$search</code>.</td></tr>
|
|
2515
|
+
</tbody>
|
|
2516
|
+
</table>
|
|
2517
|
+
</div>
|
|
2518
|
+
|
|
2519
|
+
<h5 id="rest-options"><code>restAdapter</code></h5>
|
|
2520
|
+
<p class="section-note">
|
|
2521
|
+
<strong>The parameter names are yours, and the defaults are not zero.</strong> Paging and
|
|
2522
|
+
sorting are assumed present; filtering is assumed <em>absent</em> until you declare
|
|
2523
|
+
<code>operators</code>, because an adapter that claims to filter when the endpoint ignores it
|
|
2524
|
+
returns the wrong rows silently. The query-string names default to
|
|
2525
|
+
<code>offset</code>, <code>limit</code>, <code>sort</code>, <code>order</code>,
|
|
2526
|
+
<code>filter</code> and <code>q</code> (search); <code>params</code> overrides any of them.
|
|
2527
|
+
</p>
|
|
2528
|
+
<div class="table-wrap">
|
|
2529
|
+
<table>
|
|
2530
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2531
|
+
<tbody>
|
|
2532
|
+
<tr><td class="name">url</td><td class="type">string</td><td class="type">—</td><td class="desc">The endpoint, e.g. <code>/api/orders</code>. Required.</td></tr>
|
|
2533
|
+
<tr><td class="name">headers</td><td class="type">Record<string, string></td><td class="type">{}</td><td class="desc">Sent on every request, merged over <code>Accept: application/json</code>. Where a fixed token or key goes. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2534
|
+
<tr><td class="name">fetch</td><td class="type">typeof fetch</td><td class="type">the global <code>fetch</code></td><td class="desc">Your own fetch, for an expiring token, a proxy or a non-browser runtime. See <a href="#adapter-auth">authenticating</a>.</td></tr>
|
|
2535
|
+
<tr><td class="name">params</td><td class="type">Partial<Record<'offset'|'limit'|'sort'|'order'|'filter'|'search', string>></td><td class="type">{ offset:'offset', limit:'limit', sort:'sort', order:'order', filter:'filter', search:'q' }</td><td class="desc">Renames the query-string keys to whatever your endpoint already reads. Only the keys you name change; the rest keep the defaults above.</td></tr>
|
|
2536
|
+
<tr><td class="name">capabilities</td><td class="type">PushdownCapabilities</td><td class="type">{ range:true, total:true, sort:'multi', filter:false, quick:false }</td><td class="desc">What the endpoint can answer, merged over the defaults. Declaring <code>operators</code> is the usual way to turn filtering on; reach for this to switch off paging or sorting an endpoint cannot do.</td></tr>
|
|
2537
|
+
<tr><td class="name">operators</td><td class="type">string[]</td><td class="type">— (filtering off)</td><td class="desc">The comparisons the endpoint genuinely applies, e.g. <code>['eq','gt','lt','contains']</code>. Setting it turns filtering on as a <code>tree</code>; a condition using any other operator stays with the grid.</td></tr>
|
|
2538
|
+
<tr><td class="name">encodeFilter</td><td class="type">(filters: object) => string</td><td class="type"><code>JSON.stringify</code></td><td class="desc">How the pushed condition tree becomes the <code>filter</code> parameter's value. Override it to emit whatever query language your service parses instead of JSON.</td></tr>
|
|
2539
|
+
<tr><td class="name">rows</td><td class="type">(body: unknown) => unknown[]</td><td class="type">body itself if an array, else <code>body.rows</code> then <code>body.data</code></td><td class="desc">Pulls the row array out of the response body, for an envelope that nests it somewhere else.</td></tr>
|
|
2540
|
+
<tr><td class="name">total</td><td class="type">(body: unknown, rows: unknown[]) => number</td><td class="type"><code>body.total</code> then <code>body.count</code>, else the page length</td><td class="desc">Reads the count of <em>all</em> matching rows, not the page. The grid sizes its scrollbar from it, so a page-sized total makes a large result look like one page.</td></tr>
|
|
2541
|
+
</tbody>
|
|
2542
|
+
</table>
|
|
2543
|
+
</div>
|
|
2544
|
+
|
|
2545
|
+
<h5 id="duckdb-options"><code>duckdbAdapter</code></h5>
|
|
2546
|
+
<div class="table-wrap">
|
|
2547
|
+
<table>
|
|
2548
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2549
|
+
<tbody>
|
|
2550
|
+
<tr><td class="name">connection</td><td class="type">object</td><td class="type">—</td><td class="desc">A live connection exposing <code>query</code>, and ideally <code>prepare</code>. Required. A connection without <code>prepare</code> is used only for unfiltered queries, because interpolating a user's filter into SQL is worse than not filtering.</td></tr>
|
|
2551
|
+
<tr><td class="name">from</td><td class="type">string</td><td class="type">—</td><td class="desc">A table, a view, or any FROM expression. Required. <code>read_parquet('s3://bucket/*.parquet')</code> is as valid as a table name.</td></tr>
|
|
2552
|
+
<tr><td class="name">fields</td><td class="type">string[]</td><td class="type">everything (<code>SELECT *</code>)</td><td class="desc">The columns to select. Name them to narrow the projection when the grid shows a subset of a wide table.</td></tr>
|
|
2553
|
+
</tbody>
|
|
2554
|
+
</table>
|
|
2555
|
+
</div>
|
|
2556
|
+
|
|
2557
|
+
<h5 id="dfql-options"><code>dfqlAdapter</code></h5>
|
|
2558
|
+
<div class="table-wrap">
|
|
2559
|
+
<table>
|
|
2560
|
+
<thead><tr><th>Option</th><th>Type</th><th>Default</th><th>Meaning</th></tr></thead>
|
|
2561
|
+
<tbody>
|
|
2562
|
+
<tr><td class="name">entity</td><td class="type">string</td><td class="type">—</td><td class="desc">The DemandFlow entity to query. Required.</td></tr>
|
|
2563
|
+
<tr><td class="name">token</td><td class="type">string</td><td class="type">—</td><td class="desc">A personal access token, sent as the bearer credential. Required. Never commit one; read it from configuration at runtime.</td></tr>
|
|
2564
|
+
<tr><td class="name">url</td><td class="type">string</td><td class="type"><code>https://rest.demandflow.com</code></td><td class="desc">The API base, for a non-default region or a self-hosted deployment.</td></tr>
|
|
2565
|
+
<tr><td class="name">comboKey</td><td class="type">'comboKey' | 'comboKey2' | 'comboKey3'</td><td class="type"><code>comboKey</code></td><td class="desc">The name of the key attribute to match on. <code>comboKey</code> is the standard hierarchy.</td></tr>
|
|
2566
|
+
<tr><td class="name">query</td><td class="type">string</td><td class="type"><code>SUB</code></td><td class="desc">The prefix matched against the key attribute. <code>SUB</code> alone means every record of the entity in the tenant.</td></tr>
|
|
2567
|
+
<tr><td class="name">load</td><td class="type">string[]</td><td class="type">everything</td><td class="desc">Fields to project, which saves bandwidth but not query cost.</td></tr>
|
|
2568
|
+
<tr><td class="name">limit</td><td class="type">number</td><td class="type">server default</td><td class="desc">Caps rows <em>scanned</em>, not matched — which is why every request also sends <code>countOnly</code> to reveal the true match count.</td></tr>
|
|
2569
|
+
<tr><td class="name">headers</td><td class="type">Record<string, string></td><td class="type">{}</td><td class="desc">Extra headers merged over the bearer token, for a gateway that needs its own.</td></tr>
|
|
2570
|
+
<tr><td class="name">fetch</td><td class="type">typeof fetch</td><td class="type">the global <code>fetch</code></td><td class="desc">Your own fetch, for a proxy or a non-browser runtime.</td></tr>
|
|
2571
|
+
</tbody>
|
|
2572
|
+
</table>
|
|
2573
|
+
</div>
|
|
2574
|
+
|
|
2575
|
+
<h4 id="adapter-auth">Authenticating a remote adapter</h4>
|
|
2576
|
+
<p class="section-note">
|
|
2577
|
+
Two shapes cover almost every endpoint. A <strong>fixed credential</strong> — an API key or a
|
|
2578
|
+
long-lived token — goes in <code>headers</code>, which <code>odataAdapter</code> and
|
|
2579
|
+
<code>restAdapter</code> send on every request. A credential that <strong>expires</strong> —
|
|
2580
|
+
a short-lived bearer token you refresh — goes in a custom <code>fetch</code>, which is the one
|
|
2581
|
+
place that can mint a fresh value per request. <code>dfqlAdapter</code> takes its
|
|
2582
|
+
<code>token</code> directly, and <code>headers</code> for anything a gateway adds on top.
|
|
2583
|
+
</p>
|
|
2584
|
+
<p class="section-note">
|
|
2585
|
+
<strong>A fixed token in <code>headers</code>.</strong> The map is sent on every request, so an
|
|
2586
|
+
<code>Authorization</code> header authenticates the whole grid. Below, a custom
|
|
2587
|
+
<code>fetch</code> stands in for the network only so the example can prove the header arrived:
|
|
2588
|
+
</p>
|
|
2589
|
+
<pre data-run="js" data-expect="Bearer static-token-123" data-covers="export:odataAdapter"><code><span class="kw">const</span> { odataAdapter } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2590
|
+
|
|
2591
|
+
<span class="kw">let</span> seen;
|
|
2592
|
+
<span class="kw">const</span> adapter = odataAdapter({
|
|
2593
|
+
url: 'https://api.example.com/Orders',
|
|
2594
|
+
<span class="cmt">// A fixed credential authenticates every request.</span>
|
|
2595
|
+
headers: { Authorization: 'Bearer static-token-123' },
|
|
2596
|
+
<span class="cmt">// Only here to capture what the adapter sent; in a browser, omit it.</span>
|
|
2597
|
+
fetch: <span class="kw">async</span> (url, init) => {
|
|
2598
|
+
seen = init.headers.Authorization;
|
|
2599
|
+
<span class="kw">return</span> { ok: <span class="kw">true</span>, json: <span class="kw">async</span> () => ({ value: [], '@odata.count': 0 }) };
|
|
2600
|
+
},
|
|
2601
|
+
});
|
|
2602
|
+
|
|
2603
|
+
<span class="kw">await</span> adapter.execute({ range: { start: 0, end: 20 } }, {});
|
|
2604
|
+
<span class="kw">return</span> seen; <span class="cmt">// the header reached the request</span></code></pre>
|
|
2605
|
+
|
|
2606
|
+
<p class="section-note">
|
|
2607
|
+
<strong>An expiring token in a custom <code>fetch</code>.</strong> A token with a lifetime cannot
|
|
2608
|
+
sit in a fixed map, because the map is read once and the token outlives no request that matters.
|
|
2609
|
+
A custom <code>fetch</code> is called afresh for every request, so it is where you refresh the
|
|
2610
|
+
credential and set the header on the outgoing call:
|
|
2611
|
+
</p>
|
|
2612
|
+
<pre data-run="js" data-expect="Bearer token-2" data-covers="export:restAdapter"><code><span class="kw">const</span> { restAdapter } = <span class="kw">await</span> import('../packages/core/src/index.js');
|
|
2613
|
+
|
|
2614
|
+
<span class="cmt">// Stands in for a token service that hands out a new value each time.</span>
|
|
2615
|
+
<span class="kw">let</span> issued = 0;
|
|
2616
|
+
<span class="kw">const</span> freshToken = <span class="kw">async</span> () => `token-${++issued}`;
|
|
2617
|
+
|
|
2618
|
+
<span class="kw">let</span> lastAuth;
|
|
2619
|
+
<span class="kw">const</span> adapter = restAdapter({
|
|
2620
|
+
url: '/api/orders',
|
|
2621
|
+
fetch: <span class="kw">async</span> (url, init) => {
|
|
2622
|
+
<span class="cmt">// Refreshed per request, then merged over whatever headers the adapter set.</span>
|
|
2623
|
+
<span class="kw">const</span> headers = { ...init.headers, Authorization: `Bearer ${<span class="kw">await</span> freshToken()}` };
|
|
2624
|
+
lastAuth = headers.Authorization;
|
|
2625
|
+
<span class="kw">return</span> { ok: <span class="kw">true</span>, json: <span class="kw">async</span> () => ({ rows: [], total: 0 }) };
|
|
2626
|
+
},
|
|
2627
|
+
});
|
|
2628
|
+
|
|
2629
|
+
<span class="kw">await</span> adapter.execute({ range: { start: 0, end: 20 } }, {}); <span class="cmt">// token-1</span>
|
|
2630
|
+
<span class="kw">await</span> adapter.execute({ range: { start: 20, end: 40 } }, {}); <span class="cmt">// token-2</span>
|
|
2631
|
+
<span class="kw">return</span> lastAuth; <span class="cmt">// a fresh token on the second request</span></code></pre>
|
|
2632
|
+
|
|
2497
2633
|
<h4 id="pushdown-cookbook">Wiring it to the API you already have</h4>
|
|
2498
2634
|
<p class="section-note">
|
|
2499
2635
|
Most data sits behind a service someone on your team wrote. The adapter below sends four
|
|
@@ -5427,7 +5563,7 @@ grid.destroy();
|
|
|
5427
5563
|
<thead><tr><th>Member</th><th>Type</th><th>Description</th></tr></thead>
|
|
5428
5564
|
<tbody>
|
|
5429
5565
|
<tr><td class="name">name</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5430
|
-
<tr><td class="name">icon</td><td class="type">string</td><td class="desc"
|
|
5566
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">An icon shown in the slot before the label. Three forms, told apart without a second option so existing definitions keep working: a registered sprite name (`'download'`), a single character or emoji (`'↑'`), or author-trusted element markup (`'<i class="fa-light fa-download"></i>'`), which is rendered as an element rather than shown as text. Markup is inserted into the icon slot only — never the label — at the same trust as `action`. <small>(optional)</small></td></tr>
|
|
5431
5567
|
<tr><td class="name">shortcut</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5432
5568
|
<tr><td class="name">action</td><td class="type">() => void</td><td class="desc"><small>(optional)</small></td></tr>
|
|
5433
5569
|
<tr><td class="name">disabled</td><td class="type">boolean</td><td class="desc"><small>(optional)</small></td></tr>
|
|
@@ -6205,6 +6341,7 @@ grid.destroy();
|
|
|
6205
6341
|
<tr><td class="name">grid</td><td class="type">Grid</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6206
6342
|
<tr><td class="name">container</td><td class="type">HTMLElement | string</td><td class="desc">An element, or a CSS selector resolved against the grid's document.</td></tr>
|
|
6207
6343
|
<tr><td class="name">title</td><td class="type">string</td><td class="desc"><small>(optional)</small></td></tr>
|
|
6344
|
+
<tr><td class="name">icon</td><td class="type">string</td><td class="desc">An optional leading icon beside the title and value, using the same value contract as a menu item: a registered sprite name, a single character or emoji, or author-trusted element markup (`'<i class="fa-light fa-bolt"> </i>'`, an `<img>`). It lays out to the side without disturbing the change indicator, threshold bands or confidence interval; omit it for the plain tile layout. <small>(optional)</small></td></tr>
|
|
6208
6345
|
<tr><td class="name">value</td><td class="type">unknown | StatValueSpec | ((grid: Grid) => unknown)</td><td class="desc">A literal value, a spec to reduce, or a function of the grid. <small>(optional)</small></td></tr>
|
|
6209
6346
|
<tr><td class="name">footer</td><td class="type">string | ((value: unknown, grid: Grid) => string)</td><td class="desc">Text under the value, or a function of it. <small>(optional)</small></td></tr>
|
|
6210
6347
|
<tr><td class="name">baseline</td><td class="type">number | ((grid: Grid) => number)</td><td class="desc">What the value is compared against, for the change indicator. <small>(optional)</small></td></tr>
|
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.15.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>
|
|
@@ -4813,12 +4821,22 @@ createStat({
|
|
|
4813
4821
|
grid,
|
|
4814
4822
|
container: tile,
|
|
4815
4823
|
title: 'Total capacity',
|
|
4824
|
+
<span class="cmt">// A leading icon beside the title and value. Same three forms as a menu</span>
|
|
4825
|
+
<span class="cmt">// item: a sprite name, a character/emoji, or your own markup.</span>
|
|
4826
|
+
icon: '<i class="fa-light fa-gauge-high"></i>',
|
|
4816
4827
|
value: { of: 'capacity', fn: 'sum' },
|
|
4817
4828
|
baseline: (g) => lastMonth,
|
|
4818
4829
|
bands: { good: 5000, warn: 3000, direction: 'up' },
|
|
4819
4830
|
interval: (v, g) => g.statistics.interval('capacity'),
|
|
4820
4831
|
});</code></pre>
|
|
4821
4832
|
</div>
|
|
4833
|
+
<p class="lead-in">
|
|
4834
|
+
<code>icon</code> is optional and lays out to the side without disturbing the change indicator,
|
|
4835
|
+
threshold bands or confidence interval; omit it for the plain tile. It uses the same
|
|
4836
|
+
<a href="#custom-menu">icon contract</a> as a menu item — a sprite name, a single character or
|
|
4837
|
+
emoji, or author-supplied markup such as a Font Awesome glyph or an
|
|
4838
|
+
<code><img></code>.
|
|
4839
|
+
</p>
|
|
4822
4840
|
<div class="why">
|
|
4823
4841
|
<p><strong><code>bands</code> and <code>goodWhen</code> judge different things.</strong>
|
|
4824
4842
|
<code>goodWhen</code> says whether a rise is good news, and colours the change indicator.
|
|
@@ -4919,6 +4937,33 @@ r.ok ? r.value : r.error; <span class="cmt">// 42</span></code></pre>
|
|
|
4919
4937
|
<code>{ key, colId, value, row, data, column, index, grid }</code>. <code>data</code> is your
|
|
4920
4938
|
original row object, so an item can reach fields the grid never displayed.
|
|
4921
4939
|
</p>
|
|
4940
|
+
<div class="example">
|
|
4941
|
+
<p class="example__label">An item with your own icon</p>
|
|
4942
|
+
<pre><code>createGrid(el, {
|
|
4943
|
+
contextMenu: (params, defaults) => [
|
|
4944
|
+
...defaults,
|
|
4945
|
+
{ separator: <span class="kw">true</span> },
|
|
4946
|
+
<span class="cmt">// A registered sprite name — the built-in items use these.</span>
|
|
4947
|
+
{ name: 'Download', icon: 'download', action: () => save(params.data) },
|
|
4948
|
+
<span class="cmt">// A single character or emoji, rendered as text.</span>
|
|
4949
|
+
{ name: 'Star', icon: '★', action: () => star(params.data) },
|
|
4950
|
+
<span class="cmt">// Your own markup — a Font Awesome glyph, an inline SVG, an image.</span>
|
|
4951
|
+
<span class="cmt">// It is inserted into the icon slot as an element, at the same trust</span>
|
|
4952
|
+
<span class="cmt">// as the item's action, and never into the label.</span>
|
|
4953
|
+
{ name: 'Export', icon: '<i class="fa-light fa-file-export"></i>', action: exportRow },
|
|
4954
|
+
],
|
|
4955
|
+
});</code></pre>
|
|
4956
|
+
</div>
|
|
4957
|
+
<p class="lead-in">
|
|
4958
|
+
A <code>MenuItem</code>'s <code>icon</code> accepts three forms, told apart automatically so
|
|
4959
|
+
existing definitions keep working: a registered sprite <strong>name</strong>
|
|
4960
|
+
(<code>'download'</code>), a single <strong>character</strong> or emoji (<code>'↑'</code>),
|
|
4961
|
+
or author-supplied element <strong>markup</strong>
|
|
4962
|
+
(<code>'<i class="fa-light fa-download"></i>'</code>). Markup is rendered as an
|
|
4963
|
+
element rather than shown as text — the misbehaviour it replaces — and is written only into
|
|
4964
|
+
the icon slot, so a definition can never inject markup into the label. It is trusted like the
|
|
4965
|
+
item's <code>action</code>: a menu definition is code you wrote, not user data.
|
|
4966
|
+
</p>
|
|
4922
4967
|
<div class="why">
|
|
4923
4968
|
<p><strong>Handed the defaults, rather than replacing them.</strong> A builder that had to
|
|
4924
4969
|
return every item in order to append one would be written once as a copy of the built-ins and
|
package/lattice-grid.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Lattice Grid 1.
|
|
2
|
+
* Lattice Grid 1.15.0, type declarations
|
|
3
3
|
* Copyright (c) 2026 TOCLOCO Inc. All rights reserved.
|
|
4
4
|
* https://latticegrid.dev
|
|
5
5
|
*/
|
|
@@ -1592,6 +1592,14 @@ export type PermissionPolicy =
|
|
|
1592
1592
|
|
|
1593
1593
|
export interface MenuItem {
|
|
1594
1594
|
name?: string;
|
|
1595
|
+
/**
|
|
1596
|
+
* An icon shown in the slot before the label. Three forms, told apart without
|
|
1597
|
+
* a second option so existing definitions keep working: a registered sprite
|
|
1598
|
+
* name (`'download'`), a single character or emoji (`'↑'`), or author-trusted
|
|
1599
|
+
* element markup (`'<i class="fa-light fa-download"></i>'`), which is rendered
|
|
1600
|
+
* as an element rather than shown as text. Markup is inserted into the icon
|
|
1601
|
+
* slot only — never the label — at the same trust as `action`.
|
|
1602
|
+
*/
|
|
1595
1603
|
icon?: string;
|
|
1596
1604
|
shortcut?: string;
|
|
1597
1605
|
action?: () => void;
|
|
@@ -3193,6 +3201,15 @@ export interface StatConfig extends StatValueSpec {
|
|
|
3193
3201
|
/** An element, or a CSS selector resolved against the grid's document. */
|
|
3194
3202
|
container: HTMLElement | string;
|
|
3195
3203
|
title?: string;
|
|
3204
|
+
/**
|
|
3205
|
+
* An optional leading icon beside the title and value, using the same value
|
|
3206
|
+
* contract as a menu item: a registered sprite name, a single character or
|
|
3207
|
+
* emoji, or author-trusted element markup (`'<i class="fa-light fa-bolt">
|
|
3208
|
+
* </i>'`, an `<img>`). It lays out to the side without disturbing the change
|
|
3209
|
+
* indicator, threshold bands or confidence interval; omit it for the plain
|
|
3210
|
+
* tile layout.
|
|
3211
|
+
*/
|
|
3212
|
+
icon?: string;
|
|
3196
3213
|
/** A literal value, a spec to reduce, or a function of the grid. */
|
|
3197
3214
|
value?: unknown | StatValueSpec | ((grid: Grid) => unknown);
|
|
3198
3215
|
/** Text under the value, or a function of it. */
|
|
@@ -3824,7 +3841,16 @@ declare module 'lattice-grid/modules/htmx' {
|
|
|
3824
3841
|
}
|
|
3825
3842
|
|
|
3826
3843
|
declare module 'lattice-grid/modules/dhtmlx-compat' {
|
|
3827
|
-
/**
|
|
3844
|
+
/**
|
|
3845
|
+
* A dhtmlx Grid-shaped API over Lattice, for migrating a piece at a time.
|
|
3846
|
+
*
|
|
3847
|
+
* The module shares the page's one core rather than bundling its own: the
|
|
3848
|
+
* grid it builds comes from the `lattice-grid` package the app already loads
|
|
3849
|
+
* (or the `LatticeGrid` global a script tag publishes), so a licence set on
|
|
3850
|
+
* that core applies to these grids too. Load the core alongside this module —
|
|
3851
|
+
* a bundler wires the peer import for you; a `<script src>` page loads the
|
|
3852
|
+
* global build first.
|
|
3853
|
+
*/
|
|
3828
3854
|
export class Grid {
|
|
3829
3855
|
constructor(container: Element | string, config?: object);
|
|
3830
3856
|
}
|