@extend-ai/react-pptx 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @extend-ai/react-pptx
2
+
3
+ ## Unreleased
4
+
5
+ - Expose consumer-owned slide thumbnail rails through `usePptxViewerThumbnails`, including stable
6
+ container refs, resolution bounds, render status, imperative rendering, and cleanup. Add
7
+ DOCX-style visible/prefetch render windows and virtualize the built-in thumbnail scrollport.
8
+
9
+ ## 0.1.0
10
+
11
+ Initial public release of the React PowerPoint viewer, including:
12
+
13
+ - Browser-native PPTX and legacy PPT parsing through Rust and WebAssembly.
14
+ - A normalized presentation model and virtualized React viewer.
15
+ - Embedded and host-provided font support with missing-font diagnostics.
16
+ - Native legacy PowerPoint slide, drawing, text, and image fallback support.
17
+ - Fidelity improvements for placeholders, backgrounds, text layout, gradients, and image fills.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Extend AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # @extend-ai/react-pptx
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@extend-ai/react-pptx.svg)](https://www.npmjs.com/package/@extend-ai/react-pptx)
4
+
5
+ Browser-native PowerPoint viewing for React.
6
+
7
+ ```bash
8
+ npm install @extend-ai/react-pptx
9
+ ```
10
+
11
+ React and React DOM are peer dependencies already present in a React application.
12
+
13
+ ## Main API
14
+
15
+ - `ReactPptxViewer` renders URLs, `Blob`, `ArrayBuffer`, `Uint8Array`, parsed presentations, or normalized documents.
16
+ - `parsePresentation` performs native parsing and returns the same normalized model the viewer renders.
17
+ - `initWasm` and `setWasmSource` support eager initialization and custom Wasm/CDN hosting.
18
+ - `PptxViewerController` provides navigation, zoom, fit, text search, highlighting, model access,
19
+ and detached thumbnail rendering.
20
+
21
+ Import `@extend-ai/react-pptx/styles.css` alongside the component; the stylesheet is emitted as
22
+ a separate package asset. Parsing and rendering are client-side; importing the JavaScript package
23
+ during SSR is safe.
24
+
25
+ Unknown or degraded content is surfaced through `onWarning` and `showDiagnostics` rather
26
+ than silently removed.
27
+
28
+ ## Supported presentation formats
29
+
30
+ `.pptx` and legacy PowerPoint 97–2003 `.ppt` files are both accepted by the same `source`
31
+ prop and `parsePresentation()` API. Legacy files are opened natively from OLE compound
32
+ storage by the Rust/Wasm parser; the browser does not need LibreOffice, a server-side
33
+ conversion endpoint, or a temporary `.pptx` copy.
34
+
35
+ For `.ppt`, the parser follows the `Current User`/`UserEditAtom` edit chain and live
36
+ persist-object directory to recover the actual slide list. Supported OfficeArt group and
37
+ shape anchors, text, pictures, and referenced master drawing content are normalized into
38
+ the same `PresentationDocument` consumed by the React viewer. PNG/JPEG pictures render
39
+ directly, while decoded EMF/WMF records use a bounded browser-side image conversion for
40
+ static previews.
41
+
42
+ Legacy rendering is static, not full PowerPoint emulation. Advanced effects, editable
43
+ binary charts and tables, animations, transitions, active OLE objects, and timed media may
44
+ degrade to a static picture or remain unsupported. Inspect `onWarning` or enable
45
+ `showDiagnostics` to receive explicit `degraded-rendering` warnings:
46
+
47
+ ```tsx
48
+ <ReactPptxViewer
49
+ source={file}
50
+ showDiagnostics
51
+ onWarning={(warning) => {
52
+ if (warning.code === 'degraded-rendering') {
53
+ console.warn(warning.message, warning.slideIndex);
54
+ }
55
+ }}
56
+ />
57
+ ```
58
+
59
+ The repository regression suite opens a real three-slide PowerPoint 97–2003 file at
60
+ `tests/fixtures/legacy/file-example-250kb.ppt` directly in Rust and the playground. It
61
+ checks slide order, text, normalized nodes, thumbnail navigation, and diagnostics without
62
+ a conversion step.
63
+
64
+ ## WebAssembly asset
65
+
66
+ Native PowerPoint parsing runs through a WebAssembly module that loads lazily. Most apps can
67
+ use the default loader. If your bundler or deployment needs to host the binary explicitly,
68
+ configure it before the first presentation is parsed:
69
+
70
+ ```ts
71
+ import { setWasmSource } from '@extend-ai/react-pptx';
72
+
73
+ setWasmSource('https://cdn.example.com/pptx_wasm_bg.wasm');
74
+ // A URL, Request, Response, ArrayBuffer/TypedArray, or WebAssembly.Module also works.
75
+ ```
76
+
77
+ The binary is also available as a package subpath for bundlers that support asset URL imports:
78
+
79
+ ```ts
80
+ import wasmUrl from '@extend-ai/react-pptx/pptx_wasm_bg.wasm?url';
81
+ import { setWasmSource } from '@extend-ai/react-pptx';
82
+
83
+ setWasmSource(wasmUrl);
84
+ ```
85
+
86
+ Configured strings, URLs, request URLs, bytes, and `WebAssembly.Module` values are forwarded to
87
+ the parser worker. A `Response` is supported on the main thread because it cannot be forwarded
88
+ reliably to a worker. `initWasm()` optionally accepts the same source when eager initialization
89
+ is useful.
90
+
91
+ ## Fonts
92
+
93
+ Fonts embedded in a `.pptx` are decoded by the Rust parser and registered before the first
94
+ slide is rendered. Applications can also provide licensed/self-hosted fonts. The viewer
95
+ preserves the requested PowerPoint family, then appends Office-compatible and script-complete
96
+ fallbacks for Latin, CJK, Arabic, Hebrew, emoji, and math glyphs.
97
+
98
+ ```tsx
99
+ <ReactPptxViewer
100
+ source={file}
101
+ fonts={{
102
+ sources: [
103
+ { family: 'Brand Sans', source: '/fonts/brand-sans.woff2' },
104
+ {
105
+ family: 'Brand Sans',
106
+ source: '/fonts/brand-sans-bold.woff2',
107
+ descriptors: { weight: '700' },
108
+ },
109
+ ],
110
+ fallbacks: { 'Brand Sans': ['Inter', 'Arial'] },
111
+ fallbackFamilies: ['Noto Sans', 'Noto Sans CJK SC', 'Noto Sans Arabic'],
112
+ }}
113
+ onWarning={(warning) => {
114
+ if (warning.code === 'missing-font') console.warn(warning.message);
115
+ }}
116
+ />
117
+ ```
118
+
119
+ The package does not redistribute proprietary Microsoft fonts. For identical metrics across
120
+ machines, self-host the fonts your license permits or install metric-compatible families such
121
+ as Carlito and Caladea. `resolvePptxFontFamily()` and `OFFICE_FONT_FALLBACKS` are exported for
122
+ applications that also render presentation text outside the viewer.
123
+
124
+ ## Custom scroll areas and hooks
125
+
126
+ Pass the actual scrolling viewport through `virtualization.scrollElement`. This works with
127
+ Radix, Base UI, coss, or any host-owned scroll area and makes that element the observer root.
128
+
129
+ ```tsx
130
+ const [scrollElement, setScrollElement] = useState<HTMLDivElement | null>(null);
131
+ const viewer = usePptxViewer();
132
+ const parsed = usePptxPresentation(file);
133
+
134
+ return (
135
+ <ScrollArea.Root>
136
+ <ScrollArea.Viewport ref={setScrollElement}>
137
+ {parsed.presentation ? (
138
+ <ReactPptxViewer
139
+ ref={viewer.ref}
140
+ source={parsed.presentation}
141
+ virtualization={{ enabled: true, overscanViewport: 2, scrollElement }}
142
+ />
143
+ ) : null}
144
+ </ScrollArea.Viewport>
145
+ </ScrollArea.Root>
146
+ );
147
+ ```
148
+
149
+ `usePptxPresentation` (`usePptxModel` alias) separates parsing from rendering, while
150
+ `usePptxViewer` exposes the reactive navigation/search/zoom controller. `onViewportReady`
151
+ provides the internal slide surface for advanced integrations and test harnesses.
152
+
153
+ ## Thumbnail hook
154
+
155
+ `usePptxViewerThumbnails` exposes detached slide previews for a consumer-owned filmstrip. PPTX
156
+ thumbnails use the same live DOM/SVG renderer and loaded fonts as the main viewer, so attach each
157
+ stable `containerRef` to a regular element rather than a canvas.
158
+
159
+ ```tsx
160
+ import { ReactPptxViewer, usePptxViewer, usePptxViewerThumbnails } from '@extend-ai/react-pptx';
161
+
162
+ export function PresentationWithThumbnails({ file }: { file: ArrayBuffer }) {
163
+ const viewer = usePptxViewer();
164
+ const { thumbnails } = usePptxViewerThumbnails(viewer.controller, {
165
+ resolution: { maxWidth: 180, maxHeight: 120 },
166
+ renderWindow: {
167
+ visibleSlideIndexes: [0, 1, 2],
168
+ prefetchSlideIndexes: [3, 4, 5],
169
+ },
170
+ });
171
+
172
+ return (
173
+ <div style={{ display: 'grid', gridTemplateColumns: '200px 1fr', gap: 16 }}>
174
+ <aside style={{ display: 'grid', gap: 12, alignContent: 'start' }}>
175
+ {thumbnails.map((thumbnail) => (
176
+ <button
177
+ key={thumbnail.slideIndex}
178
+ type="button"
179
+ onClick={() => void viewer.controller?.goToSlide(thumbnail.slideIndex)}
180
+ >
181
+ <div
182
+ ref={thumbnail.containerRef}
183
+ style={{
184
+ width: thumbnail.width,
185
+ height: thumbnail.height,
186
+ overflow: 'hidden',
187
+ }}
188
+ />
189
+ </button>
190
+ ))}
191
+ </aside>
192
+ <ReactPptxViewer ref={viewer.ref} source={file} mode="slide" />
193
+ </div>
194
+ );
195
+ }
196
+ ```
197
+
198
+ The result also provides `renderThumbnail(slideIndex, element)` for imperative integrations and
199
+ `rerenderAttachedThumbnails()` for refreshing every currently attached preview. `disabled` keeps
200
+ the metadata stable while pausing and disposing thumbnail rendering. For large decks, virtualize
201
+ the rail against its actual scroll element: mount `containerRef` only for virtual rows, pass those
202
+ rows through `renderWindow.visibleSlideIndexes`, and use `prefetchSlideIndexes` to warm nearby
203
+ slides. The built-in `showThumbnails` filmstrip follows the same windowed behavior.
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) the respective authors.
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) the respective authors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # Third-party notices
2
+
3
+ ## Chart rendering stack (vendored from @extend-ai/react-xlsx)
4
+
5
+ The chart renderer and chart model builder in `src/charts/` are vendored from
6
+ [`@extend-ai/react-xlsx`](https://github.com/extend-hq/react-xlsx) (MIT) so
7
+ PowerPoint charts render with the same styles and colors as Excel charts.
8
+
9
+ The chart libraries `d3-scale`, `d3-shape`, `d3-hierarchy`, `d3-geo` (ISC,
10
+ Copyright Mike Bostock), `topojson-client` (ISC), and
11
+ [`regl`](https://github.com/regl-project/regl) (MIT) are declared as regular
12
+ package dependencies.
13
+
14
+ The published package bundles the
15
+ [`us-atlas`](https://github.com/topojson/us-atlas) and
16
+ [`world-atlas`](https://github.com/topojson/world-atlas) TopoJSON boundary data
17
+ (ISC License) inside a dynamically imported chunk that only loads when a
18
+ presentation contains an Excel map chart.
19
+
20
+ The published package includes the ISC and MIT license texts at
21
+ `THIRD_PARTY_LICENSES/ISC.txt` and `THIRD_PARTY_LICENSES/MIT.txt`.
22
+
23
+ ## Virtualized scrolling
24
+
25
+ The continuous-scroll mode and React thumbnail rails are windowed with
26
+ [`@tanstack/virtual-core`](https://github.com/TanStack/virtual) and
27
+ [`@tanstack/react-virtual`](https://github.com/TanStack/virtual) (MIT License,
28
+ Copyright (c) 2021-present Tanner Linsley), declared as regular package
29
+ dependencies.
@@ -0,0 +1,50 @@
1
+ // src/charts/region-map-data.ts
2
+ function normalizeRegionMapKey(value) {
3
+ if (typeof value !== "string") {
4
+ return "";
5
+ }
6
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/&/g, " and ").replace(/[^a-zA-Z0-9]+/g, " ").trim().toLowerCase().replace(/\s+/g, " ");
7
+ }
8
+ var REGION_MAP_COUNTRY_ALIASES = /* @__PURE__ */ new Map([
9
+ ["us", "united states of america"],
10
+ ["usa", "united states of america"],
11
+ ["u s a", "united states of america"],
12
+ ["united states", "united states of america"],
13
+ ["united states america", "united states of america"],
14
+ ["u s", "united states of america"],
15
+ ["uk", "united kingdom"],
16
+ ["u k", "united kingdom"],
17
+ ["uae", "united arab emirates"],
18
+ ["u a e", "united arab emirates"],
19
+ ["south korea", "korea, south"],
20
+ ["north korea", "korea, north"],
21
+ ["russia", "russian federation"],
22
+ ["vietnam", "viet nam"],
23
+ ["czech republic", "czechia"],
24
+ ["ivory coast", "cote d'ivoire"],
25
+ ["c\xF4te divoire", "cote d'ivoire"]
26
+ ]);
27
+ var REGION_MAP_US_STATE_ALIASES = /* @__PURE__ */ new Map([
28
+ ["district of columbia", "district of columbia"],
29
+ ["washington dc", "district of columbia"],
30
+ ["washington d c", "district of columbia"],
31
+ ["dc", "district of columbia"],
32
+ ["d c", "district of columbia"]
33
+ ]);
34
+ var loadedAtlas = null;
35
+ var pendingAtlas = null;
36
+ function getRegionMapAtlas() {
37
+ return loadedAtlas;
38
+ }
39
+ function loadRegionMapAtlas() {
40
+ if (loadedAtlas) return Promise.resolve(loadedAtlas);
41
+ pendingAtlas ??= import('./region-map-atlas-S2YXLDE2.js').then((module) => {
42
+ loadedAtlas = module.REGION_MAP_ATLAS;
43
+ return loadedAtlas;
44
+ });
45
+ return pendingAtlas;
46
+ }
47
+
48
+ export { REGION_MAP_COUNTRY_ALIASES, REGION_MAP_US_STATE_ALIASES, getRegionMapAtlas, loadRegionMapAtlas, normalizeRegionMapKey };
49
+ //# sourceMappingURL=chunk-YVOPQP3D.js.map
50
+ //# sourceMappingURL=chunk-YVOPQP3D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/charts/region-map-data.ts"],"names":[],"mappings":";AAoBO,SAAS,sBAAsB,KAAA,EAAwB;AAC5D,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,OAAO,EAAA;AAAA,EACT;AACA,EAAA,OAAO,KAAA,CACJ,UAAU,KAAK,CAAA,CACf,QAAQ,kBAAA,EAAoB,EAAE,CAAA,CAC9B,OAAA,CAAQ,IAAA,EAAM,OAAO,EACrB,OAAA,CAAQ,gBAAA,EAAkB,GAAG,CAAA,CAC7B,IAAA,GACA,WAAA,EAAY,CACZ,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA;AACxB;AAEO,IAAM,0BAAA,uBAAiC,GAAA,CAAoB;AAAA,EAChE,CAAC,MAAM,0BAA0B,CAAA;AAAA,EACjC,CAAC,OAAO,0BAA0B,CAAA;AAAA,EAClC,CAAC,SAAS,0BAA0B,CAAA;AAAA,EACpC,CAAC,iBAAiB,0BAA0B,CAAA;AAAA,EAC5C,CAAC,yBAAyB,0BAA0B,CAAA;AAAA,EACpD,CAAC,OAAO,0BAA0B,CAAA;AAAA,EAClC,CAAC,MAAM,gBAAgB,CAAA;AAAA,EACvB,CAAC,OAAO,gBAAgB,CAAA;AAAA,EACxB,CAAC,OAAO,sBAAsB,CAAA;AAAA,EAC9B,CAAC,SAAS,sBAAsB,CAAA;AAAA,EAChC,CAAC,eAAe,cAAc,CAAA;AAAA,EAC9B,CAAC,eAAe,cAAc,CAAA;AAAA,EAC9B,CAAC,UAAU,oBAAoB,CAAA;AAAA,EAC/B,CAAC,WAAW,UAAU,CAAA;AAAA,EACtB,CAAC,kBAAkB,SAAS,CAAA;AAAA,EAC5B,CAAC,eAAe,eAAe,CAAA;AAAA,EAC/B,CAAC,mBAAgB,eAAe;AAClC,CAAC;AAEM,IAAM,2BAAA,uBAAkC,GAAA,CAAoB;AAAA,EACjE,CAAC,wBAAwB,sBAAsB,CAAA;AAAA,EAC/C,CAAC,iBAAiB,sBAAsB,CAAA;AAAA,EACxC,CAAC,kBAAkB,sBAAsB,CAAA;AAAA,EACzC,CAAC,MAAM,sBAAsB,CAAA;AAAA,EAC7B,CAAC,OAAO,sBAAsB;AAChC,CAAC;AAED,IAAI,WAAA,GAAqC,IAAA;AACzC,IAAI,YAAA,GAA+C,IAAA;AAG5C,SAAS,iBAAA,GAA2C;AACzD,EAAA,OAAO,WAAA;AACT;AAGO,SAAS,kBAAA,GAA8C;AAC5D,EAAA,IAAI,WAAA,EAAa,OAAO,OAAA,CAAQ,OAAA,CAAQ,WAAW,CAAA;AACnD,EAAA,YAAA,KAAiB,OAAO,gCAAoB,CAAA,CAAE,IAAA,CAAK,CAAC,MAAA,KAAW;AAC7D,IAAA,WAAA,GAAc,MAAA,CAAO,gBAAA;AACrB,IAAA,OAAO,WAAA;AAAA,EACT,CAAC,CAAA;AACD,EAAA,OAAO,YAAA;AACT","file":"chunk-YVOPQP3D.js","sourcesContent":["/**\n * Region-map registry shared by the chart renderer and the lazily loaded\n * atlas chunk. The multi-megabyte TopoJSON boundary data only loads when a\n * presentation actually contains an Excel map chart; until then the renderer\n * sees an empty registry and draws the map without shapes.\n */\nimport type { Feature, Geometry } from 'geojson';\n\nexport type RegionMapFeature = Feature<\n Geometry,\n { name?: string; regionSet?: 'country' | 'us-state'; stateCode?: string }\n>;\n\nexport interface RegionMapAtlas {\n worldCountryFeatures: RegionMapFeature[];\n usStateFeatures: RegionMapFeature[];\n countryFeaturesByKey: Map<string, RegionMapFeature>;\n usStateFeaturesByKey: Map<string, RegionMapFeature>;\n}\n\nexport function normalizeRegionMapKey(value: unknown): string {\n if (typeof value !== 'string') {\n return '';\n }\n return value\n .normalize('NFD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .replace(/&/g, ' and ')\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .toLowerCase()\n .replace(/\\s+/g, ' ');\n}\n\nexport const REGION_MAP_COUNTRY_ALIASES = new Map<string, string>([\n ['us', 'united states of america'],\n ['usa', 'united states of america'],\n ['u s a', 'united states of america'],\n ['united states', 'united states of america'],\n ['united states america', 'united states of america'],\n ['u s', 'united states of america'],\n ['uk', 'united kingdom'],\n ['u k', 'united kingdom'],\n ['uae', 'united arab emirates'],\n ['u a e', 'united arab emirates'],\n ['south korea', 'korea, south'],\n ['north korea', 'korea, north'],\n ['russia', 'russian federation'],\n ['vietnam', 'viet nam'],\n ['czech republic', 'czechia'],\n ['ivory coast', \"cote d'ivoire\"],\n ['côte divoire', \"cote d'ivoire\"],\n]);\n\nexport const REGION_MAP_US_STATE_ALIASES = new Map<string, string>([\n ['district of columbia', 'district of columbia'],\n ['washington dc', 'district of columbia'],\n ['washington d c', 'district of columbia'],\n ['dc', 'district of columbia'],\n ['d c', 'district of columbia'],\n]);\n\nlet loadedAtlas: RegionMapAtlas | null = null;\nlet pendingAtlas: Promise<RegionMapAtlas> | null = null;\n\n/** Synchronous accessor used by the renderer; null until the chunk loads. */\nexport function getRegionMapAtlas(): RegionMapAtlas | null {\n return loadedAtlas;\n}\n\n/** Loads the atlas chunk on demand; resolves immediately once cached. */\nexport function loadRegionMapAtlas(): Promise<RegionMapAtlas> {\n if (loadedAtlas) return Promise.resolve(loadedAtlas);\n pendingAtlas ??= import('./region-map-atlas').then((module) => {\n loadedAtlas = module.REGION_MAP_ATLAS;\n return loadedAtlas;\n });\n return pendingAtlas;\n}\n"]}