@taylorwong/ichartjs 2.0.13 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/errors.mjs ADDED
@@ -0,0 +1,31 @@
1
+ /** Structured public runtime errors for failed chart mutations. */
2
+ export class ChartValidationError extends Error {
3
+ constructor(operation, diagnostics = [], message = null) {
4
+ const details = Array.isArray(diagnostics) ? diagnostics : [];
5
+ const first = details[0] || {};
6
+ super(message || details.map(item => item.message).filter(Boolean).join(' ') || `Invalid ${operation} mutation.`);
7
+ this.name = 'ChartValidationError';
8
+ this.code = 'CHART_VALIDATION_FAILED';
9
+ this.operation = operation;
10
+ this.details = details;
11
+ this.errors = details;
12
+ this.path = first.path;
13
+ this.expected = first.expected;
14
+ this.received = first.received;
15
+ this.suggestion = first.suggestion;
16
+ }
17
+
18
+ toJSON() {
19
+ return {
20
+ name: this.name,
21
+ code: this.code,
22
+ operation: this.operation,
23
+ message: this.message,
24
+ details: this.details,
25
+ ...(this.path === undefined ? {} : { path: this.path }),
26
+ ...(this.expected === undefined ? {} : { expected: this.expected }),
27
+ ...(this.received === undefined ? {} : { received: this.received }),
28
+ ...(this.suggestion === undefined ? {} : { suggestion: this.suggestion })
29
+ };
30
+ }
31
+ }
package/src/index.mjs CHANGED
@@ -22,6 +22,9 @@ import { normalizeLinkedFilters, normalizeLinkedSelection, filterProjectRows, cr
22
22
  import { explainChart, getCapabilities as discoverCapabilities, getChartCapability, getPreferenceCapabilities, planChart } from './capabilities.mjs';
23
23
  import { applyPreferencesToSpec, createPreferencesStore, defaultPreferences, mergePreferences, mergeThemePreference, normalizePreferences, validatePreferences } from './preferences.mjs';
24
24
  import { mountChartSettings } from './preferences-ui.mjs';
25
+ import { ChartValidationError } from './errors.mjs';
26
+ import { diagramOperations } from './contract-registry.mjs';
27
+ import { destroyChart } from './chart-lifecycle.mjs';
25
28
 
26
29
  import { isDiagram, paintSelection, diagramPointer, diagramKeyboard } from './diagram-interaction.mjs';
27
30
 
@@ -189,7 +192,7 @@ export class Chart {
189
192
  delete specInput.preferencesStore;
190
193
  delete specInput.chartId;
191
194
  const result = validateSpec(specInput);
192
- if (!result.valid) { const error = new Error(result.errors.map(item => item.message).join(' ')); error.details = result.errors; throw error; }
195
+ if (!result.valid) throw new ChartValidationError('create', result.errors);
193
196
  this._specDiagnostics = { warnings: result.warnings, normalizations: result.normalizations };
194
197
  this.chartId = input.chartId ?? input.id ?? null;
195
198
  this._preferencesStore = isPreferencesStore(input.preferencesStore) ? input.preferencesStore : isPreferencesStore(input.preferences) ? input.preferences : null;
@@ -209,6 +212,7 @@ export class Chart {
209
212
  this._history = new EditHistory();
210
213
  this._revision = 0;
211
214
  this._lastChangeSet = null;
215
+ this._destroyed = false;
212
216
  this._editor = new EditController(this);
213
217
  this.plugins = new PluginHost(this, this.spec.plugins);
214
218
  this._mountRenderer();
@@ -332,16 +336,114 @@ export class Chart {
332
336
  _updateCrosshair(x, y) { const vertical = this.model.scene.find('crosshair-x'), horizontal = this.model.scene.find('crosshair-y'); if (!vertical || !horizontal) return; vertical.geometry.x1 = vertical.geometry.x2 = x; vertical.style.opacity = 0.7; horizontal.geometry.y1 = horizontal.geometry.y2 = y; horizontal.style.opacity = 0.7; this.renderer.render(this.model.scene); }
333
337
  _showTooltip(payload) { if (typeof document === 'undefined' || !this.container || !payload.datum) return; let tip = this._tooltip; if (!tip) { tip = this._tooltip = document.createElement('div'); tip.className = 'ichart-v2-tooltip'; Object.assign(tip.style, { position: 'fixed', pointerEvents: 'none', zIndex: 9999, padding: '8px 10px', borderRadius: '6px', maxWidth: 'min(320px, calc(100vw - 24px))', whiteSpace: 'pre-line' }); document.body.appendChild(tip); } Object.assign(tip.style, { background: this.spec.theme.surface, color: this.spec.theme.text, border: `1px solid ${this.spec.theme.border}`, font: this.spec.theme.typography.tooltip.font, boxShadow: `0 4px 12px ${this.spec.theme.border}88` }); const text = ['gantt', 'timeline', 'milestone', 'burndown', 'flow', 'swimlane', 'architecture', 'mindmap'].includes(this.spec.type) ? projectTooltip(this.spec.type, payload.datum) : Object.entries(payload.datum).map(([key, value]) => `${key}: ${value}`).join(' · '); tip.textContent = text; const left = Math.min((payload.nativeEvent.clientX || 0) + 12, window.innerWidth - tip.offsetWidth - 12); const top = Math.min((payload.nativeEvent.clientY || 0) + 12, window.innerHeight - tip.offsetHeight - 12); tip.style.left = `${Math.max(12, left)}px`; tip.style.top = `${Math.max(12, top)}px`; }
334
338
  _hideTooltip() { if (this._tooltip) this._tooltip.style.left = '-10000px'; }
339
+ _validateMutation(operation, input) {
340
+ const result = validateSpec(input);
341
+ if (!result.valid) throw new ChartValidationError(operation, result.errors);
342
+ return result;
343
+ }
344
+ _snapshotMutation() {
345
+ return {
346
+ spec: this.spec,
347
+ model: this.model,
348
+ themeInput: clone(this._themeInput),
349
+ styleOverrides: clone(this._styleOverrides),
350
+ preferenceBase: clone(this._preferenceBase),
351
+ localPreferences: clone(this._localPreferences),
352
+ preferencesSnapshot: this._preferencesSnapshot,
353
+ specDiagnostics: clone(this._specDiagnostics)
354
+ };
355
+ }
356
+ _restoreMutation(snapshot) {
357
+ this.spec = snapshot.spec;
358
+ this.model = snapshot.model;
359
+ this._themeInput = snapshot.themeInput;
360
+ this._styleOverrides = snapshot.styleOverrides;
361
+ this._preferenceBase = snapshot.preferenceBase;
362
+ this._localPreferences = snapshot.localPreferences;
363
+ this._preferencesSnapshot = snapshot.preferencesSnapshot;
364
+ this._specDiagnostics = snapshot.specDiagnostics;
365
+ if (this.renderer) this.renderer.options = this.spec;
366
+ try { if (this.renderer?.container && this.model?.scene) this.renderer.render(this.model.scene); } catch {}
367
+ }
368
+ _commitMutation() {
369
+ const previousRevision = this._revision;
370
+ this._revision += 1;
371
+ try {
372
+ this.render();
373
+ this._history.clear();
374
+ this._lastChangeSet = null;
375
+ this._editor.pending.clear();
376
+ return this;
377
+ } catch (error) {
378
+ this._revision = previousRevision;
379
+ throw error;
380
+ }
381
+ }
335
382
  on(type, listener) { if (!this.listeners.has(type)) this.listeners.set(type, new Set()); this.listeners.get(type).add(listener); return this; }
336
383
  off(type, listener) { this.listeners.get(type)?.delete(listener); return this; }
337
384
  emit(type, event) { this.listeners.get(type)?.forEach(listener => listener(event)); }
338
- update(next = {}) { this._editor.invalidate(); const { preferences, preferencesStore, chartId, ...specUpdate } = next || {}; if (preferences !== undefined) this.setPreferences(preferences); if (specUpdate.theme !== undefined) this._themeInput = specUpdate.theme; ['colors', 'background', 'padding', 'legend', 'labels', 'grid', 'branding'].forEach(key => { if (specUpdate[key] !== undefined) this._styleOverrides[key] = true; }); this.spec = normalizeSpec({ ...this.spec, ...specUpdate, theme: this._themeInput, data: specUpdate.data === undefined ? this.spec.data : specUpdate.data }); ['legend', 'labels', 'grid', 'branding', 'padding'].forEach(key => { if (specUpdate[key] !== undefined) this._preferenceBase[key] = clone(this.spec[key]); }); this._resolveStyle(); return this.render(); }
339
- setData(data) { this._editor.invalidate(); this.spec = normalizeSpec({ ...this.spec, theme: this._themeInput, data: { values: data } }); this._resolveStyle(); return this.render(); }
340
- setTheme(theme = 'auto') { this._themeInput = theme; this._resolveStyle(); this.render(); this.emit('themechange', { chart: this, theme: this.getTheme(), source: 'user' }); return this; }
385
+ update(next = {}) {
386
+ const { preferences, preferencesStore, chartId, preferenceScope, ...specUpdate } = next || {};
387
+ if (preferences !== undefined) {
388
+ const checked = validatePreferences(preferences, { partial: true });
389
+ if (!checked.valid) throw new ChartValidationError('update', checked.errors);
390
+ }
391
+ const themeInput = specUpdate.theme === undefined ? this._themeInput : specUpdate.theme;
392
+ const result = this._validateMutation('update', { ...this.spec, ...specUpdate, theme: themeInput, data: specUpdate.data === undefined ? this.spec.data : specUpdate.data });
393
+ const snapshot = this._snapshotMutation();
394
+ try {
395
+ this._themeInput = themeInput;
396
+ ['colors', 'background', 'padding', 'legend', 'labels', 'grid', 'branding'].forEach(key => { if (specUpdate[key] !== undefined) this._styleOverrides[key] = true; });
397
+ this.spec = result.spec;
398
+ ['legend', 'labels', 'grid', 'branding', 'padding'].forEach(key => { if (specUpdate[key] !== undefined) this._preferenceBase[key] = clone(this.spec[key]); });
399
+ this._specDiagnostics = { warnings: result.warnings, normalizations: result.normalizations };
400
+ if (preferences !== undefined) {
401
+ if (this._preferencesStore) {
402
+ const scope = preferenceScope === 'global' ? 'global' : 'chart';
403
+ if (scope === 'global') this._preferencesStore.setGlobal(preferences, { source: 'update' });
404
+ else this._preferencesStore.setChart(this.chartId || 'default', preferences, { source: 'update' });
405
+ } else this._localPreferences = mergePreferences(this._localPreferences, preferences);
406
+ }
407
+ this._resolveStyle();
408
+ return this._commitMutation();
409
+ } catch (error) {
410
+ this._restoreMutation(snapshot);
411
+ throw error;
412
+ }
413
+ }
414
+ setData(data) {
415
+ const result = this._validateMutation('setData', { ...this.spec, theme: this._themeInput, data: { values: data } });
416
+ const snapshot = this._snapshotMutation();
417
+ try {
418
+ this.spec = result.spec;
419
+ this._specDiagnostics = { warnings: result.warnings, normalizations: result.normalizations };
420
+ this._resolveStyle();
421
+ return this._commitMutation();
422
+ } catch (error) {
423
+ this._restoreMutation(snapshot);
424
+ throw error;
425
+ }
426
+ }
427
+ setTheme(theme = 'auto') {
428
+ const result = this._validateMutation('setTheme', { ...this.spec, theme });
429
+ const snapshot = this._snapshotMutation();
430
+ try {
431
+ this._themeInput = theme;
432
+ this.spec = result.spec;
433
+ this._specDiagnostics = { warnings: result.warnings, normalizations: result.normalizations };
434
+ this._resolveStyle();
435
+ this._commitMutation();
436
+ this.emit('themechange', { chart: this, theme: this.getTheme(), source: 'user' });
437
+ return this;
438
+ } catch (error) {
439
+ this._restoreMutation(snapshot);
440
+ throw error;
441
+ }
442
+ }
341
443
  getTheme() { return clone(this.spec.theme); }
342
444
  getPreferences() { return this._preferencesStore ? this._preferencesStore.getEffective(this.chartId) : clone(this._localPreferences || defaultPreferences); }
343
- setPreferences(patch = {}, options = {}) { if (this._preferencesStore) { const scope = options.scope === 'global' ? 'global' : 'chart'; if (scope === 'global') this._preferencesStore.setGlobal(patch, { ...options, source: options.source || 'user' }); else this._preferencesStore.setChart(this.chartId || 'default', patch, { ...options, source: options.source || 'user' }); return this; } this._localPreferences = mergePreferences(this._localPreferences, patch); this._preferencesSnapshot = JSON.stringify(this._localPreferences); this._resolveStyle(); this.render(); this.emit('preferenceschange', { chart: this, preferences: this.getPreferences(), source: options.source || 'user', scope: 'chart', persisted: false }); return this; }
344
- resetPreferences(options = {}) { if (this._preferencesStore) { this._preferencesStore.reset({ ...options, scope: options.scope || 'chart', chartId: this.chartId || 'default' }); return this; } this._localPreferences = normalizePreferences({}); this._preferencesSnapshot = JSON.stringify(this._localPreferences); this._resolveStyle(); this.render(); this.emit('preferenceschange', { chart: this, preferences: this.getPreferences(), source: options.source || 'user', scope: 'chart', persisted: false }); return this; }
445
+ setPreferences(patch = {}, options = {}) { const checked = validatePreferences(patch, { partial: true }); if (!checked.valid) throw new ChartValidationError('setPreferences', checked.errors); if (this._preferencesStore) { const scope = options.scope === 'global' ? 'global' : 'chart'; if (scope === 'global') this._preferencesStore.setGlobal(checked.value, { ...options, source: options.source || 'user' }); else this._preferencesStore.setChart(this.chartId || 'default', checked.value, { ...options, source: options.source || 'user' }); return this; } const snapshot = this._snapshotMutation(); try { this._localPreferences = mergePreferences(this._localPreferences, checked.value); this._preferencesSnapshot = JSON.stringify(this._localPreferences); this._resolveStyle(); this._commitMutation(); this.emit('preferenceschange', { chart: this, preferences: this.getPreferences(), source: options.source || 'user', scope: 'chart', persisted: false }); return this; } catch (error) { this._restoreMutation(snapshot); throw error; } }
446
+ resetPreferences(options = {}) { if (this._preferencesStore) { this._preferencesStore.reset({ ...options, scope: options.scope || 'chart', chartId: this.chartId || 'default' }); return this; } const snapshot = this._snapshotMutation(); try { this._localPreferences = normalizePreferences({}); this._preferencesSnapshot = JSON.stringify(this._localPreferences); this._resolveStyle(); this._commitMutation(); this.emit('preferenceschange', { chart: this, preferences: this.getPreferences(), source: options.source || 'user', scope: 'chart', persisted: false }); return this; } catch (error) { this._restoreMutation(snapshot); throw error; } }
345
447
  resize(width = this.spec.width, height = this.spec.height) { this.spec.width = width; this.spec.height = height; this.render(); this.emit('resize', { chart: this, width, height }); return this; }
346
448
  getSpec() { return JSON.parse(JSON.stringify(this.spec)); }
347
449
  _getDiagnostics() { return [...new Map([...(this._specDiagnostics?.warnings || []), ...(this.model.data?.warnings || []), ...(this.spec.theme?.warnings || [])].map(item => [`${item.code}:${item.path || ''}`, item])).values()]; }
@@ -447,7 +549,36 @@ export class Chart {
447
549
  getElementAt(x, y) { return this.model.scene.hit(x, y); }
448
550
  selectBox(start, end) { const left = Math.min(start.x, end.x), right = Math.max(start.x, end.x), top = Math.min(start.y, end.y), bottom = Math.max(start.y, end.y); this._selected.clear(); this.model.scene.walk(node => { node.selected = false; if (!node.dataRef || !node.bounds || !node.interactive || (isDiagram(this) && !node.id.startsWith('node-'))) return; const intersects = node.bounds.x <= right && node.bounds.x + node.bounds.width >= left && node.bounds.y <= bottom && node.bounds.y + node.bounds.height >= top; if (intersects) { node.selected = true; this._selected.set(node.id, node.dataRef); } }); this.emit('selectionchange', { chart: this, selectedData: this.getSelectedData(), box: { start, end } }); this.render(); return this; }
449
551
  toDataTable() { return this.model.data.rows.map(row => ({ ...row })); }
450
- applyPatch(patches = []) { patches.forEach(patch => { const path = patch.path.replace(/^\//, '').split('/'); let target = this.spec; path.slice(0, -1).forEach(key => { target = target[key]; }); if (patch.op === 'remove') delete target[path.at(-1)]; else target[path.at(-1)] = patch.value; }); return this.render(); }
552
+ applyPatch(patches = []) {
553
+ const candidate = clone(this.spec);
554
+ try {
555
+ patches.forEach((patch, index) => {
556
+ if (!patch || !['add', 'replace', 'remove'].includes(patch.op) || typeof patch.path !== 'string' || !patch.path.startsWith('/')) throw new ChartValidationError('applyPatch', [{ code: 'INVALID_PATCH', path: `patches[${index}]`, message: 'Patch requires add, replace, or remove and an absolute JSON pointer path.', suggestion: 'Use for example { op: "replace", path: "/title/text", value: "Revenue" }.' }]);
557
+ const path = patch.path.replace(/^\//, '').split('/').map(key => key.replace(/~1/g, '/').replace(/~0/g, '~'));
558
+ let target = candidate;
559
+ path.slice(0, -1).forEach(key => { if (target == null || typeof target !== 'object' || !(key in target)) throw new ChartValidationError('applyPatch', [{ code: 'INVALID_PATCH_PATH', path: `patches[${index}].path`, message: `Patch path does not exist: ${patch.path}`, suggestion: 'Patch an existing Spec path or add a direct child of an existing object.' }]); target = target[key]; });
560
+ const key = path.at(-1);
561
+ if (patch.op === 'remove') delete target[key];
562
+ else target[key] = clone(patch.value);
563
+ });
564
+ } catch (error) {
565
+ if (error instanceof ChartValidationError) throw error;
566
+ throw new ChartValidationError('applyPatch', [{ code: 'INVALID_PATCH', path: 'patches', message: error.message, suggestion: 'Use JSON-safe patch values and valid Spec paths.' }]);
567
+ }
568
+ const themePatched = patches.some(patch => typeof patch?.path === 'string' && (patch.path === '/theme' || patch.path.startsWith('/theme/')));
569
+ const result = this._validateMutation('applyPatch', { ...candidate, theme: themePatched ? candidate.theme : this._themeInput });
570
+ const snapshot = this._snapshotMutation();
571
+ try {
572
+ this.spec = result.spec;
573
+ if (themePatched) this._themeInput = candidate.theme;
574
+ this._specDiagnostics = { warnings: result.warnings, normalizations: result.normalizations };
575
+ this._resolveStyle();
576
+ return this._commitMutation();
577
+ } catch (error) {
578
+ this._restoreMutation(snapshot);
579
+ throw error;
580
+ }
581
+ }
451
582
  resetZoom() { delete this.spec.view; return this.render(); }
452
583
  zoomTo(view) { this.spec.view = { ...view }; this.emit('zoomchange', { chart: this, view: this.spec.view }); return this.render(); }
453
584
  panBy(delta) { const current = this.spec.view || {}; this.spec.view = { ...current, offsetX: (current.offsetX || 0) + (delta.x || 0), offsetY: (current.offsetY || 0) + (delta.y || 0) }; this.emit('zoomchange', { chart: this, view: this.spec.view }); return this.render(); }
@@ -626,11 +757,12 @@ export class Chart {
626
757
  return { valid: false, code: 'DOWNLOAD_FAILED', message: String(err && err.message || err), suggestion: 'Try chart.export() and write the result manually.' };
627
758
  }
628
759
  }
629
- destroy() { this._preferencesUnsubscribe?.(); if (this._resizeObserver) this._resizeObserver.disconnect(); this._colorSchemeQuery?.removeEventListener?.('change', this._colorSchemeHandler); if (this._eventsBound) { const { target, handler, start, move, end, leave, touchStart, touchMove, touchEnd, wheel } = this._eventsBound; target.removeEventListener('mousemove', handler); target.removeEventListener('click', handler); target.removeEventListener('pointerdown', start); target.removeEventListener('pointermove', move); target.removeEventListener('pointerup', end); target.removeEventListener('pointercancel', end); target.removeEventListener('pointerleave', leave); target.removeEventListener('touchstart', touchStart); target.removeEventListener('touchmove', touchMove); target.removeEventListener('touchend', touchEnd); target.removeEventListener('wheel', wheel); } const target = this.renderer.svg || this.renderer.canvas; if (this._keyboardHandler) target?.removeEventListener('keydown', this._keyboardHandler); this._tooltip?.remove(); this.plugins.destroy(); this.renderer.destroy(); this.listeners.clear(); this._eventsBound = null; this._selected.clear(); this._clipboard = { nodes: [], edges: [] }; }
760
+ destroy() { destroyChart(this); }
630
761
  }
631
762
 
632
763
  export function createChart(spec) { return new Chart(spec); }
633
- export function getCapabilities() { return { ...discoverCapabilities(), interactionDefaults: { zoom: false, pan: false, brush: false, drag: false, edgeDrag: false, portConnect: false, editing: false }, diagram: { layoutModes: diagramLayoutModes, routingModes: edgeRoutingModes, entities: ['node', 'edge', 'lane', 'group', 'port', 'waypoint'], operations: ['moveNode', 'moveNodes', 'resizeNode', 'alignNodes', 'snapNodes', 'moveNodeToLane', 'moveGroup', 'resizeGroup', 'assignNodesToGroup', 'duplicateGroup', 'deleteGroup', 'updateEdge', 'removeEdge', 'addEdge', 'toggleGroupCollapse', 'duplicateSelection', 'pasteSelection'], curvedEdges: { renderers: ['canvas', 'svg'], type: 'cubic-bezier', tension: { minimum: 0.2, maximum: 0.8, default: 0.4 }, mindmapDefault: true, obstacleFallback: 'orthogonal', controlPointEditing: false }, edgeEditing: { renderers: ['canvas', 'svg'], selection: true, waypointDrag: true, segmentDrag: true, persistentWaypoints: true, enabledByDefault: false, activation: ['editing.enabled', 'interaction.edgeDrag'] }, validation: ['duplicate-ids', 'missing-endpoints', 'missing-ports', 'missing-lanes', 'invalid-waypoints', 'invalid-curve-tension'] }, editing: { modes: ['preview', 'commit', 'undo', 'redo'], operations: ['updateField', 'updateRecord', 'updateTask', 'shiftTask', 'updateProgress', 'addDependency', 'removeDependency', 'updateMilestone', 'moveNode', 'moveNodes', 'moveNodeToLane', 'resizeNode', 'alignNodes', 'snapNodes', 'moveGroup', 'resizeGroup', 'assignNodesToGroup', 'duplicateGroup', 'deleteGroup', 'updateEdge', 'removeEdge', 'addEdge', 'toggleGroupCollapse', 'duplicateSelection', 'pasteSelection'], confirmationRequiredByDefault: true, commit: 'local-runtime-host-persistence-required' } }; }
764
+ export { ChartValidationError } from './errors.mjs';
765
+ export function getCapabilities() { const capabilities = discoverCapabilities(); return { ...capabilities, interactionDefaults: { ...capabilities.interactionDefaults }, diagram: { layoutModes: diagramLayoutModes, routingModes: edgeRoutingModes, entities: ['node', 'edge', 'lane', 'group', 'port', 'waypoint'], operations: [...diagramOperations], curvedEdges: { renderers: ['canvas', 'svg'], type: 'cubic-bezier', tension: { minimum: 0.2, maximum: 0.8, default: 0.4 }, mindmapDefault: true, obstacleFallback: 'orthogonal', controlPointEditing: false }, edgeEditing: { renderers: ['canvas', 'svg'], selection: true, waypointDrag: true, segmentDrag: true, persistentWaypoints: true, enabledByDefault: false, activation: ['editing.enabled', 'interaction.edgeDrag'] }, validation: ['duplicate-ids', 'missing-endpoints', 'missing-ports', 'missing-lanes', 'invalid-waypoints', 'invalid-curve-tension'] }, editing: { modes: ['preview', 'commit', 'undo', 'redo'], operations: [...capabilities.commands], confirmationRequiredByDefault: true, commit: 'local-runtime-host-persistence-required' } }; }
634
766
  export function recommend(input, options = {}) { const plan = planChart(input, options); return { primary: plan.primary, alternatives: plan.alternatives, reason: plan.reasons.join(' '), reasons: plan.reasons, confidence: plan.confidence, requiredFields: plan.requiredFields, assumptions: plan.assumptions, warnings: plan.warnings, nextActions: plan.nextActions }; }
635
767
  export { normalizeSpec, validateSpec, normalizeData, inspectData, binData, applyTransforms, data, getBusinessSchema, inspectDataSchema, validateData, getEditCapabilities, validateEdit, previewEdit, commitPreview, validateRecipe, getChartCapability, getPreferenceCapabilities, planChart, explainChart };
636
768
  export { diagramLayoutModes, diagramModes, edgeRoutingModes, normalizeDiagramSpec, validateDiagram, layoutDiagram, routeEdge };
@@ -639,4 +771,4 @@ export { normalizeLinkedFilters, normalizeLinkedSelection, filterProjectRows, cr
639
771
 
640
772
  export { contrastRatio, planStyle, resolveTheme, styleCapabilities, themeModes, themePalettes, themePresets, validateThemeContrast, annotationPlugin, dataZoomPlugin, dataLabelsPlugin, accessibilityPlugin };
641
773
  export { applyPreferencesToSpec, createPreferencesStore, defaultPreferences, mergePreferences, mergeThemePreference, mountChartSettings, normalizePreferences, validatePreferences };
642
- export const iChart = { version: '2.0.13', createChart, inspectData, normalizeData, binData, applyTransforms, data, getCapabilities, getChartCapability, getPreferenceCapabilities, planChart, recommend, explainChart, contrastRatio, planStyle, resolveTheme, styleCapabilities, themeModes, themePalettes, themePresets, validateThemeContrast, createPreferencesStore, defaultPreferences, normalizePreferences, mergePreferences, validatePreferences, applyPreferencesToSpec, mountChartSettings, annotationPlugin, dataZoomPlugin, dataLabelsPlugin, accessibilityPlugin, getBusinessSchema, inspectDataSchema, validateData, getEditCapabilities, validateEdit, previewEdit, commitPreview, validateRecipe, normalizeProjectCalendar, applyWorkingCalendar, normalizeDependencies, analyzeSchedule, analyzeBurndownSeries, analyzeCapacity, buildCapacityView, buildCumulativeFlowSeries, buildVelocitySeries, buildReleaseForecast, buildRiskMatrixSeries, buildIssueAgingSeries, normalizeLinkedFilters, normalizeLinkedSelection, filterProjectRows, createLinkedProjectState, linkedRecordId };
774
+ export const iChart = { version: '2.0.14', createChart, ChartValidationError, inspectData, normalizeData, binData, applyTransforms, data, getCapabilities, getChartCapability, getPreferenceCapabilities, planChart, recommend, explainChart, contrastRatio, planStyle, resolveTheme, styleCapabilities, themeModes, themePalettes, themePresets, validateThemeContrast, createPreferencesStore, defaultPreferences, normalizePreferences, mergePreferences, validatePreferences, applyPreferencesToSpec, mountChartSettings, annotationPlugin, dataZoomPlugin, dataLabelsPlugin, accessibilityPlugin, getBusinessSchema, inspectDataSchema, validateData, getEditCapabilities, validateEdit, previewEdit, commitPreview, validateRecipe, normalizeProjectCalendar, applyWorkingCalendar, normalizeDependencies, analyzeSchedule, analyzeBurndownSeries, analyzeCapacity, buildCapacityView, buildCumulativeFlowSeries, buildVelocitySeries, buildReleaseForecast, buildRiskMatrixSeries, buildIssueAgingSeries, normalizeLinkedFilters, normalizeLinkedSelection, filterProjectRows, createLinkedProjectState, linkedRecordId };
package/src/schema.mjs CHANGED
@@ -87,6 +87,7 @@ const models = {
87
87
  swimlane: schema('swimlane', { label: { type: 'string', required: true, editable: true } }),
88
88
  'architecture-node': schema('architecture-node', { label: { type: 'string', required: true, editable: true }, layerId: { type: 'string', editable: true }, boundaryId: { type: 'string', editable: true }, position, size: { type: 'object', editable: true, properties: { width: { type: 'number', required: true, min: 1 }, height: { type: 'number', required: true, min: 1 } } }, ports: { type: 'array', editable: false, items: { type: 'object', properties: { id: { type: 'string', required: true }, side: { type: 'enum', values: ['left', 'right', 'top', 'bottom'] }, offset: { type: 'number', min: 0, max: 1 } } } }, description: { type: 'string', editable: true }, status }),
89
89
  'architecture-edge': schema('architecture-edge', { from: { type: 'string', required: true, editable: true }, to: { type: 'string', required: true, editable: true }, label: { type: 'string', editable: true }, relation: { type: 'string', editable: true }, routing: { type: 'enum', values: ['straight', 'orthogonal', 'curved'], editable: true }, curveTension: { type: 'number', min: 0.2, max: 0.8, editable: true }, waypoints: { type: 'array', editable: true, items: { type: 'object', properties: { x: { type: 'number', required: true }, y: { type: 'number', required: true } } } }, status }),
90
+ 'mindmap-edge': schema('mindmap-edge', { from: { type: 'string', required: true, editable: true }, to: { type: 'string', required: true, editable: true }, label: { type: 'string', editable: true }, routing: { type: 'enum', values: ['straight', 'orthogonal', 'curved'], editable: true }, curveTension: { type: 'number', min: 0.2, max: 0.8, editable: true }, waypoints: { type: 'array', editable: true, items: { type: 'object', properties: { x: { type: 'number', required: true }, y: { type: 'number', required: true } } } }, status }),
90
91
  'mindmap-node': schema('mindmap-node', { label: { type: 'string', required: true, editable: true }, parentId: { type: 'string', editable: true }, position, size: { type: 'object', editable: true, properties: { width: { type: 'number', required: true, min: 1 }, height: { type: 'number', required: true, min: 1 } } }, branch: { type: 'string', editable: true }, description: { type: 'string', editable: true }, status })
91
92
  };
92
93
 
package/src/spec.mjs CHANGED
@@ -60,6 +60,40 @@ function dependencyErrors(rows) {
60
60
  return errors;
61
61
  }
62
62
 
63
+ function isIsoDate(value) {
64
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(value)) return false;
65
+ return !Number.isNaN(Date.parse(value));
66
+ }
67
+
68
+ function projectRowErrors(type, rows) {
69
+ const errors = [], ids = new Map(), requiresId = ['gantt', 'timeline', 'milestone'].includes(type);
70
+ rows.forEach((row, index) => {
71
+ const path = `data.values[${index}]`;
72
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
73
+ errors.push({ code: 'INVALID_PROJECT_ROW', path, message: `${type} rows must be objects.`, suggestion: 'Provide one JSON object per project record.' });
74
+ return;
75
+ }
76
+ if (requiresId && (typeof row.id !== 'string' || !row.id.trim())) errors.push({ code: 'MISSING_RECORD_ID', path: `${path}.id`, message: `${type} rows require a stable string id.`, suggestion: 'Add a unique id to every project record so edits and lineage remain stable.' });
77
+ if (typeof row.id === 'string' && row.id.trim()) {
78
+ if (ids.has(row.id)) errors.push({ code: 'DUPLICATE_RECORD_ID', path: `${path}.id`, message: `Record id ${row.id} is duplicated.`, suggestion: 'Use a unique stable id for every project record.' });
79
+ ids.set(row.id, index);
80
+ }
81
+ if (type === 'gantt') {
82
+ for (const key of ['start', 'end']) {
83
+ if (!isIsoDate(row[key])) errors.push({ code: 'MISSING_REQUIRED_DATE', path: `${path}.${key}`, message: `Gantt rows require a valid ISO ${key} date.`, suggestion: `Use ${key}: "2026-09-14" or an explicit ISO timestamp.` });
84
+ }
85
+ if (isIsoDate(row.start) && isIsoDate(row.end) && Date.parse(row.end) < Date.parse(row.start)) errors.push({ code: 'INVALID_INTERVAL', path, message: 'Gantt end must not precede start.', suggestion: 'Set end to the same day or a later ISO date.' });
86
+ } else if (['timeline', 'milestone'].includes(type)) {
87
+ if (!isIsoDate(row.date)) errors.push({ code: 'MISSING_REQUIRED_DATE', path: `${path}.date`, message: `${type} rows require a valid ISO date.`, suggestion: 'Use date: "2026-09-14" or an explicit ISO timestamp.' });
88
+ if (typeof (row.title ?? row.name ?? row.label) !== 'string' || !(row.title ?? row.name ?? row.label).trim()) errors.push({ code: 'MISSING_RECORD_LABEL', path: `${path}.title`, message: `${type} rows require a title, name, or label.`, suggestion: 'Add a human-readable title for the project record.' });
89
+ } else if (type === 'burndown') {
90
+ if (!isIsoDate(row.date)) errors.push({ code: 'MISSING_REQUIRED_DATE', path: `${path}.date`, message: 'burndown rows require a valid ISO date.', suggestion: 'Use date: "2026-09-14" or an explicit ISO timestamp.' });
91
+ if (!Number.isFinite(Number(row.remaining)) || Number(row.remaining) < 0) errors.push({ code: 'INVALID_REMAINING', path: `${path}.remaining`, message: 'Burndown remaining must be a non-negative finite number.', suggestion: 'Provide remaining: 0 or a positive numeric value.' });
92
+ }
93
+ });
94
+ return errors;
95
+ }
96
+
63
97
  const encodingChannels = {
64
98
  line: ['x', 'y'], area: ['x', 'y'], bar: ['x', 'y'], column: ['x', 'y'], scatter: ['x', 'y'],
65
99
  pie: ['category', 'value'], funnel: ['category', 'value'], gauge: ['value'], heatmap: ['x', 'y', 'color'], radar: [],
@@ -188,10 +222,18 @@ export function validateSpec(input = {}) {
188
222
  const diagramValidation = validateDiagram(diagramInput);
189
223
  diagramValidation.errors.forEach(error => errors.push({ ...error, path: error.path || 'diagram' }));
190
224
  }
191
- if (['gantt', 'timeline', 'milestone'].includes(spec.type)) {
225
+ if (['gantt', 'timeline', 'milestone', 'burndown'].includes(spec.type)) {
192
226
  const rows = spec.data.values, dateKey = spec.type === 'gantt' ? 'start/end' : 'date';
193
- if (!rows.some(row => row.start || row.end || row.date)) errors.push({ code: 'INVALID_INTERVALS', path: 'data.values', message: `${spec.type} rows require ${dateKey} dates.`, suggestion: 'Add ISO date values to each row.' });
194
- rows.forEach((row, index) => { const values = spec.type === 'gantt' ? [row.start, row.end] : [row.date]; if (values.some(value => value != null && Number.isNaN(Date.parse(value)))) errors.push({ code: 'INVALID_DATE', path: `data.values[${index}]`, message: `${spec.type} contains an invalid date.`, suggestion: 'Use an ISO-8601 date such as 2026-09-14.' }); });
227
+ if (!rows.some(row => row && (row.start || row.end || row.date))) errors.push({ code: 'INVALID_INTERVALS', path: 'data.values', message: `${spec.type} rows require ${dateKey} dates.`, suggestion: 'Add ISO date values to each row.' });
228
+ errors.push(...projectRowErrors(spec.type, rows));
229
+ if (spec.type === 'burndown') {
230
+ const seenDates = new Set(), validDates = rows.map(row => row?.date).filter(isIsoDate);
231
+ validDates.forEach((date, index) => {
232
+ if (seenDates.has(date)) warnings.push({ code: 'DUPLICATE_DATE', severity: 'warning', path: 'data.values', message: `Burndown contains duplicate date ${date}; input order is preserved.`, suggestion: 'Aggregate duplicate dates before rendering for one sample per day.' });
233
+ seenDates.add(date);
234
+ if (index && Date.parse(date) < Date.parse(validDates[index - 1])) warnings.push({ code: 'OUT_OF_ORDER_DATE', severity: 'warning', path: 'data.values', message: 'Burndown dates are not sorted; input order is preserved.', suggestion: 'Sort samples by date before creating the chart.' });
235
+ });
236
+ }
195
237
  }
196
238
  if (spec.type === 'gantt') errors.push(...dependencyErrors(spec.data.values));
197
239
  const rows = Array.isArray(spec.data?.values) ? spec.data.values : [];
@@ -0,0 +1,35 @@
1
+ import {
2
+ ChartValidationError,
3
+ Chart,
4
+ ChartSpec,
5
+ createChart,
6
+ getCapabilities,
7
+ getBusinessSchema,
8
+ type ChartEvent,
9
+ type EditCommand,
10
+ type LinkedFilters
11
+ } from '@taylorwong/ichartjs';
12
+
13
+ declare const spec: ChartSpec;
14
+ declare const command: EditCommand;
15
+ declare const filters: LinkedFilters;
16
+
17
+ const chart: Chart = createChart({ ...spec, type: spec.type || 'line', data: [{ id: 'row-1', name: 'A', value: 1 }] });
18
+ const listener = (event: ChartEvent) => event.chart.getState().revision;
19
+ chart.on('render', listener).off('render', listener);
20
+ chart.update({ labels: { enabled: true } }).setData([{ id: 'row-2', name: 'B', value: 2 }]);
21
+ chart.setLinkedFilters(filters).setLinkedSelection(['row-2']);
22
+ chart.getDiagramNodes();
23
+ chart.getDiagramEdges();
24
+ chart.getClipboard();
25
+ chart.export({ type: 'json', as: 'object' });
26
+ chart.export({ type: 'svg', as: 'string' });
27
+ chart.export({ type: 'png', as: 'dataurl' });
28
+ chart.export({ type: 'jpeg', as: 'blob' });
29
+ chart.previewEdit(command);
30
+ chart.validateData();
31
+ getCapabilities().contractVersion;
32
+ getBusinessSchema('mindmap-edge');
33
+ try { chart.update({ width: 0 }); } catch (error) { if (!(error instanceof ChartValidationError)) throw error; }
34
+ chart.destroy();
35
+ chart.destroy();
package/types/index.d.ts CHANGED
@@ -21,10 +21,11 @@ export interface PreferencesStorage { getItem(key: string): string | null; setIt
21
21
  export interface StyleRecommendation { version: '1.0'; preset: Exclude<ThemePreset, 'auto'>; mode: ThemeMode; resolvedMode: Exclude<ThemeMode, 'auto'>; palette: Exclude<ThemePalette, 'auto'>; reasons: string[]; warnings: Diagnostic[]; userOverride: boolean; }
22
22
  export interface ResolvedTheme extends ThemeConfig { name: string; resolvedMode: Exclude<ThemeMode, 'auto'>; background: string; surface: string; text: string; muted: string; axis: string; grid: string; border: string; focus: string; selection: string; colors: string[]; palettes: Record<string, unknown>; status: Record<string, string>; typography: Record<string, { size: number; weight: number; lineHeight: number; font: string }>; layout: Record<string, unknown>; marks: Record<string, unknown>; reasons: string[]; warnings: Diagnostic[]; }
23
23
  export interface Diagnostic { code: string; path?: string; message: string; expected?: unknown; suggestion?: string; [key: string]: unknown; }
24
+ export class ChartValidationError extends Error { name: 'ChartValidationError'; code: 'CHART_VALIDATION_FAILED'; operation: string; details: Diagnostic[]; errors: Diagnostic[]; path?: string; expected?: unknown; received?: unknown; suggestion?: string; toJSON(): Record<string, unknown>; }
24
25
  export interface DataFieldInfo { name: string; type: 'quantitative' | 'temporal' | 'category' | 'unknown'; role: 'identifier' | 'measure' | 'temporal-dimension' | 'dimension'; unit: string | null; cardinality: number; validCount: number; nullCount: number; min?: number; max?: number; temporalMin?: string; temporalMax?: string; }
25
26
  export interface DataInspection { version: '1.0'; rows: number; fields: DataFieldInfo[]; dimensions: string[]; measures: string[]; temporalFields: string[]; missingValueCount: number; warnings: Diagnostic[]; }
26
27
  export interface ChartCapability { type: ChartType; family: string; intents: string[]; required: string[]; optional: string[]; dataShapes: string[]; interactions: string[]; features: Record<string, 'supported' | 'not-applicable' | 'degraded'>; renderers: Array<'canvas' | 'svg'>; exports: string[]; limits: Record<string, number>; }
27
- export interface RuntimeCapabilities { version: '2.0'; contractVersion: '1.0'; chartTypes: ChartType[]; charts: Record<ChartType, ChartCapability>; intents: string[]; locale?: { default: string; recommended: string[]; appliesTo: string[]; inputDates: string }; renderers: Array<'canvas' | 'svg'>; interactions: string[]; exports: Array<'png' | 'svg' | 'json' | 'jpeg'>; styleSystem: { modes: ThemeMode[]; presets: ThemePreset[]; palettes: ThemePalette[]; switchable: boolean; automatic: boolean; [key: string]: unknown }; preferences?: { version: '1.0'; scopes: string[]; persistence: string[]; fields: string[]; agentAdjustable: boolean; interactiveSettingsUI: boolean; precedence: string[]; schema: PreferenceCapabilities }; headless: { preview?: boolean; json: boolean; svg: boolean; png: boolean | string }; export: { types: string[]; mime: Record<string, string>; browser: Record<string, boolean>; headless: Record<string, string | boolean>; methods: string[]; options: Record<string, unknown>; branding: Record<string, unknown> }; branding: { defaultEnabled: boolean; signature: string; options: Record<string, unknown> }; [key: string]: unknown; }
28
+ export interface RuntimeCapabilities { version: '2.0'; contractVersion: '1.1'; chartTypes: ChartType[]; charts: Record<ChartType, ChartCapability>; intents: string[]; locale?: { default: string; recommended: string[]; appliesTo: string[]; inputDates: string }; renderers: Array<'canvas' | 'svg'>; interactions: string[]; exports: Array<'png' | 'svg' | 'json' | 'jpeg'>; styleSystem: { modes: ThemeMode[]; presets: ThemePreset[]; palettes: ThemePalette[]; switchable: boolean; automatic: boolean; [key: string]: unknown }; preferences?: { version: '1.0'; scopes: string[]; persistence: string[]; fields: string[]; agentAdjustable: boolean; interactiveSettingsUI: boolean; precedence: string[]; schema: PreferenceCapabilities }; headless: { preview?: boolean; json: boolean; svg: boolean; png: boolean | string }; export: { types: string[]; mime: Record<string, string>; browser: Record<string, boolean>; headless: Record<string, string | boolean>; methods: string[]; options: Record<string, unknown>; branding: Record<string, unknown> }; branding: { defaultEnabled: boolean; signature: string; options: Record<string, unknown> }; [key: string]: unknown; }
28
29
  export interface ChartPlan { version: '1.0'; intent: string; intentKnown?: boolean; intentSuggestions?: string[]; fallbackUsed?: boolean; primary: ChartType; alternatives: ChartType[]; confidence: number; reasons: string[]; requiredFields: string[]; suggestedEncodings: { dimension: string | null; measure: string | null; secondaryMeasure: string | null }; assumptions: string[]; warnings: Diagnostic[]; unsupportedRequests: string[]; nextActions: string[]; capability: ChartCapability; styleRecommendation: StyleRecommendation; data: DataInspection; }
29
30
  export interface AxisState { rawDomain: [number, number]; domain: [number, number]; ticks: number[]; step: number | null; policy: 'nice' | 'raw' | 'explicit'; }
30
31
  export interface ChartHealth { version: '1.0'; status: 'ready' | 'degraded' | 'empty'; renderable: boolean; issues: string[]; metrics: { warnings: number; suppressedLabels: number; clampedValues: number; renderedMarks: number }; }
@@ -40,6 +41,12 @@ export interface ArchitectureNode extends DiagramNode { layerId?: string; bounda
40
41
  export interface MindmapNode extends DiagramNode { parentId?: string; branch?: string; description?: string; }
41
42
  export interface DiagramPoint { x: number; y: number; }
42
43
  export interface DiagramEdge { id?: string; from: string; to: string; fromPort?: string; toPort?: string; label?: string; relation?: string; routing?: 'straight' | 'orthogonal' | 'curved'; curveTension?: number; waypoints?: DiagramPoint[]; }
44
+ export interface DiagramElement { id?: string; type?: string; dataRef?: Record<string, unknown>; geometry?: Record<string, unknown>; [key: string]: unknown; }
45
+ export interface ChartEvent { chart: Chart; type?: string; target?: DiagramElement | null; datum?: Record<string, unknown>; selectedData?: Array<Record<string, unknown>>; [key: string]: unknown; }
46
+ export type ChartListener = (event: ChartEvent) => void;
47
+ export interface ChartPlugin { name?: string; install?(chart: Chart): void; beforeRender?(chart: Chart, model: Record<string, unknown>): void; afterRender?(chart: Chart, model: Record<string, unknown>): void; destroy?(chart: Chart): void; }
48
+ export interface ClipboardState { nodes: DiagramNode[]; edges: DiagramEdge[]; }
49
+ export interface ChartState { renderer: string; width: number; height: number; dataCount: number; selected: unknown[]; revision: number; history: { undo: number; redo: number }; view: Record<string, unknown> | null; health: ChartHealth; warnings: Diagnostic[]; preferences: ChartPreferences; clipboard: { nodes: number; edges: number }; [key: string]: unknown; }
43
50
  export interface DiagramConfig { mode?: 'process' | 'architecture' | 'mindmap'; layout?: 'manual' | 'layered' | 'tree' | 'radial'; routing?: 'straight' | 'orthogonal' | 'curved'; curveTension?: number; grid?: number; snap?: boolean; }
44
51
  export interface ArchitectureLayer { id: string; label?: string; }
45
52
  export interface ArchitectureBoundary { id: string; label?: string; nodeIds?: string[]; padding?: number; color?: string; }
@@ -87,13 +94,35 @@ export interface Chart {
87
94
  downloadSVG(): ReturnType<Chart['download']>;
88
95
  downloadJSON(): ReturnType<Chart['download']>;
89
96
  destroy(): void;
90
- inspectDataSchema(): ReturnType<typeof inspectDataSchema>;
91
97
  validateData(): Record<string, unknown>;
92
98
  validateEdit(command: EditCommand): Record<string, unknown>;
93
99
  previewEdit(command: EditCommand): EditPreview;
94
100
  applyEdit(command: EditCommand, options?: { preview?: EditPreview; confirmed?: boolean; approval?: unknown; previewId?: string; expectedRevision?: number; actor?: string; source?: string; reason?: string }): Record<string, unknown>;
95
101
  getChangeSet(): Record<string, unknown> | null;
96
- getState(): Record<string, unknown> & { axes?: { y?: AxisState; right?: AxisState } | null; health?: ChartHealth; warnings?: Diagnostic[] };
102
+ getState(): ChartState;
103
+ on(type: string, listener: ChartListener): this;
104
+ off(type: string, listener: ChartListener): this;
105
+ getAccessibleDescription(): string;
106
+ inspectDataSchema(): Record<string, unknown>;
107
+ toDataTable(): Array<Record<string, unknown>>;
108
+ getSelectedData(): Array<Record<string, unknown>>;
109
+ getElementAt(x: number, y: number): DiagramElement | null;
110
+ select(target: DiagramElement, options?: { additive?: boolean }): this;
111
+ selectBox(start: DiagramPoint, end: DiagramPoint): this;
112
+ getDiagramNodes(): DiagramNode[];
113
+ getDiagramEdges(): DiagramEdge[];
114
+ getDiagramGroups(): Array<Record<string, unknown>>;
115
+ getCollapsedGroupIds(): string[];
116
+ getClipboard(): ClipboardState;
117
+ copySelection(): { valid: boolean; clipboard?: ClipboardState; errors?: Diagnostic[] };
118
+ duplicateSelection(options?: Record<string, unknown>): Record<string, unknown>;
119
+ pasteSelection(options?: Record<string, unknown>): Record<string, unknown>;
120
+ connectNodes(connection: DiagramEdge, options?: Record<string, unknown>): Record<string, unknown>;
121
+ toggleGroupCollapse(groupId: string, options?: Record<string, unknown>): Record<string, unknown>;
122
+ highlight(target: DiagramElement): this;
123
+ use(plugin: ChartPlugin): this;
124
+ /** Advanced JSON-pointer mutation. Prefer update() so the full public contract is explicit. */
125
+ applyPatch(patches: Array<{ op: 'add' | 'replace' | 'remove'; path: string; value?: unknown }>): this;
97
126
  getProjectAnalytics(): ProjectAnalyticsState | null;
98
127
  getLinkedState(): LinkedState | null;
99
128
  setLinkedFilters(filters: LinkedFilters): this;
@@ -117,6 +146,7 @@ export interface Chart {
117
146
  }
118
147
 
119
148
  export function createChart(spec: ChartSpec): Chart;
149
+ export const iChart: Record<string, unknown> & { version: string; ChartValidationError: typeof ChartValidationError; createChart: typeof createChart };
120
150
  export const defaultPreferences: ChartPreferences;
121
151
  export function getPreferenceCapabilities(chartType?: ChartType | string | null, options?: { locale?: 'en' | 'zh-CN' | string }): PreferenceCapabilities;
122
152
  export function createPreferencesStore(options?: { storage?: 'localStorage' | 'memory' | PreferencesStorage; storageKey?: string; global?: ChartPreferencesPatch; charts?: Record<string, ChartPreferencesPatch> }): PreferencesStore;