@revojs/ui 0.2.7 → 0.2.9

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 (34) hide show
  1. package/dist/components/button.d.ts +4 -3
  2. package/dist/components/icon.d.ts +2 -1
  3. package/dist/components/input.d.ts +3 -2
  4. package/dist/components/navigate.d.ts +3 -2
  5. package/dist/module/icons.d.ts +2 -0
  6. package/dist/module/icons.js +12 -0
  7. package/dist/module/index.d.ts +1 -1
  8. package/dist/module/index.js +37 -47
  9. package/dist/types/index.d.ts +0 -7
  10. package/dist/vue/components/breadcrumb.vue +2 -4
  11. package/dist/vue/components/button.vue +26 -26
  12. package/dist/vue/components/card.vue +7 -10
  13. package/dist/vue/components/checkbox.vue +17 -12
  14. package/dist/vue/components/collection.vue +2 -10
  15. package/dist/vue/components/container.vue +2 -4
  16. package/dist/vue/components/counter.vue +7 -11
  17. package/dist/vue/components/dialog.vue +4 -18
  18. package/dist/vue/components/field.vue +3 -13
  19. package/dist/vue/components/heading.vue +7 -8
  20. package/dist/vue/components/icon.vue +3 -41
  21. package/dist/vue/components/input.vue +9 -19
  22. package/dist/vue/components/link.vue +8 -9
  23. package/dist/vue/components/navigate.vue +3 -14
  24. package/dist/vue/components/popover.vue +3 -11
  25. package/dist/vue/components/progress.vue +3 -10
  26. package/dist/vue/components/radio.vue +12 -20
  27. package/dist/vue/components/range.vue +99 -29
  28. package/dist/vue/components/rating.vue +6 -17
  29. package/dist/vue/components/select.vue +84 -61
  30. package/dist/vue/components/separator.vue +2 -10
  31. package/dist/vue/components/sheet.vue +17 -21
  32. package/dist/vue/components/switch.vue +6 -21
  33. package/dist/vue/components/upload.vue +2 -18
  34. package/package.json +5 -4
@@ -5,7 +5,7 @@
5
5
  >
6
6
  <slot name="leading" />
7
7
 
8
- <Icon v-if="iconPrefix" :name="iconPrefix" />
8
+ <Icon v-if="iconPrefix" :is="iconPrefix" />
9
9
 
10
10
  <input
11
11
  class="w-full min-w-0 appearance-none bg-transparent py-2 outline-none placeholder:opacity-50"
@@ -20,7 +20,7 @@
20
20
  @input="input"
21
21
  />
22
22
 
23
- <Icon v-if="iconSuffix" :name="iconSuffix" />
23
+ <Icon v-if="iconSuffix" :is="iconSuffix" />
24
24
 
25
25
  <slot name="trailing" />
26
26
  </span>
@@ -29,31 +29,21 @@
29
29
  <script setup lang="ts">
30
30
  import { computed } from "vue";
31
31
  import type { ControlInteraction } from "../../components/control.ts";
32
+ import type { InputProps } from "../../components/input";
32
33
  import Icon from "./icon.vue";
33
34
 
34
35
  const {
35
- modelValue = undefined,
36
+ modelValue,
36
37
  type = "text",
37
- iconPrefix = undefined,
38
- iconSuffix = undefined,
39
- placeholder = undefined,
40
- name = undefined,
38
+ iconPrefix,
39
+ iconSuffix,
40
+ placeholder,
41
+ name,
41
42
  spellcheck = undefined,
42
43
  required = false,
43
44
  readonly = false,
44
45
  disabled = false,
45
- } = defineProps<{
46
- modelValue?: string | number;
47
- type?: string;
48
- iconPrefix?: string;
49
- iconSuffix?: string;
50
- placeholder?: string;
51
- name?: string;
52
- spellcheck?: boolean;
53
- required?: boolean;
54
- readonly?: boolean;
55
- disabled?: boolean;
56
- }>();
46
+ } = defineProps<InputProps>();
57
47
 
58
48
  const emit = defineEmits<{
59
49
  "update:modelValue": [value: string | number];
@@ -11,19 +11,18 @@
11
11
 
12
12
  <script setup lang="ts">
13
13
  import type { Component } from "vue";
14
- import type { LinkVariant } from "../../components/link.ts";
14
+ import type { LinkProps, LinkVariant } from "../../components/link";
15
15
 
16
16
  const {
17
17
  is = "a",
18
- href = undefined,
19
- label = undefined,
18
+ href,
19
+ label,
20
20
  variant = "primary",
21
- } = defineProps<{
22
- is?: string | Component;
23
- href?: string;
24
- label?: string;
25
- variant?: LinkVariant;
26
- }>();
21
+ } = defineProps<
22
+ LinkProps & {
23
+ is?: string | Component;
24
+ }
25
+ >();
27
26
 
28
27
  const variants: Record<LinkVariant, string> = {
29
28
  primary: "text-foreground",
@@ -4,7 +4,7 @@
4
4
  :href="href"
5
5
  font="normal"
6
6
  layout="left"
7
- :variant="active ? 'secondary' : 'text'"
7
+ :variant="active ? 'secondary' : 'ghost'"
8
8
  :icon-prefix="iconPrefix"
9
9
  :icon-suffix="iconSuffix"
10
10
  :label="label"
@@ -12,19 +12,8 @@
12
12
  </template>
13
13
 
14
14
  <script setup lang="ts">
15
+ import type { NavigateProps } from "../../components/navigate";
15
16
  import Button from "./button.vue";
16
17
 
17
- const {
18
- href,
19
- label = undefined,
20
- iconPrefix = undefined,
21
- iconSuffix = undefined,
22
- active = false,
23
- } = defineProps<{
24
- href: string;
25
- label?: string;
26
- iconPrefix?: string;
27
- iconSuffix?: string;
28
- active?: boolean;
29
- }>();
18
+ const { href, label, iconPrefix, iconSuffix, active = false } = defineProps<NavigateProps>();
30
19
  </script>
@@ -26,26 +26,18 @@
26
26
 
27
27
  <script setup lang="ts">
28
28
  import { ref, useId, useTemplateRef, watchEffect } from "vue";
29
- import type { ButtonLayout } from "../../components/button.ts";
29
+ import type { PopoverProps } from "../../components/popover";
30
30
  import Button from "./button.vue";
31
31
 
32
32
  const {
33
33
  modelValue = false,
34
34
  triggerLabel = "Open",
35
- triggerClass = undefined,
35
+ triggerClass,
36
36
  triggerLayout = "center",
37
37
  contentClass = "p-4",
38
38
  matchTriggerWidth = false,
39
39
  disabled = false,
40
- } = defineProps<{
41
- modelValue?: boolean;
42
- triggerLabel?: string;
43
- triggerClass?: string;
44
- triggerLayout?: ButtonLayout;
45
- contentClass?: string;
46
- matchTriggerWidth?: boolean;
47
- disabled?: boolean;
48
- }>();
40
+ } = defineProps<PopoverProps>();
49
41
 
50
42
  const emit = defineEmits<{
51
43
  "update:modelValue": [value: boolean];
@@ -9,7 +9,7 @@
9
9
  <progress class="sr-only" :value="value" :max="max">{{ percent }}%</progress>
10
10
 
11
11
  <div
12
- class="h-2 w-full overflow-hidden rounded-full bg-secondary"
12
+ class="h-2 w-full overflow-hidden rounded-full bg-input"
13
13
  role="progressbar"
14
14
  :aria-label="label"
15
15
  :aria-valuenow="value"
@@ -23,16 +23,9 @@
23
23
 
24
24
  <script setup lang="ts">
25
25
  import { computed } from "vue";
26
+ import type { ProgressProps } from "../../components/progress";
26
27
 
27
- const {
28
- value = 0,
29
- max = 100,
30
- label = undefined,
31
- } = defineProps<{
32
- value?: number;
33
- max?: number;
34
- label?: string;
35
- }>();
28
+ const { value = 0, max = 100, label } = defineProps<ProgressProps>();
36
29
 
37
30
  const percent = computed(() => (max > 0 ? Math.min(100, Math.max(0, (value / max) * 100)) : 0));
38
31
  </script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <label class="inline-flex items-start gap-3" :class="interactionVariants[interaction]">
3
3
  <input
4
- class="mt-0.5 size-5 shrink-0 cursor-inherit accent-current text-primary"
4
+ class="peer sr-only"
5
5
  type="radio"
6
6
  :checked="modelValue === value"
7
7
  :value="value"
@@ -11,6 +11,14 @@
11
11
  @change="$emit('update:modelValue', value)"
12
12
  />
13
13
 
14
+ <span
15
+ aria-hidden="true"
16
+ class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full border transition-colors peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ring"
17
+ :class="modelValue === value ? 'border-primary bg-background' : 'border-input bg-background'"
18
+ >
19
+ <span v-if="modelValue === value" class="size-2 rounded-full bg-primary" />
20
+ </span>
21
+
14
22
  <span class="flex flex-col gap-0.5">
15
23
  <span class="text-sm font-medium">
16
24
  <slot> {{ label }}</slot>
@@ -24,25 +32,9 @@
24
32
  <script setup lang="ts">
25
33
  import { computed } from "vue";
26
34
  import type { ControlInteraction } from "../../components/control.ts";
27
- import type { RadioValue } from "../../components/radio.ts";
28
-
29
- const {
30
- modelValue = undefined,
31
- value,
32
- label = undefined,
33
- description = undefined,
34
- name = undefined,
35
- required = false,
36
- disabled = false,
37
- } = defineProps<{
38
- modelValue?: RadioValue;
39
- value: RadioValue;
40
- label?: string;
41
- description?: string;
42
- name?: string;
43
- required?: boolean;
44
- disabled?: boolean;
45
- }>();
35
+ import type { RadioProps, RadioValue } from "../../components/radio";
36
+
37
+ const { modelValue, value, label, description, name, required = false, disabled = false } = defineProps<RadioProps>();
46
38
 
47
39
  defineEmits<{
48
40
  "update:modelValue": [value: RadioValue];
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <label class="flex flex-col gap-2" :class="interactionVariants[interaction]">
2
+ <div class="flex flex-col gap-2" :class="interactionVariants[interaction]">
3
3
  <span v-if="label" class="flex items-center justify-between gap-4 text-sm">
4
4
  <span class="font-medium"> {{ label }} </span>
5
5
 
@@ -8,40 +8,41 @@
8
8
  </output>
9
9
  </span>
10
10
 
11
- <input
12
- class="h-2 w-full cursor-pointer accent-current text-primary disabled:cursor-not-allowed"
13
- type="range"
14
- :value="modelValue"
15
- :min="min"
16
- :max="max"
17
- :step="step"
18
- :disabled="disabled"
19
- @input="$emit('update:modelValue', Number(($event.currentTarget as HTMLInputElement).value))"
20
- />
21
- </label>
11
+ <div
12
+ ref="track"
13
+ class="relative h-2 w-full touch-none rounded-full bg-input outline-ring focus-visible:outline-2 focus-visible:outline-offset-2"
14
+ :class="disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'"
15
+ role="slider"
16
+ :tabindex="disabled ? -1 : 0"
17
+ :aria-valuemin="min"
18
+ :aria-valuemax="max"
19
+ :aria-valuenow="modelValue"
20
+ :aria-label="label"
21
+ :aria-disabled="disabled"
22
+ @pointerdown="onPointerDown"
23
+ @pointermove="onPointerMove"
24
+ @pointerup="onPointerUp"
25
+ @pointercancel="onPointerUp"
26
+ @keydown="onKeyDown"
27
+ >
28
+ <div class="absolute inset-y-0 left-0 rounded-full bg-primary" :style="{ width: `${percent}%` }" />
29
+
30
+ <span
31
+ class="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-primary bg-background shadow"
32
+ :style="{ left: `${percent}%` }"
33
+ />
34
+ </div>
35
+ </div>
22
36
  </template>
23
37
 
24
38
  <script setup lang="ts">
25
- import { computed } from "vue";
39
+ import { computed, useTemplateRef } from "vue";
26
40
  import type { ControlInteraction } from "../../components/control.ts";
41
+ import type { RangeProps } from "../../components/range";
27
42
 
28
- const {
29
- modelValue = 0,
30
- label = undefined,
31
- min = 0,
32
- max = 100,
33
- step = 1,
34
- disabled = false,
35
- } = defineProps<{
36
- modelValue?: number;
37
- label?: string;
38
- min?: number;
39
- max?: number;
40
- step?: number;
41
- disabled?: boolean;
42
- }>();
43
+ const { modelValue = 0, label, min = 0, max = 100, step = 1, disabled = false } = defineProps<RangeProps>();
43
44
 
44
- defineEmits<{
45
+ const emit = defineEmits<{
45
46
  "update:modelValue": [value: number];
46
47
  }>();
47
48
 
@@ -51,4 +52,73 @@ const interactionVariants: Record<ControlInteraction, string> = {
51
52
  disabled: "cursor-not-allowed opacity-50",
52
53
  enabled: "",
53
54
  };
55
+
56
+ const track = useTemplateRef("track");
57
+
58
+ const percent = computed(() => (max > min ? Math.min(100, Math.max(0, ((modelValue - min) / (max - min)) * 100)) : 0));
59
+
60
+ let dragging = false;
61
+
62
+ function commit(raw: number) {
63
+ const decimals = (String(step).split(".")[1] ?? "").length;
64
+ const snapped = min + Math.round((raw - min) / step) * step;
65
+ const rounded = Number(snapped.toFixed(decimals));
66
+ const value = Math.min(max, Math.max(min, rounded));
67
+
68
+ if (value !== modelValue) {
69
+ emit("update:modelValue", value);
70
+ }
71
+ }
72
+
73
+ function setValueFromClientX(clientX: number) {
74
+ if (!track.value) {
75
+ return;
76
+ }
77
+
78
+ const rect = track.value.getBoundingClientRect();
79
+ const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
80
+
81
+ commit(min + ratio * (max - min));
82
+ }
83
+
84
+ function onPointerDown(event: PointerEvent) {
85
+ if (disabled || !track.value) {
86
+ return;
87
+ }
88
+
89
+ dragging = true;
90
+ track.value.setPointerCapture(event.pointerId);
91
+ track.value.focus({ preventScroll: true });
92
+ setValueFromClientX(event.clientX);
93
+ }
94
+
95
+ function onPointerMove(event: PointerEvent) {
96
+ if (dragging) {
97
+ setValueFromClientX(event.clientX);
98
+ }
99
+ }
100
+
101
+ function onPointerUp() {
102
+ dragging = false;
103
+ }
104
+
105
+ function onKeyDown(event: KeyboardEvent) {
106
+ if (disabled) {
107
+ return;
108
+ }
109
+
110
+ if (event.key === "ArrowRight" || event.key === "ArrowUp") {
111
+ commit(modelValue + step);
112
+ event.preventDefault();
113
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
114
+ commit(modelValue - step);
115
+ event.preventDefault();
116
+ } else if (event.key === "Home") {
117
+ commit(min);
118
+ event.preventDefault();
119
+ } else if (event.key === "End") {
120
+ commit(max);
121
+ event.preventDefault();
122
+ }
123
+ }
54
124
  </script>
@@ -16,30 +16,19 @@
16
16
  :disabled="readonly || disabled"
17
17
  @click="$emit('update:modelValue', value)"
18
18
  >
19
- <Icon name="star" />
19
+ <Icon :is="StarIcon" />
20
20
  </Button>
21
21
  </div>
22
22
  </div>
23
23
  </template>
24
24
 
25
25
  <script setup lang="ts">
26
+ import StarIcon from "~icons/hugeicons/star";
26
27
  import Button from "./button.vue";
27
28
  import Icon from "./icon.vue";
28
- import type { RatingState } from "../../components/rating.ts";
29
+ import type { RatingProps, RatingState } from "../../components/rating";
29
30
 
30
- const {
31
- modelValue = 0,
32
- max = 5,
33
- label = undefined,
34
- readonly = false,
35
- disabled = false,
36
- } = defineProps<{
37
- modelValue?: number;
38
- max?: number;
39
- label?: string;
40
- readonly?: boolean;
41
- disabled?: boolean;
42
- }>();
31
+ const { modelValue = 0, max = 5, label, readonly = false, disabled = false } = defineProps<RatingProps>();
43
32
 
44
33
  defineEmits<{
45
34
  "update:modelValue": [value: number];
@@ -50,7 +39,7 @@ function state(value: number): RatingState {
50
39
  }
51
40
 
52
41
  const variants: Record<RatingState, string> = {
53
- active: "text-amber-400",
54
- inactive: "text-neutral-300 dark:text-neutral-700",
42
+ active: "text-primary",
43
+ inactive: "text-input",
55
44
  };
56
45
  </script>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <input v-if="name" type="hidden" :name="name" :value="modelValue" :required="required" />
8
8
 
9
- <div ref="container">
9
+ <div ref="container" class="relative">
10
10
  <div
11
11
  ref="trigger"
12
12
  class="flex min-h-10 w-full cursor-text items-center gap-2 rounded-ui border border-input px-3 text-sm ring-ring focus-within:ring-2"
@@ -38,72 +38,66 @@
38
38
  :disabled="disabled"
39
39
  @click.stop="open = !open"
40
40
  >
41
- <Icon name="arrow-down-01" />
41
+ <Icon :is="ArrowDownIcon" />
42
42
  </button>
43
43
  </div>
44
- </div>
45
44
 
46
- <div
47
- ref="dropdown"
48
- popover="manual"
49
- :id="listboxId"
50
- role="listbox"
51
- :aria-label="label"
52
- class="m-0 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover p-1 text-popover-foreground shadow-xl"
53
- :style="panel"
54
- >
55
- <Button
56
- v-for="option in filtered"
57
- :key="option.value"
58
- class="w-full"
59
- variant="ghost"
60
- layout="left"
61
- :disabled="option.disabled"
62
- role="option"
63
- :aria-selected="option.value === modelValue"
64
- @click="select(option)"
45
+ <div
46
+ ref="dropdown"
47
+ :popover="popoverSupported ? 'manual' : undefined"
48
+ :id="listboxId"
49
+ role="listbox"
50
+ :aria-label="label"
51
+ class="m-0 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover p-1 text-popover-foreground shadow-xl"
52
+ :class="fallbackClass"
53
+ :style="popoverSupported ? panel : undefined"
65
54
  >
66
- <span class="flex w-full items-center justify-between gap-4 text-left">
67
- <slot name="option" :option="option" :selected="option.value === modelValue">
68
- {{ option.label }}
69
- </slot>
70
-
71
- <Icon v-if="option.value === modelValue" name="tick-02" />
72
- </span>
73
- </Button>
74
-
75
- <p v-if="filtered.length === 0" class="px-2 py-6 text-center text-sm opacity-60">
76
- {{ emptyLabel }}
77
- </p>
55
+ <Button
56
+ v-for="option in filtered"
57
+ :key="option.value"
58
+ class="w-full"
59
+ variant="ghost"
60
+ layout="left"
61
+ :disabled="option.disabled"
62
+ role="option"
63
+ :aria-selected="option.value === modelValue"
64
+ @click="select(option)"
65
+ >
66
+ <span class="flex w-full items-center justify-between gap-4 text-left">
67
+ <slot name="option" :option="option" :selected="option.value === modelValue">
68
+ {{ option.label }}
69
+ </slot>
70
+
71
+ <Icon v-if="option.value === modelValue" :is="TickIcon" />
72
+ </span>
73
+ </Button>
74
+
75
+ <p v-if="filtered.length === 0" class="px-2 py-6 text-center text-sm opacity-60">
76
+ {{ emptyLabel }}
77
+ </p>
78
+ </div>
78
79
  </div>
79
80
  </div>
80
81
  </template>
81
82
 
82
83
  <script setup lang="ts">
83
- import { computed, nextTick, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
84
- import type { SelectOption, SelectValue } from "../../components/select.ts";
84
+ import ArrowDownIcon from "~icons/hugeicons/arrow-down-01";
85
+ import TickIcon from "~icons/hugeicons/tick-02";
86
+ import { computed, nextTick, onMounted, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
87
+ import type { SelectOption, SelectProps, SelectValue } from "../../components/select";
85
88
  import Button from "./button.vue";
86
89
  import Icon from "./icon.vue";
87
90
 
88
91
  const {
89
- modelValue = undefined,
92
+ modelValue,
90
93
  options,
91
- label = undefined,
92
- placeholder = undefined,
94
+ label,
95
+ placeholder,
93
96
  emptyLabel = "No results found",
94
- name = undefined,
97
+ name,
95
98
  required = false,
96
99
  disabled = false,
97
- } = defineProps<{
98
- modelValue?: SelectValue;
99
- options: Array<SelectOption>;
100
- label?: string;
101
- placeholder?: string;
102
- emptyLabel?: string;
103
- name?: string;
104
- required?: boolean;
105
- disabled?: boolean;
106
- }>();
100
+ } = defineProps<SelectProps>();
107
101
 
108
102
  const emit = defineEmits<{
109
103
  "update:modelValue": [value: SelectValue];
@@ -121,6 +115,8 @@ const dropdown = useTemplateRef("dropdown");
121
115
 
122
116
  const listboxId = `select-${useId()}`;
123
117
 
118
+ const popoverSupported = ref(true);
119
+
124
120
  const selected = computed(() => options.find((option) => option.value === modelValue));
125
121
 
126
122
  const query = ref(selected.value?.label ?? "");
@@ -133,6 +129,14 @@ const filtered = computed(() => {
133
129
  return term === "" ? options : options.filter((option) => option.label.toLowerCase().includes(term));
134
130
  });
135
131
 
132
+ const fallbackClass = computed(() => {
133
+ if (popoverSupported.value) {
134
+ return undefined;
135
+ }
136
+
137
+ return open.value ? "absolute inset-x-0 top-full z-50 mt-2" : "hidden";
138
+ });
139
+
136
140
  function handlePointerDown(event: PointerEvent) {
137
141
  const target = event.target as Node;
138
142
 
@@ -156,12 +160,13 @@ function updatePanel() {
156
160
  return;
157
161
  }
158
162
 
163
+ const height = window.visualViewport?.height ?? window.innerHeight;
164
+ const width = window.visualViewport?.width ?? window.innerWidth;
159
165
  const bounds = anchor.getBoundingClientRect();
160
- const height = list.offsetHeight;
161
- const width = bounds.width;
162
- const left = Math.min(Math.max(8, bounds.left), window.innerWidth - width - 8);
163
- const top =
164
- bounds.bottom + height + 8 <= window.innerHeight ? bounds.bottom + 8 : Math.max(8, bounds.top - height - 8);
166
+ const listHeight = list.offsetHeight;
167
+ const listWidth = bounds.width;
168
+ const left = Math.max(8, Math.min(Math.max(8, bounds.left), width - listWidth - 8));
169
+ const top = bounds.bottom + listHeight + 8 <= height ? bounds.bottom + 8 : Math.max(8, bounds.top - listHeight - 8);
165
170
 
166
171
  panel.value = {
167
172
  bottom: "auto",
@@ -169,7 +174,7 @@ function updatePanel() {
169
174
  position: "fixed",
170
175
  right: "auto",
171
176
  top: `${top}px`,
172
- width: `${width}px`,
177
+ width: `${listWidth}px`,
173
178
  };
174
179
  }
175
180
 
@@ -186,7 +191,7 @@ watch(open, async (value) => {
186
191
  return;
187
192
  }
188
193
 
189
- if (value && !list.matches(":popover-open")) {
194
+ if (value) {
190
195
  query.value = "";
191
196
 
192
197
  document.addEventListener("pointerdown", handlePointerDown);
@@ -195,31 +200,49 @@ watch(open, async (value) => {
195
200
 
196
201
  window.addEventListener("scroll", updatePanel, true);
197
202
 
198
- list.showPopover();
203
+ window.visualViewport?.addEventListener("resize", updatePanel);
204
+
205
+ if (popoverSupported.value && !list.matches(":popover-open")) {
206
+ list.showPopover();
207
+ }
199
208
 
200
209
  await nextTick();
201
210
 
202
211
  updatePanel();
203
212
 
204
- search.value?.focus();
205
- } else if (!value && list.matches(":popover-open")) {
213
+ requestAnimationFrame(updatePanel);
214
+
215
+ if (document.activeElement !== search.value) {
216
+ search.value?.focus();
217
+ }
218
+ } else {
206
219
  document.removeEventListener("pointerdown", handlePointerDown);
207
220
 
208
221
  window.removeEventListener("resize", updatePanel);
209
222
 
210
223
  window.removeEventListener("scroll", updatePanel, true);
211
224
 
212
- list.hidePopover();
225
+ window.visualViewport?.removeEventListener("resize", updatePanel);
226
+
227
+ if (popoverSupported.value && list.matches(":popover-open")) {
228
+ list.hidePopover();
229
+ }
213
230
 
214
231
  query.value = selected.value?.label ?? "";
215
232
  }
216
233
  });
217
234
 
235
+ onMounted(() => {
236
+ popoverSupported.value = typeof dropdown.value?.showPopover === "function";
237
+ });
238
+
218
239
  onUnmounted(() => {
219
240
  document.removeEventListener("pointerdown", handlePointerDown);
220
241
 
221
242
  window.removeEventListener("resize", updatePanel);
222
243
 
223
244
  window.removeEventListener("scroll", updatePanel, true);
245
+
246
+ window.visualViewport?.removeEventListener("resize", updatePanel);
224
247
  });
225
248
  </script>
@@ -9,17 +9,9 @@
9
9
 
10
10
  <script setup lang="ts">
11
11
  import { computed } from "vue";
12
- import type { DividerVariant, SeparatorOrientation } from "../../components/separator.ts";
12
+ import type { SeparatorOrientation, SeparatorProps } from "../../components/separator";
13
13
 
14
- const {
15
- orientation = "horizontal",
16
- variant = undefined,
17
- decorative = false,
18
- } = defineProps<{
19
- orientation?: SeparatorOrientation;
20
- variant?: DividerVariant;
21
- decorative?: boolean;
22
- }>();
14
+ const { orientation = "horizontal", variant, decorative = false } = defineProps<SeparatorProps>();
23
15
 
24
16
  const selected = computed(() => variant ?? orientation);
25
17