@matthieumordrel/chart-studio 0.2.1 → 0.2.3

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 (175) hide show
  1. package/README.md +24 -0
  2. package/dist/core/chart-capabilities.d.mts +48 -0
  3. package/dist/core/chart-capabilities.mjs +55 -0
  4. package/dist/core/{colors.d.ts → colors.d.mts} +5 -3
  5. package/dist/core/colors.mjs +55 -0
  6. package/dist/core/config-utils.mjs +79 -0
  7. package/dist/core/date-utils.mjs +49 -0
  8. package/dist/core/define-chart-schema.d.mts +106 -0
  9. package/dist/core/define-chart-schema.mjs +47 -0
  10. package/dist/core/formatting.mjs +349 -0
  11. package/dist/core/infer-columns.d.mts +9 -0
  12. package/dist/core/infer-columns.mjs +481 -0
  13. package/dist/core/metric-utils.d.mts +13 -0
  14. package/dist/core/metric-utils.mjs +121 -0
  15. package/dist/core/pipeline-data-points.mjs +212 -0
  16. package/dist/core/pipeline-helpers.mjs +85 -0
  17. package/dist/core/{pipeline.d.ts → pipeline.d.mts} +21 -24
  18. package/dist/core/pipeline.mjs +153 -0
  19. package/dist/core/types.d.mts +957 -0
  20. package/dist/core/use-chart-options.d.mts +64 -0
  21. package/dist/core/use-chart-options.mjs +7 -0
  22. package/dist/core/use-chart-resolvers.mjs +34 -0
  23. package/dist/core/{use-chart.d.ts → use-chart.d.mts} +12 -9
  24. package/dist/core/use-chart.mjs +299 -0
  25. package/dist/index.d.mts +10 -0
  26. package/dist/index.mjs +8 -0
  27. package/dist/ui/chart-axis-ticks.mjs +65 -0
  28. package/dist/ui/{chart-canvas.d.ts → chart-canvas.d.mts} +13 -6
  29. package/dist/ui/chart-canvas.mjs +461 -0
  30. package/dist/ui/chart-context.d.mts +92 -0
  31. package/dist/ui/chart-context.mjs +112 -0
  32. package/dist/ui/{chart-date-range-badge.d.ts → chart-date-range-badge.d.mts} +10 -4
  33. package/dist/ui/chart-date-range-badge.mjs +49 -0
  34. package/dist/ui/chart-date-range-panel.d.mts +18 -0
  35. package/dist/ui/chart-date-range-panel.mjs +208 -0
  36. package/dist/ui/{chart-date-range.d.ts → chart-date-range.d.mts} +10 -4
  37. package/dist/ui/chart-date-range.mjs +67 -0
  38. package/dist/ui/chart-debug.d.mts +17 -0
  39. package/dist/ui/chart-debug.mjs +169 -0
  40. package/dist/ui/chart-dropdown.mjs +92 -0
  41. package/dist/ui/{chart-filters-panel.d.ts → chart-filters-panel.d.mts} +12 -5
  42. package/dist/ui/chart-filters-panel.mjs +132 -0
  43. package/dist/ui/{chart-filters.d.ts → chart-filters.d.mts} +10 -4
  44. package/dist/ui/chart-filters.mjs +48 -0
  45. package/dist/ui/chart-group-by-selector.d.mts +14 -0
  46. package/dist/ui/chart-group-by-selector.mjs +29 -0
  47. package/dist/ui/{chart-metric-panel.d.ts → chart-metric-panel.d.mts} +12 -5
  48. package/dist/ui/chart-metric-panel.mjs +172 -0
  49. package/dist/ui/chart-metric-selector.d.mts +16 -0
  50. package/dist/ui/chart-metric-selector.mjs +50 -0
  51. package/dist/ui/chart-select.mjs +62 -0
  52. package/dist/ui/{chart-source-switcher.d.ts → chart-source-switcher.d.mts} +10 -4
  53. package/dist/ui/chart-source-switcher.mjs +54 -0
  54. package/dist/ui/chart-time-bucket-selector.d.mts +15 -0
  55. package/dist/ui/chart-time-bucket-selector.mjs +34 -0
  56. package/dist/ui/chart-toolbar-overflow.d.mts +28 -0
  57. package/dist/ui/chart-toolbar-overflow.mjs +209 -0
  58. package/dist/ui/chart-toolbar.d.mts +29 -0
  59. package/dist/ui/chart-toolbar.mjs +56 -0
  60. package/dist/ui/chart-type-selector.d.mts +14 -0
  61. package/dist/ui/chart-type-selector.mjs +33 -0
  62. package/dist/ui/chart-x-axis-selector.d.mts +14 -0
  63. package/dist/ui/chart-x-axis-selector.mjs +25 -0
  64. package/dist/ui/index.d.mts +19 -0
  65. package/dist/ui/index.mjs +18 -0
  66. package/dist/ui/toolbar-types.d.mts +7 -0
  67. package/dist/ui/toolbar-types.mjs +83 -0
  68. package/package.json +11 -10
  69. package/dist/core/chart-capabilities.d.ts +0 -60
  70. package/dist/core/chart-capabilities.d.ts.map +0 -1
  71. package/dist/core/chart-capabilities.js +0 -54
  72. package/dist/core/colors.d.ts.map +0 -1
  73. package/dist/core/colors.js +0 -54
  74. package/dist/core/config-utils.d.ts +0 -43
  75. package/dist/core/config-utils.d.ts.map +0 -1
  76. package/dist/core/config-utils.js +0 -80
  77. package/dist/core/date-utils.d.ts +0 -29
  78. package/dist/core/date-utils.d.ts.map +0 -1
  79. package/dist/core/date-utils.js +0 -58
  80. package/dist/core/define-chart-schema.d.ts +0 -105
  81. package/dist/core/define-chart-schema.d.ts.map +0 -1
  82. package/dist/core/define-chart-schema.js +0 -44
  83. package/dist/core/formatting.d.ts +0 -47
  84. package/dist/core/formatting.d.ts.map +0 -1
  85. package/dist/core/formatting.js +0 -396
  86. package/dist/core/index.d.ts +0 -17
  87. package/dist/core/index.d.ts.map +0 -1
  88. package/dist/core/index.js +0 -12
  89. package/dist/core/infer-columns.d.ts +0 -6
  90. package/dist/core/infer-columns.d.ts.map +0 -1
  91. package/dist/core/infer-columns.js +0 -512
  92. package/dist/core/metric-utils.d.ts +0 -43
  93. package/dist/core/metric-utils.d.ts.map +0 -1
  94. package/dist/core/metric-utils.js +0 -141
  95. package/dist/core/pipeline-data-points.d.ts +0 -23
  96. package/dist/core/pipeline-data-points.d.ts.map +0 -1
  97. package/dist/core/pipeline-data-points.js +0 -235
  98. package/dist/core/pipeline-helpers.d.ts +0 -38
  99. package/dist/core/pipeline-helpers.d.ts.map +0 -1
  100. package/dist/core/pipeline-helpers.js +0 -97
  101. package/dist/core/pipeline.d.ts.map +0 -1
  102. package/dist/core/pipeline.js +0 -156
  103. package/dist/core/types.d.ts +0 -1109
  104. package/dist/core/types.d.ts.map +0 -1
  105. package/dist/core/types.js +0 -14
  106. package/dist/core/use-chart-options.d.ts +0 -66
  107. package/dist/core/use-chart-options.d.ts.map +0 -1
  108. package/dist/core/use-chart-options.js +0 -4
  109. package/dist/core/use-chart-resolvers.d.ts +0 -14
  110. package/dist/core/use-chart-resolvers.d.ts.map +0 -1
  111. package/dist/core/use-chart-resolvers.js +0 -41
  112. package/dist/core/use-chart.d.ts.map +0 -1
  113. package/dist/core/use-chart.js +0 -265
  114. package/dist/index.d.ts +0 -36
  115. package/dist/index.d.ts.map +0 -1
  116. package/dist/index.js +0 -35
  117. package/dist/ui/chart-axis-ticks.d.ts +0 -35
  118. package/dist/ui/chart-axis-ticks.d.ts.map +0 -1
  119. package/dist/ui/chart-axis-ticks.js +0 -79
  120. package/dist/ui/chart-canvas.d.ts.map +0 -1
  121. package/dist/ui/chart-canvas.js +0 -337
  122. package/dist/ui/chart-context.d.ts +0 -89
  123. package/dist/ui/chart-context.d.ts.map +0 -1
  124. package/dist/ui/chart-context.js +0 -128
  125. package/dist/ui/chart-date-range-badge.d.ts.map +0 -1
  126. package/dist/ui/chart-date-range-badge.js +0 -30
  127. package/dist/ui/chart-date-range-panel.d.ts +0 -25
  128. package/dist/ui/chart-date-range-panel.d.ts.map +0 -1
  129. package/dist/ui/chart-date-range-panel.js +0 -125
  130. package/dist/ui/chart-date-range.d.ts.map +0 -1
  131. package/dist/ui/chart-date-range.js +0 -37
  132. package/dist/ui/chart-debug.d.ts +0 -10
  133. package/dist/ui/chart-debug.d.ts.map +0 -1
  134. package/dist/ui/chart-debug.js +0 -126
  135. package/dist/ui/chart-dropdown.d.ts +0 -35
  136. package/dist/ui/chart-dropdown.d.ts.map +0 -1
  137. package/dist/ui/chart-dropdown.js +0 -76
  138. package/dist/ui/chart-filters-panel.d.ts.map +0 -1
  139. package/dist/ui/chart-filters-panel.js +0 -46
  140. package/dist/ui/chart-filters.d.ts.map +0 -1
  141. package/dist/ui/chart-filters.js +0 -26
  142. package/dist/ui/chart-group-by-selector.d.ts +0 -8
  143. package/dist/ui/chart-group-by-selector.d.ts.map +0 -1
  144. package/dist/ui/chart-group-by-selector.js +0 -19
  145. package/dist/ui/chart-metric-panel.d.ts.map +0 -1
  146. package/dist/ui/chart-metric-panel.js +0 -118
  147. package/dist/ui/chart-metric-selector.d.ts +0 -10
  148. package/dist/ui/chart-metric-selector.d.ts.map +0 -1
  149. package/dist/ui/chart-metric-selector.js +0 -27
  150. package/dist/ui/chart-select.d.ts +0 -25
  151. package/dist/ui/chart-select.d.ts.map +0 -1
  152. package/dist/ui/chart-select.js +0 -35
  153. package/dist/ui/chart-source-switcher.d.ts.map +0 -1
  154. package/dist/ui/chart-source-switcher.js +0 -31
  155. package/dist/ui/chart-time-bucket-selector.d.ts +0 -9
  156. package/dist/ui/chart-time-bucket-selector.d.ts.map +0 -1
  157. package/dist/ui/chart-time-bucket-selector.js +0 -25
  158. package/dist/ui/chart-toolbar-overflow.d.ts +0 -29
  159. package/dist/ui/chart-toolbar-overflow.d.ts.map +0 -1
  160. package/dist/ui/chart-toolbar-overflow.js +0 -109
  161. package/dist/ui/chart-toolbar.d.ts +0 -45
  162. package/dist/ui/chart-toolbar.d.ts.map +0 -1
  163. package/dist/ui/chart-toolbar.js +0 -44
  164. package/dist/ui/chart-type-selector.d.ts +0 -8
  165. package/dist/ui/chart-type-selector.d.ts.map +0 -1
  166. package/dist/ui/chart-type-selector.js +0 -22
  167. package/dist/ui/chart-x-axis-selector.d.ts +0 -8
  168. package/dist/ui/chart-x-axis-selector.d.ts.map +0 -1
  169. package/dist/ui/chart-x-axis-selector.js +0 -14
  170. package/dist/ui/index.d.ts +0 -25
  171. package/dist/ui/index.d.ts.map +0 -1
  172. package/dist/ui/index.js +0 -23
  173. package/dist/ui/toolbar-types.d.ts +0 -43
  174. package/dist/ui/toolbar-types.d.ts.map +0 -1
  175. package/dist/ui/toolbar-types.js +0 -50
@@ -0,0 +1,957 @@
1
+ //#region src/core/types.d.ts
2
+ /**
3
+ * Core types for chart-studio.
4
+ *
5
+ * The type system is built around two concepts:
6
+ * 1. **Columns** describe the shape of your data (date, category, boolean, number)
7
+ * 2. **Chart state** tracks what the user has selected (chart type, groupBy, filters, etc.)
8
+ *
9
+ * Column types determine what operations are available:
10
+ * - `date` → X-axis candidate, time bucketing (day/week/month/quarter/year)
11
+ * - `category` → X-axis candidate, groupBy candidate, multi-select filter
12
+ * - `boolean` → groupBy candidate, toggle filter
13
+ * - `number` → metric/aggregation (count, sum, avg, min, max)
14
+ */
15
+ type Nullish = null | undefined;
16
+ /** Primitive field values that chart-studio can infer directly from raw data. */
17
+ type InferableColumnValue = string | number | boolean | Date | Nullish;
18
+ /** Top-level dataset keys whose values can be charted without a custom accessor. */
19
+ type InferableFieldKey<T> = Extract<{ [TKey in keyof T]-?: Exclude<T[TKey], Nullish> extends InferableColumnValue ? TKey : never }[keyof T], string>;
20
+ /**
21
+ * Built-in format presets for `schema.columns.*.format`.
22
+ *
23
+ * Use these for the common cases before reaching for the object form:
24
+ * - `'number'` for a normal numeric display such as `12,340`
25
+ * - `'compact-number'` for short numeric display such as `12.3K`
26
+ * - `'currency'` for money such as `$12.3K` / `$12,340`
27
+ * - `'percent'` for ratios such as `27.8%`
28
+ * - `'date'` for date-only values such as `Mar 11, 2026`
29
+ * - `'datetime'` for date+time values such as `Mar 11, 2026, 3:30 PM`
30
+ */
31
+ type ColumnFormatPreset = 'number' | 'compact-number' | 'currency' | 'percent' | 'date' | 'datetime';
32
+ /**
33
+ * Fixed-size duration units that can be converted reliably without calendar
34
+ * ambiguity.
35
+ *
36
+ * These are used by `format: {kind: 'duration', unit: ...}` to tell the chart
37
+ * system what one raw numeric value represents.
38
+ */
39
+ type DurationInputUnit = 'seconds' | 'minutes' | 'hours' | 'days';
40
+ /**
41
+ * `Intl.NumberFormat` options for `format: {kind: 'number', ...}`.
42
+ *
43
+ * This is still a native `Intl` passthrough, but the most common properties are
44
+ * re-documented here so new users can understand them directly from editor
45
+ * hover text without opening MDN first.
46
+ *
47
+ * Typical example:
48
+ *
49
+ * ```ts
50
+ * format: {
51
+ * kind: 'number',
52
+ * options: {
53
+ * style: 'currency',
54
+ * currency: 'USD',
55
+ * notation: 'compact',
56
+ * maximumFractionDigits: 1,
57
+ * },
58
+ * }
59
+ * ```
60
+ */
61
+ type ChartNumberFormatOptions = Omit<Intl.NumberFormatOptions, 'style' | 'currency' | 'notation' | 'maximumFractionDigits'> & {
62
+ /**
63
+ * Which numeric family to render.
64
+ *
65
+ * Common values:
66
+ * - `'decimal'` -> `1,234`
67
+ * - `'currency'` -> `$1,234`
68
+ * - `'percent'` -> `27.8%`
69
+ */
70
+ style?: Intl.NumberFormatOptions['style'];
71
+ /**
72
+ * Currency code used when `style: 'currency'`.
73
+ *
74
+ * Common examples:
75
+ * - `'USD'`
76
+ * - `'EUR'`
77
+ * - `'GBP'`
78
+ */
79
+ currency?: string;
80
+ /**
81
+ * Whether large values should stay full length or use compact suffixes.
82
+ *
83
+ * Common values:
84
+ * - `'standard'` -> `1,200,000`
85
+ * - `'compact'` -> `1.2M`
86
+ */
87
+ notation?: Intl.NumberFormatOptions['notation'];
88
+ /**
89
+ * Maximum number of digits shown after the decimal separator.
90
+ *
91
+ * Examples:
92
+ * - `0` -> `1M`
93
+ * - `1` -> `1.2M`
94
+ * - `2` -> `1.23M`
95
+ */
96
+ maximumFractionDigits?: number;
97
+ };
98
+ /**
99
+ * `Intl.DateTimeFormat` options for `format: {kind: 'date', ...}`.
100
+ *
101
+ * Like `ChartNumberFormatOptions`, this is still a native `Intl` passthrough
102
+ * with friendlier docs for the properties chart users reach for most often.
103
+ *
104
+ * Typical example:
105
+ *
106
+ * ```ts
107
+ * format: {
108
+ * kind: 'date',
109
+ * options: {
110
+ * month: 'short',
111
+ * year: 'numeric',
112
+ * },
113
+ * }
114
+ * ```
115
+ */
116
+ type ChartDateFormatOptions = Omit<Intl.DateTimeFormatOptions, 'dateStyle' | 'timeStyle' | 'year' | 'month' | 'day'> & {
117
+ /**
118
+ * Prebuilt date preset.
119
+ *
120
+ * Examples:
121
+ * - `'short'` -> `3/11/26`
122
+ * - `'medium'` -> `Mar 11, 2026`
123
+ * - `'long'` -> `March 11, 2026`
124
+ */
125
+ dateStyle?: Intl.DateTimeFormatOptions['dateStyle'];
126
+ /**
127
+ * Prebuilt time preset.
128
+ *
129
+ * Examples:
130
+ * - `'short'` -> `3:30 PM`
131
+ * - `'medium'` -> `3:30:00 PM`
132
+ */
133
+ timeStyle?: Intl.DateTimeFormatOptions['timeStyle']; /** How the year should render, for example `'numeric'` or `'2-digit'`. */
134
+ year?: Intl.DateTimeFormatOptions['year']; /** How the month should render, for example `'numeric'`, `'short'`, or `'long'`. */
135
+ month?: Intl.DateTimeFormatOptions['month']; /** How the day should render, usually `'numeric'` or `'2-digit'`. */
136
+ day?: Intl.DateTimeFormatOptions['day'];
137
+ };
138
+ /**
139
+ * Explicit number formatting object.
140
+ *
141
+ * Use this when the preset strings are close, but you want to control the
142
+ * locale or specific `Intl.NumberFormat` options yourself.
143
+ */
144
+ type NumberColumnFormat = {
145
+ /** Marks this object as the number-format form of `format`. */kind: 'number';
146
+ /**
147
+ * Optional locale passed to `Intl.NumberFormat`.
148
+ *
149
+ * Examples:
150
+ * - `'en-US'`
151
+ * - `'fr-FR'`
152
+ * - `'de-DE'`
153
+ */
154
+ locale?: string;
155
+ /**
156
+ * Native `Intl.NumberFormat` options.
157
+ *
158
+ * This is where you customize things like currency, compact notation, and the
159
+ * number of decimal places to show.
160
+ */
161
+ options?: ChartNumberFormatOptions;
162
+ };
163
+ /**
164
+ * Explicit date formatting object.
165
+ *
166
+ * Use this when the preset strings are close, but you want to control the
167
+ * locale or specific `Intl.DateTimeFormat` options yourself.
168
+ */
169
+ type DateColumnFormat = {
170
+ /** Marks this object as the date-format form of `format`. */kind: 'date';
171
+ /**
172
+ * Optional locale passed to `Intl.DateTimeFormat`.
173
+ *
174
+ * Examples:
175
+ * - `'en-US'`
176
+ * - `'fr-FR'`
177
+ * - `'ja-JP'`
178
+ */
179
+ locale?: string;
180
+ /**
181
+ * Native `Intl.DateTimeFormat` options.
182
+ *
183
+ * This is where you customize things like month/year-only displays, long
184
+ * versus short month names, or full date+time output.
185
+ */
186
+ options?: ChartDateFormatOptions;
187
+ };
188
+ /**
189
+ * Explicit duration formatting object for numeric values that represent elapsed
190
+ * time.
191
+ *
192
+ * Use this when the charted value is a duration and you want chart-studio to
193
+ * render compact labels like `36s`, `1h36m`, or `1d5h`.
194
+ *
195
+ * Typical example:
196
+ *
197
+ * ```ts
198
+ * format: {
199
+ * kind: 'duration',
200
+ * unit: 'minutes',
201
+ * }
202
+ * ```
203
+ */
204
+ type DurationColumnFormat = {
205
+ /** Marks this object as the duration-format form of `format`. */kind: 'duration';
206
+ /**
207
+ * Unit represented by one raw numeric value.
208
+ *
209
+ * Examples:
210
+ * - `'seconds'` when `90` means ninety seconds
211
+ * - `'minutes'` when `45` means forty-five minutes
212
+ * - `'hours'` when `1.5` means one hour and thirty minutes
213
+ * - `'days'` when `2` means two days
214
+ */
215
+ unit: DurationInputUnit;
216
+ };
217
+ /**
218
+ * Full declarative formatting surface accepted by `schema.columns.*.format`.
219
+ *
220
+ * Start with a preset string for the common case. Move to the object form when
221
+ * you need to customize locale, low-level `Intl` options, or duration units.
222
+ */
223
+ type ColumnFormat = ColumnFormatPreset | NumberColumnFormat | DateColumnFormat | DurationColumnFormat;
224
+ /** Column kinds understood by the chart pipeline. */
225
+ type ChartColumnType = 'date' | 'category' | 'boolean' | 'number';
226
+ /** Confidence attached to runtime field inference. */
227
+ type InferenceConfidence = 'low' | 'medium' | 'high';
228
+ /** Debug metadata describing how a column was inferred. */
229
+ type ColumnInferenceMetadata = {
230
+ detectedType: ChartColumnType;
231
+ confidence: InferenceConfidence;
232
+ hinted: boolean;
233
+ };
234
+ /**
235
+ * Shared schema properties available on both raw-field overrides and derived
236
+ * columns.
237
+ *
238
+ * Most users will interact with these through `schema.columns.someField`.
239
+ */
240
+ interface BaseColumnHint<T, TValue> {
241
+ /**
242
+ * User-facing column label shown in selectors, tooltips, legends, and filter
243
+ * headers.
244
+ *
245
+ * Example: `revenuePerSeat` becomes `Revenue Per Seat` by default, but you can
246
+ * override it with `label: 'Revenue / Seat'`.
247
+ */
248
+ label?: string;
249
+ /**
250
+ * How this column should be displayed in the chart UI.
251
+ *
252
+ * Use a preset like `'currency'` or `'percent'` for the common case. Use the
253
+ * object form when you need to control locale, specific `Intl` options, or
254
+ * duration units like `'minutes'`.
255
+ *
256
+ * This affects display surfaces such as:
257
+ * - axis tick labels
258
+ * - tooltip values
259
+ * - chart data labels
260
+ * - filter option labels when the values are typed
261
+ */
262
+ format?: ColumnFormat;
263
+ /**
264
+ * Final escape hatch for fully custom display logic.
265
+ *
266
+ * Receives the resolved field value and, when the UI surface still maps to a
267
+ * single raw row, that source item as well. Prefer `format` first when a
268
+ * declarative option is enough, and use `formatter` only when the output
269
+ * really depends on custom business logic.
270
+ */
271
+ formatter?: (value: TValue | null | undefined, item?: T) => string;
272
+ }
273
+ /**
274
+ * Schema override for a raw string field.
275
+ *
276
+ * Use this when a dataset field already exists and you want to relabel it,
277
+ * force its type, or change how it formats in the chart UI.
278
+ */
279
+ interface StringColumnHint<T> extends BaseColumnHint<T, string> {
280
+ /**
281
+ * How this string field should behave in the chart system.
282
+ *
283
+ * - `'category'` keeps it as a label-like field
284
+ * - `'date'` tells chart-studio to parse it as a date/time field
285
+ */
286
+ type?: 'category' | 'date';
287
+ }
288
+ /**
289
+ * Schema override for a raw numeric field.
290
+ *
291
+ * This is the most common place to use `format: 'currency'`,
292
+ * `format: 'percent'`, or a `kind: 'number'` object.
293
+ */
294
+ interface NumberColumnHint<T> extends BaseColumnHint<T, number> {
295
+ /**
296
+ * How this numeric field should behave in the chart system.
297
+ *
298
+ * - `'number'` keeps it as an aggregatable metric
299
+ * - `'date'` is useful for Unix timestamps or numeric date representations
300
+ */
301
+ type?: 'number' | 'date';
302
+ }
303
+ /**
304
+ * Schema override for a raw boolean field.
305
+ *
306
+ * These fields are useful for grouping and filtering, especially when paired
307
+ * with `trueLabel` / `falseLabel`.
308
+ */
309
+ interface BooleanColumnHint<T> extends BaseColumnHint<T, boolean> {
310
+ /** Boolean fields always resolve to the `'boolean'` column type. */
311
+ type?: 'boolean';
312
+ /** Label shown in the UI when the value is `true`, for example `'Open'`. */
313
+ trueLabel?: string;
314
+ /** Label shown in the UI when the value is `false`, for example `'Closed'`. */
315
+ falseLabel?: string;
316
+ }
317
+ /**
318
+ * Schema override for a raw date-like field.
319
+ *
320
+ * This is useful when your field is already a `Date`, ISO string, or timestamp
321
+ * and you want date-specific behavior with an optional custom display format.
322
+ */
323
+ interface DateValueColumnHint<T> extends BaseColumnHint<T, string | number | Date> {
324
+ /** Date-valued fields always resolve to the `'date'` column type. */
325
+ type?: 'date';
326
+ }
327
+ /** Override options for mixed primitive fields when runtime values need the final say. */
328
+ interface MixedPrimitiveColumnHint<T, TValue> extends BaseColumnHint<T, TValue> {
329
+ /** Explicitly pin the runtime-inferred field to one chart column type. */
330
+ type?: ChartColumnType;
331
+ /** Human-facing label used when a mixed field is interpreted as boolean `true`. */
332
+ trueLabel?: string;
333
+ /** Human-facing label used when a mixed field is interpreted as boolean `false`. */
334
+ falseLabel?: string;
335
+ }
336
+ /**
337
+ * Type-safe schema entry for one raw dataset field.
338
+ *
339
+ * The available properties depend on the field's runtime type, so a boolean
340
+ * field gets boolean-specific options, a number field gets numeric options, and
341
+ * so on.
342
+ */
343
+ type ColumnHintFor<TValue, T> = [Exclude<TValue, Nullish>] extends [boolean] ? BooleanColumnHint<T> : [Exclude<TValue, Nullish>] extends [Date] ? DateValueColumnHint<T> : [Exclude<TValue, Nullish>] extends [number] ? NumberColumnHint<T> : [Exclude<TValue, Nullish>] extends [string] ? StringColumnHint<T> : MixedPrimitiveColumnHint<T, Exclude<TValue, Nullish>>;
344
+ /** Partial per-field overrides layered on top of automatic inference. */
345
+ type ColumnHints<T> = Partial<{ [TKey in InferableFieldKey<T>]: ColumnHintFor<T[TKey], T> | false }>;
346
+ /**
347
+ * Override or exclude one inferred raw field inside `schema.columns`.
348
+ *
349
+ * This lightweight shape intentionally matches the inference override story so
350
+ * callers can keep the common case terse while the surrounding `schema` object
351
+ * becomes the single explicit chart contract.
352
+ */
353
+ type RawColumnSchemaFor<TValue, T> = ColumnHintFor<TValue, T>;
354
+ /**
355
+ * Raw-field schema entries keyed by existing top-level dataset fields.
356
+ *
357
+ * Set a field to `false` to exclude it from the chart API entirely.
358
+ */
359
+ type RawColumnSchemaMap<T> = Partial<{ [TKey in InferableFieldKey<T>]: RawColumnSchemaFor<T[TKey], T> | false }>;
360
+ /**
361
+ * Shared contract for every explicit derived column declared in `schema.columns`.
362
+ *
363
+ * Derived columns are how you add a brand new chart field that does not exist as
364
+ * a raw property on the input row.
365
+ *
366
+ * They are intentionally narrow:
367
+ * - they are additive-only and must use a new id
368
+ * - they compute from one row at a time via `accessor`
369
+ * - they reuse the same labeling and formatting surface as raw columns
370
+ * - they do not currently expose any extra metadata channel
371
+ */
372
+ interface DerivedColumnSchemaBase<T, TValue, TType extends ChartColumnType> extends Omit<BaseColumnHint<T, TValue>, 'label'> {
373
+ /**
374
+ * Marks this schema entry as a derived column.
375
+ *
376
+ * Without `kind: 'derived'`, a `schema.columns` entry is interpreted as an
377
+ * override for an existing raw field.
378
+ */
379
+ kind: 'derived';
380
+ /** User-facing label for this new derived column. */
381
+ label: string;
382
+ /**
383
+ * Declared column role.
384
+ *
385
+ * This controls how the derived column behaves:
386
+ * - `'date'` can be used on time-series X-axes
387
+ * - `'category'` can be used for X-axis, group-by, and filters
388
+ * - `'boolean'` can be used for group-by and filters
389
+ * - `'number'` can be aggregated as a metric
390
+ */
391
+ type: TType;
392
+ }
393
+ /** Explicit derived date column definition. */
394
+ interface DerivedDateColumnSchema<T> extends DerivedColumnSchemaBase<T, string | number | Date, 'date'> {
395
+ /** Compute one date-like value from a row for time-series usage. */
396
+ accessor: (item: T) => string | number | Date | null | undefined;
397
+ }
398
+ /** Explicit derived category column definition. */
399
+ interface DerivedCategoryColumnSchema<T> extends DerivedColumnSchemaBase<T, string, 'category'> {
400
+ /** Compute one category label from a row. */
401
+ accessor: (item: T) => string | null | undefined;
402
+ }
403
+ /** Explicit derived boolean column definition. */
404
+ interface DerivedBooleanColumnSchema<T> extends DerivedColumnSchemaBase<T, boolean, 'boolean'> {
405
+ /** Compute one boolean value from a row. */
406
+ accessor: (item: T) => boolean | null | undefined;
407
+ /** Human-facing label used when the derived value is `true`. */
408
+ trueLabel?: string;
409
+ /** Human-facing label used when the derived value is `false`. */
410
+ falseLabel?: string;
411
+ }
412
+ /** Explicit derived numeric column definition. */
413
+ interface DerivedNumberColumnSchema<T> extends DerivedColumnSchemaBase<T, number, 'number'> {
414
+ /** Compute one numeric value from a row for aggregation. */
415
+ accessor: (item: T) => number | null | undefined;
416
+ }
417
+ /** Any explicit derived column accepted in `schema.columns`. */
418
+ type DerivedColumnSchema<T> = DerivedDateColumnSchema<T> | DerivedCategoryColumnSchema<T> | DerivedBooleanColumnSchema<T> | DerivedNumberColumnSchema<T>;
419
+ type ExcludedHintKeys<THints> = Extract<{ [TKey in keyof THints]-?: THints[TKey] extends false ? TKey : never }[keyof THints], string>;
420
+ /** Column ID union after removing any fields explicitly disabled by hints. */
421
+ type ResolvedColumnIdFromHints<T, THints extends ColumnHints<T> | undefined = undefined> = Exclude<InferableFieldKey<T>, THints extends ColumnHints<T> ? ExcludedHintKeys<THints> : never>;
422
+ type ExplicitHintedColumnType<THint> = Extract<THint extends {
423
+ type?: infer TType;
424
+ } ? TType : never, ChartColumnType>;
425
+ type PotentialColumnTypeFromValue<TValue> = [Exclude<TValue, Nullish>] extends [boolean] ? 'boolean' : [Exclude<TValue, Nullish>] extends [Date] ? 'date' : [Exclude<TValue, Nullish>] extends [number] ? 'date' | 'number' : [Exclude<TValue, Nullish>] extends [string] ? 'date' | 'category' : ChartColumnType;
426
+ type PotentialColumnTypeFromHints<T, THints extends ColumnHints<T> | undefined, TKey extends ResolvedColumnIdFromHints<T, THints>> = THints extends ColumnHints<T> ? [ExplicitHintedColumnType<THints[TKey]>] extends [never] ? PotentialColumnTypeFromValue<T[TKey]> : ExplicitHintedColumnType<THints[TKey]> : PotentialColumnTypeFromValue<T[TKey]>;
427
+ type ColumnIdsMatchingPotentialTypes<T, THints extends ColumnHints<T> | undefined, TAllowedType extends ChartColumnType> = Extract<{ [TKey in ResolvedColumnIdFromHints<T, THints>]: Extract<PotentialColumnTypeFromHints<T, THints, TKey>, TAllowedType> extends never ? never : TKey }[ResolvedColumnIdFromHints<T, THints>], string>;
428
+ /** Column IDs that can safely be treated as X-axis candidates from static information. */
429
+ type ResolvedXAxisColumnIdFromHints<T, THints extends ColumnHints<T> | undefined = undefined> = ColumnIdsMatchingPotentialTypes<T, THints, 'date' | 'category' | 'boolean'>;
430
+ /** Column IDs that can safely be treated as groupBy candidates from static information. */
431
+ type ResolvedGroupByColumnIdFromHints<T, THints extends ColumnHints<T> | undefined = undefined> = ColumnIdsMatchingPotentialTypes<T, THints, 'category' | 'boolean'>;
432
+ /** Column IDs that can safely be treated as filter candidates from static information. */
433
+ type ResolvedFilterColumnIdFromHints<T, THints extends ColumnHints<T> | undefined = undefined> = ColumnIdsMatchingPotentialTypes<T, THints, 'category' | 'boolean'>;
434
+ /** Column IDs that can safely be treated as metric candidates from static information. */
435
+ type ResolvedMetricColumnIdFromHints<T, THints extends ColumnHints<T> | undefined = undefined> = ColumnIdsMatchingPotentialTypes<T, THints, 'number'>;
436
+ /** Column IDs that can safely be treated as date candidates from static information. */
437
+ type ResolvedDateColumnIdFromHints<T, THints extends ColumnHints<T> | undefined = undefined> = ColumnIdsMatchingPotentialTypes<T, THints, 'date'>;
438
+ type ConfigSection<TConfig, TKey extends string> = TKey extends keyof TConfig ? TConfig[TKey] : never;
439
+ type AllowedOptionFromControlConfig<TControlConfig> = TControlConfig extends {
440
+ allowed?: readonly (infer TAllowedOption)[];
441
+ } ? TAllowedOption : never;
442
+ type HiddenOptionFromControlConfig<TControlConfig> = TControlConfig extends {
443
+ hidden?: readonly (infer THiddenOption)[];
444
+ } ? THiddenOption : never;
445
+ type RestrictUnionOrFallback<TAllowed, TFallback> = [Extract<TAllowed, TFallback>] extends [never] ? TFallback : Extract<TAllowed, TFallback>;
446
+ type ExcludeHiddenOrFallback<TBase, THidden> = [Extract<THidden, TBase>] extends [TBase] ? TBase : Exclude<TBase, Extract<THidden, TBase>>;
447
+ type RestrictOptionsFromControlConfig<TBaseOption, TControlConfig> = ExcludeHiddenOrFallback<RestrictUnionOrFallback<AllowedOptionFromControlConfig<TControlConfig>, TBaseOption>, Extract<HiddenOptionFromControlConfig<TControlConfig>, TBaseOption>>;
448
+ type AllowedMetricFromConfig<TConfig> = TConfig extends {
449
+ metric?: {
450
+ allowed?: readonly (infer TAllowedMetric)[];
451
+ };
452
+ } ? TAllowedMetric : never;
453
+ type HiddenMetricFromConfig<TConfig> = TConfig extends {
454
+ metric?: {
455
+ hidden?: readonly (infer THiddenMetric)[];
456
+ };
457
+ } ? THiddenMetric : never;
458
+ type ExpandMetricAllowance<TMetricAllowance> = TMetricAllowance extends CountMetric ? CountMetric : TMetricAllowance extends {
459
+ kind: 'aggregate';
460
+ columnId: infer TColumnId extends string;
461
+ aggregate: infer TAggregate;
462
+ includeZeros?: infer TIncludeZeros;
463
+ } ? TAggregate extends readonly NumericAggregateFunction[] ? { [TSelectedAggregate in TAggregate[number]]: {
464
+ kind: 'aggregate';
465
+ columnId: TColumnId;
466
+ aggregate: TSelectedAggregate;
467
+ includeZeros?: Extract<TIncludeZeros, boolean | undefined>;
468
+ } }[TAggregate[number]] : TAggregate extends NumericAggregateFunction ? {
469
+ kind: 'aggregate';
470
+ columnId: TColumnId;
471
+ aggregate: TAggregate;
472
+ includeZeros?: Extract<TIncludeZeros, boolean | undefined>;
473
+ } : never : never;
474
+ type MetricColumnIdFromMetric<TMetric> = Extract<TMetric extends AggregateMetric<infer TColumnId> ? TColumnId : never, string>;
475
+ type StaticConfigError<TMessage extends string> = {
476
+ __configError__: TMessage;
477
+ };
478
+ type IsExactly<TLeft, TRight> = [TLeft] extends [TRight] ? [TRight] extends [TLeft] ? true : false : false;
479
+ type IsTuple<TArray extends readonly unknown[]> = number extends TArray['length'] ? false : true;
480
+ type NarrowConfigLiteral<TValue, TWide> = IsExactly<TValue, TWide> extends true ? never : TValue;
481
+ type RequiredKeys<TObject> = Extract<{ [TKey in keyof TObject]-?: undefined extends TObject[TKey] ? never : TKey }[keyof TObject], PropertyKey>;
482
+ type ExactShape<TExpected, TActual> = TExpected extends unknown ? TActual extends readonly (infer TActualItem)[] ? TExpected extends readonly (infer TExpectedItem)[] ? readonly ExactShape<TExpectedItem, TActualItem>[] : never : TActual extends ((...args: never[]) => unknown) ? TActual extends TExpected ? TActual : never : TActual extends object ? TExpected extends object ? { [TKey in keyof TActual]: TKey extends keyof TExpected ? ExactShape<TExpected[TKey], TActual[TKey]> : never } & { [TKey in Exclude<RequiredKeys<TExpected>, keyof TActual>]-?: never } : never : TActual extends TExpected ? TActual : never : never;
483
+ type SchemaColumnsValidationShape<T, TColumns extends Record<string, unknown> | undefined> = TColumns extends Record<string, unknown> ? { [TKey in keyof TColumns]: TKey extends InferableFieldKey<T> ? RawColumnSchemaFor<T[TKey], T> | false : DerivedColumnSchema<T> } : never;
484
+ type ValidateLiteralDefaultNotHidden<TSection, TWideOption, TSectionName extends string> = TSection extends {
485
+ default?: infer TDefault;
486
+ hidden?: infer THidden;
487
+ } ? THidden extends readonly unknown[] ? IsTuple<THidden> extends true ? [NarrowConfigLiteral<Exclude<TDefault, undefined>, TWideOption>] extends [never] ? unknown : [Extract<NarrowConfigLiteral<Exclude<TDefault, undefined>, TWideOption>, THidden[number]>] extends [never] ? unknown : StaticConfigError<`${TSectionName}.default cannot also appear in ${TSectionName}.hidden`> : unknown : unknown : unknown;
488
+ type ValidateChartSchemaLiterals<T, TSchema extends ChartSchema<T, any>> = ValidateLiteralDefaultNotHidden<ConfigSection<TSchema, 'xAxis'>, ResolvedXAxisColumnIdFromSchema<T, TSchema>, 'xAxis'> & ValidateLiteralDefaultNotHidden<ConfigSection<TSchema, 'groupBy'>, ResolvedGroupByColumnIdFromSchema<T, TSchema>, 'groupBy'> & ValidateLiteralDefaultNotHidden<ConfigSection<TSchema, 'metric'>, Metric<ResolvedMetricColumnIdFromSchema<T, TSchema>>, 'metric'> & ValidateLiteralDefaultNotHidden<ConfigSection<TSchema, 'chartType'>, ChartType, 'chartType'> & ValidateLiteralDefaultNotHidden<ConfigSection<TSchema, 'timeBucket'>, TimeBucket, 'timeBucket'>;
489
+ type ChartSchemaValidationTarget<T, TSchema extends ChartSchema<T, any>> = {
490
+ columns?: SchemaColumnsValidationShape<T, ExtractSchemaColumns<TSchema>>;
491
+ xAxis?: XAxisConfig<ResolvedXAxisColumnIdFromSchema<T, TSchema>>;
492
+ groupBy?: GroupByConfig<ResolvedGroupByColumnIdFromSchema<T, TSchema>>;
493
+ filters?: FiltersConfig<ResolvedFilterColumnIdFromSchema<T, TSchema>>;
494
+ metric?: MetricConfig<ResolvedMetricColumnIdFromSchema<T, TSchema>>;
495
+ chartType?: ChartTypeConfig;
496
+ timeBucket?: TimeBucketConfig;
497
+ };
498
+ /** Strict schema object returned by `defineChartSchema(...)`. */
499
+ type ValidatedChartSchema<T, TSchema> = TSchema extends ChartSchema<T, any> ? TSchema & ExactShape<ChartSchemaValidationTarget<T, TSchema>, TSchema> & ValidateChartSchemaLiterals<T, TSchema> : TSchema;
500
+ /** Strict schema object returned by `defineChartSchema(...)`. */
501
+ type DefinedChartSchema<T, TSchema extends ChartSchema<T, any> = ChartSchema<T, any>> = TSchema & ChartSchemaDefinitionBrand;
502
+ type ResolvedChartSchemaFromDefinition<TSchema> = TSchema extends DefinedChartSchema<any, infer TResolvedSchema> ? TResolvedSchema : undefined;
503
+ /** GroupBy IDs narrowed by explicit schema restrictions when present. */
504
+ type RestrictedGroupByColumnIdFromSchema<T, TSchema extends ChartSchema<T, any> | undefined = undefined> = RestrictOptionsFromControlConfig<ResolvedGroupByColumnIdFromSchema<T, TSchema>, ConfigSection<TSchema, 'groupBy'>>;
505
+ /** X-axis IDs narrowed by explicit schema restrictions when present. */
506
+ type RestrictedXAxisColumnIdFromSchema<T, TSchema extends ChartSchema<T, any> | undefined = undefined> = RestrictOptionsFromControlConfig<ResolvedXAxisColumnIdFromSchema<T, TSchema>, ConfigSection<TSchema, 'xAxis'>>;
507
+ /** Filter column IDs narrowed by explicit schema restrictions when present. */
508
+ type RestrictedFilterColumnIdFromSchema<T, TSchema extends ChartSchema<T, any> | undefined = undefined> = RestrictOptionsFromControlConfig<ResolvedFilterColumnIdFromSchema<T, TSchema>, ConfigSection<TSchema, 'filters'>>;
509
+ /** Metric union narrowed by explicit schema restrictions when present. */
510
+ type RestrictedMetricFromSchema<T, TSchema extends ChartSchema<T, any> | undefined = undefined> = ExcludeHiddenOrFallback<RestrictUnionOrFallback<ExpandMetricAllowance<AllowedMetricFromConfig<TSchema>>, Metric<ResolvedMetricColumnIdFromSchema<T, TSchema>>>, HiddenMetricFromConfig<TSchema>>;
511
+ /** Chart types narrowed by explicit schema restrictions when present. */
512
+ type RestrictedChartTypeFromSchema<TSchema> = RestrictOptionsFromControlConfig<ChartType, ConfigSection<TSchema, 'chartType'>>;
513
+ /** Time buckets narrowed by explicit schema restrictions when present. */
514
+ type RestrictedTimeBucketFromSchema<TSchema> = RestrictOptionsFromControlConfig<TimeBucket, ConfigSection<TSchema, 'timeBucket'>>;
515
+ /** Base properties shared by all column types. */
516
+ type ColumnBase<T, TId extends string> = {
517
+ /** Unique identifier — typically the field key in the data object. */id: TId; /** Human-readable label for the UI. */
518
+ label: string; /** Optional display formatter preset used by the UI layer. */
519
+ format?: ColumnFormat;
520
+ /**
521
+ * Optional per-value formatter used by the UI layer.
522
+ *
523
+ * The source `item` is passed when the rendered value still maps to one raw
524
+ * row, such as filter option labels. Aggregated chart values do not have a
525
+ * single backing row, so `item` is optional there by design.
526
+ */
527
+ formatter?: (value: unknown, item?: T) => string; /** Optional debug metadata describing how the column was inferred. */
528
+ inference?: ColumnInferenceMetadata;
529
+ };
530
+ /**
531
+ * A date column — eligible as a time-series X-axis.
532
+ *
533
+ * @property accessor - Extracts a date value from a data item
534
+ */
535
+ type DateColumn<T, TId extends string = string> = ColumnBase<T, TId> & {
536
+ type: 'date';
537
+ accessor: (item: T) => string | number | Date | null | undefined;
538
+ };
539
+ /**
540
+ * A category column — eligible for X-axis, groupBy, and filtering.
541
+ *
542
+ * @property accessor - Extracts a string category value from a data item
543
+ */
544
+ type CategoryColumn<T, TId extends string = string> = ColumnBase<T, TId> & {
545
+ type: 'category';
546
+ accessor: (item: T) => string | null | undefined;
547
+ };
548
+ /**
549
+ * A boolean column — eligible for groupBy (2 groups) and toggle filtering.
550
+ *
551
+ * @property accessor - Extracts a boolean value from a data item
552
+ * @property trueLabel - Label for the `true` group (e.g. "Open")
553
+ * @property falseLabel - Label for the `false` group (e.g. "Closed")
554
+ */
555
+ type BooleanColumn<T, TId extends string = string> = ColumnBase<T, TId> & {
556
+ type: 'boolean';
557
+ accessor: (item: T) => boolean | null | undefined;
558
+ trueLabel: string;
559
+ falseLabel: string;
560
+ };
561
+ /**
562
+ * A number column — eligible as a metric for aggregation.
563
+ *
564
+ * @property accessor - Extracts a numeric value from a data item
565
+ */
566
+ type NumberColumn<T, TId extends string = string> = ColumnBase<T, TId> & {
567
+ type: 'number';
568
+ accessor: (item: T) => number | null | undefined;
569
+ };
570
+ /** Union of all column types. */
571
+ type ChartColumn<T, TId extends string = string> = DateColumn<T, TId> | CategoryColumn<T, TId> | BooleanColumn<T, TId> | NumberColumn<T, TId>;
572
+ /** Chart types available for time-series (date X-axis). */
573
+ type TimeSeriesChartType = 'bar' | 'line' | 'area';
574
+ /** Chart types available for categorical (category/boolean X-axis). */
575
+ type CategoricalChartType = 'bar' | 'pie' | 'donut';
576
+ /** All supported chart types. */
577
+ type ChartType = TimeSeriesChartType | CategoricalChartType;
578
+ /** Time bucket sizes for date X-axis. */
579
+ type TimeBucket = 'day' | 'week' | 'month' | 'quarter' | 'year';
580
+ /** Aggregation functions for the Y-axis metric. */
581
+ type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max';
582
+ /** Numeric aggregation functions that operate on number columns. */
583
+ type NumericAggregateFunction = Exclude<AggregateFunction, 'count'>;
584
+ /** One or many numeric aggregates accepted by declarative metric restrictions. */
585
+ type NumericAggregateSelection = NumericAggregateFunction | readonly NumericAggregateFunction[];
586
+ /**
587
+ * A metric definition — what the Y-axis measures.
588
+ *
589
+ * `count` represents item count.
590
+ * `aggregate` represents a numeric column aggregation.
591
+ */
592
+ type CountMetric = {
593
+ kind: 'count';
594
+ };
595
+ /**
596
+ * A numeric aggregation metric.
597
+ *
598
+ * @property columnId - The number column to aggregate
599
+ * @property aggregate - The numeric aggregation function to apply
600
+ * @property includeZeros - Whether zero values are included in aggregation
601
+ */
602
+ type AggregateMetric<TColumnId extends string = string> = {
603
+ kind: 'aggregate';
604
+ columnId: TColumnId;
605
+ aggregate: NumericAggregateFunction;
606
+ includeZeros?: boolean;
607
+ };
608
+ /** Metric union returned by the chart hook. */
609
+ type Metric<TColumnId extends string = string> = CountMetric | AggregateMetric<TColumnId>;
610
+ /**
611
+ * Aggregate metric restriction accepted by `config.metric.allowed`.
612
+ *
613
+ * Unlike `AggregateMetric`, the `aggregate` field may be either one aggregate
614
+ * or an array shorthand that expands into several allowed metrics.
615
+ */
616
+ type AggregateMetricAllowance<TColumnId extends string = string> = {
617
+ kind: 'aggregate';
618
+ columnId: TColumnId;
619
+ aggregate: NumericAggregateSelection;
620
+ includeZeros?: boolean;
621
+ };
622
+ /** One declarative metric entry accepted by `config.metric.allowed`. */
623
+ type MetricAllowance<TColumnId extends string = string> = CountMetric | AggregateMetricAllowance<TColumnId>;
624
+ /** Shared config shape for one selectable control. */
625
+ type SelectableControlConfig<TOption extends string = string> = {
626
+ /** Optional whitelist of options that remain visible/selectable. */allowed?: readonly TOption[]; /** Optional blacklist of options to hide after whitelisting. */
627
+ hidden?: readonly TOption[]; /** Preferred selection when the current value is missing or invalid. */
628
+ default?: TOption;
629
+ };
630
+ /** Declarative config for the X-axis control. */
631
+ type XAxisConfig<TColumnId extends string = string> = SelectableControlConfig<TColumnId>;
632
+ /** Declarative config for the groupBy control. */
633
+ type GroupByConfig<TColumnId extends string = string> = SelectableControlConfig<TColumnId>;
634
+ /**
635
+ * Declarative config for which columns may appear in the filters UI.
636
+ *
637
+ * Filter value restrictions are intentionally out of scope for now. This keeps
638
+ * `filters` aligned with the existing column-first headless model.
639
+ */
640
+ type FiltersConfig<TColumnId extends string = string> = Omit<SelectableControlConfig<TColumnId>, 'default'>;
641
+ /**
642
+ * Declarative config for the metric control.
643
+ *
644
+ * `allowed` keeps the shorthand array-expansion form while `hidden` and
645
+ * `default` operate on fully resolved metric objects.
646
+ */
647
+ type MetricConfig<TColumnId extends string = string> = {
648
+ allowed?: readonly MetricAllowance<TColumnId>[];
649
+ hidden?: readonly Metric<TColumnId>[];
650
+ default?: Metric<TColumnId>;
651
+ };
652
+ /** Declarative config for chart-type selection. */
653
+ type ChartTypeConfig = SelectableControlConfig<ChartType>;
654
+ /** Declarative config for time-bucket selection. */
655
+ type TimeBucketConfig = SelectableControlConfig<TimeBucket>;
656
+ type ExtractSchemaColumns<TSchema> = TSchema extends {
657
+ columns?: infer TColumns;
658
+ } ? Extract<TColumns, Record<string, unknown>> : undefined;
659
+ type RawSchemaColumnsFromColumns<T, TColumns extends Record<string, unknown> | undefined> = TColumns extends Record<string, unknown> ? { [TKey in keyof TColumns as TKey extends InferableFieldKey<T> ? TKey : never]: TColumns[TKey] } : undefined;
660
+ type RawSchemaColumns<T, TSchema> = RawSchemaColumnsFromColumns<T, ExtractSchemaColumns<TSchema>>;
661
+ type DerivedColumnIdsFromColumns<TColumns extends Record<string, unknown> | undefined> = Extract<TColumns extends Record<string, unknown> ? { [TKey in keyof TColumns]-?: TColumns[TKey] extends DerivedColumnSchema<any> ? TKey : never }[keyof TColumns] : never, string>;
662
+ type DerivedColumnIdsByTypeFromColumns<TColumns extends Record<string, unknown> | undefined, TAllowedType extends ChartColumnType> = Extract<TColumns extends Record<string, unknown> ? { [TKey in keyof TColumns]-?: TColumns[TKey] extends {
663
+ kind: 'derived';
664
+ type: infer TType;
665
+ } ? Extract<TType, TAllowedType> extends never ? never : TKey : never }[keyof TColumns] : never, string>;
666
+ /** Column ID union resolved from one explicit schema definition. */
667
+ type ResolvedColumnIdFromSchema<T, TSchema extends {
668
+ columns?: Record<string, unknown>;
669
+ } | undefined = undefined> = ResolvedColumnIdFromHints<T, Extract<RawSchemaColumns<T, TSchema>, ColumnHints<T> | undefined>> | DerivedColumnIdsFromColumns<ExtractSchemaColumns<TSchema>>;
670
+ /** X-axis IDs resolved from one explicit schema definition. */
671
+ type ResolvedXAxisColumnIdFromSchema<T, TSchema extends {
672
+ columns?: Record<string, unknown>;
673
+ } | undefined = undefined> = ResolvedXAxisColumnIdFromHints<T, Extract<RawSchemaColumns<T, TSchema>, ColumnHints<T> | undefined>> | DerivedColumnIdsByTypeFromColumns<ExtractSchemaColumns<TSchema>, 'date' | 'category' | 'boolean'>;
674
+ /** GroupBy IDs resolved from one explicit schema definition. */
675
+ type ResolvedGroupByColumnIdFromSchema<T, TSchema extends {
676
+ columns?: Record<string, unknown>;
677
+ } | undefined = undefined> = ResolvedGroupByColumnIdFromHints<T, Extract<RawSchemaColumns<T, TSchema>, ColumnHints<T> | undefined>> | DerivedColumnIdsByTypeFromColumns<ExtractSchemaColumns<TSchema>, 'category' | 'boolean'>;
678
+ /** Filter IDs resolved from one explicit schema definition. */
679
+ type ResolvedFilterColumnIdFromSchema<T, TSchema extends {
680
+ columns?: Record<string, unknown>;
681
+ } | undefined = undefined> = ResolvedFilterColumnIdFromHints<T, Extract<RawSchemaColumns<T, TSchema>, ColumnHints<T> | undefined>> | DerivedColumnIdsByTypeFromColumns<ExtractSchemaColumns<TSchema>, 'category' | 'boolean'>;
682
+ /** Metric column IDs resolved from one explicit schema definition. */
683
+ type ResolvedMetricColumnIdFromSchema<T, TSchema extends {
684
+ columns?: Record<string, unknown>;
685
+ } | undefined = undefined> = ResolvedMetricColumnIdFromHints<T, Extract<RawSchemaColumns<T, TSchema>, ColumnHints<T> | undefined>> | DerivedColumnIdsByTypeFromColumns<ExtractSchemaColumns<TSchema>, 'number'>;
686
+ /** Date column IDs resolved from one explicit schema definition. */
687
+ type ResolvedDateColumnIdFromSchema<T, TSchema extends {
688
+ columns?: Record<string, unknown>;
689
+ } | undefined = undefined> = ResolvedDateColumnIdFromHints<T, Extract<RawSchemaColumns<T, TSchema>, ColumnHints<T> | undefined>> | DerivedColumnIdsByTypeFromColumns<ExtractSchemaColumns<TSchema>, 'date'>;
690
+ /**
691
+ * Single authoritative explicit chart schema accepted by `useChart({schema})`.
692
+ *
693
+ * Think of this as the contract that sits on top of inference:
694
+ * - `columns` shapes what fields exist and how they behave
695
+ * - the top-level sections shape what the user is allowed to select in the UI
696
+ */
697
+ type ChartSchema<T, TColumns extends Record<string, unknown> | undefined = Record<string, unknown> | undefined> = {
698
+ /**
699
+ * Override raw inferred fields, exclude raw fields with `false`, or declare
700
+ * brand new derived columns.
701
+ */
702
+ columns?: TColumns & Partial<Record<InferableFieldKey<T>, unknown>>; /** Restrict which non-numeric columns can be selected on the X-axis. */
703
+ xAxis?: XAxisConfig<string>; /** Restrict which categorical/boolean columns can split the chart into series. */
704
+ groupBy?: GroupByConfig<string>; /** Restrict which categorical/boolean columns appear in the filters UI. */
705
+ filters?: FiltersConfig<string>; /** Restrict which metrics and aggregations can be selected. */
706
+ metric?: MetricConfig<string>; /** Restrict which chart types are available to the user. */
707
+ chartType?: ChartTypeConfig; /** Restrict which time buckets are available for date X-axes. */
708
+ timeBucket?: TimeBucketConfig;
709
+ };
710
+ type ChartSchemaDefinitionBrand = {
711
+ readonly __chartSchemaBrand: 'chart-schema-definition';
712
+ };
713
+ /**
714
+ * Active filter state per column.
715
+ * - For category columns: Set of selected values (empty = no filter = show all)
716
+ * - For boolean columns: true/false/null (null = no filter)
717
+ */
718
+ type FilterState<TColumnId extends string = string> = Map<TColumnId, Set<string>>;
719
+ /** Sort direction. */
720
+ type SortDirection = 'asc' | 'desc';
721
+ /**
722
+ * Sort configuration.
723
+ *
724
+ * @property key - The data key to sort by (e.g. "count", a group label)
725
+ * @property direction - Sort direction
726
+ */
727
+ type SortConfig = {
728
+ key: string;
729
+ direction: SortDirection;
730
+ };
731
+ /**
732
+ * Inference-first source definition accepted by multi-source charts.
733
+ *
734
+ * @property id - Unique identifier for this source
735
+ * @property label - Display label in the source switcher
736
+ * @property data - Array of raw data items
737
+ * @property schema - Optional explicit schema layered on top of inference
738
+ */
739
+ type ChartSourceOptions<TId extends string = string, T = unknown, TSchema extends ChartSchema<T, any> | undefined = undefined> = {
740
+ id: TId;
741
+ label: string;
742
+ data: readonly T[];
743
+ schema?: DefinedChartSchema<T, Exclude<TSchema, undefined>>;
744
+ };
745
+ /** Convenience alias for any multi-source input definition. */
746
+ type AnyChartSourceOptions = {
747
+ id: string;
748
+ label: string;
749
+ data: readonly any[];
750
+ schema?: any;
751
+ };
752
+ /** Multi-source charts require at least one source so an active source always exists. */
753
+ type NonEmptyChartSourceOptions = readonly [AnyChartSourceOptions, ...AnyChartSourceOptions[]];
754
+ /**
755
+ * A single series to render in the chart.
756
+ *
757
+ * @property dataKey - Key in the transformed data row
758
+ * @property label - Display label for tooltips/legend
759
+ * @property color - CSS color value (shadcn chart variable)
760
+ */
761
+ type ChartSeries = {
762
+ dataKey: string;
763
+ label: string;
764
+ color: string;
765
+ };
766
+ /**
767
+ * A single data point in the transformed output.
768
+ * Keys are dynamic based on groupBy values.
769
+ */
770
+ type TransformedDataPoint = Record<string, string | number>;
771
+ /**
772
+ * Available filter options extracted from the data for a column.
773
+ *
774
+ * @property columnId - The column this filter belongs to
775
+ * @property label - Column label
776
+ * @property type - Column type (category or boolean)
777
+ * @property options - Available values with counts
778
+ */
779
+ type AvailableFilter<TColumnId extends string = string> = {
780
+ columnId: TColumnId;
781
+ label: string;
782
+ type: 'category' | 'boolean';
783
+ options: Array<{
784
+ value: string;
785
+ label: string;
786
+ count: number;
787
+ }>;
788
+ };
789
+ /**
790
+ * Date range for a date column computed from the (filtered) data.
791
+ *
792
+ * @property columnId - The date column this range belongs to
793
+ * @property label - Column display label
794
+ * @property min - Earliest date in the data (null if no valid dates)
795
+ * @property max - Latest date in the data (null if no valid dates)
796
+ */
797
+ type DateRange<TColumnId extends string = string> = {
798
+ columnId: TColumnId;
799
+ label: string;
800
+ min: Date | null;
801
+ max: Date | null;
802
+ };
803
+ /**
804
+ * User-selected date range filter applied to the reference date column.
805
+ * Both bounds are inclusive. Null = no bound on that side.
806
+ *
807
+ * @property from - Start date (inclusive), null = no lower bound
808
+ * @property to - End date (inclusive), null = no upper bound
809
+ */
810
+ type DateRangeFilter = {
811
+ from: Date | null;
812
+ to: Date | null;
813
+ };
814
+ /**
815
+ * Full chart state returned by the useChart hook.
816
+ * Contains both controlled state and derived computations.
817
+ */
818
+ type ChartInstance<T, TColumnId extends string = string, TChartType extends ChartType = ChartType, TXAxisId extends TColumnId = TColumnId, TGroupById extends TColumnId = TColumnId, TMetricColumnId extends TColumnId = TColumnId, TMetric extends Metric<any> = Metric<TMetricColumnId>, TFilterColumnId extends TColumnId = TColumnId, TDateColumnId extends TColumnId = TColumnId, TTimeBucket extends TimeBucket = TimeBucket> = {
819
+ /** Active source ID (only relevant for multi-source). */activeSourceId: string; /** Switch to a different data source. */
820
+ setActiveSource: (sourceId: string) => void; /** Whether multiple sources are available. */
821
+ hasMultipleSources: boolean; /** Labels for all available sources. */
822
+ sources: Array<{
823
+ id: string;
824
+ label: string;
825
+ }>; /** Current chart type. */
826
+ chartType: TChartType; /** Change the chart type. Runtime accepts only values in `availableChartTypes`. */
827
+ setChartType: (type: TChartType) => void; /** Chart types currently available given the active axis, grouping, and config. */
828
+ availableChartTypes: TChartType[]; /** Current X-axis column ID. */
829
+ xAxisId: TXAxisId | null;
830
+ /**
831
+ * Change the X-axis column.
832
+ * Runtime accepts only IDs currently present in `availableXAxes`.
833
+ */
834
+ setXAxis: (columnId: TXAxisId) => void;
835
+ /**
836
+ * Columns currently eligible for X-axis at runtime.
837
+ * Typing narrows only from explicit `columnHints.type`, not from runtime inference.
838
+ */
839
+ availableXAxes: Array<{
840
+ id: TXAxisId;
841
+ label: string;
842
+ type: 'date' | 'category' | 'boolean';
843
+ }>; /** Current groupBy column ID (null = no grouping). */
844
+ groupById: TGroupById | null;
845
+ /**
846
+ * Change the groupBy column.
847
+ * Runtime accepts only `null` or IDs currently present in `availableGroupBys`.
848
+ */
849
+ setGroupBy: (columnId: TGroupById | null) => void;
850
+ /**
851
+ * Columns currently eligible for groupBy at runtime.
852
+ * Explicit `config.groupBy.allowed` further narrows this list and the setter type.
853
+ */
854
+ availableGroupBys: Array<{
855
+ id: TGroupById;
856
+ label: string;
857
+ }>; /** Current metric (what the Y-axis measures). */
858
+ metric: TMetric;
859
+ /**
860
+ * Change the metric.
861
+ * Runtime accepts only metrics currently present in `availableMetrics`.
862
+ */
863
+ setMetric: (metric: TMetric) => void;
864
+ /**
865
+ * Metrics currently available at runtime.
866
+ * Explicit `config.metric.allowed` narrows both this list and the setter type.
867
+ */
868
+ availableMetrics: TMetric[]; /** Current time bucket (only relevant when X-axis is date). */
869
+ timeBucket: TTimeBucket; /** Change the time bucket. Runtime accepts only values in `availableTimeBuckets`. */
870
+ setTimeBucket: (bucket: TTimeBucket) => void; /** Time buckets currently available for the active chart state and config. */
871
+ availableTimeBuckets: TTimeBucket[]; /** Whether time bucketing controls should be shown. */
872
+ isTimeSeries: boolean; /** Active filter values per column. */
873
+ filters: FilterState<TFilterColumnId>;
874
+ /**
875
+ * Toggle a specific filter value on/off for a column.
876
+ * Runtime accepts only values currently exposed through `availableFilters`.
877
+ */
878
+ toggleFilter: (columnId: TFilterColumnId, value: string) => void; /** Clear all filters for a column when that column is currently filterable. */
879
+ clearFilter: (columnId: TFilterColumnId) => void; /** Clear all filters. */
880
+ clearAllFilters: () => void; /** Filter options currently available from the runtime data. */
881
+ availableFilters: AvailableFilter<TFilterColumnId>[]; /** Current sort configuration (null = default order). */
882
+ sorting: SortConfig | null; /** Change sorting. */
883
+ setSorting: (sorting: SortConfig | null) => void; /** Date range for the active reference date column (computed from filtered data). */
884
+ dateRange: DateRange<TDateColumnId> | null; /** Which date column provides the visible date range context. */
885
+ referenceDateId: TDateColumnId | null;
886
+ /**
887
+ * Change the reference date column.
888
+ * Runtime accepts only IDs currently present in `availableDateColumns`.
889
+ */
890
+ setReferenceDateId: (columnId: TDateColumnId) => void; /** Date columns currently available as reference dates at runtime. */
891
+ availableDateColumns: Array<{
892
+ id: TDateColumnId;
893
+ label: string;
894
+ }>; /** Active date range filter (null = all time). */
895
+ dateRangeFilter: DateRangeFilter | null; /** Set the date range filter. Pass null to clear (show all time). */
896
+ setDateRangeFilter: (filter: DateRangeFilter | null) => void; /** Transformed data points ready for recharts. */
897
+ transformedData: TransformedDataPoint[]; /** Auto-generated series definitions for recharts. */
898
+ series: ChartSeries[]; /** Active columns for the current source. */
899
+ columns: readonly ChartColumn<T, TColumnId>[]; /** Raw data for the active source. */
900
+ rawData: readonly T[]; /** Total number of records in the active source (before filtering). */
901
+ recordCount: number;
902
+ };
903
+ /** Single-source chart instance narrowed by one explicit schema. */
904
+ type ChartInstanceFromSchema<T, TSchema extends ChartSchema<T, any> | undefined = undefined> = ChartInstance<T, ResolvedColumnIdFromSchema<T, TSchema>, RestrictedChartTypeFromSchema<TSchema>, RestrictedXAxisColumnIdFromSchema<T, TSchema>, RestrictedGroupByColumnIdFromSchema<T, TSchema>, Extract<MetricColumnIdFromMetric<RestrictedMetricFromSchema<T, TSchema>>, ResolvedMetricColumnIdFromSchema<T, TSchema>>, RestrictedMetricFromSchema<T, TSchema>, RestrictedFilterColumnIdFromSchema<T, TSchema>, ResolvedDateColumnIdFromSchema<T, TSchema>, RestrictedTimeBucketFromSchema<TSchema>>;
905
+ type SourceIdFromSource<TSource extends AnyChartSourceOptions> = TSource['id'];
906
+ type SourceRowFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, any> ? TRow : never;
907
+ type SourceColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? ResolvedColumnIdFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>> : never;
908
+ type SourceXAxisColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? RestrictedXAxisColumnIdFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>> : never;
909
+ type SourceGroupByColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? RestrictedGroupByColumnIdFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>> : never;
910
+ type SourceMetricColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? Extract<MetricColumnIdFromMetric<RestrictedMetricFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>>>, ResolvedMetricColumnIdFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>>> : never;
911
+ type SourceFilterColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? RestrictedFilterColumnIdFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>> : never;
912
+ type SourceDateColumnIdFromSource<TSource extends AnyChartSourceOptions> = TSource extends ChartSourceOptions<string, infer TRow, infer TSchema> ? ResolvedDateColumnIdFromSchema<TRow, Extract<TSchema, ChartSchema<TRow> | undefined>> : never;
913
+ type SourceIdFromSources<TSources extends NonEmptyChartSourceOptions> = Extract<TSources[number]['id'], string>;
914
+ type SourceColumnIdFromSources<TSources extends NonEmptyChartSourceOptions> = TSources[number] extends infer TSource ? TSource extends AnyChartSourceOptions ? SourceColumnIdFromSource<TSource> : never : never;
915
+ type MultiSourceChartBranch<TSources extends NonEmptyChartSourceOptions, TSource extends AnyChartSourceOptions> = Omit<ChartInstance<SourceRowFromSource<TSource>, SourceColumnIdFromSources<TSources>>, 'activeSourceId' | 'setActiveSource' | 'sources' | 'xAxisId' | 'setXAxis' | 'availableXAxes' | 'groupById' | 'setGroupBy' | 'availableGroupBys' | 'metric' | 'setMetric' | 'availableMetrics' | 'filters' | 'toggleFilter' | 'clearFilter' | 'availableFilters' | 'dateRange' | 'referenceDateId' | 'setReferenceDateId' | 'availableDateColumns' | 'columns'> & {
916
+ activeSourceId: SourceIdFromSource<TSource>;
917
+ setActiveSource: (sourceId: SourceIdFromSources<TSources>) => void;
918
+ sources: Array<{
919
+ id: SourceIdFromSources<TSources>;
920
+ label: string;
921
+ }>;
922
+ xAxisId: SourceXAxisColumnIdFromSource<TSource> | null;
923
+ setXAxis: (columnId: SourceColumnIdFromSources<TSources>) => void;
924
+ availableXAxes: Array<{
925
+ id: SourceXAxisColumnIdFromSource<TSource>;
926
+ label: string;
927
+ type: 'date' | 'category' | 'boolean';
928
+ }>;
929
+ groupById: SourceGroupByColumnIdFromSource<TSource> | null;
930
+ setGroupBy: (columnId: SourceColumnIdFromSources<TSources> | null) => void;
931
+ availableGroupBys: Array<{
932
+ id: SourceGroupByColumnIdFromSource<TSource>;
933
+ label: string;
934
+ }>;
935
+ metric: Metric<SourceMetricColumnIdFromSource<TSource>>;
936
+ setMetric: (metric: Metric<SourceColumnIdFromSources<TSources>>) => void;
937
+ availableMetrics: Metric<SourceMetricColumnIdFromSource<TSource>>[];
938
+ filters: FilterState<SourceFilterColumnIdFromSource<TSource>>;
939
+ toggleFilter: (columnId: SourceColumnIdFromSources<TSources>, value: string) => void;
940
+ clearFilter: (columnId: SourceColumnIdFromSources<TSources>) => void;
941
+ availableFilters: AvailableFilter<SourceFilterColumnIdFromSource<TSource>>[];
942
+ dateRange: DateRange<SourceDateColumnIdFromSource<TSource>> | null;
943
+ referenceDateId: SourceDateColumnIdFromSource<TSource> | null;
944
+ setReferenceDateId: (columnId: SourceColumnIdFromSources<TSources>) => void;
945
+ availableDateColumns: Array<{
946
+ id: SourceDateColumnIdFromSource<TSource>;
947
+ label: string;
948
+ }>;
949
+ columns: readonly ChartColumn<SourceRowFromSource<TSource>, SourceColumnIdFromSource<TSource>>[];
950
+ };
951
+ /**
952
+ * Direct multi-source hook return type.
953
+ * Narrow on `activeSourceId` to recover the source-specific row and column IDs.
954
+ */
955
+ type MultiSourceChartInstance<TSources extends NonEmptyChartSourceOptions> = TSources[number] extends infer TSource ? TSource extends AnyChartSourceOptions ? MultiSourceChartBranch<TSources, TSource> : never : never;
956
+ //#endregion
957
+ export { AggregateFunction, AggregateMetric, AvailableFilter, BooleanColumn, CategoricalChartType, CategoryColumn, ChartColumn, ChartColumnType, ChartInstance, ChartInstanceFromSchema, ChartSchema, ChartSeries, ChartSourceOptions, ChartType, ChartTypeConfig, ColumnFormat, ColumnFormatPreset, CountMetric, DateColumn, DateColumnFormat, DefinedChartSchema, DerivedBooleanColumnSchema, DerivedCategoryColumnSchema, DerivedColumnSchema, DerivedDateColumnSchema, DerivedNumberColumnSchema, ExactShape, FilterState, FiltersConfig, GroupByConfig, InferableFieldKey, Metric, MetricConfig, MultiSourceChartInstance, NonEmptyChartSourceOptions, NumberColumn, NumberColumnFormat, NumericAggregateFunction, RawColumnSchemaFor, RawColumnSchemaMap, ResolvedChartSchemaFromDefinition, ResolvedColumnIdFromSchema, ResolvedFilterColumnIdFromSchema, ResolvedGroupByColumnIdFromSchema, ResolvedMetricColumnIdFromSchema, ResolvedXAxisColumnIdFromSchema, RestrictedChartTypeFromSchema, RestrictedFilterColumnIdFromSchema, RestrictedGroupByColumnIdFromSchema, RestrictedMetricFromSchema, RestrictedTimeBucketFromSchema, RestrictedXAxisColumnIdFromSchema, SchemaColumnsValidationShape, SelectableControlConfig, SortConfig, SortDirection, TimeBucket, TimeBucketConfig, TimeSeriesChartType, TransformedDataPoint, ValidatedChartSchema, XAxisConfig };