vueless 1.2.10-beta.9 → 1.2.11
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/README.md +1 -1
- package/bin/commands/copy.js +14 -16
- package/composables/useRequestQueue.ts +5 -2
- package/package.json +1 -1
- package/plugin-vite.js +5 -0
- package/ui.button-link/ULink.vue +7 -0
- package/ui.form-calendar/UCalendarMonthView.vue +4 -4
- package/ui.form-calendar/tests/UCalendar.test.ts +4 -4
- package/ui.form-calendar/tests/UCalendarMonthView.test.ts +1 -1
- package/ui.navigation-tabs/UTabs.vue +0 -1
- package/utils/node/componentOverride.d.ts +2 -0
- package/utils/node/componentOverride.js +139 -0
- package/utils/node/dynamicProps.js +4 -4
- package/v.click-outside/vClickOutside.ts +3 -0
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Vueless is simple enough for everyday use and powerful enough for advanced scena
|
|
|
26
26
|
- ♿️ Accessibility (a11y)
|
|
27
27
|
- ⚙️ Server-side rendering (SSR) friendly
|
|
28
28
|
- 🖼️ 1000+ built-in SVG icons
|
|
29
|
-
- 🧪️
|
|
29
|
+
- 🧪️ 1300+ unit tests ensuring consistent logic
|
|
30
30
|
- 🛡️ Full TypeScript support with type safety
|
|
31
31
|
|
|
32
32
|
### Advanced Features
|
package/bin/commands/copy.js
CHANGED
|
@@ -14,15 +14,16 @@ import { COMPONENTS, VUELESS_PACKAGE_DIR, VUELESS_USER_COMPONENTS_DIR } from "..
|
|
|
14
14
|
/**
|
|
15
15
|
* Copies an existing Vueless component to a new location with a new name.
|
|
16
16
|
* This includes duplicating the source files and updating the component's internal references as needed.
|
|
17
|
+
* If the target name is omitted or matches the source name, the component will override the original.
|
|
17
18
|
*
|
|
18
|
-
* @param {Array<string>} options - An array containing two elements:
|
|
19
|
+
* @param {Array<string>} options - An array containing one or two elements:
|
|
19
20
|
* – The name of the component to be copied.
|
|
20
|
-
* – The desired name for the copied component.
|
|
21
|
+
* – (Optional) The desired name for the copied component. If omitted, uses the source component name.
|
|
21
22
|
* @return {Promise<void>} A promise that resolves when the component has been successfully copied.
|
|
22
23
|
* If an error occurs, no value is returned, and the operation exits early with a logged message.
|
|
23
24
|
*/
|
|
24
25
|
export async function copyVuelessComponent(options) {
|
|
25
|
-
const [componentName, newComponentName] = options;
|
|
26
|
+
const [componentName, newComponentName = ""] = options;
|
|
26
27
|
|
|
27
28
|
if (!componentName) {
|
|
28
29
|
console.log(styleText("red", "Component name is required."));
|
|
@@ -36,21 +37,14 @@ export async function copyVuelessComponent(options) {
|
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
if (newComponentName
|
|
40
|
-
console.log(
|
|
41
|
-
styleText("red", `Component with name '${newComponentName}' already exists in Vueless UI.`),
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!newComponentName.startsWith("U")) {
|
|
40
|
+
if (newComponentName && !newComponentName.startsWith("U")) {
|
|
48
41
|
console.log(styleText("red", `Component should have 'U' prefix (ex. 'UButtonCustom').`));
|
|
49
42
|
|
|
50
43
|
return;
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
const absoluteSourcePath = path.join(cwd(), VUELESS_PACKAGE_DIR, COMPONENTS[componentName]);
|
|
47
|
+
|
|
54
48
|
const absoluteDestPath = path.join(cwd(), VUELESS_USER_COMPONENTS_DIR, newComponentName);
|
|
55
49
|
|
|
56
50
|
if (existsSync(absoluteDestPath)) {
|
|
@@ -62,10 +56,14 @@ export async function copyVuelessComponent(options) {
|
|
|
62
56
|
await cp(absoluteSourcePath, absoluteDestPath, { recursive: true });
|
|
63
57
|
await modifyCreatedComponent(absoluteDestPath, componentName, newComponentName);
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const isOverridingOriginal = componentName === newComponentName;
|
|
60
|
+
const destDir = `${VUELESS_USER_COMPONENTS_DIR}/${newComponentName}`;
|
|
61
|
+
|
|
62
|
+
const successMessage = isOverridingOriginal
|
|
63
|
+
? `The '${componentName}' was successfully copied and will override the original component.`
|
|
64
|
+
: `The '${componentName}' was successfully copied into the '${destDir}' directory.`;
|
|
65
|
+
|
|
66
|
+
console.log(styleText("green", successMessage));
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readonly, ref } from "vue";
|
|
2
|
+
import { isCSR } from "../utils/helper";
|
|
2
3
|
|
|
3
4
|
import type { Ref } from "vue";
|
|
4
5
|
|
|
@@ -34,8 +35,10 @@ function onRemoveFromRequestQueue(event: CustomEvent<{ request: string }>) {
|
|
|
34
35
|
removeFromRequestQueue(event.detail.request);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
window.addEventListener("
|
|
38
|
+
if (isCSR) {
|
|
39
|
+
window.addEventListener("addToRequestQueue", onAddToRequestQueue as EventListener);
|
|
40
|
+
window.addEventListener("removeFromRequestQueue", onRemoveFromRequestQueue as EventListener);
|
|
41
|
+
}
|
|
39
42
|
|
|
40
43
|
export function useRequestQueue() {
|
|
41
44
|
return {
|
package/package.json
CHANGED
package/plugin-vite.js
CHANGED
|
@@ -20,6 +20,7 @@ import { createTailwindSafelist, clearTailwindSafelist } from "./utils/node/tail
|
|
|
20
20
|
import { componentResolver, directiveResolver } from "./utils/node/vuelessResolver.js";
|
|
21
21
|
import { setCustomPropTypes, removeCustomPropTypes } from "./utils/node/dynamicProps.js";
|
|
22
22
|
import { buildWebTypes } from "./utils/node/webTypes.js";
|
|
23
|
+
import { overrideComponents, restoreComponents } from "./utils/node/componentOverride.js";
|
|
23
24
|
import {
|
|
24
25
|
getNuxtDirs,
|
|
25
26
|
getVueDirs,
|
|
@@ -84,6 +85,7 @@ export const Vueless = function (options = {}) {
|
|
|
84
85
|
process.on("SIGINT", async () => {
|
|
85
86
|
if (isInternalEnv || isStorybookEnv) {
|
|
86
87
|
await removeCustomPropTypes(vuelessSrcDir);
|
|
88
|
+
await restoreComponents(vuelessSrcDir);
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
/* remove cached icons */
|
|
@@ -129,6 +131,9 @@ export const Vueless = function (options = {}) {
|
|
|
129
131
|
await cacheMergedConfigs({ vuelessSrcDir, basePath });
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
/* override components with custom ones from .vueless/components */
|
|
135
|
+
await overrideComponents({ vuelessSrcDir });
|
|
136
|
+
|
|
132
137
|
/* set custom prop types */
|
|
133
138
|
await setCustomPropTypes({ vuelessSrcDir, basePath });
|
|
134
139
|
|
package/ui.button-link/ULink.vue
CHANGED
|
@@ -91,7 +91,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
91
91
|
size="md"
|
|
92
92
|
v-bind="selectedMonthAttrs"
|
|
93
93
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
94
|
-
:label="formatDate(month, '
|
|
94
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
95
95
|
@click="onClickMonth(month)"
|
|
96
96
|
@mousedown.prevent.capture
|
|
97
97
|
/>
|
|
@@ -102,7 +102,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
102
102
|
color="primary"
|
|
103
103
|
v-bind="currentMonthAttrs"
|
|
104
104
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
105
|
-
:label="formatDate(month, '
|
|
105
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
106
106
|
@click="onClickMonth(month)"
|
|
107
107
|
@mousedown.prevent.capture
|
|
108
108
|
/>
|
|
@@ -114,7 +114,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
114
114
|
size="md"
|
|
115
115
|
v-bind="activeMonthAttrs"
|
|
116
116
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
117
|
-
:label="formatDate(month, '
|
|
117
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
118
118
|
@click="onClickMonth(month)"
|
|
119
119
|
@mousedown.prevent.capture
|
|
120
120
|
/>
|
|
@@ -126,7 +126,7 @@ const { monthViewAttrs, monthAttrs, currentMonthAttrs, selectedMonthAttrs, activ
|
|
|
126
126
|
size="md"
|
|
127
127
|
v-bind="monthAttrs"
|
|
128
128
|
:disabled="dateIsOutOfRange(month, minDate, maxDate, locale, dateFormat)"
|
|
129
|
-
:label="formatDate(month, '
|
|
129
|
+
:label="formatDate(month, 'M', props.locale)"
|
|
130
130
|
@click="onClickMonth(month)"
|
|
131
131
|
@mousedown.prevent.capture
|
|
132
132
|
/>
|
|
@@ -585,10 +585,10 @@ describe("UCalendar.vue", () => {
|
|
|
585
585
|
});
|
|
586
586
|
|
|
587
587
|
it("Arrow Key Navigation – moves focus correctly in month view", async () => {
|
|
588
|
-
const expectedMonthAfterRight = "
|
|
589
|
-
const expectedMonthAfterLeft = "
|
|
590
|
-
const expectedMonthAfterDown = "
|
|
591
|
-
const expectedMonthAfterUp = "
|
|
588
|
+
const expectedMonthAfterRight = "Feb";
|
|
589
|
+
const expectedMonthAfterLeft = "Jan";
|
|
590
|
+
const expectedMonthAfterDown = "Apr";
|
|
591
|
+
const expectedMonthAfterUp = "Jan";
|
|
592
592
|
|
|
593
593
|
const component = mount(UCalendar, {
|
|
594
594
|
props: {
|
|
@@ -205,7 +205,7 @@ describe("UCalendarMonthView.vue", () => {
|
|
|
205
205
|
|
|
206
206
|
const allButtons = component.findAllComponents(UButton);
|
|
207
207
|
const mayButton = allButtons.find((button) => button.props("label") === "May");
|
|
208
|
-
const decemberButton = allButtons.find((button) => button.props("label") === "
|
|
208
|
+
const decemberButton = allButtons.find((button) => button.props("label") === "Dec");
|
|
209
209
|
|
|
210
210
|
expect(mayButton?.props("disabled")).toBe(true);
|
|
211
211
|
expect(decemberButton?.props("disabled")).toBe(true);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { cwd } from "node:process";
|
|
5
|
+
|
|
6
|
+
import { CACHE_DIR, COMPONENTS, VUELESS_USER_COMPONENTS_DIR } from "../../constants.js";
|
|
7
|
+
|
|
8
|
+
async function isDirNotEmpty(dirPath) {
|
|
9
|
+
if (!existsSync(dirPath)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const files = await fs.readdir(dirPath);
|
|
15
|
+
|
|
16
|
+
return files.length > 0;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function cacheOriginalComponent(componentDir, componentName) {
|
|
23
|
+
const cacheDir = path.join(componentDir, CACHE_DIR, componentName);
|
|
24
|
+
|
|
25
|
+
if (existsSync(cacheDir)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!existsSync(path.join(componentDir, CACHE_DIR))) {
|
|
30
|
+
await fs.mkdir(path.join(componentDir, CACHE_DIR), { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
const files = await fs.readdir(componentDir);
|
|
36
|
+
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const filePath = path.join(componentDir, file);
|
|
39
|
+
const stat = await fs.stat(filePath);
|
|
40
|
+
|
|
41
|
+
if (stat.isFile()) {
|
|
42
|
+
const destPath = path.join(cacheDir, file);
|
|
43
|
+
|
|
44
|
+
await fs.copyFile(filePath, destPath);
|
|
45
|
+
} else if (stat.isDirectory() && file !== CACHE_DIR) {
|
|
46
|
+
const destPath = path.join(cacheDir, file);
|
|
47
|
+
|
|
48
|
+
await fs.cp(filePath, destPath, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function copyCustomComponent(customComponentDir, targetComponentDir) {
|
|
54
|
+
const files = await fs.readdir(customComponentDir);
|
|
55
|
+
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const sourcePath = path.join(customComponentDir, file);
|
|
58
|
+
const destPath = path.join(targetComponentDir, file);
|
|
59
|
+
const stat = await fs.stat(sourcePath);
|
|
60
|
+
|
|
61
|
+
if (stat.isFile()) {
|
|
62
|
+
await fs.copyFile(sourcePath, destPath);
|
|
63
|
+
} else if (stat.isDirectory()) {
|
|
64
|
+
await fs.cp(sourcePath, destPath, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function overrideComponents({ vuelessSrcDir } = {}) {
|
|
70
|
+
const customComponentsDir = path.join(cwd(), VUELESS_USER_COMPONENTS_DIR);
|
|
71
|
+
|
|
72
|
+
if (!existsSync(customComponentsDir)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for await (const [componentName, componentDir] of Object.entries(COMPONENTS)) {
|
|
77
|
+
const customComponentPath = path.join(customComponentsDir, componentName);
|
|
78
|
+
const isCustomComponentExists = await isDirNotEmpty(customComponentPath);
|
|
79
|
+
|
|
80
|
+
if (!isCustomComponentExists) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const targetComponentDir = path.join(vuelessSrcDir, componentDir);
|
|
85
|
+
|
|
86
|
+
await cacheOriginalComponent(targetComponentDir, componentName);
|
|
87
|
+
await copyCustomComponent(customComponentPath, targetComponentDir);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function restoreComponents(vuelessSrcDir) {
|
|
92
|
+
for await (const componentDir of Object.values(COMPONENTS)) {
|
|
93
|
+
const componentPath = path.join(vuelessSrcDir, componentDir);
|
|
94
|
+
const cachePath = path.join(componentPath, CACHE_DIR);
|
|
95
|
+
|
|
96
|
+
if (!existsSync(cachePath)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const cachedDirs = await fs.readdir(cachePath);
|
|
101
|
+
|
|
102
|
+
for (const cachedDir of cachedDirs) {
|
|
103
|
+
const cachedComponentPath = path.join(cachePath, cachedDir);
|
|
104
|
+
const stat = await fs.stat(cachedComponentPath);
|
|
105
|
+
|
|
106
|
+
if (!stat.isDirectory()) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const files = await fs.readdir(cachedComponentPath);
|
|
111
|
+
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
const sourcePath = path.join(cachedComponentPath, file);
|
|
114
|
+
const destPath = path.join(componentPath, file);
|
|
115
|
+
const fileStat = await fs.stat(sourcePath);
|
|
116
|
+
|
|
117
|
+
if (fileStat.isFile()) {
|
|
118
|
+
await fs.copyFile(sourcePath, destPath);
|
|
119
|
+
} else if (fileStat.isDirectory()) {
|
|
120
|
+
const destExists = existsSync(destPath);
|
|
121
|
+
|
|
122
|
+
if (destExists) {
|
|
123
|
+
await fs.rm(destPath, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await fs.cp(sourcePath, destPath, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await fs.rm(cachedComponentPath, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const remainingFiles = await fs.readdir(cachePath);
|
|
134
|
+
|
|
135
|
+
if (remainingFiles.length === 0) {
|
|
136
|
+
await fs.rmdir(cachePath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -22,6 +22,7 @@ const OPTIONAL_MARK = "?";
|
|
|
22
22
|
const CLOSING_BRACKET = "}";
|
|
23
23
|
const IGNORE_PROP = "@ignore";
|
|
24
24
|
const CUSTOM_PROP = "@custom";
|
|
25
|
+
const DEFAULT_PROP_TYPE = "string";
|
|
25
26
|
|
|
26
27
|
/* regular expressions */
|
|
27
28
|
const PROPS_INTERFACE_REG_EXP = /export\s+interface\s+Props(?:<[^>]+>)?\s*{([^}]*)}/s;
|
|
@@ -243,7 +244,7 @@ async function modifyComponentTypes(filePath, props) {
|
|
|
243
244
|
const lines = propsInterface.split("\n");
|
|
244
245
|
|
|
245
246
|
for (const name in props) {
|
|
246
|
-
const { type
|
|
247
|
+
const { type, values = [], description, required, ignore } = props[name];
|
|
247
248
|
|
|
248
249
|
/* Find line with prop. */
|
|
249
250
|
const propRegex = new RegExp(`^\\s*${name}[?:]?\\s*:`);
|
|
@@ -269,7 +270,7 @@ async function modifyComponentTypes(filePath, props) {
|
|
|
269
270
|
.includes("@extendOnly");
|
|
270
271
|
|
|
271
272
|
const propDescription = description?.replaceAll(/[\n\s]+/g, " ").trim() || "–"; // removes new lines and double spaces.
|
|
272
|
-
const propType = unionType.length ? unionType : type;
|
|
273
|
+
const propType = unionType.length ? unionType : type || DEFAULT_PROP_TYPE;
|
|
273
274
|
|
|
274
275
|
/* Add ignore JSDoc property. */
|
|
275
276
|
if (ignore) {
|
|
@@ -284,7 +285,6 @@ async function modifyComponentTypes(filePath, props) {
|
|
|
284
285
|
if (unionType.length && (isAssignableValue || !isExtendOnly)) {
|
|
285
286
|
// Remove multiline union types;
|
|
286
287
|
lines.splice(propIndex + 1, propEndIndex);
|
|
287
|
-
|
|
288
288
|
lines.splice(propIndex, 1, ` ${name}${defaultOptionalMark}: ${propType};`);
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -311,7 +311,7 @@ async function modifyComponentTypes(filePath, props) {
|
|
|
311
311
|
` * ${propDescription}`,
|
|
312
312
|
` * ${CUSTOM_PROP}`,
|
|
313
313
|
` */`,
|
|
314
|
-
` ${name}${optionalMark}: ${type};`,
|
|
314
|
+
` ${name}${optionalMark}: ${type || DEFAULT_PROP_TYPE};`,
|
|
315
315
|
];
|
|
316
316
|
|
|
317
317
|
lines.splice(closingBracketIndex, 0, ...propDefinition);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { unref } from "vue";
|
|
2
|
+
import { isSSR } from "../utils/helper";
|
|
2
3
|
|
|
3
4
|
import type { MaybeRef } from "vue";
|
|
4
5
|
import type {
|
|
@@ -42,6 +43,8 @@ function clickOutside(
|
|
|
42
43
|
handler(event);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
if (isSSR) return () => {};
|
|
47
|
+
|
|
45
48
|
window.addEventListener("click", onClick, { passive: true, capture });
|
|
46
49
|
window.addEventListener("pointerdown", onClick, { passive: true });
|
|
47
50
|
|