@zeyos/zx 2.2.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/DESIGN-SYSTEM.md +136 -0
  3. package/MIGRATION.md +155 -4
  4. package/README.md +51 -14
  5. package/dist/types/compat/map/data.d.ts +3 -2
  6. package/dist/types/components/account-menu/account-menu.d.ts +144 -0
  7. package/dist/types/components/app-rail/app-rail.d.ts +250 -0
  8. package/dist/types/components/app-sidebar/app-sidebar.d.ts +169 -0
  9. package/dist/types/components/avatar/avatar.d.ts +100 -0
  10. package/dist/types/components/chart/chart.d.ts +321 -0
  11. package/dist/types/components/date-picker/month-picker.d.ts +6 -0
  12. package/dist/types/components/dialog/dialog.d.ts +9 -0
  13. package/dist/types/components/dock/dock.d.ts +495 -0
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/field/field.d.ts +14 -0
  16. package/dist/types/components/field-upload/field-upload.d.ts +37 -4
  17. package/dist/types/components/form/form.d.ts +25 -3
  18. package/dist/types/components/grid/grid.d.ts +172 -0
  19. package/dist/types/components/launcher/launcher.d.ts +300 -0
  20. package/dist/types/components/layout/layout.d.ts +4 -4
  21. package/dist/types/components/menu-button/menu-button.d.ts +10 -2
  22. package/dist/types/components/modal/modal.d.ts +25 -0
  23. package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +74 -9
  24. package/dist/types/components/questionnaire/items.d.ts +327 -0
  25. package/dist/types/components/questionnaire/questionnaire.d.ts +449 -0
  26. package/dist/types/components/select/select.d.ts +36 -1
  27. package/dist/types/components/sheet/sheet-stack.d.ts +167 -0
  28. package/dist/types/components/sheet/sheet.d.ts +294 -0
  29. package/dist/types/components/split-view/split-view.d.ts +2 -21
  30. package/dist/types/components/tabbox/tabbox.d.ts +12 -3
  31. package/dist/types/components/table/table.d.ts +219 -12
  32. package/dist/types/components/tooltip/tooltip.d.ts +2 -2
  33. package/dist/types/components/value-list/value-list.d.ts +24 -3
  34. package/dist/types/core/dom.d.ts +114 -0
  35. package/dist/types/core/drag-axis.d.ts +111 -0
  36. package/dist/types/core/position.d.ts +4 -2
  37. package/dist/types/index.d.ts +12 -2
  38. package/dist/zx-compat.esm.js +943 -311
  39. package/dist/zx-compat.esm.min.js +1 -1
  40. package/dist/zx-compat.global.js +7893 -2252
  41. package/dist/zx-compat.global.min.js +3 -3
  42. package/dist/zx-zeyos.esm.js +826 -114
  43. package/dist/zx-zeyos.esm.min.js +1 -1
  44. package/dist/zx.css +1720 -243
  45. package/dist/zx.esm.js +8227 -2599
  46. package/dist/zx.esm.min.js +3 -3
  47. package/dist/zx.global.js +8227 -2599
  48. package/dist/zx.global.min.js +3 -3
  49. package/dist/zx.min.css +1 -1
  50. package/docs/api.json +2597 -255
  51. package/docs/llms.md +475 -37
  52. package/docs/llms.txt +21 -9
  53. package/package.json +4 -2
  54. package/src/compat/globals.js +3 -1
  55. package/src/compat/map/data.js +31 -9
  56. package/src/components/account-menu/account-menu.css +70 -0
  57. package/src/components/account-menu/account-menu.js +208 -0
  58. package/src/components/app-rail/app-rail.css +107 -0
  59. package/src/components/app-rail/app-rail.js +668 -0
  60. package/src/components/app-sidebar/app-sidebar.css +68 -0
  61. package/src/components/app-sidebar/app-sidebar.js +388 -0
  62. package/src/components/avatar/avatar.css +61 -0
  63. package/src/components/avatar/avatar.js +222 -0
  64. package/src/components/button/button.css +0 -1
  65. package/src/components/chart/chart.css +61 -0
  66. package/src/components/chart/chart.js +518 -0
  67. package/src/components/date-picker/date-range-picker.css +7 -0
  68. package/src/components/date-picker/month-picker.js +9 -0
  69. package/src/components/dialog/dialog.js +11 -4
  70. package/src/components/dock/dock.css +228 -0
  71. package/src/components/dock/dock.js +977 -0
  72. package/src/components/dropdown/dropdown.js +1 -1
  73. package/src/components/field/field.js +2 -0
  74. package/src/components/field-upload/field-upload.js +9 -4
  75. package/src/components/fieldset/fieldset.css +7 -0
  76. package/src/components/form/form.css +7 -0
  77. package/src/components/form/form.js +6 -3
  78. package/src/components/grid/grid.css +6 -0
  79. package/src/components/grid/grid.js +213 -0
  80. package/src/components/launcher/launcher.css +100 -0
  81. package/src/components/launcher/launcher.js +557 -0
  82. package/src/components/layout/layout.js +2 -2
  83. package/src/components/menu-button/menu-button.js +27 -2
  84. package/src/components/modal/modal.css +5 -1
  85. package/src/components/modal/modal.js +37 -2
  86. package/src/components/multi-value-editor/multi-value-editor.css +99 -15
  87. package/src/components/multi-value-editor/multi-value-editor.js +237 -19
  88. package/src/components/navigation-bar/navigation-bar.css +7 -0
  89. package/src/components/questionnaire/items.js +370 -0
  90. package/src/components/questionnaire/questionnaire.css +387 -0
  91. package/src/components/questionnaire/questionnaire.js +1033 -0
  92. package/src/components/search/search.css +10 -2
  93. package/src/components/select/select.css +6 -1
  94. package/src/components/select/select.js +99 -11
  95. package/src/components/sheet/sheet-stack.js +304 -0
  96. package/src/components/sheet/sheet.css +351 -0
  97. package/src/components/sheet/sheet.js +551 -0
  98. package/src/components/split-view/split-view.js +76 -161
  99. package/src/components/stepper/stepper.css +12 -2
  100. package/src/components/tabbox/tabbox.css +13 -3
  101. package/src/components/tabbox/tabbox.js +34 -10
  102. package/src/components/table/table.css +43 -0
  103. package/src/components/table/table.js +523 -45
  104. package/src/components/tooltip/tooltip.js +5 -2
  105. package/src/components/value-list/value-list.js +6 -3
  106. package/src/core/dom.js +302 -0
  107. package/src/core/drag-axis.js +203 -0
  108. package/src/core/position.js +68 -22
  109. package/src/index.js +19 -2
  110. package/styles/tokens/dark.css +15 -13
  111. package/styles/tokens/global.css +17 -5
  112. package/styles/tokens/semantic.css +14 -8
  113. package/styles/tokens/themes.css +72 -0
  114. package/styles/zx.css +11 -1
  115. package/dist/types/components/permission/permission.d.ts +0 -93
  116. package/src/components/permission/permission.css +0 -47
  117. package/src/components/permission/permission.js +0 -198
@@ -0,0 +1,518 @@
1
+ import { Component } from '../../core/component.js';
2
+ import { h } from '../../core/dom.js';
3
+ import { uid } from '../../core/util.js';
4
+
5
+ /** @typedef {{label?: string, data?: unknown[], [key: string]: unknown}} ChartDataset */
6
+ /** @typedef {{labels?: unknown[], datasets?: ChartDataset[], [key: string]: unknown}} ChartData */
7
+ /**
8
+ * @typedef {Object} ChartSpec
9
+ * @property {string} type Chart type.
10
+ * @property {ChartData} data Chart data.
11
+ * @property {Record<string, unknown>} options Engine-specific options.
12
+ * @property {(detail: ChartSelectDetail) => void} onSelect Point-selection callback.
13
+ */
14
+ /**
15
+ * @typedef {Object} ChartHandle
16
+ * @property {(spec: ChartSpec) => void} update Update the mounted chart.
17
+ * @property {() => void} resize Resize the mounted chart.
18
+ * @property {() => void} destroy Destroy the mounted chart.
19
+ * @property {unknown} [instance] Engine instance.
20
+ */
21
+ /** @typedef {{create: (canvas: HTMLCanvasElement, spec: ChartSpec) => ChartHandle}} ChartAdapter */
22
+ /**
23
+ * @typedef {Object} ChartJsAdapterOptions
24
+ * Generated API marker for the adapter class; its constructor accepts an injected engine and
25
+ * optional defaults directly rather than a component options object.
26
+ */
27
+ /**
28
+ * @typedef {Object} ChartSelectDetail
29
+ * @property {number} datasetIndex Dataset index.
30
+ * @property {number} index Data index.
31
+ * @property {ChartDataset|null} dataset Dataset definition.
32
+ * @property {unknown} label Label at the selected index.
33
+ * @property {unknown} value Data value.
34
+ * @property {Event|null} nativeEvent Engine-native event when available.
35
+ */
36
+ /**
37
+ * @typedef {Object} ChartOptions
38
+ * @property {ChartAdapter|null} [adapter=null] Injected engine adapter.
39
+ * @property {string} [type='bar'] Chart type passed to the adapter.
40
+ * @property {ChartData} [data={labels: [], datasets: []}] Chart data.
41
+ * @property {Record<string, unknown>} [chartOptions={}] Engine-specific options.
42
+ * @property {string} [label='Chart'] Accessible canvas and summary label; blank text falls back to `Chart`.
43
+ * @property {string} [description=''] Accessible description.
44
+ * @property {'hidden'|'visible'} [summary='hidden'] Semantic data-table presentation.
45
+ * @property {boolean} [loading=false] Initial loading state.
46
+ * @property {string} [emptyText='No chart data'] Empty-state text.
47
+ * @property {string} [loadingText='Loading chart…'] Loading-state text.
48
+ * @property {string} [errorText='Unable to render chart'] Error fallback text.
49
+ * @property {string} [aspectRatio='16 / 9'] Responsive container aspect ratio.
50
+ * @property {(event: CustomEvent<{instance: unknown}>) => void} [onready] Engine-ready listener.
51
+ * @property {(event: CustomEvent<{data: ChartData, type: string}>) => void} [onupdate] Update listener.
52
+ * @property {(event: CustomEvent<ChartSelectDetail>) => void} [onselect] Point-selection listener.
53
+ * @property {(event: CustomEvent<{error: unknown}>) => void} [onerror] Engine error listener.
54
+ */
55
+
56
+ /**
57
+ * Accessible, engine-neutral chart host with explicit adapter lifecycle.
58
+ * @fires Chart#ready
59
+ * @fires Chart#update
60
+ * @fires Chart#select
61
+ * @fires Chart#error
62
+ * @extends {Component<ChartOptions>}
63
+ */
64
+ export class Chart extends Component {
65
+ static cssName = 'chart';
66
+
67
+ /** @type {Readonly<ChartOptions>} */
68
+ static defaults = {
69
+ adapter: null,
70
+ type: 'bar',
71
+ data: { labels: [], datasets: [] },
72
+ chartOptions: {},
73
+ label: 'Chart',
74
+ description: '',
75
+ summary: 'hidden',
76
+ loading: false,
77
+ emptyText: 'No chart data',
78
+ loadingText: 'Loading chart…',
79
+ errorText: 'Unable to render chart',
80
+ aspectRatio: '16 / 9'
81
+ };
82
+
83
+ /**
84
+ * Creates or enhances a chart host.
85
+ * @param {Element|string|null} target Chart target.
86
+ * @param {ChartOptions} [options={}] Chart options.
87
+ */
88
+ constructor(target = null, options = {}) {
89
+ super(target, options);
90
+ this._mountOrUpdate(false);
91
+ }
92
+
93
+ /** @returns {HTMLElement} */
94
+ render() {
95
+ this._createdRoot = this.el === null;
96
+ const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
97
+ this.el = root;
98
+ this._original = this._createdRoot ? null : {
99
+ attributes: Array.from(root.attributes, (attribute) => [attribute.name, attribute.value]),
100
+ children: Array.from(root.childNodes)
101
+ };
102
+ this._adapter = this.options.adapter;
103
+ this._type = String(this.options.type || 'bar');
104
+ this._data = normalizeChartData(this.options.data);
105
+ this._label = normalizeChartLabel(this.options.label);
106
+ this._chartOptions = cloneChartValue(this.options.chartOptions);
107
+ this._loading = Boolean(this.options.loading);
108
+ this._error = null;
109
+ this._handle = null;
110
+ this._destroyed = false;
111
+
112
+ const descriptionId = uid('zx-chart-description');
113
+ const canvas = /** @type {HTMLCanvasElement} */ (h('canvas', {
114
+ ref: 'canvas',
115
+ class: 'zx-chart__canvas',
116
+ role: 'img',
117
+ ariaLabel: this._label,
118
+ ariaDescribedby: descriptionId
119
+ }));
120
+ const viewport = h('div', {
121
+ ref: 'viewport',
122
+ class: 'zx-chart__viewport'
123
+ }, canvas);
124
+ viewport.style.setProperty('--zx-chart-aspect-ratio', String(this.options.aspectRatio));
125
+ const description = h('p', {
126
+ ref: 'description',
127
+ class: 'zx-chart__description',
128
+ id: descriptionId
129
+ }, this.options.description);
130
+ const status = h('div', {
131
+ ref: 'status',
132
+ class: 'zx-chart__status',
133
+ role: 'status',
134
+ ariaLive: 'polite'
135
+ });
136
+ const summary = h('div', {
137
+ ref: 'summary',
138
+ class: 'zx-chart__summary',
139
+ dataset: { summary: this.options.summary === 'visible' ? 'visible' : 'hidden' }
140
+ });
141
+ root.replaceChildren(viewport, description, status, summary);
142
+ this._renderSummary();
143
+ this._syncState();
144
+
145
+ this._observer = typeof ResizeObserver === 'function' ? new ResizeObserver(() => this.resize()) : null;
146
+ this._observer?.observe(viewport);
147
+ return root;
148
+ }
149
+
150
+ /** Replaces chart data and updates or mounts the engine. @param {ChartData} data @returns {this} */
151
+ setData(data) {
152
+ this._data = normalizeChartData(data);
153
+ this._error = null;
154
+ this._loading = false;
155
+ this._renderSummary();
156
+ this._mountOrUpdate(true);
157
+ return this;
158
+ }
159
+
160
+ /** Changes the chart type and updates/recreates the engine as needed. @param {string} type @returns {this} */
161
+ setType(type) {
162
+ this._type = String(type || 'bar');
163
+ this._mountOrUpdate(true);
164
+ return this;
165
+ }
166
+
167
+ /** Replaces engine-specific options. @param {Record<string, unknown>} options @returns {this} */
168
+ update(options) {
169
+ this._chartOptions = cloneChartValue(options && typeof options === 'object' ? options : {});
170
+ this._mountOrUpdate(true);
171
+ return this;
172
+ }
173
+
174
+ /** Replaces the engine adapter. @param {ChartAdapter|null} adapter @returns {this} */
175
+ setAdapter(adapter) {
176
+ this._destroyHandle();
177
+ this._adapter = adapter;
178
+ this._mountOrUpdate(false);
179
+ return this;
180
+ }
181
+
182
+ /** Toggles loading state. Loading charts do not keep an engine instance alive. @param {boolean} [loading=true] @returns {this} */
183
+ setLoading(loading = true) {
184
+ this._loading = Boolean(loading);
185
+ if (this._loading) this._destroyHandle();
186
+ else this._mountOrUpdate(false);
187
+ this._syncState();
188
+ return this;
189
+ }
190
+
191
+ /** Shows an error and destroys the current engine. @param {unknown} error @returns {this} */
192
+ setError(error) {
193
+ this._error = error ?? new Error(String(this.options.errorText));
194
+ this._loading = false;
195
+ this._destroyHandle();
196
+ this._syncState();
197
+ this.emit('error', { error: this._error });
198
+ return this;
199
+ }
200
+
201
+ /** Clears the error and remounts when data and an adapter are available. @returns {this} */
202
+ clearError() {
203
+ this._error = null;
204
+ this._mountOrUpdate(false);
205
+ return this;
206
+ }
207
+
208
+ /** Requests an engine resize. @returns {this} */
209
+ resize() {
210
+ try { this._handle?.resize?.(); } catch (error) { this.setError(error); }
211
+ return this;
212
+ }
213
+
214
+ /** Re-applies computed theme defaults through the adapter update path. @returns {this} */
215
+ refreshTheme() {
216
+ this._mountOrUpdate(true);
217
+ return this;
218
+ }
219
+
220
+ /** Returns the underlying engine instance when the adapter exposes it. @returns {unknown} */
221
+ getInstance() {
222
+ return this._handle?.instance ?? null;
223
+ }
224
+
225
+ /** Destroys the engine/observer and restores or removes the host. @returns {void} */
226
+ destroy() {
227
+ if (this._destroyed) return;
228
+ this._destroyed = true;
229
+ const original = this._original;
230
+ this._observer?.disconnect();
231
+ this._destroyHandle();
232
+ super.destroy();
233
+ if (!this._createdRoot && original) {
234
+ for (const attribute of Array.from(this.el.attributes)) this.el.removeAttribute(attribute.name);
235
+ for (const [name, value] of original.attributes) this.el.setAttribute(name, value);
236
+ this.el.replaceChildren(...original.children);
237
+ }
238
+ }
239
+
240
+ /** @param {boolean} emitUpdate @returns {void} */
241
+ _mountOrUpdate(emitUpdate) {
242
+ this._syncState();
243
+ if (this._loading || this._error || isEmptyChartData(this._data)) {
244
+ this._destroyHandle();
245
+ return;
246
+ }
247
+ if (!this._adapter || typeof this._adapter.create !== 'function') {
248
+ this._destroyHandle();
249
+ return;
250
+ }
251
+ const spec = this._spec();
252
+ try {
253
+ if (this._handle) this._handle.update(spec);
254
+ else {
255
+ this._handle = this._adapter.create(/** @type {HTMLCanvasElement} */ (this.refs.canvas), spec);
256
+ if (!this._handle || typeof this._handle.destroy !== 'function') {
257
+ throw new TypeError('Chart adapter create() must return a lifecycle handle');
258
+ }
259
+ this.emit('ready', { instance: this.getInstance() });
260
+ }
261
+ if (emitUpdate) this.emit('update', { data: this._data, type: this._type });
262
+ } catch (error) {
263
+ this.setError(error);
264
+ }
265
+ }
266
+
267
+ /** @returns {ChartSpec} */
268
+ _spec() {
269
+ return {
270
+ type: this._type,
271
+ data: cloneChartValue(this._data),
272
+ options: {
273
+ ...themeChartOptions(/** @type {HTMLElement} */ (this.el)),
274
+ ...cloneChartValue(this._chartOptions)
275
+ },
276
+ onSelect: (detail) => this.emit('select', detail)
277
+ };
278
+ }
279
+
280
+ /** @returns {void} */
281
+ _destroyHandle() {
282
+ if (!this._handle) return;
283
+ const handle = this._handle;
284
+ this._handle = null;
285
+ try { handle.destroy(); } catch { /* teardown must remain safe */ }
286
+ }
287
+
288
+ /** @returns {void} */
289
+ _syncState() {
290
+ const empty = isEmptyChartData(this._data);
291
+ const state = this._loading ? 'loading' : this._error ? 'error' : empty ? 'empty' : 'ready';
292
+ this.el.dataset.state = state;
293
+ this.refs.viewport.hidden = state !== 'ready';
294
+ let text = '';
295
+ if (state === 'loading') text = String(this.options.loadingText);
296
+ else if (state === 'error') text = errorMessage(this._error, this.options.errorText);
297
+ else if (state === 'empty') text = String(this.options.emptyText);
298
+ this.refs.status.textContent = text;
299
+ this.refs.status.hidden = !text;
300
+ }
301
+
302
+ /** @returns {void} */
303
+ _renderSummary() {
304
+ this.refs.summary.replaceChildren(chartSummaryTable(this._data, this._label));
305
+ }
306
+ }
307
+
308
+ /** @param {unknown} value @returns {string} */
309
+ function normalizeChartLabel(value) {
310
+ return String(value ?? '').trim() || 'Chart';
311
+ }
312
+
313
+ /**
314
+ * Adapter for an injected Chart.js 4 constructor or namespace.
315
+ */
316
+ export class ChartJsAdapter {
317
+ /**
318
+ * @param {Function|{Chart?: Function}} engine Injected Chart.js constructor or namespace.
319
+ * @param {Record<string, unknown>} [defaults={}] Adapter-wide Chart.js options.
320
+ */
321
+ constructor(engine, defaults = {}) {
322
+ const constructor = typeof engine === 'function' ? engine : engine?.Chart;
323
+ if (typeof constructor !== 'function') throw new TypeError('ChartJsAdapter requires an injected Chart.js constructor');
324
+ this.engine = constructor;
325
+ this.defaults = cloneChartValue(defaults);
326
+ }
327
+
328
+ /**
329
+ * Creates one Chart.js lifecycle handle.
330
+ * @param {HTMLCanvasElement} canvas Canvas.
331
+ * @param {ChartSpec} spec Initial chart specification.
332
+ * @returns {ChartHandle}
333
+ */
334
+ create(canvas, spec) {
335
+ const Engine = this.engine;
336
+ let instance = null;
337
+ let currentType = '';
338
+ let destroyed = false;
339
+
340
+ const build = (next) => {
341
+ let created = null;
342
+ const config = chartJsConfig(next, this.defaults, () => created);
343
+ created = new Engine(canvas, config);
344
+ instance = created;
345
+ currentType = next.type;
346
+ };
347
+ build(spec);
348
+
349
+ return {
350
+ get instance() { return instance; },
351
+ update: (next) => {
352
+ if (destroyed) return;
353
+ if (next.type !== currentType) {
354
+ const previous = instance;
355
+ instance = null;
356
+ previous?.destroy?.();
357
+ build(next);
358
+ return;
359
+ }
360
+ const config = chartJsConfig(next, this.defaults, () => instance);
361
+ instance.data = config.data;
362
+ instance.options = config.options;
363
+ instance.update();
364
+ },
365
+ resize: () => { if (!destroyed) instance?.resize?.(); },
366
+ destroy: () => {
367
+ if (destroyed) return;
368
+ destroyed = true;
369
+ instance?.destroy?.();
370
+ instance = null;
371
+ }
372
+ };
373
+ }
374
+ }
375
+
376
+ /** @param {ChartSpec} spec @param {Record<string, unknown>} defaults @param {() => any} getInstance @returns {Record<string, unknown>} */
377
+ function chartJsConfig(spec, defaults, getInstance) {
378
+ const supplied = cloneChartValue(spec.options);
379
+ const options = {
380
+ responsive: true,
381
+ maintainAspectRatio: false,
382
+ ...cloneChartValue(defaults),
383
+ ...supplied
384
+ };
385
+ const originalClick = options.onClick;
386
+ options.onClick = (event, elements, chart) => {
387
+ if (typeof originalClick === 'function') originalClick(event, elements, chart);
388
+ const instance = getInstance() ?? chart;
389
+ const points = Array.isArray(elements) && elements.length ? elements
390
+ : instance?.getElementsAtEventForMode?.(event, 'nearest', { intersect: true }, true) ?? [];
391
+ const point = points[0];
392
+ if (!point) return;
393
+ const dataset = spec.data.datasets?.[point.datasetIndex] ?? null;
394
+ spec.onSelect({
395
+ datasetIndex: point.datasetIndex,
396
+ index: point.index,
397
+ dataset,
398
+ label: spec.data.labels?.[point.index],
399
+ value: dataset?.data?.[point.index],
400
+ nativeEvent: event?.native ?? event ?? null
401
+ });
402
+ };
403
+ return { type: spec.type, data: cloneChartValue(spec.data), options };
404
+ }
405
+
406
+ /**
407
+ * Clones arrays and plain objects while preserving callbacks and engine-specific object instances.
408
+ * @template T
409
+ * @param {T} value Value.
410
+ * @returns {T}
411
+ */
412
+ export function cloneChartValue(value) {
413
+ return cloneValue(value, new WeakMap());
414
+ }
415
+
416
+ /** @template T @param {T} value @param {WeakMap<object, object>} seen @returns {T} */
417
+ function cloneValue(value, seen) {
418
+ if (Array.isArray(value)) {
419
+ if (seen.has(value)) return /** @type {T} */ (seen.get(value));
420
+ const copy = [];
421
+ seen.set(value, copy);
422
+ copy.push(...value.map((item) => cloneValue(item, seen)));
423
+ return /** @type {T} */ (copy);
424
+ }
425
+ if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
426
+ if (seen.has(value)) return /** @type {T} */ (seen.get(value));
427
+ const copy = {};
428
+ seen.set(value, copy);
429
+ for (const [key, item] of Object.entries(value)) copy[key] = cloneValue(item, seen);
430
+ return /** @type {T} */ (copy);
431
+ }
432
+ return value;
433
+ }
434
+
435
+ /** @param {unknown} data @returns {ChartData} */
436
+ function normalizeChartData(data) {
437
+ if (!data || typeof data !== 'object') return { labels: [], datasets: [] };
438
+ return cloneChartValue({
439
+ ...data,
440
+ labels: Array.isArray(data.labels) ? data.labels : [],
441
+ datasets: Array.isArray(data.datasets) ? data.datasets : []
442
+ });
443
+ }
444
+
445
+ /** @param {ChartData} data @returns {boolean} */
446
+ export function isEmptyChartData(data) {
447
+ return !Array.isArray(data?.datasets) || !data.datasets.some((dataset) => Array.isArray(dataset?.data) && dataset.data.length > 0);
448
+ }
449
+
450
+ /**
451
+ * Builds the semantic table mirrored alongside the canvas.
452
+ * @param {ChartData} data Chart data.
453
+ * @param {string} label Caption.
454
+ * @returns {HTMLTableElement}
455
+ */
456
+ export function chartSummaryTable(data, label = 'Chart data') {
457
+ const summary = chartSummaryModel(data);
458
+ const table = /** @type {HTMLTableElement} */ (h('table', { class: 'zx-chart__table' },
459
+ h('caption', {}, label),
460
+ h('thead', {}, h('tr', {}, h('th', { scope: 'col' }, 'Label'),
461
+ summary.headers.map((header) => h('th', { scope: 'col' }, header)))),
462
+ h('tbody', {}, summary.rows.map((row) => h('tr', {},
463
+ h('th', { scope: 'row' }, row.label),
464
+ row.values.map((value) => h('td', {}, value)))))));
465
+ return table;
466
+ }
467
+
468
+ /**
469
+ * Produces the exact text matrix rendered by the semantic chart summary.
470
+ * @param {ChartData} data Chart data.
471
+ * @returns {{headers: string[], rows: Array<{label: string, values: string[]}>}}
472
+ */
473
+ export function chartSummaryModel(data) {
474
+ const datasets = Array.isArray(data?.datasets) ? data.datasets : [];
475
+ const labels = Array.isArray(data?.labels) ? data.labels : [];
476
+ const length = Math.max(labels.length,
477
+ ...datasets.map((dataset) => Array.isArray(dataset.data) ? dataset.data.length : 0), 0);
478
+ return {
479
+ headers: datasets.map((dataset, index) => String(dataset.label ?? `Series ${index + 1}`)),
480
+ rows: Array.from({ length }, (_, index) => ({
481
+ label: String(labels[index] ?? index + 1),
482
+ values: datasets.map((dataset) => summaryValue(dataset.data?.[index]))
483
+ }))
484
+ };
485
+ }
486
+
487
+ /** @param {unknown} value @returns {string} */
488
+ function summaryValue(value) {
489
+ if (value == null) return '';
490
+ if (typeof value === 'object') {
491
+ if ('y' in value) return String(value.y ?? '');
492
+ if ('r' in value) return String(value.r ?? '');
493
+ try { return JSON.stringify(value); } catch { return String(value); }
494
+ }
495
+ return String(value);
496
+ }
497
+
498
+ /** @param {HTMLElement} root @returns {Record<string, unknown>} */
499
+ function themeChartOptions(root) {
500
+ if (typeof getComputedStyle !== 'function') return {};
501
+ const style = getComputedStyle(root);
502
+ const color = style.getPropertyValue('--zx-color-text').trim();
503
+ const borderColor = style.getPropertyValue('--zx-color-border').trim();
504
+ return {
505
+ color: color || undefined,
506
+ borderColor: borderColor || undefined
507
+ };
508
+ }
509
+
510
+ /** @param {unknown} error @param {unknown} fallback @returns {string} */
511
+ function errorMessage(error, fallback) {
512
+ return error && typeof error === 'object' && 'message' in error ? String(error.message) : String(fallback);
513
+ }
514
+
515
+ /** Fired after the adapter creates an engine instance. @event Chart#ready @type {CustomEvent<{instance: unknown}>} */
516
+ /** Fired after an existing chart updates. @event Chart#update @type {CustomEvent<{data: ChartData, type: string}>} */
517
+ /** Fired when the engine reports a selected point. @event Chart#select @type {CustomEvent<ChartSelectDetail>} */
518
+ /** Fired when adapter creation/update fails. @event Chart#error @type {CustomEvent<{error: unknown}>} */
@@ -3,6 +3,13 @@
3
3
  .zx-date-range-picker {
4
4
  display: block;
5
5
  container-type: inline-size;
6
+ /* A size container takes no width from its own contents, so in a shrink-to-fit host — a flex
7
+ row, an inline-block, a grid `auto` track — it would collapse to nothing and spill. Only the
8
+ host can size it, so it always fills the host. That covers every host with a width of its
9
+ own; a host that shrink-wraps *its* contents still collapses, because 100% then resolves
10
+ against a containing block this element's own containment zeroed. Dropping containment is the
11
+ only fix for that — see multi-value-editor.css. */
12
+ inline-size: 100%;
6
13
  color: var(--zx-color-text);
7
14
  font-size: var(--zx-control-font-size);
8
15
  }
@@ -22,6 +22,15 @@ export class MonthPicker extends DatePicker {
22
22
  /** @type {MonthPickerOptions} */
23
23
  static defaults = { value: null, min: null, max: null };
24
24
 
25
+ /**
26
+ * Creates a month picker with its narrower month-based option contract.
27
+ * @param {Element|string|null} target Existing element, selector, or null.
28
+ * @param {MonthPickerOptions} [options={}] Month-picker options.
29
+ */
30
+ constructor(target, options = {}) {
31
+ super(target, options);
32
+ }
33
+
25
34
  /** @returns {HTMLElement} */
26
35
  render() {
27
36
  const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
@@ -95,7 +95,7 @@ export class Dialog extends Modal {
95
95
  */
96
96
  constructor(_target = null, options = {}) {
97
97
  super(null, options);
98
- this.#setSize(this.options.size);
98
+ this._applySize(this.options.size);
99
99
  this.#baseTitle = String(this.options.title ?? '');
100
100
  this.#applyTitle(this.#baseTitle);
101
101
  this.#baseButtons = copyButtons(this.options.buttons);
@@ -141,7 +141,7 @@ export class Dialog extends Modal {
141
141
  h('footer', { class: 'zx-dialog__footer', ref: 'footer' })
142
142
  ));
143
143
  dialog.dataset.state = 'closed';
144
- document.body.append(dialog);
144
+ this.mountTarget().append(dialog);
145
145
  return dialog;
146
146
  }
147
147
 
@@ -368,8 +368,15 @@ export class Dialog extends Modal {
368
368
  });
369
369
  }
370
370
 
371
- /** @param {'sm'|'md'|'lg'|number} size @returns {void} */
372
- #setSize(size) {
371
+ /**
372
+ * Applies the dialog width. Subclasses that size themselves on a different axis override this.
373
+ *
374
+ * Called from the constructor before any subclass field initializers have run, so an override
375
+ * may only touch the DOM — reading an uninitialized private field here throws.
376
+ * @param {'sm'|'md'|'lg'|number} size Preset or pixel width.
377
+ * @returns {void}
378
+ */
379
+ _applySize(size) {
373
380
  const presets = { sm: 400, md: 600, lg: 840 };
374
381
  const width = typeof size === 'number' && Number.isFinite(size) ? size : (presets[size] ?? presets.md);
375
382
  this.el.style.inlineSize = `${Math.max(0, width)}px`;