vueless 1.3.6-beta.0 → 1.3.6-beta.10

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.
Files changed (51) hide show
  1. package/components.d.ts +2 -0
  2. package/components.ts +2 -0
  3. package/constants.d.ts +2 -0
  4. package/constants.js +2 -0
  5. package/index.d.ts +10 -1
  6. package/index.ts +10 -1
  7. package/package.json +2 -2
  8. package/types.ts +2 -0
  9. package/ui.button/UButton.vue +1 -1
  10. package/ui.button/storybook/stories.ts +2 -2
  11. package/ui.container-card/storybook/stories.ts +2 -2
  12. package/ui.container-drawer/UDrawer.vue +2 -2
  13. package/ui.container-drawer/storybook/stories.ts +2 -2
  14. package/ui.container-grid/UGrid.vue +39 -0
  15. package/ui.container-grid/config.ts +123 -0
  16. package/ui.container-grid/constants.ts +5 -0
  17. package/ui.container-grid/storybook/docs.mdx +17 -0
  18. package/ui.container-grid/storybook/stories.ts +246 -0
  19. package/ui.container-grid/tests/UGrid.test.ts +297 -0
  20. package/ui.container-grid/types.ts +91 -0
  21. package/ui.container-modal/storybook/stories.ts +2 -2
  22. package/ui.container-modal-confirm/storybook/stories.ts +2 -2
  23. package/ui.container-page/storybook/stories.ts +3 -3
  24. package/ui.form-calendar/tests/UCalendar.test.ts +113 -0
  25. package/ui.form-date-picker-range/UDatePickerRangeInputs.vue +5 -1
  26. package/ui.form-date-picker-range/tests/UDatePickerRange.test.ts +114 -0
  27. package/ui.form-date-picker-range/types.ts +1 -0
  28. package/ui.form-listbox/config.ts +1 -1
  29. package/ui.image-avatar/UAvatar.vue +55 -28
  30. package/ui.image-avatar/config.ts +18 -1
  31. package/ui.image-avatar/storybook/docs.mdx +16 -1
  32. package/ui.image-avatar/storybook/stories.ts +17 -3
  33. package/ui.image-avatar/tests/UAvatar.test.ts +35 -7
  34. package/ui.image-avatar/types.ts +13 -0
  35. package/ui.image-avatar-group/UAvatarGroup.vue +87 -0
  36. package/ui.image-avatar-group/config.ts +11 -0
  37. package/ui.image-avatar-group/constants.ts +5 -0
  38. package/ui.image-avatar-group/storybook/docs.mdx +16 -0
  39. package/ui.image-avatar-group/storybook/stories.ts +147 -0
  40. package/ui.image-avatar-group/tests/UAvatarGroup.test.ts +141 -0
  41. package/ui.image-avatar-group/types.ts +51 -0
  42. package/ui.navigation-pagination/storybook/stories.ts +2 -2
  43. package/ui.navigation-tab/tests/UTab.test.ts +2 -3
  44. package/ui.navigation-tabs/UTabs.vue +44 -1
  45. package/ui.navigation-tabs/storybook/stories.ts +33 -0
  46. package/ui.navigation-tabs/tests/UTabs.test.ts +88 -0
  47. package/ui.text-block/config.ts +1 -0
  48. package/ui.text-block/storybook/stories.ts +2 -2
  49. package/ui.text-notify/UNotify.vue +31 -8
  50. package/ui.text-notify/config.ts +1 -1
  51. package/ui.text-notify/tests/UNotify.test.ts +22 -7
@@ -0,0 +1,147 @@
1
+ import {
2
+ getArgs,
3
+ getArgTypes,
4
+ getSlotNames,
5
+ getSlotsFragment,
6
+ getDocsDescription,
7
+ } from "../../utils/storybook";
8
+
9
+ import UAvatarGroup from "../../ui.image-avatar-group/UAvatarGroup.vue";
10
+ import UAvatar from "../../ui.image-avatar/UAvatar.vue";
11
+ import UCol from "../../ui.container-col/UCol.vue";
12
+ import ULink from "../../ui.button-link/ULink.vue";
13
+
14
+ import type { Meta, StoryFn } from "@storybook/vue3-vite";
15
+ import type { Props } from "../types.ts";
16
+
17
+ interface UAvatarGroupArgs extends Props {
18
+ slotTemplate?: string;
19
+ enum: "size" | "variant" | "rounded";
20
+ }
21
+
22
+ export default {
23
+ id: "6030",
24
+ title: "Images & Icons / Avatar Group",
25
+ component: UAvatarGroup,
26
+ args: {
27
+ avatars: [
28
+ { src: "https://i.pravatar.cc/300?img=1" },
29
+ { src: "https://i.pravatar.cc/300?img=2" },
30
+ { src: "https://i.pravatar.cc/300?img=3" },
31
+ { src: "https://i.pravatar.cc/300?img=4" },
32
+ ],
33
+ rounded: "full",
34
+ },
35
+ argTypes: {
36
+ ...getArgTypes(UAvatarGroup.__name),
37
+ },
38
+ parameters: {
39
+ docs: {
40
+ ...getDocsDescription(UAvatarGroup.__name),
41
+ },
42
+ },
43
+ } as Meta;
44
+
45
+ const DefaultTemplate: StoryFn<UAvatarGroupArgs> = (args: UAvatarGroupArgs) => ({
46
+ components: { UAvatarGroup, UAvatar, ULink },
47
+ setup: () => ({
48
+ args,
49
+ slots: getSlotNames(UAvatarGroup.__name),
50
+ }),
51
+ template: `
52
+ <UAvatarGroup v-bind="args">
53
+ ${args.slotTemplate || getSlotsFragment("")}
54
+ </UAvatarGroup>
55
+ `,
56
+ });
57
+
58
+ const EnumTemplate: StoryFn<UAvatarGroupArgs> = (args: UAvatarGroupArgs, { argTypes }) => ({
59
+ components: { UCol, UAvatarGroup, UAvatar },
60
+ setup: () => ({ args, argTypes, getArgs }),
61
+ template: `
62
+ <UCol>
63
+ <UAvatarGroup
64
+ v-for="option in argTypes?.[args.enum]?.options"
65
+ v-bind="getArgs(args, option)"
66
+ :key="option"
67
+ >
68
+ ${args.slotTemplate}
69
+ </UAvatarGroup>
70
+ </UCol>
71
+ `,
72
+ });
73
+
74
+ export const Default = DefaultTemplate.bind({});
75
+ Default.args = {};
76
+
77
+ export const Max = DefaultTemplate.bind({});
78
+ Max.args = { max: 2 };
79
+ Max.parameters = {
80
+ docs: {
81
+ description: {
82
+ story:
83
+ "When the number of avatars is greater than the max, the remaining count avatar is displayed.",
84
+ },
85
+ },
86
+ };
87
+
88
+ export const Sizes = EnumTemplate.bind({});
89
+ Sizes.args = {
90
+ enum: "size",
91
+ slotTemplate: `
92
+ <template #remaining>
93
+ <UAvatar :label="option" />
94
+ </template>
95
+ `,
96
+ };
97
+
98
+ export const Variants = EnumTemplate.bind({});
99
+ Variants.args = {
100
+ enum: "variant",
101
+ avatars: [{ label: "John Doe" }],
102
+ config: { avatar: "ring-0" },
103
+ };
104
+
105
+ export const AvatarConfig = DefaultTemplate.bind({});
106
+ AvatarConfig.args = {
107
+ avatars: [
108
+ { src: "https://i.pravatar.cc/300?img=1", label: "John Doe", chip: { color: "primary" } },
109
+ { color: "warning", placeholderIcon: "person" },
110
+ {
111
+ src: "https://i.pravatar.cc/300?img=9",
112
+ label: "Jane Smith",
113
+ color: "info",
114
+ chip: { color: "grayscale" },
115
+ },
116
+ ],
117
+ };
118
+ AvatarConfig.parameters = {
119
+ docs: {
120
+ description: {
121
+ story:
122
+ // eslint-disable-next-line vue/max-len
123
+ "You can customize the `label`, `color`, `placeholderIcon` and `chip` of a specific avatar by passing the corresponding props to its object.",
124
+ },
125
+ },
126
+ };
127
+
128
+ export const AvatarSlot = DefaultTemplate.bind({});
129
+ AvatarSlot.args = {
130
+ slotTemplate: `
131
+ <template #avatar-2="{ avatar }">
132
+ <UAvatar
133
+ :src="avatar.src"
134
+ class="ring-3 ring-primary"
135
+ />
136
+ </template>
137
+ `,
138
+ };
139
+
140
+ export const RemainingSlot = DefaultTemplate.bind({});
141
+ RemainingSlot.args = {
142
+ slotTemplate: `
143
+ <template #remaining="{ remainingCount }">
144
+ <ULink :label="'+' + remainingCount" size="lg" color="info" underlined />
145
+ </template>
146
+ `,
147
+ };
@@ -0,0 +1,141 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import UAvatarGroup from "../UAvatarGroup.vue";
5
+ import UAvatar from "../../ui.image-avatar/UAvatar.vue";
6
+
7
+ import type { Props } from "../types.ts";
8
+
9
+ describe("UAvatarGroup.vue", () => {
10
+ describe("Props", () => {
11
+ it("Size – applies the correct size to child avatars", async () => {
12
+ const sizes = ["3xs", "2xs", "xs", "sm", "md", "lg", "xl", "2xl", "3xl"];
13
+
14
+ sizes.forEach((size) => {
15
+ const component = mount(UAvatarGroup, {
16
+ props: {
17
+ size: size as Props["size"],
18
+ avatars: [{ label: "John Doe" }, { label: "Jane Smith" }],
19
+ },
20
+ });
21
+
22
+ // Check if avatars have the correct size (they should inherit from group)
23
+ const avatars = component.findAllComponents(UAvatar);
24
+
25
+ expect(avatars.length).toBeGreaterThan(0);
26
+ });
27
+ });
28
+
29
+ it("Max – limits the number of avatars displayed based on max prop", async () => {
30
+ const component = mount(UAvatarGroup, {
31
+ props: {
32
+ max: 2,
33
+ avatars: [{ label: "John Doe" }, { label: "Jane Smith" }, { label: "Bob Johnson" }],
34
+ },
35
+ });
36
+
37
+ // Should have 2 visible avatars + 1 remaining avatar
38
+ const avatars = component.findAllComponents(UAvatar);
39
+
40
+ expect(avatars.length).toBe(3);
41
+
42
+ // The last avatar should be the remaining count avatar
43
+ const lastAvatar = avatars[avatars.length - 1];
44
+
45
+ expect(lastAvatar.text()).toBe("+1");
46
+ });
47
+
48
+ it("Variant – applies the correct variant to child avatars", async () => {
49
+ const variants = ["solid", "outlined", "subtle", "soft"];
50
+
51
+ variants.forEach((variant) => {
52
+ const component = mount(UAvatarGroup, {
53
+ props: {
54
+ variant: variant as Props["variant"],
55
+ avatars: [{ label: "John Doe" }, { label: "Jane Smith" }],
56
+ },
57
+ });
58
+
59
+ const avatars = component.findAllComponents(UAvatar);
60
+
61
+ expect(avatars.length).toBeGreaterThan(0);
62
+ });
63
+ });
64
+
65
+ it("Rounded – applies the correct rounded to child avatars", async () => {
66
+ const roundedValues = ["none", "sm", "md", "lg", "full"];
67
+
68
+ roundedValues.forEach((rounded) => {
69
+ const component = mount(UAvatarGroup, {
70
+ props: {
71
+ rounded: rounded as Props["rounded"],
72
+ avatars: [{ label: "John Doe" }, { label: "Jane Smith" }],
73
+ },
74
+ });
75
+
76
+ const avatars = component.findAllComponents(UAvatar);
77
+
78
+ expect(avatars.length).toBeGreaterThan(0);
79
+ });
80
+ });
81
+ });
82
+
83
+ describe("Slots", () => {
84
+ it("Avatars – renders avatars from avatars prop", async () => {
85
+ const component = mount(UAvatarGroup, {
86
+ props: {
87
+ avatars: [{ label: "John Doe" }, { label: "Jane Smith" }],
88
+ },
89
+ });
90
+
91
+ const avatars = component.findAllComponents(UAvatar);
92
+
93
+ expect(avatars.length).toBe(2);
94
+ });
95
+
96
+ it("Remaining – renders custom remaining slot", async () => {
97
+ const component = mount(UAvatarGroup, {
98
+ props: {
99
+ max: 1,
100
+ avatars: [{ label: "John Doe" }, { label: "Jane Smith" }],
101
+ },
102
+ slots: {
103
+ remaining: `
104
+ <template #remaining="{ remainingCount }">
105
+ <span class="custom-remaining">
106
+ Custom {{ remainingCount }}
107
+ </span>
108
+ </template>
109
+ `,
110
+ },
111
+ global: {
112
+ components: {
113
+ UAvatar,
114
+ },
115
+ },
116
+ });
117
+
118
+ const avatars = component.findAllComponents(UAvatar);
119
+
120
+ expect(avatars.length).toBe(2);
121
+
122
+ // Check if custom remaining slot content is rendered
123
+ const customRemaining = component.find(".custom-remaining");
124
+
125
+ expect(customRemaining.exists()).toBe(true);
126
+ expect(customRemaining.text()).toContain("Custom");
127
+ });
128
+ });
129
+
130
+ describe("Exposed refs", () => {
131
+ it("exposes avatarGroupRef", () => {
132
+ const component = mount(UAvatarGroup, {
133
+ props: {
134
+ avatars: [{ label: "John Doe" }],
135
+ },
136
+ });
137
+
138
+ expect(component.vm.avatarGroupRef).toBeDefined();
139
+ });
140
+ });
141
+ });
@@ -0,0 +1,51 @@
1
+ import defaultConfig from "./config";
2
+
3
+ import type { ComponentConfig } from "../types";
4
+ import type { ChipItem } from "../ui.image-avatar/types";
5
+
6
+ export type Config = typeof defaultConfig;
7
+
8
+ export interface AvatarItem {
9
+ src?: string;
10
+ label?: string;
11
+ color?: string;
12
+ placeholderIcon?: string;
13
+ chip?: ChipItem;
14
+ }
15
+
16
+ export interface Props {
17
+ /**
18
+ * Avatar items.
19
+ */
20
+ avatars?: AvatarItem[];
21
+
22
+ /**
23
+ * Avatar group size.
24
+ */
25
+ size?: "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
26
+
27
+ /**
28
+ * Maximum number of avatars to display.
29
+ */
30
+ max?: number;
31
+
32
+ /**
33
+ * Avatar variant.
34
+ */
35
+ variant?: "solid" | "outlined" | "subtle" | "soft";
36
+
37
+ /**
38
+ * Avatar corner rounding.
39
+ */
40
+ rounded?: "none" | "sm" | "md" | "lg" | "full";
41
+
42
+ /**
43
+ * Component config object.
44
+ */
45
+ config?: ComponentConfig<Config>;
46
+
47
+ /**
48
+ * Data-test attribute for automated testing.
49
+ */
50
+ dataTest?: string | null;
51
+ }
@@ -85,8 +85,8 @@ Limit.parameters = {
85
85
  },
86
86
  };
87
87
 
88
- export const Variant = EnumTemplate.bind({});
89
- Variant.args = { enum: "variant" };
88
+ export const Variants = EnumTemplate.bind({});
89
+ Variants.args = { enum: "variant" };
90
90
 
91
91
  export const Sizes = EnumTemplate.bind({});
92
92
  Sizes.args = { enum: "size" };
@@ -1,4 +1,3 @@
1
- import { h } from "vue";
2
1
  import { mount } from "@vue/test-utils";
3
2
  import { describe, it, expect, vi } from "vitest";
4
3
 
@@ -386,7 +385,7 @@ describe("UTab.vue", () => {
386
385
  value,
387
386
  },
388
387
  slots: {
389
- left: (props) => h("div", { "data-active": props.active }),
388
+ left: '<div :data-active="params.active" />',
390
389
  },
391
390
  global: {
392
391
  provide: {
@@ -409,7 +408,7 @@ describe("UTab.vue", () => {
409
408
  leftIcon,
410
409
  },
411
410
  slots: {
412
- left: (props) => h("div", { "data-icon-name": props.iconName }),
411
+ left: '<div :data-icon-name="params.iconName" />',
413
412
  },
414
413
  global: {
415
414
  provide: defaultProvide,
@@ -128,7 +128,50 @@ const {
128
128
  :disabled="item.disabled"
129
129
  v-bind="tabAttrs"
130
130
  :data-test="getDataTest(`item-${index}`)"
131
- />
131
+ >
132
+ <template #left="{ iconName, active }">
133
+ <!--
134
+ @slot Use it to add something before the tab label.
135
+ @binding {object} item
136
+ @binding {number} index
137
+ @binding {boolean} active
138
+ @binding {string} icon-name
139
+ -->
140
+ <slot name="left" :item="item" :index="index" :active="active" :icon-name="iconName" />
141
+ </template>
142
+
143
+ <template #label="{ label, iconName, active }">
144
+ <!--
145
+ @slot Use it to add something instead of the tab label.
146
+ @binding {object} item
147
+ @binding {number} index
148
+ @binding {string} label
149
+ @binding {boolean} active
150
+ @binding {string} icon-name
151
+ -->
152
+ <slot
153
+ name="label"
154
+ :item="item"
155
+ :index="index"
156
+ :label="label"
157
+ :active="active"
158
+ :icon-name="iconName"
159
+ >
160
+ {{ label }}
161
+ </slot>
162
+ </template>
163
+
164
+ <template #right="{ iconName, active }">
165
+ <!--
166
+ @slot Use it to add something after the tab label.
167
+ @binding {object} item
168
+ @binding {number} index
169
+ @binding {boolean} active
170
+ @binding {string} icon-name
171
+ -->
172
+ <slot name="right" :item="item" :index="index" :active="active" :icon-name="iconName" />
173
+ </template>
174
+ </UTab>
132
175
  </slot>
133
176
  </div>
134
177
 
@@ -10,6 +10,12 @@ import UTabs from "../../ui.navigation-tabs/UTabs.vue";
10
10
  import URow from "../../ui.container-row/URow.vue";
11
11
  import ULabel from "../../ui.form-label/ULabel.vue";
12
12
  import UTab from "../../ui.navigation-tab/UTab.vue";
13
+ import UBadge from "../../ui.text-badge/UBadge.vue";
14
+ import UAvatar from "../../ui.image-avatar/UAvatar.vue";
15
+ import UChip from "../../ui.other-chip/UChip.vue";
16
+ import UText from "../../ui.text-block/UText.vue";
17
+
18
+ import johnDoe from "../../ui.navigation-tab/storybook/assets/john-doe.png";
13
19
 
14
20
  import type { Meta, StoryFn } from "@storybook/vue3-vite";
15
21
  import type { Props } from "../types";
@@ -109,6 +115,33 @@ export const DefaultSlot: StoryFn<UTabsArgs> = (args) => ({
109
115
  `,
110
116
  });
111
117
 
118
+ export const TabSlots: StoryFn<UTabsArgs> = (args) => ({
119
+ components: { UTabs, UBadge, UAvatar, UChip, UText },
120
+ setup: () => ({ args, johnDoe }),
121
+ template: `
122
+ <UTabs v-model="args.modelValue" v-bind="args">
123
+ <template #left="{ index }">
124
+ <UAvatar
125
+ v-if="index === 0"
126
+ :src="johnDoe"
127
+ size="3xs"
128
+ rounded="full"
129
+ />
130
+ </template>
131
+
132
+ <template #label="{ label, index }">
133
+ <UChip v-if="index === 1" size="sm">
134
+ <UText :label="label" color="primary" class="mr-1.5" />
135
+ </UChip>
136
+ </template>
137
+
138
+ <template #right="{ index }">
139
+ <UBadge v-if="index === 2" label="New!" size="sm" />
140
+ </template>
141
+ </UTabs>
142
+ `,
143
+ });
144
+
112
145
  export const PrevNextSlots: StoryFn<UTabsArgs> = (args) => ({
113
146
  components: { UTabs },
114
147
  setup: () => ({ args, getOptionsArray }),
@@ -284,6 +284,94 @@ describe("UTabs.vue", () => {
284
284
  expect(component.find(`.${slotClass}`).exists()).toBe(true);
285
285
  expect(component.find(`.${slotClass}`).text()).toBe(slotContent);
286
286
  });
287
+
288
+ it("Left – renders content from left slot for each tab", () => {
289
+ const slotText = "Left";
290
+ const slotClass = "left-content";
291
+
292
+ const component = mount(UTabs, {
293
+ props: {
294
+ options,
295
+ },
296
+ slots: {
297
+ left: `<span class="${slotClass}">${slotText}</span>`,
298
+ },
299
+ });
300
+
301
+ const leftContents = component.findAll(`.${slotClass}`);
302
+
303
+ expect(leftContents.length).toBe(options.length);
304
+ leftContents.forEach((left) => {
305
+ expect(left.text()).toBe(slotText);
306
+ });
307
+ });
308
+
309
+ it("Label – renders content from label slot instead of default label", () => {
310
+ const testOptions: UTabsOption[] = [{ value: "tab1", label: "Tab 1" }];
311
+ const slotText = "Custom Label";
312
+ const slotClass = "label-content";
313
+
314
+ const component = mount(UTabs, {
315
+ props: {
316
+ options: testOptions,
317
+ },
318
+ slots: {
319
+ label: `<span class="${slotClass}">${slotText}</span>`,
320
+ },
321
+ });
322
+
323
+ expect(component.find(`.${slotClass}`).exists()).toBe(true);
324
+ expect(component.find(`.${slotClass}`).text()).toBe(slotText);
325
+ expect(component.text()).not.toContain(testOptions[0].label);
326
+ });
327
+
328
+ it("Right – renders content from right slot", () => {
329
+ const slotText = "Right";
330
+ const slotClass = "right-content";
331
+
332
+ const component = mount(UTabs, {
333
+ props: {
334
+ options,
335
+ },
336
+ slots: {
337
+ right: `<span class="${slotClass}">${slotText}</span>`,
338
+ },
339
+ });
340
+
341
+ expect(component.findAll(`.${slotClass}`).length).toBe(options.length);
342
+ expect(component.find(`.${slotClass}`).text()).toBe(slotText);
343
+ });
344
+
345
+ it("Slot – passes item, index and active state to slots", () => {
346
+ const modelValue = "tab2";
347
+
348
+ const component = mount(UTabs, {
349
+ props: {
350
+ options,
351
+ modelValue,
352
+ },
353
+ slots: {
354
+ left: `
355
+ <div
356
+ :data-value="params.item.value"
357
+ :data-index="params.index"
358
+ :data-active="params.active"
359
+ />
360
+ `,
361
+ },
362
+ });
363
+
364
+ options.forEach((option, index) => {
365
+ const el = component.find(`[data-value="${option.value}"][data-index="${index}"]`);
366
+
367
+ expect(el.exists()).toBe(true);
368
+ });
369
+
370
+ const activeEl = component.find('[data-active="true"]');
371
+
372
+ expect(activeEl.exists()).toBe(true);
373
+ expect(activeEl.attributes("data-value")).toBe(modelValue);
374
+ });
287
375
  });
288
376
 
289
377
  describe("Events", () => {
@@ -44,6 +44,7 @@ export default /*tw*/ {
44
44
  true: "leading-none",
45
45
  },
46
46
  wrap: {
47
+ true: "text-wrap wrap-anywhere",
47
48
  false: "text-nowrap",
48
49
  },
49
50
  },
@@ -90,8 +90,8 @@ Sizes.args = { enum: "size" };
90
90
  export const Color = EnumTemplate.bind({});
91
91
  Color.args = { enum: "color" };
92
92
 
93
- export const Variant = EnumTemplate.bind({});
94
- Variant.args = { enum: "variant" };
93
+ export const Variants = EnumTemplate.bind({});
94
+ Variants.args = { enum: "variant" };
95
95
 
96
96
  export const Line: StoryFn<UTextArgs> = (args: UTextArgs) => ({
97
97
  components: { UText, UCol },
@@ -44,7 +44,7 @@ onMounted(() => {
44
44
  window.addEventListener("notifyEnd", onNotifyEnd);
45
45
  window.addEventListener("notifyClearAll", onClearAll);
46
46
 
47
- setPosition();
47
+ waitForPageElement();
48
48
  });
49
49
 
50
50
  onBeforeUnmount(() => {
@@ -86,12 +86,37 @@ function getOffsetWidth(selector: string): number {
86
86
  return element ? (element as HTMLElement).offsetWidth : 0;
87
87
  }
88
88
 
89
+ function waitForPageElement() {
90
+ const positionClasses = vuelessConfig.components?.UNotify?.positionClasses;
91
+ const pageClass = positionClasses?.page || config.value?.positionClasses?.page;
92
+ const maxWaitTime = 2000;
93
+ const startTime = Date.now();
94
+
95
+ function checkAndSetPosition() {
96
+ const element = document.querySelector(pageClass);
97
+
98
+ if (element) {
99
+ setPosition();
100
+
101
+ return;
102
+ }
103
+
104
+ if (Date.now() - startTime < maxWaitTime) {
105
+ requestAnimationFrame(checkAndSetPosition);
106
+ } else {
107
+ setPosition();
108
+ }
109
+ }
110
+
111
+ checkAndSetPosition();
112
+ }
113
+
89
114
  function setPosition() {
90
115
  const positionClasses = vuelessConfig.components?.UNotify?.positionClasses;
91
116
  const pageClass = positionClasses?.page || config.value?.positionClasses?.page;
92
117
  const asideClass = positionClasses?.aside || config.value?.positionClasses?.aside;
93
- const pageWidth = getOffsetWidth(`${pageClass}`);
94
- const asideWidth = getOffsetWidth(`${asideClass}`);
118
+ const pageWidth = getOffsetWidth(`.${pageClass}`);
119
+ const asideWidth = getOffsetWidth(`.${asideClass}`);
95
120
  const notifyWidth = notificationsWrapperRef.value?.$el.offsetWidth || 0;
96
121
 
97
122
  const styles: Record<string, string> = {
@@ -104,15 +129,13 @@ function setPosition() {
104
129
  styles[props.yPosition] = "0px";
105
130
 
106
131
  if (props.xPosition === NotificationPosition.Center) {
107
- styles.left = `calc(50% - ${notifyWidth / 2}px)`;
132
+ styles.left = pageWidth
133
+ ? `${asideWidth + pageWidth / 2 - notifyWidth / 2}px`
134
+ : `calc(50% - ${notifyWidth / 2}px)`;
108
135
  } else {
109
136
  styles[props.xPosition] = "0px";
110
137
  }
111
138
 
112
- if (pageWidth && props.xPosition !== NotificationPosition.Right) {
113
- styles.left = `${asideWidth + pageWidth / 2 - notifyWidth / 2}px`;
114
- }
115
-
116
139
  notifyPositionStyles.value = styles;
117
140
  }
118
141
 
@@ -1,5 +1,5 @@
1
1
  export default /*tw*/ {
2
- wrapper: "absolute overflow-visible md:w-[22rem]",
2
+ wrapper: "mt-3 absolute overflow-visible md:w-[22rem]",
3
3
  transitionGroup: {
4
4
  moveClass: "transition duration-500",
5
5
  enterActiveClass: "transition duration-500",