vueless 1.5.2-beta.0 → 1.5.2-beta.2

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.
@@ -1,4 +1,4 @@
1
- import { onMounted, ref, watch, computed, onBeforeUnmount } from "vue";
1
+ import { ref, computed } from "vue";
2
2
  import { isSSR } from "../utils/helper";
3
3
 
4
4
  type ResponsiveConfig<T> = Partial<Record<BreakpointName, T>>;
@@ -25,12 +25,12 @@ let isInitialized = false;
25
25
  let animationId: number | undefined;
26
26
 
27
27
  const windowWidth = ref(0);
28
- const currentBreakpoint = ref(BreakpointName.Xs);
29
28
  const BREAKPOINT_KEYS = Object.keys(BreakpointName) as (keyof typeof BreakpointName)[];
30
-
31
- watch(windowWidth, setBreakpoint, { immediate: true });
29
+ const currentBreakpoint = computed(() => getBreakpoint(windowWidth.value));
32
30
 
33
31
  export function useBreakpoint() {
32
+ initBreakpointListener();
33
+
34
34
  const isPhone = computed(() => {
35
35
  return currentBreakpoint.value === BreakpointName.Xs;
36
36
  });
@@ -67,14 +67,6 @@ export function useBreakpoint() {
67
67
  return isDesktop.value || isLargeDesktop.value;
68
68
  });
69
69
 
70
- onMounted(() => {
71
- initBreakpointListener();
72
- });
73
-
74
- onBeforeUnmount(() => {
75
- window.removeEventListener("resize", resizeListener);
76
- });
77
-
78
70
  return {
79
71
  isPhone,
80
72
  isLargePhone,
@@ -139,20 +131,16 @@ export function r<T>(config: ResponsiveConfig<T>): T {
139
131
  return config[BreakpointName[definedKeys[0]]] as T;
140
132
  }
141
133
 
142
- function setBreakpoint(newWindowWidth: number) {
143
- if (newWindowWidth === undefined) return;
144
-
134
+ function getBreakpoint(width: number) {
145
135
  for (let i = BREAKPOINT_KEYS.length - 1; i >= 0; i--) {
146
136
  const key = BREAKPOINT_KEYS[i];
147
137
 
148
- if (newWindowWidth >= BreakpointWidth[key]) {
149
- currentBreakpoint.value = BreakpointName[key];
150
-
151
- return;
138
+ if (width >= BreakpointWidth[key]) {
139
+ return BreakpointName[key];
152
140
  }
153
141
  }
154
142
 
155
- currentBreakpoint.value = BreakpointName.Xs;
143
+ return BreakpointName.Xs;
156
144
  }
157
145
 
158
146
  function resizeListener() {
@@ -53,7 +53,9 @@ export function useUI<T>(defaultConfig: T, mutatedProps?: MutatedProps, topLevel
53
53
 
54
54
  /* Hoist shared reactive primitives — create once, share across all keys. */
55
55
  const attrs = useAttrs() as KeyAttrs;
56
- const reactiveAttrsClass = computed(() => attrs.class);
56
+
57
+ /* Reading `class` explicitly keeps attrs tracked even when they start empty. */
58
+ const reactiveAttrs = computed(() => ({ class: attrs.class, ...attrs }));
57
59
 
58
60
  /**
59
61
  * Reactive wrapper for props — created once per component instead of once per key.
@@ -235,6 +237,7 @@ export function useUI<T>(defaultConfig: T, mutatedProps?: MutatedProps, topLevel
235
237
  * body row of a table on an unrelated sticky toggle). We skip the `.value` write
236
238
  * when the new object is structurally equal to the last one, so the ref keeps its
237
239
  * identity and dependents are not re-rendered.
240
+ * `JSON.stringify` drops functions, so listeners are compared by reference separately.
238
241
  */
239
242
  const attrsSignatures: Record<string, string> = {};
240
243
 
@@ -250,10 +253,10 @@ export function useUI<T>(defaultConfig: T, mutatedProps?: MutatedProps, topLevel
250
253
  /**
251
254
  * Single consolidated watcher instead of N per-key watchers.
252
255
  * Watches: config (for config changes), reactiveProps (for prop changes),
253
- * mutatedProps (for slot/computed prop changes), reactiveAttrsClass (for class attr changes).
256
+ * mutatedProps (for slot/computed prop changes), reactiveAttrs (for attr changes).
254
257
  */
255
258
  watch(
256
- [config, reactiveProps, mutatedProps || (() => undefined), reactiveAttrsClass],
259
+ [config, reactiveProps, mutatedProps || (() => undefined), reactiveAttrs],
257
260
  () => {
258
261
  const derived = configDerivedData.value;
259
262
 
@@ -285,8 +288,10 @@ export function useUI<T>(defaultConfig: T, mutatedProps?: MutatedProps, topLevel
285
288
 
286
289
  /* Keep the previous ref identity when nothing changed — see attrsSignatures. */
287
290
  const signature = JSON.stringify(nextValue);
291
+ const isUnchanged =
292
+ attrsSignatures[key] === signature && hasSameListeners(attrsRefs[key].value, nextValue);
288
293
 
289
- if (attrsSignatures[key] === signature) continue;
294
+ if (isUnchanged) continue;
290
295
 
291
296
  attrsSignatures[key] = signature;
292
297
  attrsRefs[key].value = nextValue;
@@ -312,6 +317,26 @@ export function useUI<T>(defaultConfig: T, mutatedProps?: MutatedProps, topLevel
312
317
  return { config, getDataTest, ...getKeysAttrs(mutatedProps) } as UseUI<T>;
313
318
  }
314
319
 
320
+ /**
321
+ * Check both attrs objects hold the same function-valued entries (listeners) by reference.
322
+ */
323
+ function hasSameListeners(previous: KeyAttrs, next: KeyAttrs): boolean {
324
+ let listenersDiff = 0;
325
+
326
+ for (const key in next) {
327
+ if (typeof next[key] !== "function") continue;
328
+ if (next[key] !== previous[key]) return false;
329
+
330
+ listenersDiff++;
331
+ }
332
+
333
+ for (const key in previous) {
334
+ if (typeof previous[key] === "function") listenersDiff--;
335
+ }
336
+
337
+ return listenersDiff === 0;
338
+ }
339
+
315
340
  /**
316
341
  * Return base classes.
317
342
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.5.2-beta.0",
3
+ "version": "1.5.2-beta.2",
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",
@@ -59,7 +59,7 @@
59
59
  "@vue/eslint-config-typescript": "^14.7.0",
60
60
  "@vue/test-utils": "^2.4.8",
61
61
  "@vue/tsconfig": "^0.9.1",
62
- "@vueless/storybook": "^1.8.0",
62
+ "@vueless/storybook": "^1.8.1",
63
63
  "eslint": "^10.2.1",
64
64
  "eslint-plugin-storybook": "^10.3.5",
65
65
  "eslint-plugin-tailwindcss": "^4.0.0-alpha.7",
@@ -395,6 +395,23 @@ describe("UToggle.vue", () => {
395
395
  expect.objectContaining({ value: "option1", label: "Option 1" }),
396
396
  );
397
397
  });
398
+
399
+ it("Options – emits the new option value after options are swapped", async () => {
400
+ const component = mount(UToggle, {
401
+ props: {
402
+ options: [{ value: "code" }, { value: "self" }],
403
+ name,
404
+ },
405
+ });
406
+
407
+ await component.setProps({
408
+ options: [{ value: "git" }, { value: "jira" }, { value: "slack" }],
409
+ });
410
+
411
+ await component.findAllComponents(UButton)[1].trigger("click");
412
+
413
+ expect(component.emitted("update:modelValue")?.[0][0]).toBe("jira");
414
+ });
398
415
  });
399
416
 
400
417
  describe("Exposed refs", () => {