vueless 1.3.4-beta.2 β†’ 1.3.4-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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Vueless UI
4
4
 
5
- A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [Storybook v9](https://storybook.js.org) and [Tailwind CSS v4](https://tailwindcss.com).
5
+ A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [Storybook v10](https://storybook.js.org) and [Tailwind CSS v4](https://tailwindcss.com).
6
6
 
7
7
  **With Vueless UI, you’re free to:**
8
8
  - πŸͺ„️ Customize any component
@@ -10,13 +10,14 @@ A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [
10
10
  - 🧱 Build your own from scratch
11
11
  - πŸ“• Document it all seamlessly in Storybook
12
12
 
13
- [Documentation](https://docs.vueless.com/) | [UI Components](https://ui.vueless.com/) | [Website](http://vueless.com/)
13
+ [Documentation](https://docs.vueless.com/) | [UI Components](https://ui.vueless.com/) | [Theme Builder](https://my.vueless.com/theme) | [About](http://vueless.com/)
14
14
 
15
15
  ### Key features
16
16
 
17
17
  - 🧩 65+ crafted UI components (including range date picker, multi-select, and nested table)
18
18
  - ✨ Open Architecture lets you customize, copy, extend, and create your own components
19
19
  - πŸ“• Built-in Storybook support
20
+ - πŸͺ© Theme Builder for runtime theme customization
20
21
  - 🌈 Beautiful default UI theme
21
22
  - πŸŒ€ Unstyled mode
22
23
  - πŸŒ— Light and dark mode
@@ -30,6 +31,22 @@ A UI library with Open Architecture for Vue.js 3 and Nuxt.js 3 / 4, powered by [
30
31
  - πŸ§ͺ️ 1300+ unit tests ensuring consistent logic
31
32
  - πŸ›‘οΈ Full TypeScript support with type safety
32
33
 
34
+ ## Built-In Storybook
35
+
36
+ No setup, no hacks β€” just a fully functional Storybook preset ready to test your Vueless UI design system out of the box.
37
+
38
+ [Demo](https://ui.vueless.com) | [Package](https://www.npmjs.com/package/@vueless/storybook) | [Docs](https://docs.vueless.com/installation/storybook)
39
+
40
+ ![storybook.png](public/images/storybook.png)
41
+
42
+ ## Theme Builder
43
+
44
+ Customize colors, rounding, and typography at runtime, generate full palettes, and export a ready-to-use theme to your project.
45
+
46
+ [Try Vueless UI Theme Builder](https://my.vueless.com/theme) πŸš€
47
+
48
+ ![theme-builder.png](public/images/theme-builder.png)
49
+
33
50
  ## Quick Start (Vue)
34
51
 
35
52
  ### New project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.3.4-beta.2",
3
+ "version": "1.3.4-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",
@@ -38,7 +38,7 @@ import {
38
38
 
39
39
  import defaultConfig from "./config";
40
40
 
41
- import type { Props, DateValue, RangeDate, Locale, Config } from "./types";
41
+ import type { Props, DateValue, Locale, Config } from "./types";
42
42
  import type { DateLocale } from "./utilFormatting";
43
43
  import type { ComponentExposed } from "../types";
44
44
 
@@ -189,21 +189,14 @@ const userFormatLocale = computed(() => {
189
189
  const localValue = computed({
190
190
  get: () => {
191
191
  if (props.range) {
192
- const isModelValueRangeType =
193
- props.modelValue &&
194
- typeof props.modelValue === "object" &&
195
- !(props.modelValue instanceof Date);
192
+ if (tempRangeValue.value) return tempRangeValue.value;
196
193
 
197
- const modelValue = isModelValueRangeType
198
- ? (props.modelValue as RangeDate)
199
- : (props.modelValue as string | Date);
200
-
201
- const from = isRangeDate(modelValue) ? modelValue.from : modelValue || null;
202
- const to = isRangeDate(modelValue) ? modelValue.to : null;
194
+ const from = isRangeDate(props.modelValue) ? props.modelValue.from : null;
195
+ const to = isRangeDate(props.modelValue) ? props.modelValue.to : null;
203
196
 
204
197
  return {
205
- from: parseDate(from || null, actualDateFormat.value, locale.value),
206
- to: parseDate(to || null, actualDateFormat.value, locale.value),
198
+ from: parseDate(from, actualDateFormat.value, locale.value),
199
+ to: parseDate(to, actualDateFormat.value, locale.value),
207
200
  };
208
201
  }
209
202
 
@@ -256,7 +249,14 @@ const localValue = computed({
256
249
 
257
250
  const newRangeDate = { from: newDate, to: newDateTo };
258
251
 
259
- emit("update:modelValue", props.range ? (newRangeDate as RangeDate) : newDate);
252
+ if (props.range) {
253
+ if (newDate && newDateTo) {
254
+ tempRangeValue.value = null;
255
+ emit("update:modelValue", newRangeDate);
256
+ }
257
+ } else {
258
+ emit("update:modelValue", newDate);
259
+ }
260
260
 
261
261
  if (parsedDate === null && isTimepickerEnabled.value && isInputRefs.value) {
262
262
  const currentDate = new Date();
@@ -269,6 +269,7 @@ const localValue = computed({
269
269
  });
270
270
 
271
271
  const activeDate = ref();
272
+ const tempRangeValue = ref<TModelValue | null>(null);
272
273
 
273
274
  if (isRangeDate(localValue.value)) {
274
275
  activeDate.value = localValue.value.from || getDateWithoutTime();
@@ -452,18 +453,28 @@ function onInputDate(newDate: Date | null) {
452
453
  }
453
454
 
454
455
  if (props.range && isRangeDate(localValue.value)) {
455
- const isFullReset =
456
- (localValue.value.from && newDate < localValue.value.from) ||
457
- (localValue.value.to && localValue.value.from);
456
+ const localFromTime =
457
+ localValue.value?.from instanceof Date ? localValue.value?.from.getTime() : 0;
458
458
 
459
- const updatedValue =
460
- isFullReset || !localValue.value.from
461
- ? { from: newDate, to: null }
462
- : { from: localValue.value.from, to: newDate };
459
+ const isSameAsFrom = newDate.getTime() === localFromTime;
460
+ const isNewDateLessFromDate = localValue.value.from && newDate < localValue.value.from;
461
+ const areToAndFromDateExists = localValue.value.to && localValue.value.from;
462
+ const hasFrom = localValue.value.from;
463
463
 
464
- localValue.value = updatedValue;
464
+ const isFullReset = isSameAsFrom || isNewDateLessFromDate || areToAndFromDateExists || !hasFrom;
465
465
 
466
- emit("input", updatedValue);
466
+ const updatedValue = isFullReset
467
+ ? { from: newDate, to: null }
468
+ : { from: localValue.value.from, to: newDate };
469
+
470
+ if (updatedValue.from && updatedValue.to) {
471
+ tempRangeValue.value = null;
472
+ localValue.value = updatedValue;
473
+
474
+ emit("input", updatedValue);
475
+ } else {
476
+ tempRangeValue.value = updatedValue;
477
+ }
467
478
  } else {
468
479
  localValue.value = newDate;
469
480
 
@@ -117,18 +117,18 @@ describe("UCalendar.vue", () => {
117
117
  const days = dayView.findAll('[vl-key="day"]');
118
118
 
119
119
  await days[0].trigger("click");
120
+
121
+ expect(component.emitted("update:modelValue")).toBeFalsy();
122
+
120
123
  await days[3].trigger("click");
121
124
 
122
125
  expect(component.emitted("update:modelValue")).toBeTruthy();
126
+ expect(component.emitted("update:modelValue")).toHaveLength(1);
123
127
 
124
- const firstUpdate = component.emitted("update:modelValue")![0][0] as RangeDate;
125
- const secondUpdate = component.emitted("update:modelValue")![1][0] as RangeDate;
126
-
127
- expect(firstUpdate.from).not.toBeNull();
128
- expect(firstUpdate.to).toBeNull();
128
+ const rangeUpdate = component.emitted("update:modelValue")![0][0] as RangeDate;
129
129
 
130
- expect(secondUpdate.from).not.toBeNull();
131
- expect(secondUpdate.to).not.toBeNull();
130
+ expect(rangeUpdate.from).not.toBeNull();
131
+ expect(rangeUpdate.to).not.toBeNull();
132
132
  });
133
133
 
134
134
  it("Timepicker – shows timepicker when enabled", () => {
@@ -248,28 +248,22 @@ describe("UCalendar.vue", () => {
248
248
  props: {
249
249
  range: true,
250
250
  modelValue: {
251
- from: "2023-12-02",
252
- to: "2023-12-31",
251
+ from: null,
252
+ to: null,
253
253
  },
254
254
  dateFormat: "Y-m-d",
255
255
  },
256
256
  });
257
257
 
258
- await component.setProps({
259
- "onUpdate:modelValue": (value: RangeDate) => {
260
- component.setProps({ modelValue: value });
261
- },
262
- });
263
-
264
258
  const dayView = component.findComponent(DayView);
265
259
  const days = dayView.findAll("button");
266
260
 
267
261
  await days[0].trigger("click");
268
262
  await days[3].trigger("click");
269
263
 
270
- expect(component.emitted("update:modelValue")).toHaveLength(3);
264
+ expect(component.emitted("update:modelValue")).toHaveLength(2);
271
265
 
272
- const updatedValue = component.emitted("update:modelValue")![2][0] as RangeDate;
266
+ const updatedValue = component.emitted("update:modelValue")![1][0] as RangeDate;
273
267
 
274
268
  expect(updatedValue.from).toMatch(dateFormatRegex);
275
269
  expect(updatedValue.to).toMatch(dateFormatRegex);
@@ -42,7 +42,11 @@ describe("UDatePickerRange.vue", () => {
42
42
  const input = component.findComponent(UInput).get("input");
43
43
 
44
44
  await input.trigger("focus");
45
- await component.get("[vl-key='day']").trigger("click");
45
+
46
+ const days = component.findAll("[vl-key='day']");
47
+
48
+ await days[0].trigger("click");
49
+ await days[3].trigger("click");
46
50
 
47
51
  expect(component.emitted("update:modelValue")).toBeTruthy();
48
52
  });
package/utils/theme.ts CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  DEFAULT_DISABLED_OPACITY,
36
36
  LETTER_SPACING,
37
37
  DEFAULT_LETTER_SPACING,
38
+ THEME_TOKENS,
38
39
  LIGHT_THEME,
39
40
  DARK_THEME,
40
41
  SPACING,
@@ -898,12 +899,11 @@ function setCSSVariables(
898
899
  `;
899
900
 
900
901
  if (isCSR) {
901
- const vuelessStyleId = "vueless-theme-tokens";
902
- let style = document.getElementById(vuelessStyleId) as HTMLStyleElement | null;
902
+ let style = document.getElementById(THEME_TOKENS) as HTMLStyleElement | null;
903
903
 
904
904
  if (!style) {
905
905
  style = document.createElement("style");
906
- style.id = vuelessStyleId;
906
+ style.id = THEME_TOKENS;
907
907
 
908
908
  const firstStyleOrLink = document.querySelector("link[rel='stylesheet'], style");
909
909