diff --git a/.gitignore b/.gitignore index 84e6e46..f8904d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # build output dist/ +!website/public/widgets/dist # content generated by build scripts website/public/api/backgrounds/v1/* @@ -24,6 +25,7 @@ pnpm-debug.log* # environment variables .env .env.production +.aws # macOS-specific files .DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..342f83d --- /dev/null +++ b/Makefile @@ -0,0 +1,240 @@ +# /!\ /!\ /!\ /!\ /!\ /!\ /!\ DISCLAIMER /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ +# +# This Makefile is only meant to be used for DEVELOPMENT purpose as we are +# changing the user id that will run in the container. +# +# PLEASE DO NOT USE IT FOR YOUR CI/PRODUCTION/WHATEVER... +# +# /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ +# +# Note to developers: +# +# While editing this file, please respect the following statements: +# +# 1. Every variable should be defined in the ad hoc VARIABLES section with a +# relevant subsection +# 2. Every new rule should be defined in the ad hoc RULES section with a +# relevant subsection depending on the targeted service +# 3. Rules should be sorted alphabetically within their section +# 4. When a rule has multiple dependencies, you should: +# - duplicate the rule name to add the help string (if required) +# - write one dependency per line to increase readability and diffs +# 5. .PHONY rule statement should be written after the corresponding rule +# ============================================================================== +# VARIABLES + +BOLD := \033[1m +RESET := \033[0m +GREEN := \033[1;32m + +# -- Docker +# Get the current user ID to use for docker run and docker exec commands +DOCKER_UID = $(shell id -u) +DOCKER_GID = $(shell id -g) +DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID) +COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose +COMPOSE_EXEC = $(COMPOSE) exec +COMPOSE_RUN = $(COMPOSE) run --rm --build + + +# ============================================================================== +# RULES + +default: help + +# -- Project + +create-env-files: ## Create empty .local env files for local development +create-env-files: \ + ops/env/widgets.local \ + ops/env/website.local +.PHONY: create-env-files + +bootstrap: ## Prepare the project for local development and start the services + @echo "$(BOLD)" + @echo "╔══════════════════════════════════════════════════════════════════════════════╗" + @echo "║ ║" + @echo "║ 🚀 Welcome to Integration - Shared frontend packages from La Suite! 🚀 ║" + @echo "║ ║" + @echo "║ This will set up your development environment with : ║" + @echo "║ • Docker containers for all services ║" + @echo "║ • Frontend dependencies and build ║" + @echo "║ • Environment configuration files ║" + @echo "║ ║" + @echo "║ Services will be available at: ║" + @echo "║ • Website: http://localhost:8930 ║" + @echo "║ • Widgets: http://localhost:8931 ║" + @echo "║ ║" + @echo "╚══════════════════════════════════════════════════════════════════════════════╝" + @echo "$(RESET)" + @echo "$(GREEN)Starting bootstrap process...$(RESET)" + @echo "" + @$(MAKE) update + @$(MAKE) start + @echo "" + @echo "$(GREEN)🎉 Bootstrap completed successfully!$(RESET)" + @echo "" + @echo "$(BOLD)Next steps:$(RESET)" + @echo " • Visit http://localhost:8930 to access the website" + @echo " • Visit http://localhost:8931 to access the widgets" + @echo " • Run 'make help' to see all available commands" + @echo "" +.PHONY: bootstrap + +update: ## Update the project with latest changes + @$(MAKE) create-env-files + @$(MAKE) build + @$(MAKE) widgets-install + @$(MAKE) website-install +.PHONY: update + +# -- Docker/compose +build: ## build the project containers + @$(COMPOSE) build +.PHONY: build + +down: ## stop and remove containers, networks, images, and volumes + @$(COMPOSE) down +.PHONY: down + +logs: ## display all services logs (follow mode) + @$(COMPOSE) logs -f +.PHONY: logs + +start: ## start all development services + @$(COMPOSE) up --force-recreate --build -d website-dev widgets-dev --wait +.PHONY: start + +status: ## an alias for "docker compose ps" + @$(COMPOSE) ps +.PHONY: status + +stop: ## stop all development services + @$(COMPOSE) --profile "*" stop +.PHONY: stop + +restart: ## restart all development services +restart: \ + stop \ + start +.PHONY: restart + +# -- Misc +clean: ## restore repository state as it was freshly cloned + git clean -idx +.PHONY: clean + +help: + @echo "$(BOLD)messages Makefile" + @echo "Please use 'make $(BOLD)target$(RESET)' where $(BOLD)target$(RESET) is one of:" + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(GREEN)%-30s$(RESET) %s\n", $$1, $$2}' +.PHONY: help + +ops/env/%.local: + @echo "# Local development overrides for $(notdir $*)" > $@ + @echo "# Add your local-specific environment variables below:" >> $@ + @echo "# Example: DJANGO_DEBUG=True" >> $@ + @echo "" >> $@ + +lint: ## run all linters +lint: \ + widgets-lint +.PHONY: lint + + +# Website +website-install: ## install the website locally + @args="$(filter-out $@,$(MAKECMDGOALS))" && \ + $(COMPOSE) run --build --rm website-dev npm install $${args:-${1}} +.PHONY: website-install + +website-freeze-deps: ## freeze the website dependencies + rm -rf website/package-lock.json + @$(MAKE) website-install +.PHONY: website-freeze-deps + +website-shell: ## open a shell in the website container + $(COMPOSE) run --build --rm -p 8930:8930 website-dev /bin/sh +.PHONY: website-shell + +website-start: ## start the website container + $(COMPOSE) up --force-recreate --build -d website-dev --wait + @sleep 2 + @echo "$(BOLD)" + @echo "╔══════════════════════════════════════════════════════════════════════════════╗" + @echo "║ ║" + @echo "║ 🚀 Website development server with Live Reload is started! 🚀 ║" + @echo "║ ║" + @echo "║ Open your browser at http://localhost:8930 ║" + @echo "║ ║" + @echo "╚══════════════════════════════════════════════════════════════════════════════╝" + @echo "$(RESET)" +.PHONY: website-start + +website-stop: ## stop the website container + $(COMPOSE) stop website-dev +.PHONY: website-stop + + +website-restart: ## restart the website container and rebuild +website-restart: \ + website-stop \ + website-start +.PHONY: website-restart + + +# Widgets +widgets-install: ## install the widgets locally + @args="$(filter-out $@,$(MAKECMDGOALS))" && \ + $(COMPOSE) run --build --rm widgets-dev npm install $${args:-${1}} +.PHONY: widgets-install + +widgets-freeze-deps: ## freeze the widgets dependencies + rm -rf src/widgets/package-lock.json + @$(MAKE) widgets-install +.PHONY: widgets-freeze-deps + +widgets-build: ## build the widgets + $(COMPOSE) run --build --rm widgets-dev npm run build +.PHONY: widgets-build + +widgets-lint: ## lint the widgets + $(COMPOSE) run --build --rm widgets-dev npm run lint +.PHONY: widgets-lint + +widgets-shell: ## open a shell in the widgets container + $(COMPOSE) run --build --rm widgets-dev /bin/sh +.PHONY: widgets-shell + +widgets-start: ## start the widgets container + $(COMPOSE) up --force-recreate --build -d widgets-dev --wait + @echo "$(BOLD)" + @echo "╔══════════════════════════════════════════════════════════════════════════════╗" + @echo "║ ║" + @echo "║ 🚀 Widgets development server with Live Reload is started! 🚀 ║" + @echo "║ ║" + @echo "║ Open your browser at http://localhost:8931 ║" + @echo "║ ║" + @echo "╚══════════════════════════════════════════════════════════════════════════════╝" + @echo "$(RESET)" +.PHONY: widgets-start + +widgets-stop: ## stop the widgets container + $(COMPOSE) stop widgets-dev +.PHONY: widgets-stop + +widgets-restart: ## restart the widgets container and rebuild +widgets-restart: \ + widgets-stop \ + widgets-build \ + widgets-start +.PHONY: widgets-restart + +widgets-deploy: ## deploy the widgets to an S3 bucket + @## Error if the env vars WIDGETS_S3_PATH is not set + @if [ -z "$$WIDGETS_S3_PATH" ]; then \ + echo "Error: WIDGETS_S3_PATH is not set"; \ + exit 1; \ + fi; \ + docker run --rm -ti -v .aws:/root/.aws -v `pwd`/website/public/widgets/dist:/aws amazon/aws-cli s3 cp --acl public-read --recursive . s3://$(WIDGETS_S3_PATH) +.PHONY: widgets-deploy diff --git a/README.md b/README.md index 7584e9e..f36014c 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,61 @@ Repo containing code of: - [@gouvfr-lasuite/integration npm package](https://www.npmjs.com/package/@gouvfr-lasuite/integration) in [`packages/integration`](./packages/integration/) +- Frontend widgets in [`packages/widgets`](./packages/widgets/) - [La Suite: integration docs and API](https://integration.lasuite.numerique.gouv.fr/) in [`website`](./website/) + +## Local development + +After checking out the repository, run: +``` +$ make bootstrap +``` + +For both the website and the widgets projects, it will install dependencies, build, and start the development server, respectively at http://localhost:8930 and http://localhost:8931. + +To see dev server outputs, run: +``` +$ make logs +``` + +You can view all available commands with: +``` +$ make help +``` + +### Developing the website + +If you want a faster startup than `make bootstrap`, you can run more focused commands. For the website: + +``` +$ make website-start +``` + +This will start the development server at [http://localhost:8930](http://localhost:8930). + +### Developing Widgets + +We currently develop some embeddable widgets in the `packages/widgets` directory in this repository. + +``` +$ make widgets-start +``` + +This will start the development server at [http://localhost:8931](http://localhost:8931). + +You can then build them with: + +``` +$ make widgets-build +``` + +And deploy them to an S3 bucket, with `.aws/{config|credentials}` files in the root of the repository. +``` +$ WIDGETS_S3_PATH=xxx make widgets-deploy +``` + + ## Licenses Source code is released under the [MIT](LICENSES/MIT.txt) and other diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..2db4039 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,34 @@ +name: integration + +services: + + website-dev: + user: "${DOCKER_USER:-1000}" + build: + context: ./website + dockerfile: Dockerfile + env_file: + - ops/env/website.defaults + - ops/env/website.local + command: ["npm", "run", "dev"] + volumes: + - ./website/:/home/website/ + ports: + - "8930:8930" + + widgets-dev: + user: "${DOCKER_USER:-1000}" + build: + context: ./packages/widgets + dockerfile: Dockerfile + env_file: + - ops/env/widgets.defaults + - ops/env/widgets.local + command: ["npm", "run", "dev"] + volumes: + - ./packages/widgets/:/home/widgets/ + - ./website/:/home/website/ + - ./website/src/pages/widgets-demo/:/home/widgets/widgets-demo/ + - ./website/public/widgets/:/home/widgets/widgets/ + ports: + - "8931:8931" diff --git a/website/.env.example b/ops/env/website.defaults similarity index 79% rename from website/.env.example rename to ops/env/website.defaults index 359cb4c..4829da6 100644 --- a/website/.env.example +++ b/ops/env/website.defaults @@ -1,2 +1,3 @@ PUBLIC_LASUITE_API_URL=https://integration.lasuite.numerique.gouv.fr PUBLIC_USE_GAUFRE_SUBSETTED_FONT=1 +ASTRO_TELEMETRY_DISABLED=1 diff --git a/ops/env/website.local b/ops/env/website.local new file mode 100644 index 0000000..a775563 --- /dev/null +++ b/ops/env/website.local @@ -0,0 +1,4 @@ +# Local development overrides for website +# Add your local-specific environment variables below: +# Example: DJANGO_DEBUG=True + diff --git a/ops/env/widgets.defaults b/ops/env/widgets.defaults new file mode 100644 index 0000000..cf47dd2 --- /dev/null +++ b/ops/env/widgets.defaults @@ -0,0 +1 @@ +WIDGETS_OUTPUT_DIR=/home/website/public/widgets/ \ No newline at end of file diff --git a/ops/env/widgets.local b/ops/env/widgets.local new file mode 100644 index 0000000..2317523 --- /dev/null +++ b/ops/env/widgets.local @@ -0,0 +1,4 @@ +# Local development overrides for widgets +# Add your local-specific environment variables below: +# Example: DJANGO_DEBUG=True + diff --git a/packages/integration/README.md b/packages/integration/README.md index 400d3d2..27cb94c 100644 --- a/packages/integration/README.md +++ b/packages/integration/README.md @@ -27,7 +27,7 @@ This folder is meant to generate the `@gouvfr-lasuite/integration` npm package. It's a vite app. -To start, `npm install` a first time and copy the example env file: `cp .env.example .env`. Make sure the API env var targets a running API. If you don't want to use the production one, you can run one locally easily: the API is exposed via the `/website` server, go check the README there. +To start, `npm install` a first time. Make sure the API env var targets a running API. If you don't want to use the production one, you can run one locally easily: the API is exposed via the `/website` server, go check the README there. Then, run the local dev server with `npm run dev`. diff --git a/packages/widgets/Dockerfile b/packages/widgets/Dockerfile new file mode 100644 index 0000000..24a7a24 --- /dev/null +++ b/packages/widgets/Dockerfile @@ -0,0 +1,10 @@ +FROM node:22-slim AS widgets-deps + +WORKDIR /home/widgets/ + +RUN npm install -g npm@11.3.0 && npm cache clean -f + +ARG DOCKER_USER +USER ${DOCKER_USER} + +ENV npm_config_cache=/tmp/npm-cache diff --git a/packages/widgets/build.js b/packages/widgets/build.js new file mode 100644 index 0000000..0976217 --- /dev/null +++ b/packages/widgets/build.js @@ -0,0 +1,31 @@ +import { build } from 'vite' +import { readdirSync } from 'node:fs' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) + +const widgetsDir = join(__dirname, 'src', 'widgets') + +function discoverWidgets() { + return readdirSync(widgetsDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) +} + +// Run an independent build for each widget +for (const widget of discoverWidgets()) { + await build({ + build: { + emptyOutDir: false, + outDir: join(process.env.WIDGETS_OUTPUT_DIR || "", "dist"), + rollupOptions: { + input: join(widgetsDir, widget, 'main.ts'), + output: { + entryFileNames: widget + '.js', + format: 'iife' + } + } + }, + }) +} diff --git a/packages/widgets/eslint.config.mjs b/packages/widgets/eslint.config.mjs new file mode 100644 index 0000000..7cd04f4 --- /dev/null +++ b/packages/widgets/eslint.config.mjs @@ -0,0 +1,10 @@ +// @ts-check + +import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; +import tseslint from 'typescript-eslint'; + +export default defineConfig( + eslint.configs.recommended, + tseslint.configs.recommended, +); \ No newline at end of file diff --git a/packages/widgets/index.html b/packages/widgets/index.html new file mode 100644 index 0000000..81eee2f --- /dev/null +++ b/packages/widgets/index.html @@ -0,0 +1,16 @@ + + + + + + Widgets demo + + +

Widgets demo

+ + + diff --git a/packages/widgets/package-lock.json b/packages/widgets/package-lock.json new file mode 100644 index 0000000..fa16f67 --- /dev/null +++ b/packages/widgets/package-lock.json @@ -0,0 +1,2732 @@ +{ + "name": "@gouvfr-lasuite/widgets", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@gouvfr-lasuite/widgets", + "version": "0.0.0", + "devDependencies": { + "eslint": "9.36.0", + "prettier": "3.6.2", + "typescript": "5.9.2", + "typescript-eslint": "8.45.0", + "vite": "7.1.7" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/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": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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==", + "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", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.45.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.45.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/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/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": { + "balanced-match": "^1.0.0" + } + }, + "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": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "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": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "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": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "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": { + "node": ">=0.10.0" + } + }, + "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": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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": { + "node": ">=0.12.0" + } + }, + "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/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "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/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": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/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": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/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": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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": { + "node": ">=8" + } + }, + "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": { + "node": ">=8" + } + }, + "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": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=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": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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": { + "node": ">=4" + } + }, + "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": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "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": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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": { + "node": ">=0.10.0" + } + }, + "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": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "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": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/widgets/package.json b/packages/widgets/package.json new file mode 100644 index 0000000..6adeebb --- /dev/null +++ b/packages/widgets/package.json @@ -0,0 +1,19 @@ +{ + "name": "@gouvfr-lasuite/widgets", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0 --port 8931", + "build": "tsc && node build.js", + "preview": "vite preview --host 0.0.0.0 --port 8931", + "lint": "prettier --write --print-width=120 src && eslint src && tsc --noEmit" + }, + "devDependencies": { + "typescript": "5.9.2", + "vite": "7.1.7", + "eslint": "9.36.0", + "typescript-eslint": "8.45.0", + "prettier": "3.6.2" + } +} diff --git a/packages/widgets/src/shared/events.ts b/packages/widgets/src/shared/events.ts new file mode 100644 index 0000000..e4fe556 --- /dev/null +++ b/packages/widgets/src/shared/events.ts @@ -0,0 +1,31 @@ +/* eslint @typescript-eslint/no-explicit-any:0 */ +const NAMESPACE = `lasuite-widget`; + +export const triggerEvent = ( + widgetName: string, + eventName: string, + detail?: Record, + root?: Window | Document | HTMLElement | null, +) => { + return (root || document).dispatchEvent( + new CustomEvent(`${NAMESPACE}-${widgetName}-${eventName}`, detail ? { detail } : undefined), + ); +}; + +export const listenEvent = ( + widgetName: string, + eventName: string, + root: Window | Document | HTMLElement | null, + once: boolean, + callback: (data: any) => void, +) => { + const cb = (e: any) => callback(e.detail); + const eventFullName = widgetName ? `${NAMESPACE}-${widgetName}-${eventName}` : eventName; + (root || document).addEventListener(eventFullName, cb, once ? { once: true } : undefined); + return () => + (root || document).removeEventListener( + eventFullName, + cb, + once ? ({ once: true } as EventListenerOptions) : undefined, + ); +}; diff --git a/packages/widgets/src/shared/focus.ts b/packages/widgets/src/shared/focus.ts new file mode 100644 index 0000000..11c2d1a --- /dev/null +++ b/packages/widgets/src/shared/focus.ts @@ -0,0 +1,43 @@ +const isVisible = (element: HTMLElement) => { + return element.offsetWidth > 0 && element.offsetHeight > 0; +}; + +export const trapFocus = (shadowRoot: ShadowRoot, container: HTMLElement, selector: string) => { + const handleKeydown = (e: KeyboardEvent) => { + if (e.key !== "Tab") return; + + const focusable = Array.from(container.querySelectorAll(selector)).filter((element) => + isVisible(element as HTMLElement), + ); + if (focusable.length === 0) return; + + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + + if (e.shiftKey && shadowRoot.activeElement === first) { + e.preventDefault(); + (last as HTMLElement).focus(); + } else if (!e.shiftKey && shadowRoot.activeElement === last) { + e.preventDefault(); + (first as HTMLElement).focus(); + } + }; + + container.addEventListener("keydown", handleKeydown); + + return () => { + container.removeEventListener("keydown", handleKeydown); + }; +}; + +export const trapEscape = (cb: () => void) => { + const handleKeydown = (e: KeyboardEvent) => { + if (e.key !== "Escape") return; + e.preventDefault(); + cb(); + }; + document.addEventListener("keydown", handleKeydown); + return () => { + document.removeEventListener("keydown", handleKeydown); + }; +}; diff --git a/packages/widgets/src/shared/script.ts b/packages/widgets/src/shared/script.ts new file mode 100644 index 0000000..dc6c8ed --- /dev/null +++ b/packages/widgets/src/shared/script.ts @@ -0,0 +1,70 @@ +import { triggerEvent } from "./events"; + +type WidgetEvent = [string, string, Record | undefined]; + +type EventArray = Array & { _loaded?: Record }; + +declare global { + var _lasuite_widget: EventArray; +} + +// This could have been an enum but we want to support erasableSyntaxOnly TS settings +export const STATE_NOT_LOADED = 0; +export const STATE_LOADING = 1; +export const STATE_LOADED = 2; + +export const getLoaded = (widgetName: string) => { + return window._lasuite_widget?._loaded?.[widgetName]; +}; + +export const setLoaded = (widgetName: string, status: number) => { + if (!window._lasuite_widget?._loaded) return; + window._lasuite_widget._loaded[widgetName] = status; +}; + +// Replace the push method of the _lasuite_widget array used for communication between the widget and the page +export const installHook = (widgetName: string) => { + if (!window._lasuite_widget) { + window._lasuite_widget = [] as EventArray; + } + const W = window._lasuite_widget; + + // Keep track of the loaded state of each widget + if (!W._loaded) { + W._loaded = {} as Record; + } + + if (getLoaded(widgetName) !== STATE_LOADED) { + // Replace the push method of the _lasuite_widget array used for communication between the widget and the page + W.push = ((...elts: WidgetEvent[]): number => { + for (const elt of elts) { + // If the target widget is loaded, fire the event + if (getLoaded(elt[0]) === STATE_LOADED) { + triggerEvent(elt[0], elt[1], elt[2]); + } else { + W[W.length] = elt; + } + } + return W.length; + }) as typeof Array.prototype.push; + + setLoaded(widgetName, STATE_LOADED); + + // Empty the existing array and re-push all events that were received before the hook was installed + for (const evt of W.splice(0, W.length)) { + W.push(evt); + } + } + + // Finally, fire an event to signal that we are loaded + triggerEvent(widgetName, "loaded"); +}; + +// Loads another widget from the same directory +export const injectScript = (url: string, type: string = "") => { + const newScript = document.createElement("script"); + newScript.src = url; + newScript.type = type; + newScript.defer = true; + document.body.appendChild(newScript); +}; diff --git a/packages/widgets/src/shared/shadow-dom.ts b/packages/widgets/src/shared/shadow-dom.ts new file mode 100644 index 0000000..8ba66ff --- /dev/null +++ b/packages/widgets/src/shared/shadow-dom.ts @@ -0,0 +1,30 @@ +// Shared utility for creating shadow DOM widgets +export function createShadowWidget(widgetName: string, htmlContent: string, cssContent: string): HTMLDivElement { + const id = `lasuite-widget-${widgetName}-shadow`; + // Check if widget already exists + const existingWidget = document.getElementById(id); + if (existingWidget) { + existingWidget.remove(); + } + + // Create container element + const container = document.createElement("div"); + container.id = id; + + // Create shadow root + const shadow = container.attachShadow({ mode: "open" }); + + // Create style element for scoped CSS + const style = document.createElement("style"); + style.textContent = cssContent; + + // Create content element + const content = document.createElement("div"); + content.innerHTML = htmlContent; + + // Append style and content to shadow DOM + shadow.appendChild(style); + shadow.appendChild(content); + + return container; +} diff --git a/packages/widgets/src/vite-env.d.ts b/packages/widgets/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/packages/widgets/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/widgets/src/widgets/feedback/main.ts b/packages/widgets/src/widgets/feedback/main.ts new file mode 100644 index 0000000..16626a6 --- /dev/null +++ b/packages/widgets/src/widgets/feedback/main.ts @@ -0,0 +1,204 @@ +import styles from "./styles.css?inline"; +import { createShadowWidget } from "../../shared/shadow-dom"; +import { installHook } from "../../shared/script"; +import { listenEvent, triggerEvent } from "../../shared/events"; +import { trapFocus, trapEscape } from "../../shared/focus"; + +const widgetName = "feedback"; + +type ConfigData = { + title?: string; + placeholder?: string; + emailPlaceholder?: string; + submitText?: string; + successText?: string; + successText2?: string; + closeLabel?: string; + submitUrl?: string; +}; + +type ConfigResponse = { + success?: boolean; + detail?: string; + captcha?: boolean; + config?: ConfigData; +}; + +type FeedbackWidgetArgs = { + title?: string; + placeholder?: string; + emailPlaceholder?: string; + submitText?: string; + successText?: string; + successText2?: string; + closeLabel?: string; + submitUrl?: string; + api: string; + channel: string; + email?: string; + bottomOffset?: number; + rightOffset?: number; +}; + +listenEvent(widgetName, "init", null, false, async (args: FeedbackWidgetArgs) => { + if (!args.api || !args.channel) { + console.error("Feedback widget requires an API URL and a channel ID"); + return; + } + + let configData: ConfigData | undefined; + try { + const config = await fetch(`${args.api}config/`, { + headers: { + "X-Channel-ID": args.channel, + }, + }); + const configResponse = (await config.json()) as ConfigResponse; + if (!configResponse.success) throw new Error(configResponse.detail || "Unknown error"); + if (configResponse.captcha) throw new Error("Captcha is not supported yet"); + configData = configResponse.config; + } catch (error) { + console.error("Error fetching config", error); + triggerEvent(widgetName, "closed"); + return; + } + + const title = args.title || configData?.title || "Feedback"; + const placeholder = args.placeholder || configData?.placeholder || "Share your feedback..."; + const emailPlaceholder = args.emailPlaceholder || configData?.emailPlaceholder || "Your email..."; + const submitText = args.submitText || configData?.submitText || "Send Feedback"; + const successText = args.successText || configData?.successText || "Thank you for your feedback!"; + const successText2 = args.successText2 || configData?.successText2 || ""; + const closeLabel = args.closeLabel || configData?.closeLabel || "Close the feedback widget"; + + /* prettier-ignore */ + const htmlContent = + `
` + + `` + + `
` + + `
` + + `` + + `` + + `` + + `
` + + `
` + + `
` + + `` + + `

` + + `

` + + `
` + + `
` + + `
`; + + // Create shadow DOM widget + const shadowContainer = createShadowWidget(widgetName, htmlContent, styles); + const shadowRoot = shadowContainer.shadowRoot!; + const $ = shadowRoot.querySelector.bind(shadowRoot); + + const wrapper = $("#wrapper")!; + const titleSpan = $("#title")!; + const submitBtn = $("#submit")!; + const feedbackText = $("#feedback-text")!; + const errorDiv = $("#error")!; + const closeBtn = $("#close")!; + const emailInput = $("#email")!; + const form = $("form")!; + const successDiv = $("#success")!; + const successTextP = $("#success-text")!; + const successText2P = $("#success-text2")!; + + wrapper.style.bottom = 20 + (args.bottomOffset || 0) + "px"; + wrapper.style.right = 20 + (args.rightOffset || 0) + "px"; + + titleSpan.textContent = title; + feedbackText.placeholder = placeholder; + emailInput.placeholder = emailPlaceholder; + submitBtn.textContent = submitText; + closeBtn.setAttribute("aria-label", closeLabel); + + if (args.email) { + emailInput.remove(); + } + + form.addEventListener("submit", async (e) => { + e.preventDefault(); + errorDiv.textContent = ""; + const message = feedbackText.value.trim(); + const email = args.email || emailInput.value.trim(); + try { + if (!message) { + feedbackText.focus(); + throw new Error("Missing value"); + } + if (!email) { + emailInput.focus(); + throw new Error("Missing value"); + } + + const ret = await fetch(configData?.submitUrl || `${args.api}deliver/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Channel-ID": args.channel, + }, + body: JSON.stringify({ textBody: message, email }), + }); + let retData; + try { + retData = await ret.json(); + } catch { + throw new Error("Invalid response from server"); + } + + if (!retData.success) throw new Error(retData.detail || "Unknown error"); + + form.remove(); + successDiv.style.display = "flex"; + requestAnimationFrame(() => { + // This RAF call allows screen readers to register the aria-live area + successTextP.textContent = successText; + successText2P.textContent = successText2; + }); + } catch (error) { + errorDiv.style.display = "block"; + errorDiv.textContent = "⚠ " + (error instanceof Error ? error.message : "Unknown error"); + } + }); + + let untrapFocus: (() => void) | null = null; + let untrapEscape: (() => void) | null = null; + let removeCloseListener: () => void = () => {}; + + const closeWidget = () => { + shadowRoot.host.remove(); + if (untrapFocus) { + untrapFocus(); + } + if (untrapEscape) { + untrapEscape(); + } + if (removeCloseListener) { + removeCloseListener(); + } + triggerEvent(widgetName, "closed"); + }; + + closeBtn.addEventListener("click", closeWidget); + removeCloseListener = listenEvent(widgetName, "close", null, false, closeWidget); + + document.body.appendChild(shadowContainer); + + feedbackText.focus(); + + untrapFocus = trapFocus(shadowRoot, wrapper, "textarea,input,button"); + untrapEscape = trapEscape(() => { + triggerEvent(widgetName, "close"); + }); + + triggerEvent(widgetName, "opened"); +}); + +installHook(widgetName); diff --git a/packages/widgets/src/widgets/feedback/styles.css b/packages/widgets/src/widgets/feedback/styles.css new file mode 100644 index 0000000..4a9cd1c --- /dev/null +++ b/packages/widgets/src/widgets/feedback/styles.css @@ -0,0 +1,158 @@ +/* Widget styles */ +#wrapper { + position: fixed; + z-index: 1000; + width: 350px; + height: 400px; + background: white; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); + display: flex; + flex-direction: column; +} + +#header { + background: #000091; + color: white; + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 12px 12px 0 0; +} + +h6 { + padding: 16px; + font-weight: 600; + font-size: 16px; + margin: 0; +} + +#close { + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 18px; + margin-right: 12px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; +} + +#content { + display: flex; + flex: 1; + flex-direction: column; +} + +form { + flex: 1; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +#email { + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 12px; + font-family: inherit; + font-size: 14px; +} + +#feedback-text { + flex: 1; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 12px; + font-family: inherit; + font-size: 14px; + resize: none; +} + +#submit { + background: #000091; + color: white; + border: 2px solid #000091; + border-radius: 8px; + padding: 12px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} + +#submit:hover, +#close:hover { + background: #1212ff; +} + +#feedback-text:focus, +#email:focus, +#submit:focus { + outline: 2px solid #0a76f6; + border-color: white; +} + +#close:focus { + outline: 2px solid white; +} + +#status { + font-size: 14px; + font-weight: 500; + text-align: center; +} + +#success { + display: none; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + text-align: center; +} + +#success i { + background: #27a658; + color: white; + width: 60px; + height: 60px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + font-weight: bold; + margin-bottom: 20px; + font-style: normal; +} + +#success p { + color: #0f4e27; + margin: 0; + font-size: 16px; + font-weight: 500; + margin-bottom: 30px; +} + +#error { + color: #dc3545; + margin-bottom: 12px; +} +#error:empty { + margin: 0; +} + +/* Responsive */ +@media (max-width: 420px) { + #wrapper { + width: 100%; + right: 0px !important; + border-radius: 0; + } +} diff --git a/packages/widgets/src/widgets/lagaufre/main.ts b/packages/widgets/src/widgets/lagaufre/main.ts new file mode 100644 index 0000000..d41816d --- /dev/null +++ b/packages/widgets/src/widgets/lagaufre/main.ts @@ -0,0 +1,367 @@ +import styles from "./styles.css?inline"; +import { createShadowWidget } from "../../shared/shadow-dom"; +import { installHook } from "../../shared/script"; +import { listenEvent, triggerEvent } from "../../shared/events"; +import { trapFocus, trapEscape } from "../../shared/focus"; + +const widgetName = "lagaufre"; + +type Service = { + name: string; + url: string; + maturity?: string; + logo?: string; +}; + +type Organization = { + name: string; + type: string; + siret: string; +}; + +type ServicesResponse = { + organization?: Organization; + services: Service[]; + error?: unknown; +}; + +type GaufreWidgetArgs = { + api?: string; + position?: string | (() => Record); + top?: number; + bottom?: number; + left?: number; + right?: number; + data?: ServicesResponse; + fontFamily?: string; + background?: string; + headerLogo?: string; + headerUrl?: string; + open?: boolean; + label?: string; + closeLabel?: string; + headerLabel?: string; + loadingText?: string; + newWindowLabelSuffix?: string; + showFooter?: boolean; + dialogElement?: HTMLElement; + buttonElement?: HTMLElement; +}; + +let loaded = false; + +// Initialize widget (load data and prepare shadow DOM) +listenEvent(widgetName, "init", null, false, async (args: GaufreWidgetArgs) => { + if (!args.api && !args.data) { + console.error("Missing API URL"); + return; + } + + if (loaded) { + triggerEvent(widgetName, "destroy"); + await new Promise((resolve) => setTimeout(resolve, 10)); + } + + const listeners: (() => void)[] = []; + let isVisible = false; + + // https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/ + /* prettier-ignore */ + const htmlContent = + ``; + + // Create shadow DOM widget + const shadowContainer = createShadowWidget(widgetName, htmlContent, styles); + const shadowRoot = shadowContainer.shadowRoot!; + + const wrapper = shadowRoot.querySelector("#wrapper")!; + const loadingDiv = shadowRoot.querySelector("#loading")!; + const servicesGrid = shadowRoot.querySelector("#services-grid")!; + const errorDiv = shadowRoot.querySelector("#error")!; + const closeBtn = shadowRoot.querySelector("#close"); + const okBtn = shadowRoot.querySelector("#ok-button")!; + const headerLogo = shadowRoot.querySelector("#header-logo"); + + // Configure dynamic properties + const configure = (newArgs: GaufreWidgetArgs) => { + const directions = ["top", "bottom", "left", "right"] as const; + + const applyPos = (obj: Record) => { + directions.forEach((prop) => { + wrapper.style[prop] = typeof obj[prop] === "number" ? `${obj[prop]}px` : "unset"; + }); + }; + + if (!directions.every((d) => newArgs[d] === undefined)) { + applyPos(newArgs as Record); + } + + // Positioning parameters + if (newArgs.position) { + if (typeof newArgs.position === "function") { + const pos = newArgs.position(); + wrapper.style.position = pos.position as string; + applyPos(pos); + } else { + wrapper.style.position = newArgs.position; + } + } + + // Apply font family (inherit from parent or use provided) + if (newArgs.fontFamily) { + wrapper.style.fontFamily = newArgs.fontFamily; + } + + // Apply background gradient if requested + if (newArgs.background) { + wrapper.style.background = newArgs.background; + } + + // Apply texts + const label = newArgs.label || "Services"; + const closeLabel = newArgs.closeLabel || "Close"; + loadingDiv.textContent = newArgs.loadingText || "Loading…"; + wrapper.setAttribute("aria-label", label); + if (closeBtn) { + closeBtn.setAttribute("aria-label", closeLabel); + } + + if (headerLogo) { + headerLogo.alt = (newArgs.headerLabel || "About LaSuite") + (newArgs.newWindowLabelSuffix || ""); + } + }; + + configure(args); + + listeners.push( + listenEvent("", "resize", window, false, () => { + configure(args); + }), + ); + + // Initially hide the widget + wrapper.style.display = "none"; + + const showError = (message: string) => { + loadingDiv.style.display = "none"; + servicesGrid.style.display = "none"; + errorDiv.style.display = "block"; + errorDiv.textContent = message; + }; + + const renderServices = (data: ServicesResponse) => { + // Clear previous content + servicesGrid.innerHTML = ""; + + data.services.forEach((service) => { + if (!service.logo) return; + if (service.maturity == "stable") delete service.maturity; + + const serviceCard = document.createElement("li"); + serviceCard.className = `service-card`; + + /* prettier-ignore */ + serviceCard.innerHTML = + `` + + `` + + `
` + + `
` + + `
` + + `
`; + + const anchor = serviceCard.querySelector("a")!; + const img = serviceCard.querySelector("img")!; + const serviceName = serviceCard.querySelector(".service-name")!; + + if (service.maturity) { + const maturityBadge = document.createElement("div"); + maturityBadge.className = "maturity-badge"; + maturityBadge.textContent = service.maturity; + anchor.insertBefore(maturityBadge, img.nextSibling); + } + + anchor.setAttribute( + "aria-label", + service.name + (service.maturity ? ` (${service.maturity})` : "") + (args.newWindowLabelSuffix || ""), + ); + anchor.href = service.url; + img.src = service.logo; + serviceName.textContent = service.name; + + servicesGrid.appendChild(serviceCard); + }); + + loadingDiv.style.display = "none"; + errorDiv.style.display = "none"; + servicesGrid.style.display = "grid"; + }; + + // Load data + if (args.data) { + renderServices(args.data); + } else { + // Fetch services from API + try { + const response = await fetch(args.api!, { + method: "GET", + }); + + const data = (await response.json()) as ServicesResponse; + + if (data.error) { + showError(`Error: ${JSON.stringify(data.error)}`); + } else if (data.services && data.services.length > 0) { + renderServices(data); + } else { + showError("No services found"); + } + } catch (error) { + showError(`Failed to load services: ${error instanceof Error ? error.message : "Unknown error"}`); + } + } + + const handleClickOutside = (event: MouseEvent) => { + if (args.dialogElement) { + return; + } + if (!shadowContainer.contains(event.target as Node)) { + triggerEvent(widgetName, "close"); + } + }; + + let untrapFocus: (() => void) | null = null; + let untrapEscape: (() => void) | null = null; + + // Open widget (show the prepared shadow DOM) + listeners.push( + listenEvent(widgetName, "open", null, false, () => { + wrapper.style.display = "block"; + + // Add click outside listener after a short delay to prevent immediate closing or double-clicks. + setTimeout(() => { + isVisible = true; + document.addEventListener("click", handleClickOutside); + wrapper.focus(); + }, 200); + + untrapFocus = trapFocus(shadowRoot, wrapper, "a,button"); + + if (!args.dialogElement) { + untrapEscape = trapEscape(() => { + triggerEvent(widgetName, "close"); + }); + } + + if (args.buttonElement) { + args.buttonElement.setAttribute("aria-expanded", "true"); + } + + triggerEvent(widgetName, "opened"); + }), + ); + + // Close widget (hide the shadow DOM) + listeners.push( + listenEvent(widgetName, "close", null, false, () => { + if (untrapFocus) { + untrapFocus(); + } + if (untrapEscape) { + untrapEscape(); + } + + if (!isVisible) { + return; // Already closed + } + + wrapper.style.display = "none"; + isVisible = false; + + // Return the focus to the button that opened the widget if any + if (args.buttonElement) { + args.buttonElement.focus(); + args.buttonElement.setAttribute("aria-expanded", "false"); + } + + // Remove click outside listener + document.removeEventListener("click", handleClickOutside); + + triggerEvent(widgetName, "closed"); + }), + ); + + // Toggle widget visibility + listeners.push( + listenEvent(widgetName, "toggle", null, false, () => { + if (isVisible) { + triggerEvent(widgetName, "close"); + } else { + triggerEvent(widgetName, "open"); + } + }), + ); + + listeners.push(listenEvent(widgetName, "configure", null, false, configure)); + + // Close button click handlers + if (okBtn) { + okBtn.addEventListener("click", () => { + triggerEvent(widgetName, "close"); + }); + } + if (closeBtn) { + closeBtn.addEventListener("click", () => { + triggerEvent(widgetName, "close"); + }); + } + + if (args.buttonElement) { + listeners.push( + listenEvent("", "click", args.buttonElement, false, () => { + triggerEvent(widgetName, "toggle"); + }), + ); + } + + // Add to DOM but keep hidden + if (args.dialogElement) { + args.dialogElement.appendChild(shadowContainer); + } else { + wrapper.className = "wrapper-dialog"; + document.body.appendChild(shadowContainer); + } + + listenEvent(widgetName, "destroy", null, true, () => { + triggerEvent(widgetName, "close"); + loaded = false; + shadowContainer.remove(); + // Unlisten all events + listeners.forEach((listener) => listener()); + }); + loaded = true; + + triggerEvent(widgetName, "initialized"); + + if (args.open) { + triggerEvent(widgetName, "open"); + } +}); + +installHook(widgetName); diff --git a/packages/widgets/src/widgets/lagaufre/styles.css b/packages/widgets/src/widgets/lagaufre/styles.css new file mode 100644 index 0000000..6f404ef --- /dev/null +++ b/packages/widgets/src/widgets/lagaufre/styles.css @@ -0,0 +1,271 @@ +/* La Gaufre Widget styles */ +#wrapper { + display: flex; + flex-direction: column; + font-family: inherit; +} + +.wrapper-dialog { + position: fixed; + top: 60px; + right: 60px; + z-index: 1000; + width: 340px; + max-height: 480px; + background: white; + border-radius: 0.25rem; + box-shadow: 0 0px 6px rgba(0, 0, 145, 0.1); + border: 1px solid #e5e5e5; + overflow: hidden; +} + +#header { + display: flex; + justify-content: center; + align-items: center; + padding: 4px 16px; + background: transparent; + border-bottom: 1px solid #dfe2ea; + min-height: 48px; + position: relative; +} + +#header-logo { + height: 25px; + width: auto; + object-fit: contain; + display: block; + margin: 0 auto; +} + +#close { + position: absolute; + right: 16px; +} + +#close { + background: none; + border: none; + color: #64748b; + cursor: pointer; + font-size: 36px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background-color 0.2s; +} + +#close:hover { + background-color: #f0f0f0; +} + +#close:focus { + outline: 2px solid #0a76f6; +} + +#footer { + display: flex; + padding: 16px; + background: transparent; + border-top: 1px solid #dfe2ea; + justify-content: flex-end; +} + +#ok-button { + background: #3e5de7; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s; + min-width: 60px; +} + +#ok-button:hover { + background: #1d4ed8; +} + +#ok-button:focus { + outline: 2px solid #0a76f6; +} + +#content { + flex: 1; + padding: 16px; + display: flex; + flex-direction: column; + overflow-y: auto; + min-height: 0; +} + +/* Loading state */ +#loading { + display: flex; + align-items: center; + justify-content: center; + padding: 40px 20px; + color: #666; + font-size: 14px; +} + +/* Error state */ +#error { + display: flex; + align-items: center; + justify-content: center; + padding: 40px 20px; + color: #dc3545; + font-size: 14px; + text-align: center; +} + +/* Services grid */ +#services-grid { + grid-template-columns: repeat(3, 1fr); + gap: 4px; + justify-items: center; + list-style: none; + padding: 0; + margin: 0; +} + +/* Service cards */ +.service-card { + background: transparent; + text-align: center; + transition: all 0.2s ease; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + list-style: none; + padding: 0; + margin: 0; +} + +.service-card:hover { + background-color: #eef1f4; + border-radius: 6px; +} + +/* Service logo container */ +.service-card a { + position: relative; + padding: 8px; + width: 100%; + text-decoration: none; + display: block; +} + +.service-logo { + width: 42px; + height: 42px; + object-fit: contain; +} + +.maturity-badge { + position: absolute; + top: 40px; + left: 50%; + transform: translateX(-50%); + background: #eef1f4; + color: #2845c1; + border-radius: 12px; + padding: 2px 4px; + font-size: 9px; + line-height: 9px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.3px; + white-space: nowrap; +} + +/* Service info */ +.service-info { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding-top: 4px; +} + +.service-name { + font-weight: 600; + font-size: 14px; + color: #1e40af; + margin-bottom: 2px; + line-height: 1.2; + text-align: center; +} + +/* Responsive design */ +@media (max-width: 480px) { + #wrapper { + width: 100%; + right: 0 !important; + left: 0 !important; + border-radius: 0; + bottom: 0 !important; + top: 0 !important; + max-height: 100vh; + position: fixed !important; + } + + #header { + height: 40px; + display: flex; + } + + #header-logo { + height: 35px; + } + + #footer { + display: flex; + } + + #services-grid { + grid-template-columns: repeat(3, 1fr); + gap: 12px; + } + + .service-card { + width: 70px; + padding: 6px; + } + + .service-logo, + .service-logo-placeholder { + width: 40px; + height: 40px; + } + + .service-name { + font-size: 11px; + } +} + +/* Scrollbar styling */ +#content::-webkit-scrollbar { + width: 4px; +} + +#content::-webkit-scrollbar-track { + background: transparent; +} + +#content::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 2px; +} + +#content::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} diff --git a/packages/widgets/src/widgets/loader/icon.svg b/packages/widgets/src/widgets/loader/icon.svg new file mode 100644 index 0000000..2c72d5c --- /dev/null +++ b/packages/widgets/src/widgets/loader/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/widgets/src/widgets/loader/main.ts b/packages/widgets/src/widgets/loader/main.ts new file mode 100644 index 0000000..750e10a --- /dev/null +++ b/packages/widgets/src/widgets/loader/main.ts @@ -0,0 +1,86 @@ +import styles from "./styles.css?inline"; +import { createShadowWidget } from "../../shared/shadow-dom"; +import icon from "./icon.svg?raw"; +import { injectScript, installHook, getLoaded, setLoaded, STATE_LOADED, STATE_LOADING } from "../../shared/script"; +import { triggerEvent, listenEvent } from "../../shared/events"; +const widgetName = "loader"; + +type LoaderWidgetArgs = { + widget: string; + closeLabel: string; + label: string; + params: Record; + script: string; + scriptType: string; +}; + +// The init event is sent from the embedding code +listenEvent(widgetName, "init", null, false, (args: LoaderWidgetArgs) => { + const targetWidget = args.widget || "feedback"; + + const htmlContent = `
`; + + // Create shadow DOM widget + const shadowContainer = createShadowWidget(widgetName, htmlContent, styles); + const shadowRoot = shadowContainer.shadowRoot!; + + const btn = shadowRoot.querySelector("button")!; + + const ariaOpen = () => { + btn.setAttribute("aria-label", String(args.closeLabel || "Close widget")); + btn.setAttribute("aria-expanded", "true"); + // TODO: How could we set the aria-controls attribute too, given that we + // have no id for the widget? Should we ask for it via an event? + }; + const ariaClose = () => { + btn.setAttribute("aria-label", String(args.label || "Load widget")); + btn.setAttribute("aria-expanded", "false"); + }; + ariaClose(); + + listenEvent(targetWidget, "closed", null, false, () => { + btn.classList.remove("opened"); + ariaClose(); + }); + listenEvent(targetWidget, "opened", null, false, () => { + btn.classList.add("opened"); + ariaOpen(); + }); + + btn.addEventListener("click", () => { + if (btn.classList.contains("opened")) { + triggerEvent(targetWidget, "close"); + return; + } + + const loadTimeout = setTimeout(() => { + btn.classList.remove("loading"); + }, 10000); + + // Add loading state to the UI + btn.classList.add("loading"); + + const loadedCallback = () => { + clearTimeout(loadTimeout); + btn.classList.remove("loading"); + const params = Object.assign({}, args.params); + params.bottomOffset = btn.offsetHeight + 20; + window._lasuite_widget.push([targetWidget, "init", params]); + }; + + if (getLoaded(targetWidget) === STATE_LOADED) { + loadedCallback(); + } else { + listenEvent(targetWidget, "loaded", null, true, loadedCallback); + // If it isn't even loading, we need to inject the script + if (!getLoaded(targetWidget)) { + injectScript(args.script, args.scriptType || ""); + setLoaded(targetWidget, STATE_LOADING); + } + } + }); + + document.body.appendChild(shadowContainer); +}); + +installHook(widgetName); diff --git a/packages/widgets/src/widgets/loader/styles.css b/packages/widgets/src/widgets/loader/styles.css new file mode 100644 index 0000000..f1b7cc5 --- /dev/null +++ b/packages/widgets/src/widgets/loader/styles.css @@ -0,0 +1,105 @@ +/* Widget styles */ +div { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; +} + +button { + width: 60px; + height: 60px; + border-radius: 50%; + background: #000091; + border: none; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 145, 0.3); + transition: + transform 0.2s ease, + box-shadow 0.2s ease, + background-color 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + margin: 0; +} + +svg { + width: 35px; + height: 35px; +} + +button:focus-visible { + outline: 3px solid #0a76f6; + outline-offset: 2px; +} + +button:hover { + background: #1212ff; + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(18, 18, 255, 0.4); +} + +button:active { + transform: scale(0.95); +} + +button.loading { + background: #2323ff; +} + +button.loading::after { + content: ""; + width: 25px; + height: 25px; + border: 3px solid transparent; + border-top: 3px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +button.loading svg { + display: none; +} + +button.opened svg { + display: none; +} + +button.opened::after { + content: "+"; + font-size: 50px; + color: white; + height: 60px; + line-height: 60px; + font-family: Arial; + transform: rotate(45deg); +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@media (max-width: 420px) { + button { + width: 40px; + height: 40px; + } + svg { + width: 25px; + height: 25px; + } + div { + right: 15px; + bottom: 15px; + } + button.opened::after { + font-size: 40px; + } +} diff --git a/packages/widgets/tsconfig.json b/packages/widgets/tsconfig.json new file mode 100644 index 0000000..4f5edc2 --- /dev/null +++ b/packages/widgets/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/packages/widgets/vite.config.ts b/packages/widgets/vite.config.ts new file mode 100644 index 0000000..c63019b --- /dev/null +++ b/packages/widgets/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' + + +export default defineConfig({ + server: { + proxy: { + '^/widgets/dist/(.+)\\.js$': { + target: 'http://localhost:8931', + rewrite: (path) => { + const match = path.match(/^\/widgets\/dist\/(.+)\.js$/) + if (match) { + return `/src/widgets/${match[1]}/main.ts` + } + return path + } + } + } + } +}) \ No newline at end of file diff --git a/website/Dockerfile b/website/Dockerfile new file mode 100644 index 0000000..5c1c835 --- /dev/null +++ b/website/Dockerfile @@ -0,0 +1,10 @@ +FROM node:22-slim AS website-deps + +WORKDIR /home/website/ + +RUN npm install -g npm@11.3.0 && npm cache clean -f + +ARG DOCKER_USER +USER ${DOCKER_USER} + +ENV npm_config_cache=/tmp/npm-cache diff --git a/website/README.md b/website/README.md index 7bb2850..8c68d7c 100644 --- a/website/README.md +++ b/website/README.md @@ -21,7 +21,6 @@ This is a starlight-based Astro app. Follow the official docs if more info is ne ```sh npm install -cp .env.example .env npm run dev ``` diff --git a/website/package.json b/website/package.json index 6feb253..3345cec 100644 --- a/website/package.json +++ b/website/package.json @@ -7,12 +7,12 @@ "node": "20" }, "scripts": { - "dev": "astro dev", + "dev": "astro dev --port 8930 --host 0.0.0.0", "start": "node ./server.mjs", "build-backgrounds": "node ./bin/build-services-backgrounds.mjs", "gaufre-glyphhanger-cmd": "node ./bin/gaufre-font-cmd.mjs", "build": "astro check && npm run build-backgrounds && astro build", - "preview": "astro preview", + "preview": "astro preview --port 8930 --host 0.0.0.0", "astro": "astro" }, "dependencies": { diff --git a/website/public/widgets/demo/feedback-api-error.json b/website/public/widgets/demo/feedback-api-error.json new file mode 100644 index 0000000..3d1cff4 --- /dev/null +++ b/website/public/widgets/demo/feedback-api-error.json @@ -0,0 +1 @@ +{"success": true, "config": {"captcha": false, "submitUrl": "/error"}} \ No newline at end of file diff --git a/website/public/widgets/demo/feedback-api.json b/website/public/widgets/demo/feedback-api.json new file mode 100644 index 0000000..ed13f72 --- /dev/null +++ b/website/public/widgets/demo/feedback-api.json @@ -0,0 +1 @@ +{"success": true, "config": {"captcha": false}} \ No newline at end of file diff --git a/website/public/widgets/demo/lagaufre-data.json b/website/public/widgets/demo/lagaufre-data.json new file mode 100644 index 0000000..0da3e44 --- /dev/null +++ b/website/public/widgets/demo/lagaufre-data.json @@ -0,0 +1,57 @@ +{ + "organization": { + "name": "Example Organization", + "type": "Public Administration", + "siret": "12345678901234" + }, + "services": [ + { + "id": 1, + "name": "Authentication Service", + "url": "https://example.com/auth", + "maturity": "stable", + "logo": "/widgets/demo/logos/auth.svg", + "subscribed": true + }, + { + "id": 2, + "name": "Document Portal", + "url": "https://example.com/docs", + "maturity": "stable", + "logo": "/widgets/demo/logos/docs.svg", + "subscribed": true + }, + { + "id": 3, + "name": "Payment Gateway", + "url": "https://example.com/payments", + "maturity": "stable", + "logo": "/widgets/demo/logos/payment.svg", + "subscribed": false + }, + { + "id": 4, + "name": "Analytics Dashboard", + "url": "https://example.com/analytics", + "maturity": "beta", + "logo": "/widgets/demo/logos/analytics.svg", + "subscribed": true + }, + { + "id": 5, + "name": "Notification Center", + "url": "https://example.com/notifications", + "maturity": "alpha", + "logo": "/widgets/demo/logos/notifications.svg", + "subscribed": false + }, + { + "id": 6, + "name": "File Storage", + "url": "https://example.com/storage", + "maturity": "stable", + "logo": "/widgets/demo/logos/storage.svg", + "subscribed": true + } + ] +} diff --git a/website/public/widgets/demo/logos/analytics.svg b/website/public/widgets/demo/logos/analytics.svg new file mode 100644 index 0000000..48b144f --- /dev/null +++ b/website/public/widgets/demo/logos/analytics.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/website/public/widgets/demo/logos/auth.svg b/website/public/widgets/demo/logos/auth.svg new file mode 100644 index 0000000..fbe693e --- /dev/null +++ b/website/public/widgets/demo/logos/auth.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/website/public/widgets/demo/logos/docs.svg b/website/public/widgets/demo/logos/docs.svg new file mode 100644 index 0000000..6dbd659 --- /dev/null +++ b/website/public/widgets/demo/logos/docs.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/public/widgets/demo/logos/notifications.svg b/website/public/widgets/demo/logos/notifications.svg new file mode 100644 index 0000000..bb2d968 --- /dev/null +++ b/website/public/widgets/demo/logos/notifications.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/website/public/widgets/demo/logos/payment.svg b/website/public/widgets/demo/logos/payment.svg new file mode 100644 index 0000000..543424b --- /dev/null +++ b/website/public/widgets/demo/logos/payment.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/website/public/widgets/demo/logos/storage.svg b/website/public/widgets/demo/logos/storage.svg new file mode 100644 index 0000000..07e6bde --- /dev/null +++ b/website/public/widgets/demo/logos/storage.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/public/widgets/dist/feedback.js b/website/public/widgets/dist/feedback.js new file mode 100644 index 0000000..5ec8519 --- /dev/null +++ b/website/public/widgets/dist/feedback.js @@ -0,0 +1 @@ +(function(){"use strict";const P="#wrapper{position:fixed;z-index:1000;width:350px;height:400px;background:#fff;border-radius:12px;box-shadow:0 8px 32px #0000001f;display:flex;flex-direction:column}#header{background:#000091;color:#fff;display:flex;align-items:center;justify-content:space-between;border-radius:12px 12px 0 0}h6{padding:16px;font-weight:600;font-size:16px;margin:0}#close{background:none;border:none;color:#fff;cursor:pointer;font-size:18px;margin-right:12px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;border-radius:8px}#content{display:flex;flex:1;flex-direction:column}form{flex:1;padding:16px;display:flex;flex-direction:column;gap:12px}#email{border:1px solid #e0e0e0;border-radius:8px;padding:12px;font-family:inherit;font-size:14px}#feedback-text{flex:1;border:1px solid #e0e0e0;border-radius:8px;padding:12px;font-family:inherit;font-size:14px;resize:none}#submit{background:#000091;color:#fff;border:2px solid #000091;border-radius:8px;padding:12px;font-weight:600;cursor:pointer;transition:background .2s}#submit:hover,#close:hover{background:#1212ff}#feedback-text:focus,#email:focus,#submit:focus{outline:2px solid #0a76f6;border-color:#fff}#close:focus{outline:2px solid white}#status{font-size:14px;font-weight:500;text-align:center}#success{display:none;flex:1;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center}#success i{background:#27a658;color:#fff;width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;margin-bottom:20px;font-style:normal}#success p{color:#0f4e27;margin:0 0 30px;font-size:16px;font-weight:500}#error{color:#dc3545;margin-bottom:12px}#error:empty{margin:0}@media (max-width: 420px){#wrapper{width:100%;right:0!important;border-radius:0}}";function j(e,t,o){const n=`lasuite-widget-${e}-shadow`,s=document.getElementById(n);s&&s.remove();const i=document.createElement("div");i.id=n;const c=i.attachShadow({mode:"open"}),d=document.createElement("style");d.textContent=o;const f=document.createElement("div");return f.innerHTML=t,c.appendChild(d),c.appendChild(f),i}const T="lasuite-widget",u=(e,t,o,n)=>document.dispatchEvent(new CustomEvent(`${T}-${e}-${t}`,o?{detail:o}:void 0)),_=(e,t,o,n,s)=>{const i=d=>s(d.detail),c=`${T}-${e}-${t}`;return document.addEventListener(c,i,void 0),()=>document.removeEventListener(c,i,void 0)},b=2,L=e=>window._lasuite_widget?._loaded?.[e],F=(e,t)=>{window._lasuite_widget?._loaded&&(window._lasuite_widget._loaded[e]=t)},I=e=>{window._lasuite_widget||(window._lasuite_widget=[]);const t=window._lasuite_widget;if(t._loaded||(t._loaded={}),L(e)!==b){t.push=(...o)=>{for(const n of o)L(n[0])===b?u(n[0],n[1],n[2]):t[t.length]=n;return t.length},F(e,b);for(const o of t.splice(0,t.length))t.push(o)}u(e,"loaded")},q=e=>e.offsetWidth>0&&e.offsetHeight>0,O=(e,t,o)=>{const n=s=>{if(s.key!=="Tab")return;const i=Array.from(t.querySelectorAll(o)).filter(f=>q(f));if(i.length===0)return;const c=i[0],d=i[i.length-1];s.shiftKey&&e.activeElement===c?(s.preventDefault(),d.focus()):!s.shiftKey&&e.activeElement===d&&(s.preventDefault(),c.focus())};return t.addEventListener("keydown",n),()=>{t.removeEventListener("keydown",n)}},U=e=>{const t=o=>{o.key==="Escape"&&(o.preventDefault(),e())};return document.addEventListener("keydown",t),()=>{document.removeEventListener("keydown",t)}},a="feedback";_(a,"init",null,!1,async e=>{if(!e.api||!e.channel){console.error("Feedback widget requires an API URL and a channel ID");return}let t;try{const l=await(await fetch(`${e.api}config/`,{headers:{"X-Channel-ID":e.channel}})).json();if(!l.success)throw new Error(l.detail||"Unknown error");if(l.captcha)throw new Error("Captcha is not supported yet");t=l.config}catch(h){console.error("Error fetching config",h),u(a,"closed");return}const o=e.title||t?.title||"Feedback",n=e.placeholder||t?.placeholder||"Share your feedback...",s=e.emailPlaceholder||t?.emailPlaceholder||"Your email...",i=e.submitText||t?.submitText||"Send Feedback",c=e.successText||t?.successText||"Thank you for your feedback!",d=e.successText2||t?.successText2||"",f=e.closeLabel||t?.closeLabel||"Close the feedback widget",D=j(a,'

',P),p=D.shadowRoot,r=p.querySelector.bind(p),g=r("#wrapper"),W=r("#title"),B=r("#submit"),m=r("#feedback-text"),v=r("#error"),S=r("#close"),x=r("#email"),$=r("form"),K=r("#success"),M=r("#success-text"),H=r("#success-text2");g.style.bottom=20+(e.bottomOffset||0)+"px",g.style.right=20+(e.rightOffset||0)+"px",W.textContent=o,m.placeholder=n,x.placeholder=s,B.textContent=i,S.setAttribute("aria-label",f),e.email&&x.remove(),$.addEventListener("submit",async h=>{h.preventDefault(),v.textContent="";const l=m.value.trim(),A=e.email||x.value.trim();try{if(!l)throw m.focus(),new Error("Missing value");if(!A)throw x.focus(),new Error("Missing value");const w=await fetch(t?.submitUrl||`${e.api}deliver/`,{method:"POST",headers:{"Content-Type":"application/json","X-Channel-ID":e.channel},body:JSON.stringify({textBody:l,email:A})});let C;try{C=await w.json()}catch{throw new Error("Invalid response from server")}if(!C.success)throw new Error(C.detail||"Unknown error");$.remove(),K.style.display="flex",requestAnimationFrame(()=>{M.textContent=c,H.textContent=d})}catch(w){v.style.display="block",v.textContent="⚠ "+(w instanceof Error?w.message:"Unknown error")}});let y=null,E=null,k=()=>{};const z=()=>{p.host.remove(),y&&y(),E&&E(),k&&k(),u(a,"closed")};S.addEventListener("click",z),k=_(a,"close",null,!1,z),document.body.appendChild(D),m.focus(),y=O(p,g,"textarea,input,button"),E=U(()=>{u(a,"close")}),u(a,"opened")}),I(a)})(); diff --git a/website/public/widgets/dist/lagaufre.js b/website/public/widgets/dist/lagaufre.js new file mode 100644 index 0000000..c5c325d --- /dev/null +++ b/website/public/widgets/dist/lagaufre.js @@ -0,0 +1 @@ +(function(){"use strict";const F="#wrapper{display:flex;flex-direction:column;font-family:inherit}.wrapper-dialog{position:fixed;top:60px;right:60px;z-index:1000;width:340px;max-height:480px;background:#fff;border-radius:.25rem;box-shadow:0 0 6px #0000911a;border:1px solid #e5e5e5;overflow:hidden}#header{display:flex;justify-content:center;align-items:center;padding:4px 16px;background:transparent;border-bottom:1px solid #dfe2ea;min-height:48px;position:relative}#header-logo{height:25px;width:auto;object-fit:contain;display:block;margin:0 auto}#close{position:absolute;right:16px}#close{background:none;border:none;color:#64748b;cursor:pointer;font-size:36px;width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background-color .2s}#close:hover{background-color:#f0f0f0}#close:focus{outline:2px solid #0a76f6}#footer{display:flex;padding:16px;background:transparent;border-top:1px solid #dfe2ea;justify-content:flex-end}#ok-button{background:#3e5de7;color:#fff;border:none;border-radius:6px;padding:8px 16px;font-size:14px;font-weight:600;cursor:pointer;transition:background-color .2s;min-width:60px}#ok-button:hover{background:#1d4ed8}#ok-button:focus{outline:2px solid #0a76f6}#content{flex:1;padding:16px;display:flex;flex-direction:column;overflow-y:auto;min-height:0}#loading{display:flex;align-items:center;justify-content:center;padding:40px 20px;color:#666;font-size:14px}#error{display:flex;align-items:center;justify-content:center;padding:40px 20px;color:#dc3545;font-size:14px;text-align:center}#services-grid{grid-template-columns:repeat(3,1fr);gap:4px;justify-items:center;list-style:none;padding:0;margin:0}.service-card{background:transparent;text-align:center;transition:all .2s ease;position:relative;display:flex;flex-direction:column;align-items:center;width:100%;list-style:none;padding:0;margin:0}.service-card:hover{background-color:#eef1f4;border-radius:6px}.service-card a{position:relative;padding:8px;width:100%;text-decoration:none;display:block}.service-logo{width:42px;height:42px;object-fit:contain}.maturity-badge{position:absolute;top:40px;left:50%;transform:translate(-50%);background:#eef1f4;color:#2845c1;border-radius:12px;padding:2px 4px;font-size:9px;line-height:9px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;white-space:nowrap}.service-info{display:flex;flex-direction:column;align-items:center;width:100%;padding-top:4px}.service-name{font-weight:600;font-size:14px;color:#1e40af;margin-bottom:2px;line-height:1.2;text-align:center}@media (max-width: 480px){#wrapper{width:100%;inset:0!important;border-radius:0;max-height:100vh;position:fixed!important}#header{height:40px;display:flex}#header-logo{height:35px}#footer{display:flex}#services-grid{grid-template-columns:repeat(3,1fr);gap:12px}.service-card{width:70px;padding:6px}.service-logo,.service-logo-placeholder{width:40px;height:40px}.service-name{font-size:11px}}#content::-webkit-scrollbar{width:4px}#content::-webkit-scrollbar-track{background:transparent}#content::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:2px}#content::-webkit-scrollbar-thumb:hover{background:#94a3b8}";function D(e,t,n){const s=`lasuite-widget-${e}-shadow`,d=document.getElementById(s);d&&d.remove();const l=document.createElement("div");l.id=s;const r=l.attachShadow({mode:"open"}),p=document.createElement("style");p.textContent=n;const u=document.createElement("div");return u.innerHTML=t,r.appendChild(p),r.appendChild(u),l}const C="lasuite-widget",c=(e,t,n,s)=>document.dispatchEvent(new CustomEvent(`${C}-${e}-${t}`,n?{detail:n}:void 0)),m=(e,t,n,s,d)=>{const l=p=>d(p.detail),r=e?`${C}-${e}-${t}`:t;return(n||document).addEventListener(r,l,s?{once:!0}:void 0),()=>(n||document).removeEventListener(r,l,s?{once:!0}:void 0)},v=2,$=e=>window._lasuite_widget?._loaded?.[e],W=(e,t)=>{window._lasuite_widget?._loaded&&(window._lasuite_widget._loaded[e]=t)},B=e=>{window._lasuite_widget||(window._lasuite_widget=[]);const t=window._lasuite_widget;if(t._loaded||(t._loaded={}),$(e)!==v){t.push=(...n)=>{for(const s of n)$(s[0])===v?c(s[0],s[1],s[2]):t[t.length]=s;return t.length},W(e,v);for(const n of t.splice(0,t.length))t.push(n)}c(e,"loaded")},H=e=>e.offsetWidth>0&&e.offsetHeight>0,K=(e,t,n)=>{const s=d=>{if(d.key!=="Tab")return;const l=Array.from(t.querySelectorAll(n)).filter(u=>H(u));if(l.length===0)return;const r=l[0],p=l[l.length-1];d.shiftKey&&e.activeElement===r?(d.preventDefault(),p.focus()):!d.shiftKey&&e.activeElement===p&&(d.preventDefault(),r.focus())};return t.addEventListener("keydown",s),()=>{t.removeEventListener("keydown",s)}},M=e=>{const t=n=>{n.key==="Escape"&&(n.preventDefault(),e())};return document.addEventListener("keydown",t),()=>{document.removeEventListener("keydown",t)}},i="lagaufre";let w=!1;m(i,"init",null,!1,async e=>{if(!e.api&&!e.data){console.error("Missing API URL");return}w&&(c(i,"destroy"),await new Promise(o=>setTimeout(o,10)));const t=[];let n=!1;const s='",d=D(i,s,F),l=d.shadowRoot,r=l.querySelector("#wrapper"),p=l.querySelector("#loading"),u=l.querySelector("#services-grid"),k=l.querySelector("#error"),x=l.querySelector("#close"),q=l.querySelector("#ok-button"),z=l.querySelector("#header-logo"),E=o=>{const a=["top","bottom","left","right"],f=h=>{a.forEach(b=>{r.style[b]=typeof h[b]=="number"?`${h[b]}px`:"unset"})};if(a.every(h=>o[h]===void 0)||f(o),o.position)if(typeof o.position=="function"){const h=o.position();r.style.position=h.position,f(h)}else r.style.position=o.position;o.fontFamily&&(r.style.fontFamily=o.fontFamily),o.background&&(r.style.background=o.background);const g=o.label||"Services",y=o.closeLabel||"Close";p.textContent=o.loadingText||"Loading…",r.setAttribute("aria-label",g),x&&x.setAttribute("aria-label",y),z&&(z.alt=(o.headerLabel||"About LaSuite")+(o.newWindowLabelSuffix||""))};E(e),t.push(m("","resize",window,!1,()=>{E(e)})),r.style.display="none";const L=o=>{p.style.display="none",u.style.display="none",k.style.display="block",k.textContent=o},T=o=>{u.innerHTML="",o.services.forEach(a=>{if(!a.logo)return;a.maturity=="stable"&&delete a.maturity;const f=document.createElement("li");f.className="service-card",f.innerHTML=`
`;const g=f.querySelector("a"),y=f.querySelector("img"),h=f.querySelector(".service-name");if(a.maturity){const b=document.createElement("div");b.className="maturity-badge",b.textContent=a.maturity,g.insertBefore(b,y.nextSibling)}g.setAttribute("aria-label",a.name+(a.maturity?` (${a.maturity})`:"")+(e.newWindowLabelSuffix||"")),g.href=a.url,y.src=a.logo,h.textContent=a.name,u.appendChild(f)}),p.style.display="none",k.style.display="none",u.style.display="grid"};if(e.data)T(e.data);else try{const a=await(await fetch(e.api,{method:"GET"})).json();a.error?L(`Error: ${JSON.stringify(a.error)}`):a.services&&a.services.length>0?T(a):L("No services found")}catch(o){L(`Failed to load services: ${o instanceof Error?o.message:"Unknown error"}`)}const j=o=>{e.dialogElement||d.contains(o.target)||c(i,"close")};let S=null,_=null;t.push(m(i,"open",null,!1,()=>{r.style.display="block",setTimeout(()=>{n=!0,document.addEventListener("click",j),r.focus()},200),S=K(l,r,"a,button"),e.dialogElement||(_=M(()=>{c(i,"close")})),e.buttonElement&&e.buttonElement.setAttribute("aria-expanded","true"),c(i,"opened")})),t.push(m(i,"close",null,!1,()=>{S&&S(),_&&_(),n&&(r.style.display="none",n=!1,e.buttonElement&&(e.buttonElement.focus(),e.buttonElement.setAttribute("aria-expanded","false")),document.removeEventListener("click",j),c(i,"closed"))})),t.push(m(i,"toggle",null,!1,()=>{n?c(i,"close"):c(i,"open")})),t.push(m(i,"configure",null,!1,E)),q&&q.addEventListener("click",()=>{c(i,"close")}),x&&x.addEventListener("click",()=>{c(i,"close")}),e.buttonElement&&t.push(m("","click",e.buttonElement,!1,()=>{c(i,"toggle")})),e.dialogElement?e.dialogElement.appendChild(d):(r.className="wrapper-dialog",document.body.appendChild(d)),m(i,"destroy",null,!0,()=>{c(i,"close"),w=!1,d.remove(),t.forEach(o=>o())}),w=!0,c(i,"initialized"),e.open&&c(i,"open")}),B(i)})(); diff --git a/website/public/widgets/dist/loader.js b/website/public/widgets/dist/loader.js new file mode 100644 index 0000000..0eccae1 --- /dev/null +++ b/website/public/widgets/dist/loader.js @@ -0,0 +1 @@ +(function(){"use strict";const m='div{position:fixed;bottom:20px;right:20px;z-index:1000}button{width:60px;height:60px;border-radius:50%;background:#000091;border:none;cursor:pointer;box-shadow:0 4px 12px #0000914d;transition:transform .2s ease,box-shadow .2s ease,background-color .2s ease;display:flex;align-items:center;justify-content:center;padding:0;margin:0}svg{width:35px;height:35px}button:focus-visible{outline:3px solid #0a76f6;outline-offset:2px}button:hover{background:#1212ff;transform:scale(1.1);box-shadow:0 6px 16px #1212ff66}button:active{transform:scale(.95)}button.loading{background:#2323ff}button.loading:after{content:"";width:25px;height:25px;border:3px solid transparent;border-top:3px solid white;border-radius:50%;animation:spin 1s linear infinite}button.loading svg{display:none}button.opened svg{display:none}button.opened:after{content:"+";font-size:50px;color:#fff;height:60px;line-height:60px;font-family:Arial;transform:rotate(45deg)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media (max-width: 420px){button{width:40px;height:40px}svg{width:25px;height:25px}div{right:15px;bottom:15px}button.opened:after{font-size:40px}}';function x(e,t,n){const i=`lasuite-widget-${e}-shadow`,a=document.getElementById(i);a&&a.remove();const o=document.createElement("div");o.id=i;const s=o.attachShadow({mode:"open"}),d=document.createElement("style");d.textContent=n;const u=document.createElement("div");return u.innerHTML=t,s.appendChild(d),s.appendChild(u),o}const v='',f="lasuite-widget",p=(e,t,n,i)=>document.dispatchEvent(new CustomEvent(`${f}-${e}-${t}`,n?{detail:n}:void 0)),r=(e,t,n,i,a)=>{const o=d=>a(d.detail),s=e?`${f}-${e}-${t}`:t;return document.addEventListener(s,o,i?{once:!0}:void 0),()=>document.removeEventListener(s,o,i?{once:!0}:void 0)},C=1,l=2,c=e=>window._lasuite_widget?._loaded?.[e],h=(e,t)=>{window._lasuite_widget?._loaded&&(window._lasuite_widget._loaded[e]=t)},_=e=>{window._lasuite_widget||(window._lasuite_widget=[]);const t=window._lasuite_widget;if(t._loaded||(t._loaded={}),c(e)!==l){t.push=(...n)=>{for(const i of n)c(i[0])===l?p(i[0],i[1],i[2]):t[t.length]=i;return t.length},h(e,l);for(const n of t.splice(0,t.length))t.push(n)}p(e,"loaded")},E=(e,t="")=>{const n=document.createElement("script");n.src=e,n.type=t,n.defer=!0,document.body.appendChild(n)},g="loader";r(g,"init",null,!1,e=>{const t=e.widget||"feedback",n=`
`,i=x(g,n,m),o=i.shadowRoot.querySelector("button"),s=()=>{o.setAttribute("aria-label",String(e.closeLabel||"Close widget")),o.setAttribute("aria-expanded","true")},d=()=>{o.setAttribute("aria-label",String(e.label||"Load widget")),o.setAttribute("aria-expanded","false")};d(),r(t,"closed",null,!1,()=>{o.classList.remove("opened"),d()}),r(t,"opened",null,!1,()=>{o.classList.add("opened"),s()}),o.addEventListener("click",()=>{if(o.classList.contains("opened")){p(t,"close");return}const u=setTimeout(()=>{o.classList.remove("loading")},1e4);o.classList.add("loading");const w=()=>{clearTimeout(u),o.classList.remove("loading");const b=Object.assign({},e.params);b.bottomOffset=o.offsetHeight+20,window._lasuite_widget.push([t,"init",b])};c(t)===l?w():(r(t,"loaded",null,!0,w),c(t)||(E(e.script,e.scriptType||""),h(t,C)))}),document.body.appendChild(i)}),_(g)})(); diff --git a/website/src/content/docs/guides/feedback.mdx b/website/src/content/docs/guides/feedback.mdx new file mode 100644 index 0000000..12acfc2 --- /dev/null +++ b/website/src/content/docs/guides/feedback.mdx @@ -0,0 +1,64 @@ +--- +title: Retours utilisateurs +sidebar: + order: 50 +--- + +
+![](./feedback.png) +
+ +Ce widget permet aux visiteurs de votre service d'envoyer des questions ou des retours à votre équipe. + +Ces retours sont ensuite consultables dans une instance de [Messages](https://github.com/suitenumerique/messages), +qui permet d'y répondre collaborativement par email. + + +## Intégration + +Il y a deux façons principales d'intégrer ce widget dans votre service: + +### 1. Via le composant React + +Ce composant sera bientôt disponible dans le [UI Kit](https://github.com/suitenumerique/ui-kit). + +En attendant, vous pouvez : +* soit [copier ce composant](https://github.com/suitenumerique/messages/blob/main/src/frontend/src/features/ui/components/feedback-widget/index.tsx) +si vous souhaitez intégrer la pop-in de retour avec son bouton de chargement qui s'affiche en bas à droite des pages, +* soit [copier ce composant](https://github.com/suitenumerique/messages/blob/main/src/frontend/src/features/ui/components/feedback-button/index.tsx) +si vous souhaitez juste ouvrir la pop-in de retour dynamiquement, avec un bouton existant de votre application par exemple. + +### 2. Via le script JavaScript + +Cette option est à privilégier si vous n'utilisez pas React ou si vous ne souhaitez pas intégrer l'UI Kit dans votre application. + +Voici comment l'intégrer avec le bouton de chargement : + +```html + + +``` + +D'autres exemples d'intégration sont disponibles ci-dessous. + +## Exemples + +Nous avons une documentation interactive avec plusieurs exemples de configuration pour : + * [version avec bouton de chargement](/widgets-demo/loader). + * [version sans bouton de chargement](/widgets-demo/feedback). + +Vous pouvez aussi tester La Gaufre v2 en ligne sur ces services : + +* [Suite territoriale - site vitrine](https://suiteterritoriale.anct.gouv.fr/) +* [Suite territoriale - Messages](https://messages.suite.anct.gouv.fr) diff --git a/website/src/content/docs/guides/feedback.png b/website/src/content/docs/guides/feedback.png new file mode 100644 index 0000000..3ba0954 Binary files /dev/null and b/website/src/content/docs/guides/feedback.png differ diff --git a/website/src/content/docs/guides/gaufre-v2-anct.png b/website/src/content/docs/guides/gaufre-v2-anct.png new file mode 100644 index 0000000..d9e47f9 Binary files /dev/null and b/website/src/content/docs/guides/gaufre-v2-anct.png differ diff --git a/website/src/content/docs/guides/gaufre-v2-dinum.png b/website/src/content/docs/guides/gaufre-v2-dinum.png new file mode 100644 index 0000000..e640069 Binary files /dev/null and b/website/src/content/docs/guides/gaufre-v2-dinum.png differ diff --git a/website/src/content/docs/guides/gaufre-v2.mdx b/website/src/content/docs/guides/gaufre-v2.mdx new file mode 100644 index 0000000..d7adc9b --- /dev/null +++ b/website/src/content/docs/guides/gaufre-v2.mdx @@ -0,0 +1,87 @@ +--- +title: La Gaufre v2 +sidebar: + order: 40 +--- + + +
+![](./gaufre-v2-dinum.png) +
+ +La Gaufre v2 est une évolution de la [première version](/guides/gaufre) historique, qui ajoute plusieurs fonctionnalités : +* Un design en accord avec le [UI Kit](https://github.com/suitenumerique/ui-kit) de LaSuite. +* Un chargement des services à afficher en JSON, qui peut être effectué via une API dynamique ou fichier statique. +* L'utilisation du "Shadow DOM" qui permet aux styles du widget d'être indépendants de ceux de la page + +En configurant la Gaufre v2, il est possible de changer son contenu ou même son apparence. + +Voici par exemple une Gaufre pour la Suite territoriale : + +
+![](./gaufre-v2-anct.png) +
+ +## Intégration + +Il y a deux façons principales d'intégrer La Gaufre v2 dans votre service: + +### 1. Via le composant React + +Ce composant, qui intègre le bouton "Gaufre" ainsi que la pop-in, sera bientôt disponible dans le [UI Kit](https://github.com/suitenumerique/ui-kit). + +En attendant, vous pouvez [copier ce composant](https://github.com/suitenumerique/messages/blob/main/src/frontend/src/features/ui/components/lagaufre/index.tsx). + +### 2. Via le script JavaScript + +Cette option est à privilégier si vous n'utilisez pas React ou si vous ne souhaitez pas intégrer l'UI Kit dans votre application. + +Dans ce cas, vous devez intégrer le bouton "Gaufre". Le script gérera la pop-in et s'occupera de mettre à jour les attributs nécessaires sur le bouton lors des interactions. + +Voici un exemple complet d'intégration dans ce cas : + +```html + + + + + +``` + +Vous pouvez tester cet exemple sur cette [page dédiée](/widgets-demo/lagaufre-single). + +Nous avons une documentation interactive avec plusieurs [autres exemples de configuration](/widgets-demo/lagaufre). + +## Intégrations existantes + +Vous pouvez tester La Gaufre v2 en ligne sur ces services : + +* [Suite territoriale - Messages](https://messages.suite.anct.gouv.fr) diff --git a/website/src/pages/widgets-demo/feedback.html b/website/src/pages/widgets-demo/feedback.html new file mode 100644 index 0000000..812e44c --- /dev/null +++ b/website/src/pages/widgets-demo/feedback.html @@ -0,0 +1,98 @@ + + + + + + Feedback Widget Demo + + + + Back to index +

Feedback Widget Demo

+ + + + + + + + +

+ + + + + +

+ + + + + \ No newline at end of file diff --git a/website/src/pages/widgets-demo/lagaufre-single.html b/website/src/pages/widgets-demo/lagaufre-single.html new file mode 100644 index 0000000..5817348 --- /dev/null +++ b/website/src/pages/widgets-demo/lagaufre-single.html @@ -0,0 +1,89 @@ + + + + + + La Gaufre Widget Demo + + + +
+ +

+ Back to index +

+ + + + + + + + + + +

La Gaufre - Single Page Demo

+

+ Try opening the Gaufre by clicking on the button on the top right! You can also use keyboard navigation. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +

+ + + +
diff --git a/website/src/pages/widgets-demo/lagaufre.html b/website/src/pages/widgets-demo/lagaufre.html new file mode 100644 index 0000000..53b8887 --- /dev/null +++ b/website/src/pages/widgets-demo/lagaufre.html @@ -0,0 +1,297 @@ + + + + + + La Gaufre Widget Demo + + + +
+ Back to index +

La Gaufre Widget Demo

+

This demo shows how to use the La Gaufre widget to display a list of services with subscription status.

+ +
+

Basic Usage

+

Open the widget with default settings:

+ +
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json',
+    open: true
+}]);
+
+ +
+

With ANCT Data

+

Open the widget with ANCT services data:

+ +
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: 'https://operateurs.suite.anct.gouv.fr/api/v1.0/lagaufre/services/?operator=9f5624fc-ef99-4d10-ae3f-403a81eb16ef&siret=21870030000013',
+    open: true
+}]);
+
+ +
+

With LaSuite data and style

+

Open the widget with gradient background:

+ +
+// With gradient background
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: 'https://lasuite.numerique.gouv.fr/api/services',
+    open: true,
+    background: 'linear-gradient(180deg, #eceffd 0%, #FFFFFF 20%)',
+    headerLogo: 'https://lasuite.numerique.gouv.fr/_next/static/media/suite-numerique.ebdb6ce9.svg',
+    headerUrl: 'https://lasuite.numerique.gouv.fr',
+    showFooter: true
+}]);
+
+ +
+

With Custom Font

+

Open the widget with custom font family:

+ +
+// With custom font
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json',
+    open: true,
+    fontFamily: 'Georgia, serif'
+}]);
+
+ +
+

Positioning Options

+

Control widget position and behavior:

+ + +
+// Top Right positioning
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json',
+    open: true,
+    label: 'La Gaufre - Top Right',
+    position: 'fixed',
+    top: 20,
+    right: 20
+}]);
+ + +
+// Bottom Left positioning
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json',
+    open: true,
+    label: 'La Gaufre - Bottom Left',
+    position: 'fixed',
+    bottom: 20,
+    left: 20
+}]);
+ + +
+// Absolute & dynamic positioning, tied to a button
+(button) => {
+    _lasuite_widget.push(['lagaufre', 'init', {
+        api: '/widgets/demo/lagaufre-data.json',
+        open: true,
+        label: 'La Gaufre - Bottom Left',
+        position: () => {
+            return {
+                position: 'absolute',
+                top: button.offsetTop + button.offsetHeight + 10,
+                right: window.innerWidth - button.offsetLeft - button.offsetWidth
+            };
+        },
+        buttonElement: button
+    }]);
+}
+ +
+ +
+

No dialog

+

You can insert the widget into an existing container:

+
+ + +
+// No dialog
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json',
+    open: true,
+    dialogElement: document.getElementById('custom-box')
+}]);
+ + +
+_lasuite_widget.push(['lagaufre', 'close']);
+
+ +
+

With hardcoded data

+

You can pass static data and avoid any API calls:

+ +
+_lasuite_widget.push(['lagaufre', 'init', {
+    open: true,
+    data: {
+        "services": [
+            {
+                "name": "Authentication Service",
+                "url": "https://example.com/auth",
+                "logo": "/widgets/demo/logos/auth.svg",
+            },
+            {
+                "name": "Document Portal",
+                "url": "https://example.com/docs",
+                "logo": "/widgets/demo/logos/docs.svg",
+            },
+        ]
+    }
+}]);
+
+ +
+

Open and close Widget Programmatically

+

Close the widget programmatically after 2 seconds:

+ +
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json'
+}]);
+setTimeout(() => {
+    _lasuite_widget.push(['lagaufre', 'open']);
+}, 300);
+setTimeout(() => {
+    _lasuite_widget.push(['lagaufre', 'close']);
+}, 2000);
+ + +
+_lasuite_widget.push(['lagaufre', 'toggle']);
+ +
+ +
+

Full localization

+

You can fully localize the widget:

+ +
+// Full localization
+_lasuite_widget.push(['lagaufre', 'init', {
+    api: '/widgets/demo/lagaufre-data.json',
+    open: true,
+    label: 'La Gaufre',
+    headerLogo: 'https://lasuite.numerique.gouv.fr/_next/static/media/suite-numerique.ebdb6ce9.svg',
+    headerUrl: 'https://lasuite.numerique.gouv.fr',
+    headerLabel: 'A propos de LaSuite',
+    closeLabel: 'Fermer la liste des services',
+    loadingText: 'Chargement des services...',
+    newWindowLabelSuffix: ' (nouvelle fenêtre)'
+}]);
+
+ +
+

API Response Format

+

The widget expects the following API response format:

+
+{
+  "services": [
+    {
+      "name": "Service Name",
+      "url": "https://service-url.com",
+      "maturity": "stable",
+      "logo": "/path/to.svg"
+    }
+  ]
+}
+
+
+ + + + + + + diff --git a/website/src/pages/widgets-demo/loader.html b/website/src/pages/widgets-demo/loader.html new file mode 100644 index 0000000..f974ff6 --- /dev/null +++ b/website/src/pages/widgets-demo/loader.html @@ -0,0 +1,69 @@ + + + + + + Loader Widget Demo + + + + Back to index +

Loader Widget Demo

+ + + + + + + + + + \ No newline at end of file