vueless 1.2.5-beta.1 → 1.2.5-beta.10

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/utils/theme.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { cloneDeep, merge } from "lodash-es";
2
2
 
3
3
  import { vuelessConfig } from "./ui";
4
- import { isCSR, setCookie } from "./helper";
4
+ import { isCSR, getStored, getCookie, setCookie, deleteCookie, toNumber } from "./helper";
5
5
 
6
6
  import {
7
7
  PX_IN_REM,
8
- COLOR_MODE_KEY,
9
8
  AUTO_MODE_KEY,
9
+ COLOR_MODE_KEY,
10
10
  LIGHT_MODE_CLASS,
11
11
  DARK_MODE_CLASS,
12
12
  GRAYSCALE_COLOR,
@@ -44,6 +44,7 @@ import type {
44
44
  ThemeConfigText,
45
45
  ThemeConfigOutline,
46
46
  ThemeConfigRounding,
47
+ MergedThemeConfig,
47
48
  VuelessCssVariables,
48
49
  } from "../types";
49
50
  import { ColorMode } from "../types";
@@ -60,6 +61,11 @@ declare interface RootCSSVariableOptions {
60
61
  darkTheme: Partial<VuelessCssVariables>;
61
62
  }
62
63
 
64
+ declare interface SetColorMode {
65
+ colorMode: `${ColorMode}`;
66
+ isColorModeAuto: boolean;
67
+ }
68
+
63
69
  /* Creates a media query that checks if the user's system color scheme is set to the dark. */
64
70
  const prefersColorSchemeDark = isCSR && window.matchMedia("(prefers-color-scheme: dark)");
65
71
 
@@ -69,23 +75,25 @@ function toggleColorModeClass() {
69
75
  const colorMode = prefersColorSchemeDark.matches ? ColorMode.Dark : ColorMode.Light;
70
76
 
71
77
  setCookie(COLOR_MODE_KEY, colorMode);
72
- localStorage.setItem(COLOR_MODE_KEY, colorMode);
78
+ setCookie(AUTO_MODE_KEY, String(Number(true)));
73
79
 
74
80
  document.documentElement.classList.toggle(DARK_MODE_CLASS, prefersColorSchemeDark.matches);
75
81
  document.documentElement.classList.toggle(LIGHT_MODE_CLASS, !prefersColorSchemeDark.matches);
76
82
  }
77
83
 
78
84
  /**
79
- * Sets color mode.
80
- * @param {string} mode (dark | light | auto)
81
- * @param {boolean} isCachedAutoMode
82
- * @return {string} current color mode
85
+ * Sets the client-side rendering (CSR) color mode by applying the specified mode,
86
+ * configuring the appropriate event listeners, setting CSS classes, and saving the mode
87
+ * in cookies and local storage.
88
+ *
89
+ * @param {`${ColorMode}`} mode - The desired color mode (dark | light | auto).
90
+ * @return {Object} An object containing:
91
+ * - `colorMode` {string}: The applied color mode (e.g., "light", "dark").
92
+ * - `isColorModeAuto` {boolean}: Indicates whether the color mode is set to auto.
83
93
  */
84
- function setColorMode(mode: `${ColorMode}`, isCachedAutoMode?: boolean): string {
85
- const colorMode = mode || getStored(COLOR_MODE_KEY) || vuelessConfig.colorMode || ColorMode.Light;
86
-
87
- // TODO: This always true `!!Number(getStored(AUTO_MODE_KEY))` > `!!Number(undefined) = true`
88
- isCachedAutoMode = isCachedAutoMode ?? !!Number(getStored(AUTO_MODE_KEY));
94
+ function setCSRColorMode(mode: `${ColorMode}`): SetColorMode {
95
+ const colorMode = mode || getCookie(COLOR_MODE_KEY) || vuelessConfig.colorMode || ColorMode.Light;
96
+ const isCachedAutoMode = !!Number(getCookie(AUTO_MODE_KEY) ?? 0);
89
97
 
90
98
  const isAutoMode = colorMode === ColorMode.Auto;
91
99
  const isSystemDarkMode = isAutoMode && prefersColorSchemeDark && prefersColorSchemeDark?.matches;
@@ -115,15 +123,39 @@ function setColorMode(mode: `${ColorMode}`, isCachedAutoMode?: boolean): string
115
123
  currentColorMode = isDarkMode ? ColorMode.Dark : ColorMode.Light;
116
124
  }
117
125
 
118
- if (mode) {
126
+ /* Define color mode cookies to be used in both CSR and SSR */
127
+ if (mode || getCookie(AUTO_MODE_KEY) === undefined) {
119
128
  setCookie(COLOR_MODE_KEY, currentColorMode);
120
- setCookie(AUTO_MODE_KEY, String(Number(isAutoMode || isCachedAutoMode)));
129
+ setCookie(AUTO_MODE_KEY, String(Number(isAutoMode)));
121
130
 
122
- localStorage.setItem(COLOR_MODE_KEY, currentColorMode);
123
- localStorage.setItem(AUTO_MODE_KEY, String(Number(isAutoMode || isCachedAutoMode)));
131
+ if (mode !== ColorMode.Auto && prefersColorSchemeDark) {
132
+ prefersColorSchemeDark.removeEventListener("change", toggleColorModeClass);
133
+ }
124
134
  }
125
135
 
126
- return currentColorMode;
136
+ return {
137
+ colorMode: currentColorMode,
138
+ isColorModeAuto: isAutoMode || isCachedAutoMode,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Gets server-side rendering (SSR) color mode.
144
+ *
145
+ * @param {`${ColorMode}`} mode - The desired color mode (dark | light | auto).
146
+ * @param {boolean} isColorModeAuto - Indicates whether the color mode is set to auto.
147
+ * @return {Object} An object containing:
148
+ * - `colorMode` {string}: The applied color mode (e.g., "light", "dark").
149
+ * - `isColorModeAuto` {boolean}: Indicates whether the color mode is set to auto.
150
+ */
151
+ function getSSRColorMode(mode: `${ColorMode}`, isColorModeAuto: boolean = false): SetColorMode {
152
+ const currentColorMode = mode || vuelessConfig.colorMode || ColorMode.Light;
153
+ const isAutoMode = currentColorMode === ColorMode.Auto;
154
+
155
+ return {
156
+ colorMode: currentColorMode,
157
+ isColorModeAuto: isAutoMode || isColorModeAuto,
158
+ };
127
159
  }
128
160
 
129
161
  /**
@@ -135,11 +167,72 @@ export function cssVar(name: string) {
135
167
  }
136
168
 
137
169
  /**
138
- * Get a stored value from local storage.
139
- * @return string | undefined
170
+ * Resets all theme data by clearing cookies and localStorage.
171
+ * This removes all stored theme preferences including color mode, colors, text sizes,
172
+ * outline sizes, rounding values, letter spacing, and disabled opacity.
140
173
  */
141
- export function getStored(key: string) {
142
- return isCSR ? localStorage.getItem(key) : undefined;
174
+ export function resetTheme() {
175
+ if (!isCSR) return;
176
+
177
+ const themeKeys = [
178
+ AUTO_MODE_KEY,
179
+ COLOR_MODE_KEY,
180
+ `vl-${PRIMARY_COLOR}`,
181
+ `vl-${NEUTRAL_COLOR}`,
182
+ `vl-${TEXT}-xs`,
183
+ `vl-${TEXT}-sm`,
184
+ `vl-${TEXT}-md`,
185
+ `vl-${TEXT}-lg`,
186
+ `vl-${OUTLINE}-sm`,
187
+ `vl-${OUTLINE}-md`,
188
+ `vl-${OUTLINE}-lg`,
189
+ `vl-${ROUNDING}-sm`,
190
+ `vl-${ROUNDING}-md`,
191
+ `vl-${ROUNDING}-lg`,
192
+ `vl-${LETTER_SPACING}`,
193
+ `vl-${DISABLED_OPACITY}`,
194
+ ];
195
+
196
+ themeKeys.forEach((key) => {
197
+ localStorage.removeItem(key);
198
+ deleteCookie(key);
199
+ });
200
+ }
201
+
202
+ /**
203
+ * Retrieves the current theme configuration.
204
+ * @return ThemeConfig - current theme configuration
205
+ */
206
+ export function getTheme(config?: ThemeConfig): MergedThemeConfig {
207
+ const { colorMode, isColorModeAuto } = isCSR
208
+ ? setCSRColorMode(config?.colorMode as ColorMode)
209
+ : getSSRColorMode(config?.colorMode as ColorMode, config?.isColorModeAuto);
210
+
211
+ const primary = getPrimaryColor(config?.primary);
212
+ const neutral = getNeutralColor(config?.neutral);
213
+
214
+ const text = getText(config?.text);
215
+ const outline = getOutlines(config?.outline);
216
+ const rounding = getRoundings(config?.rounding);
217
+ const letterSpacing = getLetterSpacing(config?.letterSpacing);
218
+ const disabledOpacity = getDisabledOpacity(config?.disabledOpacity);
219
+
220
+ const lightTheme = merge({}, DEFAULT_LIGHT_THEME, vuelessConfig.lightTheme);
221
+ const darkTheme = merge({}, DEFAULT_DARK_THEME, vuelessConfig.darkTheme);
222
+
223
+ return {
224
+ colorMode,
225
+ isColorModeAuto,
226
+ primary,
227
+ neutral,
228
+ text,
229
+ outline,
230
+ rounding,
231
+ letterSpacing,
232
+ disabledOpacity,
233
+ lightTheme,
234
+ darkTheme,
235
+ };
143
236
  }
144
237
 
145
238
  /**
@@ -147,8 +240,10 @@ export function getStored(key: string) {
147
240
  * Changes and reset Vueless CSS variables.
148
241
  * @return string - CSS variables
149
242
  */
150
- export function setTheme(config: ThemeConfig = {}, isCachedAutoMode?: boolean) {
151
- if (isCSR) setColorMode(config.colorMode as ColorMode, isCachedAutoMode);
243
+ export function setTheme(config: ThemeConfig = {}) {
244
+ isCSR
245
+ ? setCSRColorMode(config.colorMode as ColorMode)
246
+ : getSSRColorMode(config.colorMode as ColorMode);
152
247
 
153
248
  const text = getText(config.text);
154
249
  const outline = getOutlines(config.outline);
@@ -226,6 +321,45 @@ export function setTheme(config: ThemeConfig = {}, isCachedAutoMode?: boolean) {
226
321
  });
227
322
  }
228
323
 
324
+ /**
325
+ * Normalizes the provided theme configuration object into a structured format.
326
+ *
327
+ * @param {object} theme - The theme configuration object to normalize.
328
+ * @return {MergedThemeConfig}
329
+ */
330
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
+ export function normalizeThemeConfig(theme: any): MergedThemeConfig {
332
+ return {
333
+ colorMode: theme.colorMode,
334
+ isColorModeAuto: theme.isColorModeAuto,
335
+ primary: theme.primary,
336
+ neutral: theme.neutral,
337
+ text: {
338
+ xs: toNumber(theme.text?.xs),
339
+ sm: toNumber(theme.text?.sm),
340
+ md: toNumber(theme.text?.md),
341
+ lg: toNumber(theme.text?.lg),
342
+ },
343
+ outline: {
344
+ sm: toNumber(theme.outline?.sm),
345
+ md: toNumber(theme.outline?.md),
346
+ lg: toNumber(theme.outline?.lg),
347
+ },
348
+ rounding: {
349
+ sm: toNumber(theme.rounding?.sm),
350
+ md: toNumber(theme.rounding?.md),
351
+ lg: toNumber(theme.rounding?.lg),
352
+ },
353
+ letterSpacing: toNumber(theme.letterSpacing),
354
+ disabledOpacity: toNumber(theme.disabledOpacity),
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Determines if the provided color mode configuration has a primary color
360
+ * that differs from the default color mode configuration.
361
+ * @return {boolean}
362
+ */
229
363
  function hasPrimaryColor(
230
364
  colorModeConfig: Partial<VuelessCssVariables> | undefined,
231
365
  defaultColorModeConfig: Partial<VuelessCssVariables>,
@@ -306,6 +440,12 @@ function getText(text?: ThemeConfig["text"]) {
306
440
 
307
441
  const runtimeText = primitiveToObject(text) as ThemeConfigText;
308
442
  const globalText = primitiveToObject(vuelessConfig.text) as ThemeConfigText;
443
+ const storedText = {
444
+ xs: getStored(storageKey.xs),
445
+ sm: getStored(storageKey.sm),
446
+ md: getStored(storageKey.md),
447
+ lg: getStored(storageKey.lg),
448
+ };
309
449
 
310
450
  const textMd = Math.max(0, Number(runtimeText.md ?? globalText.md ?? DEFAULT_TEXT));
311
451
  const textXs = Math.max(0, textMd - TEXT_DECREMENT * 2);
@@ -319,20 +459,22 @@ function getText(text?: ThemeConfig["text"]) {
319
459
  lg: Math.max(0, Number(runtimeText.lg ?? getStored(storageKey.lg) ?? globalText.lg ?? 0)),
320
460
  };
321
461
 
462
+ /* eslint-disable prettier/prettier,vue/max-len */
322
463
  const mergedText = {
323
- xs: runtimeText.xs === undefined && globalText.xs === undefined ? textXs : definedText.xs,
324
- sm: runtimeText.sm === undefined && globalText.sm === undefined ? textSm : definedText.sm,
325
- md: runtimeText.md === undefined && globalText.md === undefined ? textMd : definedText.md,
326
- lg: runtimeText.lg === undefined && globalText.lg === undefined ? textLg : definedText.lg,
464
+ xs: runtimeText.xs === undefined && globalText.xs === undefined && (storedText.xs === undefined || typeof text === "number") ? textXs : definedText.xs,
465
+ sm: runtimeText.sm === undefined && globalText.sm === undefined && (storedText.sm === undefined || typeof text === "number") ? textSm : definedText.sm,
466
+ md: runtimeText.md === undefined && globalText.md === undefined && storedText.md === undefined ? textMd : definedText.md,
467
+ lg: runtimeText.lg === undefined && globalText.lg === undefined && (storedText.lg === undefined || typeof text === "number") ? textLg : definedText.lg,
327
468
  };
469
+ /* eslint-enable prettier/prettier,vue/max-len */
328
470
 
329
- if (isCSR && text) {
330
- setCookie(storageKey.sm, String(mergedText.xs));
471
+ if (isCSR && text !== undefined) {
472
+ setCookie(storageKey.xs, String(mergedText.xs));
331
473
  setCookie(storageKey.sm, String(mergedText.sm));
332
474
  setCookie(storageKey.md, String(mergedText.md));
333
475
  setCookie(storageKey.lg, String(mergedText.lg));
334
476
 
335
- localStorage.setItem(storageKey.sm, String(mergedText.xs));
477
+ localStorage.setItem(storageKey.xs, String(mergedText.xs));
336
478
  localStorage.setItem(storageKey.sm, String(mergedText.sm));
337
479
  localStorage.setItem(storageKey.md, String(mergedText.md));
338
480
  localStorage.setItem(storageKey.lg, String(mergedText.lg));
@@ -354,6 +496,11 @@ function getOutlines(outline?: ThemeConfig["outline"]) {
354
496
 
355
497
  const runtimeOutline = primitiveToObject(outline) as ThemeConfigOutline;
356
498
  const globalOutline = primitiveToObject(vuelessConfig.outline) as ThemeConfigOutline;
499
+ const storedOutline = {
500
+ sm: getStored(storageKey.sm),
501
+ md: getStored(storageKey.md),
502
+ lg: getStored(storageKey.lg),
503
+ };
357
504
 
358
505
  const outlineMd = Math.max(0, Number(runtimeOutline.md ?? globalOutline.md ?? DEFAULT_OUTLINE));
359
506
  const outlineSm = Math.max(0, outlineMd - OUTLINE_DECREMENT);
@@ -369,15 +516,15 @@ function getOutlines(outline?: ThemeConfig["outline"]) {
369
516
  lg: Math.max(0, Number(runtimeOutline.lg ?? getStored(storageKey.lg) ?? globalOutline.lg ?? 0)),
370
517
  };
371
518
 
372
- /* eslint-disable prettier/prettier */
519
+ /* eslint-disable prettier/prettier,vue/max-len */
373
520
  const mergedOutline = {
374
- sm: runtimeOutline.sm === undefined && globalOutline.sm === undefined ? outlineSm : definedOutline.sm,
375
- md: runtimeOutline.md === undefined && globalOutline.md === undefined ? outlineMd : definedOutline.md,
376
- lg: runtimeOutline.lg === undefined && globalOutline.lg === undefined ? outlineLg : definedOutline.lg,
521
+ sm: runtimeOutline.sm === undefined && globalOutline.sm === undefined && (storedOutline.sm === undefined || typeof outline === "number") ? outlineSm : definedOutline.sm,
522
+ md: runtimeOutline.md === undefined && globalOutline.md === undefined && storedOutline.md === undefined ? outlineMd : definedOutline.md,
523
+ lg: runtimeOutline.lg === undefined && globalOutline.lg === undefined && (storedOutline.lg === undefined || typeof outline === "number") ? outlineLg : definedOutline.lg,
377
524
  };
378
- /* eslint-enable prettier/prettier */
525
+ /* eslint-enable prettier/prettier,vue/max-len */
379
526
 
380
- if (isCSR && outline) {
527
+ if (isCSR && outline !== undefined) {
381
528
  setCookie(storageKey.sm, String(mergedOutline.sm));
382
529
  setCookie(storageKey.md, String(mergedOutline.md));
383
530
  setCookie(storageKey.lg, String(mergedOutline.lg));
@@ -403,6 +550,11 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
403
550
 
404
551
  const runtimeRounding = primitiveToObject(rounding) as ThemeConfigRounding;
405
552
  const globalRounding = primitiveToObject(vuelessConfig.rounding) as ThemeConfigRounding;
553
+ const storedRounding = {
554
+ sm: getStored(storageKey.sm),
555
+ md: getStored(storageKey.md),
556
+ lg: getStored(storageKey.lg),
557
+ };
406
558
 
407
559
  // eslint-disable-next-line prettier/prettier
408
560
  const roundingMd = Math.max(0, Number(runtimeRounding.md ?? globalRounding.md ?? DEFAULT_ROUNDING));
@@ -421,7 +573,7 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
421
573
  roundingLg = ROUNDING_INCREMENT - ROUNDING_DECREMENT;
422
574
  }
423
575
 
424
- /* eslint-disable prettier/prettier */
576
+ /* eslint-disable prettier/prettier,vue/max-len */
425
577
  const definedRounding = {
426
578
  sm: Math.max(0, Number(runtimeRounding.sm ?? getStored(storageKey.sm) ?? globalRounding.sm ?? 0)),
427
579
  md: Math.max(0, Number(runtimeRounding.md ?? getStored(storageKey.md) ?? globalRounding.md ?? 0)),
@@ -429,13 +581,13 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
429
581
  };
430
582
 
431
583
  const mergedRounding = {
432
- sm: runtimeRounding.sm === undefined && globalRounding.sm === undefined ? roundingSm : definedRounding.sm,
433
- md: runtimeRounding.md === undefined && globalRounding.md === undefined ? roundingMd : definedRounding.md,
434
- lg: runtimeRounding.lg === undefined && globalRounding.lg === undefined ? roundingLg : definedRounding.lg,
584
+ sm: runtimeRounding.sm === undefined && globalRounding.sm === undefined && (storedRounding.sm === undefined || typeof rounding === "number") ? roundingSm : definedRounding.sm,
585
+ md: runtimeRounding.md === undefined && globalRounding.md === undefined && storedRounding.md === undefined ? roundingMd : definedRounding.md,
586
+ lg: runtimeRounding.lg === undefined && globalRounding.lg === undefined && (storedRounding.lg === undefined || typeof rounding === "number") ? roundingLg : definedRounding.lg,
435
587
  };
436
- /* eslint-enable prettier/prettier */
588
+ /* eslint-enable prettier/prettier,vue/max-len */
437
589
 
438
- if (isCSR && rounding) {
590
+ if (isCSR && rounding !== undefined) {
439
591
  setCookie(storageKey.sm, String(mergedRounding.sm));
440
592
  setCookie(storageKey.md, String(mergedRounding.md));
441
593
  setCookie(storageKey.lg, String(mergedRounding.lg));
@@ -456,9 +608,9 @@ function getLetterSpacing(letterSpacing?: ThemeConfig["letterSpacing"]) {
456
608
  const storageKey = `vl-${LETTER_SPACING}`;
457
609
 
458
610
  const spacing = letterSpacing ?? getStored(storageKey) ?? vuelessConfig.letterSpacing;
459
- const mergedSpacing = Number(spacing ?? DEFAULT_LETTER_SPACING);
611
+ const mergedSpacing = Math.max(0, Number(spacing ?? DEFAULT_LETTER_SPACING));
460
612
 
461
- if (isCSR && letterSpacing) {
613
+ if (isCSR && letterSpacing !== undefined) {
462
614
  setCookie(storageKey, String(mergedSpacing));
463
615
  localStorage.setItem(storageKey, String(mergedSpacing));
464
616
  }
@@ -476,7 +628,7 @@ function getDisabledOpacity(disabledOpacity?: ThemeConfig["disabledOpacity"]) {
476
628
  const opacity = disabledOpacity ?? getStored(storageKey) ?? vuelessConfig.disabledOpacity;
477
629
  const mergedOpacity = Math.max(0, Number(opacity ?? DEFAULT_DISABLED_OPACITY));
478
630
 
479
- if (isCSR && disabledOpacity) {
631
+ if (isCSR && disabledOpacity !== undefined) {
480
632
  setCookie(storageKey, String(mergedOpacity));
481
633
  localStorage.setItem(storageKey, String(mergedOpacity));
482
634
  }
@@ -484,14 +636,6 @@ function getDisabledOpacity(disabledOpacity?: ThemeConfig["disabledOpacity"]) {
484
636
  return mergedOpacity;
485
637
  }
486
638
 
487
- /**
488
- * Converts a primitive value into an object with the primitive value assigned to a key "md".
489
- * If the provided value is already an object, it returns a deeply cloned copy of that object.
490
- */
491
- function primitiveToObject(value: unknown): object {
492
- return typeof value === "object" ? cloneDeep(value as object) : { md: value };
493
- }
494
-
495
639
  /**
496
640
  * Generate and apply Vueless CSS variables.
497
641
  * @return string - Vueless CSS variables string.
@@ -588,3 +732,11 @@ function setCSSVariables(
588
732
 
589
733
  return rootVariables;
590
734
  }
735
+
736
+ /**
737
+ * Converts a primitive value into an object with the primitive value assigned to a key "md".
738
+ * If the provided value is already an object, it returns a deeply cloned copy of that object.
739
+ */
740
+ function primitiveToObject(value: unknown): object {
741
+ return typeof value === "object" ? cloneDeep(value as object) : { md: value };
742
+ }
package/utils/ui.ts CHANGED
@@ -49,9 +49,14 @@ if (isSSR) {
49
49
  /* Load Tailwind config from the project root in IIFE (no top-level await). */
50
50
  (async () => {
51
51
  try {
52
+ const { readFile } = await import("node:fs/promises");
53
+
54
+ const path = `${process.cwd()}/${VUELESS_CACHE_DIR}/${VUELESS_CONFIG_FILE_NAME}.mjs`;
55
+ const code = await readFile(path, "utf8");
56
+
52
57
  vuelessConfig = (
53
58
  await import(
54
- /* @vite-ignore */ `${process.cwd()}/${VUELESS_CACHE_DIR}/${VUELESS_CONFIG_FILE_NAME}.mjs`
59
+ /* @vite-ignore */ `data:text/javascript;base64,${Buffer.from(code).toString("base64")}`
55
60
  )
56
61
  ).default;
57
62
  } catch {