vueless 1.2.8-beta.9 → 1.2.9

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
@@ -131,6 +131,7 @@ export namespace SYSTEM_CONFIG_KEY {
131
131
  let unstyled: string;
132
132
  let transition: string;
133
133
  let colors: string;
134
+ let props: string;
134
135
  }
135
136
  export const ICON_NON_PROPS_DEFAULTS: string[];
136
137
  export namespace DIRECTIVES {
package/constants.js CHANGED
@@ -225,6 +225,7 @@ export const SYSTEM_CONFIG_KEY = {
225
225
  unstyled: "unstyled",
226
226
  transition: "transition",
227
227
  colors: "colors",
228
+ props: "props",
228
229
  ...CVA_CONFIG_KEY,
229
230
  };
230
231
 
package/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export {
17
17
  } from "./utils/helper";
18
18
  export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
19
19
  export { cx, cva, compose, getDefaults, setVuelessConfig, setColor, vuelessConfig } from "./utils/ui";
20
- export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar } from "./utils/theme";
20
+ export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar, setRootCSSVariables } from "./utils/theme";
21
21
  export { getArgs, getArgTypes, getSlotNames, getSlotsFragment, getSource, getDocsDescription } from "./utils/storybook";
22
22
  /* adapters */
23
23
  export { default as defaultEnLocale } from "./adapter.locale/locales/en";
@@ -140,6 +140,8 @@ export type {
140
140
  NestedComponent,
141
141
  ComponentConfig,
142
142
  ComponentDefaults,
143
+ ComponentCustomProp,
144
+ ComponentCustomProps,
143
145
  CreateVuelessOptions,
144
146
  /* Color and theme types */
145
147
  StateColors,
package/index.ts CHANGED
@@ -23,7 +23,7 @@ export {
23
23
  } from "./utils/helper";
24
24
  export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows } from "./utils/platform";
25
25
  export { cx, cva, compose, getDefaults, setVuelessConfig, setColor, vuelessConfig } from "./utils/ui";
26
- export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar } from "./utils/theme";
26
+ export { getTheme, setTheme, resetTheme, normalizeThemeConfig, cssVar, setRootCSSVariables } from "./utils/theme";
27
27
  export { getArgs, getArgTypes, getSlotNames, getSlotsFragment, getSource, getDocsDescription } from "./utils/storybook";
28
28
  /* adapters */
29
29
  export { default as defaultEnLocale } from "./adapter.locale/locales/en";
@@ -146,6 +146,8 @@ export type {
146
146
  NestedComponent,
147
147
  ComponentConfig,
148
148
  ComponentDefaults,
149
+ ComponentCustomProp,
150
+ ComponentCustomProps,
149
151
  CreateVuelessOptions,
150
152
  /* Color and theme types */
151
153
  StateColors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.2.8-beta.9",
3
+ "version": "1.2.9",
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",
@@ -18,10 +18,10 @@
18
18
  "release:icons": "npx node .scripts/icons",
19
19
  "release:types": "rm -f ./node_modules/.tmp/tsconfig.app.tsbuildinfo && npx tsc --project tsconfig.build.json",
20
20
  "release:prepare": "npm run release:icons && rm -rf dist && mkdir -p dist && cp -r src/. package.json LICENSE README.md dist/ && npx node .scripts/writeLocales && npm run release:types",
21
- "release:beta": "release-it --ci --npm.publish --preRelease=beta --increment=prerelease",
22
- "release:patch": "release-it patch --ci --npm.publish --git.tag --github.release",
23
- "release:minor": "release-it minor --ci --npm.publish --git.tag --github.release",
24
- "release:major": "release-it major --ci --npm.publish --git.tag --github.release",
21
+ "release:beta": "release-it --increment=prerelease --preRelease=beta --ci --no-git.tag --no-github.release",
22
+ "release:patch": "release-it patch --ci",
23
+ "release:minor": "release-it minor --ci",
24
+ "release:major": "release-it major --ci",
25
25
  "lint": "eslint --no-fix src/ test/ .storybook/ .vueless/ .scripts/",
26
26
  "lint:fix": "eslint --fix src/ test/ .storybook/ .vueless/ .scripts/",
27
27
  "lint:ci": "eslint --no-fix --max-warnings=0",
@@ -47,6 +47,7 @@
47
47
  "devDependencies": {
48
48
  "@material-symbols/svg-500": "^0.35.0",
49
49
  "@release-it/bumper": "^7.0.5",
50
+ "@release-it/conventional-changelog": "^10.0.1",
50
51
  "@tsconfig/node20": "^20.1.6",
51
52
  "@types/jsdom": "^21.1.7",
52
53
  "@types/node": "^24.1.0",
@@ -106,6 +107,9 @@
106
107
  "types": "./modules.d.ts"
107
108
  }
108
109
  },
110
+ "overrides": {
111
+ "conventional-changelog-conventionalcommits": "8.0.0"
112
+ },
109
113
  "resolutions": {
110
114
  "jackspeak": "2.3.6"
111
115
  },
package/plugin-vite.js CHANGED
@@ -129,9 +129,14 @@ export const Vueless = function (options = {}) {
129
129
  await cacheMergedConfigs({ vuelessSrcDir, basePath });
130
130
  }
131
131
 
132
- await buildWebTypes({ vuelessSrcDir, basePath });
132
+ /* set custom prop types */
133
133
  await setCustomPropTypes({ vuelessSrcDir, basePath });
134
134
 
135
+ /* build web-types.json with delay for right custom props behavior */
136
+ setTimeout(async () => {
137
+ await buildWebTypes({ vuelessSrcDir, basePath });
138
+ }, 2000);
139
+
135
140
  /* collect used in project colors for tailwind safelist */
136
141
  await createTailwindSafelist({ env, srcDir: vuelessSrcDir, targetFiles, basePath, debug });
137
142
 
package/types.ts CHANGED
@@ -198,7 +198,7 @@ export type MergedThemeConfig = Omit<ThemeConfig, "text | outline | rounding"> &
198
198
 
199
199
  export type UnknownObject = Record<string, unknown>;
200
200
  export type UnknownArray = unknown[];
201
- export type UnknownType = string | number | boolean | UnknownObject | undefined | null;
201
+ export type UnknownType = string | number | boolean | UnknownObject | undefined | null | unknown;
202
202
 
203
203
  export type ComponentNames = keyof Components & string; // keys union
204
204
 
@@ -325,7 +325,19 @@ export interface NestedComponent {
325
325
 
326
326
  export type ComponentDefaults = {
327
327
  color?: string;
328
- [key: string]: unknown | UnknownObject;
328
+ [key: string]: UnknownType;
329
+ };
330
+
331
+ export type ComponentCustomProps = {
332
+ [key: string]: ComponentCustomProp;
333
+ };
334
+
335
+ export type ComponentCustomProp = {
336
+ required?: boolean;
337
+ ignore?: boolean;
338
+ values?: string[];
339
+ default?: UnknownType;
340
+ description?: string;
329
341
  };
330
342
 
331
343
  export interface CVA {
@@ -2,7 +2,7 @@ export default /*tw*/ {
2
2
  button: {
3
3
  base: `
4
4
  flex items-center justify-center font-medium !leading-snug whitespace-nowrap
5
- border border-transparent transition cursor-pointer
5
+ border border-solid transition cursor-pointer
6
6
  focus-visible:outline-medium focus-visible:outline-offset-2 focus-visible:outline-{color}
7
7
  disabled:cursor-not-allowed disabled:outline-0 disabled:outline-offset-0
8
8
  `,
@@ -17,7 +17,7 @@ export default /*tw*/ {
17
17
  },
18
18
  variant: {
19
19
  solid: `
20
- text-inverted bg-{color}
20
+ bg-{color} border-transparent text-inverted
21
21
  hover:bg-{color}-lifted
22
22
  active:bg-{color}-accented
23
23
  disabled:!bg-{color}/(--vl-disabled-opacity)
@@ -35,13 +35,13 @@ export default /*tw*/ {
35
35
  disabled:!text-{color}/(--vl-disabled-opacity) disabled:!bg-{color}/5 disabled:!border-{color}/10
36
36
  `,
37
37
  soft: `
38
- text-{color} bg-{color}/5
38
+ text-{color} bg-{color}/5 border-transparent
39
39
  hover:text-{color}-lifted hover:bg-{color}-lifted/10
40
40
  active:text-{color}-accented active:bg-{color}-accented/15
41
41
  disabled:!text-{color}/(--vl-disabled-opacity) disabled:!bg-{color}/5
42
42
  `,
43
43
  ghost: `
44
- text-{color} bg-transparent
44
+ text-{color} bg-transparent border-transparent
45
45
  hover:text-{color}-lifted hover:bg-{color}-lifted/10
46
46
  active:text-{color}-accented active:bg-{color}-accented/15
47
47
  disabled:!text-{color}/(--vl-disabled-opacity) disabled:!bg-transparent
@@ -12,11 +12,11 @@ describe("UButton.vue", () => {
12
12
  it("Variant – applies the correct variant class", async () => {
13
13
  const color = "primary";
14
14
  const variants = {
15
- solid: "text-inverted bg-primary",
15
+ solid: "bg-primary border-transparent text-inverted",
16
16
  outlined: "text-primary border-primary",
17
17
  subtle: "text-primary bg-primary/5 border-primary/15",
18
- soft: "text-primary bg-primary/5",
19
- ghost: "text-primary bg-transparent",
18
+ soft: "text-primary bg-primary/5 border-transparent",
19
+ ghost: "text-primary bg-transparent border-transparent",
20
20
  };
21
21
 
22
22
  Object.entries(variants).forEach(([variant, classes]) => {
@@ -12,7 +12,7 @@ export default /*tw*/ {
12
12
  },
13
13
  split: {
14
14
  true: "flex-wrap",
15
- false: "flex-nowrap gap-px p-1 w-fit border rounded-medium border-default",
15
+ false: "flex-nowrap gap-px p-1 w-fit border border-solid rounded-medium border-default",
16
16
  },
17
17
  disabled: {
18
18
  true: "bg-lifted cursor-not-allowed",
@@ -26,7 +26,7 @@ export default /*tw*/ {
26
26
  },
27
27
  },
28
28
  divider: {
29
- base: "my-1 border-r border-muted last:hidden",
29
+ base: "my-1 border-r border-solid border-muted last:hidden",
30
30
  variants: {
31
31
  split: {
32
32
  true: "hidden",
@@ -1,6 +1,6 @@
1
1
  export default /*tw*/ {
2
2
  wrapper: {
3
- base: "p-4 md:p-6 border rounded-large w-full text-medium",
3
+ base: "p-4 md:p-6 border border-solid rounded-large w-full text-medium",
4
4
  variants: {
5
5
  variant: {
6
6
  solid: "bg-default border-transparent",
@@ -2,7 +2,7 @@ export default /*tw*/ {
2
2
  wrapper: "relative w-full overflow-auto",
3
3
  headerCounterBase: "mr-1.5 pr-1.5 font-medium text-medium",
4
4
  stickyHeader: {
5
- base: "fixed top-0 flex items-center z-30 overflow-hidden border rounded-none",
5
+ base: "fixed top-0 flex items-center z-30 overflow-hidden border border-solid rounded-none",
6
6
  variants: {
7
7
  stickedHeader: {
8
8
  false: "absolute",
@@ -29,12 +29,12 @@ export default /*tw*/ {
29
29
  stickyHeaderLoader: "{ULoaderProgress} absolute top-auto bottom-0",
30
30
  headerActionsCheckbox: "{UCheckbox}",
31
31
  headerActionsCounter: "{>headerCounterBase} -ml-1.5",
32
- tableWrapper: "border border-muted rounded-medium bg-default overflow-x-auto",
32
+ tableWrapper: "border border-solid border-muted rounded-medium bg-default overflow-x-auto",
33
33
  table: "min-w-full border-none text-medium w-full table-auto",
34
34
  header:
35
35
  "border-b border-muted [&>tr:first-child>*]:first:rounded-tl-medium [&>tr:last-child>*]:last:rounded-tr-medium relative",
36
36
  headerRow: "",
37
- beforeHeaderRow: "border border-muted",
37
+ beforeHeaderRow: "border border-solid border-muted",
38
38
  beforeHeaderCell: "{>headerCellBase}",
39
39
  headerCellBase: {
40
40
  base: "p-4 text-medium font-normal text-lifted text-left text-nowrap",
@@ -100,7 +100,7 @@ export default /*tw*/ {
100
100
  },
101
101
  },
102
102
  stickyFooterRow: `
103
- fixed bottom-0 -ml-px border border-b border-muted bg-default
103
+ fixed bottom-0 -ml-px border-b border-solid border-muted bg-default
104
104
  collapse group-[*]/footer-fixed:[visibility:inherit]
105
105
  `,
106
106
  i18n: {
@@ -66,6 +66,7 @@ const emit = defineEmits([
66
66
  type UListboxRef = InstanceType<typeof UListbox>;
67
67
 
68
68
  const isShownOptions = ref(false);
69
+ const isClickingOption = ref(false);
69
70
  const listboxRef = useTemplateRef<UListboxRef>("dropdown-list");
70
71
  const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
71
72
 
@@ -157,14 +158,29 @@ function onClickBadge() {
157
158
 
158
159
  function hideOptions() {
159
160
  isShownOptions.value = false;
161
+ dropdownSearch.value = "";
160
162
 
161
163
  emit("close");
162
164
  }
163
165
 
164
166
  function onClickOption(option: Option) {
167
+ isClickingOption.value = true;
168
+
165
169
  emit("clickOption", option);
166
170
 
167
171
  if (!props.multiple && props.closeOnSelect) hideOptions();
172
+
173
+ nextTick(() => {
174
+ setTimeout(() => {
175
+ isClickingOption.value = false;
176
+ }, 10);
177
+ });
178
+ }
179
+
180
+ function handleClickOutside() {
181
+ if (isClickingOption.value) return;
182
+
183
+ hideOptions();
168
184
  }
169
185
 
170
186
  defineExpose({
@@ -197,7 +213,7 @@ const { getDataTest, config, wrapperAttrs, dropdownBadgeAttrs, listboxAttrs, tog
197
213
  <template>
198
214
  <div
199
215
  ref="wrapper"
200
- v-click-outside="hideOptions"
216
+ v-click-outside="handleClickOutside"
201
217
  v-bind="wrapperAttrs"
202
218
  :data-test="getDataTest('wrapper')"
203
219
  >
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { nextTick, computed, provide, ref, useId, useTemplateRef } from "vue";
2
+ import { nextTick, computed, ref, useId, useTemplateRef } from "vue";
3
3
  import { isEqual } from "lodash-es";
4
4
 
5
5
  import useUI from "../composables/useUI";
@@ -63,11 +63,10 @@ const emit = defineEmits([
63
63
  "update:search",
64
64
  ]);
65
65
 
66
- provide("hideDropdownOptions", hideOptions);
67
-
68
66
  type UListboxRef = InstanceType<typeof UListbox>;
69
67
 
70
68
  const isShownOptions = ref(false);
69
+ const isClickingOption = ref(false);
71
70
  const listboxRef = useTemplateRef<UListboxRef>("dropdown-list");
72
71
  const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
73
72
 
@@ -148,9 +147,23 @@ function getFullOptionLabels(value: Option | Option[]) {
148
147
  }
149
148
 
150
149
  function onClickOption(option: Option) {
150
+ isClickingOption.value = true;
151
+
151
152
  emit("clickOption", option);
152
153
 
153
154
  if (!props.multiple && props.closeOnSelect) hideOptions();
155
+
156
+ nextTick(() => {
157
+ setTimeout(() => {
158
+ isClickingOption.value = false;
159
+ }, 10);
160
+ });
161
+ }
162
+
163
+ function handleClickOutside() {
164
+ if (isClickingOption.value) return;
165
+
166
+ hideOptions();
154
167
  }
155
168
 
156
169
  function onClickButton() {
@@ -165,6 +178,7 @@ function onClickButton() {
165
178
 
166
179
  function hideOptions() {
167
180
  isShownOptions.value = false;
181
+ dropdownSearch.value = "";
168
182
 
169
183
  emit("close");
170
184
  }
@@ -199,7 +213,7 @@ const { getDataTest, config, dropdownButtonAttrs, listboxAttrs, toggleIconAttrs,
199
213
  <template>
200
214
  <div
201
215
  ref="wrapper"
202
- v-click-outside="hideOptions"
216
+ v-click-outside="handleClickOutside"
203
217
  v-bind="wrapperAttrs"
204
218
  :data-test="getDataTest('wrapper')"
205
219
  >
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { nextTick, computed, provide, ref, useId, useTemplateRef } from "vue";
2
+ import { nextTick, computed, ref, useId, useTemplateRef } from "vue";
3
3
  import { isEqual } from "lodash-es";
4
4
 
5
5
  import useUI from "../composables/useUI";
@@ -63,11 +63,10 @@ const emit = defineEmits([
63
63
  "update:search",
64
64
  ]);
65
65
 
66
- provide("hideDropdownOptions", hideOptions);
67
-
68
66
  type ULisboxRef = InstanceType<typeof ULisbox>;
69
67
 
70
68
  const isShownOptions = ref(false);
69
+ const isClickingOption = ref(false);
71
70
  const listboxRef = useTemplateRef<ULisboxRef>("dropdown-list");
72
71
  const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
73
72
 
@@ -161,14 +160,29 @@ function onClickLink() {
161
160
 
162
161
  function hideOptions() {
163
162
  isShownOptions.value = false;
163
+ dropdownSearch.value = "";
164
164
 
165
165
  emit("close");
166
166
  }
167
167
 
168
168
  function onClickOption(option: Option) {
169
+ isClickingOption.value = true;
170
+
169
171
  emit("clickOption", option);
170
172
 
171
173
  if (!props.multiple && props.closeOnSelect) hideOptions();
174
+
175
+ nextTick(() => {
176
+ setTimeout(() => {
177
+ isClickingOption.value = false;
178
+ }, 10);
179
+ });
180
+ }
181
+
182
+ function handleClickOutside() {
183
+ if (isClickingOption.value) return;
184
+
185
+ hideOptions();
172
186
  }
173
187
 
174
188
  defineExpose({
@@ -201,7 +215,7 @@ const { config, getDataTest, wrapperAttrs, dropdownLinkAttrs, listboxAttrs, togg
201
215
  <template>
202
216
  <div
203
217
  ref="wrapper"
204
- v-click-outside="hideOptions"
218
+ v-click-outside="handleClickOutside"
205
219
  tabindex="1"
206
220
  v-bind="wrapperAttrs"
207
221
  :data-test="getDataTest('wrapper')"
@@ -1,5 +1,7 @@
1
1
  export default /*tw*/ {
2
- wrapper: "p-3 w-[19rem] border border-default rounded-medium bg-default shadow-sm overflow-hidden focus:outline-hidden",
2
+ wrapper: `
3
+ p-3 w-[19rem] bg-default shadow-sm overflow-hidden focus:outline-hidden
4
+ border border-solid border-default rounded-medium`,
3
5
  navigation: "mb-2 pb-2 border-b border-muted flex items-center justify-between",
4
6
  viewSwitchButton: {
5
7
  base: "{UButton}",
@@ -54,7 +56,7 @@ export default /*tw*/ {
54
56
  timepicker: "mt-2 pl-1 pt-3 text-medium flex items-stretch justify-between gap-2 border-t border-muted",
55
57
  timepickerLabel: "w-full self-center",
56
58
  timepickerInputWrapper: `
57
- flex items-center rounded-medium border border-default
59
+ flex items-center rounded-medium border border-solid border-default
58
60
  hover:focus-within:border-primary focus-within:border-primary
59
61
  focus-within:outline focus-within:outline-small focus-within:outline-primary
60
62
  `,
@@ -3,7 +3,7 @@ export default /*tw*/ {
3
3
  checkbox: {
4
4
  base: `
5
5
  bg-default cursor-pointer transition
6
- border border-default rounded-small outline-transparent
6
+ border border-solid border-default rounded-small outline-transparent
7
7
  appearance-none p-0 print:color-adjust-exact inline-block align-middle bg-origin-border select-none shrink-0
8
8
  hover:border-lifted
9
9
  active:border-{color} active:bg-{color}/15
@@ -31,7 +31,7 @@ export default /*tw*/ {
31
31
  menu: {
32
32
  base: `
33
33
  absolute z-40 mb-3 w-80 overflow-hidden rounded-medium
34
- border border-default bg-default p-2 shadow-sm focus:outline-hidden
34
+ border border-solid border-default bg-default p-2 shadow-sm focus:outline-hidden
35
35
  `,
36
36
  variants: {
37
37
  openDirectionX: {
@@ -12,7 +12,7 @@ export default /*tw*/ {
12
12
  wrapper: {
13
13
  base: `
14
14
  flex gap-3 w-full px-3 relative bg-default transition
15
- border rounded-medium border-default outline-transparent
15
+ border border-solid rounded-medium border-default outline-transparent
16
16
  hover:border-lifted hover:focus-within:border-primary focus-within:border-primary
17
17
  focus-within:outline focus-within:outline-small focus-within:outline-primary focus-within:transition
18
18
  `,
@@ -170,7 +170,7 @@ const {
170
170
  subtractIconAttrs,
171
171
  addButtonAttrs,
172
172
  addIconAttrs,
173
- } = useUI<Config>(defaultConfig, undefined, "counterInput");
173
+ } = useUI<Config>(defaultConfig);
174
174
  </script>
175
175
 
176
176
  <template>
@@ -10,7 +10,7 @@ export default /*tw*/ {
10
10
  },
11
11
  },
12
12
  counterInput: {
13
- base: "{UInputNumber} w-fit",
13
+ base: "{UInputNumber} w-inherit",
14
14
  numberInput: {
15
15
  base: "{UInput}",
16
16
  input: "text-center",
@@ -103,11 +103,7 @@ const selectedValue = computed({
103
103
 
104
104
  return props.modelValue;
105
105
  },
106
- set: (value) => {
107
- if (searchModel.value) searchModel.value = "";
108
-
109
- emit("update:modelValue", value);
110
- },
106
+ set: (value) => emit("update:modelValue", value),
111
107
  });
112
108
 
113
109
  const addOptionKeyCombination = computed(() => {
@@ -2,7 +2,7 @@ export default /*tw*/ {
2
2
  wrapper: {
3
3
  base: `
4
4
  my-2 p-1 flex flex-col gap-1 w-auto absolute z-50 shadow-sm
5
- rounded-medium border border-default bg-default
5
+ rounded-medium border border-solid border-default bg-default
6
6
  overflow-auto [-webkit-overflow-scrolling:touch]
7
7
  focus:outline-hidden
8
8
  `,
@@ -3,7 +3,7 @@ export default /*tw*/ {
3
3
  radio: {
4
4
  base: `
5
5
  bg-default cursor-pointer transition
6
- border border-default rounded-full outline-transparent
6
+ border border-solid border-default rounded-full outline-transparent
7
7
  appearance-none p-0 print:color-adjust-exact inline-block align-middle bg-origin-border select-none shrink-0
8
8
  hover:border-lifted hover:checked:border-{color}
9
9
  active:border-{color} active:bg-{color}/15
@@ -266,6 +266,7 @@ function deactivate() {
266
266
  wrapperRef.value?.blur();
267
267
 
268
268
  isOpen.value = false;
269
+ dropdownSearch.value = "";
269
270
 
270
271
  nextTick(() => emit("close", localValue.value, elementId));
271
272
  }
@@ -12,7 +12,7 @@ export default /*tw*/ {
12
12
  wrapper: {
13
13
  base: `
14
14
  flex flex-row-reverse justify-between w-full min-h-full box-border relative
15
- rounded-medium border border-default bg-default outline-transparent
15
+ rounded-medium border border-solid border-default bg-default outline-transparent
16
16
  hover:border-lifted hover:transition hover:focus-within:border-primary focus-within:border-primary
17
17
  focus-within:outline focus-within:outline-small focus-within:outline-primary focus-within:transition
18
18
  `,
@@ -15,7 +15,7 @@ export default /*tw*/ {
15
15
  wrapper: {
16
16
  base: `
17
17
  flex px-3 py-2 gap-3 w-full bg-default transition
18
- rounded-medium border border-default outline-transparent
18
+ rounded-medium border border-solid border-default outline-transparent
19
19
  hover:border-lifted hover:focus-within:border-primary focus-within:border-primary
20
20
  focus-within:outline focus-within:outline-small focus-within:outline-primary focus-within:transition
21
21
  `,
@@ -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
@@ -51,18 +51,6 @@ import type {
51
51
  } from "../types";
52
52
  import { ColorMode } from "../types";
53
53
 
54
- declare interface RootCSSVariableOptions {
55
- primary: PrimaryColors | string;
56
- neutral: NeutralColors | string;
57
- text: ThemeConfigText;
58
- rounding: ThemeConfigRounding;
59
- outline: ThemeConfigOutline;
60
- letterSpacing: number;
61
- disabledOpacity: number;
62
- lightTheme: Partial<VuelessCssVariables>;
63
- darkTheme: Partial<VuelessCssVariables>;
64
- }
65
-
66
54
  declare interface SetColorMode {
67
55
  colorMode: `${ColorMode}`;
68
56
  isColorModeAuto: boolean;
@@ -334,10 +322,10 @@ export function setTheme(config: ThemeConfig = {}) {
334
322
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
335
323
  export function normalizeThemeConfig(theme: any): MergedThemeConfig {
336
324
  return {
337
- colorMode: theme.colorMode,
338
- isColorModeAuto: theme.isColorModeAuto,
339
- primary: theme.primary,
340
- 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 ?? ""),
341
329
  text: {
342
330
  xs: toNumber(theme.text?.xs),
343
331
  sm: toNumber(theme.text?.sm),
@@ -696,20 +684,20 @@ function getDarkTheme(darkTheme?: Partial<VuelessCssVariables>) {
696
684
  * Generate and apply Vueless CSS variables.
697
685
  * @return string - Vueless CSS variables string.
698
686
  */
699
- function setRootCSSVariables(vars: RootCSSVariableOptions) {
687
+ export function setRootCSSVariables(vars: MergedThemeConfig) {
700
688
  let darkVariables: Partial<VuelessCssVariables> = {};
701
689
 
702
690
  let variables: Partial<VuelessCssVariables> = {
703
- "--vl-text-xs": `${vars.text.xs / PX_IN_REM}rem`,
704
- "--vl-text-sm": `${vars.text.sm / PX_IN_REM}rem`,
705
- "--vl-text-md": `${vars.text.md / PX_IN_REM}rem`,
706
- "--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`,
707
695
  "--vl-outline-sm": `${vars.outline.sm}px`,
708
696
  "--vl-outline-md": `${vars.outline.md}px`,
709
697
  "--vl-outline-lg": `${vars.outline.lg}px`,
710
- "--vl-rounding-sm": `${vars.rounding.sm / PX_IN_REM}rem`,
711
- "--vl-rounding-md": `${vars.rounding.md / PX_IN_REM}rem`,
712
- "--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`,
713
701
  "--vl-letter-spacing": `${vars.letterSpacing}em`,
714
702
  "--vl-disabled-opacity": `${vars.disabledOpacity}%`,
715
703
  };
@@ -724,7 +712,7 @@ function setRootCSSVariables(vars: RootCSSVariableOptions) {
724
712
  `var(--color-${vars.neutral}-${shade})`;
725
713
  }
726
714
 
727
- const [light, dark] = generateCSSColorVariables(vars.lightTheme, vars.darkTheme);
715
+ const [light, dark] = generateCSSColorVariables(vars.lightTheme ?? {}, vars.darkTheme ?? {});
728
716
 
729
717
  variables = { ...variables, ...light };
730
718
  darkVariables = { ...darkVariables, ...dark };
@@ -775,7 +763,7 @@ function setCSSVariables(
775
763
  .join(" ");
776
764
 
777
765
  const rootVariables = `
778
- :root {${variablesString}}
766
+ :host, :root {${variablesString}}
779
767
  .${DARK_MODE_CLASS} {${darkVariablesString}}
780
768
  `;
781
769
 
package/utils/ui.ts CHANGED
@@ -7,7 +7,13 @@ import { createGetMergedConfig } from "./node/mergeConfigs";
7
7
  import { COMPONENT_NAME as U_ICON } from "../ui.image-icon/constants";
8
8
  import { ICON_NON_PROPS_DEFAULTS, TAILWIND_MERGE_EXTENSION } from "../constants";
9
9
 
10
- import type { Config, ComponentDefaults, UnknownObject, ComponentNames } from "../types";
10
+ import type {
11
+ Config,
12
+ UnknownObject,
13
+ ComponentNames,
14
+ ComponentDefaults,
15
+ ComponentCustomProps,
16
+ } from "../types";
11
17
 
12
18
  interface MergedConfigOptions {
13
19
  defaultConfig: unknown;
@@ -87,10 +93,14 @@ export const cva = ({ base = "", variants = {}, compoundVariants = [], defaultVa
87
93
  * Return default values for component props, icons, etc..
88
94
  */
89
95
  export function getDefaults<Props, Config>(defaultConfig: Config, name: ComponentNames) {
90
- const componentDefaults = (defaultConfig as UnknownObject).defaults || {};
96
+ const componentDefaults = (defaultConfig as Config & UnknownObject).defaults || {};
91
97
  const globalDefaults = vuelessConfig.components?.[name]?.defaults || {};
92
98
 
93
- const defaults = merge({}, componentDefaults, globalDefaults) as Props & ComponentDefaults;
99
+ const customProps = vuelessConfig.components?.[name]?.props as ComponentCustomProps;
100
+ const customPropsDefaults = getCustomPropsDefaults(customProps) || {};
101
+
102
+ const defaults = merge({}, componentDefaults, globalDefaults, customPropsDefaults) as Props &
103
+ ComponentDefaults;
94
104
 
95
105
  /* Remove non a props defaults. */
96
106
  for (const key in defaults) {
@@ -116,3 +126,22 @@ export function getDefaults<Props, Config>(defaultConfig: Config, name: Componen
116
126
  export function setColor(classes: string, color: string) {
117
127
  return classes?.replace(/{color}/g, color);
118
128
  }
129
+
130
+ /**
131
+ * Retrieves the default values from the provided component custom properties.
132
+ */
133
+ export function getCustomPropsDefaults(props: ComponentCustomProps) {
134
+ const customPropsDefaults: ComponentDefaults = {};
135
+
136
+ if (!props) {
137
+ return customPropsDefaults;
138
+ }
139
+
140
+ for (const [key, value] of Object.entries(props)) {
141
+ if (value.default) {
142
+ customPropsDefaults[key] = value.default;
143
+ }
144
+ }
145
+
146
+ return customPropsDefaults;
147
+ }