@bicharts/chart-host 0.6.80 → 0.6.82

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 CHANGED
@@ -278,6 +278,79 @@ made for: every word shares a comment with many others, so the default lit most
278
278
  click. The cross-filter sent out is the same under both rules. `onSelectionPaint` reports how each
279
279
  paint of a declaring chart decided, including when no mark matched and the default was used.
280
280
 
281
+ ## Remembering what the reader did
282
+
283
+ A chart keeps its resting state (a sort, an expanded row, a 3D camera angle) in a small bag. It
284
+ reads the bag from `options.uiState` and writes it through `options.setUiState`, and generated code
285
+ never learns where the bag goes. Where it goes is your host's choice, and so is which keys may
286
+ outlive what: a redraw, a close and reopen, or a new version of the chart.
287
+
288
+ `createChartHost` still takes one `viewState` provider, as before. When your host needs more than
289
+ one store, describe it with a `ViewStateService`:
290
+
291
+ ```ts
292
+ import { sessionViewStateProvider, type ViewStateService } from "@bicharts/chart-host";
293
+
294
+ const viewState: ViewStateService = {
295
+ viewing: sessionViewStateProvider(el), // survives a redraw; a page switch starts clean
296
+ durable: workbookStore, // optional: survives close and reopen
297
+ policy: { // the keys your host scopes, by name
298
+ camera: { lifetime: "durable", dropOnNewVersion: true, dropOnFreshViewing: false, durableWhen: "rememberView" },
299
+ userStopped: { lifetime: "durable", dropOnNewVersion: false, dropOnFreshViewing: true },
300
+ },
301
+ versionKey: "cameraVersion", // optional: how a version change is noticed
302
+ };
303
+ ```
304
+
305
+ **What you provide.** `viewing` and `policy` are required (`policy: {}` is fine). Everything else is
306
+ optional, and leaving it out says something about your host:
307
+
308
+ - no `durable` store: the host can't remember across a close (a static preview, a server-side
309
+ render). `viewStateIsDurable(service)` is the one definition of "remembers the view", so it can't
310
+ be claimed without a store to back it. `noopViewStateService()` says "nothing is stored" out loud.
311
+ - no `chartKeys`: keys your policy doesn't name belong to the chart. They're kept, durably, through a
312
+ new version and a fresh viewing, and the chart validates what it reads. Set `chartKeys` to give
313
+ them another rule. A host that forgets everything when its chart changes sets `dropOnNewVersion`.
314
+ - no `versionKey`: you apply the version rule yourself, where you learn the chart changed.
315
+ - no `durableMaxChars`: the durable store accepts any size.
316
+
317
+ **What a key can be.** `lifetime: "viewing"` keeps a key out of the durable store; `"durable"` puts it
318
+ in both. `dropOnNewVersion` drops it when the chart on screen is another version (a camera is aimed
319
+ at one chart's axes). `dropOnFreshViewing` drops it when the bag is read at the start of a viewing
320
+ instead of in the one that wrote it. `durableWhen` names a preference that must be on for the key to
321
+ reach the durable store; the viewing store keeps it either way.
322
+
323
+ **The rules are plain functions** in `@bicharts/chart-host/view-state`, with no DOM, timers or
324
+ storage of their own:
325
+
326
+ ```ts
327
+ import { resolveViewState, commitViewState } from "@bicharts/chart-host/view-state";
328
+
329
+ const { bag, dropped } = resolveViewState(viewState, { codeVersion, freshViewing }); // before a render
330
+ commitViewState(next, viewState, prefs); // when the chart writes
331
+ ```
332
+
333
+ `resolveViewState` builds the bag the chart is handed: the viewing store, else the durable one, with
334
+ the policy applied. `commitViewState` decides what a write reaches: the viewing store gets all of
335
+ it, the durable store gets what the policy allows. Both report what they did and neither throws.
336
+ `viewStateAfterSwap(service, bag)` applies the version rule for a host that sees the swap itself.
337
+
338
+ **Check your stores** with the conformance suite in the testing subpath. It confirms `load` never
339
+ throws and reads `{}` when empty, that `save` replaces, that nested values round-trip, that the
340
+ viewing store survives your host being re-created on the same element, and that the durable store
341
+ holds what it's given across a reopen:
342
+
343
+ ```ts
344
+ import { assertViewStateConformance } from "@bicharts/chart-host/testing";
345
+
346
+ const report = await assertViewStateConformance(service, {
347
+ recreate: () => buildService(sameElement), // the host drawing again
348
+ reopen: () => buildService(newElement), // a new viewing over the same saved file
349
+ settle: () => flushPendingWrites(), // only if your durable save is debounced
350
+ });
351
+ expect(report.skipped).toEqual([]); // nothing went unchecked
352
+ ```
353
+
281
354
  ## Architecture — three layers, and why it matters if you contribute
282
355
 
283
356
  Read this before adding code. The package has **three** concerns, and they are independent.
package/dist/card.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
2
  import {
3
3
  computeSelectionCard
4
- } from "./chunk-Y3CCDWVO.mjs";
5
- import "./chunk-P3HUTWQB.mjs";
4
+ } from "./chunk-7HPVGRQ2.mjs";
5
+ import "./chunk-ZRSEKV4I.mjs";
6
6
  import "./chunk-GHODWOAL.mjs";
7
7
 
8
8
  // src/selectionCardChrome.ts
@@ -1,18 +1,18 @@
1
1
  /*! @bicharts/chart-host — Apache-2.0. Bundled reference data: GeoNames (https://www.geonames.org/) CC BY 4.0; Natural Earth and US Census/TIGER (public domain). Full text: NOTICE in this package. */
2
2
  import {
3
- L,
4
- it,
5
- le,
6
- oi,
7
- rt,
8
- ui
9
- } from "./chunk-P3HUTWQB.mjs";
3
+ Dr,
4
+ Z,
5
+ g,
6
+ ht,
7
+ kr,
8
+ pt
9
+ } from "./chunk-ZRSEKV4I.mjs";
10
10
 
11
11
  // src/sourceDateFormat.ts
12
12
  function formatSourceDate(raw, opts = {}) {
13
13
  const d = toDate(raw);
14
14
  if (!d) return null;
15
- const dayFrame = L(d);
15
+ const dayFrame = g(d);
16
16
  const frame = dayFrame ?? (opts.dialect === "dotnet" ? "local" : "utc");
17
17
  const p = partsIn(d, frame);
18
18
  const format = cleanFormat(opts.format, opts.dialect);
@@ -33,7 +33,7 @@ var ISO_LIKE_RE = /^\s*\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(
33
33
  function toDate(raw) {
34
34
  if (raw instanceof Date) return isNaN(raw.getTime()) ? null : raw;
35
35
  if (typeof raw !== "string" || !ISO_LIKE_RE.test(raw)) return null;
36
- const d = le(raw.trim());
36
+ const d = Z(raw.trim());
37
37
  return d && !isNaN(d.getTime()) ? d : null;
38
38
  }
39
39
  function partsIn(d, frame) {
@@ -435,15 +435,15 @@ function displayText(col, raw, opts) {
435
435
  return String(raw);
436
436
  }
437
437
  function isSuppressed(col) {
438
- return it(col).suppressed;
438
+ return pt(col).suppressed;
439
439
  }
440
440
  function resolveAgg(col, requested, auto) {
441
441
  const fallback = () => {
442
- const d = oi(col);
442
+ const d = Dr(col);
443
443
  return d === "first" ? "count" : d;
444
444
  };
445
445
  if (auto) return fallback();
446
- return rt(col).indexOf(requested) >= 0 ? requested : fallback();
446
+ return ht(col).indexOf(requested) >= 0 ? requested : fallback();
447
447
  }
448
448
  function toNumber(v) {
449
449
  if (v == null || v === "") return null;
@@ -546,7 +546,7 @@ function computeSelectionCard(payload, selectedRowIdxs, opts = {}) {
546
546
  }
547
547
  const value = reduce(selVals, colAgg, selRaw);
548
548
  let sharePct = null;
549
- if (ui(col, colAgg) && value != null) {
549
+ if (kr(col, colAgg) && value != null) {
550
550
  const allVals = [];
551
551
  const allRaw = [];
552
552
  for (let r = 0; r < rows.length; r++) {
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  Fn,
4
4
  J3,
5
+ X,
5
6
  Xn,
6
- Yn,
7
- ge
8
- } from "./chunk-P3HUTWQB.mjs";
7
+ Yn
8
+ } from "./chunk-ZRSEKV4I.mjs";
9
9
  import {
10
10
  censusHitBands,
11
11
  createMarkResolver,
@@ -244,10 +244,10 @@ function f(t) {
244
244
  }
245
245
  function rgbToLab(r, g, b) {
246
246
  const R = linearise(r), G = linearise(g), B = linearise(b);
247
- const X = 0.4124564 * R + 0.3575761 * G + 0.1804375 * B;
247
+ const X2 = 0.4124564 * R + 0.3575761 * G + 0.1804375 * B;
248
248
  const Y = 0.2126729 * R + 0.7151522 * G + 0.072175 * B;
249
249
  const Z = 0.0193339 * R + 0.119192 * G + 0.9503041 * B;
250
- const fx = f(X / Xn2), fy = f(Y / Yn2), fz = f(Z / Zn);
250
+ const fx = f(X2 / Xn2), fy = f(Y / Yn2), fz = f(Z / Zn);
251
251
  return { L: 116 * fy - 16, a: 500 * (fx - fy), b: 200 * (fy - fz) };
252
252
  }
253
253
  function hexToLab(hex2) {
@@ -302,13 +302,13 @@ function labToHex(lab) {
302
302
  return t3 > EPS ? t3 : (116 * t - 16) / KAPPA;
303
303
  };
304
304
  const fy = (lab.L + 16) / 116, fx = fy + lab.a / 500, fz = fy - lab.b / 200;
305
- const X = Xn2 * finv(fx);
305
+ const X2 = Xn2 * finv(fx);
306
306
  const Y = Yn2 * (lab.L > KAPPA * EPS ? fy * fy * fy : lab.L / KAPPA);
307
307
  const Z = Zn * finv(fz);
308
308
  const lin = [
309
- 3.2404542 * X - 1.5371385 * Y - 0.4985314 * Z,
310
- -0.969266 * X + 1.8760108 * Y + 0.041556 * Z,
311
- 0.0556434 * X - 0.2040259 * Y + 1.0572252 * Z
309
+ 3.2404542 * X2 - 1.5371385 * Y - 0.4985314 * Z,
310
+ -0.969266 * X2 + 1.8760108 * Y + 0.041556 * Z,
311
+ 0.0556434 * X2 - 0.2040259 * Y + 1.0572252 * Z
312
312
  ];
313
313
  return "#" + lin.map((v) => {
314
314
  const c = Math.max(0, Math.min(1, v));
@@ -852,7 +852,7 @@ function buildRenderPayload(cols, rowObjs, geo, point, destination, opts) {
852
852
  cols = cols.filter((c) => c.name !== ROW_IDX_COLUMN);
853
853
  let utcDayColumns = null;
854
854
  if (opts?.utcDays) {
855
- const norm = ge(cols, rowObjs);
855
+ const norm = X(cols, rowObjs);
856
856
  rowObjs = norm.rows;
857
857
  utcDayColumns = norm.columns;
858
858
  }
@@ -1866,10 +1866,10 @@ var EMPTY4 = {
1866
1866
  };
1867
1867
  function backedSamples(el, pts) {
1868
1868
  try {
1869
- const ge2 = el;
1870
- if (typeof ge2.isPointInFill !== "function" || typeof ge2.getScreenCTM !== "function") return null;
1871
- const m = ge2.getScreenCTM();
1872
- const svg = ge2.ownerSVGElement;
1869
+ const ge = el;
1870
+ if (typeof ge.isPointInFill !== "function" || typeof ge.getScreenCTM !== "function") return null;
1871
+ const m = ge.getScreenCTM();
1872
+ const svg = ge.ownerSVGElement;
1873
1873
  if (!m || !svg || typeof svg.createSVGPoint !== "function") return null;
1874
1874
  if (pts.length === 0) return null;
1875
1875
  const inv = m.inverse();
@@ -1878,7 +1878,7 @@ function backedSamples(el, pts) {
1878
1878
  const p = svg.createSVGPoint();
1879
1879
  p.x = pts[i].x;
1880
1880
  p.y = pts[i].y;
1881
- hit.push(ge2.isPointInFill(p.matrixTransform(inv)));
1881
+ hit.push(ge.isPointInFill(p.matrixTransform(inv)));
1882
1882
  }
1883
1883
  return hit;
1884
1884
  } catch {
@@ -1923,9 +1923,9 @@ function sampleBackgrounds(shapes, pts, pageRGB, effAlpha) {
1923
1923
  function extendPillOver(pill, tr) {
1924
1924
  try {
1925
1925
  if (String(pill.tagName || "").toLowerCase() !== "rect") return false;
1926
- const ge2 = pill;
1927
- if (typeof ge2.getScreenCTM !== "function") return false;
1928
- const m = ge2.getScreenCTM();
1926
+ const ge = pill;
1927
+ if (typeof ge.getScreenCTM !== "function") return false;
1928
+ const m = ge.getScreenCTM();
1929
1929
  if (!m || Math.abs(m.b) > 1e-6 || Math.abs(m.c) > 1e-6 || !(m.a > 0) || !(m.d > 0)) return false;
1930
1930
  const inv = m.inverse();
1931
1931
  const toUser = (x2, y2) => ({ x: inv.a * x2 + inv.c * y2 + inv.e, y: inv.b * x2 + inv.d * y2 + inv.f });