vueless 1.2.8 → 1.2.10-beta.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.
Files changed (78) hide show
  1. package/constants.d.ts +4 -0
  2. package/constants.js +4 -0
  3. package/icons/storybook/rocket_launch.svg +1 -0
  4. package/index.d.ts +3 -1
  5. package/index.ts +3 -1
  6. package/package.json +9 -5
  7. package/plugin-vite.js +6 -1
  8. package/types.ts +14 -2
  9. package/ui.button/config.ts +4 -4
  10. package/ui.button/tests/UButton.test.ts +3 -3
  11. package/ui.button-toggle/config.ts +2 -2
  12. package/ui.container-accordion/UAccordion.vue +0 -1
  13. package/ui.container-accordion/config.ts +1 -1
  14. package/ui.container-accordion/storybook/stories.ts +13 -1
  15. package/ui.container-accordion-item/UAccordionItem.vue +17 -4
  16. package/ui.container-accordion-item/config.ts +1 -1
  17. package/ui.container-accordion-item/storybook/stories.ts +26 -1
  18. package/ui.container-accordion-item/tests/UAccordionItem.test.ts +186 -0
  19. package/ui.container-card/config.ts +1 -1
  20. package/ui.data-table/config.ts +4 -4
  21. package/ui.dropdown-badge/UDropdownBadge.vue +68 -3
  22. package/ui.dropdown-badge/config.ts +5 -1
  23. package/ui.dropdown-badge/storybook/stories.ts +280 -4
  24. package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +194 -0
  25. package/ui.dropdown-badge/types.ts +30 -0
  26. package/ui.dropdown-button/UDropdownButton.vue +69 -6
  27. package/ui.dropdown-button/config.ts +5 -1
  28. package/ui.dropdown-button/storybook/stories.ts +288 -3
  29. package/ui.dropdown-button/tests/UDropdownButton.test.ts +190 -0
  30. package/ui.dropdown-button/types.ts +30 -0
  31. package/ui.dropdown-link/UDropdownLink.vue +69 -6
  32. package/ui.dropdown-link/config.ts +5 -1
  33. package/ui.dropdown-link/storybook/stories.ts +281 -4
  34. package/ui.dropdown-link/tests/UDropdownLink.test.ts +194 -0
  35. package/ui.dropdown-link/types.ts +30 -0
  36. package/ui.form-calendar/config.ts +4 -2
  37. package/ui.form-checkbox/config.ts +1 -1
  38. package/ui.form-checkbox/tests/UCheckbox.test.ts +2 -2
  39. package/ui.form-checkbox-group/tests/UCheckboxGroup.test.ts +2 -2
  40. package/ui.form-date-picker-range/config.ts +1 -1
  41. package/ui.form-input/UInput.vue +4 -2
  42. package/ui.form-input/config.ts +1 -1
  43. package/ui.form-input/tests/UInput.test.ts +2 -2
  44. package/ui.form-input-counter/UInputCounter.vue +25 -1
  45. package/ui.form-input-counter/config.ts +7 -2
  46. package/ui.form-input-counter/tests/UInputCounter.test.ts +85 -1
  47. package/ui.form-input-counter/types.ts +25 -0
  48. package/ui.form-input-file/tests/UInputFile.test.ts +2 -2
  49. package/ui.form-input-number/UInputNumber.vue +15 -3
  50. package/ui.form-input-number/utilFormat.ts +17 -7
  51. package/ui.form-input-password/UInputPassword.vue +23 -1
  52. package/ui.form-label/ULabel.vue +10 -4
  53. package/ui.form-label/tests/ULabel.test.ts +29 -12
  54. package/ui.form-listbox/UListbox.vue +21 -9
  55. package/ui.form-listbox/config.ts +1 -1
  56. package/ui.form-listbox/storybook/stories.ts +188 -1
  57. package/ui.form-listbox/tests/UListbox.test.ts +36 -0
  58. package/ui.form-listbox/types.ts +5 -0
  59. package/ui.form-radio/config.ts +1 -1
  60. package/ui.form-radio/tests/URadio.test.ts +2 -2
  61. package/ui.form-radio-group/tests/URadioGroup.test.ts +2 -2
  62. package/ui.form-select/USelect.vue +20 -2
  63. package/ui.form-select/config.ts +2 -1
  64. package/ui.form-select/storybook/stories.ts +31 -4
  65. package/ui.form-select/tests/USelect.test.ts +143 -0
  66. package/ui.form-select/types.ts +10 -0
  67. package/ui.form-textarea/config.ts +1 -1
  68. package/ui.form-textarea/tests/UTextarea.test.ts +2 -2
  69. package/ui.text-alert/config.ts +1 -1
  70. package/ui.text-badge/config.ts +1 -1
  71. package/utils/helper.ts +4 -0
  72. package/utils/node/dynamicProps.d.ts +5 -2
  73. package/utils/node/dynamicProps.js +126 -53
  74. package/utils/node/helper.d.ts +10 -7
  75. package/utils/node/helper.js +59 -2
  76. package/utils/node/tailwindSafelist.js +9 -2
  77. package/utils/theme.ts +75 -31
  78. package/utils/ui.ts +32 -3
@@ -408,7 +408,7 @@ describe("UTextarea.vue", () => {
408
408
  },
409
409
  });
410
410
 
411
- const labelElement = component.getComponent(ULabel).find("label");
411
+ const labelElement = component.getComponent(ULabel).find("[vl-child-key='label']");
412
412
 
413
413
  expect(labelElement.text()).toBe(customLabelContent);
414
414
  });
@@ -425,7 +425,7 @@ describe("UTextarea.vue", () => {
425
425
  },
426
426
  });
427
427
 
428
- const labelElement = component.getComponent(ULabel).find("label");
428
+ const labelElement = component.getComponent(ULabel).find("[vl-child-key='label']");
429
429
 
430
430
  expect(labelElement.text()).toBe(`Modified ${defaultLabel}`);
431
431
  });
@@ -1,6 +1,6 @@
1
1
  export default /*tw*/ {
2
2
  wrapper: {
3
- base: "p-4 border-box flex flex-col border rounded-medium w-full",
3
+ base: "p-4 border-box flex flex-col border border-solid rounded-medium w-full",
4
4
  variants: {
5
5
  variant: {
6
6
  solid: "text-inverted bg-{color} border-transparent",
@@ -2,7 +2,7 @@ export default /*tw*/ {
2
2
  badge: {
3
3
  base: `
4
4
  inline-flex items-center justify-between py-1
5
- border rounded-medium !leading-none outline-hidden
5
+ border border-solid rounded-medium !leading-none outline-hidden
6
6
  `,
7
7
  variants: {
8
8
  variant: {
package/utils/helper.ts CHANGED
@@ -196,6 +196,10 @@ export function toNumber(value: unknown): number | undefined {
196
196
  return value;
197
197
  }
198
198
 
199
+ if (typeof value === "boolean") {
200
+ return Number(value);
201
+ }
202
+
199
203
  if (typeof value === "string" && value.trim() !== "") {
200
204
  const number = Number(value);
201
205
 
@@ -1,2 +1,5 @@
1
- export function setCustomPropTypes({ vuelessSrcDir, basePath }?: {}): Promise<void>;
2
- export function removeCustomPropTypes(srcDir: any): Promise<void>;
1
+ export function setCustomPropTypes({ vuelessSrcDir, basePath }?: {
2
+ vuelessSrcDir: string;
3
+ basePath: string;
4
+ }): Promise<void>;
5
+ export function removeCustomPropTypes(srcDir: string): Promise<void>;
@@ -1,6 +1,6 @@
1
+ import path from "node:path";
1
2
  import fs from "node:fs/promises";
2
3
  import { existsSync } from "node:fs";
3
- import path from "node:path";
4
4
 
5
5
  import { removeFolderIfEmpty } from "./helper.js";
6
6
  import { getVuelessConfig } from "./vuelessConfig.js";
@@ -8,86 +8,97 @@ import { getVuelessConfig } from "./vuelessConfig.js";
8
8
  import {
9
9
  CACHE_DIR,
10
10
  COMPONENTS,
11
- GRAYSCALE_COLOR,
11
+ TEXT_COLOR,
12
12
  INHERIT_COLOR,
13
13
  PRIMARY_COLOR,
14
- TEXT_COLOR,
14
+ GRAYSCALE_COLOR,
15
+ VUELESS_CACHE_DIR,
15
16
  } from "../../constants.js";
17
+ import { buildWebTypes } from "./webTypes.js";
16
18
 
19
+ /* local constants */
20
+ const SAFE_COLORS = [PRIMARY_COLOR, GRAYSCALE_COLOR, INHERIT_COLOR, TEXT_COLOR];
17
21
  const OPTIONAL_MARK = "?";
18
22
  const CLOSING_BRACKET = "}";
19
23
  const IGNORE_PROP = "@ignore";
20
24
  const CUSTOM_PROP = "@custom";
21
25
 
26
+ /* regular expressions */
22
27
  const PROPS_INTERFACE_REG_EXP = /export\s+interface\s+Props(?:<[^>]+>)?\s*{([^}]*)}/s;
23
28
  const UNION_SYMBOLS_REG_EXP = /[?|:"|;]/g;
24
29
  const WORD_IN_QUOTE_REG_EXP = /"([^"]+)"/g;
25
30
 
26
- const DEFAULT_SAFE_COLORS = [PRIMARY_COLOR, GRAYSCALE_COLOR, INHERIT_COLOR, TEXT_COLOR];
27
-
31
+ /**
32
+ * Updates custom PropTypes for components based on provided configuration and colors.
33
+ *
34
+ * @param {Object} options Configuration options.
35
+ * @param {string} options.vuelessSrcDir The source directory for Vueless components.
36
+ * @param {string} options.basePath The base path for retrieving the Vueless configuration file.
37
+ * @return {Promise<void>} Resolves when custom PropTypes for all components are updated successfully.
38
+ */
28
39
  export async function setCustomPropTypes({ vuelessSrcDir, basePath } = {}) {
29
40
  const vuelessConfig = await getVuelessConfig(basePath);
30
41
 
42
+ const hasCustomColors = vuelessConfig.colors?.length;
43
+ const hasCustomColorProp = !!Object.values(vuelessConfig.components || {}).find(
44
+ (component) => component.props?.color,
45
+ );
46
+
47
+ let componentsWithColorProp = [];
48
+
49
+ /* Build web-types.json to get list of components with color prop */
50
+ if (hasCustomColors || hasCustomColorProp) {
51
+ await buildWebTypes({ vuelessSrcDir, basePath });
52
+
53
+ componentsWithColorProp = await getComponentsWithColors();
54
+ }
55
+
31
56
  for await (const [componentName, componentDir] of Object.entries(COMPONENTS)) {
32
57
  let componentGlobalConfig = vuelessConfig.components?.[componentName];
58
+ const hasDefaultColorProp = componentsWithColorProp.some((item) => item.name === componentName);
59
+
60
+ /* Skip components without props and without global colors in config */
61
+ if (!componentGlobalConfig?.props && !(hasCustomColors && hasDefaultColorProp)) {
62
+ continue;
63
+ }
33
64
 
34
- if (vuelessConfig.colors && vuelessConfig.colors.length && componentGlobalConfig) {
35
- const customProps = componentGlobalConfig.props || [];
36
- const colorPropsIndex = customProps.findIndex((prop) => prop.name === "color");
37
- const isCustomColorProp = colorPropsIndex !== -1;
65
+ /* Add colors to the default or custom color prop */
66
+ if (componentGlobalConfig?.props?.color || (hasCustomColors && hasDefaultColorProp)) {
67
+ // eslint-disable-next-line prettier/prettier
68
+ const defaultColors = componentsWithColorProp.find((component) => component.name === componentName)?.colors || [];
69
+ const safelistedColors = defaultColors.filter((color) => SAFE_COLORS.includes(color));
38
70
 
39
- const modifiedCustomColorProp = isCustomColorProp
40
- ? customProps.with(colorPropsIndex, {
41
- ...customProps[colorPropsIndex],
42
- name: "color",
71
+ componentGlobalConfig = {
72
+ ...(componentGlobalConfig || {}),
73
+ props: {
74
+ ...(componentGlobalConfig?.props || {}),
75
+ color: {
76
+ ...(componentGlobalConfig?.props?.color || {}),
43
77
  values: [
44
78
  ...new Set([
45
- ...(customProps[colorPropsIndex]?.values || []),
46
- ...vuelessConfig.colors,
47
- ...DEFAULT_SAFE_COLORS,
79
+ ...(componentGlobalConfig?.props?.color?.values || []),
80
+ ...(vuelessConfig.colors || []),
81
+ ...safelistedColors,
48
82
  ]),
49
83
  ],
50
- })
51
- : undefined;
52
-
53
- const customPropsWithColor = [
54
- ...customProps,
55
- {
56
- name: "color",
57
- values: [...new Set([...vuelessConfig.colors, ...DEFAULT_SAFE_COLORS])],
58
- required: false,
59
- },
60
- ];
61
-
62
- componentGlobalConfig = {
63
- ...componentGlobalConfig,
64
- props: isCustomColorProp ? modifiedCustomColorProp : customPropsWithColor,
65
- };
66
- }
67
-
68
- if (vuelessConfig.colors && vuelessConfig.colors.length && !componentGlobalConfig) {
69
- componentGlobalConfig = {
70
- props: [
71
- {
72
- name: "color",
73
- values: [...new Set([...vuelessConfig.colors, ...DEFAULT_SAFE_COLORS])],
74
- required: false,
75
84
  },
76
- ],
85
+ },
77
86
  };
78
87
  }
79
88
 
80
- const isCustomProps = componentGlobalConfig && componentGlobalConfig.props;
89
+ const cachePath = path.join(vuelessSrcDir, componentDir);
81
90
 
82
- if (isCustomProps) {
83
- const cachePath = path.join(vuelessSrcDir, componentDir);
84
-
85
- await cacheComponentTypes(cachePath);
86
- await modifyComponentTypes(cachePath, componentGlobalConfig.props);
87
- }
91
+ await cacheComponentTypes(cachePath);
92
+ await modifyComponentTypes(cachePath, componentGlobalConfig.props);
88
93
  }
89
94
  }
90
95
 
96
+ /**
97
+ * Removes custom prop types definitions for components.
98
+ *
99
+ * @param {string} srcDir - The source directory containing the component directories.
100
+ * @return {Promise<void>} - A promise that resolves when custom prop types have been removed.
101
+ */
91
102
  export async function removeCustomPropTypes(srcDir) {
92
103
  for await (const componentDir of Object.values(COMPONENTS)) {
93
104
  await restoreComponentTypes(path.join(srcDir, componentDir));
@@ -95,6 +106,39 @@ export async function removeCustomPropTypes(srcDir) {
95
106
  }
96
107
  }
97
108
 
109
+ /**
110
+ * Retrieves a list of components that have a "color" prop from the `web-types.json` file.
111
+ *
112
+ * @return {Promise<Array<string>>} A promise that resolves with an array of component names.
113
+ */
114
+ async function getComponentsWithColors() {
115
+ const webTypesPath = path.join(VUELESS_CACHE_DIR, "web-types.json");
116
+
117
+ if (!existsSync(webTypesPath)) {
118
+ return [];
119
+ }
120
+
121
+ const webTypesContent = await fs.readFile(webTypesPath, "utf8");
122
+ const webTypes = JSON.parse(webTypesContent);
123
+
124
+ if (!webTypes.contributions?.html?.tags) {
125
+ return [];
126
+ }
127
+
128
+ return webTypes.contributions.html.tags
129
+ .filter((component) => component?.attributes.some((attribute) => attribute.name === "color"))
130
+ .map((component) => ({
131
+ name: component.name,
132
+ colors: component.attributes.find((attribute) => attribute.name === "color")?.enum,
133
+ }));
134
+ }
135
+
136
+ /**
137
+ * Caches the component types by copying a source file to a specified cache directory.
138
+ *
139
+ * @param {string} filePath - The directory path where the source file is located and the cache directory will be created.
140
+ * @return {Promise<void>} A promise that resolves when the file has been successfully copied, or immediately if no action is taken.
141
+ */
98
142
  async function cacheComponentTypes(filePath) {
99
143
  const cacheDir = path.join(filePath, CACHE_DIR);
100
144
  const sourceFile = path.join(filePath, "types.ts");
@@ -111,6 +155,13 @@ async function cacheComponentTypes(filePath) {
111
155
  await fs.cp(sourceFile, destFile);
112
156
  }
113
157
 
158
+ /**
159
+ * Clears the cached component types by removing the specified cache file
160
+ * and deleting the corresponding folder if it is empty.
161
+ *
162
+ * @param {string} filePath - The base file path where the cache directory resides.
163
+ * @return {Promise<void>} A promise that resolves when the cache has been cleared.
164
+ */
114
165
  async function clearComponentTypesCache(filePath) {
115
166
  const cacheDir = path.join(filePath, CACHE_DIR);
116
167
  const sourceFile = path.join(cacheDir, "types.ts");
@@ -122,6 +173,13 @@ async function clearComponentTypesCache(filePath) {
122
173
  await removeFolderIfEmpty(cacheDir);
123
174
  }
124
175
 
176
+ /**
177
+ * Restores the component type definitions by copying a cached file to the destination.
178
+ *
179
+ * @param {string} filePath - The directory path where the component types should be restored.
180
+ * This path serves as the base for locating the cached file and the destination file.
181
+ * @return {Promise<void>} A promise that resolves when the component types have been successfully restored.
182
+ */
125
183
  async function restoreComponentTypes(filePath) {
126
184
  const cacheDir = path.join(filePath, CACHE_DIR);
127
185
  const sourceFile = path.join(cacheDir, "types.ts");
@@ -132,6 +190,14 @@ async function restoreComponentTypes(filePath) {
132
190
  }
133
191
  }
134
192
 
193
+ /**
194
+ * Extracts and processes the values from multiple lines based on the given indices.
195
+ *
196
+ * @param {Array<string>} lines - The array of lines to process.
197
+ * @param {number} propIndex - The index from which to start slicing the lines.
198
+ * @param {number} propEndIndex - The index until which to slice the lines (inclusive).
199
+ * @return {Array<string>} An array of strings with processed values, trimmed of unnecessary symbols.
200
+ */
135
201
  function getMultiLineUnionValues(lines, propIndex, propEndIndex) {
136
202
  return lines
137
203
  .slice(propIndex)
@@ -139,6 +205,15 @@ function getMultiLineUnionValues(lines, propIndex, propEndIndex) {
139
205
  .map((item) => item.replace(UNION_SYMBOLS_REG_EXP, "").trim());
140
206
  }
141
207
 
208
+ /**
209
+ * Extracts and returns inline union values from the specified lines of text,
210
+ * based on provided property indices.
211
+ *
212
+ * @param {string[]} lines - The array of string lines to extract union values from.
213
+ * @param {number} propIndex - The starting index in the lines array from where extraction begins.
214
+ * @param {number} propEndIndex - The ending index in the lines array up to which extraction is performed.
215
+ * @return {string[]} An array of extracted union values, or an empty array if no matches are found.
216
+ */
142
217
  function getInlineUnionValues(lines, propIndex, propEndIndex) {
143
218
  const types = lines
144
219
  .slice(propIndex)
@@ -167,10 +242,8 @@ async function modifyComponentTypes(filePath, props) {
167
242
 
168
243
  const lines = propsInterface.split("\n");
169
244
 
170
- for (const prop of props) {
171
- const { name, type, values = [], description, required, ignore } = prop;
172
-
173
- if (!name) return;
245
+ for (const name in props) {
246
+ const { type = "string", values = [], description, required, ignore } = props[name];
174
247
 
175
248
  /* Find line with prop. */
176
249
  const propRegex = new RegExp(`^\\s*${name}[?:]?\\s*:`);
@@ -1,15 +1,18 @@
1
- export function getDirFiles(dirPath: any, ext: any, { recursive, exclude }?: {
1
+ export function getDirFiles(dirPath: string, ext: string, { recursive, exclude }?: {
2
2
  recursive?: boolean | undefined;
3
- exclude?: never[] | undefined;
3
+ exclude?: string[] | undefined;
4
4
  }): Promise<string[]>;
5
5
  export function getNuxtDirs(): string[];
6
6
  export function getVueDirs(): string[];
7
7
  export function getVuelessConfigDirs(): string[];
8
- export function getMergedComponentConfig(name: any): Promise<any>;
9
- export function getDefaultComponentConfig(name: any, configDir: any): Promise<{}>;
10
- export function cacheMergedConfigs({ vuelessSrcDir, basePath }?: {}): Promise<void>;
11
- export function buildTSFile(entryPath: any, configOutFile: any): Promise<void>;
12
- export function removeFolderIfEmpty(dirPath: any): Promise<void>;
8
+ export function getMergedComponentConfig(name: string): Promise<Object>;
9
+ export function getDefaultComponentConfig(name: string, configDir: string): Promise<Object>;
10
+ export function cacheMergedConfigs({ vuelessSrcDir, basePath }?: {
11
+ vuelessSrcDir: string;
12
+ basePath: string;
13
+ }): Promise<void>;
14
+ export function buildTSFile(entryPath: string, configOutFile: string): Promise<void>;
15
+ export function removeFolderIfEmpty(dirPath: string): Promise<void>;
13
16
  export function detectTypeScript(): Promise<boolean>;
14
17
  export function autoImportUserConfigs(basePath?: string): Promise<void>;
15
18
  export function generateConfigIndexContent(imports?: string[], componentEntries?: string[]): Promise<string>;
@@ -20,6 +20,16 @@ import {
20
20
  CONFIG_INDEX_FILE_NAME,
21
21
  } from "../../constants.js";
22
22
 
23
+ /**
24
+ * Retrieves a list of file names in a specified directory that match the given extension and filtering criteria.
25
+ *
26
+ * @param {string} dirPath - The path of the directory to search for files.
27
+ * @param {string} ext - The extension of the files to include in the result.
28
+ * @param {Object} [options] - Optional settings to customize the file search.
29
+ * @param {boolean} [options.recursive=true] - Whether to search directories recursively.
30
+ * @param {string[]} [options.exclude=[]] - A list of file or directory names to exclude from the result.
31
+ * @return {Promise<string[]>} - A promise that resolves to an array of file paths that match the specified criteria.
32
+ */
23
33
  export async function getDirFiles(dirPath, ext, { recursive = true, exclude = [] } = {}) {
24
34
  let fileNames = [];
25
35
 
@@ -65,6 +75,10 @@ export async function getDirFiles(dirPath, ext, { recursive = true, exclude = []
65
75
  .filter((filePath) => !statSync(filePath).isDirectory());
66
76
  }
67
77
 
78
+ /**
79
+ * Retrieves an array of directory paths and specific file paths within the current working directory related to a Nuxt.js project.
80
+ * @return {string[]}.
81
+ */
68
82
  export function getNuxtDirs() {
69
83
  return [
70
84
  path.join(cwd(), "app"),
@@ -82,14 +96,28 @@ export function getNuxtDirs() {
82
96
  ];
83
97
  }
84
98
 
99
+ /**
100
+ * Retrieves an array of directory paths and specific file paths within the current working directory related to a Vue.js project.
101
+ * @return {string[]}.
102
+ */
85
103
  export function getVueDirs() {
86
104
  return [path.join(cwd(), "src")];
87
105
  }
88
106
 
107
+ /**
108
+ * Retrieves an array of directory paths and specific file paths within the current working directory related to a Vueless project.
109
+ * @return {string[]}.
110
+ */
89
111
  export function getVuelessConfigDirs() {
90
112
  return [path.join(cwd(), VUELESS_CONFIG_DIR)];
91
113
  }
92
114
 
115
+ /**
116
+ * Retrieves the merged config for a specific component.
117
+ *
118
+ * @param {string} name - The name of the component.
119
+ * @return {Promise<Object>} A promise that resolves to the merged configuration object for the specified component.
120
+ */
93
121
  export async function getMergedComponentConfig(name) {
94
122
  const configOutPath = path.join(cwd(), `${VUELESS_MERGED_CONFIGS_CACHED_DIR}/${name}.json`);
95
123
 
@@ -100,6 +128,13 @@ export async function getMergedComponentConfig(name) {
100
128
  }
101
129
  }
102
130
 
131
+ /**
132
+ * Retrieves the default config for a specific component.
133
+ *
134
+ * @param {string} name - The name of the component.
135
+ * @param {string} configDir - The directory path where the component's configuration file is located.
136
+ * @return {Promise<Object>} A promise that resolves to the default configuration object for the specified component.
137
+ */
103
138
  export async function getDefaultComponentConfig(name, configDir) {
104
139
  const configOutPath = path.join(cwd(), `${VUELESS_CONFIGS_CACHED_DIR}/${name}.mjs`);
105
140
  let config = {};
@@ -117,6 +152,14 @@ export async function getDefaultComponentConfig(name, configDir) {
117
152
  return config;
118
153
  }
119
154
 
155
+ /**
156
+ * Caches merged configs for all components.
157
+ *
158
+ * @param {Object} options - Configuration options.
159
+ * @param {string} options.vuelessSrcDir - The source directory for Vueless components.
160
+ * @param {string} options.basePath - The base path for retrieving the Vueless configuration file.
161
+ * @return {Promise<void>} A promise that resolves when all merged configs have been cached.
162
+ */
120
163
  export async function cacheMergedConfigs({ vuelessSrcDir, basePath } = {}) {
121
164
  const vuelessConfig = await getVuelessConfig(basePath);
122
165
  const componentNames = Object.entries(COMPONENTS);
@@ -146,6 +189,13 @@ export async function cacheMergedConfigs({ vuelessSrcDir, basePath } = {}) {
146
189
  }
147
190
  }
148
191
 
192
+ /**
193
+ * Builds a TypeScript file into a JavaScript file using esbuild.
194
+ *
195
+ * @param {string} entryPath - The path to the TypeScript file to be built.
196
+ * @param {string} configOutFile - The output path for the resulting JavaScript file.
197
+ * @return {Promise<void>} A promise that resolves when the build is complete.
198
+ */
149
199
  export async function buildTSFile(entryPath, configOutFile) {
150
200
  await esbuild.build({
151
201
  entryPoints: [entryPath],
@@ -158,13 +208,20 @@ export async function buildTSFile(entryPath, configOutFile) {
158
208
  });
159
209
  }
160
210
 
211
+ /**
212
+ * Removes a folder if it is empty.
213
+ * @param {string} dirPath - The path to the directory to be removed.
214
+ * @return {Promise<void>} A promise that resolves when the directory has been removed.
215
+ */
161
216
  export async function removeFolderIfEmpty(dirPath) {
162
- if (existsSync(dirPath)) {
217
+ try {
163
218
  const files = await readdir(dirPath);
164
219
 
165
220
  if (!files.length) {
166
- await rmdir(dirPath);
221
+ await rmdir(dirPath, { recursive: false, force: true });
167
222
  }
223
+ } catch {
224
+ // suppress errors
168
225
  }
169
226
  }
170
227
 
@@ -233,7 +233,14 @@ function getClassesToSafelist(config) {
233
233
  const safelistItems = [];
234
234
 
235
235
  for (const key in config) {
236
- if (key === SYSTEM_CONFIG_KEY.defaults) continue;
236
+ const nonClassKeys = [
237
+ SYSTEM_CONFIG_KEY.defaults,
238
+ SYSTEM_CONFIG_KEY.props,
239
+ SYSTEM_CONFIG_KEY.i18n,
240
+ SYSTEM_CONFIG_KEY.colors,
241
+ ];
242
+
243
+ if (nonClassKeys.some((item) => key === item)) continue;
237
244
 
238
245
  if (Object.hasOwn(config, key)) {
239
246
  const classes = config[key];
@@ -267,7 +274,7 @@ function getClassesToSafelist(config) {
267
274
  */
268
275
  function getSafelistClasses(config, colors) {
269
276
  const classes = new Set();
270
- const defaultColor = config.defaults?.color || "";
277
+ const defaultColor = config.defaults?.color || config.props?.color?.default || "";
271
278
 
272
279
  getClassesToSafelist(config).map((safelistClass) => {
273
280
  [...colors, defaultColor].forEach((color) => {
package/utils/theme.ts CHANGED
@@ -35,6 +35,8 @@ import {
35
35
  DEFAULT_DISABLED_OPACITY,
36
36
  LETTER_SPACING,
37
37
  DEFAULT_LETTER_SPACING,
38
+ LIGHT_THEME,
39
+ DARK_THEME,
38
40
  } from "../constants";
39
41
 
40
42
  import type {
@@ -49,18 +51,6 @@ import type {
49
51
  } from "../types";
50
52
  import { ColorMode } from "../types";
51
53
 
52
- declare interface RootCSSVariableOptions {
53
- primary: PrimaryColors | string;
54
- neutral: NeutralColors | string;
55
- text: ThemeConfigText;
56
- rounding: ThemeConfigRounding;
57
- outline: ThemeConfigOutline;
58
- letterSpacing: number;
59
- disabledOpacity: number;
60
- lightTheme: Partial<VuelessCssVariables>;
61
- darkTheme: Partial<VuelessCssVariables>;
62
- }
63
-
64
54
  declare interface SetColorMode {
65
55
  colorMode: `${ColorMode}`;
66
56
  isColorModeAuto: boolean;
@@ -191,6 +181,8 @@ export function resetTheme() {
191
181
  `vl-${ROUNDING}-lg`,
192
182
  `vl-${LETTER_SPACING}`,
193
183
  `vl-${DISABLED_OPACITY}`,
184
+ `vl-${LIGHT_THEME}`,
185
+ `vl-${DARK_THEME}`,
194
186
  ];
195
187
 
196
188
  themeKeys.forEach((key) => {
@@ -217,8 +209,8 @@ export function getTheme(config?: ThemeConfig): MergedThemeConfig {
217
209
  const letterSpacing = getLetterSpacing(config?.letterSpacing);
218
210
  const disabledOpacity = getDisabledOpacity(config?.disabledOpacity);
219
211
 
220
- const lightTheme = merge({}, DEFAULT_LIGHT_THEME, vuelessConfig.lightTheme);
221
- const darkTheme = merge({}, DEFAULT_DARK_THEME, vuelessConfig.darkTheme);
212
+ const lightTheme = getLightTheme(config?.lightTheme);
213
+ const darkTheme = getDarkTheme(config?.darkTheme);
222
214
 
223
215
  return {
224
216
  colorMode,
@@ -254,8 +246,8 @@ export function setTheme(config: ThemeConfig = {}) {
254
246
  let primary = getPrimaryColor(config.primary);
255
247
  const neutral = getNeutralColor(config.neutral);
256
248
 
257
- const lightTheme = merge({}, DEFAULT_LIGHT_THEME, vuelessConfig.lightTheme, config.lightTheme);
258
- const darkTheme = merge({}, DEFAULT_DARK_THEME, vuelessConfig.darkTheme, config.darkTheme);
249
+ const lightTheme = getLightTheme(config.lightTheme);
250
+ const darkTheme = getDarkTheme(config.darkTheme);
259
251
 
260
252
  /* Redeclare primary color if grayscale color set as default */
261
253
  if (primary === GRAYSCALE_COLOR) {
@@ -330,10 +322,10 @@ export function setTheme(config: ThemeConfig = {}) {
330
322
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
323
  export function normalizeThemeConfig(theme: any): MergedThemeConfig {
332
324
  return {
333
- colorMode: theme.colorMode,
334
- isColorModeAuto: theme.isColorModeAuto,
335
- primary: theme.primary,
336
- neutral: theme.neutral,
325
+ colorMode: String(theme.colorMode ?? "") as ColorMode,
326
+ isColorModeAuto: !!toNumber(theme.isColorModeAuto),
327
+ primary: String(theme.primary ?? ""),
328
+ neutral: String(theme.neutral ?? ""),
337
329
  text: {
338
330
  xs: toNumber(theme.text?.xs),
339
331
  sm: toNumber(theme.text?.sm),
@@ -608,7 +600,7 @@ function getLetterSpacing(letterSpacing?: ThemeConfig["letterSpacing"]) {
608
600
  const storageKey = `vl-${LETTER_SPACING}`;
609
601
 
610
602
  const spacing = letterSpacing ?? getStored(storageKey) ?? vuelessConfig.letterSpacing;
611
- const mergedSpacing = Math.max(0, Number(spacing ?? DEFAULT_LETTER_SPACING));
603
+ const mergedSpacing = Number(spacing ?? DEFAULT_LETTER_SPACING);
612
604
 
613
605
  if (isCSR && letterSpacing !== undefined) {
614
606
  setCookie(storageKey, String(mergedSpacing));
@@ -636,24 +628,76 @@ function getDisabledOpacity(disabledOpacity?: ThemeConfig["disabledOpacity"]) {
636
628
  return mergedOpacity;
637
629
  }
638
630
 
631
+ /**
632
+ * Retrieve light theme configuration and save them to cookie and localStorage.
633
+ * @return Partial<VuelessCssVariables> - light theme configuration.
634
+ */
635
+ function getLightTheme(lightTheme?: Partial<VuelessCssVariables>) {
636
+ const storageKey = `vl-${LIGHT_THEME}`;
637
+
638
+ const storedLightTheme: Partial<VuelessCssVariables> = JSON.parse(getStored(storageKey) ?? "{}");
639
+
640
+ const mergedLightTheme = merge(
641
+ {},
642
+ DEFAULT_LIGHT_THEME,
643
+ vuelessConfig.lightTheme,
644
+ storedLightTheme,
645
+ lightTheme,
646
+ );
647
+
648
+ if (isCSR && lightTheme !== undefined) {
649
+ const themeString = JSON.stringify(mergedLightTheme);
650
+
651
+ localStorage.setItem(storageKey, themeString);
652
+ }
653
+
654
+ return mergedLightTheme;
655
+ }
656
+
657
+ /**
658
+ * Retrieve dark theme configuration and save them to cookie and localStorage.
659
+ * @return Partial<VuelessCssVariables> - dark theme configuration.
660
+ */
661
+ function getDarkTheme(darkTheme?: Partial<VuelessCssVariables>) {
662
+ const storageKey = `vl-${DARK_THEME}`;
663
+
664
+ const storedDarkTheme: Partial<VuelessCssVariables> = JSON.parse(getStored(storageKey) ?? "{}");
665
+
666
+ const mergedDarkTheme = merge(
667
+ {},
668
+ DEFAULT_DARK_THEME,
669
+ vuelessConfig.darkTheme,
670
+ storedDarkTheme,
671
+ darkTheme,
672
+ );
673
+
674
+ if (isCSR && darkTheme !== undefined) {
675
+ const themeString = JSON.stringify(mergedDarkTheme);
676
+
677
+ localStorage.setItem(storageKey, themeString);
678
+ }
679
+
680
+ return mergedDarkTheme;
681
+ }
682
+
639
683
  /**
640
684
  * Generate and apply Vueless CSS variables.
641
685
  * @return string - Vueless CSS variables string.
642
686
  */
643
- function setRootCSSVariables(vars: RootCSSVariableOptions) {
687
+ export function setRootCSSVariables(vars: MergedThemeConfig) {
644
688
  let darkVariables: Partial<VuelessCssVariables> = {};
645
689
 
646
690
  let variables: Partial<VuelessCssVariables> = {
647
- "--vl-text-xs": `${vars.text.xs / PX_IN_REM}rem`,
648
- "--vl-text-sm": `${vars.text.sm / PX_IN_REM}rem`,
649
- "--vl-text-md": `${vars.text.md / PX_IN_REM}rem`,
650
- "--vl-text-lg": `${vars.text.lg / PX_IN_REM}rem`,
691
+ "--vl-text-xs": `${Number(vars.text?.xs ?? 0) / PX_IN_REM}rem`,
692
+ "--vl-text-sm": `${Number(vars.text?.sm ?? 0) / PX_IN_REM}rem`,
693
+ "--vl-text-md": `${Number(vars.text?.md ?? 0) / PX_IN_REM}rem`,
694
+ "--vl-text-lg": `${Number(vars.text?.lg ?? 0) / PX_IN_REM}rem`,
651
695
  "--vl-outline-sm": `${vars.outline.sm}px`,
652
696
  "--vl-outline-md": `${vars.outline.md}px`,
653
697
  "--vl-outline-lg": `${vars.outline.lg}px`,
654
- "--vl-rounding-sm": `${vars.rounding.sm / PX_IN_REM}rem`,
655
- "--vl-rounding-md": `${vars.rounding.md / PX_IN_REM}rem`,
656
- "--vl-rounding-lg": `${vars.rounding.lg / PX_IN_REM}rem`,
698
+ "--vl-rounding-sm": `${Number(vars.rounding.sm ?? 0) / PX_IN_REM}rem`,
699
+ "--vl-rounding-md": `${Number(vars.rounding.md ?? 0) / PX_IN_REM}rem`,
700
+ "--vl-rounding-lg": `${Number(vars.rounding.lg ?? 0) / PX_IN_REM}rem`,
657
701
  "--vl-letter-spacing": `${vars.letterSpacing}em`,
658
702
  "--vl-disabled-opacity": `${vars.disabledOpacity}%`,
659
703
  };
@@ -668,7 +712,7 @@ function setRootCSSVariables(vars: RootCSSVariableOptions) {
668
712
  `var(--color-${vars.neutral}-${shade})`;
669
713
  }
670
714
 
671
- const [light, dark] = generateCSSColorVariables(vars.lightTheme, vars.darkTheme);
715
+ const [light, dark] = generateCSSColorVariables(vars.lightTheme ?? {}, vars.darkTheme ?? {});
672
716
 
673
717
  variables = { ...variables, ...light };
674
718
  darkVariables = { ...darkVariables, ...dark };
@@ -719,7 +763,7 @@ function setCSSVariables(
719
763
  .join(" ");
720
764
 
721
765
  const rootVariables = `
722
- :root {${variablesString}}
766
+ :host, :root {${variablesString}}
723
767
  .${DARK_MODE_CLASS} {${darkVariablesString}}
724
768
  `;
725
769