vueless 0.0.324 → 0.0.326

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.
@@ -0,0 +1,45 @@
1
+ import { onBeforeUnmount, onMounted, toValue, watch } from "vue";
2
+
3
+ export function useMutationObserver(
4
+ target,
5
+ callBack,
6
+ config = { childList: true, attributes: true, characterData: true },
7
+ ) {
8
+ const observer = new MutationObserver(callBack);
9
+
10
+ onMounted(() => {
11
+ if (!toValue(target)) return;
12
+
13
+ if (Array.isArray) {
14
+ toValue(target).forEach((element) => {
15
+ observer.observe(element, config);
16
+ });
17
+ } else {
18
+ observer.observe(toValue(target), config);
19
+ }
20
+ });
21
+
22
+ watch(
23
+ () => toValue(target),
24
+ () => {
25
+ if (Array.isArray) {
26
+ toValue(target).forEach((element) => {
27
+ observer.observe(element, config);
28
+ });
29
+
30
+ return;
31
+ }
32
+
33
+ observer.observe(toValue(target), config);
34
+ },
35
+ {
36
+ deep: true,
37
+ },
38
+ );
39
+
40
+ onBeforeUnmount(() => {
41
+ observer.disconnect();
42
+ });
43
+
44
+ return { observer };
45
+ }
@@ -10,17 +10,15 @@ import {
10
10
  Fragment,
11
11
  } from "vue";
12
12
 
13
- import {
14
- cx,
15
- setColor,
16
- getColor,
17
- strategy,
18
- nestedComponentRegEx,
19
- globalComponentConfig,
20
- } from "../service.ui";
13
+ import { cx, setColor, getColor, vuelessConfig } from "../service.ui/index.js";
21
14
 
22
- import { cloneDeep } from "../service.helper";
23
- import { STRATEGY_TYPE, CVA_CONFIG_KEY, SYSTEM_CONFIG_KEY } from "../constants";
15
+ import { cloneDeep, isCSR } from "../service.helper/index.js";
16
+ import {
17
+ STRATEGY_TYPE,
18
+ CVA_CONFIG_KEY,
19
+ SYSTEM_CONFIG_KEY,
20
+ NESTED_COMPONENT_REG_EXP,
21
+ } from "../constants/index.js";
24
22
 
25
23
  /**
26
24
  Merging component configs in a given sequence (bigger number = bigger priority):
@@ -32,10 +30,11 @@ import { STRATEGY_TYPE, CVA_CONFIG_KEY, SYSTEM_CONFIG_KEY } from "../constants";
32
30
  export default function useUI(defaultConfig = {}, propsConfigGetter = null, topLevelClassKey) {
33
31
  const { type, props } = getCurrentInstance();
34
32
  const componentName = type.name;
35
- const globalConfig = globalComponentConfig[componentName];
33
+ const globalConfig = vuelessConfig?.component ? vuelessConfig?.component[componentName] : {};
36
34
 
37
- const isStrategyValid = strategy && Object.values(STRATEGY_TYPE).includes(strategy);
38
- const vuelessStrategy = isStrategyValid ? strategy : STRATEGY_TYPE.merge;
35
+ const isStrategyValid =
36
+ vuelessConfig.strategy && Object.values(STRATEGY_TYPE).includes(vuelessConfig.strategy);
37
+ const vuelessStrategy = isStrategyValid ? vuelessConfig.strategy : STRATEGY_TYPE.merge;
39
38
 
40
39
  const [firstClassKey] = Object.keys(defaultConfig);
41
40
  const config = ref({});
@@ -58,7 +57,7 @@ export default function useUI(defaultConfig = {}, propsConfigGetter = null, topL
58
57
  const nestedComponent = getNestedComponent(defaultConfig[configKey]);
59
58
 
60
59
  const attrs = useAttrs();
61
- const isDev = import.meta.env.DEV;
60
+ const isDev = isCSR && import.meta.env?.DEV;
62
61
  const vuelessAttrs = ref({});
63
62
  const isTopLevelKey = (topLevelClassKey || firstClassKey) === configKey;
64
63
 
@@ -199,7 +198,6 @@ function mergeConfigs({
199
198
  safelistColors,
200
199
  defaultVariants,
201
200
  compoundVariants,
202
- iconName,
203
201
  } = SYSTEM_CONFIG_KEY;
204
202
 
205
203
  for (let key in composedConfig) {
@@ -235,7 +233,6 @@ function mergeConfigs({
235
233
 
236
234
  const isObject = isObjectComposedConfig || isObjectGlobalConfig || isObjectPropsConfig;
237
235
  const isEmpty = composedConfig[key] === null;
238
- const isIconName = key.toLowerCase().includes(iconName.toLowerCase());
239
236
  const isI18n = key === i18n;
240
237
 
241
238
  if (key === "variants" && !isVariants) {
@@ -252,7 +249,7 @@ function mergeConfigs({
252
249
  isReplace,
253
250
  isVariants,
254
251
  })
255
- : isReplace || isIconName || isI18n
252
+ : isReplace || isI18n
256
253
  ? propsConfig[key] || globalConfig[key] || defaultConfig[key]
257
254
  : cx([defaultConfig[key], globalConfig[key], propsConfig[key]]);
258
255
  }
@@ -388,7 +385,8 @@ function getBaseClasses(value) {
388
385
  function getNestedComponent(value) {
389
386
  const classes = getBaseClasses(value);
390
387
  const component = value?.component || "";
391
- const match = classes.match(nestedComponentRegEx) || component.match(nestedComponentRegEx);
388
+ const match =
389
+ classes.match(NESTED_COMPONENT_REG_EXP) || component.match(NESTED_COMPONENT_REG_EXP);
392
390
 
393
391
  return match ? match[1] : "";
394
392
  }
@@ -401,11 +399,7 @@ function getNestedComponent(value) {
401
399
  function isSystemKey(key) {
402
400
  const isExactKey = Object.values(SYSTEM_CONFIG_KEY).some((value) => value === key);
403
401
 
404
- return (
405
- isExactKey ||
406
- key.toLowerCase().includes(SYSTEM_CONFIG_KEY.iconName.toLowerCase()) ||
407
- key.toLowerCase().includes(SYSTEM_CONFIG_KEY.transition.toLowerCase())
408
- );
402
+ return isExactKey || key.toLowerCase().includes(SYSTEM_CONFIG_KEY.transition.toLowerCase());
409
403
  }
410
404
 
411
405
  /**
@@ -62,6 +62,10 @@ export const SYSTEM_CONFIG_KEY = {
62
62
  component: "component",
63
63
  transition: "transition",
64
64
  safelistColors: "safelistColors",
65
- iconName: "iconName",
66
65
  ...CVA_CONFIG_KEY,
67
66
  };
67
+
68
+ /* Other */
69
+ export const PX_IN_REM = 16;
70
+ export const HYPHEN_SYMBOL = "-";
71
+ export const NESTED_COMPONENT_REG_EXP = /\{U[^}]*}/g;
@@ -1,14 +1,13 @@
1
1
  import tippy from "tippy.js";
2
2
  import { merge } from "lodash-es";
3
3
 
4
+ // Fix for SSR
4
5
  import "tippy.js/dist/tippy.css";
5
6
  import "tippy.js/themes/light.css";
6
7
  import "tippy.js/animations/shift-away.css";
7
8
 
8
- /* Load Vueless config from the project root. */
9
- const [vuelessConfig] = Object.values(
10
- import.meta.glob("/vueless.config.js", { eager: true, import: "default" }),
11
- );
9
+ import { vuelessConfig } from "../service.ui/index.js";
10
+ import { isCSR, isSSR } from "../service.helper/index.js";
12
11
 
13
12
  const globalSettings = vuelessConfig?.directive?.tooltip || {};
14
13
  const defaultSettings = {
@@ -19,21 +18,21 @@ const defaultSettings = {
19
18
 
20
19
  const mergedSettings = merge(defaultSettings, globalSettings);
21
20
 
22
- tippy.setDefaultProps(mergedSettings);
21
+ isCSR && tippy.setDefaultProps(mergedSettings);
23
22
 
24
23
  export default {
25
24
  mounted(el, bindings) {
26
- tippy(el, merge(mergedSettings, bindings.value || {}));
25
+ isCSR && tippy(el, merge(mergedSettings, bindings.value || {}));
27
26
  },
28
27
 
29
28
  updated(el, bindings) {
30
- if (!el._tippy) return;
29
+ if (!el._tippy || isSSR) return;
31
30
 
32
31
  el._tippy.setProps(merge(mergedSettings, bindings.value || {}));
33
32
  },
34
33
 
35
34
  unmounted(el) {
36
- if (!el._tippy) return;
35
+ if (!el._tippy || isSSR) return;
37
36
 
38
37
  el._tippy.destroy();
39
38
  },
package/index.js CHANGED
@@ -2,8 +2,10 @@
2
2
  import { createLocale, LocaleSymbol } from "./composable.locale";
3
3
  import { createLoaderRendering, LoaderRenderingSymbol } from "./ui.loader-rendering/composables/useLoaderRendering";
4
4
  import { createLoaderTop, LoaderTopSymbol } from "./ui.loader-top/composables/useLoaderTop";
5
+ import { themeInit } from "./service.theme";
5
6
 
6
- export { setTheme } from "./service.ui";
7
+ export { setTitle } from "./service.helper";
8
+ export { setTheme } from "./service.theme";
7
9
  export { default as createVueI18nAdapter } from "./adatper.locale/vue-i18n";
8
10
  export { default as defaultEnLocale } from "./adatper.locale/locales/en";
9
11
  export { useLocale } from "./composable.locale";
@@ -33,6 +35,8 @@ export function createVueless(options = {}) {
33
35
  app.provide(LoaderTopSymbol, loaderTop);
34
36
  };
35
37
 
38
+ themeInit();
39
+
36
40
  return {
37
41
  install,
38
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "0.0.324",
3
+ "version": "0.0.326",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless Component Framework.",
6
6
  "homepage": "https://vueless.com",
@@ -37,6 +37,13 @@ export function cloneDeep(entity, cache = new WeakMap()) {
37
37
  );
38
38
  }
39
39
 
40
+ /**
41
+ Invoke function with delay (same as lodash.debounce).
42
+ @param {Function} func
43
+ @param {Number} wait
44
+
45
+ @returns {Function}
46
+ */
40
47
  export function debounce(func, wait) {
41
48
  let timeout;
42
49
 
@@ -47,3 +54,28 @@ export function debounce(func, wait) {
47
54
  timeout = setTimeout(() => func.apply(context, args), wait);
48
55
  };
49
56
  }
57
+
58
+ /**
59
+ Change page title in runtime by provided config.
60
+ @param {Object} config
61
+ @param {string} config.title
62
+ @param {string} config.separator
63
+ @param {string} config.suffix
64
+
65
+ @returns {VoidFunction}
66
+ */
67
+ export function setTitle({ title, separator = " / ", suffix = "" }) {
68
+ document.title = title ? title + separator + suffix : suffix;
69
+ }
70
+
71
+ /**
72
+ Check is code rendering on the server side.
73
+ @returns {boolean}
74
+ */
75
+ export const isSSR = typeof window === "undefined";
76
+
77
+ /**
78
+ Check is code rendering on the client side.
79
+ @returns {boolean}
80
+ */
81
+ export const isCSR = typeof window !== "undefined";
@@ -0,0 +1,124 @@
1
+ import colors from "tailwindcss/colors.js";
2
+
3
+ import { vuelessConfig } from "../service.ui";
4
+ import {
5
+ GRAY_COLOR,
6
+ COOL_COLOR,
7
+ BRAND_COLORS,
8
+ GRAYSCALE_COLOR,
9
+ DEFAULT_RING,
10
+ DEFAULT_RING_OFFSET,
11
+ DEFAULT_ROUNDING,
12
+ DEFAULT_BRAND_COLOR,
13
+ DEFAULT_GRAY_COLOR,
14
+ DARK_MODE_SELECTOR,
15
+ GRAY_COLORS,
16
+ PX_IN_REM,
17
+ } from "../constants/index.js";
18
+
19
+ export function themeInit() {
20
+ const prefersColorSchemeDark = window && window.matchMedia("(prefers-color-scheme: dark)");
21
+
22
+ setTheme({ systemDarkMode: prefersColorSchemeDark.matches });
23
+
24
+ prefersColorSchemeDark.addEventListener("change", (event) =>
25
+ setTheme({ systemDarkMode: event.matches }),
26
+ );
27
+ }
28
+
29
+ export function setTheme(config = {}) {
30
+ const isDarkMode = setDarkMode(config);
31
+ const ring = config?.ring ?? vuelessConfig?.ring ?? DEFAULT_RING;
32
+ const ringOffset = config?.ringOffset ?? vuelessConfig?.ringOffset ?? DEFAULT_RING_OFFSET;
33
+ const rounding = config?.rounding ?? vuelessConfig?.rounding ?? DEFAULT_ROUNDING;
34
+ let brand = config?.brand ?? vuelessConfig?.brand ?? DEFAULT_BRAND_COLOR;
35
+ let gray = config?.gray ?? vuelessConfig?.gray ?? DEFAULT_GRAY_COLOR;
36
+
37
+ const isBrandColor = BRAND_COLORS.some((color) => color === brand);
38
+ const isGrayColor = GRAY_COLORS.some((color) => color === gray);
39
+
40
+ if (!isBrandColor) {
41
+ // eslint-disable-next-line no-console
42
+ console.warn(`Brand color '${brand}' is incorrect.`);
43
+ }
44
+
45
+ if (!isGrayColor) {
46
+ // eslint-disable-next-line no-console
47
+ console.warn(`Gray color '${gray}' is incorrect.`);
48
+ }
49
+
50
+ const defaultBrandShade = isDarkMode ? 400 : 600;
51
+ const defaultGrayShade = isDarkMode ? 400 : 600;
52
+
53
+ if (gray === COOL_COLOR) {
54
+ gray = GRAY_COLOR;
55
+ }
56
+
57
+ if (brand === GRAYSCALE_COLOR) {
58
+ brand = gray;
59
+ }
60
+
61
+ const variables = {
62
+ "--vl-ring": `${ring}px`,
63
+ "--vl-ring-offset": `${ringOffset}px`,
64
+ "--vl-rounding": `${Number(rounding) / PX_IN_REM}rem`,
65
+ "--vl-color-gray-default": convertHexInRgb(colors[gray][defaultBrandShade]),
66
+ "--vl-color-brand-default": convertHexInRgb(colors[brand][defaultGrayShade]),
67
+ };
68
+
69
+ for (const key in colors[gray]) {
70
+ variables[`--vl-color-gray-${key}`] = convertHexInRgb(colors[gray][key]);
71
+ }
72
+
73
+ for (const key in colors[brand]) {
74
+ variables[`--vl-color-brand-${key}`] = convertHexInRgb(colors[brand][key]);
75
+ }
76
+
77
+ const style = document.createElement("style");
78
+ const stringVariables = Object.entries(variables)
79
+ .map(([key, value]) => `${key}: ${value};`)
80
+ .join(" ");
81
+
82
+ style.innerHTML = `:root {${stringVariables}`;
83
+
84
+ document.head.appendChild(style);
85
+ }
86
+
87
+ function setDarkMode(config) {
88
+ config?.darkMode === undefined
89
+ ? localStorage.removeItem(DARK_MODE_SELECTOR)
90
+ : localStorage.setItem(DARK_MODE_SELECTOR, Number(!!config?.darkMode));
91
+
92
+ const storedDarkMode = localStorage.getItem(DARK_MODE_SELECTOR);
93
+
94
+ let isDarkMode =
95
+ storedDarkMode !== null
96
+ ? !!Number(storedDarkMode)
97
+ : !!(config?.darkMode ?? vuelessConfig?.darkMode ?? config?.systemDarkMode);
98
+
99
+ isDarkMode
100
+ ? document.documentElement.classList.add(DARK_MODE_SELECTOR)
101
+ : document.documentElement.classList.remove(DARK_MODE_SELECTOR);
102
+
103
+ return isDarkMode;
104
+ }
105
+
106
+ function convertHexInRgb(hex) {
107
+ const color = hex.replace(/#/g, "");
108
+
109
+ let r, g, b;
110
+
111
+ if (color.length === 6) {
112
+ r = parseInt(color.substring(0, 2), 16);
113
+ g = parseInt(color.substring(2, 4), 16);
114
+ b = parseInt(color.substring(4, 6), 16);
115
+ }
116
+
117
+ if (color.length === 3) {
118
+ r = parseInt(color.substring(0, 1).repeat(2), 16);
119
+ g = parseInt(color.substring(1, 2).repeat(2), 16);
120
+ b = parseInt(color.substring(2, 3).repeat(2), 16);
121
+ }
122
+
123
+ return color.length === 6 || color.length === 3 ? `${r}, ${g}, ${b}` : "";
124
+ }
@@ -1,46 +1,36 @@
1
1
  import { merge } from "lodash-es";
2
2
  import { defineConfig } from "cva";
3
3
  import { extendTailwindMerge } from "tailwind-merge";
4
- import colors from "tailwindcss/colors";
5
-
6
- import { cloneDeep } from "../service.helper";
4
+ import { cloneDeep, isCSR, isSSR } from "../service.helper/index.js";
7
5
  import {
8
- GRAY_COLOR,
9
- COOL_COLOR,
10
6
  BRAND_COLOR,
11
- BRAND_COLORS,
12
7
  GRAYSCALE_COLOR,
13
- DEFAULT_RING,
14
- DEFAULT_RING_OFFSET,
15
- DEFAULT_ROUNDING,
16
8
  DEFAULT_BRAND_COLOR,
17
- DEFAULT_GRAY_COLOR,
18
- DARK_MODE_SELECTOR,
19
- GRAY_COLORS,
20
- } from "../constants";
9
+ NESTED_COMPONENT_REG_EXP,
10
+ } from "../constants/index.js";
21
11
 
22
- /* Load Vueless config from the project root. */
23
- const [vuelessConfig] = Object.values(
24
- import.meta.glob("/vueless.config.js", { eager: true, import: "default" }),
25
- );
12
+ /**
13
+ Load Vueless config from the project root.
14
+ Both for server and client side renderings.
15
+ IIFE is used to cache the results.
16
+ */
17
+ export const vuelessConfig = (() => {
18
+ let config = {};
26
19
 
27
- /*
28
- Export global config settings for the current library.
29
- Did as a separate variables for more comfortable usage.
30
- */
31
- export const {
32
- layout,
33
- strategy,
34
- rounding,
35
- gray,
36
- brand,
37
- tailwindMerge: globalTailwindMergeConfig,
38
- component: globalComponentConfig,
39
- } = vuelessConfig;
20
+ if (isSSR) {
21
+ // TODO: test it in SSR, maybe `await` is needed
22
+ config = import(/* @vite-ignore */ `${process.cwd()}/vueless.config.js`).default;
23
+ }
40
24
 
41
- export const nestedComponentRegEx = /\{U[^}]*}/g;
25
+ if (isCSR) {
26
+ config = Object.values(
27
+ import.meta.glob("/vueless.config.js", { eager: true, import: "default" }),
28
+ )[0];
29
+ }
30
+
31
+ return config;
32
+ })();
42
33
 
43
- //
44
34
  /**
45
35
  Extend twMerge (tailwind merge) by vueless and user config:
46
36
  All list of rules available here:
@@ -61,7 +51,7 @@ const twMerge = extendTailwindMerge(
61
51
  },
62
52
  },
63
53
  },
64
- globalTailwindMergeConfig,
54
+ vuelessConfig.tailwindMerge,
65
55
  ),
66
56
  );
67
57
 
@@ -69,83 +59,52 @@ const twMerge = extendTailwindMerge(
69
59
  Export cva (class variance authority) methods:
70
60
  * extended with tailwind-merge
71
61
  * remove all Vueless nested component names ({U...} strings) from class list string.
72
- It helps to make class variation switchers and removes class duplications.
73
62
  https://beta.cva.style
74
63
  */
75
- export const {
76
- cva: classVarianceAuthority,
77
- cx,
78
- compose,
79
- } = defineConfig({
64
+ export const { cva, cx, compose } = defineConfig({
80
65
  hooks: {
81
- onComplete: (classNames) => twMerge(classNames).replace(nestedComponentRegEx, ""),
66
+ onComplete: (classNames) => twMerge(classNames).replace(NESTED_COMPONENT_REG_EXP, ""),
82
67
  },
83
68
  });
84
69
 
85
- export const cva = ({ base = "", variants = {}, compoundVariants = [], defaultVariants = {} }) =>
86
- classVarianceAuthority({
87
- base,
88
- variants,
89
- compoundVariants,
90
- defaultVariants,
91
- });
92
-
93
- const PX_IN_REM = 16;
94
- const HYPHEN_SYMBOL = "-";
95
-
96
- (() => {
97
- const prefersColorSchemeDark = window && window.matchMedia("(prefers-color-scheme: dark)");
98
-
99
- setTheme({ systemDarkMode: prefersColorSchemeDark.matches });
70
+ /**
71
+ Return default values for component props, icons, etc..
72
+ @param { Object } defaultConfig
73
+ @param { String } name
100
74
 
101
- prefersColorSchemeDark.addEventListener("change", (event) =>
102
- setTheme({ systemDarkMode: event.matches }),
75
+ @returns { Object }
76
+ */
77
+ export function getDefault(defaultConfig, name) {
78
+ const defaults = merge(
79
+ cloneDeep(defaultConfig.defaults),
80
+ vuelessConfig?.component ? vuelessConfig?.component[name]?.defaults : {},
103
81
  );
104
- })();
105
-
106
- function getRandomId(length = 15) {
107
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
108
- const charactersLength = characters.length;
109
- let id = "";
110
-
111
- while (id.length < length) {
112
- id += characters.charAt(Math.floor(Math.random() * charactersLength));
113
- }
114
-
115
- return id;
116
- }
117
-
118
- function setTitle({ title, separator = " / ", suffix = "" }) {
119
- document.title = title ? title + separator + suffix : suffix;
120
- }
121
-
122
- function setFavicon(faviconPath) {
123
- if (!faviconPath) return;
124
-
125
- const head = document.querySelector("head");
126
- const faviconTag = document.createElement("link");
127
-
128
- faviconTag.setAttribute("rel", "shortcut icon");
129
- faviconTag.setAttribute("href", `${faviconPath}?${Math.random()}`);
130
-
131
- head.appendChild(faviconTag);
132
- }
133
-
134
- function getDefault(defaultConfig, name) {
135
- const defaults = merge(cloneDeep(defaultConfig.defaults), globalComponentConfig[name]?.defaults);
136
82
 
137
83
  defaults.color = getColor(defaults.color);
138
84
 
139
85
  return defaults;
140
86
  }
141
87
 
142
- function getColor(color) {
143
- return (brand ?? DEFAULT_BRAND_COLOR) === GRAYSCALE_COLOR && color === BRAND_COLOR
88
+ /**
89
+ Return `grayscale` color if in component config it `brand` but in vueless config it `grayscale`
90
+ Otherwise return given color.
91
+ @param { String } color
92
+ @returns { String }
93
+ */
94
+ export function getColor(color) {
95
+ return (vuelessConfig.brand ?? DEFAULT_BRAND_COLOR) === GRAYSCALE_COLOR && color === BRAND_COLOR
144
96
  ? GRAYSCALE_COLOR
145
97
  : color;
146
98
  }
147
99
 
148
- function setColor(classes, color) {
100
+ /**
101
+ Replace in tailwind classes `{color}` variable into given color.
102
+ @param { String } classes
103
+ @param { String } color
104
+
105
+ @returns { String }
106
+ */
107
+ export function setColor(classes, color) {
149
108
  if (typeof classes !== "string") {
150
109
  return "";
151
110
  }
@@ -153,113 +112,20 @@ function setColor(classes, color) {
153
112
  return classes?.replaceAll("{color}", color);
154
113
  }
155
114
 
156
- function setDarkMode(config) {
157
- config?.darkMode === undefined
158
- ? localStorage.removeItem(DARK_MODE_SELECTOR)
159
- : localStorage.setItem(DARK_MODE_SELECTOR, Number(!!config?.darkMode));
160
-
161
- const storedDarkMode = localStorage.getItem(DARK_MODE_SELECTOR);
162
-
163
- let isDarkMode =
164
- storedDarkMode !== null
165
- ? !!Number(storedDarkMode)
166
- : !!(config?.darkMode ?? vuelessConfig?.darkMode ?? config?.systemDarkMode);
167
-
168
- isDarkMode
169
- ? document.documentElement.classList.add(DARK_MODE_SELECTOR)
170
- : document.documentElement.classList.remove(DARK_MODE_SELECTOR);
171
-
172
- return isDarkMode;
173
- }
174
-
175
- function setTheme(config = {}) {
176
- const isDarkMode = setDarkMode(config);
177
- const ring = config?.ring ?? vuelessConfig?.ring ?? DEFAULT_RING;
178
- const ringOffset = config?.ringOffset ?? vuelessConfig?.ringOffset ?? DEFAULT_RING_OFFSET;
179
- const rounding = config?.rounding ?? vuelessConfig?.rounding ?? DEFAULT_ROUNDING;
180
- let brand = config?.brand ?? vuelessConfig?.brand ?? DEFAULT_BRAND_COLOR;
181
- let gray = config?.gray ?? vuelessConfig?.gray ?? DEFAULT_GRAY_COLOR;
182
-
183
- const isBrandColor = BRAND_COLORS.some((color) => color === brand);
184
- const isGrayColor = GRAY_COLORS.some((color) => color === gray);
185
-
186
- if (!isBrandColor) {
187
- // eslint-disable-next-line no-console
188
- console.warn(`Brand color '${brand}' is incorrect.`);
189
- }
190
-
191
- if (!isGrayColor) {
192
- // eslint-disable-next-line no-console
193
- console.warn(`Gray color '${gray}' is incorrect.`);
194
- }
195
-
196
- const defaultBrandShade = isDarkMode ? 400 : 600;
197
- const defaultGrayShade = isDarkMode ? 400 : 600;
198
-
199
- if (gray === COOL_COLOR) {
200
- gray = GRAY_COLOR;
201
- }
202
-
203
- if (brand === GRAYSCALE_COLOR) {
204
- brand = gray;
205
- }
206
-
207
- const variables = {
208
- "--vl-ring": `${ring}px`,
209
- "--vl-ring-offset": `${ringOffset}px`,
210
- "--vl-rounding": `${Number(rounding) / PX_IN_REM}rem`,
211
- "--vl-color-gray-default": convertHexInRgb(colors[gray][defaultBrandShade]),
212
- "--vl-color-brand-default": convertHexInRgb(colors[brand][defaultGrayShade]),
213
- };
214
-
215
- for (const key in colors[gray]) {
216
- variables[`--vl-color-gray-${key}`] = convertHexInRgb(colors[gray][key]);
217
- }
218
-
219
- for (const key in colors[brand]) {
220
- variables[`--vl-color-brand-${key}`] = convertHexInRgb(colors[brand][key]);
221
- }
222
-
223
- const style = document.createElement("style");
224
- const stringVariables = Object.entries(variables)
225
- .map(([key, value]) => `${key}: ${value};`)
226
- .join(" ");
227
-
228
- style.innerHTML = `:root {${stringVariables}`;
229
-
230
- document.head.appendChild(style);
231
- }
232
-
233
- function convertHexInRgb(hex) {
234
- const color = hex.replace(/#/g, "");
235
-
236
- let r, g, b;
115
+ /**
116
+ Generates unique #id.
117
+ @param { Number } length
118
+ @returns { String }
119
+ */
120
+ export function getRandomId(length = 15) {
121
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
122
+ const charactersLength = characters.length;
237
123
 
238
- if (color.length === 6) {
239
- r = parseInt(color.substring(0, 2), 16);
240
- g = parseInt(color.substring(2, 4), 16);
241
- b = parseInt(color.substring(4, 6), 16);
242
- }
124
+ let id = "";
243
125
 
244
- if (color.length === 3) {
245
- r = parseInt(color.substring(0, 1).repeat(2), 16);
246
- g = parseInt(color.substring(1, 2).repeat(2), 16);
247
- b = parseInt(color.substring(2, 3).repeat(2), 16);
126
+ while (id.length < length) {
127
+ id += characters.charAt(Math.floor(Math.random() * charactersLength));
248
128
  }
249
129
 
250
- return color.length === 6 || color.length === 3 ? `${r}, ${g}, ${b}` : "";
130
+ return id;
251
131
  }
252
-
253
- export {
254
- PX_IN_REM,
255
- HYPHEN_SYMBOL,
256
- getRandomId,
257
- setTitle,
258
- setFavicon,
259
- getDefault,
260
- getColor,
261
- setColor,
262
- setDarkMode,
263
- setTheme,
264
- convertHexInRgb,
265
- };
@@ -43,29 +43,48 @@
43
43
 
44
44
  <div v-if="value?.hasOwnProperty('secondary')">
45
45
  <slot :name="`cell-${key}`" :value="value" :row="row">
46
- <div :data-test="`${dataTest}-${key}-cell`" v-bind="attrs.bodyCellPrimaryAttrs">
46
+ <div
47
+ v-bind="attrs.bodyCellPrimaryAttrs"
48
+ ref="cellRef"
49
+ :title="isElementOverflown(cellRef?.[index]) ? value.primary : ''"
50
+ :data-test="`${dataTest}-${key}-cell`"
51
+ >
47
52
  {{ value.primary || HYPHEN_SYMBOL }}
48
53
  </div>
49
54
 
50
55
  <div v-bind="attrs.bodyCellSecondaryAttrs">
51
56
  <template v-if="Array.isArray(value.secondary)">
52
- <div v-for="(secondary, idx) in value.secondary" :key="idx">
57
+ <div
58
+ v-for="(secondary, idx) in value.secondary"
59
+ ref="cellRef"
60
+ :key="idx"
61
+ :title="isElementOverflown(cellRef?.[index]) ? value.secondary : ''"
62
+ >
53
63
  <span v-bind="attrs.bodyCellSecondaryEmptyAttrs">
54
64
  {{ secondary }}
55
65
  </span>
56
66
  </div>
57
67
  </template>
58
68
 
59
- <template v-else>
69
+ <div
70
+ v-else
71
+ ref="cellRef"
72
+ :title="isElementOverflown(cellRef?.[index]) ? value.secondary : ''"
73
+ >
60
74
  {{ value.secondary }}
61
- </template>
75
+ </div>
62
76
  </div>
63
77
  </slot>
64
78
  </div>
65
79
 
66
80
  <template v-else>
67
81
  <slot :name="`cell-${key}`" :value="value" :row="row">
68
- <div :data-test="`${dataTest}-${key}-cell`" v-bind="attrs.bodyCellPrimaryAttrs">
82
+ <div
83
+ v-bind="attrs.bodyCellPrimaryAttrs"
84
+ ref="cellRef"
85
+ :title="isElementOverflown(cellRef?.[index]) ? value : ''"
86
+ :data-test="`${dataTest}-${key}-cell`"
87
+ >
69
88
  {{ value || value === 0 ? value : HYPHEN_SYMBOL }}
70
89
  </div>
71
90
  </slot>
@@ -90,11 +109,13 @@
90
109
  </template>
91
110
 
92
111
  <script setup>
93
- import { computed } from "vue";
112
+ import { computed, ref } from "vue";
94
113
 
95
- import { HYPHEN_SYMBOL } from "../../service.ui";
114
+ import { HYPHEN_SYMBOL } from "../../constants";
96
115
  import { getFilteredRow } from "../services/table.service.js";
97
116
 
117
+ import { useMutationObserver } from "../../composable.mutationObserver/index.js";
118
+
98
119
  import UIcon from "../../ui.image-icon";
99
120
  import UCheckbox from "../../ui.form-checkbox";
100
121
 
@@ -137,6 +158,10 @@ const emit = defineEmits(["toggleRowVisibility", "click"]);
137
158
 
138
159
  const selectedRows = defineModel("selectedRows", { type: Array, default: () => [] });
139
160
 
161
+ const cellRef = ref(null);
162
+
163
+ useMutationObserver(cellRef, setCellTitle, { childList: true });
164
+
140
165
  const toggleIconConfig = computed(() =>
141
166
  props.row?.row?.isHidden
142
167
  ? props.attrs.bodyCellNestedExpandIconAttrs
@@ -166,4 +191,26 @@ function onClickToggleRowChild(rowId) {
166
191
  function onClick(row) {
167
192
  emit("click", row);
168
193
  }
194
+
195
+ function setCellTitle(mutations) {
196
+ mutations.forEach((mutation) => {
197
+ const { target } = mutation;
198
+
199
+ const isOverflown = isElementOverflown(target);
200
+
201
+ if (isOverflown) {
202
+ target.setAttribute("title", target.textContent);
203
+ }
204
+
205
+ if (!isOverflown && target.hasAttribute("title")) {
206
+ target.removeAttribute("title");
207
+ }
208
+ });
209
+ }
210
+
211
+ function isElementOverflown(element) {
212
+ if (!cellRef.value) return false;
213
+
214
+ return element.clientWidth < element.scrollWidth || element.clientHeight < element.scrollHeight;
215
+ }
169
216
  </script>
@@ -292,7 +292,7 @@ import {
292
292
  getFlatRows,
293
293
  } from "./services/table.service";
294
294
 
295
- import { PX_IN_REM } from "../service.ui";
295
+ import { PX_IN_REM } from "../constants";
296
296
  import { UTable } from "./constants";
297
297
  import useAttrs from "./composables/attrs.composable";
298
298
  import { useLocale } from "../composable.locale";
@@ -1,7 +1,7 @@
1
1
  import useUI from "../../composable.ui";
2
2
  import { cva, cx } from "../../service.ui";
3
3
 
4
- import defaultConfig from "../configs/default.config";
4
+ import defaultConfig from "../configs/default.config.js";
5
5
  import { computed } from "vue";
6
6
 
7
7
  export default function useAttrs(props) {
@@ -80,8 +80,7 @@
80
80
  import { computed, onBeforeUnmount, onMounted, ref } from "vue";
81
81
  import { merge } from "lodash-es";
82
82
 
83
- import { globalComponentConfig, cx, getDefault } from "../service.ui";
84
-
83
+ import { cx, getDefault, vuelessConfig } from "../service.ui";
85
84
  import { useLocale } from "../composable.locale";
86
85
  import useAttrs from "./composables/attrs.composable";
87
86
 
@@ -193,7 +192,7 @@ function getOffsetWidth(selector) {
193
192
  }
194
193
 
195
194
  function setPosition() {
196
- const positionClasses = globalComponentConfig.UNotify?.positionClasses;
195
+ const positionClasses = vuelessConfig.component?.UNotify?.positionClasses;
197
196
  const pageClass = positionClasses?.page || config.value.positionClasses.page;
198
197
  const asideClass = positionClasses?.aside || config.value.positionClasses.aside;
199
198
  const pageWidth = getOffsetWidth(`${pageClass}`);
@@ -1,7 +1,7 @@
1
- import { globalComponentConfig, getRandomId } from "../../service.ui";
1
+ import { getRandomId, vuelessConfig } from "../../service.ui";
2
2
  import { DELAY_BETWEEN_CLONES, DURATION, LOCAL_STORAGE_ID, NOTIFY_TYPE } from "../constants";
3
3
 
4
- const globalNotifyDuration = globalComponentConfig.UNotify?.duration;
4
+ const globalNotifyDuration = vuelessConfig.component?.UNotify?.duration;
5
5
  const notifyClearAllEvent = new Event("notifyClearAll");
6
6
 
7
7
  let lastMessageTime = undefined;
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "framework": "vue",
3
3
  "name": "vueless",
4
- "version": "0.0.324",
4
+ "version": "0.0.326",
5
5
  "contributions": {
6
6
  "html": {
7
7
  "description-markup": "markdown",