vuetify-nuxt-module 1.0.0-beta.1 → 1.0.0-beta.11
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/README.md +16 -19
- package/configuration.d.ts +3 -0
- package/dist/module.d.mts +55 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +320 -379
- package/dist/runtime/plugins/first-request-reload-guard.d.ts +13 -0
- package/dist/runtime/plugins/first-request-reload-guard.js +11 -0
- package/dist/runtime/plugins/i18n.js +6 -1
- package/dist/runtime/plugins/vuetify-client-hints.client.js +21 -7
- package/dist/runtime/plugins/vuetify-client-hints.server.js +14 -2
- package/package.json +16 -14
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session cookie marking that the one-time `reloadOnFirstRequest` reload has
|
|
3
|
+
* already happened this browser session. Prevents an infinite reload loop when
|
|
4
|
+
* a browser requests client hints but never delivers them (e.g. Brave Shields
|
|
5
|
+
* strip `Sec-CH-*`), since `firstRequest` would otherwise stay `true` forever (#334).
|
|
6
|
+
*/
|
|
7
|
+
export declare const RELOAD_GUARD_COOKIE = "vuetify-nuxt-client-hints-reloaded";
|
|
8
|
+
/** True when the guard cookie is present in a `document.cookie` string. */
|
|
9
|
+
export declare function hasReloadGuardCookie(cookie: string): boolean;
|
|
10
|
+
/** Build a session guard cookie (no expiry → cleared on browser close). */
|
|
11
|
+
export declare function buildReloadGuardCookie(path: string): string;
|
|
12
|
+
/** Whether to perform the first-request reload: only once per session. */
|
|
13
|
+
export declare function shouldReloadOnFirstRequest(firstRequest: boolean, reloadOnFirstRequest: boolean, alreadyReloaded: boolean): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const RELOAD_GUARD_COOKIE = "vuetify-nuxt-client-hints-reloaded";
|
|
2
|
+
export function hasReloadGuardCookie(cookie) {
|
|
3
|
+
const prefix = `${RELOAD_GUARD_COOKIE}=`;
|
|
4
|
+
return cookie.split(";").some((c) => c.trim().startsWith(prefix));
|
|
5
|
+
}
|
|
6
|
+
export function buildReloadGuardCookie(path) {
|
|
7
|
+
return `${RELOAD_GUARD_COOKIE}=1; Path=${path}; SameSite=Lax`;
|
|
8
|
+
}
|
|
9
|
+
export function shouldReloadOnFirstRequest(firstRequest, reloadOnFirstRequest, alreadyReloaded) {
|
|
10
|
+
return firstRequest && reloadOnFirstRequest && !alreadyReloaded;
|
|
11
|
+
}
|
|
@@ -4,6 +4,9 @@ import { useI18n } from "vue-i18n";
|
|
|
4
4
|
function inferDecimalSeparator(n) {
|
|
5
5
|
return n(0.1).includes(",") ? "," : ".";
|
|
6
6
|
}
|
|
7
|
+
function inferNumericGroupSeparator(n) {
|
|
8
|
+
return n(1e4, { useGrouping: true }).replace(/\p{Nd}/gu, "") || " ";
|
|
9
|
+
}
|
|
7
10
|
export function createAdapter(vuetifyOptions) {
|
|
8
11
|
vuetifyOptions.locale = {};
|
|
9
12
|
const nuxtApp = useNuxtApp();
|
|
@@ -32,7 +35,8 @@ export function createAdapter(vuetifyOptions) {
|
|
|
32
35
|
t: (key, ...params) => i18n.t(key, params),
|
|
33
36
|
n: i18n.n,
|
|
34
37
|
provide: createProvideFunction({ current: currentLocale, fallback, messages }),
|
|
35
|
-
decimalSeparator: toRef(() => inferDecimalSeparator(i18n.n))
|
|
38
|
+
decimalSeparator: toRef(() => inferDecimalSeparator(i18n.n)),
|
|
39
|
+
numericGroupSeparator: toRef(() => inferNumericGroupSeparator(i18n.n))
|
|
36
40
|
};
|
|
37
41
|
}
|
|
38
42
|
function createProvideFunction(data) {
|
|
@@ -61,6 +65,7 @@ function createProvideFunction(data) {
|
|
|
61
65
|
t,
|
|
62
66
|
n,
|
|
63
67
|
decimalSeparator: toRef(() => props.decimalSeparator ?? inferDecimalSeparator(n)),
|
|
68
|
+
numericGroupSeparator: toRef(() => inferNumericGroupSeparator(n)),
|
|
64
69
|
provide: createProvideFunction({ current: currentLocale, fallback: data.fallback, messages: data.messages })
|
|
65
70
|
};
|
|
66
71
|
};
|
|
@@ -2,6 +2,7 @@ import { defineNuxtPlugin, useNuxtApp, useState } from "#imports";
|
|
|
2
2
|
import { ssrClientHintsConfiguration } from "virtual:vuetify-ssr-client-hints-configuration";
|
|
3
3
|
import { reactive, ref, watch } from "vue";
|
|
4
4
|
import { VuetifyHTTPClientHints } from "./client-hints.js";
|
|
5
|
+
import { buildReloadGuardCookie, hasReloadGuardCookie, shouldReloadOnFirstRequest } from "./first-request-reload-guard.js";
|
|
5
6
|
const plugin = defineNuxtPlugin({
|
|
6
7
|
name: "vuetify:client-hints:client:plugin",
|
|
7
8
|
order: -25,
|
|
@@ -22,7 +23,11 @@ const plugin = defineNuxtPlugin({
|
|
|
22
23
|
prefersColorScheme,
|
|
23
24
|
prefersColorSchemeOptions
|
|
24
25
|
} = ssrClientHintsConfiguration;
|
|
25
|
-
if (firstRequest
|
|
26
|
+
if (shouldReloadOnFirstRequest(firstRequest, reloadOnFirstRequest, hasReloadGuardCookie(document.cookie))) {
|
|
27
|
+
const markAndReload = () => {
|
|
28
|
+
document.cookie = buildReloadGuardCookie(prefersColorSchemeOptions?.baseUrl ?? "/");
|
|
29
|
+
window.location.reload();
|
|
30
|
+
};
|
|
26
31
|
if (prefersColorScheme) {
|
|
27
32
|
const themeCookie = state.value.colorSchemeCookie;
|
|
28
33
|
if (prefersColorSchemeOptions && themeCookie) {
|
|
@@ -32,19 +37,19 @@ const plugin = defineNuxtPlugin({
|
|
|
32
37
|
const cookieEntry = `${parseCookieName}${state.value.colorSchemeFromCookie ?? prefersColorSchemeOptions.defaultTheme};`;
|
|
33
38
|
const newThemeName = prefersDark ? prefersColorSchemeOptions.darkThemeName : prefersColorSchemeOptions.lightThemeName;
|
|
34
39
|
document.cookie = themeCookie.replace(cookieEntry, `${cookieName}=${newThemeName};`);
|
|
35
|
-
|
|
40
|
+
markAndReload();
|
|
36
41
|
} else if (prefersColorSchemeAvailable) {
|
|
37
|
-
|
|
42
|
+
markAndReload();
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
if (prefersReducedMotion && prefersReducedMotionAvailable) {
|
|
41
|
-
|
|
46
|
+
markAndReload();
|
|
42
47
|
}
|
|
43
48
|
if (viewportSize && viewportHeightAvailable) {
|
|
44
|
-
|
|
49
|
+
markAndReload();
|
|
45
50
|
}
|
|
46
51
|
if (viewportSize && viewportWidthAvailable) {
|
|
47
|
-
|
|
52
|
+
markAndReload();
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
nuxtApp.hook("vuetify:before-create", ({ vuetifyOptions }) => {
|
|
@@ -119,12 +124,21 @@ function useSSRClientHints() {
|
|
|
119
124
|
const {
|
|
120
125
|
baseUrl,
|
|
121
126
|
cookieName,
|
|
127
|
+
cookieDomain,
|
|
128
|
+
cookieSecure,
|
|
129
|
+
cookieSameSite,
|
|
122
130
|
defaultTheme
|
|
123
131
|
} = ssrClientHintsConfiguration.prefersColorSchemeOptions;
|
|
124
132
|
const cookieNamePrefix = `${cookieName}=`;
|
|
125
133
|
initial.value.colorSchemeFromCookie = document.cookie?.split(";")?.find((c) => c.trim().startsWith(cookieNamePrefix))?.split("=")[1] ?? defaultTheme;
|
|
126
134
|
const date = /* @__PURE__ */ new Date();
|
|
127
135
|
const expires = new Date(date.setDate(date.getDate() + 365));
|
|
128
|
-
initial.value.colorSchemeCookie = `${cookieName}=${initial.value.colorSchemeFromCookie}; Path=${baseUrl}; Expires=${expires.toUTCString()}; SameSite
|
|
136
|
+
initial.value.colorSchemeCookie = `${cookieName}=${initial.value.colorSchemeFromCookie}; Path=${baseUrl}; Expires=${expires.toUTCString()}; SameSite=${cookieSameSite[0].toUpperCase()}${cookieSameSite.slice(1)}`;
|
|
137
|
+
if (cookieDomain) {
|
|
138
|
+
initial.value.colorSchemeCookie += `; Domain=${cookieDomain}`;
|
|
139
|
+
}
|
|
140
|
+
if (cookieSecure) {
|
|
141
|
+
initial.value.colorSchemeCookie += "; Secure";
|
|
142
|
+
}
|
|
129
143
|
return initial;
|
|
130
144
|
}
|
|
@@ -273,15 +273,27 @@ function writeThemeCookie(clientHintsRequest, ssrClientHintsConfiguration2) {
|
|
|
273
273
|
const cookieName = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieName;
|
|
274
274
|
const themeName = clientHintsRequest.colorSchemeFromCookie ?? ssrClientHintsConfiguration2.prefersColorSchemeOptions.defaultTheme;
|
|
275
275
|
const path = ssrClientHintsConfiguration2.prefersColorSchemeOptions.baseUrl;
|
|
276
|
+
const domain = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieDomain;
|
|
277
|
+
const secure = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieSecure;
|
|
278
|
+
const sameSite = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieSameSite;
|
|
276
279
|
const date = /* @__PURE__ */ new Date();
|
|
277
280
|
const expires = new Date(date.setDate(date.getDate() + 365));
|
|
278
281
|
if (!clientHintsRequest.firstRequest || !ssrClientHintsConfiguration2.reloadOnFirstRequest) {
|
|
279
282
|
useCookie(cookieName, {
|
|
280
283
|
path,
|
|
284
|
+
domain,
|
|
281
285
|
expires,
|
|
282
|
-
sameSite
|
|
286
|
+
sameSite,
|
|
287
|
+
secure
|
|
283
288
|
}).value = themeName;
|
|
284
289
|
}
|
|
285
|
-
|
|
290
|
+
let cookie = `${cookieName}=${themeName}; Path=${path}; Expires=${expires.toUTCString()}; SameSite=${sameSite[0].toUpperCase()}${sameSite.slice(1)}`;
|
|
291
|
+
if (domain) {
|
|
292
|
+
cookie += `; Domain=${domain}`;
|
|
293
|
+
}
|
|
294
|
+
if (secure) {
|
|
295
|
+
cookie += "; Secure";
|
|
296
|
+
}
|
|
297
|
+
return cookie;
|
|
286
298
|
}
|
|
287
299
|
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vuetify-nuxt-module",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.11",
|
|
5
5
|
"description": "Zero-Config Nuxt Module for Vuetify",
|
|
6
6
|
"author": "userquin <userquin@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -52,17 +52,18 @@
|
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@nuxt/kit": "^4.3.1",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
55
|
+
"@vuetify/loader-shared": "^2.1.2",
|
|
56
|
+
"@vuetify/unplugin-styles": "^1.0.0-beta.11",
|
|
57
|
+
"defu": "^6.1.7",
|
|
57
58
|
"local-pkg": "^1.1.2",
|
|
58
59
|
"pathe": "^2.0.3",
|
|
59
60
|
"perfect-debounce": "^2.1.0",
|
|
60
61
|
"semver": "^7.7.4",
|
|
61
62
|
"ufo": "^1.6.3",
|
|
62
|
-
"unconfig": "^7.5.0"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"vuetify": "^4.0.
|
|
63
|
+
"unconfig": "^7.5.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"vuetify": "^3.4.0 || ^4.0.0"
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"@antfu/eslint-config": "^7.6.1",
|
|
@@ -87,21 +88,23 @@
|
|
|
87
88
|
"eslint": "^10.0.2",
|
|
88
89
|
"luxon": "^3.7.2",
|
|
89
90
|
"nuxt": "^4.3.1",
|
|
91
|
+
"playwright-core": "^1.58.0",
|
|
90
92
|
"publint": "^0.3.18",
|
|
91
93
|
"rimraf": "^6.1.3",
|
|
92
94
|
"sass": "^1.97.3",
|
|
93
95
|
"typescript": "^5.9.3",
|
|
94
|
-
"vite": "7.3.
|
|
96
|
+
"vite": "7.3.5",
|
|
95
97
|
"vitest": "^4.0.18",
|
|
96
|
-
"vue-tsc": "^3.2.5"
|
|
98
|
+
"vue-tsc": "^3.2.5",
|
|
99
|
+
"vuetify": "^4.0.1"
|
|
97
100
|
},
|
|
98
101
|
"build": {
|
|
99
102
|
"externals": [
|
|
100
103
|
"@vuetify/loader-shared",
|
|
104
|
+
"@vuetify/unplugin-styles",
|
|
101
105
|
"node:child_process",
|
|
102
106
|
"node:fs",
|
|
103
107
|
"consola",
|
|
104
|
-
"destr",
|
|
105
108
|
"esbuild",
|
|
106
109
|
"local-pkg",
|
|
107
110
|
"pathe",
|
|
@@ -109,11 +112,9 @@
|
|
|
109
112
|
"rollup",
|
|
110
113
|
"sass",
|
|
111
114
|
"sass-embedded",
|
|
112
|
-
"upath",
|
|
113
115
|
"ufo",
|
|
114
116
|
"unconfig",
|
|
115
117
|
"vite",
|
|
116
|
-
"vite-plugin-vuetify",
|
|
117
118
|
"vuetify"
|
|
118
119
|
]
|
|
119
120
|
},
|
|
@@ -139,8 +140,9 @@
|
|
|
139
140
|
"lint": "eslint .",
|
|
140
141
|
"lint:fix": "nr lint --fix",
|
|
141
142
|
"publint": "publint",
|
|
142
|
-
"test": "vitest run",
|
|
143
|
-
"test:watch": "vitest watch",
|
|
143
|
+
"test": "vitest run --exclude 'test/e2e/**'",
|
|
144
|
+
"test:watch": "vitest watch --exclude 'test/e2e/**'",
|
|
145
|
+
"test:e2e": "vitest run test/e2e",
|
|
144
146
|
"release": "bumpp"
|
|
145
147
|
}
|
|
146
148
|
}
|