vueless 1.5.1-beta.5 → 1.5.1-beta.7
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/composables/tests/useUI.test.ts +11 -0
- package/composables/useUI.ts +2 -2
- package/constants.js +1 -1
- package/package.json +1 -1
- package/ui.form-label/config.ts +2 -2
- package/ui.form-label/tests/ULabel.test.ts +12 -0
- package/utils/node/vuelessConfig.js +2 -1
- package/utils/tests/ui.test.ts +48 -0
- package/utils/ui.ts +14 -9
|
@@ -284,6 +284,17 @@ describe("useUI", () => {
|
|
|
284
284
|
expect(result).toHaveProperty("iconAttrs");
|
|
285
285
|
expect(result).toHaveProperty("buttonAttrs");
|
|
286
286
|
});
|
|
287
|
+
|
|
288
|
+
it("should strip every nested component marker from classes", () => {
|
|
289
|
+
const defaultConfig = {
|
|
290
|
+
label: "{ULabel} {ULabel} label-class",
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const result = useUI(defaultConfig);
|
|
294
|
+
|
|
295
|
+
expect(result.labelAttrs.value.class).not.toContain("{ULabel}");
|
|
296
|
+
expect(result.labelAttrs.value.class).toContain("label-class");
|
|
297
|
+
});
|
|
287
298
|
});
|
|
288
299
|
|
|
289
300
|
describe("Reactivity", () => {
|
package/composables/useUI.ts
CHANGED
|
@@ -170,7 +170,7 @@ export function useUI<T>(defaultConfig: T, mutatedProps?: MutatedProps, topLevel
|
|
|
170
170
|
|
|
171
171
|
classes = classes
|
|
172
172
|
.replaceAll(EXTENDS_PATTERN_REG_EXP, "")
|
|
173
|
-
.
|
|
173
|
+
.replaceAll(NESTED_COMPONENT_PATTERN_REG_EXP, "");
|
|
174
174
|
|
|
175
175
|
return color && !isNestedComponent ? setColor(classes, color) : classes;
|
|
176
176
|
}
|
|
@@ -335,7 +335,7 @@ function getExtendsKeys(configItemValue?: string | CVA | NestedComponent): strin
|
|
|
335
335
|
*/
|
|
336
336
|
function getNestedComponent(value?: string | CVA | NestedComponent) {
|
|
337
337
|
const classes = getBaseClasses(value);
|
|
338
|
-
const match = classes.
|
|
338
|
+
const [match] = classes.matchAll(NESTED_COMPONENT_PATTERN_REG_EXP);
|
|
339
339
|
|
|
340
340
|
return match ? match[1] : "";
|
|
341
341
|
}
|
package/constants.js
CHANGED
|
@@ -445,7 +445,7 @@ export const FAILURE_CODE = 1;
|
|
|
445
445
|
|
|
446
446
|
/* Other */
|
|
447
447
|
export const PX_IN_REM = 16;
|
|
448
|
-
export const NESTED_COMPONENT_PATTERN_REG_EXP = /\{(U[^}]*)}
|
|
448
|
+
export const NESTED_COMPONENT_PATTERN_REG_EXP = /\{(U[^}]*)}/g;
|
|
449
449
|
export const EXTENDS_PATTERN_REG_EXP = /\{>[^}]*}/g;
|
|
450
450
|
export const DYNAMIC_COLOR_PATTERN = "{color}";
|
|
451
451
|
export const JAVASCRIPT_EXT = ".js";
|
package/package.json
CHANGED
package/ui.form-label/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default /*tw*/ {
|
|
2
2
|
wrapper: {
|
|
3
|
-
base: "flex w-full group/label",
|
|
3
|
+
base: "flex w-full max-w-full group/label",
|
|
4
4
|
variants: {
|
|
5
5
|
align: {
|
|
6
6
|
topInside: "flex-col gap-0 relative",
|
|
@@ -25,7 +25,7 @@ export default /*tw*/ {
|
|
|
25
25
|
},
|
|
26
26
|
content: "flex",
|
|
27
27
|
label: {
|
|
28
|
-
base: "z-10 block leading-none!
|
|
28
|
+
base: "z-10 block leading-none! transition",
|
|
29
29
|
variants: {
|
|
30
30
|
size: {
|
|
31
31
|
sm: "text-small",
|
|
@@ -151,6 +151,18 @@ describe("ULabel.vue", () => {
|
|
|
151
151
|
});
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
it("Align – side-aligned label can wrap within its container", () => {
|
|
155
|
+
const component = mount(ULabel, {
|
|
156
|
+
props: {
|
|
157
|
+
label: "Label",
|
|
158
|
+
align: "right",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(component.attributes("class")).toContain("max-w-full");
|
|
163
|
+
expect(component.find("[vl-key='label']").attributes("class")).not.toContain("w-max");
|
|
164
|
+
});
|
|
165
|
+
|
|
154
166
|
it("Size – applies correct class for size prop", () => {
|
|
155
167
|
const sizeCases = {
|
|
156
168
|
sm: "text-small",
|
|
@@ -60,7 +60,8 @@ const twMerge = extendTailwindMerge(
|
|
|
60
60
|
export const { cx } = defineConfig({
|
|
61
61
|
cx: cvaCx,
|
|
62
62
|
hooks: {
|
|
63
|
-
onComplete: (classNames) =>
|
|
63
|
+
onComplete: (classNames) =>
|
|
64
|
+
twMerge(classNames).replaceAll(NESTED_COMPONENT_PATTERN_REG_EXP, ""),
|
|
64
65
|
},
|
|
65
66
|
});
|
|
66
67
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { COLOR_MODE_KEY } from "../../constants.js";
|
|
4
|
+
import { loadSSRVuelessConfig, setVuelessConfig, vuelessConfig } from "../ui";
|
|
5
|
+
|
|
6
|
+
import type { Config } from "../../types";
|
|
7
|
+
|
|
8
|
+
/* jsdom doesn't implement matchMedia, which theme.ts evaluates on import. */
|
|
9
|
+
const prefersDark = { matches: false, addEventListener: vi.fn(), removeEventListener: vi.fn() };
|
|
10
|
+
|
|
11
|
+
window.matchMedia = vi.fn(() => prefersDark) as unknown as typeof window.matchMedia;
|
|
12
|
+
|
|
13
|
+
const { getThemeCookieName } = await import("../theme");
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
Object.keys(vuelessConfig).forEach((key) => delete vuelessConfig[key as keyof Config]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("loadSSRVuelessConfig", () => {
|
|
20
|
+
it.each([
|
|
21
|
+
["resolves", () => ({ cookiePrefix: "from-file" })],
|
|
22
|
+
[
|
|
23
|
+
"fails",
|
|
24
|
+
() => {
|
|
25
|
+
throw new Error("Cannot find module '/vueless.config'");
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
])("keeps the injected config when a deferred import %s", async (_, settle: () => Config) => {
|
|
29
|
+
let release!: () => void;
|
|
30
|
+
const released = new Promise<void>((resolve) => (release = resolve));
|
|
31
|
+
|
|
32
|
+
const loading = loadSSRVuelessConfig(() => released.then(() => ({ default: settle() })));
|
|
33
|
+
|
|
34
|
+
setVuelessConfig({ cookiePrefix: "app-a" });
|
|
35
|
+
expect(getThemeCookieName(COLOR_MODE_KEY)).toBe(`app-a-${COLOR_MODE_KEY}`);
|
|
36
|
+
|
|
37
|
+
release();
|
|
38
|
+
await loading;
|
|
39
|
+
|
|
40
|
+
expect(getThemeCookieName(COLOR_MODE_KEY)).toBe(`app-a-${COLOR_MODE_KEY}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("uses the imported config when none was injected", async () => {
|
|
44
|
+
await loadSSRVuelessConfig(async () => ({ default: { cookiePrefix: "from-file" } }));
|
|
45
|
+
|
|
46
|
+
expect(getThemeCookieName(COLOR_MODE_KEY)).toBe(`from-file-${COLOR_MODE_KEY}`);
|
|
47
|
+
});
|
|
48
|
+
});
|
package/utils/ui.ts
CHANGED
|
@@ -48,17 +48,22 @@ if (isCSR) {
|
|
|
48
48
|
)[0] || {};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
/** Load Vueless config on the server; a config injected via `createVueless` meanwhile wins. */
|
|
52
|
+
export async function loadSSRVuelessConfig(
|
|
53
|
+
// @ts-expect-error: vueless.config.{js,ts} is optional
|
|
54
|
+
importConfig = () => import(/* @vite-ignore */ "/vueless.config"),
|
|
55
|
+
) {
|
|
56
|
+
try {
|
|
57
|
+
const { default: config } = await importConfig();
|
|
58
|
+
|
|
59
|
+
if (!Object.keys(vuelessConfig).length) vuelessConfig = config;
|
|
60
|
+
} catch {
|
|
61
|
+
/* No config file: keep the current config. */
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
if (isSSR) loadSSRVuelessConfig();
|
|
66
|
+
|
|
62
67
|
/**
|
|
63
68
|
* Extend twMerge (tailwind merge) by vueless and user config:
|
|
64
69
|
*/
|