@opendata-ai/openchart-svelte 8.0.0-rc.2 → 8.0.0-rc.21

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 (42) hide show
  1. package/dist/Chart.svelte +4 -0
  2. package/dist/Chart.svelte.d.ts +1 -0
  3. package/dist/Chart.svelte.d.ts.map +1 -1
  4. package/dist/GeoMap.svelte +8 -10
  5. package/dist/GeoMap.svelte.d.ts +3 -7
  6. package/dist/GeoMap.svelte.d.ts.map +1 -1
  7. package/dist/Graph.svelte +135 -12
  8. package/dist/Graph.svelte.d.ts +56 -3
  9. package/dist/Graph.svelte.d.ts.map +1 -1
  10. package/dist/__tests__/Graph.test.js +50 -0
  11. package/dist/composables/useGraph.svelte.d.ts +66 -9
  12. package/dist/composables/useGraph.svelte.d.ts.map +1 -1
  13. package/dist/composables/useGraph.svelte.js +48 -8
  14. package/dist/styles.css +1 -1
  15. package/dist/styles.css.map +1 -1
  16. package/package.json +5 -6
  17. package/src/BarList.svelte +0 -95
  18. package/src/Chart.svelte +0 -192
  19. package/src/ChartStory.svelte +0 -116
  20. package/src/DataTable.svelte +0 -132
  21. package/src/GeoMap.svelte +0 -91
  22. package/src/Graph.svelte +0 -119
  23. package/src/Sankey.svelte +0 -94
  24. package/src/ThemeProvider.svelte +0 -27
  25. package/src/TileMap.svelte +0 -112
  26. package/src/Visualization.svelte +0 -54
  27. package/src/__tests__/Chart.test.ts +0 -134
  28. package/src/__tests__/ChartStory.test.ts +0 -64
  29. package/src/__tests__/ChartStoryHarness.svelte +0 -13
  30. package/src/__tests__/DataTable.test.ts +0 -126
  31. package/src/__tests__/Graph.test.ts +0 -130
  32. package/src/__tests__/ThemeContext.test.ts +0 -106
  33. package/src/__tests__/composables.test.ts +0 -101
  34. package/src/__tests__/useTableState.test.ts +0 -134
  35. package/src/composables/useChart.svelte.ts +0 -108
  36. package/src/composables/useDarkMode.svelte.ts +0 -51
  37. package/src/composables/useGraph.svelte.ts +0 -115
  38. package/src/composables/useTable.svelte.ts +0 -85
  39. package/src/composables/useTableState.svelte.ts +0 -78
  40. package/src/context.ts +0 -35
  41. package/src/index.ts +0 -100
  42. package/src/types.ts +0 -149
@@ -1,95 +0,0 @@
1
- <!--
2
- BarList component: Svelte 5 wrapper around the vanilla adapter.
3
- -->
4
- <script lang="ts">
5
- import type { BarListSpec, DarkMode, ThemeConfig } from '@opendata-ai/openchart-core';
6
- import {
7
- type BarListInstance,
8
- type BarListMountOptions,
9
- createBarList,
10
- } from '@opendata-ai/openchart-vanilla';
11
- import { onMount, untrack } from 'svelte';
12
- import { getVizDarkMode, getVizTheme } from './context.js';
13
-
14
- let {
15
- spec,
16
- theme,
17
- darkMode,
18
- onrowclick,
19
- onrowhover,
20
- class: className,
21
- style,
22
- }: {
23
- spec: BarListSpec;
24
- theme?: ThemeConfig;
25
- darkMode?: DarkMode;
26
- onrowclick?: (row: { label: string; value: number; data: Record<string, unknown> }) => void;
27
- onrowhover?: (
28
- row: {
29
- label: string;
30
- value: number;
31
- data: Record<string, unknown>;
32
- } | null,
33
- ) => void;
34
- class?: string;
35
- style?: string;
36
- } = $props();
37
-
38
- let containerEl: HTMLDivElement;
39
- let instance: BarListInstance | null = null;
40
-
41
- const ctxTheme = getVizTheme();
42
- const ctxDarkMode = getVizDarkMode();
43
-
44
- onMount(() => {
45
- return () => {
46
- instance?.destroy();
47
- instance = null;
48
- };
49
- });
50
-
51
- let prevSpec = '';
52
-
53
- $effect(() => {
54
- const resolvedTheme = theme ?? ctxTheme?.();
55
- const resolvedDarkMode = darkMode ?? ctxDarkMode?.();
56
- const currentSpec = untrack(() => spec);
57
-
58
- instance?.destroy();
59
-
60
- const options: BarListMountOptions = {
61
- theme: resolvedTheme,
62
- darkMode: resolvedDarkMode,
63
- onRowClick: (row: { label: string; value: number; data: Record<string, unknown> }) =>
64
- untrack(() => onrowclick)?.(row),
65
- onRowHover: (
66
- row: {
67
- label: string;
68
- value: number;
69
- data: Record<string, unknown>;
70
- } | null,
71
- ) => untrack(() => onrowhover)?.(row),
72
- responsive: true,
73
- };
74
-
75
- instance = createBarList(containerEl, currentSpec, options);
76
- prevSpec = JSON.stringify(currentSpec);
77
- });
78
-
79
- $effect(() => {
80
- const currentSpec = spec;
81
- if (!instance) return;
82
-
83
- const specString = JSON.stringify(currentSpec);
84
- if (specString !== prevSpec) {
85
- prevSpec = specString;
86
- instance.update(currentSpec);
87
- }
88
- });
89
- </script>
90
-
91
- <div
92
- bind:this={containerEl}
93
- class={className ? `oc-barlist-root ${className}` : 'oc-barlist-root'}
94
- {style}
95
- ></div>
package/src/Chart.svelte DELETED
@@ -1,192 +0,0 @@
1
- <!--
2
- Chart component: Svelte 5 wrapper around the vanilla adapter.
3
-
4
- Mounts a chart instance on render, updates when spec/options change,
5
- and cleans up on unmount. All heavy lifting is done by the vanilla
6
- createChart() function.
7
- -->
8
- <script lang="ts">
9
- import type {
10
- Annotation,
11
- AnnotationOffset,
12
- ChartSpec,
13
- DarkMode,
14
- ElementEdit,
15
- ElementRef,
16
- GraphSpec,
17
- LayerSpec,
18
- MarkEvent,
19
- TextAnnotation,
20
- ThemeConfig,
21
- } from '@opendata-ai/openchart-core';
22
- import { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';
23
- import { onMount, untrack } from 'svelte';
24
- import { getVizDarkMode, getVizTheme } from './context.js';
25
-
26
- let {
27
- spec,
28
- theme,
29
- darkMode,
30
- onmarkclick,
31
- onmarkhover,
32
- onmarkleave,
33
- onlegendtoggle,
34
- onannotationclick,
35
- onannotationedit,
36
- onedit,
37
- onselect,
38
- ondeselect,
39
- ontextedit,
40
- ondatapointclick,
41
- selectedElement: selectedElementProp,
42
- highlight,
43
- class: className,
44
- style,
45
- }: {
46
- spec: ChartSpec | LayerSpec | GraphSpec;
47
- theme?: ThemeConfig;
48
- darkMode?: DarkMode;
49
- onmarkclick?: (event: MarkEvent) => void;
50
- onmarkhover?: (event: MarkEvent) => void;
51
- onmarkleave?: () => void;
52
- onlegendtoggle?: (series: string, visible: boolean) => void;
53
- onannotationclick?: (annotation: Annotation, event: MouseEvent) => void;
54
- onannotationedit?: (annotation: TextAnnotation, offset: AnnotationOffset) => void;
55
- onedit?: (edit: ElementEdit) => void;
56
- onselect?: (element: ElementRef) => void;
57
- ondeselect?: (element: ElementRef) => void;
58
- ontextedit?: (element: ElementRef, oldText: string, newText: string) => void;
59
- ondatapointclick?: (data: Record<string, unknown>) => void;
60
- selectedElement?: ElementRef;
61
- highlight?: string[] | null;
62
- class?: string;
63
- style?: string;
64
- } = $props();
65
-
66
- let containerEl: HTMLDivElement;
67
- let instance: ChartInstance | null = null;
68
-
69
- const ctxTheme = getVizTheme();
70
- const ctxDarkMode = getVizDarkMode();
71
-
72
- onMount(() => {
73
- return () => {
74
- instance?.destroy();
75
- instance = null;
76
- };
77
- });
78
-
79
- // Stable callback wrappers that read current handler props without
80
- // creating reactive dependencies. This prevents callback prop changes
81
- // from triggering a full chart destroy/recreate cycle.
82
- const stableHandlers: MountOptions = {
83
- onMarkClick: (event: MarkEvent) => untrack(() => onmarkclick)?.(event),
84
- onMarkHover: (event: MarkEvent) => untrack(() => onmarkhover)?.(event),
85
- onMarkLeave: () => untrack(() => onmarkleave)?.(),
86
- onLegendToggle: (series: string, visible: boolean) =>
87
- untrack(() => onlegendtoggle)?.(series, visible),
88
- onAnnotationClick: (annotation: Annotation, event: MouseEvent) =>
89
- untrack(() => onannotationclick)?.(annotation, event),
90
- onDataPointClick: (data: Record<string, unknown>) => untrack(() => ondatapointclick)?.(data),
91
- };
92
-
93
- // Editing callbacks - only defined as stable wrappers, but only
94
- // included in options when the consumer provides the prop.
95
- const stableOnAnnotationEdit = (annotation: TextAnnotation, offset: AnnotationOffset) =>
96
- untrack(() => onannotationedit)?.(annotation, offset);
97
- const stableOnEdit = (edit: ElementEdit) => untrack(() => onedit)?.(edit);
98
- const stableOnSelect = (element: ElementRef) => untrack(() => onselect)?.(element);
99
- const stableOnDeselect = (element: ElementRef) => untrack(() => ondeselect)?.(element);
100
- const stableOnTextEdit = (element: ElementRef, oldText: string, newText: string) =>
101
- untrack(() => ontextedit)?.(element, oldText, newText);
102
-
103
- let prevSpec = '';
104
-
105
- // Effect 1: Mount/recreate chart on theme/darkMode changes.
106
- // Reads spec via untrack() so spec changes don't trigger full recreate.
107
- $effect(() => {
108
- const resolvedTheme = theme ?? ctxTheme?.();
109
- const resolvedDarkMode = darkMode ?? ctxDarkMode?.();
110
- // Read spec without tracking - spec changes handled in Effect 2
111
- const currentSpec = untrack(() => spec);
112
-
113
- instance?.destroy();
114
-
115
- const hasAnnotationEdit = untrack(() => onannotationedit) !== undefined;
116
- const hasEdit = untrack(() => onedit) !== undefined;
117
- const hasSelect = untrack(() => onselect) !== undefined;
118
- const hasDeselect = untrack(() => ondeselect) !== undefined;
119
- const hasTextEdit = untrack(() => ontextedit) !== undefined;
120
- const currentSelectedElement = untrack(() => selectedElementProp);
121
-
122
- const options: MountOptions = {
123
- theme: resolvedTheme,
124
- darkMode: resolvedDarkMode,
125
- responsive: true,
126
- ...stableHandlers,
127
- ...(hasAnnotationEdit ? { onAnnotationEdit: stableOnAnnotationEdit } : {}),
128
- ...(hasEdit ? { onEdit: stableOnEdit } : {}),
129
- ...(hasSelect ? { onSelect: stableOnSelect } : {}),
130
- ...(hasDeselect ? { onDeselect: stableOnDeselect } : {}),
131
- ...(hasTextEdit ? { onTextEdit: stableOnTextEdit } : {}),
132
- ...(currentSelectedElement ? { selectedElement: currentSelectedElement } : {}),
133
- };
134
-
135
- instance = createChart(containerEl, currentSpec, options);
136
- prevSpec = JSON.stringify(currentSpec);
137
- });
138
-
139
- // Effect 2: Update chart when spec changes (no destroy/recreate).
140
- $effect(() => {
141
- const currentSpec = spec;
142
- if (!instance) return;
143
-
144
- const specString = JSON.stringify(currentSpec);
145
- if (specString !== prevSpec) {
146
- prevSpec = specString;
147
- instance.update(currentSpec, { selectedElement: untrack(() => selectedElementProp) });
148
- }
149
- });
150
-
151
- // Effect 3: Watch selectedElement prop changes.
152
- $effect(() => {
153
- const sel = selectedElementProp;
154
- // Read instance without tracking to avoid coupling to Effect 1
155
- const inst = untrack(() => instance);
156
- if (!inst) return;
157
-
158
- if (sel) {
159
- inst.select(sel);
160
- } else {
161
- inst.deselect();
162
- }
163
- });
164
-
165
- // Effect 4: Watch highlight prop changes.
166
- $effect(() => {
167
- const h = highlight;
168
- if (h === undefined) return;
169
- const inst = untrack(() => instance);
170
- if (!inst) return;
171
- inst.setHighlight(h ?? null);
172
- });
173
-
174
- // Imperative methods exposed via component exports
175
- export function getSelectedElement(): ElementRef | null {
176
- return instance?.getSelectedElement() ?? null;
177
- }
178
-
179
- export function select(ref: ElementRef): void {
180
- instance?.select(ref);
181
- }
182
-
183
- export function deselect(): void {
184
- instance?.deselect();
185
- }
186
- </script>
187
-
188
- <div
189
- bind:this={containerEl}
190
- class={className ? `oc-chart-root ${className}` : 'oc-chart-root'}
191
- {style}
192
- ></div>
@@ -1,116 +0,0 @@
1
- <!--
2
- ChartStory: Svelte 5 scrollytelling shell around the vanilla
3
- `createChartStory`.
4
-
5
- A sticky chart panel plus a scrolling narrative column whose blocks advance
6
- the story. The vanilla driver owns cumulative patch application, mark
7
- morphing, crossfade, and camera; this component owns only Svelte lifecycle
8
- and the sticky layout.
9
-
10
- Narrative is provided as an array of snippets (`narrative`), one per step.
11
- Pass `step` to drive the story externally; omit for self-driving scroll.
12
- -->
13
- <script lang="ts">
14
- import type { ChartSpec, GraphSpec, LayerSpec, MapSpec } from '@opendata-ai/openchart-core';
15
- import type { MountOptions } from '@opendata-ai/openchart-vanilla';
16
- import {
17
- type ChartStoryInstance,
18
- type ChartStoryOptions,
19
- createChartStory,
20
- } from '@opendata-ai/openchart-vanilla/story';
21
- import { onMount, type Snippet, untrack } from 'svelte';
22
-
23
- let {
24
- spec,
25
- steps,
26
- narrative,
27
- step,
28
- triggerPosition,
29
- cameraMode,
30
- mountOptions,
31
- class: className,
32
- }: {
33
- spec: ChartSpec | LayerSpec | GraphSpec | MapSpec;
34
- steps: ChartStoryOptions['steps'];
35
- narrative: Snippet[];
36
- step?: number;
37
- triggerPosition?: number;
38
- cameraMode?: 'step' | 'scrub';
39
- mountOptions?: MountOptions;
40
- class?: string;
41
- } = $props();
42
-
43
- let sectionEl: HTMLElement;
44
- let mountEl: HTMLDivElement;
45
- const stepEls: Array<HTMLElement | null> = $state([]);
46
- let story: ChartStoryInstance | null = null;
47
-
48
- onMount(() => {
49
- if (narrative.length !== steps.length) {
50
- console.warn(
51
- `[openchart] ChartStory: narrative length (${narrative.length}) does not match steps length (${steps.length}). Each step should have a corresponding narrative element.`,
52
- );
53
- }
54
-
55
- story = createChartStory(
56
- mountEl,
57
- {
58
- spec: untrack(() => spec),
59
- steps: untrack(() => steps),
60
- triggerPosition: untrack(() => triggerPosition),
61
- cameraMode: untrack(() => cameraMode),
62
- },
63
- untrack(() => mountOptions),
64
- );
65
- story.setContainer(sectionEl);
66
- stepEls.forEach((el, i) => {
67
- if (el) story?.registerStep(i, el);
68
- });
69
- const initialStep = untrack(() => step);
70
- if (initialStep !== undefined) story.goTo(initialStep);
71
-
72
- return () => {
73
- story?.destroy();
74
- story = null;
75
- };
76
- });
77
-
78
- // Controlled mode: drive the story when `step` changes.
79
- $effect(() => {
80
- const next = step;
81
- if (next === undefined) return;
82
- untrack(() => story)?.goTo(next);
83
- });
84
-
85
- export function goTo(index: number): void {
86
- story?.goTo(index);
87
- }
88
-
89
- export function getInstance(): ChartStoryInstance | null {
90
- return story;
91
- }
92
- </script>
93
-
94
- <section
95
- bind:this={sectionEl}
96
- class={className ? `oc-story ${className}` : 'oc-story'}
97
- style="position:relative;display:grid;grid-template-columns:minmax(0,26rem) minmax(0,1fr);gap:3rem;align-items:start;"
98
- >
99
- <div style="position:relative;">
100
- <!-- Keyed by index: steps are a fixed ordered list, so the index is the
101
- stable identity. Don't reorder or splice narrative entries at runtime
102
- (it would remap step refs to the wrong content); rebuild the story. -->
103
- {#each narrative as block, i (i)}
104
- <div
105
- bind:this={stepEls[i]}
106
- data-oc-story-step={i}
107
- style="min-height:80vh;display:flex;align-items:center;"
108
- >
109
- <div style="max-width:36rem;">{@render block()}</div>
110
- </div>
111
- {/each}
112
- </div>
113
- <div style="position:sticky;top:2rem;height:calc(100vh - 4rem);display:flex;align-items:center;justify-content:center;">
114
- <div bind:this={mountEl} style="width:100%;"></div>
115
- </div>
116
- </section>
@@ -1,132 +0,0 @@
1
- <!--
2
- DataTable component: Svelte 5 wrapper around the vanilla table adapter.
3
-
4
- Mounts a table instance on render, updates when spec changes,
5
- and cleans up on unmount. Supports both controlled and uncontrolled modes
6
- for sort, search, and pagination state.
7
- -->
8
- <script lang="ts">
9
- import type { DarkMode, SortState, TableSpec, ThemeConfig } from '@opendata-ai/openchart-core';
10
- import {
11
- createTable,
12
- type TableInstance,
13
- type TableMountOptions,
14
- } from '@opendata-ai/openchart-vanilla';
15
- import { onMount, untrack } from 'svelte';
16
- import { getVizDarkMode, getVizTheme } from './context.js';
17
-
18
- let {
19
- spec,
20
- theme,
21
- darkMode,
22
- onrowclick,
23
- onsortchange,
24
- onsearchchange,
25
- onpagechange,
26
- sort,
27
- search,
28
- page,
29
- class: className,
30
- style,
31
- }: {
32
- spec: TableSpec;
33
- theme?: ThemeConfig;
34
- darkMode?: DarkMode;
35
- onrowclick?: (row: Record<string, unknown>) => void;
36
- onsortchange?: (sort: SortState | null) => void;
37
- onsearchchange?: (query: string) => void;
38
- onpagechange?: (page: number) => void;
39
- sort?: SortState | null;
40
- search?: string;
41
- page?: number;
42
- class?: string;
43
- style?: string;
44
- } = $props();
45
-
46
- let containerEl: HTMLDivElement;
47
- let instance: TableInstance | null = null;
48
-
49
- const ctxTheme = getVizTheme();
50
- const ctxDarkMode = getVizDarkMode();
51
-
52
- const isControlled = $derived(sort !== undefined || search !== undefined || page !== undefined);
53
-
54
- onMount(() => {
55
- return () => {
56
- instance?.destroy();
57
- instance = null;
58
- };
59
- });
60
-
61
- let prevSpec = '';
62
-
63
- // Effect 1: Mount/recreate table on theme/darkMode changes.
64
- $effect(() => {
65
- const resolvedTheme = theme ?? ctxTheme?.();
66
- const resolvedDarkMode = darkMode ?? ctxDarkMode?.();
67
- // Read spec and controlled state without tracking
68
- const currentSpec = untrack(() => spec);
69
- const currentIsControlled = untrack(() => isControlled);
70
- const currentSort = untrack(() => sort);
71
- const currentSearch = untrack(() => search);
72
- const currentPage = untrack(() => page);
73
-
74
- instance?.destroy();
75
-
76
- const mountOptions: TableMountOptions = {
77
- theme: resolvedTheme,
78
- darkMode: resolvedDarkMode,
79
- onRowClick: (row: Record<string, unknown>) => untrack(() => onrowclick)?.(row),
80
- responsive: true,
81
- onStateChange: (state) => {
82
- if (state.sort !== undefined) untrack(() => onsortchange)?.(state.sort);
83
- if (state.search !== undefined) untrack(() => onsearchchange)?.(state.search);
84
- if (state.page !== undefined) untrack(() => onpagechange)?.(state.page);
85
- },
86
- };
87
-
88
- if (currentIsControlled) {
89
- mountOptions.externalState = {
90
- sort: currentSort ?? null,
91
- search: currentSearch ?? '',
92
- page: currentPage ?? 0,
93
- };
94
- }
95
-
96
- instance = createTable(containerEl, currentSpec, mountOptions);
97
- prevSpec = JSON.stringify(currentSpec);
98
- });
99
-
100
- // Effect 2: Update table when spec changes (no destroy/recreate).
101
- $effect(() => {
102
- const currentSpec = spec;
103
- if (!instance) return;
104
-
105
- const specString = JSON.stringify(currentSpec);
106
- if (specString !== prevSpec) {
107
- prevSpec = specString;
108
- instance.update(currentSpec);
109
- }
110
- });
111
-
112
- // Effect 3: Sync controlled state without remounting.
113
- $effect(() => {
114
- const currentIsControlled = isControlled;
115
- const currentSort = sort;
116
- const currentSearch = search;
117
- const currentPage = page;
118
- if (!instance || !currentIsControlled) return;
119
-
120
- instance.setState({
121
- sort: currentSort ?? null,
122
- search: currentSearch ?? '',
123
- page: currentPage ?? 0,
124
- });
125
- });
126
- </script>
127
-
128
- <div
129
- bind:this={containerEl}
130
- class={className ? `oc-table-root ${className}` : 'oc-table-root'}
131
- {style}
132
- ></div>
package/src/GeoMap.svelte DELETED
@@ -1,91 +0,0 @@
1
- <!--
2
- Map component: Svelte 5 wrapper around the vanilla adapter.
3
-
4
- Mounts a map instance on render, updates when spec changes,
5
- and cleans up on unmount. All heavy lifting is done by the vanilla
6
- createMap() function.
7
- -->
8
- <script lang="ts">
9
- import type { DarkMode, MapSpec, ThemeConfig } from '@opendata-ai/openchart-core';
10
-
11
- type MapFeatureEvent = {
12
- id: string | number;
13
- name?: string;
14
- data: Record<string, unknown> | null;
15
- };
16
-
17
- import { createMap, type MapInstance, type MapMountOptions } from '@opendata-ai/openchart-vanilla';
18
- import { onMount, untrack } from 'svelte';
19
- import { getVizDarkMode, getVizTheme } from './context.js';
20
-
21
- let {
22
- spec,
23
- theme,
24
- darkMode,
25
- onmarkclick,
26
- onmarkhover,
27
- class: className,
28
- style,
29
- }: {
30
- spec: MapSpec;
31
- theme?: ThemeConfig;
32
- darkMode?: DarkMode;
33
- onmarkclick?: (event: MapFeatureEvent) => void;
34
- onmarkhover?: (event: MapFeatureEvent | null) => void;
35
- class?: string;
36
- style?: string;
37
- } = $props();
38
-
39
- let containerEl: HTMLDivElement;
40
- let instance: MapInstance | null = null;
41
-
42
- const ctxTheme = getVizTheme();
43
- const ctxDarkMode = getVizDarkMode();
44
-
45
- onMount(() => {
46
- return () => {
47
- instance?.destroy();
48
- instance = null;
49
- };
50
- });
51
-
52
- let prevSpec = '';
53
-
54
- // Effect 1: Mount/recreate map on theme/darkMode changes.
55
- $effect(() => {
56
- const resolvedTheme = theme ?? ctxTheme?.();
57
- const resolvedDarkMode = darkMode ?? ctxDarkMode?.();
58
- const currentSpec = untrack(() => spec);
59
-
60
- instance?.destroy();
61
-
62
- const options: MapMountOptions = {
63
- theme: resolvedTheme,
64
- darkMode: resolvedDarkMode,
65
- onMarkClick: (feature) => untrack(() => onmarkclick)?.(feature),
66
- onMarkHover: (feature) => untrack(() => onmarkhover)?.(feature),
67
- responsive: true,
68
- };
69
-
70
- instance = createMap(containerEl, currentSpec, options);
71
- prevSpec = JSON.stringify(currentSpec);
72
- });
73
-
74
- // Effect 2: Update map when spec changes (no destroy/recreate).
75
- $effect(() => {
76
- const currentSpec = spec;
77
- if (!instance) return;
78
-
79
- const specString = JSON.stringify(currentSpec);
80
- if (specString !== prevSpec) {
81
- prevSpec = specString;
82
- instance.update(currentSpec);
83
- }
84
- });
85
- </script>
86
-
87
- <div
88
- bind:this={containerEl}
89
- class={className ? `oc-map-root ${className}` : 'oc-map-root'}
90
- {style}
91
- ></div>