@lenne.tech/nuxt-extensions 1.11.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +63 -0
- package/README.md +137 -0
- package/dist/module.d.mts +1 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +23 -1
- package/dist/runtime/lib/auth-client.d.ts +102 -44
- package/dist/runtime/plugins/form-label-association.client.d.ts +82 -0
- package/dist/runtime/plugins/form-label-association.client.js +67 -0
- package/dist/runtime/plugins/pre-hydration-input.client.d.ts +71 -0
- package/dist/runtime/plugins/pre-hydration-input.client.js +57 -0
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/module.d.ts +113 -0
- package/package.json +13 -12
package/CLAUDE.md
CHANGED
|
@@ -76,6 +76,14 @@ export default defineNuxtConfig({
|
|
|
76
76
|
enabled: true,
|
|
77
77
|
basePath: '/ai', // Must match the nest-server AI controller
|
|
78
78
|
},
|
|
79
|
+
formLabelAssociation: {
|
|
80
|
+
enabled: true, // repair <label for> broken by an SSR/client useId() divergence
|
|
81
|
+
maxRepairMs: 1500, // how long to keep repairing deferred subtrees
|
|
82
|
+
},
|
|
83
|
+
preHydrationInput: {
|
|
84
|
+
enabled: true, // keep text typed before hydration instead of losing it
|
|
85
|
+
maxRestoreMs: 1500, // how long to keep repairing deferred subtrees
|
|
86
|
+
},
|
|
79
87
|
},
|
|
80
88
|
});
|
|
81
89
|
```
|
|
@@ -171,6 +179,61 @@ export default defineNuxtRouteMiddleware((to) => {
|
|
|
171
179
|
});
|
|
172
180
|
```
|
|
173
181
|
|
|
182
|
+
## Form Label Repair
|
|
183
|
+
|
|
184
|
+
**The defect:** Nuxt UI's `FormField` derives the label's `for` and the control's `id` from one
|
|
185
|
+
`useId()` call. When `useId()` diverges between server and client, the control's `id` is
|
|
186
|
+
force-patched on hydration (vuejs/core#9083) while the label's `for` — a prop on reka-ui's
|
|
187
|
+
`Label` component — is not. The label keeps the stale value and the association breaks.
|
|
188
|
+
|
|
189
|
+
**What this module adds:** `runtime/plugins/form-label-association.client.ts` re-points
|
|
190
|
+
dangling Nuxt UI field labels at the control in their own field, at `app:mounted` and for a
|
|
191
|
+
bounded window afterwards (`maxRepairMs`, default 1500 ms).
|
|
192
|
+
|
|
193
|
+
**What this means when you debug a consuming project:**
|
|
194
|
+
|
|
195
|
+
- **A `for` attribute that changes after mount is this plugin, not a bug.** It logs a
|
|
196
|
+
`console.warn` in development naming how many labels it repaired.
|
|
197
|
+
- **It refuses to guess.** The repair only fires when the field holds exactly one eligible
|
|
198
|
+
control. Radio and checkbox groups, hidden form-value proxies, `aria-hidden`/`disabled`
|
|
199
|
+
controls and button-only fields are skipped on purpose — binding a group caption to its
|
|
200
|
+
first option would submit a value the user never chose.
|
|
201
|
+
- **Only Nuxt UI's own field labels are touched** (`label[data-slot="label"][for]`). Labels
|
|
202
|
+
your application wrote are never rewritten.
|
|
203
|
+
- **Do not "fix" a skipped group by loosening the rule.** Inert beats wrong here; the correct
|
|
204
|
+
fix for a group is an SSR-stable id at the source.
|
|
205
|
+
- This is a **stopgap** for a framework-level divergence. Opt out with
|
|
206
|
+
`ltExtensions.formLabelAssociation.enabled: false`.
|
|
207
|
+
|
|
208
|
+
## Pre-Hydration Input Preservation
|
|
209
|
+
|
|
210
|
+
**The defect:** until Vue hydrates a server-rendered `<input>`, it carries no framework
|
|
211
|
+
listener. Text typed in that window goes into the DOM node, the `input` event reaches nothing,
|
|
212
|
+
and `v-model`'s mounted hook then overwrites it. The entry is **erased, not delayed**. It is
|
|
213
|
+
load-dependent, so it hides in development.
|
|
214
|
+
|
|
215
|
+
**Vue already fixes most of it.** Since 3.5.41 (vuejs/core#14411) Vue adopts the typed value
|
|
216
|
+
into the model on hydration — but only for `type="text"` and `textarea`. `email`, `password`,
|
|
217
|
+
`tel`, `url`, `search` and `number` are still erased. Upstream tracking: vuejs/core#15210.
|
|
218
|
+
|
|
219
|
+
**What this module adds:** `runtime/plugins/pre-hydration-input.client.ts` snapshots edited
|
|
220
|
+
fields at `app:beforeMount` and restores them at `app:mounted`, dispatching a synthetic
|
|
221
|
+
`input` event so `v-model` adopts the value.
|
|
222
|
+
|
|
223
|
+
**What this means when you debug a consuming project:**
|
|
224
|
+
|
|
225
|
+
- **Do not add a `readonly`-until-mounted guard to inputs.** It was tried and removed: it makes
|
|
226
|
+
the field look usable while silently refusing, suppresses browser autofill (`readonly` is the
|
|
227
|
+
documented technique for exactly that), makes screen readers announce "read only" with no
|
|
228
|
+
announcement when it flips back, and stops mobile browsers raising the on-screen keyboard.
|
|
229
|
+
- A field is treated as edited when `value !== defaultValue`, so server-pre-filled values are
|
|
230
|
+
never restored over a legitimate change made during hydration.
|
|
231
|
+
- Each field is restored at most once; a later programmatic clear is not undone.
|
|
232
|
+
- This is a **stopgap**. When Vue widens its gate, delete the plugin —
|
|
233
|
+
`test/pre-hydration-input.test.ts` pins the current behaviour and will fail on the types Vue
|
|
234
|
+
takes over.
|
|
235
|
+
- Opt out with `ltExtensions.preHydrationInput.enabled: false`.
|
|
236
|
+
|
|
174
237
|
## Development Rules
|
|
175
238
|
|
|
176
239
|
1. **ALWAYS read source code** in `dist/runtime/` to understand available composables and components
|
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ npm install tus-js-client
|
|
|
41
41
|
- **Transition Components** - Ready-to-use Vue transition wrappers
|
|
42
42
|
- **i18n Support** - English and German translations (works without i18n too)
|
|
43
43
|
- **Auto-imports** - All composables and components are auto-imported
|
|
44
|
+
- **Pre-hydration input preservation** - text typed before hydration is kept, not erased
|
|
45
|
+
- **Form label repair** - restores `<label for>` associations that hydration breaks
|
|
44
46
|
|
|
45
47
|
## Environment Variables
|
|
46
48
|
|
|
@@ -156,6 +158,141 @@ export default defineNuxtConfig({
|
|
|
156
158
|
});
|
|
157
159
|
```
|
|
158
160
|
|
|
161
|
+
## Form Label Repair
|
|
162
|
+
|
|
163
|
+
Restores `<label for>` associations that hydration breaks. Active by default.
|
|
164
|
+
|
|
165
|
+
### The problem
|
|
166
|
+
|
|
167
|
+
Nuxt UI's `FormField` derives the label's `for` and the control's `id` from a single
|
|
168
|
+
`useId()` call, so they cannot disagree — unless `useId()` itself returns different values on
|
|
169
|
+
server and client, which happens when the two walk a different number of async boundaries.
|
|
170
|
+
|
|
171
|
+
Measured against `@nuxt/ui` 4.11.x:
|
|
172
|
+
|
|
173
|
+
| | label `for` | control `id` |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| SSR payload | `v-0-4-2` | `v-0-4-2` |
|
|
176
|
+
| after hydration | `v-0-4-2` | `v-0-1-2` |
|
|
177
|
+
|
|
178
|
+
Vue 3.5.39 did not cause this, it exposed it
|
|
179
|
+
([vuejs/core#9083](https://github.com/vuejs/core/pull/9083) force-patches an element's
|
|
180
|
+
dynamic props on hydration). The control's `id` is such a prop; the label's `for`, passed
|
|
181
|
+
through reka-ui's `Label` component, is not — so only the label keeps the stale value.
|
|
182
|
+
|
|
183
|
+
### What it costs when it breaks
|
|
184
|
+
|
|
185
|
+
The control loses its programmatic label. Without a placeholder it has no accessible name at
|
|
186
|
+
all and a screen reader announces "edit text, blank"; **with** a placeholder that becomes the
|
|
187
|
+
name instead, so the visible label is no longer part of it and speech input stops working.
|
|
188
|
+
Clicking the label focuses nothing either — which on a checkbox or radio is the primary hit
|
|
189
|
+
target, not a convenience. Tests using `getByRole('textbox', { name })` stop finding fields.
|
|
190
|
+
|
|
191
|
+
### What the repair does
|
|
192
|
+
|
|
193
|
+
After mount, and for a bounded window afterwards, every Nuxt UI field label whose target does
|
|
194
|
+
not resolve is re-pointed at the control in its own field.
|
|
195
|
+
|
|
196
|
+
**It refuses to guess.** A repair that fires where it should not is worse than the defect: a
|
|
197
|
+
dangling label is inert and visible, a mis-pointed one is confidently wrong and silent, and
|
|
198
|
+
on a radio or checkbox a click on the caption would change a value the user never chose. So
|
|
199
|
+
the repair only fires when the field contains exactly **one** eligible control. Skipped
|
|
200
|
+
deliberately:
|
|
201
|
+
|
|
202
|
+
| Case | Why |
|
|
203
|
+
|------|-----|
|
|
204
|
+
| Radio / checkbox groups | One field root, many controls — binding every caption to the first option would submit a value nobody picked |
|
|
205
|
+
| Hidden form-value proxies, `aria-hidden`, `disabled` | Not the user's control; pointing at one restores neither the name nor the click |
|
|
206
|
+
| Fields whose only visible control is a `button` | Nothing safe to point at |
|
|
207
|
+
| Labels your application wrote | Only Nuxt UI's own field labels (`data-slot="label"`) are touched |
|
|
208
|
+
|
|
209
|
+
`for` is repaired rather than `aria-labelledby` added, because `for` carries **both** the
|
|
210
|
+
accessible name and click-to-focus.
|
|
211
|
+
|
|
212
|
+
**This is a repair, not a cure.** The real fix is for the id not to diverge; this exists
|
|
213
|
+
because the divergence sits in the framework stack rather than in any one application.
|
|
214
|
+
|
|
215
|
+
### Opting out
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
export default defineNuxtConfig({
|
|
219
|
+
ltExtensions: {
|
|
220
|
+
formLabelAssociation: {
|
|
221
|
+
enabled: false,
|
|
222
|
+
// or keep it on and only change how long deferred subtrees are waited for:
|
|
223
|
+
// maxRepairMs: 3000,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Two real reasons to: your application assigns `for` itself and depends on those exact values,
|
|
230
|
+
or your tests assert on literal `for` / `id` strings rather than on the accessible name.
|
|
231
|
+
|
|
232
|
+
## Pre-Hydration Input Preservation
|
|
233
|
+
|
|
234
|
+
Text typed before a page has hydrated is kept instead of being silently thrown away.
|
|
235
|
+
|
|
236
|
+
### The problem
|
|
237
|
+
|
|
238
|
+
Until Vue hydrates a server-rendered `<input>`, the element carries **no framework listener**.
|
|
239
|
+
Text typed in that window is written to the DOM node, the `input` event reaches nothing, and
|
|
240
|
+
`v-model`'s mounted hook then writes the model value back over it. The entry is **erased, not
|
|
241
|
+
delayed** — no error, no toast, nothing persisted.
|
|
242
|
+
|
|
243
|
+
It is load-dependent, so it hides in development and shows up on a cold cache, a slow device
|
|
244
|
+
or a throttled CPU — most often on the login screen, where people type on sight.
|
|
245
|
+
|
|
246
|
+
### What Vue already does, and where it stops
|
|
247
|
+
|
|
248
|
+
Vue fixed this in **3.5.41** ([vuejs/core#14411](https://github.com/vuejs/core/pull/14411)):
|
|
249
|
+
on hydration it compares the field's live value against what the server rendered and, when
|
|
250
|
+
they differ, adopts the typed value into the model instead of overwriting the node.
|
|
251
|
+
|
|
252
|
+
That adoption is gated on `type="text"` and `textarea`. **Every other type still loses the
|
|
253
|
+
entry** — and those are exactly the fields a sign-in form uses:
|
|
254
|
+
|
|
255
|
+
| Input type | Typed before hydration |
|
|
256
|
+
|------------|------------------------|
|
|
257
|
+
| `text`, `textarea` | kept by Vue itself |
|
|
258
|
+
| `email`, `password`, `tel`, `url`, `search`, `number` | **erased** |
|
|
259
|
+
|
|
260
|
+
Widening the gate is tracked upstream as
|
|
261
|
+
[vuejs/core#15210](https://github.com/vuejs/core/issues/15210) — open, labelled
|
|
262
|
+
`p2-edge-case`, no milestone.
|
|
263
|
+
|
|
264
|
+
### What this module adds
|
|
265
|
+
|
|
266
|
+
A small client plugin that closes the remaining gap. Just before hydration it reads what is
|
|
267
|
+
in each field; just after, it writes back anything that was overwritten and dispatches a
|
|
268
|
+
synthetic `input` event so `v-model` adopts it.
|
|
269
|
+
|
|
270
|
+
A field counts as edited when its value differs from `defaultValue` — the same test Vue uses,
|
|
271
|
+
so values the server pre-filled are never mistaken for user input. A browser autofill that
|
|
272
|
+
lands before hydration is recovered the same way.
|
|
273
|
+
|
|
274
|
+
Fields stay ordinary editable fields throughout. Nothing is made `readonly`, so autofill,
|
|
275
|
+
screen-reader semantics and the mobile on-screen keyboard are untouched, and the user never
|
|
276
|
+
faces a field that looks usable and silently refuses.
|
|
277
|
+
|
|
278
|
+
**This is a stopgap.** When Vue covers the remaining types, delete it —
|
|
279
|
+
`test/pre-hydration-input.test.ts` pins the current gate and will fail on the types Vue takes
|
|
280
|
+
over, which is the signal.
|
|
281
|
+
|
|
282
|
+
### Opting out
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
export default defineNuxtConfig({
|
|
286
|
+
ltExtensions: {
|
|
287
|
+
preHydrationInput: {
|
|
288
|
+
enabled: false,
|
|
289
|
+
// or keep it on and only change how long deferred subtrees are waited for:
|
|
290
|
+
// maxRestoreMs: 3000,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
159
296
|
## Usage
|
|
160
297
|
|
|
161
298
|
### Authentication
|
package/dist/module.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { LtExtensionsModuleOptions } from '../dist/runtime/types/index.js';
|
|
|
3
3
|
export * from '../dist/runtime/types/index.js';
|
|
4
4
|
|
|
5
5
|
declare const name = "@lenne.tech/nuxt-extensions";
|
|
6
|
-
declare const version = "1.
|
|
6
|
+
declare const version = "1.13.0";
|
|
7
7
|
declare const configKey = "ltExtensions";
|
|
8
8
|
declare const DEFAULT_LT_AUTH_STATE_COOKIE = "lt-auth-state";
|
|
9
9
|
declare const DEFAULT_LT_JWT_TOKEN_COOKIE = "lt-jwt-token";
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, tryResolveModule, addImports, addComponent, addPlugin, addRouteMiddleware } from '@nuxt/kit';
|
|
2
2
|
|
|
3
3
|
const name = "@lenne.tech/nuxt-extensions";
|
|
4
|
-
const version = "1.
|
|
4
|
+
const version = "1.13.0";
|
|
5
5
|
const configKey = "ltExtensions";
|
|
6
6
|
const DEFAULT_LT_AUTH_STATE_COOKIE = "lt-auth-state";
|
|
7
7
|
const DEFAULT_LT_JWT_TOKEN_COOKIE = "lt-jwt-token";
|
|
@@ -36,9 +36,15 @@ const defaultOptions = {
|
|
|
36
36
|
enabled: true,
|
|
37
37
|
defaultLocale: "de"
|
|
38
38
|
},
|
|
39
|
+
formLabelAssociation: {
|
|
40
|
+
enabled: true
|
|
41
|
+
},
|
|
39
42
|
i18n: {
|
|
40
43
|
autoMerge: true
|
|
41
44
|
},
|
|
45
|
+
preHydrationInput: {
|
|
46
|
+
enabled: true
|
|
47
|
+
},
|
|
42
48
|
tus: {
|
|
43
49
|
defaultChunkSize: 5 * 1024 * 1024,
|
|
44
50
|
// 5MB
|
|
@@ -63,7 +69,9 @@ const module$1 = defineNuxtModule({
|
|
|
63
69
|
systemSetup: { ...defaultOptions.auth.systemSetup, ...options.auth?.systemSetup }
|
|
64
70
|
},
|
|
65
71
|
errorTranslation: { ...defaultOptions.errorTranslation, ...options.errorTranslation },
|
|
72
|
+
formLabelAssociation: { ...defaultOptions.formLabelAssociation, ...options.formLabelAssociation },
|
|
66
73
|
i18n: { ...defaultOptions.i18n, ...options.i18n },
|
|
74
|
+
preHydrationInput: { ...defaultOptions.preHydrationInput, ...options.preHydrationInput },
|
|
67
75
|
tus: { ...defaultOptions.tus, ...options.tus }
|
|
68
76
|
};
|
|
69
77
|
const passkeyAvailable = !!await tryResolveModule("@better-auth/passkey/client", new URL(import.meta.url));
|
|
@@ -120,6 +128,14 @@ const module$1 = defineNuxtModule({
|
|
|
120
128
|
enabled: resolvedOptions.errorTranslation?.enabled ?? true,
|
|
121
129
|
defaultLocale: resolvedOptions.errorTranslation?.defaultLocale || "de"
|
|
122
130
|
},
|
|
131
|
+
formLabelAssociation: {
|
|
132
|
+
enabled: resolvedOptions.formLabelAssociation?.enabled ?? true,
|
|
133
|
+
maxRepairMs: resolvedOptions.formLabelAssociation?.maxRepairMs ?? 1500
|
|
134
|
+
},
|
|
135
|
+
preHydrationInput: {
|
|
136
|
+
enabled: resolvedOptions.preHydrationInput?.enabled ?? true,
|
|
137
|
+
maxRestoreMs: resolvedOptions.preHydrationInput?.maxRestoreMs ?? 1500
|
|
138
|
+
},
|
|
123
139
|
tus: {
|
|
124
140
|
defaultChunkSize: resolvedOptions.tus?.defaultChunkSize || 5 * 1024 * 1024,
|
|
125
141
|
defaultEndpoint: resolvedOptions.tus?.defaultEndpoint || "/files/upload"
|
|
@@ -208,6 +224,12 @@ const module$1 = defineNuxtModule({
|
|
|
208
224
|
});
|
|
209
225
|
});
|
|
210
226
|
}
|
|
227
|
+
if (resolvedOptions.preHydrationInput.enabled !== false) {
|
|
228
|
+
addPlugin(resolve("./runtime/plugins/pre-hydration-input.client"));
|
|
229
|
+
}
|
|
230
|
+
if (resolvedOptions.formLabelAssociation.enabled !== false) {
|
|
231
|
+
addPlugin(resolve("./runtime/plugins/form-label-association.client"));
|
|
232
|
+
}
|
|
211
233
|
nuxt.options.build.transpile.push(resolve("./runtime"));
|
|
212
234
|
nuxt.options.build.transpile.push("tus-js-client");
|
|
213
235
|
console.log(`[${name}] Module loaded with config:`, {
|
|
@@ -284,7 +284,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
284
284
|
requestPasswordReset: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
285
285
|
email: string;
|
|
286
286
|
redirectTo?: string | undefined;
|
|
287
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
287
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
288
288
|
email: string;
|
|
289
289
|
redirectTo?: string | undefined;
|
|
290
290
|
} & {
|
|
@@ -488,8 +488,9 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
488
488
|
scopes?: string[] | undefined;
|
|
489
489
|
requestSignUp?: boolean | undefined;
|
|
490
490
|
loginHint?: string | undefined;
|
|
491
|
+
additionalParams?: Record<string, string> | undefined;
|
|
491
492
|
additionalData?: Record<string, any> | undefined;
|
|
492
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
493
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<Omit<{
|
|
493
494
|
provider: (string & {}) | "linear" | "huggingface" | "github" | "apple" | "atlassian" | "cognito" | "discord" | "facebook" | "figma" | "microsoft" | "google" | "slack" | "spotify" | "twitch" | "twitter" | "dropbox" | "kick" | "linkedin" | "gitlab" | "tiktok" | "reddit" | "roblox" | "salesforce" | "vk" | "zoom" | "notion" | "kakao" | "naver" | "line" | "paybin" | "paypal" | "polar" | "railway" | "vercel" | "wechat";
|
|
494
495
|
callbackURL?: string | undefined;
|
|
495
496
|
newUserCallbackURL?: string | undefined;
|
|
@@ -512,9 +513,13 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
512
513
|
scopes?: string[] | undefined;
|
|
513
514
|
requestSignUp?: boolean | undefined;
|
|
514
515
|
loginHint?: string | undefined;
|
|
516
|
+
additionalParams?: Record<string, string> | undefined;
|
|
515
517
|
additionalData?: Record<string, any> | undefined;
|
|
516
518
|
} & {
|
|
517
519
|
fetchOptions?: FetchOptions | undefined;
|
|
520
|
+
}, "provider"> & {
|
|
521
|
+
provider: string | (string & {});
|
|
522
|
+
fetchOptions?: FetchOptions | undefined;
|
|
518
523
|
}>, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
519
524
|
redirect: boolean;
|
|
520
525
|
url: string;
|
|
@@ -546,11 +551,20 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
546
551
|
message?: string | undefined;
|
|
547
552
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
548
553
|
};
|
|
549
|
-
signOut: <FetchOptions extends import("better-auth").ClientFetchOption<
|
|
550
|
-
|
|
554
|
+
signOut: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
555
|
+
callbackURL?: string | undefined;
|
|
556
|
+
disableRedirect?: boolean | undefined;
|
|
557
|
+
state?: string | undefined;
|
|
558
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
559
|
+
callbackURL?: string | undefined;
|
|
560
|
+
disableRedirect?: boolean | undefined;
|
|
561
|
+
state?: string | undefined;
|
|
562
|
+
} & {
|
|
551
563
|
fetchOptions?: FetchOptions | undefined;
|
|
552
564
|
}> | undefined, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
553
565
|
success: boolean;
|
|
566
|
+
url: string | undefined;
|
|
567
|
+
redirect: boolean | undefined;
|
|
554
568
|
}, {
|
|
555
569
|
code?: string | undefined;
|
|
556
570
|
message?: string | undefined;
|
|
@@ -664,7 +678,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
664
678
|
verifyEmail: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<{
|
|
665
679
|
token: string;
|
|
666
680
|
callbackURL?: string | undefined;
|
|
667
|
-
}> & Record<string, any>, Record<string,
|
|
681
|
+
}> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
668
682
|
query: {
|
|
669
683
|
token: string;
|
|
670
684
|
callbackURL?: string | undefined;
|
|
@@ -679,7 +693,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
679
693
|
sendVerificationEmail: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
680
694
|
email: string;
|
|
681
695
|
callbackURL?: string | undefined;
|
|
682
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
696
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
683
697
|
email: string;
|
|
684
698
|
callbackURL?: string | undefined;
|
|
685
699
|
} & {
|
|
@@ -693,7 +707,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
693
707
|
changeEmail: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
694
708
|
newEmail: string;
|
|
695
709
|
callbackURL?: string | undefined;
|
|
696
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
710
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
697
711
|
newEmail: string;
|
|
698
712
|
callbackURL?: string | undefined;
|
|
699
713
|
} & {
|
|
@@ -704,7 +718,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
704
718
|
code?: string | undefined;
|
|
705
719
|
message?: string | undefined;
|
|
706
720
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
707
|
-
updateSession: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<Partial<{}>> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
721
|
+
updateSession: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<Partial<{}>> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<import("better-auth/client").InferSessionUpdateCtx<{
|
|
708
722
|
basePath: string;
|
|
709
723
|
baseURL: any;
|
|
710
724
|
fetchOptions: {
|
|
@@ -729,7 +743,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
729
743
|
updateUser: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<Partial<{}> & {
|
|
730
744
|
name?: string | undefined;
|
|
731
745
|
image?: string | undefined | null;
|
|
732
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
746
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<import("better-auth/client").InferUserUpdateCtx<{
|
|
733
747
|
basePath: string;
|
|
734
748
|
baseURL: any;
|
|
735
749
|
fetchOptions: {
|
|
@@ -746,7 +760,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
746
760
|
callbackURL?: string | undefined;
|
|
747
761
|
password?: string | undefined;
|
|
748
762
|
token?: string | undefined;
|
|
749
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
763
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
750
764
|
callbackURL?: string | undefined;
|
|
751
765
|
password?: string | undefined;
|
|
752
766
|
token?: string | undefined;
|
|
@@ -762,7 +776,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
762
776
|
callback: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<{
|
|
763
777
|
token: string;
|
|
764
778
|
callbackURL?: string | undefined;
|
|
765
|
-
}> & Record<string, any>, Record<string,
|
|
779
|
+
}> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
766
780
|
query: {
|
|
767
781
|
token: string;
|
|
768
782
|
callbackURL?: string | undefined;
|
|
@@ -776,7 +790,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
776
790
|
message?: string | undefined;
|
|
777
791
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
778
792
|
};
|
|
779
|
-
listSessions: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
793
|
+
listSessions: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
780
794
|
query?: Record<string, any> | undefined;
|
|
781
795
|
fetchOptions?: FetchOptions | undefined;
|
|
782
796
|
}> | undefined, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<import("better-auth").Prettify<{
|
|
@@ -794,7 +808,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
794
808
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
795
809
|
revokeSession: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
796
810
|
token: string;
|
|
797
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
811
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
798
812
|
token: string;
|
|
799
813
|
} & {
|
|
800
814
|
fetchOptions?: FetchOptions | undefined;
|
|
@@ -804,7 +818,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
804
818
|
code?: string | undefined;
|
|
805
819
|
message?: string | undefined;
|
|
806
820
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
807
|
-
revokeSessions: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
821
|
+
revokeSessions: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
808
822
|
query?: Record<string, any> | undefined;
|
|
809
823
|
fetchOptions?: FetchOptions | undefined;
|
|
810
824
|
}> | undefined, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
@@ -813,7 +827,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
813
827
|
code?: string | undefined;
|
|
814
828
|
message?: string | undefined;
|
|
815
829
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
816
|
-
revokeOtherSessions: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
830
|
+
revokeOtherSessions: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
817
831
|
query?: Record<string, any> | undefined;
|
|
818
832
|
fetchOptions?: FetchOptions | undefined;
|
|
819
833
|
}> | undefined, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
@@ -830,14 +844,15 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
830
844
|
nonce?: string | undefined;
|
|
831
845
|
accessToken?: string | undefined;
|
|
832
846
|
refreshToken?: string | undefined;
|
|
833
|
-
scopes?: string[] | undefined;
|
|
834
847
|
} | undefined;
|
|
835
848
|
requestSignUp?: boolean | undefined;
|
|
836
849
|
scopes?: string[] | undefined;
|
|
837
850
|
errorCallbackURL?: string | undefined;
|
|
838
851
|
disableRedirect?: boolean | undefined;
|
|
852
|
+
loginHint?: string | undefined;
|
|
853
|
+
additionalParams?: Record<string, string> | undefined;
|
|
839
854
|
additionalData?: Record<string, any> | undefined;
|
|
840
|
-
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
855
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
841
856
|
provider: unknown;
|
|
842
857
|
callbackURL?: string | undefined;
|
|
843
858
|
idToken?: {
|
|
@@ -845,12 +860,13 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
845
860
|
nonce?: string | undefined;
|
|
846
861
|
accessToken?: string | undefined;
|
|
847
862
|
refreshToken?: string | undefined;
|
|
848
|
-
scopes?: string[] | undefined;
|
|
849
863
|
} | undefined;
|
|
850
864
|
requestSignUp?: boolean | undefined;
|
|
851
865
|
scopes?: string[] | undefined;
|
|
852
866
|
errorCallbackURL?: string | undefined;
|
|
853
867
|
disableRedirect?: boolean | undefined;
|
|
868
|
+
loginHint?: string | undefined;
|
|
869
|
+
additionalParams?: Record<string, string> | undefined;
|
|
854
870
|
additionalData?: Record<string, any> | undefined;
|
|
855
871
|
} & {
|
|
856
872
|
fetchOptions?: FetchOptions | undefined;
|
|
@@ -861,7 +877,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
861
877
|
code?: string | undefined;
|
|
862
878
|
message?: string | undefined;
|
|
863
879
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
864
|
-
listAccounts: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string,
|
|
880
|
+
listAccounts: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
865
881
|
query?: Record<string, any> | undefined;
|
|
866
882
|
fetchOptions?: FetchOptions | undefined;
|
|
867
883
|
}> | undefined, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
@@ -871,17 +887,16 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
871
887
|
updatedAt: Date;
|
|
872
888
|
userId: string;
|
|
873
889
|
providerId: string;
|
|
890
|
+
issuer: string;
|
|
874
891
|
accountId: string;
|
|
875
892
|
}[], {
|
|
876
893
|
code?: string | undefined;
|
|
877
894
|
message?: string | undefined;
|
|
878
895
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
879
896
|
unlinkAccount: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
providerId: string;
|
|
884
|
-
accountId?: string | undefined;
|
|
897
|
+
accountId: string;
|
|
898
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
899
|
+
accountId: string;
|
|
885
900
|
} & {
|
|
886
901
|
fetchOptions?: FetchOptions | undefined;
|
|
887
902
|
}>, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
@@ -891,12 +906,18 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
891
906
|
message?: string | undefined;
|
|
892
907
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
893
908
|
refreshToken: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
894
|
-
|
|
895
|
-
accountId?: string | undefined;
|
|
909
|
+
accountId: string;
|
|
896
910
|
userId?: string | undefined;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
911
|
+
} | {
|
|
912
|
+
useAccountCookie: true;
|
|
913
|
+
userId?: string | undefined;
|
|
914
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
915
|
+
accountId: string;
|
|
916
|
+
userId?: string | undefined;
|
|
917
|
+
} & {
|
|
918
|
+
fetchOptions?: FetchOptions | undefined;
|
|
919
|
+
}> | import("better-auth").Prettify<{
|
|
920
|
+
useAccountCookie: true;
|
|
900
921
|
userId?: string | undefined;
|
|
901
922
|
} & {
|
|
902
923
|
fetchOptions?: FetchOptions | undefined;
|
|
@@ -914,12 +935,18 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
914
935
|
message?: string | undefined;
|
|
915
936
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
916
937
|
getAccessToken: <FetchOptions extends import("better-auth").ClientFetchOption<Partial<{
|
|
917
|
-
|
|
918
|
-
accountId?: string | undefined;
|
|
938
|
+
accountId: string;
|
|
919
939
|
userId?: string | undefined;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
940
|
+
} | {
|
|
941
|
+
useAccountCookie: true;
|
|
942
|
+
userId?: string | undefined;
|
|
943
|
+
}> & Record<string, any>, Partial<Record<string, any>> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
944
|
+
accountId: string;
|
|
945
|
+
userId?: string | undefined;
|
|
946
|
+
} & {
|
|
947
|
+
fetchOptions?: FetchOptions | undefined;
|
|
948
|
+
}> | import("better-auth").Prettify<{
|
|
949
|
+
useAccountCookie: true;
|
|
923
950
|
userId?: string | undefined;
|
|
924
951
|
} & {
|
|
925
952
|
fetchOptions?: FetchOptions | undefined;
|
|
@@ -933,19 +960,29 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
933
960
|
message?: string | undefined;
|
|
934
961
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
935
962
|
accountInfo: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<{
|
|
936
|
-
accountId
|
|
937
|
-
providerId?: string | undefined;
|
|
963
|
+
accountId: string;
|
|
938
964
|
userId?: string | undefined;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
965
|
+
} | {
|
|
966
|
+
useAccountCookie: true;
|
|
967
|
+
userId?: string | undefined;
|
|
968
|
+
}> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0: import("better-auth").Prettify<{
|
|
969
|
+
query: {
|
|
970
|
+
accountId: string;
|
|
943
971
|
userId?: string | undefined;
|
|
944
|
-
} |
|
|
972
|
+
} | {
|
|
973
|
+
useAccountCookie: true;
|
|
974
|
+
userId?: string | undefined;
|
|
975
|
+
};
|
|
945
976
|
fetchOptions?: FetchOptions | undefined;
|
|
946
|
-
}
|
|
977
|
+
}>, data_1?: FetchOptions | undefined) => Promise<import("better-auth/client").BetterFetchResponse<{
|
|
978
|
+
account: {
|
|
979
|
+
id: string;
|
|
980
|
+
providerId: string;
|
|
981
|
+
issuer: string;
|
|
982
|
+
accountId: string;
|
|
983
|
+
};
|
|
947
984
|
user: import("better-auth").OAuth2UserInfo;
|
|
948
|
-
data:
|
|
985
|
+
data: object;
|
|
949
986
|
}, {
|
|
950
987
|
code?: string | undefined;
|
|
951
988
|
message?: string | undefined;
|
|
@@ -953,7 +990,7 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
953
990
|
getSession: <FetchOptions extends import("better-auth").ClientFetchOption<never, Partial<{
|
|
954
991
|
disableCookieCache?: unknown;
|
|
955
992
|
disableRefresh?: unknown;
|
|
956
|
-
}> & Record<string, any>, Record<string,
|
|
993
|
+
}> & Record<string, any>, Record<string, string | undefined> | undefined>>(data_0?: import("better-auth").Prettify<{
|
|
957
994
|
query?: {
|
|
958
995
|
disableCookieCache?: unknown;
|
|
959
996
|
disableRefresh?: unknown;
|
|
@@ -983,6 +1020,27 @@ export declare function createLtAuthClient(config?: LtAuthClientConfig): {
|
|
|
983
1020
|
code?: string | undefined;
|
|
984
1021
|
message?: string | undefined;
|
|
985
1022
|
}, FetchOptions["throw"] extends true ? true : false>>;
|
|
1023
|
+
hydrateSession: (session: {
|
|
1024
|
+
user: import("better-auth").StripEmptyObjects<{
|
|
1025
|
+
id: string;
|
|
1026
|
+
createdAt: Date;
|
|
1027
|
+
updatedAt: Date;
|
|
1028
|
+
email: string;
|
|
1029
|
+
emailVerified: boolean;
|
|
1030
|
+
name: string;
|
|
1031
|
+
image?: string | null | undefined;
|
|
1032
|
+
}>;
|
|
1033
|
+
session: import("better-auth").StripEmptyObjects<{
|
|
1034
|
+
id: string;
|
|
1035
|
+
createdAt: Date;
|
|
1036
|
+
updatedAt: Date;
|
|
1037
|
+
userId: string;
|
|
1038
|
+
expiresAt: Date;
|
|
1039
|
+
token: string;
|
|
1040
|
+
ipAddress?: string | null | undefined;
|
|
1041
|
+
userAgent?: string | null | undefined;
|
|
1042
|
+
}>;
|
|
1043
|
+
} | null) => void;
|
|
986
1044
|
$ERROR_CODES: {
|
|
987
1045
|
USER_NOT_FOUND: {
|
|
988
1046
|
readonly code: "USER_NOT_FOUND";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair `<label for>` associations that hydration leaves pointing at nothing.
|
|
3
|
+
*
|
|
4
|
+
* ## The defect
|
|
5
|
+
*
|
|
6
|
+
* Nuxt UI's `FormField` generates ONE id with `useId()` and uses it for both halves of
|
|
7
|
+
* the association: `<Label :for="id">` and, via `provide`/`inject`, the control's `id`.
|
|
8
|
+
* One source, so the two cannot disagree — unless `useId()` itself returns different
|
|
9
|
+
* values on the server and on the client.
|
|
10
|
+
*
|
|
11
|
+
* It does. Measured on a Nuxt 4 app page (SVL, DEV-3093), against `@nuxt/ui` 4.11.x:
|
|
12
|
+
*
|
|
13
|
+
* | | label `for` | control `id` |
|
|
14
|
+
* | -- | -- | -- |
|
|
15
|
+
* | SSR payload | `v-0-4-2` | `v-0-4-2` |
|
|
16
|
+
* | after hydration | `v-0-4-2` | `v-0-1-2` |
|
|
17
|
+
*
|
|
18
|
+
* The middle segment is Vue's per-branch id prefix, which is extended at every async
|
|
19
|
+
* boundary. Server and client walked a different number of them, so the ids diverge.
|
|
20
|
+
*
|
|
21
|
+
* **Vue >= 3.5.39 did not cause this — it exposed it.** "force patch dynamic props when
|
|
22
|
+
* hydrating" (vuejs/core#9083, merged 2026-06-25) makes a hydrated element adopt its
|
|
23
|
+
* freshly computed client value, where previously the server value simply stayed.
|
|
24
|
+
*
|
|
25
|
+
* Note WHY only the label is left behind, because it points at the real fix: the control's
|
|
26
|
+
* `id` is a compiled element binding (`<input :id="id">`), so it is in the element's
|
|
27
|
+
* `dynamicProps` and gets force-patched. The label's `for` is a PROP on reka-ui's `Label`
|
|
28
|
+
* component and arrives at the `<label>` through `$attrs`, which is not part of that set.
|
|
29
|
+
* The asymmetry is the component boundary, not the id generation. Fixing the divergence
|
|
30
|
+
* upstream — or having `FormField` pass an SSR-stable id — removes the need for this file.
|
|
31
|
+
*
|
|
32
|
+
* ## Why it is worth a plugin
|
|
33
|
+
*
|
|
34
|
+
* A control whose label does not resolve loses its programmatic label: the visible text is
|
|
35
|
+
* no longer its accessible name (WCAG 1.3.1). Where the field has no placeholder it has no
|
|
36
|
+
* accessible name at all and a screen reader announces "edit text, blank" (4.1.2). Where it
|
|
37
|
+
* has one, the placeholder becomes the name instead — which is arguably worse, because the
|
|
38
|
+
* name no longer contains the visible label and speech input stops working (2.5.3 Label in
|
|
39
|
+
* Name). Clicking the label also no longer focuses the control, and for a checkbox or radio
|
|
40
|
+
* that is not a convenience but the primary hit target.
|
|
41
|
+
*
|
|
42
|
+
* And any test that addresses fields the way assistive technology does
|
|
43
|
+
* (`getByRole('textbox', { name })`, the recommended Playwright locator) stops finding
|
|
44
|
+
* them, which is how this surfaced: 39 specs at once.
|
|
45
|
+
*
|
|
46
|
+
* ## What this does, and what it deliberately does not
|
|
47
|
+
*
|
|
48
|
+
* After mount — and for a bounded window afterwards — every Nuxt UI field label whose
|
|
49
|
+
* target does not resolve is re-pointed at the control inside its own field.
|
|
50
|
+
*
|
|
51
|
+
* It **refuses to guess**, which is the rule that matters most here. A repair that fires
|
|
52
|
+
* where it should not is worse than the defect: a dangling label is inert and visible, a
|
|
53
|
+
* mis-pointed one is confidently wrong and silent, and on a radio or checkbox a click on
|
|
54
|
+
* the caption would then change a value the user never chose. So the repair only happens
|
|
55
|
+
* when the field contains exactly ONE eligible control. A group of radios or checkboxes
|
|
56
|
+
* shares one field root and is therefore skipped, not guessed at.
|
|
57
|
+
*
|
|
58
|
+
* `for` is repaired rather than `aria-labelledby` added, because `for` carries BOTH the
|
|
59
|
+
* accessible name and click-to-focus. Adding `aria-labelledby` would restore the name
|
|
60
|
+
* and leave the label dead to the mouse.
|
|
61
|
+
*
|
|
62
|
+
* This is a repair, not a cure: the right fix is for the id not to diverge in the first
|
|
63
|
+
* place (see the component-boundary note above). It is applied here because the divergence
|
|
64
|
+
* sits in the framework stack rather than in any one application, and because a nameless
|
|
65
|
+
* form is not something to leave in place while that is resolved upstream.
|
|
66
|
+
*
|
|
67
|
+
* Default on; opt out with `ltExtensions.formLabelAssociation.enabled: false`.
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* Re-point every dangling field label at the control in its own field.
|
|
71
|
+
*
|
|
72
|
+
* Exported for the unit test: it runs against a DOM fixture rather than a live app, so the
|
|
73
|
+
* pairing rules stay verifiable without a browser. Not part of the package's public API.
|
|
74
|
+
*
|
|
75
|
+
* @param root Where to look. Ids are resolved against the owning document regardless, since
|
|
76
|
+
* `id` is document-scoped — so a scoped root narrows which labels are visited, not which
|
|
77
|
+
* ids count as resolvable.
|
|
78
|
+
* @returns how many labels were repaired.
|
|
79
|
+
*/
|
|
80
|
+
export declare function repairDanglingLabels(root: Document | ParentNode): number;
|
|
81
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
82
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig } from "#imports";
|
|
2
|
+
const COMPONENT_ROOT = '[data-slot="root"]';
|
|
3
|
+
const FIELD_LABEL = 'label[data-slot="label"][for]';
|
|
4
|
+
const CONTROL = "input, textarea, select";
|
|
5
|
+
const NOT_A_USER_CONTROL = '[type="hidden"], [hidden], [aria-hidden="true"], [disabled]';
|
|
6
|
+
function findControlForLabel(field) {
|
|
7
|
+
const eligible = [...field.querySelectorAll(CONTROL)].filter((control) => !control.matches(NOT_A_USER_CONTROL));
|
|
8
|
+
return eligible.length === 1 && eligible[0].id !== "" ? eligible[0] : null;
|
|
9
|
+
}
|
|
10
|
+
export function repairDanglingLabels(root) {
|
|
11
|
+
let repaired = 0;
|
|
12
|
+
for (const label of root.querySelectorAll(FIELD_LABEL)) {
|
|
13
|
+
const field = label.closest(COMPONENT_ROOT);
|
|
14
|
+
if (field === null) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const target = label.ownerDocument.getElementById(label.htmlFor);
|
|
18
|
+
if (target !== null && field.contains(target)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const control = findControlForLabel(field);
|
|
22
|
+
if (control === null || control.id === label.htmlFor) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
label.htmlFor = control.id;
|
|
26
|
+
repaired += 1;
|
|
27
|
+
}
|
|
28
|
+
return repaired;
|
|
29
|
+
}
|
|
30
|
+
export default defineNuxtPlugin({
|
|
31
|
+
// After the app's own plugins, so a page that assigns ids itself has already run.
|
|
32
|
+
enforce: "post",
|
|
33
|
+
name: "lt-form-label-association",
|
|
34
|
+
setup(nuxtApp) {
|
|
35
|
+
const options = useRuntimeConfig().public.ltExtensions?.formLabelAssociation;
|
|
36
|
+
if (options?.enabled === false) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const maxRepairMs = options?.maxRepairMs ?? 1500;
|
|
40
|
+
let sweeping = null;
|
|
41
|
+
const sweep = () => {
|
|
42
|
+
const repaired = repairDanglingLabels(document);
|
|
43
|
+
if (repaired > 0 && import.meta.dev) {
|
|
44
|
+
console.warn(
|
|
45
|
+
`[@lenne.tech/nuxt-extensions] repaired ${repaired} form label association(s) broken by an SSR/client useId() divergence. This is a stopgap \u2014 see runtime/plugins/form-label-association.client.ts.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const sweepFor = () => {
|
|
50
|
+
sweep();
|
|
51
|
+
if (maxRepairMs <= 0 || sweeping !== null) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
sweeping = setInterval(sweep, 250);
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
if (sweeping !== null) {
|
|
57
|
+
clearInterval(sweeping);
|
|
58
|
+
sweeping = null;
|
|
59
|
+
}
|
|
60
|
+
}, maxRepairMs);
|
|
61
|
+
};
|
|
62
|
+
nuxtApp.hook("app:mounted", sweepFor);
|
|
63
|
+
nuxtApp.hook("page:finish", () => {
|
|
64
|
+
requestAnimationFrame(sweepFor);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-hydration input preservation — the part Vue does not do yet.
|
|
3
|
+
*
|
|
4
|
+
* ## What Vue already handles
|
|
5
|
+
*
|
|
6
|
+
* Until Vue hydrates a server-rendered `<input>`, the element carries no framework listener.
|
|
7
|
+
* What the user types is written to the DOM node, the `input` event reaches no model, and
|
|
8
|
+
* `v-model`'s mounted hook then writes the model value back over it — the entry is ERASED,
|
|
9
|
+
* not delayed.
|
|
10
|
+
*
|
|
11
|
+
* Vue fixed this in `vModelText` (vuejs/core#14411, shipped in 3.5.41): `created` records
|
|
12
|
+
* `el.defaultValue`, and `mounted` compares the live value against it. When they differ, the
|
|
13
|
+
* DOM value is pushed INTO the model instead of the node being overwritten.
|
|
14
|
+
*
|
|
15
|
+
* That adoption is gated on `el.type === 'text' || el.type === 'textarea'`. **Every other
|
|
16
|
+
* input type still falls through to the erasure** — `email`, `password`, `tel`, `url`,
|
|
17
|
+
* `search`, `number`. Verified against Vue 3.5.41 in `test/pre-hydration-input.test.ts`.
|
|
18
|
+
*
|
|
19
|
+
* Widening the gate is tracked upstream as vuejs/core#15210 (open, `p2-edge-case`, no
|
|
20
|
+
* milestone, PR #15211 pending). **This plugin is a stopgap for exactly that gap and should
|
|
21
|
+
* be deleted once the upstream fix lands** — the test above will start failing on the types
|
|
22
|
+
* Vue has taken over, which is the signal to remove it.
|
|
23
|
+
*
|
|
24
|
+
* ## Why this matters more than the gate suggests
|
|
25
|
+
*
|
|
26
|
+
* The uncovered types are precisely the ones a sign-in form uses. That is also where people
|
|
27
|
+
* type on sight, and where losing the entry is most expensive: a password silently truncated
|
|
28
|
+
* to its last few characters produces "invalid credentials", which sends the user down the
|
|
29
|
+
* wrong diagnostic path entirely.
|
|
30
|
+
*
|
|
31
|
+
* ## How it works
|
|
32
|
+
*
|
|
33
|
+
* At `app:beforeMount` the client bundle has loaded but hydration has not run, so every
|
|
34
|
+
* server-rendered field still holds whatever the user typed. A field is treated as edited
|
|
35
|
+
* when its live value differs from `defaultValue` — the same test Vue itself uses, so the
|
|
36
|
+
* server's own pre-filled values are never mistaken for user input.
|
|
37
|
+
*
|
|
38
|
+
* At `app:mounted` any field whose value was overwritten gets it back, followed by a
|
|
39
|
+
* synthetic `input` event so the framework's own listener adopts it. Setting `.value` alone
|
|
40
|
+
* would not do: the model would still hold the old value and the next render would wipe the
|
|
41
|
+
* node again.
|
|
42
|
+
*
|
|
43
|
+
* A browser autofill that landed before hydration is recovered the same way.
|
|
44
|
+
*
|
|
45
|
+
* ## Why not make the field `readonly` until mounted
|
|
46
|
+
*
|
|
47
|
+
* Declining the keystroke was the first approach tried and it is worse. The user is left with
|
|
48
|
+
* a field that looks usable and silently refuses, which reads as a broken page. It also costs
|
|
49
|
+
* real things: `readonly` is the documented technique for SUPPRESSING browser autofill,
|
|
50
|
+
* screen readers announce "read only" and never announce the silent flip back, and mobile
|
|
51
|
+
* browsers do not raise the on-screen keyboard for it. Preserving the entry avoids all of
|
|
52
|
+
* that, because the field is never anything other than a normal, editable field.
|
|
53
|
+
*/
|
|
54
|
+
type EditableField = HTMLInputElement | HTMLTextAreaElement;
|
|
55
|
+
/**
|
|
56
|
+
* Collect fields the user edited before hydration.
|
|
57
|
+
*
|
|
58
|
+
* `value !== defaultValue` is the discriminator, matching Vue's own: `defaultValue` is what
|
|
59
|
+
* the server rendered, so a difference can only have come from the user (or from an autofill,
|
|
60
|
+
* which is just as worth keeping). Server-supplied values therefore never enter the snapshot
|
|
61
|
+
* and can never be restored over a legitimate change made during hydration.
|
|
62
|
+
*/
|
|
63
|
+
export declare function collectEditedFields(root: ParentNode): Map<EditableField, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Write a preserved value back onto a field and tell the framework about it.
|
|
66
|
+
*
|
|
67
|
+
* @returns `true` when a value was actually restored.
|
|
68
|
+
*/
|
|
69
|
+
export declare function restoreField(field: EditableField, value: string): boolean;
|
|
70
|
+
declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
|
|
71
|
+
export default _default;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig } from "#imports";
|
|
2
|
+
export function collectEditedFields(root) {
|
|
3
|
+
const edited = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const node of root.querySelectorAll("input, textarea")) {
|
|
5
|
+
const field = node;
|
|
6
|
+
if (field.value !== field.defaultValue) {
|
|
7
|
+
edited.set(field, field.value);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return edited;
|
|
11
|
+
}
|
|
12
|
+
export function restoreField(field, value) {
|
|
13
|
+
if (!field.isConnected || field.value === value) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
field.value = value;
|
|
17
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
export default defineNuxtPlugin({
|
|
21
|
+
// After the app's own plugins: the restore has to land once hydration has done its writes.
|
|
22
|
+
enforce: "post",
|
|
23
|
+
name: "lt-pre-hydration-input",
|
|
24
|
+
setup(nuxtApp) {
|
|
25
|
+
const options = useRuntimeConfig().public.ltExtensions?.preHydrationInput;
|
|
26
|
+
if (options?.enabled === false) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const maxRestoreMs = options?.maxRestoreMs ?? 1500;
|
|
30
|
+
let edited = /* @__PURE__ */ new Map();
|
|
31
|
+
nuxtApp.hook("app:beforeMount", () => {
|
|
32
|
+
edited = collectEditedFields(document);
|
|
33
|
+
});
|
|
34
|
+
nuxtApp.hook("app:mounted", () => {
|
|
35
|
+
if (edited.size === 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const sweep = () => {
|
|
39
|
+
for (const [field, value] of edited) {
|
|
40
|
+
if (restoreField(field, value)) {
|
|
41
|
+
edited.delete(field);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
sweep();
|
|
46
|
+
if (maxRestoreMs <= 0) {
|
|
47
|
+
edited = /* @__PURE__ */ new Map();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const interval = setInterval(sweep, 250);
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
clearInterval(interval);
|
|
53
|
+
edited = /* @__PURE__ */ new Map();
|
|
54
|
+
}, maxRestoreMs);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { LtAuthClientConfig, LtAuthMode, LtAuthResponse, LtAuthState, LtPasskeyAuthResult, LtPasskeyRegisterResult, LtUser, UseLtAuthReturn } from './auth.js';
|
|
2
2
|
export type { LtFileInfo, LtUploadItem, LtUploadOptions, LtUploadProgress, LtUploadStatus, UseLtFileReturn, UseLtTusUploadReturn } from './upload.js';
|
|
3
|
-
export type { LtAiModuleOptions, LtAuthCookieNamesOptions, LtAuthModuleOptions, LtErrorTranslationModuleOptions, LtExtensionsModuleOptions, LtExtensionsPublicRuntimeConfig, LtI18nModuleOptions, LtSystemSetupModuleOptions, LtTusModuleOptions, } from './module.js';
|
|
3
|
+
export type { LtAiModuleOptions, LtAuthCookieNamesOptions, LtAuthModuleOptions, LtErrorTranslationModuleOptions, LtExtensionsModuleOptions, LtExtensionsPublicRuntimeConfig, LtFormLabelAssociationOptions, LtI18nModuleOptions, LtPreHydrationInputOptions, LtSystemSetupModuleOptions, LtTusModuleOptions, } from './module.js';
|
|
4
4
|
export type { LtErrorTranslationResponse, LtParsedError, UseLtErrorTranslationReturn } from './error.js';
|
|
5
5
|
export type { LtAiAction, LtAiAvailableConnection, LtAiBudgetLimit, LtAiBudgetSummary, LtAiConnection, LtAiConnectionInput, LtAiConnectionPreference, LtAiEffectiveSlot, LtAiInteraction, LtAiMessage, LtAiMode, LtAiPlaceholder, LtAiPrompt, LtAiPromptHint, LtAiPromptHintInput, LtAiPromptInput, LtAiPromptRunInput, LtAiResponse, LtAiSlot, LtAiSlotInput, LtAiStreamEvent, LtAiStreamHandlers, LtAiUsage, LtAiUsageInfo, LtAiUsageScope, UseLtAiAdminReturn, UseLtAiChatOptions, UseLtAiChatReturn, UseLtAiConnectionsReturn, UseLtAiPlaceholdersReturn, UseLtAiPromptsReturn, UseLtAiReturn, UseLtAiUsageReturn, } from './ai.js';
|
|
@@ -127,11 +127,116 @@ export interface LtExtensionsModuleOptions {
|
|
|
127
127
|
auth?: LtAuthModuleOptions;
|
|
128
128
|
/** Error translation configuration */
|
|
129
129
|
errorTranslation?: LtErrorTranslationModuleOptions;
|
|
130
|
+
/** Form label association repair configuration */
|
|
131
|
+
formLabelAssociation?: LtFormLabelAssociationOptions;
|
|
130
132
|
/** i18n configuration */
|
|
131
133
|
i18n?: LtI18nModuleOptions;
|
|
134
|
+
/** Pre-hydration input preservation configuration */
|
|
135
|
+
preHydrationInput?: LtPreHydrationInputOptions;
|
|
132
136
|
/** TUS upload module configuration */
|
|
133
137
|
tus?: LtTusModuleOptions;
|
|
134
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Repair of `<label for>` associations broken by an SSR/client `useId()` divergence.
|
|
141
|
+
*
|
|
142
|
+
* Nuxt UI's `FormField` derives the label's `for` and the control's `id` from ONE `useId()`
|
|
143
|
+
* call, so they cannot disagree — unless `useId()` itself returns different values on server
|
|
144
|
+
* and client, which it does whenever the two walk a different number of async boundaries.
|
|
145
|
+
* The label then keeps the server value while the control adopts the client one.
|
|
146
|
+
*
|
|
147
|
+
* The control loses its programmatic label. Without a placeholder it has no accessible name
|
|
148
|
+
* at all and a screen reader announces "edit text, blank"; with one, the placeholder becomes
|
|
149
|
+
* the name instead, so the visible label is no longer part of it and speech input stops
|
|
150
|
+
* working. Either way clicking the label focuses nothing — which on a checkbox or radio is
|
|
151
|
+
* the primary hit target, not a convenience.
|
|
152
|
+
*
|
|
153
|
+
* Vue >= 3.5.39 did not cause this, it exposed it: vuejs/core#9083 force-patches an element's
|
|
154
|
+
* dynamic props during hydration, and the control's `id` is one while the label's `for` —
|
|
155
|
+
* passed through reka-ui's `Label` component and `$attrs` — is not.
|
|
156
|
+
*
|
|
157
|
+
* The repair refuses to guess: it only fires when the field contains exactly one eligible
|
|
158
|
+
* control, so a radio or checkbox group is skipped rather than having every caption bound to
|
|
159
|
+
* its first option.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* export default defineNuxtConfig({
|
|
164
|
+
* ltExtensions: {
|
|
165
|
+
* formLabelAssociation: { enabled: false },
|
|
166
|
+
* },
|
|
167
|
+
* });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export interface LtFormLabelAssociationOptions {
|
|
171
|
+
/**
|
|
172
|
+
* Enable the repair. Defaults to `true`.
|
|
173
|
+
*
|
|
174
|
+
* Turn it off only with a reason. Two real ones: an application that assigns `for` itself
|
|
175
|
+
* and depends on those exact values, or a test suite asserting on literal `for` / `id`
|
|
176
|
+
* strings rather than on the accessible name. The alternative is a form whose fields carry
|
|
177
|
+
* no programmatic label.
|
|
178
|
+
*/
|
|
179
|
+
enabled?: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* How long after mount to keep repairing, in milliseconds. Defaults to `1500`; `0` sweeps
|
|
182
|
+
* once and stops.
|
|
183
|
+
*
|
|
184
|
+
* Most fields are repaired in the first pass. The window exists for server-rendered
|
|
185
|
+
* subtrees whose hydration is deferred — `hydrate-on-visible`, an unresolved `<Suspense>`,
|
|
186
|
+
* an island — which carry the same divergence but hydrate after `app:mounted`.
|
|
187
|
+
*/
|
|
188
|
+
maxRepairMs?: number;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Pre-hydration input preservation.
|
|
192
|
+
*
|
|
193
|
+
* Until Vue hydrates a server-rendered `<input>`, the element carries no framework listener:
|
|
194
|
+
* text typed into it reaches no model and is then overwritten by the mounting value — ERASED,
|
|
195
|
+
* not delayed.
|
|
196
|
+
*
|
|
197
|
+
* Vue fixed this in 3.5.41 (vuejs/core#14411), but only for `type="text"` and `textarea`.
|
|
198
|
+
* Every other type still loses the entry: `email`, `password`, `tel`, `url`, `search`,
|
|
199
|
+
* `number` — precisely the fields a sign-in form uses, and where people are most likely to
|
|
200
|
+
* type on sight. Widening it is tracked upstream as vuejs/core#15210.
|
|
201
|
+
*
|
|
202
|
+
* This closes that remaining gap: what the user typed is read out of the DOM just before
|
|
203
|
+
* hydration and written back afterwards, with a synthetic `input` event so `v-model` adopts
|
|
204
|
+
* it. Fields stay ordinary editable fields throughout — nothing is made `readonly`, so
|
|
205
|
+
* autofill, screen-reader semantics and the mobile keyboard are untouched. A browser autofill
|
|
206
|
+
* that lands before hydration is recovered the same way.
|
|
207
|
+
*
|
|
208
|
+
* Applies to every `<input>` and `<textarea>` on the page, regardless of which UI library
|
|
209
|
+
* rendered it. It is a stopgap and is meant to be removed once Vue covers the remaining
|
|
210
|
+
* types.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* export default defineNuxtConfig({
|
|
215
|
+
* ltExtensions: {
|
|
216
|
+
* preHydrationInput: { enabled: false },
|
|
217
|
+
* },
|
|
218
|
+
* });
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export interface LtPreHydrationInputOptions {
|
|
222
|
+
/**
|
|
223
|
+
* Enable preservation. Defaults to `true`.
|
|
224
|
+
*
|
|
225
|
+
* Turn it off only with a reason: the alternative is that a user who types immediately
|
|
226
|
+
* after a page appears loses the entry silently.
|
|
227
|
+
*/
|
|
228
|
+
enabled?: boolean;
|
|
229
|
+
/**
|
|
230
|
+
* How long after mount to keep restoring, in milliseconds. Defaults to `1500`; `0` restores
|
|
231
|
+
* once and stops immediately.
|
|
232
|
+
*
|
|
233
|
+
* Most fields are repaired in the first pass. The window exists for subtrees whose mount is
|
|
234
|
+
* deferred — an unresolved `<Suspense>` boundary writes its model value later than
|
|
235
|
+
* `app:mounted`. When it elapses the snapshot is dropped, so preserved values, passwords
|
|
236
|
+
* included, are not kept alive for the session.
|
|
237
|
+
*/
|
|
238
|
+
maxRestoreMs?: number;
|
|
239
|
+
}
|
|
135
240
|
/**
|
|
136
241
|
* Public runtime config added by this module
|
|
137
242
|
*/
|
|
@@ -167,6 +272,14 @@ export interface LtExtensionsPublicRuntimeConfig {
|
|
|
167
272
|
enabled: boolean;
|
|
168
273
|
defaultLocale: string;
|
|
169
274
|
};
|
|
275
|
+
formLabelAssociation: {
|
|
276
|
+
enabled: boolean;
|
|
277
|
+
maxRepairMs: number;
|
|
278
|
+
};
|
|
279
|
+
preHydrationInput: {
|
|
280
|
+
enabled: boolean;
|
|
281
|
+
maxRestoreMs: number;
|
|
282
|
+
};
|
|
170
283
|
tus: {
|
|
171
284
|
defaultChunkSize: number;
|
|
172
285
|
defaultEndpoint: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nuxt-extensions",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Reusable Nuxt 4 composables, components, and Better-Auth integration for lenne.tech projects",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -99,21 +99,22 @@
|
|
|
99
99
|
}
|
|
100
100
|
},
|
|
101
101
|
"devDependencies": {
|
|
102
|
-
"@better-auth/passkey": "1.
|
|
103
|
-
"@nuxt/module-builder": "1.0.
|
|
102
|
+
"@better-auth/passkey": "1.7.1",
|
|
103
|
+
"@nuxt/module-builder": "1.0.3",
|
|
104
104
|
"@nuxt/schema": "4.5.2",
|
|
105
|
-
"@playwright/test": "1.
|
|
106
|
-
"@types/node": "26.
|
|
107
|
-
"@vitest/coverage-v8": "4.1.
|
|
108
|
-
"
|
|
109
|
-
"
|
|
105
|
+
"@playwright/test": "1.62.1",
|
|
106
|
+
"@types/node": "26.2.0",
|
|
107
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
108
|
+
"@vue/server-renderer": "3.5.41",
|
|
109
|
+
"better-auth": "1.7.1",
|
|
110
|
+
"happy-dom": "20.11.6",
|
|
110
111
|
"nuxt": "4.5.2",
|
|
111
|
-
"oxfmt": "0.
|
|
112
|
-
"oxlint": "1.
|
|
112
|
+
"oxfmt": "0.64.0",
|
|
113
|
+
"oxlint": "1.79.0",
|
|
113
114
|
"tus-js-client": "4.3.1",
|
|
114
115
|
"typescript": "5.9.3",
|
|
115
|
-
"vitest": "4.1.
|
|
116
|
-
"vue-tsc": "3.3.
|
|
116
|
+
"vitest": "4.1.11",
|
|
117
|
+
"vue-tsc": "3.3.11"
|
|
117
118
|
},
|
|
118
119
|
"keywords": [
|
|
119
120
|
"nuxt",
|