vueless 1.5.2-beta.3 → 1.5.2-beta.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.5.2-beta.3",
3
+ "version": "1.5.2-beta.4",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
package/plugin-vite.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  removeIconsCache,
13
13
  copyIconsCache,
14
14
  generateIconExports,
15
+ hasIconsCache,
15
16
  reloadServerOnIconsCacheUpdate,
16
17
  extractIconLines,
17
18
  isIconChanged,
@@ -81,21 +82,28 @@ export const Vueless = function (options = {}) {
81
82
  // Cache to store previous icon content for change detection
82
83
  const iconContentCache = new Map();
83
84
 
85
+ let isProductionBuild = false;
86
+
84
87
  const targetFiles = [
85
88
  ...(include || []),
86
89
  ...getVuelessAppDirs(),
87
90
  ...(isNuxtModuleEnv ? getNuxtDirs() : getVueDirs()),
88
91
  ];
89
92
 
93
+ async function cacheIcons(files) {
94
+ await createIconsCache({ env, targetFiles: files, basePath, debug });
95
+
96
+ if (isNuxtModuleEnv) {
97
+ await copyIconsCache(basePath);
98
+ }
99
+ }
100
+
90
101
  /* if server stopped by developer (Ctrl+C) */
91
102
  process.on("SIGINT", async () => {
92
103
  /* remove `.cache` folder in components and restore changes */
93
104
  await removeCustomPropTypes(vuelessSrcDir);
94
105
  await restoreComponents(vuelessSrcDir);
95
106
 
96
- /* remove cached icons */
97
- await removeIconsCache(basePath);
98
-
99
107
  /* clear tailwind safelist */
100
108
  await clearTailwindSafelist(debug);
101
109
 
@@ -130,6 +138,8 @@ export const Vueless = function (options = {}) {
130
138
  }),
131
139
 
132
140
  configResolved: async (config) => {
141
+ isProductionBuild = config.command === "build" && !config.build.watch;
142
+
133
143
  if (!isNuxtModuleEnv) {
134
144
  /* auto import user configs */
135
145
  await autoImportUserConfigs(basePath);
@@ -152,14 +162,13 @@ export const Vueless = function (options = {}) {
152
162
  /* collect used in project colors for tailwind safelist */
153
163
  await createTailwindSafelist({ env, srcDir: vuelessSrcDir, targetFiles, basePath, debug });
154
164
 
155
- /* cache vueless built-in and project icons */
156
- await removeIconsCache(basePath);
157
- await createIconsCache({ env, targetFiles, basePath, debug });
158
-
159
- if (isNuxtModuleEnv) {
160
- await copyIconsCache(basePath);
165
+ /* cache vueless built-in and project icons (the dev cache is shared between processes) */
166
+ if (isProductionBuild) {
167
+ await removeIconsCache(basePath);
161
168
  }
162
169
 
170
+ await cacheIcons(targetFiles);
171
+
163
172
  /* suppress SOURCEMAP_BROKEN warning from Tailwind */
164
173
  for (const key of ["rollupOptions", "rolldownOptions"]) {
165
174
  const opts = config.build?.[key];
@@ -197,11 +206,7 @@ export const Vueless = function (options = {}) {
197
206
 
198
207
  if (hasIconChanges && currentIconLines.length) {
199
208
  /* cache vueless built-in and project icons */
200
- await createIconsCache({ env, targetFiles: [file], basePath, debug });
201
-
202
- if (isNuxtModuleEnv) {
203
- await copyIconsCache(basePath);
204
- }
209
+ await cacheIcons([file]);
205
210
 
206
211
  reloadServerOnIconsCacheUpdate(server);
207
212
 
@@ -219,6 +224,11 @@ export const Vueless = function (options = {}) {
219
224
  /* load SVG images as a Vue components */
220
225
  load: async (id) => {
221
226
  if (id === RESOLVED_ICONS_VIRTUAL_MODULE_ID) {
227
+ /* restore icons cache if it was removed by another process */
228
+ if (!isProductionBuild && !(await hasIconsCache())) {
229
+ await cacheIcons(targetFiles);
230
+ }
231
+
222
232
  return generateIconExports();
223
233
  }
224
234
 
@@ -226,9 +236,9 @@ export const Vueless = function (options = {}) {
226
236
  },
227
237
 
228
238
  /* remove cached icons */
229
- buildEnd: async function () {
230
- /* skip in watch mode — configResolved won't re-run */
231
- if (this.meta.watchMode) return;
239
+ buildEnd: async () => {
240
+ /* skip in dev and watch mode — the cache is shared and configResolved won't re-run */
241
+ if (!isProductionBuild) return;
232
242
 
233
243
  await removeIconsCache(basePath);
234
244
  },
@@ -379,7 +379,7 @@ export const Slots: StoryFn<DefaultUDatePickerArgs> = (args) => ({
379
379
  v-bind="args"
380
380
  v-model="args.leftModel"
381
381
  class="w-full"
382
- :config="{ datepickerInput: { wrapper: 'pl-0' } }"
382
+ :config="{ datepickerInput: { leftSlot: 'pl-0' } }"
383
383
  >
384
384
  <template #left>
385
385
  <UButton
@@ -396,7 +396,7 @@ export const Slots: StoryFn<DefaultUDatePickerArgs> = (args) => ({
396
396
  v-bind="args"
397
397
  v-model="args.modelValue"
398
398
  class="w-full"
399
- :config="{ datepickerInput: { wrapper: 'pr-0' } }"
399
+ :config="{ datepickerInput: { rightSlot: 'pr-0' } }"
400
400
  >
401
401
  <template #right>
402
402
  <UButton
@@ -391,7 +391,7 @@ export const Slots: StoryFn<DefaultUDatePickerRangeArgs> = (args) => ({
391
391
  v-model="leftModel"
392
392
  variant="input"
393
393
  class="w-full"
394
- :config="{ datepickerInput: { wrapper: 'pl-0' } }"
394
+ :config="{ datepickerInput: { leftSlot: 'pl-0' } }"
395
395
  >
396
396
  <template #left>
397
397
  <UButton
@@ -408,7 +408,7 @@ export const Slots: StoryFn<DefaultUDatePickerRangeArgs> = (args) => ({
408
408
  v-model="args.modelValue"
409
409
  variant="input"
410
410
  class="w-full"
411
- :config="{ datepickerInput: { wrapper: 'pr-0' } }"
411
+ :config="{ datepickerInput: { rightSlot: 'pr-0' } }"
412
412
  >
413
413
  <template #right>
414
414
  <UButton
@@ -222,19 +222,9 @@ function setLabelPosition() {
222
222
 
223
223
  if (leftSlotWrapperRef.value && inputRef.value && labelComponentRef.value?.labelElement) {
224
224
  const leftSlotOrIconWidth = leftSlotWrapperRef.value.getBoundingClientRect().width;
225
- const wrapperElement = inputRef.value.parentElement;
225
+ const inputLeftPadding = parseFloat(getComputedStyle(inputRef.value).paddingLeft);
226
226
 
227
- let wrapperGap = 0;
228
- let wrapperLeftPadding = 0;
229
-
230
- if (wrapperElement) {
231
- wrapperGap = parseFloat(getComputedStyle(wrapperElement).gap);
232
- wrapperLeftPadding = parseFloat(getComputedStyle(wrapperElement).paddingLeft);
233
- }
234
-
235
- if (labelComponentRef.value?.labelElement) {
236
- labelComponentRef.value.labelElement.style.left = `${leftSlotOrIconWidth + wrapperLeftPadding + wrapperGap}px`;
237
- }
227
+ labelComponentRef.value.labelElement.style.left = `${leftSlotOrIconWidth + inputLeftPadding}px`;
238
228
  }
239
229
  }
240
230
 
@@ -11,7 +11,7 @@ export default /*tw*/ {
11
11
  },
12
12
  wrapper: {
13
13
  base: `
14
- flex gap-3 w-full px-3 relative bg-default transition
14
+ flex w-full relative bg-default transition
15
15
  border border-solid rounded-medium border-default outline-solid outline-small outline-transparent
16
16
  hover:border-lifted hover:focus-within:border-primary focus-within:border-primary
17
17
  focus-within:outline focus-within:outline-small focus-within:outline-primary focus-within:transition
@@ -37,11 +37,11 @@ export default /*tw*/ {
37
37
  },
38
38
  leftIcon: "{UIcon} {>inputIcon} -mr-0.5",
39
39
  rightIcon: "{UIcon} {>inputIcon} -ml-0.5",
40
- leftSlot: "flex items-center",
41
- rightSlot: "flex items-center",
40
+ leftSlot: "flex items-center shrink-0 pl-3",
41
+ rightSlot: "flex items-center shrink-0 pr-3",
42
42
  input: {
43
43
  base: `
44
- block w-full py-2 font-normal leading-none! bg-transparent
44
+ block w-full min-w-0 px-3 py-2 font-normal leading-none! bg-transparent
45
45
  border-none rounded-inherit transition focus:outline-none
46
46
  placeholder:font-normal placeholder:text-muted
47
47
  disabled:text-accented disabled:cursor-not-allowed
@@ -211,7 +211,7 @@ export const Slots: StoryFn<UInputArgs> = (args) => ({
211
211
  <UInput
212
212
  label="Phone Number"
213
213
  placeholder="Enter your phone number"
214
- :config="{ wrapper: 'pl-0' }"
214
+ :config="{ leftSlot: 'pl-0' }"
215
215
  >
216
216
  <template #label="{ label }">
217
217
  {{ label }}
@@ -221,7 +221,7 @@ export const Slots: StoryFn<UInputNumberArgs> = (args) => ({
221
221
  <UInputNumber
222
222
  label="Left slot"
223
223
  placeholder="Enter discount amount"
224
- :config="{ numberInput: { wrapper: 'pl-0' } }"
224
+ :config="{ numberInput: { leftSlot: 'pl-0' } }"
225
225
  >
226
226
  <template #left>
227
227
  <UDropdownButton
@@ -161,7 +161,7 @@ export const Slots: StoryFn<UInputPasswordArgs> = (args) => ({
161
161
  v-bind="args"
162
162
  v-model="args.modelValue"
163
163
  label="Enter your password"
164
- :config="{ passwordInput: { wrapper: 'pl-0' } }"
164
+ :config="{ passwordInput: { leftSlot: 'pl-0' } }"
165
165
  >
166
166
  <template #label="{ label }">
167
167
  {{ label }}
@@ -184,7 +184,7 @@ export const Slots: StoryFn<UInputPasswordArgs> = (args) => ({
184
184
  v-bind="args"
185
185
  v-model="args.modelValue"
186
186
  label="Enter your password"
187
- :config="{ passwordInput: { wrapper: 'pr-0' } }"
187
+ :config="{ passwordInput: { rightSlot: 'pr-0' } }"
188
188
  >
189
189
  <template #label="{ label }">
190
190
  {{ label }}
@@ -2,7 +2,7 @@ export default /*tw*/ {
2
2
  searchInput: "{UInput}",
3
3
  searchInputWithButton: {
4
4
  base: "{UInput} {>searchInput}",
5
- wrapper: "pr-0",
5
+ rightSlot: "pr-0",
6
6
  },
7
7
  inputIcon: {
8
8
  base: "{UIcon}",
@@ -17,7 +17,7 @@ export default /*tw*/ {
17
17
  clearIcon: "{>inputIcon}",
18
18
  searchIcon: "{>inputIcon} -mr-1",
19
19
  searchButton: {
20
- base: "{UButton} rounded-l-none ml-2",
20
+ base: "{UButton} self-stretch py-0 rounded-l-none ml-2",
21
21
  defaults: {
22
22
  size: {
23
23
  sm: "xs",
@@ -179,7 +179,7 @@ export const Slots: StoryFn<UInputSearchArgs> = (args) => ({
179
179
 
180
180
  <UInputSearch
181
181
  placeholder="Ask something..."
182
- :config="{ searchInput: { wrapper: 'pl-0' } }"
182
+ :config="{ searchInput: { leftSlot: 'pl-0' } }"
183
183
  >
184
184
  <template #left>
185
185
  <UDropdownButton
@@ -25,7 +25,7 @@ export default /*tw*/ {
25
25
  },
26
26
  content: "flex",
27
27
  label: {
28
- base: "z-10 block leading-none! transition",
28
+ base: "z-1 block leading-none! transition",
29
29
  variants: {
30
30
  size: {
31
31
  sm: "text-small",
@@ -146,19 +146,9 @@ function setLabelPosition() {
146
146
 
147
147
  if (leftSlotWrapperRef.value && textareaRef.value && labelComponentRef.value?.labelElement) {
148
148
  const leftSlotWidth = leftSlotWrapperRef.value.getBoundingClientRect().width;
149
- const wrapperElement = textareaRef.value.parentElement;
149
+ const textareaPaddingLeft = parseFloat(getComputedStyle(textareaRef.value).paddingLeft);
150
150
 
151
- let wrapperGap = 0;
152
- let wrapperLeftPadding = 0;
153
-
154
- if (wrapperElement) {
155
- wrapperGap = parseFloat(getComputedStyle(wrapperElement).gap);
156
- wrapperLeftPadding = parseFloat(getComputedStyle(wrapperElement).paddingLeft);
157
- }
158
-
159
- if (labelComponentRef.value?.labelElement) {
160
- labelComponentRef.value.labelElement.style.left = `${leftSlotWidth + wrapperLeftPadding + wrapperGap}px`;
161
- }
151
+ labelComponentRef.value.labelElement.style.left = `${leftSlotWidth + textareaPaddingLeft}px`;
162
152
  }
163
153
  }
164
154
 
@@ -9,12 +9,12 @@ export default /*tw*/ {
9
9
  },
10
10
  },
11
11
  },
12
- slot: "flex items-center justify-center whitespace-nowrap gap-1 rounded-medium",
13
- leftSlot: "{>slot} rounded-r-none",
14
- rightSlot: "{>slot} rounded-l-none",
12
+ slot: "flex shrink-0 items-center justify-center whitespace-nowrap gap-1 rounded-medium",
13
+ leftSlot: "{>slot} pl-3 rounded-r-none",
14
+ rightSlot: "{>slot} pr-3 rounded-l-none",
15
15
  wrapper: {
16
16
  base: `
17
- flex px-3 py-2 gap-3 w-full bg-default transition
17
+ flex w-full bg-default transition
18
18
  rounded-medium border border-solid border-default outline-solid outline-small outline-transparent
19
19
  hover:border-lifted hover:focus-within:border-primary focus-within:border-primary
20
20
  focus-within:outline focus-within:outline-small focus-within:outline-primary focus-within:transition
@@ -30,7 +30,7 @@ export default /*tw*/ {
30
30
  },
31
31
  textarea: {
32
32
  base: `
33
- block w-full bg-transparent border-none font-normal
33
+ block w-full min-w-0 px-3 py-2 bg-transparent border-none font-normal
34
34
  placeholder:text-muted placeholder:font-normal placeholder:leading-none
35
35
  focus:outline-none disabled:cursor-not-allowed
36
36
  `,
@@ -51,9 +51,9 @@ export default /*tw*/ {
51
51
  },
52
52
  },
53
53
  compoundVariants: [
54
- { labelAlign: "topInside", label: true, size: "sm", class: "mt-3" },
55
- { labelAlign: "topInside", label: true, size: "md", class: "mt-4" },
56
- { labelAlign: "topInside", label: true, size: "lg", class: "mt-5" },
54
+ { labelAlign: "topInside", label: true, size: "sm", class: "pt-5" },
55
+ { labelAlign: "topInside", label: true, size: "md", class: "pt-6" },
56
+ { labelAlign: "topInside", label: true, size: "lg", class: "pt-7" },
57
57
  ],
58
58
  },
59
59
  defaults: {
@@ -2,6 +2,7 @@ export function createIconsCache({ env, debug, targetFiles, basePath }?: string)
2
2
  export function removeIconsCache(basePath: string, isVuelessEnv?: boolean): Promise<void>;
3
3
  export function copyIconsCache(basePath: string, isVuelessEnv?: boolean): Promise<void>;
4
4
  export function generateIconExports(): string;
5
+ export function hasIconsCache(): Promise<boolean>;
5
6
  export function reloadServerOnIconsCacheUpdate(server: Object): void;
6
7
  export function extractIconLines(content: string): string[];
7
8
  export function isIconChanged(currentLines: string[], previousLines: string[]): boolean;
@@ -126,10 +126,7 @@ export async function copyIconsCache(basePath, isVuelessEnv = false) {
126
126
  * @return {string} A string containing the export statement for the cached SVG icons as an array to be used in Vite.
127
127
  */
128
128
  export async function generateIconExports() {
129
- const cachePath = path.join(cwd(), ICONS_CACHED_DIR);
130
- const files = await getDirFiles(cachePath, ".svg", {
131
- recursive: true,
132
- });
129
+ const files = await getCachedIconFiles();
133
130
 
134
131
  const entries = files.map((filePath) => {
135
132
  const fullImportPath = path.resolve(filePath).replace(/\\/g, "/");
@@ -142,6 +139,22 @@ export async function generateIconExports() {
142
139
  return `export const cachedIcons = [\n${entries.join(",\n")}\n];`;
143
140
  }
144
141
 
142
+ /**
143
+ * Check if the icons cache contains at least one SVG icon.
144
+ * @returns {Promise<boolean>}
145
+ */
146
+ export async function hasIconsCache() {
147
+ return (await getCachedIconFiles()).length > 0;
148
+ }
149
+
150
+ /**
151
+ * Get paths of all cached SVG icons.
152
+ * @returns {Promise<string[]>}
153
+ */
154
+ function getCachedIconFiles() {
155
+ return getDirFiles(path.join(cwd(), ICONS_CACHED_DIR), ".svg", { recursive: true });
156
+ }
157
+
145
158
  /**
146
159
  * Reloads the server when the icons cache is updated. This function sets up a file system watcher
147
160
  * on the icons cache directory and triggers a full server reload whenever files are added or removed.
@@ -193,9 +206,11 @@ async function findAndCopyIcons(files, library, vuelessConfig, debug) {
193
206
  const validIconNamesRegex = /^(?!icon$|name$)[a-z0-9_-]+$/;
194
207
  const ternaryRegex = /\?.*:/;
195
208
 
209
+ const iconNames = new Set();
210
+
196
211
  safelistIcons?.forEach((iconName) => {
197
212
  if (validIconNamesRegex.test(iconName)) {
198
- copyIcon(iconName, library);
213
+ iconNames.add(iconName);
199
214
  }
200
215
  });
201
216
 
@@ -212,11 +227,7 @@ async function findAndCopyIcons(files, library, vuelessConfig, debug) {
212
227
  const iconName = iconNameMatch && iconNameMatch[2];
213
228
 
214
229
  if (validIconNamesRegex.test(iconName)) {
215
- try {
216
- iconName && copyIcon(iconName, library);
217
- } catch (error) {
218
- debug && console.log(error);
219
- }
230
+ iconNames.add(iconName);
220
231
  }
221
232
 
222
233
  iconNameRegex.lastIndex = 0;
@@ -249,10 +260,10 @@ async function findAndCopyIcons(files, library, vuelessConfig, debug) {
249
260
  continue;
250
261
  }
251
262
 
252
- copyIcon(trueName, library);
253
- copyIcon(falseName, library);
263
+ iconNames.add(trueName);
264
+ iconNames.add(falseName);
254
265
  } else {
255
- copyIcon(iconName, library);
266
+ iconNames.add(iconName);
256
267
  }
257
268
  } catch (error) {
258
269
  debug && console.log(error);
@@ -260,6 +271,12 @@ async function findAndCopyIcons(files, library, vuelessConfig, debug) {
260
271
  }
261
272
  }
262
273
  });
274
+
275
+ await Promise.all(
276
+ [...iconNames].map((iconName) =>
277
+ copyIcon(iconName, library).catch((error) => debug && console.log(error)),
278
+ ),
279
+ );
263
280
  }
264
281
 
265
282
  /**