diff --git a/.changeset/add-layer-control-maplibre.md b/.changeset/add-layer-control-maplibre.md new file mode 100644 index 0000000..767bacf --- /dev/null +++ b/.changeset/add-layer-control-maplibre.md @@ -0,0 +1,6 @@ +--- +"@xtramaps/layer-control-maplibre": minor +"@xtramaps/layer-control-maplibre-react": minor +--- + +Add `@xtramaps/layer-control-maplibre` (core) and `@xtramaps/layer-control-maplibre-react`: the LayerControl component extracted from `ogcapi-html`, supporting nested groups, radio-groups and merge-groups (including `sourceLayer` auto-collection). Legend icons are now provided by `@xtramaps/legend-symbols-maplibre-react` instead of a local copy. Mounted via `@vis.gl/react-maplibre`'s `useControl`/`useMap` and composed explicitly as a child of `@xtramaps/web-map-maplibre-react`'s `MapLibre` component (no dependency between the two packages). diff --git a/.changeset/add-web-map-cesium.md b/.changeset/add-web-map-cesium.md new file mode 100644 index 0000000..e8c30e6 --- /dev/null +++ b/.changeset/add-web-map-cesium.md @@ -0,0 +1,14 @@ +--- +"@xtramaps/web-map-cesium": minor +"@xtramaps/web-map-cesium-react": minor +--- + +Add `@xtramaps/web-map-cesium` (core) and `@xtramaps/web-map-cesium-react`: the Cesium map component migrated from `ogcapi-html`, onto current `@cesium/engine`/`@cesium/widgets` (`^26`/`^16`, up from `^2.3.0`/`^2.2.0`). Same viewer construction (background imagery, terrain), 3D Tileset loading (including the vertical-shift correction math), and no LayerControl/legend equivalent - the original never had one for Cesium either. + +Unlike the OpenLayers/MapLibre migrations, the original `Cesium` component was never a React component - it was a single imperative function called once per page load, with no cleanup, no error handling on its async tileset/style loading, and two previously-dead CSS rules (`style.css` was never imported). `web-map-cesium-react` wraps the same logic in a `useEffect`-driven component and makes three deliberate additions the original didn't have, since a reusable library component can be mounted and unmounted repeatedly (Storybook, demos, consumer apps) unlike a one-shot page script: + +- `viewer.destroy()` runs on unmount +- viewer/tileset creation failures are caught and logged instead of failing silently +- the credit-bar CSS rules are now actually imported and applied + +The component mounts once; prop changes after the initial render aren't reflected, matching the original's single-mount design (Cesium never had a dynamic update path the way OpenLayers/MapLibre's TileMatrixSet switching does). diff --git a/.changeset/add-web-map-maplibre.md b/.changeset/add-web-map-maplibre.md new file mode 100644 index 0000000..4d2b411 --- /dev/null +++ b/.changeset/add-web-map-maplibre.md @@ -0,0 +1,6 @@ +--- +"@xtramaps/web-map-maplibre": minor +"@xtramaps/web-map-maplibre-react": minor +--- + +Add `@xtramaps/web-map-maplibre` (core) and `@xtramaps/web-map-maplibre-react`: the MapLibre map component extracted from `ogcapi-html`, rebuilt on `@vis.gl/react-maplibre` and current `maplibre-gl` (`^6`) instead of the unmaintained `react-maplibre-ui`. Includes style/data-layer helpers, hover/click popups, and a `CanvasOverlay` (formerly `CanvasPlugin`) for rendering arbitrary React children into the map canvas. diff --git a/.changeset/add-web-map-openlayers.md b/.changeset/add-web-map-openlayers.md new file mode 100644 index 0000000..639fbfc --- /dev/null +++ b/.changeset/add-web-map-openlayers.md @@ -0,0 +1,6 @@ +--- +"@xtramaps/web-map-openlayers": minor +"@xtramaps/web-map-openlayers-react": minor +--- + +Add `@xtramaps/web-map-openlayers` (core) and `@xtramaps/web-map-openlayers-react`: the OpenLayers map component migrated 1:1 from `ogcapi-html`, onto current `rlayers` (`3.9.0`) and `ol` (`10.8.0`) instead of the outdated `rlayers@1.1.1`/`ol@6.9.0`. Unlike the MapLibre extraction, this is a pure migration with no new LayerControl/legend equivalent — the original component never had one. The `globalThis._map.setCurrentTileMatrixSet` global bridge is replaced with a `forwardRef`/`useImperativeHandle`-based ref API. diff --git a/.changeset/config.json b/.changeset/config.json index e45f3ba..29c9123 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,7 +3,13 @@ "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], - "linked": [["@xtramaps/legend-symbols-maplibre", "@xtramaps/legend-symbols-maplibre-react", "@xtramaps/legend-symbols-maplibre-svelte", "@xtramaps/legend-symbols-maplibre-vue"]], + "linked": [ + ["@xtramaps/legend-symbols-maplibre", "@xtramaps/legend-symbols-maplibre-react", "@xtramaps/legend-symbols-maplibre-svelte", "@xtramaps/legend-symbols-maplibre-vue"], + ["@xtramaps/web-map-maplibre", "@xtramaps/web-map-maplibre-react"], + ["@xtramaps/layer-control-maplibre", "@xtramaps/layer-control-maplibre-react"], + ["@xtramaps/web-map-openlayers", "@xtramaps/web-map-openlayers-react"], + ["@xtramaps/web-map-cesium", "@xtramaps/web-map-cesium-react"] + ], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..49cd7a5 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,48 @@ +{ + "permissions": { + "allow": [ + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://raw.githubusercontent.com/CesiumGS/3d-tiles-samples/main/tilesets/TilesetWithDiscreteLOD/tileset.json\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://cdn.jsdelivr.net/gh/CesiumGS/3d-tiles-samples@main/tilesets/TilesetWithDiscreteLOD/tileset.json\")", + "Bash(curl -s \"https://demo.ldproxy.net/\")", + "Bash(curl -s \"https://demo.ldproxy.net/daraa/collections?f=json\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://demo.ldproxy.net/daraa/collections/AeronauticSrf/tiles3d\")", + "Bash(curl -s \"https://demo.ldproxy.net/daraa\")", + "Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2?f=json\")", + "Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections?f=json\")", + "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\([c['id'] for c in d['collections']]\\)\")", + "Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json\")", + "Bash(curl -s -w \"\\\\nHTTP:%{http_code}\\\\n\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles/tileset.json\")", + "Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=html\" -o /tmp/globe.html -w \"%{http_code}\\\\n\")", + "Read(//tmp/**)", + "Bash(grep -n *)", + "Read(//private/tmp/**)", + "Bash(curl -sI \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json\")", + "Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type?f=3dtiles\")", + "Bash(curl -sI \"https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type?f=3dtiles\")", + "Bash(curl -sI \"https://sgx.geodatenzentrum.de/wmts_basemapde/tile/1.0.0/de_basemapde_web_raster_farbe/default/GLOBAL_WEBMERCATOR/10/340/550.png\")", + "Bash(curl -sI \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json\" -H \"Origin: http://localhost:6006\")", + "Bash(npx turbo *)", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Cesium.js\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Workers/\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Widgets/widgets.css\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Workers/createVerticesFromHeightmap.js\")", + "Bash(curl -sI \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Workers/createVerticesFromHeightmap.js\")", + "Bash(npx biome *)", + "Bash(npm run *)", + "Bash(curl -sf http://localhost:8080/)", + "Bash(npx playwright *)", + "Bash(node -e \"require\\('playwright'\\); console.log\\('playwright module resolvable'\\)\")", + "Bash(node -e \"require\\('@playwright/test'\\); console.log\\('@playwright/test resolvable'\\)\")", + "Bash(node /private/tmp/claude-501/-Users-pascal-Documents-GitHub-xtramaps/321bbf2b-9197-4bba-af79-5791d6180135/scratchpad/check-cesium-demo.mjs)", + "Bash(node .tmp-check-cesium-demo.mjs)", + "Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" -H \"Accept: application/json\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")", + "Bash(curl -s -H \"Accept: application/json\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")", + "Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")", + "Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" -H \"Accept: application/json, text/plain, */*\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")", + "Bash(curl -s -o /dev/null -w \"style: %{http_code} %{content_type}\\\\n\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type\")", + "Bash(lsof -ti:8080 -sTCP:LISTEN)", + "Bash(npx vitest *)", + "Bash(gh pr *)" + ] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65f7a79..00824e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,3 +19,4 @@ jobs: - run: npm run lint - run: npm run build - run: npm run typecheck + - run: npm run test diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..806d12e --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,25 @@ +import type { StorybookConfig } from "@storybook/react-vite"; + +const config: StorybookConfig = { + stories: ["../packages/react/*/src/**/*.stories.@(ts|tsx)"], + framework: { + name: "@storybook/react-vite", + options: {}, + }, + addons: ["@storybook/addon-vitest"], + async viteFinal(config) { + // maplibre-gl spawns its worker via a relative sibling-file URL + // (maplibre-gl-worker.mjs). Vite's dependency pre-bundler copies packages + // into a content-hashed cache dir and breaks that relative reference, + // so the worker 404s and the map never renders tile data. Excluding + // maplibre-gl from pre-bundling serves it straight from node_modules, + // where the sibling worker file resolves correctly. + config.optimizeDeps = { + ...config.optimizeDeps, + exclude: [...(config.optimizeDeps?.exclude ?? []), "maplibre-gl"], + }; + return config; + }, +}; + +export default config; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 0000000..07a5e4a --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,13 @@ +import type { Preview } from "@storybook/react-vite"; + +const preview: Preview = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default preview; diff --git a/AGENTS.md b/AGENTS.md index c1c2eda..2107574 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,9 @@ Each library has: `package.json`, `tsconfig.json`, `vite.config.ts`, `src/index. - `npm run lint` — lint and format check (Biome) - `npm run lint:fix` — auto-fix lint/format issues - `npm run typecheck` — type-check all packages +- `npm run test` — run all Storybook stories as automated tests (Vitest browser mode, real Chromium via Playwright) +- `npm run storybook` — start the Storybook dev server (root-level, covers all packages) +- `npm run build-storybook` — build a static Storybook ## Key Conventions @@ -43,6 +46,8 @@ Each library has: `package.json`, `tsconfig.json`, `vite.config.ts`, `src/index. - Core deps are listed in `dependencies` (not peers) - Biome handles formatting and linting — no Prettier or ESLint - Changesets for versioning — run `npx changeset` after changes +- Storybook config lives at the repo root (`.storybook/`); stories live next to the code they cover (`packages/react//src/*.stories.tsx`), not in a separate app. `@storybook/addon-vitest` runs every story (including its `play` function) as a Vitest test in real Chromium — no separate test framework needed +- Playwright's Chromium binary installs automatically via the root `prepare` script (`npm install` → `playwright install chromium`) ## Adding a New Library @@ -56,3 +61,4 @@ Each library has: `package.json`, `tsconfig.json`, `vite.config.ts`, `src/index. 1. `npm run lint` passes 2. `npm run build` succeeds 3. `npm run typecheck` passes +4. `npm run test` passes (if stories exist for the changed package) diff --git a/README.md b/README.md index 4beb3c0..33c8c1c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ Monorepo for libraries enhancing web map clients. | `@xtramaps/legend-symbols-maplibre-react` | React bindings | | `@xtramaps/legend-symbols-maplibre-vue` | Vue bindings | | `@xtramaps/legend-symbols-maplibre-svelte` | Svelte bindings | +| `@xtramaps/web-map-maplibre` | MapLibre web map core logic (styles, data layers, popups) | +| `@xtramaps/web-map-maplibre-react` | React MapLibre map component | +| `@xtramaps/layer-control-maplibre` | LayerControl core logic (groups, radio-groups, merge-groups) | +| `@xtramaps/layer-control-maplibre-react` | React LayerControl component | +| `@xtramaps/web-map-openlayers` | OpenLayers web map core logic (projections, view/source switching) | +| `@xtramaps/web-map-openlayers-react` | React OpenLayers map component | +| `@xtramaps/web-map-cesium` | Cesium web map core logic (viewer construction, 3D Tileset loading) | +| `@xtramaps/web-map-cesium-react` | React Cesium map component | ## Setup @@ -23,6 +31,7 @@ npm install npm run build # Build all packages npm run typecheck # Type-check all packages npm run lint # Lint all packages +npm run test # Run every Storybook story as an automated test (Vitest + real Chromium) ``` Turborepo handles task orchestration and caching. To build a single package: @@ -31,6 +40,16 @@ Turborepo handles task orchestration and caching. To build a single package: npx turbo build --filter=@xtramaps/legend-symbols-maplibre ``` +## Trying things out manually + +- **Storybook** (`npm run storybook`, then open `http://localhost:6006`) is the main way to browse and interact with components — currently covers `legend-symbols-maplibre`, `layer-control-maplibre`, `web-map-openlayers`, and `web-map-cesium`. `npm run build-storybook` builds a static version. Storybook's CLI requires Node `>=20.19`; see `engines` in `package.json`. +- Some packages also ship a standalone HTML demo under `examples/*.html` (see that package's README). These aren't published anywhere yet, so they load sibling packages from local `dist/` output instead of a CDN — they only work when served over `http://`, not opened directly as a `file://` URL (browsers block ES module imports there). Any static file server works, e.g. from the repo root: + ```sh + npm run build + python3 -m http.server 8080 + ``` + then open e.g. `http://localhost:8080/packages/react/web-map-maplibre/examples/web-map.html`. + ## Project Structure ``` diff --git a/package-lock.json b/package-lock.json index 5c7193e..af9c408 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,22 +17,38 @@ "@changesets/cli": "^2.30.0", "@commitlint/cli": "20.5.0", "@commitlint/config-conventional": "20.5.0", + "@storybook/addon-vitest": "10.5.7", + "@storybook/react-vite": "10.5.7", + "@vitest/browser": "4.1.10", + "@vitest/browser-playwright": "4.1.10", + "@vitest/coverage-v8": "4.1.10", "lefthook": "2.1.4", "npm-check-updates": "19.6.3", + "playwright": "1.62.1", + "react": "19.2.8", + "react-dom": "19.2.8", + "storybook": "10.5.7", "turbo": "^2.8.16", "typescript": "^5.9.3", "vite": "^8.0.0", - "vite-plugin-dts": "^4.5.4" + "vite-plugin-dts": "^4.5.4", + "vitest": "4.1.10" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -40,10 +56,140 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", "dev": true, "license": "MIT", "engines": { @@ -51,23 +197,39 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", "dev": true, "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.9", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -78,32 +240,63 @@ }, "node_modules/@babel/runtime": { "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", "dev": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.8", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@biomejs/biome": { "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.6.tgz", - "integrity": "sha512-QnHe81PMslpy3mnpL8DnO2M4S4ZnYPkjlGCLWBZT/3R9M6b5daArWMMtEfP52/n174RKnwRIf3oT8+wc9ihSfQ==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -129,8 +322,6 @@ }, "node_modules/@biomejs/cli-darwin-arm64": { "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.6.tgz", - "integrity": "sha512-NW18GSyxr+8sJIqgoGwVp5Zqm4SALH4b4gftIA0n62PTuBs6G2tHlwNAOj0Vq0KKSs7Sf88VjjmHh0O36EnzrQ==", "cpu": [ "arm64" ], @@ -169,6 +360,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -186,6 +380,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -203,6 +400,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -220,6 +420,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -263,10 +466,61 @@ "node": ">=14.21.3" } }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@cesium/engine": { + "version": "26.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@cesium/wasm-splats": "^0.1.0-alpha.2", + "@spz-loader/core": "0.3.1", + "@tweenjs/tween.js": "^25.0.0", + "@zip.js/zip.js": "^2.9.0", + "autolinker": "^4.0.0", + "bitmap-sdf": "^1.0.3", + "dompurify": "^3.4.14", + "draco3d": "^1.5.1", + "earcut": "3.0.2", + "grapheme-splitter": "^1.0.4", + "jsep": "^1.3.8", + "kdbush": "^4.1.0", + "ktx-parse": "^1.0.0", + "lerc": "^2.0.0", + "mersenne-twister": "^1.1.0", + "meshoptimizer": "^1.2.0", + "pako": "^3.0.0", + "protobufjs": "^8.8.0", + "rbush": "^4.0.1", + "topojson-client": "^3.1.0", + "urijs": "^1.19.7" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cesium/wasm-splats": { + "version": "0.1.0-alpha.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@cesium/widgets": { + "version": "16.2.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@cesium/engine": "^26.3.0", + "nosleep.js": "^0.12.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/@changesets/apply-release-plan": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.0.tgz", - "integrity": "sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -287,8 +541,6 @@ }, "node_modules/@changesets/assemble-release-plan": { "version": "6.0.9", - "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.9.tgz", - "integrity": "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==", "dev": true, "license": "MIT", "dependencies": { @@ -302,8 +554,6 @@ }, "node_modules/@changesets/changelog-git": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", - "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -312,8 +562,6 @@ }, "node_modules/@changesets/cli": { "version": "2.30.0", - "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.30.0.tgz", - "integrity": "sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA==", "dev": true, "license": "MIT", "dependencies": { @@ -350,8 +598,6 @@ }, "node_modules/@changesets/config": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.3.tgz", - "integrity": "sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw==", "dev": true, "license": "MIT", "dependencies": { @@ -367,8 +613,6 @@ }, "node_modules/@changesets/errors": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", - "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", "dev": true, "license": "MIT", "dependencies": { @@ -377,8 +621,6 @@ }, "node_modules/@changesets/get-dependents-graph": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.3.tgz", - "integrity": "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -390,8 +632,6 @@ }, "node_modules/@changesets/get-release-plan": { "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.15.tgz", - "integrity": "sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g==", "dev": true, "license": "MIT", "dependencies": { @@ -405,15 +645,11 @@ }, "node_modules/@changesets/get-version-range-type": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", - "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", "dev": true, "license": "MIT" }, "node_modules/@changesets/git": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", - "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", "dev": true, "license": "MIT", "dependencies": { @@ -426,8 +662,6 @@ }, "node_modules/@changesets/logger": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", - "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", "dev": true, "license": "MIT", "dependencies": { @@ -436,8 +670,6 @@ }, "node_modules/@changesets/parse": { "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz", - "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==", "dev": true, "license": "MIT", "dependencies": { @@ -447,8 +679,6 @@ }, "node_modules/@changesets/pre": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", - "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", "dev": true, "license": "MIT", "dependencies": { @@ -460,8 +690,6 @@ }, "node_modules/@changesets/read": { "version": "0.6.7", - "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz", - "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==", "dev": true, "license": "MIT", "dependencies": { @@ -476,8 +704,6 @@ }, "node_modules/@changesets/should-skip-package": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", - "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", "dev": true, "license": "MIT", "dependencies": { @@ -487,15 +713,11 @@ }, "node_modules/@changesets/types": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", - "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", "dev": true, "license": "MIT" }, "node_modules/@changesets/write": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", - "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", "dev": true, "license": "MIT", "dependencies": { @@ -507,8 +729,6 @@ }, "node_modules/@commitlint/cli": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.5.0.tgz", - "integrity": "sha512-yNkyN/tuKTJS3wdVfsZ2tXDM4G4Gi7z+jW54Cki8N8tZqwKBltbIvUUrSbT4hz1bhW/h0CdR+5sCSpXD+wMKaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -529,8 +749,6 @@ }, "node_modules/@commitlint/config-conventional": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.5.0.tgz", - "integrity": "sha512-t3Ni88rFw1XMa4nZHgOKJ8fIAT9M2j5TnKyTqJzsxea7FUetlNdYFus9dz+MhIRZmc16P0PPyEfh6X2d/qw8SA==", "dev": true, "license": "MIT", "dependencies": { @@ -543,8 +761,6 @@ }, "node_modules/@commitlint/config-validator": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.5.0.tgz", - "integrity": "sha512-T/Uh6iJUzyx7j35GmHWdIiGRQB+ouZDk0pwAaYq4SXgB54KZhFdJ0vYmxiW6AMYICTIWuyMxDBl1jK74oFp/Gw==", "dev": true, "license": "MIT", "dependencies": { @@ -557,8 +773,6 @@ }, "node_modules/@commitlint/ensure": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.5.0.tgz", - "integrity": "sha512-IpHqAUesBeW1EDDdjzJeaOxU9tnogLAyXLRBn03SHlj1SGENn2JGZqSWGkFvBJkJzfXAuCNtsoYzax+ZPS+puw==", "dev": true, "license": "MIT", "dependencies": { @@ -575,8 +789,6 @@ }, "node_modules/@commitlint/execute-rule": { "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", - "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", "dev": true, "license": "MIT", "engines": { @@ -585,8 +797,6 @@ }, "node_modules/@commitlint/format": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.5.0.tgz", - "integrity": "sha512-TI9EwFU/qZWSK7a5qyXMpKPPv3qta7FO4tKW+Wt2al7sgMbLWTsAcDpX1cU8k16TRdsiiet9aOw0zpvRXNJu7Q==", "dev": true, "license": "MIT", "dependencies": { @@ -599,8 +809,6 @@ }, "node_modules/@commitlint/is-ignored": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.5.0.tgz", - "integrity": "sha512-JWLarAsurHJhPozbuAH6GbP4p/hdOCoqS9zJMfqwswne+/GPs5V0+rrsfOkP68Y8PSLphwtFXV0EzJ+GTXTTGg==", "dev": true, "license": "MIT", "dependencies": { @@ -613,8 +821,6 @@ }, "node_modules/@commitlint/lint": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.5.0.tgz", - "integrity": "sha512-jiM3hNUdu04jFBf1VgPdjtIPvbuVfDTBAc6L98AWcoLjF5sYqkulBHBzlVWll4rMF1T5zeQFB6r//a+s+BBKlA==", "dev": true, "license": "MIT", "dependencies": { @@ -629,8 +835,6 @@ }, "node_modules/@commitlint/load": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.5.0.tgz", - "integrity": "sha512-sLhhYTL/KxeOTZjjabKDhwidGZan84XKK1+XFkwDYL/4883kIajcz/dZFAhBJmZPtL8+nBx6bnkzA95YxPeDPw==", "dev": true, "license": "MIT", "dependencies": { @@ -650,8 +854,6 @@ }, "node_modules/@commitlint/message": { "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", - "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", "dev": true, "license": "MIT", "engines": { @@ -660,8 +862,6 @@ }, "node_modules/@commitlint/parse": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.5.0.tgz", - "integrity": "sha512-SeKWHBMk7YOTnnEWUhx+d1a9vHsjjuo6Uo1xRfPNfeY4bdYFasCH1dDpAv13Lyn+dDPOels+jP6D2GRZqzc5fA==", "dev": true, "license": "MIT", "dependencies": { @@ -675,8 +875,6 @@ }, "node_modules/@commitlint/read": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.5.0.tgz", - "integrity": "sha512-JDEIJ2+GnWpK8QqwfmW7O42h0aycJEWNqcdkJnyzLD11nf9dW2dWLTVEa8Wtlo4IZFGLPATjR5neA5QlOvIH1w==", "dev": true, "license": "MIT", "dependencies": { @@ -692,8 +890,6 @@ }, "node_modules/@commitlint/resolve-extends": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.5.0.tgz", - "integrity": "sha512-3SHPWUW2v0tyspCTcfSsYml0gses92l6TlogwzvM2cbxDgmhSRc+fldDjvGkCXJrjSM87BBaWYTPWwwyASZRrg==", "dev": true, "license": "MIT", "dependencies": { @@ -710,8 +906,6 @@ }, "node_modules/@commitlint/rules": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.5.0.tgz", - "integrity": "sha512-5NdQXQEdnDPT5pK8O39ZA7HohzPRHEsDGU23cyVCNPQy4WegAbAwrQk3nIu7p2sl3dutPk8RZd91yKTrMTnRkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -726,8 +920,6 @@ }, "node_modules/@commitlint/to-lines": { "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", - "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", "dev": true, "license": "MIT", "engines": { @@ -736,8 +928,6 @@ }, "node_modules/@commitlint/top-level": { "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", - "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", "dev": true, "license": "MIT", "dependencies": { @@ -749,8 +939,6 @@ }, "node_modules/@commitlint/types": { "version": "20.5.0", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.5.0.tgz", - "integrity": "sha512-ZJoS8oSq2CAZEpc/YI9SulLrdiIyXeHb/OGqGrkUP6Q7YV+0ouNAa7GjqRdXeQPncHQIDz/jbCTlHScvYvO/gA==", "dev": true, "license": "MIT", "dependencies": { @@ -763,8 +951,6 @@ }, "node_modules/@conventional-changelog/git-client": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.6.0.tgz", - "integrity": "sha512-T+uPDciKf0/ioNNDpMGc8FDsehJClZP0yR3Q5MN6wE/Y/1QZ7F+80OgznnTCOlMEG4AV0LvH2UJi3C/nBnaBUg==", "dev": true, "license": "MIT", "dependencies": { @@ -789,21 +975,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, @@ -812,9 +998,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -822,335 +1008,1547 @@ "tslib": "^2.4.0" } }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.0.0" + "node": ">=18" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@manypkg/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "@types/node": "^12.7.1", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@manypkg/find-root/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@manypkg/find-root/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6 <7 || >=8" + "node": ">=18" } }, - "node_modules/@manypkg/get-packages": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", - "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.5.5", - "@changesets/types": "^4.0.1", - "@manypkg/find-root": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "^11.0.0", - "read-yaml-file": "^1.1.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", - "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@manypkg/get-packages/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6 <7 || >=8" + "node": ">=18" } }, - "node_modules/@mapbox/jsonlint-lines-primitives": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", - "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/@mapbox/unitbezier": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", - "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==", - "license": "BSD-2-Clause" + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "22.0.1", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-22.0.1.tgz", - "integrity": "sha512-V7bSw7Ui6+NhpeeuYqGoqamvKuy+3+uCvQ/t4ZJkwN8cx527CAlQQQ2kp+w5R9q+Tw6bUAH+fsq+mPEkicgT8g==", - "license": "ISC", - "dependencies": { - "@mapbox/jsonlint-lines-primitives": "~2.0.2", - "@mapbox/unitbezier": "^0.0.1", - "json-stringify-pretty-compact": "^4.0.0", - "minimist": "^1.2.8", - "quickselect": "^3.0.0", - "rw": "^1.3.3", - "tinyqueue": "^3.0.0" - }, - "bin": { - "gl-style-format": "dist/gl-style-format.mjs", - "gl-style-migrate": "dist/gl-style-migrate.mjs", - "gl-style-validate": "dist/gl-style-validate.mjs" + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@microsoft/api-extractor": { - "version": "7.57.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.57.7.tgz", - "integrity": "sha512-kmnmVs32MFWbV5X6BInC1/TfCs7y1ugwxv1xHsAIj/DyUfoe7vtO0alRUgbQa57+yRGHBBjlNcEk33SCAt5/dA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@microsoft/api-extractor-model": "7.33.4", - "@microsoft/tsdoc": "~0.16.0", - "@microsoft/tsdoc-config": "~0.18.1", - "@rushstack/node-core-library": "5.20.3", - "@rushstack/rig-package": "0.7.2", - "@rushstack/terminal": "0.22.3", - "@rushstack/ts-command-line": "5.3.3", - "diff": "~8.0.2", - "lodash": "~4.17.23", - "minimatch": "10.2.3", - "resolve": "~1.22.1", - "semver": "~7.5.4", - "source-map": "~0.6.1", - "typescript": "5.8.2" - }, - "bin": { - "api-extractor": "bin/api-extractor" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@microsoft/api-extractor-model": { - "version": "7.33.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.4.tgz", - "integrity": "sha512-u1LTaNTikZAQ9uK6KG1Ms7nvNedsnODnspq/gH2dcyETWvH4hVNGNDvRAEutH66kAmxA4/necElqGNs1FggC8w==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "~0.16.0", - "@microsoft/tsdoc-config": "~0.18.1", - "@rushstack/node-core-library": "5.20.3" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@microsoft/api-extractor/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^13.0.1", + "react-docgen-typescript": "^2.2.2" + }, + "peerDependencies": { + "typescript": ">= 4.3.x", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@mapbox/geojson-area": { + "version": "0.2.2", + "license": "BSD-2-Clause", + "dependencies": { + "wgs84": "0.0.0" + } + }, + "node_modules/@mapbox/geojson-normalize": { + "version": "0.0.1", + "license": "ISC", + "bin": { + "geojson-normalize": "geojson-normalize" + } + }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.3", + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "node_modules/@mapbox/mapbox-gl-draw": { + "version": "1.5.2", + "license": "ISC", + "dependencies": { + "@mapbox/geojson-area": "^0.2.2", + "@mapbox/geojson-normalize": "^0.0.1", + "@mapbox/point-geometry": "^1.1.0", + "@turf/projection": "^7.2.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^6.0.0" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/@mapbox/mapbox-gl-draw/node_modules/nanoid": { + "version": "6.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^22 || ^24 || >=26" + } + }, + "node_modules/@mapbox/point-geometry": { + "version": "1.1.0", + "license": "ISC" + }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.2.0", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/vector-tile": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "~1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^5.0.0" + } + }, + "node_modules/@maplibre/geojson-vt": { + "version": "6.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "kdbush": "^4.1.0" + } + }, + "node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "22.0.1", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "rw": "^1.3.3", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@maplibre/mlt": { + "version": "1.3.0", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0" + } + }, + "node_modules/@maplibre/vt-pbf": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@types/geojson": "^7946.0.16", + "pbf": "^5.1.0" + } + }, + "node_modules/@microsoft/api-extractor": { + "version": "7.57.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor-model": "7.33.4", + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.20.3", + "@rushstack/rig-package": "0.7.2", + "@rushstack/terminal": "0.22.3", + "@rushstack/ts-command-line": "5.3.3", + "diff": "~8.0.2", + "lodash": "~4.17.23", + "minimatch": "10.2.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.8.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.33.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.20.3" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.8.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.18.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "ajv": "~8.18.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.127.0.tgz", + "integrity": "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.127.0.tgz", + "integrity": "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.127.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.127.0.tgz", + "integrity": "sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.127.0.tgz", + "integrity": "sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.127.0.tgz", + "integrity": "sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.127.0.tgz", + "integrity": "sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.127.0.tgz", + "integrity": "sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.127.0.tgz", + "integrity": "sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.127.0.tgz", + "integrity": "sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.127.0.tgz", + "integrity": "sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.127.0.tgz", + "integrity": "sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.127.0.tgz", + "integrity": "sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.127.0.tgz", + "integrity": "sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.127.0.tgz", + "integrity": "sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.127.0.tgz", + "integrity": "sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.127.0.tgz", + "integrity": "sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.127.0.tgz", + "integrity": "sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.127.0.tgz", + "integrity": "sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.127.0.tgz", + "integrity": "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.120.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz", + "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz", + "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.24.2", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz", + "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz", + "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz", + "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz", + "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz", + "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz", + "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz", + "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz", + "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz", + "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz", + "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz", + "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz", + "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz", + "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz", + "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==", + "cpu": [ + "wasm32" + ], "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": ">=14.17" + "node": ">=14.0.0" } }, - "node_modules/@microsoft/tsdoc": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", - "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@microsoft/tsdoc-config": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz", - "integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==", + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@microsoft/tsdoc": "0.16.0", - "ajv": "~8.18.0", - "jju": "~1.4.0", - "resolve": "~1.22.2" + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "tslib": "^2.4.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "tslib": "^2.4.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", + "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz", + "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@oxc-project/types": { - "version": "0.120.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", - "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "node_modules/@petamoriken/float16": { + "version": "3.9.3", + "license": "MIT" + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "type": "opencollective", + "url": "https://opencollective.com/popperjs" } }, "node_modules/@rolldown/binding-android-arm64": { @@ -1172,8 +2570,6 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", "cpu": [ "arm64" ], @@ -1246,6 +2642,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1263,6 +2662,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1280,6 +2682,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1297,6 +2702,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1314,6 +2722,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1331,6 +2742,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1410,15 +2824,11 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", - "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", "dev": true, "license": "MIT" }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1440,8 +2850,6 @@ }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -1453,8 +2861,6 @@ }, "node_modules/@rushstack/node-core-library": { "version": "5.20.3", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.20.3.tgz", - "integrity": "sha512-95JgEPq2k7tHxhF9/OJnnyHDXfC9cLhhta0An/6MlkDsX2A6dTzDrTUG18vx4vjc280V0fi0xDH9iQczpSuWsw==", "dev": true, "license": "MIT", "dependencies": { @@ -1478,8 +2884,6 @@ }, "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "dependencies": { @@ -1493,8 +2897,6 @@ }, "node_modules/@rushstack/node-core-library/node_modules/jsonfile": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -1506,8 +2908,6 @@ }, "node_modules/@rushstack/node-core-library/node_modules/semver": { "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "license": "ISC", "dependencies": { @@ -1522,8 +2922,6 @@ }, "node_modules/@rushstack/node-core-library/node_modules/universalify": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { @@ -1532,8 +2930,6 @@ }, "node_modules/@rushstack/problem-matcher": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.2.1.tgz", - "integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1547,8 +2943,6 @@ }, "node_modules/@rushstack/rig-package": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.2.tgz", - "integrity": "sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A==", "dev": true, "license": "MIT", "dependencies": { @@ -1558,8 +2952,6 @@ }, "node_modules/@rushstack/terminal": { "version": "0.22.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.22.3.tgz", - "integrity": "sha512-gHC9pIMrUPzAbBiI4VZMU7Q+rsCzb8hJl36lFIulIzoceKotyKL3Rd76AZ2CryCTKEg+0bnTj406HE5YY5OQvw==", "dev": true, "license": "MIT", "dependencies": { @@ -1578,8 +2970,6 @@ }, "node_modules/@rushstack/ts-command-line": { "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.3.tgz", - "integrity": "sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==", "dev": true, "license": "MIT", "dependencies": { @@ -1591,8 +2981,6 @@ }, "node_modules/@rushstack/ts-command-line/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -1601,8 +2989,6 @@ }, "node_modules/@simple-libs/child-process-utils": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", - "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", "dev": true, "license": "MIT", "dependencies": { @@ -1617,8 +3003,6 @@ }, "node_modules/@simple-libs/stream-utils": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", - "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", "dev": true, "license": "MIT", "engines": { @@ -1628,10 +3012,209 @@ "url": "https://ko-fi.com/dangreen" } }, + "node_modules/@spz-loader/core": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16", + "pnpm": ">=8" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/addon-vitest": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@vitest/browser": "^3.0.0 || ^4.0.0", + "@vitest/browser-playwright": "^4.0.0", + "@vitest/runner": "^3.0.0 || ^4.0.0", + "storybook": "^10.5.7", + "vitest": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/runner": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-vite": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/csf-plugin": "10.5.7", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.5.7", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "dependencies": { + "unplugin": "^2.3.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "esbuild": "*", + "rollup": "*", + "storybook": "^10.5.7", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/icons": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@storybook/react": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/react-dom-shim": "10.5.7", + "react-docgen": "^8.0.2", + "react-docgen-typescript": "^2.2.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.5.7", + "typescript": ">= 4.9.x" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.5.7" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/react-vite": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", + "@rollup/pluginutils": "^5.0.2", + "@storybook/builder-vite": "10.5.7", + "@storybook/react": "10.5.7", + "empathic": "^2.0.0", + "magic-string": "^0.30.0", + "react-docgen": "^8.0.2", + "resolve": "^1.22.8", + "tsconfig-paths": "^4.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.5.7", + "typescript": ">= 4.9.x", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", - "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1640,8 +3223,6 @@ }, "node_modules/@sveltejs/vite-plugin-svelte": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.0.0.tgz", - "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", "dev": true, "license": "MIT", "dependencies": { @@ -1658,6 +3239,67 @@ "vite": "^8.0.0-beta.7 || ^8.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@turbo/darwin-64": { "version": "2.8.20", "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.8.20.tgz", @@ -1674,8 +3316,6 @@ }, "node_modules/@turbo/darwin-arm64": { "version": "2.8.20", - "resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.8.20.tgz", - "integrity": "sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw==", "cpu": [ "arm64" ], @@ -1742,10 +3382,90 @@ "win32" ] }, + "node_modules/@turf/bbox": { + "version": "7.4.0", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.4.0", + "@turf/meta": "7.4.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "7.4.0", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.4.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/combine": { + "version": "7.4.0", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.4.0", + "@turf/meta": "7.4.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.4.0", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "7.4.0", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.4.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "7.4.0", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.4.0", + "@turf/helpers": "7.4.0", + "@turf/meta": "7.4.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, @@ -1755,22 +3475,90 @@ }, "node_modules/@types/argparse": { "version": "1.0.38", - "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", - "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/doctrine": { + "version": "0.0.9", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "license": "MIT" + }, + "node_modules/@types/mapbox__mapbox-gl-draw": { + "version": "1.4.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "mapbox-gl": "*" + } + }, "node_modules/@types/node": { "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "peer": true, @@ -1778,10 +3566,12 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/rbush": { + "version": "4.0.0", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", "dependencies": { @@ -1790,25 +3580,24 @@ }, "node_modules/@types/react-dom": { "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, + "node_modules/@types/resolve": { + "version": "1.20.6", + "dev": true, + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/types": { "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz", - "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==", "dev": true, "license": "MIT", "engines": { @@ -1819,10 +3608,49 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vis.gl/react-maplibre": { + "version": "8.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^19.2.1" + }, + "peerDependencies": { + "maplibre-gl": ">=4.0.0", + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + }, + "peerDependenciesMeta": { + "maplibre-gl": { + "optional": true + } + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "19.3.3", + "dev": true, + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "~2.0.2", + "@mapbox/unitbezier": "^0.0.1", + "json-stringify-pretty-compact": "^3.0.0", + "minimist": "^1.2.8", + "rw": "^1.3.3", + "sort-object": "^3.0.3" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/@vis.gl/react-maplibre/node_modules/json-stringify-pretty-compact": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", - "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1845,41 +3673,254 @@ } } }, - "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.7", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", - "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitejs/plugin-vue/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/browser": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.10" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.10", + "@vitest/mocker": "4.1.10", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/spy": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz", - "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==", + "node_modules/@vitest/utils": { + "version": "4.1.10", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitejs/plugin-vue/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", - "dev": true, - "license": "MIT" - }, "node_modules/@volar/language-core": { "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1888,15 +3929,11 @@ }, "node_modules/@volar/source-map": { "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", - "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", "dev": true, "license": "MIT", "dependencies": { @@ -1907,8 +3944,6 @@ }, "node_modules/@vue/compiler-core": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz", - "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", "dev": true, "license": "MIT", "dependencies": { @@ -1921,8 +3956,6 @@ }, "node_modules/@vue/compiler-dom": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", - "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", "dev": true, "license": "MIT", "dependencies": { @@ -1932,8 +3965,6 @@ }, "node_modules/@vue/compiler-sfc": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", - "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", "dev": true, "license": "MIT", "dependencies": { @@ -1950,8 +3981,6 @@ }, "node_modules/@vue/compiler-ssr": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", - "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", "dev": true, "license": "MIT", "dependencies": { @@ -1961,8 +3990,6 @@ }, "node_modules/@vue/compiler-vue2": { "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", "dev": true, "license": "MIT", "dependencies": { @@ -1972,8 +3999,6 @@ }, "node_modules/@vue/language-core": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", - "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", "dev": true, "license": "MIT", "dependencies": { @@ -1997,15 +4022,11 @@ }, "node_modules/@vue/language-core/node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/@vue/language-core/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2014,8 +4035,6 @@ }, "node_modules/@vue/language-core/node_modules/minimatch": { "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { @@ -2030,8 +4049,6 @@ }, "node_modules/@vue/reactivity": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz", - "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2040,8 +4057,6 @@ }, "node_modules/@vue/runtime-core": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz", - "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", "dev": true, "license": "MIT", "dependencies": { @@ -2051,8 +4066,6 @@ }, "node_modules/@vue/runtime-dom": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", - "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", "dev": true, "license": "MIT", "dependencies": { @@ -2064,8 +4077,6 @@ }, "node_modules/@vue/server-renderer": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz", - "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2078,11 +4089,22 @@ }, "node_modules/@vue/shared": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz", - "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", "dev": true, "license": "MIT" }, + "node_modules/@webcontainer/env": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@xtramaps/layer-control-maplibre": { + "resolved": "packages/core/layer-control-maplibre", + "link": true + }, + "node_modules/@xtramaps/layer-control-maplibre-react": { + "resolved": "packages/react/layer-control-maplibre", + "link": true + }, "node_modules/@xtramaps/legend-symbols-maplibre": { "resolved": "packages/core/legend-symbols-maplibre", "link": true @@ -2099,10 +4121,50 @@ "resolved": "packages/vue/legend-symbols-maplibre", "link": true }, + "node_modules/@xtramaps/web-map-cesium": { + "resolved": "packages/core/web-map-cesium", + "link": true + }, + "node_modules/@xtramaps/web-map-cesium-react": { + "resolved": "packages/react/web-map-cesium", + "link": true + }, + "node_modules/@xtramaps/web-map-maplibre": { + "resolved": "packages/core/web-map-maplibre", + "link": true + }, + "node_modules/@xtramaps/web-map-maplibre-react": { + "resolved": "packages/react/web-map-maplibre", + "link": true + }, + "node_modules/@xtramaps/web-map-openlayers": { + "resolved": "packages/core/web-map-openlayers", + "link": true + }, + "node_modules/@xtramaps/web-map-openlayers-react": { + "resolved": "packages/react/web-map-openlayers", + "link": true + }, + "node_modules/@zarrita/storage": { + "version": "0.1.4", + "license": "MIT", + "dependencies": { + "reference-spec-reader": "^0.2.0", + "unzipit": "1.4.3" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.18.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=18.0.0" + } + }, "node_modules/acorn": { "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -2114,8 +4176,6 @@ }, "node_modules/ajv": { "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -2131,8 +4191,6 @@ }, "node_modules/ajv-draft-04": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2146,8 +4204,6 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2164,15 +4220,11 @@ }, "node_modules/alien-signals": { "version": "0.4.14", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", - "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", "dev": true, "license": "MIT" }, "node_modules/ansi-colors": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", "engines": { @@ -2181,8 +4233,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -2191,8 +4241,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -2207,42 +4255,101 @@ }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, + "node_modules/arr-union": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-ify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true, "license": "MIT" }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.16.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/autolinker": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + }, + "engines": { + "pnpm": ">=10.10.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2251,18 +4358,25 @@ }, "node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.26", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/better-path-resolve": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", - "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", "dev": true, "license": "MIT", "dependencies": { @@ -2272,10 +4386,21 @@ "node": ">=4" } }, + "node_modules/bidi-js": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bitmap-sdf": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { @@ -2287,8 +4412,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -2298,27 +4421,130 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.29.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytewise": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "node_modules/bytewise-core": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2" + } + }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001813", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chardet": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, + "node_modules/check-error": { + "version": "2.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2332,8 +4558,6 @@ }, "node_modules/clsx": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true, "license": "MIT", "engines": { @@ -2342,8 +4566,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2355,15 +4577,16 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", "dev": true, "license": "MIT" }, "node_modules/compare-func": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "license": "MIT", "dependencies": { @@ -2373,22 +4596,16 @@ }, "node_modules/compare-versions": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", "dev": true, "license": "MIT" }, "node_modules/confbox": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "dev": true, "license": "MIT" }, "node_modules/conventional-changelog-angular": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz", - "integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==", "dev": true, "license": "ISC", "dependencies": { @@ -2400,8 +4617,6 @@ }, "node_modules/conventional-changelog-conventionalcommits": { "version": "9.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.0.tgz", - "integrity": "sha512-kYFx6gAyjSIMwNtASkI3ZE99U1fuVDJr0yTYgVy+I2QG46zNZfl2her+0+eoviG82c5WQvW1jMt1eOQTeJLodA==", "dev": true, "license": "ISC", "dependencies": { @@ -2413,8 +4628,6 @@ }, "node_modules/conventional-commits-parser": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", - "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", "dev": true, "license": "MIT", "dependencies": { @@ -2428,10 +4641,13 @@ "node": ">=18" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2457,8 +4673,6 @@ }, "node_modules/cosmiconfig-typescript-loader": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", - "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2475,8 +4689,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -2488,52 +4700,99 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/de-indent": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, "engines": { - "node": ">=6.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/dequal": { + "version": "2.0.3", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, "node_modules/detect-indent": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", "dev": true, "license": "MIT", "engines": { @@ -2542,8 +4801,6 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2552,15 +4809,11 @@ }, "node_modules/devalue": { "version": "5.6.4", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", - "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", "dev": true, "license": "MIT" }, "node_modules/diff": { "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2569,8 +4822,6 @@ }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "license": "MIT", "dependencies": { @@ -2580,10 +4831,40 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.4.16", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dot-prop": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2593,17 +4874,35 @@ "node": ">=8" } }, + "node_modules/draco3d": { + "version": "1.5.7", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/earcut": { + "version": "3.0.2", + "license": "ISC" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.442", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/enquirer": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2616,8 +4915,6 @@ }, "node_modules/entities": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -2629,8 +4926,6 @@ }, "node_modules/env-paths": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { @@ -2639,18 +4934,59 @@ }, "node_modules/error-ex": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -2659,15 +4995,11 @@ }, "node_modules/esm-env": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "dev": true, "license": "MIT" }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -2680,8 +5012,6 @@ }, "node_modules/esrap": { "version": "2.2.4", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.4.tgz", - "integrity": "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg==", "dev": true, "license": "MIT", "dependencies": { @@ -2691,36 +5021,52 @@ }, "node_modules/estree-walker": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, "license": "MIT" }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exsolve": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", "dev": true, "license": "MIT" }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/extendable-error": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", - "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", "dev": true, "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2736,8 +5082,6 @@ }, "node_modules/fast-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -2753,18 +5097,18 @@ }, "node_modules/fastq": { "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.3", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -2776,8 +5120,6 @@ }, "node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -2790,8 +5132,6 @@ }, "node_modules/fs-extra": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", "dependencies": { @@ -2805,10 +5145,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -2820,28 +5157,73 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/geotiff": { + "version": "3.0.5", + "license": "MIT", + "dependencies": { + "@petamoriken/float16": "^3.9.3", + "lerc": "^3.0.0", + "pako": "^2.0.4", + "parse-headers": "^2.0.2", + "quick-lru": "^6.1.1", + "web-worker": "^1.5.0", + "xml-utils": "^1.10.2", + "zstddec": "^0.2.0" + }, + "engines": { + "node": ">=10.19" + } + }, + "node_modules/geotiff/node_modules/lerc": { + "version": "3.0.0", + "license": "Apache-2.0" + }, + "node_modules/geotiff/node_modules/pako": { + "version": "2.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-value": { + "version": "2.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/git-raw-commits": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz", - "integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2855,10 +5237,29 @@ "node": ">=18" } }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "dev": true, + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { @@ -2870,8 +5271,6 @@ }, "node_modules/global-directory": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2886,8 +5285,6 @@ }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2907,15 +5304,16 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -2924,8 +5322,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2937,18 +5333,19 @@ }, "node_modules/he": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", "bin": { "he": "bin/he" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, "node_modules/human-id": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.3.tgz", - "integrity": "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==", "dev": true, "license": "MIT", "bin": { @@ -2957,8 +5354,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -2974,8 +5369,6 @@ }, "node_modules/ignore": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -2984,8 +5377,6 @@ }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3001,8 +5392,6 @@ }, "node_modules/import-fresh/node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -3011,8 +5400,6 @@ }, "node_modules/import-lazy": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true, "license": "MIT", "engines": { @@ -3021,8 +5408,6 @@ }, "node_modules/import-meta-resolve": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", "funding": { @@ -3030,10 +5415,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ini": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "dev": true, "license": "ISC", "engines": { @@ -3042,15 +5433,11 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -3063,10 +5450,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { @@ -3075,8 +5482,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -3085,8 +5490,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { @@ -3096,10 +5499,25 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -3108,71 +5526,134 @@ }, "node_modules/is-obj": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/is-subdir": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@types/estree": "^1.0.6" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/is-subdir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", - "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", "dev": true, "license": "MIT", "dependencies": { - "better-path-resolve": "1.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "node_modules/istanbul-reports": { + "version": "3.2.0", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, "node_modules/jiti": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { @@ -3181,22 +5662,15 @@ }, "node_modules/jju": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true, "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -3206,47 +5680,80 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-pretty-compact": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", - "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "dev": true, "license": "MIT" }, "node_modules/jsonfile": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } }, + "node_modules/kdbush": { + "version": "4.1.0", + "dev": true, + "license": "ISC" + }, "node_modules/kolorist": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ktx-parse": { + "version": "1.1.0", "dev": true, "license": "MIT" }, "node_modules/lefthook": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-2.1.4.tgz", - "integrity": "sha512-JNfJ5gAn0KADvJ1I6/xMcx70+/6TL6U9gqGkKvPw5RNMfatC7jIg0Evl97HN846xmfz959BV70l8r3QsBJk30w==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3268,8 +5775,6 @@ }, "node_modules/lefthook-darwin-arm64": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-2.1.4.tgz", - "integrity": "sha512-BUAAE9+rUrjr39a+wH/1zHmGrDdwUQ2Yq/z6BQbM/yUb9qtXBRcQ5eOXxApqWW177VhGBpX31aqIlfAZ5Q7wzw==", "cpu": [ "arm64" ], @@ -3406,10 +5911,13 @@ "win32" ] }, + "node_modules/lerc": { + "version": "2.0.0", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -3459,8 +5967,6 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -3549,6 +6055,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3570,6 +6079,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3591,6 +6103,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3612,6 +6127,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3669,15 +6187,11 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, "node_modules/local-pkg": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", "dev": true, "license": "MIT", "dependencies": { @@ -3694,15 +6208,11 @@ }, "node_modules/locate-character": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "dev": true, "license": "MIT" }, "node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -3714,57 +6224,61 @@ }, "node_modules/lodash": { "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true, "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", "dev": true, "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", "dev": true, "license": "MIT" }, "node_modules/lodash.startcase": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", "dev": true, "license": "MIT" }, "node_modules/lodash.upperfirst": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.1", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "license": "ISC", "dependencies": { @@ -3774,20 +6288,123 @@ "node": ">=10" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mapbox-gl": { + "version": "3.32.0", + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "workspaces": [ + "src/style-spec", + "plugins/mapbox-gl-pmtiles-provider", + "test/bundlers/*", + "test/build/typings" + ] + }, + "node_modules/mapbox-to-css-font": { + "version": "3.2.1", + "license": "BSD-2-Clause" + }, + "node_modules/maplibre-gl": { + "version": "6.11.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@mapbox/point-geometry": "^1.1.0", + "@mapbox/tiny-sdf": "^2.2.0", + "@mapbox/unitbezier": "^1.0.0", + "@mapbox/vector-tile": "^3.0.0", + "@maplibre/geojson-vt": "^6.1.1", + "@maplibre/maplibre-gl-style-spec": "^26.4.4", + "@maplibre/mlt": "^1.3.0", + "@maplibre/vt-pbf": "^4.3.2", + "@types/geojson": "^7946.0.16", + "bidi-js": "^1.1.0", + "earcut": "^3.2.3", + "gl-matrix": "^3.4.4", + "kdbush": "^4.1.0", + "murmurhash-js": "^1.0.0", + "pbf": "^5.1.2", + "potpack": "^2.1.0", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "engines": { + "node": ">=16.14.0", + "npm": ">=8.1.0" + }, + "funding": { + "url": "https://github.com/maplibre/maplibre-gl-js?sponsor=1" + } + }, + "node_modules/maplibre-gl/node_modules/@mapbox/unitbezier": { + "version": "1.0.0", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/maplibre-gl/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "26.4.4", + "dev": true, + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.3", + "@mapbox/unitbezier": "^1.0.0", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/maplibre-gl/node_modules/earcut": { + "version": "3.2.4", + "dev": true, + "license": "ISC" + }, "node_modules/meow": { "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", "engines": { @@ -3799,18 +6416,28 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", "engines": { "node": ">= 8" } }, + "node_modules/mersenne-twister": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/meshoptimizer": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/mgrs": { + "version": "1.0.0", + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -3821,10 +6448,16 @@ "node": ">=8.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "10.2.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", - "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3839,17 +6472,21 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mlly": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", - "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "dev": true, "license": "MIT", "dependencies": { @@ -3861,15 +6498,11 @@ }, "node_modules/mlly/node_modules/confbox": { "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "dev": true, "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3880,32 +6513,37 @@ }, "node_modules/mri": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/muggle-string": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/murmurhash-js": { + "version": "1.0.0", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3921,10 +6559,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-releases": { + "version": "2.0.57", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/nosleep.js": { + "version": "0.12.0", + "dev": true, + "license": "MIT" + }, "node_modules/npm-check-updates": { "version": "19.6.3", - "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.6.3.tgz", - "integrity": "sha512-VAt9Bp26eLaymZ0nZyh5n/by+YZIuegXlvWR0yv1zBqd984f8VnEnBbn+1lS3nN5LyEjn62BJ+yYgzNSpb6Gzg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3936,10 +6585,22 @@ "npm": ">=8.12.1" } }, + "node_modules/numcodecs": { + "version": "0.3.2", + "license": "MIT", + "dependencies": { + "fflate": "^0.8.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/obug": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -3947,17 +6608,164 @@ ], "license": "MIT" }, + "node_modules/ol": { + "version": "10.8.0", + "license": "BSD-2-Clause", + "dependencies": { + "@types/rbush": "4.0.0", + "earcut": "^3.0.0", + "geotiff": "^3.0.2", + "pbf": "4.0.1", + "rbush": "^4.0.0", + "zarrita": "^0.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/openlayers" + } + }, + "node_modules/ol-mapbox-style": { + "version": "13.5.1", + "license": "BSD-2-Clause", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^26.4.1", + "mapbox-to-css-font": "^3.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ahocevar" + }, + "peerDependencies": { + "ol": "*" + } + }, + "node_modules/ol-mapbox-style/node_modules/@mapbox/unitbezier": { + "version": "1.0.0", + "license": "BSD-2-Clause" + }, + "node_modules/ol-mapbox-style/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "26.4.4", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.3", + "@mapbox/unitbezier": "^1.0.0", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "node_modules/ol/node_modules/pbf": { + "version": "4.0.1", + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, + "node_modules/open": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/outdent": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", - "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", "dev": true, "license": "MIT" }, + "node_modules/oxc-parser": { + "version": "0.127.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.127.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.127.0", + "@oxc-parser/binding-android-arm64": "0.127.0", + "@oxc-parser/binding-darwin-arm64": "0.127.0", + "@oxc-parser/binding-darwin-x64": "0.127.0", + "@oxc-parser/binding-freebsd-x64": "0.127.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.127.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.127.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.127.0", + "@oxc-parser/binding-linux-arm64-musl": "0.127.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.127.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.127.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.127.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.127.0", + "@oxc-parser/binding-linux-x64-gnu": "0.127.0", + "@oxc-parser/binding-linux-x64-musl": "0.127.0", + "@oxc-parser/binding-openharmony-arm64": "0.127.0", + "@oxc-parser/binding-wasm32-wasi": "0.127.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.127.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.127.0", + "@oxc-parser/binding-win32-x64-msvc": "0.127.0" + } + }, + "node_modules/oxc-parser/node_modules/@oxc-project/types": { + "version": "0.127.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/oxc-resolver": { + "version": "11.24.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.24.2", + "@oxc-resolver/binding-android-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-x64": "11.24.2", + "@oxc-resolver/binding-freebsd-x64": "11.24.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-musl": "11.24.2", + "@oxc-resolver/binding-openharmony-arm64": "11.24.2", + "@oxc-resolver/binding-wasm32-wasi": "11.24.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" + } + }, "node_modules/p-filter": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", - "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", "dev": true, "license": "MIT", "dependencies": { @@ -3969,8 +6777,6 @@ }, "node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3985,8 +6791,6 @@ }, "node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -3998,8 +6802,6 @@ }, "node_modules/p-map": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true, "license": "MIT", "engines": { @@ -4008,8 +6810,6 @@ }, "node_modules/p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", "engines": { @@ -4018,18 +6818,29 @@ }, "node_modules/package-manager-detector": { "version": "0.2.11", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", - "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", "dev": true, "license": "MIT", "dependencies": { "quansync": "^0.2.7" } }, + "node_modules/pako": { + "version": "3.0.2", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -4039,10 +6850,12 @@ "node": ">=6" } }, + "node_modules/parse-headers": { + "version": "2.0.6", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -4060,15 +6873,11 @@ }, "node_modules/path-browserify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", "dev": true, "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -4077,8 +6886,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -4087,15 +6894,34 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", "engines": { @@ -4104,22 +6930,35 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pbf": { + "version": "5.1.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -4131,8 +6970,6 @@ }, "node_modules/pify": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, "license": "MIT", "engines": { @@ -4141,8 +6978,6 @@ }, "node_modules/pkg-types": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", "dev": true, "license": "MIT", "dependencies": { @@ -4151,10 +6986,59 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.62.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postcss": { "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { @@ -4180,10 +7064,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/potpack": { + "version": "2.1.0", + "dev": true, + "license": "ISC" + }, "node_modules/prettier": { "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", "bin": { @@ -4196,10 +7083,79 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proj4": { + "version": "2.22.0", + "license": "MIT", + "dependencies": { + "mgrs": "1.0.0", + "wkt-parser": "^1.5.5" + }, + "funding": { + "url": "https://github.com/sponsors/ahocevar" + }, + "peerDependencies": { + "geotiff": "*" + }, + "peerDependenciesMeta": { + "geotiff": { + "optional": true + } + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "8.8.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.1", + "license": "MIT" + }, "node_modules/quansync": { "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", "dev": true, "funding": [ { @@ -4215,8 +7171,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -4234,39 +7188,126 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "6.1.2", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/quickselect": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, + "node_modules/rbush": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "quickselect": "^3.0.0" + } + }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "dev": true, + "version": "19.2.8", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "node_modules/react-docgen": { + "version": "8.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@types/babel__core": "^7.20.5", + "@types/babel__traverse": "^7.20.7", + "@types/doctrine": "^0.0.9", + "@types/resolve": "^1.20.2", + "doctrine": "^3.0.0", + "resolve": "^1.22.1", + "strip-indent": "^4.0.0" + }, + "engines": { + "node": "^20.9.0 || >=22" + } + }, + "node_modules/react-docgen-typescript": { + "version": "2.4.0", "dev": true, "license": "MIT", + "peerDependencies": { + "typescript": ">= 4.3.x" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.8" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "license": "MIT" + }, + "node_modules/react-is": { + "version": "17.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reactstrap": { + "version": "9.2.3", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@popperjs/core": "^2.6.0", + "classnames": "^2.2.3", + "prop-types": "^15.5.8", + "react-popper": "^2.2.4", + "react-transition-group": "^4.4.2" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/reactstrap/node_modules/react-popper": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" } }, "node_modules/read-yaml-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", - "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", "dev": true, "license": "MIT", "dependencies": { @@ -4281,8 +7322,6 @@ }, "node_modules/read-yaml-file/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -4291,8 +7330,6 @@ }, "node_modules/read-yaml-file/node_modules/js-yaml": { "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -4303,10 +7340,50 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/recast": { + "version": "0.23.21", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reference-spec-reader": { + "version": "0.2.0", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -4315,8 +7392,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { @@ -4325,8 +7400,6 @@ }, "node_modules/resolve": { "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4346,18 +7419,21 @@ }, "node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -4365,10 +7441,29 @@ "node": ">=0.10.0" } }, + "node_modules/rlayers": { + "version": "3.9.0", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.0.0" + }, + "peerDependencies": { + "ol": "=10.8.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rlayers/node_modules/lru-cache": { + "version": "11.5.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/rolldown": { "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", - "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", "dev": true, "license": "MIT", "dependencies": { @@ -4399,10 +7494,19 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -4425,28 +7529,19 @@ }, "node_modules/rw": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/scheduler": { "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -4456,10 +7551,22 @@ "node": ">=10" } }, + "node_modules/set-value": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -4471,18 +7578,19 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", "engines": { @@ -4492,20 +7600,61 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sirv": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/sort-asc": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-desc": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-object": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "bytewise": "^1.1.0", + "get-value": "^2.0.2", + "is-extendable": "^0.1.1", + "sort-asc": "^0.2.0", + "sort-desc": "^0.2.0", + "union-value": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4514,8 +7663,6 @@ }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4524,8 +7671,6 @@ }, "node_modules/spawndamnit": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", - "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", "dev": true, "license": "SEE LICENSE IN LICENSE", "dependencies": { @@ -4533,17 +7678,115 @@ "signal-exit": "^4.0.1" } }, + "node_modules/split-string": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/storybook": { + "version": "10.5.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.2", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/user-event": "^14.6.1", + "@vitest/expect": "3.2.4", + "@vitest/spy": "3.2.4", + "@webcontainer/env": "^1.1.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", + "jsonc-parser": "^3.3.1", + "open": "^10.2.0", + "oxc-parser": "^0.127.0", + "oxc-resolver": "^11.19.1", + "recast": "^0.23.5", + "semver": "^7.7.3", + "use-sync-external-store": "^1.5.0", + "ws": "^8.21.1" + }, + "bin": { + "storybook": "dist/bin/dispatcher.js" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "prettier": "^2 || ^3", + "vite-plus": "^0.1.15 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "prettier": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/storybook/node_modules/@vitest/spy": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/string-argv": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, "license": "MIT", "engines": { @@ -4552,8 +7795,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -4567,8 +7808,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -4580,18 +7819,25 @@ }, "node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/strip-indent": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", "engines": { @@ -4603,8 +7849,6 @@ }, "node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -4619,8 +7863,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { @@ -4632,8 +7874,6 @@ }, "node_modules/svelte": { "version": "5.54.0", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.54.0.tgz", - "integrity": "sha512-TTDxwYnHkova6Wsyj1PGt9TByuWqvMoeY1bQiuAf2DM/JeDSMw7FjRKzk8K/5mJ99vGOKhbCqTDpyAKwjp4igg==", "dev": true, "license": "MIT", "dependencies": { @@ -4660,8 +7900,6 @@ }, "node_modules/term-size": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", "dev": true, "license": "MIT", "engines": { @@ -4671,10 +7909,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "dev": true, "license": "MIT", "engines": { @@ -4683,8 +7929,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4700,8 +7944,6 @@ }, "node_modules/tinyglobby/node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -4718,8 +7960,6 @@ }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -4731,14 +7971,26 @@ }, "node_modules/tinyqueue": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-3.0.0.tgz", - "integrity": "sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==", "license": "ISC" }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4748,18 +8000,54 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/turbo": { "version": "2.8.20", - "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.8.20.tgz", - "integrity": "sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ==", "dev": true, "license": "MIT", "bin": { @@ -4776,47 +8064,145 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typewise": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "typewise-core": "^1.2.0" + } + }, + "node_modules/typewise-core": { + "version": "1.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.3", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/union-value": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin/node_modules/picomatch": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unzipit": { + "version": "1.4.3", + "license": "MIT", + "dependencies": { + "uzip-module": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" }, - "engines": { - "node": ">=14.17" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "node_modules/urijs": { + "version": "1.19.11", "dev": true, "license": "MIT" }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/use-sync-external-store": { + "version": "1.7.0", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/uzip-module": { + "version": "1.0.3", + "license": "MIT" + }, "node_modules/vite": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", - "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", "dev": true, "license": "MIT", "dependencies": { @@ -4893,8 +8279,6 @@ }, "node_modules/vite-plugin-dts": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", - "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", "dev": true, "license": "MIT", "dependencies": { @@ -4920,8 +8304,6 @@ }, "node_modules/vite/node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -4933,8 +8315,6 @@ }, "node_modules/vitefu": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", - "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", "dev": true, "license": "MIT", "workspaces": [ @@ -4951,17 +8331,136 @@ } } }, + "node_modules/vitest": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vscode-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "dev": true, "license": "MIT" }, "node_modules/vue": { "version": "3.5.30", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz", - "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", "dev": true, "license": "MIT", "dependencies": { @@ -4980,10 +8479,28 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/web-worker": { + "version": "1.5.0", + "license": "Apache-2.0" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/wgs84": { + "version": "0.0.0", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -4996,10 +8513,30 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wkt-parser": { + "version": "1.5.6", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ahocevar" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5014,10 +8551,46 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/ws": { + "version": "8.22.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-utils": { + "version": "1.10.2", + "license": "CC0-1.0" + }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -5026,15 +8599,11 @@ }, "node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -5052,21 +8621,38 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/zarrita": { + "version": "0.6.2", + "license": "MIT", + "dependencies": { + "@zarrita/storage": "^0.1.4", + "numcodecs": "^0.3.2" + } + }, "node_modules/zimmerframe": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "dev": true, "license": "MIT" }, + "node_modules/zstddec": { + "version": "0.2.0", + "license": "MIT AND BSD-3-Clause" + }, + "packages/core/layer-control-maplibre": { + "name": "@xtramaps/layer-control-maplibre", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^22.0.0", + "@xtramaps/legend-symbols-maplibre": "1.0.0" + } + }, "packages/core/legend-symbols-maplibre": { "name": "@xtramaps/legend-symbols-maplibre", "version": "1.0.0", @@ -5075,6 +8661,98 @@ "@maplibre/maplibre-gl-style-spec": "^22.0.0" } }, + "packages/core/web-map-cesium": { + "name": "@xtramaps/web-map-cesium", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@cesium/engine": "^26.2.0", + "@cesium/widgets": "^16.1.1" + }, + "peerDependencies": { + "@cesium/engine": "^26.0.0", + "@cesium/widgets": "^16.0.0" + } + }, + "packages/core/web-map-maplibre": { + "name": "@xtramaps/web-map-maplibre", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^26.0.0", + "@turf/bbox": "7.4.0" + }, + "devDependencies": { + "maplibre-gl": "^6.3.0" + }, + "peerDependencies": { + "maplibre-gl": "^6.0.0" + } + }, + "packages/core/web-map-maplibre/node_modules/@mapbox/unitbezier": { + "version": "1.0.0", + "license": "BSD-2-Clause" + }, + "packages/core/web-map-maplibre/node_modules/@maplibre/maplibre-gl-style-spec": { + "version": "26.4.4", + "license": "ISC", + "dependencies": { + "@mapbox/jsonlint-lines-primitives": "^2.0.3", + "@mapbox/unitbezier": "^1.0.0", + "json-stringify-pretty-compact": "^4.0.0", + "minimist": "^1.2.8", + "quickselect": "^3.0.0", + "tinyqueue": "^3.0.0" + }, + "bin": { + "gl-style-format": "dist/gl-style-format.mjs", + "gl-style-migrate": "dist/gl-style-migrate.mjs", + "gl-style-validate": "dist/gl-style-validate.mjs" + } + }, + "packages/core/web-map-openlayers": { + "name": "@xtramaps/web-map-openlayers", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "ol-mapbox-style": "^13.4.2", + "proj4": "^2.21.0" + }, + "devDependencies": { + "ol": "10.8.0" + }, + "peerDependencies": { + "ol": "10.8.0" + } + }, + "packages/react/layer-control-maplibre": { + "name": "@xtramaps/layer-control-maplibre-react", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@xtramaps/layer-control-maplibre": "1.0.0", + "@xtramaps/legend-symbols-maplibre-react": "1.0.0", + "reactstrap": "^9.2.3" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vis.gl/react-maplibre": "^8.1.2", + "@vitejs/plugin-react": "^6.0.0", + "@xtramaps/web-map-maplibre-react": "1.0.0", + "maplibre-gl": "^6.3.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "storybook": "10.5.7" + }, + "peerDependencies": { + "@vis.gl/react-maplibre": "^8.1.2", + "maplibre-gl": "^6.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "packages/react/legend-symbols-maplibre": { "name": "@xtramaps/legend-symbols-maplibre-react", "version": "1.0.0", @@ -5083,17 +8761,98 @@ "@xtramaps/legend-symbols-maplibre": "1.0.0" }, "devDependencies": { + "@maplibre/maplibre-gl-style-spec": "^22.0.0", + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "storybook": "10.5.7" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "packages/react/web-map-cesium": { + "name": "@xtramaps/web-map-cesium-react", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@xtramaps/web-map-cesium": "1.0.0" + }, + "devDependencies": { + "@cesium/engine": "^26.2.0", + "@cesium/widgets": "^16.1.1", + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "storybook": "10.5.7" + }, + "peerDependencies": { + "@cesium/engine": "^26.0.0", + "@cesium/widgets": "^16.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "packages/react/web-map-maplibre": { + "name": "@xtramaps/web-map-maplibre-react", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@mapbox/mapbox-gl-draw": "^1.5.0", + "@turf/combine": "^7.2.0", + "@xtramaps/web-map-maplibre": "1.0.0" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/mapbox__mapbox-gl-draw": "^1.4.8", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "@vis.gl/react-maplibre": "^8.1.2", "@vitejs/plugin-react": "^6.0.0", + "maplibre-gl": "^6.3.0", "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "storybook": "10.5.7" }, "peerDependencies": { + "@vis.gl/react-maplibre": "^8.1.2", + "maplibre-gl": "^6.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, + "packages/react/web-map-openlayers": { + "name": "@xtramaps/web-map-openlayers-react", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@xtramaps/web-map-openlayers": "1.0.0" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "ol": "10.8.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "rlayers": "^3.9.0", + "storybook": "10.5.7" + }, + "peerDependencies": { + "ol": "10.8.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "rlayers": "^3.9.0" + } + }, "packages/svelte/legend-symbols-maplibre": { "name": "@xtramaps/legend-symbols-maplibre-svelte", "version": "1.0.0", diff --git a/package.json b/package.json index d92bc73..5bf288e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "url": "https://github.com/ldproxy/xtramaps.git" }, "packageManager": "npm@11.11.1", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, "workspaces": [ "packages/core/*", "packages/react/*", @@ -22,20 +25,33 @@ "changeset": "changeset", "version-packages": "changeset version", "release": "turbo build && changeset publish", - "prepare": "lefthook install", - "upgrade": "ncu --workspaces --root --interactive --format group" + "prepare": "lefthook install && playwright install chromium", + "upgrade": "ncu --workspaces --root --interactive --format group", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build", + "test": "vitest run" }, "devDependencies": { "@biomejs/biome": "2.4.6", "@changesets/cli": "^2.30.0", + "@storybook/addon-vitest": "10.5.7", + "@storybook/react-vite": "10.5.7", + "@vitest/browser": "4.1.10", + "@vitest/browser-playwright": "4.1.10", + "@vitest/coverage-v8": "4.1.10", "@commitlint/cli": "20.5.0", "@commitlint/config-conventional": "20.5.0", "lefthook": "2.1.4", "npm-check-updates": "19.6.3", + "playwright": "1.62.1", + "react": "19.2.8", + "react-dom": "19.2.8", + "storybook": "10.5.7", "turbo": "^2.8.16", "typescript": "^5.9.3", "vite": "^8.0.0", - "vite-plugin-dts": "^4.5.4" + "vite-plugin-dts": "^4.5.4", + "vitest": "4.1.10" }, "commitlint": { "extends": [ diff --git a/packages/core/layer-control-maplibre/README.md b/packages/core/layer-control-maplibre/README.md new file mode 100644 index 0000000..adc6780 --- /dev/null +++ b/packages/core/layer-control-maplibre/README.md @@ -0,0 +1,31 @@ +# @xtramaps/layer-control-maplibre + +Framework-agnostic core logic for the LayerControl component: parses a nested group / radio-group / merge-group configuration (or a style's own embedded `metadata["ldproxy:layerControl"]`) against a MapLibre style into a hydrated, ready-to-render tree. Extracted from `ogcapi-html`'s `LayerControl` component. + +## Install + +```sh +npm install @xtramaps/layer-control-maplibre +``` + +Uses [`@xtramaps/legend-symbols-maplibre`](https://github.com/ldproxy/xtramaps/tree/main/packages/core/legend-symbols-maplibre)'s `loadSprites()` for sprite loading. + +## Usage + +```ts +import { parse, initialCfg } from "@xtramaps/layer-control-maplibre"; + +const config = await parse(style, entries, /* preferStyle */ true); +// config.entries -> hydrated tree ready to render +// config.deps / depsParent / depsChild -> selection-cascade dependency maps +``` + +### Exports + +- `parse()` - the main entry point: hydrates `entries` against a style's layers and loads sprites +- `getIds()`, `getDeps()`, `getParentDeps()`, `getChildDeps()`, `getRadioGroups()`, `asLayer()`, `getId()`, `getLabel()` - tree-walking helpers +- `initialCfg` - empty default config, useful as initial React/Vue/Svelte state + +## License + +MIT diff --git a/packages/core/layer-control-maplibre/package.json b/packages/core/layer-control-maplibre/package.json new file mode 100644 index 0000000..c90176d --- /dev/null +++ b/packages/core/layer-control-maplibre/package.json @@ -0,0 +1,38 @@ +{ + "name": "@xtramaps/layer-control-maplibre", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/core/layer-control-maplibre#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/core/layer-control-maplibre" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@xtramaps/legend-symbols-maplibre": "1.0.0", + "@maplibre/maplibre-gl-style-spec": "^22.0.0" + } +} diff --git a/packages/core/layer-control-maplibre/src/config.ts b/packages/core/layer-control-maplibre/src/config.ts new file mode 100644 index 0000000..ec46dea --- /dev/null +++ b/packages/core/layer-control-maplibre/src/config.ts @@ -0,0 +1,364 @@ +import { loadSprites } from "@xtramaps/legend-symbols-maplibre"; +import type { + HydratedEntry, + LayerControlConfig, + LayerControlEntry, + StyleWithSpriteLoaded, +} from "./types"; + +/** + * Loose recursive tree-node shape shared by the config-parsing helpers below - entries + * are either plain layer-id strings or objects that may recurse via `entries`, + * `subLayers` or `layers` (the latter two never occur in the current data model but are + * preserved 1:1 from the source implementation, which supported them defensively). + */ +type EntryLike = + | string + | (Record & { id?: string; type?: string }); + +export const getLabel = (entry: EntryLike): string => + typeof entry === "string" + ? entry + : (entry.label as string) || (entry.id as string); + +export const getId = (entry: EntryLike): string => + typeof entry === "string" ? entry : (entry.id as string); + +export const asLayer = (entry: EntryLike): Record => + typeof entry === "string" + ? { id: entry, isLayer: true, type: "layer" } + : { ...entry, isLayer: true, type: "layer" }; + +export const getRadioGroups = ( + groups: EntryLike[], + selected?: boolean, +): Record => { + const radioGroups: Record = {}; + + groups + .filter( + (g): g is Record => + typeof g !== "string" && g.isBasemap === true, + ) + .forEach((g) => { + const entries = g.entries as EntryLike[] | undefined; + const id = g.id as string; + if (selected) { + radioGroups[id] = + entries && entries.length > 0 ? getId(entries[0]) : null; + } else { + radioGroups[id] = entries ? entries.map((e) => getId(e)) : []; + } + }); + + return radioGroups; +}; + +const getRadioGroupLayers = ( + groups: EntryLike[], + selectedOnly?: boolean, +): Record => { + const radioGroups: Record = {}; + + groups + .filter( + (g): g is Record => + typeof g !== "string" && g.type === "radio-group", + ) + .forEach((g) => { + const entries = g.entries as EntryLike[] | undefined; + const id = g.id as string; + if (selectedOnly) { + radioGroups[id] = + entries && entries.length > 0 ? getId(entries[0]) : null; + } else { + radioGroups[id] = entries ? entries.map((e) => getId(e)) : []; + } + }); + + return radioGroups; +}; + +export const getIds = (entries: EntryLike[], types?: string[]): string[] => { + const ids: string[] = []; + + entries.forEach((e) => { + const type = typeof e === "string" ? undefined : e.type; + if (!types || types.includes(type as string)) { + ids.push(getId(e)); + } + const children = + typeof e === "string" + ? undefined + : (e.entries as EntryLike[] | undefined); + if (children) { + getIds(children, types).forEach((id) => { + ids.push(id); + }); + } + }); + + return ids; +}; + +/** Every entry (string or object) reduces to a plain id string - preserved from `g.id || g`. */ +const idOf = (g: EntryLike): string => + typeof g === "string" ? g : (g.id as string); + +const childrenOf = (g: EntryLike): EntryLike[] => + typeof g === "string" + ? [] + : ((g.entries || g.subLayers || g.layers || []) as EntryLike[]); + +export const getDeps = ( + groups: EntryLike[], + parents: string[] = [], +): Record => { + const deps: Record = {}; + + groups.forEach((g) => { + const id = idOf(g); + const childDeps = getDeps(childrenOf(g), parents.concat([id])); + deps[id] = [ + ...new Set(parents.concat([id]).concat(Object.values(childDeps).flat())), + ]; + Object.keys(childDeps).forEach((c) => { + deps[c] = childDeps[c]; + }); + }); + + return deps; +}; + +export const getParentDeps = ( + groups: EntryLike[], + parents: string[] = [], +): Record => { + const deps: Record = {}; + + groups.forEach((g) => { + const id = idOf(g); + const childDeps = getParentDeps(childrenOf(g), [id].concat(parents)); + deps[id] = parents.filter((id2) => id2 !== id); + Object.keys(childDeps).forEach((c) => { + deps[c] = childDeps[c]; + }); + }); + + return deps; +}; + +export function getChildDeps(groups: EntryLike[]): Record; +export function getChildDeps( + groups: EntryLike[], + tmp: true, +): { clean: Record; tmp: Record }; +export function getChildDeps( + groups: EntryLike[], + tmp?: boolean, +): + | Record + | { clean: Record; tmp: Record } { + const deps: { + clean: Record; + tmp: Record; + } = { + clean: {}, + tmp: {}, + }; + + groups.forEach((g) => { + const id = idOf(g); + const childDeps = getChildDeps(childrenOf(g), true); + deps.tmp[id] = + Object.keys(childDeps.tmp).length > 0 + ? [...new Set([id].concat(Object.values(childDeps.tmp).flat()))] + : [id]; + deps.clean[id] = deps.tmp[id].filter((id2) => id2 !== id); + Object.keys(childDeps.clean).forEach((c) => { + deps.clean[c] = childDeps.clean[c]; + }); + }); + + return tmp ? deps : deps.clean; +} + +const getLayers = ( + style: StyleWithSpriteLoaded | null, +): Record> => + style?.layers + ? style.layers.reduce( + (layers: Record>, layer, index) => { + layers[layer.id] = { + id: layer.id, + isLayer: true, + type: "layer", + sourceLayer: (layer as { "source-layer"?: string })["source-layer"], + index, + }; + return layers; + }, + {}, + ) + : {}; + +const getLayersFor = ( + sourceLayer: string, + layers: Record>, +): Record[] => + Object.values(layers) + .filter((layer) => layer.sourceLayer === sourceLayer) + .slice() + .sort((layer) => layer.index as number); + +const enrichLayer = ( + target: Record, + layers: Record>, +): Record => + Object.hasOwn(layers, target.id as string) + ? { ...target, ...layers[target.id as string] } + : target; + +const mergeLayers = ( + from: Record[], + into: Record[], +): Record[] => { + return from.map((layer) => { + const matching = into.find((layer2) => layer2.id === layer.id); + if (matching) { + return { ...matching, ...layer }; + } + return layer; + }); +}; + +const entryTypes = ["merge-group", "radio-group", "group", "layer", undefined]; + +const validate = (entry: EntryLike): boolean => { + if (typeof entry !== "string" && !Object.hasOwn(entry, "id")) { + // eslint-disable-next-line no-console + console.error( + "Invalid entry, only strings or objects with id allowed.", + entry, + ); + return false; + } + if ( + !entryTypes.includes(typeof entry === "string" ? undefined : entry.type) + ) { + // eslint-disable-next-line no-console + console.error( + "Invalid entry, unknown type:", + typeof entry === "string" ? undefined : entry.type, + ); + return false; + } + return true; +}; + +const hydrate = ( + entries: EntryLike[], + layers: Record>, +): Record[] => { + return entries + .map((entry) => { + if (!validate(entry)) { + return null; + } + if ( + typeof entry !== "string" && + entry.type === "merge-group" && + entry.sourceLayer + ) { + const matching = getLayersFor(entry.sourceLayer as string, layers); + return { + ...entry, + entries: entry.entries + ? mergeLayers( + matching, + hydrate(entry.entries as EntryLike[], layers), + ) + : matching, + }; + } + if (typeof entry !== "string" && entry.entries) { + return { + ...entry, + entries: hydrate(entry.entries as EntryLike[], layers), + }; + } + if (typeof entry !== "string" && (entry.layers || entry.subLayers)) { + return entry; + } + return enrichLayer(asLayer(entry), layers); + }) + .filter((entry): entry is Record => entry !== null); +}; + +export const parse = async ( + style: StyleWithSpriteLoaded, + entriesCfg: LayerControlEntry[], + preferStyle?: boolean, +): Promise => { + const styleMetadataCfg = + preferStyle && + style.metadata && + (style.metadata as Record)["ldproxy:layerControl"] + ? ((style.metadata as Record)[ + "ldproxy:layerControl" + ] as { + opened?: boolean; + onlyLegend?: boolean; + entries?: LayerControlEntry[]; + }) + : ({} as { + opened?: boolean; + onlyLegend?: boolean; + entries?: LayerControlEntry[]; + }); + + const { opened, onlyLegend, entries = entriesCfg } = styleMetadataCfg; + const layers = getLayers(style); + const hydrated = hydrate(entries as EntryLike[], layers); + + const config: LayerControlConfig = { + opened, + onlyLegend, + entries: hydrated as unknown as HydratedEntry[], + allIds: getIds(hydrated as EntryLike[]), + layerIds: getIds(hydrated as EntryLike[], ["layer"]), + groupIds: getIds(hydrated as EntryLike[], ["group", "radio-group"]), + radioIds: getRadioGroupLayers(hydrated as EntryLike[], true) as Record< + string, + string | null + >, + radioGroups: getRadioGroupLayers(hydrated as EntryLike[]) as Record< + string, + string[] + >, + deps: getDeps(hydrated as EntryLike[]), + depsParent: getParentDeps(hydrated as EntryLike[]), + depsChild: getChildDeps(hydrated as EntryLike[]), + style, + }; + + const sprite = await loadSprites(style); + if (sprite) { + style.spriteLoaded = sprite; + } + + return config; +}; + +export const initialCfg: LayerControlConfig = { + entries: [], + allIds: [], + layerIds: [], + groupIds: [], + radioIds: {}, + radioGroups: {}, + deps: {}, + depsParent: {}, + depsChild: {}, + style: null, +}; diff --git a/packages/core/layer-control-maplibre/src/index.ts b/packages/core/layer-control-maplibre/src/index.ts new file mode 100644 index 0000000..c768c1b --- /dev/null +++ b/packages/core/layer-control-maplibre/src/index.ts @@ -0,0 +1,27 @@ +export { + asLayer, + getChildDeps, + getDeps, + getId, + getIds, + getLabel, + getParentDeps, + getRadioGroups, + initialCfg, + parse, +} from "./config"; +export type { + GroupEntry, + HydratedEntry, + HydratedGroup, + HydratedLayer, + HydratedMergeGroup, + HydratedRadioGroup, + LayerControlConfig, + LayerControlEntry, + LayerEntry, + LayerEntryObject, + MergeGroupEntry, + RadioGroupEntry, + StyleWithSpriteLoaded, +} from "./types"; diff --git a/packages/core/layer-control-maplibre/src/types.ts b/packages/core/layer-control-maplibre/src/types.ts new file mode 100644 index 0000000..36a38b3 --- /dev/null +++ b/packages/core/layer-control-maplibre/src/types.ts @@ -0,0 +1,102 @@ +import type { StyleSpecification } from "@maplibre/maplibre-gl-style-spec"; +import type { SpriteData } from "@xtramaps/legend-symbols-maplibre"; + +export interface LayerEntryObject { + id: string; + label?: string; + zoom?: number; + properties?: Record; +} + +export type LayerEntry = string | LayerEntryObject; + +export interface RadioGroupEntry { + id: string; + label?: string; + type: "radio-group"; + entries?: LayerEntry[]; +} + +export interface MergeGroupEntry { + id: string; + label?: string; + type: "merge-group"; + sourceLayer?: string; + entries?: LayerEntry[]; +} + +export interface GroupEntry { + id: string; + label?: string; + type: "group"; + onlyLegend?: boolean; + entries: LayerControlEntry[]; +} + +export type LayerControlEntry = + | LayerEntry + | RadioGroupEntry + | MergeGroupEntry + | GroupEntry; + +export interface HydratedLayer { + id: string; + label?: string; + zoom?: number; + properties?: Record; + isLayer: true; + type: "layer"; + sourceLayer?: string; + index?: number; + onlyLegend?: boolean; +} + +export interface HydratedRadioGroup { + id: string; + label?: string; + type: "radio-group"; + entries: HydratedLayer[]; + onlyLegend?: boolean; +} + +export interface HydratedMergeGroup { + id: string; + label?: string; + type: "merge-group"; + sourceLayer?: string; + entries: HydratedLayer[]; + onlyLegend?: boolean; +} + +export interface HydratedGroup { + id: string; + label?: string; + type: "group"; + onlyLegend?: boolean; + entries: HydratedEntry[]; +} + +export type HydratedEntry = + | HydratedLayer + | HydratedRadioGroup + | HydratedMergeGroup + | HydratedGroup; + +export interface StyleWithSpriteLoaded extends StyleSpecification { + spriteLoaded?: SpriteData; +} + +export interface LayerControlConfig { + opened?: boolean; + onlyLegend?: boolean; + entries: HydratedEntry[]; + allIds: string[]; + layerIds: string[]; + groupIds: string[]; + radioIds: Record; + radioGroups: Record; + deps: Record; + depsParent: Record; + depsChild: Record; + style: StyleWithSpriteLoaded | null; +} diff --git a/packages/core/layer-control-maplibre/tsconfig.json b/packages/core/layer-control-maplibre/tsconfig.json new file mode 100644 index 0000000..e1b7131 --- /dev/null +++ b/packages/core/layer-control-maplibre/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/core/layer-control-maplibre/vite.config.ts b/packages/core/layer-control-maplibre/vite.config.ts new file mode 100644 index 0000000..ab50432 --- /dev/null +++ b/packages/core/layer-control-maplibre/vite.config.ts @@ -0,0 +1,8 @@ +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + external: [ + "@xtramaps/legend-symbols-maplibre", + "@maplibre/maplibre-gl-style-spec", + ], +}); diff --git a/packages/core/web-map-cesium/README.md b/packages/core/web-map-cesium/README.md new file mode 100644 index 0000000..5469a56 --- /dev/null +++ b/packages/core/web-map-cesium/README.md @@ -0,0 +1,43 @@ +# @xtramaps/web-map-cesium + +Framework-agnostic core logic for the Cesium web map component: viewer construction (background imagery, terrain) and 3D Tileset loading. Migrated from `ogcapi-html`'s `Cesium` component onto current `@cesium/engine`/`@cesium/widgets` (`^26`/`^16`). + +Unlike the MapLibre/OpenLayers migrations, the original `Cesium` component was never a React component - it was a single imperative function called once per page load. This package ports that same imperative logic (viewer + tileset construction) so it can be called from any framework; [`@xtramaps/web-map-cesium-react`](https://github.com/ldproxy/xtramaps/tree/main/packages/react/web-map-cesium) wraps it in a `useEffect`-driven React component. + +## Install + +```sh +npm install @xtramaps/web-map-cesium +``` + +Requires `@cesium/engine` (`^26.0.0`) and `@cesium/widgets` (`^16.0.0`) as peer dependencies. + +## Usage + +```ts +import { createViewer, loadTileset } from "@xtramaps/web-map-cesium"; + +const viewer = createViewer(container, { + backgroundUrl: + "https://sgx.geodatenzentrum.de/wmts_basemapde/tile/1.0.0/de_basemapde_web_raster_farbe/default/GLOBAL_WEBMERCATOR/{TileMatrix}/{TileRow}/{TileCol}.png", + attribution: "Geobasis NRW | © basemap.de / BKG", + accessToken: null, +}); + +await loadTileset( + viewer, + { url: "https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json" }, + "https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type?f=3dtiles", +); + +// call viewer.destroy() when done with it - the original never needed to (single page-lifetime mount) +``` + +### Exports + +- `createViewer()` - builds a Cesium `Viewer` with the background imagery layer and, optionally, a terrain provider (Ion asset `1` / Cesium World Terrain by default if no `terrainProvider.url` is given) +- `loadTileset()` - loads a 3D Tileset, applies the optional vertical-shift correction (`terrainHeightDifference`) and an optional external 3D Tiles style, then flies the camera to it. Unlike the original (fire-and-forget, no error handling), failures are caught and logged instead of producing an unhandled rejection + +## License + +MIT diff --git a/packages/core/web-map-cesium/package.json b/packages/core/web-map-cesium/package.json new file mode 100644 index 0000000..7e851c2 --- /dev/null +++ b/packages/core/web-map-cesium/package.json @@ -0,0 +1,42 @@ +{ + "name": "@xtramaps/web-map-cesium", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/core/web-map-cesium#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/core/web-map-cesium" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "peerDependencies": { + "@cesium/engine": "^26.0.0", + "@cesium/widgets": "^16.0.0" + }, + "devDependencies": { + "@cesium/engine": "^26.2.0", + "@cesium/widgets": "^16.1.1" + } +} diff --git a/packages/core/web-map-cesium/src/index.ts b/packages/core/web-map-cesium/src/index.ts new file mode 100644 index 0000000..83c6a56 --- /dev/null +++ b/packages/core/web-map-cesium/src/index.ts @@ -0,0 +1,9 @@ +export { loadTileset } from "./tileset"; +export type { + CesiumExtent, + CesiumMapOptions, + CesiumTerrainHeightDifference, + CesiumTerrainProviderConfig, + CesiumTilesetConfig, +} from "./types"; +export { createViewer } from "./viewer"; diff --git a/packages/core/web-map-cesium/src/tileset.ts b/packages/core/web-map-cesium/src/tileset.ts new file mode 100644 index 0000000..7fe39d0 --- /dev/null +++ b/packages/core/web-map-cesium/src/tileset.ts @@ -0,0 +1,59 @@ +import { + Cartesian3, + Cesium3DTileStyle, + Cesium3DTileset, + Color, + Matrix4, +} from "@cesium/engine"; +import type { Viewer } from "@cesium/widgets"; +import type { CesiumTilesetConfig } from "./types"; + +/** + * Loads a 3D Tileset into `viewer`, ported from ldproxy's ogcapi-html `Cesium` + * component. Unlike the original (fire-and-forget, no `.catch`), failures are + * logged instead of failing silently - a reusable component can be mounted + * more than once and shouldn't leave an unhandled rejection behind. + */ +export async function loadTileset( + viewer: Viewer, + config: CesiumTilesetConfig, + additionalStyleUrl?: string, +): Promise { + const { url, terrainHeightDifference, outlineColor } = config; + + const options: Cesium3DTileset.ConstructorOptions = { + outlineColor: outlineColor + ? (Color[outlineColor as keyof typeof Color] as Color) + : Color.DIMGREY, + }; + + if (terrainHeightDifference) { + const { difference, centerLon, centerLat, centerHeight } = + terrainHeightDifference; + options.modelMatrix = Matrix4.fromTranslation( + Cartesian3.subtract( + Cartesian3.fromRadians(centerLon, centerLat, centerHeight + difference), + Cartesian3.fromRadians(centerLon, centerLat, centerHeight), + new Cartesian3(), + ), + ); + } + + try { + const tileset = await Cesium3DTileset.fromUrl(url, options); + + if (additionalStyleUrl) { + const style = await fetch(additionalStyleUrl).then((response) => + response.json(), + ); + tileset.style = new Cesium3DTileStyle(style); + } else { + tileset.style = new Cesium3DTileStyle({}); + } + + viewer.scene.primitives.add(tileset); + viewer.flyTo(tileset); + } catch (error) { + console.error("[web-map-cesium] Failed to load tileset:", error); + } +} diff --git a/packages/core/web-map-cesium/src/types.ts b/packages/core/web-map-cesium/src/types.ts new file mode 100644 index 0000000..fd43951 --- /dev/null +++ b/packages/core/web-map-cesium/src/types.ts @@ -0,0 +1,35 @@ +export interface CesiumExtent { + minLon: number; + minLat: number; + maxLon: number; + maxLat: number; +} + +export interface CesiumTerrainHeightDifference { + difference: number; + centerLon: number; + centerLat: number; + centerHeight: number; +} + +export interface CesiumTilesetConfig { + url: string; + terrainHeightDifference?: CesiumTerrainHeightDifference; + outlineColor?: string; +} + +export interface CesiumTerrainProviderConfig { + url?: string; + credit?: string; + requestVertexNormals?: boolean; +} + +export interface CesiumMapOptions { + backgroundUrl: string; + attribution?: string; + extent?: CesiumExtent; + accessToken?: string | null; + tileset?: CesiumTilesetConfig; + terrainProvider?: CesiumTerrainProviderConfig; + additionalStyleUrl?: string; +} diff --git a/packages/core/web-map-cesium/src/viewer.ts b/packages/core/web-map-cesium/src/viewer.ts new file mode 100644 index 0000000..98ac8ca --- /dev/null +++ b/packages/core/web-map-cesium/src/viewer.ts @@ -0,0 +1,91 @@ +import { + Camera, + CesiumTerrainProvider, + Credit, + ImageryLayer, + Ion, + IonResource, + Rectangle, + WebMapTileServiceImageryProvider, +} from "@cesium/engine"; +import { Viewer } from "@cesium/widgets"; +import type { CesiumMapOptions } from "./types"; + +/** + * Mirrors ldproxy's ogcapi-html `Cesium` component. `Ion.defaultAccessToken` and + * `Camera.DEFAULT_VIEW_RECTANGLE`/`DEFAULT_VIEW_FACTOR` are mutated here because + * they're global statics on the Cesium API itself, not per-viewer state. + * + * Async because `CesiumTerrainProvider.fromUrl`/`IonResource.fromAssetId` are + * promise-based in current `@cesium/engine` - the original's synchronous + * `new CesiumTerrainProvider({ url, ... })` no longer exists. + */ +export async function createViewer( + container: string | HTMLElement, + options: CesiumMapOptions, +): Promise { + const { backgroundUrl, attribution, extent, accessToken, terrainProvider } = + options; + + Ion.defaultAccessToken = accessToken ?? ""; + + if (extent) { + Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees( + extent.minLon, + extent.minLat, + extent.maxLon, + extent.maxLat, + ); + Camera.DEFAULT_VIEW_FACTOR = 0; + } + + const baseLayer = new ImageryLayer( + new WebMapTileServiceImageryProvider({ + url: backgroundUrl, + layer: "Base", + style: "default", + tileMatrixSetID: "WebMercatorQuad", + }), + ); + + let viewer: Viewer; + if (terrainProvider) { + const { url, credit, ...rest } = terrainProvider; + viewer = new Viewer(container, { + baseLayer, + terrainProvider: await CesiumTerrainProvider.fromUrl( + url ?? (await IonResource.fromAssetId(1)), + { + ...rest, + credit: credit ? new Credit(credit, true) : undefined, + }, + ), + animation: false, + baseLayerPicker: false, + homeButton: false, + geocoder: false, + timeline: false, + scene3DOnly: true, + fullscreenButton: false, + navigationInstructionsInitiallyVisible: false, + }); + } else { + viewer = new Viewer(container, { + baseLayer, + animation: false, + baseLayerPicker: false, + homeButton: true, + geocoder: false, + timeline: false, + scene3DOnly: true, + fullscreenButton: false, + navigationInstructionsInitiallyVisible: false, + }); + } + + if (attribution) { + viewer.creditDisplay.addStaticCredit(new Credit(attribution)); + } + + return viewer; +} diff --git a/packages/core/web-map-cesium/tsconfig.json b/packages/core/web-map-cesium/tsconfig.json new file mode 100644 index 0000000..e1b7131 --- /dev/null +++ b/packages/core/web-map-cesium/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/core/web-map-cesium/vite.config.ts b/packages/core/web-map-cesium/vite.config.ts new file mode 100644 index 0000000..ae226eb --- /dev/null +++ b/packages/core/web-map-cesium/vite.config.ts @@ -0,0 +1,5 @@ +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + external: ["@cesium/engine", "@cesium/widgets"], +}); diff --git a/packages/core/web-map-maplibre/README.md b/packages/core/web-map-maplibre/README.md new file mode 100644 index 0000000..0bb7a10 --- /dev/null +++ b/packages/core/web-map-maplibre/README.md @@ -0,0 +1,39 @@ +# @xtramaps/web-map-maplibre + +Framework-agnostic core logic for the MapLibre web map component: basemap style resolution, GeoJSON/vector/raster data-layer wiring, and hover/click popups. Extracted from `ogcapi-html`'s `MapLibre` component. + +## Install + +```sh +npm install @xtramaps/web-map-maplibre +``` + +Requires `maplibre-gl` (`^6.0.0`) as a peer dependency. + +## Usage + +```ts +import { resolveWireframeBaseStyle, addData } from "@xtramaps/web-map-maplibre"; + +const style = await resolveWireframeBaseStyle({ + backgroundUrl: "https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution: '© OpenStreetMap contributors', + defaultUrl: "https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png", + defaultAttribution: '© OpenStreetMap contributors', +}); + +// after creating a maplibre-gl Map with `style`: +addData(map, null, false, geojson, "geojson", {}, defaultStyle, true, "HOVER_ID", featureTitles); +``` + +### Exports + +- `emptyStyle()`, `resolveWireframeBaseStyle()`, `rasterBaseStyle()`, `fetchBasemapStyle()`, `sanitizeBasemapStyle()`, `isValidStyle()`, `isRasterTileUrl()` - basemap style helpers +- `geoJsonLayers()`, `vectorLayers()`, `hoverLayers`, `isDataLayer()` - default layer builders +- `addData()`, `setStyleGeoJson()`, `setStyleVector()` - data-layer wiring +- `addPopup()`, `addPopupProps()` - hover/click popups +- `getBounds()`, `getFeaturesWithIdAsProperty()`, `polygonFromBounds()`, `idProperty` - GeoJSON helpers + +## License + +MIT diff --git a/packages/core/web-map-maplibre/package.json b/packages/core/web-map-maplibre/package.json new file mode 100644 index 0000000..a40550e --- /dev/null +++ b/packages/core/web-map-maplibre/package.json @@ -0,0 +1,44 @@ +{ + "name": "@xtramaps/web-map-maplibre", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/core/web-map-maplibre#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/core/web-map-maplibre" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@maplibre/maplibre-gl-style-spec": "^26.0.0", + "@turf/bbox": "7.4.0" + }, + "peerDependencies": { + "maplibre-gl": "^6.0.0" + }, + "devDependencies": { + "maplibre-gl": "^6.3.0" + } +} diff --git a/packages/core/web-map-maplibre/src/data.ts b/packages/core/web-map-maplibre/src/data.ts new file mode 100644 index 0000000..0759799 --- /dev/null +++ b/packages/core/web-map-maplibre/src/data.ts @@ -0,0 +1,271 @@ +import type { GeoJSON } from "geojson"; +import type { Map as MaplibreMap } from "maplibre-gl"; +import { getBounds, getFeaturesWithIdAsProperty, idProperty } from "./geojson"; +import { addPopup, addPopupProps } from "./popup"; +import { + geoJsonLayers, + hoverLayers, + isDataLayer, + vectorLayers, +} from "./styles"; +import type { + DefaultStyleOptions, + FeatureTitles, + PopupMode, + StyleSpecification, +} from "./types"; + +export const setStyleGeoJson = ( + map: MaplibreMap, + styleUrl: string, + removeZoomLevelConstraints: boolean, + popup: PopupMode | null | undefined, + featureTitles: FeatureTitles, +): void => { + fetch(styleUrl) + .then((response) => response.json()) + .then((style: StyleSpecification) => { + let baseStyle: Partial = { sources: {} }; + try { + baseStyle = map.getStyle() as StyleSpecification; + } catch { + // ignore + } + const dataStyle: StyleSpecification = { + ...style, + sources: { + ...style.sources, + ...baseStyle.sources, + }, + layers: style.layers.map((layer) => { + if (isDataLayer(layer)) { + const newLayer = { + ...layer, + source: "data", + }; + delete (newLayer as Record)["source-layer"]; + if (removeZoomLevelConstraints) { + delete newLayer.minzoom; + delete newLayer.maxzoom; + } + return newLayer; + } + + return layer; + }), + }; + + const dataSource = dataStyle.sources.data; + if (dataSource && "attribution" in dataSource) { + dataSource.attribution = style.layers + .filter(isDataLayer) + .map( + (layer) => + ( + style.sources[layer.source] as + | { attribution?: string } + | undefined + )?.attribution, + ) + .filter((attribution): attribution is string => Boolean(attribution)) + .filter((v, i, a) => a.indexOf(v) === i) + .join(" | "); + } + + delete (dataStyle as Record).terrain; + + map.setStyle(dataStyle, { diff: false }); + + if (popup === "HOVER_ID") { + addPopup( + map, + featureTitles, + dataStyle.layers + .filter((layer) => isDataLayer(layer) === true) + .map((layer) => layer.id), + ); + } + }); +}; + +export const setStyleVector = ( + map: MaplibreMap, + styleUrl: string, + removeZoomLevelConstraints: boolean, + popup: PopupMode | null | undefined, + sourceUrl?: string, + sourceLayers: string[] = [], +): void => { + fetch(styleUrl) + .then((response) => response.json()) + .then((style: StyleSpecification) => { + let baseStyle: Partial = { sources: {} }; + try { + baseStyle = map.getStyle() as StyleSpecification; + } catch { + // ignore + } + let dataStyle: StyleSpecification = style; + + if (sourceUrl) { + const newSources: StyleSpecification["sources"] = {}; + Object.keys(style.sources).forEach((source) => { + const sourceSpec = style.sources[source]; + if (sourceSpec.type === "vector") { + newSources[source] = { + ...sourceSpec, + tiles: [sourceUrl], + }; + } else { + newSources[source] = sourceSpec; + } + }); + + dataStyle = { + ...style, + sources: { + ...newSources, + ...baseStyle.sources, + }, + layers: style.layers + .filter( + (layer) => + !isDataLayer(layer) || + ("source-layer" in layer && + layer["source-layer"] && + (sourceLayers.length === 0 || + sourceLayers.includes(layer["source-layer"]))), + ) + .map((layer) => { + // "vector" is a source type, not a layer type, so this never matches any + // real layer - preserved 1:1 from the source implementation. + if ((layer as { type: string }).type === "vector") { + const newLayer = { + ...layer, + }; + if (removeZoomLevelConstraints) { + delete newLayer.minzoom; + delete newLayer.maxzoom; + } + return newLayer; + } + return layer; + }), + }; + } + + map.setStyle(dataStyle, { diff: false }); + + if (popup === "CLICK_PROPERTIES") { + addPopupProps( + map, + dataStyle.layers + .filter((layer) => isDataLayer(layer) === true) + .map((layer) => layer.id), + ); + } + }); +}; + +export const addData = ( + map: MaplibreMap, + styleUrl: string | null | undefined, + removeZoomLevelConstraints: boolean, + data: GeoJSON | string, + dataType: "geojson" | "vector" | "raster", + dataLayers: Record, + defaultStyle: DefaultStyleOptions, + fitBounds: boolean | undefined, + popup: PopupMode | null | undefined, + featureTitles: FeatureTitles, +): void => { + if (dataType === "geojson") { + const features = getFeaturesWithIdAsProperty(data as GeoJSON); + + map.addSource("data", { + type: "geojson", + data: features, + promoteId: idProperty, + }); + + if (fitBounds) { + const bounds = getBounds(data as GeoJSON); + + map.fitBounds(bounds, { + padding: 50, + maxZoom: 16, + duration: 500, + }); + } + + if (styleUrl) { + setStyleGeoJson( + map, + styleUrl, + removeZoomLevelConstraints, + popup, + featureTitles, + ); + } else { + const defaultLayers = geoJsonLayers(defaultStyle); + + defaultLayers.forEach((layer) => { + map.addLayer(layer); + }); + + if (popup === "HOVER_ID") { + addPopup(map, featureTitles, hoverLayers); + } else if (popup === "CLICK_PROPERTIES") { + addPopupProps( + map, + defaultLayers.map((l) => l.id), + ); + } + } + } else if (dataType === "vector") { + if (styleUrl) { + setStyleVector( + map, + styleUrl, + removeZoomLevelConstraints, + popup, + data as string, + Object.keys(dataLayers), + ); + } else { + map.addSource("data", { + type: "vector", + tiles: [data as string], + }); + + const layers = Object.keys(dataLayers).flatMap((layer) => + vectorLayers( + layer, + dataLayers[layer] as ("points" | "lines" | "polygons")[], + defaultStyle, + ), + ); + + layers.forEach((vectorLayer) => { + map.addLayer(vectorLayer); + }); + + if (popup === "CLICK_PROPERTIES") { + addPopupProps( + map, + layers.map((l) => l.id), + ); + } + } + } else if (dataType === "raster") { + map.addSource("data", { + type: "raster", + tiles: [data as string], + }); + map.addLayer({ + id: "data", + type: "raster", + source: "data", + }); + } +}; diff --git a/packages/core/web-map-maplibre/src/geojson.ts b/packages/core/web-map-maplibre/src/geojson.ts new file mode 100644 index 0000000..fc654c8 --- /dev/null +++ b/packages/core/web-map-maplibre/src/geojson.ts @@ -0,0 +1,47 @@ +import turfBbox from "@turf/bbox"; +import type { Feature, FeatureCollection, GeoJSON, Polygon } from "geojson"; + +export type Bounds2D = [number, number, number, number]; + +export const getBounds = (geojson: GeoJSON): Bounds2D => + turfBbox(geojson) as Bounds2D; + +export const idProperty = "__id"; + +export const isCollection = (geojson: GeoJSON): geojson is FeatureCollection => + geojson.type === "FeatureCollection"; + +const featureWithIdAsProperty = (feature: Feature): Feature => ({ + ...feature, + properties: { + ...feature.properties, + [idProperty]: feature.id, + }, +}); + +export const getFeaturesWithIdAsProperty = (geojson: GeoJSON): GeoJSON => + isCollection(geojson) + ? { + ...geojson, + features: geojson.features.map(featureWithIdAsProperty), + } + : featureWithIdAsProperty(geojson as Feature); + +export const polygonFromBounds = ( + bounds: [[number, number], [number, number]], +): Feature => ({ + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [ + [ + [bounds[0][0], bounds[0][1]], + [bounds[1][0], bounds[0][1]], + [bounds[1][0], bounds[1][1]], + [bounds[0][0], bounds[1][1]], + [bounds[0][0], bounds[0][1]], + ], + ], + }, + properties: {}, +}); diff --git a/packages/core/web-map-maplibre/src/index.ts b/packages/core/web-map-maplibre/src/index.ts new file mode 100644 index 0000000..59c6081 --- /dev/null +++ b/packages/core/web-map-maplibre/src/index.ts @@ -0,0 +1,31 @@ +export { addData, setStyleGeoJson, setStyleVector } from "./data"; +export { + getBounds, + getFeaturesWithIdAsProperty, + idProperty, + isCollection, + polygonFromBounds, +} from "./geojson"; +export { addPopup, addPopupProps } from "./popup"; +export type { ResolveWireframeBaseStyleOptions } from "./styles"; +export { + baseStyle, + emptyStyle, + fetchBasemapStyle, + geoJsonLayers, + hoverLayers, + isDataLayer, + isRasterTileUrl, + isValidStyle, + rasterBaseStyle, + resolveWireframeBaseStyle, + sanitizeBasemapStyle, + vectorLayers, +} from "./styles"; +export type { + DefaultStyleOptions, + FeatureTitles, + GeometryType, + PopupMode, + StyleSpecification, +} from "./types"; diff --git a/packages/core/web-map-maplibre/src/popup.ts b/packages/core/web-map-maplibre/src/popup.ts new file mode 100644 index 0000000..93f12ee --- /dev/null +++ b/packages/core/web-map-maplibre/src/popup.ts @@ -0,0 +1,261 @@ +import type { + MapGeoJSONFeature, + MapLayerMouseEvent, + Map as MaplibreMap, + MapMouseEvent, +} from "maplibre-gl"; +import { Popup } from "maplibre-gl"; +import type { FeatureTitles } from "./types"; + +type Geometry = { + type: string; + coordinates: unknown; +}; + +const changeCursor = (map: MaplibreMap, cursor: string) => { + const canvas = map.getCanvas(); + canvas.style.cursor = cursor; +}; + +const firstCoordinate = (geometry: Geometry): [number, number] | null => { + switch (geometry.type) { + case "Point": + return geometry.coordinates as [number, number]; + case "LineString": + case "MultiPoint": + return (geometry.coordinates as [number, number][])[0]; + case "Polygon": + case "MultiLineString": + return (geometry.coordinates as [number, number][][])[0][0]; + case "MultiPolygon": + return (geometry.coordinates as [number, number][][][])[0][0][0]; + default: + return null; + } +}; + +const showPopup = ( + map: MaplibreMap, + popup: Popup, + featureTitles: FeatureTitles, +) => { + let currentLngLat: [number, number] | null; + return (e: MapLayerMouseEvent) => { + const feature = e.features?.[0]; + if (!feature) return; + + const lngLat = firstCoordinate(feature.geometry as Geometry); + if (currentLngLat !== lngLat && lngLat) { + currentLngLat = lngLat; + changeCursor(map, "pointer"); + const description = + featureTitles[String(feature.id)] || String(feature.id); + + // Ensure that if the map is zoomed out such that multiple + // copies of the feature are visible, the popup appears + // over the copy being pointed to. + while (Math.abs(e.lngLat.lng - lngLat[0]) > 180) { + lngLat[0] += e.lngLat.lng > lngLat[0] ? 360 : -360; + } + + if (lngLat && description) { + popup.setLngLat(lngLat).setHTML(description).addTo(map); + } + } + }; +}; + +const featureHtml = ( + feature: MapGeoJSONFeature, + idx: number, + total: number, +): string => { + const title = + feature.sourceLayer || + (feature.properties?.featureType as string) || + "feature"; + const header = ` +
+
${title}
+ ${ + total > 1 + ? ` + + ` + : "" + } +
+ `; + let description = `${header}
`; + Object.keys(feature.properties ?? {}) + .sort() + .forEach((prop) => { + let val = feature.properties?.[prop]; + if (typeof val === "string" && /^https?:\/\/[^\s]+$/.test(val)) { + val = `${val}`; + } + description += ``; + }); + description += "
${prop}${val}
"; + return ``; +}; + +const getDefaultPopupContent = ({ + features, +}: { + features: MapGeoJSONFeature[]; +}): Promise => { + if (!features || features.length === 0) { + return Promise.resolve(""); + } + + if (features.length === 1) { + return Promise.resolve(featureHtml(features[0], 0, 1)); + } + + const allFeaturesHtml = features + .map((f, i) => featureHtml(f, i, features.length)) + .join(""); + return Promise.resolve(allFeaturesHtml); +}; + +declare global { + // eslint-disable-next-line no-var + var getPopupContent: + | ((features: MapGeoJSONFeature[], map: MaplibreMap) => Promise) + | undefined; +} + +const showPopupProps = + (map: MaplibreMap, popup: Popup) => (e: MapMouseEvent) => { + const allFeatures = map.queryRenderedFeatures(e.point); + + if (!allFeatures || allFeatures.length === 0) { + return; + } + + // Deduplicate features based on id and sourceLayer + const featuresMap = new Map(); + allFeatures.forEach((f, index) => { + const featureId = f.id !== undefined ? f.id : `idx-${index}`; + const layerId = f.sourceLayer || f.layer?.id || ""; + const key = `${featureId}-${layerId}`; + if (!featuresMap.has(key)) { + featuresMap.set(key, f); + } + }); + const features = Array.from(featuresMap.values()); + + if (features.length === 0) { + return; + } + + // Use the click location instead of the first coordinate of the geometry + // This is better for polygons and lines where the first coordinate might be far from the click + const lngLat: [number, number] = [e.lngLat.lng, e.lngLat.lat]; + + const description = globalThis.getPopupContent + ? globalThis.getPopupContent(features, map) + : getDefaultPopupContent({ features }); + + if (lngLat && description) { + description.then((d) => { + popup.setLngLat(lngLat).setHTML(d).addTo(map); + + if (features.length > 1) { + let idx = 0; + const total = features.length; + + const update = (newIdx: number) => { + if (newIdx < 0 || newIdx >= total) return; + const oldEl = document.getElementById(`popup-feature-${idx}`); + if (oldEl) oldEl.style.display = "none"; + idx = newIdx; + const newEl = document.getElementById(`popup-feature-${idx}`); + if (newEl) newEl.style.display = "block"; + const indexElement = document.getElementById(`popup-index-${idx}`); + if (indexElement) { + indexElement.textContent = `${idx + 1}/${total}`; + } + const prevBtn = document.getElementById(`popup-prev-${idx}`); + const nextBtn = document.getElementById(`popup-next-${idx}`); + if (prevBtn) { + prevBtn.onclick = (evt) => { + evt.preventDefault(); + update(idx - 1); + }; + } + if (nextBtn) { + nextBtn.onclick = (evt) => { + evt.preventDefault(); + update(idx + 1); + }; + } + }; + + const initialPrevBtn = document.getElementById(`popup-prev-${idx}`); + const initialNextBtn = document.getElementById(`popup-next-${idx}`); + if (initialPrevBtn) { + initialPrevBtn.onclick = (evt) => { + evt.preventDefault(); + update(idx - 1); + }; + } + if (initialNextBtn) { + initialNextBtn.onclick = (evt) => { + evt.preventDefault(); + update(idx + 1); + }; + } + } + }); + } + }; + +const hidePopup = (map: MaplibreMap, popup: Popup) => () => { + changeCursor(map, ""); + popup.remove(); +}; + +export const addPopup = ( + map: MaplibreMap, + featureTitles: FeatureTitles = {}, + layerIds: string[] = ["points"], +): void => { + const popup = new Popup({ + closeButton: false, + closeOnClick: false, + }); + + layerIds.forEach((layerId) => { + // Make sure to detect feature change for overlapping features and use mousemove instead of mouseenter event + map.on("mousemove", layerId, showPopup(map, popup, featureTitles)); + map.on("mouseleave", layerId, hidePopup(map, popup)); + }); +}; + +export const addPopupProps = ( + map: MaplibreMap, + layerIds: string[] = [], +): void => { + const popup = new Popup({ + closeButton: true, + closeOnClick: true, + maxWidth: "50%", + className: "popup-props", + anchor: "top", + }); + + map.on("click", showPopupProps(map, popup)); + + layerIds.forEach((layerId) => { + map.on("mouseenter", layerId, () => changeCursor(map, "pointer")); + map.on("mouseleave", layerId, () => changeCursor(map, "")); + }); +}; diff --git a/packages/core/web-map-maplibre/src/styles.ts b/packages/core/web-map-maplibre/src/styles.ts new file mode 100644 index 0000000..3d2c62e --- /dev/null +++ b/packages/core/web-map-maplibre/src/styles.ts @@ -0,0 +1,399 @@ +import type { + BackgroundLayerSpecification, + HillshadeLayerSpecification, + LayerSpecification, + RasterLayerSpecification, +} from "@maplibre/maplibre-gl-style-spec"; +import type { + DefaultStyleOptions, + GeometryType, + StyleSpecification, +} from "./types"; + +export type DataLayerSpecification = Exclude< + LayerSpecification, + | BackgroundLayerSpecification + | RasterLayerSpecification + | HillshadeLayerSpecification +>; + +export const emptyStyle = (): StyleSpecification => ({ + version: 8, + sources: {}, + layers: [], +}); + +const normalizedUrl = (url = "") => { + try { + return decodeURIComponent(url); + } catch { + return url; + } +}; + +const hasPlaceholder = (url: string, key: string) => + new RegExp(`\\{${key}\\}`, "i").test(url); + +export const isRasterTileUrl = (url = ""): boolean => { + const decoded = normalizedUrl(url); + return ( + hasPlaceholder(decoded, "z") && + hasPlaceholder(decoded, "x") && + hasPlaceholder(decoded, "y") + ); +}; + +const getBaseAttribution = ( + url: string | null | undefined, + attribution: string | undefined, + defaultUrl: string, + defaultAttribution: string, +): string | string[] => + url === defaultUrl && attribution !== defaultAttribution + ? [attribution ?? "", defaultAttribution] + : attribution || defaultAttribution; + +const expandTileServers = (url: string): string[] => + url.indexOf("{s}") > -1 || url.indexOf("{a-c}") > -1 + ? ["a", "b", "c"].map((prefix) => + url.replace(/\{s\}/, prefix).replace(/\{a-c\}/, prefix), + ) + : [url]; + +export const rasterBaseStyle = ( + url: string | null | undefined, + attribution: string | undefined, + defaultUrl: string, + defaultAttribution: string, +): StyleSpecification => { + const baseAttribution = getBaseAttribution( + url, + attribution, + defaultUrl, + defaultAttribution, + ); + + const finalUrl = url || defaultUrl; + const servers = expandTileServers(finalUrl); + + return { + version: 8, + sources: { + base: { + type: "raster", + tiles: servers, + tileSize: 256, + attribution: Array.isArray(baseAttribution) + ? baseAttribution.join(" | ") + : baseAttribution, + }, + }, + layers: [ + { + id: "background", + type: "raster", + source: "base", + }, + ], + }; +}; + +const isObject = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +export const isValidStyle = (style: unknown): style is StyleSpecification => + isObject(style) && + typeof style.version === "number" && + isObject(style.sources) && + Array.isArray(style.layers); + +export const sanitizeBasemapStyle = ( + style: unknown, +): StyleSpecification | null => { + if (!isValidStyle(style)) { + return null; + } + + return { + ...style, + version: style.version || 8, + sources: style.sources || {}, + layers: style.layers || [], + }; +}; + +export const fetchBasemapStyle = async ( + url: string, +): Promise => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load basemap style: ${response.status}`); + } + const style = await response.json(); + const sanitizedStyle = sanitizeBasemapStyle(style); + + if (!sanitizedStyle) { + throw new Error("Invalid basemap style"); + } + + return sanitizedStyle; +}; + +export interface ResolveWireframeBaseStyleOptions { + backgroundUrl?: string; + attribution?: string; + defaultUrl: string; + defaultAttribution: string; +} + +export const resolveWireframeBaseStyle = async ({ + backgroundUrl, + attribution, + defaultUrl, + defaultAttribution, +}: ResolveWireframeBaseStyleOptions): Promise => { + const finalUrl = backgroundUrl || defaultUrl; + + if (isRasterTileUrl(finalUrl)) { + return rasterBaseStyle( + backgroundUrl, + attribution, + defaultUrl, + defaultAttribution, + ); + } + + try { + return await fetchBasemapStyle(finalUrl); + } catch { + return rasterBaseStyle(null, attribution, defaultUrl, defaultAttribution); + } +}; + +// backward-compatible export name +export const baseStyle = rasterBaseStyle; + +export const hoverLayers: GeometryType[] = ["points", "lines", "polygons"]; + +export const isDataLayer = ( + layer: LayerSpecification, +): layer is DataLayerSpecification => { + switch (layer.type) { + case "raster": + case "hillshade": + case "background": + return false; + default: + return true; + } +}; + +const circleLayers = ( + color: string, + opacity: number, + circleRadius: number, + minZoom: number, + maxZoom: number, +): LayerSpecification[] => [ + { + id: "points", + type: "circle", + source: "data", + paint: { + "circle-color": color, + "circle-opacity": opacity, + "circle-radius": circleRadius, + }, + minzoom: minZoom, + maxzoom: maxZoom, + }, +]; + +const lineLayers = ( + color: string, + opacity: number, + lineWidth: number, + minZoom: number, + maxZoom: number, +): LayerSpecification[] => [ + { + id: "lines", + type: "line", + source: "data", + layout: { + "line-join": "round", + "line-cap": "round", + }, + paint: { + "line-color": color, + "line-opacity": opacity, + "line-width": lineWidth, + }, + minzoom: minZoom, + maxzoom: maxZoom, + }, +]; + +const polygonLayers = ( + color: string, + opacity: number, + fillOpacity: number, + outlineWidth: number, + minZoom: number, + maxZoom: number, +): LayerSpecification[] => [ + { + id: "polygons", + type: "fill", + source: "data", + paint: { + "fill-color": color, + "fill-opacity": fillOpacity, + }, + minzoom: minZoom, + maxzoom: maxZoom, + }, + { + id: "polygons-outline", + type: "line", + source: "data", + layout: { + "line-join": "round", + "line-cap": "round", + }, + paint: { + "line-color": color, + "line-opacity": opacity, + "line-width": outlineWidth, + }, + minzoom: minZoom, + maxzoom: maxZoom, + }, +]; + +const withFilter = ( + layers: LayerSpecification[], + geometryType: string, +): LayerSpecification[] => + layers.map((layer) => ({ + ...layer, + filter: ["==", "$type", geometryType], + })) as LayerSpecification[]; + +export const geoJsonLayers = ({ + color, + opacity, + circleRadius, + circleMinZoom, + circleMaxZoom, + lineWidth, + lineMinZoom, + lineMaxZoom, + fillOpacity, + outlineWidth, + polygonMinZoom, + polygonMaxZoom, +}: DefaultStyleOptions): LayerSpecification[] => { + return ["Polygon", "LineString", "Point"].flatMap((geometryType) => { + switch (geometryType) { + case "Point": + return withFilter( + circleLayers( + color, + opacity, + circleRadius, + circleMinZoom, + circleMaxZoom, + ), + geometryType, + ); + case "LineString": + return withFilter( + lineLayers(color, opacity, lineWidth, lineMinZoom, lineMaxZoom), + geometryType, + ); + case "Polygon": + return withFilter( + polygonLayers( + color, + opacity, + fillOpacity, + outlineWidth, + polygonMinZoom, + polygonMaxZoom, + ), + geometryType, + ); + default: + return []; + } + }); +}; + +const withSourceAndFilter = ( + layers: LayerSpecification[], + source: string, + geometryType: string, +): LayerSpecification[] => + layers.map((layer) => ({ + ...layer, + id: `${source}_${layer.id}`, + "source-layer": source, + filter: ["==", "$type", geometryType], + })) as LayerSpecification[]; + +export const vectorLayers = ( + source: string, + geometryTypes: GeometryType[], + { + color, + opacity, + circleRadius, + circleMinZoom, + circleMaxZoom, + lineWidth, + lineMinZoom, + lineMaxZoom, + fillOpacity, + outlineWidth, + polygonMinZoom, + polygonMaxZoom, + }: DefaultStyleOptions, +): LayerSpecification[] => + geometryTypes.flatMap((geometryType) => { + switch (geometryType) { + case "points": + return withSourceAndFilter( + circleLayers( + color, + opacity, + circleRadius, + circleMinZoom, + circleMaxZoom, + ), + source, + "Point", + ); + case "lines": + return withSourceAndFilter( + lineLayers(color, opacity, lineWidth, lineMinZoom, lineMaxZoom), + source, + "LineString", + ); + case "polygons": + return withSourceAndFilter( + polygonLayers( + color, + opacity, + fillOpacity, + outlineWidth, + polygonMinZoom, + polygonMaxZoom, + ), + source, + "Polygon", + ); + default: + return []; + } + }); diff --git a/packages/core/web-map-maplibre/src/types.ts b/packages/core/web-map-maplibre/src/types.ts new file mode 100644 index 0000000..6f284c3 --- /dev/null +++ b/packages/core/web-map-maplibre/src/types.ts @@ -0,0 +1,26 @@ +import type { StyleSpecification } from "@maplibre/maplibre-gl-style-spec"; + +export type { StyleSpecification }; + +export interface DefaultStyleOptions { + color: string; + opacity: number; + circleRadius: number; + circleMinZoom: number; + circleMaxZoom: number; + lineWidth: number; + lineMinZoom: number; + lineMaxZoom: number; + fillOpacity: number; + outlineWidth: number; + polygonMinZoom: number; + polygonMaxZoom: number; +} + +export type GeometryType = "points" | "lines" | "polygons"; + +export type PopupMode = "HOVER_ID" | "CLICK_PROPERTIES"; + +export interface FeatureTitles { + [featureId: string]: string; +} diff --git a/packages/core/web-map-maplibre/tsconfig.json b/packages/core/web-map-maplibre/tsconfig.json new file mode 100644 index 0000000..e1b7131 --- /dev/null +++ b/packages/core/web-map-maplibre/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/core/web-map-maplibre/vite.config.ts b/packages/core/web-map-maplibre/vite.config.ts new file mode 100644 index 0000000..eaad665 --- /dev/null +++ b/packages/core/web-map-maplibre/vite.config.ts @@ -0,0 +1,9 @@ +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + external: [ + "@maplibre/maplibre-gl-style-spec", + "@mapbox/geojson-extent", + "maplibre-gl", + ], +}); diff --git a/packages/core/web-map-openlayers/README.md b/packages/core/web-map-openlayers/README.md new file mode 100644 index 0000000..ad8de48 --- /dev/null +++ b/packages/core/web-map-openlayers/README.md @@ -0,0 +1,39 @@ +# @xtramaps/web-map-openlayers + +Framework-agnostic core logic for the OpenLayers web map component: projection setup, initial-view computation, and dynamic view/source switching when the active TileMatrixSet changes. Migrated from `ogcapi-html`'s `OpenLayers` component onto current `ol` (`10.8.0`). + +## Install + +```sh +npm install @xtramaps/web-map-openlayers +``` + +Requires `ol` (`10.8.0` exactly — pinned by [`rlayers`](https://github.com/mmomtchev/rlayers)'s own peer dependency) as a peer dependency. + +## Usage + +```ts +import { setupProjections, computeInitialView } from "@xtramaps/web-map-openlayers"; + +// register EPSG:25832/25833/3395 with ol - call once at module load, like the source did +setupProjections(); + +const initial = computeInitialView({ + effectiveStyleUrl: styleUrl, + styleConfig, + tileMatrixSets, + currentTileMatrixSet, + bounds, +}); +``` + +### Exports + +- `setupProjections()` - registers the custom EPSG:25832/25833/3395 projections used by ldproxy's TileMatrixSets +- `computeInitialView()` - computes the initial `{center, zoom}` view from a style, a TileMatrixSet, or bounds (in that precedence order) +- `getRasterBackgroundAndAttributions()` - derives a raster background URL/id and combined attribution string from a style +- `setDynamicView()` / `setDynamicSource()` - imperative helpers that swap an `ol` map's view or a layer's source when the active TileMatrixSet changes + +## License + +MIT diff --git a/packages/core/web-map-openlayers/package.json b/packages/core/web-map-openlayers/package.json new file mode 100644 index 0000000..0a80ceb --- /dev/null +++ b/packages/core/web-map-openlayers/package.json @@ -0,0 +1,44 @@ +{ + "name": "@xtramaps/web-map-openlayers", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/core/web-map-openlayers#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/core/web-map-openlayers" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "ol-mapbox-style": "^13.4.2", + "proj4": "^2.21.0" + }, + "peerDependencies": { + "ol": "10.8.0" + }, + "devDependencies": { + "ol": "10.8.0" + } +} diff --git a/packages/core/web-map-openlayers/src/dynamicSource.ts b/packages/core/web-map-openlayers/src/dynamicSource.ts new file mode 100644 index 0000000..c88cf57 --- /dev/null +++ b/packages/core/web-map-openlayers/src/dynamicSource.ts @@ -0,0 +1,75 @@ +import { MVT } from "ol/format"; +import type { Layer } from "ol/layer"; +import type VectorTileLayer from "ol/layer/VectorTile"; +import { VectorTile as VectorTileSource, XYZ as XYZSource } from "ol/source"; +import TileGrid from "ol/tilegrid/TileGrid"; +import { stylefunction } from "ol-mapbox-style"; +import type { OpenLayersStyle, TileMatrixSet } from "./types"; + +export interface SetDynamicSourceOptions { + tileMatrixSet?: TileMatrixSet | null; + dataUrl: string; + dataType?: string | null; + update: boolean; + styleObject?: OpenLayersStyle | null; +} + +export const setDynamicSource = ( + layer: Layer, + { + tileMatrixSet, + dataUrl, + dataType, + update, + styleObject, + }: SetDynamicSourceOptions, +): void => { + if (!tileMatrixSet || !update) { + return; + } + + const url = dataUrl.replace( + "/WebMercatorQuad/", + `/${tileMatrixSet.tileMatrixSet}/`, + ); + const tileGrid = new TileGrid({ + extent: JSON.parse(tileMatrixSet.extent as string), + resolutions: JSON.parse(tileMatrixSet.resolutions as string), + sizes: JSON.parse(tileMatrixSet.sizes as string), + }); + + layer.set( + "source", + dataType === "raster" + ? new XYZSource({ + url, + maxZoom: tileMatrixSet.maxLevel, + projection: tileMatrixSet.projection, + tileGrid, + }) + : new VectorTileSource({ + url, + format: new MVT(), + maxZoom: tileMatrixSet.maxLevel, + projection: tileMatrixSet.projection, + tileGrid, + }), + ); + + if (styleObject?.sources) { + const sourceName = Object.entries(styleObject.sources).find( + ([, source]) => source.type === "vector", + )?.[0]; + if (sourceName) { + // The source passes styleObject for both raster and vector-tile layers alike + // (preserved from the original), but stylefunction only makes sense - and is only + // ever actually exercised - for the vector-tile layer. + stylefunction( + layer as unknown as VectorTileLayer, + styleObject, + sourceName, + JSON.parse(tileMatrixSet.resolutions as string), + ); + } + } +}; diff --git a/packages/core/web-map-openlayers/src/dynamicView.ts b/packages/core/web-map-openlayers/src/dynamicView.ts new file mode 100644 index 0000000..c12ed69 --- /dev/null +++ b/packages/core/web-map-openlayers/src/dynamicView.ts @@ -0,0 +1,23 @@ +import type { Map as OlMap } from "ol"; +import { fromLonLat } from "ol/proj"; +import View from "ol/View"; +import type { TileMatrixSet } from "./types"; + +export const setDynamicView = ( + map: OlMap, + tileMatrixSet: TileMatrixSet | null | undefined, + update: boolean, +): void => { + if (update && tileMatrixSet) { + map.setView( + new View({ + center: fromLonLat( + [tileMatrixSet.defaultCenterLon, tileMatrixSet.defaultCenterLat], + tileMatrixSet.projection, + ), + zoom: tileMatrixSet.defaultZoomLevel, + projection: tileMatrixSet.projection, + }), + ); + } +}; diff --git a/packages/core/web-map-openlayers/src/index.ts b/packages/core/web-map-openlayers/src/index.ts new file mode 100644 index 0000000..da51e81 --- /dev/null +++ b/packages/core/web-map-openlayers/src/index.ts @@ -0,0 +1,14 @@ +export type { SetDynamicSourceOptions } from "./dynamicSource"; +export { setDynamicSource } from "./dynamicSource"; +export { setDynamicView } from "./dynamicView"; +export { setupProjections } from "./proj"; +export type { RasterBackgroundInfo } from "./style"; +export { getRasterBackgroundAndAttributions } from "./style"; +export type { + OpenLayersStyle, + OpenLayersStyleLayer, + OpenLayersStyleSource, + TileMatrixSet, +} from "./types"; +export type { ComputeInitialViewOptions, RView } from "./view"; +export { computeInitialView } from "./view"; diff --git a/packages/core/web-map-openlayers/src/proj.ts b/packages/core/web-map-openlayers/src/proj.ts new file mode 100644 index 0000000..e5e43da --- /dev/null +++ b/packages/core/web-map-openlayers/src/proj.ts @@ -0,0 +1,31 @@ +import { get } from "ol/proj"; +import { register } from "ol/proj/proj4"; +import proj4 from "proj4"; + +export const setupProjections = (): void => { + proj4.defs( + "EPSG:25832", + "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + ); + proj4.defs( + "EPSG:25833", + "+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs", + ); + proj4.defs( + "EPSG:3395", + "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs", + ); + + register(proj4); + + get("EPSG:25832")?.setExtent([ + -46133.17, 5048875.268576, 1206211.101424, 6301219.54, + ]); + get("EPSG:25833")?.setExtent([ + -464849.38, 5057815.86857567, 787494.89142433, 6310160.14, + ]); + get("EPSG:3395")?.setExtent([ + -20037508.342789244, -20037508.342789244, 20037508.342789244, + 20037508.342789244, + ]); +}; diff --git a/packages/core/web-map-openlayers/src/style.ts b/packages/core/web-map-openlayers/src/style.ts new file mode 100644 index 0000000..ed43684 --- /dev/null +++ b/packages/core/web-map-openlayers/src/style.ts @@ -0,0 +1,43 @@ +import type { OpenLayersStyle } from "./types"; + +export interface RasterBackgroundInfo { + rasterBackgroundId?: string; + rasterBackgroundUrl?: string; + combinedAttribution: string; +} + +export const getRasterBackgroundAndAttributions = ( + style: OpenLayersStyle, +): RasterBackgroundInfo => { + let rasterBackgroundId: string | undefined; + let rasterBackgroundUrl: string | undefined; + const usedSourceKeys: string[] = []; + const attributionsList: string[] = []; + + style.layers?.forEach((layer, i) => { + const key = layer.source; + if (key && !usedSourceKeys.includes(key)) { + usedSourceKeys.push(key); + const source = style.sources?.[key]; + if ( + i === 0 && + layer.type === "raster" && + source?.tiles && + !rasterBackgroundUrl + ) { + const [firstTile] = source.tiles; + rasterBackgroundUrl = firstTile; + rasterBackgroundId = key; + } + if (source?.attribution) { + attributionsList.push(source.attribution); + } + } + }); + + return { + rasterBackgroundId, + rasterBackgroundUrl, + combinedAttribution: attributionsList.join(" | "), + }; +}; diff --git a/packages/core/web-map-openlayers/src/types.ts b/packages/core/web-map-openlayers/src/types.ts new file mode 100644 index 0000000..d2d963f --- /dev/null +++ b/packages/core/web-map-openlayers/src/types.ts @@ -0,0 +1,32 @@ +export interface TileMatrixSet { + tileMatrixSet: string; + projection?: string; + defaultCenterLon: number; + defaultCenterLat: number; + defaultZoomLevel: number; + maxLevel?: number; + extent?: string; + resolutions?: string; + sizes?: string; +} + +export interface OpenLayersStyleSource { + type?: string; + tiles?: string[]; + attribution?: string; + bounds?: [number, number, number, number]; +} + +export interface OpenLayersStyleLayer { + type?: string; + source?: string; +} + +export interface OpenLayersStyle { + layers?: OpenLayersStyleLayer[]; + sources?: Record; + center?: [number, number]; + zoom?: number; + bounds?: [number, number, number, number]; + metadata?: Record; +} diff --git a/packages/core/web-map-openlayers/src/view.ts b/packages/core/web-map-openlayers/src/view.ts new file mode 100644 index 0000000..9349306 --- /dev/null +++ b/packages/core/web-map-openlayers/src/view.ts @@ -0,0 +1,64 @@ +import type { Coordinate } from "ol/coordinate"; +import { boundingExtent, getCenter } from "ol/extent"; +import { fromLonLat } from "ol/proj"; +import type { OpenLayersStyle, TileMatrixSet } from "./types"; + +export interface RView { + center: Coordinate; + zoom: number; +} + +export interface ComputeInitialViewOptions { + effectiveStyleUrl?: string | null; + styleConfig?: Pick | null; + tileMatrixSets: TileMatrixSet[]; + currentTileMatrixSet?: string | null; + bounds?: [[number, number], [number, number]] | null; +} + +/** + * Precedence, ported 1:1 from the source: a style's own center/bounds first, then the + * active TileMatrixSet's default view, then the `bounds` prop, else no initial view at all. + */ +export const computeInitialView = ({ + effectiveStyleUrl, + styleConfig, + tileMatrixSets, + currentTileMatrixSet, + bounds, +}: ComputeInitialViewOptions): RView | null => { + const tms = tileMatrixSets.find( + (t) => t.tileMatrixSet === currentTileMatrixSet, + ); + + if (effectiveStyleUrl && styleConfig) { + if (styleConfig.center) { + return { + center: fromLonLat(styleConfig.center), + zoom: styleConfig.zoom || 0, + }; + } + if (styleConfig.bounds && styleConfig.bounds.length === 4) { + const boundsExtent = boundingExtent([ + fromLonLat([styleConfig.bounds[0], styleConfig.bounds[1]]), + fromLonLat([styleConfig.bounds[2], styleConfig.bounds[3]]), + ]); + return { center: getCenter(boundsExtent), zoom: styleConfig.zoom || 0 }; + } + return { center: fromLonLat([0, 0]), zoom: styleConfig.zoom || 0 }; + } + if (tms) { + return { + center: fromLonLat([tms.defaultCenterLon, tms.defaultCenterLat]), + zoom: tms.defaultZoomLevel, + }; + } + if (bounds) { + const extent = boundingExtent([ + fromLonLat(bounds[0]), + fromLonLat(bounds[1]), + ]); + return { center: getCenter(extent), zoom: 6 }; + } + return null; +}; diff --git a/packages/core/web-map-openlayers/tsconfig.json b/packages/core/web-map-openlayers/tsconfig.json new file mode 100644 index 0000000..e1b7131 --- /dev/null +++ b/packages/core/web-map-openlayers/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/core/web-map-openlayers/vite.config.ts b/packages/core/web-map-openlayers/vite.config.ts new file mode 100644 index 0000000..4f1277a --- /dev/null +++ b/packages/core/web-map-openlayers/vite.config.ts @@ -0,0 +1,5 @@ +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + external: ["ol", "ol-mapbox-style", "proj4", /^ol\//], +}); diff --git a/packages/react/layer-control-maplibre/README.md b/packages/react/layer-control-maplibre/README.md new file mode 100644 index 0000000..f620fe0 --- /dev/null +++ b/packages/react/layer-control-maplibre/README.md @@ -0,0 +1,56 @@ +# @xtramaps/layer-control-maplibre-react + +React LayerControl component for MapLibre: nested groups, radio-groups, and merge-groups (including `sourceLayer` auto-collection), with legend icons via [`@xtramaps/legend-symbols-maplibre-react`](https://github.com/ldproxy/xtramaps/tree/main/packages/react/legend-symbols-maplibre). Must be rendered as a child of [`@xtramaps/web-map-maplibre-react`](https://github.com/ldproxy/xtramaps/tree/main/packages/react/web-map-maplibre)'s `` (or any `@vis.gl/react-maplibre` ``) — the two packages have no dependency on each other. + +## Example + +Browse it interactively via Storybook: `npm run storybook` from the repo root, then open the `layer-control-maplibre/LayerControl` story. + +Not published to npm yet, so there is no CDN-hosted live example. To try the standalone demo locally: + +```sh +git clone https://github.com/ldproxy/xtramaps.git +cd xtramaps +npm install +npm run build +python3 -m http.server 8080 # any static file server serving the repo root works +``` + +Then open `http://localhost:8080/packages/react/layer-control-maplibre/examples/layer-control.html`. A plain `file://` open won't work — browsers block ES module imports from `file://` URLs. + +## Install + +```sh +npm install @xtramaps/layer-control-maplibre-react +``` + +Requires `react`, `react-dom` (`^18.0.0 || ^19.0.0`), `@vis.gl/react-maplibre` (`^8.1.2`), and `maplibre-gl` (`^6.0.0`) as peer dependencies. Ships `reactstrap` as a regular dependency — bring your own Bootstrap CSS. + +## Usage + +```tsx +import MapLibre from "@xtramaps/web-map-maplibre-react"; +import { LayerControl } from "@xtramaps/layer-control-maplibre-react"; + + + +; +``` + +### Exports + +- `LayerControl` (default) - the map control component + +## License + +MIT diff --git a/packages/react/layer-control-maplibre/examples/layer-control.html b/packages/react/layer-control-maplibre/examples/layer-control.html new file mode 100644 index 0000000..549dd21 --- /dev/null +++ b/packages/react/layer-control-maplibre/examples/layer-control.html @@ -0,0 +1,137 @@ + + + + + + Layer Control MapLibre - React Example + + + + + + + +

Layer Control MapLibre

+

+ @xtramaps/layer-control-maplibre-react - nested groups, a radio-group, + merge-groups (incl. sourceLayer auto-collection) and legend icons via + @xtramaps/legend-symbols-maplibre-react. Composed explicitly as + <MapLibre><LayerControl /></MapLibre> - the two packages + have no dependency on each other. +

+ +
+
+
+ + + + diff --git a/packages/react/layer-control-maplibre/package.json b/packages/react/layer-control-maplibre/package.json new file mode 100644 index 0000000..91df0ef --- /dev/null +++ b/packages/react/layer-control-maplibre/package.json @@ -0,0 +1,57 @@ +{ + "name": "@xtramaps/layer-control-maplibre-react", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/react/layer-control-maplibre#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/react/layer-control-maplibre" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@xtramaps/layer-control-maplibre": "1.0.0", + "@xtramaps/legend-symbols-maplibre-react": "1.0.0", + "reactstrap": "^9.2.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "@vis.gl/react-maplibre": "^8.1.2", + "maplibre-gl": "^6.0.0" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "@vis.gl/react-maplibre": "^8.1.2", + "@xtramaps/web-map-maplibre-react": "1.0.0", + "maplibre-gl": "^6.3.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "storybook": "10.5.7" + } +} diff --git a/packages/react/layer-control-maplibre/src/CollapseButton.tsx b/packages/react/layer-control-maplibre/src/CollapseButton.tsx new file mode 100644 index 0000000..9936afe --- /dev/null +++ b/packages/react/layer-control-maplibre/src/CollapseButton.tsx @@ -0,0 +1,48 @@ +export interface CollapseButtonProps { + collapsed: boolean; + toggleCollapsed: () => void; +} + +function CollapseButton({ collapsed, toggleCollapsed }: CollapseButtonProps) { + return ( + + ); +} + +CollapseButton.displayName = "CollapseButton"; + +export default CollapseButton; diff --git a/packages/react/layer-control-maplibre/src/Control.tsx b/packages/react/layer-control-maplibre/src/Control.tsx new file mode 100644 index 0000000..bac17e6 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/Control.tsx @@ -0,0 +1,174 @@ +import type { LayerControlEntry } from "@xtramaps/layer-control-maplibre"; +import { initialCfg, parse } from "@xtramaps/layer-control-maplibre"; +import type { Map as MaplibreMap } from "maplibre-gl"; +import { useCallback, useEffect, useState } from "react"; +import ControlButton from "./ControlButton"; +import ControlPanel from "./ControlPanel"; + +export interface ControlProps { + entries?: LayerControlEntry[]; + maxHeight: number; + map: MaplibreMap; + opened?: boolean; + onlyLegend?: boolean; + preferStyle?: boolean; +} + +function Control({ + entries = [], + maxHeight, + map, + opened = false, + onlyLegend = false, + preferStyle = true, +}: ControlProps) { + const [isVisible, setIsVisible] = useState(opened); + const [isControlable, setIsControlable] = useState(!onlyLegend); + const [cfg, setCfg] = useState(initialCfg); + const [selectedRadioGroups, setSelectedRadioGroups] = useState( + initialCfg.radioIds, + ); + const [selected, setSelected] = useState(initialCfg.allIds); + const [open, setOpen] = useState(initialCfg.groupIds); + + const onOpen = (id: string) => { + if (open.includes(id)) { + setOpen(open.filter((id2) => id2 !== id)); + } else { + setOpen([...open, id]); + } + }; + + const isOpened = (name: string) => { + return open.includes(name); + }; + + const onSelect = (id: string, radioGroup?: string) => { + if (radioGroup) { + if (selectedRadioGroups[radioGroup] !== id) { + setSelectedRadioGroups({ ...selectedRadioGroups, [radioGroup]: id }); + } + return; + } + if (selected.includes(id)) { + setSelected(selected.filter((id2) => !cfg.deps[id].includes(id2))); + } else { + const newSelected = [...selected, id]; + + cfg.depsChild[id].forEach((child) => { + if (!newSelected.includes(child)) { + newSelected.push(child); + } + }); + + cfg.depsParent[id].forEach((parent) => { + if ( + !newSelected.includes(parent) && + cfg.depsChild[parent].every((child) => newSelected.includes(child)) + ) { + newSelected.push(parent); + } + }); + + setSelected(newSelected); + } + }; + + const isSelected = useCallback( + (id: string, radioGroup?: string) => + radioGroup + ? selectedRadioGroups[radioGroup] === id + : selected.includes(id), + [selected, selectedRadioGroups], + ); + + const toggleLayerControlVisible = () => { + setIsVisible(!isVisible); + }; + + // initialize state from configuration when style is loaded + useEffect(() => { + const applyConfig = () => { + const style = map.getStyle(); + if (!style) return; + // map.getStyle() is typed against maplibre-gl's own (newer) nested copy of + // @maplibre/maplibre-gl-style-spec; structurally identical to the one this + // package depends on, so this cast is safe. + parse( + style as unknown as Parameters[0], + entries, + preferStyle, + ).then((config) => { + setCfg(config); + if (config.opened === true) setIsVisible(true); + if (config.onlyLegend === true) setIsControlable(false); + setSelectedRadioGroups(config.radioIds); + setSelected(config.allIds); + setOpen(config.groupIds); + }); + }; + + // Deliberately gating on map.getStyle() (truthy once the style spec itself is set), NOT + // map.isStyleLoaded() - that also requires every currently-visible tile across every + // source to have finished loading, which parse() has no need to wait for (it only reads + // the style's own sources/layers definitions). Gating on isStyleLoaded() here raced + // intermittently on datasets with many/slow-loading tiles (large vector styles): whether + // it fired at all in time depended on how long tiles happened to take, so it "randomly" + // worked or didn't depending on network conditions - not fixed by the isStyleLoaded() + // check that previously lived here, which was gating on the wrong signal entirely. + applyConfig(); + map.on("style.load", applyConfig); + return () => { + map.off("style.load", applyConfig); + }; + }, [entries, map, preferStyle]); + + // apply selection state to map + useEffect(() => { + cfg.layerIds.forEach((id) => { + if (map.getLayer(id)) { + const visible = map.getLayoutProperty(id, "visibility") !== "none"; + if (visible && !isSelected(id)) { + map.setLayoutProperty(id, "visibility", "none"); + } else if (!visible && isSelected(id)) { + map.setLayoutProperty(id, "visibility", "visible"); + } + } + }); + Object.keys(cfg.radioGroups).forEach((group) => { + cfg.radioGroups[group].forEach((id) => { + if (map.getLayer(id)) { + const visible = map.getLayoutProperty(id, "visibility") !== "none"; + if (visible && !isSelected(id, group)) { + map.setLayoutProperty(id, "visibility", "none"); + } else if (!visible && isSelected(id, group)) { + map.setLayoutProperty(id, "visibility", "visible"); + } + } + }); + }); + }, [map, cfg, isSelected]); + + return ( + <> + + {cfg?.style && ( + + )} + + ); +} + +Control.displayName = "Control"; + +export default Control; diff --git a/packages/react/layer-control-maplibre/src/ControlButton.tsx b/packages/react/layer-control-maplibre/src/ControlButton.tsx new file mode 100644 index 0000000..1f26301 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/ControlButton.tsx @@ -0,0 +1,43 @@ +import { useState } from "react"; + +export interface ControlButtonProps { + isEnabled: boolean; + toggle: () => void; +} + +function ControlButton({ isEnabled, toggle }: ControlButtonProps) { + const [hover, setHover] = useState(false); + + return ( +
+ +
+ ); +} + +ControlButton.displayName = "ControlButton"; + +export default ControlButton; diff --git a/packages/react/layer-control-maplibre/src/ControlPanel.tsx b/packages/react/layer-control-maplibre/src/ControlPanel.tsx new file mode 100644 index 0000000..ac1cd21 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/ControlPanel.tsx @@ -0,0 +1,111 @@ +import type { + HydratedEntry, + StyleWithSpriteLoaded, +} from "@xtramaps/layer-control-maplibre"; +import { Fragment } from "react"; +import { Container, Row } from "reactstrap"; +import Group from "./Group"; +import Header from "./Header"; +import Layer from "./Layer"; +import Separator from "./Separator"; + +export interface ControlPanelProps { + entries: HydratedEntry[]; + style: (StyleWithSpriteLoaded & { zoom?: number }) | null; + maxHeight: number; + isVisible: boolean; + isControlable: boolean; + isOpened: (id: string) => boolean; + isSelected: (id: string, radioGroup?: string) => boolean; + onSelect: (id: string, radioGroup?: string) => void; + onOpen: (id: string) => void; +} + +function ControlPanel({ + entries, + style, + maxHeight, + isVisible, + isControlable, + isOpened, + isSelected, + onSelect, + onOpen, +}: ControlPanelProps) { + let lastWasSingle = true; + + return ( + + {entries.map((entry, i) => { + const isSingle = entry.type === "layer" || entry.type === "merge-group"; + const newSection = i === 0 || !lastWasSingle || !isSingle; + lastWasSingle = isSingle; + + return entry.type === "layer" ? ( + + + + + + + ) : ( + + + {entry.type !== "merge-group" && ( +
+ )} + + + ); + })} + + ); +} + +ControlPanel.displayName = "ControlPanel"; + +export default ControlPanel; diff --git a/packages/react/layer-control-maplibre/src/Group.tsx b/packages/react/layer-control-maplibre/src/Group.tsx new file mode 100644 index 0000000..0b13a39 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/Group.tsx @@ -0,0 +1,139 @@ +import type { + HydratedEntry, + StyleWithSpriteLoaded, +} from "@xtramaps/layer-control-maplibre"; +import { Fragment } from "react"; +import { Collapse, Row } from "reactstrap"; +import Header from "./Header"; +import Layer from "./Layer"; +import Separator from "./Separator"; + +export interface GroupProps { + parent: HydratedEntry; + style: (StyleWithSpriteLoaded & { zoom?: number }) | null; + level?: number; + isControlable: boolean; + isOpened: (id: string) => boolean; + isSelected: (id: string, radioGroup?: string) => boolean; + onSelect: (id: string, radioGroup?: string) => void; + onOpen: (id: string) => void; +} + +function Group({ + parent, + style, + level = 0, + isControlable, + isOpened, + isSelected, + onSelect, + onOpen, +}: GroupProps) { + if (parent.type === "radio-group") { + return ( + + {parent.entries.map((entry, i) => { + return ( + + {i > 0 && } + + + + + ); + })} + + ); + } + if (parent.type === "merge-group") { + return ( + + + + ); + } + if (parent.type === "group") { + return ( + + {parent.entries.map((entry, i) => { + if (entry.type === "layer") { + return ( + + {i > 0 && } + + + + + ); + } + return ( + + {i > 0 && } + {entry.type !== "merge-group" && ( +
+ )} + + + ); + })} + + ); + } + + return null; +} + +Group.displayName = "Group"; + +export default Group; diff --git a/packages/react/layer-control-maplibre/src/Header.tsx b/packages/react/layer-control-maplibre/src/Header.tsx new file mode 100644 index 0000000..a8c50aa --- /dev/null +++ b/packages/react/layer-control-maplibre/src/Header.tsx @@ -0,0 +1,130 @@ +import type { ReactNode } from "react"; +import { Col, FormGroup, Input, Label, Row } from "reactstrap"; +import CollapseButton from "./CollapseButton"; + +interface HeaderPlainProps { + label?: string; + level?: number; +} + +function HeaderPlain({ label, level = 0 }: HeaderPlainProps) { + return ( + + {label} + + ); +} + +export interface HeaderCheckProps { + id: string; + level?: number; + radioGroup?: string; + isControlable?: boolean; + isSelected: (id: string, radioGroup?: string) => boolean; + onSelect: (id: string, radioGroup?: string) => void; + children: ReactNode; +} + +export function HeaderCheck({ + id, + level = 0, + radioGroup, + isControlable = true, + isSelected, + onSelect, + children, +}: HeaderCheckProps) { + return isControlable ? ( + + + + ) : ( +
+ {children} +
+ ); +} + +HeaderCheck.displayName = "HeaderCheck"; + +export interface HeaderProps { + id: string; + label?: string; + level?: number; + check?: boolean; + isControlable?: boolean; + isOpened: (id: string) => boolean; + onOpen: (id: string) => void; + isSelected: (id: string, radioGroup?: string) => boolean; + onSelect: (id: string, radioGroup?: string) => void; +} + +function Header({ + id, + label, + level = 0, + check = false, + isControlable = true, + isOpened, + onOpen, + isSelected, + onSelect, +}: HeaderProps) { + return ( + + + {check ? ( + + + + ) : ( + + )} + + + onOpen(id)} + /> + + + ); +} + +Header.displayName = "Header"; + +export default Header; diff --git a/packages/react/layer-control-maplibre/src/Layer.tsx b/packages/react/layer-control-maplibre/src/Layer.tsx new file mode 100644 index 0000000..bf1aa0a --- /dev/null +++ b/packages/react/layer-control-maplibre/src/Layer.tsx @@ -0,0 +1,93 @@ +import type { + HydratedLayer, + StyleWithSpriteLoaded, +} from "@xtramaps/layer-control-maplibre"; +import { LegendSymbolReact } from "@xtramaps/legend-symbols-maplibre-react"; +import { Col } from "reactstrap"; +import { HeaderCheck } from "./Header"; + +export interface LayerProps { + id: string; + label?: string; + icons: HydratedLayer[]; + style: (StyleWithSpriteLoaded & { zoom?: number }) | null; + level?: number; + isControlable?: boolean; + radioGroup?: string; + isSelected: (id: string, radioGroup?: string) => boolean; + onSelect: (id: string, radioGroup?: string) => void; +} + +function Layer({ + id, + label, + icons, + style, + level = 0, + isControlable = false, + radioGroup, + isSelected, + onSelect, +}: LayerProps) { + const hasFill = icons.some( + (icon) => style?.layers?.[icon.index as number]?.type === "fill", + ); + const hasRaster = icons.some( + (icon) => style?.layers?.[icon.index as number]?.type === "raster", + ); + const cleanIcons = hasFill + ? icons.filter( + (icon) => style?.layers?.[icon.index as number]?.type !== "line", + ) + : icons; + + return ( + + +
+ {style?.layers && + cleanIcons.map( + (icon) => + style.layers[icon.index as number] && ( + + ), + )} +
+ {label || id} +
+ + ); +} + +Layer.displayName = "Layer"; + +export default Layer; diff --git a/packages/react/layer-control-maplibre/src/LayerControl.stories.tsx b/packages/react/layer-control-maplibre/src/LayerControl.stories.tsx new file mode 100644 index 0000000..a6a1e1c --- /dev/null +++ b/packages/react/layer-control-maplibre/src/LayerControl.stories.tsx @@ -0,0 +1,252 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import MapLibre from "@xtramaps/web-map-maplibre-react"; +import "@xtramaps/web-map-maplibre-react/dist/index.css"; +import type { Map as MaplibreMap } from "maplibre-gl"; +import { expect, userEvent, waitFor, within } from "storybook/test"; +import LayerControl from "./LayerControl"; + +const STYLE_URL = "https://demo.ldproxy.net/daraa/styles/topographic?f=mbs"; + +// Real layer ids from the Daraa "topographic" style, structured to exercise every entry +// kind LayerControl supports - mirrors the layerGroupControl example from the original +// ogcapi-html MapLibre/stories.jsx. preferStyle is disabled so these entries are used +// deterministically instead of the style's own embedded "ldproxy:layerControl" metadata. +const entries = [ + { + id: "outer", + label: "Overview", + type: "group" as const, + entries: [ + { + id: "Railway", + type: "merge-group" as const, + entries: [ + { id: "transportationgroundcrv.0a" }, + { id: "transportationgroundcrv.0b" }, + ], + }, + { + id: "Hydro", + label: "Hydrography", + type: "group" as const, + entries: ["hydrographycrv", { id: "hydrographysrf" }], + }, + ], + }, + { + id: "Basemap", + label: "Basemap (radio)", + type: "radio-group" as const, + entries: ["utilityinfrastructurepnt", { id: "agriculturesrf" }], + }, + { + id: "Transportation", + type: "merge-group" as const, + sourceLayer: "TransportationGroundCrv", + }, + { + id: "Settlement", + type: "merge-group" as const, + sourceLayer: "SettlementSrf", + }, + "militarysrf", +]; + +// Captured via MapLibre's `custom` escape hatch so the play function can assert on the +// real maplibre-gl Map instance, not just the LayerControl's own DOM/checkbox state. +let capturedMap: MaplibreMap | null = null; + +function LayerControlDemo() { + return ( + { + capturedMap = map; + }} + > + + + ); +} + +const meta = { + title: "layer-control-maplibre/LayerControl", + component: LayerControlDemo, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +/** Finds the checkbox/radio input associated with a visible label's text. */ +async function findInputByText( + canvas: ReturnType, + text: string, +): Promise { + const label = await canvas.findByText(text, {}, { timeout: 15000 }); + const input = label.closest("label")?.querySelector("input"); + await expect(input).not.toBeNull(); + return input as HTMLInputElement; +} + +export const NestedGroups: Story = { + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step( + "layer control panel renders the configured entries", + async () => { + await canvas.findByText("Basemap (radio)", {}, { timeout: 15000 }); + await canvas.findByText("militarysrf", {}, { timeout: 15000 }); + }, + ); + + await step( + "toggling a layer checkbox unchecks it and hides it on the map", + async () => { + const checkbox = await findInputByText(canvas, "militarysrf"); + await expect(checkbox).toBeChecked(); + + await userEvent.click(checkbox); + await expect(checkbox).not.toBeChecked(); + + await waitFor(() => expect(capturedMap).not.toBeNull()); + await waitFor(() => + expect( + capturedMap?.getLayoutProperty("militarysrf", "visibility"), + ).toBe("none"), + ); + }, + ); + + await step( + "switching a radio-group entry is mutually exclusive on the map", + async () => { + const utilityRadio = await findInputByText( + canvas, + "utilityinfrastructurepnt", + ); + const agricultureRadio = await findInputByText( + canvas, + "agriculturesrf", + ); + + // The first entry of a radio-group is selected by default. + await expect(utilityRadio).toBeChecked(); + await expect(agricultureRadio).not.toBeChecked(); + await waitFor(() => + expect( + capturedMap?.getLayoutProperty("agriculturesrf", "visibility"), + ).toBe("none"), + ); + + await userEvent.click(agricultureRadio); + + await expect(agricultureRadio).toBeChecked(); + await expect(utilityRadio).not.toBeChecked(); + await waitFor(() => + expect( + capturedMap?.getLayoutProperty("agriculturesrf", "visibility"), + ).not.toBe("none"), + ); + await waitFor(() => + expect( + capturedMap?.getLayoutProperty( + "utilityinfrastructurepnt", + "visibility", + ), + ).toBe("none"), + ); + }, + ); + + await step( + "unchecking a sourceLayer merge-group hides every real layer it auto-collected", + async () => { + const transportationCheckbox = await findInputByText( + canvas, + "Transportation", + ); + await expect(transportationCheckbox).toBeChecked(); + + await userEvent.click(transportationCheckbox); + await expect(transportationCheckbox).not.toBeChecked(); + + // "Transportation" itself isn't a real style layer - it's a virtual entry that + // auto-collected every real layer sharing the "TransportationGroundCrv" source-layer. + // Confirm several of the actual underlying layers were hidden together. + for (const layerId of [ + "transportationgroundcrv.1", + "transportationgroundcrv.2", + "transportationgroundcrv.3", + ]) { + await waitFor(() => + expect(capturedMap?.getLayoutProperty(layerId, "visibility")).toBe( + "none", + ), + ); + } + }, + ); + }, +}; + +// The style's own metadata is what production actually uses day to day (LayerControl +// defaults to `preferStyle: true`), yet it was never exercised by an automated test - the +// story above deliberately disables it for deterministic, hand-authored entries. This +// story renders LayerControl "as ogcapi-html would" - no `entries` prop at all. +export const StyleMetadata: Story = { + render: () => ( + { + capturedMap = map; + }} + > + + + ), + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step( + "the style's own ldproxy:layerControl metadata is used", + async () => { + // Labels below come from the Daraa style's embedded metadata, not from any prop + // this story passes in - if the demo style's metadata ever changes shape, this is + // the test that will need updating, not the LayerControl code. + await canvas.findByText("Basemap", {}, { timeout: 15000 }); + await canvas.findByText( + "Agriculture (Surfaces)", + {}, + { timeout: 15000 }, + ); + }, + ); + + await step( + "toggling a checkbox from the metadata-driven tree hides it on the map", + async () => { + const checkbox = await findInputByText( + canvas, + "Agriculture (Surfaces)", + ); + await expect(checkbox).toBeChecked(); + + await userEvent.click(checkbox); + await expect(checkbox).not.toBeChecked(); + + await waitFor(() => expect(capturedMap).not.toBeNull()); + await waitFor(() => + expect( + capturedMap?.getLayoutProperty("agriculturesrf", "visibility"), + ).toBe("none"), + ); + }, + ); + }, +}; diff --git a/packages/react/layer-control-maplibre/src/LayerControl.tsx b/packages/react/layer-control-maplibre/src/LayerControl.tsx new file mode 100644 index 0000000..803bcd1 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/LayerControl.tsx @@ -0,0 +1,59 @@ +import { useControl, useMap } from "@vis.gl/react-maplibre"; +import type { LayerControlEntry } from "@xtramaps/layer-control-maplibre"; +import type { IControl } from "maplibre-gl"; +import { useRef } from "react"; +import { createPortal } from "react-dom"; +import Control from "./Control"; + +export interface LayerControlProps { + opened?: boolean; + onlyLegend?: boolean; + preferStyle?: boolean; + entries?: LayerControlEntry[]; +} + +function LayerControl({ + opened = false, + onlyLegend = false, + preferStyle = true, + entries = [], +}: LayerControlProps) { + const { current: mapRef } = useMap(); + const containerRef = useRef(null); + if (!containerRef.current) { + containerRef.current = document.createElement("div"); + } + const container = containerRef.current; + + useControl( + () => ({ + onAdd: () => container, + onRemove: () => { + container.parentNode?.removeChild(container); + }, + }), + { position: "top-left" }, + ); + + if (!mapRef) { + return null; + } + + const map = mapRef.getMap(); + + return createPortal( + , + container, + ); +} + +LayerControl.displayName = "LayerControl"; + +export default LayerControl; diff --git a/packages/react/layer-control-maplibre/src/Separator.tsx b/packages/react/layer-control-maplibre/src/Separator.tsx new file mode 100644 index 0000000..25ef284 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/Separator.tsx @@ -0,0 +1,22 @@ +import { Row } from "reactstrap"; + +export interface SeparatorProps { + section?: boolean; + first?: boolean; +} + +function Separator({ section = false, first = false }: SeparatorProps) { + return ( + + ); +} + +Separator.displayName = "Separator"; + +export default Separator; diff --git a/packages/react/layer-control-maplibre/src/index.ts b/packages/react/layer-control-maplibre/src/index.ts new file mode 100644 index 0000000..63690f1 --- /dev/null +++ b/packages/react/layer-control-maplibre/src/index.ts @@ -0,0 +1,2 @@ +export type { LayerControlProps } from "./LayerControl"; +export { default as LayerControl, default } from "./LayerControl"; diff --git a/packages/react/layer-control-maplibre/tsconfig.json b/packages/react/layer-control-maplibre/tsconfig.json new file mode 100644 index 0000000..214853b --- /dev/null +++ b/packages/react/layer-control-maplibre/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/packages/react/layer-control-maplibre/vite.config.ts b/packages/react/layer-control-maplibre/vite.config.ts new file mode 100644 index 0000000..12e92c2 --- /dev/null +++ b/packages/react/layer-control-maplibre/vite.config.ts @@ -0,0 +1,16 @@ +import react from "@vitejs/plugin-react"; +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + plugins: [react()], + external: [ + "react", + "react-dom", + "react/jsx-runtime", + "@vis.gl/react-maplibre", + "maplibre-gl", + "reactstrap", + "@xtramaps/layer-control-maplibre", + "@xtramaps/legend-symbols-maplibre-react", + ], +}); diff --git a/packages/react/legend-symbols-maplibre/package.json b/packages/react/legend-symbols-maplibre/package.json index e249862..4b48099 100644 --- a/packages/react/legend-symbols-maplibre/package.json +++ b/packages/react/legend-symbols-maplibre/package.json @@ -39,10 +39,13 @@ "react-dom": "^18.0.0 || ^19.0.0" }, "devDependencies": { + "@maplibre/maplibre-gl-style-spec": "^22.0.0", + "@storybook/react-vite": "10.5.7", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.0", "react": "^19.2.4", - "react-dom": "^19.2.4" + "react-dom": "^19.2.4", + "storybook": "10.5.7" } } diff --git a/packages/react/legend-symbols-maplibre/src/LegendSymbol.stories.tsx b/packages/react/legend-symbols-maplibre/src/LegendSymbol.stories.tsx new file mode 100644 index 0000000..8b4cc06 --- /dev/null +++ b/packages/react/legend-symbols-maplibre/src/LegendSymbol.stories.tsx @@ -0,0 +1,128 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { createLegend } from "@xtramaps/legend-symbols-maplibre-react"; +import { useEffect, useState } from "react"; +import { expect, within } from "storybook/test"; + +const STYLE_URL = "https://demo.ldproxy.net/daraa/styles/topographic?f=mbs"; +const ZOOM = 14; + +interface StyleLayer { + id: string; + type: string; + layout?: { visibility?: string }; +} + +function LegendDemo() { + const [layers, setLayers] = useState(null); + const [Legend, setLegend] = useState + > | null>(null); + const [error, setError] = useState(null); + + useEffect(() => { + fetch(STYLE_URL) + .then((response) => response.json()) + .then((style) => { + setLayers(style.layers); + return createLegend(style, ZOOM); + }) + .then((legend) => { + setLegend(() => legend); + }) + .catch((e) => setError(e.message)); + }, []); + + if (error) { + return

Error: {error}

; + } + + if (!layers || !Legend) { + return ( +

+ Loading style... +

+ ); + } + + const visibleLayers = layers.filter( + (l) => l.layout?.visibility !== "none" && l.type !== "background", + ); + + return ( +
+

Daraa Topographic Legend

+
+ {visibleLayers.map((layer) => ( +
+
+ +
+ {layer.id} +
+ ))} +
+
+ ); +} + +const meta = { + title: "legend-symbols-maplibre/LegendSymbol", + component: LegendDemo, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const DaraaTopographic: Story = { + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step( + "legend renders with icons for every visible layer", + async () => { + const legend = await canvas.findByTestId( + "legend", + {}, + { timeout: 10000 }, + ); + await expect(legend).toBeInTheDocument(); + + const icons = legend.querySelectorAll("svg, img, canvas"); + await expect(icons.length).toBeGreaterThan(0); + }, + ); + }, +}; diff --git a/packages/react/web-map-cesium/README.md b/packages/react/web-map-cesium/README.md new file mode 100644 index 0000000..4765a14 --- /dev/null +++ b/packages/react/web-map-cesium/README.md @@ -0,0 +1,61 @@ +# @xtramaps/web-map-cesium-react + +React Cesium map component. Wraps [`@xtramaps/web-map-cesium`](https://github.com/ldproxy/xtramaps/tree/main/packages/core/web-map-cesium) - migrated from `ogcapi-html`'s `Cesium` component onto current `@cesium/engine`/`@cesium/widgets` (`^26`/`^16`). + +Unlike the MapLibre/OpenLayers migrations, the original `Cesium` component was never a React component - it was a single imperative function, called once per page load, with no cleanup, no error handling on its async tileset/style loading, and no LayerControl/legend equivalent (there never was one for Cesium either). This wrapper is new: a thin `useEffect`-driven component around the same viewer/tileset logic, plus two deliberate additions the original didn't have - + +- `viewer.destroy()` runs on unmount, since a reusable component can mount/unmount repeatedly (Storybook, demos, consumer apps), unlike the original's one-shot page script +- viewer/tileset creation failures are caught and logged instead of failing silently + +The component mounts once; prop changes after the initial render are not reflected (Cesium never had a dynamic update path the way OpenLayers/MapLibre's TileMatrixSet switching does). + +## Example + +Browse it interactively via Storybook: `npm run storybook` from the repo root, then open the `web-map-cesium/CesiumMap` story. + +Not published to npm yet, so there is no CDN-hosted live example. To try the standalone demo locally: + +```sh +git clone https://github.com/ldproxy/xtramaps.git +cd xtramaps +npm install +npm run build +python3 -m http.server 8080 # any static file server serving the repo root works +``` + +Then open `http://localhost:8080/packages/react/web-map-cesium/examples/web-map-cesium.html`. A plain `file://` open won't work - browsers block ES module imports from `file://` URLs. + +## Install + +```sh +npm install @xtramaps/web-map-cesium-react +``` + +Requires `react`, `react-dom` (`^18.0.0 || ^19.0.0`), `@cesium/engine` (`^26.0.0`), and `@cesium/widgets` (`^16.0.0`) as peer dependencies. Import `@xtramaps/web-map-cesium-react/dist/index.css` once - it bundles Cesium's own widget stylesheet plus the component's own styles. + +Cesium also needs its `Workers`/`Assets`/`ThirdParty` static files served separately, with `window.CESIUM_BASE_URL` set to point at them **before** the component mounts - this package does not do that for you. The example page sets it to a CDN-hosted, version-matched build (`https://unpkg.com/cesium@/Build/Cesium/`); a production app would typically copy these files into its own build output instead (e.g. via `vite-plugin-static-copy` or an equivalent bundler plugin). + +## Usage + +```tsx +import CesiumMap from "@xtramaps/web-map-cesium-react"; +import "@xtramaps/web-map-cesium-react/dist/index.css"; + +window.CESIUM_BASE_URL = "https://unpkg.com/cesium@1.144.0/Build/Cesium/"; + +; +``` + +### Exports + +- `CesiumMap` (default) - the map component + +## License + +MIT diff --git a/packages/react/web-map-cesium/examples/web-map-cesium.html b/packages/react/web-map-cesium/examples/web-map-cesium.html new file mode 100644 index 0000000..7addc6d --- /dev/null +++ b/packages/react/web-map-cesium/examples/web-map-cesium.html @@ -0,0 +1,110 @@ + + + + + + Web Map Cesium - React Example + + + + + + +

Web Map Cesium

+

+ @xtramaps/web-map-cesium-react - migrated from + ogcapi-html onto current @cesium/engine/@cesium/widgets + (26.2.0/16.1.1). Data: "3D Buildings in Cologne (LoD2)" from + demo.ldproxy.net. +

+ +
+

Buildings 3D Tileset

+
+
+ + + + diff --git a/packages/react/web-map-cesium/package.json b/packages/react/web-map-cesium/package.json new file mode 100644 index 0000000..f458a5e --- /dev/null +++ b/packages/react/web-map-cesium/package.json @@ -0,0 +1,55 @@ +{ + "name": "@xtramaps/web-map-cesium-react", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/react/web-map-cesium#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/react/web-map-cesium" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./dist/index.css": "./dist/index.css" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@xtramaps/web-map-cesium": "1.0.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "@cesium/engine": "^26.0.0", + "@cesium/widgets": "^16.0.0" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@cesium/engine": "^26.2.0", + "@cesium/widgets": "^16.1.1", + "@vitejs/plugin-react": "^6.0.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "storybook": "10.5.7" + } +} diff --git a/packages/react/web-map-cesium/src/CesiumMap.stories.tsx b/packages/react/web-map-cesium/src/CesiumMap.stories.tsx new file mode 100644 index 0000000..ea73326 --- /dev/null +++ b/packages/react/web-map-cesium/src/CesiumMap.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, waitFor } from "storybook/test"; +import CesiumMap from "./CesiumMap"; + +// Cesium needs its Workers/Assets/ThirdParty static files served separately and +// `window.CESIUM_BASE_URL` set before any Viewer is created - consuming apps are +// responsible for this themselves (see the package README). Here we point at the +// prebuilt static assets shipped by the unified `cesium` npm package on unpkg, +// version-matched to this repo's `@cesium/engine`/`@cesium/widgets` dependency. +declare global { + interface Window { + CESIUM_BASE_URL?: string; + } +} +window.CESIUM_BASE_URL = "https://unpkg.com/cesium@1.144.0/Build/Cesium/"; + +// Real dataset from ldproxy's own demo deployment - "3D Buildings in Cologne +// (LoD2)" - the same one the original ogcapi-html Cesium component was built to +// render (`FeaturesView`/`TilesetView`'s glTF-from-features and Tiles3D paths). +const BACKGROUND_URL = + "https://sgx.geodatenzentrum.de/wmts_basemapde/tile/1.0.0/de_basemapde_web_raster_farbe/default/GLOBAL_WEBMERCATOR/{TileMatrix}/{TileRow}/{TileCol}.png"; +const ATTRIBUTION = "Geobasis NRW | © basemap.de / BKG"; +// No `?f=json` here (unlike a typical ldproxy format-negotiation URL): Cesium's +// `Resource` propagates the tileset URL's query parameters onto every derived +// subresource request (subtrees, tile content) - harmless for e.g. an auth +// token, but ldproxy's `.subtree` endpoint 400s if `f=json` rides along. The +// bare URL still negotiates to JSON by default (via `Accept`), so it's dropped. +const TILESET_URL = + "https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles"; +const STYLE_URL = + "https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type?f=3dtiles"; + +function CesiumMapDemo() { + return ( +
+ +
+ ); +} + +const meta = { + title: "web-map-cesium/CesiumMap", + component: CesiumMapDemo, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const BuildingsTileset: Story = { + play: async ({ canvasElement, step }) => { + await step("globe renders with a background layer", async () => { + await waitFor( + () => expect(canvasElement.querySelector("canvas")).not.toBeNull(), + { timeout: 20000 }, + ); + }); + }, +}; diff --git a/packages/react/web-map-cesium/src/CesiumMap.tsx b/packages/react/web-map-cesium/src/CesiumMap.tsx new file mode 100644 index 0000000..16f148a --- /dev/null +++ b/packages/react/web-map-cesium/src/CesiumMap.tsx @@ -0,0 +1,54 @@ +import type { Viewer } from "@cesium/widgets"; +import type { CesiumMapOptions } from "@xtramaps/web-map-cesium"; +import { createViewer, loadTileset } from "@xtramaps/web-map-cesium"; +import { useEffect, useRef } from "react"; + +// The original ported this as ~19 individual widget CSS imports, with a comment +// that the aggregate `widgets.css` "does not work because of relative engine +// import" under the old webpack build. That relative import (crossing from +// @cesium/widgets/Source into @cesium/engine/Source) resolves correctly under +// this repo's Vite build (verified via a full production build + browser +// check), so the single aggregate import is used here instead. +import "@cesium/widgets/Source/widgets.css"; +import "./custom.css"; + +export interface CesiumMapProps extends CesiumMapOptions {} + +export function CesiumMap(props: CesiumMapProps) { + const containerRef = useRef(null); + + // biome-ignore lint/correctness/useExhaustiveDependencies: mounted once, matching the original's single page-lifetime mount - Cesium never had a dynamic prop-update path (unlike the OpenLayers/MapLibre TMS-switching equivalents) + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + let cancelled = false; + let viewer: Viewer | undefined; + + createViewer(container, props) + .then((createdViewer) => { + if (cancelled) { + createdViewer.destroy(); + return; + } + viewer = createdViewer; + if (props.tileset?.url) { + loadTileset(createdViewer, props.tileset, props.additionalStyleUrl); + } + }) + .catch((error) => { + console.error("[web-map-cesium-react] Failed to create viewer:", error); + }); + + return () => { + cancelled = true; + viewer?.destroy(); + }; + }, []); + + return
; +} + +CesiumMap.displayName = "CesiumMap"; + +export default CesiumMap; diff --git a/packages/react/web-map-cesium/src/custom.css b/packages/react/web-map-cesium/src/custom.css new file mode 100644 index 0000000..d11a047 --- /dev/null +++ b/packages/react/web-map-cesium/src/custom.css @@ -0,0 +1,16 @@ +.cesium-map-container { + position: relative; + width: 100%; + height: 100%; +} + +/* Ported from ogcapi-html's Cesium/style.css - repositions the credit bar and + hides the duplicate "Cesium ion" link, leaving only the custom attribution. */ +.cesium-viewer-bottom { + left: 5px !important; + bottom: 0 !important; +} + +.cesium-viewer-bottom a:nth-child(2) { + display: none !important; +} diff --git a/packages/react/web-map-cesium/src/index.ts b/packages/react/web-map-cesium/src/index.ts new file mode 100644 index 0000000..611b187 --- /dev/null +++ b/packages/react/web-map-cesium/src/index.ts @@ -0,0 +1,2 @@ +export type { CesiumMapProps } from "./CesiumMap"; +export { default, default as CesiumMap } from "./CesiumMap"; diff --git a/packages/react/web-map-cesium/tsconfig.json b/packages/react/web-map-cesium/tsconfig.json new file mode 100644 index 0000000..214853b --- /dev/null +++ b/packages/react/web-map-cesium/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/packages/react/web-map-cesium/vite.config.ts b/packages/react/web-map-cesium/vite.config.ts new file mode 100644 index 0000000..a792345 --- /dev/null +++ b/packages/react/web-map-cesium/vite.config.ts @@ -0,0 +1,14 @@ +import react from "@vitejs/plugin-react"; +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + plugins: [react()], + external: [ + "react", + "react-dom", + "react/jsx-runtime", + "@cesium/engine", + "@cesium/widgets", + "@xtramaps/web-map-cesium", + ], +}); diff --git a/packages/react/web-map-maplibre/README.md b/packages/react/web-map-maplibre/README.md new file mode 100644 index 0000000..b4d45cc --- /dev/null +++ b/packages/react/web-map-maplibre/README.md @@ -0,0 +1,49 @@ +# @xtramaps/web-map-maplibre-react + +React MapLibre map component. Wraps [`@xtramaps/web-map-maplibre`](https://github.com/ldproxy/xtramaps/tree/main/packages/core/web-map-maplibre) and [`@vis.gl/react-maplibre`](https://visgl.github.io/react-maplibre/) — replaces the unmaintained `react-maplibre-ui`. + +## Example + +Not published to npm yet, so there is no CDN-hosted live example (once published, this section will link one the same way [`legend-symbols-maplibre-react`](https://github.com/ldproxy/xtramaps/tree/main/packages/react/legend-symbols-maplibre#example) does). To try the standalone demo locally: + +```sh +git clone https://github.com/ldproxy/xtramaps.git +cd xtramaps +npm install +npm run build +python3 -m http.server 8080 # any static file server serving the repo root works +``` + +Then open `http://localhost:8080/packages/react/web-map-maplibre/examples/web-map.html`. A plain `file://` open won't work — browsers block ES module imports from `file://` URLs. + +## Install + +```sh +npm install @xtramaps/web-map-maplibre-react +``` + +Requires `react`, `react-dom` (`^18.0.0 || ^19.0.0`), `@vis.gl/react-maplibre` (`^8.1.2`), and `maplibre-gl` (`^6.0.0`) as peer dependencies. Import `@xtramaps/web-map-maplibre-react/dist/index.css` once — it bundles `maplibre-gl.css`, `mapbox-gl-draw.css`, and the component's own styles. + +## Usage + +```tsx +import MapLibre from "@xtramaps/web-map-maplibre-react"; +import "@xtramaps/web-map-maplibre-react/dist/index.css"; + +; +``` + +### Exports + +- `MapLibre` (default) - the map component +- `Configuration` - data-layer/popup wiring, used internally by `MapLibre` but exported for custom compositions +- `CanvasOverlay` (alias `CanvasPlugin`) - renders arbitrary React children into the map canvas, with `map`/`maplibre` injected as props +- `polygonFromBounds` - re-exported from core + +## License + +MIT diff --git a/packages/react/web-map-maplibre/examples/web-map.html b/packages/react/web-map-maplibre/examples/web-map.html new file mode 100644 index 0000000..5cf2a0f --- /dev/null +++ b/packages/react/web-map-maplibre/examples/web-map.html @@ -0,0 +1,160 @@ + + + + + + Web Map MapLibre - React Example + + + + + + +

Web Map MapLibre

+

+ @xtramaps/web-map-maplibre-react - built on + @vis.gl/react-maplibre instead of the unmaintained + react-maplibre-ui. +

+ +
+
+

Basemap from styleUrl

+
+
+
+

GeoJSON data layer + hover popup + canvas overlay

+
+
+
+ + + + diff --git a/packages/react/web-map-maplibre/package.json b/packages/react/web-map-maplibre/package.json new file mode 100644 index 0000000..c581d7f --- /dev/null +++ b/packages/react/web-map-maplibre/package.json @@ -0,0 +1,58 @@ +{ + "name": "@xtramaps/web-map-maplibre-react", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/react/web-map-maplibre#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/react/web-map-maplibre" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./dist/index.css": "./dist/index.css" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@xtramaps/web-map-maplibre": "1.0.0", + "@mapbox/mapbox-gl-draw": "^1.5.0", + "@turf/combine": "^7.2.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "@vis.gl/react-maplibre": "^8.1.2", + "maplibre-gl": "^6.0.0" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/mapbox__mapbox-gl-draw": "^1.4.8", + "@vitejs/plugin-react": "^6.0.0", + "@vis.gl/react-maplibre": "^8.1.2", + "maplibre-gl": "^6.3.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "storybook": "10.5.7" + } +} diff --git a/packages/react/web-map-maplibre/src/CanvasOverlay.tsx b/packages/react/web-map-maplibre/src/CanvasOverlay.tsx new file mode 100644 index 0000000..33d16ab --- /dev/null +++ b/packages/react/web-map-maplibre/src/CanvasOverlay.tsx @@ -0,0 +1,48 @@ +import { useMap } from "@vis.gl/react-maplibre"; +import * as maplibregl from "maplibre-gl"; +import type { ReactElement } from "react"; +import { cloneElement, useEffect, useState } from "react"; +import { createPortal } from "react-dom"; + +export interface CanvasOverlayInjectedProps { + map: maplibregl.Map; + maplibre: typeof maplibregl; +} + +export interface CanvasOverlayProps { + children: ReactElement>; +} + +function CanvasOverlay({ children }: CanvasOverlayProps) { + const { current: mapRef } = useMap(); + const [wrapper, setWrapper] = useState(null); + + useEffect(() => { + if (!mapRef) return; + + const canvas = mapRef.getMap().getCanvasContainer(); + const el = document.createElement("div"); + el.className = "canvas-container"; + canvas.appendChild(el); + setWrapper(el); + + return () => { + canvas.removeChild(el); + }; + }, [mapRef]); + + if (!wrapper || !mapRef) { + return null; + } + + const childrenWithMap = cloneElement(children, { + map: mapRef.getMap(), + maplibre: maplibregl, + }); + + return createPortal(childrenWithMap, wrapper); +} + +CanvasOverlay.displayName = "CanvasOverlay"; + +export default CanvasOverlay; diff --git a/packages/react/web-map-maplibre/src/Configuration.tsx b/packages/react/web-map-maplibre/src/Configuration.tsx new file mode 100644 index 0000000..eb00ee6 --- /dev/null +++ b/packages/react/web-map-maplibre/src/Configuration.tsx @@ -0,0 +1,181 @@ +import MapboxDraw from "@mapbox/mapbox-gl-draw"; +import combine from "@turf/combine"; +import { useMap } from "@vis.gl/react-maplibre"; +import type { + DefaultStyleOptions, + FeatureTitles, + PopupMode, +} from "@xtramaps/web-map-maplibre"; +import { + addData, + addPopup, + addPopupProps, + hoverLayers, + isDataLayer, +} from "@xtramaps/web-map-maplibre"; +import type { GeoJSON } from "geojson"; +import * as maplibregl from "maplibre-gl"; +import { useEffect } from "react"; + +import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; + +const DEFAULT_STYLE: DefaultStyleOptions = { + color: "#1D4E89", + opacity: 1, + circleRadius: 8, + circleMinZoom: 0, + circleMaxZoom: 24, + lineWidth: 4, + lineMinZoom: 0, + lineMaxZoom: 24, + fillOpacity: 0.2, + outlineWidth: 2, + polygonMinZoom: 0, + polygonMaxZoom: 24, +}; + +export type CustomCallback = ( + map: maplibregl.Map, + maplibre: typeof maplibregl, + MapboxGlDraw: typeof MapboxDraw, + turf: { combine: typeof combine }, +) => void; + +export interface ConfigurationProps { + styleUrl?: string | null; + removeZoomLevelConstraints?: boolean; + data?: string | GeoJSON; + dataType?: "geojson" | "vector" | "raster"; + dataLayers?: Record; + controls?: boolean; + defaultStyle?: Partial; + fitBounds?: boolean; + popup?: PopupMode | null; + featureTitles?: FeatureTitles; + custom?: CustomCallback | null; + showCompass?: boolean; +} + +function Configuration({ + styleUrl = null, + removeZoomLevelConstraints = false, + data, + dataType = "geojson", + dataLayers = {}, + controls = true, + defaultStyle, + fitBounds = true, + popup = null, + featureTitles = {}, + custom = null, + showCompass = true, +}: ConfigurationProps) { + const { current: mapRef } = useMap(); + + // biome-ignore lint/correctness/useExhaustiveDependencies: run once when the map instance becomes available, matching the original useMaplibreUIEffect(..., []) semantics + useEffect(() => { + if (!mapRef) return; + const map = mapRef.getMap(); + + map.addControl( + new maplibregl.AttributionControl({ + compact: false, + }), + ); + map.addControl(new maplibregl.ScaleControl()); + if (controls) { + map.addControl(new maplibregl.NavigationControl({ showCompass })); + } + + // Everything below touches sources/layers, which maplibre-gl refuses ("Style is not done + // loading") if the map's own initial style hasn't finished loading yet - a race that isn't + // guaranteed to lose even on a freshly-mounted map (it did, reliably, right up until an + // unrelated change shifted the timing). Deferring via isStyleLoaded()/once("styledata") + // makes this deterministic instead of relying on incidental slowness elsewhere to win the race. + const runWhenStyleLoaded = (fn: () => void) => { + if (map.isStyleLoaded()) { + fn(); + } else { + map.once("styledata", fn); + } + }; + + if (data) { + // Java emits unset optional style fields (e.g. circleMinZoom) as the literal JS value + // `undefined`, not as an absent key - a plain `{...DEFAULT_STYLE, ...defaultStyle}` spread + // still copies those `undefined`s over DEFAULT_STYLE's real numbers, so every layer ends + // up with minzoom/maxzoom: undefined and maplibre-gl silently refuses to add it. + const style = { ...DEFAULT_STYLE }; + ( + Object.keys(defaultStyle ?? {}) as (keyof DefaultStyleOptions)[] + ).forEach((key) => { + const value = defaultStyle?.[key]; + if (value !== undefined) { + (style as Record)[key] = value; + } + }); + + if (dataType === "geojson" && typeof data === "string") { + fetch(data) + .then((response) => response.json()) + .then((json: GeoJSON) => { + runWhenStyleLoaded(() => + addData( + map, + styleUrl, + removeZoomLevelConstraints, + json, + dataType, + dataLayers, + style, + fitBounds, + popup, + featureTitles, + ), + ); + }); + } else { + runWhenStyleLoaded(() => + addData( + map, + styleUrl, + false, + data, + dataType, + dataLayers, + style, + fitBounds, + popup, + featureTitles, + ), + ); + } + } else if (styleUrl && popup) { + // The style itself is already applied via MapLibre's `mapStyle` prop (see MapLibre.tsx) - + // setting it imperatively here instead used to leave @vis.gl/react-maplibre's own source + // cache bookkeeping out of sync, so vector sources were registered but never actually + // started loading tiles. Only popup wiring for the style's own layers is left to do here. + runWhenStyleLoaded(() => { + if (popup === "HOVER_ID") { + addPopup(map, featureTitles, hoverLayers); + } else if (popup === "CLICK_PROPERTIES") { + const dataLayerIds = (map.getStyle()?.layers ?? []) + .filter((layer) => isDataLayer(layer) === true) + .map((layer) => layer.id); + addPopupProps(map, dataLayerIds); + } + }); + } + if (custom) { + runWhenStyleLoaded(() => + custom(map, maplibregl, MapboxDraw, { combine }), + ); + } + }, [mapRef]); + + return null; +} + +Configuration.displayName = "MapLibreConfiguration"; + +export default Configuration; diff --git a/packages/react/web-map-maplibre/src/MapLibre.stories.tsx b/packages/react/web-map-maplibre/src/MapLibre.stories.tsx new file mode 100644 index 0000000..e34b118 --- /dev/null +++ b/packages/react/web-map-maplibre/src/MapLibre.stories.tsx @@ -0,0 +1,485 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { CanvasOverlayInjectedProps } from "@xtramaps/web-map-maplibre-react"; +import MapLibre, { CanvasOverlay } from "@xtramaps/web-map-maplibre-react"; +import "@xtramaps/web-map-maplibre-react/dist/index.css"; +import type { Map as MaplibreMap } from "maplibre-gl"; +import { expect, waitFor, within } from "storybook/test"; + +const STYLE_URL = "https://demo.ldproxy.net/daraa/styles/topographic?f=mbs"; + +// An inline `data:` URI instead of a live endpoint - this story is a regression test for the +// `defaultStyle` merge logic itself, not for network/remote-data handling, so it shouldn't +// depend on an external server's availability. `fetch()` resolves `data:` URIs natively, so +// this still exercises the exact same `dataUrl` -> fetch -> addData() code path as a real URL. +const INLINE_POLYGON_GEOJSON = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + id: 1, + properties: { name: "Test polygon" }, + geometry: { + type: "Polygon", + coordinates: [ + [ + [6.6, 49.6], + [6.62, 49.6], + [6.62, 49.62], + [6.6, 49.62], + [6.6, 49.6], + ], + ], + }, + }, + ], +}; +const INLINE_POLYGON_DATA_URL = `data:application/json,${encodeURIComponent( + JSON.stringify(INLINE_POLYGON_GEOJSON), +)}`; + +// Without an explicit `backgroundUrl`, MapLibre falls back to a real OpenStreetMap raster +// tile server for the wireframe background - and `map.isStyleLoaded()` (which the component's +// `runWhenStyleLoaded` guard waits on) only resolves once every source's *currently visible +// tiles* have loaded too, not just once the style spec itself is set. That made this story +// flaky in sandboxed/restricted-network test runs, unrelated to the actual thing under test +// (the `defaultStyle` merge). An empty, sourceless style removes that dependency entirely - +// nothing to fetch, so `isStyleLoaded()` resolves immediately. +const EMPTY_BASEMAP_STYLE_URL = `data:application/json,${encodeURIComponent( + JSON.stringify({ version: 8, sources: {}, layers: [] }), +)}`; + +// Captured via MapLibre's `custom` escape hatch so play functions can assert on the real +// maplibre-gl Map instance - `Configuration` itself renders nothing, so there is no other DOM +// output to query. +let capturedMap: MaplibreMap | null = null; + +const meta = { + title: "web-map-maplibre/MapLibre", + component: MapLibre, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +// Regression test for a real bug found during the maplibre-gl 2.x -> 6.x upgrade: Java emits +// unset optional style fields (e.g. `polygonMaxZoom`) as the literal value `undefined`, not as +// an absent key. A naive `{...DEFAULT_STYLE, ...defaultStyle}` spread still copies that +// `undefined` straight over DEFAULT_STYLE's real default, and maplibre-gl then silently +// refuses to add the layer at all (minzoom/maxzoom: undefined). `polygonMaxZoom` below is +// deliberately passed as `undefined` - mirroring the exact Java payload shape - so this keeps +// failing loudly if the merge regresses. +export const GeoJsonDataWithDefaultStyleMerge: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ step }) => { + await step( + "map instance is captured and the polygons layer gets added", + async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor( + () => expect(capturedMap?.getLayer("polygons")).toBeDefined(), + { timeout: 15000 }, + ); + }, + ); + + await step("explicit defaultStyle fields are applied", async () => { + await expect( + capturedMap?.getPaintProperty("polygons", "fill-color"), + ).toBe("#ff3300"); + await expect( + capturedMap?.getPaintProperty("polygons", "fill-opacity"), + ).toBe(0.6); + await expect(capturedMap?.getLayer("polygons")?.minzoom).toBe(5); + }); + + await step( + "an explicit `undefined` field falls back to DEFAULT_STYLE's own default instead of breaking the layer", + async () => { + // Before the fix this was `undefined` and the "polygons" layer never got added at all + // (the first step above would already have failed to find it). + await expect(capturedMap?.getLayer("polygons")?.maxzoom).toBe(24); + }, + ); + }, +}; + +// Regression test for a second real bug from the same upgrade: `Configuration` used to call +// map.addSource/addLayer (and the popup wiring below) before the map's own style had finished +// loading, throwing "Style is not done loading". Also doubles as a health check for +// maplibre-gl's worker file (maplibre-gl-worker.mjs, loaded via a relative import.meta.url path +// bundlers can't statically discover) - if that file is missing or 404s, vector tiles never +// render and queryRenderedFeatures() stays empty forever. +export const StyleUrlRendersVectorTilesWithPopup: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ step }) => { + await step( + "style loads and popup wiring runs without throwing", + async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor(() => expect(capturedMap?.isStyleLoaded()).toBe(true), { + timeout: 15000, + }); + }, + ); + + await step( + "vector tiles actually render (i.e. the worker file loaded correctly)", + async () => { + await waitFor( + () => + expect(capturedMap?.queryRenderedFeatures().length).toBeGreaterThan( + 0, + ), + { timeout: 20000 }, + ); + }, + ); + }, +}; + +// A hovering user is the whole point of `popup="HOVER_ID"` - the previous story only proved +// the wiring doesn't throw, not that a real hover actually surfaces the configured feature +// title. `map.fire()` drives the exact same internal, layer-filtered listener a real mouse +// event would (maplibre-gl queries rendered features at the event's `point` itself), so this +// is a faithful simulation, not a mock of the popup logic. +export const HoverPopupShowsFeatureTitle: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("map and polygon layer are ready", async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor( + () => expect(capturedMap?.getLayer("polygons")).toBeDefined(), + { timeout: 15000 }, + ); + }); + + await step( + "hovering the polygon shows a popup with the configured feature title", + async () => { + const map = capturedMap; + if (!map) throw new Error("map was not captured"); + const point = map.project([6.61, 49.61]); + + // `getLayer` only proves the style *definition* was added, not that a frame has + // actually been painted yet - querying/firing too early finds nothing at `point`. + // Re-firing on every poll (instead of once, up front) makes this robust to that race + // instead of depending on incidental timing. + await waitFor( + () => { + map.fire("mousemove", { point, lngLat: map.unproject(point) }); + expect(canvas.getByText("My Test Feature")).toBeInTheDocument(); + }, + { timeout: 15000 }, + ); + }, + ); + }, +}; + +// `popup="CLICK_PROPERTIES"` is a genuinely separate code path from HOVER_ID above - a +// map-wide (not layer-filtered) click listener that does its own `queryRenderedFeatures` and +// renders a properties table (`showPopupProps`/`featureHtml`) instead of a plain title. +export const ClickPopupShowsFeaturePropertiesTable: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step("map and polygon layer are ready", async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor( + () => expect(capturedMap?.getLayer("polygons")).toBeDefined(), + { timeout: 15000 }, + ); + }); + + await step( + "clicking the polygon shows a popup with its properties table", + async () => { + const map = capturedMap; + if (!map) throw new Error("map was not captured"); + const point = map.project([6.61, 49.61]); + + // Wait for an actual painted frame at `point` first, then fire exactly once - unlike + // the hover story's synchronous handler (which de-dupes on lngLat), showPopupProps + // resolves its content via a `.then()` chain with no such guard, so re-firing on every + // poll (as the hover story does) stacks up overlapping popup renders instead of settling. + await waitFor( + () => + expect( + map.queryRenderedFeatures(point, { layers: ["polygons"] }).length, + ).toBeGreaterThan(0), + { timeout: 15000 }, + ); + + map.fire("click", { point, lngLat: map.unproject(point) }); + + await waitFor( + () => expect(canvas.getByText("Test polygon")).toBeInTheDocument(), + { timeout: 10000 }, + ); + }, + ); + }, +}; + +// Vector/raster `dataType`s take a completely different branch in `addData()` than the +// GeoJSON default-style path above (a plain tile-URL source instead of a fetch + geoJsonLayers +// merge) - never exercised by any story so far. The tile URL itself is fake/unreachable on +// purpose: `map.addSource`/`addLayer` register synchronously and don't need tiles to actually +// load for that. +export const VectorDataTypeAddsLayersPerDataLayer: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ step }) => { + await step( + "the vector source and one layer per dataLayers entry get added", + async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor( + () => expect(capturedMap?.getSource("data")).toBeDefined(), + { + timeout: 15000, + }, + ); + await waitFor( + () => expect(capturedMap?.getLayer("myLayer_polygons")).toBeDefined(), + { timeout: 15000 }, + ); + await waitFor( + () => + expect( + capturedMap?.getLayer("myLayer_polygons-outline"), + ).toBeDefined(), + { timeout: 15000 }, + ); + }, + ); + + await step("defaultStyle applies to vector layers too", async () => { + await expect( + capturedMap?.getPaintProperty("myLayer_polygons", "fill-color"), + ).toBe("#00ff00"); + }); + }, +}; + +export const RasterDataTypeAddsRasterLayer: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ step }) => { + await step("the raster source and its single layer get added", async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor( + () => expect(capturedMap?.getSource("data")).toBeDefined(), + { + timeout: 15000, + }, + ); + await waitFor(() => expect(capturedMap?.getLayer("data")).toBeDefined(), { + timeout: 15000, + }); + await expect(capturedMap?.getLayer("data")?.type).toBe("raster"); + }); + }, +}; + +// `drawBounds` takes a third, separate route into the exact same default-style geojson layers +// (`polygonFromBounds(bounds)` fed through the identical `addData` call as the plain GeoJSON +// story above) - and it flips `fitBounds` off in `MapLibre.tsx` (`fitBounds={!drawBounds && +// Boolean(bounds)}`), which is easy to get backwards in a refactor. Asserting the zoom stays +// put (rather than jumping toward fitBoundsOptions' maxZoom) catches exactly that regression. +export const DrawBoundsRendersStaticBboxOutline: Story = { + render: () => ( + { + capturedMap = map; + }} + /> + ), + play: async ({ step }) => { + await step( + "the bounds are rendered as polygon outline layers, not left as raw data", + async () => { + await waitFor(() => expect(capturedMap).not.toBeNull(), { + timeout: 15000, + }); + await waitFor( + () => expect(capturedMap?.getLayer("polygons")).toBeDefined(), + { timeout: 15000 }, + ); + await waitFor( + () => expect(capturedMap?.getLayer("polygons-outline")).toBeDefined(), + { timeout: 15000 }, + ); + }, + ); + + await step( + "addData does not trigger a second, redundant fitBounds while drawing bounds", + async () => { + // Passing `bounds` always makes @vis.gl/react-maplibre fit the camera once at mount + // (via `initialViewState.bounds`, animate: false - settles immediately). What + // `fitBounds={!drawBounds && Boolean(bounds)}` actually guards against is `addData` + // calling `map.fitBounds()` a *second* time with different options (padding: 50, + // duration: 500) - which would keep the camera animating for another ~500ms. Confirm + // the map has gone idle and then stays put, instead of drifting further. + const map = capturedMap; + if (!map) throw new Error("map was not captured"); + await waitFor(() => expect(map.isMoving()).toBe(false), { + timeout: 5000, + }); + const zoomAfterInitialFit = map.getZoom(); + + await new Promise((resolve) => { + setTimeout(resolve, 700); + }); + await expect(map.getZoom()).toBe(zoomAfterInitialFit); + }, + ); + }, +}; + +function CanvasOverlayProbe({ + map, + maplibre, +}: Partial) { + return ( +
+ {map && maplibre ? "ready" : "not-ready"} +
+ ); +} + +// `CanvasOverlay`/`CanvasPlugin` is the portal ldproxy's own bbox-drawing UI (Resizer/handles, +// which lives outside this repo) mounts into - it never got any coverage of its own here. This +// checks the two things that would silently break every consumer of it: the child actually +// gets portaled into the map's canvas container (not just rendered anywhere), and it really +// receives a live `map`/`maplibre` instance, not stale/undefined props. +export const CanvasOverlayPortalsChildIntoMapCanvas: Story = { + render: () => ( + + + + + + ), + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step( + "the overlay child is portaled into the map's canvas container with live map/maplibre props", + async () => { + const probe = await canvas.findByTestId( + "overlay-probe", + {}, + { + timeout: 15000, + }, + ); + await expect(probe).toHaveTextContent("ready"); + await expect(probe.closest(".canvas-container")).not.toBeNull(); + }, + ); + }, +}; diff --git a/packages/react/web-map-maplibre/src/MapLibre.tsx b/packages/react/web-map-maplibre/src/MapLibre.tsx new file mode 100644 index 0000000..74f4f17 --- /dev/null +++ b/packages/react/web-map-maplibre/src/MapLibre.tsx @@ -0,0 +1,169 @@ +import { Map as MapLibreGlMap } from "@vis.gl/react-maplibre"; +import type { + DefaultStyleOptions, + FeatureTitles, + PopupMode, + StyleSpecification, +} from "@xtramaps/web-map-maplibre"; +import { + polygonFromBounds, + resolveWireframeBaseStyle, +} from "@xtramaps/web-map-maplibre"; +import "maplibre-gl/dist/maplibre-gl.css"; +import type { ReactNode } from "react"; +import { useEffect, useState } from "react"; +import Configuration, { type CustomCallback } from "./Configuration"; +import "./custom.css"; + +const DEFAULT_BACKGROUND_URL = + "https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png"; +const DEFAULT_ATTRIBUTION = + '© OpenStreetMap contributors'; +const DEFAULT_FIT_BOUNDS_OPTIONS = { + padding: 30, + maxZoom: 16, + animate: false, +}; + +export interface MapLibreProps { + styleUrl?: string | null; + removeZoomLevelConstraints?: boolean; + backgroundUrl?: string; + attribution?: string; + center?: [number, number]; + zoom?: number; + bounds?: [[number, number], [number, number]] | null; + dataUrl?: string | null; + dataType?: "geojson" | "vector" | "raster"; + dataLayers?: Record; + interactive?: boolean; + savePosition?: boolean; + drawBounds?: boolean; + defaultStyle?: Partial; + fitBoundsOptions?: { + padding?: number; + maxZoom?: number; + animate?: boolean; + }; + popup?: PopupMode | null; + featureTitles?: FeatureTitles; + custom?: CustomCallback | null; + showCompass?: boolean; + children?: ReactNode; +} + +function MapLibre({ + styleUrl = null, + removeZoomLevelConstraints = false, + backgroundUrl = DEFAULT_BACKGROUND_URL, + center = [0, 0], + zoom = 0, + bounds = null, + attribution = DEFAULT_ATTRIBUTION, + dataUrl = null, + dataType = "geojson", + dataLayers = {}, + interactive = true, + savePosition = false, + drawBounds = false, + defaultStyle, + fitBoundsOptions = DEFAULT_FIT_BOUNDS_OPTIONS, + popup = null, + featureTitles = {}, + custom = null, + showCompass = true, + children, +}: MapLibreProps) { + const [style, setStyle] = useState(null); + + useEffect(() => { + let cancelled = false; + + if (styleUrl) { + // Fetched and applied here (React state -> the `mapStyle` prop below) rather than + // imperatively via map.setStyle() from within Configuration: @vis.gl/react-maplibre's + // Map wrapper owns the source cache bookkeeping through its own managed `mapStyle` + // update path, and setting the style outside of that leaves the wrapper unaware of the + // change - vector sources get registered (map.getStyle() shows them fine) but never + // actually start loading tiles (isSourceLoaded stays false forever, no tile requests). + fetch(styleUrl) + .then((response) => response.json()) + .then((fetchedStyle: StyleSpecification) => { + if (!cancelled) { + setStyle(fetchedStyle); + } + }); + return () => { + cancelled = true; + }; + } + + resolveWireframeBaseStyle({ + backgroundUrl, + attribution, + defaultUrl: DEFAULT_BACKGROUND_URL, + defaultAttribution: DEFAULT_ATTRIBUTION, + }).then((nextStyle) => { + if (!cancelled) { + setStyle(nextStyle); + } + }); + + return () => { + cancelled = true; + }; + }, [styleUrl, backgroundUrl, attribution]); + + const data = drawBounds && bounds ? polygonFromBounds(bounds) : dataUrl; + + if (!style) { + return ( +
+ ); + } + + return ( + + + {children} + + ); +} + +MapLibre.displayName = "MapLibre"; + +export default MapLibre; diff --git a/packages/react/web-map-maplibre/src/custom.css b/packages/react/web-map-maplibre/src/custom.css new file mode 100644 index 0000000..b0feeb8 --- /dev/null +++ b/packages/react/web-map-maplibre/src/custom.css @@ -0,0 +1,91 @@ +.map-sm { + height: 480px; + background: #f8f4f0; +} + +.map-md { + height: 600px; + background: #f8f4f0; +} + +.map-full { + height: 100vh; +} + +#popup-index { + display: inline-block; + margin-top: 12px; + font-size: 15px; + font-weight: 500; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translateZ(0); +} + +.popup-feature { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translateZ(0); + backface-visibility: hidden; +} + +.popup-navigation { + display: flex; + align-items: center; + gap: 4px; + font-size: 16px; + line-height: 0.8; +} + +.popup-nav-btn { + text-decoration: none; + color: #333; + cursor: pointer; + display: inline-flex; + align-items: center; + font-size: 16px; + line-height: 0.8; +} + +.popup-nav-btn:hover { + color: #000; +} + +.popup-index { + white-space: nowrap; + line-height: 0.8; + margin-top: 2px; + font-size: 16px; + font-weight: bold; + letter-spacing: 1.5px; +} + +.maplibregl-ctrl.maplibregl-ctrl-attrib { + background-color: rgba(255, 255, 255, 0.7) !important; +} + +/*TODO: get color from bootstrap? */ +.maplibregl-ctrl-attrib a { + color: #007bff !important; +} + +.maplibregl-popup-content { + max-height: 50vh; + overflow-y: auto; + overflow-x: auto; +} +.map-sm .maplibregl-popup-content { + max-height: 240px; +} +.map-md .maplibregl-popup-content { + max-height: 300px; +} + +.maplibregl-popup-content td { + text-overflow: ellipsis; + white-space: nowrap; +} + +.mapboxgl-ctrl.mgl-layerControl { + max-height: 80vh; +} diff --git a/packages/react/web-map-maplibre/src/index.ts b/packages/react/web-map-maplibre/src/index.ts new file mode 100644 index 0000000..965e1b2 --- /dev/null +++ b/packages/react/web-map-maplibre/src/index.ts @@ -0,0 +1,13 @@ +export { polygonFromBounds } from "@xtramaps/web-map-maplibre"; +export type { + CanvasOverlayInjectedProps, + CanvasOverlayProps, +} from "./CanvasOverlay"; +export { + default as CanvasOverlay, + default as CanvasPlugin, +} from "./CanvasOverlay"; +export type { ConfigurationProps, CustomCallback } from "./Configuration"; +export { default as Configuration } from "./Configuration"; +export type { MapLibreProps } from "./MapLibre"; +export { default as MapLibre, default } from "./MapLibre"; diff --git a/packages/react/web-map-maplibre/tsconfig.json b/packages/react/web-map-maplibre/tsconfig.json new file mode 100644 index 0000000..214853b --- /dev/null +++ b/packages/react/web-map-maplibre/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/packages/react/web-map-maplibre/vite.config.ts b/packages/react/web-map-maplibre/vite.config.ts new file mode 100644 index 0000000..07e9a05 --- /dev/null +++ b/packages/react/web-map-maplibre/vite.config.ts @@ -0,0 +1,16 @@ +import react from "@vitejs/plugin-react"; +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + plugins: [react()], + external: [ + "react", + "react-dom", + "react/jsx-runtime", + "@vis.gl/react-maplibre", + "maplibre-gl", + "@mapbox/mapbox-gl-draw", + "@turf/combine", + "@xtramaps/web-map-maplibre", + ], +}); diff --git a/packages/react/web-map-openlayers/README.md b/packages/react/web-map-openlayers/README.md new file mode 100644 index 0000000..8b2263a --- /dev/null +++ b/packages/react/web-map-openlayers/README.md @@ -0,0 +1,58 @@ +# @xtramaps/web-map-openlayers-react + +React OpenLayers map component. Wraps [`@xtramaps/web-map-openlayers`](https://github.com/ldproxy/xtramaps/tree/main/packages/core/web-map-openlayers) and [`rlayers`](https://github.com/mmomtchev/rlayers) — migrated 1:1 from `ogcapi-html`'s `OpenLayers` component onto current `rlayers` (`3.9.0`) and `ol` (`10.8.0`). Unlike the MapLibre side of this migration, there is no LayerControl/legend equivalent to port here — the original component never had one. + +## Example + +Browse it interactively via Storybook: `npm run storybook` from the repo root, then open the `web-map-openlayers/OpenLayers` story. + +Not published to npm yet, so there is no CDN-hosted live example. To try the standalone demo locally: + +```sh +git clone https://github.com/ldproxy/xtramaps.git +cd xtramaps +npm install +npm run build +python3 -m http.server 8080 # any static file server serving the repo root works +``` + +Then open `http://localhost:8080/packages/react/web-map-openlayers/examples/web-map-openlayers.html`. A plain `file://` open won't work — browsers block ES module imports from `file://` URLs. + +## Install + +```sh +npm install @xtramaps/web-map-openlayers-react +``` + +Requires `react`, `react-dom` (`^18.0.0 || ^19.0.0`), `rlayers` (`^3.9.0`), and `ol` (`10.8.0` exactly) as peer dependencies. Import `@xtramaps/web-map-openlayers-react/dist/index.css` once — it bundles `ol`'s own stylesheet plus the component's own styles. + +## Usage + +```tsx +import OpenLayers, { type OpenLayersRef } from "@xtramaps/web-map-openlayers-react"; +import "@xtramaps/web-map-openlayers-react/dist/index.css"; +import { useRef } from "react"; + +const ref = useRef(null); + +; + +// switch the active TileMatrixSet - replaces the old `globalThis._map.setCurrentTileMatrixSet` bridge +ref.current?.setCurrentTileMatrixSet("WorldMercatorWGS84Quad"); +``` + +`styleUrl` requires at least one complete entry in `tileMatrixSets` (with `extent`/`resolutions`/`sizes` as JSON-encoded strings) - the style fetch only fires once a TileMatrixSet is active. In production, ldproxy computes these entries server-side; see the example page for how to build one from the raw OGC TileMatrixSet resource. + +### Exports + +- `OpenLayers` (default) - the map component, accepts a `ref` typed as `OpenLayersRef` + +## License + +MIT diff --git a/packages/react/web-map-openlayers/examples/web-map-openlayers.html b/packages/react/web-map-openlayers/examples/web-map-openlayers.html new file mode 100644 index 0000000..51c2d53 --- /dev/null +++ b/packages/react/web-map-openlayers/examples/web-map-openlayers.html @@ -0,0 +1,141 @@ + + + + + + Web Map OpenLayers - React Example + + + + + + +

Web Map OpenLayers

+

+ @xtramaps/web-map-openlayers-react - migrated from + ogcapi-html onto current rlayers (3.9.0) and + ol (10.8.0). +

+ +
+
+

Raster background (defaults)

+
+
+
+

Vector tiles + style

+
+
+
+ + + + diff --git a/packages/react/web-map-openlayers/package.json b/packages/react/web-map-openlayers/package.json new file mode 100644 index 0000000..b5a613b --- /dev/null +++ b/packages/react/web-map-openlayers/package.json @@ -0,0 +1,55 @@ +{ + "name": "@xtramaps/web-map-openlayers-react", + "version": "1.0.0", + "license": "MIT", + "homepage": "https://github.com/ldproxy/xtramaps/tree/main/packages/react/web-map-openlayers#readme", + "repository": { + "type": "git", + "url": "https://github.com/ldproxy/xtramaps.git", + "directory": "packages/react/web-map-openlayers" + }, + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./dist/index.css": "./dist/index.css" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@xtramaps/web-map-openlayers": "1.0.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "rlayers": "^3.9.0", + "ol": "10.8.0" + }, + "devDependencies": { + "@storybook/react-vite": "10.5.7", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.0", + "ol": "10.8.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "rlayers": "^3.9.0", + "storybook": "10.5.7" + } +} diff --git a/packages/react/web-map-openlayers/src/DynamicSource.tsx b/packages/react/web-map-openlayers/src/DynamicSource.tsx new file mode 100644 index 0000000..d9f7079 --- /dev/null +++ b/packages/react/web-map-openlayers/src/DynamicSource.tsx @@ -0,0 +1,41 @@ +import type { + OpenLayersStyle, + TileMatrixSet, +} from "@xtramaps/web-map-openlayers"; +import { setDynamicSource } from "@xtramaps/web-map-openlayers"; +import { useEffect } from "react"; +import { useOL } from "rlayers"; + +export interface DynamicSourceProps { + tileMatrixSet?: TileMatrixSet | null; + dataUrl: string; + dataType?: string | null; + update?: boolean; + styleObject?: OpenLayersStyle | null; +} + +function DynamicSource({ + tileMatrixSet = null, + dataUrl, + dataType = null, + update = false, + styleObject = null, +}: DynamicSourceProps) { + const { layer } = useOL(); + + useEffect(() => { + setDynamicSource(layer, { + tileMatrixSet, + dataUrl, + dataType, + update, + styleObject, + }); + }, [layer, tileMatrixSet, dataUrl, dataType, update, styleObject]); + + return null; +} + +DynamicSource.displayName = "DynamicSource"; + +export default DynamicSource; diff --git a/packages/react/web-map-openlayers/src/DynamicView.tsx b/packages/react/web-map-openlayers/src/DynamicView.tsx new file mode 100644 index 0000000..85a6a67 --- /dev/null +++ b/packages/react/web-map-openlayers/src/DynamicView.tsx @@ -0,0 +1,26 @@ +import type { TileMatrixSet } from "@xtramaps/web-map-openlayers"; +import { setDynamicView } from "@xtramaps/web-map-openlayers"; +import { useEffect } from "react"; +import { useOL } from "rlayers"; + +export interface DynamicViewProps { + tileMatrixSet?: TileMatrixSet | null; + update?: boolean; +} + +function DynamicView({ + tileMatrixSet = null, + update = false, +}: DynamicViewProps) { + const { map } = useOL(); + + useEffect(() => { + setDynamicView(map, tileMatrixSet, update); + }, [map, tileMatrixSet, update]); + + return null; +} + +DynamicView.displayName = "DynamicView"; + +export default DynamicView; diff --git a/packages/react/web-map-openlayers/src/OpenLayers.stories.tsx b/packages/react/web-map-openlayers/src/OpenLayers.stories.tsx new file mode 100644 index 0000000..e38d49b --- /dev/null +++ b/packages/react/web-map-openlayers/src/OpenLayers.stories.tsx @@ -0,0 +1,137 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { createRef, useEffect, useState } from "react"; +import { expect, waitFor } from "storybook/test"; +import OpenLayers, { type OpenLayersRef } from "./OpenLayers"; + +const STYLE_URL = "https://demo.ldproxy.net/daraa/styles/topographic?f=mbs"; +const VECTOR_TILES_URL = + "https://demo.ldproxy.net/daraa/tiles/WebMercatorQuad/{z}/{y}/{x}?f=mvt"; + +interface TileMatrixSetInput { + tileMatrixSet: string; + projection: string; + defaultCenterLon: number; + defaultCenterLat: number; + defaultZoomLevel: number; + maxLevel: number; + extent: string; + resolutions: string; + sizes: string; +} + +// The styleUrl fetch only fires once `currentTileMatrixSet` is truthy, so a *complete* +// entry (extent/resolutions/sizes included - DynamicSource JSON.parses them +// unconditionally on mount) is required for every TileMatrixSet used. In production +// ldproxy computes this server-side; here it's built from the same OGC TileMatrixSet +// resource the server itself would read. +async function buildTileMatrixSet( + id: string, + projection: string, +): Promise { + const tmsDefinition = await fetch( + `https://demo.ldproxy.net/daraa/tileMatrixSets/${id}?f=json`, + ).then((r) => r.json()); + const tileMatrices: { + cellSize: number; + matrixWidth: number; + matrixHeight: number; + }[] = tmsDefinition.tileMatrices; + + return { + tileMatrixSet: id, + projection, + defaultCenterLon: 36.1033, + defaultCenterLat: 32.6264, + defaultZoomLevel: 12, + maxLevel: tileMatrices.length - 1, + extent: JSON.stringify([ + -20037508.342789244, -20037508.342789244, 20037508.342789244, + 20037508.342789244, + ]), + resolutions: JSON.stringify(tileMatrices.map((tm) => tm.cellSize)), + sizes: JSON.stringify( + tileMatrices.map((tm) => [tm.matrixWidth, tm.matrixHeight]), + ), + }; +} + +// A second, genuinely different TileMatrixSet to switch to - proves the ref API's +// setCurrentTileMatrixSet actually reloads the map with a new projection/source, not +// just re-selecting the one it already has (which React would no-op on). +const tileMatrixSetsPromise = Promise.all([ + buildTileMatrixSet("WebMercatorQuad", "EPSG:3857"), + buildTileMatrixSet("WorldMercatorWGS84Quad", "EPSG:3395"), +]); + +const olRef = createRef(); + +function OpenLayersDemo() { + const [tileMatrixSets, setTileMatrixSets] = useState( + [], + ); + + useEffect(() => { + tileMatrixSetsPromise.then(setTileMatrixSets); + }, []); + + if (tileMatrixSets.length === 0) { + return null; + } + + return ( +
+ +
+ ); +} + +const meta = { + title: "web-map-openlayers/OpenLayers", + component: OpenLayersDemo, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const VectorTilesWithStyle: Story = { + play: async ({ canvasElement, step }) => { + await step( + "map renders with vector tiles and a style applied", + async () => { + await waitFor( + () => expect(canvasElement.querySelector("canvas")).not.toBeNull(), + { + timeout: 15000, + }, + ); + await waitFor(() => expect(olRef.current).not.toBeNull(), { + timeout: 15000, + }); + }, + ); + + await step( + "switching to a different TileMatrixSet via the ref API reloads the map", + async () => { + olRef.current?.setCurrentTileMatrixSet("WorldMercatorWGS84Quad"); + + // The switch resets styleConfig, so the component briefly renders nothing while + // it re-fetches the style (now parameterized for the new TileMatrixSet) before + // the map reappears. + await waitFor( + () => expect(canvasElement.querySelector("canvas")).not.toBeNull(), + { + timeout: 15000, + }, + ); + }, + ); + }, +}; diff --git a/packages/react/web-map-openlayers/src/OpenLayers.tsx b/packages/react/web-map-openlayers/src/OpenLayers.tsx new file mode 100644 index 0000000..3795bf0 --- /dev/null +++ b/packages/react/web-map-openlayers/src/OpenLayers.tsx @@ -0,0 +1,259 @@ +import type { + OpenLayersStyle, + TileMatrixSet, +} from "@xtramaps/web-map-openlayers"; +import { + computeInitialView, + getRasterBackgroundAndAttributions, + setupProjections, +} from "@xtramaps/web-map-openlayers"; +import type { FeatureLike } from "ol/Feature"; +import { MVT } from "ol/format"; +import { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react"; +import type { RFeatureUIEvent } from "rlayers"; +import { RControl, RLayerTile, RLayerVectorTile, RMap } from "rlayers"; +import DynamicSource from "./DynamicSource"; +import DynamicView from "./DynamicView"; + +import "ol/ol.css"; +import "./custom.css"; + +setupProjections(); + +const DEFAULT_BACKGROUND_URL = + "https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png"; +const DEFAULT_ATTRIBUTION = + '© OpenStreetMap contributors'; + +interface StyleConfig { + center?: [number, number]; + zoom?: number; + bounds?: [number, number, number, number]; + styleObject?: OpenLayersStyle; + backgroundId?: string; + backgroundUrl?: string; + attributions?: string; +} + +export interface OpenLayersRef { + /** Switches the active TileMatrixSet - replaces the old `globalThis._map.setCurrentTileMatrixSet` bridge. */ + setCurrentTileMatrixSet: (tileMatrixSet: string) => void; +} + +export interface OpenLayersProps { + backgroundUrl?: string; + attribution?: string; + bounds?: [[number, number], [number, number]] | null; + dataUrl?: string | null; + dataType?: "raster" | "vector" | null; + tileMatrixSets?: TileMatrixSet[]; + styleUrl?: string | null; +} + +const OpenLayers = forwardRef( + function OpenLayers( + { + backgroundUrl = DEFAULT_BACKGROUND_URL, + attribution = DEFAULT_ATTRIBUTION, + bounds = null, + dataUrl = null, + dataType = null, + tileMatrixSets = [], + styleUrl = null, + }, + ref, + ) { + const effectiveStyleUrl = styleUrl?.trim() ? styleUrl : null; + + const [currentFeature, setCurrentFeature] = useState( + null, + ); + const [currentTileMatrixSet, setCurrentTileMatrixSet] = useState< + string | null + >(tileMatrixSets[0] ? tileMatrixSets[0].tileMatrixSet : null); + const [styleConfig, setStyleConfig] = useState(null); + + useImperativeHandle( + ref, + () => ({ + setCurrentTileMatrixSet: (tms: string) => { + setStyleConfig(null); + setCurrentTileMatrixSet(tms); + }, + }), + [], + ); + + const onPointerEnter = useCallback( + (e: RFeatureUIEvent) => setCurrentFeature(e.target), + [], + ); + const onPointerLeave = useCallback( + (e: RFeatureUIEvent) => { + if (currentFeature === e.target) { + setCurrentFeature(null); + } + }, + [currentFeature], + ); + + useEffect(() => { + if (effectiveStyleUrl && currentTileMatrixSet) { + let updatedStyleUrl = effectiveStyleUrl; + if (currentTileMatrixSet !== "WebMercatorQuad") { + updatedStyleUrl = `${effectiveStyleUrl}${ + effectiveStyleUrl.includes("?") ? "&" : "?" + }tile-matrix-set=${currentTileMatrixSet}`; + } + fetch(updatedStyleUrl) + .then((response) => response.json()) + .then((style: OpenLayersStyle) => { + const config: StyleConfig = { + center: style.center, + zoom: style.zoom, + styleObject: style, + }; + if (style.sources) { + const sourceWithBounds = Object.values(style.sources).find( + (s) => s.bounds, + ); + if (sourceWithBounds?.bounds) { + config.bounds = sourceWithBounds.bounds; + } + } + + const { + rasterBackgroundId, + rasterBackgroundUrl, + combinedAttribution, + } = getRasterBackgroundAndAttributions(style); + if (rasterBackgroundId) config.backgroundId = rasterBackgroundId; + if (rasterBackgroundUrl) config.backgroundUrl = rasterBackgroundUrl; + if (combinedAttribution) config.attributions = combinedAttribution; + + setStyleConfig(config); + }) + .catch((error) => { + console.error( + "[OpenLayers] Failed to load style configuration:", + error, + ); + setStyleConfig({}); + }); + } + }, [effectiveStyleUrl, currentTileMatrixSet]); + + const prevTMSRef = useRef(null); + useEffect(() => { + // only update previous TMS if style is not used or style is already loaded + if (!effectiveStyleUrl || styleConfig) { + prevTMSRef.current = currentTileMatrixSet; + } + }, [currentTileMatrixSet, effectiveStyleUrl, styleConfig]); + const previousTileMatrixSet = prevTMSRef.current; + + // Wait until style is loaded if styleUrl is provided + if (effectiveStyleUrl && !styleConfig) { + return null; + } + + const baseUrl = backgroundUrl.indexOf("{s}") + ? backgroundUrl.replace(/\{s\}/, "{a-c}") + : backgroundUrl; + + const tms = + tileMatrixSets.find((t) => t.tileMatrixSet === currentTileMatrixSet) ?? + null; + + const initial = computeInitialView({ + effectiveStyleUrl, + styleConfig, + tileMatrixSets, + currentTileMatrixSet, + bounds, + }); + + const backgroundTms = ( + styleConfig?.styleObject?.metadata?.["ldproxy:tileMatrixSets"] as + | Record + | undefined + )?.[styleConfig?.backgroundId ?? ""]; + const applyTmsToBackground = + Boolean(backgroundTms) && backgroundTms !== "WebMercatorQuad"; + + const update = previousTileMatrixSet !== currentTileMatrixSet; + + return ( + <> + {initial && ( + + + + {applyTmsToBackground && ( + + )} + + {dataType === "raster" && dataUrl && ( + + + + )} + {dataType === "vector" && dataUrl && ( + + + + )} + + + + )} +
+ {currentFeature && + JSON.stringify( + { id: currentFeature.getId(), ...currentFeature.getProperties() }, + null, + 2, + )} +
+ + ); + }, +); + +OpenLayers.displayName = "OpenLayers"; + +export default OpenLayers; diff --git a/packages/react/web-map-openlayers/src/custom.css b/packages/react/web-map-openlayers/src/custom.css new file mode 100644 index 0000000..c200cd8 --- /dev/null +++ b/packages/react/web-map-openlayers/src/custom.css @@ -0,0 +1,17 @@ +#map { + position: relative; +} + +#map-info { + z-index: 1; + opacity: 0; + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0; + background: rgba(0, 0, 0, 0.5); + color: white; + border: 0; + transition: opacity 100ms ease-in; +} diff --git a/packages/react/web-map-openlayers/src/index.ts b/packages/react/web-map-openlayers/src/index.ts new file mode 100644 index 0000000..d6c1e76 --- /dev/null +++ b/packages/react/web-map-openlayers/src/index.ts @@ -0,0 +1,2 @@ +export type { OpenLayersProps, OpenLayersRef } from "./OpenLayers"; +export { default, default as OpenLayers } from "./OpenLayers"; diff --git a/packages/react/web-map-openlayers/tsconfig.json b/packages/react/web-map-openlayers/tsconfig.json new file mode 100644 index 0000000..214853b --- /dev/null +++ b/packages/react/web-map-openlayers/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "jsx": "react-jsx" + }, + "include": ["src"] +} diff --git a/packages/react/web-map-openlayers/vite.config.ts b/packages/react/web-map-openlayers/vite.config.ts new file mode 100644 index 0000000..b0922a3 --- /dev/null +++ b/packages/react/web-map-openlayers/vite.config.ts @@ -0,0 +1,20 @@ +import react from "@vitejs/plugin-react"; +import { defineLibConfig } from "../../vite.config.base"; + +export default defineLibConfig({ + plugins: [react()], + // ol/ol.css must stay bundled (it's a real stylesheet, not a JS module to defer to the + // peer); everything else under the "ol"/"ol/..." specifiers is externalized. + external: (id) => { + if (id === "ol/ol.css") return false; + return ( + id === "react" || + id === "react-dom" || + id === "react/jsx-runtime" || + id === "rlayers" || + id === "ol" || + id.startsWith("ol/") || + id === "@xtramaps/web-map-openlayers" + ); + }, +}); diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..49ce2b7 --- /dev/null +++ b/renovate.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "prConcurrentLimit": 5, + "packageRules": [ + { + "description": "Patch bumps are low-risk and covered by CI (lint/build/typecheck/test) - merge automatically once checks pass. Requires 'build' to be a required status check in this repo's branch protection.", + "matchUpdateTypes": ["patch"], + "automerge": true, + "automergeType": "pr", + "platformAutomerge": true + }, + { + "description": "Minor/major bumps (incl. maplibre-gl, @maplibre/*, @vis.gl/react-maplibre) need a manual browser check before merging - CI alone doesn't yet cover enough of the map-rendering surface to trust these unattended. Revisit once test coverage for web-map-maplibre(-react) is broader.", + "matchUpdateTypes": ["minor", "major"], + "automerge": false + } + ] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..43ab6d9 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,44 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { defineConfig } from 'vitest/config'; + +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; + +import { playwright } from '@vitest/browser-playwright'; + +const dirname = + typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); + +// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon +export default defineConfig({ + // See .storybook/main.ts's viteFinal for why maplibre-gl is excluded here too. + optimizeDeps: { + exclude: ['maplibre-gl'], + }, + test: { + projects: [ + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ configDir: path.join(dirname, '.storybook') }), + ], + test: { + name: 'storybook', + // Default (15s) is tight for stories that fetch live remote data (e.g. a GeoJSON + // feature collection) before a map/layer even exists to assert on - bumped so a + // slow-but-successful network round trip doesn't fail the test on its own. + testTimeout: 30000, + browser: { + enabled: true, + headless: true, + provider: playwright({}), + instances: [{ browser: 'chromium' }], + }, + }, + }, + ], + }, +}); diff --git a/vitest.shims.d.ts b/vitest.shims.d.ts new file mode 100644 index 0000000..7782f28 --- /dev/null +++ b/vitest.shims.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file