diff --git a/.github/workflows/lint-and-typecheck.yml b/.github/workflows/lint-and-typecheck.yml index 1397f9a..cc14f20 100644 --- a/.github/workflows/lint-and-typecheck.yml +++ b/.github/workflows/lint-and-typecheck.yml @@ -21,7 +21,7 @@ jobs: - name: Clear NPM cache run: npm cache clean --force - - name: Install dependencies and build + - name: Install dependencies run: npm ci - name: Build diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..294cd04 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,24 @@ +name: Unit tests + +on: [pull_request] + +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Install Node.js dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Running unit tests + run: npm run test diff --git a/demos/08-bootstrap.html b/demos/08-bootstrap.html new file mode 100644 index 0000000..d4feaef --- /dev/null +++ b/demos/08-bootstrap.html @@ -0,0 +1,66 @@ + + + + MapTiler Geocoding Control example + + + + + + + + + + + + + + + +
+ + +
+
+ + +
+ + + +
+
+ + + diff --git a/demos/index.html b/demos/index.html index 064beb4..e162871 100644 --- a/demos/index.html +++ b/demos/index.html @@ -54,6 +54,7 @@ Leaflet → OpenLayers → Worldview → + Bootstrap → diff --git a/demos/src/08-bootstrap.ts b/demos/src/08-bootstrap.ts new file mode 100644 index 0000000..9e16837 --- /dev/null +++ b/demos/src/08-bootstrap.ts @@ -0,0 +1,7 @@ +import "../../src"; + +import { getApiKey } from "./demo-utils"; + +for (const el of document.querySelectorAll("maptiler-geocoder")) { + el.apiKey = getApiKey(); +} diff --git a/package-lock.json b/package-lock.json index 15cf727..2154668 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@types/node": "ts5.8", "@types/react": "^19.2.6", "@types/react-dom": "^19.2.3", - "@vitest/web-worker": "^4.0.9", + "@vitest/browser-playwright": "^4.1.5", "dotenv": "^16.4.7", "eslint": "^9.38.0", "eslint-config-prettier": "^10.1.8", @@ -65,7 +65,7 @@ "vite-plugin-dts": "^4.5.4", "vite-plugin-externalize-deps": "^0.10.0", "vite-plugin-static-copy": "^3.1.4", - "vitest": "^4.0.8" + "vitest": "^4.1.5" }, "peerDependencies": { "@maptiler/sdk": "3 - 4", @@ -88,6 +88,65 @@ } } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "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", @@ -138,6 +197,28 @@ "node": ">=6.9.0" } }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@canvas/image-data": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@canvas/image-data/-/image-data-1.1.0.tgz", @@ -145,6 +226,158 @@ "dev": true, "license": "MIT" }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "optional": true, + "peer": true, + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", @@ -731,6 +964,26 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1459,6 +1712,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", @@ -1969,9 +2229,9 @@ } }, "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, @@ -2519,32 +2779,79 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vitest/browser": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.5.tgz", + "integrity": "sha512-iCDGI8c4yg+xmjUg2VsygdAUSIIB4x5Rht/P68OXy1hPELKXHDkzh87lkuTcdYmemRChDkEpB426MmDjzC0ziA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.5", + "@vitest/utils": "4.1.5", + "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.5" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.5.tgz", + "integrity": "sha512-CWy0lBQJq97nionyJJdnaU4961IXTl43a7UCu5nHy51IoKxAt6PVIJLo+76rVl7KOOgcWHNkG4kbJu/pW7knvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.5", + "@vitest/mocker": "4.1.5", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.5" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, "node_modules/@vitest/expect": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.9.tgz", - "integrity": "sha512-C2vyXf5/Jfj1vl4DQYxjib3jzyuswMi/KHHVN2z+H4v16hdJ7jMZ0OGe3uOVIt6LyJsAofDdaJNIFEpQcrSTFw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", + "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.9", - "@vitest/utils": "4.0.9", - "chai": "^6.2.0", - "tinyrainbow": "^3.0.3" + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.9.tgz", - "integrity": "sha512-PUyaowQFHW+9FKb4dsvvBM4o025rWMlEDXdWRxIOilGaHREYTi5Q2Rt9VCgXgPy/hHZu1LeuXtrA/GdzOatP2g==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.9", + "@vitest/spy": "4.1.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -2553,7 +2860,7 @@ }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "msw": { @@ -2575,26 +2882,26 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.9.tgz", - "integrity": "sha512-Hor0IBTwEi/uZqB7pvGepyElaM8J75pYjrrqbC8ZYMB9/4n5QA63KC15xhT+sqHpdGWfdnPo96E8lQUxs2YzSQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.0.3" + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.9.tgz", - "integrity": "sha512-aF77tsXdEvIJRkj9uJZnHtovsVIx22Ambft9HudC+XuG/on1NY/bf5dlDti1N35eJT+QZLb4RF/5dTIG18s98w==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.9", + "@vitest/utils": "4.1.5", "pathe": "^2.0.3" }, "funding": { @@ -2602,13 +2909,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.9.tgz", - "integrity": "sha512-r1qR4oYstPbnOjg0Vgd3E8ADJbi4ditCzqr+Z9foUrRhIy778BleNyZMeAJ2EjV+r4ASAaDsdciC9ryMy8xMMg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.9", + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2617,9 +2925,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.9.tgz", - "integrity": "sha512-J9Ttsq0hDXmxmT8CUOWUr1cqqAj2FJRGTdyEjSR+NjoOGKEqkEWj+09yC0HhI8t1W6t4Ctqawl1onHgipJve1A==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", "dev": true, "license": "MIT", "funding": { @@ -2627,33 +2935,18 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.9.tgz", - "integrity": "sha512-cEol6ygTzY4rUPvNZM19sDf7zGa35IYTm9wfzkHoT/f5jX10IOY7QleWSOh5T0e3I3WVozwK5Asom79qW8DiuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.9", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/web-worker": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@vitest/web-worker/-/web-worker-4.0.9.tgz", - "integrity": "sha512-rAX5p1S1ZE9ZAzTDkk7N0l1L0y5V5QLc/xKXwhS2X/GuIdDxrJQHXsmY65HR51x9p3LU0OgaeZu+x1rh1dLP+Q==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.3" + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, "funding": { "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "vitest": "4.0.9" } }, "node_modules/@volar/language-core": { @@ -2952,6 +3245,18 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -3064,9 +3369,9 @@ "license": "CC-BY-4.0" }, "node_modules/chai": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", - "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -3207,6 +3512,13 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3222,6 +3534,22 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -3229,6 +3557,34 @@ "dev": true, "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -3254,6 +3610,15 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3302,9 +3667,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -3640,9 +4005,9 @@ } }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4057,6 +4422,21 @@ "he": "bin/he" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -4213,6 +4593,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4247,6 +4636,73 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.0.tgz", + "integrity": "sha512-YNUc7fB9QuvSSQWfrH0xF+TyABkxUwx8sswgIDaCrw4Hol8BghdZDkITtZheRJeMtzWlnTfsM3bBBusRvpO1wg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "peer": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4799,6 +5255,15 @@ "pbf": "bin/pbf" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0", + "optional": true, + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4878,6 +5343,16 @@ "pathe": "^2.0.1" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "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", @@ -4942,6 +5417,17 @@ "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", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/ol": { "version": "10.6.1", "resolved": "https://registry.npmjs.org/ol/-/ol-10.6.1.tgz", @@ -5226,6 +5712,36 @@ "dev": true, "license": "MIT" }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", @@ -5312,6 +5828,66 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "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" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/polyclip-ts": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/polyclip-ts/-/polyclip-ts-0.16.8.tgz", @@ -5686,6 +6262,21 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5736,6 +6327,21 @@ "dev": true, "license": "ISC" }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "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/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5777,9 +6383,9 @@ "license": "MIT" }, "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, @@ -5842,6 +6448,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/synckit": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", @@ -5866,11 +6481,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -5928,15 +6546,39 @@ "license": "ISC" }, "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", + "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tldts-core": "^7.0.28" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.28", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", + "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5950,6 +6592,46 @@ "node": ">=8.0" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6027,6 +6709,18 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -6311,31 +7005,31 @@ } }, "node_modules/vitest": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.9.tgz", - "integrity": "sha512-E0Ja2AX4th+CG33yAFRC+d1wFx2pzU5r6HtG6LiPSE04flaE0qB6YyjSw9ZcpJAtVPfsvZGtJlKWZpuW7EHRxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.0.9", - "@vitest/mocker": "4.0.9", - "@vitest/pretty-format": "4.0.9", - "@vitest/runner": "4.0.9", - "@vitest/snapshot": "4.0.9", - "@vitest/spy": "4.0.9", - "@vitest/utils": "4.0.9", - "debug": "^4.4.3", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "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": "^3.10.0", + "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", + "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "bin": { @@ -6349,20 +7043,23 @@ }, "peerDependencies": { "@edge-runtime/vm": "*", - "@types/debug": "^4.1.12", + "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.9", - "@vitest/browser-preview": "4.0.9", - "@vitest/browser-webdriverio": "4.0.9", - "@vitest/ui": "4.0.9", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", "happy-dom": "*", - "jsdom": "*" + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, - "@types/debug": { + "@opentelemetry/api": { "optional": true }, "@types/node": { @@ -6377,6 +7074,12 @@ "@vitest/browser-webdriverio": { "optional": true }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, "@vitest/ui": { "optional": true }, @@ -6385,6 +7088,9 @@ }, "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, @@ -6451,6 +7157,21 @@ "pbf": "bin/pbf" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/web-worker": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", @@ -6458,6 +7179,18 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=20" + } + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -6475,6 +7208,23 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6518,6 +7268,40 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "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/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/xml-utils": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", @@ -6525,6 +7309,15 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 5f18682..4ad8c7e 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@types/node": "ts5.8", "@types/react": "^19.2.6", "@types/react-dom": "^19.2.3", - "@vitest/web-worker": "^4.0.9", + "@vitest/browser-playwright": "^4.1.5", "dotenv": "^16.4.7", "eslint": "^9.38.0", "eslint-config-prettier": "^10.1.8", @@ -138,7 +138,7 @@ "vite-plugin-dts": "^4.5.4", "vite-plugin-externalize-deps": "^0.10.0", "vite-plugin-static-copy": "^3.1.4", - "vitest": "^4.0.8" + "vitest": "^4.1.5" }, "peerDependencies": { "@maptiler/sdk": "3 - 4", diff --git a/src/components/clear-icon.ts b/src/components/clear-icon.ts index c2d643b..faf2401 100644 --- a/src/components/clear-icon.ts +++ b/src/components/clear-icon.ts @@ -7,7 +7,7 @@ export class MaptilerGeocodeClearIconElement extends LitElement { static styles = css` svg { display: block; - fill: var(--color-icon-button); + fill: var(--color-icon-button, #444952); } `; diff --git a/src/components/fail-icon.ts b/src/components/fail-icon.ts index 6f8d5a8..41c7bcd 100644 --- a/src/components/fail-icon.ts +++ b/src/components/fail-icon.ts @@ -7,7 +7,7 @@ export class MaptilerGeocodeFailIconElement extends LitElement { static styles = css` svg { display: block; - fill: #e15042; + fill: var(--color-fail-icon, #e15042); } `; diff --git a/src/components/reverse-geocoding-icon.ts b/src/components/reverse-geocoding-icon.ts index ac33b82..336673e 100644 --- a/src/components/reverse-geocoding-icon.ts +++ b/src/components/reverse-geocoding-icon.ts @@ -7,7 +7,7 @@ export class MaptilerGeocodeReverseGeocodingIconElement extends LitElement { static styles = css` svg { display: block; - fill: var(--color-icon-button); + fill: var(--color-icon-button, #444952); } `; diff --git a/src/components/search-icon.ts b/src/components/search-icon.ts index 2865512..9736457 100644 --- a/src/components/search-icon.ts +++ b/src/components/search-icon.ts @@ -17,7 +17,7 @@ export class MaptilerGeocodeSearchIconElement extends LitElement { svg { display: block; - stroke: var(--color-icon-button); + stroke: var(--color-icon-button, #444952); } `; diff --git a/src/geocoder/geocoder-feature-item.css b/src/geocoder/geocoder-feature-item.css index 42478fa..d7b8949 100644 --- a/src/geocoder/geocoder-feature-item.css +++ b/src/geocoder/geocoder-feature-item.css @@ -1,7 +1,7 @@ .sprite-icon { align-self: center; justify-self: center; - opacity: 0.75; + opacity: var(--icon-items-opacity, 0.75); background-repeat: no-repeat; } @@ -10,70 +10,59 @@ li { cursor: default; display: grid; grid-template-columns: 40px 1fr; - color: var(--color-text); + color: var(--color-items, #444952); padding: 8px 0px; - font-size: 14px; - line-height: 18px; + font-size: var(--font-size-items, 14px); + line-height: 1.3; min-width: fit-content; outline: 0; +} - &:first-child { - padding-top: 10px; - } - - &:last-child { - padding-bottom: 10px; - } - - &.picked { - background-color: #e7edff; - - .secondary { - color: #96a4c7; - padding-left: 4px; - } +li:first-child { + padding-top: 10px; +} - .line2 { - color: #96a4c7; - } - } +li:last-child { + padding-bottom: 10px; +} - &.selected { - background-color: #f3f6ff; +li.picked { + background-color: var(--color-items-picked-bg, #e7edff); +} - & { - animation: backAndForth 5s linear infinite; - } +li.picked .secondary, +li.picked .line2 { + color: var(--color-items-picked-secondary, #96a4c7); +} - & .primary { - color: #2b8bfb; - } +li.selected { + background-color: var(--color-items-selected-bg, #f3f6ff); + animation: backAndForth 5s linear infinite; +} - .secondary { - color: #a2adc7; - padding-left: 4px; - } +li.selected .primary { + color: var(--color-items-selected, #2b8bfb); +} - .line2 { - color: #a2adc7; - } - } +li.selected .secondary, +li.selected .line2 { + color: var(--color-items-selected-secondary, #a2adc7); +} - & > img { - align-self: center; - justify-self: center; - opacity: 0.75; - } +li > img { + align-self: center; + justify-self: center; + opacity: var(--icon-opacity, 0.75); } .texts { padding: 0 17px 0 0; +} - & > * { - white-space: nowrap; - display: block; - min-width: fit-content; - } +.texts > * { + white-space: nowrap; + display: block; + min-width: fit-content; } .primary { @@ -81,12 +70,12 @@ li { } .secondary { - color: #aeb6c7; + color: var(--color-items-secondary, #aeb6c7); padding-left: 4px; } .line2 { - color: #aeb6c7; + color: var(--color-items-secondary, #aeb6c7); } @keyframes backAndForth { diff --git a/src/geocoder/geocoder-feature-item.ts b/src/geocoder/geocoder-feature-item.ts index a11c12f..66f845d 100644 --- a/src/geocoder/geocoder-feature-item.ts +++ b/src/geocoder/geocoder-feature-item.ts @@ -1,8 +1,6 @@ /* eslint-disable -@typescript-eslint/no-unnecessary-condition, + @typescript-eslint/no-unnecessary-condition, @typescript-eslint/no-unsafe-assignment, - @typescript-eslint/no-unsafe-call, - @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/restrict-template-expressions, @typescript-eslint/unbound-method, @@ -39,14 +37,14 @@ export class MaptilerGeocoderFeatureItemElement extends LitElement { @property({ attribute: false }) missingIconsCache: Set = new Set(); @property({ type: String }) iconsBaseUrl: string = ""; - get #categories() { + get #categories(): string[] | undefined { return this.feature?.properties?.categories; } get #isReverse() { return this.feature?.place_type[0] === "reverse"; } get #placeType() { - return this.feature?.properties?.categories?.join(", ") ?? this.feature?.place_type_name?.[0] ?? this.feature?.place_type[0]; + return this.#categories?.join(", ") ?? this.feature?.place_type_name?.[0] ?? this.feature?.place_type[0]; } @state() private category: string | undefined; diff --git a/src/geocoder/geocoder.css b/src/geocoder/geocoder.css index 6da9045..aa05f08 100644 --- a/src/geocoder/geocoder.css +++ b/src/geocoder/geocoder.css @@ -1,14 +1,12 @@ form { - font-family: "Open Sans", "Ubuntu", "Helvetica Neue", Arial, Helvetica, sans-serif; + font-family: var(--font-family, "Open Sans", "Ubuntu", "Helvetica Neue", Arial, Helvetica, sans-serif); position: relative; - background-color: #fff; + background-color: var(--color-bg, #fff); z-index: 10; - border-radius: 4px; + border-radius: var(--radius, 4px); margin: 0; transition: max-width 0.25s; - box-shadow: 0px 2px 5px rgba(51, 51, 89, 0.15); - --color-text: #444952; - --color-icon-button: #444952; + box-shadow: var(--shadow, 0px 2px 5px rgba(51, 51, 89, 0.15)); pointer-events: all; } form, @@ -18,20 +16,17 @@ form *:before { box-sizing: border-box; } form.can-collapse { - max-width: 29px; + max-width: var(--size-collapse-width, 29px); } form.can-collapse input::placeholder { transition: opacity 0.25s; opacity: 0; } -form { - width: 270px; - max-width: 270px; -} -form:focus-within, -form:hover { - width: 270px; - max-width: 270px; +form, +form.can-collapse:focus-within, +form.can-collapse:hover { + width: var(--size-width, 270px); + max-width: var(--size-width, 270px); } form input::placeholder, form:focus-within input::placeholder, @@ -40,11 +35,11 @@ form:hover input::placeholder { } input { font: inherit; - font-size: 14px; + font-size: var(--font-size-input, 14px); flex-grow: 1; - min-height: 29px; + min-height: var(--size-input-min-height, 29px); background-color: transparent; - color: #444952; + color: var(--color-input, #444952); white-space: nowrap; overflow: hidden; border: 0; @@ -52,16 +47,15 @@ input { padding: 0; } input:focus { - color: #444952; + color: var(--color-input-focus, #444952); outline: 0; - outline: none; box-shadow: none; } ul, div.error, div.no-results { - background-color: #fff; - border-radius: 4px; + background-color: var(--color-bg-dropdown, #fff); + border-radius: var(--radius-dropdown, 4px); left: 0; list-style: none; margin: 0; @@ -72,23 +66,23 @@ div.no-results { overflow: hidden; } ul { - font-size: 14px; + font-size: var(--font-size-items, 14px); line-height: 16px; - box-shadow: 0px 5px 10px rgba(51, 51, 89, 0.15); + box-shadow: var(--shadow-dropdown, 0px 5px 10px rgba(51, 51, 89, 0.15)); } div.error, div.no-results { font: inherit; line-height: 18px; - font-size: 12px; + font-size: var(--font-size-error, 12px); display: flex; gap: 16px; } div.error { padding: 16px; font-weight: 600; - color: #e25041; - background-color: #fbeae8; + color: var(--color-error-text, #e25041); + background-color: var(--color-error-bg, #fbeae8); } div.error div { flex-grow: 1; @@ -102,7 +96,7 @@ div.error button { flex-shrink: 0; } div.error button maptiler-geocode-clear-icon { - --color-icon-button: #e25041; + --color-icon-button: var(--color-error-icon, #e25041); } div.error button:hover maptiler-geocode-clear-icon, div.error button:active maptiler-geocode-clear-icon { @@ -111,8 +105,8 @@ div.error button:active maptiler-geocode-clear-icon { div.no-results { padding: 14px 24px 14px 16px; font-weight: 400; - color: #6b7c93; - box-shadow: 0px 5px 10px rgba(51, 51, 89, 0.15); + color: var(--color-no-results-text, #6b7c93); + box-shadow: var(--shadow-dropdown, 0px 5px 10px rgba(51, 51, 89, 0.15)); } div.no-results maptiler-geocode-fail-icon { margin-top: 4px; @@ -137,24 +131,23 @@ button:hover { } button:hover, button:active { - --color-icon-button: #2b8bfb; + --color-icon-button: var(--color-icon-button-hover, #2b8bfb); } .input-group { display: flex; align-items: stretch; - gap: 7px; - padding-inline: 8px; - border-radius: 4px; + gap: var(--input-group-gap, 7px); + padding: var(--input-group-padding, 0 8px); + border-radius: var(--radius-input-group, 4px); overflow: hidden; } .input-group:focus-within { - outline: #2b8bfb solid 2px; + outline: var(--color-focus-outline, #2b8bfb) solid var(--size-focus-outline, 2px); } .search-button { flex-shrink: 0; } .clear-button-container { - display: flex; display: none; position: relative; align-items: stretch; @@ -167,25 +160,27 @@ button:active { box-shadow: none; } :host(.maptiler-geocoder) .input-group { - border: white solid 2px; + border: var(--color-input-group-border, white solid 2px); } :host(.maptiler-geocoder) .input-group:focus-within { - border: #2b8bfb solid 2px; + border: var(--color-input-group-border-focused, #2b8bfb solid 2px); outline: 0; - outline: none; } :host(.maptiler-geocoder) form.can-collapse { - max-width: 33px; + max-width: var(--size-collapse-width, 33px); } :host(.maptiler-geocoder) form, -:host(.maptiler-geocoder) form:focus-within, -:host(.maptiler-geocoder) form:hover { - width: 270px; - max-width: 270px; +:host(.leaflet-geocoder) form, +:host(.maptiler-geocoder) form.can-collapse:focus-within, +:host(.leaflet-geocoder) form.can-collapse:focus-within, +:host(.maptiler-geocoder) form.can-collapse:hover, +:host(.leaflet-geocoder) form.can-collapse:hover { + width: var(--size-width, 270px); + max-width: var(--size-width, 270px); } :host(.leaflet-geocoder) .input-group { - border: white solid 1px; + border: var(--color-input-group-border, white solid 1px); } :host(.leaflet-geocoder) form.can-collapse { - max-width: 30px; + max-width: var(--size-collapse-width, 30px); } diff --git a/src/geocoder/geocoder.ts b/src/geocoder/geocoder.ts index 5f468cf..afa3340 100644 --- a/src/geocoder/geocoder.ts +++ b/src/geocoder/geocoder.ts @@ -1,6 +1,6 @@ import { convert } from "geo-coordinates-parser"; import { LitElement, css, html, nothing, unsafeCSS } from "lit"; -import { customElement, property, query, state } from "lit/decorators.js"; +import { customElement, property, queryAssignedElements, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { repeat } from "lit/directives/repeat.js"; @@ -19,6 +19,11 @@ import type { MaptilerGeocoderEventName, MaptilerGeocoderEventNameMap } from "./ import type { MaptilerGeocoderOptions } from "./geocoder-options"; import styles from "./geocoder.css?inline"; +type InputElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; +type ButtonElement = HTMLButtonElement | HTMLInputElement; +const INPUT_SELECTOR = "input:not([type]), input[type=text], input[type=search], select, textarea"; +const BUTTON_SELECTOR = "button, input[type=button]"; + @customElement("maptiler-geocoder") export class MaptilerGeocoderElement extends LitElement implements MaptilerGeocoderOptions { /** @internal */ @@ -64,8 +69,18 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco @property({ type: Array }) types?: TypeRule[]; @property({ type: String }) worldview?: Worldview; - /** Reference to the input element the user can type a query into */ - @query("input") private input!: HTMLInputElement; + @queryAssignedElements({ slot: "content", flatten: false }) private contentSlotElements!: HTMLElement[]; + @queryAssignedElements({ slot: "input", flatten: true, selector: INPUT_SELECTOR }) private inputSlotElements!: InputElement[]; + + @queryAssignedElements({ slot: "search-button", flatten: true, selector: BUTTON_SELECTOR }) private searchButtonElements!: ButtonElement[]; + @queryAssignedElements({ slot: "clear-button", flatten: true, selector: BUTTON_SELECTOR }) private clearButtonElements!: ButtonElement[]; + @queryAssignedElements({ slot: "reverse-button", flatten: true, selector: BUTTON_SELECTOR }) private reverseButtonElements!: ButtonElement[]; + @queryAssignedElements({ slot: "clear-error-button", flatten: true, selector: BUTTON_SELECTOR }) private clearErrorButtonElements!: ButtonElement[]; + + @queryAssignedElements({ slot: "search-icon", flatten: true }) private searchIconElements!: HTMLElement[]; + @queryAssignedElements({ slot: "clear-icon", flatten: true }) private clearIconElements!: HTMLElement[]; + @queryAssignedElements({ slot: "reverse-icon", flatten: true }) private reverseIconElements!: HTMLElement[]; + @queryAssignedElements({ slot: "clear-error-icon", flatten: true }) private clearErrorIconElements!: HTMLElement[]; /** Value to search via geocoding */ @state() private searchValue: string = ""; @@ -102,15 +117,27 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco get #selected(): Feature | undefined { return this.listFeatures?.[this.selectedItemIndex]; } - get #isLoading(): boolean { + get isLoading(): boolean { return this.abortController !== undefined; } get #isSearchValueTooShort(): boolean { return this.searchValue.length < (this.minLength ?? 2); } + get input(): InputElement | undefined { + for (const element of this.contentSlotElements) { + if (this.#isInput(element)) return element; + const nestedElement = element.querySelector(INPUT_SELECTOR); + if (this.#isInput(nestedElement)) return nestedElement; + } + return this.inputSlotElements[0]; + } protected firstUpdated() { this.#isInitialized = true; + + if (this.input) { + this.input.value = this.searchValue; + } } /** @@ -161,14 +188,14 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco * @param options [FocusOptions](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#options) */ override focus(options?: FocusOptions) { - this.input.focus(options); + this.input?.focus(options); } /** * Blur the search input box. */ override blur() { - this.input.blur(); + this.input?.blur(); } override addEventListener( @@ -250,9 +277,13 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco #focusInputAndSelectText() { setTimeout(() => { - this.input.focus(); + this.input?.focus(); this.focused = true; - this.input.select(); + if (this.input instanceof HTMLSelectElement) { + this.input.showPicker(); + } else { + this.input?.select(); + } }); } @@ -469,7 +500,7 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco } if (isReverse) { - this.input.focus(); + this.input?.focus(); } } } catch (e: unknown) { @@ -495,6 +526,20 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco this.#focusInputAndSelectText(); } + #handleClick(e: PointerEvent & { target: HTMLElement }) { + if (this.#isInput(e.target)) { + this.focused = true; + } else if (this.#isFromButton(e.target, this.searchButtonElements, this.searchIconElements, "search-button")) { + this.input?.focus(); + } else if (this.#isFromButton(e.target, this.clearButtonElements, this.clearIconElements, "clear-button")) { + this.#handleClear(); + } else if (this.#isFromButton(e.target, this.reverseButtonElements, this.reverseIconElements, "reverse-button")) { + this.reverseActive = !this.reverseActive; + } else if (this.#isFromButton(e.target, this.clearErrorButtonElements, this.clearErrorIconElements, "clear-error-button")) { + this.error = undefined; + } + } + #handleKeyDown(e: KeyboardEvent) { if (!this.listFeatures) { return; @@ -506,7 +551,7 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco return; } - this.input.focus(); + this.input?.focus(); this.focused = true; @@ -531,8 +576,28 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco } } - #handleInput(event: InputEvent & { target: HTMLInputElement }) { - this.#changeSearchValue(event.target.value); + #handleInput(e: InputEvent & { target: HTMLElement }) { + if (this.#isInput(e.target)) { + this.#changeSearchValue(e.target.value); + } + } + + #handleChange(e: Event & { target: HTMLElement }) { + if (this.#isInput(e.target)) { + this.picked = undefined; + } + } + + #handleFocusIn(e: FocusEvent & { target: HTMLElement }) { + if (this.#isInput(e.target)) { + this.focused = true; + } + } + + #handleFocusOut(e: FocusEvent & { target: HTMLElement }) { + if (this.#isInput(e.target)) { + this.focused = false; + } } #pick(feature: Feature) { @@ -570,7 +635,15 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco this.#dispatch("queryclear"); this.picked = undefined; - this.input.focus(); + this.input?.focus(); + } + + #isInput(element: EventTarget | null): element is InputElement { + return element instanceof HTMLElement && element.matches(INPUT_SELECTOR); + } + + #isFromButton(element: EventTarget | null, buttonSlotElements: ButtonElement[], iconSlotElements: HTMLElement[], id: string): boolean { + return element instanceof HTMLElement && ([...buttonSlotElements, ...iconSlotElements].some((el) => el.contains(element)) || element.closest(`[data-${id}]`) !== null); } willUpdate(changedProperties: Map) { @@ -650,71 +723,91 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco if (["reverseActive"].some((prop) => changedProperties.has(prop))) { this.#dispatch("reversetoggle", { reverse: this.reverseActive }); } + + if (["searchValue"].some((prop) => changedProperties.has(prop)) && this.input) { + this.input.value = this.searchValue; + } } render() { /* eslint-disable @typescript-eslint/unbound-method */ return html` -
-
- - - (this.focused = true)} - @blur=${() => (this.focused = false)} - @click=${() => (this.focused = true)} - @keydown=${this.#handleKeyDown} - @input=${this.#handleInput} - @change=${() => (this.picked = undefined)} - placeholder=${this.placeholder ?? "Search"} - aria-label=${this.placeholder ?? "Search"} - /> - -
- ${!this.#isLoading + + +
+ + + + + + + + + + +
+ ${!this.isLoading + ? html` + + + + ` + : html` + + + + `} +
+ + ${this.enableReverse === "button" ? html` - + + + ` - : html``} -
+ : nothing} - ${this.enableReverse === "button" - ? html` - - ` - : nothing} - - -
+ +
+ ${this.error ? html`
- + + +
${this.errorMessage ?? "Something went wrong…"}
- + + +
` : (!this.focused && !this.isFeatureListInteractedWith && !this.keepListOpen) || this.listFeatures === undefined @@ -722,7 +815,9 @@ export class MaptilerGeocoderElement extends LitElement implements MaptilerGeoco : this.listFeatures.length === 0 ? html`
- + + +
${this.noResultsMessage ?? diff --git a/test/geocoder.test.ts b/test/geocoder.test.ts index ddf9e34..6e3ad2b 100644 --- a/test/geocoder.test.ts +++ b/test/geocoder.test.ts @@ -188,7 +188,7 @@ describe("standalone control", () => { await wait(); element.shadowRoot!.querySelector("input")!.value = "svitavy"; - element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input")); + element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input", { bubbles: true })); element.shadowRoot!.querySelector("form")!.dispatchEvent(new Event("submit")); await wait(); @@ -202,7 +202,7 @@ describe("standalone control", () => { await wait(); element.shadowRoot!.querySelector("input")!.value = "svitavy"; - element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input")); + element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input", { bubbles: true })); expect(fetch).not.toHaveBeenCalled(); @@ -234,7 +234,7 @@ describe("standalone control", () => { await wait(); element.shadowRoot!.querySelector("input")!.value = "svitavy"; - element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input")); + element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input", { bubbles: true })); expect(fetch).not.toHaveBeenCalled(); @@ -266,13 +266,13 @@ describe("standalone control", () => { document.body.append(element as Node); await wait(); - element.shadowRoot!.querySelector("maptiler-geocode-reverse-geocoding-icon")!.parentElement!.dispatchEvent(new Event("click")); + element.shadowRoot!.querySelector("maptiler-geocode-reverse-geocoding-icon")!.parentElement!.dispatchEvent(new Event("click", { bubbles: true })); await wait(); expect(listener).toHaveBeenCalledOnce(); expect(listener).toHaveBeenLastCalledWith(expect.objectContaining({ detail: { reverse: true } })); - element.shadowRoot!.querySelector("maptiler-geocode-reverse-geocoding-icon")!.parentElement!.dispatchEvent(new Event("click")); + element.shadowRoot!.querySelector("maptiler-geocode-reverse-geocoding-icon")!.parentElement!.dispatchEvent(new Event("click", { bubbles: true })); await wait(); expect(listener).toHaveBeenCalledTimes(2); @@ -299,7 +299,7 @@ describe("standalone control", () => { await wait(); element.shadowRoot!.querySelector("input")!.value = "svitavy"; - element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input")); + element.shadowRoot!.querySelector("input")!.dispatchEvent(new Event("input", { bubbles: true })); expect(listener).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ detail: { query: "svitavy", reverseCoords: false } })); }); @@ -311,7 +311,7 @@ describe("standalone control", () => { document.body.append(element as Node); await wait(); - element.shadowRoot!.querySelector("maptiler-geocode-clear-icon")!.parentElement!.dispatchEvent(new Event("click")); + element.shadowRoot!.querySelector("maptiler-geocode-clear-icon")!.parentElement!.dispatchEvent(new Event("click", { bubbles: true })); expect(listener).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ detail: null })); }); @@ -382,7 +382,7 @@ describe("standalone control", () => { element.setQuery("svitavy"); await wait(200); // debounce - element.shadowRoot!.querySelector("input")!.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown" })); + element.shadowRoot!.querySelector("input")!.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true })); await wait(); expect(listener).toHaveBeenCalledTimes(2); @@ -415,7 +415,7 @@ describe("standalone control", () => { element.setQuery("svitavy"); await wait(200); // debounce - element.shadowRoot!.querySelector("input")!.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown" })); + element.shadowRoot!.querySelector("input")!.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true })); element.shadowRoot!.querySelector("form")!.dispatchEvent(new Event("submit")); await wait(); @@ -499,6 +499,7 @@ describe("standalone control", () => { const element = document.createElement("maptiler-geocoder"); element.addEventListener("focusout", listener); document.body.append(element as Node); + document.body.tabIndex = 1; await wait(); element.focus(); diff --git a/vite.config-test.ts b/vite.config-test.ts index 3b2c74e..67be8bc 100644 --- a/vite.config-test.ts +++ b/vite.config-test.ts @@ -1,3 +1,4 @@ +import { playwright } from "@vitest/browser-playwright"; import { defineConfig } from "vitest/config"; export default defineConfig({ @@ -6,8 +7,13 @@ export default defineConfig({ typecheck: { tsconfig: "./tsconfig.json", }, - environment: "happy-dom", + browser: { + provider: playwright(), + enabled: true, + headless: true, + instances: [{ browser: "chromium" }], + }, globals: true, - setupFiles: ["@vitest/web-worker", "./vitest-setup-tests.ts"], + setupFiles: ["./vitest-setup-tests.ts"], }, }); diff --git a/vitest-setup-tests.ts b/vitest-setup-tests.ts index 959b2b5..3a0854c 100644 --- a/vitest-setup-tests.ts +++ b/vitest-setup-tests.ts @@ -1,3 +1,3 @@ import ImageData from "@canvas/image-data"; // @ts-expect-error: Global type missing -global.ImageData = ImageData; +window.ImageData = ImageData;