origam 2.27.0 → 2.29.2

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/src/assets/css/main.css +1 -1
  3. package/dist/src/assets/css/tokens/dark.css +2 -0
  4. package/dist/src/assets/css/tokens/light.css +1 -0
  5. package/dist/src/assets/scss/tokens/_dark.scss +2 -0
  6. package/dist/src/assets/scss/tokens/_light.scss +1 -0
  7. package/dist/src/components/Btn/OrigamBtn.vue +53 -14
  8. package/dist/src/components/Chart/OrigamChartCartesian.vue +1 -1
  9. package/dist/src/components/Chart/OrigamChartPolar.vue +1 -1
  10. package/dist/src/components/Chip/OrigamChip.vue +1 -1
  11. package/dist/src/components/Code/OrigamCode.vue +0 -2
  12. package/dist/src/composables/Commons/style.composable.cjs +14 -1
  13. package/dist/src/composables/Commons/style.composable.d.ts +0 -14
  14. package/dist/src/composables/Commons/style.composable.js +13 -2
  15. package/dist/src/composables/Commons/styleTag.composable.cjs +10 -4
  16. package/dist/src/composables/Commons/styleTag.composable.js +11 -5
  17. package/dist/src/consts/Commons/ssr-style.const.cjs +8 -0
  18. package/dist/src/consts/Commons/ssr-style.const.d.ts +19 -0
  19. package/dist/src/consts/Commons/ssr-style.const.js +2 -0
  20. package/dist/src/consts/index.cjs +11 -0
  21. package/dist/src/consts/index.d.ts +1 -0
  22. package/dist/src/consts/index.js +1 -0
  23. package/dist/src/directives/Contrast/contrast.directive.cjs +58 -6
  24. package/dist/src/directives/Contrast/contrast.directive.d.ts +10 -0
  25. package/dist/src/directives/Contrast/contrast.directive.js +61 -9
  26. package/dist/src/interfaces/Btn/btn.interface.d.ts +1 -0
  27. package/dist/src/interfaces/Commons/commons.interface.d.ts +14 -0
  28. package/dist/src/interfaces/Commons/nuxt-module.interface.d.ts +18 -4
  29. package/dist/src/interfaces/Commons/style.interface.d.ts +21 -0
  30. package/dist/src/nuxt/module.d.ts +8 -2
  31. package/dist/src/nuxt/plugin.client.cjs +10 -2
  32. package/dist/src/nuxt/plugin.client.js +12 -4
  33. package/dist/src/nuxt/plugin.server.cjs +29 -20
  34. package/dist/src/nuxt/plugin.server.js +26 -22
  35. package/dist/src/origam.cjs +3 -0
  36. package/dist/src/origam.js +5 -1
  37. package/dist/src/types/tokens.type.d.ts +1 -1
  38. package/dist/src/utils/Commons/apply-theme.util.cjs +20 -0
  39. package/dist/src/utils/Commons/apply-theme.util.d.ts +34 -0
  40. package/dist/src/utils/Commons/apply-theme.util.js +18 -0
  41. package/dist/src/utils/Commons/ssr-styles.util.cjs +41 -0
  42. package/dist/src/utils/Commons/ssr-styles.util.d.ts +6 -0
  43. package/dist/src/utils/Commons/ssr-styles.util.js +26 -0
  44. package/dist/src/utils/index.cjs +11 -0
  45. package/dist/src/utils/index.d.ts +1 -0
  46. package/dist/src/utils/index.js +1 -0
  47. package/package.json +1 -1
@@ -120,15 +120,70 @@ function enforceContrast(el, enabled) {
120
120
  const better = wcagRatio(WHITE, bg) >= wcagRatio(BLACK, bg) ? "#fff" : "#000";
121
121
  el.style.setProperty("color", better, "important");
122
122
  el.dataset.origamContrastFixed = "true";
123
- console.warn(
124
- `[origam] Low text contrast (${ratio.toFixed(2)}:1 < ${config.threshold}:1): text rgb(${fg.join(", ")}) on background rgb(${bg.join(", ")}). Overriding text colour to ${better} for legibility. Disable with createOrigam({ contrast: false }).`,
125
- el
126
- );
123
+ if (import.meta.env?.DEV) {
124
+ console.warn(
125
+ `[origam] Low text contrast (${ratio.toFixed(2)}:1 < ${config.threshold}:1): text rgb(${fg.join(", ")}) on background rgb(${bg.join(", ")}). Overriding text colour to ${better} for legibility. Disable with createOrigam({ contrast: false }).`,
126
+ el
127
+ );
128
+ }
129
+ }
130
+ const MAX_SETTLE_PASSES = 4;
131
+ const COLOR_TRANSITION_PROPERTIES = /* @__PURE__ */ new Set(["color", "background-color"]);
132
+ function declaredTransitionMs(el) {
133
+ const toMs = (value) => {
134
+ const n = parseFloat(value);
135
+ if (Number.isNaN(n)) return 0;
136
+ return value.trim().endsWith("ms") ? n : n * 1e3;
137
+ };
138
+ let longest = 0;
139
+ for (let node = el; node; node = node.parentElement) {
140
+ const style = getComputedStyle(node);
141
+ const durations = style.transitionDuration.split(",");
142
+ const delays = style.transitionDelay.split(",");
143
+ durations.forEach((duration, i) => {
144
+ longest = Math.max(longest, toMs(duration) + toMs(delays[i % delays.length] ?? "0"));
145
+ });
146
+ }
147
+ return longest;
148
+ }
149
+ function runningColorTransitions(el) {
150
+ const found = [];
151
+ for (let node = el; node; node = node.parentElement) {
152
+ for (const animation of node.getAnimations()) {
153
+ const property = animation.transitionProperty;
154
+ if (property && COLOR_TRANSITION_PROPERTIES.has(property) && animation.playState !== "finished") {
155
+ found.push(animation);
156
+ }
157
+ }
158
+ }
159
+ return found;
160
+ }
161
+ function forceStyleRecalc(el) {
162
+ return getComputedStyle(el).color;
163
+ }
164
+ function settleThenEnforce(el, enabled, pending, pass = 0) {
165
+ if (pending.cancelled) return;
166
+ if (!forceStyleRecalc(el)) return;
167
+ if (typeof el.getAnimations === "function") {
168
+ const running = pass < MAX_SETTLE_PASSES ? runningColorTransitions(el) : [];
169
+ if (running.length) {
170
+ Promise.allSettled(running.map((animation) => animation.finished)).then(() => settleThenEnforce(el, enabled, pending, pass + 1));
171
+ return;
172
+ }
173
+ enforceContrast(el, enabled);
174
+ return;
175
+ }
176
+ const wait = declaredTransitionMs(el);
177
+ if (wait > 0 && pass === 0 && typeof setTimeout !== "undefined") {
178
+ pending.timeout = setTimeout(() => settleThenEnforce(el, enabled, pending, 1), wait);
179
+ return;
180
+ }
181
+ enforceContrast(el, enabled);
127
182
  }
128
- const SETTLE_MS = 250;
129
183
  function cancelScheduled(el) {
130
184
  const pending = el._contrastTimers;
131
185
  if (!pending) return;
186
+ pending.cancelled = true;
132
187
  if (pending.raf !== void 0 && typeof cancelAnimationFrame !== "undefined") {
133
188
  cancelAnimationFrame(pending.raf);
134
189
  }
@@ -141,10 +196,7 @@ function schedule(el, enabled) {
141
196
  cancelScheduled(el);
142
197
  const pending = {};
143
198
  if (typeof requestAnimationFrame !== "undefined") {
144
- pending.raf = requestAnimationFrame(() => enforceContrast(el, enabled));
145
- }
146
- if (typeof setTimeout !== "undefined") {
147
- pending.timeout = setTimeout(() => enforceContrast(el, enabled), SETTLE_MS);
199
+ pending.raf = requestAnimationFrame(() => settleThenEnforce(el, enabled, pending));
148
200
  }
149
201
  el._contrastTimers = pending;
150
202
  }
@@ -33,6 +33,7 @@ export interface IBtnProps extends ICommonsComponentProps, IColorProps, IBgColor
33
33
  block?: boolean;
34
34
  slim?: boolean;
35
35
  stacked?: boolean;
36
+ wrap?: boolean;
36
37
  text?: string;
37
38
  status?: TStatus;
38
39
  statusIconPosition?: TStatusPosition;
@@ -15,6 +15,20 @@ import { TSSROptions } from '../../types/Commons/display.type';
15
15
  * `enabled` toggles the whole feature; `threshold` is the minimum acceptable
16
16
  * WCAG contrast ratio (default 4.5:1 — AA for normal text).
17
17
  */
18
+ /*********************************************************
19
+ * IContrastScheduled
20
+ *
21
+ * @description
22
+ * What `v-contrast` has queued for one element: the animation frame that
23
+ * starts the measure, the fallback timer (browsers without `getAnimations`,
24
+ * derived from the declared transition) and a flag that cancels a measure
25
+ * already waiting on a transition. Stored on the element as `_contrastTimers`.
26
+ ********************************************************/
27
+ export interface IContrastScheduled {
28
+ raf?: number;
29
+ timeout?: number;
30
+ cancelled?: boolean;
31
+ }
18
32
  export interface IContrastOptions {
19
33
  enabled?: boolean;
20
34
  threshold?: number;
@@ -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 + `Sec-CH-Prefers-Color-Scheme` hint).
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 and no usable
44
- * `Sec-CH-Prefers-Color-Scheme` hint is sent. Use `'auto'` to let the
45
- * server fall back to the client hint (or `'light'` if absent).
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
+ }
@@ -23,3 +23,24 @@ export interface IStyleTagOptions extends IConfigurableDocument {
23
23
  */
24
24
  id?: string;
25
25
  }
26
+ /*********************************************************
27
+ * ISsrStyleEntry / ISsrStyleCollector (#1092)
28
+ *
29
+ * @description
30
+ * `ISsrStyleEntry` is one `useStyle()` rule resolved for the server render:
31
+ * `id` is the DOM id of the `<style>` tag it belongs to, `css` the
32
+ * `#element-id { … }` rule, already safe to embed in a `<style>`.
33
+ *
34
+ * @description
35
+ * `ISsrStyleCollector` is the per-app registry of those rules. Rules are
36
+ * registered as GETTERS and read at collection time, so a value a later pass
37
+ * of the render settled on is what ships.
38
+ ********************************************************/
39
+ export interface ISsrStyleEntry {
40
+ id: string;
41
+ css: string;
42
+ }
43
+ export interface ISsrStyleCollector {
44
+ add(id: string, css: () => string): void;
45
+ entries(): ISsrStyleEntry[];
46
+ }
@@ -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.watch)([themeApi.theme, themeApi.resolvedMode], ([brand, mode]) => {
39
- origam._defaultsRef.value = origam._activeDefaultsFor(brand === "auto" ? "" : brand, mode);
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 }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
 
7
7
  var _origam = require("../origam.cjs");
8
8
  var _applyTheme = require("../utils/Commons/apply-theme.util.cjs");
9
+ var _ssrStyles = require("../utils/Commons/ssr-styles.util.cjs");
9
10
  var _theme = require("../consts/Commons/theme.const.cjs");
10
11
  var _app = require("#app");
11
12
  function resolveServerTheme(cookieValue, config) {
@@ -17,21 +18,11 @@ function resolveServerTheme(cookieValue, config) {
17
18
  }
18
19
  return _theme.ORIGAM_THEME_AUTO;
19
20
  }
20
- function resolveServerMode(cookieValue, config, headers) {
21
+ function resolveServerMode(cookieValue, config) {
21
22
  if (cookieValue === _theme.ORIGAM_MODE_LIGHT || cookieValue === _theme.ORIGAM_MODE_DARK) {
22
23
  return cookieValue;
23
24
  }
24
- if (config.defaultMode === _theme.ORIGAM_MODE_LIGHT || config.defaultMode === _theme.ORIGAM_MODE_DARK) {
25
- return config.defaultMode;
26
- }
27
- const hint = headers["sec-ch-prefers-color-scheme"];
28
- if (hint === _theme.ORIGAM_MODE_DARK && config.modes.includes(_theme.ORIGAM_MODE_DARK)) {
29
- return _theme.ORIGAM_MODE_DARK;
30
- }
31
- if (hint === _theme.ORIGAM_MODE_LIGHT && config.modes.includes(_theme.ORIGAM_MODE_LIGHT)) {
32
- return _theme.ORIGAM_MODE_LIGHT;
33
- }
34
- return _theme.ORIGAM_MODE_LIGHT;
25
+ return config.defaultMode === _theme.ORIGAM_MODE_DARK ? _theme.ORIGAM_MODE_DARK : _theme.ORIGAM_MODE_LIGHT;
35
26
  }
36
27
  module.exports = (0, _app.defineNuxtPlugin)({
37
28
  name: "origam:server",
@@ -49,22 +40,23 @@ module.exports = (0, _app.defineNuxtPlugin)({
49
40
  sameSite: "lax",
50
41
  path: "/"
51
42
  });
52
- const headers = (0, _app.useRequestHeaders)(["sec-ch-prefers-color-scheme"]);
53
43
  const resolvedTheme = resolveServerTheme(themeCookie.value, config);
54
- const resolvedMode = resolveServerMode(modeCookie.value, config, headers);
55
- const htmlAttrs = {
56
- [_theme.ORIGAM_MODE_ATTR]: resolvedMode
57
- };
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
+ }
58
50
  if (resolvedTheme !== _theme.ORIGAM_THEME_AUTO) {
59
51
  htmlAttrs[_theme.ORIGAM_THEME_ATTR] = resolvedTheme;
60
52
  }
61
53
  const themes = config.themes ?? [];
62
54
  const activeThemes = themes.filter(theme => {
63
55
  const brandMatches = resolvedTheme === _theme.ORIGAM_THEME_AUTO ? theme.name === void 0 || theme.name === "" : theme.name === resolvedTheme;
64
- const modeMatches = theme.mode === void 0 || theme.mode === resolvedMode;
56
+ const modeMatches = !modePinned || theme.mode === void 0 || theme.mode === "auto" || theme.mode === resolvedMode;
65
57
  return brandMatches && modeMatches;
66
58
  });
67
- 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");
68
60
  (0, _app.useHead)({
69
61
  htmlAttrs,
70
62
  style: ssrThemeCss ? [{
@@ -79,11 +71,28 @@ module.exports = (0, _app.defineNuxtPlugin)({
79
71
  });
80
72
  origam._defaultsRef.value = origam._activeDefaultsFor(resolvedTheme === _theme.ORIGAM_THEME_AUTO ? "" : resolvedTheme, resolvedMode);
81
73
  nuxtApp.vueApp.use(origam);
74
+ nuxtApp.hook("app:rendered", ({
75
+ ssrContext
76
+ }) => {
77
+ const styles = (0, _ssrStyles.getSsrStyles)(nuxtApp.vueApp);
78
+ if (styles.length && ssrContext) {
79
+ ssrContext.head.push({
80
+ style: styles.map(({
81
+ id,
82
+ css
83
+ }) => ({
84
+ id,
85
+ key: id,
86
+ innerHTML: css
87
+ }))
88
+ });
89
+ }
90
+ });
82
91
  return {
83
92
  provide: {
84
93
  origam: {
85
94
  theme: resolvedTheme,
86
- mode: resolvedMode
95
+ mode: modePinned ? resolvedMode : "auto"
87
96
  }
88
97
  }
89
98
  };
@@ -1,7 +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
+ import { getSsrStyles } from "../utils/Commons/ssr-styles.util.js";
3
4
  import { ORIGAM_MODE_ATTR, ORIGAM_MODE_DARK, ORIGAM_MODE_LIGHT, ORIGAM_THEME_ATTR, ORIGAM_THEME_AUTO } from "../consts/Commons/theme.const.js";
4
- import { defineNuxtPlugin, useCookie, useHead, useRequestHeaders, useRuntimeConfig } from "#app";
5
+ import { defineNuxtPlugin, useCookie, useHead, useRuntimeConfig } from "#app";
5
6
  function resolveServerTheme(cookieValue, config) {
6
7
  if (cookieValue && cookieValue !== ORIGAM_THEME_AUTO) {
7
8
  return cookieValue;
@@ -11,21 +12,11 @@ function resolveServerTheme(cookieValue, config) {
11
12
  }
12
13
  return ORIGAM_THEME_AUTO;
13
14
  }
14
- function resolveServerMode(cookieValue, config, headers) {
15
+ function resolveServerMode(cookieValue, config) {
15
16
  if (cookieValue === ORIGAM_MODE_LIGHT || cookieValue === ORIGAM_MODE_DARK) {
16
17
  return cookieValue;
17
18
  }
18
- if (config.defaultMode === ORIGAM_MODE_LIGHT || config.defaultMode === ORIGAM_MODE_DARK) {
19
- return config.defaultMode;
20
- }
21
- const hint = headers["sec-ch-prefers-color-scheme"];
22
- if (hint === ORIGAM_MODE_DARK && config.modes.includes(ORIGAM_MODE_DARK)) {
23
- return ORIGAM_MODE_DARK;
24
- }
25
- if (hint === ORIGAM_MODE_LIGHT && config.modes.includes(ORIGAM_MODE_LIGHT)) {
26
- return ORIGAM_MODE_LIGHT;
27
- }
28
- return ORIGAM_MODE_LIGHT;
19
+ return config.defaultMode === ORIGAM_MODE_DARK ? ORIGAM_MODE_DARK : ORIGAM_MODE_LIGHT;
29
20
  }
30
21
  export default defineNuxtPlugin({
31
22
  name: "origam:server",
@@ -43,22 +34,27 @@ export default defineNuxtPlugin({
43
34
  sameSite: "lax",
44
35
  path: "/"
45
36
  });
46
- const headers = useRequestHeaders(["sec-ch-prefers-color-scheme"]);
47
37
  const resolvedTheme = resolveServerTheme(themeCookie.value, config);
48
- const resolvedMode = resolveServerMode(modeCookie.value, config, headers);
49
- const htmlAttrs = {
50
- [ORIGAM_MODE_ATTR]: resolvedMode
51
- };
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
+ }
52
44
  if (resolvedTheme !== ORIGAM_THEME_AUTO) {
53
45
  htmlAttrs[ORIGAM_THEME_ATTR] = resolvedTheme;
54
46
  }
55
47
  const themes = config.themes ?? [];
56
48
  const activeThemes = themes.filter((theme) => {
57
49
  const brandMatches = resolvedTheme === ORIGAM_THEME_AUTO ? theme.name === void 0 || theme.name === "" : theme.name === resolvedTheme;
58
- const modeMatches = theme.mode === void 0 || theme.mode === resolvedMode;
50
+ const modeMatches = !modePinned || theme.mode === void 0 || theme.mode === "auto" || theme.mode === resolvedMode;
59
51
  return brandMatches && modeMatches;
60
52
  });
61
- 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");
62
58
  useHead({
63
59
  htmlAttrs,
64
60
  style: ssrThemeCss ? [{ id: "origam-theme-ssr", key: "origam-theme-ssr", innerHTML: ssrThemeCss }] : []
@@ -69,11 +65,19 @@ export default defineNuxtPlugin({
69
65
  resolvedMode
70
66
  );
71
67
  nuxtApp.vueApp.use(origam);
68
+ nuxtApp.hook("app:rendered", ({ ssrContext }) => {
69
+ const styles = getSsrStyles(nuxtApp.vueApp);
70
+ if (styles.length && ssrContext) {
71
+ ssrContext.head.push({
72
+ style: styles.map(({ id, css }) => ({ id, key: id, innerHTML: css }))
73
+ });
74
+ }
75
+ });
72
76
  return {
73
77
  provide: {
74
78
  origam: {
75
79
  theme: resolvedTheme,
76
- mode: resolvedMode
80
+ mode: modePinned ? resolvedMode : "auto"
77
81
  }
78
82
  }
79
83
  };
@@ -69,6 +69,9 @@ function createOrigam(origam = {}) {
69
69
  app.provide(_consts.ORIGAM_GO_TO_KEY, goTo);
70
70
  app.provide(_consts.ORIGAM_THEMES_KEY, installedThemes);
71
71
  app.provide(_consts.ORIGAM_DEFAULTS_KEY, defaultsRef);
72
+ if (!_consts.IN_BROWSER) {
73
+ app.provide(_consts.ORIGAM_SSR_STYLES_KEY, (0, _utils.registerSsrStyleCollector)(app));
74
+ }
72
75
  (0, _composables.installThemePropsResolver)(app, themedKeysUnion, variantPresets);
73
76
  app.provide(_consts.ORIGAM_THEME_DEFAULTS_KEY, (brand, mode) => activeDefaultsFor(allThemes, brand, mode));
74
77
  if (_consts.IN_BROWSER && options.ssr) {
@@ -20,13 +20,14 @@ import {
20
20
  ORIGAM_GO_TO_KEY,
21
21
  ORIGAM_ICONS_KEY,
22
22
  ORIGAM_LOCALE_KEY,
23
+ ORIGAM_SSR_STYLES_KEY,
23
24
  ORIGAM_THEME_DEFAULTS_KEY,
24
25
  ORIGAM_THEMES_KEY,
25
26
  VARIANT_PRESETS
26
27
  } from "./consts/index.js";
27
28
  import * as origamDirectives from "./directives/index.js";
28
29
  import { setContrastConfig } from "./directives/Contrast/contrast.directive.js";
29
- import { applyThemes, installedThemesFromList, mergeDeep } from "./utils/index.js";
30
+ import { applyThemes, installedThemesFromList, mergeDeep, registerSsrStyleCollector } from "./utils/index.js";
30
31
  import "@mdi/font/css/materialdesignicons.css";
31
32
  import { defineComponent, effectScope, nextTick, reactive } from "vue";
32
33
  export function createOrigam(origam = {}) {
@@ -88,6 +89,9 @@ export function createOrigam(origam = {}) {
88
89
  app.provide(ORIGAM_GO_TO_KEY, goTo);
89
90
  app.provide(ORIGAM_THEMES_KEY, installedThemes);
90
91
  app.provide(ORIGAM_DEFAULTS_KEY, defaultsRef);
92
+ if (!IN_BROWSER) {
93
+ app.provide(ORIGAM_SSR_STYLES_KEY, registerSsrStyleCollector(app));
94
+ }
91
95
  installThemePropsResolver(app, themedKeysUnion, variantPresets);
92
96
  app.provide(ORIGAM_THEME_DEFAULTS_KEY, (brand, mode) => activeDefaultsFor(allThemes, brand, mode));
93
97
  if (IN_BROWSER && options.ssr) {