@lenne.tech/nuxt-extensions 1.11.2 → 1.12.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 +33 -0
- package/README.md +65 -0
- package/dist/module.d.mts +1 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +12 -1
- package/dist/runtime/lib/auth-client.d.ts +102 -44
- 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 +56 -0
- package/package.json +13 -12
package/CLAUDE.md
CHANGED
|
@@ -76,6 +76,10 @@ export default defineNuxtConfig({
|
|
|
76
76
|
enabled: true,
|
|
77
77
|
basePath: '/ai', // Must match the nest-server AI controller
|
|
78
78
|
},
|
|
79
|
+
preHydrationInput: {
|
|
80
|
+
enabled: true, // keep text typed before hydration instead of losing it
|
|
81
|
+
maxRestoreMs: 1500, // how long to keep repairing deferred subtrees
|
|
82
|
+
},
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
85
|
```
|
|
@@ -171,6 +175,35 @@ export default defineNuxtRouteMiddleware((to) => {
|
|
|
171
175
|
});
|
|
172
176
|
```
|
|
173
177
|
|
|
178
|
+
## Pre-Hydration Input Preservation
|
|
179
|
+
|
|
180
|
+
**The defect:** until Vue hydrates a server-rendered `<input>`, it carries no framework
|
|
181
|
+
listener. Text typed in that window goes into the DOM node, the `input` event reaches nothing,
|
|
182
|
+
and `v-model`'s mounted hook then overwrites it. The entry is **erased, not delayed**. It is
|
|
183
|
+
load-dependent, so it hides in development.
|
|
184
|
+
|
|
185
|
+
**Vue already fixes most of it.** Since 3.5.41 (vuejs/core#14411) Vue adopts the typed value
|
|
186
|
+
into the model on hydration — but only for `type="text"` and `textarea`. `email`, `password`,
|
|
187
|
+
`tel`, `url`, `search` and `number` are still erased. Upstream tracking: vuejs/core#15210.
|
|
188
|
+
|
|
189
|
+
**What this module adds:** `runtime/plugins/pre-hydration-input.client.ts` snapshots edited
|
|
190
|
+
fields at `app:beforeMount` and restores them at `app:mounted`, dispatching a synthetic
|
|
191
|
+
`input` event so `v-model` adopts the value.
|
|
192
|
+
|
|
193
|
+
**What this means when you debug a consuming project:**
|
|
194
|
+
|
|
195
|
+
- **Do not add a `readonly`-until-mounted guard to inputs.** It was tried and removed: it makes
|
|
196
|
+
the field look usable while silently refusing, suppresses browser autofill (`readonly` is the
|
|
197
|
+
documented technique for exactly that), makes screen readers announce "read only" with no
|
|
198
|
+
announcement when it flips back, and stops mobile browsers raising the on-screen keyboard.
|
|
199
|
+
- A field is treated as edited when `value !== defaultValue`, so server-pre-filled values are
|
|
200
|
+
never restored over a legitimate change made during hydration.
|
|
201
|
+
- Each field is restored at most once; a later programmatic clear is not undone.
|
|
202
|
+
- This is a **stopgap**. When Vue widens its gate, delete the plugin —
|
|
203
|
+
`test/pre-hydration-input.test.ts` pins the current behaviour and will fail on the types Vue
|
|
204
|
+
takes over.
|
|
205
|
+
- Opt out with `ltExtensions.preHydrationInput.enabled: false`.
|
|
206
|
+
|
|
174
207
|
## Development Rules
|
|
175
208
|
|
|
176
209
|
1. **ALWAYS read source code** in `dist/runtime/` to understand available composables and components
|
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ 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
|
|
44
45
|
|
|
45
46
|
## Environment Variables
|
|
46
47
|
|
|
@@ -156,6 +157,70 @@ export default defineNuxtConfig({
|
|
|
156
157
|
});
|
|
157
158
|
```
|
|
158
159
|
|
|
160
|
+
## Pre-Hydration Input Preservation
|
|
161
|
+
|
|
162
|
+
Text typed before a page has hydrated is kept instead of being silently thrown away.
|
|
163
|
+
|
|
164
|
+
### The problem
|
|
165
|
+
|
|
166
|
+
Until Vue hydrates a server-rendered `<input>`, the element carries **no framework listener**.
|
|
167
|
+
Text typed in that window is written to the DOM node, the `input` event reaches nothing, and
|
|
168
|
+
`v-model`'s mounted hook then writes the model value back over it. The entry is **erased, not
|
|
169
|
+
delayed** — no error, no toast, nothing persisted.
|
|
170
|
+
|
|
171
|
+
It is load-dependent, so it hides in development and shows up on a cold cache, a slow device
|
|
172
|
+
or a throttled CPU — most often on the login screen, where people type on sight.
|
|
173
|
+
|
|
174
|
+
### What Vue already does, and where it stops
|
|
175
|
+
|
|
176
|
+
Vue fixed this in **3.5.41** ([vuejs/core#14411](https://github.com/vuejs/core/pull/14411)):
|
|
177
|
+
on hydration it compares the field's live value against what the server rendered and, when
|
|
178
|
+
they differ, adopts the typed value into the model instead of overwriting the node.
|
|
179
|
+
|
|
180
|
+
That adoption is gated on `type="text"` and `textarea`. **Every other type still loses the
|
|
181
|
+
entry** — and those are exactly the fields a sign-in form uses:
|
|
182
|
+
|
|
183
|
+
| Input type | Typed before hydration |
|
|
184
|
+
|------------|------------------------|
|
|
185
|
+
| `text`, `textarea` | kept by Vue itself |
|
|
186
|
+
| `email`, `password`, `tel`, `url`, `search`, `number` | **erased** |
|
|
187
|
+
|
|
188
|
+
Widening the gate is tracked upstream as
|
|
189
|
+
[vuejs/core#15210](https://github.com/vuejs/core/issues/15210) — open, labelled
|
|
190
|
+
`p2-edge-case`, no milestone.
|
|
191
|
+
|
|
192
|
+
### What this module adds
|
|
193
|
+
|
|
194
|
+
A small client plugin that closes the remaining gap. Just before hydration it reads what is
|
|
195
|
+
in each field; just after, it writes back anything that was overwritten and dispatches a
|
|
196
|
+
synthetic `input` event so `v-model` adopts it.
|
|
197
|
+
|
|
198
|
+
A field counts as edited when its value differs from `defaultValue` — the same test Vue uses,
|
|
199
|
+
so values the server pre-filled are never mistaken for user input. A browser autofill that
|
|
200
|
+
lands before hydration is recovered the same way.
|
|
201
|
+
|
|
202
|
+
Fields stay ordinary editable fields throughout. Nothing is made `readonly`, so autofill,
|
|
203
|
+
screen-reader semantics and the mobile on-screen keyboard are untouched, and the user never
|
|
204
|
+
faces a field that looks usable and silently refuses.
|
|
205
|
+
|
|
206
|
+
**This is a stopgap.** When Vue covers the remaining types, delete it —
|
|
207
|
+
`test/pre-hydration-input.test.ts` pins the current gate and will fail on the types Vue takes
|
|
208
|
+
over, which is the signal.
|
|
209
|
+
|
|
210
|
+
### Opting out
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
export default defineNuxtConfig({
|
|
214
|
+
ltExtensions: {
|
|
215
|
+
preHydrationInput: {
|
|
216
|
+
enabled: false,
|
|
217
|
+
// or keep it on and only change how long deferred subtrees are waited for:
|
|
218
|
+
// maxRestoreMs: 3000,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
159
224
|
## Usage
|
|
160
225
|
|
|
161
226
|
### 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.12.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.12.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";
|
|
@@ -39,6 +39,9 @@ const defaultOptions = {
|
|
|
39
39
|
i18n: {
|
|
40
40
|
autoMerge: true
|
|
41
41
|
},
|
|
42
|
+
preHydrationInput: {
|
|
43
|
+
enabled: true
|
|
44
|
+
},
|
|
42
45
|
tus: {
|
|
43
46
|
defaultChunkSize: 5 * 1024 * 1024,
|
|
44
47
|
// 5MB
|
|
@@ -64,6 +67,7 @@ const module$1 = defineNuxtModule({
|
|
|
64
67
|
},
|
|
65
68
|
errorTranslation: { ...defaultOptions.errorTranslation, ...options.errorTranslation },
|
|
66
69
|
i18n: { ...defaultOptions.i18n, ...options.i18n },
|
|
70
|
+
preHydrationInput: { ...defaultOptions.preHydrationInput, ...options.preHydrationInput },
|
|
67
71
|
tus: { ...defaultOptions.tus, ...options.tus }
|
|
68
72
|
};
|
|
69
73
|
const passkeyAvailable = !!await tryResolveModule("@better-auth/passkey/client", new URL(import.meta.url));
|
|
@@ -120,6 +124,10 @@ const module$1 = defineNuxtModule({
|
|
|
120
124
|
enabled: resolvedOptions.errorTranslation?.enabled ?? true,
|
|
121
125
|
defaultLocale: resolvedOptions.errorTranslation?.defaultLocale || "de"
|
|
122
126
|
},
|
|
127
|
+
preHydrationInput: {
|
|
128
|
+
enabled: resolvedOptions.preHydrationInput?.enabled ?? true,
|
|
129
|
+
maxRestoreMs: resolvedOptions.preHydrationInput?.maxRestoreMs ?? 1500
|
|
130
|
+
},
|
|
123
131
|
tus: {
|
|
124
132
|
defaultChunkSize: resolvedOptions.tus?.defaultChunkSize || 5 * 1024 * 1024,
|
|
125
133
|
defaultEndpoint: resolvedOptions.tus?.defaultEndpoint || "/files/upload"
|
|
@@ -208,6 +216,9 @@ const module$1 = defineNuxtModule({
|
|
|
208
216
|
});
|
|
209
217
|
});
|
|
210
218
|
}
|
|
219
|
+
if (resolvedOptions.preHydrationInput.enabled !== false) {
|
|
220
|
+
addPlugin(resolve("./runtime/plugins/pre-hydration-input.client"));
|
|
221
|
+
}
|
|
211
222
|
nuxt.options.build.transpile.push(resolve("./runtime"));
|
|
212
223
|
nuxt.options.build.transpile.push("tus-js-client");
|
|
213
224
|
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,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, 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';
|
|
@@ -129,9 +129,61 @@ export interface LtExtensionsModuleOptions {
|
|
|
129
129
|
errorTranslation?: LtErrorTranslationModuleOptions;
|
|
130
130
|
/** i18n configuration */
|
|
131
131
|
i18n?: LtI18nModuleOptions;
|
|
132
|
+
/** Pre-hydration input preservation configuration */
|
|
133
|
+
preHydrationInput?: LtPreHydrationInputOptions;
|
|
132
134
|
/** TUS upload module configuration */
|
|
133
135
|
tus?: LtTusModuleOptions;
|
|
134
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Pre-hydration input preservation.
|
|
139
|
+
*
|
|
140
|
+
* Until Vue hydrates a server-rendered `<input>`, the element carries no framework listener:
|
|
141
|
+
* text typed into it reaches no model and is then overwritten by the mounting value — ERASED,
|
|
142
|
+
* not delayed.
|
|
143
|
+
*
|
|
144
|
+
* Vue fixed this in 3.5.41 (vuejs/core#14411), but only for `type="text"` and `textarea`.
|
|
145
|
+
* Every other type still loses the entry: `email`, `password`, `tel`, `url`, `search`,
|
|
146
|
+
* `number` — precisely the fields a sign-in form uses, and where people are most likely to
|
|
147
|
+
* type on sight. Widening it is tracked upstream as vuejs/core#15210.
|
|
148
|
+
*
|
|
149
|
+
* This closes that remaining gap: what the user typed is read out of the DOM just before
|
|
150
|
+
* hydration and written back afterwards, with a synthetic `input` event so `v-model` adopts
|
|
151
|
+
* it. Fields stay ordinary editable fields throughout — nothing is made `readonly`, so
|
|
152
|
+
* autofill, screen-reader semantics and the mobile keyboard are untouched. A browser autofill
|
|
153
|
+
* that lands before hydration is recovered the same way.
|
|
154
|
+
*
|
|
155
|
+
* Applies to every `<input>` and `<textarea>` on the page, regardless of which UI library
|
|
156
|
+
* rendered it. It is a stopgap and is meant to be removed once Vue covers the remaining
|
|
157
|
+
* types.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* export default defineNuxtConfig({
|
|
162
|
+
* ltExtensions: {
|
|
163
|
+
* preHydrationInput: { enabled: false },
|
|
164
|
+
* },
|
|
165
|
+
* });
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export interface LtPreHydrationInputOptions {
|
|
169
|
+
/**
|
|
170
|
+
* Enable preservation. Defaults to `true`.
|
|
171
|
+
*
|
|
172
|
+
* Turn it off only with a reason: the alternative is that a user who types immediately
|
|
173
|
+
* after a page appears loses the entry silently.
|
|
174
|
+
*/
|
|
175
|
+
enabled?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* How long after mount to keep restoring, in milliseconds. Defaults to `1500`; `0` restores
|
|
178
|
+
* once and stops immediately.
|
|
179
|
+
*
|
|
180
|
+
* Most fields are repaired in the first pass. The window exists for subtrees whose mount is
|
|
181
|
+
* deferred — an unresolved `<Suspense>` boundary writes its model value later than
|
|
182
|
+
* `app:mounted`. When it elapses the snapshot is dropped, so preserved values, passwords
|
|
183
|
+
* included, are not kept alive for the session.
|
|
184
|
+
*/
|
|
185
|
+
maxRestoreMs?: number;
|
|
186
|
+
}
|
|
135
187
|
/**
|
|
136
188
|
* Public runtime config added by this module
|
|
137
189
|
*/
|
|
@@ -167,6 +219,10 @@ export interface LtExtensionsPublicRuntimeConfig {
|
|
|
167
219
|
enabled: boolean;
|
|
168
220
|
defaultLocale: string;
|
|
169
221
|
};
|
|
222
|
+
preHydrationInput: {
|
|
223
|
+
enabled: boolean;
|
|
224
|
+
maxRestoreMs: number;
|
|
225
|
+
};
|
|
170
226
|
tus: {
|
|
171
227
|
defaultChunkSize: number;
|
|
172
228
|
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.12.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",
|