origam 2.29.1 → 2.30.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/CHANGELOG.md +19 -0
- package/dist/src/components/Btn/OrigamBtn.vue +5 -1
- package/dist/src/interfaces/Commons/nuxt-module.interface.d.ts +18 -4
- package/dist/src/nuxt/module.d.ts +8 -2
- package/dist/src/nuxt/plugin.client.cjs +10 -2
- package/dist/src/nuxt/plugin.client.js +12 -4
- package/dist/src/nuxt/plugin.server.cjs +11 -20
- package/dist/src/nuxt/plugin.server.js +17 -22
- package/dist/src/utils/Commons/apply-theme.util.cjs +20 -0
- package/dist/src/utils/Commons/apply-theme.util.d.ts +34 -0
- package/dist/src/utils/Commons/apply-theme.util.js +18 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,25 @@ This project follows [Semantic Versioning](https://semver.org).
|
|
|
18
18
|
|
|
19
19
|
## [Unreleased]
|
|
20
20
|
|
|
21
|
+
## [2.30.0] - 2026-10-15
|
|
22
|
+
|
|
23
|
+
### Changed — `OrigamBtn` `block` : `flex: 0 0 auto` + `width: 100%`, plus de croissance sur l'axe principal (#1095)
|
|
24
|
+
|
|
25
|
+
`.origam-btn--block` portait `flex: 1 0 auto` : dans un parent flex en colonne le bouton GRANDISSAIT pour remplir la hauteur (une carte etiree a la hauteur de sa voisine, ou une colonne a hauteur fixe). Il passe a **`flex: 0 0 auto` + `width: 100%`** : pleine largeur, aucune croissance. Mesure Chromium (story « Prop — block (flex column) », `size="large"`) : bouton `block` dans une carte etiree 188 px -> **44 px**, dans une colonne de 200 px 168 px -> **44 px** ; largeur = 100 % du parent en colonne et en ligne (inchangee).
|
|
26
|
+
|
|
27
|
+
**Rupture (assumee, pre-1.0) :** une rangee flex de plusieurs boutons `block` qui se partageaient la largeur par `grow` (`flex-grow: 1`) donne desormais N boutons de 100 % de large. **Migration :** envelopper les boutons dans un `OrigamBtnGroup` ou une grille CSS (`display: grid; grid-auto-flow: column`) ; ou retirer `block` et poser `flex: 1` sur chaque bouton via une classe. **Ecart de rendu mesure au repos** (sonde `pnpm -F @origam/tests audit:btn-height -- --compare`, 54 752 releves, 2 544 configurations au repos, 8 identites x 2 modes, meme code de sonde avant/apres) : **160 ecarts de valeur sur 80 configurations**, TOUS `block` + `density="compact"`, sur 5 tailles : la largeur passe de 296 px a **304 px** dans un conteneur de 304 px (le `min-width: calc(100% + density)` retirait 8 px a un bouton `block` compact ; `width: 100%` le remet a la pleine largeur) et le contenu se decale de 4 px. 0 ecart de hauteur, 0 ecart hors `block` + `compact`, 0 sur `BtnGroup` / `BtnToggle`. `--origam-btn---min-width: 100%` est inchange (`max-width` ne plafonne toujours pas un `block`), `stacked`, `wrap` et la hauteur minimale se comportent comme avant.
|
|
28
|
+
|
|
29
|
+
## [2.29.2] - 2026-10-15
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Fixed — plus de flash clair -> sombre a la premiere visite : le bloc de theme SSR contient les deux modes (#1103)
|
|
33
|
+
|
|
34
|
+
`origam-theme-ssr` n'emettait que la marque x le mode actifs ; un visiteur en systeme sombre sans cookie recevait le bloc clair (`data-mode` `light` -> `dark`, `--origam-color__surface---default` `#fff` -> `#0a0a0a`, fond du body blanc -> `rgb(10,10,10)`). Quand aucun mode n'est epingle (pas de cookie `light`/`dark`, `defaultMode: 'auto'`), le serveur omet desormais `data-mode` et inline les deux modes de la marque : le clair sous `@media not (prefers-color-scheme: dark)`, le sombre sous `@media (prefers-color-scheme: dark)`, avec des selecteurs ancres sur la racine et `:not([data-mode])` (marque nommee `:root:root[data-theme="X"]:not([data-mode])` (0,4,0) ; sans nom `html:not([data-theme]):not([data-mode])` (0,2,1)). Un mode epingle (cookie, `defaultMode` `light`/`dark`, ou le `data-mode` ecrit par le client a l'hydratation) l'emporte toujours. Cout : un bloc de theme de plus par theme a mode dans le HTML (+6,8 Ko mesures sur le site marketing).
|
|
35
|
+
|
|
36
|
+
Les props de composants (`theme.components`) ne suivent pas le `@media` : mode non epingle, elles sont amorcees en CLAIR cote serveur et pour le rendu d'hydratation, puis passent au mode systeme apres l'hydratation (`app:mounted`) — pas de mismatch. Une marque dont les `components` different entre clair et sombre (`editorial`) montre donc ses props claires une frame sur un systeme sombre ; les tokens, eux, ne clignotent pas.
|
|
37
|
+
|
|
38
|
+
La branche `Sec-CH-Prefers-Color-Scheme` de `resolveServerMode` est retiree (aucun `Accept-CH` n'etait emis : morte en pratique) ; l'overload `useRequestHeaders` du shim `#app` aussi. Le site marketing passe en `defaultMode: 'auto'`, et son bouton de bascule de mode hydrate avec l'icone « clair » avant de passer au mode systeme. Specs : `marketing-ssr-both-modes.spec.ts`, `marketing-theme-honored.spec.ts` (adaptee), `apply-theme-unpinned.util.spec.ts`.
|
|
39
|
+
|
|
21
40
|
## [2.29.1] - 2026-10-15
|
|
22
41
|
|
|
23
42
|
### Fixed — publication de rattrapage : 2.28.0 et 2.29.0 n'avaient pas été publiées
|
|
@@ -933,7 +933,11 @@ defineExpose({
|
|
|
933
933
|
|
|
934
934
|
&--block {
|
|
935
935
|
display: flex;
|
|
936
|
-
flex: 1 0 auto
|
|
936
|
+
// #1095 — `flex: 1 0 auto` faisait GRANDIR le bouton sur l'axe principal
|
|
937
|
+
// d'un parent flex en colonne (80 px au lieu de 44 px dans une carte a
|
|
938
|
+
// hauteur egale). La pleine largeur est portee par `width`, pas par `grow`.
|
|
939
|
+
flex: 0 0 auto;
|
|
940
|
+
width: 100%;
|
|
937
941
|
|
|
938
942
|
--origam-btn---min-width: 100%;
|
|
939
943
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IOrigamTheme } from './theme.interface';
|
|
2
|
+
import type { TMode, TTheme } from '../../types/Commons/theme.type';
|
|
2
3
|
/**
|
|
3
4
|
* Options passed by a consumer in `nuxt.config.ts` under the `origam` key.
|
|
4
5
|
*
|
|
@@ -9,7 +10,7 @@ import type { IOrigamTheme } from './theme.interface';
|
|
|
9
10
|
* a name×mode scoped `--origam-*` block. The theme-invariant `primitive` +
|
|
10
11
|
* `utilities` sheets are still loaded as CSS. Two orthogonal axes are resolved
|
|
11
12
|
* SSR-side to avoid FOUC: the brand `theme` (cookie) and the color `mode`
|
|
12
|
-
* (cookie
|
|
13
|
+
* (cookie, else the system `prefers-color-scheme` via `@media`).
|
|
13
14
|
*
|
|
14
15
|
* ADR-004 (Implemented): the DS ships exactly ONE neutral identity (the origam
|
|
15
16
|
* baseline). Brand themes live in the consuming app (e.g. the marketing
|
|
@@ -40,9 +41,9 @@ export interface IOrigamNuxtModuleOptions {
|
|
|
40
41
|
*/
|
|
41
42
|
modes?: string[];
|
|
42
43
|
/**
|
|
43
|
-
* Color mode applied when no cookie is set
|
|
44
|
-
* `
|
|
45
|
-
*
|
|
44
|
+
* Color mode applied when no cookie is set. `'light'` / `'dark'` pins it.
|
|
45
|
+
* `'auto'` leaves it to the system: the server inlines both modes, the dark
|
|
46
|
+
* one under `@media (prefers-color-scheme: dark)` (#1103).
|
|
46
47
|
*
|
|
47
48
|
* @default 'auto'
|
|
48
49
|
*/
|
|
@@ -111,3 +112,16 @@ export interface IOrigamNuxtRuntimeConfig {
|
|
|
111
112
|
modeCookieName: string;
|
|
112
113
|
cookieMaxAge: number;
|
|
113
114
|
}
|
|
115
|
+
/*********************************************************
|
|
116
|
+
* IOrigamNuxtProvide
|
|
117
|
+
*
|
|
118
|
+
* @description
|
|
119
|
+
* Shape of what the Nuxt plugins `provide` under `nuxtApp.$origam`.
|
|
120
|
+
* @description
|
|
121
|
+
* `mode` is `'auto'` when the colour mode is NOT pinned (#1103), otherwise the
|
|
122
|
+
* pinned concrete mode.
|
|
123
|
+
********************************************************/
|
|
124
|
+
export interface IOrigamNuxtProvide {
|
|
125
|
+
theme: TTheme;
|
|
126
|
+
mode: TMode;
|
|
127
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IOrigamNuxtModuleOptions, IOrigamNuxtRuntimeConfig } from '../interfaces/Commons/nuxt-module.interface'
|
|
1
|
+
import type { IOrigamNuxtModuleOptions, IOrigamNuxtProvide, IOrigamNuxtRuntimeConfig } from '../interfaces/Commons/nuxt-module.interface'
|
|
2
2
|
|
|
3
3
|
declare module '@nuxt/schema' {
|
|
4
4
|
interface NuxtConfig {
|
|
@@ -14,6 +14,12 @@ declare module '@nuxt/schema' {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
declare module '#app' {
|
|
18
|
+
interface NuxtApp {
|
|
19
|
+
$origam: IOrigamNuxtProvide
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
declare module 'nuxt/schema' {
|
|
18
24
|
interface NuxtConfig {
|
|
19
25
|
origam?: IOrigamNuxtModuleOptions
|
|
@@ -28,4 +34,4 @@ declare module 'nuxt/schema' {
|
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export { IOrigamNuxtModuleOptions, IOrigamNuxtRuntimeConfig }
|
|
37
|
+
export { IOrigamNuxtModuleOptions, IOrigamNuxtProvide, IOrigamNuxtRuntimeConfig }
|
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
var _vue = require("vue");
|
|
8
8
|
var _theme = require("../composables/Commons/theme.composable.cjs");
|
|
9
9
|
var _origam = require("../origam.cjs");
|
|
10
|
+
var _applyTheme = require("../utils/Commons/apply-theme.util.cjs");
|
|
10
11
|
var _app = require("#app");
|
|
11
12
|
module.exports = (0, _app.defineNuxtPlugin)({
|
|
12
13
|
name: "origam:client",
|
|
@@ -23,6 +24,7 @@ module.exports = (0, _app.defineNuxtPlugin)({
|
|
|
23
24
|
sameSite: "lax",
|
|
24
25
|
path: "/"
|
|
25
26
|
});
|
|
27
|
+
const modePinned = (0, _applyTheme.isModePinned)(modeCookie.value, config.defaultMode);
|
|
26
28
|
const themeFromDom = typeof document !== "undefined" && document.documentElement.dataset.theme || null;
|
|
27
29
|
const initialTheme = themeFromDom ?? themeCookie.value ?? config.defaultTheme;
|
|
28
30
|
const initialMode = modeCookie.value ?? config.defaultMode;
|
|
@@ -35,8 +37,14 @@ module.exports = (0, _app.defineNuxtPlugin)({
|
|
|
35
37
|
const themeApi = (0, _theme.useTheme)();
|
|
36
38
|
themeApi.setTheme(initialTheme);
|
|
37
39
|
themeApi.setMode(initialMode);
|
|
38
|
-
(0, _vue.
|
|
39
|
-
|
|
40
|
+
const hydrated = (0, _vue.ref)(modePinned);
|
|
41
|
+
if (!modePinned) {
|
|
42
|
+
nuxtApp.hook("app:mounted", () => {
|
|
43
|
+
hydrated.value = true;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
(0, _vue.watch)([themeApi.theme, themeApi.resolvedMode, hydrated], ([brand, mode, isHydrated]) => {
|
|
47
|
+
origam._defaultsRef.value = origam._activeDefaultsFor(brand === "auto" ? "" : brand, isHydrated ? mode : "light");
|
|
40
48
|
}, {
|
|
41
49
|
immediate: true
|
|
42
50
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { watch } from "vue";
|
|
1
|
+
import { ref, watch } from "vue";
|
|
2
2
|
import { useTheme } from "../composables/Commons/theme.composable.js";
|
|
3
3
|
import { createOrigam } from "../origam.js";
|
|
4
|
+
import { isModePinned } from "../utils/Commons/apply-theme.util.js";
|
|
4
5
|
import { defineNuxtPlugin, useCookie, useRuntimeConfig } from "#app";
|
|
5
6
|
export default defineNuxtPlugin({
|
|
6
7
|
name: "origam:client",
|
|
@@ -17,6 +18,7 @@ export default defineNuxtPlugin({
|
|
|
17
18
|
sameSite: "lax",
|
|
18
19
|
path: "/"
|
|
19
20
|
});
|
|
21
|
+
const modePinned = isModePinned(modeCookie.value, config.defaultMode);
|
|
20
22
|
const themeFromDom = typeof document !== "undefined" && document.documentElement.dataset.theme || null;
|
|
21
23
|
const initialTheme = themeFromDom ?? themeCookie.value ?? config.defaultTheme;
|
|
22
24
|
const initialMode = modeCookie.value ?? config.defaultMode;
|
|
@@ -26,12 +28,18 @@ export default defineNuxtPlugin({
|
|
|
26
28
|
const themeApi = useTheme();
|
|
27
29
|
themeApi.setTheme(initialTheme);
|
|
28
30
|
themeApi.setMode(initialMode);
|
|
31
|
+
const hydrated = ref(modePinned);
|
|
32
|
+
if (!modePinned) {
|
|
33
|
+
nuxtApp.hook("app:mounted", () => {
|
|
34
|
+
hydrated.value = true;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
29
37
|
watch(
|
|
30
|
-
[themeApi.theme, themeApi.resolvedMode],
|
|
31
|
-
([brand, mode]) => {
|
|
38
|
+
[themeApi.theme, themeApi.resolvedMode, hydrated],
|
|
39
|
+
([brand, mode, isHydrated]) => {
|
|
32
40
|
origam._defaultsRef.value = origam._activeDefaultsFor(
|
|
33
41
|
brand === "auto" ? "" : brand,
|
|
34
|
-
mode
|
|
42
|
+
isHydrated ? mode : "light"
|
|
35
43
|
);
|
|
36
44
|
},
|
|
37
45
|
{ immediate: true }
|
|
@@ -18,21 +18,11 @@ function resolveServerTheme(cookieValue, config) {
|
|
|
18
18
|
}
|
|
19
19
|
return _theme.ORIGAM_THEME_AUTO;
|
|
20
20
|
}
|
|
21
|
-
function resolveServerMode(cookieValue, config
|
|
21
|
+
function resolveServerMode(cookieValue, config) {
|
|
22
22
|
if (cookieValue === _theme.ORIGAM_MODE_LIGHT || cookieValue === _theme.ORIGAM_MODE_DARK) {
|
|
23
23
|
return cookieValue;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
return config.defaultMode;
|
|
27
|
-
}
|
|
28
|
-
const hint = headers["sec-ch-prefers-color-scheme"];
|
|
29
|
-
if (hint === _theme.ORIGAM_MODE_DARK && config.modes.includes(_theme.ORIGAM_MODE_DARK)) {
|
|
30
|
-
return _theme.ORIGAM_MODE_DARK;
|
|
31
|
-
}
|
|
32
|
-
if (hint === _theme.ORIGAM_MODE_LIGHT && config.modes.includes(_theme.ORIGAM_MODE_LIGHT)) {
|
|
33
|
-
return _theme.ORIGAM_MODE_LIGHT;
|
|
34
|
-
}
|
|
35
|
-
return _theme.ORIGAM_MODE_LIGHT;
|
|
25
|
+
return config.defaultMode === _theme.ORIGAM_MODE_DARK ? _theme.ORIGAM_MODE_DARK : _theme.ORIGAM_MODE_LIGHT;
|
|
36
26
|
}
|
|
37
27
|
module.exports = (0, _app.defineNuxtPlugin)({
|
|
38
28
|
name: "origam:server",
|
|
@@ -50,22 +40,23 @@ module.exports = (0, _app.defineNuxtPlugin)({
|
|
|
50
40
|
sameSite: "lax",
|
|
51
41
|
path: "/"
|
|
52
42
|
});
|
|
53
|
-
const headers = (0, _app.useRequestHeaders)(["sec-ch-prefers-color-scheme"]);
|
|
54
43
|
const resolvedTheme = resolveServerTheme(themeCookie.value, config);
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
const modePinned = (0, _applyTheme.isModePinned)(modeCookie.value, config.defaultMode);
|
|
45
|
+
const resolvedMode = resolveServerMode(modeCookie.value, config);
|
|
46
|
+
const htmlAttrs = {};
|
|
47
|
+
if (modePinned) {
|
|
48
|
+
htmlAttrs[_theme.ORIGAM_MODE_ATTR] = resolvedMode;
|
|
49
|
+
}
|
|
59
50
|
if (resolvedTheme !== _theme.ORIGAM_THEME_AUTO) {
|
|
60
51
|
htmlAttrs[_theme.ORIGAM_THEME_ATTR] = resolvedTheme;
|
|
61
52
|
}
|
|
62
53
|
const themes = config.themes ?? [];
|
|
63
54
|
const activeThemes = themes.filter(theme => {
|
|
64
55
|
const brandMatches = resolvedTheme === _theme.ORIGAM_THEME_AUTO ? theme.name === void 0 || theme.name === "" : theme.name === resolvedTheme;
|
|
65
|
-
const modeMatches = theme.mode === void 0 || theme.mode === resolvedMode;
|
|
56
|
+
const modeMatches = !modePinned || theme.mode === void 0 || theme.mode === "auto" || theme.mode === resolvedMode;
|
|
66
57
|
return brandMatches && modeMatches;
|
|
67
58
|
});
|
|
68
|
-
const ssrThemeCss = activeThemes.map(_applyTheme.themeToCss).join("\n");
|
|
59
|
+
const ssrThemeCss = modePinned ? activeThemes.map(_applyTheme.themeToCss).join("\n") : [...activeThemes.filter(t => t.mode !== "light" && t.mode !== "dark").map(_applyTheme.themeToCss), ...activeThemes.filter(t => t.mode === "light").map(_applyTheme.themeToUnpinnedCss), ...activeThemes.filter(t => t.mode === "dark").map(_applyTheme.themeToUnpinnedCss)].join("\n");
|
|
69
60
|
(0, _app.useHead)({
|
|
70
61
|
htmlAttrs,
|
|
71
62
|
style: ssrThemeCss ? [{
|
|
@@ -101,7 +92,7 @@ module.exports = (0, _app.defineNuxtPlugin)({
|
|
|
101
92
|
provide: {
|
|
102
93
|
origam: {
|
|
103
94
|
theme: resolvedTheme,
|
|
104
|
-
mode: resolvedMode
|
|
95
|
+
mode: modePinned ? resolvedMode : "auto"
|
|
105
96
|
}
|
|
106
97
|
}
|
|
107
98
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createOrigam } from "../origam.js";
|
|
2
|
-
import { themeToCss } from "../utils/Commons/apply-theme.util.js";
|
|
2
|
+
import { isModePinned, themeToCss, themeToUnpinnedCss } from "../utils/Commons/apply-theme.util.js";
|
|
3
3
|
import { getSsrStyles } from "../utils/Commons/ssr-styles.util.js";
|
|
4
4
|
import { ORIGAM_MODE_ATTR, ORIGAM_MODE_DARK, ORIGAM_MODE_LIGHT, ORIGAM_THEME_ATTR, ORIGAM_THEME_AUTO } from "../consts/Commons/theme.const.js";
|
|
5
|
-
import { defineNuxtPlugin, useCookie, useHead,
|
|
5
|
+
import { defineNuxtPlugin, useCookie, useHead, useRuntimeConfig } from "#app";
|
|
6
6
|
function resolveServerTheme(cookieValue, config) {
|
|
7
7
|
if (cookieValue && cookieValue !== ORIGAM_THEME_AUTO) {
|
|
8
8
|
return cookieValue;
|
|
@@ -12,21 +12,11 @@ function resolveServerTheme(cookieValue, config) {
|
|
|
12
12
|
}
|
|
13
13
|
return ORIGAM_THEME_AUTO;
|
|
14
14
|
}
|
|
15
|
-
function resolveServerMode(cookieValue, config
|
|
15
|
+
function resolveServerMode(cookieValue, config) {
|
|
16
16
|
if (cookieValue === ORIGAM_MODE_LIGHT || cookieValue === ORIGAM_MODE_DARK) {
|
|
17
17
|
return cookieValue;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
return config.defaultMode;
|
|
21
|
-
}
|
|
22
|
-
const hint = headers["sec-ch-prefers-color-scheme"];
|
|
23
|
-
if (hint === ORIGAM_MODE_DARK && config.modes.includes(ORIGAM_MODE_DARK)) {
|
|
24
|
-
return ORIGAM_MODE_DARK;
|
|
25
|
-
}
|
|
26
|
-
if (hint === ORIGAM_MODE_LIGHT && config.modes.includes(ORIGAM_MODE_LIGHT)) {
|
|
27
|
-
return ORIGAM_MODE_LIGHT;
|
|
28
|
-
}
|
|
29
|
-
return ORIGAM_MODE_LIGHT;
|
|
19
|
+
return config.defaultMode === ORIGAM_MODE_DARK ? ORIGAM_MODE_DARK : ORIGAM_MODE_LIGHT;
|
|
30
20
|
}
|
|
31
21
|
export default defineNuxtPlugin({
|
|
32
22
|
name: "origam:server",
|
|
@@ -44,22 +34,27 @@ export default defineNuxtPlugin({
|
|
|
44
34
|
sameSite: "lax",
|
|
45
35
|
path: "/"
|
|
46
36
|
});
|
|
47
|
-
const headers = useRequestHeaders(["sec-ch-prefers-color-scheme"]);
|
|
48
37
|
const resolvedTheme = resolveServerTheme(themeCookie.value, config);
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
const modePinned = isModePinned(modeCookie.value, config.defaultMode);
|
|
39
|
+
const resolvedMode = resolveServerMode(modeCookie.value, config);
|
|
40
|
+
const htmlAttrs = {};
|
|
41
|
+
if (modePinned) {
|
|
42
|
+
htmlAttrs[ORIGAM_MODE_ATTR] = resolvedMode;
|
|
43
|
+
}
|
|
53
44
|
if (resolvedTheme !== ORIGAM_THEME_AUTO) {
|
|
54
45
|
htmlAttrs[ORIGAM_THEME_ATTR] = resolvedTheme;
|
|
55
46
|
}
|
|
56
47
|
const themes = config.themes ?? [];
|
|
57
48
|
const activeThemes = themes.filter((theme) => {
|
|
58
49
|
const brandMatches = resolvedTheme === ORIGAM_THEME_AUTO ? theme.name === void 0 || theme.name === "" : theme.name === resolvedTheme;
|
|
59
|
-
const modeMatches = theme.mode === void 0 || theme.mode === resolvedMode;
|
|
50
|
+
const modeMatches = !modePinned || theme.mode === void 0 || theme.mode === "auto" || theme.mode === resolvedMode;
|
|
60
51
|
return brandMatches && modeMatches;
|
|
61
52
|
});
|
|
62
|
-
const ssrThemeCss = activeThemes.map(themeToCss).join("\n")
|
|
53
|
+
const ssrThemeCss = modePinned ? activeThemes.map(themeToCss).join("\n") : [
|
|
54
|
+
...activeThemes.filter((t) => t.mode !== "light" && t.mode !== "dark").map(themeToCss),
|
|
55
|
+
...activeThemes.filter((t) => t.mode === "light").map(themeToUnpinnedCss),
|
|
56
|
+
...activeThemes.filter((t) => t.mode === "dark").map(themeToUnpinnedCss)
|
|
57
|
+
].join("\n");
|
|
63
58
|
useHead({
|
|
64
59
|
htmlAttrs,
|
|
65
60
|
style: ssrThemeCss ? [{ id: "origam-theme-ssr", key: "origam-theme-ssr", innerHTML: ssrThemeCss }] : []
|
|
@@ -82,7 +77,7 @@ export default defineNuxtPlugin({
|
|
|
82
77
|
provide: {
|
|
83
78
|
origam: {
|
|
84
79
|
theme: resolvedTheme,
|
|
85
|
-
mode: resolvedMode
|
|
80
|
+
mode: modePinned ? resolvedMode : "auto"
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
83
|
};
|
|
@@ -6,10 +6,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.applyTheme = applyTheme;
|
|
7
7
|
exports.applyThemes = applyThemes;
|
|
8
8
|
exports.installedThemesFromList = installedThemesFromList;
|
|
9
|
+
exports.isModePinned = isModePinned;
|
|
9
10
|
exports.resolveThemeVars = resolveThemeVars;
|
|
10
11
|
exports.semanticTreeToVars = semanticTreeToVars;
|
|
11
12
|
exports.themeSelector = themeSelector;
|
|
12
13
|
exports.themeToCss = themeToCss;
|
|
14
|
+
exports.themeToUnpinnedCss = themeToUnpinnedCss;
|
|
13
15
|
exports.themeVarsToVars = themeVarsToVars;
|
|
14
16
|
var _tokenName = require("./token-name.util.cjs");
|
|
15
17
|
const STYLE_ELEMENT_PREFIX = "origam-theme";
|
|
@@ -91,6 +93,24 @@ ${decls}
|
|
|
91
93
|
}
|
|
92
94
|
`;
|
|
93
95
|
}
|
|
96
|
+
function isModePinned(cookieValue, defaultMode) {
|
|
97
|
+
const concrete = v => v === "light" || v === "dark";
|
|
98
|
+
return concrete(cookieValue) || concrete(defaultMode);
|
|
99
|
+
}
|
|
100
|
+
function themeToUnpinnedCss(theme) {
|
|
101
|
+
if (theme.mode !== "light" && theme.mode !== "dark") return "";
|
|
102
|
+
const vars = resolveThemeVars(theme);
|
|
103
|
+
const decls = Object.keys(vars).map(name => ` ${name}: ${vars[name]};`).join("\n");
|
|
104
|
+
const hasName = typeof theme.name === "string" && theme.name.length > 0;
|
|
105
|
+
const selector = hasName ? `:root:root[data-theme="${theme.name}"]:not([data-mode])` : "html:not([data-theme]):not([data-mode])";
|
|
106
|
+
const query = theme.mode === "dark" ? "(prefers-color-scheme: dark)" : "not (prefers-color-scheme: dark)";
|
|
107
|
+
return `@media ${query} {
|
|
108
|
+
${selector} {
|
|
109
|
+
${decls}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
94
114
|
function applyTheme(theme) {
|
|
95
115
|
if (typeof document === "undefined") return null;
|
|
96
116
|
const vars = resolveThemeVars(theme);
|
|
@@ -42,6 +42,40 @@ export declare function themeSelector(theme: IOrigamTheme): string;
|
|
|
42
42
|
* the Theme Builder's CSS export so a single code path produces both.
|
|
43
43
|
*/
|
|
44
44
|
export declare function themeToCss(theme: IOrigamTheme): string;
|
|
45
|
+
/*********************************************************
|
|
46
|
+
* isModePinned
|
|
47
|
+
*
|
|
48
|
+
* @description
|
|
49
|
+
* Is the colour mode PINNED by the visitor or the configuration? Pinned means a
|
|
50
|
+
* `light` / `dark` mode cookie, or a configured `defaultMode` of `light` /
|
|
51
|
+
* `dark`. Anything else (`auto`, absent, junk) leaves the mode to the system.
|
|
52
|
+
* @description
|
|
53
|
+
* Shared by both Nuxt plugins so server and client never disagree on it (#1103).
|
|
54
|
+
********************************************************/
|
|
55
|
+
export declare function isModePinned(cookieValue: string | null | undefined, defaultMode: string | undefined): boolean;
|
|
56
|
+
/*********************************************************
|
|
57
|
+
* themeToUnpinnedCss
|
|
58
|
+
*
|
|
59
|
+
* @description
|
|
60
|
+
* Serialise a MODE-SCOPED theme (`mode` = `light` | `dark`) for a page whose
|
|
61
|
+
* mode is NOT pinned (#1103): no `data-mode` on `<html>`, the system decides.
|
|
62
|
+
* Returns `''` for a mode-agnostic theme — its selector never mentions
|
|
63
|
+
* `data-mode`, so the plain `themeToCss` block already serves both modes.
|
|
64
|
+
* @description
|
|
65
|
+
* light → wrapped in `@media not (prefers-color-scheme: dark)`; dark → wrapped
|
|
66
|
+
* in `@media (prefers-color-scheme: dark)`.
|
|
67
|
+
* @description
|
|
68
|
+
* Selectors are ROOT-ONLY and carry `:not([data-mode])`, so the moment a mode is
|
|
69
|
+
* pinned (the client writes `data-mode`) they stop matching and the regular
|
|
70
|
+
* `[data-mode]` blocks take over. No sub-tree form is emitted on purpose: an
|
|
71
|
+
* `<OrigamThemeProvider>` nested in a pinned page must never match them.
|
|
72
|
+
* @description
|
|
73
|
+
* Specificity is kept equal to the pinned form: named brand
|
|
74
|
+
* `:root:root[…]:not([data-mode])` = (0,4,0); unnamed
|
|
75
|
+
* `html:not([data-theme]):not([data-mode])` = (0,2,1), which beats the built-in
|
|
76
|
+
* #794 rule (0,2,0) and stays under every named brand.
|
|
77
|
+
********************************************************/
|
|
78
|
+
export declare function themeToUnpinnedCss(theme: IOrigamTheme): string;
|
|
45
79
|
/**
|
|
46
80
|
* Inject (or replace) a theme's CSS variables into `<head>` at runtime.
|
|
47
81
|
* SSR-safe: a no-op when `document` is unavailable. Returns the `<style>`
|
|
@@ -72,6 +72,24 @@ ${decls}
|
|
|
72
72
|
}
|
|
73
73
|
`;
|
|
74
74
|
}
|
|
75
|
+
export function isModePinned(cookieValue, defaultMode) {
|
|
76
|
+
const concrete = (v) => v === "light" || v === "dark";
|
|
77
|
+
return concrete(cookieValue) || concrete(defaultMode);
|
|
78
|
+
}
|
|
79
|
+
export function themeToUnpinnedCss(theme) {
|
|
80
|
+
if (theme.mode !== "light" && theme.mode !== "dark") return "";
|
|
81
|
+
const vars = resolveThemeVars(theme);
|
|
82
|
+
const decls = Object.keys(vars).map((name) => ` ${name}: ${vars[name]};`).join("\n");
|
|
83
|
+
const hasName = typeof theme.name === "string" && theme.name.length > 0;
|
|
84
|
+
const selector = hasName ? `:root:root[data-theme="${theme.name}"]:not([data-mode])` : "html:not([data-theme]):not([data-mode])";
|
|
85
|
+
const query = theme.mode === "dark" ? "(prefers-color-scheme: dark)" : "not (prefers-color-scheme: dark)";
|
|
86
|
+
return `@media ${query} {
|
|
87
|
+
${selector} {
|
|
88
|
+
${decls}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
75
93
|
export function applyTheme(theme) {
|
|
76
94
|
if (typeof document === "undefined") return null;
|
|
77
95
|
const vars = resolveThemeVars(theme);
|