vueless 1.2.5-beta.2 → 1.2.5-beta.4

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/constants.d.ts CHANGED
@@ -293,11 +293,9 @@ export namespace DEFAULT_SVGO_CONFIG {
293
293
  let plugins: {
294
294
  name: string;
295
295
  params: {
296
- overrides: {
297
- convertColors: {
298
- currentColor: boolean;
299
- };
300
- };
296
+ attributes: {
297
+ fill: string;
298
+ }[];
301
299
  };
302
300
  }[];
303
301
  }
package/constants.js CHANGED
@@ -368,13 +368,9 @@ export const TAILWIND_MERGE_EXTENSION = {
368
368
  export const DEFAULT_SVGO_CONFIG = {
369
369
  plugins: [
370
370
  {
371
- name: "preset-default",
371
+ name: "addAttributesToSVGElement",
372
372
  params: {
373
- overrides: {
374
- convertColors: {
375
- currentColor: true,
376
- },
377
- },
373
+ attributes: [{ fill: "currentColor" }],
378
374
  },
379
375
  },
380
376
  ],
package/index.d.ts CHANGED
@@ -8,15 +8,12 @@ export {
8
8
  isCSR,
9
9
  setTitle,
10
10
  getRandomId,
11
- getCookie,
12
- setCookie,
13
- deleteCookie,
14
11
  createDebounce,
15
12
  hasSlotContent
16
13
  } from "./utils/helper";
17
- export { getStored, setTheme, cssVar } from "./utils/theme";
18
14
  export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
19
15
  export { cx, cva, compose, getDefaults, setVuelessConfig, setColor, vuelessConfig } from "./utils/ui";
16
+ export { getTheme, setTheme, resetTheme, normalizeThemeConfig, getStored, cssVar } from "./utils/theme";
20
17
  export { getArgTypes, getSlotNames, getSlotsFragment, getSource, getDocsDescription } from "./utils/storybook";
21
18
  /* adapters */
22
19
  export { default as defaultEnLocale } from "./adapter.locale/locales/en";
@@ -133,6 +130,7 @@ export type {
133
130
  ThemeConfigText,
134
131
  ThemeConfigRounding,
135
132
  ThemeConfigOutline,
133
+ MergedThemeConfig,
136
134
  NestedComponent,
137
135
  ComponentConfig,
138
136
  ComponentDefaults,
package/index.ts CHANGED
@@ -14,15 +14,12 @@ export {
14
14
  isCSR,
15
15
  setTitle,
16
16
  getRandomId,
17
- getCookie,
18
- setCookie,
19
- deleteCookie,
20
17
  createDebounce,
21
18
  hasSlotContent
22
19
  } from "./utils/helper";
23
- export { getStored, setTheme, cssVar } from "./utils/theme";
24
20
  export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
25
21
  export { cx, cva, compose, getDefaults, setVuelessConfig, setColor, vuelessConfig } from "./utils/ui";
22
+ export { getTheme, setTheme, resetTheme, normalizeThemeConfig, getStored, cssVar } from "./utils/theme";
26
23
  export { getArgTypes, getSlotNames, getSlotsFragment, getSource, getDocsDescription } from "./utils/storybook";
27
24
  /* adapters */
28
25
  export { default as defaultEnLocale } from "./adapter.locale/locales/en";
@@ -139,6 +136,7 @@ export type {
139
136
  ThemeConfigText,
140
137
  ThemeConfigRounding,
141
138
  ThemeConfigOutline,
139
+ MergedThemeConfig,
142
140
  NestedComponent,
143
141
  ComponentConfig,
144
142
  ComponentDefaults,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.2.5-beta.2",
3
+ "version": "1.2.5-beta.4",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
@@ -56,7 +56,7 @@
56
56
  "@vue/eslint-config-typescript": "^14.6.0",
57
57
  "@vue/test-utils": "^2.4.6",
58
58
  "@vue/tsconfig": "^0.7.0",
59
- "@vueless/storybook": "^1.2.1",
59
+ "@vueless/storybook": "^1.2.2",
60
60
  "eslint": "^9.32.0",
61
61
  "eslint-plugin-storybook": "^9.0.18",
62
62
  "eslint-plugin-vue": "^10.3.0",
package/types.ts CHANGED
@@ -129,6 +129,11 @@ export interface ThemeConfig {
129
129
  */
130
130
  colorMode?: `${ColorMode}`;
131
131
 
132
+ /**
133
+ * Defines the color mode to auto.
134
+ */
135
+ isColorModeAuto?: boolean;
136
+
132
137
  /**
133
138
  * Light theme design system CSS variables.
134
139
  */
@@ -185,6 +190,12 @@ export interface Config extends ThemeConfig {
185
190
  tailwindMerge?: UnknownObject;
186
191
  }
187
192
 
193
+ export type MergedThemeConfig = Omit<ThemeConfig, "text | outline | rounding"> & {
194
+ text: Partial<ThemeConfigText>;
195
+ outline: Partial<ThemeConfigOutline>;
196
+ rounding: Partial<ThemeConfigRounding>;
197
+ };
198
+
188
199
  export type UnknownObject = Record<string, unknown>;
189
200
  export type UnknownArray = unknown[];
190
201
  export type UnknownType = string | number | boolean | UnknownObject | undefined | null;
@@ -1,6 +1,6 @@
1
1
  export default /*tw*/ {
2
2
  icon: {
3
- base: "text-{color} fill-current shrink-0 grow-0 focus:outline-0",
3
+ base: "text-{color} shrink-0 grow-0 focus:outline-0",
4
4
  variants: {
5
5
  variant: {
6
6
  light: "brightness-125",
@@ -2,13 +2,15 @@ import fs from "node:fs";
2
2
  import { compileTemplate } from "vue/compiler-sfc";
3
3
  import { optimize as optimizeSvg } from "svgo";
4
4
 
5
+ import { getVuelessConfig } from "./vuelessConfig.js";
5
6
  import { DEFAULT_SVGO_CONFIG } from "../../constants.js";
6
7
 
7
8
  export async function loadSvg(id, options) {
8
- const {
9
+ let {
10
+ basePath = "",
9
11
  defaultImport = "url",
12
+ svgoConfig = {},
10
13
  svgo = true,
11
- svgoConfig = DEFAULT_SVGO_CONFIG,
12
14
  debug = false,
13
15
  } = options;
14
16
  const svgRegex = /\.svg(\?(raw|url|component|skipsvgo))?$/;
@@ -45,7 +47,11 @@ export async function loadSvg(id, options) {
45
47
  }
46
48
 
47
49
  if (svgo !== false && query !== "skipsvgo") {
50
+ const vuelessConfig = getVuelessConfig(basePath);
51
+ const isLucideLibrary = vuelessConfig.components?.UIcon?.defaults?.library === "lucide-static";
52
+
48
53
  svg = optimizeSvg(svg, {
54
+ ...(isLucideLibrary ? {} : DEFAULT_SVGO_CONFIG),
49
55
  ...svgoConfig,
50
56
  svgPath,
51
57
  }).data;
package/utils/theme.ts CHANGED
@@ -1,12 +1,11 @@
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, isSSR, setCookie, deleteCookie } from "./helper";
5
5
 
6
6
  import {
7
7
  PX_IN_REM,
8
8
  COLOR_MODE_KEY,
9
- AUTO_MODE_KEY,
10
9
  LIGHT_MODE_CLASS,
11
10
  DARK_MODE_CLASS,
12
11
  GRAYSCALE_COLOR,
@@ -35,6 +34,7 @@ import {
35
34
  DEFAULT_DISABLED_OPACITY,
36
35
  LETTER_SPACING,
37
36
  DEFAULT_LETTER_SPACING,
37
+ AUTO_MODE_KEY,
38
38
  } from "../constants";
39
39
 
40
40
  import type {
@@ -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
 
@@ -76,16 +82,18 @@ function toggleColorModeClass() {
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 {
94
+ function setCSRColorMode(mode: `${ColorMode}`): SetColorMode {
85
95
  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));
96
+ const isCachedAutoMode = !!Number(getStored(AUTO_MODE_KEY) ?? 0);
89
97
 
90
98
  const isAutoMode = colorMode === ColorMode.Auto;
91
99
  const isSystemDarkMode = isAutoMode && prefersColorSchemeDark && prefersColorSchemeDark?.matches;
@@ -117,13 +125,35 @@ function setColorMode(mode: `${ColorMode}`, isCachedAutoMode?: boolean): string
117
125
 
118
126
  if (mode) {
119
127
  setCookie(COLOR_MODE_KEY, currentColorMode);
120
- setCookie(AUTO_MODE_KEY, String(Number(isAutoMode || isCachedAutoMode)));
128
+ setCookie(AUTO_MODE_KEY, String(Number(isAutoMode)));
121
129
 
122
130
  localStorage.setItem(COLOR_MODE_KEY, currentColorMode);
123
- localStorage.setItem(AUTO_MODE_KEY, String(Number(isAutoMode || isCachedAutoMode)));
131
+ localStorage.setItem(AUTO_MODE_KEY, String(Number(isAutoMode)));
124
132
  }
125
133
 
126
- return currentColorMode;
134
+ return {
135
+ colorMode: currentColorMode,
136
+ isColorModeAuto: isAutoMode || isCachedAutoMode,
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Gets server-side rendering (SSR) color mode.
142
+ *
143
+ * @param {`${ColorMode}`} mode - The desired color mode (dark | light | auto).
144
+ * @param {boolean} isColorModeAuto - Indicates whether the color mode is set to auto.
145
+ * @return {Object} An object containing:
146
+ * - `colorMode` {string}: The applied color mode (e.g., "light", "dark").
147
+ * - `isColorModeAuto` {boolean}: Indicates whether the color mode is set to auto.
148
+ */
149
+ function getSSRColorMode(mode: `${ColorMode}`, isColorModeAuto: boolean = false): SetColorMode {
150
+ const currentColorMode = mode || vuelessConfig.colorMode || ColorMode.Light;
151
+ const isAutoMode = currentColorMode === ColorMode.Auto;
152
+
153
+ return {
154
+ colorMode: currentColorMode,
155
+ isColorModeAuto: isAutoMode || isColorModeAuto,
156
+ };
127
157
  }
128
158
 
129
159
  /**
@@ -139,7 +169,112 @@ export function cssVar(name: string) {
139
169
  * @return string | undefined
140
170
  */
141
171
  export function getStored(key: string) {
142
- return isCSR ? localStorage.getItem(key) : undefined;
172
+ if (isSSR) return;
173
+
174
+ return localStorage.getItem(key) ?? undefined;
175
+ }
176
+
177
+ /**
178
+ * Resets all theme data by clearing cookies and localStorage.
179
+ * This removes all stored theme preferences including color mode, colors, text sizes,
180
+ * outline sizes, rounding values, letter spacing, and disabled opacity.
181
+ */
182
+ export function resetTheme() {
183
+ if (!isCSR) return;
184
+
185
+ const themeKeys = [
186
+ AUTO_MODE_KEY,
187
+ COLOR_MODE_KEY,
188
+ `vl-${PRIMARY_COLOR}`,
189
+ `vl-${NEUTRAL_COLOR}`,
190
+ `vl-${TEXT}-xs`,
191
+ `vl-${TEXT}-sm`,
192
+ `vl-${TEXT}-md`,
193
+ `vl-${TEXT}-lg`,
194
+ `vl-${OUTLINE}-sm`,
195
+ `vl-${OUTLINE}-md`,
196
+ `vl-${OUTLINE}-lg`,
197
+ `vl-${ROUNDING}-sm`,
198
+ `vl-${ROUNDING}-md`,
199
+ `vl-${ROUNDING}-lg`,
200
+ `vl-${LETTER_SPACING}`,
201
+ `vl-${DISABLED_OPACITY}`,
202
+ ];
203
+
204
+ themeKeys.forEach((key) => {
205
+ localStorage.removeItem(key);
206
+ deleteCookie(key);
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Normalizes the provided theme configuration object into a structured format.
212
+ *
213
+ * @param {object} theme - The theme configuration object to normalize.
214
+ * @return {MergedThemeConfig}
215
+ */
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ export function normalizeThemeConfig(theme: any): MergedThemeConfig {
218
+ return {
219
+ colorMode: theme.colorMode,
220
+ isColorModeAuto: theme.isColorModeAuto,
221
+ primary: theme.primary,
222
+ neutral: theme.neutral,
223
+ text: {
224
+ xs: toNumber(theme.text?.xs),
225
+ sm: toNumber(theme.text?.sm),
226
+ md: toNumber(theme.text?.md),
227
+ lg: toNumber(theme.text?.lg),
228
+ },
229
+ outline: {
230
+ sm: toNumber(theme.outline?.sm),
231
+ md: toNumber(theme.outline?.md),
232
+ lg: toNumber(theme.outline?.lg),
233
+ },
234
+ rounding: {
235
+ sm: toNumber(theme.rounding?.sm),
236
+ md: toNumber(theme.rounding?.md),
237
+ lg: toNumber(theme.rounding?.lg),
238
+ },
239
+ letterSpacing: toNumber(theme.letterSpacing),
240
+ disabledOpacity: toNumber(theme.disabledOpacity),
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Retrieves the current theme configuration.
246
+ * @return ThemeConfig - current theme configuration
247
+ */
248
+ export function getTheme(config?: ThemeConfig): MergedThemeConfig {
249
+ const { colorMode, isColorModeAuto } = isCSR
250
+ ? setCSRColorMode(config?.colorMode as ColorMode)
251
+ : getSSRColorMode(config?.colorMode as ColorMode, config?.isColorModeAuto);
252
+
253
+ const primary = getPrimaryColor(config?.primary);
254
+ const neutral = getNeutralColor(config?.neutral);
255
+
256
+ const text = getText(config?.text);
257
+ const outline = getOutlines(config?.outline);
258
+ const rounding = getRoundings(config?.rounding);
259
+ const letterSpacing = getLetterSpacing(config?.letterSpacing);
260
+ const disabledOpacity = getDisabledOpacity(config?.disabledOpacity);
261
+
262
+ const lightTheme = merge({}, DEFAULT_LIGHT_THEME, vuelessConfig.lightTheme);
263
+ const darkTheme = merge({}, DEFAULT_DARK_THEME, vuelessConfig.darkTheme);
264
+
265
+ return {
266
+ colorMode,
267
+ isColorModeAuto,
268
+ primary,
269
+ neutral,
270
+ text,
271
+ outline,
272
+ rounding,
273
+ letterSpacing,
274
+ disabledOpacity,
275
+ lightTheme,
276
+ darkTheme,
277
+ };
143
278
  }
144
279
 
145
280
  /**
@@ -147,8 +282,10 @@ export function getStored(key: string) {
147
282
  * Changes and reset Vueless CSS variables.
148
283
  * @return string - CSS variables
149
284
  */
150
- export function setTheme(config: ThemeConfig = {}, isCachedAutoMode?: boolean) {
151
- if (isCSR) setColorMode(config.colorMode as ColorMode, isCachedAutoMode);
285
+ export function setTheme(config: ThemeConfig = {}) {
286
+ isCSR
287
+ ? setCSRColorMode(config.colorMode as ColorMode)
288
+ : getSSRColorMode(config.colorMode as ColorMode);
152
289
 
153
290
  const text = getText(config.text);
154
291
  const outline = getOutlines(config.outline);
@@ -306,6 +443,12 @@ function getText(text?: ThemeConfig["text"]) {
306
443
 
307
444
  const runtimeText = primitiveToObject(text) as ThemeConfigText;
308
445
  const globalText = primitiveToObject(vuelessConfig.text) as ThemeConfigText;
446
+ const storedText = {
447
+ xs: getStored(storageKey.xs),
448
+ sm: getStored(storageKey.sm),
449
+ md: getStored(storageKey.md),
450
+ lg: getStored(storageKey.lg),
451
+ };
309
452
 
310
453
  const textMd = Math.max(0, Number(runtimeText.md ?? globalText.md ?? DEFAULT_TEXT));
311
454
  const textXs = Math.max(0, textMd - TEXT_DECREMENT * 2);
@@ -319,20 +462,22 @@ function getText(text?: ThemeConfig["text"]) {
319
462
  lg: Math.max(0, Number(runtimeText.lg ?? getStored(storageKey.lg) ?? globalText.lg ?? 0)),
320
463
  };
321
464
 
465
+ /* eslint-disable prettier/prettier,vue/max-len */
322
466
  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,
467
+ xs: runtimeText.xs === undefined && globalText.xs === undefined && (storedText.xs === undefined || typeof text === "number") ? textXs : definedText.xs,
468
+ sm: runtimeText.sm === undefined && globalText.sm === undefined && (storedText.sm === undefined || typeof text === "number") ? textSm : definedText.sm,
469
+ md: runtimeText.md === undefined && globalText.md === undefined && storedText.md === undefined ? textMd : definedText.md,
470
+ lg: runtimeText.lg === undefined && globalText.lg === undefined && (storedText.lg === undefined || typeof text === "number") ? textLg : definedText.lg,
327
471
  };
472
+ /* eslint-enable prettier/prettier,vue/max-len */
328
473
 
329
- if (isCSR && text) {
330
- setCookie(storageKey.sm, String(mergedText.xs));
474
+ if (isCSR && text !== undefined) {
475
+ setCookie(storageKey.xs, String(mergedText.xs));
331
476
  setCookie(storageKey.sm, String(mergedText.sm));
332
477
  setCookie(storageKey.md, String(mergedText.md));
333
478
  setCookie(storageKey.lg, String(mergedText.lg));
334
479
 
335
- localStorage.setItem(storageKey.sm, String(mergedText.xs));
480
+ localStorage.setItem(storageKey.xs, String(mergedText.xs));
336
481
  localStorage.setItem(storageKey.sm, String(mergedText.sm));
337
482
  localStorage.setItem(storageKey.md, String(mergedText.md));
338
483
  localStorage.setItem(storageKey.lg, String(mergedText.lg));
@@ -354,6 +499,11 @@ function getOutlines(outline?: ThemeConfig["outline"]) {
354
499
 
355
500
  const runtimeOutline = primitiveToObject(outline) as ThemeConfigOutline;
356
501
  const globalOutline = primitiveToObject(vuelessConfig.outline) as ThemeConfigOutline;
502
+ const storedOutline = {
503
+ sm: getStored(storageKey.sm),
504
+ md: getStored(storageKey.md),
505
+ lg: getStored(storageKey.lg),
506
+ };
357
507
 
358
508
  const outlineMd = Math.max(0, Number(runtimeOutline.md ?? globalOutline.md ?? DEFAULT_OUTLINE));
359
509
  const outlineSm = Math.max(0, outlineMd - OUTLINE_DECREMENT);
@@ -369,15 +519,15 @@ function getOutlines(outline?: ThemeConfig["outline"]) {
369
519
  lg: Math.max(0, Number(runtimeOutline.lg ?? getStored(storageKey.lg) ?? globalOutline.lg ?? 0)),
370
520
  };
371
521
 
372
- /* eslint-disable prettier/prettier */
522
+ /* eslint-disable prettier/prettier,vue/max-len */
373
523
  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,
524
+ sm: runtimeOutline.sm === undefined && globalOutline.sm === undefined && (storedOutline.sm === undefined || typeof outline === "number") ? outlineSm : definedOutline.sm,
525
+ md: runtimeOutline.md === undefined && globalOutline.md === undefined && storedOutline.md === undefined ? outlineMd : definedOutline.md,
526
+ lg: runtimeOutline.lg === undefined && globalOutline.lg === undefined && (storedOutline.lg === undefined || typeof outline === "number") ? outlineLg : definedOutline.lg,
377
527
  };
378
- /* eslint-enable prettier/prettier */
528
+ /* eslint-enable prettier/prettier,vue/max-len */
379
529
 
380
- if (isCSR && outline) {
530
+ if (isCSR && outline !== undefined) {
381
531
  setCookie(storageKey.sm, String(mergedOutline.sm));
382
532
  setCookie(storageKey.md, String(mergedOutline.md));
383
533
  setCookie(storageKey.lg, String(mergedOutline.lg));
@@ -403,6 +553,11 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
403
553
 
404
554
  const runtimeRounding = primitiveToObject(rounding) as ThemeConfigRounding;
405
555
  const globalRounding = primitiveToObject(vuelessConfig.rounding) as ThemeConfigRounding;
556
+ const storedRounding = {
557
+ sm: getStored(storageKey.sm),
558
+ md: getStored(storageKey.md),
559
+ lg: getStored(storageKey.lg),
560
+ };
406
561
 
407
562
  // eslint-disable-next-line prettier/prettier
408
563
  const roundingMd = Math.max(0, Number(runtimeRounding.md ?? globalRounding.md ?? DEFAULT_ROUNDING));
@@ -421,7 +576,7 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
421
576
  roundingLg = ROUNDING_INCREMENT - ROUNDING_DECREMENT;
422
577
  }
423
578
 
424
- /* eslint-disable prettier/prettier */
579
+ /* eslint-disable prettier/prettier,vue/max-len */
425
580
  const definedRounding = {
426
581
  sm: Math.max(0, Number(runtimeRounding.sm ?? getStored(storageKey.sm) ?? globalRounding.sm ?? 0)),
427
582
  md: Math.max(0, Number(runtimeRounding.md ?? getStored(storageKey.md) ?? globalRounding.md ?? 0)),
@@ -429,13 +584,13 @@ function getRoundings(rounding?: ThemeConfig["rounding"]) {
429
584
  };
430
585
 
431
586
  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,
587
+ sm: runtimeRounding.sm === undefined && globalRounding.sm === undefined && (storedRounding.sm === undefined || typeof rounding === "number") ? roundingSm : definedRounding.sm,
588
+ md: runtimeRounding.md === undefined && globalRounding.md === undefined && storedRounding.md === undefined ? roundingMd : definedRounding.md,
589
+ lg: runtimeRounding.lg === undefined && globalRounding.lg === undefined && (storedRounding.lg === undefined || typeof rounding === "number") ? roundingLg : definedRounding.lg,
435
590
  };
436
- /* eslint-enable prettier/prettier */
591
+ /* eslint-enable prettier/prettier,vue/max-len */
437
592
 
438
- if (isCSR && rounding) {
593
+ if (isCSR && rounding !== undefined) {
439
594
  setCookie(storageKey.sm, String(mergedRounding.sm));
440
595
  setCookie(storageKey.md, String(mergedRounding.md));
441
596
  setCookie(storageKey.lg, String(mergedRounding.lg));
@@ -456,9 +611,9 @@ function getLetterSpacing(letterSpacing?: ThemeConfig["letterSpacing"]) {
456
611
  const storageKey = `vl-${LETTER_SPACING}`;
457
612
 
458
613
  const spacing = letterSpacing ?? getStored(storageKey) ?? vuelessConfig.letterSpacing;
459
- const mergedSpacing = Number(spacing ?? DEFAULT_LETTER_SPACING);
614
+ const mergedSpacing = Math.max(0, Number(spacing ?? DEFAULT_LETTER_SPACING));
460
615
 
461
- if (isCSR && letterSpacing) {
616
+ if (isCSR && letterSpacing !== undefined) {
462
617
  setCookie(storageKey, String(mergedSpacing));
463
618
  localStorage.setItem(storageKey, String(mergedSpacing));
464
619
  }
@@ -476,7 +631,7 @@ function getDisabledOpacity(disabledOpacity?: ThemeConfig["disabledOpacity"]) {
476
631
  const opacity = disabledOpacity ?? getStored(storageKey) ?? vuelessConfig.disabledOpacity;
477
632
  const mergedOpacity = Math.max(0, Number(opacity ?? DEFAULT_DISABLED_OPACITY));
478
633
 
479
- if (isCSR && disabledOpacity) {
634
+ if (isCSR && disabledOpacity !== undefined) {
480
635
  setCookie(storageKey, String(mergedOpacity));
481
636
  localStorage.setItem(storageKey, String(mergedOpacity));
482
637
  }
@@ -588,3 +743,25 @@ function setCSSVariables(
588
743
 
589
744
  return rootVariables;
590
745
  }
746
+
747
+ /**
748
+ * Converts the given value to a number if possible.
749
+ *
750
+ * @param {unknown} value - The value to be converted to a number. Can be of any data type.
751
+ * @return {number | undefined} The numeric representation of the value if conversion is successful; otherwise, undefined.
752
+ */
753
+ function toNumber(value: unknown): number | undefined {
754
+ if (typeof value === "number") {
755
+ return value;
756
+ }
757
+
758
+ if (typeof value === "string" && value.trim() !== "") {
759
+ const number = Number(value);
760
+
761
+ if (!Number.isNaN(number)) {
762
+ return number;
763
+ }
764
+ }
765
+
766
+ return;
767
+ }