@kanzo-tech/ui 0.12.0 → 0.13.1

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.
Files changed (86) hide show
  1. package/dist/analytics.d.ts +1 -0
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/charts/chart-inputs.d.ts +6 -5
  4. package/dist/charts/chart-inputs.d.ts.map +1 -1
  5. package/dist/charts/chart-inputs.js +78 -77
  6. package/dist/charts/chart-inputs.js.map +1 -1
  7. package/dist/charts/chart-marks.d.ts +2 -1
  8. package/dist/charts/chart-marks.d.ts.map +1 -1
  9. package/dist/charts/chart-marks.js.map +1 -1
  10. package/dist/charts/chart-root.d.ts +8 -3
  11. package/dist/charts/chart-root.d.ts.map +1 -1
  12. package/dist/charts/chart-root.js +60 -60
  13. package/dist/charts/chart-root.js.map +1 -1
  14. package/dist/charts/chart-spec.d.ts +11 -4
  15. package/dist/charts/chart-spec.d.ts.map +1 -1
  16. package/dist/charts/chart-spec.js +44 -40
  17. package/dist/charts/chart-spec.js.map +1 -1
  18. package/dist/charts/chart-stat.d.ts +3 -2
  19. package/dist/charts/chart-stat.d.ts.map +1 -1
  20. package/dist/charts/chart-stat.js +10 -9
  21. package/dist/charts/chart-stat.js.map +1 -1
  22. package/dist/composites/language-picker.d.ts +7 -0
  23. package/dist/composites/language-picker.d.ts.map +1 -1
  24. package/dist/composites/language-picker.js +38 -36
  25. package/dist/composites/language-picker.js.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +661 -660
  29. package/dist/lib/control-size.d.ts +26 -0
  30. package/dist/lib/control-size.d.ts.map +1 -0
  31. package/dist/lib/control-size.js +11 -0
  32. package/dist/lib/control-size.js.map +1 -0
  33. package/dist/simples/button.d.ts.map +1 -1
  34. package/dist/simples/button.js +34 -33
  35. package/dist/simples/button.js.map +1 -1
  36. package/dist/simples/checkbox.d.ts.map +1 -1
  37. package/dist/simples/checkbox.js +42 -42
  38. package/dist/simples/checkbox.js.map +1 -1
  39. package/dist/simples/combobox.d.ts.map +1 -1
  40. package/dist/simples/combobox.js +48 -49
  41. package/dist/simples/combobox.js.map +1 -1
  42. package/dist/simples/date-picker.d.ts +58 -2
  43. package/dist/simples/date-picker.d.ts.map +1 -1
  44. package/dist/simples/date-picker.js +207 -76
  45. package/dist/simples/date-picker.js.map +1 -1
  46. package/dist/simples/input-group.d.ts +9 -9
  47. package/dist/simples/input-group.d.ts.map +1 -1
  48. package/dist/simples/input-group.js +68 -78
  49. package/dist/simples/input-group.js.map +1 -1
  50. package/dist/simples/input.d.ts +9 -9
  51. package/dist/simples/input.d.ts.map +1 -1
  52. package/dist/simples/input.js +13 -14
  53. package/dist/simples/input.js.map +1 -1
  54. package/dist/simples/native-select.d.ts +9 -9
  55. package/dist/simples/native-select.d.ts.map +1 -1
  56. package/dist/simples/native-select.js +29 -28
  57. package/dist/simples/native-select.js.map +1 -1
  58. package/dist/simples/number-input.d.ts +9 -9
  59. package/dist/simples/number-input.d.ts.map +1 -1
  60. package/dist/simples/number-input.js +27 -27
  61. package/dist/simples/number-input.js.map +1 -1
  62. package/dist/simples/password-input.d.ts.map +1 -1
  63. package/dist/simples/password-input.js +24 -20
  64. package/dist/simples/password-input.js.map +1 -1
  65. package/dist/simples/pin-input.d.ts +9 -9
  66. package/dist/simples/pin-input.d.ts.map +1 -1
  67. package/dist/simples/pin-input.js +30 -29
  68. package/dist/simples/pin-input.js.map +1 -1
  69. package/dist/simples/radio-group.d.ts.map +1 -1
  70. package/dist/simples/radio-group.js +44 -43
  71. package/dist/simples/radio-group.js.map +1 -1
  72. package/dist/simples/segment-group.d.ts +8 -0
  73. package/dist/simples/segment-group.d.ts.map +1 -1
  74. package/dist/simples/segment-group.js +55 -47
  75. package/dist/simples/segment-group.js.map +1 -1
  76. package/dist/simples/switch.d.ts.map +1 -1
  77. package/dist/simples/switch.js +18 -12
  78. package/dist/simples/switch.js.map +1 -1
  79. package/dist/simples/tags-input.d.ts.map +1 -1
  80. package/dist/simples/tags-input.js +44 -39
  81. package/dist/simples/tags-input.js.map +1 -1
  82. package/dist/simples/toggle.d.ts +9 -9
  83. package/dist/simples/toggle.d.ts.map +1 -1
  84. package/dist/simples/toggle.js +24 -23
  85. package/dist/simples/toggle.js.map +1 -1
  86. package/package.json +5 -5
@@ -26,6 +26,7 @@ export { Coordinator, Selection, engine, type Engine } from '@kanzo-tech/mosaic'
26
26
  export { loadCSV, loadJSON, loadObjects, loadParquet, loadSpatial, loadExtension, } from '@kanzo-tech/mosaic';
27
27
  export { count, sum, avg, min, max, median, quantile, stddev, mode, bin, sql } from '@uwdata/vgplot';
28
28
  export type { ExprValue } from '@kanzo-tech/mosaic';
29
+ export type { TableExpr } from '@kanzo-tech/mosaic';
29
30
  export { asc, desc } from '@kanzo-tech/mosaic';
30
31
  export { axisX, axisY, axisFx, axisFy, gridFx, gridFy } from '@uwdata/vgplot';
31
32
  export { MosaicClient, makeClient, clausePoint, clausePoints, clauseInterval, clauseIntervals, clauseMatch, } from '@kanzo-tech/mosaic';
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { TableExpr } from '@kanzo-tech/mosaic';
1
2
  import { ReactNode, default as React } from 'react';
2
3
  import { ark } from '@ark-ui/react/factory';
3
4
  import { MosaicClient, Selection, SelectionClause } from '@uwdata/mosaic-core';
@@ -66,8 +67,8 @@ export interface ChartFilterOption {
66
67
  }>;
67
68
  }
68
69
  export interface ChartFilterProps extends Omit<React.ComponentProps<typeof ark.div>, "defaultValue">, SelectionProps {
69
- /** The relation the values and their counts come from. */
70
- table?: string;
70
+ /** The relation the values and their counts come from, as `ChartRoot` takes it. */
71
+ table?: TableExpr;
71
72
  /** The column grouped by. */
72
73
  column?: string;
73
74
  /** The column named in the published clause. Defaults to `column`. */
@@ -112,8 +113,8 @@ export interface ChartFilterProps extends Omit<React.ComponentProps<typeof ark.d
112
113
  */
113
114
  export declare function ChartFilter(props: ChartFilterProps): React.JSX.Element;
114
115
  export interface ChartSearchProps extends Omit<React.ComponentProps<typeof ark.div>, "defaultValue">, SelectionProps {
115
- /** The relation the autocomplete values come from. */
116
- table?: string;
116
+ /** The relation the autocomplete values come from, as `ChartRoot` takes it. */
117
+ table?: TableExpr;
117
118
  /** The column to search. */
118
119
  column?: string;
119
120
  /** The column named in the published clause. Defaults to `column`. */
@@ -143,7 +144,7 @@ export interface ChartSearchProps extends Omit<React.ComponentProps<typeof ark.d
143
144
  export declare function ChartSearch(props: ChartSearchProps): React.JSX.Element;
144
145
  export interface ChartSliderProps extends Omit<React.ComponentProps<typeof ark.div>, "defaultValue">, SelectionProps {
145
146
  /** The relation the extent comes from, when `min`/`max` are not both given. */
146
- table?: string;
147
+ table?: TableExpr;
147
148
  /** The numeric column. */
148
149
  column?: string;
149
150
  /** The column named in the published clause. Defaults to `column`. */
@@ -1 +1 @@
1
- {"version":3,"file":"chart-inputs.d.ts","sourceRoot":"","sources":["../../src/charts/chart-inputs.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chart-inputs.d.ts","sourceRoot":"","sources":["../../src/charts/chart-inputs.tsx"],"names":[],"mappings":"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"}
@@ -1,26 +1,27 @@
1
1
  "use client";
2
2
  import { jsxs as A, jsx as C } from "react/jsx-runtime";
3
- import { useRef as B, useEffect as N, useMemo as ne, useState as _, useCallback as Z } from "react";
4
- import { ark as re } from "@ark-ui/react/factory";
5
- import { clausePoints as le, clausePoint as oe, clauseMatch as ie, clauseInterval as ce } from "@uwdata/mosaic-core";
6
- import { Query as G, count as ee, desc as se, max as ae, min as ue } from "@uwdata/mosaic-sql";
7
- import { useListCollection as me } from "@ark-ui/react/collection";
8
- import { useFilter as de } from "@ark-ui/react/locale";
3
+ import { useRef as B, useEffect as N, useMemo as re, useState as _, useCallback as ee } from "react";
4
+ import { ark as oe } from "@ark-ui/react/factory";
5
+ import { clausePoints as ie, clausePoint as le, clauseMatch as ce, clauseInterval as se } from "@uwdata/mosaic-core";
6
+ import { Query as G, count as te, desc as ae, max as ue, min as me } from "@uwdata/mosaic-sql";
7
+ import { useListCollection as de } from "@ark-ui/react/collection";
8
+ import { useFilter as fe } from "@ark-ui/react/locale";
9
9
  import { cn as J } from "../lib/cn.js";
10
- import { Combobox as fe, ComboboxInput as he, ComboboxContent as pe, ComboboxEmpty as ve, ComboboxItem as be } from "../simples/combobox.js";
11
- import { FacetFilter as ge } from "../simples/FacetFilter.js";
12
- import { Field as ye, FieldLabel as xe } from "../simples/field.js";
13
- import { Input as Ce } from "../simples/input.js";
14
- import { Skeleton as Ne } from "../simples/skeleton.js";
15
- import { Slider as Se, SliderLabel as Ve, SliderValue as we } from "../simples/slider.js";
10
+ import { Combobox as he, ComboboxInput as pe, ComboboxContent as ve, ComboboxEmpty as be, ComboboxItem as ge } from "../simples/combobox.js";
11
+ import { FacetFilter as ye } from "../simples/FacetFilter.js";
12
+ import { Field as xe, FieldLabel as Ce } from "../simples/field.js";
13
+ import { Input as Ne } from "../simples/input.js";
14
+ import { Skeleton as Se } from "../simples/skeleton.js";
15
+ import { Slider as Ve, SliderLabel as we, SliderValue as Fe } from "../simples/slider.js";
16
+ import { chartTableKey as W } from "./chart-spec.js";
16
17
  import { useMosaic as q } from "./mosaic-provider.js";
17
- import { ChartQueryClient as Fe } from "./query-client.js";
18
- function W(t) {
18
+ import { ChartQueryClient as Ie } from "./query-client.js";
19
+ function X(t) {
19
20
  const { filterBy: r, as: e, deps: i } = t, { coordinator: a } = q(), d = B(t);
20
21
  d.current = t;
21
22
  const p = B(null), [b, x] = _(null), [y, m] = _(null), [u, v] = _(void 0);
22
23
  N(() => {
23
- const s = new Fe(
24
+ const s = new Ie(
24
25
  r ?? void 0,
25
26
  (h) => d.current.build(h),
26
27
  m
@@ -38,20 +39,20 @@ function W(t) {
38
39
  e.removeEventListener("value", s), e.valueFor(b) !== void 0 && e.update(d.current.clause(b, void 0));
39
40
  };
40
41
  }, [e, b]);
41
- const S = Z(
42
+ const S = ee(
42
43
  (s) => {
43
44
  v(s);
44
45
  const h = p.current;
45
46
  e && h && e.update(d.current.clause(h, s));
46
47
  },
47
48
  [e]
48
- ), k = Z(() => {
49
+ ), k = ee(() => {
49
50
  const s = p.current;
50
51
  e && s && e.activate(d.current.clause(s, d.current.activateValue));
51
52
  }, [e]);
52
53
  return { rows: y, selected: u, publish: S, activate: k };
53
54
  }
54
- function X(t, r) {
55
+ function Y(t, r) {
55
56
  return {
56
57
  onFocus: (e) => {
57
58
  var i;
@@ -63,24 +64,24 @@ function X(t, r) {
63
64
  }
64
65
  };
65
66
  }
66
- const Ie = 50;
67
- function Be(t) {
67
+ const Be = 50;
68
+ function ke(t) {
68
69
  return typeof t == "object" && t !== null && !Array.isArray(t) && "value" in t;
69
70
  }
70
- function ke(t) {
71
+ function Ee(t) {
71
72
  return String(t ?? "");
72
73
  }
73
- function Ee(t) {
74
+ function Le(t) {
74
75
  if (typeof t == "number") return t;
75
76
  if (typeof t == "bigint") return Number(t);
76
77
  }
77
- function Le(t, r, e) {
78
+ function Te(t, r, e) {
78
79
  if (t)
79
80
  return e ? `Top ${r} values — the field searches these, not the column. Filter further for the rest.` : `Top ${r} values — filter further to see the rest.`;
80
81
  }
81
- function Te(t, r, e) {
82
+ function Me(t, r, e) {
82
83
  return t ? t.map((i) => {
83
- const a = Be(i) ? i : { value: i };
84
+ const a = ke(i) ? i : { value: i };
84
85
  return {
85
86
  key: String(a.value),
86
87
  label: a.label ?? e(a.value),
@@ -91,10 +92,10 @@ function Te(t, r, e) {
91
92
  key: String(i.value),
92
93
  label: e(i.value),
93
94
  raw: i.value,
94
- count: Ee(i.count)
95
+ count: Le(i.count)
95
96
  }));
96
97
  }
97
- function We(t) {
98
+ function Ye(t) {
98
99
  const {
99
100
  table: r,
100
101
  column: e,
@@ -102,11 +103,11 @@ function We(t) {
102
103
  as: a,
103
104
  filterBy: d,
104
105
  options: p,
105
- format: b = ke,
106
+ format: b = Ee,
106
107
  label: x = e,
107
108
  multiple: y = !0,
108
109
  defaultValue: m,
109
- limit: u = Ie,
110
+ limit: u = Be,
110
111
  searchable: v = !1,
111
112
  disabled: S,
112
113
  size: k = "md",
@@ -115,19 +116,19 @@ function We(t) {
115
116
  children: E,
116
117
  slot: P,
117
118
  ...Q
118
- } = t, { crossfilter: j } = q(), R = a === void 0 ? j : a, K = d === void 0 ? j : d, g = p === void 0 && !!(r && e), { rows: V, selected: L, publish: w, activate: f } = W({
119
+ } = t, { crossfilter: j } = q(), R = a === void 0 ? j : a, K = d === void 0 ? j : d, g = p === void 0 && !!(r && e), { rows: V, selected: L, publish: w, activate: f } = X({
119
120
  as: R,
120
121
  filterBy: K,
121
- deps: [r, e, g, u],
122
+ deps: [W(r), e, g, u],
122
123
  activateValue: [0],
123
- build: (n) => g ? G.from(r).select({ value: e, count: ee() }).where(n).groupby(e).orderby(se(ee()), e).limit(u + 1) : null,
124
- clause: (n, c) => y ? le([i], c == null ? void 0 : c.map((H) => [H]), { source: n }) : oe(i, c == null ? void 0 : c[0], { source: n }),
124
+ build: (n) => g ? G.from(r).select({ value: e, count: te() }).where(n).groupby(e).orderby(ae(te()), e).limit(u + 1) : null,
125
+ clause: (n, c) => y ? ie([i], c == null ? void 0 : c.map((H) => [H]), { source: n }) : le(i, c == null ? void 0 : c[0], { source: n }),
125
126
  decode: (n) => y ? n.map((c) => c[0]) : [n]
126
127
  }), T = B(!1);
127
128
  N(() => {
128
129
  T.current || m === void 0 || (T.current = !0, w(Array.isArray(m) ? m : [m]));
129
130
  }, [m, w]);
130
- const M = L ?? [], F = Te(p, V, b), I = F.length > u, O = I ? F.slice(0, u) : F, $ = O.map((n) => ({
131
+ const M = L ?? [], F = Me(p, V, b), I = F.length > u, O = I ? F.slice(0, u) : F, $ = O.map((n) => ({
131
132
  count: n.count,
132
133
  icon: n.icon,
133
134
  label: n.label,
@@ -135,15 +136,15 @@ function We(t) {
135
136
  })), l = new Map(O.map((n) => [n.key, n.raw]));
136
137
  for (const n of M) l.set(String(n), n);
137
138
  return /* @__PURE__ */ A(
138
- re.div,
139
+ oe.div,
139
140
  {
140
141
  className: J("w-fit", s),
141
142
  ...Q,
142
- ...X(f, t),
143
+ ...Y(f, t),
143
144
  "data-slot": P ?? "chart-filter",
144
145
  children: [
145
146
  /* @__PURE__ */ C(
146
- ge,
147
+ ye,
147
148
  {
148
149
  className: h,
149
150
  slot: "chart-filter-trigger",
@@ -152,7 +153,7 @@ function We(t) {
152
153
  items: $,
153
154
  label: x,
154
155
  multiple: y,
155
- note: Le(I, u, v),
156
+ note: Te(I, u, v),
156
157
  onValueChange: (n) => w(n.map((c) => l.has(c) ? l.get(c) : c)),
157
158
  searchEmpty: I ? `No match among the top ${u} values.` : void 0,
158
159
  searchable: v,
@@ -165,7 +166,7 @@ function We(t) {
165
166
  }
166
167
  );
167
168
  }
168
- function Xe(t) {
169
+ function Ze(t) {
169
170
  const {
170
171
  table: r,
171
172
  column: e,
@@ -186,17 +187,17 @@ function Xe(t) {
186
187
  children: E,
187
188
  slot: P,
188
189
  ...Q
189
- } = t, { crossfilter: j } = q(), R = a === void 0 ? j : a, K = d === void 0 ? j : d, g = u > 0 && !!(r && e), { rows: V, selected: L, publish: w, activate: f } = W({
190
+ } = t, { crossfilter: j } = q(), R = a === void 0 ? j : a, K = d === void 0 ? j : d, g = u > 0 && !!(r && e), { rows: V, selected: L, publish: w, activate: f } = X({
190
191
  as: R,
191
192
  filterBy: K,
192
- deps: [r, e, g, u],
193
+ deps: [W(r), e, g, u],
193
194
  activateValue: "",
194
195
  build: (o) => g ? G.from(r).select({ value: e }).distinct().where(o).orderby(e).limit(u) : null,
195
- clause: (o, z) => ie(i, z ?? null, { source: o, method: p, caseSensitive: b })
196
- }), T = ne(
196
+ clause: (o, z) => ce(i, z ?? null, { source: o, method: p, caseSensitive: b })
197
+ }), T = re(
197
198
  () => (V ?? []).map((o) => ({ label: String(o.value), value: String(o.value) })),
198
199
  [V]
199
- ), M = T.length >= u, { contains: F } = de({ sensitivity: "base" }), { collection: I, filter: O, set: $ } = me({ filter: F, initialItems: [] }), l = T.map((o) => o.value).join("\0");
200
+ ), M = T.length >= u, { contains: F } = fe({ sensitivity: "base" }), { collection: I, filter: O, set: $ } = de({ filter: F, initialItems: [] }), l = T.map((o) => o.value).join("\0");
200
201
  N(() => {
201
202
  $(T);
202
203
  }, [l, $]);
@@ -207,9 +208,9 @@ function Xe(t) {
207
208
  }, [L]), N(() => () => {
208
209
  U.current && clearTimeout(U.current);
209
210
  }, []);
210
- const Y = B(!1);
211
+ const Z = B(!1);
211
212
  N(() => {
212
- Y.current || !v || (Y.current = !0, w(v));
213
+ Z.current || !v || (Z.current = !0, w(v));
213
214
  }, [v, w]);
214
215
  const D = (o) => {
215
216
  c(o), U.current && clearTimeout(U.current);
@@ -219,15 +220,15 @@ function Xe(t) {
219
220
  m > 0 ? U.current = setTimeout(z, m) : z();
220
221
  };
221
222
  return /* @__PURE__ */ A(
222
- ye,
223
+ xe,
223
224
  {
224
225
  className: J("w-fit min-w-48 gap-1.5", s),
225
226
  disabled: S,
226
227
  ...Q,
227
- ...X(f, t),
228
+ ...Y(f, t),
228
229
  slot: P ?? "chart-search",
229
230
  children: [
230
- x ? /* @__PURE__ */ C(xe, { children: x }) : null,
231
+ x ? /* @__PURE__ */ C(Ce, { children: x }) : null,
231
232
  g ? (
232
233
  // `allowCustomValue`, because this control is a FILTER and not a picker: what it publishes
233
234
  // is a match clause over whatever you typed, and the list only ever offered completions.
@@ -235,7 +236,7 @@ function Xe(t) {
235
236
  // in every browser, no empty state, and silent about its own cap. Same clause, same
236
237
  // debounce; only the surface changed.
237
238
  /* @__PURE__ */ A(
238
- fe,
239
+ he,
239
240
  {
240
241
  allowCustomValue: !0,
241
242
  collection: I,
@@ -250,10 +251,10 @@ function Xe(t) {
250
251
  z !== void 0 && D(z);
251
252
  },
252
253
  children: [
253
- /* @__PURE__ */ C(he, { className: h, placeholder: y, size: k }),
254
- /* @__PURE__ */ A(pe, { children: [
255
- /* @__PURE__ */ C(ve, { children: "No matching values." }),
256
- I.items.map((o) => /* @__PURE__ */ C(be, { item: o, children: o.label }, o.value)),
254
+ /* @__PURE__ */ C(pe, { className: h, placeholder: y, size: k }),
255
+ /* @__PURE__ */ A(ve, { children: [
256
+ /* @__PURE__ */ C(be, { children: "No matching values." }),
257
+ I.items.map((o) => /* @__PURE__ */ C(ge, { item: o, children: o.label }, o.value)),
257
258
  M ? /* @__PURE__ */ A("p", { className: "border-t px-2 py-1.5 text-muted-foreground text-xs", children: [
258
259
  "First ",
259
260
  u,
@@ -264,7 +265,7 @@ function Xe(t) {
264
265
  }
265
266
  )
266
267
  ) : /* @__PURE__ */ C(
267
- Ce,
268
+ Ne,
268
269
  {
269
270
  className: h,
270
271
  disabled: S,
@@ -280,10 +281,10 @@ function Xe(t) {
280
281
  }
281
282
  );
282
283
  }
283
- function Me(t, r) {
284
+ function je(t, r) {
284
285
  return Number.isInteger(t) && Number.isInteger(r) ? 1 : (r - t) / 500 || 1;
285
286
  }
286
- function Ye(t) {
287
+ function et(t) {
287
288
  const {
288
289
  table: r,
289
290
  column: e,
@@ -302,35 +303,35 @@ function Ye(t) {
302
303
  children: s,
303
304
  slot: h,
304
305
  ...E
305
- } = t, { crossfilter: P } = q(), Q = a === void 0 ? P : a, j = d === void 0 ? P : d, R = (b === void 0 || x === void 0) && !!(r && e), K = B(1), { rows: g, selected: V, publish: L, activate: w } = W({
306
+ } = t, { crossfilter: P } = q(), Q = a === void 0 ? P : a, j = d === void 0 ? P : d, R = (b === void 0 || x === void 0) && !!(r && e), K = B(1), { rows: g, selected: V, publish: L, activate: w } = X({
306
307
  as: Q,
307
308
  filterBy: j,
308
- deps: [r, e, R],
309
+ deps: [W(r), e, R],
309
310
  activateValue: p === "interval" ? [0, 0] : 0,
310
- build: (l) => R ? G.select({ min: ue(e), max: ae(e) }).from(r).where(l) : null,
311
+ build: (l) => R ? G.select({ min: me(e), max: ue(e) }).from(r).where(l) : null,
311
312
  clause: (l, n) => {
312
- if (p !== "interval") return oe(i, n, { source: l });
313
+ if (p !== "interval") return le(i, n, { source: l });
313
314
  const c = n;
314
- return ce(i, c ?? null, {
315
+ return se(i, c ?? null, {
315
316
  source: l,
316
317
  bin: "ceil",
317
318
  scale: c ? { type: "identity", domain: c } : void 0,
318
319
  pixelSize: K.current
319
320
  });
320
321
  }
321
- }), f = ne(() => {
322
+ }), f = re(() => {
322
323
  const l = g == null ? void 0 : g[0], n = b ?? (typeof (l == null ? void 0 : l.min) == "number" ? l.min : void 0), c = x ?? (typeof (l == null ? void 0 : l.max) == "number" ? l.max : void 0);
323
- return n === void 0 || c === void 0 ? null : { min: n, max: c, step: y ?? Me(n, c) };
324
+ return n === void 0 || c === void 0 ? null : { min: n, max: c, step: y ?? je(n, c) };
324
325
  }, [g, b, x, y]);
325
326
  N(() => {
326
327
  K.current = (f == null ? void 0 : f.step) ?? 1;
327
328
  }, [f]);
328
329
  const [T, M] = _(
329
- m === void 0 ? null : [...te(m)]
330
+ m === void 0 ? null : [...ne(m)]
330
331
  ), F = B(!1);
331
332
  N(() => {
332
333
  if (V !== void 0) {
333
- F.current = !0, M([...te(V)]);
334
+ F.current = !0, M([...ne(V)]);
334
335
  return;
335
336
  }
336
337
  F.current && (F.current = !1, M(null));
@@ -343,15 +344,15 @@ function Ye(t) {
343
344
  M(l), L(p === "interval" ? [l[0], l[1]] : l[0]);
344
345
  }, $ = T ?? (f ? p === "interval" ? [f.min, f.max] : [f.min] : null);
345
346
  return /* @__PURE__ */ A(
346
- re.div,
347
+ oe.div,
347
348
  {
348
349
  className: J("flex w-full min-w-48 flex-col gap-1.5", k),
349
350
  ...E,
350
- ...X(w, t),
351
+ ...Y(w, t),
351
352
  "data-slot": h ?? "chart-slider",
352
353
  children: [
353
354
  f && $ ? /* @__PURE__ */ C(
354
- Se,
355
+ Ve,
355
356
  {
356
357
  disabled: S,
357
358
  max: f.max,
@@ -360,23 +361,23 @@ function Ye(t) {
360
361
  step: f.step,
361
362
  value: $,
362
363
  children: u || v ? /* @__PURE__ */ A("div", { className: "flex items-center", children: [
363
- u ? /* @__PURE__ */ C(Ve, { children: u }) : null,
364
- v ? /* @__PURE__ */ C(we, {}) : null
364
+ u ? /* @__PURE__ */ C(we, { children: u }) : null,
365
+ v ? /* @__PURE__ */ C(Fe, {}) : null
365
366
  ] }) : null
366
367
  }
367
- ) : /* @__PURE__ */ C(Ne, { className: "h-2 w-full rounded-full" }),
368
+ ) : /* @__PURE__ */ C(Se, { className: "h-2 w-full rounded-full" }),
368
369
  s
369
370
  ]
370
371
  }
371
372
  );
372
373
  }
373
- function te(t) {
374
+ function ne(t) {
374
375
  return typeof t == "number" ? [t] : t;
375
376
  }
376
377
  export {
377
- We as ChartFilter,
378
- Xe as ChartSearch,
379
- Ye as ChartSlider,
380
- W as useMosaicInput
378
+ Ye as ChartFilter,
379
+ Ze as ChartSearch,
380
+ et as ChartSlider,
381
+ X as useMosaicInput
381
382
  };
382
383
  //# sourceMappingURL=chart-inputs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chart-inputs.js","sources":["../../src/charts/chart-inputs.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type React from \"react\";\nimport { ark } from \"@ark-ui/react/factory\";\nimport {\n clauseInterval,\n clauseMatch,\n clausePoint,\n clausePoints,\n MosaicClient,\n type Selection,\n type SelectionClause,\n} from \"@uwdata/mosaic-core\";\nimport {\n count as sqlCount,\n desc as sqlDesc,\n max as sqlMax,\n min as sqlMin,\n Query,\n type FilterExpr,\n} from \"@uwdata/mosaic-sql\";\nimport { useListCollection } from \"@ark-ui/react/collection\";\nimport { useFilter } from \"@ark-ui/react/locale\";\nimport { cn } from \"../lib/cn.js\";\nimport {\n Combobox,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n} from \"../simples/combobox.js\";\nimport { FacetFilter, type FacetFilterItem } from \"../simples/FacetFilter.js\";\nimport { Field, FieldLabel } from \"../simples/field.js\";\nimport { Input } from \"../simples/input.js\";\nimport { Skeleton } from \"../simples/skeleton.js\";\nimport { Slider, SliderLabel, SliderValue } from \"../simples/slider.js\";\nimport { useMosaic } from \"./mosaic-provider.js\";\nimport { ChartQueryClient, type ChartQueryRow as QueryRow } from \"./query-client.js\";\n\n/**\n * Mosaic **inputs** — controls that publish into a `Selection` without being charts.\n *\n * Unlike marks and interactors, these are real DOM parts (`ark.*`, `data-slot`, forwarded props),\n * not inert descriptors: they carry no `__chart` static and belong *outside* `<ChartRoot>`. What\n * makes them Mosaic and not just a styled `<select>` is that each one is a `MosaicClient`: it\n * declares a `query()` for the values it needs (grouped counts, distinct values, a numeric extent),\n * the coordinator answers it, and the control publishes `clausePoints` / `clauseMatch` /\n * `clauseInterval` back into the selection the charts filter by.\n *\n * The vgplot equivalents (`vg.menu`, `vg.search`, `vg.slider`) build raw HTML. These use our\n * controls, our tokens and our accessibility, and are controllable *and* observable: an external\n * change to the selection flows back into the widget.\n */\n\n\nexport interface MosaicInputOptions<T> {\n /** Filters the widget's own lookup query. `null` = the full relation. */\n filterBy: Selection | null;\n /** Where the widget publishes. `null` = nowhere. */\n as: Selection | null;\n /** The lookup query, or `null` when there is nothing to ask the coordinator for. */\n build: (filter: FilterExpr) => Query | null;\n /** Rebuilds the client — and re-runs the lookup — when any entry changes. */\n deps: readonly unknown[];\n clause: (source: MosaicClient, value: T | undefined) => SelectionClause;\n /** The representative value of the warm-up clause published on hover/focus. */\n activateValue: T;\n /** Reads the widget's own clause value back out of the selection. Defaults to an identity cast. */\n decode?: (value: unknown) => T;\n}\n\nexport interface MosaicInputState<T> {\n /** Lookup rows, or `null` while the query is in flight (or when there is no query). */\n rows: readonly QueryRow[] | null;\n /** The value this widget currently holds in the selection — its observable half. */\n selected: T | undefined;\n publish: (value: T | undefined) => void;\n activate: () => void;\n}\n\n/**\n * The conversation with Mosaic that every input in this file has: a lookup query that re-runs under\n * the crossfilter, a clause published with `source` set so a control never filters itself, and a\n * warm-up so the first interaction is not the first query.\n *\n * Exported because the three controls here are surfaces over it, not the only surfaces it allows —\n * a chip row, a segmented switch or a map lasso is the same conversation with different chrome, and\n * without this the alternative is reimplementing it.\n */\nexport function useMosaicInput<T>(\n options: MosaicInputOptions<T>\n): MosaicInputState<T> {\n const { filterBy, as, deps } = options;\n const { coordinator } = useMosaic();\n const latest = useRef(options);\n latest.current = options;\n\n const clientRef = useRef<ChartQueryClient | null>(null);\n const [client, setClient] = useState<ChartQueryClient | null>(null);\n const [rows, setRows] = useState<readonly QueryRow[] | null>(null);\n const [selected, setSelected] = useState<T | undefined>(undefined);\n\n useEffect(() => {\n const instance = new ChartQueryClient(\n filterBy ?? undefined,\n (filter) => latest.current.build(filter),\n setRows,\n );\n clientRef.current = instance;\n setRows(null);\n setClient(instance);\n coordinator.connect(instance);\n return () => {\n clientRef.current = null;\n coordinator.disconnect(instance);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [coordinator, filterBy, ...deps]);\n\n useEffect(() => {\n if (!as || !client) return;\n const sync = () => {\n const raw = as.valueFor(client);\n const { decode } = latest.current;\n setSelected(raw === undefined ? undefined : decode ? decode(raw) : (raw as T));\n };\n sync();\n as.addEventListener(\"value\", sync);\n return () => {\n as.removeEventListener(\"value\", sync);\n // `Selection.remove` returns a *clone*; the live way to retract a clause is to publish an\n // empty one, which the resolver drops because its predicate is null.\n if (as.valueFor(client) !== undefined) as.update(latest.current.clause(client, undefined));\n };\n }, [as, client]);\n\n const publish = useCallback(\n (value: T | undefined) => {\n setSelected(value);\n const instance = clientRef.current;\n if (as && instance) as.update(latest.current.clause(instance, value));\n },\n [as],\n );\n\n const activate = useCallback(() => {\n const instance = clientRef.current;\n if (as && instance) as.activate(latest.current.clause(instance, latest.current.activateValue));\n }, [as]);\n\n return { rows, selected, publish, activate };\n}\n\n/** vgplot's inputs warm the selection on hover/focus; ours do it on the wrapper, and compose. */\nfunction warmUpHandlers(\n activate: () => void,\n props: Pick<React.ComponentProps<typeof ark.div>, \"onFocus\" | \"onPointerEnter\">,\n) {\n return {\n onFocus: (event: React.FocusEvent<HTMLDivElement>) => {\n props.onFocus?.(event);\n activate();\n },\n onPointerEnter: (event: React.PointerEvent<HTMLDivElement>) => {\n props.onPointerEnter?.(event);\n if (!event.buttons) activate();\n },\n };\n}\n\ninterface SelectionProps {\n /** Where the control publishes. Defaults to the provider's crossfilter; `null` = nowhere. */\n as?: Selection | null;\n /** What the control's own lookup filters by. Defaults to the crossfilter; `null` = unfiltered. */\n filterBy?: Selection | null;\n}\n\ntype ControlSize = \"sm\" | \"md\" | \"lg\";\n\n// ── ChartFilter ──────────────────────────────────────────────────────────────\n\n/** An explicit entry. Bare values work too — they are labelled by `format`. */\nexport interface ChartFilterOption {\n value: unknown;\n label?: string;\n icon?: React.ComponentType<{ className?: string }>;\n}\n\nexport interface ChartFilterProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"defaultValue\">,\n SelectionProps {\n /** The relation the values and their counts come from. */\n table?: string;\n /** The column grouped by. */\n column?: string;\n /** The column named in the published clause. Defaults to `column`. */\n field?: string;\n /** Explicit options — a literal value or a `{ value, label, icon }` object. Skips the lookup. */\n options?: readonly unknown[];\n /** Labels an option value that carries no explicit label. */\n format?: (value: unknown) => string;\n /** Trigger label. Defaults to `column`. */\n label?: ReactNode;\n /** Tick any number of values (an `IN` list). `false` keeps at most one ticked. */\n multiple?: boolean;\n /** Published once, when the control first connects. A bare value or an array of them. */\n defaultValue?: unknown;\n /** Most-frequent values kept; past this the list says so rather than rendering the tail. */\n limit?: number;\n /**\n * Draw a filter field above the values.\n *\n * It narrows **the fetched page** — the `limit` most frequent values this control asked for — and\n * runs no query of its own. When the page is truncated the note and the no-match message say so,\n * because a field that answers \"no matching values\" about a database it never asked is worse than\n * no field. Searching the column itself is `ChartSearch`, which publishes a match clause.\n *\n * @default false\n */\n searchable?: boolean;\n disabled?: boolean;\n size?: ControlSize;\n /** Class for the trigger; `className` styles the wrapper. */\n controlClassName?: string;\n}\n\n/**\n * A `GROUP BY` over a high-cardinality column can answer with thousands of rows, and neither the\n * wire nor the DOM wants them. The lookup is capped at the *most frequent* values — the ones a\n * facet is for — and the control owns up to the truncation instead of pretending the tail is\n * absent. Needle-in-a-haystack lookup is `ChartSearch`'s job, not a facet filter's.\n */\nconst FILTER_LIMIT = 50;\n\ninterface FilterEntry {\n key: string;\n label: string;\n raw: unknown;\n count?: number;\n icon?: React.ComponentType<{ className?: string }>;\n}\n\nfunction isOptionObject(value: unknown): value is ChartFilterOption {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) && \"value\" in value;\n}\n\nfunction defaultFormat(value: unknown): string {\n return String(value ?? \"\");\n}\n\n/** DuckDB answers a `count(*)` with a BigInt over the WASM bridge. */\nfunction toCount(value: unknown): number | undefined {\n if (typeof value === \"number\") return value;\n if (typeof value === \"bigint\") return Number(value);\n return undefined;\n}\n\nfunction truncationNote(truncated: boolean, limit: number, searchable: boolean) {\n if (!truncated) return undefined;\n return searchable\n ? `Top ${limit} values — the field searches these, not the column. Filter further for the rest.`\n : `Top ${limit} values — filter further to see the rest.`;\n}\n\nfunction filterEntries(\n options: readonly unknown[] | undefined,\n rows: readonly QueryRow[] | null,\n format: (value: unknown) => string,\n): FilterEntry[] {\n if (options) {\n return options.map((entry) => {\n const option = isOptionObject(entry) ? entry : { value: entry };\n return {\n key: String(option.value),\n label: option.label ?? format(option.value),\n raw: option.value,\n icon: option.icon,\n };\n });\n }\n return (rows ?? []).map((row) => ({\n key: String(row.value),\n label: format(row.value),\n raw: row.value,\n count: toCount(row.count),\n }));\n}\n\n/**\n * A facet filter over a column's values, publishing a **points** clause — the same `FacetFilter`\n * surface as the table's, so a dashboard has one filter idiom rather than two.\n *\n * The values are a query, not a prop: the control connects as a Mosaic client and asks for\n * `SELECT <column>, count(*) … GROUP BY <column>` filtered by `filterBy`. The counts therefore\n * react to the *other* filters but not to this one — a crossfilter resolver drops a clause when it\n * builds the predicate for the clause's own source, and `clausePoints` tags the clause with this\n * client. (A non-crossfilter `filterBy` has no such exemption and would count itself.)\n */\nexport function ChartFilter(props: ChartFilterProps) {\n const {\n table,\n column,\n field = column,\n as,\n filterBy,\n options,\n format = defaultFormat,\n label = column,\n multiple = true,\n defaultValue,\n limit = FILTER_LIMIT,\n searchable = false,\n disabled,\n size = \"md\",\n className,\n controlClassName,\n children,\n slot,\n ...rest\n } = props;\n const { crossfilter } = useMosaic();\n const target = as === undefined ? crossfilter : as;\n const source = filterBy === undefined ? crossfilter : filterBy;\n const lookup = options === undefined && Boolean(table && column);\n\n const { rows, selected, publish, activate } = useMosaicInput<readonly unknown[]>({\n as: target,\n filterBy: source,\n deps: [table, column, lookup, limit],\n activateValue: [0],\n build: (filter) =>\n lookup\n ? Query.from(table as string)\n .select({ value: column as string, count: sqlCount() })\n .where(filter)\n .groupby(column as string)\n .orderby(sqlDesc(sqlCount()), column as string)\n // One past the cap: enough to know the tail exists without asking a second question.\n .limit(limit + 1)\n : null,\n clause: (client, value) =>\n multiple\n ? clausePoints([field as string], value?.map((entry) => [entry]), { source: client })\n : clausePoint(field as string, value?.[0], { source: client }),\n decode: (value) => (multiple ? (value as unknown[][]).map((tuple) => tuple[0]) : [value]),\n });\n\n const primed = useRef(false);\n useEffect(() => {\n if (primed.current || defaultValue === undefined) return;\n primed.current = true;\n publish(Array.isArray(defaultValue) ? (defaultValue as unknown[]) : [defaultValue]);\n }, [defaultValue, publish]);\n\n const values = selected ?? [];\n\n const fetched = filterEntries(options, rows, format);\n const truncated = fetched.length > limit;\n const entries = truncated ? fetched.slice(0, limit) : fetched;\n const items: FacetFilterItem[] = entries.map((entry) => ({\n count: entry.count,\n icon: entry.icon,\n label: entry.label,\n value: entry.key,\n }));\n\n // `FacetFilter` speaks strings; a clause carries the raw value the query answered with, so the\n // way back from a ticked row to `alpha` / `3` / `true` is this map. The selected values are in it\n // too: one of them may be a value the crossfilter has since faceted away, which `FacetFilter`\n // keeps listed so it can still be unticked.\n const raw = new Map<string, unknown>(entries.map((entry) => [entry.key, entry.raw]));\n for (const value of values) raw.set(String(value), value);\n\n return (\n <ark.div\n className={cn(\"w-fit\", className)}\n {...rest}\n {...warmUpHandlers(activate, props)}\n data-slot={slot ?? \"chart-filter\"}\n >\n <FacetFilter\n className={controlClassName}\n slot=\"chart-filter-trigger\"\n disabled={disabled}\n empty={lookup && rows === null ? \"Loading…\" : \"No values.\"}\n items={items}\n label={label}\n multiple={multiple}\n // A search field over a truncated list is a half-truth unless it names its own scope, so\n // both messages change when the tail was dropped: the field covers the fetched page, and a\n // miss inside it says nothing about the column.\n note={truncationNote(truncated, limit, searchable)}\n onValueChange={(next) => publish(next.map((key) => (raw.has(key) ? raw.get(key) : key)))}\n searchEmpty={truncated ? `No match among the top ${limit} values.` : undefined}\n searchable={searchable}\n size={size}\n value={values.map((value) => String(value))}\n />\n {children}\n </ark.div>\n );\n}\n\n// ── ChartSearch ──────────────────────────────────────────────────────────────\n\nexport interface ChartSearchProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"defaultValue\">,\n SelectionProps {\n /** The relation the autocomplete values come from. */\n table?: string;\n /** The column to search. */\n column?: string;\n /** The column named in the published clause. Defaults to `column`. */\n field?: string;\n /** Where the query string has to appear. */\n type?: \"contains\" | \"prefix\" | \"suffix\" | \"regexp\";\n caseSensitive?: boolean;\n label?: ReactNode;\n placeholder?: string;\n /** ms of quiet before publishing. `0` publishes on every keystroke, as vgplot does. */\n debounce?: number;\n /** Distinct values offered as a native autocomplete list. `0` skips the lookup entirely. */\n autocompleteLimit?: number;\n defaultValue?: string;\n disabled?: boolean;\n size?: ControlSize;\n /** Class for the control; `className` styles the wrapper. */\n controlClassName?: string;\n}\n\n/**\n * A text box publishing a **match** clause (`contains` / `prefix` / `suffix` / `regexp`).\n *\n * Every keystroke is a query, so the publish is debounced by default. The distinct values of the\n * column feed a native `<datalist>`, exactly as vgplot's `search` does — and, as there, the list\n * narrows with the query, since a match clause does not exempt its own client from the crossfilter.\n */\nexport function ChartSearch(props: ChartSearchProps) {\n const {\n table,\n column,\n field = column,\n as,\n filterBy,\n type = \"contains\",\n caseSensitive = false,\n label,\n placeholder = \"Search…\",\n debounce = 200,\n autocompleteLimit = 100,\n defaultValue,\n disabled,\n size = \"md\",\n className,\n controlClassName,\n children,\n slot,\n ...rest\n } = props;\n const { crossfilter } = useMosaic();\n const target = as === undefined ? crossfilter : as;\n const source = filterBy === undefined ? crossfilter : filterBy;\n const lookup = autocompleteLimit > 0 && Boolean(table && column);\n\n const { rows, selected, publish, activate } = useMosaicInput<string>({\n as: target,\n filterBy: source,\n deps: [table, column, lookup, autocompleteLimit],\n activateValue: \"\",\n build: (filter) =>\n lookup\n ? Query.from(table as string)\n .select({ value: column as string })\n .distinct()\n .where(filter)\n .orderby(column as string)\n .limit(autocompleteLimit)\n : null,\n clause: (client, value) =>\n clauseMatch(field as string, value ?? null, { source: client, method: type, caseSensitive }),\n });\n\n // The completions, filtered in the browser. The query already asked the database for the\n // `autocompleteLimit` distinct values of the column; narrowing THOSE as you type is not a second\n // question, so it does not become a second query.\n const items = useMemo(\n () => (rows ?? []).map((row) => ({ label: String(row.value), value: String(row.value) })),\n [rows],\n );\n const capped = items.length >= autocompleteLimit;\n const { contains } = useFilter({ sensitivity: \"base\" });\n const { collection, filter: completions, set } = useListCollection<{\n label: string;\n value: string;\n }>({ filter: contains, initialItems: [] });\n\n // Keyed on the values, not on the array: `rows` is a fresh array on every settled query, and\n // setting the collection from an identity-unstable dep is a render loop.\n const itemsKey = items.map((item) => item.value).join(\"\\u0000\");\n useEffect(() => {\n set(items);\n // `items` is derived from `itemsKey`; depending on it directly is the loop above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [itemsKey, set]);\n\n const [text, setText] = useState(defaultValue ?? \"\");\n const published = useRef(defaultValue ?? \"\");\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n const external = selected ?? \"\";\n if (external !== published.current) {\n published.current = external;\n setText(external);\n }\n }, [selected]);\n\n useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);\n\n const primed = useRef(false);\n useEffect(() => {\n if (primed.current || !defaultValue) return;\n primed.current = true;\n publish(defaultValue);\n }, [defaultValue, publish]);\n\n const change = (next: string) => {\n setText(next);\n if (timer.current) clearTimeout(timer.current);\n const send = () => {\n published.current = next;\n publish(next || undefined);\n };\n if (debounce > 0) timer.current = setTimeout(send, debounce);\n else send();\n };\n\n return (\n <Field\n className={cn(\"w-fit min-w-48 gap-1.5\", className)}\n disabled={disabled}\n {...rest}\n {...warmUpHandlers(activate, props)}\n slot={slot ?? \"chart-search\"}\n >\n {label ? <FieldLabel>{label}</FieldLabel> : null}\n {lookup ? (\n // `allowCustomValue`, because this control is a FILTER and not a picker: what it publishes\n // is a match clause over whatever you typed, and the list only ever offered completions.\n // A `<datalist>` was the honest first answer to that and a poor one — unstyleable, different\n // in every browser, no empty state, and silent about its own cap. Same clause, same\n // debounce; only the surface changed.\n <Combobox\n allowCustomValue\n collection={collection}\n slot=\"chart-search-list\"\n disabled={disabled}\n inputValue={text}\n onInputValueChange={(details) => {\n completions(details.inputValue);\n change(details.inputValue);\n }}\n onValueChange={(details) => {\n const picked = details.value[0];\n if (picked !== undefined) change(picked);\n }}\n >\n <ComboboxInput className={controlClassName} placeholder={placeholder} size={size} />\n <ComboboxContent>\n <ComboboxEmpty>No matching values.</ComboboxEmpty>\n {collection.items.map((item) => (\n <ComboboxItem item={item} key={item.value}>\n {item.label}\n </ComboboxItem>\n ))}\n {/* The cap, said out loud. The datalist could not: it silently stopped at its limit,\n so \"nothing else matches\" and \"nothing else was asked for\" looked identical. */}\n {capped ? (\n <p className=\"border-t px-2 py-1.5 text-muted-foreground text-xs\">\n First {autocompleteLimit}. Keep typing to narrow it.\n </p>\n ) : null}\n </ComboboxContent>\n </Combobox>\n ) : (\n <Input\n className={controlClassName}\n disabled={disabled}\n onChange={(event) => change(event.target.value)}\n placeholder={placeholder}\n size={size}\n type=\"search\"\n value={text}\n />\n )}\n {children}\n </Field>\n );\n}\n\n// ── ChartSlider ──────────────────────────────────────────────────────────────\n\nexport interface ChartSliderProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"defaultValue\">,\n SelectionProps {\n /** The relation the extent comes from, when `min`/`max` are not both given. */\n table?: string;\n /** The numeric column. */\n column?: string;\n /** The column named in the published clause. Defaults to `column`. */\n field?: string;\n /**\n * `point` publishes an equality clause for one thumb; `interval` publishes a two-thumb range.\n * (vgplot's slider only ever offers `[min, value]`; ours brushes a real interval.)\n */\n select?: \"point\" | \"interval\";\n min?: number;\n max?: number;\n step?: number;\n /** Published once, when the control first connects. */\n defaultValue?: number | readonly [number, number];\n label?: ReactNode;\n /** Show the current value beside the label. */\n showValue?: boolean;\n disabled?: boolean;\n}\n\ntype SliderValueType = number | readonly [number, number];\n\ninterface SliderExtent {\n min: number;\n max: number;\n step: number;\n}\n\n/** vgplot's `(max - min) / 500`, except an all-integer extent steps by 1 — 500 fractions of a count is noise. */\nfunction defaultStep(min: number, max: number): number {\n if (Number.isInteger(min) && Number.isInteger(max)) return 1;\n return (max - min) / 500 || 1;\n}\n\n/**\n * A slider over a numeric column, publishing a **point** or **interval** clause.\n *\n * The extent is a query when `min`/`max` are not both given: `SELECT min(col), max(col)`, answered\n * by the coordinator. A `Skeleton` shaped like the track holds the space until it arrives.\n */\nexport function ChartSlider(props: ChartSliderProps) {\n const {\n table,\n column,\n field = column,\n as,\n filterBy,\n select = \"point\",\n min,\n max,\n step,\n defaultValue,\n label,\n showValue = true,\n disabled,\n className,\n children,\n slot,\n ...rest\n } = props;\n const { crossfilter } = useMosaic();\n const target = as === undefined ? crossfilter : as;\n const source = filterBy === undefined ? crossfilter : filterBy;\n const lookup = (min === undefined || max === undefined) && Boolean(table && column);\n // The binning hint for the interval clause. A ref, not `extent` itself: reading the memo from\n // the clause would make the widget's own row type circular.\n const pixelSize = useRef(1);\n\n const { rows, selected, publish, activate } = useMosaicInput<SliderValueType>({\n as: target,\n filterBy: source,\n deps: [table, column, lookup],\n activateValue: select === \"interval\" ? ([0, 0] as const) : 0,\n build: (filter) =>\n lookup\n ? Query.select({ min: sqlMin(column as string), max: sqlMax(column as string) })\n .from(table as string)\n .where(filter)\n : null,\n clause: (client, value) => {\n if (select !== \"interval\") return clausePoint(field as string, value, { source: client });\n const domain = value as [number, number] | undefined;\n return clauseInterval(field as string, domain ?? null, {\n source: client,\n bin: \"ceil\",\n scale: domain ? { type: \"identity\", domain } : undefined,\n pixelSize: pixelSize.current,\n });\n },\n });\n\n const extent = useMemo<SliderExtent | null>(() => {\n const row = rows?.[0];\n const low = min ?? (typeof row?.min === \"number\" ? row.min : undefined);\n const high = max ?? (typeof row?.max === \"number\" ? row.max : undefined);\n if (low === undefined || high === undefined) return null;\n return { min: low, max: high, step: step ?? defaultStep(low, high) };\n }, [rows, min, max, step]);\n\n useEffect(() => {\n pixelSize.current = extent?.step ?? 1;\n }, [extent]);\n\n const [values, setValues] = useState<number[] | null>(\n defaultValue === undefined ? null : [...toValues(defaultValue)],\n );\n /** Distinguishes \"never published\" from \"published and then cleared\" — see the effect below. */\n const held = useRef(false);\n\n useEffect(() => {\n if (selected !== undefined) {\n held.current = true;\n setValues([...toValues(selected)]);\n return;\n }\n // The clause is gone: a `Selection.reset()`, a \"Clear filters\" button, another control\n // retracting it. Fall back to the extent instead of leaving the thumbs where they were — a\n // slider that still reads 5–22 while nothing is filtered is a control lying about the state,\n // which is worse than one that never moved. Gated on `held` so the first render does not\n // stomp on `defaultValue` before the priming effect has published it.\n if (held.current) {\n held.current = false;\n setValues(null);\n }\n }, [selected]);\n\n const primed = useRef(false);\n useEffect(() => {\n if (primed.current || defaultValue === undefined) return;\n primed.current = true;\n publish(defaultValue);\n }, [defaultValue, publish]);\n\n const change = (next: number[]) => {\n setValues(next);\n publish(select === \"interval\" ? [next[0] as number, next[1] as number] : (next[0] as number));\n };\n\n const shown = values ?? (extent ? (select === \"interval\" ? [extent.min, extent.max] : [extent.min]) : null);\n\n return (\n <ark.div\n className={cn(\"flex w-full min-w-48 flex-col gap-1.5\", className)}\n {...rest}\n {...warmUpHandlers(activate, props)}\n data-slot={slot ?? \"chart-slider\"}\n >\n {extent && shown ? (\n <Slider\n disabled={disabled}\n max={extent.max}\n min={extent.min}\n onValueChange={(details) => change(details.value)}\n step={extent.step}\n value={shown}\n >\n {label || showValue ? (\n <div className=\"flex items-center\">\n {label ? <SliderLabel>{label}</SliderLabel> : null}\n {showValue ? <SliderValue /> : null}\n </div>\n ) : null}\n </Slider>\n ) : (\n <Skeleton className=\"h-2 w-full rounded-full\" />\n )}\n {children}\n </ark.div>\n );\n}\n\nfunction toValues(value: SliderValueType): readonly number[] {\n return typeof value === \"number\" ? [value] : value;\n}\n"],"names":["min","max"],"mappings":";;;;;;;;;;;;;;;;;AA0FO;AAGL;AAGA;AAEA;AAKA;AACE;AAAqB;AACP;AAC2B;AACvC;AAEF;AAKE;AAC+B;AACjC;AAKA;AACA;AACE;AAEA;AAA6E;AAE/E;AAGE;AAGyF;AAC3F;AAGF;AAAgB;AAEZ;AACA;AACA;AAAoE;AACtE;AACG;AAIH;AACA;AAA6F;AAG/F;AACF;AAGA;AAIE;AAAO;;AAEH;AACA;AACF;;AAEE;AACoB;AACtB;AAEJ;AAgEA;AAUA;AACE;AACF;AAEA;AACE;AACF;AAGA;AACE;AACA;AAEF;AAEA;AACE;AACA;AAGF;AAEA;AAKE;AAEI;AACA;AAAO;AACmB;AACkB;AAC9B;AACC;AAAA;AAIe;AACX;AACE;AACd;AACe;AAE5B;AAYO;AACL;AAAM;AACJ;AACA;AACQ;AACR;AACA;AACA;AACS;AACD;AACG;AACX;AACQ;AACK;AACb;AACO;AACP;AACA;AACA;AACA;AACG;AAO4E;AAC3E;AACM;AACyB;AAClB;AAUX;AAI2D;AACsB;AAIzF;AACE;AAEkF;AAGpF;AAKyD;AAC1C;AACD;AACC;AACA;AAQf;AAEA;AACE;AAAK;AAAJ;AACiC;AAC5B;AAC8B;AACf;AAEnB;AAAA;AAAC;AAAA;AACY;AACN;AACL;AAC8C;AAC9C;AACA;AACA;AAIiD;AACsC;AAClB;AACrE;AACA;AAC0C;AAAA;AAAA;AAE3C;AAAA;AAAA;AAGP;AAoCO;AACL;AAAM;AACJ;AACA;AACQ;AACR;AACA;AACO;AACS;AAChB;AACc;AACH;AACS;AACpB;AACA;AACO;AACP;AACA;AACA;AACA;AACG;AAOgE;AAC/D;AACM;AACqC;AAChC;AAST;AAEuF;AAMjF;AAC4E;AACnF;AAYP;AACE;AAAS;AAKX;AAIA;AACE;AACA;AAEkB;AAII;AAA6C;AAErE;AACA;AACE;AAEoB;AAGtB;AACE;AAEA;AACE;AACyB;AAE3B;AACK;AAGP;AACE;AAAC;AAAA;AACkD;AACjD;AACI;AAC8B;AACpB;AAEb;AAA2C;AAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAMC;AAAC;AAAA;AACiB;AAChB;AACK;AACL;AACY;AAEV;AACyB;AAC3B;AAEE;AACA;AAAuC;AACzC;AAEA;AAAkF;AAEhF;AAAkC;AAKjC;AAImE;AAAA;AACzD;AAAkB;AAEzB;AACN;AAAA;AAAA;AAAA;AAGF;AAAC;AAAA;AACY;AACX;AAC8C;AAC9C;AACA;AACK;AACE;AAAA;AAAA;AAGV;AAAA;AAAA;AAGP;AAsCA;AACE;AAEF;AAQO;AACL;AAAM;AACJ;AACA;AACQ;AACR;AACA;AACS;AACTA;AACAC;AACA;AACA;AACA;AACY;AACZ;AACA;AACA;AACA;AACG;AAUyE;AACxE;AACM;AACkB;AAC+B;AAMrD;AAEJ;AACA;AACA;AAAuD;AAC7C;AACH;AAC0C;AAC1B;AACtB;AACH;AAIA;AAGA;AACiE;AAGnE;AACE;AAAoC;AAGtC;AAA4B;AACoC;AAKhE;AACE;AACE;AAEA;AAAA;AAOF;AAEgB;AAIlB;AACA;AACE;AAEoB;AAGtB;AACE;AAC4F;AAK9F;AACE;AAAK;AAAJ;AACiE;AAC5D;AAC8B;AACf;AAElB;AACC;AAAC;AAAA;AACC;AACY;AACA;AACoC;AACnC;AACN;AAIF;AAA6C;AACf;AAE/B;AAAA;AAGwC;AAE/C;AAAA;AAAA;AAGP;AAEA;AACE;AACF;;;;;;;"}
1
+ {"version":3,"file":"chart-inputs.js","sources":["../../src/charts/chart-inputs.tsx"],"sourcesContent":["\"use client\";\n\nimport type { TableExpr } from \"@kanzo-tech/mosaic\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type React from \"react\";\nimport { ark } from \"@ark-ui/react/factory\";\nimport {\n clauseInterval,\n clauseMatch,\n clausePoint,\n clausePoints,\n MosaicClient,\n type Selection,\n type SelectionClause,\n} from \"@uwdata/mosaic-core\";\nimport {\n count as sqlCount,\n desc as sqlDesc,\n max as sqlMax,\n min as sqlMin,\n Query,\n type FilterExpr,\n} from \"@uwdata/mosaic-sql\";\nimport { useListCollection } from \"@ark-ui/react/collection\";\nimport { useFilter } from \"@ark-ui/react/locale\";\nimport { cn } from \"../lib/cn.js\";\nimport {\n Combobox,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxInput,\n ComboboxItem,\n} from \"../simples/combobox.js\";\nimport { FacetFilter, type FacetFilterItem } from \"../simples/FacetFilter.js\";\nimport { Field, FieldLabel } from \"../simples/field.js\";\nimport { Input } from \"../simples/input.js\";\nimport { Skeleton } from \"../simples/skeleton.js\";\nimport { Slider, SliderLabel, SliderValue } from \"../simples/slider.js\";\nimport { chartTableKey } from \"./chart-spec.js\";\nimport { useMosaic } from \"./mosaic-provider.js\";\nimport { ChartQueryClient, type ChartQueryRow as QueryRow } from \"./query-client.js\";\n\n/**\n * Mosaic **inputs** — controls that publish into a `Selection` without being charts.\n *\n * Unlike marks and interactors, these are real DOM parts (`ark.*`, `data-slot`, forwarded props),\n * not inert descriptors: they carry no `__chart` static and belong *outside* `<ChartRoot>`. What\n * makes them Mosaic and not just a styled `<select>` is that each one is a `MosaicClient`: it\n * declares a `query()` for the values it needs (grouped counts, distinct values, a numeric extent),\n * the coordinator answers it, and the control publishes `clausePoints` / `clauseMatch` /\n * `clauseInterval` back into the selection the charts filter by.\n *\n * The vgplot equivalents (`vg.menu`, `vg.search`, `vg.slider`) build raw HTML. These use our\n * controls, our tokens and our accessibility, and are controllable *and* observable: an external\n * change to the selection flows back into the widget.\n */\n\n\nexport interface MosaicInputOptions<T> {\n /** Filters the widget's own lookup query. `null` = the full relation. */\n filterBy: Selection | null;\n /** Where the widget publishes. `null` = nowhere. */\n as: Selection | null;\n /** The lookup query, or `null` when there is nothing to ask the coordinator for. */\n build: (filter: FilterExpr) => Query | null;\n /** Rebuilds the client — and re-runs the lookup — when any entry changes. */\n deps: readonly unknown[];\n clause: (source: MosaicClient, value: T | undefined) => SelectionClause;\n /** The representative value of the warm-up clause published on hover/focus. */\n activateValue: T;\n /** Reads the widget's own clause value back out of the selection. Defaults to an identity cast. */\n decode?: (value: unknown) => T;\n}\n\nexport interface MosaicInputState<T> {\n /** Lookup rows, or `null` while the query is in flight (or when there is no query). */\n rows: readonly QueryRow[] | null;\n /** The value this widget currently holds in the selection — its observable half. */\n selected: T | undefined;\n publish: (value: T | undefined) => void;\n activate: () => void;\n}\n\n/**\n * The conversation with Mosaic that every input in this file has: a lookup query that re-runs under\n * the crossfilter, a clause published with `source` set so a control never filters itself, and a\n * warm-up so the first interaction is not the first query.\n *\n * Exported because the three controls here are surfaces over it, not the only surfaces it allows —\n * a chip row, a segmented switch or a map lasso is the same conversation with different chrome, and\n * without this the alternative is reimplementing it.\n */\nexport function useMosaicInput<T>(\n options: MosaicInputOptions<T>\n): MosaicInputState<T> {\n const { filterBy, as, deps } = options;\n const { coordinator } = useMosaic();\n const latest = useRef(options);\n latest.current = options;\n\n const clientRef = useRef<ChartQueryClient | null>(null);\n const [client, setClient] = useState<ChartQueryClient | null>(null);\n const [rows, setRows] = useState<readonly QueryRow[] | null>(null);\n const [selected, setSelected] = useState<T | undefined>(undefined);\n\n useEffect(() => {\n const instance = new ChartQueryClient(\n filterBy ?? undefined,\n (filter) => latest.current.build(filter),\n setRows,\n );\n clientRef.current = instance;\n setRows(null);\n setClient(instance);\n coordinator.connect(instance);\n return () => {\n clientRef.current = null;\n coordinator.disconnect(instance);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [coordinator, filterBy, ...deps]);\n\n useEffect(() => {\n if (!as || !client) return;\n const sync = () => {\n const raw = as.valueFor(client);\n const { decode } = latest.current;\n setSelected(raw === undefined ? undefined : decode ? decode(raw) : (raw as T));\n };\n sync();\n as.addEventListener(\"value\", sync);\n return () => {\n as.removeEventListener(\"value\", sync);\n // `Selection.remove` returns a *clone*; the live way to retract a clause is to publish an\n // empty one, which the resolver drops because its predicate is null.\n if (as.valueFor(client) !== undefined) as.update(latest.current.clause(client, undefined));\n };\n }, [as, client]);\n\n const publish = useCallback(\n (value: T | undefined) => {\n setSelected(value);\n const instance = clientRef.current;\n if (as && instance) as.update(latest.current.clause(instance, value));\n },\n [as],\n );\n\n const activate = useCallback(() => {\n const instance = clientRef.current;\n if (as && instance) as.activate(latest.current.clause(instance, latest.current.activateValue));\n }, [as]);\n\n return { rows, selected, publish, activate };\n}\n\n/** vgplot's inputs warm the selection on hover/focus; ours do it on the wrapper, and compose. */\nfunction warmUpHandlers(\n activate: () => void,\n props: Pick<React.ComponentProps<typeof ark.div>, \"onFocus\" | \"onPointerEnter\">,\n) {\n return {\n onFocus: (event: React.FocusEvent<HTMLDivElement>) => {\n props.onFocus?.(event);\n activate();\n },\n onPointerEnter: (event: React.PointerEvent<HTMLDivElement>) => {\n props.onPointerEnter?.(event);\n if (!event.buttons) activate();\n },\n };\n}\n\ninterface SelectionProps {\n /** Where the control publishes. Defaults to the provider's crossfilter; `null` = nowhere. */\n as?: Selection | null;\n /** What the control's own lookup filters by. Defaults to the crossfilter; `null` = unfiltered. */\n filterBy?: Selection | null;\n}\n\ntype ControlSize = \"sm\" | \"md\" | \"lg\";\n\n// ── ChartFilter ──────────────────────────────────────────────────────────────\n\n/** An explicit entry. Bare values work too — they are labelled by `format`. */\nexport interface ChartFilterOption {\n value: unknown;\n label?: string;\n icon?: React.ComponentType<{ className?: string }>;\n}\n\nexport interface ChartFilterProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"defaultValue\">,\n SelectionProps {\n /** The relation the values and their counts come from, as `ChartRoot` takes it. */\n table?: TableExpr;\n /** The column grouped by. */\n column?: string;\n /** The column named in the published clause. Defaults to `column`. */\n field?: string;\n /** Explicit options — a literal value or a `{ value, label, icon }` object. Skips the lookup. */\n options?: readonly unknown[];\n /** Labels an option value that carries no explicit label. */\n format?: (value: unknown) => string;\n /** Trigger label. Defaults to `column`. */\n label?: ReactNode;\n /** Tick any number of values (an `IN` list). `false` keeps at most one ticked. */\n multiple?: boolean;\n /** Published once, when the control first connects. A bare value or an array of them. */\n defaultValue?: unknown;\n /** Most-frequent values kept; past this the list says so rather than rendering the tail. */\n limit?: number;\n /**\n * Draw a filter field above the values.\n *\n * It narrows **the fetched page** — the `limit` most frequent values this control asked for — and\n * runs no query of its own. When the page is truncated the note and the no-match message say so,\n * because a field that answers \"no matching values\" about a database it never asked is worse than\n * no field. Searching the column itself is `ChartSearch`, which publishes a match clause.\n *\n * @default false\n */\n searchable?: boolean;\n disabled?: boolean;\n size?: ControlSize;\n /** Class for the trigger; `className` styles the wrapper. */\n controlClassName?: string;\n}\n\n/**\n * A `GROUP BY` over a high-cardinality column can answer with thousands of rows, and neither the\n * wire nor the DOM wants them. The lookup is capped at the *most frequent* values — the ones a\n * facet is for — and the control owns up to the truncation instead of pretending the tail is\n * absent. Needle-in-a-haystack lookup is `ChartSearch`'s job, not a facet filter's.\n */\nconst FILTER_LIMIT = 50;\n\ninterface FilterEntry {\n key: string;\n label: string;\n raw: unknown;\n count?: number;\n icon?: React.ComponentType<{ className?: string }>;\n}\n\nfunction isOptionObject(value: unknown): value is ChartFilterOption {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) && \"value\" in value;\n}\n\nfunction defaultFormat(value: unknown): string {\n return String(value ?? \"\");\n}\n\n/** DuckDB answers a `count(*)` with a BigInt over the WASM bridge. */\nfunction toCount(value: unknown): number | undefined {\n if (typeof value === \"number\") return value;\n if (typeof value === \"bigint\") return Number(value);\n return undefined;\n}\n\nfunction truncationNote(truncated: boolean, limit: number, searchable: boolean) {\n if (!truncated) return undefined;\n return searchable\n ? `Top ${limit} values — the field searches these, not the column. Filter further for the rest.`\n : `Top ${limit} values — filter further to see the rest.`;\n}\n\nfunction filterEntries(\n options: readonly unknown[] | undefined,\n rows: readonly QueryRow[] | null,\n format: (value: unknown) => string,\n): FilterEntry[] {\n if (options) {\n return options.map((entry) => {\n const option = isOptionObject(entry) ? entry : { value: entry };\n return {\n key: String(option.value),\n label: option.label ?? format(option.value),\n raw: option.value,\n icon: option.icon,\n };\n });\n }\n return (rows ?? []).map((row) => ({\n key: String(row.value),\n label: format(row.value),\n raw: row.value,\n count: toCount(row.count),\n }));\n}\n\n/**\n * A facet filter over a column's values, publishing a **points** clause — the same `FacetFilter`\n * surface as the table's, so a dashboard has one filter idiom rather than two.\n *\n * The values are a query, not a prop: the control connects as a Mosaic client and asks for\n * `SELECT <column>, count(*) … GROUP BY <column>` filtered by `filterBy`. The counts therefore\n * react to the *other* filters but not to this one — a crossfilter resolver drops a clause when it\n * builds the predicate for the clause's own source, and `clausePoints` tags the clause with this\n * client. (A non-crossfilter `filterBy` has no such exemption and would count itself.)\n */\nexport function ChartFilter(props: ChartFilterProps) {\n const {\n table,\n column,\n field = column,\n as,\n filterBy,\n options,\n format = defaultFormat,\n label = column,\n multiple = true,\n defaultValue,\n limit = FILTER_LIMIT,\n searchable = false,\n disabled,\n size = \"md\",\n className,\n controlClassName,\n children,\n slot,\n ...rest\n } = props;\n const { crossfilter } = useMosaic();\n const target = as === undefined ? crossfilter : as;\n const source = filterBy === undefined ? crossfilter : filterBy;\n const lookup = options === undefined && Boolean(table && column);\n\n const { rows, selected, publish, activate } = useMosaicInput<readonly unknown[]>({\n as: target,\n filterBy: source,\n deps: [chartTableKey(table), column, lookup, limit],\n activateValue: [0],\n build: (filter) =>\n lookup\n ? Query.from(table as TableExpr)\n .select({ value: column as string, count: sqlCount() })\n .where(filter)\n .groupby(column as string)\n .orderby(sqlDesc(sqlCount()), column as string)\n // One past the cap: enough to know the tail exists without asking a second question.\n .limit(limit + 1)\n : null,\n clause: (client, value) =>\n multiple\n ? clausePoints([field as string], value?.map((entry) => [entry]), { source: client })\n : clausePoint(field as string, value?.[0], { source: client }),\n decode: (value) => (multiple ? (value as unknown[][]).map((tuple) => tuple[0]) : [value]),\n });\n\n const primed = useRef(false);\n useEffect(() => {\n if (primed.current || defaultValue === undefined) return;\n primed.current = true;\n publish(Array.isArray(defaultValue) ? (defaultValue as unknown[]) : [defaultValue]);\n }, [defaultValue, publish]);\n\n const values = selected ?? [];\n\n const fetched = filterEntries(options, rows, format);\n const truncated = fetched.length > limit;\n const entries = truncated ? fetched.slice(0, limit) : fetched;\n const items: FacetFilterItem[] = entries.map((entry) => ({\n count: entry.count,\n icon: entry.icon,\n label: entry.label,\n value: entry.key,\n }));\n\n // `FacetFilter` speaks strings; a clause carries the raw value the query answered with, so the\n // way back from a ticked row to `alpha` / `3` / `true` is this map. The selected values are in it\n // too: one of them may be a value the crossfilter has since faceted away, which `FacetFilter`\n // keeps listed so it can still be unticked.\n const raw = new Map<string, unknown>(entries.map((entry) => [entry.key, entry.raw]));\n for (const value of values) raw.set(String(value), value);\n\n return (\n <ark.div\n className={cn(\"w-fit\", className)}\n {...rest}\n {...warmUpHandlers(activate, props)}\n data-slot={slot ?? \"chart-filter\"}\n >\n <FacetFilter\n className={controlClassName}\n slot=\"chart-filter-trigger\"\n disabled={disabled}\n empty={lookup && rows === null ? \"Loading…\" : \"No values.\"}\n items={items}\n label={label}\n multiple={multiple}\n // A search field over a truncated list is a half-truth unless it names its own scope, so\n // both messages change when the tail was dropped: the field covers the fetched page, and a\n // miss inside it says nothing about the column.\n note={truncationNote(truncated, limit, searchable)}\n onValueChange={(next) => publish(next.map((key) => (raw.has(key) ? raw.get(key) : key)))}\n searchEmpty={truncated ? `No match among the top ${limit} values.` : undefined}\n searchable={searchable}\n size={size}\n value={values.map((value) => String(value))}\n />\n {children}\n </ark.div>\n );\n}\n\n// ── ChartSearch ──────────────────────────────────────────────────────────────\n\nexport interface ChartSearchProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"defaultValue\">,\n SelectionProps {\n /** The relation the autocomplete values come from, as `ChartRoot` takes it. */\n table?: TableExpr;\n /** The column to search. */\n column?: string;\n /** The column named in the published clause. Defaults to `column`. */\n field?: string;\n /** Where the query string has to appear. */\n type?: \"contains\" | \"prefix\" | \"suffix\" | \"regexp\";\n caseSensitive?: boolean;\n label?: ReactNode;\n placeholder?: string;\n /** ms of quiet before publishing. `0` publishes on every keystroke, as vgplot does. */\n debounce?: number;\n /** Distinct values offered as a native autocomplete list. `0` skips the lookup entirely. */\n autocompleteLimit?: number;\n defaultValue?: string;\n disabled?: boolean;\n size?: ControlSize;\n /** Class for the control; `className` styles the wrapper. */\n controlClassName?: string;\n}\n\n/**\n * A text box publishing a **match** clause (`contains` / `prefix` / `suffix` / `regexp`).\n *\n * Every keystroke is a query, so the publish is debounced by default. The distinct values of the\n * column feed a native `<datalist>`, exactly as vgplot's `search` does — and, as there, the list\n * narrows with the query, since a match clause does not exempt its own client from the crossfilter.\n */\nexport function ChartSearch(props: ChartSearchProps) {\n const {\n table,\n column,\n field = column,\n as,\n filterBy,\n type = \"contains\",\n caseSensitive = false,\n label,\n placeholder = \"Search…\",\n debounce = 200,\n autocompleteLimit = 100,\n defaultValue,\n disabled,\n size = \"md\",\n className,\n controlClassName,\n children,\n slot,\n ...rest\n } = props;\n const { crossfilter } = useMosaic();\n const target = as === undefined ? crossfilter : as;\n const source = filterBy === undefined ? crossfilter : filterBy;\n const lookup = autocompleteLimit > 0 && Boolean(table && column);\n\n const { rows, selected, publish, activate } = useMosaicInput<string>({\n as: target,\n filterBy: source,\n deps: [chartTableKey(table), column, lookup, autocompleteLimit],\n activateValue: \"\",\n build: (filter) =>\n lookup\n ? Query.from(table as TableExpr)\n .select({ value: column as string })\n .distinct()\n .where(filter)\n .orderby(column as string)\n .limit(autocompleteLimit)\n : null,\n clause: (client, value) =>\n clauseMatch(field as string, value ?? null, { source: client, method: type, caseSensitive }),\n });\n\n // The completions, filtered in the browser. The query already asked the database for the\n // `autocompleteLimit` distinct values of the column; narrowing THOSE as you type is not a second\n // question, so it does not become a second query.\n const items = useMemo(\n () => (rows ?? []).map((row) => ({ label: String(row.value), value: String(row.value) })),\n [rows],\n );\n const capped = items.length >= autocompleteLimit;\n const { contains } = useFilter({ sensitivity: \"base\" });\n const { collection, filter: completions, set } = useListCollection<{\n label: string;\n value: string;\n }>({ filter: contains, initialItems: [] });\n\n // Keyed on the values, not on the array: `rows` is a fresh array on every settled query, and\n // setting the collection from an identity-unstable dep is a render loop.\n const itemsKey = items.map((item) => item.value).join(\"\\u0000\");\n useEffect(() => {\n set(items);\n // `items` is derived from `itemsKey`; depending on it directly is the loop above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [itemsKey, set]);\n\n const [text, setText] = useState(defaultValue ?? \"\");\n const published = useRef(defaultValue ?? \"\");\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n const external = selected ?? \"\";\n if (external !== published.current) {\n published.current = external;\n setText(external);\n }\n }, [selected]);\n\n useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);\n\n const primed = useRef(false);\n useEffect(() => {\n if (primed.current || !defaultValue) return;\n primed.current = true;\n publish(defaultValue);\n }, [defaultValue, publish]);\n\n const change = (next: string) => {\n setText(next);\n if (timer.current) clearTimeout(timer.current);\n const send = () => {\n published.current = next;\n publish(next || undefined);\n };\n if (debounce > 0) timer.current = setTimeout(send, debounce);\n else send();\n };\n\n return (\n <Field\n className={cn(\"w-fit min-w-48 gap-1.5\", className)}\n disabled={disabled}\n {...rest}\n {...warmUpHandlers(activate, props)}\n slot={slot ?? \"chart-search\"}\n >\n {label ? <FieldLabel>{label}</FieldLabel> : null}\n {lookup ? (\n // `allowCustomValue`, because this control is a FILTER and not a picker: what it publishes\n // is a match clause over whatever you typed, and the list only ever offered completions.\n // A `<datalist>` was the honest first answer to that and a poor one — unstyleable, different\n // in every browser, no empty state, and silent about its own cap. Same clause, same\n // debounce; only the surface changed.\n <Combobox\n allowCustomValue\n collection={collection}\n slot=\"chart-search-list\"\n disabled={disabled}\n inputValue={text}\n onInputValueChange={(details) => {\n completions(details.inputValue);\n change(details.inputValue);\n }}\n onValueChange={(details) => {\n const picked = details.value[0];\n if (picked !== undefined) change(picked);\n }}\n >\n <ComboboxInput className={controlClassName} placeholder={placeholder} size={size} />\n <ComboboxContent>\n <ComboboxEmpty>No matching values.</ComboboxEmpty>\n {collection.items.map((item) => (\n <ComboboxItem item={item} key={item.value}>\n {item.label}\n </ComboboxItem>\n ))}\n {/* The cap, said out loud. The datalist could not: it silently stopped at its limit,\n so \"nothing else matches\" and \"nothing else was asked for\" looked identical. */}\n {capped ? (\n <p className=\"border-t px-2 py-1.5 text-muted-foreground text-xs\">\n First {autocompleteLimit}. Keep typing to narrow it.\n </p>\n ) : null}\n </ComboboxContent>\n </Combobox>\n ) : (\n <Input\n className={controlClassName}\n disabled={disabled}\n onChange={(event) => change(event.target.value)}\n placeholder={placeholder}\n size={size}\n type=\"search\"\n value={text}\n />\n )}\n {children}\n </Field>\n );\n}\n\n// ── ChartSlider ──────────────────────────────────────────────────────────────\n\nexport interface ChartSliderProps\n extends Omit<React.ComponentProps<typeof ark.div>, \"defaultValue\">,\n SelectionProps {\n /** The relation the extent comes from, when `min`/`max` are not both given. */\n table?: TableExpr;\n /** The numeric column. */\n column?: string;\n /** The column named in the published clause. Defaults to `column`. */\n field?: string;\n /**\n * `point` publishes an equality clause for one thumb; `interval` publishes a two-thumb range.\n * (vgplot's slider only ever offers `[min, value]`; ours brushes a real interval.)\n */\n select?: \"point\" | \"interval\";\n min?: number;\n max?: number;\n step?: number;\n /** Published once, when the control first connects. */\n defaultValue?: number | readonly [number, number];\n label?: ReactNode;\n /** Show the current value beside the label. */\n showValue?: boolean;\n disabled?: boolean;\n}\n\ntype SliderValueType = number | readonly [number, number];\n\ninterface SliderExtent {\n min: number;\n max: number;\n step: number;\n}\n\n/** vgplot's `(max - min) / 500`, except an all-integer extent steps by 1 — 500 fractions of a count is noise. */\nfunction defaultStep(min: number, max: number): number {\n if (Number.isInteger(min) && Number.isInteger(max)) return 1;\n return (max - min) / 500 || 1;\n}\n\n/**\n * A slider over a numeric column, publishing a **point** or **interval** clause.\n *\n * The extent is a query when `min`/`max` are not both given: `SELECT min(col), max(col)`, answered\n * by the coordinator. A `Skeleton` shaped like the track holds the space until it arrives.\n */\nexport function ChartSlider(props: ChartSliderProps) {\n const {\n table,\n column,\n field = column,\n as,\n filterBy,\n select = \"point\",\n min,\n max,\n step,\n defaultValue,\n label,\n showValue = true,\n disabled,\n className,\n children,\n slot,\n ...rest\n } = props;\n const { crossfilter } = useMosaic();\n const target = as === undefined ? crossfilter : as;\n const source = filterBy === undefined ? crossfilter : filterBy;\n const lookup = (min === undefined || max === undefined) && Boolean(table && column);\n // The binning hint for the interval clause. A ref, not `extent` itself: reading the memo from\n // the clause would make the widget's own row type circular.\n const pixelSize = useRef(1);\n\n const { rows, selected, publish, activate } = useMosaicInput<SliderValueType>({\n as: target,\n filterBy: source,\n deps: [chartTableKey(table), column, lookup],\n activateValue: select === \"interval\" ? ([0, 0] as const) : 0,\n build: (filter) =>\n lookup\n ? Query.select({ min: sqlMin(column as string), max: sqlMax(column as string) })\n .from(table as TableExpr)\n .where(filter)\n : null,\n clause: (client, value) => {\n if (select !== \"interval\") return clausePoint(field as string, value, { source: client });\n const domain = value as [number, number] | undefined;\n return clauseInterval(field as string, domain ?? null, {\n source: client,\n bin: \"ceil\",\n scale: domain ? { type: \"identity\", domain } : undefined,\n pixelSize: pixelSize.current,\n });\n },\n });\n\n const extent = useMemo<SliderExtent | null>(() => {\n const row = rows?.[0];\n const low = min ?? (typeof row?.min === \"number\" ? row.min : undefined);\n const high = max ?? (typeof row?.max === \"number\" ? row.max : undefined);\n if (low === undefined || high === undefined) return null;\n return { min: low, max: high, step: step ?? defaultStep(low, high) };\n }, [rows, min, max, step]);\n\n useEffect(() => {\n pixelSize.current = extent?.step ?? 1;\n }, [extent]);\n\n const [values, setValues] = useState<number[] | null>(\n defaultValue === undefined ? null : [...toValues(defaultValue)],\n );\n /** Distinguishes \"never published\" from \"published and then cleared\" — see the effect below. */\n const held = useRef(false);\n\n useEffect(() => {\n if (selected !== undefined) {\n held.current = true;\n setValues([...toValues(selected)]);\n return;\n }\n // The clause is gone: a `Selection.reset()`, a \"Clear filters\" button, another control\n // retracting it. Fall back to the extent instead of leaving the thumbs where they were — a\n // slider that still reads 5–22 while nothing is filtered is a control lying about the state,\n // which is worse than one that never moved. Gated on `held` so the first render does not\n // stomp on `defaultValue` before the priming effect has published it.\n if (held.current) {\n held.current = false;\n setValues(null);\n }\n }, [selected]);\n\n const primed = useRef(false);\n useEffect(() => {\n if (primed.current || defaultValue === undefined) return;\n primed.current = true;\n publish(defaultValue);\n }, [defaultValue, publish]);\n\n const change = (next: number[]) => {\n setValues(next);\n publish(select === \"interval\" ? [next[0] as number, next[1] as number] : (next[0] as number));\n };\n\n const shown = values ?? (extent ? (select === \"interval\" ? [extent.min, extent.max] : [extent.min]) : null);\n\n return (\n <ark.div\n className={cn(\"flex w-full min-w-48 flex-col gap-1.5\", className)}\n {...rest}\n {...warmUpHandlers(activate, props)}\n data-slot={slot ?? \"chart-slider\"}\n >\n {extent && shown ? (\n <Slider\n disabled={disabled}\n max={extent.max}\n min={extent.min}\n onValueChange={(details) => change(details.value)}\n step={extent.step}\n value={shown}\n >\n {label || showValue ? (\n <div className=\"flex items-center\">\n {label ? <SliderLabel>{label}</SliderLabel> : null}\n {showValue ? <SliderValue /> : null}\n </div>\n ) : null}\n </Slider>\n ) : (\n <Skeleton className=\"h-2 w-full rounded-full\" />\n )}\n {children}\n </ark.div>\n );\n}\n\nfunction toValues(value: SliderValueType): readonly number[] {\n return typeof value === \"number\" ? [value] : value;\n}\n"],"names":["min","max"],"mappings":";;;;;;;;;;;;;;;;;;AA4FO;AAGL;AAGA;AAEA;AAKA;AACE;AAAqB;AACP;AAC2B;AACvC;AAEF;AAKE;AAC+B;AACjC;AAKA;AACA;AACE;AAEA;AAA6E;AAE/E;AAGE;AAGyF;AAC3F;AAGF;AAAgB;AAEZ;AACA;AACA;AAAoE;AACtE;AACG;AAIH;AACA;AAA6F;AAG/F;AACF;AAGA;AAIE;AAAO;;AAEH;AACA;AACF;;AAEE;AACoB;AACtB;AAEJ;AAgEA;AAUA;AACE;AACF;AAEA;AACE;AACF;AAGA;AACE;AACA;AAEF;AAEA;AACE;AACA;AAGF;AAEA;AAKE;AAEI;AACA;AAAO;AACmB;AACkB;AAC9B;AACC;AAAA;AAIe;AACX;AACE;AACd;AACe;AAE5B;AAYO;AACL;AAAM;AACJ;AACA;AACQ;AACR;AACA;AACA;AACS;AACD;AACG;AACX;AACQ;AACK;AACb;AACO;AACP;AACA;AACA;AACA;AACG;AAO4E;AAC3E;AACM;AACwC;AACjC;AAUX;AAI2D;AACsB;AAIzF;AACE;AAEkF;AAGpF;AAKyD;AAC1C;AACD;AACC;AACA;AAQf;AAEA;AACE;AAAK;AAAJ;AACiC;AAC5B;AAC8B;AACf;AAEnB;AAAA;AAAC;AAAA;AACY;AACN;AACL;AAC8C;AAC9C;AACA;AACA;AAIiD;AACsC;AAClB;AACrE;AACA;AAC0C;AAAA;AAAA;AAE3C;AAAA;AAAA;AAGP;AAoCO;AACL;AAAM;AACJ;AACA;AACQ;AACR;AACA;AACO;AACS;AAChB;AACc;AACH;AACS;AACpB;AACA;AACO;AACP;AACA;AACA;AACA;AACG;AAOgE;AAC/D;AACM;AACoD;AAC/C;AAST;AAEuF;AAMjF;AAC4E;AACnF;AAYP;AACE;AAAS;AAKX;AAIA;AACE;AACA;AAEkB;AAII;AAA6C;AAErE;AACA;AACE;AAEoB;AAGtB;AACE;AAEA;AACE;AACyB;AAE3B;AACK;AAGP;AACE;AAAC;AAAA;AACkD;AACjD;AACI;AAC8B;AACpB;AAEb;AAA2C;AAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAMC;AAAC;AAAA;AACiB;AAChB;AACK;AACL;AACY;AAEV;AACyB;AAC3B;AAEE;AACA;AAAuC;AACzC;AAEA;AAAkF;AAEhF;AAAkC;AAKjC;AAImE;AAAA;AACzD;AAAkB;AAEzB;AACN;AAAA;AAAA;AAAA;AAGF;AAAC;AAAA;AACY;AACX;AAC8C;AAC9C;AACA;AACK;AACE;AAAA;AAAA;AAGV;AAAA;AAAA;AAGP;AAsCA;AACE;AAEF;AAQO;AACL;AAAM;AACJ;AACA;AACQ;AACR;AACA;AACS;AACTA;AACAC;AACA;AACA;AACA;AACY;AACZ;AACA;AACA;AACA;AACG;AAUyE;AACxE;AACM;AACiC;AACgB;AAMrD;AAEJ;AACA;AACA;AAAuD;AAC7C;AACH;AAC0C;AAC1B;AACtB;AACH;AAIA;AAGA;AACiE;AAGnE;AACE;AAAoC;AAGtC;AAA4B;AACoC;AAKhE;AACE;AACE;AAEA;AAAA;AAOF;AAEgB;AAIlB;AACA;AACE;AAEoB;AAGtB;AACE;AAC4F;AAK9F;AACE;AAAK;AAAJ;AACiE;AAC5D;AAC8B;AACf;AAElB;AACC;AAAC;AAAA;AACC;AACY;AACA;AACoC;AACnC;AACN;AAIF;AAA6C;AACf;AAE/B;AAAA;AAGwC;AAE/C;AAAA;AAAA;AAGP;AAEA;AACE;AACF;;;;;;;"}
@@ -1,4 +1,5 @@
1
1
  import { Selection } from '@uwdata/mosaic-core';
2
+ import { TableExpr } from '@kanzo-tech/mosaic';
2
3
  import { ChartDescriptor } from './chart-spec.js';
3
4
  /**
4
5
  * The mark vocabulary. Every component here renders `null` — `ChartRoot` reads the `__chart`
@@ -35,7 +36,7 @@ export interface ChartMarkProps {
35
36
  * until a plot needs two — a node-link view draws nodes from one relation and edges from
36
37
  * another, and both still filter by the same selection.
37
38
  */
38
- table?: string;
39
+ table?: TableExpr;
39
40
  /** Literal rows instead of the root's table. */
40
41
  data?: readonly unknown[];
41
42
  /** Positions a rule/tick without a table: `at={0}` is the zero line. */