@r2digisolutions/components 0.14.0 → 0.14.1

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 CHANGED
@@ -97,24 +97,27 @@ Hay dos ejes independientes en `<html>`:
97
97
  - **slate** — gris frío (hue 265, el look actual).
98
98
  - **neutral** — croma 0, sin castaño azul. Misma familia que Tailwind `neutral` (`#171717`, `#fafafa`).
99
99
 
100
- Para evitar FOUC, fíjalo en `app.html` antes de pintar:
100
+ Para evitar FOUC, fíjalo en `app.html` con **cookie** (no `localStorage`; en SSR no existe `setItem`):
101
101
 
102
102
  ```html
103
103
  <html lang="es" data-theme="neutral">
104
104
  <head>
105
105
  <script>
106
106
  (function () {
107
- var palette = localStorage.getItem('r2-theme-palette') || 'neutral';
108
- document.documentElement.setAttribute('data-theme', palette);
109
- if (
110
- localStorage.theme === 'dark' ||
111
- localStorage.getItem('r2-theme') === 'dark' ||
112
- (!('theme' in localStorage) &&
113
- !localStorage.getItem('r2-theme') &&
114
- window.matchMedia('(prefers-color-scheme: dark)').matches)
115
- ) {
116
- document.documentElement.classList.add('dark');
107
+ function getCookie(name) {
108
+ var m = document.cookie.match(
109
+ new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1') + '=([^;]*)')
110
+ );
111
+ return m ? decodeURIComponent(m[1]) : null;
117
112
  }
113
+ var palette = getCookie('r2-theme-palette') || 'neutral';
114
+ document.documentElement.setAttribute('data-theme', palette);
115
+ var theme = getCookie('r2-theme');
116
+ var dark =
117
+ theme === 'dark' ||
118
+ ((!theme || theme === 'system') &&
119
+ window.matchMedia('(prefers-color-scheme: dark)').matches);
120
+ document.documentElement.classList.toggle('dark', dark);
118
121
  })();
119
122
  </script>
120
123
  </head>
@@ -4,9 +4,7 @@
4
4
  * Mode: `class="dark"` on `<html>`
5
5
  * Palette: `data-theme="slate" | "neutral"` on `<html>`
6
6
  *
7
- * - Persists preference to localStorage
8
- * - Detects system preference via prefers-color-scheme
9
- * - Reactive via Svelte 5 runes ($state)
7
+ * Persists to cookies (readable by the FOUC script in `app.html` and by SSR).
10
8
  */
11
9
  export type Theme = 'light' | 'dark' | 'system';
12
10
  export type ThemePalette = 'slate' | 'neutral';
@@ -23,7 +21,7 @@ declare class ThemeStore {
23
21
  * `slate` = cool (hue 265). `neutral` = chroma 0, no blue cast.
24
22
  *
25
23
  * Pass `{ persist: false }` for a page-level override that does not
26
- * stick in localStorage.
24
+ * write the cookie.
27
25
  */
28
26
  setPalette(palette: ThemePalette, opts?: {
29
27
  persist?: boolean;
@@ -4,32 +4,67 @@
4
4
  * Mode: `class="dark"` on `<html>`
5
5
  * Palette: `data-theme="slate" | "neutral"` on `<html>`
6
6
  *
7
- * - Persists preference to localStorage
8
- * - Detects system preference via prefers-color-scheme
9
- * - Reactive via Svelte 5 runes ($state)
7
+ * Persists to cookies (readable by the FOUC script in `app.html` and by SSR).
10
8
  */
11
- const STORAGE_KEY = 'r2-theme';
12
- const PALETTE_KEY = 'r2-theme-palette';
9
+ const THEME_COOKIE = 'r2-theme';
10
+ const PALETTE_COOKIE = 'r2-theme-palette';
11
+ const LEGACY_THEME_KEY = 'r2-theme';
12
+ const LEGACY_PALETTE_KEY = 'r2-theme-palette';
13
13
  const DEFAULT_PALETTE = 'slate';
14
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
14
15
  function getSystemTheme() {
15
16
  if (typeof window === 'undefined')
16
17
  return 'light';
17
18
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
18
19
  }
19
- function getStoredTheme() {
20
- if (typeof localStorage === 'undefined')
21
- return 'system';
22
- return localStorage.getItem(STORAGE_KEY) ?? 'system';
20
+ function isTheme(value) {
21
+ return value === 'light' || value === 'dark' || value === 'system';
23
22
  }
24
23
  function isPalette(value) {
25
24
  return value === 'slate' || value === 'neutral';
26
25
  }
26
+ function readCookie(name) {
27
+ if (typeof document === 'undefined')
28
+ return null;
29
+ const escaped = name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1');
30
+ const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`));
31
+ return match ? decodeURIComponent(match[1]) : null;
32
+ }
33
+ function writeCookie(name, value) {
34
+ if (typeof document === 'undefined')
35
+ return;
36
+ document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`;
37
+ }
38
+ function readLegacyStorage(key) {
39
+ if (typeof localStorage?.getItem !== 'function')
40
+ return null;
41
+ try {
42
+ return localStorage.getItem(key);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ function getStoredTheme() {
49
+ const fromCookie = readCookie(THEME_COOKIE);
50
+ if (isTheme(fromCookie))
51
+ return fromCookie;
52
+ const legacy = readLegacyStorage(LEGACY_THEME_KEY);
53
+ if (isTheme(legacy)) {
54
+ writeCookie(THEME_COOKIE, legacy);
55
+ return legacy;
56
+ }
57
+ return 'system';
58
+ }
27
59
  function getStoredPalette() {
28
- if (typeof localStorage === 'undefined')
29
- return DEFAULT_PALETTE;
30
- const stored = localStorage.getItem(PALETTE_KEY);
31
- if (isPalette(stored))
32
- return stored;
60
+ const fromCookie = readCookie(PALETTE_COOKIE);
61
+ if (isPalette(fromCookie))
62
+ return fromCookie;
63
+ const legacy = readLegacyStorage(LEGACY_PALETTE_KEY);
64
+ if (isPalette(legacy)) {
65
+ writeCookie(PALETTE_COOKIE, legacy);
66
+ return legacy;
67
+ }
33
68
  if (typeof document !== 'undefined') {
34
69
  const attr = document.documentElement.getAttribute('data-theme');
35
70
  if (isPalette(attr))
@@ -53,17 +88,16 @@ class ThemeStore {
53
88
  #palette = $state(DEFAULT_PALETTE);
54
89
  #resolved = $derived(this.#theme === 'system' ? getSystemTheme() : this.#theme);
55
90
  constructor() {
56
- if (typeof window !== 'undefined') {
57
- this.#theme = getStoredTheme();
58
- this.#palette = getStoredPalette();
59
- applyMode(this.#theme);
60
- applyPalette(this.#palette);
61
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
62
- if (this.#theme === 'system') {
63
- applyMode('system');
64
- }
65
- });
66
- }
91
+ if (typeof window === 'undefined')
92
+ return;
93
+ this.#theme = getStoredTheme();
94
+ this.#palette = getStoredPalette();
95
+ applyMode(this.#theme);
96
+ applyPalette(this.#palette);
97
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
98
+ if (this.#theme === 'system')
99
+ applyMode('system');
100
+ });
67
101
  }
68
102
  get theme() {
69
103
  return this.#theme;
@@ -79,9 +113,7 @@ class ThemeStore {
79
113
  }
80
114
  set(theme) {
81
115
  this.#theme = theme;
82
- if (typeof localStorage !== 'undefined') {
83
- localStorage.setItem(STORAGE_KEY, theme);
84
- }
116
+ writeCookie(THEME_COOKIE, theme);
85
117
  applyMode(theme);
86
118
  }
87
119
  /**
@@ -89,13 +121,12 @@ class ThemeStore {
89
121
  * `slate` = cool (hue 265). `neutral` = chroma 0, no blue cast.
90
122
  *
91
123
  * Pass `{ persist: false }` for a page-level override that does not
92
- * stick in localStorage.
124
+ * write the cookie.
93
125
  */
94
126
  setPalette(palette, opts) {
95
127
  this.#palette = palette;
96
- if (opts?.persist !== false && typeof localStorage !== 'undefined') {
97
- localStorage.setItem(PALETTE_KEY, palette);
98
- }
128
+ if (opts?.persist !== false)
129
+ writeCookie(PALETTE_COOKIE, palette);
99
130
  applyPalette(palette);
100
131
  }
101
132
  toggle() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",