vueless 0.0.477 → 0.0.478-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/composables/useBreakpoint.js +1 -1
  2. package/composables/useUI.js +204 -1
  3. package/composablesTs/useAutoPosition.ts +115 -0
  4. package/composablesTs/useBreakpoint.ts +106 -0
  5. package/composablesTs/useLocale.ts +25 -0
  6. package/composablesTs/useMutationObserver.ts +50 -0
  7. package/composablesTs/useUI.ts +562 -0
  8. package/constants.js +2 -1
  9. package/constants.ts +73 -0
  10. package/directives/clickOutside/vClickOutside.js +2 -2
  11. package/directives/tooltip/storybook/stories.js +5 -5
  12. package/{index.js → index.ts} +10 -7
  13. package/package.json +28 -17
  14. package/preset.tailwind.js +16 -7
  15. package/types.ts +223 -0
  16. package/ui.button/config.js +12 -0
  17. package/ui.button-link/ULink.vue +1 -1
  18. package/ui.button-link/config.js +9 -0
  19. package/ui.data-list/UDataList.vue +4 -4
  20. package/ui.dropdown-badge/config.js +1 -0
  21. package/ui.dropdown-button/config.js +1 -0
  22. package/ui.form-checkbox/config.js +9 -0
  23. package/ui.form-color-picker/config.js +7 -0
  24. package/ui.form-input/UInput.vue +1 -1
  25. package/ui.form-input-money/useFormatCurrency.js +1 -1
  26. package/ui.form-input-number/UInputNumber.vue +4 -3
  27. package/ui.form-label/config.js +2 -2
  28. package/ui.form-radio/config.js +6 -0
  29. package/ui.form-switch/config.js +6 -0
  30. package/ui.image-avatar/config.js +5 -0
  31. package/ui.image-icon/config.js +5 -0
  32. package/ui.loader/config.js +1 -0
  33. package/ui.loader-overlay/config.js +1 -0
  34. package/ui.loader-progress/config.js +1 -0
  35. package/ui.navigation-progress/config.js +9 -0
  36. package/ui.other-dot/config.js +1 -0
  37. package/ui.text-alert/config.js +7 -0
  38. package/ui.text-badge/config.js +8 -0
  39. package/ui.text-block/UText.vue +18 -62
  40. package/ui.text-block/storybook/Docs.mdx +3 -3
  41. package/ui.text-block/storybook/{stories.js → stories.ts} +13 -8
  42. package/ui.text-block/types.ts +33 -0
  43. package/ui.text-block/useAttrs.ts +20 -0
  44. package/ui.text-file/UFile.vue +12 -14
  45. package/ui.text-file/config.js +12 -2
  46. package/ui.text-files/config.js +1 -1
  47. package/ui.text-header/config.js +1 -0
  48. package/ui.text-money/config.js +1 -0
  49. package/ui.text-money/utilMoney.js +2 -2
  50. package/utils/utilUI.js +0 -204
  51. package/utilsTs/utilHelper.ts +68 -0
  52. package/utilsTs/utilPlatform.ts +53 -0
  53. package/utilsTs/utilStorybook.ts +296 -0
  54. package/utilsTs/utilTailwind.ts +38 -0
  55. package/{utils/utilTheme.js → utilsTs/utilTheme.ts} +31 -27
  56. package/utilsTs/utilUI.ts +143 -0
  57. package/web-types.json +1 -1
  58. package/ui.text-block/useAttrs.js +0 -15
  59. /package/ui.text-block/{config.js → config.ts} +0 -0
  60. /package/ui.text-block/{constants.js → constants.ts} +0 -0
@@ -0,0 +1,53 @@
1
+ import { isCSR } from "./utilHelper.ts";
2
+
3
+ interface ModernNavigator extends Navigator {
4
+ standalone: string;
5
+ userAgentData: {
6
+ platform: string;
7
+ };
8
+ }
9
+
10
+ const isWindows = isCSR && checkIsWindows();
11
+ const isMac = isCSR && checkIsMac();
12
+ const isPWA = isCSR && checkIsPWA();
13
+ const isIOS = isCSR && checkIsIOS();
14
+ const isAndroid = isCSR && checkIsAndroid();
15
+ const isMobileApp = isPWA || isIOS || isAndroid;
16
+
17
+ function checkIsWindows() {
18
+ return getPlatform().toUpperCase().indexOf("WINDOWS") >= 0;
19
+ }
20
+
21
+ function checkIsMac() {
22
+ return getPlatform().toUpperCase().indexOf("MAC") >= 0;
23
+ }
24
+
25
+ function checkIsPWA() {
26
+ return !!(navigator as ModernNavigator).standalone;
27
+ }
28
+
29
+ function checkIsIOS() {
30
+ const iOSDevices = [
31
+ "iPad Simulator",
32
+ "iPhone Simulator",
33
+ "iPod Simulator",
34
+ "iPad",
35
+ "iPhone",
36
+ "iPod",
37
+ ];
38
+
39
+ const platform = getPlatform();
40
+ const isIpodIOS13 = platform.includes("Mac") && "ontouchend" in document;
41
+
42
+ return iOSDevices.includes(platform) || isIpodIOS13;
43
+ }
44
+
45
+ function checkIsAndroid() {
46
+ return getPlatform().toUpperCase().indexOf("ANDROID") >= 0;
47
+ }
48
+
49
+ function getPlatform() {
50
+ return (navigator as ModernNavigator).userAgentData?.platform || navigator.platform || "unknown";
51
+ }
52
+
53
+ export { isMac, isPWA, isIOS, isAndroid, isMobileApp, isWindows };
@@ -0,0 +1,296 @@
1
+ interface WebTypes {
2
+ framework: string;
3
+ name: string;
4
+ version: string;
5
+ contributions: Contributions;
6
+ }
7
+
8
+ interface Contributions {
9
+ html: HtmlContributions;
10
+ }
11
+
12
+ interface HtmlContributions {
13
+ "description-markup": string;
14
+ "types-syntax": string;
15
+ tags: Tag[];
16
+ }
17
+
18
+ interface Tag {
19
+ name: string;
20
+ description?: string;
21
+ attributes?: Attribute[];
22
+ events?: Event[];
23
+ slots?: Slot[];
24
+ exposes?: Expose[];
25
+ source?: Source;
26
+ }
27
+
28
+ interface Attribute {
29
+ name: string;
30
+ required?: boolean;
31
+ description?: string;
32
+ value: AttributeValue;
33
+ default?: unknown;
34
+ }
35
+
36
+ interface AttributeValue {
37
+ kind: string;
38
+ type: string;
39
+ }
40
+
41
+ interface Event {
42
+ name: string;
43
+ description?: string;
44
+ properties?: EventProperty[];
45
+ }
46
+
47
+ interface EventProperty {
48
+ type: string[];
49
+ name: string;
50
+ description?: string;
51
+ }
52
+
53
+ interface Slot {
54
+ name: string;
55
+ description?: string;
56
+ scoped?: boolean;
57
+ bindings?: SlotBinding[];
58
+ }
59
+
60
+ interface SlotBinding {
61
+ type: string;
62
+ name: string;
63
+ description?: string;
64
+ }
65
+
66
+ interface Expose {
67
+ name: string;
68
+ description?: string;
69
+ properties: ExposeProperty[];
70
+ }
71
+
72
+ interface ExposeProperty {
73
+ type: string;
74
+ description?: string;
75
+ }
76
+
77
+ interface Source {
78
+ module: string;
79
+ symbol: string;
80
+ }
81
+
82
+ interface Types {
83
+ [key: string]: ArgType | undefined;
84
+ }
85
+
86
+ interface ArgType {
87
+ control?: "text" | "number" | "boolean" | "array" | "select" | false;
88
+ options?: string[];
89
+ table?: TableConfig;
90
+ name?: string;
91
+ description?: string;
92
+ type?: string | null;
93
+ action?: string;
94
+ }
95
+
96
+ interface TableConfig {
97
+ disable?: boolean;
98
+ defaultValue?: { summary: unknown };
99
+ category?: "slots" | "expose" | "Storybook Events";
100
+ }
101
+
102
+ /* Load Web-Types from the project root. */
103
+ const [webTypes]: WebTypes[] = Object.values(
104
+ import.meta.glob("/web-types.json", { eager: true, import: "default" }),
105
+ );
106
+
107
+ const getComponentData = (componentName: string | undefined) => {
108
+ if (!componentName) return;
109
+
110
+ return webTypes.contributions.html.tags.find((item: Tag) => item.name === componentName);
111
+ };
112
+
113
+ export function getSlotNames(componentName: string | undefined) {
114
+ if (!componentName) return;
115
+
116
+ return getComponentData(componentName)?.slots?.map((item) => item.name);
117
+ }
118
+
119
+ export function getArgTypes(componentName: string | undefined) {
120
+ const component = getComponentData(componentName);
121
+
122
+ if (!component) return;
123
+
124
+ const types: Partial<Types> = {
125
+ // Hide default template arg in docs.
126
+ defaultTemplate: { table: { disable: true } },
127
+ // Hide slot template arg in docs.
128
+ slotTemplate: { table: { disable: true } },
129
+ };
130
+
131
+ component.attributes?.forEach((attribute: Attribute) => {
132
+ const type = attribute.value.type;
133
+
134
+ if (type === "string" || type.includes("string")) {
135
+ types[attribute.name] = {
136
+ control: "text",
137
+ table: {
138
+ defaultValue: { summary: attribute.default || "" },
139
+ },
140
+ };
141
+ }
142
+
143
+ if (type === "number") {
144
+ types[attribute.name] = {
145
+ control: "number",
146
+ table: {
147
+ defaultValue: { summary: attribute.default || "" },
148
+ },
149
+ };
150
+ }
151
+
152
+ if (type === "boolean") {
153
+ types[attribute.name] = {
154
+ control: "boolean",
155
+ table: {
156
+ defaultValue: { summary: attribute.default || "" },
157
+ },
158
+ };
159
+ }
160
+
161
+ if (type === "array") {
162
+ types[attribute.name] = {
163
+ control: "array",
164
+ table: {
165
+ defaultValue: { summary: attribute.default || [] },
166
+ },
167
+ };
168
+ }
169
+
170
+ if (type.includes("|")) {
171
+ const options = attribute.value.type.replace(/['|]/g, "").split(/\s+/);
172
+
173
+ if (options.length > 1) {
174
+ types[attribute.name] = {
175
+ options,
176
+ control: "select",
177
+ table: {
178
+ defaultValue: { summary: attribute.default || "" },
179
+ },
180
+ };
181
+ } else {
182
+ types[attribute.name] = {
183
+ control: type.split("|")[0] as ArgType["control"],
184
+ table: {
185
+ defaultValue: { summary: attribute.default || "" },
186
+ },
187
+ };
188
+ }
189
+ }
190
+
191
+ if (attribute.description?.includes("@ignore")) {
192
+ types[attribute.name] = {
193
+ table: {
194
+ disable: true,
195
+ },
196
+ };
197
+ }
198
+ });
199
+
200
+ component.slots?.forEach((slot) => {
201
+ const bindings: string[] = [];
202
+
203
+ slot.bindings?.forEach((binding: SlotBinding) => {
204
+ if (binding.name === "name") return;
205
+
206
+ const description = binding.description ? ` (${binding.description})` : "";
207
+
208
+ bindings.push(`${binding.name}: ${binding.type}${description}`);
209
+ });
210
+
211
+ types[`${slot.name}Slot`] = {
212
+ name: slot.name,
213
+ description: slot.description,
214
+ type: slot.bindings ? `{ ${bindings.join(", ")} }` : null,
215
+ control: "text",
216
+ table: { category: "slots" },
217
+ };
218
+
219
+ // Hide autogenerated slot docs, but keep props with the same name
220
+ if (!component.attributes?.map((item) => item.name)?.includes(slot.name)) {
221
+ types[slot.name] = {
222
+ table: {
223
+ disable: true,
224
+ },
225
+ };
226
+ }
227
+ });
228
+
229
+ component.exposes?.forEach((expose) => {
230
+ const properties: string[] = [];
231
+
232
+ expose.properties?.forEach((property: ExposeProperty) => {
233
+ const description = property.description ? ` (${property.description})` : "";
234
+
235
+ properties.push(`${property.type}${description}`);
236
+ });
237
+
238
+ types[`${expose.name}Expose`] = {
239
+ type: expose.properties ? properties.join(", ") : null,
240
+ name: expose.name,
241
+ description: expose.description,
242
+ control: false,
243
+ table: { category: "expose" },
244
+ };
245
+
246
+ // Hide autogenerated expose docs, but keep props with the same name
247
+ if (!component.attributes?.map((item) => item.name)?.includes(expose.name)) {
248
+ types[expose.name] = {
249
+ table: {
250
+ disable: true,
251
+ },
252
+ };
253
+ }
254
+ });
255
+
256
+ component.events?.forEach((event) => {
257
+ const properties: string[] = [];
258
+
259
+ event.properties?.forEach((property: EventProperty) => {
260
+ const description = property.description ? ` (${property.description})` : "";
261
+
262
+ properties.push(`${property.name}: ${property.type}${description}`);
263
+ });
264
+
265
+ types[event.name] = {
266
+ type: event.properties ? properties.join(", ") : null,
267
+ name: event.name,
268
+ description: event.description,
269
+ };
270
+
271
+ if (import.meta.env.STORYBOOK_FULL) {
272
+ const eventName = "on" + event.name.charAt(0).toUpperCase() + event.name.slice(1);
273
+
274
+ types[eventName] = {
275
+ action: event.name,
276
+ table: { category: "Storybook Events" },
277
+ };
278
+ }
279
+ });
280
+
281
+ return types;
282
+ }
283
+
284
+ export function getSource(defaultConfig: string) {
285
+ return defaultConfig.replace("export default /*tw*/ ", "").replace(";", "");
286
+ }
287
+
288
+ export function getSlotsFragment(defaultTemplate: string) {
289
+ return `
290
+ <template v-for="(slot, index) of slots" :key="index" v-slot:[slot]>
291
+ <template v-if="slot === 'default' && !args['defaultSlot']">${defaultTemplate || ""}</template>
292
+ <template v-else-if="slot === 'default' && args['defaultSlot']">{{ args['defaultSlot'] }}</template>
293
+ <template v-else-if="args[slot + 'Slot']">{{ args[slot + 'Slot'] }}</template>
294
+ </template>
295
+ `;
296
+ }
@@ -0,0 +1,38 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import resolveConfig from "tailwindcss/resolveConfig";
4
+ import { isSSR, isCSR } from "./utilHelper.ts";
5
+
6
+ import type { Config } from "tailwindcss";
7
+
8
+ /**
9
+ * Load Tailwind config from the project root.
10
+ * Both for server and client side renderings.
11
+ * IIFE for SSR is used to prevent top level await issue.
12
+ */
13
+ export let fullTailwindConfig: any;
14
+
15
+ if (isSSR) {
16
+ /* Load Tailwind config from the project root in IIFE (no top-level await). */
17
+ (async () => {
18
+ try {
19
+ const filePath = `${process.cwd()}/tailwind.config`;
20
+
21
+ let tailwindConfig = (await import(/* @vite-ignore */ `${filePath}.js`)).default;
22
+
23
+ if (!tailwindConfig) {
24
+ tailwindConfig = (await import(/* @vite-ignore */ `${filePath}.ts`)).default;
25
+ }
26
+
27
+ fullTailwindConfig = resolveConfig(tailwindConfig);
28
+ } catch {}
29
+ })();
30
+ }
31
+
32
+ if (isCSR) {
33
+ const tailwindConfig = Object.values(
34
+ import.meta.glob("/tailwind.config.{js,ts}", { eager: true, import: "default" }),
35
+ )[0] as Config;
36
+
37
+ fullTailwindConfig = resolveConfig(tailwindConfig);
38
+ }
@@ -1,23 +1,26 @@
1
- import colors from "tailwindcss/colors.js";
2
-
3
- import { vuelessConfig } from "./utilUI.js";
4
- import { isSSR, isCSR } from "./utilHelper.js";
1
+ import { fullTailwindConfig } from "./utilTailwind.ts";
2
+ import { isSSR, isCSR } from "./utilHelper.ts";
3
+ import { vuelessConfig } from "./utilUI.ts";
5
4
  import {
6
- GRAY_COLOR,
7
- COOL_COLOR,
8
5
  BRAND_COLORS,
9
6
  GRAYSCALE_COLOR,
10
7
  DEFAULT_RING,
11
8
  DEFAULT_RING_OFFSET,
12
- DEFAULT_RING_OFFSET_COLOR_LIGHT,
13
- DEFAULT_RING_OFFSET_COLOR_DARK,
14
9
  DEFAULT_ROUNDING,
15
10
  DEFAULT_BRAND_COLOR,
16
11
  DEFAULT_GRAY_COLOR,
12
+ DEFAULT_RING_OFFSET_COLOR_LIGHT,
13
+ DEFAULT_RING_OFFSET_COLOR_DARK,
17
14
  DARK_MODE_SELECTOR,
18
15
  GRAY_COLORS,
19
16
  PX_IN_REM,
20
- } from "../constants.js";
17
+ } from "../constants.ts";
18
+
19
+ import type { ThemeConfig, GrayColors, BrandColors, VuelessCssVariables } from "../types.ts";
20
+
21
+ interface InternalThemeConfig extends ThemeConfig {
22
+ systemDarkMode?: boolean;
23
+ }
21
24
 
22
25
  export function themeInit() {
23
26
  if (isSSR) return;
@@ -31,8 +34,12 @@ export function themeInit() {
31
34
  );
32
35
  }
33
36
 
34
- export function setTheme(config = {}) {
37
+ export function setTheme(config: InternalThemeConfig = {}) {
35
38
  const isDarkMode = setDarkMode(config);
39
+ const rounding = config?.rounding ?? vuelessConfig.rounding ?? DEFAULT_ROUNDING;
40
+ let brand: BrandColors | GrayColors = config?.brand ?? vuelessConfig.brand ?? DEFAULT_BRAND_COLOR;
41
+ const gray = config?.gray ?? vuelessConfig.gray ?? DEFAULT_GRAY_COLOR;
42
+
36
43
  const ring = config?.ring ?? vuelessConfig.ring ?? DEFAULT_RING;
37
44
  const ringOffset = config?.ringOffset ?? vuelessConfig.ringOffset ?? DEFAULT_RING_OFFSET;
38
45
 
@@ -46,10 +53,7 @@ export function setTheme(config = {}) {
46
53
  vuelessConfig.ringOffsetColorLight ??
47
54
  DEFAULT_RING_OFFSET_COLOR_LIGHT;
48
55
 
49
- const rounding = config?.rounding ?? vuelessConfig.rounding ?? DEFAULT_ROUNDING;
50
- let brand = config?.brand ?? vuelessConfig.brand ?? DEFAULT_BRAND_COLOR;
51
- let gray = config?.gray ?? vuelessConfig.gray ?? DEFAULT_GRAY_COLOR;
52
-
56
+ const colors = fullTailwindConfig.theme.colors;
53
57
  const isBrandColor = BRAND_COLORS.some((color) => color === brand);
54
58
  const isGrayColor = GRAY_COLORS.some((color) => color === gray);
55
59
 
@@ -69,29 +73,29 @@ export function setTheme(config = {}) {
69
73
  ? defaultRingOffsetColorDark
70
74
  : defaultRingOffsetColorLight;
71
75
 
72
- if (gray === COOL_COLOR) {
73
- gray = GRAY_COLOR;
74
- }
75
-
76
76
  if (brand === GRAYSCALE_COLOR) {
77
77
  brand = gray;
78
78
  }
79
79
 
80
- const variables = {
80
+ const variables: Partial<VuelessCssVariables> = {
81
+ "--vl-rounding": `${Number(rounding) / PX_IN_REM}rem`,
81
82
  "--vl-ring": `${ring}px`,
82
83
  "--vl-ring-offset": `${ringOffset}px`,
83
- "--vl-ring-offset-color": `rgb(${convertHexInRgb(defaultRingOffsetColor)})`,
84
- "--vl-rounding": `${Number(rounding) / PX_IN_REM}rem`,
84
+ "--vl-ring-offset-color": convertHexInRgb(defaultRingOffsetColor),
85
85
  "--vl-color-gray-default": convertHexInRgb(colors[gray][defaultBrandShade]),
86
86
  "--vl-color-brand-default": convertHexInRgb(colors[brand][defaultGrayShade]),
87
87
  };
88
88
 
89
89
  for (const key in colors[gray]) {
90
- variables[`--vl-color-gray-${key}`] = convertHexInRgb(colors[gray][key]);
90
+ variables[`--vl-color-gray-${key}` as keyof VuelessCssVariables] = convertHexInRgb(
91
+ colors[gray][key],
92
+ );
91
93
  }
92
94
 
93
95
  for (const key in colors[brand]) {
94
- variables[`--vl-color-brand-${key}`] = convertHexInRgb(colors[brand][key]);
96
+ variables[`--vl-color-brand-${key}` as keyof VuelessCssVariables] = convertHexInRgb(
97
+ colors[brand][key],
98
+ );
95
99
  }
96
100
 
97
101
  const stringVariables = Object.entries(variables)
@@ -110,14 +114,14 @@ export function setTheme(config = {}) {
110
114
  return rootVariables;
111
115
  }
112
116
 
113
- function setDarkMode(config) {
117
+ function setDarkMode(config: InternalThemeConfig) {
114
118
  config?.darkMode === undefined
115
119
  ? isCSR && localStorage.removeItem(DARK_MODE_SELECTOR)
116
- : isCSR && localStorage.setItem(DARK_MODE_SELECTOR, Number(!!config?.darkMode));
120
+ : isCSR && localStorage.setItem(DARK_MODE_SELECTOR, Number(config?.darkMode).toString());
117
121
 
118
122
  const storedDarkMode = isCSR ? localStorage.getItem(DARK_MODE_SELECTOR) : null;
119
123
 
120
- let isDarkMode =
124
+ const isDarkMode =
121
125
  storedDarkMode !== null
122
126
  ? !!Number(storedDarkMode)
123
127
  : !!(config?.darkMode ?? vuelessConfig.darkMode ?? config?.systemDarkMode);
@@ -129,7 +133,7 @@ function setDarkMode(config) {
129
133
  return isDarkMode;
130
134
  }
131
135
 
132
- export function convertHexInRgb(hex) {
136
+ export function convertHexInRgb(hex: string) {
133
137
  const color = hex.replace(/#/g, "");
134
138
 
135
139
  let r, g, b;
@@ -0,0 +1,143 @@
1
+ import { merge } from "lodash-es";
2
+ import { defineConfig } from "cva";
3
+ import { extendTailwindMerge } from "tailwind-merge";
4
+ import { cloneDeep, isCSR, isSSR } from "./utilHelper.ts";
5
+ import {
6
+ BRAND_COLOR,
7
+ GRAYSCALE_COLOR,
8
+ DEFAULT_BRAND_COLOR,
9
+ NESTED_COMPONENT_REG_EXP,
10
+ } from "../constants.ts";
11
+
12
+ import type { BrandColors, Config, ComponentNames, Component, Defaults } from "../types.ts";
13
+
14
+ /**
15
+ * Load Vueless config from the project root.
16
+ * Both for server and client side renderings.
17
+ * IIFE for SSR is used to prevent top level await issue.
18
+ */
19
+ export let vuelessConfig: Config = {};
20
+
21
+ if (isSSR) {
22
+ /* Load Vueless config from the project root in IIFE (no top-level await). */
23
+ (async () => {
24
+ try {
25
+ const filePath = `${process.cwd()}/vueless.config`;
26
+
27
+ vuelessConfig = (await import(/* @vite-ignore */ `${filePath}.js`)).default;
28
+
29
+ if (!vuelessConfig) {
30
+ vuelessConfig = (await import(/* @vite-ignore */ `${filePath}.ts`)).default;
31
+ }
32
+ } catch {
33
+ vuelessConfig = {};
34
+ }
35
+ })();
36
+ }
37
+
38
+ if (isCSR) {
39
+ vuelessConfig =
40
+ Object.values(
41
+ import.meta.glob("/vueless.config.{js,ts}", { eager: true, import: "default" }),
42
+ )[0] || {};
43
+ }
44
+
45
+ /**
46
+ * Extend twMerge (tailwind merge) by vueless and user config:
47
+ * All list of rules available here:
48
+ * https://github.com/dcastil/tailwind-merge/blob/v2.3.0/src/lib/default-config.ts
49
+ */
50
+ const twMerge = extendTailwindMerge(
51
+ merge(
52
+ {
53
+ extend: {
54
+ theme: {
55
+ spacing: ["safe-top", "safe-bottom", "safe-left", "safe-right"],
56
+ },
57
+ classGroups: {
58
+ "ring-w": [{ ring: ["dynamic"] }],
59
+ "ring-offset-w": [{ "ring-offset": ["dynamic"] }],
60
+ "ring-offset-color": [{ "ring-offset": ["dynamic"] }],
61
+ "font-size": [{ text: ["2xs"] }],
62
+ rounded: [{ rounded: ["dynamic"] }],
63
+ },
64
+ },
65
+ },
66
+ vuelessConfig.tailwindMerge,
67
+ ),
68
+ );
69
+
70
+ /**
71
+ * Export cva (class variance authority) methods:
72
+ * – extended with tailwind-merge
73
+ * – remove all Vueless nested component names ({U...} strings) from class list string.
74
+ * Learn more here: https://beta.cva.style
75
+ */
76
+ export const {
77
+ cx,
78
+ compose,
79
+ cva: classVarianceAuthority,
80
+ } = defineConfig({
81
+ hooks: {
82
+ onComplete: (classNames) => twMerge(classNames).replace(NESTED_COMPONENT_REG_EXP, ""),
83
+ },
84
+ });
85
+
86
+ /* This allows skipping some CVA config keys in vueless config. */
87
+ export const cva = ({ base = "", variants = {}, compoundVariants = [], defaultVariants = {} }) =>
88
+ classVarianceAuthority({
89
+ base,
90
+ variants,
91
+ compoundVariants,
92
+ defaultVariants,
93
+ });
94
+
95
+ /**
96
+ * Return default values for component props, icons, etc..
97
+ */
98
+ export function getDefault<T>(defaultConfig: Component, name: ComponentNames): T {
99
+ const componentDefaults = cloneDeep(defaultConfig.defaults) || {};
100
+ const globalDefaults = cloneDeep(vuelessConfig.component?.[name]?.defaults) || {};
101
+
102
+ const defaults = merge(componentDefaults, globalDefaults) as T & Defaults;
103
+
104
+ if (defaults.color) {
105
+ defaults.color = getColor(defaults.color as BrandColors);
106
+ }
107
+
108
+ return defaults;
109
+ }
110
+
111
+ /**
112
+ * Return `grayscale` color if in component config it `brand` but in vueless config it `grayscale`
113
+ * Otherwise return given color.
114
+ */
115
+ export function getColor(color: string) {
116
+ const isBrandColorGrayscale = (vuelessConfig.brand ?? DEFAULT_BRAND_COLOR) === GRAYSCALE_COLOR;
117
+ const isComponentColorBrand = color === BRAND_COLOR;
118
+
119
+ return isBrandColorGrayscale && isComponentColorBrand ? GRAYSCALE_COLOR : color;
120
+ }
121
+
122
+ /**
123
+ * Replace in tailwind classes `{color}` variable into given color.
124
+ */
125
+ export function setColor(classes: string, color: string) {
126
+ return classes?.replace(/{color}/g, color);
127
+ }
128
+
129
+ /**
130
+ * Generates simple unique identifier.
131
+ */
132
+ export function getRandomId(length = 15) {
133
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
134
+ const charactersLength = characters.length;
135
+
136
+ let id = "";
137
+
138
+ while (id.length < length) {
139
+ id += characters.charAt(Math.floor(Math.random() * charactersLength));
140
+ }
141
+
142
+ return id;
143
+ }
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "vueless",
4
- "version": "0.0.477",
4
+ "version": "0.0.478-beta.1",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",
@@ -1,15 +0,0 @@
1
- import useUI from "../composables/useUI.js";
2
-
3
- import defaultConfig from "./config.js";
4
-
5
- export default function useAttrs(props) {
6
- const { config, getKeysAttrs, hasSlotContent } = useUI(defaultConfig, () => props.config);
7
-
8
- const keysAttrs = getKeysAttrs();
9
-
10
- return {
11
- config,
12
- ...keysAttrs,
13
- hasSlotContent,
14
- };
15
- }
File without changes
File without changes