vueless 0.0.477 → 0.0.478-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 (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
@@ -59,7 +59,7 @@ export default function useBreakpoint() {
59
59
  onBeforeUnmount(() => {
60
60
  if (isSSR) return;
61
61
 
62
- window.removeEventListener("resize", resizeListener, { passive: true });
62
+ window.removeEventListener("resize", resizeListener);
63
63
  });
64
64
 
65
65
  watch(windowWidth, setBreakpoint, { immediate: true });
@@ -11,7 +11,7 @@ import {
11
11
  computed,
12
12
  } from "vue";
13
13
 
14
- import { cx, cva, setColor, getColor, vuelessConfig, mergeConfigs } from "../utils/utilUI.js";
14
+ import { cx, cva, setColor, getColor, vuelessConfig } from "../utils/utilUI.js";
15
15
 
16
16
  import { cloneDeep, isCSR } from "../utils/utilHelper.js";
17
17
  import {
@@ -241,6 +241,209 @@ function getMergedConfig({ defaultConfig, globalConfig, propsConfig, vuelessStra
241
241
  }
242
242
  }
243
243
 
244
+ /**
245
+ * Recursively merge config objects with removing tailwind classes duplicates.
246
+ * @param {Object} defaultConfig
247
+ * @param {Object} globalConfig
248
+ * @param {Object} propsConfig
249
+ * @param {Object} config - final merged config.
250
+ * @param {boolean} isReplace - enables class replacement instead of merge.
251
+ * @param {boolean} isVarinants - if true, prevents adding a "base" key into nested objects.
252
+ *
253
+ * @returns {Object}
254
+ */
255
+ function mergeConfigs({
256
+ defaultConfig,
257
+ globalConfig,
258
+ propsConfig,
259
+ config = {},
260
+ isReplace = false,
261
+ isVariants = false,
262
+ }) {
263
+ globalConfig = cloneDeep(globalConfig || {});
264
+ propsConfig = cloneDeep(propsConfig || {});
265
+
266
+ const isGlobalConfig = Object.keys(globalConfig).length;
267
+ const isPropsConfig = Object.keys(propsConfig).length;
268
+
269
+ // Add unique keys from defaultConfig to composedConfig
270
+ let composedConfig = cloneDeep(defaultConfig);
271
+
272
+ // Add unique keys from globalConfig to composedConfig
273
+ for (let key in globalConfig) {
274
+ if (!Object.keys(composedConfig).includes(key)) {
275
+ composedConfig[key] = globalConfig[key];
276
+ }
277
+ }
278
+
279
+ // Add unique keys from propsConfig to composedConfig
280
+ for (let key in propsConfig) {
281
+ if (!Object.keys(composedConfig).includes(key)) {
282
+ composedConfig[key] = propsConfig[key];
283
+ }
284
+ }
285
+
286
+ const {
287
+ i18n,
288
+ defaults,
289
+ strategy,
290
+ safelist,
291
+ component,
292
+ safelistColors,
293
+ defaultVariants,
294
+ compoundVariants,
295
+ } = SYSTEM_CONFIG_KEY;
296
+
297
+ for (let key in composedConfig) {
298
+ if (isGlobalConfig || isPropsConfig) {
299
+ if (key === safelist || key === safelistColors) {
300
+ if (propsConfig[key]) {
301
+ // eslint-disable-next-line no-console
302
+ console.warn(`Passing '${key}' key in 'config' prop is not allowed.`);
303
+ }
304
+ } else if (key === component) {
305
+ config[key] = propsConfig[key] || defaultConfig[key];
306
+
307
+ if (globalConfig[key]) {
308
+ // eslint-disable-next-line no-console
309
+ console.warn(`Passing '${key}' key in 'config' prop or by global config is not allowed.`);
310
+ }
311
+ } else if (key === strategy) {
312
+ config[key] = propsConfig[key] || globalConfig[key] || defaultConfig[key];
313
+ } else if (key === defaults || key === defaultVariants) {
314
+ config[key] = { ...defaultConfig[key], ...globalConfig[key], ...propsConfig[key] };
315
+ } else if (key === compoundVariants) {
316
+ config[key] = mergeCompoundVariants({
317
+ defaultConfig: composedConfig,
318
+ globalConfig,
319
+ propsConfig,
320
+ isReplace,
321
+ key,
322
+ });
323
+ } else {
324
+ const isObjectComposedConfig = typeof composedConfig[key] === "object";
325
+ const isObjectGlobalConfig = typeof globalConfig[key] === "object";
326
+ const isObjectPropsConfig = typeof propsConfig[key] === "object";
327
+
328
+ const isObject = isObjectComposedConfig || isObjectGlobalConfig || isObjectPropsConfig;
329
+ const isEmpty = composedConfig[key] === null;
330
+ const isI18n = key === i18n;
331
+
332
+ if (key === "variants" && !isVariants) {
333
+ isVariants = true;
334
+ }
335
+
336
+ config[key] =
337
+ isObject && !isEmpty && !isI18n
338
+ ? mergeConfigs({
339
+ defaultConfig: stringToObject(composedConfig[key], { addBase: !isVariants }),
340
+ globalConfig: stringToObject(globalConfig[key], { addBase: !isVariants }),
341
+ propsConfig: stringToObject(propsConfig[key], { addBase: !isVariants }),
342
+ config: stringToObject(composedConfig[key], { addBase: !isVariants }),
343
+ isReplace,
344
+ isVariants,
345
+ })
346
+ : isReplace || isI18n
347
+ ? propsConfig[key] || globalConfig[key] || defaultConfig[key]
348
+ : cx([defaultConfig[key], globalConfig[key], propsConfig[key]]);
349
+ }
350
+ } else {
351
+ config[key] = composedConfig[key];
352
+ }
353
+ }
354
+
355
+ return config;
356
+ }
357
+
358
+ /**
359
+ Turn simplified nested component config to regular config.
360
+ @param {Object | String} value
361
+ @param {Boolean} addBase
362
+ @returns {Object}
363
+ */
364
+ function stringToObject(value, { addBase = false }) {
365
+ if (value === undefined) value = "";
366
+
367
+ return typeof value !== "object" ? addBase && { base: value } : value;
368
+ }
369
+
370
+ /**
371
+ * Merge CVA compound variants arrays.
372
+ * @param {Object} defaultConfig
373
+ * @param {Object} globalConfig
374
+ * @param {Object} propsConfig
375
+ * @param {string} key
376
+ * @param {boolean} isReplace - enables class replacement instead of merge.
377
+ *
378
+ * @returns {Array}
379
+ */
380
+ function mergeCompoundVariants({ defaultConfig, globalConfig, propsConfig, key, isReplace }) {
381
+ if (
382
+ (globalConfig[key] && !Array.isArray(globalConfig[key])) ||
383
+ (propsConfig[key] && !Array.isArray(propsConfig[key])) ||
384
+ (defaultConfig[key] && !Array.isArray(defaultConfig[key]))
385
+ ) {
386
+ // eslint-disable-next-line no-console
387
+ console.error("CompoundVariants should be an array.");
388
+ }
389
+
390
+ let globalConfigUniqueItems = cloneDeep(globalConfig[key] || []);
391
+ let propsConfigUniqueItems = cloneDeep(propsConfig[key] || []);
392
+
393
+ const config = defaultConfig[key].map((defaultConfigItem) => {
394
+ /**
395
+ * Compare two objects by keys for match.
396
+ * @param {Object} configItem
397
+ * @returns {Boolean}
398
+ */
399
+ function isSameItem(configItem) {
400
+ const hasConfigItemKeys = Object.keys(defaultConfigItem)
401
+ .map((key) => defaultConfigItem[key] === configItem[key] || key === "class")
402
+ .every((item) => !!item);
403
+
404
+ const hasDefaultConfigItemKeys = Object.keys(configItem)
405
+ .map((key) => defaultConfigItem[key] === configItem[key] || key === "class")
406
+ .every((item) => !!item);
407
+
408
+ return hasConfigItemKeys && hasDefaultConfigItemKeys;
409
+ }
410
+
411
+ /**
412
+ * Find the same compound variant item in custom config if exist.
413
+ * @param {Object} config
414
+ * @returns {Object|undefined}
415
+ */
416
+ function findItem(config = []) {
417
+ const globalConfigUniqueItemIndex = globalConfigUniqueItems.findIndex(isSameItem);
418
+ const propsConfigUniqueItemIndex = propsConfigUniqueItems.findIndex(isSameItem);
419
+
420
+ if (~globalConfigUniqueItemIndex) {
421
+ globalConfigUniqueItems.splice(globalConfigUniqueItemIndex, 1);
422
+ }
423
+
424
+ if (~propsConfigUniqueItemIndex) {
425
+ propsConfigUniqueItems.splice(propsConfigUniqueItemIndex, 1);
426
+ }
427
+
428
+ return config.find(isSameItem);
429
+ }
430
+
431
+ const globalConfigItem = findItem(globalConfig[key]);
432
+ const propsConfigItem = findItem(propsConfig[key]);
433
+
434
+ return globalConfigItem || propsConfigItem
435
+ ? {
436
+ ...defaultConfigItem,
437
+ class: isReplace
438
+ ? propsConfigItem?.class || globalConfigItem?.class || defaultConfigItem.class
439
+ : cx([defaultConfigItem.class, globalConfigItem?.class, propsConfigItem?.class]),
440
+ }
441
+ : defaultConfigItem;
442
+ });
443
+
444
+ return [...config, ...globalConfigUniqueItems, ...propsConfigUniqueItems];
445
+ }
446
+
244
447
  /**
245
448
  * Merge component classes from "class" attribute into final config.
246
449
  * @param {Object} config
@@ -0,0 +1,115 @@
1
+ import { computed, toValue, ref } from "vue";
2
+ import { isSSR } from "../utilsTs/utilHelper.ts";
3
+
4
+ import type { Ref, ComputedRef } from "vue";
5
+
6
+ interface PositionXY {
7
+ x: Position;
8
+ y: Position;
9
+ }
10
+
11
+ export enum Position {
12
+ Left = "left",
13
+ Right = "right",
14
+ Top = "top",
15
+ Bottom = "bottom",
16
+ Auto = "auto",
17
+ }
18
+
19
+ // TODO: Remove after full TS migration, use enum instead.
20
+ export const POSITION = {
21
+ left: "left",
22
+ right: "right",
23
+ top: "top",
24
+ bottom: "bottom",
25
+ auto: "auto",
26
+ };
27
+
28
+ export function useAutoPosition(
29
+ anchorElement: Ref<HTMLElement | null>,
30
+ targetElement: Ref<HTMLElement | null>,
31
+ position: PositionXY | ComputedRef<PositionXY>,
32
+ preferredPosition: PositionXY | ComputedRef<PositionXY>,
33
+ ) {
34
+ const localAnchorElement = computed(() => toValue(anchorElement));
35
+ const localTargetElement = computed(() => toValue(targetElement));
36
+ const localPosition = computed(() => toValue(position));
37
+ const localPreferredPosition = computed(() => toValue(preferredPosition));
38
+
39
+ const preferredOpenDirectionY = ref(localPreferredPosition.value?.y || Position.Bottom);
40
+ const preferredOpenDirectionX = ref(localPreferredPosition.value?.x || Position.Left);
41
+
42
+ const isTop = computed(() => {
43
+ if (localPosition.value.y !== Position.Auto) {
44
+ return localPosition.value.y === Position.Top;
45
+ }
46
+
47
+ return preferredOpenDirectionY.value === Position.Top;
48
+ });
49
+
50
+ const isLeft = computed(() => {
51
+ if (localPosition.value.x !== Position.Auto) {
52
+ return localPosition.value.x === Position.Left;
53
+ }
54
+
55
+ return preferredOpenDirectionX.value === Position.Left;
56
+ });
57
+
58
+ const isBottom = computed(() => {
59
+ if (localPosition.value.y !== Position.Auto) {
60
+ return localPosition.value.y === Position.Bottom;
61
+ }
62
+
63
+ return preferredOpenDirectionY.value === Position.Bottom;
64
+ });
65
+
66
+ const isRight = computed(() => {
67
+ if (localPosition.value.x !== Position.Auto) {
68
+ return localPosition.value.x === Position.Right;
69
+ }
70
+
71
+ return preferredOpenDirectionX.value === Position.Right;
72
+ });
73
+
74
+ function adjustPositionY(): void {
75
+ if (isSSR || !localAnchorElement.value || !localTargetElement.value) return;
76
+
77
+ const spaceAbove = localAnchorElement.value.getBoundingClientRect().top;
78
+ const spaceBelow = window.innerHeight - localAnchorElement.value.getBoundingClientRect().bottom;
79
+ const hasEnoughSpaceBelow =
80
+ spaceBelow > localTargetElement.value.getBoundingClientRect().height;
81
+ const hasEnoughSpaceAbove =
82
+ spaceAbove > localTargetElement.value.getBoundingClientRect().height;
83
+
84
+ if (localPreferredPosition.value.y === Position.Bottom) {
85
+ preferredOpenDirectionY.value =
86
+ hasEnoughSpaceBelow || spaceBelow > spaceAbove ? Position.Bottom : Position.Top;
87
+ }
88
+
89
+ if (localPreferredPosition.value.y === Position.Top) {
90
+ preferredOpenDirectionY.value =
91
+ hasEnoughSpaceAbove || spaceAbove > spaceBelow ? Position.Top : Position.Bottom;
92
+ }
93
+ }
94
+
95
+ function adjustPositionX(): void {
96
+ if (isSSR || !localAnchorElement.value || !localTargetElement.value) return;
97
+
98
+ const spaceRight = localAnchorElement.value.getBoundingClientRect().right;
99
+ const spaceLeft = window.innerWidth - localAnchorElement.value.getBoundingClientRect().left;
100
+ const hasEnoughSpaceLeft = spaceLeft > localTargetElement.value.getBoundingClientRect().width;
101
+ const hasEnoughSpaceRight = spaceRight > localTargetElement.value.getBoundingClientRect().width;
102
+
103
+ if (localPreferredPosition.value.x === Position.Right) {
104
+ preferredOpenDirectionX.value =
105
+ hasEnoughSpaceRight || spaceRight > spaceLeft ? Position.Right : Position.Left;
106
+ }
107
+
108
+ if (localPreferredPosition.value.x === Position.Left) {
109
+ preferredOpenDirectionX.value =
110
+ hasEnoughSpaceLeft || spaceLeft > spaceRight ? Position.Left : Position.Right;
111
+ }
112
+ }
113
+
114
+ return { isTop, isRight, isBottom, isLeft, adjustPositionY, adjustPositionX };
115
+ }
@@ -0,0 +1,106 @@
1
+ import { onMounted, ref, watch, computed, onBeforeUnmount } from "vue";
2
+ import { isSSR } from "../utilsTs/utilHelper.ts";
3
+
4
+ import type { Ref } from "vue";
5
+
6
+ const BREAKPOINT_NAME = {
7
+ xs: "xs",
8
+ sm: "sm",
9
+ md: "md",
10
+ lg: "lg",
11
+ xl: "xl",
12
+ "2xl": "2xl",
13
+ };
14
+
15
+ const BREAKPOINT = {
16
+ xs: 0,
17
+ sm: 640,
18
+ md: 768,
19
+ lg: 1024,
20
+ xl: 1280,
21
+ "2xl": 1536,
22
+ };
23
+
24
+ const mobileDevices = ["xs", "sm"];
25
+ const portableDevices = ["xs", "sm", "md"];
26
+
27
+ export default function useBreakpoint() {
28
+ let timeout: number;
29
+
30
+ const windowWidth: Ref<number | undefined> = ref(undefined);
31
+ const currentBreakpoint = ref(BREAKPOINT_NAME.xs);
32
+
33
+ const isMobileBreakpoint = computed(() => {
34
+ return mobileDevices.includes(currentBreakpoint.value);
35
+ });
36
+
37
+ const isTabletBreakpoint = computed(() => {
38
+ return mobileDevices.includes(currentBreakpoint.value);
39
+ });
40
+
41
+ const isLaptopBreakpoint = computed(() => {
42
+ return currentBreakpoint.value === BREAKPOINT_NAME.lg;
43
+ });
44
+
45
+ const isPortableBreakpoint = computed(() => {
46
+ return portableDevices.includes(currentBreakpoint.value);
47
+ });
48
+
49
+ const elementSize = computed(() => {
50
+ return isMobileBreakpoint.value ? BREAKPOINT_NAME.md : BREAKPOINT_NAME.lg;
51
+ });
52
+
53
+ onMounted(() => {
54
+ if (isSSR) return;
55
+
56
+ windowWidth.value = window.innerWidth;
57
+
58
+ window.addEventListener("resize", resizeListener, { passive: true });
59
+ });
60
+
61
+ onBeforeUnmount(() => {
62
+ if (isSSR) return;
63
+
64
+ window.removeEventListener("resize", resizeListener);
65
+ });
66
+
67
+ watch(windowWidth, setBreakpoint, { immediate: true });
68
+
69
+ function resizeListener() {
70
+ if (isSSR) return;
71
+
72
+ if (timeout) {
73
+ window.cancelAnimationFrame(timeout);
74
+ }
75
+
76
+ timeout = window.requestAnimationFrame(() => {
77
+ windowWidth.value = window.innerWidth;
78
+ });
79
+ }
80
+
81
+ function setBreakpoint(newWindowWidth: number | undefined) {
82
+ if (newWindowWidth === undefined) return;
83
+
84
+ currentBreakpoint.value = "xs";
85
+
86
+ if (newWindowWidth >= BREAKPOINT.sm && newWindowWidth < BREAKPOINT.md) {
87
+ currentBreakpoint.value = "sm";
88
+ } else if (newWindowWidth >= BREAKPOINT.md && newWindowWidth < BREAKPOINT.lg) {
89
+ currentBreakpoint.value = "md";
90
+ } else if (newWindowWidth >= BREAKPOINT.lg && newWindowWidth < BREAKPOINT.xl) {
91
+ currentBreakpoint.value = "lg";
92
+ } else if (newWindowWidth >= BREAKPOINT.xl && newWindowWidth < BREAKPOINT["2xl"]) {
93
+ currentBreakpoint.value = "xl";
94
+ } else if (newWindowWidth >= BREAKPOINT["2xl"]) {
95
+ currentBreakpoint.value = "2xl";
96
+ }
97
+ }
98
+
99
+ return {
100
+ isLaptopBreakpoint,
101
+ isTabletBreakpoint,
102
+ isMobileBreakpoint,
103
+ isPortableBreakpoint,
104
+ elementSize,
105
+ };
106
+ }
@@ -0,0 +1,25 @@
1
+ import { inject } from "vue";
2
+ import createVuelessAdapter from "../adatper.locale/vueless.js";
3
+
4
+ export const LocaleSymbol = Symbol.for("vueless:locale");
5
+
6
+ function isLocaleInstance(obj): boolean {
7
+ return obj.name !== null;
8
+ }
9
+
10
+ export function createLocale(options) {
11
+ const i18n =
12
+ options?.adapter && isLocaleInstance(options?.adapter)
13
+ ? options?.adapter
14
+ : createVuelessAdapter(options);
15
+
16
+ return { ...i18n };
17
+ }
18
+
19
+ export function useLocale() {
20
+ const locale = inject(LocaleSymbol);
21
+
22
+ if (!locale) throw new Error("[vueless] Could not find injected locale instance");
23
+
24
+ return locale;
25
+ }
@@ -0,0 +1,50 @@
1
+ import { onBeforeUnmount, onMounted, toValue, watch } from "vue";
2
+ import { isSSR } from "../utilsTs/utilHelper.ts";
3
+
4
+ import type { Ref } from "vue";
5
+
6
+ export function useMutationObserver(
7
+ target: Ref<HTMLElement | null>,
8
+ callBack: () => void,
9
+ config = { childList: true, attributes: true, characterData: true },
10
+ ) {
11
+ if (isSSR) return;
12
+
13
+ const observer = new MutationObserver(callBack);
14
+
15
+ onMounted(() => {
16
+ if (!toValue(target)) return;
17
+
18
+ if (Array.isArray(toValue(target))) {
19
+ toValue(target).forEach((element) => {
20
+ observer.observe(element, config);
21
+ });
22
+ } else {
23
+ observer.observe(toValue(target), config);
24
+ }
25
+ });
26
+
27
+ watch(
28
+ () => toValue(target),
29
+ () => {
30
+ if (Array.isArray(toValue(target))) {
31
+ toValue(target).forEach((element) => {
32
+ observer.observe(element, config);
33
+ });
34
+
35
+ return;
36
+ }
37
+
38
+ observer.observe(toValue(target), config);
39
+ },
40
+ {
41
+ deep: true,
42
+ },
43
+ );
44
+
45
+ onBeforeUnmount(() => {
46
+ observer.disconnect();
47
+ });
48
+
49
+ return { observer };
50
+ }