@toclocoinc/lattice-grid 1.26.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -437,7 +437,7 @@
437
437
  <div class="shell">
438
438
  <aside class="rail">
439
439
  <p class="rail__brand">Lattice Grid</p>
440
- <p class="rail__sub">Developer guide · v1.26.0</p>
440
+ <p class="rail__sub">Developer guide · v1.27.0</p>
441
441
  <nav>
442
442
  <div class="rail__group">
443
443
  <span class="rail__label">Start here</span>
@@ -835,10 +835,12 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
835
835
 
836
836
  <span class="kw">const</span> lattice = createLatticeAction({ createGrid });
837
837
  <span class="kw">let</span> rows = [];
838
+ <span class="kw">let</span> grid;
838
839
  &lt;/script&gt;
839
840
 
840
841
  &lt;div
841
- use:lattice={{ columns, rows, rowKey: 'id' }}
842
+ use:lattice={{ columns, rows, rowKey: 'id', onGrid: (g) =&gt; (grid = g) }}
843
+ on:ready={(e) =&gt; (grid = e.detail.grid)}
842
844
  on:cell-changed={(e) =&gt; save(e.detail)}
843
845
  &gt;&lt;/div&gt;</code></pre>
844
846
  </div>
@@ -851,13 +853,92 @@ createGrid(element, { direction: 'rtl' }); <span class="cmt">// or say so out
851
853
  framework runtime behind it. Grid events arrive as <code>CustomEvent</code>s on the node,
852
854
  with the grid event as <code>detail</code>. If you want a component, four lines around this
853
855
  gets you one.</p>
856
+ <p><strong>Reaching the grid.</strong> The action's return value is Svelte's
857
+ <code>{ update, destroy }</code> and cannot carry the instance, so — at parity with React's
858
+ <code>ref.current.grid</code> and Vue's <code>expose({ grid })</code> — there are two documented
859
+ routes to the same object the vanilla examples use, for charts, statistics and imperative
860
+ pivot. Pass an <code>onGrid</code> callback in the action params and it is called once with the
861
+ live grid the moment it is created (synchronously, before the first render); it is consumed by
862
+ the adapter and never reaches the grid config. Or read it off the <code>ready</code> event,
863
+ whose <code>detail</code> carries <code>{ grid }</code> alongside the event's own fields —
864
+ <code>ready</code> fires a turn later, so use <code>onGrid</code> when you need it up front.</p>
865
+ </div>
866
+
867
+ <h3 id="adapters-grid-instance">Accessing the grid instance from your framework</h3>
868
+ <p class="lead-in">
869
+ Anything the adapters do not surface as a prop — <code>grid.export</code>,
870
+ <code>grid.selection</code>, <code>grid.state</code> and the rest — is reached on the live
871
+ grid instance directly. It is the same <code>Grid</code> object <code>createGrid</code>
872
+ returns; the adapter only builds and drives it. There is one documented way to reach it per
873
+ adapter, and it is null until the grid is mounted.
874
+ </p>
875
+ <div class="table-wrap">
876
+ <table>
877
+ <thead><tr><th>Adapter</th><th>How you reach the grid</th></tr></thead>
878
+ <tbody>
879
+ <tr>
880
+ <td class="name">React</td>
881
+ <td>A forwarded ref: <code>ref.current.grid</code>.</td>
882
+ </tr>
883
+ <tr>
884
+ <td class="name">Vue 3</td>
885
+ <td>A template ref calling the exposed method:
886
+ <code>this.$refs.grid.grid()</code> (the component <code>expose</code>s a
887
+ <code>grid()</code> getter).</td>
888
+ </tr>
889
+ <tr>
890
+ <td class="name">Svelte</td>
891
+ <td>Either an <code>onGrid</code> callback in the action params, called with the live grid
892
+ the moment it is created:
893
+ <code>use:lattice={{ ...config, onGrid: (g) =&gt; grid = g }}</code>; or, a turn later,
894
+ the grid on any event's <code>detail</code> — the first <code>ready</code> event hands it
895
+ over: <code>on:ready={(e) =&gt; grid = e.detail.grid}</code>.</td>
896
+ </tr>
897
+ <tr>
898
+ <td class="name">Web Component</td>
899
+ <td>A property on the element: <code>el.grid</code>.</td>
900
+ </tr>
901
+ </tbody>
902
+ </table>
903
+ </div>
904
+ <div class="example">
905
+ <p class="example__label">React</p>
906
+ <pre><code><span class="kw">const</span> ref = React.useRef(null);
907
+ <span class="cmt">// after mount:</span>
908
+ ref.current.grid.export.toCsv();</code></pre>
909
+ </div>
910
+ <div class="example">
911
+ <p class="example__label">Vue 3</p>
912
+ <pre><code>&lt;LatticeGrid ref="grid" :columns="columns" :rows="rows" /&gt;
913
+
914
+ <span class="cmt">// in a method, after mount:</span>
915
+ <span class="kw">this</span>.$refs.grid.grid().export.toCsv();</code></pre>
916
+ </div>
917
+ <div class="example">
918
+ <p class="example__label">Svelte</p>
919
+ <pre><code>&lt;script&gt;
920
+ <span class="kw">let</span> grid;
921
+ &lt;/script&gt;
922
+
923
+ &lt;div
924
+ use:lattice={{ columns, rows, rowKey: 'id' }}
925
+ on:ready={(e) =&gt; grid = e.detail.grid}
926
+ &gt;&lt;/div&gt;
927
+
928
+ &lt;button on:click={() =&gt; grid?.export.toCsv()}&gt;Export&lt;/button&gt;</code></pre>
929
+ </div>
930
+ <div class="example">
931
+ <p class="example__label">Web Component</p>
932
+ <pre><code><span class="kw">const</span> el = document.querySelector('lattice-grid');
933
+ el.grid.export.toCsv();</code></pre>
854
934
  </div>
855
935
 
856
936
  <div class="why">
857
937
  <p><strong>What the adapters do not do.</strong> They add no features and wrap no API: the
858
938
  grid instance is the same object the vanilla examples use, and anything without a prop is
859
- reached through it directly (via the ref in React, <code>expose</code> in Vue, or a
860
- reference you keep in Svelte). Nothing is proxied, so nothing can lag behind the grid.</p>
939
+ reached through it directly via the ref in React, <code>expose</code> in Vue, or
940
+ <code>onGrid</code> / the <code>ready</code> event in Svelte, as the table above shows.
941
+ Nothing is proxied, so nothing can lag behind the grid.</p>
861
942
  </div>
862
943
 
863
944
  <h3>The web component is self-contained: use it or the API, not both</h3>
@@ -1968,6 +2049,47 @@ grid.destroy();
1968
2049
  <span class="kw">return</span> `grouped by ${groups.join(', ')}`;</code></pre>
1969
2050
  </div>
1970
2051
 
2052
+ <p class="lead-in">
2053
+ <code>kpis</code> is the same idea for the tile every dashboard opens with. A
2054
+ <code>createStat</code> renders a KPI tile — a label, a value, its change against a
2055
+ baseline — reading the grid so it agrees with the grid; what it needs is a container and
2056
+ the wiring to place it. <code>kpis</code> is that placement done by the grid: an array of
2057
+ stat specs becomes a labelled band of tiles above the column header, and the grid creates
2058
+ the container for each and drives <code>createStat</code> itself. Each entry takes the
2059
+ fields <code>createStat</code> takes — <code>of</code>, <code>fn</code>, <code>title</code>,
2060
+ <code>interval</code>, <code>footer</code>, <code>format</code> and the rest — minus
2061
+ <code>grid</code> and <code>container</code>, which the grid supplies. The tiles follow the
2062
+ grid's filters, so the strip cannot disagree with the table beneath it. Off by default and
2063
+ free when absent; no <code>kpis</code>, no band.
2064
+ </p>
2065
+ <div class="example">
2066
+ <p class="example__label">A built-in KPI strip, following the grid</p>
2067
+ <pre data-run="js" data-expect="revenue 4200000" data-covers="config:kpis"><code><span class="kw">const</span> { createHeadlessGrid } = <span class="kw">await</span> import('../packages/core/src/index.js');
2068
+
2069
+ <span class="cmt">// `kpis` is chrome: with createGrid it renders a labelled band of stat tiles</span>
2070
+ <span class="cmt">// above the header, each following the grid's filters. The config is accepted</span>
2071
+ <span class="cmt">// everywhere, and each tile reads the same kernel the totals row uses — which</span>
2072
+ <span class="cmt">// is what a headless grid can show.</span>
2073
+ <span class="kw">const</span> grid = createHeadlessGrid({
2074
+ rowKey: 'region',
2075
+ columns: [{ field: 'region' }, { field: 'mrr', type: 'number' }],
2076
+ rows: [
2077
+ { region: 'EMEA', mrr: 1200000 },
2078
+ { region: 'AMER', mrr: 2400000 },
2079
+ { region: 'APAC', mrr: 600000 },
2080
+ ],
2081
+ kpis: [
2082
+ { title: 'Accounts', fn: 'count' },
2083
+ { title: 'Revenue', of: 'mrr', fn: 'sum' },
2084
+ ],
2085
+ });
2086
+
2087
+ <span class="cmt">// The value the "Revenue" tile would read, from the kernel createStat uses.</span>
2088
+ <span class="kw">const</span> revenue = grid.statistics.reduce('mrr', 'sum');
2089
+ grid.destroy();
2090
+ <span class="kw">return</span> `revenue ${revenue}`;</code></pre>
2091
+ </div>
2092
+
1971
2093
  <div class="example">
1972
2094
  <p class="example__label">Totals</p>
1973
2095
  <pre><code>{ field: 'capacity', type: 'number', total: 'sum' }
@@ -2403,6 +2525,36 @@ createGrid(right, { columns, rows,
2403
2525
  </table>
2404
2526
  </div>
2405
2527
 
2528
+ <h2 id="anomaly-summary">Anomaly summary chip</h2>
2529
+ <p class="lead-in">
2530
+ Give a column an <code>anomalyFlag</code> shadow (§9.4) and turn on
2531
+ <code>anomalySummary</code>, and the grid shows a small chip reading how many rows that column
2532
+ flags. Click it to filter the grid to exactly those rows; click again to clear.
2533
+ </p>
2534
+
2535
+ <div class="example">
2536
+ <p class="example__label">One shadow column, counted and filtered on a click</p>
2537
+ <pre><code>createGrid(element, {
2538
+ columns: [
2539
+ { field: 'reading', title: 'Reading', type: 'number' },
2540
+ { field: 'outlier', title: 'Anomaly', shadow: { kind: 'anomalyFlag', of: 'reading', threshold: 3.5 } },
2541
+ ],
2542
+ rows,
2543
+ anomalySummary: <span class="kw">true</span>, <span class="cmt">// or { column: 'reading', label: '…' }</span>
2544
+ });</code></pre>
2545
+ </div>
2546
+
2547
+ <div class="why">
2548
+ <p><strong>The count and the filter are the same question.</strong> The chip is not a second
2549
+ detector: it reads the <code>anomalyFlag</code> shadow column, so the number it shows is the
2550
+ number of rows that column marks, and the click sets a filter on that same column
2551
+ (<code>{ col, op: 'eq', value: true }</code>). The count comes from
2552
+ <code>grid.statistics.anomalies()</code> over the shadow's base column, so the chip adds no
2553
+ detection of its own — the score is the API's, shown with its method, never an opaque verdict.</p>
2554
+ <p>Off by default, and it draws nothing unless a column carries an <code>anomalyFlag</code>
2555
+ shadow. When more than one does, <code>column</code> names the base to summarise.</p>
2556
+ </div>
2557
+
2406
2558
  <h3>Headings without the controls</h3>
2407
2559
  <p class="lead-in">
2408
2560
  A dense grid often wants the heading and nothing else. <code>showColumnFunctions: false</code>
@@ -2750,12 +2902,18 @@ createGrid(el, {
2750
2902
  });</code></pre>
2751
2903
  </div>
2752
2904
  <p>
2753
- Four adapters ship. <code>odataAdapter</code> writes <code>$filter</code>,
2905
+ Five adapters ship. <code>odataAdapter</code> writes <code>$filter</code>,
2754
2906
  <code>$orderby</code>, <code>$top</code> and <code>$skip</code>, and follows
2755
2907
  <code>@odata.nextLink</code> when the server pages on its own terms.
2756
2908
  <code>restAdapter</code> covers an ordinary JSON endpoint whose parameter names are yours to
2757
2909
  give. <code>dfqlAdapter</code> speaks DemandFlow's query API. <code>duckdbAdapter</code>
2758
2910
  takes a live DuckDB connection.
2911
+ <code>graphqlAdapter</code> POSTs a GraphQL operation: because GraphQL has no fixed query
2912
+ semantics, you supply a <code>buildQuery</code> that turns the plan into
2913
+ <code>{ query, variables }</code> and a <code>parseResponse</code> that reads
2914
+ <code>data</code> back into rows and total, with defaults for an offset/limit list and a
2915
+ Relay cursor connection. The default pushes only the window and the total, so anything the
2916
+ schema cannot answer stays with the grid.
2759
2917
  </p>
2760
2918
 
2761
2919
  <h3>Declaring what an engine can do</h3>