From 73aef17f76082bb2f821392104cb9d0f5ed5cab9 Mon Sep 17 00:00:00 2001 From: Stefan Popov Date: Sat, 9 May 2026 21:23:02 +0200 Subject: [PATCH 01/24] feat(angular): Draft first version of angular package --- README.md | 37 +- eslint.config.js | 1 + packages/phone-mask-angular/CHANGELOG.md | 5 + packages/phone-mask-angular/README.md | 158 ++++ .../phone-mask-angular/core/ng-package.json | 6 + .../phone-mask-angular/core/src/public-api.ts | 2 + packages/phone-mask-angular/ng-package.json | 8 + packages/phone-mask-angular/package.json | 86 ++ packages/phone-mask-angular/src/config.ts | 16 + packages/phone-mask-angular/src/core.ts | 2 + .../src/internal/boolean-input.ts | 6 + .../src/internal/formatting.ts | 71 ++ .../phone-input/phone-input.component.html | 184 ++++ .../phone-input/phone-input.component.scss | 450 ++++++++++ .../src/phone-input/phone-input.component.ts | 597 +++++++++++++ .../src/phone-mask.directive.ts | 291 ++++++ .../phone-mask-angular/src/phone-mask.pipe.ts | 31 + .../src/phone-mask.service.ts | 97 ++ packages/phone-mask-angular/src/public-api.ts | 30 + packages/phone-mask-angular/src/types.ts | 77 ++ .../tests/unit/service-pipe.test.ts | 39 + packages/phone-mask-angular/tsconfig.json | 26 + packages/phone-mask-angular/vitest.config.ts | 9 + pnpm-lock.yaml | 829 +++++++++++++++++- tsconfig.json | 3 +- 25 files changed, 3045 insertions(+), 16 deletions(-) create mode 100644 packages/phone-mask-angular/CHANGELOG.md create mode 100644 packages/phone-mask-angular/README.md create mode 100644 packages/phone-mask-angular/core/ng-package.json create mode 100644 packages/phone-mask-angular/core/src/public-api.ts create mode 100644 packages/phone-mask-angular/ng-package.json create mode 100644 packages/phone-mask-angular/package.json create mode 100644 packages/phone-mask-angular/src/config.ts create mode 100644 packages/phone-mask-angular/src/core.ts create mode 100644 packages/phone-mask-angular/src/internal/boolean-input.ts create mode 100644 packages/phone-mask-angular/src/internal/formatting.ts create mode 100644 packages/phone-mask-angular/src/phone-input/phone-input.component.html create mode 100644 packages/phone-mask-angular/src/phone-input/phone-input.component.scss create mode 100644 packages/phone-mask-angular/src/phone-input/phone-input.component.ts create mode 100644 packages/phone-mask-angular/src/phone-mask.directive.ts create mode 100644 packages/phone-mask-angular/src/phone-mask.pipe.ts create mode 100644 packages/phone-mask-angular/src/phone-mask.service.ts create mode 100644 packages/phone-mask-angular/src/public-api.ts create mode 100644 packages/phone-mask-angular/src/types.ts create mode 100644 packages/phone-mask-angular/tests/unit/service-pipe.test.ts create mode 100644 packages/phone-mask-angular/tsconfig.json create mode 100644 packages/phone-mask-angular/vitest.config.ts diff --git a/README.md b/README.md index f4772c2a..4174a707 100644 --- a/README.md +++ b/README.md @@ -108,19 +108,21 @@ Ready-made plugins for your stack: - ✅ **Nuxt** — Auto-imported, SSR-compatible - ✅ **React** — Component & hook with modern React patterns - ✅ **Svelte** — Component, composable, action, and attachment for Svelte 5 +- ✅ **Angular** — Standalone component, directive, pipe, and service - ✅ **TypeScript/Vanilla JS** — Framework-agnostic core --- ## 📦 Packages -| Package | Version | Description | -| ----------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------- | -| [@desource/phone-mask](./packages/phone-mask) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask?color=blue&logo=typescript) | Core library — TypeScript/JS | -| [@desource/phone-mask-react](./packages/phone-mask-react) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-react?color=blue&logo=react) | React component + hook | -| [@desource/phone-mask-vue](./packages/phone-mask-vue) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-vue?color=blue&logo=vuedotjs) | Vue 3 component + composable + directive | -| [@desource/phone-mask-svelte](./packages/phone-mask-svelte) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-svelte?color=blue&logo=svelte) | Svelte 5 component + composable + action + attachment | -| [@desource/phone-mask-nuxt](./packages/phone-mask-nuxt) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-nuxt?color=blue&logo=nuxt) | Nuxt module | +| Package | Version | Description | +| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| [@desource/phone-mask](./packages/phone-mask) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask?color=blue&logo=typescript) | Core library — TypeScript/JS | +| [@desource/phone-mask-react](./packages/phone-mask-react) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-react?color=blue&logo=react) | React component + hook | +| [@desource/phone-mask-vue](./packages/phone-mask-vue) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-vue?color=blue&logo=vuedotjs) | Vue 3 component + composable + directive | +| [@desource/phone-mask-svelte](./packages/phone-mask-svelte) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-svelte?color=blue&logo=svelte) | Svelte 5 component + composable + action + attachment | +| [@desource/phone-mask-angular](./packages/phone-mask-angular) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-angular?color=blue&logo=angular) | Angular component + directive + pipe + service | +| [@desource/phone-mask-nuxt](./packages/phone-mask-nuxt) | ![npm](https://img.shields.io/npm/v/@desource/phone-mask-nuxt?color=blue&logo=nuxt) | Nuxt module | --- @@ -181,6 +183,27 @@ npm install @desource/phone-mask-svelte ``` +### Angular + +```bash +npm install @desource/phone-mask-angular +``` + +```ts +import { Component, signal } from '@angular/core'; +import { PhoneInputComponent } from '@desource/phone-mask-angular'; + +@Component({ + selector: 'app-phone', + standalone: true, + imports: [PhoneInputComponent], + template: `` +}) +export class PhoneComponent { + readonly phone = signal(''); +} +``` + ### Nuxt ```bash diff --git a/eslint.config.js b/eslint.config.js index 1e717ede..7c28d507 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -31,6 +31,7 @@ const BROWSER_FILES = [ 'packages/phone-mask-react/**/*.{ts,tsx,js,jsx}', 'packages/phone-mask-vue/**/*.{ts,js,mts,cts,vue}', 'packages/phone-mask-svelte/**/*.{ts,js,mts,cts,svelte}', + 'packages/phone-mask-angular/**/*.{ts,js,mts,cts}', 'demo/**/*.{ts,tsx,js,jsx,vue}' ]; diff --git a/packages/phone-mask-angular/CHANGELOG.md b/packages/phone-mask-angular/CHANGELOG.md new file mode 100644 index 00000000..93c3322a --- /dev/null +++ b/packages/phone-mask-angular/CHANGELOG.md @@ -0,0 +1,5 @@ +# @desource/phone-mask-angular + +## 1.4.0 + +- Initial Angular package with standalone component, directive, pipe, service, and provider APIs. diff --git a/packages/phone-mask-angular/README.md b/packages/phone-mask-angular/README.md new file mode 100644 index 00000000..a802ed69 --- /dev/null +++ b/packages/phone-mask-angular/README.md @@ -0,0 +1,158 @@ +# @desource/phone-mask-angular + +> Angular phone input component, directive, pipe, and service with smart masking and Google libphonenumber data + +[![npm version](https://img.shields.io/npm/v/@desource/phone-mask-angular?color=blue&logo=angular)](https://www.npmjs.com/package/@desource/phone-mask-angular) +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/DeSource-Labs/phone-mask/blob/main/LICENSE) + +Beautiful, accessible, tree-shakeable Angular phone input with auto-formatting, country selector, validation, forms support, and signal-first APIs. + +## Features + +- Standalone Angular component, directive, pipe, and service +- Signal inputs and `model()` two-way value binding +- Works with Angular forms through `ControlValueAccessor` +- Smart country search with keyboard navigation +- As-you-type formatting with stable caret handling +- Optional GeoIP and locale country detection +- APF package output with partial compilation +- Core mask data and kit utilities re-exported from `@desource/phone-mask-angular/core` + +## Installation + +```bash +npm install @desource/phone-mask-angular +# or +pnpm add @desource/phone-mask-angular +``` + +## Quick Start + +```ts +import { Component, signal } from '@angular/core'; +import { PhoneInputComponent, type PMaskPhoneNumber } from '@desource/phone-mask-angular'; + +@Component({ + selector: 'app-checkout-phone', + standalone: true, + imports: [PhoneInputComponent], + template: ` + + + @if (isValid()) { +

Valid phone number

+ } + ` +}) +export class CheckoutPhoneComponent { + readonly phoneDigits = signal(''); + readonly isValid = signal(false); + + onPhoneChange(phone: PMaskPhoneNumber): void { + console.log(phone.digits, phone.full, phone.fullFormatted); + } +} +``` + +The component also works as an Angular form control: + +```html + +``` + +## Directive Mode + +Use the directive when you want to own the input markup and styling: + +```ts +import { Component, signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { PhoneMaskDirective, type PMaskPhoneNumber } from '@desource/phone-mask-angular'; + +@Component({ + selector: 'app-custom-phone', + standalone: true, + imports: [FormsModule, PhoneMaskDirective], + template: ` + + ` +}) +export class CustomPhoneComponent { + readonly country = signal('US'); + digits = ''; + + onPhoneChange(phone: PMaskPhoneNumber): void { + console.log(phone.fullFormatted); + } +} +``` + +You can also bind directive options directly: + +```html + +``` + +## Pipe And Service + +```ts +import { Component } from '@angular/core'; +import { PhoneMaskPipe, PhoneMaskService, providePhoneMask } from '@desource/phone-mask-angular'; + +@Component({ + selector: 'app-phone-summary', + standalone: true, + imports: [PhoneMaskPipe], + providers: [providePhoneMask({ country: 'US', locale: 'en' })], + template: ` +

{{ '2025551234' | phoneMask }}

+

{{ '2025551234' | phoneMask: { mode: 'fullFormatted' } }}

+ ` +}) +export class PhoneSummaryComponent { + constructor(phoneMask: PhoneMaskService) { + phoneMask.getPhoneNumber('2025551234'); + } +} +``` + +## Custom Templates + +```html + + + {{ country.id }} + + + + + + +``` + +Available template refs are `#flag`, `#actionsBefore`, `#copySvg`, and `#clearSvg`. + +## Core Utilities + +```ts +import { getFlagEmoji, formatDigitsWithMap } from '@desource/phone-mask-angular/core'; +``` + +## Styles + +The component ships its default styles with the component. A compiled stylesheet is also available for manual use: + +```ts +import '@desource/phone-mask-angular/assets/lib.css'; +``` diff --git a/packages/phone-mask-angular/core/ng-package.json b/packages/phone-mask-angular/core/ng-package.json new file mode 100644 index 00000000..139198bd --- /dev/null +++ b/packages/phone-mask-angular/core/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/packages/phone-mask-angular/core/src/public-api.ts b/packages/phone-mask-angular/core/src/public-api.ts new file mode 100644 index 00000000..8bee598d --- /dev/null +++ b/packages/phone-mask-angular/core/src/public-api.ts @@ -0,0 +1,2 @@ +export * from '@desource/phone-mask'; +export * from '@desource/phone-mask/kit'; diff --git a/packages/phone-mask-angular/ng-package.json b/packages/phone-mask-angular/ng-package.json new file mode 100644 index 00000000..014f6e9b --- /dev/null +++ b/packages/phone-mask-angular/ng-package.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "dist", + "allowedNonPeerDependencies": ["@desource/phone-mask", "tslib"], + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/packages/phone-mask-angular/package.json b/packages/phone-mask-angular/package.json new file mode 100644 index 00000000..e34503fc --- /dev/null +++ b/packages/phone-mask-angular/package.json @@ -0,0 +1,86 @@ +{ + "name": "@desource/phone-mask-angular", + "version": "1.4.0", + "description": "📱 Angular component, directive, pipe, and service for international phone number masking. Powered by @desource/phone-mask and Google libphonenumber.", + "keywords": [ + "angular", + "phone", + "phone-input", + "phone-mask", + "phone-number", + "international-phone", + "libphonenumber", + "angular-component", + "angular-directive", + "angular-pipe", + "input-mask", + "validation", + "tel", + "telephone", + "country-code", + "formatting" + ], + "author": "Stefan Popov ", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/DeSource-Labs/phone-mask.git", + "directory": "packages/phone-mask-angular" + }, + "bugs": { + "url": "https://github.com/DeSource-Labs/phone-mask/issues" + }, + "homepage": "https://github.com/DeSource-Labs/phone-mask/tree/main/packages/phone-mask-angular#readme", + "private": false, + "publishConfig": { + "access": "public" + }, + "sideEffects": false, + "type": "module", + "module": "./dist/fesm2022/desource-phone-mask-angular.mjs", + "types": "./dist/types/desource-phone-mask-angular.d.ts", + "exports": { + ".": { + "types": "./dist/types/desource-phone-mask-angular.d.ts", + "default": "./dist/fesm2022/desource-phone-mask-angular.mjs" + }, + "./core": { + "types": "./dist/types/desource-phone-mask-angular-core.d.ts", + "default": "./dist/fesm2022/desource-phone-mask-angular-core.mjs" + }, + "./assets/lib.css": "./dist/assets/lib.css" + }, + "files": [ + "dist", + "CHANGELOG.md", + "README.md" + ], + "scripts": { + "clean": "rimraf dist test-results coverage tsconfig.tsbuildinfo", + "clean:modules": "rimraf node_modules", + "build": "pnpm clean && pnpm build:lib && pnpm build:styles", + "build:lib": "ng-packagr -p ng-package.json", + "build:styles": "sass src/phone-input/phone-input.component.scss dist/assets/lib.css --style=compressed --no-source-map", + "typecheck": "ngc -p tsconfig.json --noEmit", + "test:unit": "vitest run", + "test:unit:coverage": "vitest run --coverage.enabled --coverage.provider=v8 --coverage.reporter=lcov --coverage.reportsDirectory=coverage" + }, + "peerDependencies": { + "@angular/common": "^19.0.0 || ^20.0.0 || ^21.0.0", + "@angular/core": "^19.0.0 || ^20.0.0 || ^21.0.0", + "@angular/forms": "^19.0.0 || ^20.0.0 || ^21.0.0" + }, + "dependencies": { + "@desource/phone-mask": "workspace:*", + "tslib": "^2.8.1" + }, + "devDependencies": { + "@angular/common": "^21.2.12", + "@angular/compiler": "^21.2.12", + "@angular/compiler-cli": "^21.2.12", + "@angular/core": "^21.2.12", + "@angular/forms": "^21.2.12", + "ng-packagr": "^21.2.3", + "sass": "^1.99.0" + } +} diff --git a/packages/phone-mask-angular/src/config.ts b/packages/phone-mask-angular/src/config.ts new file mode 100644 index 00000000..0cb3f799 --- /dev/null +++ b/packages/phone-mask-angular/src/config.ts @@ -0,0 +1,16 @@ +import { InjectionToken, makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core'; +import type { PhoneMaskConfig } from './types'; + +export const PHONE_MASK_CONFIG = new InjectionToken('PHONE_MASK_CONFIG', { + providedIn: 'root', + factory: () => ({}) +}); + +export function providePhoneMask(config: PhoneMaskConfig = {}): EnvironmentProviders { + return makeEnvironmentProviders([ + { + provide: PHONE_MASK_CONFIG, + useValue: config + } + ]); +} diff --git a/packages/phone-mask-angular/src/core.ts b/packages/phone-mask-angular/src/core.ts new file mode 100644 index 00000000..8bee598d --- /dev/null +++ b/packages/phone-mask-angular/src/core.ts @@ -0,0 +1,2 @@ +export * from '@desource/phone-mask'; +export * from '@desource/phone-mask/kit'; diff --git a/packages/phone-mask-angular/src/internal/boolean-input.ts b/packages/phone-mask-angular/src/internal/boolean-input.ts new file mode 100644 index 00000000..6a1061d2 --- /dev/null +++ b/packages/phone-mask-angular/src/internal/boolean-input.ts @@ -0,0 +1,6 @@ +import { booleanAttribute } from '@angular/core'; + +export function optionalBooleanAttribute(value: unknown): boolean | undefined { + if (value === undefined || value === null) return undefined; + return booleanAttribute(value); +} diff --git a/packages/phone-mask-angular/src/internal/formatting.ts b/packages/phone-mask-angular/src/internal/formatting.ts new file mode 100644 index 00000000..298d6466 --- /dev/null +++ b/packages/phone-mask-angular/src/internal/formatting.ts @@ -0,0 +1,71 @@ +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { + createPhoneFormatter, + extractDigits, + getCountry, + getNavigatorLang, + parseCountryCode, + type FormatterHelpers +} from '@desource/phone-mask/kit'; +import type { PhoneMaskConfig, PhoneMaskFormatMode, PhoneMaskFormatOptions, PhoneNumber } from '../types'; + +export const DEFAULT_COUNTRY = 'US'; + +export function resolveLocale(locale: string | undefined, config: PhoneMaskConfig): string { + return locale || config.locale || getNavigatorLang(); +} + +export function resolveCountryCode(country: CountryKey | string | null | undefined, config: PhoneMaskConfig): string { + return parseCountryCode(country, parseCountryCode(config.country, DEFAULT_COUNTRY)); +} + +export function resolveCountry( + country: CountryKey | string | null | undefined, + locale: string | undefined, + config: PhoneMaskConfig +): MaskFull { + return getCountry(resolveCountryCode(country, config), resolveLocale(locale, config)); +} + +export function createPhoneNumber(digits: string, country: MaskFull, formatter: FormatterHelpers): PhoneNumber { + const displayValue = formatter.formatDisplay(digits); + + return { + full: digits ? `${country.code}${digits}` : '', + fullFormatted: displayValue ? `${country.code} ${displayValue}` : '', + digits + }; +} + +export function createPhoneState(value: string | number | null | undefined, country: MaskFull) { + const formatter = createPhoneFormatter(country); + const digits = extractDigits(String(value ?? ''), formatter.getMaxDigits()); + const phone = createPhoneNumber(digits, country, formatter); + const isComplete = formatter.isComplete(digits); + const isEmpty = digits.length === 0; + + return { + ...phone, + country, + formatter, + isComplete, + isEmpty, + shouldShowWarn: !isEmpty && !isComplete + }; +} + +export function formatPhoneValue( + value: string | number | null | undefined, + options: PhoneMaskFormatOptions, + config: PhoneMaskConfig +): string { + const mode: PhoneMaskFormatMode = options.mode ?? 'display'; + const country = resolveCountry(options.country, options.locale, config); + const state = createPhoneState(value, country); + + if (mode === 'placeholder') return state.formatter.getPlaceholder(); + if (mode === 'full') return state.full; + if (mode === 'fullFormatted') return state.fullFormatted; + + return state.formatter.formatDisplay(state.digits); +} diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.html b/packages/phone-mask-angular/src/phone-input/phone-input.component.html new file mode 100644 index 00000000..9fa2da9d --- /dev/null +++ b/packages/phone-mask-angular/src/phone-input/phone-input.component.html @@ -0,0 +1,184 @@ +
+
+ +
+ +
+ + + +
+
+ +@if (renderDropdown()) { +
+ @if (dropdownOpen()) { +
+ +
+
    + @for (c of filteredCountries(); track c.id; let idx = $index) { +
  • + + @if (flagTemplate(); as flagTpl) { + + } @else { + {{ c.flag }} + } + + {{ c.name }} + {{ c.code }} +
  • + } @empty { +
  • {{ noResultsText() }}
  • + } +
+ } +
+} + +
diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.scss b/packages/phone-mask-angular/src/phone-input/phone-input.component.scss new file mode 100644 index 00000000..85febefc --- /dev/null +++ b/packages/phone-mask-angular/src/phone-input/phone-input.component.scss @@ -0,0 +1,450 @@ +// Global styles and CSS variables +.phone-input, +.phone-dropdown { + // CSS custom properties + --pi-bg: #ffffff; + --pi-fg: #111827; + --pi-muted: #6b7280; + --pi-border: #e5e7eb; + --pi-border-hover: #d1d5db; + --pi-border-focus: #3b82f6; + --pi-radius: 8px; + --pi-padding: 12px; + --pi-font-size: 16px; + --pi-height: 44px; + --pi-actions-size: 32px; + --pi-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --pi-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05); + --pi-warning: #f59e0b; + --pi-warning-light: #fbbf24; + --pi-success: #10b981; + --pi-focus-ring: 3px solid rgb(59 130 246 / 0.15); + --pi-focus-ring-warning: 3px solid rgb(245 158 11 / 0.15); + --pi-focus-ring-success: 3px solid rgb(16 185 129 / 0.15); + --pi-disabled-bg: #f9fafb; + --pi-disabled-fg: #9ca3af; + + // Theme variants + &.theme-dark { + --pi-bg: #1f2937; + --pi-fg: #f9fafb; + --pi-muted: #9ca3af; + --pi-border: #374151; + --pi-border-hover: #4b5563; + --pi-border-focus: #60a5fa; + --pi-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.3); + --pi-shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.3), 0 10px 10px -5px rgb(0 0 0 / 0.2); + --pi-warning: #fbbf24; + --pi-warning-light: #fcd34d; + --pi-focus-ring: 3px solid rgb(96 165 250 / 0.2); + --pi-focus-ring-warning: 3px solid rgb(251 191 24 / 0.2); + --pi-focus-ring-success: 3px solid rgb(16 185 129 / 0.2); + --pi-disabled-bg: #374151; + } + + // Shared styles used by both the field and the top-layer dropdown. + font-size: var(--pi-font-size); + background: var(--pi-bg); + color: var(--pi-fg); + border-radius: var(--pi-radius); + border: 1px solid var(--pi-border); + + // Reset styles + *, + *::before, + *::after { + box-sizing: border-box; + } + + button, + input { + margin: 0; + padding: 0; + border: none; + background: none; + font: inherit; + color: inherit; + } + + button { + cursor: pointer; + &:disabled { + cursor: not-allowed; + } + } + + input { + outline: none; + &::placeholder { + opacity: 0.5; + } + &:disabled { + cursor: not-allowed; + } + } +} + +.phone-input { + position: relative; + display: flex; + align-items: stretch; + width: 100%; + + &:focus-within { + outline: var(--pi-focus-ring); + } + + &.is-incomplete { + border-color: var(--pi-warning); + &:focus-within { + outline: var(--pi-focus-ring-warning); + } + } + + &.is-complete { + border-color: var(--pi-success); + &:focus-within { + outline: var(--pi-focus-ring-success); + } + } + + &.is-disabled { + background: var(--pi-disabled-bg); + color: var(--pi-disabled-fg); + } + + &.is-readonly { + cursor: default; + } + + // Size variants + &.size-compact { + --pi-font-size: 14px; + --pi-height: 36px; + --pi-padding: 10px; + --pi-actions-size: 24px; + } + + &.size-large { + --pi-font-size: 18px; + --pi-height: 52px; + --pi-padding: 16px; + --pi-actions-size: 32px; + } + + &.is-unstyled { + all: initial; + display: block; + } + + // Selector + .pi-selector { + flex-shrink: 0; + } + + .pi-selector-btn { + display: flex; + align-items: center; + gap: 6px; + padding-left: var(--pi-padding); + padding-right: 0; + min-height: var(--pi-height); + border: 1px solid transparent; + border-radius: var(--pi-radius) 0 0 var(--pi-radius); + transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1); + + &.no-dropdown { + cursor: default; + } + + &:focus-visible { + border-color: var(--pi-border-focus); + outline: none; + } + + &:disabled > .pi-flag { + opacity: 0.5; + } + } + + .pi-flag { + font-size: 1.25em; + line-height: 1; + display: inline-flex; + } + + .pi-code { + color: var(--pi-fg); + } + + .pi-chevron { + margin-left: 2px; + color: var(--pi-muted); + transition: transform 200ms ease; + + &.is-open { + transform: rotate(180deg); + } + } + + // Input wrap + .pi-input-wrap { + flex: 1; + position: relative; + display: flex; + align-items: center; + } + + // Input + .pi-input { + flex: 1; + width: 100%; + padding-left: var(--pi-padding); + // Calc right padding based on number of action buttons + 2px gaps + base padding + padding-right: calc((var(--pi-actions-size) + 2px) * var(--pi-actions-count) + var(--pi-padding)); + min-height: var(--pi-height); + border-radius: 0 var(--pi-radius) var(--pi-radius) 0; + transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1); + + &:hover:not(:disabled):not(:read-only) { + border-color: var(--pi-border-hover); + } + + &:focus { + border-color: var(--pi-border-focus); + position: relative; + } + + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus, + &:-webkit-autofill:active { + -webkit-text-fill-color: var(--pi-fg); + caret-color: var(--pi-fg); + -webkit-box-shadow: 0 0 0 1000px var(--pi-bg) inset; + box-shadow: 0 0 0 1000px var(--pi-bg) inset; + transition: + background-color 9999s ease-out, + color 9999s ease-out; + } + &:-moz-autofill { + -moz-text-fill-color: var(--pi-fg); + box-shadow: 0 0 0 1000px var(--pi-bg) inset; + } + } + + // Actions + .pi-actions { + position: absolute; + right: 2px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 2px; + } + + .pi-btn { + display: flex; + align-items: center; + justify-content: center; + width: var(--pi-actions-size); + height: var(--pi-actions-size); + background: transparent; + color: var(--pi-muted); + border: none; + border-radius: 9999px; + transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1); + + &:hover { + background: var(--pi-disabled-bg); + color: var(--pi-fg); + } + + &:active:not(:disabled) { + transform: scale(0.95); + } + + &:focus { + outline: 1px solid var(--pi-border-focus); + outline-offset: -1px; + } + + &.is-copied { + color: var(--pi-success); + border-color: var(--pi-success); + } + + svg { + flex-shrink: 0; + } + } +} + +.phone-dropdown { + position: fixed; + top: var(--pi-dd-top, 0); + left: var(--pi-dd-left, 0); + margin: 0; + width: var(--pi-dd-width, 0); + max-width: calc(100vw - 16px); + display: flex; + flex-direction: column; + box-shadow: var(--pi-shadow-lg); + overflow: hidden; + opacity: 0; + visibility: hidden; + pointer-events: none; + transform: scale(0.98); + transform-origin: top center; + will-change: opacity, transform; + z-index: 1000; + transition: none; + + &.is-open { + opacity: 1; + visibility: visible; + pointer-events: auto; + transform: scale(1); + transition: + opacity 160ms cubic-bezier(0.4, 0, 0.2, 1), + transform 160ms cubic-bezier(0.4, 0, 0.2, 1); + } + + &[data-placement='top'] { + transform-origin: bottom center; + } + + .pi-search-wrap { + padding: 8px; + border-bottom: 1px solid var(--pi-border); + } + + .pi-search { + width: 100%; + padding: 8px 12px; + font-size: 16px; + border: 1px solid var(--pi-border); + border-radius: calc(var(--pi-radius) - 2px); + background: var(--pi-bg); + transition: border-color 150ms ease; + + &:focus { + border-color: var(--pi-border-focus); + } + } + + .pi-options { + max-height: var(--pi-dd-max-height, 300px); + min-height: 0; + flex: 1 1 auto; + overflow-y: auto; + padding: 4px 0; + margin: 0; + list-style: none; + + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } + + &::-webkit-scrollbar-thumb { + background: var(--pi-border); + border-radius: 4px; + + &:hover { + background: var(--pi-border-hover); + } + } + } + + .pi-option { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + cursor: pointer; + transition: background-color 100ms ease; + + &:hover, + &.is-focused { + background: var(--pi-disabled-bg); + } + + &.is-selected { + background: var(--pi-border); + font-weight: 500; + } + } + + .pi-opt-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .pi-opt-code { + color: var(--pi-muted); + font-size: 0.875em; + } + + .pi-empty { + padding: 12px; + text-align: center; + color: var(--pi-muted); + font-size: 0.875em; + } +} + +@keyframes fade-scale-in { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.pi-actions .pi-btn { + animation: fade-scale-in 200ms cubic-bezier(0.4, 0, 0.2, 1) both; +} + +// Screen reader only +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +// Responsive +@media (max-width: 480px) { + .phone-input { + --pi-padding: 8px; + --pi-actions-size: 24px; + } + + .size-compact { + --pi-actions-size: 20px; + } +} + +// Reduced motion +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.ts b/packages/phone-mask-angular/src/phone-input/phone-input.component.ts new file mode 100644 index 00000000..cc5a9ca6 --- /dev/null +++ b/packages/phone-mask-angular/src/phone-input/phone-input.component.ts @@ -0,0 +1,597 @@ +import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef, + ElementRef, + Inject, + Optional, + TemplateRef, + ViewEncapsulation, + booleanAttribute, + computed, + contentChild, + effect, + forwardRef, + input, + model, + output, + signal, + untracked, + viewChild +} from '@angular/core'; +import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'; +import { MasksFull, type CountryKey, type MaskFull } from '@desource/phone-mask'; +import { + bindCountryDropdownListeners, + createPhoneFormatter, + detectByGeoIp, + detectCountryFromLocale, + extractDigits, + filterCountries, + getCountry, + getNavigatorLang, + handleCountryButtonKeydown, + handleCountrySearchKeydown, + parseCountryCode, + positionCountryDropdown, + processBeforeInput, + processInput, + processKeydown, + processPaste, + scrollCountryOptionIntoView, + setCaret +} from '@desource/phone-mask/kit'; +import { PHONE_MASK_CONFIG } from '../config'; +import { optionalBooleanAttribute } from '../internal/boolean-input'; +import { createPhoneNumber } from '../internal/formatting'; +import type { PhoneInputRef, PhoneMaskConfig, PhoneNumber, Size, Theme } from '../types'; + +type IndexUpdate = number | ((index: number) => number); + +const HINT_DELAY_INPUT = 500; +const HINT_DELAY_ACTION = 300; +const COPY_RESET_DELAY = 1_800; + +let nextDropdownId = 0; + +@Component({ + selector: 'desource-phone-input', + standalone: true, + imports: [NgTemplateOutlet], + templateUrl: './phone-input.component.html', + styleUrl: './phone-input.component.scss', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PhoneInputComponent), + multi: true + } + ] +}) +export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef { + readonly value = model(''); + + readonly id = input(); + readonly name = input(); + readonly countryInput = input(undefined, { alias: 'country' }); + readonly detectInput = input(undefined, { + alias: 'detect', + transform: optionalBooleanAttribute + }); + readonly localeInput = input(undefined, { alias: 'locale' }); + readonly size = input('normal'); + readonly theme = input('auto'); + readonly disabledInput = input(false, { alias: 'disabled', transform: booleanAttribute }); + readonly readOnlyInput = input(false, { alias: 'readonly', transform: booleanAttribute }); + readonly showCopy = input(true, { transform: booleanAttribute }); + readonly showClear = input(false, { transform: booleanAttribute }); + readonly withValidity = input(true, { transform: booleanAttribute }); + readonly searchPlaceholder = input('Search country or code...'); + readonly noResultsText = input('No countries found'); + readonly clearButtonLabel = input('Clear phone number'); + readonly dropdownClass = input(''); + readonly disableDefaultStyles = input(false, { transform: booleanAttribute }); + + readonly phoneChange = output(); + readonly countryChange = output(); + readonly validationChange = output(); + readonly focused = output({ alias: 'focus' }); + readonly blurred = output({ alias: 'blur' }); + readonly copiedValue = output({ alias: 'copy' }); + readonly cleared = output({ alias: 'clear' }); + + readonly actionsBeforeTemplate = contentChild>('actionsBefore'); + readonly flagTemplate = contentChild>('flag'); + readonly copySvgTemplate = contentChild>('copySvg'); + readonly clearSvgTemplate = contentChild>('clearSvg'); + + private readonly rootRef = viewChild>('root'); + private readonly telRef = viewChild>('telInput'); + private readonly liveRef = viewChild>('live'); + private readonly dropdownRef = viewChild>('dropdown'); + private readonly searchRef = viewChild>('searchInput'); + private readonly selectorRef = viewChild>('selectorButton'); + + private readonly countryCode = signal('US'); + private readonly formDisabled = signal(false); + private readonly systemDark = signal(false); + private readonly showValidationHint = signal(false); + readonly copied = signal(false); + + readonly dropdownOpen = signal(false); + readonly search = signal(''); + readonly focusedIndex = signal(0); + + readonly dropdownId = ++nextDropdownId; + readonly dropdownElementId = `pi-dropdown-${this.dropdownId}`; + readonly listboxId = `pi-options-${this.dropdownId}`; + + private validationTimer: ReturnType | undefined; + private copyTimer: ReturnType | undefined; + private themeMediaQuery: MediaQueryList | undefined; + private openByKeyboard = false; + private detectionKey = ''; + + private onTouched: () => void = () => {}; + private onChange: (value: string) => void = () => {}; + + readonly locale = computed(() => this.localeInput() || this.config.locale || getNavigatorLang()); + readonly detect = computed(() => this.detectInput() ?? this.config.detect ?? !this.config.country); + readonly country = computed(() => getCountry(this.countryCode(), this.locale())); + readonly countries = computed(() => MasksFull(this.locale())); + readonly formatter = computed(() => createPhoneFormatter(this.country())); + readonly digits = computed(() => extractDigits(this.value(), this.formatter().getMaxDigits())); + readonly displayPlaceholder = computed(() => this.formatter().getPlaceholder()); + readonly displayValue = computed(() => this.formatter().formatDisplay(this.digits())); + readonly phoneData = computed(() => createPhoneNumber(this.digits(), this.country(), this.formatter())); + readonly full = computed(() => this.phoneData().full); + readonly fullFormatted = computed(() => this.phoneData().fullFormatted); + readonly isCompleteSignal = computed(() => this.formatter().isComplete(this.digits())); + readonly isEmpty = computed(() => this.digits().length === 0); + readonly shouldShowWarn = computed(() => !this.isEmpty() && !this.isCompleteSignal()); + readonly isDisabled = computed(() => this.disabledInput() || this.formDisabled()); + readonly isReadOnly = computed(() => this.readOnlyInput()); + readonly inactive = computed(() => this.isDisabled() || this.isReadOnly()); + readonly incomplete = computed(() => this.showValidationHint() && this.shouldShowWarn()); + readonly showCopyButton = computed(() => this.showCopy() && !this.isEmpty() && !this.isDisabled()); + readonly showClearButton = computed(() => this.showClear() && !this.isEmpty() && !this.inactive()); + readonly filteredCountries = computed(() => filterCountries(this.countries(), this.search())); + readonly hasDropdown = computed(() => !this.countryInput() && this.countries().length > 1); + readonly canOpenDropdown = computed(() => this.hasDropdown() && !this.inactive()); + readonly renderDropdown = computed(() => this.hasDropdown() && (!this.inactive() || this.dropdownOpen())); + readonly activeOptionId = computed(() => + this.dropdownOpen() && this.filteredCountries()[this.focusedIndex()] + ? this.getOptionId(this.focusedIndex()) + : undefined + ); + readonly themeClass = computed(() => { + const theme = this.theme(); + if (theme === 'auto') return this.systemDark() ? 'theme-dark' : 'theme-light'; + return `theme-${theme}`; + }); + readonly rootClasses = computed(() => + [ + 'phone-input', + `size-${this.size()}`, + this.themeClass(), + this.isDisabled() && 'is-disabled', + this.isReadOnly() && 'is-readonly', + this.disableDefaultStyles() && 'is-unstyled', + this.withValidity() && this.incomplete() && 'is-incomplete', + this.withValidity() && this.isCompleteSignal() && 'is-complete' + ] + .filter(Boolean) + .join(' ') + ); + readonly dropdownClasses = computed(() => + ['phone-dropdown', this.dropdownOpen() && 'is-open', this.dropdownClass(), this.themeClass()] + .filter(Boolean) + .join(' ') + ); + readonly actionsCount = computed( + () => +this.showCopyButton() + +this.showClearButton() + (this.actionsBeforeTemplate() ? 1 : 0) + ); + readonly copyAriaLabel = computed(() => (this.copied() ? 'Copied' : `Copy ${this.fullFormatted()}`)); + readonly copyButtonTitle = computed(() => (this.copied() ? 'Copied' : 'Copy phone number')); + + constructor( + private readonly destroyRef: DestroyRef, + private readonly cdr: ChangeDetectorRef, + @Optional() @Inject(DOCUMENT) private readonly document: Document | null, + @Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {} + ) { + this.countryCode.set(parseCountryCode(this.config.country, 'US')); + this.bindThemePreference(); + + this.destroyRef.onDestroy(() => { + if (this.validationTimer) clearTimeout(this.validationTimer); + if (this.copyTimer) clearTimeout(this.copyTimer); + this.themeMediaQuery?.removeEventListener('change', this.handleThemeChange); + }); + + effect(() => { + const country = parseCountryCode(this.countryInput()); + + if (country && country !== this.countryCode()) { + queueMicrotask(() => this.selectCountry(country)); + } + }); + + effect(() => { + if (!this.detect() || this.countryInput() || this.config.country) return; + + const key = `${this.locale()}:${this.detect()}`; + if (this.detectionKey === key) return; + + this.detectionKey = key; + void this.detectCountry(); + }); + + effect(() => { + const value = this.value(); + const digits = this.digits(); + + if (value !== digits) { + queueMicrotask(() => { + if (this.value() !== this.digits()) { + this.setValue(this.digits(), false); + } + }); + } + }); + + effect(() => { + this.phoneChange.emit(this.phoneData()); + }); + + effect(() => { + this.validationChange.emit(this.isCompleteSignal()); + }); + + effect(() => { + this.countryChange.emit(this.country()); + }); + + effect((onCleanup) => { + if (!this.dropdownOpen()) return; + + queueMicrotask(() => { + this.updateDropdownPosition(); + if (this.openByKeyboard) this.focusSearch(); + }); + + onCleanup( + bindCountryDropdownListeners( + () => this.dropdownRef()?.nativeElement, + () => this.selectorRef()?.nativeElement, + () => this.closeDropdown(), + () => this.updateDropdownPosition() + ) + ); + }); + } + + writeValue(value: string | number | null | undefined): void { + this.setValue(String(value ?? ''), false); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.formDisabled.set(isDisabled); + } + + focus(): void { + setTimeout(() => this.telRef()?.nativeElement.focus()); + } + + blur(): void { + this.telRef()?.nativeElement.blur(); + } + + clear(): void { + this.setValue('', true); + this.clearValidationHint(); + this.cleared.emit(); + } + + selectCountry(country: CountryKey | string): boolean { + const parsed = parseCountryCode(country); + + if (!parsed) return false; + + untracked(() => this.countryCode.set(parsed)); + + const maxDigits = this.formatter().getMaxDigits(); + if (this.digits().length > maxDigits) { + this.setValue(this.digits().slice(0, maxDigits), true); + } + + return true; + } + + getFullNumber(): string { + return this.full(); + } + + getFullFormattedNumber(): string { + return this.fullFormatted(); + } + + getDigits(): string { + return this.digits(); + } + + isValid(): boolean { + return this.isComplete(); + } + + isComplete(): boolean { + return this.isCompleteSignal(); + } + + getOptionId(index: number): string { + return `pi-option-${this.dropdownId}-${index}`; + } + + flagContext(country: MaskFull): { $implicit: MaskFull; country: MaskFull } { + return { $implicit: country, country }; + } + + copyContext(): { copied: boolean } { + return { copied: this.copied() }; + } + + handleBeforeInput(event: Event): void { + processBeforeInput(event as InputEvent); + } + + handleInput(event: Event): void { + if (this.inactive()) return; + + const result = processInput(event, { formatter: this.formatter() }); + if (!result) return; + + this.setValue(result.newDigits, true); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + this.scheduleValidationHint(HINT_DELAY_INPUT); + } + + handleKeydown(event: KeyboardEvent): void { + if (this.inactive()) return; + + const result = processKeydown(event, { digits: this.digits(), formatter: this.formatter() }); + if (!result) return; + + this.setValue(result.newDigits, true); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + this.scheduleValidationHint(HINT_DELAY_ACTION); + } + + handlePaste(event: ClipboardEvent): void { + if (this.inactive()) return; + + const result = processPaste(event, { digits: this.digits(), formatter: this.formatter() }); + if (!result) return; + + this.setValue(result.newDigits, true); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + this.scheduleValidationHint(HINT_DELAY_ACTION); + } + + handleFocus(event: FocusEvent): void { + this.clearValidationHint(false); + this.closeDropdown(); + this.focused.emit(event); + } + + handleBlur(event: FocusEvent): void { + this.onTouched(); + this.blurred.emit(event); + } + + handleSelectorPointerDown(event: PointerEvent): void { + this.openByKeyboard = event.pointerType === 'mouse'; + } + + handleSelectorKeydown(event: KeyboardEvent): void { + handleCountryButtonKeydown( + event, + this.dropdownOpen(), + () => { + this.openByKeyboard = true; + }, + () => this.focusSearch(), + () => this.openDropdown() + ); + } + + handleSearchChange(event: Event): void { + this.search.set((event.target as HTMLInputElement).value); + this.focusedIndex.set(0); + } + + handleSearchKeydown(event: KeyboardEvent): void { + handleCountrySearchKeydown( + event, + this.focusedIndex(), + this.filteredCountries(), + (index) => this.setFocusedIndex(index), + (index) => this.scrollFocusedIntoView(index), + (country) => this.selectDropdownCountry(country.id) + ); + } + + toggleDropdown(): void { + if (this.inactive() || !this.hasDropdown()) return; + + if (this.dropdownOpen()) { + this.closeDropdown(); + } else { + this.openDropdown(); + } + } + + openDropdown(): void { + if (this.inactive() || !this.hasDropdown() || this.dropdownOpen()) return; + if (!this.dropdownRef()?.nativeElement || !this.selectorRef()?.nativeElement) return; + + this.updateDropdownPosition(); + this.focusedIndex.set(0); + this.dropdownOpen.set(true); + } + + closeDropdown(): void { + this.dropdownOpen.set(false); + this.resetDropdownState(); + } + + setFocusedIndex(index: IndexUpdate): void { + this.focusedIndex.update((current) => (typeof index === 'function' ? index(current) : index)); + } + + selectDropdownCountry(country: CountryKey | string): void { + this.selectCountry(country); + this.closeDropdown(); + this.focus(); + } + + onClearClick(): void { + this.clear(); + this.focus(); + } + + async onCopyClick(): Promise { + const value = this.fullFormatted().trim(); + const success = await this.copyToClipboard(value); + + if (!success) return; + + this.copied.set(true); + this.copiedValue.emit(value); + this.announceToScreenReader('Phone number copied to clipboard'); + + if (this.copyTimer) clearTimeout(this.copyTimer); + this.copyTimer = setTimeout(() => { + this.copied.set(false); + this.cdr.markForCheck(); + }, COPY_RESET_DELAY); + } + + private setValue(value: string, emit: boolean): void { + const nextDigits = extractDigits(value, this.formatter().getMaxDigits()); + + untracked(() => this.value.set(nextDigits)); + + if (emit) { + this.onChange(nextDigits); + } + } + + private async detectCountry(): Promise { + const geoCountry = parseCountryCode(await detectByGeoIp()); + + if (geoCountry && this.selectCountry(geoCountry)) return; + + this.selectCountry(detectCountryFromLocale() ?? 'US'); + } + + private resetDropdownState(): void { + this.search.set(''); + this.focusedIndex.set(0); + this.openByKeyboard = false; + } + + private updateDropdownPosition(): void { + positionCountryDropdown(this.rootRef()?.nativeElement ?? null, this.dropdownRef()?.nativeElement ?? null); + } + + private focusSearch(): void { + setTimeout(() => this.searchRef()?.nativeElement.focus({ preventScroll: true })); + } + + private scrollFocusedIntoView(index: number): void { + setTimeout(() => scrollCountryOptionIntoView(this.dropdownRef()?.nativeElement, index)); + } + + private scheduleCaretUpdate(el: HTMLInputElement | null, digitIndex: number): void { + setTimeout(() => { + const position = this.formatter().getCaretPosition(digitIndex); + setCaret(el, position); + }); + } + + private clearValidationHint(hideHint = true): void { + if (hideHint) this.showValidationHint.set(false); + if (this.validationTimer) clearTimeout(this.validationTimer); + } + + private scheduleValidationHint(delay: number): void { + this.showValidationHint.set(false); + if (this.validationTimer) clearTimeout(this.validationTimer); + this.validationTimer = setTimeout(() => { + this.showValidationHint.set(true); + this.cdr.markForCheck(); + }, delay); + } + + private announceToScreenReader(message: string): void { + const live = this.liveRef()?.nativeElement; + if (!live) return; + + live.textContent = message; + setTimeout(() => { + live.textContent = ''; + }, COPY_RESET_DELAY); + } + + private async copyToClipboard(value: string): Promise { + if (!value) return false; + + try { + if (globalThis.navigator?.clipboard?.writeText) { + await globalThis.navigator.clipboard.writeText(value); + return true; + } + + if (!this.document?.body) return false; + + const textarea = this.document.createElement('textarea'); + textarea.value = value; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + this.document.body.appendChild(textarea); + textarea.select(); + const copied = this.document.execCommand('copy'); + textarea.remove(); + + return copied; + } catch { + return false; + } + } + + private bindThemePreference(): void { + this.themeMediaQuery = globalThis.matchMedia?.('(prefers-color-scheme: dark)') ?? undefined; + if (!this.themeMediaQuery) return; + + this.systemDark.set(this.themeMediaQuery.matches); + this.themeMediaQuery.addEventListener('change', this.handleThemeChange); + } + + private readonly handleThemeChange = (event: MediaQueryListEvent) => { + this.systemDark.set(event.matches); + this.cdr.markForCheck(); + }; +} diff --git a/packages/phone-mask-angular/src/phone-mask.directive.ts b/packages/phone-mask-angular/src/phone-mask.directive.ts new file mode 100644 index 00000000..5d4ee610 --- /dev/null +++ b/packages/phone-mask-angular/src/phone-mask.directive.ts @@ -0,0 +1,291 @@ +import { + DestroyRef, + Directive, + ElementRef, + Inject, + Optional, + effect, + forwardRef, + input, + model, + output, + signal, + untracked +} from '@angular/core'; +import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { + createPhoneFormatter, + detectByGeoIp, + detectCountryFromLocale, + extractDigits, + getCountry, + getNavigatorLang, + parseCountryCode, + processBeforeInput, + processInput, + processKeydown, + processPaste, + setCaret +} from '@desource/phone-mask/kit'; +import { PHONE_MASK_CONFIG } from './config'; +import { optionalBooleanAttribute } from './internal/boolean-input'; +import { createPhoneNumber } from './internal/formatting'; +import type { PhoneMaskConfig, PhoneMaskDirectiveInput, PhoneMaskDirectiveOptions, PhoneNumber } from './types'; + +function parseOptions(value: PhoneMaskDirectiveInput): PhoneMaskDirectiveOptions { + if (typeof value === 'string') return { country: value }; + if (value && typeof value === 'object') return value; + return {}; +} + +@Directive({ + selector: 'input[phoneMask]', + standalone: true, + exportAs: 'phoneMask', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PhoneMaskDirective), + multi: true + } + ] +}) +export class PhoneMaskDirective implements ControlValueAccessor { + readonly phoneMask = input(undefined); + readonly countryInput = input(undefined, { alias: 'phoneMaskCountry' }); + readonly localeInput = input(undefined, { alias: 'phoneMaskLocale' }); + readonly detectInput = input(undefined, { + alias: 'phoneMaskDetect', + transform: optionalBooleanAttribute + }); + readonly value = model('', { alias: 'phoneMaskValue' }); + + readonly phoneChange = output({ alias: 'phoneMaskChange' }); + readonly countryChange = output({ alias: 'phoneMaskCountryChange' }); + + private readonly countryCode = signal('US'); + private readonly disabled = signal(false); + private detectionKey = ''; + + private onTouched: () => void = () => {}; + private onChange: (value: string) => void = () => {}; + + private readonly options = () => { + const parsed = parseOptions(this.phoneMask()); + + return { + ...parsed, + country: this.countryInput() ?? parsed.country ?? this.config.country, + locale: this.localeInput() ?? parsed.locale ?? this.config.locale, + detect: this.detectInput() ?? parsed.detect ?? this.config.detect ?? false + }; + }; + + private readonly locale = () => this.options().locale || getNavigatorLang(); + private readonly country = () => getCountry(this.countryCode(), this.locale()); + private readonly formatter = () => createPhoneFormatter(this.country()); + private readonly digits = () => extractDigits(this.value(), this.formatter().getMaxDigits()); + private readonly phoneData = () => createPhoneNumber(this.digits(), this.country(), this.formatter()); + + constructor( + private readonly elementRef: ElementRef, + private readonly destroyRef: DestroyRef, + @Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {} + ) { + const el = this.elementRef.nativeElement; + + this.countryCode.set(parseCountryCode(this.config.country, 'US')); + + el.setAttribute('type', 'tel'); + el.setAttribute('inputmode', 'tel'); + el.setAttribute('placeholder', ''); + + const beforeInputHandler = (event: InputEvent) => this.handleBeforeInput(event); + const inputHandler = (event: Event) => this.handleInput(event); + const keydownHandler = (event: KeyboardEvent) => this.handleKeydown(event); + const pasteHandler = (event: ClipboardEvent) => this.handlePaste(event); + const blurHandler = () => this.onTouched(); + + el.addEventListener('beforeinput', beforeInputHandler); + el.addEventListener('input', inputHandler); + el.addEventListener('keydown', keydownHandler); + el.addEventListener('paste', pasteHandler); + el.addEventListener('blur', blurHandler); + + this.destroyRef.onDestroy(() => { + el.removeEventListener('beforeinput', beforeInputHandler); + el.removeEventListener('input', inputHandler); + el.removeEventListener('keydown', keydownHandler); + el.removeEventListener('paste', pasteHandler); + el.removeEventListener('blur', blurHandler); + }); + + effect(() => { + const options = this.options(); + const parsed = parseCountryCode(options.country); + + if (parsed && parsed !== this.countryCode()) { + queueMicrotask(() => this.setCountry(parsed)); + } + }); + + effect(() => { + const options = this.options(); + + if (!options.detect || options.country) return; + + const key = `${this.locale()}:${options.detect}`; + if (this.detectionKey === key) return; + + this.detectionKey = key; + void this.detectCountry(); + }); + + effect(() => { + const el = this.elementRef.nativeElement; + const formatter = this.formatter(); + const digits = this.digits(); + + el.value = formatter.formatDisplay(digits); + el.placeholder = formatter.getPlaceholder(); + }); + + effect(() => { + const phone = this.phoneData(); + const options = this.options(); + + this.phoneChange.emit(phone); + options.onChange?.(phone); + }); + + effect(() => { + const country = this.country(); + const options = this.options(); + + this.countryChange.emit(country); + options.onCountryChange?.(country); + }); + } + + writeValue(value: string | number | null | undefined): void { + this.setValue(extractDigits(String(value ?? ''), this.formatter().getMaxDigits()), false); + } + + registerOnChange(fn: (value: string) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled.set(isDisabled); + this.elementRef.nativeElement.disabled = isDisabled; + } + + clear(): void { + this.setValue('', true); + } + + selectCountry(country: CountryKey | string): boolean { + return this.setCountry(country); + } + + getDigits(): string { + return this.digits(); + } + + getFullNumber(): string { + return this.phoneData().full; + } + + getFullFormattedNumber(): string { + return this.phoneData().fullFormatted; + } + + isComplete(): boolean { + return this.formatter().isComplete(this.digits()); + } + + isValid(): boolean { + return this.isComplete(); + } + + private setValue(value: string, emit: boolean): void { + const nextDigits = extractDigits(value, this.formatter().getMaxDigits()); + + untracked(() => this.value.set(nextDigits)); + + if (emit) { + this.onChange(nextDigits); + } + } + + private setCountry(country: CountryKey | string | null | undefined): boolean { + const parsed = parseCountryCode(country); + + if (!parsed) return false; + + untracked(() => this.countryCode.set(parsed)); + + const digits = this.digits(); + const maxDigits = this.formatter().getMaxDigits(); + + if (digits.length > maxDigits) { + this.setValue(digits.slice(0, maxDigits), true); + } + + return true; + } + + private async detectCountry(): Promise { + const geoCountry = parseCountryCode(await detectByGeoIp()); + + if (geoCountry && this.setCountry(geoCountry)) return; + + this.setCountry(detectCountryFromLocale()); + } + + private scheduleCaretUpdate(el: HTMLInputElement | null, digitIndex: number): void { + setTimeout(() => { + const position = this.formatter().getCaretPosition(digitIndex); + setCaret(el, position); + }); + } + + private handleBeforeInput(event: InputEvent): void { + processBeforeInput(event); + } + + private handleInput(event: Event): void { + if (this.disabled()) return; + + const result = processInput(event, { formatter: this.formatter() }); + if (!result) return; + + this.setValue(result.newDigits, true); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + } + + private handleKeydown(event: KeyboardEvent): void { + if (this.disabled()) return; + + const result = processKeydown(event, { digits: this.digits(), formatter: this.formatter() }); + if (!result) return; + + this.setValue(result.newDigits, true); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + } + + private handlePaste(event: ClipboardEvent): void { + if (this.disabled()) return; + + const result = processPaste(event, { digits: this.digits(), formatter: this.formatter() }); + if (!result) return; + + this.setValue(result.newDigits, true); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + } +} diff --git a/packages/phone-mask-angular/src/phone-mask.pipe.ts b/packages/phone-mask-angular/src/phone-mask.pipe.ts new file mode 100644 index 00000000..94ce7709 --- /dev/null +++ b/packages/phone-mask-angular/src/phone-mask.pipe.ts @@ -0,0 +1,31 @@ +import { Inject, Optional, Pipe, type PipeTransform } from '@angular/core'; +import type { CountryKey } from '@desource/phone-mask'; +import { PHONE_MASK_CONFIG } from './config'; +import { formatPhoneValue } from './internal/formatting'; +import type { PhoneMaskConfig, PhoneMaskFormatMode, PhoneMaskFormatOptions } from './types'; + +function isFormatOptions(value: unknown): value is PhoneMaskFormatOptions { + return !!value && typeof value === 'object'; +} + +@Pipe({ + name: 'phoneMask', + standalone: true, + pure: true +}) +export class PhoneMaskPipe implements PipeTransform { + constructor(@Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {}) {} + + transform( + value: string | number | null | undefined, + countryOrOptions?: CountryKey | string | PhoneMaskFormatOptions, + mode: PhoneMaskFormatMode = 'display', + locale?: string + ): string { + const options: PhoneMaskFormatOptions = isFormatOptions(countryOrOptions) + ? countryOrOptions + : { country: countryOrOptions as CountryKey | string | undefined, mode, locale }; + + return formatPhoneValue(value, options, this.config); + } +} diff --git a/packages/phone-mask-angular/src/phone-mask.service.ts b/packages/phone-mask-angular/src/phone-mask.service.ts new file mode 100644 index 00000000..95146ffe --- /dev/null +++ b/packages/phone-mask-angular/src/phone-mask.service.ts @@ -0,0 +1,97 @@ +import { Inject, Injectable, Optional } from '@angular/core'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { createPhoneFormatter, extractDigits, type FormatterHelpers } from '@desource/phone-mask/kit'; +import { PHONE_MASK_CONFIG } from './config'; +import { + createPhoneNumber, + createPhoneState, + formatPhoneValue, + resolveCountry, + resolveLocale +} from './internal/formatting'; +import type { + PhoneMaskConfig, + PhoneMaskFormatMode, + PhoneMaskFormatOptions, + PhoneMaskState, + PhoneNumber +} from './types'; + +function isMaskFull(value: unknown): value is MaskFull { + return !!value && typeof value === 'object' && 'code' in value && 'mask' in value; +} + +function isFormatOptions(value: unknown): value is PhoneMaskFormatOptions { + return !!value && typeof value === 'object'; +} + +@Injectable({ + providedIn: 'root' +}) +export class PhoneMaskService { + constructor(@Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {}) {} + + getLocale(locale?: string): string { + return resolveLocale(locale, this.config); + } + + getCountry(country?: CountryKey | string | null, locale?: string): MaskFull { + return resolveCountry(country, locale, this.config); + } + + createFormatter(country?: CountryKey | string | MaskFull | null, locale?: string): FormatterHelpers { + if (isMaskFull(country)) return createPhoneFormatter(country); + return createPhoneFormatter(this.getCountry(country as CountryKey | string | null | undefined, locale)); + } + + getDigits(value: string | number | null | undefined, country?: CountryKey | string | MaskFull | null): string { + const formatter = this.createFormatter(country); + return extractDigits(String(value ?? ''), formatter.getMaxDigits()); + } + + getPhoneNumber( + value: string | number | null | undefined, + country?: CountryKey | string | MaskFull | null, + locale?: string + ): PhoneNumber { + const resolvedCountry = isMaskFull(country) + ? country + : this.getCountry(country as CountryKey | string | null | undefined, locale); + const formatter = createPhoneFormatter(resolvedCountry); + const digits = extractDigits(String(value ?? ''), formatter.getMaxDigits()); + + return createPhoneNumber(digits, resolvedCountry, formatter); + } + + getState( + value: string | number | null | undefined, + country?: CountryKey | string | MaskFull | null, + locale?: string + ): PhoneMaskState { + const resolvedCountry = isMaskFull(country) + ? country + : this.getCountry(country as CountryKey | string | null | undefined, locale); + return createPhoneState(value, resolvedCountry); + } + + format( + value: string | number | null | undefined, + countryOrOptions?: CountryKey | string | PhoneMaskFormatOptions, + mode: PhoneMaskFormatMode = 'display', + locale?: string + ): string { + const options: PhoneMaskFormatOptions = isFormatOptions(countryOrOptions) + ? countryOrOptions + : { country: countryOrOptions as CountryKey | string | undefined, mode, locale }; + + return formatPhoneValue(value, options, this.config); + } + + isComplete( + value: string | number | null | undefined, + country?: CountryKey | string | MaskFull | null, + locale?: string + ): boolean { + return this.getState(value, country, locale).isComplete; + } +} diff --git a/packages/phone-mask-angular/src/public-api.ts b/packages/phone-mask-angular/src/public-api.ts new file mode 100644 index 00000000..4d1cc74b --- /dev/null +++ b/packages/phone-mask-angular/src/public-api.ts @@ -0,0 +1,30 @@ +export { PHONE_MASK_CONFIG, providePhoneMask } from './config'; +export { PhoneInputComponent } from './phone-input/phone-input.component'; +export { PhoneMaskDirective } from './phone-mask.directive'; +export { PhoneMaskPipe } from './phone-mask.pipe'; +export { PhoneMaskService } from './phone-mask.service'; + +export type { + PhoneInputRef, + PhoneMaskConfig, + PhoneMaskDirectiveInput, + PhoneMaskDirectiveOptions, + PhoneMaskFormatMode, + PhoneMaskFormatOptions, + PhoneMaskState, + PhoneNumber as PMaskPhoneNumber, + Size as PhoneInputSize, + Theme as PhoneInputTheme +} from './types'; + +export type { + CountryKey as PCountryKey, + MaskBase as PMaskBase, + MaskBaseMap as PMaskBaseMap, + Mask as PMask, + MaskMap as PMaskMap, + MaskWithFlag as PMaskWithFlag, + MaskWithFlagMap as PMaskWithFlagMap, + MaskFull as PMaskFull, + MaskFullMap as PMaskFullMap +} from '@desource/phone-mask'; diff --git a/packages/phone-mask-angular/src/types.ts b/packages/phone-mask-angular/src/types.ts new file mode 100644 index 00000000..565b00c6 --- /dev/null +++ b/packages/phone-mask-angular/src/types.ts @@ -0,0 +1,77 @@ +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import type { FormatterHelpers } from '@desource/phone-mask/kit'; + +export type Size = 'compact' | 'normal' | 'large'; +export type Theme = 'auto' | 'light' | 'dark'; +export type PhoneMaskFormatMode = 'display' | 'full' | 'fullFormatted' | 'placeholder'; + +export interface PhoneNumber { + full: string; + fullFormatted: string; + digits: string; +} + +export interface PhoneMaskConfig { + /** Default ISO 3166-1 alpha-2 country code. */ + country?: CountryKey | string; + /** Default locale for country names. */ + locale?: string; + /** Default country auto-detection behavior for APIs that support detection. */ + detect?: boolean; +} + +export interface PhoneMaskFormatOptions { + /** ISO 3166-1 alpha-2 country code. */ + country?: CountryKey | string; + /** Locale for country names. */ + locale?: string; + /** Returned formatting mode. */ + mode?: PhoneMaskFormatMode; +} + +export interface PhoneMaskDirectiveOptions { + /** Country ISO code (e.g. US, DE, GB). */ + country?: CountryKey | string; + /** Locale for country names. */ + locale?: string; + /** Auto-detect country from GeoIP/locale. */ + detect?: boolean; + /** Called when formatted phone data changes. */ + onChange?: (phone: PhoneNumber) => void; + /** Called when selected country changes. */ + onCountryChange?: (country: MaskFull) => void; +} + +export type PhoneMaskDirectiveInput = CountryKey | string | PhoneMaskDirectiveOptions | null | undefined; + +export interface PhoneInputRef { + /** Focus the phone input. */ + focus: () => void; + /** Blur the phone input. */ + blur: () => void; + /** Clear the phone input. */ + clear: () => void; + /** Select a country by its ISO 3166-1 alpha-2 code. */ + selectCountry: (country: CountryKey | string) => boolean; + /** Get the full phone number with country code (e.g. +1234567890). */ + getFullNumber: () => string; + /** Get the full phone number formatted according to country rules (e.g. +1 234-567-890). */ + getFullFormattedNumber: () => string; + /** Get only the digits of the phone number without country code (e.g. 234567890). */ + getDigits: () => string; + /** Check if the current phone number is valid. */ + isValid: () => boolean; + /** Check if the current phone number is complete. */ + isComplete: () => boolean; +} + +export interface PhoneMaskState { + country: MaskFull; + formatter: FormatterHelpers; + digits: string; + full: string; + fullFormatted: string; + isComplete: boolean; + isEmpty: boolean; + shouldShowWarn: boolean; +} diff --git a/packages/phone-mask-angular/tests/unit/service-pipe.test.ts b/packages/phone-mask-angular/tests/unit/service-pipe.test.ts new file mode 100644 index 00000000..30029b31 --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/service-pipe.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { PhoneMaskPipe } from '../../src/phone-mask.pipe'; +import { PhoneMaskService } from '../../src/phone-mask.service'; + +describe('PhoneMaskService', () => { + const service = new PhoneMaskService({ country: 'US', locale: 'en' }); + + it('formats national display values', () => { + expect(service.format('2025551234')).toBe('202-555-1234'); + }); + + it('returns full phone payloads', () => { + expect(service.getPhoneNumber('2025551234')).toEqual({ + digits: '2025551234', + full: '+12025551234', + fullFormatted: '+1 202-555-1234' + }); + }); + + it('checks completion against the selected country mask', () => { + expect(service.isComplete('2025551234')).toBe(true); + expect(service.isComplete('202555')).toBe(false); + }); +}); + +describe('PhoneMaskPipe', () => { + const pipe = new PhoneMaskPipe({ country: 'US', locale: 'en' }); + + it('supports display, full, fullFormatted, and placeholder modes', () => { + expect(pipe.transform('2025551234')).toBe('202-555-1234'); + expect(pipe.transform('2025551234', { mode: 'full' })).toBe('+12025551234'); + expect(pipe.transform('2025551234', { mode: 'fullFormatted' })).toBe('+1 202-555-1234'); + expect(pipe.transform('', { mode: 'placeholder' })).toBe('###-###-####'); + }); + + it('accepts country, mode, and locale shorthand arguments', () => { + expect(pipe.transform('442071234567', 'GB', 'fullFormatted', 'en')).toMatch(/^\+44 /); + }); +}); diff --git a/packages/phone-mask-angular/tsconfig.json b/packages/phone-mask-angular/tsconfig.json new file mode 100644 index 00000000..acfce103 --- /dev/null +++ b/packages/phone-mask-angular/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "composite": false, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": false, + "experimentalDecorators": true, + "inlineSources": true, + "outDir": "dist/out-tsc", + "rootDir": ".", + "skipLibCheck": true, + "types": [] + }, + "angularCompilerOptions": { + "compilationMode": "partial", + "strictTemplates": true, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true + }, + "include": ["src/**/*.ts", "core/src/**/*.ts"], + "exclude": ["dist", "tests/**/*.ts"] +} diff --git a/packages/phone-mask-angular/vitest.config.ts b/packages/phone-mask-angular/vitest.config.ts new file mode 100644 index 00000000..8af597ed --- /dev/null +++ b/packages/phone-mask-angular/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + include: ['tests/unit/**/*.test.ts'], + globals: true + } +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd56a3ce..d79afe12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,37 @@ importers: specifier: ^1.0.0 version: 1.0.0(rollup@4.60.3) + packages/phone-mask-angular: + dependencies: + '@desource/phone-mask': + specifier: workspace:* + version: link:../phone-mask + tslib: + specifier: ^2.8.1 + version: 2.8.1 + devDependencies: + '@angular/common': + specifier: ^21.2.12 + version: 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^21.2.12 + version: 21.2.12 + '@angular/compiler-cli': + specifier: ^21.2.12 + version: 21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3) + '@angular/core': + specifier: ^21.2.12 + version: 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + '@angular/forms': + specifier: ^21.2.12 + version: 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)))(rxjs@7.8.2) + ng-packagr: + specifier: ^21.2.3 + version: 21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + sass: + specifier: ^1.99.0 + version: 1.99.0 + packages/phone-mask-nuxt: dependencies: '@desource/phone-mask-vue': @@ -212,6 +243,65 @@ importers: packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@angular/common@21.2.12': + resolution: {integrity: sha512-b7IRSM9fWPmZ1SLN0utVcW87IkhiRte3Wsnwr2nEsjum2soRMfvKqHwtEFGfCztlwOmZLgKiGW9pqKpzBkIjnQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/core': 21.2.12 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/compiler-cli@21.2.12': + resolution: {integrity: sha512-YQ15Yp2OWBS1NnzZH77HLH1ZDn+/A5Mc1EobKl4CX8dYUEPIB/KwmGKLaKtbJ0KNcVsDlmsTTWodRgqe2n5erw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@angular/compiler': 21.2.12 + typescript: '>=5.9 <6.1' + peerDependenciesMeta: + typescript: + optional: true + + '@angular/compiler@21.2.12': + resolution: {integrity: sha512-246iBwMAVGzrYPqu/Wwzb9L/kt+dkT12Hllr/dYZu6aHeIxaHPRZoPBKSweAgOPXeOl+q+nlPtK34glsMb1CRw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@angular/core@21.2.12': + resolution: {integrity: sha512-wcD6tzE30nwg58KmAU19347Jf/1F/vFg2CEd9Qcu5cA1Z4s3umzvaqs/7988ne4HaS4iJEpvTbRvGss7EYZEfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/compiler': 21.2.12 + rxjs: ^6.5.3 || ^7.4.0 + zone.js: ~0.15.0 || ~0.16.0 + peerDependenciesMeta: + '@angular/compiler': + optional: true + zone.js: + optional: true + + '@angular/forms@21.2.12': + resolution: {integrity: sha512-jhHaIgMWcgPcVFEPwhjLhByvA2xou6Th5PR6iC3H0YeLQyRmOFPWdczszytlWB1CeJ0UT9epxzOZT25zNcGSfg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/common': 21.2.12 + '@angular/core': 21.2.12 + '@angular/platform-browser': 21.2.12 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/platform-browser@21.2.12': + resolution: {integrity: sha512-P4MVColcYgBPmHyQ9nPVw9NjWPNxkC++N2Bjh3kOUFflC/6D/ufYJytsI/y1WQ8dtoHPHxiuRf3xHvcwUMPgEQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/animations': 21.2.12 + '@angular/common': 21.2.12 + '@angular/core': 21.2.12 + peerDependenciesMeta: + '@angular/animations': + optional: true + '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -507,6 +597,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -519,6 +615,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.28.0': resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} engines: {node: '>=18'} @@ -531,6 +633,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.28.0': resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} engines: {node: '>=18'} @@ -543,6 +651,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.28.0': resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} engines: {node: '>=18'} @@ -555,6 +669,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.28.0': resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} engines: {node: '>=18'} @@ -567,6 +687,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.28.0': resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} engines: {node: '>=18'} @@ -579,6 +705,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.28.0': resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} engines: {node: '>=18'} @@ -591,6 +723,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.28.0': resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} engines: {node: '>=18'} @@ -603,6 +741,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.28.0': resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} engines: {node: '>=18'} @@ -615,6 +759,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.28.0': resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} engines: {node: '>=18'} @@ -627,6 +777,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.28.0': resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} engines: {node: '>=18'} @@ -639,6 +795,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.28.0': resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} engines: {node: '>=18'} @@ -651,6 +813,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.28.0': resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} engines: {node: '>=18'} @@ -663,6 +831,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.28.0': resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} engines: {node: '>=18'} @@ -675,6 +849,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.28.0': resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} engines: {node: '>=18'} @@ -687,6 +867,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.28.0': resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} engines: {node: '>=18'} @@ -699,6 +885,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.28.0': resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} engines: {node: '>=18'} @@ -711,6 +903,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.28.0': resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} engines: {node: '>=18'} @@ -723,6 +921,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.28.0': resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} engines: {node: '>=18'} @@ -735,6 +939,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.28.0': resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} engines: {node: '>=18'} @@ -747,6 +957,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.28.0': resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} engines: {node: '>=18'} @@ -759,6 +975,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.28.0': resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} engines: {node: '>=18'} @@ -771,6 +993,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.28.0': resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} @@ -783,6 +1011,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.28.0': resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} engines: {node: '>=18'} @@ -795,6 +1029,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.28.0': resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} engines: {node: '>=18'} @@ -807,6 +1047,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.28.0': resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} engines: {node: '>=18'} @@ -1108,6 +1354,119 @@ packages: engines: {node: '>=18'} hasBin: true + '@napi-rs/nice-android-arm-eabi@1.1.1': + resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/nice-android-arm64@1.1.1': + resolution: {integrity: sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/nice-darwin-arm64@1.1.1': + resolution: {integrity: sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/nice-darwin-x64@1.1.1': + resolution: {integrity: sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/nice-freebsd-x64@1.1.1': + resolution: {integrity: sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + resolution: {integrity: sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + resolution: {integrity: sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-musl@1.1.1': + resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-openharmony-arm64@1.1.1': + resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + resolution: {integrity: sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + resolution: {integrity: sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + resolution: {integrity: sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/nice@1.1.1': + resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} + engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: @@ -2110,6 +2469,11 @@ packages: cpu: [x64] os: [win32] + '@rollup/wasm-node@4.60.3': + resolution: {integrity: sha512-SVhQ4TJk0BvnJKwceVsCWHtmquucfjU0eu+Bonrjb6W3zombkA/tqw1efaqT2BONX/TJniqkzumF6Sz/sXMJ2w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} @@ -2480,6 +2844,9 @@ packages: ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + alien-signals@3.1.2: resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} @@ -2769,6 +3136,10 @@ packages: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-truncate@5.2.0: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} @@ -2800,9 +3171,16 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + common-path-prefix@3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -2829,6 +3207,9 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -3018,6 +3399,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dependency-graph@1.0.0: + resolution: {integrity: sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==} + engines: {node: '>=4'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3174,6 +3559,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} @@ -3379,6 +3769,9 @@ packages: fast-string-width@1.1.0: resolution: {integrity: sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-wrap-ansi@0.1.6: resolution: {integrity: sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==} @@ -3412,6 +3805,14 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-cache-directory@6.0.0: + resolution: {integrity: sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==} + engines: {node: '>=20'} + + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -3701,6 +4102,9 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + injection-js@2.6.1: + resolution: {integrity: sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -3790,6 +4194,10 @@ packages: resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} engines: {node: '>=18'} + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -3858,6 +4266,10 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -3969,6 +4381,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -3977,6 +4392,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -4148,6 +4566,10 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -4319,6 +4741,19 @@ packages: engines: {node: '>= 4.4.x'} hasBin: true + ng-packagr@21.2.3: + resolution: {integrity: sha512-jGq6yu0G6KReVK0i5RYVoV9HDL0mU626HrLBu5xvc8ZJ92n/+rLrFJuXdCnkroB9um+FBTQe/or6/A/2GAKhLw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@angular/compiler-cli': ^21.0.0 || ^21.2.0-next + tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 + tslib: ^2.3.0 + typescript: '>=5.9 <6.0' + peerDependenciesMeta: + tailwindcss: + optional: true + nitropack@2.13.4: resolution: {integrity: sha512-tX7bT6zxNeMwkc6hxHiZeUoTOjVrcjoh1Z3cmxOlodIqjl4HISgqfGOmkWSayky3Nv9Z5+KQH52F8nmXJY5AAA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4486,6 +4921,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@9.4.0: + resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} + engines: {node: '>=20'} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -4613,6 +5052,14 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + piscina@5.1.4: + resolution: {integrity: sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==} + engines: {node: '>=20.x'} + + pkg-dir@8.0.0: + resolution: {integrity: sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==} + engines: {node: '>=18'} + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} @@ -4952,6 +5399,9 @@ packages: resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} engines: {node: '>=4'} + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -5018,6 +5468,13 @@ packages: rollup: ^3.29.4 || ^4 typescript: ^4.5 || ^5.0 + rollup-plugin-dts@6.4.1: + resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} + engines: {node: '>=20'} + peerDependencies: + rollup: ^3.29.4 || ^4 + typescript: ^4.5 || ^5.0 || ^6.0 + rollup-plugin-visualizer@7.0.1: resolution: {integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==} engines: {node: '>=22'} @@ -5046,6 +5503,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -5256,6 +5716,10 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + engines: {node: '>=18'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -6018,6 +6482,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} @@ -6036,6 +6504,59 @@ packages: snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + rxjs: 7.8.2 + tslib: 2.8.1 + + '@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3)': + dependencies: + '@angular/compiler': 21.2.12 + '@babel/core': 7.29.0 + '@jridgewell/sourcemap-codec': 1.5.5 + chokidar: 5.0.0 + convert-source-map: 1.9.0 + reflect-metadata: 0.2.2 + semver: 7.7.4 + tslib: 2.8.1 + yargs: 18.0.0 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@angular/compiler@21.2.12': + dependencies: + tslib: 2.8.1 + + '@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)': + dependencies: + rxjs: 7.8.2 + tslib: 2.8.1 + optionalDependencies: + '@angular/compiler': 21.2.12 + + '@angular/forms@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)))(rxjs@7.8.2)': + dependencies: + '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + '@angular/platform-browser': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)) + '@standard-schema/spec': 1.1.0 + rxjs: 7.8.2 + tslib: 2.8.1 + + '@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))': + dependencies: + '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + tslib: 2.8.1 + '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 @@ -6467,156 +6988,234 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.27.7': + optional: true + '@esbuild/android-arm64@0.28.0': optional: true '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.27.7': + optional: true + '@esbuild/android-arm@0.28.0': optional: true '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.27.7': + optional: true + '@esbuild/android-x64@0.28.0': optional: true '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.27.7': + optional: true + '@esbuild/darwin-arm64@0.28.0': optional: true '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.27.7': + optional: true + '@esbuild/darwin-x64@0.28.0': optional: true '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.27.7': + optional: true + '@esbuild/freebsd-arm64@0.28.0': optional: true '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.27.7': + optional: true + '@esbuild/freebsd-x64@0.28.0': optional: true '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.27.7': + optional: true + '@esbuild/linux-arm64@0.28.0': optional: true '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.27.7': + optional: true + '@esbuild/linux-arm@0.28.0': optional: true '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.27.7': + optional: true + '@esbuild/linux-ia32@0.28.0': optional: true '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.27.7': + optional: true + '@esbuild/linux-loong64@0.28.0': optional: true '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.27.7': + optional: true + '@esbuild/linux-mips64el@0.28.0': optional: true '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.27.7': + optional: true + '@esbuild/linux-ppc64@0.28.0': optional: true '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.27.7': + optional: true + '@esbuild/linux-riscv64@0.28.0': optional: true '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.27.7': + optional: true + '@esbuild/linux-s390x@0.28.0': optional: true '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.27.7': + optional: true + '@esbuild/linux-x64@0.28.0': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.27.7': + optional: true + '@esbuild/netbsd-arm64@0.28.0': optional: true '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.27.7': + optional: true + '@esbuild/netbsd-x64@0.28.0': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.27.7': + optional: true + '@esbuild/openbsd-arm64@0.28.0': optional: true '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.27.7': + optional: true + '@esbuild/openbsd-x64@0.28.0': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.27.7': + optional: true + '@esbuild/openharmony-arm64@0.28.0': optional: true '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.27.7': + optional: true + '@esbuild/sunos-x64@0.28.0': optional: true '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.27.7': + optional: true + '@esbuild/win32-arm64@0.28.0': optional: true '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.27.7': + optional: true + '@esbuild/win32-ia32@0.28.0': optional: true '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.27.7': + optional: true + '@esbuild/win32-x64@0.28.0': optional: true @@ -6891,6 +7490,78 @@ snapshots: - encoding - supports-color + '@napi-rs/nice-android-arm-eabi@1.1.1': + optional: true + + '@napi-rs/nice-android-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-x64@1.1.1': + optional: true + + '@napi-rs/nice-freebsd-x64@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + optional: true + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-musl@1.1.1': + optional: true + + '@napi-rs/nice-openharmony-arm64@1.1.1': + optional: true + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + optional: true + + '@napi-rs/nice@1.1.1': + optionalDependencies: + '@napi-rs/nice-android-arm-eabi': 1.1.1 + '@napi-rs/nice-android-arm64': 1.1.1 + '@napi-rs/nice-darwin-arm64': 1.1.1 + '@napi-rs/nice-darwin-x64': 1.1.1 + '@napi-rs/nice-freebsd-x64': 1.1.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 + '@napi-rs/nice-linux-arm64-gnu': 1.1.1 + '@napi-rs/nice-linux-arm64-musl': 1.1.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 + '@napi-rs/nice-linux-s390x-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-musl': 1.1.1 + '@napi-rs/nice-openharmony-arm64': 1.1.1 + '@napi-rs/nice-win32-arm64-msvc': 1.1.1 + '@napi-rs/nice-win32-ia32-msvc': 1.1.1 + '@napi-rs/nice-win32-x64-msvc': 1.1.1 + optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -7837,6 +8508,12 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.3': optional: true + '@rollup/wasm-node@4.60.3': + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + '@sindresorhus/is@7.2.0': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -8310,6 +8987,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + alien-signals@3.1.2: {} ansi-colors@4.1.3: {} @@ -8622,6 +9306,8 @@ snapshots: dependencies: restore-cursor: 5.1.0 + cli-spinners@3.4.0: {} + cli-truncate@5.2.0: dependencies: slice-ansi: 8.0.0 @@ -8647,8 +9333,12 @@ snapshots: commander@11.1.0: {} + commander@14.0.3: {} + commander@2.20.3: {} + common-path-prefix@3.0.0: {} + commondir@1.0.1: {} compatx@0.2.0: {} @@ -8674,6 +9364,8 @@ snapshots: consola@3.4.2: {} + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.3: {} @@ -8685,7 +9377,6 @@ snapshots: copy-anything@3.0.5: dependencies: is-what: 4.1.16 - optional: true core-util-is@1.0.3: {} @@ -8874,6 +9565,8 @@ snapshots: depd@2.0.0: {} + dependency-graph@1.0.0: {} + dequal@2.0.3: {} destr@2.0.5: {} @@ -9112,6 +9805,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 @@ -9404,6 +10126,8 @@ snapshots: dependencies: fast-string-truncated-width: 1.2.1 + fast-uri@3.1.2: {} + fast-wrap-ansi@0.1.6: dependencies: fast-string-width: 1.1.0 @@ -9436,6 +10160,13 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-cache-directory@6.0.0: + dependencies: + common-path-prefix: 3.0.0 + pkg-dir: 8.0.0 + + find-up-simple@1.0.1: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -9716,6 +10447,10 @@ snapshots: ini@4.1.1: {} + injection-js@2.6.1: + dependencies: + tslib: 2.8.1 + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -9858,6 +10593,8 @@ snapshots: global-directory: 4.0.1 is-path-inside: 4.0.0 + is-interactive@2.0.0: {} + is-map@2.0.3: {} is-module@1.0.0: {} @@ -9919,6 +10656,8 @@ snapshots: dependencies: which-typed-array: 1.1.20 + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -9930,8 +10669,7 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-what@4.1.16: - optional: true + is-what@4.1.16: {} is-windows@1.0.2: {} @@ -10034,10 +10772,14 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -10082,7 +10824,6 @@ snapshots: mime: 1.6.0 needle: 3.5.0 source-map: 0.6.1 - optional: true levn@0.4.1: dependencies: @@ -10210,6 +10951,11 @@ snapshots: lodash@4.17.23: {} + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.1.2 + log-update@6.1.0: dependencies: ansi-escapes: 7.3.0 @@ -10370,6 +11116,35 @@ snapshots: sax: 1.5.0 optional: true + ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3): + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular/compiler-cli': 21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3) + '@rollup/plugin-json': 6.1.0(rollup@4.60.3) + '@rollup/wasm-node': 4.60.3 + ajv: 8.20.0 + ansi-colors: 4.1.3 + browserslist: 4.28.2 + chokidar: 5.0.0 + commander: 14.0.3 + dependency-graph: 1.0.0 + esbuild: 0.27.7 + find-cache-directory: 6.0.0 + injection-js: 2.6.1 + jsonc-parser: 3.3.1 + less: 4.6.4 + ora: 9.4.0 + piscina: 5.1.4 + postcss: 8.5.14 + rollup-plugin-dts: 6.4.1(rollup@4.60.3)(typescript@5.9.3) + rxjs: 7.8.2 + sass: 1.99.0 + tinyglobby: 0.2.16 + tslib: 2.8.1 + typescript: 5.9.3 + optionalDependencies: + rollup: 4.60.3 + nitropack@2.13.4(oxc-parser@0.128.0)(rolldown@1.0.0-rc.18): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 @@ -10774,6 +11549,17 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@9.4.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.2.0 + outdent@0.5.0: {} own-keys@1.0.1: @@ -10892,8 +11678,7 @@ snapshots: dependencies: callsites: 3.1.0 - parse-node-version@1.0.1: - optional: true + parse-node-version@1.0.1: {} parse5@8.0.1: dependencies: @@ -10934,6 +11719,14 @@ snapshots: pify@4.0.1: {} + piscina@5.1.4: + optionalDependencies: + '@napi-rs/nice': 1.1.1 + + pkg-dir@8.0.0: + dependencies: + find-up-simple: 1.0.1 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -11247,6 +12040,8 @@ snapshots: dependencies: redis-errors: 1.2.0 + reflect-metadata@0.2.2: {} + reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -11335,6 +12130,17 @@ snapshots: optionalDependencies: '@babel/code-frame': 7.29.0 + rollup-plugin-dts@6.4.1(rollup@4.60.3)(typescript@5.9.3): + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + convert-source-map: 2.0.0 + magic-string: 0.30.21 + rollup: 4.60.3 + typescript: 5.9.3 + optionalDependencies: + '@babel/code-frame': 7.29.0 + rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.18)(rollup@4.60.3): dependencies: open: 11.0.0 @@ -11384,6 +12190,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + sade@1.8.1: dependencies: mri: 1.2.0 @@ -11636,6 +12446,8 @@ snapshots: std-env@4.1.0: {} + stdin-discarder@0.3.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -11887,8 +12699,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 - tslib@2.8.1: - optional: true + tslib@2.8.1: {} type-check@0.4.0: dependencies: @@ -12454,6 +13265,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors@2.1.2: {} + youch-core@0.3.3: dependencies: '@poppinss/exception': 1.2.3 diff --git a/tsconfig.json b/tsconfig.json index 7a8a152a..181955cf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ { "path": "./packages/phone-mask-vue" }, { "path": "./packages/phone-mask-nuxt" }, { "path": "./packages/phone-mask-react" }, - { "path": "./packages/phone-mask-svelte" } + { "path": "./packages/phone-mask-svelte" }, + { "path": "./packages/phone-mask-angular" } ], "include": [], "exclude": ["node_modules", "dist"] From fb38844bbf5bbf17368ebdf07a88aec2fc2979e1 Mon Sep 17 00:00:00 2001 From: Stefan Popov Date: Sat, 9 May 2026 23:01:47 +0200 Subject: [PATCH 02/24] feat(angular): Improve lib arch, add demo project, lint & tests --- .gitignore | 2 + eslint.config.js | 29 + package.json | 1 + packages/phone-mask-angular/README.md | 10 +- packages/phone-mask-angular/angular.json | 31 + .../demo/src/app.component.ts | 437 +++ .../phone-mask-angular/demo/src/index.html | 12 + packages/phone-mask-angular/demo/src/main.ts | 4 + .../phone-mask-angular/demo/tsconfig.json | 16 + packages/phone-mask-angular/ng-package.json | 2 +- packages/phone-mask-angular/package.json | 13 +- .../phone-mask-angular/playwright.config.ts | 13 + .../phone-input/phone-input.component.html | 3 + .../src/phone-input/phone-input.component.ts | 442 +-- .../src/phone-mask.directive.ts | 289 +- .../phone-mask-angular/src/phone-mask.pipe.ts | 4 +- .../src/phone-mask.service.ts | 97 - packages/phone-mask-angular/src/public-api.ts | 15 +- .../internal/useCopyAction.service.ts | 64 + .../services/internal/useCountry.service.ts | 100 + .../internal/useCountrySelector.service.ts | 172 + .../services/internal/useFormatter.service.ts | 79 + .../internal/useInputHandlers.service.ts | 86 + .../src/services/internal/useTheme.service.ts | 38 + .../internal/useValidationHint.service.ts | 30 + .../src/services/usePhoneMask.service.ts | 144 + .../services/utility/useClipboard.service.ts | 61 + .../src/services/utility/useTimer.service.ts | 22 + packages/phone-mask-angular/src/types.ts | 13 + .../tests/e2e/PhoneInput.spec.ts | 37 + .../tests/e2e/UsePhoneMask.spec.ts | 27 + .../phone-mask-angular/tests/tsconfig.json | 16 + .../tests/unit/PhoneInput.test.ts | 130 + .../tests/unit/index.test.ts | 12 + .../tests/unit/phoneMaskDirective.test.ts | 94 + .../tests/unit/service-pipe.test.ts | 32 +- .../tests/unit/setup/angular.ts | 7 + .../tests/unit/setup/tools.ts | 21 + .../tests/unit/usePhoneMask.test.ts | 77 + .../phone-mask-angular/tsconfig.spec.json | 8 + packages/phone-mask-angular/vitest.config.ts | 18 +- pnpm-lock.yaml | 3040 ++++++++++++++++- 42 files changed, 5084 insertions(+), 664 deletions(-) create mode 100644 packages/phone-mask-angular/angular.json create mode 100644 packages/phone-mask-angular/demo/src/app.component.ts create mode 100644 packages/phone-mask-angular/demo/src/index.html create mode 100644 packages/phone-mask-angular/demo/src/main.ts create mode 100644 packages/phone-mask-angular/demo/tsconfig.json create mode 100644 packages/phone-mask-angular/playwright.config.ts delete mode 100644 packages/phone-mask-angular/src/phone-mask.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useCopyAction.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useCountry.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useCountrySelector.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useFormatter.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useTheme.service.ts create mode 100644 packages/phone-mask-angular/src/services/internal/useValidationHint.service.ts create mode 100644 packages/phone-mask-angular/src/services/usePhoneMask.service.ts create mode 100644 packages/phone-mask-angular/src/services/utility/useClipboard.service.ts create mode 100644 packages/phone-mask-angular/src/services/utility/useTimer.service.ts create mode 100644 packages/phone-mask-angular/tests/e2e/PhoneInput.spec.ts create mode 100644 packages/phone-mask-angular/tests/e2e/UsePhoneMask.spec.ts create mode 100644 packages/phone-mask-angular/tests/tsconfig.json create mode 100644 packages/phone-mask-angular/tests/unit/PhoneInput.test.ts create mode 100644 packages/phone-mask-angular/tests/unit/index.test.ts create mode 100644 packages/phone-mask-angular/tests/unit/phoneMaskDirective.test.ts create mode 100644 packages/phone-mask-angular/tests/unit/setup/angular.ts create mode 100644 packages/phone-mask-angular/tests/unit/setup/tools.ts create mode 100644 packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts create mode 100644 packages/phone-mask-angular/tsconfig.spec.json diff --git a/.gitignore b/.gitignore index 7f5dce7e..85ce3a4f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ .vscode .idea .claude +.angular dist +dist-demo build coverage node_modules diff --git a/eslint.config.js b/eslint.config.js index 7c28d507..59afabad 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -7,6 +7,7 @@ import svelte from 'eslint-plugin-svelte'; import svelteParser from 'svelte-eslint-parser'; import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; +import angular from 'angular-eslint'; import prettier from 'eslint-config-prettier'; import globals from 'globals'; @@ -14,6 +15,8 @@ const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts']; const JS_FILES = ['**/*.js', '**/*.mjs', '**/*.cjs', '**/*.jsx']; const REACT_FILES = ['packages/phone-mask-react/**/*.{ts,tsx,js,jsx}']; +const ANGULAR_TS_FILES = ['packages/phone-mask-angular/**/*.ts']; +const ANGULAR_TEMPLATE_FILES = ['packages/phone-mask-angular/**/*.html']; const VUE_SFC_FILES = ['packages/phone-mask-vue/**/*.vue', 'packages/phone-mask-nuxt/**/*.vue', 'demo/**/*.vue']; const VUE_TS_FILES = [ 'packages/phone-mask-vue/**/*.{ts,mts,cts}', @@ -57,6 +60,7 @@ export default [ ignores: [ '**/node_modules/**', '**/dist/**', + '**/dist-demo/**', '**/.nuxt/**', '**/.output/**', '**/.svelte-kit/**', @@ -150,6 +154,31 @@ export default [ } }, + ...angular.configs.tsRecommended.map((config) => ({ + ...config, + files: ANGULAR_TS_FILES + })), + + { + files: ANGULAR_TS_FILES, + processor: angular.processInlineTemplates, + rules: { + '@angular-eslint/no-input-rename': 'off', + '@angular-eslint/no-output-native': 'off', + '@angular-eslint/no-output-rename': 'off' + } + }, + + ...angular.configs.templateRecommended.map((config) => ({ + ...config, + files: ANGULAR_TEMPLATE_FILES + })), + + ...angular.configs.templateAccessibility.map((config) => ({ + ...config, + files: ANGULAR_TEMPLATE_FILES + })), + { files: VUE_TS_FILES, languageOptions: { diff --git a/package.json b/package.json index b2fab01f..cf7dc652 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@typescript-eslint/eslint-plugin": "^8.59.2", "@typescript-eslint/parser": "^8.59.2", "@vitest/coverage-v8": "^4.1.5", + "angular-eslint": "^21.3.1", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react": "^7.37.5", diff --git a/packages/phone-mask-angular/README.md b/packages/phone-mask-angular/README.md index a802ed69..8a53beef 100644 --- a/packages/phone-mask-angular/README.md +++ b/packages/phone-mask-angular/README.md @@ -104,11 +104,11 @@ You can also bind directive options directly: /> ``` -## Pipe And Service +## Pipe ```ts import { Component } from '@angular/core'; -import { PhoneMaskPipe, PhoneMaskService, providePhoneMask } from '@desource/phone-mask-angular'; +import { PhoneMaskPipe, providePhoneMask } from '@desource/phone-mask-angular'; @Component({ selector: 'app-phone-summary', @@ -120,11 +120,7 @@ import { PhoneMaskPipe, PhoneMaskService, providePhoneMask } from '@desource/pho

{{ '2025551234' | phoneMask: { mode: 'fullFormatted' } }}

` }) -export class PhoneSummaryComponent { - constructor(phoneMask: PhoneMaskService) { - phoneMask.getPhoneNumber('2025551234'); - } -} +export class PhoneSummaryComponent {} ``` ## Custom Templates diff --git a/packages/phone-mask-angular/angular.json b/packages/phone-mask-angular/angular.json new file mode 100644 index 00000000..7f7b3c8d --- /dev/null +++ b/packages/phone-mask-angular/angular.json @@ -0,0 +1,31 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "demo": { + "projectType": "application", + "root": "demo", + "sourceRoot": "demo/src", + "prefix": "demo", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "demo/src/main.ts", + "index": "demo/src/index.html", + "outputPath": "dist-demo", + "tsConfig": "demo/tsconfig.json", + "styles": [] + } + }, + "serve": { + "builder": "@angular/build:dev-server", + "options": { + "buildTarget": "demo:build" + } + } + } + } + } +} diff --git a/packages/phone-mask-angular/demo/src/app.component.ts b/packages/phone-mask-angular/demo/src/app.component.ts new file mode 100644 index 00000000..b91ad237 --- /dev/null +++ b/packages/phone-mask-angular/demo/src/app.component.ts @@ -0,0 +1,437 @@ +import { + ChangeDetectorRef, + Component, + ElementRef, + ViewEncapsulation, + inject, + signal, + viewChild, + type AfterViewInit, + type OnDestroy +} from '@angular/core'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { + PhoneInputComponent, + UseCountryService, + UseFormatterService, + UseInputHandlersService, + UsePhoneMaskService, + type PhoneInputSize, + type PhoneInputTheme +} from '../../src/public-api'; + +@Component({ + selector: 'demo-hook', + standalone: true, + template: ` +
+

UsePhoneMask Service

+
+ + + + +
+
+
Digits: {{ mask.digits() || '—' }}
+
Full: {{ mask.full() || '—' }}
+
Formatted: {{ mask.fullFormatted() || '—' }}
+
Valid: {{ mask.isComplete() ? 'Yes' : 'No' }}
+
+
+ `, + providers: [UseCountryService, UseFormatterService, UseInputHandlersService, UsePhoneMaskService] +}) +class DemoHookComponent implements AfterViewInit, OnDestroy { + protected readonly mask = inject(UsePhoneMaskService); + private readonly inputRef = viewChild>('phoneInput'); + private readonly value = signal(''); + + constructor() { + this.mask.configure({ + value: this.value, + detect: () => false, + onChange: (digits) => this.value.set(digits) + }); + this.mask.setCountry('GB'); + } + + ngAfterViewInit(): void { + queueMicrotask(() => this.mask.connect(this.inputRef()?.nativeElement ?? null)); + } + + ngOnDestroy(): void { + this.mask.connect(null); + } + + protected setCountry(country: CountryKey): void { + this.mask.setCountry(country); + } + + protected clear(): void { + this.mask.clear(); + this.value.set(''); + } +} + +@Component({ + selector: 'demo-root', + standalone: true, + imports: [PhoneInputComponent, DemoHookComponent], + encapsulation: ViewEncapsulation.None, + template: ` +
+
+

@desource/phone-mask-angular

+

Interactive component and service playground

+
+ +
+

Component Playground

+
+
+

Preview

+
+ +
+
Value: {{ digits() || '—' }}
+
+
+
+ +
+

Props

+
+ + + + + + + +
+ +
+ + + + + + + +
+ +
+ + + + + + + +
+
+
+
+ + +
+ `, + styles: [ + ` + :host { + display: block; + } + + * { + box-sizing: border-box; + } + + body { + margin: 0; + } + + .app-main { + min-height: 100vh; + width: 100vw; + background: #101014; + color: #ffffff; + font-family: Arial, sans-serif; + padding: 40px 20px; + } + + .app-header, + .section { + max-width: 1200px; + margin: 0 auto 24px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + background: rgba(255, 255, 255, 0.06); + padding: 28px; + } + + .app-header { + text-align: center; + } + + h1, + h2, + h3, + p { + margin-top: 0; + } + + .grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 24px; + } + + .panel, + .preview, + .meta { + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + background: rgba(0, 0, 0, 0.18); + padding: 20px; + } + + .meta { + display: grid; + gap: 6px; + margin-top: 16px; + font-size: 14px; + } + + .control-group, + .controls { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 16px; + } + + .row { + flex-direction: row; + flex-wrap: wrap; + } + + .label { + display: flex; + flex-direction: column; + gap: 6px; + font-size: 14px; + font-weight: 600; + } + + .checkbox-label { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + } + + .select, + .input, + .btn { + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + background: rgba(255, 255, 255, 0.08); + color: #ffffff; + font: inherit; + padding: 9px 12px; + } + + .select option { + color: #000000; + } + + .input::placeholder { + color: rgba(255, 255, 255, 0.45); + } + + .btn { + cursor: pointer; + font-weight: 600; + } + ` + ] +}) +export class AppComponent { + private readonly changeDetector = inject(ChangeDetectorRef); + private readonly playgroundPhone = viewChild('playgroundPhone'); + + protected readonly digits = signal(''); + protected readonly country = signal(undefined); + protected readonly locale = signal(undefined); + protected readonly detect = signal(true); + protected readonly showCopy = signal(true); + protected readonly showClear = signal(true); + protected readonly size = signal('normal'); + protected readonly theme = signal('dark'); + protected readonly withValidity = signal(true); + protected readonly disabled = signal(false); + protected readonly readonly = signal(false); + protected readonly searchPlaceholder = signal(''); + protected readonly noResultsText = signal(''); + protected readonly clearButtonLabel = signal(''); + protected readonly dropdownClass = signal(''); + protected readonly disableDefaultStyles = signal(false); + + protected setDetect(event: Event): void { + const checked = (event.target as HTMLInputElement).checked; + this.detect.set(checked); + if (checked) this.country.set(undefined); + } + + protected setDisabled(event: Event): void { + const checked = (event.target as HTMLInputElement).checked; + this.disabled.set(checked); + this.playgroundPhone()?.setDisabledState(checked); + this.changeDetector.detectChanges(); + } + + protected setCountryOption(event: Event): void { + const value = (event.target as HTMLSelectElement).value; + this.country.set((value as CountryKey) || undefined); + } + + protected setLocaleOption(event: Event): void { + this.locale.set((event.target as HTMLSelectElement).value || undefined); + } + + protected setSizeOption(event: Event): void { + this.size.set((event.target as HTMLSelectElement).value as PhoneInputSize); + } + + protected setThemeOption(event: Event): void { + this.theme.set((event.target as HTMLSelectElement).value as PhoneInputTheme); + } + + protected onCountryChange(_country: MaskFull): void {} + + protected onValidationChange(_valid: boolean): void {} +} diff --git a/packages/phone-mask-angular/demo/src/index.html b/packages/phone-mask-angular/demo/src/index.html new file mode 100644 index 00000000..1607259b --- /dev/null +++ b/packages/phone-mask-angular/demo/src/index.html @@ -0,0 +1,12 @@ + + + + + @desource/phone-mask-angular + + + + + + + diff --git a/packages/phone-mask-angular/demo/src/main.ts b/packages/phone-mask-angular/demo/src/main.ts new file mode 100644 index 00000000..326d8cdb --- /dev/null +++ b/packages/phone-mask-angular/demo/src/main.ts @@ -0,0 +1,4 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; + +void bootstrapApplication(AppComponent); diff --git a/packages/phone-mask-angular/demo/tsconfig.json b/packages/phone-mask-angular/demo/tsconfig.json new file mode 100644 index 00000000..a4e41d46 --- /dev/null +++ b/packages/phone-mask-angular/demo/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "composite": false, + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "outDir": "../dist/out-tsc/demo", + "types": [] + }, + "angularCompilerOptions": { + "compilationMode": "full", + "strictTemplates": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/phone-mask-angular/ng-package.json b/packages/phone-mask-angular/ng-package.json index 014f6e9b..94788762 100644 --- a/packages/phone-mask-angular/ng-package.json +++ b/packages/phone-mask-angular/ng-package.json @@ -1,5 +1,5 @@ { - "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "$schema": "./node_modules/ng-packagr/ng-package.schema.json", "dest": "dist", "allowedNonPeerDependencies": ["@desource/phone-mask", "tslib"], "lib": { diff --git a/packages/phone-mask-angular/package.json b/packages/phone-mask-angular/package.json index e34503fc..9122a2c8 100644 --- a/packages/phone-mask-angular/package.json +++ b/packages/phone-mask-angular/package.json @@ -56,14 +56,18 @@ "README.md" ], "scripts": { - "clean": "rimraf dist test-results coverage tsconfig.tsbuildinfo", + "clean": "rimraf dist dist-demo test-results coverage tsconfig.tsbuildinfo", "clean:modules": "rimraf node_modules", + "dev": "ng serve demo --port 5173", + "build:demo": "ng build demo", "build": "pnpm clean && pnpm build:lib && pnpm build:styles", "build:lib": "ng-packagr -p ng-package.json", "build:styles": "sass src/phone-input/phone-input.component.scss dist/assets/lib.css --style=compressed --no-source-map", + "lint": "eslint .", "typecheck": "ngc -p tsconfig.json --noEmit", "test:unit": "vitest run", - "test:unit:coverage": "vitest run --coverage.enabled --coverage.provider=v8 --coverage.reporter=lcov --coverage.reportsDirectory=coverage" + "test:unit:coverage": "vitest run --coverage.enabled --coverage.provider=v8 --coverage.reporter=lcov --coverage.reportsDirectory=coverage", + "test:e2e": "playwright test" }, "peerDependencies": { "@angular/common": "^19.0.0 || ^20.0.0 || ^21.0.0", @@ -75,11 +79,16 @@ "tslib": "^2.8.1" }, "devDependencies": { + "@angular/build": "^21.2.10", "@angular/common": "^21.2.12", "@angular/compiler": "^21.2.12", "@angular/compiler-cli": "^21.2.12", "@angular/core": "^21.2.12", + "@angular/cli": "^21.2.10", "@angular/forms": "^21.2.12", + "@angular/platform-browser": "^21.2.12", + "@analogjs/vite-plugin-angular": "^2.5.0", + "@testing-library/angular": "^19.2.1", "ng-packagr": "^21.2.3", "sass": "^1.99.0" } diff --git a/packages/phone-mask-angular/playwright.config.ts b/packages/phone-mask-angular/playwright.config.ts new file mode 100644 index 00000000..2d7a3f34 --- /dev/null +++ b/packages/phone-mask-angular/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/e2e', + use: { + baseURL: 'http://localhost:5173' + }, + webServer: { + command: 'pnpm dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI + } +}); diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.html b/packages/phone-mask-angular/src/phone-input/phone-input.component.html index 9fa2da9d..b26535ee 100644 --- a/packages/phone-mask-angular/src/phone-input/phone-input.component.html +++ b/packages/phone-mask-angular/src/phone-input/phone-input.component.html @@ -156,11 +156,14 @@ [id]="getOptionId(idx)" role="option" class="pi-option" + tabindex="-1" [class.is-focused]="idx === focusedIndex()" [class.is-selected]="c.id === country().id" [attr.aria-selected]="c.id === country().id" [title]="c.name" (click)="selectDropdownCountry(c.id)" + (keydown.enter)="selectDropdownCountry(c.id)" + (keydown.space)="$event.preventDefault(); selectDropdownCountry(c.id)" (mouseenter)="setFocusedIndex(idx)" > diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.ts b/packages/phone-mask-angular/src/phone-input/phone-input.component.ts index cc5a9ca6..e55040c8 100644 --- a/packages/phone-mask-angular/src/phone-input/phone-input.component.ts +++ b/packages/phone-mask-angular/src/phone-input/phone-input.component.ts @@ -1,12 +1,8 @@ -import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; +import { NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, - ChangeDetectorRef, Component, - DestroyRef, ElementRef, - Inject, - Optional, TemplateRef, ViewEncapsulation, booleanAttribute, @@ -14,6 +10,7 @@ import { contentChild, effect, forwardRef, + inject, input, model, output, @@ -22,38 +19,20 @@ import { viewChild } from '@angular/core'; import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'; -import { MasksFull, type CountryKey, type MaskFull } from '@desource/phone-mask'; -import { - bindCountryDropdownListeners, - createPhoneFormatter, - detectByGeoIp, - detectCountryFromLocale, - extractDigits, - filterCountries, - getCountry, - getNavigatorLang, - handleCountryButtonKeydown, - handleCountrySearchKeydown, - parseCountryCode, - positionCountryDropdown, - processBeforeInput, - processInput, - processKeydown, - processPaste, - scrollCountryOptionIntoView, - setCaret -} from '@desource/phone-mask/kit'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { extractDigits } from '@desource/phone-mask/kit'; import { PHONE_MASK_CONFIG } from '../config'; import { optionalBooleanAttribute } from '../internal/boolean-input'; -import { createPhoneNumber } from '../internal/formatting'; +import { UseCopyActionService } from '../services/internal/useCopyAction.service'; +import { UseCountryService } from '../services/internal/useCountry.service'; +import { UseCountrySelectorService } from '../services/internal/useCountrySelector.service'; +import { UseFormatterService } from '../services/internal/useFormatter.service'; +import { UseInputHandlersService } from '../services/internal/useInputHandlers.service'; +import { UseThemeService } from '../services/internal/useTheme.service'; +import { UseValidationHintService } from '../services/internal/useValidationHint.service'; +import { UseClipboardService } from '../services/utility/useClipboard.service'; import type { PhoneInputRef, PhoneMaskConfig, PhoneNumber, Size, Theme } from '../types'; -type IndexUpdate = number | ((index: number) => number); - -const HINT_DELAY_INPUT = 500; -const HINT_DELAY_ACTION = 300; -const COPY_RESET_DELAY = 1_800; - let nextDropdownId = 0; @Component({ @@ -65,6 +44,14 @@ let nextDropdownId = 0; encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [ + UseClipboardService, + UseCopyActionService, + UseCountryService, + UseCountrySelectorService, + UseFormatterService, + UseInputHandlersService, + UseThemeService, + UseValidationHintService, { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => PhoneInputComponent), @@ -116,51 +103,52 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef private readonly searchRef = viewChild>('searchInput'); private readonly selectorRef = viewChild>('selectorButton'); - private readonly countryCode = signal('US'); + private readonly countryState = inject(UseCountryService); + private readonly formatterState = inject(UseFormatterService); + private readonly inputHandlers = inject(UseInputHandlersService); + private readonly validationHint = inject(UseValidationHintService); + private readonly countrySelector = inject(UseCountrySelectorService); + private readonly copyAction = inject(UseCopyActionService); + private readonly themeState = inject(UseThemeService); + private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; + private readonly formDisabled = signal(false); - private readonly systemDark = signal(false); - private readonly showValidationHint = signal(false); - readonly copied = signal(false); + private onTouched: () => void = () => {}; + private onChange: (value: string) => void = () => {}; - readonly dropdownOpen = signal(false); - readonly search = signal(''); - readonly focusedIndex = signal(0); + readonly locale = this.countryState.locale; + readonly country = this.countryState.country; + readonly formatter = this.formatterState.formatter; + readonly digits = this.formatterState.digits; + readonly displayPlaceholder = this.formatterState.displayPlaceholder; + readonly displayValue = this.formatterState.displayValue; + readonly phoneData = this.formatterState.phoneData; + readonly full = this.formatterState.full; + readonly fullFormatted = this.formatterState.fullFormatted; + readonly isCompleteSignal = this.formatterState.isComplete; + readonly isEmpty = this.formatterState.isEmpty; + readonly shouldShowWarn = this.formatterState.shouldShowWarn; + readonly showValidationHint = this.validationHint.showValidationHint; + readonly dropdownOpen = this.countrySelector.dropdownOpen; + readonly search = this.countrySelector.search; + readonly focusedIndex = this.countrySelector.focusedIndex; + readonly filteredCountries = this.countrySelector.filteredCountries; + readonly hasDropdown = this.countrySelector.hasDropdown; + readonly themeClass = this.themeState.themeClass; + readonly copied = this.copyAction.copied; + readonly copyAriaLabel = this.copyAction.copyAriaLabel; + readonly copyButtonTitle = this.copyAction.copyButtonTitle; readonly dropdownId = ++nextDropdownId; readonly dropdownElementId = `pi-dropdown-${this.dropdownId}`; readonly listboxId = `pi-options-${this.dropdownId}`; - private validationTimer: ReturnType | undefined; - private copyTimer: ReturnType | undefined; - private themeMediaQuery: MediaQueryList | undefined; - private openByKeyboard = false; - private detectionKey = ''; - - private onTouched: () => void = () => {}; - private onChange: (value: string) => void = () => {}; - - readonly locale = computed(() => this.localeInput() || this.config.locale || getNavigatorLang()); - readonly detect = computed(() => this.detectInput() ?? this.config.detect ?? !this.config.country); - readonly country = computed(() => getCountry(this.countryCode(), this.locale())); - readonly countries = computed(() => MasksFull(this.locale())); - readonly formatter = computed(() => createPhoneFormatter(this.country())); - readonly digits = computed(() => extractDigits(this.value(), this.formatter().getMaxDigits())); - readonly displayPlaceholder = computed(() => this.formatter().getPlaceholder()); - readonly displayValue = computed(() => this.formatter().formatDisplay(this.digits())); - readonly phoneData = computed(() => createPhoneNumber(this.digits(), this.country(), this.formatter())); - readonly full = computed(() => this.phoneData().full); - readonly fullFormatted = computed(() => this.phoneData().fullFormatted); - readonly isCompleteSignal = computed(() => this.formatter().isComplete(this.digits())); - readonly isEmpty = computed(() => this.digits().length === 0); - readonly shouldShowWarn = computed(() => !this.isEmpty() && !this.isCompleteSignal()); readonly isDisabled = computed(() => this.disabledInput() || this.formDisabled()); readonly isReadOnly = computed(() => this.readOnlyInput()); readonly inactive = computed(() => this.isDisabled() || this.isReadOnly()); readonly incomplete = computed(() => this.showValidationHint() && this.shouldShowWarn()); readonly showCopyButton = computed(() => this.showCopy() && !this.isEmpty() && !this.isDisabled()); readonly showClearButton = computed(() => this.showClear() && !this.isEmpty() && !this.inactive()); - readonly filteredCountries = computed(() => filterCountries(this.countries(), this.search())); - readonly hasDropdown = computed(() => !this.countryInput() && this.countries().length > 1); readonly canOpenDropdown = computed(() => this.hasDropdown() && !this.inactive()); readonly renderDropdown = computed(() => this.hasDropdown() && (!this.inactive() || this.dropdownOpen())); readonly activeOptionId = computed(() => @@ -168,11 +156,6 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef ? this.getOptionId(this.focusedIndex()) : undefined ); - readonly themeClass = computed(() => { - const theme = this.theme(); - if (theme === 'auto') return this.systemDark() ? 'theme-dark' : 'theme-light'; - return `theme-${theme}`; - }); readonly rootClasses = computed(() => [ 'phone-input', @@ -195,83 +178,69 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef readonly actionsCount = computed( () => +this.showCopyButton() + +this.showClearButton() + (this.actionsBeforeTemplate() ? 1 : 0) ); - readonly copyAriaLabel = computed(() => (this.copied() ? 'Copied' : `Copy ${this.fullFormatted()}`)); - readonly copyButtonTitle = computed(() => (this.copied() ? 'Copied' : 'Copy phone number')); - - constructor( - private readonly destroyRef: DestroyRef, - private readonly cdr: ChangeDetectorRef, - @Optional() @Inject(DOCUMENT) private readonly document: Document | null, - @Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {} - ) { - this.countryCode.set(parseCountryCode(this.config.country, 'US')); - this.bindThemePreference(); - - this.destroyRef.onDestroy(() => { - if (this.validationTimer) clearTimeout(this.validationTimer); - if (this.copyTimer) clearTimeout(this.copyTimer); - this.themeMediaQuery?.removeEventListener('change', this.handleThemeChange); - }); - effect(() => { - const country = parseCountryCode(this.countryInput()); - - if (country && country !== this.countryCode()) { - queueMicrotask(() => this.selectCountry(country)); - } + constructor() { + this.countryState.configure({ + country: this.countryInput, + locale: this.localeInput, + detect: this.detectInput, + defaultDetect: !this.config.country, + onCountryChange: (country) => this.countryChange.emit(country) }); - effect(() => { - if (!this.detect() || this.countryInput() || this.config.country) return; - - const key = `${this.locale()}:${this.detect()}`; - if (this.detectionKey === key) return; - - this.detectionKey = key; - void this.detectCountry(); + this.formatterState.configure({ + country: this.country, + value: this.value, + onChange: (digits) => this.setValue(digits, true), + onPhoneChange: (phone) => this.phoneChange.emit(phone), + onValidationChange: (isComplete) => this.validationChange.emit(isComplete) }); - effect(() => { - const value = this.value(); - const digits = this.digits(); - - if (value !== digits) { - queueMicrotask(() => { - if (this.value() !== this.digits()) { - this.setValue(this.digits(), false); - } - }); - } + this.inputHandlers.configure({ + formatter: this.formatter, + digits: this.digits, + inactive: this.inactive, + onChange: (digits) => this.setValue(digits, true), + scheduleValidationHint: (delay) => this.validationHint.scheduleValidationHint(delay) }); - effect(() => { - this.phoneChange.emit(this.phoneData()); + this.countrySelector.configure({ + rootElement: () => this.rootRef()?.nativeElement, + dropdownElement: () => this.dropdownRef()?.nativeElement, + searchElement: () => this.searchRef()?.nativeElement, + selectorElement: () => this.selectorRef()?.nativeElement, + locale: this.locale, + inactive: this.inactive, + countryOption: this.countryInput, + onSelectCountry: (country) => this.selectCountry(country), + onAfterSelect: () => this.focus() }); - effect(() => { - this.validationChange.emit(this.isCompleteSignal()); + this.copyAction.configure({ + fullFormatted: this.fullFormatted, + liveElement: () => this.liveRef()?.nativeElement, + onCopy: (value) => this.copiedValue.emit(value) }); - effect(() => { - this.countryChange.emit(this.country()); - }); + this.themeState.configure({ theme: this.theme }); effect((onCleanup) => { - if (!this.dropdownOpen()) return; + if (typeof document === 'undefined') return; - queueMicrotask(() => { - this.updateDropdownPosition(); - if (this.openByKeyboard) this.focusSearch(); - }); + const dropdown = this.dropdownRef()?.nativeElement; + if (!dropdown || dropdown.parentElement === document.body) return; + + const placeholder = document.createComment('phone-input-dropdown'); + const parent = dropdown.parentNode; + parent?.insertBefore(placeholder, dropdown); + document.body.appendChild(dropdown); - onCleanup( - bindCountryDropdownListeners( - () => this.dropdownRef()?.nativeElement, - () => this.selectorRef()?.nativeElement, - () => this.closeDropdown(), - () => this.updateDropdownPosition() - ) - ); + onCleanup(() => { + if (placeholder.parentNode && dropdown.isConnected) { + placeholder.parentNode.insertBefore(dropdown, placeholder); + } + placeholder.remove(); + }); }); } @@ -301,16 +270,14 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef clear(): void { this.setValue('', true); - this.clearValidationHint(); + this.validationHint.clearValidationHint(); this.cleared.emit(); } selectCountry(country: CountryKey | string): boolean { - const parsed = parseCountryCode(country); - - if (!parsed) return false; + const updated = this.countryState.setCountry(country); - untracked(() => this.countryCode.set(parsed)); + if (!updated) return false; const maxDigits = this.formatter().getMaxDigits(); if (this.digits().length > maxDigits) { @@ -353,45 +320,24 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef } handleBeforeInput(event: Event): void { - processBeforeInput(event as InputEvent); + this.inputHandlers.handleBeforeInput(event); } handleInput(event: Event): void { - if (this.inactive()) return; - - const result = processInput(event, { formatter: this.formatter() }); - if (!result) return; - - this.setValue(result.newDigits, true); - this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); - this.scheduleValidationHint(HINT_DELAY_INPUT); + this.inputHandlers.handleInput(event); } handleKeydown(event: KeyboardEvent): void { - if (this.inactive()) return; - - const result = processKeydown(event, { digits: this.digits(), formatter: this.formatter() }); - if (!result) return; - - this.setValue(result.newDigits, true); - this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); - this.scheduleValidationHint(HINT_DELAY_ACTION); + this.inputHandlers.handleKeydown(event); } handlePaste(event: ClipboardEvent): void { - if (this.inactive()) return; - - const result = processPaste(event, { digits: this.digits(), formatter: this.formatter() }); - if (!result) return; - - this.setValue(result.newDigits, true); - this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); - this.scheduleValidationHint(HINT_DELAY_ACTION); + this.inputHandlers.handlePaste(event); } handleFocus(event: FocusEvent): void { - this.clearValidationHint(false); - this.closeDropdown(); + this.validationHint.clearValidationHint(false); + this.countrySelector.closeDropdown(); this.focused.emit(event); } @@ -401,69 +347,31 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef } handleSelectorPointerDown(event: PointerEvent): void { - this.openByKeyboard = event.pointerType === 'mouse'; + this.countrySelector.handleSelectorPointerDown(event); } handleSelectorKeydown(event: KeyboardEvent): void { - handleCountryButtonKeydown( - event, - this.dropdownOpen(), - () => { - this.openByKeyboard = true; - }, - () => this.focusSearch(), - () => this.openDropdown() - ); + this.countrySelector.handleSelectorKeydown(event); } handleSearchChange(event: Event): void { - this.search.set((event.target as HTMLInputElement).value); - this.focusedIndex.set(0); + this.countrySelector.handleSearchChange(event); } handleSearchKeydown(event: KeyboardEvent): void { - handleCountrySearchKeydown( - event, - this.focusedIndex(), - this.filteredCountries(), - (index) => this.setFocusedIndex(index), - (index) => this.scrollFocusedIntoView(index), - (country) => this.selectDropdownCountry(country.id) - ); + this.countrySelector.handleSearchKeydown(event); } toggleDropdown(): void { - if (this.inactive() || !this.hasDropdown()) return; - - if (this.dropdownOpen()) { - this.closeDropdown(); - } else { - this.openDropdown(); - } - } - - openDropdown(): void { - if (this.inactive() || !this.hasDropdown() || this.dropdownOpen()) return; - if (!this.dropdownRef()?.nativeElement || !this.selectorRef()?.nativeElement) return; - - this.updateDropdownPosition(); - this.focusedIndex.set(0); - this.dropdownOpen.set(true); + this.countrySelector.toggleDropdown(); } - closeDropdown(): void { - this.dropdownOpen.set(false); - this.resetDropdownState(); + setFocusedIndex(index: number): void { + this.countrySelector.setFocusedIndex(index); } - setFocusedIndex(index: IndexUpdate): void { - this.focusedIndex.update((current) => (typeof index === 'function' ? index(current) : index)); - } - - selectDropdownCountry(country: CountryKey | string): void { - this.selectCountry(country); - this.closeDropdown(); - this.focus(); + selectDropdownCountry(country: CountryKey): void { + this.countrySelector.selectCountry(country); } onClearClick(): void { @@ -472,20 +380,7 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef } async onCopyClick(): Promise { - const value = this.fullFormatted().trim(); - const success = await this.copyToClipboard(value); - - if (!success) return; - - this.copied.set(true); - this.copiedValue.emit(value); - this.announceToScreenReader('Phone number copied to clipboard'); - - if (this.copyTimer) clearTimeout(this.copyTimer); - this.copyTimer = setTimeout(() => { - this.copied.set(false); - this.cdr.markForCheck(); - }, COPY_RESET_DELAY); + await this.copyAction.onCopyClick(); } private setValue(value: string, emit: boolean): void { @@ -497,101 +392,4 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef this.onChange(nextDigits); } } - - private async detectCountry(): Promise { - const geoCountry = parseCountryCode(await detectByGeoIp()); - - if (geoCountry && this.selectCountry(geoCountry)) return; - - this.selectCountry(detectCountryFromLocale() ?? 'US'); - } - - private resetDropdownState(): void { - this.search.set(''); - this.focusedIndex.set(0); - this.openByKeyboard = false; - } - - private updateDropdownPosition(): void { - positionCountryDropdown(this.rootRef()?.nativeElement ?? null, this.dropdownRef()?.nativeElement ?? null); - } - - private focusSearch(): void { - setTimeout(() => this.searchRef()?.nativeElement.focus({ preventScroll: true })); - } - - private scrollFocusedIntoView(index: number): void { - setTimeout(() => scrollCountryOptionIntoView(this.dropdownRef()?.nativeElement, index)); - } - - private scheduleCaretUpdate(el: HTMLInputElement | null, digitIndex: number): void { - setTimeout(() => { - const position = this.formatter().getCaretPosition(digitIndex); - setCaret(el, position); - }); - } - - private clearValidationHint(hideHint = true): void { - if (hideHint) this.showValidationHint.set(false); - if (this.validationTimer) clearTimeout(this.validationTimer); - } - - private scheduleValidationHint(delay: number): void { - this.showValidationHint.set(false); - if (this.validationTimer) clearTimeout(this.validationTimer); - this.validationTimer = setTimeout(() => { - this.showValidationHint.set(true); - this.cdr.markForCheck(); - }, delay); - } - - private announceToScreenReader(message: string): void { - const live = this.liveRef()?.nativeElement; - if (!live) return; - - live.textContent = message; - setTimeout(() => { - live.textContent = ''; - }, COPY_RESET_DELAY); - } - - private async copyToClipboard(value: string): Promise { - if (!value) return false; - - try { - if (globalThis.navigator?.clipboard?.writeText) { - await globalThis.navigator.clipboard.writeText(value); - return true; - } - - if (!this.document?.body) return false; - - const textarea = this.document.createElement('textarea'); - textarea.value = value; - textarea.setAttribute('readonly', ''); - textarea.style.position = 'fixed'; - textarea.style.opacity = '0'; - this.document.body.appendChild(textarea); - textarea.select(); - const copied = this.document.execCommand('copy'); - textarea.remove(); - - return copied; - } catch { - return false; - } - } - - private bindThemePreference(): void { - this.themeMediaQuery = globalThis.matchMedia?.('(prefers-color-scheme: dark)') ?? undefined; - if (!this.themeMediaQuery) return; - - this.systemDark.set(this.themeMediaQuery.matches); - this.themeMediaQuery.addEventListener('change', this.handleThemeChange); - } - - private readonly handleThemeChange = (event: MediaQueryListEvent) => { - this.systemDark.set(event.matches); - this.cdr.markForCheck(); - }; } diff --git a/packages/phone-mask-angular/src/phone-mask.directive.ts b/packages/phone-mask-angular/src/phone-mask.directive.ts index 5d4ee610..8ed066bc 100644 --- a/packages/phone-mask-angular/src/phone-mask.directive.ts +++ b/packages/phone-mask-angular/src/phone-mask.directive.ts @@ -1,37 +1,30 @@ import { - DestroyRef, Directive, + DestroyRef, ElementRef, - Inject, - Optional, effect, forwardRef, + inject, input, model, output, - signal, untracked } from '@angular/core'; import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'; import type { CountryKey, MaskFull } from '@desource/phone-mask'; -import { - createPhoneFormatter, - detectByGeoIp, - detectCountryFromLocale, - extractDigits, - getCountry, - getNavigatorLang, - parseCountryCode, - processBeforeInput, - processInput, - processKeydown, - processPaste, - setCaret -} from '@desource/phone-mask/kit'; +import { extractDigits } from '@desource/phone-mask/kit'; import { PHONE_MASK_CONFIG } from './config'; import { optionalBooleanAttribute } from './internal/boolean-input'; -import { createPhoneNumber } from './internal/formatting'; -import type { PhoneMaskConfig, PhoneMaskDirectiveInput, PhoneMaskDirectiveOptions, PhoneNumber } from './types'; +import { UseCountryService } from './services/internal/useCountry.service'; +import { UseFormatterService } from './services/internal/useFormatter.service'; +import { UseInputHandlersService } from './services/internal/useInputHandlers.service'; +import type { + DirectiveHTMLInputElement, + PhoneMaskConfig, + PhoneMaskDirectiveInput, + PhoneMaskDirectiveOptions, + PhoneNumber +} from './types'; function parseOptions(value: PhoneMaskDirectiveInput): PhoneMaskDirectiveOptions { if (typeof value === 'string') return { country: value }; @@ -40,10 +33,13 @@ function parseOptions(value: PhoneMaskDirectiveInput): PhoneMaskDirectiveOptions } @Directive({ - selector: 'input[phoneMask]', + selector: '[phoneMask]', standalone: true, exportAs: 'phoneMask', providers: [ + UseCountryService, + UseFormatterService, + UseInputHandlersService, { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => PhoneMaskDirective), @@ -64,10 +60,15 @@ export class PhoneMaskDirective implements ControlValueAccessor { readonly phoneChange = output({ alias: 'phoneMaskChange' }); readonly countryChange = output({ alias: 'phoneMaskCountryChange' }); - private readonly countryCode = signal('US'); - private readonly disabled = signal(false); - private detectionKey = ''; + private readonly elementRef = inject>(ElementRef); + private readonly destroyRef = inject(DestroyRef); + private readonly countryState = inject(UseCountryService); + private readonly formatterState = inject(UseFormatterService); + private readonly inputHandlers = inject(UseInputHandlersService); + private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; + private readonly inputElement = this.elementRef.nativeElement as DirectiveHTMLInputElement; + private readonly isInput = this.inputElement.tagName === 'INPUT'; private onTouched: () => void = () => {}; private onChange: (value: string) => void = () => {}; @@ -82,94 +83,109 @@ export class PhoneMaskDirective implements ControlValueAccessor { }; }; - private readonly locale = () => this.options().locale || getNavigatorLang(); - private readonly country = () => getCountry(this.countryCode(), this.locale()); - private readonly formatter = () => createPhoneFormatter(this.country()); - private readonly digits = () => extractDigits(this.value(), this.formatter().getMaxDigits()); - private readonly phoneData = () => createPhoneNumber(this.digits(), this.country(), this.formatter()); - - constructor( - private readonly elementRef: ElementRef, - private readonly destroyRef: DestroyRef, - @Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {} - ) { - const el = this.elementRef.nativeElement; - - this.countryCode.set(parseCountryCode(this.config.country, 'US')); - - el.setAttribute('type', 'tel'); - el.setAttribute('inputmode', 'tel'); - el.setAttribute('placeholder', ''); - - const beforeInputHandler = (event: InputEvent) => this.handleBeforeInput(event); - const inputHandler = (event: Event) => this.handleInput(event); - const keydownHandler = (event: KeyboardEvent) => this.handleKeydown(event); - const pasteHandler = (event: ClipboardEvent) => this.handlePaste(event); - const blurHandler = () => this.onTouched(); - - el.addEventListener('beforeinput', beforeInputHandler); - el.addEventListener('input', inputHandler); - el.addEventListener('keydown', keydownHandler); - el.addEventListener('paste', pasteHandler); - el.addEventListener('blur', blurHandler); + readonly locale = this.countryState.locale; + readonly country = this.countryState.country; + readonly formatter = this.formatterState.formatter; + readonly digits = this.formatterState.digits; + readonly full = this.formatterState.full; + readonly fullFormatted = this.formatterState.fullFormatted; + + constructor() { + if (!this.isInput) { + console.warn('[phoneMask] Directive can only be used on input elements'); + return; + } - this.destroyRef.onDestroy(() => { - el.removeEventListener('beforeinput', beforeInputHandler); - el.removeEventListener('input', inputHandler); - el.removeEventListener('keydown', keydownHandler); - el.removeEventListener('paste', pasteHandler); - el.removeEventListener('blur', blurHandler); + this.inputElement.setAttribute('type', 'tel'); + this.inputElement.setAttribute('inputmode', 'tel'); + this.inputElement.setAttribute('placeholder', ''); + + this.countryState.configure({ + country: () => this.options().country, + locale: () => this.options().locale, + detect: () => this.options().detect, + onCountryChange: (country) => { + this.countryChange.emit(country); + this.options().onCountryChange?.(country); + } }); - effect(() => { - const options = this.options(); - const parsed = parseCountryCode(options.country); - - if (parsed && parsed !== this.countryCode()) { - queueMicrotask(() => this.setCountry(parsed)); + this.formatterState.configure({ + country: this.country, + value: this.value, + onChange: (digits) => this.setValue(digits, true), + onPhoneChange: (phone) => { + this.phoneChange.emit(phone); + this.options().onChange?.(phone); } }); - effect(() => { - const options = this.options(); - - if (!options.detect || options.country) return; - - const key = `${this.locale()}:${options.detect}`; - if (this.detectionKey === key) return; - - this.detectionKey = key; - void this.detectCountry(); + this.inputHandlers.configure({ + formatter: this.formatter, + digits: this.digits, + onChange: (digits) => this.setValue(digits, true) }); - effect(() => { - const el = this.elementRef.nativeElement; - const formatter = this.formatter(); - const digits = this.digits(); - - el.value = formatter.formatDisplay(digits); - el.placeholder = formatter.getPlaceholder(); - }); + const beforeInputHandler = (event: Event) => this.inputHandlers.handleBeforeInput(event); + const inputHandler = (event: Event) => this.inputHandlers.handleInput(event); + const keydownHandler = (event: KeyboardEvent) => this.inputHandlers.handleKeydown(event); + const pasteHandler = (event: ClipboardEvent) => this.inputHandlers.handlePaste(event); + const blurHandler = () => this.onTouched(); - effect(() => { - const phone = this.phoneData(); - const options = this.options(); + this.inputElement.addEventListener('beforeinput', beforeInputHandler); + this.inputElement.addEventListener('input', inputHandler); + this.inputElement.addEventListener('keydown', keydownHandler); + this.inputElement.addEventListener('paste', pasteHandler); + this.inputElement.addEventListener('blur', blurHandler); - this.phoneChange.emit(phone); - options.onChange?.(phone); + this.destroyRef.onDestroy(() => { + this.inputElement.removeEventListener('beforeinput', beforeInputHandler); + this.inputElement.removeEventListener('input', inputHandler); + this.inputElement.removeEventListener('keydown', keydownHandler); + this.inputElement.removeEventListener('paste', pasteHandler); + this.inputElement.removeEventListener('blur', blurHandler); + delete this.inputElement.__phoneMaskState; }); effect(() => { - const country = this.country(); - const options = this.options(); + this.inputElement.value = this.formatterState.displayValue(); + this.inputElement.placeholder = this.formatterState.displayPlaceholder(); + + let state = this.inputElement.__phoneMaskState; + + if (!state) { + state = { + country: this.country(), + formatter: this.formatter(), + digits: this.digits(), + locale: this.locale(), + options: this.options(), + setCountry: (code) => { + const updated = this.selectCountry(code); + if (!updated || !this.inputElement.__phoneMaskState) return false; + + this.inputElement.__phoneMaskState.country = this.country(); + this.inputElement.__phoneMaskState.formatter = this.formatter(); + this.inputElement.__phoneMaskState.digits = this.digits(); + this.inputElement.__phoneMaskState.locale = this.locale(); + this.inputElement.__phoneMaskState.options = this.options(); + return true; + } + }; + } - this.countryChange.emit(country); - options.onCountryChange?.(country); + state.country = this.country(); + state.formatter = this.formatter(); + state.digits = this.digits(); + state.locale = this.locale(); + state.options = this.options(); + this.inputElement.__phoneMaskState = state; }); } writeValue(value: string | number | null | undefined): void { - this.setValue(extractDigits(String(value ?? ''), this.formatter().getMaxDigits()), false); + if (!this.isInput) return; + this.setValue(String(value ?? ''), false); } registerOnChange(fn: (value: string) => void): void { @@ -181,8 +197,8 @@ export class PhoneMaskDirective implements ControlValueAccessor { } setDisabledState(isDisabled: boolean): void { - this.disabled.set(isDisabled); - this.elementRef.nativeElement.disabled = isDisabled; + if (!this.isInput) return; + this.inputElement.disabled = isDisabled; } clear(): void { @@ -190,7 +206,16 @@ export class PhoneMaskDirective implements ControlValueAccessor { } selectCountry(country: CountryKey | string): boolean { - return this.setCountry(country); + const updated = this.countryState.setCountry(country); + + if (!updated) return false; + + const maxDigits = this.formatter().getMaxDigits(); + if (this.digits().length > maxDigits) { + this.setValue(this.digits().slice(0, maxDigits), true); + } + + return true; } getDigits(): string { @@ -198,11 +223,11 @@ export class PhoneMaskDirective implements ControlValueAccessor { } getFullNumber(): string { - return this.phoneData().full; + return this.full(); } getFullFormattedNumber(): string { - return this.phoneData().fullFormatted; + return this.fullFormatted(); } isComplete(): boolean { @@ -222,70 +247,4 @@ export class PhoneMaskDirective implements ControlValueAccessor { this.onChange(nextDigits); } } - - private setCountry(country: CountryKey | string | null | undefined): boolean { - const parsed = parseCountryCode(country); - - if (!parsed) return false; - - untracked(() => this.countryCode.set(parsed)); - - const digits = this.digits(); - const maxDigits = this.formatter().getMaxDigits(); - - if (digits.length > maxDigits) { - this.setValue(digits.slice(0, maxDigits), true); - } - - return true; - } - - private async detectCountry(): Promise { - const geoCountry = parseCountryCode(await detectByGeoIp()); - - if (geoCountry && this.setCountry(geoCountry)) return; - - this.setCountry(detectCountryFromLocale()); - } - - private scheduleCaretUpdate(el: HTMLInputElement | null, digitIndex: number): void { - setTimeout(() => { - const position = this.formatter().getCaretPosition(digitIndex); - setCaret(el, position); - }); - } - - private handleBeforeInput(event: InputEvent): void { - processBeforeInput(event); - } - - private handleInput(event: Event): void { - if (this.disabled()) return; - - const result = processInput(event, { formatter: this.formatter() }); - if (!result) return; - - this.setValue(result.newDigits, true); - this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); - } - - private handleKeydown(event: KeyboardEvent): void { - if (this.disabled()) return; - - const result = processKeydown(event, { digits: this.digits(), formatter: this.formatter() }); - if (!result) return; - - this.setValue(result.newDigits, true); - this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); - } - - private handlePaste(event: ClipboardEvent): void { - if (this.disabled()) return; - - const result = processPaste(event, { digits: this.digits(), formatter: this.formatter() }); - if (!result) return; - - this.setValue(result.newDigits, true); - this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); - } } diff --git a/packages/phone-mask-angular/src/phone-mask.pipe.ts b/packages/phone-mask-angular/src/phone-mask.pipe.ts index 94ce7709..48428de9 100644 --- a/packages/phone-mask-angular/src/phone-mask.pipe.ts +++ b/packages/phone-mask-angular/src/phone-mask.pipe.ts @@ -1,4 +1,4 @@ -import { Inject, Optional, Pipe, type PipeTransform } from '@angular/core'; +import { Pipe, inject, type PipeTransform } from '@angular/core'; import type { CountryKey } from '@desource/phone-mask'; import { PHONE_MASK_CONFIG } from './config'; import { formatPhoneValue } from './internal/formatting'; @@ -14,7 +14,7 @@ function isFormatOptions(value: unknown): value is PhoneMaskFormatOptions { pure: true }) export class PhoneMaskPipe implements PipeTransform { - constructor(@Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {}) {} + private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; transform( value: string | number | null | undefined, diff --git a/packages/phone-mask-angular/src/phone-mask.service.ts b/packages/phone-mask-angular/src/phone-mask.service.ts deleted file mode 100644 index 95146ffe..00000000 --- a/packages/phone-mask-angular/src/phone-mask.service.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Inject, Injectable, Optional } from '@angular/core'; -import type { CountryKey, MaskFull } from '@desource/phone-mask'; -import { createPhoneFormatter, extractDigits, type FormatterHelpers } from '@desource/phone-mask/kit'; -import { PHONE_MASK_CONFIG } from './config'; -import { - createPhoneNumber, - createPhoneState, - formatPhoneValue, - resolveCountry, - resolveLocale -} from './internal/formatting'; -import type { - PhoneMaskConfig, - PhoneMaskFormatMode, - PhoneMaskFormatOptions, - PhoneMaskState, - PhoneNumber -} from './types'; - -function isMaskFull(value: unknown): value is MaskFull { - return !!value && typeof value === 'object' && 'code' in value && 'mask' in value; -} - -function isFormatOptions(value: unknown): value is PhoneMaskFormatOptions { - return !!value && typeof value === 'object'; -} - -@Injectable({ - providedIn: 'root' -}) -export class PhoneMaskService { - constructor(@Optional() @Inject(PHONE_MASK_CONFIG) private readonly config: PhoneMaskConfig = {}) {} - - getLocale(locale?: string): string { - return resolveLocale(locale, this.config); - } - - getCountry(country?: CountryKey | string | null, locale?: string): MaskFull { - return resolveCountry(country, locale, this.config); - } - - createFormatter(country?: CountryKey | string | MaskFull | null, locale?: string): FormatterHelpers { - if (isMaskFull(country)) return createPhoneFormatter(country); - return createPhoneFormatter(this.getCountry(country as CountryKey | string | null | undefined, locale)); - } - - getDigits(value: string | number | null | undefined, country?: CountryKey | string | MaskFull | null): string { - const formatter = this.createFormatter(country); - return extractDigits(String(value ?? ''), formatter.getMaxDigits()); - } - - getPhoneNumber( - value: string | number | null | undefined, - country?: CountryKey | string | MaskFull | null, - locale?: string - ): PhoneNumber { - const resolvedCountry = isMaskFull(country) - ? country - : this.getCountry(country as CountryKey | string | null | undefined, locale); - const formatter = createPhoneFormatter(resolvedCountry); - const digits = extractDigits(String(value ?? ''), formatter.getMaxDigits()); - - return createPhoneNumber(digits, resolvedCountry, formatter); - } - - getState( - value: string | number | null | undefined, - country?: CountryKey | string | MaskFull | null, - locale?: string - ): PhoneMaskState { - const resolvedCountry = isMaskFull(country) - ? country - : this.getCountry(country as CountryKey | string | null | undefined, locale); - return createPhoneState(value, resolvedCountry); - } - - format( - value: string | number | null | undefined, - countryOrOptions?: CountryKey | string | PhoneMaskFormatOptions, - mode: PhoneMaskFormatMode = 'display', - locale?: string - ): string { - const options: PhoneMaskFormatOptions = isFormatOptions(countryOrOptions) - ? countryOrOptions - : { country: countryOrOptions as CountryKey | string | undefined, mode, locale }; - - return formatPhoneValue(value, options, this.config); - } - - isComplete( - value: string | number | null | undefined, - country?: CountryKey | string | MaskFull | null, - locale?: string - ): boolean { - return this.getState(value, country, locale).isComplete; - } -} diff --git a/packages/phone-mask-angular/src/public-api.ts b/packages/phone-mask-angular/src/public-api.ts index 4d1cc74b..0d230d0e 100644 --- a/packages/phone-mask-angular/src/public-api.ts +++ b/packages/phone-mask-angular/src/public-api.ts @@ -2,13 +2,24 @@ export { PHONE_MASK_CONFIG, providePhoneMask } from './config'; export { PhoneInputComponent } from './phone-input/phone-input.component'; export { PhoneMaskDirective } from './phone-mask.directive'; export { PhoneMaskPipe } from './phone-mask.pipe'; -export { PhoneMaskService } from './phone-mask.service'; +export { UsePhoneMaskService } from './services/usePhoneMask.service'; +export { UseCopyActionService } from './services/internal/useCopyAction.service'; +export { UseCountryService } from './services/internal/useCountry.service'; +export { UseCountrySelectorService } from './services/internal/useCountrySelector.service'; +export { UseFormatterService } from './services/internal/useFormatter.service'; +export { UseInputHandlersService } from './services/internal/useInputHandlers.service'; +export { UseThemeService } from './services/internal/useTheme.service'; +export { UseValidationHintService } from './services/internal/useValidationHint.service'; +export { UseClipboardService } from './services/utility/useClipboard.service'; +export { UseTimerService } from './services/utility/useTimer.service'; export type { + DirectiveHTMLInputElement, PhoneInputRef, PhoneMaskConfig, PhoneMaskDirectiveInput, PhoneMaskDirectiveOptions, + PhoneMaskDirectiveState, PhoneMaskFormatMode, PhoneMaskFormatOptions, PhoneMaskState, @@ -17,6 +28,8 @@ export type { Theme as PhoneInputTheme } from './types'; +export type { UsePhoneMaskOptions } from './services/usePhoneMask.service'; + export type { CountryKey as PCountryKey, MaskBase as PMaskBase, diff --git a/packages/phone-mask-angular/src/services/internal/useCopyAction.service.ts b/packages/phone-mask-angular/src/services/internal/useCopyAction.service.ts new file mode 100644 index 00000000..f8239b43 --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useCopyAction.service.ts @@ -0,0 +1,64 @@ +import { ChangeDetectorRef, DestroyRef, Injectable, computed, inject, signal } from '@angular/core'; +import { UseClipboardService } from '../utility/useClipboard.service'; + +interface UseCopyActionOptions { + fullFormatted: () => string; + liveElement?: () => HTMLElement | null | undefined; + onCopy?: (value: string) => void; +} + +const DELAY = 1_800; + +@Injectable() +export class UseCopyActionService { + private readonly clipboard = inject(UseClipboardService); + private readonly cdr = inject(ChangeDetectorRef); + private readonly destroyRef = inject(DestroyRef); + private fullFormattedGetter = () => ''; + private liveElementGetter: () => HTMLElement | null | undefined = () => undefined; + private onCopy: ((value: string) => void) | undefined; + private liveTimer: ReturnType | undefined; + + readonly copied = signal(false); + readonly copyAriaLabel = computed(() => (this.copied() ? 'Copied' : `Copy ${this.fullFormattedGetter()}`)); + readonly copyButtonTitle = computed(() => (this.copied() ? 'Copied' : 'Copy phone number')); + + constructor() { + this.destroyRef.onDestroy(() => { + if (this.liveTimer) clearTimeout(this.liveTimer); + }); + } + + configure(options: UseCopyActionOptions): void { + this.fullFormattedGetter = options.fullFormatted; + this.liveElementGetter = options.liveElement ?? this.liveElementGetter; + this.onCopy = options.onCopy; + } + + async onCopyClick(): Promise { + const value = this.fullFormattedGetter().trim(); + const success = await this.clipboard.copy(value, DELAY); + + if (!success) return; + + this.copied.set(true); + this.onCopy?.(value); + this.announceToScreenReader('Phone number copied to clipboard'); + + setTimeout(() => { + this.copied.set(false); + this.cdr.markForCheck(); + }, DELAY); + } + + private announceToScreenReader(message: string): void { + const live = this.liveElementGetter(); + if (!live) return; + + live.textContent = message; + if (this.liveTimer) clearTimeout(this.liveTimer); + this.liveTimer = setTimeout(() => { + live.textContent = ''; + }, DELAY); + } +} diff --git a/packages/phone-mask-angular/src/services/internal/useCountry.service.ts b/packages/phone-mask-angular/src/services/internal/useCountry.service.ts new file mode 100644 index 00000000..a3ed5630 --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useCountry.service.ts @@ -0,0 +1,100 @@ +import { Injectable, Injector, computed, effect, inject, signal, untracked } from '@angular/core'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { + detectByGeoIp, + detectCountryFromLocale, + getCountry, + getNavigatorLang, + parseCountryCode +} from '@desource/phone-mask/kit'; +import { PHONE_MASK_CONFIG } from '../../config'; +import type { PhoneMaskConfig } from '../../types'; + +interface UseCountryOptions { + country?: () => CountryKey | string | null | undefined; + locale?: () => string | undefined; + detect?: () => boolean | undefined; + defaultDetect?: boolean; + onCountryChange?: (country: MaskFull) => void; +} + +@Injectable() +export class UseCountryService { + private readonly injector = inject(Injector); + private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; + private countryOption: () => CountryKey | string | null | undefined = () => undefined; + private localeOption: () => string | undefined = () => undefined; + private detectOption: () => boolean | undefined = () => undefined; + private defaultDetect = false; + private onCountryChange: ((country: MaskFull) => void) | undefined; + private detectionKey = ''; + private configured = false; + + readonly countryCode = signal('US'); + readonly locale = computed(() => this.localeOption() || this.config.locale || getNavigatorLang()); + readonly detect = computed(() => this.detectOption() ?? this.config.detect ?? this.defaultDetect); + readonly country = computed(() => getCountry(this.countryCode(), this.locale())); + + constructor() { + this.countryCode.set(parseCountryCode(this.config.country, 'US')); + } + + configure(options: UseCountryOptions = {}): void { + if (this.configured) return; + this.configured = true; + + this.countryOption = options.country ?? this.countryOption; + this.localeOption = options.locale ?? this.localeOption; + this.detectOption = options.detect ?? this.detectOption; + this.defaultDetect = options.defaultDetect ?? this.defaultDetect; + this.onCountryChange = options.onCountryChange; + + effect( + () => { + const parsed = parseCountryCode(this.countryOption() ?? this.config.country); + + if (parsed && parsed !== this.countryCode()) { + queueMicrotask(() => this.setCountry(parsed)); + } + }, + { injector: this.injector } + ); + + effect( + () => { + if (!this.detect() || this.countryOption() || this.config.country) return; + + const key = `${this.locale()}:${this.detect()}`; + if (this.detectionKey === key) return; + + this.detectionKey = key; + void this.detectCountry(); + }, + { injector: this.injector } + ); + + effect( + () => { + this.onCountryChange?.(this.country()); + }, + { injector: this.injector } + ); + } + + setCountry(countryCode?: CountryKey | string | null): boolean { + const parsed = parseCountryCode(countryCode); + + if (!parsed) return false; + + untracked(() => this.countryCode.set(parsed)); + return true; + } + + private async detectCountry(): Promise { + const geoCountry = parseCountryCode(await detectByGeoIp()); + + if (geoCountry && this.setCountry(geoCountry)) return; + + this.setCountry(detectCountryFromLocale()); + } +} diff --git a/packages/phone-mask-angular/src/services/internal/useCountrySelector.service.ts b/packages/phone-mask-angular/src/services/internal/useCountrySelector.service.ts new file mode 100644 index 00000000..46e1c4dc --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useCountrySelector.service.ts @@ -0,0 +1,172 @@ +import { Injectable, Injector, computed, effect, inject, signal } from '@angular/core'; +import { MasksFull, type CountryKey, type MaskFull } from '@desource/phone-mask'; +import { + bindCountryDropdownListeners, + filterCountries, + handleCountryButtonKeydown, + handleCountrySearchKeydown, + positionCountryDropdown, + scrollCountryOptionIntoView +} from '@desource/phone-mask/kit'; + +type IndexUpdate = number | ((index: number) => number); + +interface UseCountrySelectorOptions { + rootElement: () => HTMLElement | null | undefined; + dropdownElement: () => HTMLElement | null | undefined; + searchElement: () => HTMLInputElement | null | undefined; + selectorElement: () => HTMLButtonElement | null | undefined; + locale: () => string; + inactive?: () => boolean; + countryOption?: () => string | undefined; + onSelectCountry: (code: CountryKey) => void; + onAfterSelect?: () => void; +} + +@Injectable() +export class UseCountrySelectorService { + private readonly injector = inject(Injector); + private rootElementGetter: () => HTMLElement | null | undefined = () => undefined; + private dropdownElementGetter: () => HTMLElement | null | undefined = () => undefined; + private searchElementGetter: () => HTMLInputElement | null | undefined = () => undefined; + private selectorElementGetter: () => HTMLButtonElement | null | undefined = () => undefined; + private localeGetter = () => 'en'; + private inactiveGetter = () => false; + private countryOptionGetter: () => string | undefined = () => undefined; + private onSelectCountry: (code: CountryKey) => void = () => {}; + private onAfterSelect: (() => void) | undefined; + private openByKeyboard = false; + private configured = false; + + readonly dropdownOpen = signal(false); + readonly search = signal(''); + readonly focusedIndex = signal(0); + readonly countries = computed(() => MasksFull(this.localeGetter())); + readonly filteredCountries = computed(() => filterCountries(this.countries(), this.search())); + readonly hasDropdown = computed(() => !this.countryOptionGetter() && this.countries().length > 1); + + configure(options: UseCountrySelectorOptions): void { + if (this.configured) return; + this.configured = true; + + this.rootElementGetter = options.rootElement; + this.dropdownElementGetter = options.dropdownElement; + this.searchElementGetter = options.searchElement; + this.selectorElementGetter = options.selectorElement; + this.localeGetter = options.locale; + this.inactiveGetter = options.inactive ?? this.inactiveGetter; + this.countryOptionGetter = options.countryOption ?? this.countryOptionGetter; + this.onSelectCountry = options.onSelectCountry; + this.onAfterSelect = options.onAfterSelect; + + effect( + () => { + if ((this.inactiveGetter() || !this.hasDropdown()) && this.dropdownOpen()) { + this.closeDropdown(); + } + }, + { injector: this.injector } + ); + + effect( + (onCleanup) => { + if (!this.dropdownOpen()) return; + + queueMicrotask(() => { + this.updateDropdownPosition(); + if (this.openByKeyboard) this.focusSearch(); + }); + + onCleanup( + bindCountryDropdownListeners( + () => this.dropdownElementGetter(), + () => this.selectorElementGetter(), + () => this.closeDropdown(), + () => this.updateDropdownPosition() + ) + ); + }, + { injector: this.injector } + ); + } + + openDropdown(): void { + if (this.inactiveGetter() || !this.hasDropdown() || this.dropdownOpen()) return; + if (!this.dropdownElementGetter() || !this.selectorElementGetter()) return; + + this.updateDropdownPosition(); + this.focusedIndex.set(0); + this.dropdownOpen.set(true); + } + + closeDropdown(): void { + this.dropdownOpen.set(false); + this.resetDropdownState(); + } + + toggleDropdown(): void { + if (this.inactiveGetter() || !this.hasDropdown()) return; + if (this.dropdownOpen()) this.closeDropdown(); + else this.openDropdown(); + } + + selectCountry(code: CountryKey): void { + this.onSelectCountry(code); + this.closeDropdown(); + this.onAfterSelect?.(); + } + + setFocusedIndex(index: IndexUpdate): void { + this.focusedIndex.update((current) => (typeof index === 'function' ? index(current) : index)); + } + + handleSearchChange(event: Event): void { + this.search.set((event.target as HTMLInputElement).value); + this.focusedIndex.set(0); + } + + handleSearchKeydown(event: KeyboardEvent): void { + handleCountrySearchKeydown( + event, + this.focusedIndex(), + this.filteredCountries(), + (index) => this.setFocusedIndex(index), + (index) => this.scrollFocusedIntoView(index), + (country: MaskFull) => this.selectCountry(country.id) + ); + } + + handleSelectorPointerDown(event: PointerEvent): void { + this.openByKeyboard = event.pointerType === 'mouse'; + } + + handleSelectorKeydown(event: KeyboardEvent): void { + handleCountryButtonKeydown( + event, + this.dropdownOpen(), + () => { + this.openByKeyboard = true; + }, + () => this.focusSearch(), + () => this.openDropdown() + ); + } + + private resetDropdownState(): void { + this.search.set(''); + this.focusedIndex.set(0); + this.openByKeyboard = false; + } + + private updateDropdownPosition(): void { + positionCountryDropdown(this.rootElementGetter() ?? null, this.dropdownElementGetter() ?? null); + } + + private focusSearch(): void { + setTimeout(() => this.searchElementGetter()?.focus({ preventScroll: true })); + } + + private scrollFocusedIntoView(index: number): void { + setTimeout(() => scrollCountryOptionIntoView(this.dropdownElementGetter(), index)); + } +} diff --git a/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts b/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts new file mode 100644 index 00000000..838ca9d2 --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts @@ -0,0 +1,79 @@ +import { Injectable, Injector, computed, effect, inject } from '@angular/core'; +import type { MaskFull } from '@desource/phone-mask'; +import { createPhoneFormatter, extractDigits } from '@desource/phone-mask/kit'; +import { createPhoneNumber } from '../../internal/formatting'; +import type { PhoneNumber } from '../../types'; + +interface UseFormatterOptions { + country: () => MaskFull; + value: () => string; + onChange: (digits: string) => void; + onPhoneChange?: (phone: PhoneNumber) => void; + onValidationChange?: (isComplete: boolean) => void; +} + +@Injectable() +export class UseFormatterService { + private readonly injector = inject(Injector); + private countryGetter: () => MaskFull = () => ({ + id: 'US', + code: '+1', + name: 'United States', + flag: '🇺🇸', + mask: ['###-###-####'] + }); + private valueGetter = () => ''; + private onChange: (digits: string) => void = () => {}; + private onPhoneChange: ((phone: PhoneNumber) => void) | undefined; + private onValidationChange: ((isComplete: boolean) => void) | undefined; + private configured = false; + + readonly country = computed(() => this.countryGetter()); + readonly formatter = computed(() => createPhoneFormatter(this.country())); + readonly digits = computed(() => extractDigits(this.valueGetter(), this.formatter().getMaxDigits())); + readonly displayPlaceholder = computed(() => this.formatter().getPlaceholder()); + readonly displayValue = computed(() => this.formatter().formatDisplay(this.digits())); + readonly phoneData = computed(() => createPhoneNumber(this.digits(), this.country(), this.formatter())); + readonly full = computed(() => this.phoneData().full); + readonly fullFormatted = computed(() => this.phoneData().fullFormatted); + readonly isComplete = computed(() => this.formatter().isComplete(this.digits())); + readonly isEmpty = computed(() => this.digits().length === 0); + readonly shouldShowWarn = computed(() => !this.isEmpty() && !this.isComplete()); + + configure(options: UseFormatterOptions): void { + if (this.configured) return; + this.configured = true; + + this.countryGetter = options.country; + this.valueGetter = options.value; + this.onChange = options.onChange; + this.onPhoneChange = options.onPhoneChange; + this.onValidationChange = options.onValidationChange; + + effect( + () => { + const value = this.valueGetter(); + const digits = this.digits(); + + if (value !== digits) { + queueMicrotask(() => this.onChange(this.digits())); + } + }, + { injector: this.injector } + ); + + effect( + () => { + this.onPhoneChange?.(this.phoneData()); + }, + { injector: this.injector } + ); + + effect( + () => { + this.onValidationChange?.(this.isComplete()); + }, + { injector: this.injector } + ); + } +} diff --git a/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts b/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts new file mode 100644 index 00000000..b4620a33 --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@angular/core'; +import { + processBeforeInput, + processInput, + processKeydown, + processPaste, + setCaret, + type FormatterHelpers +} from '@desource/phone-mask/kit'; + +interface UseInputHandlersOptions { + formatter: () => FormatterHelpers; + digits: () => string; + inactive?: () => boolean; + onChange?: (digits: string) => void; + scheduleValidationHint?: (delay: number) => void; +} + +const HINT_DELAY_INPUT = 500; +const HINT_DELAY_ACTION = 300; + +@Injectable() +export class UseInputHandlersService { + private formatterGetter!: () => FormatterHelpers; + private digitsGetter = () => ''; + private inactiveGetter = () => false; + private onChange: ((digits: string) => void) | undefined; + private scheduleValidationHint: ((delay: number) => void) | undefined; + + configure(options: UseInputHandlersOptions): void { + this.formatterGetter = options.formatter; + this.digitsGetter = options.digits; + this.inactiveGetter = options.inactive ?? this.inactiveGetter; + this.onChange = options.onChange; + this.scheduleValidationHint = options.scheduleValidationHint; + } + + handleBeforeInput(event: Event): void { + if (this.inactiveGetter()) { + event.preventDefault(); + return; + } + + processBeforeInput(event as InputEvent); + } + + handleInput(event: Event): void { + if (this.inactiveGetter()) return; + + const result = processInput(event, { formatter: this.formatterGetter() }); + if (!result) return; + + this.onChange?.(result.newDigits); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + this.scheduleValidationHint?.(HINT_DELAY_INPUT); + } + + handleKeydown(event: KeyboardEvent): void { + if (this.inactiveGetter()) return; + + const result = processKeydown(event, { digits: this.digitsGetter(), formatter: this.formatterGetter() }); + if (!result) return; + + this.onChange?.(result.newDigits); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + this.scheduleValidationHint?.(HINT_DELAY_ACTION); + } + + handlePaste(event: ClipboardEvent): void { + if (this.inactiveGetter()) return; + + const result = processPaste(event, { digits: this.digitsGetter(), formatter: this.formatterGetter() }); + if (!result) return; + + this.onChange?.(result.newDigits); + this.scheduleCaretUpdate(event.target as HTMLInputElement | null, result.caretDigitIndex); + this.scheduleValidationHint?.(HINT_DELAY_ACTION); + } + + private scheduleCaretUpdate(el: HTMLInputElement | null, digitIndex: number): void { + setTimeout(() => { + const position = this.formatterGetter().getCaretPosition(digitIndex); + setCaret(el, position); + }); + } +} diff --git a/packages/phone-mask-angular/src/services/internal/useTheme.service.ts b/packages/phone-mask-angular/src/services/internal/useTheme.service.ts new file mode 100644 index 00000000..69d373cb --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useTheme.service.ts @@ -0,0 +1,38 @@ +import { ChangeDetectorRef, DestroyRef, Injectable, computed, inject, signal } from '@angular/core'; +import type { Theme } from '../../types'; + +@Injectable() +export class UseThemeService { + private readonly cdr = inject(ChangeDetectorRef); + private readonly destroyRef = inject(DestroyRef); + private readonly systemDark = signal(false); + private themeGetter: () => Theme = () => 'auto'; + private mediaQuery: MediaQueryList | undefined; + + readonly themeClass = computed(() => { + const theme = this.themeGetter(); + if (theme === 'auto') return this.systemDark() ? 'theme-dark' : 'theme-light'; + return `theme-${theme}`; + }); + + constructor() { + this.mediaQuery = globalThis.matchMedia?.('(prefers-color-scheme: dark)') ?? undefined; + if (this.mediaQuery) { + this.systemDark.set(this.mediaQuery.matches); + this.mediaQuery.addEventListener('change', this.handleThemeChange); + } + + this.destroyRef.onDestroy(() => { + this.mediaQuery?.removeEventListener('change', this.handleThemeChange); + }); + } + + configure(options: { theme: () => Theme }): void { + this.themeGetter = options.theme; + } + + private readonly handleThemeChange = (event: MediaQueryListEvent) => { + this.systemDark.set(event.matches); + this.cdr.markForCheck(); + }; +} diff --git a/packages/phone-mask-angular/src/services/internal/useValidationHint.service.ts b/packages/phone-mask-angular/src/services/internal/useValidationHint.service.ts new file mode 100644 index 00000000..d7f3c0b2 --- /dev/null +++ b/packages/phone-mask-angular/src/services/internal/useValidationHint.service.ts @@ -0,0 +1,30 @@ +import { ChangeDetectorRef, DestroyRef, Injectable, inject, signal } from '@angular/core'; + +@Injectable() +export class UseValidationHintService { + private readonly cdr = inject(ChangeDetectorRef); + private readonly destroyRef = inject(DestroyRef); + + readonly showValidationHint = signal(false); + + private validationTimer: ReturnType | undefined; + + constructor() { + this.destroyRef.onDestroy(() => this.clearValidationHint()); + } + + clearValidationHint(hideHint = true): void { + if (hideHint) this.showValidationHint.set(false); + if (this.validationTimer) clearTimeout(this.validationTimer); + this.validationTimer = undefined; + } + + scheduleValidationHint(delay: number): void { + this.showValidationHint.set(false); + if (this.validationTimer) clearTimeout(this.validationTimer); + this.validationTimer = setTimeout(() => { + this.showValidationHint.set(true); + this.cdr.markForCheck(); + }, delay); + } +} diff --git a/packages/phone-mask-angular/src/services/usePhoneMask.service.ts b/packages/phone-mask-angular/src/services/usePhoneMask.service.ts new file mode 100644 index 00000000..92de4043 --- /dev/null +++ b/packages/phone-mask-angular/src/services/usePhoneMask.service.ts @@ -0,0 +1,144 @@ +import { DestroyRef, Injectable, Injector, computed, effect, inject, signal, untracked } from '@angular/core'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import type { FormatterHelpers } from '@desource/phone-mask/kit'; +import { UseCountryService } from './internal/useCountry.service'; +import { UseFormatterService } from './internal/useFormatter.service'; +import { UseInputHandlersService } from './internal/useInputHandlers.service'; +import type { PhoneNumber } from '../types'; + +export interface UsePhoneMaskOptions { + /** Controlled value: digits only, without country code. */ + value: () => string; + /** Called when digits change. */ + onChange: (digits: string) => void; + /** Country ISO 3166-1 alpha-2 code. */ + country?: () => CountryKey | string | undefined; + /** Locale for country names. */ + locale?: () => string | undefined; + /** Auto-detect country via GeoIP/locale. */ + detect?: () => boolean | undefined; + /** Called on every phone number update. */ + onPhoneChange?: (phone: PhoneNumber) => void; + /** Called when country changes. */ + onCountryChange?: (country: MaskFull) => void; +} + +@Injectable() +export class UsePhoneMaskService { + private readonly injector = inject(Injector); + private readonly destroyRef = inject(DestroyRef); + private readonly countryState = inject(UseCountryService); + private readonly formatterState = inject(UseFormatterService); + private readonly inputHandlers = inject(UseInputHandlersService); + private readonly inputElement = signal(null); + private onChange: (digits: string) => void = () => {}; + private configured = false; + + readonly country = this.countryState.country; + readonly locale = this.countryState.locale; + readonly digits = this.formatterState.digits; + readonly formatter = this.formatterState.formatter; + readonly full = this.formatterState.full; + readonly fullFormatted = this.formatterState.fullFormatted; + readonly isComplete = this.formatterState.isComplete; + readonly isEmpty = this.formatterState.isEmpty; + readonly shouldShowWarn = this.formatterState.shouldShowWarn; + readonly inputRef = computed(() => this.inputElement()); + + configure(options: UsePhoneMaskOptions): void { + if (this.configured) return; + this.configured = true; + this.onChange = options.onChange; + + this.countryState.configure({ + country: options.country, + locale: options.locale, + detect: options.detect, + onCountryChange: options.onCountryChange + }); + + this.formatterState.configure({ + country: this.country, + value: options.value, + onChange: options.onChange, + onPhoneChange: options.onPhoneChange + }); + + this.inputHandlers.configure({ + formatter: this.formatter, + digits: this.digits, + onChange: options.onChange + }); + + effect( + () => { + const el = this.inputElement(); + if (!el) return; + + this.syncInputElement(el); + }, + { injector: this.injector } + ); + + effect( + (onCleanup) => { + const el = this.inputElement(); + if (!el) return; + + const beforeInputHandler = (event: Event) => this.inputHandlers.handleBeforeInput(event); + const inputHandler = (event: Event) => this.inputHandlers.handleInput(event); + const keydownHandler = (event: KeyboardEvent) => this.inputHandlers.handleKeydown(event); + const pasteHandler = (event: ClipboardEvent) => this.inputHandlers.handlePaste(event); + + el.addEventListener('beforeinput', beforeInputHandler); + el.addEventListener('input', inputHandler); + el.addEventListener('keydown', keydownHandler); + el.addEventListener('paste', pasteHandler); + + onCleanup(() => { + el.removeEventListener('beforeinput', beforeInputHandler); + el.removeEventListener('input', inputHandler); + el.removeEventListener('keydown', keydownHandler); + el.removeEventListener('paste', pasteHandler); + }); + }, + { injector: this.injector } + ); + + this.destroyRef.onDestroy(() => this.connect(null)); + } + + connect(inputElement: HTMLInputElement | null): void { + untracked(() => this.inputElement.set(inputElement)); + } + + setCountry(countryCode?: CountryKey | string | null): boolean { + const updated = this.countryState.setCountry(countryCode); + if (updated) this.syncInputElement(); + + return updated; + } + + clear(): void { + const el = this.inputElement(); + if (el) el.value = ''; + this.onChange(''); + } + + getDigits(): string { + return this.digits(); + } + + getFormatter(): FormatterHelpers { + return this.formatter(); + } + + private syncInputElement(inputElement = this.inputElement()): void { + if (!inputElement) return; + + inputElement.setAttribute('type', 'tel'); + inputElement.setAttribute('inputmode', 'tel'); + inputElement.value = this.formatterState.displayValue(); + inputElement.setAttribute('placeholder', this.formatterState.displayPlaceholder()); + } +} diff --git a/packages/phone-mask-angular/src/services/utility/useClipboard.service.ts b/packages/phone-mask-angular/src/services/utility/useClipboard.service.ts new file mode 100644 index 00000000..624bafb1 --- /dev/null +++ b/packages/phone-mask-angular/src/services/utility/useClipboard.service.ts @@ -0,0 +1,61 @@ +import { DOCUMENT } from '@angular/common'; +import { ChangeDetectorRef, DestroyRef, Injectable, inject, signal } from '@angular/core'; + +@Injectable() +export class UseClipboardService { + private readonly cdr = inject(ChangeDetectorRef); + private readonly destroyRef = inject(DestroyRef); + private readonly document = inject(DOCUMENT, { optional: true }); + + readonly copied = signal(false); + + private resetTimer: ReturnType | undefined; + + constructor() { + this.destroyRef.onDestroy(() => { + if (this.resetTimer) clearTimeout(this.resetTimer); + }); + } + + async copy(value: string, resetDelay = 1_800): Promise { + if (!value) return false; + + const success = await this.writeText(value); + if (!success) return false; + + this.copied.set(true); + + if (this.resetTimer) clearTimeout(this.resetTimer); + this.resetTimer = setTimeout(() => { + this.copied.set(false); + this.cdr.markForCheck(); + }, resetDelay); + + return true; + } + + private async writeText(value: string): Promise { + try { + if (globalThis.navigator?.clipboard?.writeText) { + await globalThis.navigator.clipboard.writeText(value); + return true; + } + + if (!this.document?.body) return false; + + const textarea = this.document.createElement('textarea'); + textarea.value = value; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + this.document.body.appendChild(textarea); + textarea.select(); + const copied = this.document.execCommand('copy'); + textarea.remove(); + + return copied; + } catch { + return false; + } + } +} diff --git a/packages/phone-mask-angular/src/services/utility/useTimer.service.ts b/packages/phone-mask-angular/src/services/utility/useTimer.service.ts new file mode 100644 index 00000000..3e7c9a6b --- /dev/null +++ b/packages/phone-mask-angular/src/services/utility/useTimer.service.ts @@ -0,0 +1,22 @@ +import { DestroyRef, Injectable, inject } from '@angular/core'; + +@Injectable() +export class UseTimerService { + private readonly destroyRef = inject(DestroyRef); + private timer: ReturnType | undefined; + + constructor() { + this.destroyRef.onDestroy(() => this.clear()); + } + + set(callback: () => void, delay: number): void { + this.clear(); + this.timer = setTimeout(callback, delay); + } + + clear(): void { + if (!this.timer) return; + clearTimeout(this.timer); + this.timer = undefined; + } +} diff --git a/packages/phone-mask-angular/src/types.ts b/packages/phone-mask-angular/src/types.ts index 565b00c6..9e363f4e 100644 --- a/packages/phone-mask-angular/src/types.ts +++ b/packages/phone-mask-angular/src/types.ts @@ -44,6 +44,19 @@ export interface PhoneMaskDirectiveOptions { export type PhoneMaskDirectiveInput = CountryKey | string | PhoneMaskDirectiveOptions | null | undefined; +export interface PhoneMaskDirectiveState { + country: MaskFull; + formatter: FormatterHelpers; + digits: string; + locale: string; + options: PhoneMaskDirectiveOptions; + setCountry?: (code: string) => boolean; +} + +export interface DirectiveHTMLInputElement extends HTMLInputElement { + __phoneMaskState?: PhoneMaskDirectiveState; +} + export interface PhoneInputRef { /** Focus the phone input. */ focus: () => void; diff --git a/packages/phone-mask-angular/tests/e2e/PhoneInput.spec.ts b/packages/phone-mask-angular/tests/e2e/PhoneInput.spec.ts new file mode 100644 index 00000000..cff6821f --- /dev/null +++ b/packages/phone-mask-angular/tests/e2e/PhoneInput.spec.ts @@ -0,0 +1,37 @@ +import { testPhoneInput } from '../../../../common/tests/e2e/PhoneInput'; + +const PLAYGROUND_SELECTOR = '[data-testid="playground"]'; + +const COUNTRY_SELECTOR = '[data-testid="props-country"]'; +const READONLY_SELECTOR = '[data-testid="props-readonly"] input[type="checkbox"]'; +const DISABLED_SELECTOR = '[data-testid="props-disabled"] input[type="checkbox"]'; +const SHOW_COPY_SELECTOR = '[data-testid="props-show-copy"] input[type="checkbox"]'; +const SHOW_CLEAR_SELECTOR = '[data-testid="props-show-clear"] input[type="checkbox"]'; +const WITH_VALIDITY_SELECTOR = '[data-testid="props-with-validity"] input[type="checkbox"]'; +const DETECT_SELECTOR = '[data-testid="props-detect"] input[type="checkbox"]'; +const LOCALE_SELECTOR = '[data-testid="props-locale"]'; +const SIZE_SELECTOR = '[data-testid="props-size"]'; +const THEME_SELECTOR = '[data-testid="props-theme"]'; +const SEARCH_PLACEHOLDER_SELECTOR = '[data-testid="props-search-placeholder"]'; +const NO_RESULTS_TEXT_SELECTOR = '[data-testid="props-no-results-text"]'; +const CLEAR_BUTTON_LABEL_SELECTOR = '[data-testid="props-clear-button-label"]'; +const DROPDOWN_CLASS_SELECTOR = '[data-testid="props-dropdown-class"]'; +const DISABLE_DEFAULT_STYLES_SELECTOR = '[data-testid="props-disable-default-styles"] input[type="checkbox"]'; + +testPhoneInput(PLAYGROUND_SELECTOR, { + country: COUNTRY_SELECTOR, + readonly: READONLY_SELECTOR, + disabled: DISABLED_SELECTOR, + showCopy: SHOW_COPY_SELECTOR, + showClear: SHOW_CLEAR_SELECTOR, + withValidity: WITH_VALIDITY_SELECTOR, + detect: DETECT_SELECTOR, + locale: LOCALE_SELECTOR, + size: SIZE_SELECTOR, + theme: THEME_SELECTOR, + searchPlaceholder: SEARCH_PLACEHOLDER_SELECTOR, + noResultsText: NO_RESULTS_TEXT_SELECTOR, + clearButtonLabel: CLEAR_BUTTON_LABEL_SELECTOR, + dropdownClass: DROPDOWN_CLASS_SELECTOR, + disableDefaultStyles: DISABLE_DEFAULT_STYLES_SELECTOR +}); diff --git a/packages/phone-mask-angular/tests/e2e/UsePhoneMask.spec.ts b/packages/phone-mask-angular/tests/e2e/UsePhoneMask.spec.ts new file mode 100644 index 00000000..bd118897 --- /dev/null +++ b/packages/phone-mask-angular/tests/e2e/UsePhoneMask.spec.ts @@ -0,0 +1,27 @@ +import { testUsePhoneMask } from '../../../../common/tests/e2e/UsePhoneMask'; + +const HOOK_SELECTOR = '[data-testid="hook"]'; + +const SET_COUNTRY_US_SELECTOR = '[data-testid="control-country-us"]'; +const SET_COUNTRY_DE_SELECTOR = '[data-testid="control-country-de"]'; +const CLEAR_SELECTOR = '[data-testid="control-clear"]'; + +const META_DIGITS_SELECTOR = '[data-testid="meta-digits"]'; +const META_FULL_SELECTOR = '[data-testid="meta-full"]'; +const META_FORMATTED_SELECTOR = '[data-testid="meta-formatted"]'; +const META_VALID_SELECTOR = '[data-testid="meta-valid"]'; + +testUsePhoneMask( + HOOK_SELECTOR, + { + setCountryUs: SET_COUNTRY_US_SELECTOR, + setCountryDe: SET_COUNTRY_DE_SELECTOR, + clear: CLEAR_SELECTOR + }, + { + digits: META_DIGITS_SELECTOR, + full: META_FULL_SELECTOR, + formatted: META_FORMATTED_SELECTOR, + valid: META_VALID_SELECTOR + } +); diff --git a/packages/phone-mask-angular/tests/tsconfig.json b/packages/phone-mask-angular/tests/tsconfig.json new file mode 100644 index 00000000..19c93f43 --- /dev/null +++ b/packages/phone-mask-angular/tests/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "composite": false, + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "paths": { + "@common/*": ["../../common/*"], + "@src/*": ["../src/*"] + }, + "types": ["vitest/globals"] + }, + "include": ["unit/**/*.ts", "e2e/**/*.ts", "../src/**/*.ts", "../../common/tests/**/*.ts"], + "exclude": [] +} diff --git a/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts b/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts new file mode 100644 index 00000000..761f3d1f --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts @@ -0,0 +1,130 @@ +/// +import { Component } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { render } from '@testing-library/angular'; +import type { CountryKey, MaskFull } from '@desource/phone-mask'; +import { testPhoneInput, type SetupFn } from '@common/tests/unit/PhoneInput'; +import { PhoneInputComponent } from '../../src/phone-input/phone-input.component'; +import type { PhoneNumber, Size, Theme } from '../../src/types'; +import { tools } from './setup/tools'; + +@Component({ + standalone: true, + imports: [PhoneInputComponent], + template: ` + + @if (withCustomRenderers) { + + Before + + + {{ country.id }} + + + {{ copied ? 'copied' : 'copy' }} + + + clear + + } + + ` +}) +class PhoneInputHostComponent { + value = ''; + id?: string; + name?: string; + detect = false; + showClear = false; + showCopy = true; + disabled = false; + readonly = false; + country?: CountryKey | string; + size: Size = 'normal'; + theme: Theme = 'auto'; + disableDefaultStyles = false; + withCustomRenderers = false; + dropdownClass = ''; + + onChange = vi.fn(); + onCountryChange = vi.fn(); + onCopy = vi.fn(); + onFocus = vi.fn(); + onBlur = vi.fn(); + + handleValueChange(value: string): void { + this.value = value; + this.onChange(value); + } + + handleCountryChange(country: MaskFull): void { + this.onCountryChange(country); + } + + handlePhoneChange(_phone: PhoneNumber): void {} +} + +const setup: SetupFn = async ({ + value = '', + id, + name, + detect = false, + showClear = false, + showCopy = true, + disabled = false, + readonly = false, + country, + disableDefaultStyles = false, + withCustomRenderers = false +} = {}) => { + const result = await render(PhoneInputHostComponent, { + componentProperties: { + value, + id, + name, + detect, + showClear, + showCopy, + disabled, + readonly, + country, + disableDefaultStyles, + withCustomRenderers, + dropdownClass: withCustomRenderers ? 'custom-dropdown' : '' + } + }); + + const host = result.fixture.componentInstance; + const ref = result.debugElement.query(By.directive(PhoneInputComponent))?.componentInstance as PhoneInputComponent; + if (!ref) throw new Error('PhoneInput ref is not created'); + + return { + ref, + onChange: host.onChange, + onCountryChange: host.onCountryChange, + onCopy: host.onCopy, + onFocus: host.onFocus, + onBlur: host.onBlur, + container: result.container, + unmount: () => result.fixture.destroy() + }; +}; + +testPhoneInput(setup, tools); diff --git a/packages/phone-mask-angular/tests/unit/index.test.ts b/packages/phone-mask-angular/tests/unit/index.test.ts new file mode 100644 index 00000000..d0a4ea19 --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/index.test.ts @@ -0,0 +1,12 @@ +/// +import { testIndexImports } from '@common/tests/unit/index'; +import * as indexModule from '../../src/public-api'; +import * as coreModule from '../../src/core'; + +testIndexImports({ + suiteName: 'angular', + indexModule, + coreModule, + expectedDefinedExports: ['PhoneInputComponent', 'PhoneMaskDirective', 'PhoneMaskPipe', 'UsePhoneMaskService'], + expectedFunctionExports: ['providePhoneMask'] +}); diff --git a/packages/phone-mask-angular/tests/unit/phoneMaskDirective.test.ts b/packages/phone-mask-angular/tests/unit/phoneMaskDirective.test.ts new file mode 100644 index 00000000..9a76a60d --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/phoneMaskDirective.test.ts @@ -0,0 +1,94 @@ +/// +import { Component } from '@angular/core'; +import { render } from '@testing-library/angular'; +import { detectByGeoIp } from '@desource/phone-mask/kit'; +import { testPhoneMaskBinding } from '@common/tests/unit/phoneMaskBinding'; +import { PhoneMaskDirective } from '../../src/phone-mask.directive'; +import type { DirectiveHTMLInputElement, PhoneMaskDirectiveInput, PhoneMaskDirectiveOptions } from '../../src/types'; +import { tools } from './setup/tools'; + +vi.mock('@desource/phone-mask/kit', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + detectByGeoIp: vi.fn().mockResolvedValue(null) + }; +}); + +@Component({ + standalone: true, + imports: [PhoneMaskDirective], + template: ` + @if (tag === 'input') { + + } @else { +
+ } + ` +}) +class PhoneMaskDirectiveHostComponent { + tag: 'input' | 'div' = 'input'; + value = ''; + options?: PhoneMaskDirectiveInput; +} + +const flushAngular = async (detectChanges: () => void) => { + await Promise.resolve(); + await Promise.resolve(); + detectChanges(); +}; + +const setup = + (elTag: 'input' | 'div' = 'input', elValue?: string) => + async (options?: string | PhoneMaskDirectiveOptions) => { + const onChange = vi.fn(); + const onCountryChange = vi.fn(); + + const mergeOptions = ( + opts?: string | PhoneMaskDirectiveOptions + ): string | PhoneMaskDirectiveOptions | undefined => + typeof opts === 'object' ? { ...opts, onChange, onCountryChange } : opts; + + const result = await render(PhoneMaskDirectiveHostComponent, { + imports: [PhoneMaskDirective], + componentProperties: { + tag: elTag, + value: elValue ?? '', + options: mergeOptions(options) + } + }); + + await flushAngular(result.detectChanges); + + const el = result.container.querySelector(elTag) as DirectiveHTMLInputElement; + + const update = async (newOptions?: string | PhoneMaskDirectiveOptions) => { + const host = result.fixture.componentInstance; + if (elTag === 'input') host.value = el.value; + host.options = mergeOptions(newOptions); + await flushAngular(result.detectChanges); + }; + + return { + el, + onChange, + onCountryChange, + update, + unmount: () => result.fixture.destroy() + }; + }; + +describe('PhoneMaskDirective', () => { + testPhoneMaskBinding( + setup, + { + warnMessage: '[phoneMask] Directive can only be used on input elements', + detectByGeoIpMock: vi.mocked(detectByGeoIp) + }, + tools + ); +}); diff --git a/packages/phone-mask-angular/tests/unit/service-pipe.test.ts b/packages/phone-mask-angular/tests/unit/service-pipe.test.ts index 30029b31..9af3f517 100644 --- a/packages/phone-mask-angular/tests/unit/service-pipe.test.ts +++ b/packages/phone-mask-angular/tests/unit/service-pipe.test.ts @@ -1,32 +1,19 @@ import { describe, expect, it } from 'vitest'; +import { TestBed } from '@angular/core/testing'; +import { PHONE_MASK_CONFIG } from '../../src/config'; import { PhoneMaskPipe } from '../../src/phone-mask.pipe'; -import { PhoneMaskService } from '../../src/phone-mask.service'; -describe('PhoneMaskService', () => { - const service = new PhoneMaskService({ country: 'US', locale: 'en' }); - - it('formats national display values', () => { - expect(service.format('2025551234')).toBe('202-555-1234'); - }); - - it('returns full phone payloads', () => { - expect(service.getPhoneNumber('2025551234')).toEqual({ - digits: '2025551234', - full: '+12025551234', - fullFormatted: '+1 202-555-1234' +describe('PhoneMaskPipe', () => { + const setup = () => { + TestBed.configureTestingModule({ + providers: [{ provide: PHONE_MASK_CONFIG, useValue: { country: 'US', locale: 'en' } }] }); - }); - it('checks completion against the selected country mask', () => { - expect(service.isComplete('2025551234')).toBe(true); - expect(service.isComplete('202555')).toBe(false); - }); -}); - -describe('PhoneMaskPipe', () => { - const pipe = new PhoneMaskPipe({ country: 'US', locale: 'en' }); + return TestBed.runInInjectionContext(() => new PhoneMaskPipe()); + }; it('supports display, full, fullFormatted, and placeholder modes', () => { + const pipe = setup(); expect(pipe.transform('2025551234')).toBe('202-555-1234'); expect(pipe.transform('2025551234', { mode: 'full' })).toBe('+12025551234'); expect(pipe.transform('2025551234', { mode: 'fullFormatted' })).toBe('+1 202-555-1234'); @@ -34,6 +21,7 @@ describe('PhoneMaskPipe', () => { }); it('accepts country, mode, and locale shorthand arguments', () => { + const pipe = setup(); expect(pipe.transform('442071234567', 'GB', 'fullFormatted', 'en')).toMatch(/^\+44 /); }); }); diff --git a/packages/phone-mask-angular/tests/unit/setup/angular.ts b/packages/phone-mask-angular/tests/unit/setup/angular.ts new file mode 100644 index 00000000..bdb3e7bb --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/setup/angular.ts @@ -0,0 +1,7 @@ +import '@angular/compiler'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), { + teardown: { destroyAfterEach: true } +}); diff --git a/packages/phone-mask-angular/tests/unit/setup/tools.ts b/packages/phone-mask-angular/tests/unit/setup/tools.ts new file mode 100644 index 00000000..2216826a --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/setup/tools.ts @@ -0,0 +1,21 @@ +import { TestBed } from '@angular/core/testing'; +import { fireEvent, screen, waitFor } from '@testing-library/angular'; +import type { MaybeRef, TestTools } from '@common/tests/unit/setup/tools'; + +export const act = async (callback: () => void | Promise): Promise => { + await callback(); + TestBed.tick(); + await Promise.resolve(); +}; + +export const tools: TestTools = { + toValue: (val: MaybeRef) => { + if (typeof val === 'function') return (val as () => T)(); + if (val && typeof val === 'object' && 'value' in val) return val.value as T; + return val as T; + }, + act, + waitFor, + fireEvent, + screen +}; diff --git a/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts b/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts new file mode 100644 index 00000000..395e33df --- /dev/null +++ b/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts @@ -0,0 +1,77 @@ +/// +import { Component, ElementRef, inject, signal, viewChild, type AfterViewInit, type OnDestroy } from '@angular/core'; +import { render } from '@testing-library/angular'; +import { testUsePhoneMask, type UsePhoneMaskSetupOptions } from '@common/tests/unit/usePhoneMask'; +import { UseCountryService } from '../../src/services/internal/useCountry.service'; +import { UseFormatterService } from '../../src/services/internal/useFormatter.service'; +import { UseInputHandlersService } from '../../src/services/internal/useInputHandlers.service'; +import { UsePhoneMaskService } from '../../src/services/usePhoneMask.service'; +import { tools } from './setup/tools'; + +@Component({ + standalone: true, + template: '', + providers: [UseCountryService, UseFormatterService, UseInputHandlersService, UsePhoneMaskService] +}) +class UsePhoneMaskHostComponent implements AfterViewInit, OnDestroy { + readonly mask = inject(UsePhoneMaskService); + readonly value = signal(''); + attachRef = true; + onChange = vi.fn(); + private readonly inputRef = viewChild>('phoneInput'); + + constructor() { + this.mask.configure({ + value: this.value, + detect: () => false, + onChange: (digits) => { + this.value.set(digits); + this.onChange(digits); + } + }); + } + + ngAfterViewInit(): void { + if (this.attachRef) { + this.mask.connect(this.inputRef()?.nativeElement ?? null); + } + } + + ngOnDestroy(): void { + this.mask.connect(null); + } +} + +async function setup(initialValue = '', options: UsePhoneMaskSetupOptions = {}) { + const attachRef = options.attachRef ?? true; + const result = await render(UsePhoneMaskHostComponent, { + detectChangesOnRender: false, + componentProperties: { attachRef } + }); + + const host = result.fixture.componentInstance; + host.value.set(initialValue); + result.detectChanges(); + if (attachRef) host.mask.connect(result.container.querySelector('input')); + await tools.act(async () => {}); + + const inputEl = result.container.querySelector('input') as HTMLInputElement; + + return { + inputEl, + onChange: host.onChange, + getValue: () => host.value(), + unmount: () => result.fixture.destroy(), + api: { + getDigits: () => host.mask.digits(), + getFull: () => host.mask.full(), + getFullFormatted: () => host.mask.fullFormatted(), + isEmpty: () => host.mask.isEmpty(), + shouldShowWarn: () => host.mask.shouldShowWarn(), + setCountry: (countryCode: string) => host.mask.setCountry(countryCode), + clear: () => host.mask.clear() + } + }; +} + +testUsePhoneMask(setup, tools); diff --git a/packages/phone-mask-angular/tsconfig.spec.json b/packages/phone-mask-angular/tsconfig.spec.json new file mode 100644 index 00000000..660d9fe7 --- /dev/null +++ b/packages/phone-mask-angular/tsconfig.spec.json @@ -0,0 +1,8 @@ +{ + "extends": "./tests/tsconfig.json", + "compilerOptions": { + "types": ["vitest/globals", "node"] + }, + "include": ["src/**/*.ts", "core/src/**/*.ts", "tests/**/*.ts", "../../common/tests/**/*.ts"], + "exclude": [] +} diff --git a/packages/phone-mask-angular/vitest.config.ts b/packages/phone-mask-angular/vitest.config.ts index 8af597ed..d7ca67c8 100644 --- a/packages/phone-mask-angular/vitest.config.ts +++ b/packages/phone-mask-angular/vitest.config.ts @@ -1,9 +1,25 @@ +import angular from '@analogjs/vite-plugin-angular'; import { defineConfig } from 'vitest/config'; +import { fileURLToPath } from 'node:url'; + +const tsconfig = fileURLToPath(new URL('./tsconfig.spec.json', import.meta.url)); export default defineConfig({ + plugins: [angular({ tsconfig })], + resolve: { + alias: { + '@common': fileURLToPath(new URL('../../common', import.meta.url)), + '@src': fileURLToPath(new URL('./src', import.meta.url)) + } + }, test: { + setupFiles: ['tests/unit/setup/angular.ts'], environment: 'jsdom', include: ['tests/unit/**/*.test.ts'], - globals: true + globals: true, + coverage: { + include: ['src/**/*.ts'], + exclude: ['src/**/*.d.ts'] + } } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d79afe12..9a93b82b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,9 @@ importers: '@vitest/coverage-v8': specifier: ^4.1.5 version: 4.1.5(vitest@4.1.5) + angular-eslint: + specifier: ^21.3.1 + version: 21.3.1(@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.6.1))(typescript-eslint@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3) eslint: specifier: ^9.39.4 version: 9.39.4(jiti@2.6.1) @@ -134,9 +137,18 @@ importers: specifier: ^2.8.1 version: 2.8.1 devDependencies: + '@analogjs/vite-plugin-angular': + specifier: ^2.5.0 + version: 2.5.0(@angular/build@21.2.10(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(@angular/compiler@21.2.12)(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.5)(yaml@2.8.4))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4)) + '@angular/build': + specifier: ^21.2.10 + version: 21.2.10(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(@angular/compiler@21.2.12)(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.5)(yaml@2.8.4) + '@angular/cli': + specifier: ^21.2.10 + version: 21.2.10(@types/node@25.6.2)(chokidar@5.0.0) '@angular/common': specifier: ^21.2.12 - version: 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2) + version: 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) '@angular/compiler': specifier: ^21.2.12 version: 21.2.12 @@ -145,10 +157,16 @@ importers: version: 21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3) '@angular/core': specifier: ^21.2.12 - version: 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + version: 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) '@angular/forms': specifier: ^21.2.12 - version: 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)))(rxjs@7.8.2) + version: 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + '@angular/platform-browser': + specifier: ^21.2.12 + version: 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)) + '@testing-library/angular': + specifier: ^19.2.1 + version: 19.2.1(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/router@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2))(@testing-library/dom@10.4.1) ng-packagr: specifier: ^21.2.3 version: 21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) @@ -243,10 +261,193 @@ importers: packages: + '@algolia/abtesting@1.14.1': + resolution: {integrity: sha512-Dkj0BgPiLAaim9sbQ97UKDFHJE/880wgStAM18U++NaJ/2Cws34J5731ovJifr6E3Pv4T2CqvMXf8qLCC417Ew==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-abtesting@5.48.1': + resolution: {integrity: sha512-LV5qCJdj+/m9I+Aj91o+glYszrzd7CX6NgKaYdTOj4+tUYfbS62pwYgUfZprYNayhkQpVFcrW8x8ZlIHpS23Vw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.48.1': + resolution: {integrity: sha512-/AVoMqHhPm14CcHq7mwB+bUJbfCv+jrxlNvRjXAuO+TQa+V37N8k1b0ijaRBPdmSjULMd8KtJbQyUyabXOu6Kg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.48.1': + resolution: {integrity: sha512-VXO+qu2Ep6ota28ktvBm3sG53wUHS2n7bgLWmce5jTskdlCD0/JrV4tnBm1l7qpla1CeoQb8D7ShFhad+UoSOw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.48.1': + resolution: {integrity: sha512-zl+Qyb0nLg+Y5YvKp1Ij+u9OaPaKg2/EPzTwKNiVyOHnQJlFxmXyUZL1EInczAZsEY8hVpPCLtNfhMhfxluXKQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.48.1': + resolution: {integrity: sha512-r89Qf9Oo9mKWQXumRu/1LtvVJAmEDpn8mHZMc485pRfQUMAwSSrsnaw1tQ3sszqzEgAr1c7rw6fjBI+zrAXTOw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.48.1': + resolution: {integrity: sha512-TPKNPKfghKG/bMSc7mQYD9HxHRUkBZA4q1PEmHgICaSeHQscGqL4wBrKkhfPlDV1uYBKW02pbFMUhsOt7p4ZpA==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.48.1': + resolution: {integrity: sha512-4Fu7dnzQyQmMFknYwTiN/HxPbH4DyxvQ1m+IxpPp5oslOgz8m6PG5qhiGbqJzH4HiT1I58ecDiCAC716UyVA8Q==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.48.1': + resolution: {integrity: sha512-/RFq3TqtXDUUawwic/A9xylA2P3LDMO8dNhphHAUOU51b1ZLHrmZ6YYJm3df1APz7xLY1aht6okCQf+/vmrV9w==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.48.1': + resolution: {integrity: sha512-Of0jTeAZRyRhC7XzDSjJef0aBkgRcvRAaw0ooYRlOw57APii7lZdq+layuNdeL72BRq1snaJhoMMwkmLIpJScw==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.48.1': + resolution: {integrity: sha512-bE7JcpFXzxF5zHwj/vkl2eiCBvyR1zQ7aoUdO+GDXxGp0DGw7nI0p8Xj6u8VmRQ+RDuPcICFQcCwRIJT5tDJFw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.48.1': + resolution: {integrity: sha512-MK3wZ2koLDnvH/AmqIF1EKbJlhRS5j74OZGkLpxI4rYvNi9Jn/C7vb5DytBnQ4KUWts7QsmbdwHkxY5txQHXVw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.48.1': + resolution: {integrity: sha512-2oDT43Y5HWRSIQMPQI4tA/W+TN/N2tjggZCUsqQV440kxzzoPGsvv9QP1GhQ4CoDa+yn6ygUsGp6Dr+a9sPPSg==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.48.1': + resolution: {integrity: sha512-xcaCqbhupVWhuBP1nwbk1XNvwrGljozutEiLx06mvqDf3o8cHyEgQSHS4fKJM+UAggaWVnnFW+Nne5aQ8SUJXg==} + engines: {node: '>= 14.0.0'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@analogjs/vite-plugin-angular@2.5.0': + resolution: {integrity: sha512-TPH74k6qZefHX/RNFqS2D9ZPzR5rFZ6gy5sz63MzQP9iwzRjFbutyBmtnNflTOtQMZIfTLI34ktB9HBzCvvn2g==} + peerDependencies: + '@angular-devkit/build-angular': ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 + '@angular/build': ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 + vite: ^8.0.11 + peerDependenciesMeta: + '@angular-devkit/build-angular': + optional: true + '@angular/build': + optional: true + vite: + optional: true + + '@angular-devkit/architect@0.2102.10': + resolution: {integrity: sha512-deiDH9ug1//eAM6IcyFT5T3eDDAudZex7F1K6lJkVUsjic/DwLU/KabvqF/i+PM05YmxMwLZsGNN0oj0qCxP8A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/core@21.2.10': + resolution: {integrity: sha512-LMpwxn2PsIdFEZCJJpaym7B2MSuMvo2BUfEl+EZwJT7Zk4RdIMP9eTFOP7JTz9Mis+ODQWO4ei0nqGDE/UanQg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^5.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics@21.2.10': + resolution: {integrity: sha512-ydmYDqbX7c2yZl25MDzeKKH+Sy9x3qq5AdWhXJh2SsqbQWp88DgrYNV315nznZONukLkg7eSNyWbweuBcIHmKA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular-eslint/builder@21.3.1': + resolution: {integrity: sha512-1f1Lyp5e7OH6txiV224HaY3G1uRCj91OSKq7hT2Vw9NRw6zWFc1anBpDeLVjpL9ptUxzUGIQR5jEV54hOPayoQ==} + peerDependencies: + '@angular/cli': '>= 21.0.0 < 22.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + + '@angular-eslint/bundled-angular-compiler@21.3.1': + resolution: {integrity: sha512-jjbnJPUXQeQBJ8RM+ahlbt4GH2emVN8JvG3AhFbPci1FrqXi9cOOfkbwLmvpoyTli4LF8gy7g4ctFqnlRgqryw==} + + '@angular-eslint/eslint-plugin-template@21.3.1': + resolution: {integrity: sha512-ndPWJodkcEOu2PVUxlUwyz4D2u3r9KO7veWmStVNOLeNrICJA+nQvrz2BWCu0l48rO0K5ezsy0JFcQDVwE/5mw==} + peerDependencies: + '@angular-eslint/template-parser': 21.3.1 + '@typescript-eslint/types': ^7.11.0 || ^8.0.0 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + + '@angular-eslint/eslint-plugin@21.3.1': + resolution: {integrity: sha512-08NNTxwawRLTWPLl8dg1BnXMwimx93y4wMEwx2aWQpJbIt4pmNvwJzd+NgoD/Ag2VdLS/gOMadhJH5fgaYKsPQ==} + peerDependencies: + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + + '@angular-eslint/schematics@21.3.1': + resolution: {integrity: sha512-1U2u4ZsZvwT30aXRLsIJf6tULIiioo9BtASNsldpYecU3/m/1+F61lCYG79qt7YWbif9KABPYZlFTJUFGN8HWA==} + peerDependencies: + '@angular/cli': '>= 21.0.0 < 22.0.0' + + '@angular-eslint/template-parser@21.3.1': + resolution: {integrity: sha512-moERVCTekQKOvR8RMuEOtWSO3VS1qrzA3keI1dPto/JVB8Nqp9w3R5ZpEoXHzh4zgEryosxmPgdi6UczJe2ouQ==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + + '@angular-eslint/utils@21.3.1': + resolution: {integrity: sha512-Q3SGA1/36phZhmsp1mYrKzp/jcmqofRr861MYn46FaWIKSYXBYRzl+H3FIJKBu5CE36Bggu6hbNpwGPuUp+MCg==} + peerDependencies: + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + + '@angular/build@21.2.10': + resolution: {integrity: sha512-jnFN56y9tyqsZbbDueEzITYAfug7bSF9KcOi9fhPppbmTdjSN5xrXXltDSqwgRdvGtcctZ55NunT7sGF7+ubXQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler': ^21.0.0 + '@angular/compiler-cli': ^21.0.0 + '@angular/core': ^21.0.0 + '@angular/localize': ^21.0.0 + '@angular/platform-browser': ^21.0.0 + '@angular/platform-server': ^21.0.0 + '@angular/service-worker': ^21.0.0 + '@angular/ssr': ^21.2.10 + karma: ^6.4.0 + less: ^4.2.0 + ng-packagr: ^21.0.0 + postcss: ^8.4.0 + tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 + tslib: ^2.3.0 + typescript: '>=5.9 <6.0' + vitest: ^4.0.8 + peerDependenciesMeta: + '@angular/core': + optional: true + '@angular/localize': + optional: true + '@angular/platform-browser': + optional: true + '@angular/platform-server': + optional: true + '@angular/service-worker': + optional: true + '@angular/ssr': + optional: true + karma: + optional: true + less: + optional: true + ng-packagr: + optional: true + postcss: + optional: true + tailwindcss: + optional: true + vitest: + optional: true + + '@angular/cli@21.2.10': + resolution: {integrity: sha512-ezf9LM0GgexG2l8ae/uN4fUyxGqeFEH9iu30mUMU5dwow76aK+b4Abuf5eSuR0F8zTLEA3ZUEYywI+gajbAUuA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + '@angular/common@21.2.12': resolution: {integrity: sha512-b7IRSM9fWPmZ1SLN0utVcW87IkhiRte3Wsnwr2nEsjum2soRMfvKqHwtEFGfCztlwOmZLgKiGW9pqKpzBkIjnQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -302,6 +503,15 @@ packages: '@angular/animations': optional: true + '@angular/router@21.2.12': + resolution: {integrity: sha512-2/RDHt3GdW2ABNRVrgLX7IxgJLdF7u8Sbh11kAUn04QhNI/GObxIV4M5Hm/NTeDoi+hCXavkaHVBlj/dG5ANbw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/common': 21.2.12 + '@angular/core': 21.2.12 + '@angular/platform-browser': 21.2.12 + rxjs: ^6.5.3 || ^7.4.0 + '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -383,6 +593,10 @@ packages: resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -597,6 +811,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} @@ -615,6 +835,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.7': resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} @@ -633,6 +859,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} @@ -651,6 +883,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} @@ -669,6 +907,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.7': resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} @@ -687,6 +931,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} @@ -705,6 +955,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} @@ -723,6 +979,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} @@ -741,6 +1003,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.7': resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} @@ -759,6 +1027,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} @@ -777,6 +1051,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} @@ -795,6 +1075,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} @@ -813,6 +1099,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} @@ -831,6 +1123,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} @@ -849,6 +1147,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} @@ -867,6 +1171,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} @@ -885,6 +1195,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} @@ -903,6 +1219,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} @@ -921,6 +1243,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} @@ -939,6 +1267,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} @@ -957,6 +1291,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} @@ -975,6 +1315,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} @@ -993,6 +1339,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.7': resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} @@ -1011,6 +1363,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.7': resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} @@ -1029,6 +1387,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} @@ -1047,6 +1411,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} @@ -1129,6 +1499,19 @@ packages: '@fastify/accept-negotiator@2.0.1': resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==} + '@gar/promise-retry@1.0.3': + resolution: {integrity: sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@harperfast/extended-iterable@1.0.3': + resolution: {integrity: sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw==} + + '@hono/node-server@1.19.14': + resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1298,6 +1681,55 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -1307,6 +1739,82 @@ packages: '@types/node': optional: true + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} @@ -1318,6 +1826,10 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1343,16 +1855,98 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} - '@manypkg/find-root@1.1.0': - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + '@listr2/prompt-adapter-inquirer@3.0.5': + resolution: {integrity: sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@inquirer/prompts': '>= 3 < 8' + listr2: 9.0.5 + + '@lmdb/lmdb-darwin-arm64@3.5.1': + resolution: {integrity: sha512-tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ==} + cpu: [arm64] + os: [darwin] + + '@lmdb/lmdb-darwin-x64@3.5.1': + resolution: {integrity: sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w==} + cpu: [x64] + os: [darwin] + + '@lmdb/lmdb-linux-arm64@3.5.1': + resolution: {integrity: sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg==} + cpu: [arm64] + os: [linux] + + '@lmdb/lmdb-linux-arm@3.5.1': + resolution: {integrity: sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A==} + cpu: [arm] + os: [linux] + + '@lmdb/lmdb-linux-x64@3.5.1': + resolution: {integrity: sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g==} + cpu: [x64] + os: [linux] + + '@lmdb/lmdb-win32-arm64@3.5.1': + resolution: {integrity: sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w==} + cpu: [arm64] + os: [win32] + + '@lmdb/lmdb-win32-x64@3.5.1': + resolution: {integrity: sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg==} + cpu: [x64] + os: [win32] + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@mapbox/node-pre-gyp@2.0.3': + resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} + engines: {node: '>=18'} + hasBin: true + + '@modelcontextprotocol/sdk@1.26.0': + resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] - '@manypkg/get-packages@1.1.3': - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] - '@mapbox/node-pre-gyp@2.0.3': - resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} - engines: {node: '>=18'} - hasBin: true + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] '@napi-rs/nice-android-arm-eabi@1.1.1': resolution: {integrity: sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==} @@ -1489,6 +2083,43 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@npmcli/agent@4.0.0': + resolution: {integrity: sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/fs@5.0.0': + resolution: {integrity: sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/git@7.0.2': + resolution: {integrity: sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/installed-package-contents@4.0.0': + resolution: {integrity: sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + '@npmcli/node-gyp@5.0.0': + resolution: {integrity: sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/package-json@7.0.5': + resolution: {integrity: sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/promise-spawn@9.0.1': + resolution: {integrity: sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/redact@4.0.0': + resolution: {integrity: sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@npmcli/run-script@10.0.4': + resolution: {integrity: sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==} + engines: {node: ^20.17.0 || >=22.9.0} + '@nuxt/cli@3.35.1': resolution: {integrity: sha512-nX9XO+e3l9pnhHL2zsbnBmQb/nsOQYhGz2XiqE8X962QN9ufc1ZSuDZoTmQVv/ymkbYNR6hpNWW8RZQhuhzadw==} engines: {node: ^16.14.0 || >=18.0.0} @@ -1769,48 +2400,97 @@ packages: cpu: [x64] os: [win32] + '@oxc-parser/binding-android-arm-eabi@0.121.0': + resolution: {integrity: sha512-n07FQcySwOlzap424/PLMtOkbS7xOu8nsJduKL8P3COGHKgKoDYXwoAHCbChfgFpHnviehrLWIPX0lKGtbEk/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@oxc-parser/binding-android-arm-eabi@0.128.0': resolution: {integrity: sha512-aca6ZvzmCBUGOANQRiRQRZuRKYI3ENhcit6GisnknOOmcezfQc7xJ4dxlPU7MV7mOvrC7RNR1u3LAD7xyaiCxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] + '@oxc-parser/binding-android-arm64@0.121.0': + resolution: {integrity: sha512-/Dd1xIXboYAicw+twT2utxPD7bL8qh7d3ej0qvaYIMj3/EgIrGR+tSnjCUkiCT6g6uTC0neSS4JY8LxhdSU/sA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@oxc-parser/binding-android-arm64@0.128.0': resolution: {integrity: sha512-BbeDmuohoJ7Rz/it5wnkj69i/OsCPS3Z51nLEzwO/Y6YshtC4JU+15oNwhY8v4LRKRYclRc7ggOikwrsJ/eOEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@oxc-parser/binding-darwin-arm64@0.121.0': + resolution: {integrity: sha512-A0jNEvv7QMtCO1yk205t3DWU9sWUjQ2KNF0hSVO5W9R9r/R1BIvzG01UQAfmtC0dQm7sCrs5puixurKSfr2bRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@oxc-parser/binding-darwin-arm64@0.128.0': resolution: {integrity: sha512-tRUHPt80417QmvNpoSslJT1VY8NUbWdrWR+L14Zn+RbOTcaqB8E6PYE/ZGN8jjWBzqporiA/H4MfO50ew/NCNA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@oxc-parser/binding-darwin-x64@0.121.0': + resolution: {integrity: sha512-SsHzipdxTKUs3I9EOAPmnIimEeJOemqRlRDOp9LIj+96wtxZejF51gNibmoGq8KoqbT1ssAI5po/E3J+vEtXGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@oxc-parser/binding-darwin-x64@0.128.0': resolution: {integrity: sha512-rWI2Hb1Nt3U/vKsjyNvZzDC8i/l144U20DKjhzaTmwIhIiSRGeroPWWiImwypmKLqrw8GuIixbWJkpGWLbkzrQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@oxc-parser/binding-freebsd-x64@0.121.0': + resolution: {integrity: sha512-v1APOTkCp+RWOIDAHRoaeW/UoaHF15a60E8eUL6kUQXh+i4K7PBwq2Wi7jm8p0ymID5/m/oC1w3W31Z/+r7HQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@oxc-parser/binding-freebsd-x64@0.128.0': resolution: {integrity: sha512-hhpdVMaNCLgQxjgNPeeFzSeJMmZPc5lKfv0NGSI3egZq9EdnEGqeC8JsYsQjK7PoQgbvZ17xlj0SO5ziH5Obkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0': + resolution: {integrity: sha512-PmqPQuqHZyFVWA4ycr0eu4VnTMmq9laOHZd+8R359w6kzuNZPvmmunmNJ8ybkm769A0nCoVp3TJ6dUz7B3FYIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-gnueabihf@0.128.0': resolution: {integrity: sha512-093zNw0zZ/e/obML+rhlSdmnzR0mVZluPcAkxunEc5E3F0yBVsFn24Y1ILfsEte11Ud041qn/gp2OJ1jxNqUng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.121.0': + resolution: {integrity: sha512-vF24htj+MOH+Q7y9A8NuC6pUZu8t/C2Fr/kDOi2OcNf28oogr2xadBPXAbml802E8wRAVfbta6YLDQTearz+jw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@oxc-parser/binding-linux-arm-musleabihf@0.128.0': resolution: {integrity: sha512-fq7DmKmfC+dvD97IXrgbph6Jzwe0EDu+PYMofmzZ6fv5X1k9vtaqLpDGMuICO9MmUnyKAQmVl+wIv2RNy4Dz8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@oxc-parser/binding-linux-arm64-gnu@0.121.0': + resolution: {integrity: sha512-wjH8cIG2Lu/3d64iZpbYr73hREMgKAfu7fqpXjgM2S16y2zhTfDIp8EQjxO8vlDtKP5Rc7waZW72lh8nZtWrpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-arm64-gnu@0.128.0': resolution: {integrity: sha512-Xvm48jJah8TlIrURIjNOP/gNiGe6aKvCB+r06VliflFo8Kq7VOLE8PxtgShJzZIqubrgdMdYfvuPPozn7F6MbQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1818,6 +2498,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-arm64-musl@0.121.0': + resolution: {integrity: sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-arm64-musl@0.128.0': resolution: {integrity: sha512-M7iwBGmYJTx+pKOYFjI0buop4gJvlmcVzFGaXPt21DKpQkbQZG1f63Yg7LloIYT/t9yLxCw0Lhfx/RFlAlMSjA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1825,6 +2512,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': + resolution: {integrity: sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-ppc64-gnu@0.128.0': resolution: {integrity: sha512-21LGNIZb1Pcfk5/EGsqabrxv4yqQOWis1407JJrClS7XpFCrbvr74YAB1V+m54cYbwvO6UWwQqS4WecxiyfCRg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1832,6 +2526,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': + resolution: {integrity: sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-riscv64-gnu@0.128.0': resolution: {integrity: sha512-gyHjOTFpg9bTTYjxPmQirvufb89+VdZwVfcMtAUyPr6F5H8ZswvCQshK4qOW+Q+2Xyb33hduRgY/eFHJQjU/vQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1839,6 +2540,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-riscv64-musl@0.121.0': + resolution: {integrity: sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-riscv64-musl@0.128.0': resolution: {integrity: sha512-X6Q2oKUrP5GyDd2xniuEBLk6aFQCZ97W2+aVXGgJXdjx5t4/oFuA9ri0wLOUrBIX+qdSuK581snMBio4z910eA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1846,6 +2554,13 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-linux-s390x-gnu@0.121.0': + resolution: {integrity: sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-s390x-gnu@0.128.0': resolution: {integrity: sha512-BdzTmqxfxoYkpgokoLaSnOX6T+R3/goL42klre2tnG+kHbG2TXS0VN+P5BPofH1axdKOHy5ei4ENZrjmCOt2lA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1853,6 +2568,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.121.0': + resolution: {integrity: sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@oxc-parser/binding-linux-x64-gnu@0.128.0': resolution: {integrity: sha512-OO1nW2Q7sSYYvJZpDHdvyFSdRaVcQqRijZSSmWVMqFxPYy8cEF45zJ9fcdIYuzIT3jYq6YRhEFm/VMWNWhE22Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1860,6 +2582,13 @@ packages: os: [linux] libc: [glibc] + '@oxc-parser/binding-linux-x64-musl@0.121.0': + resolution: {integrity: sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@oxc-parser/binding-linux-x64-musl@0.128.0': resolution: {integrity: sha512-4NehAe404MRdoZVS9DW8C5XbJwbXIc/KfVlYdpi5vE4081zc9Y0YzKVqyOYj/Puye7/Do+ohaONBFWlEHYl9hw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1867,35 +2596,70 @@ packages: os: [linux] libc: [musl] + '@oxc-parser/binding-openharmony-arm64@0.121.0': + resolution: {integrity: sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@oxc-parser/binding-openharmony-arm64@0.128.0': resolution: {integrity: sha512-kVbqgW9xLL8bh8oc7aYOJilRKXE5G33+tE0jan+duo/9OriaFRpijcCwT2waWs2oqYROYq0GlE7/p3ywoshVeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@oxc-parser/binding-wasm32-wasi@0.121.0': + resolution: {integrity: sha512-5TFISkPTymKvsmIlKasPVTPuWxzCcrT8pM+p77+mtQbIZDd1UC8zww4CJcRI46kolmgrEX6QpKO8AvWMVZ+ifw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@oxc-parser/binding-wasm32-wasi@0.128.0': resolution: {integrity: sha512-L38ojghJYHmgiz6fJd7jwLB/ESDBpB02NdFxh+smqVM6P2anCEvHn0jhaSrt5eVNR1Ak8+moOeftUlofeyvniA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@oxc-parser/binding-win32-arm64-msvc@0.121.0': + resolution: {integrity: sha512-V0pxh4mql4XTt3aiEtRNUeBAUFOw5jzZNxPABLaOKAWrVzSr9+XUaB095lY7jqMf5t8vkfh8NManGB28zanYKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@oxc-parser/binding-win32-arm64-msvc@0.128.0': resolution: {integrity: sha512-xgvO35GyHBtjlQ5AEpaYr7Rll1rvY7zqIhT6ty8E3ezBW2J1SFLjIDEvI/tcgDg6oaseDAqVcM+jU1HuCekgZw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.121.0': + resolution: {integrity: sha512-4Ob1qvYMPnlF2N9rdmKdkQFdrq16QVcQwBsO8yiPZXof0fHKFF+LmQV501XFbi7lHyrKm8rlJRfQ/M8bZZPVLw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@oxc-parser/binding-win32-ia32-msvc@0.128.0': resolution: {integrity: sha512-OY+3eM2SN72prHKRB22mPz8o5A/7dJ+f5DFLBVvggyZhEaNDAH9IB+ElMjmOkOIwf5MDCUAowCK7pAncNxzpBA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.121.0': + resolution: {integrity: sha512-BOp1KCzdboB1tPqoCPXgntgFs0jjeSyOXHzgxVFR7B/qfr3F8r4YDacHkTOUNXtDgM8YwKnkf3rE5gwALYX7NA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-parser/binding-win32-x64-msvc@0.128.0': resolution: {integrity: sha512-NE9ny+cPUCCObXa0IKLfj0tCdPd7pe/dz9ZpkxpUOymB3miNeMPybdlYYTBSGJUalMWeBM85/4JcCErCNTqOXw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@oxc-project/types@0.113.0': + resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==} + + '@oxc-project/types@0.121.0': + resolution: {integrity: sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw==} + '@oxc-project/types@0.128.0': resolution: {integrity: sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==} @@ -2143,30 +2907,60 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.4': + resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.18': resolution: {integrity: sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.4': + resolution: {integrity: sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.18': resolution: {integrity: sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.4': + resolution: {integrity: sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.18': resolution: {integrity: sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.4': + resolution: {integrity: sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18': resolution: {integrity: sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': + resolution: {integrity: sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2174,6 +2968,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': + resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': resolution: {integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2181,6 +2982,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': + resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2202,6 +3010,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': + resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': resolution: {integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2209,35 +3024,68 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': + resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': resolution: {integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': + resolution: {integrity: sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': resolution: {integrity: sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': + resolution: {integrity: sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': resolution: {integrity: sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': + resolution: {integrity: sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': resolution: {integrity: sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': + resolution: {integrity: sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-rc.13': resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} '@rolldown/pluginutils@1.0.0-rc.18': resolution: {integrity: sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==} + '@rolldown/pluginutils@1.0.0-rc.4': + resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==} + '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} @@ -2474,6 +3322,34 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + '@schematics/angular@21.2.10': + resolution: {integrity: sha512-RWoD2iARXfHmMkAzmAsefj5rcyihhVPW4OY7+pdpfFYCHdGPreSbEAhCcTF2dJjJA/71N5qj5bFdSIJhO2aZ1A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@sigstore/bundle@4.0.0': + resolution: {integrity: sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/core@3.2.0': + resolution: {integrity: sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/protobuf-specs@0.5.1': + resolution: {integrity: sha512-/ScWUhhoFasJsSRGTVBwId1loQjjnjAfE4djL6ZhrXRpNCmPTnUKF5Jokd58ILseOMjzET3UrMOtJPS9sYeI0g==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/sign@4.1.1': + resolution: {integrity: sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/tuf@4.0.2': + resolution: {integrity: sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + '@sigstore/verify@3.1.0': + resolution: {integrity: sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==} + engines: {node: ^20.17.0 || >=22.9.0} + '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} @@ -2500,6 +3376,15 @@ packages: svelte: ^5.46.4 vite: ^8.0.11 + '@testing-library/angular@19.2.1': + resolution: {integrity: sha512-COWnkcTKFwb4fReLlInNATH1cPYmujWINnVMXdy0oJHidz0XIrdJopx/jwCBDIAgD4qtz+wEDsUWM4gI78Hakw==} + peerDependencies: + '@angular/common': '>= 21.0.0' + '@angular/core': '>= 21.0.0' + '@angular/platform-browser': '>= 21.0.0' + '@angular/router': '>= 21.0.0' + '@testing-library/dom': ^10.0.0 + '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} @@ -2552,6 +3437,17 @@ packages: '@vue/compiler-sfc': optional: true + '@ts-morph/common@0.22.0': + resolution: {integrity: sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==} + + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tufjs/models@4.1.0': + resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} + engines: {node: ^20.17.0 || >=22.9.0} + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -2662,6 +3558,12 @@ packages: engines: {node: '>=20'} hasBin: true + '@vitejs/plugin-basic-ssl@2.1.4': + resolution: {integrity: sha512-HXciTXN/sDBYWgeAD4V4s0DN0g72x5mlxQhHxtYu3Tt8BLa6MzcJZUyDVFCdtjNs3bfENVHVzOsmooTVuNgAAw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + peerDependencies: + vite: ^8.0.11 + '@vitejs/plugin-react@6.0.1': resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2810,6 +3712,9 @@ packages: '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} + '@yarnpkg/lockfile@1.1.0': + resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -2818,10 +3723,18 @@ packages: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} engines: {node: ^18.17.0 || >=20.5.0} + abbrev@4.0.0: + resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} + engines: {node: ^20.17.0 || >=22.9.0} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2841,15 +3754,38 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + algoliasearch@5.48.1: + resolution: {integrity: sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==} + engines: {node: '>= 14.0.0'} + alien-signals@3.1.2: resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==} + angular-eslint@21.3.1: + resolution: {integrity: sha512-VGQWTyuPAEO/AnZuqHxGBJMYSiZ0tbrHx/OgPCRTKHfbrFU4x+zivS84h9UWoDpDtius1RyD+ZReFjTAEWptiA==} + peerDependencies: + '@angular/cli': '>= 21.0.0 < 22.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + typescript-eslint: ^8.0.0 + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -2910,6 +3846,10 @@ packages: resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.2: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} @@ -3013,6 +3953,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + beasties@0.4.1: + resolution: {integrity: sha512-2Imdcw3LznDuxAbJM26RHniOLAzE6WgrK8OuvVXCQtNBS8rsnD9zsSEa3fHl4hHpUY7BYTlrpvtPVbvu9G6neg==} + engines: {node: '>=18.0.0'} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -3029,6 +3973,10 @@ packages: birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -3065,6 +4013,10 @@ packages: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + c12@3.3.4: resolution: {integrity: sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==} peerDependencies: @@ -3077,6 +4029,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cacache@20.0.4: + resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} + engines: {node: ^20.17.0 || >=22.9.0} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -3144,6 +4100,10 @@ packages: resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@9.0.1: resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} engines: {node: '>=20'} @@ -3156,6 +4116,9 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + code-block-writer@12.0.0: + resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -3163,6 +4126,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -3207,6 +4173,14 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -3222,6 +4196,14 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + copy-anything@3.0.5: resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} engines: {node: '>=12.13'} @@ -3229,6 +4211,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -3258,6 +4244,9 @@ packages: css-select@5.2.2: resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-select@6.0.0: + resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==} + css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -3270,6 +4259,10 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + css-what@7.0.0: + resolution: {integrity: sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -3482,6 +4475,9 @@ packages: emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -3494,6 +4490,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -3502,10 +4502,17 @@ packages: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true @@ -3559,6 +4566,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.27.7: resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} @@ -3725,6 +4737,14 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -3733,6 +4753,19 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + express-rate-limit@8.5.1: + resolution: {integrity: sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -3805,6 +4838,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-cache-directory@6.0.0: resolution: {integrity: sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==} engines: {node: '>=20'} @@ -3835,6 +4872,10 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -3850,6 +4891,10 @@ packages: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3924,6 +4969,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -4010,12 +5058,20 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hono@4.12.18: + resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} + engines: {node: '>=16.9.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -4023,10 +5079,20 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + http-shutdown@1.2.2: resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -4062,6 +5128,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-walk@8.0.0: + resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==} + engines: {node: ^20.17.0 || >=22.9.0} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4102,6 +5172,10 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + injection-js@2.6.1: resolution: {integrity: sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==} @@ -4113,6 +5187,14 @@ packages: resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} engines: {node: '>=12.22.0'} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + ipx@3.1.1: resolution: {integrity: sha512-7Xnt54Dco7uYkfdAw0r2vCly3z0rSaVhEXMzPvl3FndsTVm5p26j+PO+gyinkYmcsEUvX2Rh7OGK7KzYWRu6BA==} hasBin: true @@ -4169,6 +5251,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} @@ -4224,6 +5310,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} @@ -4311,6 +5400,10 @@ packages: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} @@ -4335,6 +5428,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-beautify@1.15.4: resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} engines: {node: '>=14'} @@ -4378,12 +5474,19 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@5.0.0: + resolution: {integrity: sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==} + engines: {node: ^20.17.0 || >=22.9.0} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -4398,6 +5501,10 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -4530,6 +5637,14 @@ packages: resolution: {integrity: sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==} engines: {node: '>=22.13.0'} + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + lmdb@3.5.1: + resolution: {integrity: sha512-NYHA0MRPjvNX+vSw8Xxg6FLKxzAG+e7Pt8RqAQA/EehzHVXq9SxDqJIN3JL1hK0dweb884y8kIh6rkWvPyg9Wg==} + hasBin: true + local-pkg@1.1.2: resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} @@ -4610,6 +5725,10 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + make-fetch-happen@15.0.5: + resolution: {integrity: sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==} + engines: {node: ^20.17.0 || >=22.9.0} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4620,10 +5739,18 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -4676,6 +5803,30 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@5.0.2: + resolution: {integrity: sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -4684,6 +5835,11 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mkdist@2.4.1: resolution: {integrity: sha512-Ezk0gi04GJBkqMfsksICU5Rjoemc4biIekwgrONWVPor2EO/N9nBgN6MZXAf7Yw4mDDhrNyKbdETaHNevfumKg==} hasBin: true @@ -4722,9 +5878,20 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.12: + resolution: {integrity: sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4741,6 +5908,10 @@ packages: engines: {node: '>= 4.4.x'} hasBin: true + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + ng-packagr@21.2.3: resolution: {integrity: sha512-jGq6yu0G6KReVK0i5RYVoV9HDL0mU626HrLBu5xvc8ZJ92n/+rLrFJuXdCnkroB9um+FBTQe/or6/A/2GAKhLw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -4764,6 +5935,9 @@ packages: xml2js: optional: true + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -4787,10 +5961,19 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true + node-gyp@12.3.0: + resolution: {integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + node-mock-http@1.0.4: resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} @@ -4807,10 +5990,43 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + nopt@9.0.0: + resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npm-bundled@5.0.0: + resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-install-checks@8.0.0: + resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-normalize-package-bin@5.0.0: + resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-package-arg@13.0.2: + resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-packlist@10.0.4: + resolution: {integrity: sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-pick-manifest@11.0.3: + resolution: {integrity: sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-registry-fetch@19.1.1: + resolution: {integrity: sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==} + engines: {node: ^20.17.0 || >=22.9.0} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4901,6 +6117,9 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} @@ -4921,10 +6140,17 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@9.3.0: + resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} + engines: {node: '>=20'} + ora@9.4.0: resolution: {integrity: sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==} engines: {node: '>=20'} + ordered-binary@1.6.1: + resolution: {integrity: sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -4936,6 +6162,10 @@ packages: resolution: {integrity: sha512-VIXQO2W886aB+N17yV55Sack6aCpbUqtuNAYhNcPV6dFiWIZ5+kwOjvvw36igWwoljfjWhasu99CQ5wtvPJDYg==} engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.121.0: + resolution: {integrity: sha512-ek9o58+SCv6AV7nchiAcUJy1DNE2CC5WRdBcO0mF+W4oRjNQfPO7b3pLjTHSFECpHkKGOZSQxx3hk8viIL5YCg==} + engines: {node: ^20.19.0 || >=22.12.0} + oxc-parser@0.128.0: resolution: {integrity: sha512-XkOw3eiIxAgQ19WRew/Bq9wc5Ga/guaWIzDBzq80z1PyuDNGvWBpPby9k6YGwV8A8uMw+Nlq3xqlzuDYmUFYUw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4973,6 +6203,10 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} + p-map@7.0.4: + resolution: {integrity: sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==} + engines: {node: '>=18'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -4983,6 +6217,11 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + pacote@21.3.1: + resolution: {integrity: sha512-O0EDXi85LF4AzdjG74GUwEArhdvawi/YOHcsW6IijKNj7wm8IvEWNF5GnfuxNpQ/ZpO3L37+v8hqdVh8GgWYhg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -4991,6 +6230,12 @@ packages: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} + parse5-html-rewriting-stream@8.0.0: + resolution: {integrity: sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==} + + parse5-sax-parser@8.0.0: + resolution: {integrity: sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} @@ -5024,6 +6269,9 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -5056,6 +6304,10 @@ packages: resolution: {integrity: sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==} engines: {node: '>=20.x'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-dir@8.0.0: resolution: {integrity: sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==} engines: {node: '>=18'} @@ -5134,6 +6386,9 @@ packages: ts-node: optional: true + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + postcss-merge-longhand@7.0.7: resolution: {integrity: sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -5315,6 +6570,10 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + proc-log@6.1.0: + resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} + engines: {node: ^20.17.0 || >=22.9.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -5322,6 +6581,10 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -5331,6 +6594,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -5338,6 +6605,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} + engines: {node: '>=0.6'} + quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} @@ -5351,6 +6622,10 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc9@3.0.1: resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} @@ -5461,12 +6736,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup-plugin-dts@6.3.0: - resolution: {integrity: sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==} - engines: {node: '>=16'} - peerDependencies: - rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 + rolldown@1.0.0-rc.4: + resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true rollup-plugin-dts@6.4.1: resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} @@ -5496,6 +6769,10 @@ packages: rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-applescript@7.1.0: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} @@ -5531,6 +6808,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass@1.97.3: + resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==} + engines: {node: '>=14.0.0'} + hasBin: true + sass@1.99.0: resolution: {integrity: sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==} engines: {node: '>=14.0.0'} @@ -5639,6 +6921,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sigstore@4.1.0: + resolution: {integrity: sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==} + engines: {node: ^20.17.0 || >=22.9.0} + simple-git@3.33.0: resolution: {integrity: sha512-D4V/tGC2sjsoNhoMybKyGoE+v8A60hRawKQ1iFRA1zwuDgGZCBJ4ByOzZ5J8joBbi4Oam0qiPH+GhzmSBwbJng==} @@ -5670,10 +6956,22 @@ packages: resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} engines: {node: '>=20'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + smob@1.6.1: resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} engines: {node: '>=20.0.0'} + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -5692,6 +6990,15 @@ packages: spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -5700,6 +7007,10 @@ packages: engines: {node: '>=20.16.0'} hasBin: true + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} + engines: {node: ^20.17.0 || >=22.9.0} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -5731,6 +7042,10 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -5877,6 +7192,10 @@ packages: resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} engines: {node: '>=18'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} @@ -5921,6 +7240,9 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-morph@21.0.1: + resolution: {integrity: sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==} + tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -5934,6 +7256,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tuf-js@4.1.0: + resolution: {integrity: sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==} + engines: {node: ^20.17.0 || >=22.9.0} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -5942,6 +7268,10 @@ packages: resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} engines: {node: '>=20'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + type-level-regexp@0.1.17: resolution: {integrity: sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==} @@ -5961,6 +7291,13 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typescript-eslint@8.59.2: + resolution: {integrity: sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -5994,6 +7331,14 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + undici@6.25.0: + resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} + engines: {node: '>=18.17'} + + undici@7.24.4: + resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} + engines: {node: '>=20.18.1'} + undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} @@ -6025,6 +7370,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unplugin-utils@0.3.1: resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} engines: {node: '>=20.19.0'} @@ -6128,6 +7477,14 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + validate-npm-package-name@7.0.2: + resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} + engines: {node: ^20.17.0 || >=22.9.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vite-dev-rpc@1.1.0: resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} peerDependencies: @@ -6350,6 +7707,13 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + weak-lru-cache@1.2.2: + resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -6410,10 +7774,17 @@ packages: resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} engines: {node: '>=20'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -6457,6 +7828,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -6482,6 +7856,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -6499,19 +7877,300 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zone.js@0.16.2: + resolution: {integrity: sha512-Eky7p2Z1Ig3NnbfodSPoARCjKBSTFMnE/ACsP1L/XJEfY4SdOFce19BsUCWVwL6K5ABZFy5J3bjcMWffX+YM3Q==} + +snapshots: + + '@algolia/abtesting@1.14.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-abtesting@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-analytics@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-common@5.48.1': {} + + '@algolia/client-insights@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-personalization@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-query-suggestions@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/client-search@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/ingestion@1.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/monitoring@1.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/recommend@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + + '@algolia/requester-browser-xhr@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + + '@algolia/requester-fetch@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + + '@algolia/requester-node-http@5.48.1': + dependencies: + '@algolia/client-common': 5.48.1 + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@analogjs/vite-plugin-angular@2.5.0(@angular/build@21.2.10(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(@angular/compiler@21.2.12)(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.5)(yaml@2.8.4))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4))': + dependencies: + magic-string: 0.30.21 + obug: 2.1.1 + oxc-parser: 0.121.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + tinyglobby: 0.2.16 + ts-morph: 21.0.1 + optionalDependencies: + '@angular/build': 21.2.10(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(@angular/compiler@21.2.12)(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.5)(yaml@2.8.4) + vite: 8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + '@angular-devkit/architect@0.2102.10(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/core@21.2.10(chokidar@5.0.0)': + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + jsonc-parser: 3.3.1 + picomatch: 4.0.4 + rxjs: 7.8.2 + source-map: 0.7.6 + optionalDependencies: + chokidar: 5.0.0 + + '@angular-devkit/schematics@21.2.10(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + jsonc-parser: 3.3.1 + magic-string: 0.30.21 + ora: 9.3.0 + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@angular-eslint/builder@21.3.1(@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@angular-devkit/architect': 0.2102.10(chokidar@5.0.0) + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + '@angular/cli': 21.2.10(@types/node@25.6.2)(chokidar@5.0.0) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - chokidar + + '@angular-eslint/bundled-angular-compiler@21.3.1': {} + + '@angular-eslint/eslint-plugin-template@21.3.1(@angular-eslint/template-parser@21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.59.2)(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.3.1 + '@angular-eslint/template-parser': 21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/utils': 21.3.1(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + aria-query: 5.3.2 + axobject-query: 4.1.0 + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + + '@angular-eslint/eslint-plugin@21.3.1(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.3.1 + '@angular-eslint/utils': 21.3.1(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + + '@angular-eslint/schematics@21.3.1(@angular-eslint/template-parser@21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0))(@typescript-eslint/types@8.59.2)(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.10(chokidar@5.0.0) + '@angular-eslint/eslint-plugin': 21.3.1(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.59.2)(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular/cli': 21.2.10(@types/node@25.6.2)(chokidar@5.0.0) + ignore: 7.0.5 + semver: 7.7.4 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@angular-eslint/template-parser' + - '@typescript-eslint/types' + - '@typescript-eslint/utils' + - chokidar + - eslint + - typescript + + '@angular-eslint/template-parser@21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.3.1 + eslint: 9.39.4(jiti@2.6.1) + eslint-scope: 9.1.2 + typescript: 5.9.3 -snapshots: + '@angular-eslint/utils@21.3.1(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.3.1 + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 - '@ampproject/remapping@2.3.0': + '@angular/build@21.2.10(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(@angular/compiler@21.2.12)(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.6.2)(chokidar@5.0.0)(jiti@2.6.1)(less@4.6.4)(ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3))(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)(vitest@4.1.5)(yaml@2.8.4)': dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@ampproject/remapping': 2.3.0 + '@angular-devkit/architect': 0.2102.10(chokidar@5.0.0) + '@angular/compiler': 21.2.12 + '@angular/compiler-cli': 21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3) + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-split-export-declaration': 7.24.7 + '@inquirer/confirm': 5.1.21(@types/node@25.6.2) + '@vitejs/plugin-basic-ssl': 2.1.4(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4)) + beasties: 0.4.1 + browserslist: 4.28.2 + esbuild: 0.27.3 + https-proxy-agent: 7.0.6 + istanbul-lib-instrument: 6.0.3 + jsonc-parser: 3.3.1 + listr2: 9.0.5 + magic-string: 0.30.21 + mrmime: 2.0.1 + parse5-html-rewriting-stream: 8.0.0 + picomatch: 4.0.4 + piscina: 5.1.4 + rolldown: 1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + sass: 1.97.3 + semver: 7.7.4 + source-map-support: 0.5.21 + tinyglobby: 0.2.15 + tslib: 2.8.1 + typescript: 5.9.3 + undici: 7.24.4 + vite: 8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) + watchpack: 2.5.1 + optionalDependencies: + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)) + less: 4.6.4 + lmdb: 3.5.1 + ng-packagr: 21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3) + postcss: 8.5.14 + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4)) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - '@types/node' + - '@vitejs/devtools' + - chokidar + - jiti + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0)': + dependencies: + '@angular-devkit/architect': 0.2102.10(chokidar@5.0.0) + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.10(chokidar@5.0.0) + '@inquirer/prompts': 7.10.1(@types/node@25.6.2) + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@25.6.2))(@types/node@25.6.2)(listr2@9.0.5) + '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) + '@schematics/angular': 21.2.10(chokidar@5.0.0) + '@yarnpkg/lockfile': 1.1.0 + algoliasearch: 5.48.1 + ini: 6.0.0 + jsonc-parser: 3.3.1 + listr2: 9.0.5 + npm-package-arg: 13.0.2 + pacote: 21.3.1 + parse5-html-rewriting-stream: 8.0.0 + semver: 7.7.4 + yargs: 18.0.0 + zod: 4.3.6 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@types/node' + - chokidar + - supports-color - '@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2)': + '@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2)': dependencies: - '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) rxjs: 7.8.2 tslib: 2.8.1 @@ -6535,26 +8194,35 @@ snapshots: dependencies: tslib: 2.8.1 - '@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)': + '@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: '@angular/compiler': 21.2.12 + zone.js: 0.16.2 - '@angular/forms@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)))(rxjs@7.8.2)': + '@angular/forms@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2) - '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) - '@angular/platform-browser': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)) + '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))': + '@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))': + dependencies: + '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) + tslib: 2.8.1 + + '@angular/router@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2))(rxjs@7.8.2) - '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2) + '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)) + rxjs: 7.8.2 tslib: 2.8.1 '@asamuzakjp/css-color@5.1.11': @@ -6685,6 +8353,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.29.0 + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -6988,6 +8660,9 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.27.3': + optional: true + '@esbuild/aix-ppc64@0.27.7': optional: true @@ -6997,6 +8672,9 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.27.3': + optional: true + '@esbuild/android-arm64@0.27.7': optional: true @@ -7006,6 +8684,9 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.27.3': + optional: true + '@esbuild/android-arm@0.27.7': optional: true @@ -7015,6 +8696,9 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.27.3': + optional: true + '@esbuild/android-x64@0.27.7': optional: true @@ -7024,6 +8708,9 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.27.3': + optional: true + '@esbuild/darwin-arm64@0.27.7': optional: true @@ -7033,6 +8720,9 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.27.3': + optional: true + '@esbuild/darwin-x64@0.27.7': optional: true @@ -7042,6 +8732,9 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.27.3': + optional: true + '@esbuild/freebsd-arm64@0.27.7': optional: true @@ -7051,6 +8744,9 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.27.3': + optional: true + '@esbuild/freebsd-x64@0.27.7': optional: true @@ -7060,6 +8756,9 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.27.3': + optional: true + '@esbuild/linux-arm64@0.27.7': optional: true @@ -7069,6 +8768,9 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.27.3': + optional: true + '@esbuild/linux-arm@0.27.7': optional: true @@ -7078,6 +8780,9 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.27.3': + optional: true + '@esbuild/linux-ia32@0.27.7': optional: true @@ -7087,6 +8792,9 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.27.3': + optional: true + '@esbuild/linux-loong64@0.27.7': optional: true @@ -7096,6 +8804,9 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.27.3': + optional: true + '@esbuild/linux-mips64el@0.27.7': optional: true @@ -7105,6 +8816,9 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.27.3': + optional: true + '@esbuild/linux-ppc64@0.27.7': optional: true @@ -7114,6 +8828,9 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.27.3': + optional: true + '@esbuild/linux-riscv64@0.27.7': optional: true @@ -7123,6 +8840,9 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.27.3': + optional: true + '@esbuild/linux-s390x@0.27.7': optional: true @@ -7132,6 +8852,9 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.27.3': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true @@ -7141,6 +8864,9 @@ snapshots: '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.27.3': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true @@ -7150,6 +8876,9 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.27.3': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true @@ -7159,6 +8888,9 @@ snapshots: '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.27.3': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true @@ -7168,6 +8900,9 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.27.3': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true @@ -7177,6 +8912,9 @@ snapshots: '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.27.3': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true @@ -7186,6 +8924,9 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.27.3': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true @@ -7195,6 +8936,9 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.27.3': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true @@ -7204,6 +8948,9 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.27.3': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true @@ -7213,6 +8960,9 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.27.3': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true @@ -7306,6 +9056,15 @@ snapshots: '@fastify/accept-negotiator@2.0.1': optional: true + '@gar/promise-retry@1.0.3': {} + + '@harperfast/extended-iterable@1.0.3': + optional: true + + '@hono/node-server@1.19.14(hono@4.12.18)': + dependencies: + hono: 4.12.18 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -7414,6 +9173,54 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@25.6.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/confirm@5.1.21(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/core@10.3.2(@types/node@25.6.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.2) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/editor@4.2.23(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/external-editor': 1.0.3(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/expand@4.0.23(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.2 + '@inquirer/external-editor@1.0.3(@types/node@25.6.2)': dependencies: chardet: 2.1.1 @@ -7421,6 +9228,76 @@ snapshots: optionalDependencies: '@types/node': 25.6.2 + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/number@3.0.23(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/password@4.0.23(@types/node@25.6.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/prompts@7.10.1(@types/node@25.6.2)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.6.2) + '@inquirer/confirm': 5.1.21(@types/node@25.6.2) + '@inquirer/editor': 4.2.23(@types/node@25.6.2) + '@inquirer/expand': 4.0.23(@types/node@25.6.2) + '@inquirer/input': 4.3.1(@types/node@25.6.2) + '@inquirer/number': 3.0.23(@types/node@25.6.2) + '@inquirer/password': 4.0.23(@types/node@25.6.2) + '@inquirer/rawlist': 4.1.11(@types/node@25.6.2) + '@inquirer/search': 3.2.2(@types/node@25.6.2) + '@inquirer/select': 4.4.2(@types/node@25.6.2) + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/rawlist@4.1.11(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/search@3.2.2(@types/node@25.6.2)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/select@4.4.2(@types/node@25.6.2)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.2) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.2) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.2 + + '@inquirer/type@3.0.10(@types/node@25.6.2)': + optionalDependencies: + '@types/node': 25.6.2 + '@ioredis/commands@1.5.1': {} '@isaacs/cliui@9.0.0': {} @@ -7429,6 +9306,8 @@ snapshots: dependencies: minipass: 7.1.3 + '@istanbuljs/schema@0.1.6': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -7461,6 +9340,35 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.6.2))(@types/node@25.6.2)(listr2@9.0.5)': + dependencies: + '@inquirer/prompts': 7.10.1(@types/node@25.6.2) + '@inquirer/type': 3.0.10(@types/node@25.6.2) + listr2: 9.0.5 + transitivePeerDependencies: + - '@types/node' + + '@lmdb/lmdb-darwin-arm64@3.5.1': + optional: true + + '@lmdb/lmdb-darwin-x64@3.5.1': + optional: true + + '@lmdb/lmdb-linux-arm64@3.5.1': + optional: true + + '@lmdb/lmdb-linux-arm@3.5.1': + optional: true + + '@lmdb/lmdb-linux-x64@3.5.1': + optional: true + + '@lmdb/lmdb-win32-arm64@3.5.1': + optional: true + + '@lmdb/lmdb-win32-x64@3.5.1': + optional: true + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.28.6 @@ -7490,6 +9398,46 @@ snapshots: - encoding - supports-color + '@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.14(hono@4.12.18) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.8 + express: 5.2.1 + express-rate-limit: 8.5.1(express@5.2.1) + hono: 4.12.18 + jose: 6.2.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + '@napi-rs/nice-android-arm-eabi@1.1.1': optional: true @@ -7584,6 +9532,62 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@npmcli/agent@4.0.0': + dependencies: + agent-base: 7.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 11.3.6 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@5.0.0': + dependencies: + semver: 7.7.4 + + '@npmcli/git@7.0.2': + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/promise-spawn': 9.0.1 + ini: 6.0.0 + lru-cache: 11.3.6 + npm-pick-manifest: 11.0.3 + proc-log: 6.1.0 + semver: 7.7.4 + which: 6.0.1 + + '@npmcli/installed-package-contents@4.0.0': + dependencies: + npm-bundled: 5.0.0 + npm-normalize-package-bin: 5.0.0 + + '@npmcli/node-gyp@5.0.0': {} + + '@npmcli/package-json@7.0.5': + dependencies: + '@npmcli/git': 7.0.2 + glob: 13.0.6 + hosted-git-info: 9.0.3 + json-parse-even-better-errors: 5.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + spdx-expression-parse: 4.0.0 + + '@npmcli/promise-spawn@9.0.1': + dependencies: + which: 6.0.1 + + '@npmcli/redact@4.0.0': {} + + '@npmcli/run-script@10.0.4': + dependencies: + '@npmcli/node-gyp': 5.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/promise-spawn': 9.0.1 + node-gyp: 12.3.0 + proc-log: 6.1.0 + '@nuxt/cli@3.35.1(@nuxt/schema@4.4.4)(cac@6.7.14)(magicast@0.5.2)': dependencies: '@bomb.sh/tab': 0.0.14(cac@6.7.14)(citty@0.2.2) @@ -8086,54 +10090,110 @@ snapshots: '@oxc-minify/binding-win32-x64-msvc@0.128.0': optional: true + '@oxc-parser/binding-android-arm-eabi@0.121.0': + optional: true + '@oxc-parser/binding-android-arm-eabi@0.128.0': optional: true + '@oxc-parser/binding-android-arm64@0.121.0': + optional: true + '@oxc-parser/binding-android-arm64@0.128.0': optional: true + '@oxc-parser/binding-darwin-arm64@0.121.0': + optional: true + '@oxc-parser/binding-darwin-arm64@0.128.0': optional: true + '@oxc-parser/binding-darwin-x64@0.121.0': + optional: true + '@oxc-parser/binding-darwin-x64@0.128.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.128.0': + '@oxc-parser/binding-freebsd-x64@0.121.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.128.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0': optional: true '@oxc-parser/binding-linux-arm-gnueabihf@0.128.0': optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm-musleabihf@0.128.0': optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm64-gnu@0.128.0': optional: true + '@oxc-parser/binding-linux-arm64-musl@0.121.0': + optional: true + '@oxc-parser/binding-linux-arm64-musl@0.128.0': optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-ppc64-gnu@0.128.0': optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-riscv64-gnu@0.128.0': optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.121.0': + optional: true + '@oxc-parser/binding-linux-riscv64-musl@0.128.0': optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-s390x-gnu@0.128.0': optional: true + '@oxc-parser/binding-linux-x64-gnu@0.121.0': + optional: true + '@oxc-parser/binding-linux-x64-gnu@0.128.0': optional: true + '@oxc-parser/binding-linux-x64-musl@0.121.0': + optional: true + '@oxc-parser/binding-linux-x64-musl@0.128.0': optional: true + '@oxc-parser/binding-openharmony-arm64@0.121.0': + optional: true + '@oxc-parser/binding-openharmony-arm64@0.128.0': optional: true + '@oxc-parser/binding-wasm32-wasi@0.121.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + '@oxc-parser/binding-wasm32-wasi@0.128.0': dependencies: '@emnapi/core': 1.10.0 @@ -8141,15 +10201,28 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.121.0': + optional: true + '@oxc-parser/binding-win32-arm64-msvc@0.128.0': optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.121.0': + optional: true + '@oxc-parser/binding-win32-ia32-msvc@0.128.0': optional: true + '@oxc-parser/binding-win32-x64-msvc@0.121.0': + optional: true + '@oxc-parser/binding-win32-x64-msvc@0.128.0': optional: true + '@oxc-project/types@0.113.0': {} + + '@oxc-project/types@0.121.0': {} + '@oxc-project/types@0.128.0': {} '@oxc-transform/binding-android-arm-eabi@0.128.0': @@ -8302,24 +10375,45 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.18': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.4': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.18': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.4': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.18': optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.4': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.18': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.4': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': optional: true @@ -8329,12 +10423,21 @@ snapshots: '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': dependencies: '@emnapi/core': 1.10.0 @@ -8342,16 +10445,32 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': + optional: true + '@rolldown/pluginutils@1.0.0-rc.13': {} '@rolldown/pluginutils@1.0.0-rc.18': {} + '@rolldown/pluginutils@1.0.0-rc.4': {} + '@rolldown/pluginutils@1.0.0-rc.7': {} '@rollup/plugin-alias@5.1.1(rollup@4.60.3)': @@ -8514,6 +10633,46 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + '@schematics/angular@21.2.10(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.10(chokidar@5.0.0) + jsonc-parser: 3.3.1 + transitivePeerDependencies: + - chokidar + + '@sigstore/bundle@4.0.0': + dependencies: + '@sigstore/protobuf-specs': 0.5.1 + + '@sigstore/core@3.2.0': {} + + '@sigstore/protobuf-specs@0.5.1': {} + + '@sigstore/sign@4.1.1': + dependencies: + '@gar/promise-retry': 1.0.3 + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.0 + '@sigstore/protobuf-specs': 0.5.1 + make-fetch-happen: 15.0.5 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color + + '@sigstore/tuf@4.0.2': + dependencies: + '@sigstore/protobuf-specs': 0.5.1 + tuf-js: 4.1.0 + transitivePeerDependencies: + - supports-color + + '@sigstore/verify@3.1.0': + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.0 + '@sigstore/protobuf-specs': 0.5.1 + '@sindresorhus/is@7.2.0': {} '@sindresorhus/merge-streams@4.0.0': {} @@ -8535,6 +10694,15 @@ snapshots: vite: 8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) vitefu: 1.1.2(vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4)) + '@testing-library/angular@19.2.1(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/router@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2))(@testing-library/dom@10.4.1)': + dependencies: + '@angular/common': 21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/router': 21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.12(@angular/common@21.2.12(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.12(@angular/compiler@21.2.12)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + '@testing-library/dom': 10.4.1 + tslib: 2.8.1 + '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 @@ -8589,6 +10757,20 @@ snapshots: optionalDependencies: '@vue/compiler-sfc': 3.5.34 + '@ts-morph/common@0.22.0': + dependencies: + fast-glob: 3.3.3 + minimatch: 9.0.5 + mkdirp: 3.0.1 + path-browserify: 1.0.1 + + '@tufjs/canonical-json@2.0.0': {} + + '@tufjs/models@4.1.0': + dependencies: + '@tufjs/canonical-json': 2.0.0 + minimatch: 10.2.5 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -8743,6 +10925,10 @@ snapshots: - rollup - supports-color + '@vitejs/plugin-basic-ssl@2.1.4(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4))': + dependencies: + vite: 8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) + '@vitejs/plugin-react@6.0.1(vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 @@ -8778,7 +10964,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4)) '@vitest/expect@4.1.5': dependencies: @@ -8789,6 +10975,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/mocker@4.1.5(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) + '@vitest/mocker@4.1.5(vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4))': dependencies: '@vitest/spy': 4.1.5 @@ -8960,14 +11154,23 @@ snapshots: js-beautify: 1.15.4 vue-component-type-helpers: 2.2.12 + '@yarnpkg/lockfile@1.1.0': {} + abbrev@2.0.0: {} abbrev@3.0.1: {} + abbrev@4.0.0: {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-import-attributes@1.9.5(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -8980,6 +11183,14 @@ snapshots: agent-base@7.1.4: {} + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -8987,6 +11198,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -8994,8 +11212,44 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + algoliasearch@5.48.1: + dependencies: + '@algolia/abtesting': 1.14.1 + '@algolia/client-abtesting': 5.48.1 + '@algolia/client-analytics': 5.48.1 + '@algolia/client-common': 5.48.1 + '@algolia/client-insights': 5.48.1 + '@algolia/client-personalization': 5.48.1 + '@algolia/client-query-suggestions': 5.48.1 + '@algolia/client-search': 5.48.1 + '@algolia/ingestion': 1.48.1 + '@algolia/monitoring': 1.48.1 + '@algolia/recommend': 5.48.1 + '@algolia/requester-browser-xhr': 5.48.1 + '@algolia/requester-fetch': 5.48.1 + '@algolia/requester-node-http': 5.48.1 + alien-signals@3.1.2: {} + angular-eslint@21.3.1(@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.6.1))(typescript-eslint@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3): + dependencies: + '@angular-devkit/core': 21.2.10(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.10(chokidar@5.0.0) + '@angular-eslint/builder': 21.3.1(@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/eslint-plugin': 21.3.1(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.59.2)(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/schematics': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.10(@types/node@25.6.2)(chokidar@5.0.0))(@typescript-eslint/types@8.59.2)(@typescript-eslint/utils@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/template-parser': 21.3.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@angular/cli': 21.2.10(@types/node@25.6.2)(chokidar@5.0.0) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + typescript-eslint: 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + transitivePeerDependencies: + - chokidar + - supports-color + ansi-colors@4.1.3: {} ansi-escapes@7.3.0: @@ -9060,6 +11314,8 @@ snapshots: aria-query@5.3.1: {} + aria-query@5.3.2: {} + array-buffer-byte-length@1.0.2: dependencies: call-bound: 1.0.4 @@ -9172,6 +11428,18 @@ snapshots: baseline-browser-mapping@2.10.27: {} + beasties@0.4.1: + dependencies: + css-select: 6.0.0 + css-what: 7.0.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + htmlparser2: 10.1.0 + picocolors: 1.1.1 + postcss: 8.5.14 + postcss-media-query-parser: 0.2.3 + postcss-safe-parser: 7.0.1(postcss@8.5.14) + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -9188,6 +11456,20 @@ snapshots: birpc@4.0.0: {} + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + boolbase@1.0.0: {} brace-expansion@1.1.12: @@ -9228,6 +11510,8 @@ snapshots: dependencies: run-applescript: 7.1.0 + bytes@3.1.2: {} + c12@3.3.4(magicast@0.5.2): dependencies: chokidar: 5.0.0 @@ -9247,6 +11531,19 @@ snapshots: cac@6.7.14: {} + cacache@20.0.4: + dependencies: + '@npmcli/fs': 5.0.0 + fs-minipass: 3.0.3 + glob: 13.0.6 + lru-cache: 11.3.6 + minipass: 7.1.3 + minipass-collect: 2.0.1 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + p-map: 7.0.4 + ssri: 13.0.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -9313,6 +11610,8 @@ snapshots: slice-ansi: 8.0.0 string-width: 8.2.0 + cli-width@4.1.0: {} + cliui@9.0.1: dependencies: string-width: 7.2.0 @@ -9323,12 +11622,16 @@ snapshots: cluster-key-slot@1.1.2: {} + code-block-writer@12.0.0: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + colorette@2.0.20: {} + commander@10.0.1: {} commander@11.1.0: {} @@ -9364,6 +11667,10 @@ snapshots: consola@3.4.2: {} + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} @@ -9374,12 +11681,21 @@ snapshots: cookie-es@3.1.1: {} + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + crc-32@1.2.2: {} crc32-stream@6.0.0: @@ -9411,6 +11727,14 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-select@6.0.0: + dependencies: + boolbase: 1.0.0 + css-what: 7.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + css-tree@2.2.1: dependencies: mdn-data: 2.0.28 @@ -9423,6 +11747,8 @@ snapshots: css-what@6.2.2: {} + css-what@7.0.0: {} + cssesc@3.0.0: {} cssfilter@0.0.10: @@ -9636,6 +11962,8 @@ snapshots: emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} + encodeurl@2.0.0: {} enquirer@2.4.1: @@ -9645,12 +11973,18 @@ snapshots: entities@4.5.0: {} + entities@6.0.1: {} + entities@7.0.1: {} entities@8.0.0: {} + env-paths@2.2.1: {} + environment@1.1.0: {} + err-code@2.0.3: {} + errno@0.1.8: dependencies: prr: 1.0.1 @@ -9805,6 +12139,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -10082,6 +12445,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.8: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.8 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -10096,6 +12465,46 @@ snapshots: expect-type@1.3.0: {} + exponential-backoff@3.1.3: {} + + express-rate-limit@8.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.8: {} extendable-error@0.1.7: {} @@ -10160,6 +12569,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-cache-directory@6.0.0: dependencies: common-path-prefix: 3.0.0 @@ -10194,6 +12614,8 @@ snapshots: dependencies: is-callable: 1.2.7 + forwarded@0.2.0: {} + fraction.js@5.3.4: {} fresh@2.0.0: {} @@ -10210,6 +12632,10 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.3 + fsevents@2.3.2: optional: true @@ -10279,6 +12705,8 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} + glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -10367,10 +12795,16 @@ snapshots: dependencies: function-bind: 1.1.2 + hono@4.12.18: {} + hookable@5.5.3: {} hookable@6.1.1: {} + hosted-git-info@9.0.3: + dependencies: + lru-cache: 11.3.6 + html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0): dependencies: '@exodus/bytes': 1.15.0(@noble/hashes@1.8.0) @@ -10379,6 +12813,15 @@ snapshots: html-escaper@2.0.2: {} + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + + http-cache-semantics@4.2.0: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -10387,6 +12830,13 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + http-shutdown@1.2.2: {} https-proxy-agent@7.0.6: @@ -10415,6 +12865,10 @@ snapshots: ieee754@1.2.1: {} + ignore-walk@8.0.0: + dependencies: + minimatch: 10.2.5 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -10447,6 +12901,8 @@ snapshots: ini@4.1.1: {} + ini@6.0.0: {} + injection-js@2.6.1: dependencies: tslib: 2.8.1 @@ -10471,6 +12927,10 @@ snapshots: transitivePeerDependencies: - supports-color + ip-address@10.2.0: {} + + ipaddr.js@1.9.1: {} + ipx@3.1.1(db0@0.3.4)(ioredis@5.10.1): dependencies: '@fastify/accept-negotiator': 2.0.1 @@ -10566,6 +13026,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: dependencies: get-east-asian-width: 1.5.0 @@ -10612,6 +13074,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-reference@1.2.1: dependencies: '@types/estree': 1.0.8 @@ -10687,6 +13151,16 @@ snapshots: istanbul-lib-coverage@3.2.2: {} + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 @@ -10715,6 +13189,8 @@ snapshots: jiti@2.6.1: {} + jose@6.2.3: {} + js-beautify@1.15.4: dependencies: config-chain: 1.1.13 @@ -10770,10 +13246,14 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@5.0.0: {} + json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -10784,6 +13264,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonparse@1.3.1: {} + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -10913,13 +13395,40 @@ snapshots: untun: 0.1.3 uqr: 0.1.3 - listr2@10.2.1: + listr2@10.2.1: + dependencies: + cli-truncate: 5.2.0 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 10.0.0 + + listr2@9.0.5: dependencies: cli-truncate: 5.2.0 + colorette: 2.0.20 eventemitter3: 5.0.4 log-update: 6.1.0 rfdc: 1.4.1 - wrap-ansi: 10.0.0 + wrap-ansi: 9.0.2 + + lmdb@3.5.1: + dependencies: + '@harperfast/extended-iterable': 1.0.3 + msgpackr: 1.11.12 + node-addon-api: 6.1.0 + node-gyp-build-optional-packages: 5.2.2 + ordered-binary: 1.6.1 + weak-lru-cache: 1.2.2 + optionalDependencies: + '@lmdb/lmdb-darwin-arm64': 3.5.1 + '@lmdb/lmdb-darwin-x64': 3.5.1 + '@lmdb/lmdb-linux-arm': 3.5.1 + '@lmdb/lmdb-linux-arm64': 3.5.1 + '@lmdb/lmdb-linux-x64': 3.5.1 + '@lmdb/lmdb-win32-arm64': 3.5.1 + '@lmdb/lmdb-win32-x64': 3.5.1 + optional: true local-pkg@1.1.2: dependencies: @@ -11010,15 +13519,36 @@ snapshots: dependencies: semver: 7.7.4 + make-fetch-happen@15.0.5: + dependencies: + '@gar/promise-retry': 1.0.3 + '@npmcli/agent': 4.0.0 + '@npmcli/redact': 4.0.0 + cacache: 20.0.4 + http-cache-semantics: 4.2.0 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 6.1.0 + ssri: 13.0.1 + transitivePeerDependencies: + - supports-color + math-intrinsics@1.1.0: {} mdn-data@2.0.28: {} mdn-data@2.27.1: {} + media-typer@1.1.0: {} + meow@13.2.0: optional: true + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -11059,12 +13589,42 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.3 + + minipass-fetch@5.0.2: + dependencies: + minipass: 7.1.3 + minipass-sized: 2.0.0 + minizlib: 3.1.0 + optionalDependencies: + iconv-lite: 0.7.2 + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@2.0.0: + dependencies: + minipass: 7.1.3 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + minipass@7.1.3: {} minizlib@3.1.0: dependencies: minipass: 7.1.3 + mkdirp@3.0.1: {} + mkdist@2.4.1(sass@1.99.0)(typescript@5.9.3)(vue-sfc-transformer@0.1.17(@vue/compiler-core@3.5.34)(esbuild@0.28.0)(vue@3.5.34(typescript@5.9.3)))(vue-tsc@3.2.8(typescript@5.9.3))(vue@3.5.34(typescript@5.9.3)): dependencies: autoprefixer: 10.5.0(postcss@8.5.14) @@ -11102,8 +13662,27 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.12: + optionalDependencies: + msgpackr-extract: 3.0.3 + optional: true + muggle-string@0.4.1: {} + mute-stream@2.0.0: {} + nanoid@3.3.11: {} nanotar@0.3.0: {} @@ -11116,6 +13695,8 @@ snapshots: sax: 1.5.0 optional: true + negotiator@1.0.0: {} + ng-packagr@21.2.3(@angular/compiler-cli@21.2.12(@angular/compiler@21.2.12)(typescript@5.9.3))(tslib@2.8.1)(typescript@5.9.3): dependencies: '@ampproject/remapping': 2.3.0 @@ -11248,6 +13829,9 @@ snapshots: - supports-color - uploadthing + node-addon-api@6.1.0: + optional: true + node-addon-api@7.1.1: {} node-exports-info@1.6.0: @@ -11265,8 +13849,26 @@ snapshots: node-forge@1.4.0: {} + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.1.2 + optional: true + node-gyp-build@4.8.4: {} + node-gyp@12.3.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + graceful-fs: 4.2.11 + nopt: 9.0.0 + proc-log: 6.1.0 + semver: 7.7.4 + tar: 7.5.9 + tinyglobby: 0.2.16 + undici: 6.25.0 + which: 6.0.1 + node-mock-http@1.0.4: {} node-releases@2.0.38: {} @@ -11279,8 +13881,54 @@ snapshots: dependencies: abbrev: 3.0.1 + nopt@9.0.0: + dependencies: + abbrev: 4.0.0 + normalize-path@3.0.0: {} + npm-bundled@5.0.0: + dependencies: + npm-normalize-package-bin: 5.0.0 + + npm-install-checks@8.0.0: + dependencies: + semver: 7.7.4 + + npm-normalize-package-bin@5.0.0: {} + + npm-package-arg@13.0.2: + dependencies: + hosted-git-info: 9.0.3 + proc-log: 6.1.0 + semver: 7.7.4 + validate-npm-package-name: 7.0.2 + + npm-packlist@10.0.4: + dependencies: + ignore-walk: 8.0.0 + proc-log: 6.1.0 + + npm-pick-manifest@11.0.3: + dependencies: + npm-install-checks: 8.0.0 + npm-normalize-package-bin: 5.0.0 + npm-package-arg: 13.0.2 + semver: 7.7.4 + + npm-registry-fetch@19.1.1: + dependencies: + '@npmcli/redact': 4.0.0 + jsonparse: 1.3.1 + make-fetch-happen: 15.0.5 + minipass: 7.1.3 + minipass-fetch: 5.0.2 + minizlib: 3.1.0 + npm-package-arg: 13.0.2 + proc-log: 6.1.0 + transitivePeerDependencies: + - supports-color + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -11516,6 +14164,10 @@ snapshots: dependencies: ee-first: 1.1.1 + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 @@ -11549,6 +14201,17 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@9.3.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.2.0 + ora@9.4.0: dependencies: chalk: 5.6.2 @@ -11560,6 +14223,9 @@ snapshots: stdin-discarder: 0.3.2 string-width: 8.2.0 + ordered-binary@1.6.1: + optional: true + outdent@0.5.0: {} own-keys@1.0.1: @@ -11591,6 +14257,34 @@ snapshots: '@oxc-minify/binding-win32-ia32-msvc': 0.128.0 '@oxc-minify/binding-win32-x64-msvc': 0.128.0 + oxc-parser@0.121.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + dependencies: + '@oxc-project/types': 0.121.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.121.0 + '@oxc-parser/binding-android-arm64': 0.121.0 + '@oxc-parser/binding-darwin-arm64': 0.121.0 + '@oxc-parser/binding-darwin-x64': 0.121.0 + '@oxc-parser/binding-freebsd-x64': 0.121.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.121.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.121.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.121.0 + '@oxc-parser/binding-linux-arm64-musl': 0.121.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.121.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.121.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.121.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.121.0 + '@oxc-parser/binding-linux-x64-gnu': 0.121.0 + '@oxc-parser/binding-linux-x64-musl': 0.121.0 + '@oxc-parser/binding-openharmony-arm64': 0.121.0 + '@oxc-parser/binding-wasm32-wasi': 0.121.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@oxc-parser/binding-win32-arm64-msvc': 0.121.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.121.0 + '@oxc-parser/binding-win32-x64-msvc': 0.121.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + oxc-parser@0.128.0: dependencies: '@oxc-project/types': 0.128.0 @@ -11666,6 +14360,8 @@ snapshots: p-map@2.1.0: {} + p-map@7.0.4: {} + p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -11674,12 +14370,44 @@ snapshots: dependencies: quansync: 0.2.11 + pacote@21.3.1: + dependencies: + '@npmcli/git': 7.0.2 + '@npmcli/installed-package-contents': 4.0.0 + '@npmcli/package-json': 7.0.5 + '@npmcli/promise-spawn': 9.0.1 + '@npmcli/run-script': 10.0.4 + cacache: 20.0.4 + fs-minipass: 3.0.3 + minipass: 7.1.3 + npm-package-arg: 13.0.2 + npm-packlist: 10.0.4 + npm-pick-manifest: 11.0.3 + npm-registry-fetch: 19.1.1 + proc-log: 6.1.0 + promise-retry: 2.0.1 + sigstore: 4.1.0 + ssri: 13.0.1 + tar: 7.5.9 + transitivePeerDependencies: + - supports-color + parent-module@1.0.1: dependencies: callsites: 3.1.0 parse-node-version@1.0.1: {} + parse5-html-rewriting-stream@8.0.0: + dependencies: + entities: 6.0.1 + parse5: 8.0.1 + parse5-sax-parser: 8.0.0 + + parse5-sax-parser@8.0.0: + dependencies: + parse5: 8.0.1 + parse5@8.0.1: dependencies: entities: 8.0.0 @@ -11703,6 +14431,8 @@ snapshots: lru-cache: 11.3.6 minipass: 7.1.3 + path-to-regexp@8.4.2: {} + path-type@4.0.0: {} pathe@1.1.2: {} @@ -11723,6 +14453,8 @@ snapshots: optionalDependencies: '@napi-rs/nice': 1.1.1 + pkce-challenge@5.0.1: {} + pkg-dir@8.0.0: dependencies: find-up-simple: 1.0.1 @@ -11793,6 +14525,8 @@ snapshots: optionalDependencies: postcss: 8.5.14 + postcss-media-query-parser@0.2.3: {} + postcss-merge-longhand@7.0.7(postcss@8.5.14): dependencies: postcss: 8.5.14 @@ -11954,10 +14688,17 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + proc-log@6.1.0: {} + process-nextick-args@2.0.1: {} process@0.11.10: {} + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -11972,11 +14713,20 @@ snapshots: proto-list@1.2.4: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + prr@1.0.1: optional: true punycode@2.3.1: {} + qs@6.15.1: + dependencies: + side-channel: 1.1.0 + quansync@0.2.11: {} queue-microtask@1.2.3: {} @@ -11985,6 +14735,13 @@ snapshots: range-parser@1.2.1: {} + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + rc9@3.0.1: dependencies: defu: 6.1.7 @@ -12122,13 +14879,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.18 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.18 - rollup-plugin-dts@6.3.0(rollup@4.60.3)(typescript@5.9.3): + rolldown@1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: - magic-string: 0.30.21 - rollup: 4.60.3 - typescript: 5.9.3 + '@oxc-project/types': 0.113.0 + '@rolldown/pluginutils': 1.0.0-rc.4 optionalDependencies: - '@babel/code-frame': 7.29.0 + '@rolldown/binding-android-arm64': 1.0.0-rc.4 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.4 + '@rolldown/binding-darwin-x64': 1.0.0-rc.4 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.4 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.4 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.4 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.4 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.4 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.4 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.4 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' rollup-plugin-dts@6.4.1(rollup@4.60.3)(typescript@5.9.3): dependencies: @@ -12184,6 +14955,16 @@ snapshots: rou3@0.8.1: {} + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + run-applescript@7.1.0: {} run-parallel@1.2.0: @@ -12223,6 +15004,14 @@ snapshots: safer-buffer@2.1.2: {} + sass@1.97.3: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.5 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + sass@1.99.0: dependencies: chokidar: 4.0.3 @@ -12379,6 +15168,17 @@ snapshots: signal-exit@4.1.0: {} + sigstore@4.1.0: + dependencies: + '@sigstore/bundle': 4.0.0 + '@sigstore/core': 3.2.0 + '@sigstore/protobuf-specs': 0.5.1 + '@sigstore/sign': 4.1.1 + '@sigstore/tuf': 4.0.2 + '@sigstore/verify': 3.1.0 + transitivePeerDependencies: + - supports-color + simple-git@3.33.0: dependencies: '@kwsites/file-exists': 1.1.1 @@ -12414,8 +15214,23 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + smart-buffer@4.2.0: {} + smob@1.6.1: {} + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.2.0 + smart-buffer: 4.2.0 + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -12432,10 +15247,23 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + sprintf-js@1.0.3: {} srvx@0.11.15: {} + ssri@13.0.1: + dependencies: + minipass: 7.1.3 + stackback@0.0.2: {} standard-as-callback@2.1.0: {} @@ -12464,6 +15292,12 @@ snapshots: string-argv@0.3.2: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -12660,6 +15494,11 @@ snapshots: tinyexec@1.1.2: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -12695,12 +15534,25 @@ snapshots: dependencies: typescript: 5.9.3 + ts-morph@21.0.1: + dependencies: + '@ts-morph/common': 0.22.0 + code-block-writer: 12.0.0 + tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 tslib@2.8.1: {} + tuf-js@4.1.0: + dependencies: + '@tufjs/models': 4.1.0 + debug: 4.4.3 + make-fetch-happen: 15.0.5 + transitivePeerDependencies: + - supports-color + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -12709,6 +15561,12 @@ snapshots: dependencies: tagged-tag: 1.0.0 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + type-level-regexp@0.1.17: {} typed-array-buffer@1.0.3: @@ -12744,6 +15602,17 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typescript-eslint@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} ufo@1.6.4: {} @@ -12779,7 +15648,7 @@ snapshots: pkg-types: 2.3.1 pretty-bytes: 7.1.0 rollup: 4.60.3 - rollup-plugin-dts: 6.3.0(rollup@4.60.3)(typescript@5.9.3) + rollup-plugin-dts: 6.4.1(rollup@4.60.3)(typescript@5.9.3) scule: 1.3.0 tinyglobby: 0.2.16 untyped: 2.0.0 @@ -12802,6 +15671,10 @@ snapshots: undici-types@7.19.2: {} + undici@6.25.0: {} + + undici@7.24.4: {} + undici@7.25.0: {} unenv@2.0.0-rc.24: @@ -12837,6 +15710,8 @@ snapshots: universalify@0.1.2: {} + unpipe@1.0.0: {} + unplugin-utils@0.3.1: dependencies: pathe: 2.0.3 @@ -12911,6 +15786,10 @@ snapshots: util-deprecate@1.0.2: {} + validate-npm-package-name@7.0.2: {} + + vary@1.1.2: {} + vite-dev-rpc@1.1.0(vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4)): dependencies: birpc: 2.9.0 @@ -12988,6 +15867,23 @@ snapshots: vite: 8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) vue: 3.5.34(typescript@5.9.3) + vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.14 + rolldown: 1.0.0-rc.18 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.6.2 + esbuild: 0.27.3 + fsevents: 2.3.3 + jiti: 2.6.1 + less: 4.6.4 + sass: 1.99.0 + terser: 5.46.0 + yaml: 2.8.4 + vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4): dependencies: lightningcss: 1.32.0 @@ -13028,6 +15924,36 @@ snapshots: - vite - vitest + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.97.3)(terser@5.46.0)(yaml@2.8.4)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.11(@types/node@25.6.2)(esbuild@0.27.3)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 25.6.2 + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + jsdom: 29.1.1(@noble/hashes@1.8.0) + transitivePeerDependencies: + - msw + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@8.0.11(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.6.4)(sass@1.99.0)(terser@5.46.0)(yaml@2.8.4)): dependencies: '@vitest/expect': 4.1.5 @@ -13130,6 +16056,14 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + watchpack@2.5.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + weak-lru-cache@1.2.2: + optional: true + webidl-conversions@3.0.1: {} webidl-conversions@8.0.1: {} @@ -13213,12 +16147,20 @@ snapshots: string-width: 8.2.0 strip-ansi: 7.2.0 + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + ws@8.19.0: {} wsl-utils@0.1.0: @@ -13246,6 +16188,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yaml@1.10.2: {} @@ -13265,6 +16209,8 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} + yoctocolors@2.1.2: {} youch-core@0.3.3: @@ -13288,5 +16234,11 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod@4.3.6: + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + + zone.js@0.16.2: optional: true From 69bf4996fadeb456a4bf7b434056971089f6f6b3 Mon Sep 17 00:00:00 2001 From: Stefan Popov Date: Sat, 9 May 2026 23:02:21 +0200 Subject: [PATCH 03/24] style(angular): Format code --- .../demo/src/app.component.ts | 42 ++++++++++++++++--- .../tests/unit/phoneMaskDirective.test.ts | 10 +---- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/phone-mask-angular/demo/src/app.component.ts b/packages/phone-mask-angular/demo/src/app.component.ts index b91ad237..7b24f595 100644 --- a/packages/phone-mask-angular/demo/src/app.component.ts +++ b/packages/phone-mask-angular/demo/src/app.component.ts @@ -126,7 +126,12 @@ class DemoHookComponent implements AfterViewInit, OnDestroy {
`, - providers: [UseCountryService, UseFormatterService, UseInputHandlersService, UsePhoneMaskService] + providers: [UsePhoneMaskService] }) class DemoHookComponent implements AfterViewInit, OnDestroy { protected readonly mask = inject(UsePhoneMaskService); @@ -269,7 +266,7 @@ class DemoHookComponent implements AfterViewInit, OnDestroy { type="checkbox" class="checkbox" [checked]="readonly()" - (change)="readonly.set($any($event.target).checked)" + (change)="setReadonly($event)" /> Readonly @@ -444,6 +441,11 @@ export class AppComponent { this.changeDetector.detectChanges(); } + protected setReadonly(event: Event): void { + this.readonly.set((event.target as HTMLInputElement).checked); + this.changeDetector.detectChanges(); + } + protected setCountryOption(event: Event): void { const value = (event.target as HTMLSelectElement).value; this.country.set((value as CountryKey) || undefined); diff --git a/packages/phone-mask-angular/package.json b/packages/phone-mask-angular/package.json index d141ed06..62c3fdc0 100644 --- a/packages/phone-mask-angular/package.json +++ b/packages/phone-mask-angular/package.json @@ -48,7 +48,7 @@ "build:demo": "ng build demo", "build": "pnpm clean && pnpm build:lib && pnpm build:styles", "build:lib": "ng-packagr -p ng-package.json", - "build:styles": "sass src/phone-input/phone-input.component.scss dist/assets/lib.css --style=compressed --no-source-map", + "build:styles": "sass src/components/phone-input/phone-input.component.scss dist/assets/lib.css --style=compressed --no-source-map", "lint": "eslint .", "typecheck": "ngc -p tsconfig.json --noEmit", "test:unit": "vitest run", diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.html b/packages/phone-mask-angular/src/components/phone-input/phone-input.component.html similarity index 100% rename from packages/phone-mask-angular/src/phone-input/phone-input.component.html rename to packages/phone-mask-angular/src/components/phone-input/phone-input.component.html diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.scss b/packages/phone-mask-angular/src/components/phone-input/phone-input.component.scss similarity index 100% rename from packages/phone-mask-angular/src/phone-input/phone-input.component.scss rename to packages/phone-mask-angular/src/components/phone-input/phone-input.component.scss diff --git a/packages/phone-mask-angular/src/phone-input/phone-input.component.ts b/packages/phone-mask-angular/src/components/phone-input/phone-input.component.ts similarity index 92% rename from packages/phone-mask-angular/src/phone-input/phone-input.component.ts rename to packages/phone-mask-angular/src/components/phone-input/phone-input.component.ts index e55040c8..a267e754 100644 --- a/packages/phone-mask-angular/src/phone-input/phone-input.component.ts +++ b/packages/phone-mask-angular/src/components/phone-input/phone-input.component.ts @@ -21,17 +21,16 @@ import { import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'; import type { CountryKey, MaskFull } from '@desource/phone-mask'; import { extractDigits } from '@desource/phone-mask/kit'; -import { PHONE_MASK_CONFIG } from '../config'; -import { optionalBooleanAttribute } from '../internal/boolean-input'; -import { UseCopyActionService } from '../services/internal/useCopyAction.service'; -import { UseCountryService } from '../services/internal/useCountry.service'; -import { UseCountrySelectorService } from '../services/internal/useCountrySelector.service'; -import { UseFormatterService } from '../services/internal/useFormatter.service'; -import { UseInputHandlersService } from '../services/internal/useInputHandlers.service'; -import { UseThemeService } from '../services/internal/useTheme.service'; -import { UseValidationHintService } from '../services/internal/useValidationHint.service'; -import { UseClipboardService } from '../services/utility/useClipboard.service'; -import type { PhoneInputRef, PhoneMaskConfig, PhoneNumber, Size, Theme } from '../types'; +import { UseCopyActionService } from '../../services/internal/useCopyAction.service'; +import { UseCountryService } from '../../services/internal/useCountry.service'; +import { UseCountrySelectorService } from '../../services/internal/useCountrySelector.service'; +import { UseFormatterService } from '../../services/internal/useFormatter.service'; +import { UseInputHandlersService } from '../../services/internal/useInputHandlers.service'; +import { UseThemeService } from '../../services/internal/useTheme.service'; +import { UseValidationHintService } from '../../services/internal/useValidationHint.service'; +import { UseClipboardService } from '../../services/utility/useClipboard.service'; +import { optionalBooleanAttribute } from '../../utils/optionalBooleanAttribute'; +import type { PhoneInputRef, PhoneNumber, Size, Theme } from '../../types'; let nextDropdownId = 0; @@ -110,7 +109,6 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef private readonly countrySelector = inject(UseCountrySelectorService); private readonly copyAction = inject(UseCopyActionService); private readonly themeState = inject(UseThemeService); - private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; private readonly formDisabled = signal(false); private onTouched: () => void = () => {}; @@ -184,7 +182,7 @@ export class PhoneInputComponent implements ControlValueAccessor, PhoneInputRef country: this.countryInput, locale: this.localeInput, detect: this.detectInput, - defaultDetect: !this.config.country, + defaultDetect: true, onCountryChange: (country) => this.countryChange.emit(country) }); diff --git a/packages/phone-mask-angular/src/config.ts b/packages/phone-mask-angular/src/config.ts deleted file mode 100644 index 0cb3f799..00000000 --- a/packages/phone-mask-angular/src/config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { InjectionToken, makeEnvironmentProviders, type EnvironmentProviders } from '@angular/core'; -import type { PhoneMaskConfig } from './types'; - -export const PHONE_MASK_CONFIG = new InjectionToken('PHONE_MASK_CONFIG', { - providedIn: 'root', - factory: () => ({}) -}); - -export function providePhoneMask(config: PhoneMaskConfig = {}): EnvironmentProviders { - return makeEnvironmentProviders([ - { - provide: PHONE_MASK_CONFIG, - useValue: config - } - ]); -} diff --git a/packages/phone-mask-angular/src/internal/formatting.ts b/packages/phone-mask-angular/src/internal/formatting.ts deleted file mode 100644 index 298d6466..00000000 --- a/packages/phone-mask-angular/src/internal/formatting.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { CountryKey, MaskFull } from '@desource/phone-mask'; -import { - createPhoneFormatter, - extractDigits, - getCountry, - getNavigatorLang, - parseCountryCode, - type FormatterHelpers -} from '@desource/phone-mask/kit'; -import type { PhoneMaskConfig, PhoneMaskFormatMode, PhoneMaskFormatOptions, PhoneNumber } from '../types'; - -export const DEFAULT_COUNTRY = 'US'; - -export function resolveLocale(locale: string | undefined, config: PhoneMaskConfig): string { - return locale || config.locale || getNavigatorLang(); -} - -export function resolveCountryCode(country: CountryKey | string | null | undefined, config: PhoneMaskConfig): string { - return parseCountryCode(country, parseCountryCode(config.country, DEFAULT_COUNTRY)); -} - -export function resolveCountry( - country: CountryKey | string | null | undefined, - locale: string | undefined, - config: PhoneMaskConfig -): MaskFull { - return getCountry(resolveCountryCode(country, config), resolveLocale(locale, config)); -} - -export function createPhoneNumber(digits: string, country: MaskFull, formatter: FormatterHelpers): PhoneNumber { - const displayValue = formatter.formatDisplay(digits); - - return { - full: digits ? `${country.code}${digits}` : '', - fullFormatted: displayValue ? `${country.code} ${displayValue}` : '', - digits - }; -} - -export function createPhoneState(value: string | number | null | undefined, country: MaskFull) { - const formatter = createPhoneFormatter(country); - const digits = extractDigits(String(value ?? ''), formatter.getMaxDigits()); - const phone = createPhoneNumber(digits, country, formatter); - const isComplete = formatter.isComplete(digits); - const isEmpty = digits.length === 0; - - return { - ...phone, - country, - formatter, - isComplete, - isEmpty, - shouldShowWarn: !isEmpty && !isComplete - }; -} - -export function formatPhoneValue( - value: string | number | null | undefined, - options: PhoneMaskFormatOptions, - config: PhoneMaskConfig -): string { - const mode: PhoneMaskFormatMode = options.mode ?? 'display'; - const country = resolveCountry(options.country, options.locale, config); - const state = createPhoneState(value, country); - - if (mode === 'placeholder') return state.formatter.getPlaceholder(); - if (mode === 'full') return state.full; - if (mode === 'fullFormatted') return state.fullFormatted; - - return state.formatter.formatDisplay(state.digits); -} diff --git a/packages/phone-mask-angular/src/phone-mask.directive.ts b/packages/phone-mask-angular/src/phone-mask.directive.ts index 8ed066bc..e28a2892 100644 --- a/packages/phone-mask-angular/src/phone-mask.directive.ts +++ b/packages/phone-mask-angular/src/phone-mask.directive.ts @@ -13,18 +13,16 @@ import { import { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'; import type { CountryKey, MaskFull } from '@desource/phone-mask'; import { extractDigits } from '@desource/phone-mask/kit'; -import { PHONE_MASK_CONFIG } from './config'; -import { optionalBooleanAttribute } from './internal/boolean-input'; import { UseCountryService } from './services/internal/useCountry.service'; import { UseFormatterService } from './services/internal/useFormatter.service'; import { UseInputHandlersService } from './services/internal/useInputHandlers.service'; import type { DirectiveHTMLInputElement, - PhoneMaskConfig, PhoneMaskDirectiveInput, PhoneMaskDirectiveOptions, PhoneNumber } from './types'; +import { optionalBooleanAttribute } from './utils/optionalBooleanAttribute'; function parseOptions(value: PhoneMaskDirectiveInput): PhoneMaskDirectiveOptions { if (typeof value === 'string') return { country: value }; @@ -65,7 +63,6 @@ export class PhoneMaskDirective implements ControlValueAccessor { private readonly countryState = inject(UseCountryService); private readonly formatterState = inject(UseFormatterService); private readonly inputHandlers = inject(UseInputHandlersService); - private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; private readonly inputElement = this.elementRef.nativeElement as DirectiveHTMLInputElement; private readonly isInput = this.inputElement.tagName === 'INPUT'; @@ -77,9 +74,9 @@ export class PhoneMaskDirective implements ControlValueAccessor { return { ...parsed, - country: this.countryInput() ?? parsed.country ?? this.config.country, - locale: this.localeInput() ?? parsed.locale ?? this.config.locale, - detect: this.detectInput() ?? parsed.detect ?? this.config.detect ?? false + country: this.countryInput() ?? parsed.country, + locale: this.localeInput() ?? parsed.locale, + detect: this.detectInput() ?? parsed.detect ?? false }; }; diff --git a/packages/phone-mask-angular/src/phone-mask.pipe.ts b/packages/phone-mask-angular/src/phone-mask.pipe.ts index 48428de9..a50033ea 100644 --- a/packages/phone-mask-angular/src/phone-mask.pipe.ts +++ b/packages/phone-mask-angular/src/phone-mask.pipe.ts @@ -1,21 +1,40 @@ -import { Pipe, inject, type PipeTransform } from '@angular/core'; +import { Pipe, type PipeTransform } from '@angular/core'; import type { CountryKey } from '@desource/phone-mask'; -import { PHONE_MASK_CONFIG } from './config'; -import { formatPhoneValue } from './internal/formatting'; -import type { PhoneMaskConfig, PhoneMaskFormatMode, PhoneMaskFormatOptions } from './types'; +import { + createPhoneFormatter, + extractDigits, + getCountry, + getNavigatorLang, + parseCountryCode +} from '@desource/phone-mask/kit'; +import type { PhoneMaskFormatMode, PhoneMaskFormatOptions } from './types'; + +const DEFAULT_COUNTRY = 'US'; function isFormatOptions(value: unknown): value is PhoneMaskFormatOptions { return !!value && typeof value === 'object'; } +function formatPhoneValue(value: string | number | null | undefined, options: PhoneMaskFormatOptions): string { + const mode: PhoneMaskFormatMode = options.mode ?? 'display'; + const country = getCountry(parseCountryCode(options.country, DEFAULT_COUNTRY), options.locale || getNavigatorLang()); + const formatter = createPhoneFormatter(country); + const digits = extractDigits(String(value ?? ''), formatter.getMaxDigits()); + const displayValue = formatter.formatDisplay(digits); + + if (mode === 'placeholder') return formatter.getPlaceholder(); + if (mode === 'full') return digits ? `${country.code}${digits}` : ''; + if (mode === 'fullFormatted') return displayValue ? `${country.code} ${displayValue}` : ''; + + return displayValue; +} + @Pipe({ name: 'phoneMask', standalone: true, pure: true }) export class PhoneMaskPipe implements PipeTransform { - private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; - transform( value: string | number | null | undefined, countryOrOptions?: CountryKey | string | PhoneMaskFormatOptions, @@ -26,6 +45,6 @@ export class PhoneMaskPipe implements PipeTransform { ? countryOrOptions : { country: countryOrOptions as CountryKey | string | undefined, mode, locale }; - return formatPhoneValue(value, options, this.config); + return formatPhoneValue(value, options); } } diff --git a/packages/phone-mask-angular/src/public-api.ts b/packages/phone-mask-angular/src/public-api.ts index 0d230d0e..72dbbed0 100644 --- a/packages/phone-mask-angular/src/public-api.ts +++ b/packages/phone-mask-angular/src/public-api.ts @@ -1,28 +1,16 @@ -export { PHONE_MASK_CONFIG, providePhoneMask } from './config'; -export { PhoneInputComponent } from './phone-input/phone-input.component'; +export { PhoneInputComponent } from './components/phone-input/phone-input.component'; export { PhoneMaskDirective } from './phone-mask.directive'; export { PhoneMaskPipe } from './phone-mask.pipe'; export { UsePhoneMaskService } from './services/usePhoneMask.service'; -export { UseCopyActionService } from './services/internal/useCopyAction.service'; -export { UseCountryService } from './services/internal/useCountry.service'; -export { UseCountrySelectorService } from './services/internal/useCountrySelector.service'; -export { UseFormatterService } from './services/internal/useFormatter.service'; -export { UseInputHandlersService } from './services/internal/useInputHandlers.service'; -export { UseThemeService } from './services/internal/useTheme.service'; -export { UseValidationHintService } from './services/internal/useValidationHint.service'; -export { UseClipboardService } from './services/utility/useClipboard.service'; -export { UseTimerService } from './services/utility/useTimer.service'; export type { DirectiveHTMLInputElement, PhoneInputRef, - PhoneMaskConfig, PhoneMaskDirectiveInput, PhoneMaskDirectiveOptions, PhoneMaskDirectiveState, PhoneMaskFormatMode, PhoneMaskFormatOptions, - PhoneMaskState, PhoneNumber as PMaskPhoneNumber, Size as PhoneInputSize, Theme as PhoneInputTheme diff --git a/packages/phone-mask-angular/src/services/internal/useCountry.service.ts b/packages/phone-mask-angular/src/services/internal/useCountry.service.ts index a3ed5630..ea374469 100644 --- a/packages/phone-mask-angular/src/services/internal/useCountry.service.ts +++ b/packages/phone-mask-angular/src/services/internal/useCountry.service.ts @@ -7,8 +7,6 @@ import { getNavigatorLang, parseCountryCode } from '@desource/phone-mask/kit'; -import { PHONE_MASK_CONFIG } from '../../config'; -import type { PhoneMaskConfig } from '../../types'; interface UseCountryOptions { country?: () => CountryKey | string | null | undefined; @@ -21,7 +19,6 @@ interface UseCountryOptions { @Injectable() export class UseCountryService { private readonly injector = inject(Injector); - private readonly config: PhoneMaskConfig = inject(PHONE_MASK_CONFIG, { optional: true }) ?? {}; private countryOption: () => CountryKey | string | null | undefined = () => undefined; private localeOption: () => string | undefined = () => undefined; private detectOption: () => boolean | undefined = () => undefined; @@ -31,14 +28,10 @@ export class UseCountryService { private configured = false; readonly countryCode = signal('US'); - readonly locale = computed(() => this.localeOption() || this.config.locale || getNavigatorLang()); - readonly detect = computed(() => this.detectOption() ?? this.config.detect ?? this.defaultDetect); + readonly locale = computed(() => this.localeOption() || getNavigatorLang()); + readonly detect = computed(() => this.detectOption() ?? this.defaultDetect); readonly country = computed(() => getCountry(this.countryCode(), this.locale())); - constructor() { - this.countryCode.set(parseCountryCode(this.config.country, 'US')); - } - configure(options: UseCountryOptions = {}): void { if (this.configured) return; this.configured = true; @@ -51,7 +44,7 @@ export class UseCountryService { effect( () => { - const parsed = parseCountryCode(this.countryOption() ?? this.config.country); + const parsed = parseCountryCode(this.countryOption()); if (parsed && parsed !== this.countryCode()) { queueMicrotask(() => this.setCountry(parsed)); @@ -62,7 +55,7 @@ export class UseCountryService { effect( () => { - if (!this.detect() || this.countryOption() || this.config.country) return; + if (!this.detect() || this.countryOption()) return; const key = `${this.locale()}:${this.detect()}`; if (this.detectionKey === key) return; diff --git a/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts b/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts index 838ca9d2..3e44c7bd 100644 --- a/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts +++ b/packages/phone-mask-angular/src/services/internal/useFormatter.service.ts @@ -1,7 +1,6 @@ import { Injectable, Injector, computed, effect, inject } from '@angular/core'; import type { MaskFull } from '@desource/phone-mask'; import { createPhoneFormatter, extractDigits } from '@desource/phone-mask/kit'; -import { createPhoneNumber } from '../../internal/formatting'; import type { PhoneNumber } from '../../types'; interface UseFormatterOptions { @@ -33,7 +32,11 @@ export class UseFormatterService { readonly digits = computed(() => extractDigits(this.valueGetter(), this.formatter().getMaxDigits())); readonly displayPlaceholder = computed(() => this.formatter().getPlaceholder()); readonly displayValue = computed(() => this.formatter().formatDisplay(this.digits())); - readonly phoneData = computed(() => createPhoneNumber(this.digits(), this.country(), this.formatter())); + readonly phoneData = computed(() => ({ + full: this.digits() ? `${this.country().code}${this.digits()}` : '', + fullFormatted: this.displayValue() ? `${this.country().code} ${this.displayValue()}` : '', + digits: this.digits() + })); readonly full = computed(() => this.phoneData().full); readonly fullFormatted = computed(() => this.phoneData().fullFormatted); readonly isComplete = computed(() => this.formatter().isComplete(this.digits())); diff --git a/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts b/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts index b4620a33..c1304f04 100644 --- a/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts +++ b/packages/phone-mask-angular/src/services/internal/useInputHandlers.service.ts @@ -56,7 +56,10 @@ export class UseInputHandlersService { } handleKeydown(event: KeyboardEvent): void { - if (this.inactiveGetter()) return; + if (this.inactiveGetter()) { + event.preventDefault(); + return; + } const result = processKeydown(event, { digits: this.digitsGetter(), formatter: this.formatterGetter() }); if (!result) return; @@ -67,7 +70,10 @@ export class UseInputHandlersService { } handlePaste(event: ClipboardEvent): void { - if (this.inactiveGetter()) return; + if (this.inactiveGetter()) { + event.preventDefault(); + return; + } const result = processPaste(event, { digits: this.digitsGetter(), formatter: this.formatterGetter() }); if (!result) return; diff --git a/packages/phone-mask-angular/src/services/usePhoneMask.service.ts b/packages/phone-mask-angular/src/services/usePhoneMask.service.ts index 92de4043..ae753249 100644 --- a/packages/phone-mask-angular/src/services/usePhoneMask.service.ts +++ b/packages/phone-mask-angular/src/services/usePhoneMask.service.ts @@ -27,9 +27,14 @@ export interface UsePhoneMaskOptions { export class UsePhoneMaskService { private readonly injector = inject(Injector); private readonly destroyRef = inject(DestroyRef); - private readonly countryState = inject(UseCountryService); - private readonly formatterState = inject(UseFormatterService); - private readonly inputHandlers = inject(UseInputHandlersService); + private readonly stateInjector = Injector.create({ + providers: [UseCountryService, UseFormatterService, UseInputHandlersService], + parent: this.injector, + name: 'UsePhoneMaskService' + }); + private readonly countryState = this.stateInjector.get(UseCountryService); + private readonly formatterState = this.stateInjector.get(UseFormatterService); + private readonly inputHandlers = this.stateInjector.get(UseInputHandlersService); private readonly inputElement = signal(null); private onChange: (digits: string) => void = () => {}; private configured = false; @@ -45,6 +50,13 @@ export class UsePhoneMaskService { readonly shouldShowWarn = this.formatterState.shouldShowWarn; readonly inputRef = computed(() => this.inputElement()); + constructor() { + this.destroyRef.onDestroy(() => { + this.connect(null); + this.stateInjector.destroy(); + }); + } + configure(options: UsePhoneMaskOptions): void { if (this.configured) return; this.configured = true; @@ -105,7 +117,6 @@ export class UsePhoneMaskService { { injector: this.injector } ); - this.destroyRef.onDestroy(() => this.connect(null)); } connect(inputElement: HTMLInputElement | null): void { diff --git a/packages/phone-mask-angular/src/types.ts b/packages/phone-mask-angular/src/types.ts index 9e363f4e..8c96b7aa 100644 --- a/packages/phone-mask-angular/src/types.ts +++ b/packages/phone-mask-angular/src/types.ts @@ -11,15 +11,6 @@ export interface PhoneNumber { digits: string; } -export interface PhoneMaskConfig { - /** Default ISO 3166-1 alpha-2 country code. */ - country?: CountryKey | string; - /** Default locale for country names. */ - locale?: string; - /** Default country auto-detection behavior for APIs that support detection. */ - detect?: boolean; -} - export interface PhoneMaskFormatOptions { /** ISO 3166-1 alpha-2 country code. */ country?: CountryKey | string; @@ -77,14 +68,3 @@ export interface PhoneInputRef { /** Check if the current phone number is complete. */ isComplete: () => boolean; } - -export interface PhoneMaskState { - country: MaskFull; - formatter: FormatterHelpers; - digits: string; - full: string; - fullFormatted: string; - isComplete: boolean; - isEmpty: boolean; - shouldShowWarn: boolean; -} diff --git a/packages/phone-mask-angular/src/internal/boolean-input.ts b/packages/phone-mask-angular/src/utils/optionalBooleanAttribute.ts similarity index 100% rename from packages/phone-mask-angular/src/internal/boolean-input.ts rename to packages/phone-mask-angular/src/utils/optionalBooleanAttribute.ts diff --git a/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts b/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts index 761f3d1f..6766d01d 100644 --- a/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts +++ b/packages/phone-mask-angular/tests/unit/PhoneInput.test.ts @@ -4,7 +4,7 @@ import { By } from '@angular/platform-browser'; import { render } from '@testing-library/angular'; import type { CountryKey, MaskFull } from '@desource/phone-mask'; import { testPhoneInput, type SetupFn } from '@common/tests/unit/PhoneInput'; -import { PhoneInputComponent } from '../../src/phone-input/phone-input.component'; +import { PhoneInputComponent } from '../../src/components/phone-input/phone-input.component'; import type { PhoneNumber, Size, Theme } from '../../src/types'; import { tools } from './setup/tools'; diff --git a/packages/phone-mask-angular/tests/unit/index.test.ts b/packages/phone-mask-angular/tests/unit/index.test.ts index d0a4ea19..8ff21bcd 100644 --- a/packages/phone-mask-angular/tests/unit/index.test.ts +++ b/packages/phone-mask-angular/tests/unit/index.test.ts @@ -8,5 +8,5 @@ testIndexImports({ indexModule, coreModule, expectedDefinedExports: ['PhoneInputComponent', 'PhoneMaskDirective', 'PhoneMaskPipe', 'UsePhoneMaskService'], - expectedFunctionExports: ['providePhoneMask'] + expectedFunctionExports: [] }); diff --git a/packages/phone-mask-angular/tests/unit/service-pipe.test.ts b/packages/phone-mask-angular/tests/unit/service-pipe.test.ts index 9af3f517..a2b8bf9e 100644 --- a/packages/phone-mask-angular/tests/unit/service-pipe.test.ts +++ b/packages/phone-mask-angular/tests/unit/service-pipe.test.ts @@ -1,14 +1,9 @@ import { describe, expect, it } from 'vitest'; import { TestBed } from '@angular/core/testing'; -import { PHONE_MASK_CONFIG } from '../../src/config'; import { PhoneMaskPipe } from '../../src/phone-mask.pipe'; describe('PhoneMaskPipe', () => { const setup = () => { - TestBed.configureTestingModule({ - providers: [{ provide: PHONE_MASK_CONFIG, useValue: { country: 'US', locale: 'en' } }] - }); - return TestBed.runInInjectionContext(() => new PhoneMaskPipe()); }; diff --git a/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts b/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts index 395e33df..f370fd4b 100644 --- a/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts +++ b/packages/phone-mask-angular/tests/unit/usePhoneMask.test.ts @@ -2,16 +2,13 @@ import { Component, ElementRef, inject, signal, viewChild, type AfterViewInit, type OnDestroy } from '@angular/core'; import { render } from '@testing-library/angular'; import { testUsePhoneMask, type UsePhoneMaskSetupOptions } from '@common/tests/unit/usePhoneMask'; -import { UseCountryService } from '../../src/services/internal/useCountry.service'; -import { UseFormatterService } from '../../src/services/internal/useFormatter.service'; -import { UseInputHandlersService } from '../../src/services/internal/useInputHandlers.service'; import { UsePhoneMaskService } from '../../src/services/usePhoneMask.service'; import { tools } from './setup/tools'; @Component({ standalone: true, template: '', - providers: [UseCountryService, UseFormatterService, UseInputHandlersService, UsePhoneMaskService] + providers: [UsePhoneMaskService] }) class UsePhoneMaskHostComponent implements AfterViewInit, OnDestroy { readonly mask = inject(UsePhoneMaskService); From e2da1246935f090113f413e638f506d2008d973b Mon Sep 17 00:00:00 2001 From: Stefan Popov Date: Sat, 9 May 2026 23:53:07 +0200 Subject: [PATCH 06/24] style(angular): Format code --- packages/phone-mask-angular/demo/src/app.component.ts | 7 +------ .../src/services/usePhoneMask.service.ts | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/phone-mask-angular/demo/src/app.component.ts b/packages/phone-mask-angular/demo/src/app.component.ts index e3b073e0..3978a7fa 100644 --- a/packages/phone-mask-angular/demo/src/app.component.ts +++ b/packages/phone-mask-angular/demo/src/app.component.ts @@ -262,12 +262,7 @@ class DemoHookComponent implements AfterViewInit, OnDestroy { Disabled