From 2b61958ba3ed5ba67b989b76b3ac331895920aa7 Mon Sep 17 00:00:00 2001 From: Alan North Date: Tue, 9 Jun 2026 00:30:41 -0300 Subject: [PATCH 1/8] v0feat: added auth components Added/moved more generic version of some of @witchcraft/nuxt-auth's components to this repo as they will be getting deprecated. --- package.json | 5 +- plans/temp/auth-components.md | 89 +++++ pnpm-lock.yaml | 121 ++++++- pnpm-workspace.yaml | 2 + src/runtime/assets/locales/en.json | 15 +- src/runtime/components/WAuth/WAuth.md | 8 + src/runtime/components/WAuth/WAuth.stories.ts | 109 ++++++ src/runtime/components/WAuth/WAuth.vue | 76 ++++ .../components/WAuth/WAuthLocalUsers.md | 8 + .../components/WAuth/WAuthLocalUsers.vue | 341 ++++++++++++++++++ src/runtime/components/index.ts | 2 + src/runtime/types/index.ts | 20 + 12 files changed, 793 insertions(+), 3 deletions(-) create mode 100644 plans/temp/auth-components.md create mode 100644 src/runtime/components/WAuth/WAuth.md create mode 100644 src/runtime/components/WAuth/WAuth.stories.ts create mode 100644 src/runtime/components/WAuth/WAuth.vue create mode 100644 src/runtime/components/WAuth/WAuthLocalUsers.md create mode 100644 src/runtime/components/WAuth/WAuthLocalUsers.vue diff --git a/package.json b/package.json index f29fcff1..31e7a1c5 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,8 @@ "@nuxt/kit": "catalog:", "@nuxt/schema": "catalog:", "@nuxt/types": "catalog:", + "@regle/core": "catalog:", + "@regle/rules": "catalog:", "@tailwindcss/vite": "catalog:", "@witchcraft/nuxt-utils": "catalog:", "colord": "catalog:", @@ -103,7 +105,8 @@ "tailwind-merge": "catalog:", "unplugin-icons": "catalog:", "unplugin-vue-components": "catalog:", - "vue-component-type-helpers": "catalog:" + "vue-component-type-helpers": "catalog:", + "zod": "catalog:" }, "devDependencies": { "@alanscodelog/commitlint-config": "catalog:", diff --git a/plans/temp/auth-components.md b/plans/temp/auth-components.md new file mode 100644 index 00000000..d5fb3e84 --- /dev/null +++ b/plans/temp/auth-components.md @@ -0,0 +1,89 @@ +# Auth UI Components Plan + +## Goal + +Move the login provider button rendering logic and the local user auth component into `@witchcraft/ui` as generic, decoupled components. `nuxt-auth` keeps its auth-specific types, provider styles, and composables. `pyramid` stops maintaining a custom auth component. + +## What Exists Today + +1. **`LoginProviderButtons.client.vue`** in `nuxt-auth` renders OAuth provider buttons. It mixes UI rendering (WButton, WIcon, dark mode styles) with auth logic (useAuth, runtime config, providerStyles). + +2. **`AuthLocalUserProviderButtons.vue`** in `pyramid/layers/auth/components/` handles local user creation/selection. It depends on pyramid-specific `LocalWebUsersPresetManager`, `UiSearchSelect` (pyramid-specific), `isElectron`, `@regle/rules`, and `LocalWebUsersPresetManager`. + +## Architecture + +**Two new components in `@witchcraft/ui`:** + +### `WLoginProviderButtons` +- Generic provider button renderer — accepts `providers` (string[]), `providerStyles` (Record), and `onLogin(provider: string, options)` as props +- `ProviderStyle` type lives in ui: `{ name: string, logo: any, style: { bg: string, text: string, bgDark: string, textDark: string } }` — copied from nuxt-auth as-is for now, dark mode color resolution stays in the component. Restructuring this is deferred. +- Consumers (like `nuxt-auth`) pass their own provider styles and login handler +- The `#extra` slot passes `icon-class` and `class` to slot content (same as current) +- No dependency on `useAuth`, `useRuntimeConfig`, or any auth module + +### `WLocalUserProvider` +- Genericized local user management component +- Callback-based API: + - `users`: computed/ref of existing local users `{ id, username }[]` + - `onCreateUser(username: string)`: creates a local user + - `onSignIn(userId: string)`: signs in with a local user + - `onRemoveUser(userId: string)`: removes a local user + - `usernameValidation`: `{ schema: ZodSchema, rules: RegleRules }` — keeps @regle/rules +- Replaces `UiSearchSelect` with `WCombobox` (already in ui, same reka-ui Combobox) +- Platform-agnostic — no Electron-specific code; caller handles platform via callbacks +- No `LocalWebUsersPresetManager` dependency — caller manages storage +- Keeps `useNotificationHandler` from ui for confirmation dialogs +- `#extra` slot on parent `WLoginProviderButtons` — this component is designed to be slotted into it via the `providerButtonsSlotProps` pattern + +**After moving:** +- `nuxt-auth`'s `LoginProviderButtons.client.vue` is rewritten to use `WLoginProviderButtons` internally, passing its own `providerStyles`, `enabledProviders` from runtime config, and `useAuth().login` as the handler. Same component name, same API — just the rendering is delegated to ui. +- `pyramid` replaces `AuthLocalUserProviderButtons.vue` with `WLocalUserProvider` wrapped in a thin adapter that passes pyramid-specific callbacks. + +## Implementation Steps + +1. **Create `WLoginProviderButtons`** in `@witchcraft/ui/src/runtime/components/WLoginProviderButtons/` + - Extract the template + dark mode logic from nuxt-auth's `LoginProviderButtons.client.vue` + - Props: `providers: string[]`, `providerStyles: Record`, `onLogin: (provider: string, options: object) => void`, `loginOptions?: object` + - Define `ProviderStyle` type in ui types + - Add `.md` doc, `.stories.ts` + +2. **Create `WLocalUserProvider`** in `@witchcraft/ui/src/runtime/components/WLocalUserProvider/` + - Genericize `AuthLocalUserProviderButtons.vue` + - Replace `UiSearchSelect` with `WCombobox` + - Keep `@regle/rules` (peer dep, same as pyramid uses it) + - Replace Electron branches with callback-based behavior + - Add `.md` doc, `.stories.ts` + +3. **Rewrite `nuxt-auth`'s `LoginProviderButtons.client.vue`** + - Import `WLoginProviderButtons` from ui + - Pass `config.enabledProviders`, `providerStyles` (merged with base), and `useAuth().login` + - Same external API — no breaking change for consumers + - No thin wrapper component needed, just rewrite the existing one + +4. **Update `pyramid`** + - Replace `AuthLocalUserProviderButtons.vue` with `WLocalUserProvider` + thin adapter + - Pass pyramid-specific callbacks (`LocalWebUsersPresetManager`, `isElectron` logic) + +## Files to Create (in @witchcraft/ui) + +- `src/runtime/components/WLoginProviderButtons/WLoginProviderButtons.vue` +- `src/runtime/components/WLoginProviderButtons/WLoginProviderButtons.md` +- `src/runtime/components/WLoginProviderButtons/WLoginProviderButtons.stories.ts` +- `src/runtime/components/WLocalUserProvider/WLocalUserProvider.vue` +- `src/runtime/components/WLocalUserProvider/WLocalUserProvider.md` +- `src/runtime/components/WLocalUserProvider/WLocalUserProvider.stories.ts` +- `src/runtime/types/providerStyle.ts` (or add to existing types index) + +## Files to Modify + +- `@witchcraft/nuxt-auth/src/runtime/components/LoginProviderButtons.client.vue` — rewrite to use WLoginProviderButtons +- `@witchcraft/nuxt-auth/src/runtime/types.ts` — remove `ProviderStyle`, `FullProviderStyles`, `providerStylesInjectionKey` (moved to ui) +- `pyramid/layers/auth/components/AuthLocalUserProviderButtons.vue` — replace with WLocalUserProvider adapter +- `@witchcraft/ui/src/runtime/components/index.ts` — export new components +- `@witchcraft/ui/src/runtime/composables/index.ts` — if new composables needed + +## Risks / Tradeoffs + +- `@regle/rules` as a peer dependency of ui means consumers of `WLocalUserProvider` must install it. Pyramid already does. If another consumer doesn't, they'll get a runtime error. Acceptable since it's an optional component. +- `WCombobox` may not be a 1:1 replacement for `UiSearchSelect` — the pyramid component uses a button-triggered dropdown with custom trigger slot. `WCombobox` uses an input-triggered combobox. The local user selector needs to adapt to WCombobox's API (search-as-you-type vs button click). +- `ProviderStyle.logo` is a Vue component (icon from unplugin-icons). Consumers need icon resolution set up — this is already the case since `nuxt-auth` uses it. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7d23d6b..f4aafb28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,12 @@ catalogs: '@playwright/test': specifier: '=1.58.2' version: 1.58.2 + '@regle/core': + specifier: ^1.26.1 + version: 1.26.1 + '@regle/rules': + specifier: ^1.26.1 + version: 1.26.1 '@rollup/plugin-node-resolve': specifier: ^16.0.3 version: 16.0.3 @@ -305,6 +311,12 @@ importers: '@nuxt/types': specifier: 'catalog:' version: 2.18.1 + '@regle/core': + specifier: 'catalog:' + version: 1.26.1(vue@3.5.34(typescript@5.9.3)) + '@regle/rules': + specifier: 'catalog:' + version: 1.26.1(vue@3.5.34(typescript@5.9.3)) '@tailwindcss/vite': specifier: 'catalog:' version: 4.3.0(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0)) @@ -341,6 +353,9 @@ importers: vue-component-type-helpers: specifier: 'catalog:' version: 2.2.12 + zod: + specifier: 'catalog:' + version: 4.4.3 devDependencies: '@alanscodelog/commitlint-config': specifier: 'catalog:' @@ -3129,6 +3144,18 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@regle/core@1.26.1': + resolution: {integrity: sha512-hGOjAjb9IhjaVB1ET9zxaonWooj3sQr6hjTH+CT+Jg4YKoIyX13mBAvX2dlI01nCX3w9l6hr9YnSTVxL8cDkKA==} + peerDependencies: + pinia: '>=2.2.5' + vue: '>=3.3.0' + peerDependenciesMeta: + pinia: + optional: true + + '@regle/rules@1.26.1': + resolution: {integrity: sha512-ajIE+omS+R/L0PZy5kgvH4vjk88qs02tPzf+g0cjhwaUkEEID3PKWjyzOWjsAQULm0OrGKUvoaVO9woL+ZIaVA==} + '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} @@ -4895,6 +4922,9 @@ packages: '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + '@vue/devtools-api@8.1.2': resolution: {integrity: sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg==} @@ -4903,9 +4933,15 @@ packages: peerDependencies: vue: ^3.0.0 + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + '@vue/devtools-kit@8.1.2': resolution: {integrity: sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==} + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + '@vue/devtools-shared@8.1.2': resolution: {integrity: sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==} @@ -5842,6 +5878,10 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} @@ -7709,6 +7749,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + is-windows@0.2.0: resolution: {integrity: sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==} engines: {node: '>=0.10.0'} @@ -8586,6 +8630,9 @@ packages: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -9356,6 +9403,9 @@ packages: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} @@ -10187,6 +10237,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -10496,6 +10549,10 @@ packages: spdx-license-ids@3.0.23: resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} @@ -10672,6 +10729,10 @@ packages: resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} engines: {node: '>=18'} + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -11538,6 +11599,9 @@ packages: vue-component-type-helpers@3.3.1: resolution: {integrity: sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==} + vue-component-type-helpers@3.3.2: + resolution: {integrity: sha512-l4Z2Y34m7nFMlx8vrslJaVtXxUpzgDMSESC7TakG/c5kwjYT/do+E0NcT2/vWDzaoIhsShg/2OKwX7Q4nbzC0g==} + vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} @@ -14705,6 +14769,21 @@ snapshots: dependencies: quansync: 1.0.0 + '@regle/core@1.26.1(vue@3.5.34(typescript@5.9.3))': + dependencies: + '@standard-schema/spec': 1.1.0 + '@vue/devtools-api': 7.7.9 + type-fest: 5.6.0 + vue: 3.5.34(typescript@5.9.3) + + '@regle/rules@1.26.1(vue@3.5.34(typescript@5.9.3))': + dependencies: + '@regle/core': 1.26.1(vue@3.5.34(typescript@5.9.3)) + type-fest: 5.6.0 + transitivePeerDependencies: + - pinia + - vue + '@rolldown/pluginutils@1.0.1': {} '@rollup/plugin-alias@5.1.1(rollup@4.60.4)': @@ -15408,7 +15487,7 @@ snapshots: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.5.34(typescript@5.9.3) - vue-component-type-helpers: 3.3.1 + vue-component-type-helpers: 3.3.2 '@stylistic/eslint-plugin@5.10.0(eslint@9.39.4(jiti@2.7.0))': dependencies: @@ -16664,6 +16743,10 @@ snapshots: '@vue/devtools-api@6.6.4': {} + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + '@vue/devtools-api@8.1.2': dependencies: '@vue/devtools-kit': 8.1.2 @@ -16674,6 +16757,16 @@ snapshots: '@vue/devtools-shared': 8.1.2 vue: 3.5.34(typescript@5.9.3) + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + '@vue/devtools-kit@8.1.2': dependencies: '@vue/devtools-shared': 8.1.2 @@ -16681,6 +16774,10 @@ snapshots: hookable: 5.5.3 perfect-debounce: 2.1.0 + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + '@vue/devtools-shared@8.1.2': {} '@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.9.1(@stylistic/eslint-plugin@5.10.0(eslint@9.39.4(jiti@2.7.0)))(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.7.0))))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': @@ -17630,6 +17727,10 @@ snapshots: cookie-es@3.1.1: {} + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + core-js-compat@3.49.0: dependencies: browserslist: 4.28.2 @@ -19869,6 +19970,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-what@5.5.0: {} + is-windows@0.2.0: {} is-windows@1.0.2: {} @@ -21122,6 +21225,8 @@ snapshots: dependencies: minipass: 7.1.3 + mitt@3.0.1: {} + mkdirp@1.0.4: {} mkdist@2.4.1(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.4(typescript@5.9.3))(vue@3.5.34(typescript@5.9.3)): @@ -22240,6 +22345,8 @@ snapshots: pathval@2.0.1: {} + perfect-debounce@1.0.0: {} + perfect-debounce@2.1.0: {} picocolors@1.1.1: {} @@ -23218,6 +23325,8 @@ snapshots: reusify@1.1.0: {} + rfdc@1.4.1: {} + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -23685,6 +23794,8 @@ snapshots: spdx-license-ids@3.0.23: {} + speakingurl@14.0.1: {} + split2@1.0.0: dependencies: through2: 2.0.5 @@ -23883,6 +23994,10 @@ snapshots: make-asynchronous: 1.1.0 time-span: 5.1.0 + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + supports-color@10.2.2: {} supports-color@5.5.0: @@ -24809,6 +24924,8 @@ snapshots: vue-component-type-helpers@3.3.1: {} + vue-component-type-helpers@3.3.2: {} + vue-demi@0.14.10(vue@3.5.34(typescript@5.9.3)): dependencies: vue: 3.5.34(typescript@5.9.3) @@ -25244,6 +25361,8 @@ time: '@nuxtjs/i18n@9.5.6': '2025-06-25T14:47:31.453Z' '@nuxtjs/mdc@0.22.0': '2026-05-12T10:44:10.517Z' '@playwright/test@1.58.2': '2026-02-06T16:42:52.725Z' + '@regle/core@1.26.1': '2026-05-21T12:44:45.048Z' + '@regle/rules@1.26.1': '2026-05-21T12:44:51.737Z' '@rollup/plugin-node-resolve@16.0.3': '2025-10-13T00:33:10.742Z' '@storybook/addon-a11y@8.6.18': '2026-03-06T11:05:57.917Z' '@storybook/addon-actions@8.6.18': '2026-03-06T11:06:01.311Z' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2d6c8227..85590d07 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -87,6 +87,8 @@ catalog: nuxt-og-image: ^6.5.1 playwright: =1.58.2 playwright-core: =1.58.2 + '@regle/core': ^1.26.1 + '@regle/rules': ^1.26.1 reka-ui: ^2.9.5 semantic-release: ^24.2.9 storybook: ^8.6.18 diff --git a/src/runtime/assets/locales/en.json b/src/runtime/assets/locales/en.json index f0e8b412..f7bdb8cc 100644 --- a/src/runtime/assets/locales/en.json +++ b/src/runtime/assets/locales/en.json @@ -30,6 +30,19 @@ "pagination.previous-page": "Prev", "pagination.next-page": "Next", "recorder.recording": "Recording", - "":"hate forgetting json can't handle a trailing comma" + "auth.local.create": "Create Local User", + "auth.local.username": "Username:", + "auth.local.select-user": "Select Local User:", + "auth.local.create-and-switch": "Create and Switch", + "auth.local.aria.sign-in": "Sign In with Selected Local User", + "auth.local.aria.remove": "Remove Selected Local User", + "auth.local.switching-user": "Switching user...", + "auth.local.removing-user": "Removing user...", + "auth.local.username-taken": "Username already exists.", + "auth.local.remove-notification.title": "Warning:", + "auth.local.remove-notification.message": "Are you sure you want to remove this user?", + "auth.local.remove-notification.button": "Remove", + "auth.sign-in-register": "Sign in / Register with", + "": "hate forgetting json can't handle a trailing comma" } } diff --git a/src/runtime/components/WAuth/WAuth.md b/src/runtime/components/WAuth/WAuth.md new file mode 100644 index 00000000..c551011a --- /dev/null +++ b/src/runtime/components/WAuth/WAuth.md @@ -0,0 +1,8 @@ +--- +title: 'WAuth' +path: '/components/w-auth' +--- + + + + diff --git a/src/runtime/components/WAuth/WAuth.stories.ts b/src/runtime/components/WAuth/WAuth.stories.ts new file mode 100644 index 00000000..0285d54c --- /dev/null +++ b/src/runtime/components/WAuth/WAuth.stories.ts @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { delay } from "@alanscodelog/utils/delay" +import type { Meta, StoryObj } from "@storybook/vue3" + +import GithubLogo from "~icons/logos/github-icon" +import GoogleLogo from "~icons/logos/google-icon" + +import type { AuthLocalUser, AuthProviderStyle } from "../../types/index.js" +import * as components from "../index.js" + +type ExtraTestArgs = { + _localUsersSlotProps?: any +} + + +const providerStyles: Record> = { + github: { + name: "Github", + logo: GithubLogo + + }, + google: { + name: "Google", + logo: GoogleLogo + + } +} +const meta: Meta = { + component: components.WAuth, + title: "Components/Auth", + args: { + providers: ["github", "google"], + providerStyles, + onLogin: provider => { + // eslint-disable-next-line no-console + console.log("Login:", provider) + } + } +} + + +const sampleUsers: AuthLocalUser[] = [ + { id: "1", username: "alice", isLocal: true }, + { id: "2", username: "bob", isLocal: true }, + { id: "3", username: "charlie", isLocal: true }, + { id: "4", username: "daniel", isLocal: false } +] + +function onCreateUser(username: string) { + // eslint-disable-next-line no-console + console.log("Create user:", username) +} + +function onLoginUser(userId: string) { + // eslint-disable-next-line no-console + console.log("Sign in:", userId) +} + +function onRemoveUser(userId: string) { + // eslint-disable-next-line no-console + console.log("Remove user:", userId) +} +export default meta +type Story = StoryObj & { args?: ExtraTestArgs } + +export const Primary: Story = { + render: _args => { + const args = _args as any as NonNullable + return { + components: components as any, + setup: () => ({ args }), + template: ` +
+ +
+ ` + } + } +} + +export const WithLocalUsers: Story = { + render: _args => { + const args = _args as any as NonNullable + return { + components: components as any, + setup: () => ({ args }), + template: ` +
+ + + +
+ ` + } + }, + args: { + _localUsersSlotProps: { + users: sampleUsers, + onCreate: onCreateUser, + onLogin: onLoginUser, + onRemove: onRemoveUser + } + } +} diff --git a/src/runtime/components/WAuth/WAuth.vue b/src/runtime/components/WAuth/WAuth.vue new file mode 100644 index 00000000..949cce6a --- /dev/null +++ b/src/runtime/components/WAuth/WAuth.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/runtime/components/WAuth/WAuthLocalUsers.md b/src/runtime/components/WAuth/WAuthLocalUsers.md new file mode 100644 index 00000000..95bc9b98 --- /dev/null +++ b/src/runtime/components/WAuth/WAuthLocalUsers.md @@ -0,0 +1,8 @@ +--- +title: 'WAuthLocalUser' +path: '/components/w-auth-local-user' +--- + +See [WAuth](/components/w-auth) for storybook stories. + + diff --git a/src/runtime/components/WAuth/WAuthLocalUsers.vue b/src/runtime/components/WAuth/WAuthLocalUsers.vue new file mode 100644 index 00000000..b26cd712 --- /dev/null +++ b/src/runtime/components/WAuth/WAuthLocalUsers.vue @@ -0,0 +1,341 @@ + + + diff --git a/src/runtime/components/index.ts b/src/runtime/components/index.ts index 82f3daac..e4d74723 100644 --- a/src/runtime/components/index.ts +++ b/src/runtime/components/index.ts @@ -1,5 +1,7 @@ /* Manually Generated Index */ +export { default as WAuth } from "./WAuth/WAuth.vue" +export { default as WAuthLocalUsers } from "./WAuth/WAuthLocalUsers.vue" export { default as WButton } from "./WButton/WButton.vue" export { default as WCheckbox } from "./WCheckbox/WCheckbox.vue" export { default as WColorInput } from "./WColorInput/WColorInput.vue" diff --git a/src/runtime/types/index.ts b/src/runtime/types/index.ts index 2c1c9327..2e34a7bf 100644 --- a/src/runtime/types/index.ts +++ b/src/runtime/types/index.ts @@ -202,3 +202,23 @@ export type EmitsToProps = { ? (...args: T[K]) => void : T[K] } + + +/** An auth local user entry. */ +export type AuthLocalUser = { + /** Unique identifier. */ + id: string + /** Display username. */ + username: string + isLocal: boolean +} + +/** Style configuration for a single login provider button. */ +export type AuthProviderStyle = { + /** Display name shown on the button. */ + name: string + /** Vue component (icon) to render on the button. */ + logo: any + /** Additional classes for the button. */ + class?: string +} From d59166a80faaa99c284019e9fd9cbae591d9b019 Mon Sep 17 00:00:00 2001 From: Alan North Date: Thu, 11 Jun 2026 20:43:57 -0300 Subject: [PATCH 2/8] v0feat: added WAsyncValidatedInput component This is for usage with @witchcraft/nuxt-utils's useAsyncValidation for handling complex validation scenarios (client+server) as in the case of a username. --- package.json | 4 +- pnpm-lock.yaml | 26 ++- pnpm-workspace.yaml | 3 +- src/runtime/assets/locales/en.json | 3 + .../WAsyncValidatedInput.md | 10 ++ .../WAsyncValidatedInput.stories.ts | 103 ++++++++++++ .../WAsyncValidatedInput.vue | 148 ++++++++++++++++++ src/runtime/components/index.ts | 1 + 8 files changed, 288 insertions(+), 10 deletions(-) create mode 100644 src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.md create mode 100644 src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.stories.ts create mode 100644 src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.vue diff --git a/package.json b/package.json index 31e7a1c5..f9c3cde3 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@nuxt/types": "catalog:", "@regle/core": "catalog:", "@regle/rules": "catalog:", + "@standard-schema/spec": "catalog:", "@tailwindcss/vite": "catalog:", "@witchcraft/nuxt-utils": "catalog:", "colord": "catalog:", @@ -198,6 +199,5 @@ }, "publishConfig": { "access": "public" - }, - "optionalDependencies": {} + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4aafb28..0b4f6521 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ catalogs: '@rollup/plugin-node-resolve': specifier: ^16.0.3 version: 16.0.3 + '@standard-schema/spec': + specifier: ^1.1.0 + version: 1.1.0 '@storybook/addon-a11y': specifier: ^8.6.18 version: 8.6.18 @@ -163,8 +166,8 @@ catalogs: specifier: ^13.9.0 version: 13.9.0 '@witchcraft/nuxt-utils': - specifier: ^0.3.6 - version: 0.3.7 + specifier: ^0.3.10 + version: 0.3.10 autoprefixer: specifier: ^10.4.27 version: 10.5.0 @@ -317,12 +320,15 @@ importers: '@regle/rules': specifier: 'catalog:' version: 1.26.1(vue@3.5.34(typescript@5.9.3)) + '@standard-schema/spec': + specifier: 'catalog:' + version: 1.1.0 '@tailwindcss/vite': specifier: 'catalog:' version: 4.3.0(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(yaml@2.9.0)) '@witchcraft/nuxt-utils': specifier: 'catalog:' - version: 0.3.7(magicast@0.5.3) + version: 0.3.10(magicast@0.5.3)(vue@3.5.34(typescript@5.9.3)) colord: specifier: 'catalog:' version: 2.9.3 @@ -5072,8 +5078,8 @@ packages: '@webcontainer/env@1.1.1': resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} - '@witchcraft/nuxt-utils@0.3.7': - resolution: {integrity: sha512-BVBImUPNL/hVj7KOaqjpNs9gn9Qih8stV7rLCB64LKBkjzTFG5k4efuPlDQ2cw1g0tzqJBYL7euSjl/VRYGqww==} + '@witchcraft/nuxt-utils@0.3.10': + resolution: {integrity: sha512-X5YksvLXTJiLP0ORP62b+D60ASk/ITN3j4GwcSQ99qjV6qg9hA50tWjj53dnOi4xEMpEd45j/FJyi000TgkmYA==} engines: {node: '>=20.0.0'} abbrev@3.0.1: @@ -16913,15 +16919,20 @@ snapshots: '@webcontainer/env@1.1.1': {} - '@witchcraft/nuxt-utils@0.3.7(magicast@0.5.3)': + '@witchcraft/nuxt-utils@0.3.10(magicast@0.5.3)(vue@3.5.34(typescript@5.9.3))': dependencies: '@alanscodelog/utils': 6.2.1 '@nuxt/kit': 4.4.6(magicast@0.5.3) + '@standard-schema/spec': 1.1.0 + '@vueuse/core': 14.3.0(vue@3.5.34(typescript@5.9.3)) change-case: 5.4.4 defu: 6.1.7 fast-glob: 3.3.3 + tailwind-merge: 3.6.0 + zod: 4.4.3 transitivePeerDependencies: - magicast + - vue abbrev@3.0.1: {} @@ -25364,6 +25375,7 @@ time: '@regle/core@1.26.1': '2026-05-21T12:44:45.048Z' '@regle/rules@1.26.1': '2026-05-21T12:44:51.737Z' '@rollup/plugin-node-resolve@16.0.3': '2025-10-13T00:33:10.742Z' + '@standard-schema/spec@1.1.0': '2025-12-15T20:49:46.431Z' '@storybook/addon-a11y@8.6.18': '2026-03-06T11:05:57.917Z' '@storybook/addon-actions@8.6.18': '2026-03-06T11:06:01.311Z' '@storybook/addon-essentials@8.6.18': '2026-03-06T11:06:07.552Z' @@ -25387,7 +25399,7 @@ time: '@vue/runtime-dom@3.5.34': '2026-05-06T07:20:04.095Z' '@vueuse/components@13.9.0': '2025-09-01T11:42:46.111Z' '@vueuse/core@13.9.0': '2025-09-01T11:42:03.511Z' - '@witchcraft/nuxt-utils@0.3.7': '2026-06-02T21:56:21.890Z' + '@witchcraft/nuxt-utils@0.3.10': '2026-06-11T05:05:04.579Z' autoprefixer@10.5.0: '2026-04-13T20:41:45.117Z' colord@2.9.3: '2022-08-10T13:52:12.556Z' colorjs.io@0.6.0-alpha.1: '2024-12-26T17:42:13.193Z' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 85590d07..51c8abbf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,7 +67,7 @@ catalog: '@vue/runtime-dom': ^3.5.30 '@vueuse/components': ^13.9.0 '@vueuse/core': ^13.9.0 - '@witchcraft/nuxt-utils': ^0.3.6 + '@witchcraft/nuxt-utils': ^0.3.10 autoprefixer: ^10.4.27 colord: ^2.9.3 colorjs.io: 0.6.0-alpha.1 @@ -111,6 +111,7 @@ catalog: zod: ^4.4.3 scule: ^1.3.0 vue-component-meta: ^3.3.1 + '@standard-schema/spec': ^1.1.0 patchedDependencies: '@nuxt/module-builder': patches/@nuxt__module-builder.patch diff --git a/src/runtime/assets/locales/en.json b/src/runtime/assets/locales/en.json index f7bdb8cc..32d581de 100644 --- a/src/runtime/assets/locales/en.json +++ b/src/runtime/assets/locales/en.json @@ -43,6 +43,9 @@ "auth.local.remove-notification.message": "Are you sure you want to remove this user?", "auth.local.remove-notification.button": "Remove", "auth.sign-in-register": "Sign in / Register with", + "async-validated-input.checking": "Checking...", + "async-validated-input.valid": "Valid.", + "async-validated-input.invalid": "Invalid.", "": "hate forgetting json can't handle a trailing comma" } } diff --git a/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.md b/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.md new file mode 100644 index 00000000..f93820cf --- /dev/null +++ b/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.md @@ -0,0 +1,10 @@ +--- +title: 'WAsyncValidatedInput' +path: '/components/w-async-validated-input' +--- +This is for usage with @witchcraft/nuxt-utils's useAsyncValidation for handling +complex validation scenarios (client+server) as in the case of a username. + + + + diff --git a/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.stories.ts b/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.stories.ts new file mode 100644 index 00000000..f626d505 --- /dev/null +++ b/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.stories.ts @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { delay } from "@alanscodelog/utils/delay" +import type { Meta, StoryObj } from "@storybook/vue3" +import { ref, watch } from "vue" + +import WAsyncValidatedInput from "./WAsyncValidatedInput.vue" + +import * as components from "../index.js" + +type ExtraTestArgs = { +} + +const meta: Meta = { + component: WAsyncValidatedInput, + title: "Components/AsyncValidatedInput", + args: { + } +} + +export default meta + +type Story = StoryObj & { args?: ExtraTestArgs } + +export const Primary: Story = { + render: _args => { + let args = _args as any as NonNullable + return { + components: components as any, + setup: () => { + args = { + errors: [], + inputValid: true, + ...args + } + const modelValue = ref(args.modelValue ?? "") + const status = ref(args.status) + const canSubmit = ref(true) + const forceInvalidResult = ref(false) + const forceInvalidInput = ref(false) + watch(modelValue, () => { + status.value = "loading" + delay(1000).then(() => { + status.value = (args.errors?.length ?? 0) > 0 || forceInvalidResult.value + ? "invalid" + : args.status ?? "valid" + }) + }) + watch(status, () => { + if ((args.errors?.length ?? 0) > 0 || status.value === "invalid") { + canSubmit.value = false + } else { + canSubmit.value = true + } + }) + return { + args, + canSubmit, + status, + modelValue, + forceInvalidResult, + forceInvalidInput + } + }, + template: ` +
+
+ The async nature of this component is loosely simulated by the story as it depends on @witchcraft/nuxt-utils's useAsyncValidation composable that uses Nuxt's async data. +
+
+ Note that the input can be an invalid state (schema error) independent of the "status" property. +
+ Force Invalid Result + Force Invalid Input (e.g. this would correspond to a schema only check if using the composable) +
+ +
+
+ ` + } + } +} + +export const WithErrors: Story = { + ...Primary, + args: { + status: "invalid", + errors: ["Some error here.", "Some very long error text here. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."] + } +} + +export const LoadingStatus: Story = { + ...Primary, + args: { + status: "loading" + } +} + diff --git a/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.vue b/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.vue new file mode 100644 index 00000000..fe5b412c --- /dev/null +++ b/src/runtime/components/WAsyncValidatedInput/WAsyncValidatedInput.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/runtime/components/index.ts b/src/runtime/components/index.ts index e4d74723..69a19ee9 100644 --- a/src/runtime/components/index.ts +++ b/src/runtime/components/index.ts @@ -1,5 +1,6 @@ /* Manually Generated Index */ +export { default as WAsyncValidatedInput } from "./WAsyncValidatedInput/WAsyncValidatedInput.vue" export { default as WAuth } from "./WAuth/WAuth.vue" export { default as WAuthLocalUsers } from "./WAuth/WAuthLocalUsers.vue" export { default as WButton } from "./WButton/WButton.vue" From fc6c3d43f57c96e4a4d8ee7ccfae5150fcce2908 Mon Sep 17 00:00:00 2001 From: Alan North Date: Fri, 12 Jun 2026 02:26:58 -0300 Subject: [PATCH 3/8] v0fix: fixed useInjectedI18n.ts erroring on missing key when it shouldnt Moved messages injection to useInjectedI18n instead of the defaultTranslationFunction, that was causing multiple injects on each use and weird things to happen. This required a small change to the defaultTranslationFunction or any custom function. It should now take in a third param, messages. --- src/runtime/composables/useInjectedI18n.ts | 15 ++++++++++++--- src/runtime/composables/useSetupI18n.ts | 5 +++-- src/runtime/helpers/defaultTranslationFunction.ts | 7 +++++-- src/runtime/injectionKeys.ts | 4 ++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/runtime/composables/useInjectedI18n.ts b/src/runtime/composables/useInjectedI18n.ts index aeb00ed5..349972b8 100644 --- a/src/runtime/composables/useInjectedI18n.ts +++ b/src/runtime/composables/useInjectedI18n.ts @@ -2,10 +2,19 @@ import { inject } from "vue" import type { TranslationFunction } from "./useSetupI18n.js" -import { i18nInjectionKey } from "../injectionKeys.js" +import { i18nInjectionKey, translationMessagesInjectionKey } from "../injectionKeys.js" export function useInjectedI18n(): TranslationFunction { const val = inject(i18nInjectionKey, undefined) - if (val === undefined) throw new Error("witchcraft/ui: i18n is not injected. See useSetupI18n.") - return val + const messages = inject(translationMessagesInjectionKey, undefined) + + if (val === undefined) { + throw new Error("witchcraft/ui: i18n is not injected. See useSetupI18n.") + } + + if (messages === undefined) { + throw new Error("witchcraft/ui: The default translation function requires the useSetupI18n `useBuiltinTranslations` options to be true. Did you set it?") + } + return (key: string, replacements?: Record) => + val(key, replacements, messages) } diff --git a/src/runtime/composables/useSetupI18n.ts b/src/runtime/composables/useSetupI18n.ts index 8d53e772..a00a7420 100644 --- a/src/runtime/composables/useSetupI18n.ts +++ b/src/runtime/composables/useSetupI18n.ts @@ -8,7 +8,8 @@ import { import { defaultTranslationFunction } from "../helpers/defaultTranslationFunction.js" import { i18nInjectionKey, translationMessagesInjectionKey } from "../injectionKeys.js" -export type TranslationFunction = (key: string, replacements?: Record) => string +export type TranslationFunction = (key: string, replacements?: Record | undefined) => string +export type InternalTranslationFunction = (key: string, replacements: Record | undefined, messages: Record) => string const messagesGlob = import.meta.glob("../assets/locales/*.json") @@ -47,7 +48,7 @@ export async function useSetupI18n({ /** To avoid having to wrap the component in a Suspense component because of the await on `useSetupI18n`, we can provide a dummy message proxy that just returns empty text until the messages are loaded. */ useDummyMessageSetWhileLoading?: boolean /** A custom translation function. The default requires the `useBuiltinTranslations` option to be true. */ - translationFunction?: TranslationFunction + translationFunction?: InternalTranslationFunction }): Promise { provide(i18nInjectionKey, translationFunction) if (useBuiltinTranslations) { diff --git a/src/runtime/helpers/defaultTranslationFunction.ts b/src/runtime/helpers/defaultTranslationFunction.ts index 6b046060..26c3ba69 100644 --- a/src/runtime/helpers/defaultTranslationFunction.ts +++ b/src/runtime/helpers/defaultTranslationFunction.ts @@ -19,8 +19,11 @@ import { translationMessagesInjectionKey } from "../injectionKeys.js" * Would return "This is a message". */ -export function defaultTranslationFunction(key: string, replacements?: Record): string { - const messages = inject(translationMessagesInjectionKey) +export function defaultTranslationFunction( + key: string, + replacements: Record | undefined, + messages: Record +): string { if (messages === undefined) throw new Error("witchcraft/ui: The default translation function requires the useSetupI18n `useBuiltinTranslations` options to be true. Did you set it?") let value = (messages.value)["witchcraft-ui"][key] if (value === undefined) throw new Error(`No translation for key ${key}.`) diff --git a/src/runtime/injectionKeys.ts b/src/runtime/injectionKeys.ts index ffc9534a..c90e7ef8 100644 --- a/src/runtime/injectionKeys.ts +++ b/src/runtime/injectionKeys.ts @@ -1,7 +1,7 @@ import type { Theme } from "metamorphosis/Theme" import type { InjectionKey, Ref, ShallowRef } from "vue" -import type { TranslationFunction } from "./composables/useSetupI18n.js" +import type { InternalTranslationFunction } from "./composables/useSetupI18n.js" import type { DarkModeCommands, DarkModeState } from "./types/index.js" export const darkModeCommandsInjectionKey = Symbol.for("@witchcraft/ui:darkModeCommands") as InjectionKey @@ -14,7 +14,7 @@ export const languageLocaleInjectionKey = Symbol.for("@witchcraft/ui:languageLoc export const timeLocaleInjectionKey = Symbol.for("@witchcraft/ui:timeLocale") as InjectionKey> -export const i18nInjectionKey = Symbol.for("@witchcraft/ui:i18n") as InjectionKey +export const i18nInjectionKey = Symbol.for("@witchcraft/ui:i18n") as InjectionKey export const translationMessagesInjectionKey = Symbol.for("@witchcraft/ui:i18nMessages") as InjectionKey>> From dd647a6342eb80dcb7771bac95310b25eebe4e6e Mon Sep 17 00:00:00 2001 From: Alan North Date: Sat, 13 Jun 2026 12:11:45 -0300 Subject: [PATCH 4/8] refactor: deprecated copy, readFile, and base64ToImg readFile is deprecated completele, copy has been moved to @alanscodelog/utils and improved (copyToClipboard and readFromClipboard were added). --- pnpm-lock.yaml | 22 +++++++++++------- pnpm-workspace.yaml | 2 +- .../components/WColorPicker/WColorPicker.vue | 4 ++-- src/runtime/components/WDebug/WDebug.vue | 23 +++++++++---------- .../components/WMultiValues/WMultiValues.vue | 4 ++-- .../WNotifications/WNotification.vue | 4 ++-- src/runtime/helpers/base64ToImg.ts | 18 +++++++++++++++ src/runtime/helpers/copy.ts | 5 +++- src/runtime/helpers/readFile.ts | 5 +++- 9 files changed, 58 insertions(+), 29 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b4f6521..d6ae9c06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,8 +19,8 @@ catalogs: specifier: ^6.3.0 version: 6.3.0 '@alanscodelog/utils': - specifier: ^6.2.0 - version: 6.2.1 + specifier: ^6.3.0 + version: 6.3.0 '@alanscodelog/vite-config': specifier: ^0.0.7 version: 0.0.7 @@ -301,7 +301,7 @@ importers: dependencies: '@alanscodelog/utils': specifier: 'catalog:' - version: 6.2.1 + version: 6.3.0 '@iconify/json': specifier: 'catalog:' version: 2.2.476 @@ -666,6 +666,10 @@ packages: resolution: {integrity: sha512-V1escbZY0PWsjmfL+c3VZ19wzK07swstAB9v5npX71GtlRppDxCoy0t0RqOVyYTR2OOvRTIrjWc8f1ImkHtSVQ==} engines: {node: '>=20.0.0'} + '@alanscodelog/utils@6.3.0': + resolution: {integrity: sha512-g8slXo4V7jcXHEWebwR5aIhCQTPyMoLteabXgjnT3nidHcuHJsh4JNyQUaglCEHnd/rJzvvVC67BdHBq1sa2WA==} + engines: {node: '>=20.0.0'} + '@alanscodelog/vite-config@0.0.7': resolution: {integrity: sha512-LyzFSNV3w6evFZRTrhYQgx1ORKfNxlPFdUVobINIeWXXGGoeLyu3ey7sPuU7h4zgzU22uaDF7r/bASPkkhmfdQ==} engines: {node: '>=20.0.0'} @@ -11605,8 +11609,8 @@ packages: vue-component-type-helpers@3.3.1: resolution: {integrity: sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==} - vue-component-type-helpers@3.3.2: - resolution: {integrity: sha512-l4Z2Y34m7nFMlx8vrslJaVtXxUpzgDMSESC7TakG/c5kwjYT/do+E0NcT2/vWDzaoIhsShg/2OKwX7Q4nbzC0g==} + vue-component-type-helpers@3.3.3: + resolution: {integrity: sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==} vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} @@ -12033,6 +12037,8 @@ snapshots: '@alanscodelog/utils@6.2.1': {} + '@alanscodelog/utils@6.3.0': {} + '@alanscodelog/vite-config@0.0.7(@types/node@24.12.4)(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.4.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.47.1)(typescript@5.9.3)(yaml@2.9.0)': dependencies: '@alanscodelog/commitlint-config': 3.1.3(@alanscodelog/semantic-release-config@6.0.3(@types/node@24.12.4)(typescript@5.9.3)(yaml@2.9.0)) @@ -15493,7 +15499,7 @@ snapshots: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.5.34(typescript@5.9.3) - vue-component-type-helpers: 3.3.2 + vue-component-type-helpers: 3.3.3 '@stylistic/eslint-plugin@5.10.0(eslint@9.39.4(jiti@2.7.0))': dependencies: @@ -24935,7 +24941,7 @@ snapshots: vue-component-type-helpers@3.3.1: {} - vue-component-type-helpers@3.3.2: {} + vue-component-type-helpers@3.3.3: {} vue-demi@0.14.10(vue@3.5.34(typescript@5.9.3)): dependencies: @@ -25350,7 +25356,7 @@ time: '@alanscodelog/eslint-config@6.3.2': '2026-06-02T19:32:43.740Z' '@alanscodelog/semantic-release-config@6.0.3': '2026-06-02T18:50:13.331Z' '@alanscodelog/tsconfigs@6.3.0': '2026-03-23T15:56:05.919Z' - '@alanscodelog/utils@6.2.1': '2026-06-02T19:34:35.701Z' + '@alanscodelog/utils@6.3.0': '2026-06-13T16:57:03.034Z' '@alanscodelog/vite-config@0.0.7': '2025-11-30T16:05:26.946Z' '@chromatic-com/storybook@3.2.7': '2025-06-20T08:02:12.227Z' '@commitlint/cli@20.5.3': '2026-04-30T08:16:16.929Z' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 51c8abbf..f122b1f0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -21,7 +21,7 @@ catalog: '@alanscodelog/eslint-config': ^6.3.1 '@alanscodelog/semantic-release-config': ^6.0.2 '@alanscodelog/tsconfigs': ^6.3.0 - '@alanscodelog/utils': ^6.2.0 + '@alanscodelog/utils': ^6.3.0 '@alanscodelog/vite-config': ^0.0.7 '@chromatic-com/storybook': ^3.2.7 '@commitlint/cli': ^20.5.0 diff --git a/src/runtime/components/WColorPicker/WColorPicker.vue b/src/runtime/components/WColorPicker/WColorPicker.vue index c816535f..c49c790c 100644 --- a/src/runtime/components/WColorPicker/WColorPicker.vue +++ b/src/runtime/components/WColorPicker/WColorPicker.vue @@ -160,7 +160,7 @@ @@ -189,6 +189,7 @@ diff --git a/src/runtime/components/WMultiValues/WMultiValues.vue b/src/runtime/components/WMultiValues/WMultiValues.vue index 311e7b64..d98694ca 100644 --- a/src/runtime/components/WMultiValues/WMultiValues.vue +++ b/src/runtime/components/WMultiValues/WMultiValues.vue @@ -61,7 +61,7 @@ v-for="(value, index) of $modelValue" :key="value" ref="itemRefs" - @keydown.ctrl.c.prevent="copy(value.toString())" + @keydown.ctrl.c.prevent="copyToClipboard(value.toString())" @focus="activeIndex = index" > {{ value }} @@ -80,12 +80,12 @@