edvoyui-component-library-test-flight 0.0.135 → 0.0.137

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,16 +1,31 @@
1
1
  <template>
2
- <div ref="popperWrapper" class="isolate w-max">
2
+ <div ref="popperWrapper" class="w-max">
3
3
  <div
4
4
  ref="popperButton"
5
+ :id="triggerId"
6
+ :aria-describedby="tooltipId"
7
+ :aria-expanded="isOpen"
8
+ aria-haspopup="true"
5
9
  :class="['inline-flex items-center text-sm font-semibold gap-x-2 cursor-pointer', customButton]"
10
+ @click="handleClick"
11
+ @mouseenter="handleMouseEnter"
12
+ @mouseleave="handleMouseLeave"
13
+ @focus="handleFocus"
14
+ @blur="handleBlur"
6
15
  >
7
16
  <slot name="referenceButton" :open="isOpen"/>
8
17
  </div>
9
18
  <div
10
- ref="tooltip"
11
- :class="['poppertooltip h-auto text-sm font-normal bg-white', className]"
19
+ ref="tooltip"
20
+ :id="tooltipId"
21
+ role="tooltip"
22
+ :class="['poppertooltip h-auto text-sm font-normal select-none z-50', className]"
23
+ :data-show="isOpen ? '' : null"
24
+ @click.stop
25
+ @mouseenter="handleTooltipMouseEnter"
26
+ @mouseleave="handleTooltipMouseLeave"
12
27
  >
13
- <div class="arrow" data-popper-arrow />
28
+ <div ref="arrowElement" class="arrow" data-popper-arrow />
14
29
  <div
15
30
  v-if="title"
16
31
  class="text-base font-semibold text-current leading-[normal] px-4 py-2"
@@ -22,19 +37,23 @@
22
37
  </template>
23
38
 
24
39
  <script setup lang="ts">
25
- import { PropType } from "vue";
40
+ import { computed, nextTick, onBeforeUnmount, PropType, watch } from "vue";
26
41
  import { createPopper, Instance } from "@popperjs/core";
27
- import { onClickOutside, useEventListener } from "@vueuse/core";
42
+ import { onClickOutside } from "@vueuse/core";
28
43
  import { onMounted, ref } from "vue";
29
44
  const props = defineProps({
45
+ defaultOpen: {
46
+ type: Boolean,
47
+ default: false,
48
+ },
30
49
  title: {
31
50
  type: String,
32
51
  default: "",
33
52
  },
34
53
  trigger: {
35
- type: String as PropType<"click" | "hover">,
54
+ type: String as PropType<"click" | "hover" | "manual">,
36
55
  default: "click",
37
- validator: (value: string) => ["click", "hover"].includes(value),
56
+ validator: (value: string) => ["click", "hover", "manual"].includes(value),
38
57
  },
39
58
  className: {
40
59
  type: String,
@@ -49,70 +68,179 @@ const props = defineProps({
49
68
  type: String,
50
69
  required: false,
51
70
  default: ''
52
- }
71
+ },
72
+ visible: {
73
+ type: Boolean,
74
+ default: undefined,
75
+ },
76
+ hoverHideDelay: {
77
+ type: Number,
78
+ default: 100,
79
+ },
53
80
  });
54
81
 
55
82
  const popperWrapper = ref<HTMLElement | null>(null);
56
83
  const popperInstance = ref<Instance | null>(null);
57
84
  const popperButton = ref<HTMLElement | null>(null);
58
85
  const tooltip = ref<HTMLElement | null>(null);
59
- const isOpen = ref(false);
86
+ const arrowElement = ref<HTMLElement | null>(null);
60
87
 
61
- const emit = defineEmits(['showPopover', 'hidePopover']);
88
+ const isOpen = ref(props.visible === undefined ? props.defaultOpen : props.visible);
89
+ const hideTimer = ref<ReturnType<typeof setTimeout> | null>(null);
62
90
 
63
- onMounted(() => {
64
- onClickOutside(popperWrapper.value, hide);
91
+ const uniqueId = `popover-${Math.random().toString(36).substring(2, 9)}`;
92
+ const triggerId = computed(() => `${uniqueId}-trigger`);
93
+ const tooltipId = computed(() => `${uniqueId}-tooltip`);
65
94
 
66
- if (props.trigger === "click") {
67
- useEventListener(popperButton.value, "click", toggle);
68
- } else if (props.trigger === "hover") {
69
- const showEvents = ["mouseenter", "focus"];
70
- const hideEvents = ["mouseleave", "blur"];
95
+ const emit = defineEmits<{
96
+ (e: 'update:visible', value: boolean): void;
97
+ (e: 'show'): void;
98
+ (e: 'hide'): void;
99
+ }>();
71
100
 
72
- showEvents.forEach((event) => {
73
- popperButton.value?.addEventListener(event, show);
74
- });
101
+ watch(() => props.visible, (newValue) => {
102
+ if (newValue !== undefined && newValue !== isOpen.value) {
103
+ if (newValue) {
104
+ show();
105
+ } else {
106
+ hide();
107
+ }
108
+ }
109
+ });
75
110
 
76
- hideEvents.forEach((event) => {
77
- popperButton.value?.addEventListener(event, hide);
78
- });
111
+ watch(isOpen, (newValue) => {
112
+ if (props.visible === undefined || newValue !== props.visible) {
113
+ emit('update:visible', newValue);
79
114
  }
115
+ nextTick(() => {
116
+ popperInstance.value?.update();
117
+ });
118
+ });
80
119
 
81
- if (popperButton.value && tooltip.value) {
120
+
121
+ const initializePopper = () => {
122
+ if (popperButton.value && tooltip.value && arrowElement.value) {
82
123
  popperInstance.value = createPopper(popperButton.value, tooltip.value, {
83
124
  placement: props.placement,
84
125
  modifiers: [
126
+ { name: 'offset', options: { offset: [0, 10] } },
127
+ { name: 'arrow', options: { element: arrowElement.value } },
85
128
  {
86
- name: "offset",
129
+ name: 'preventOverflow',
87
130
  options: {
88
- offset: [0, 8],
131
+ padding: 8,
89
132
  },
90
133
  },
91
134
  ],
92
135
  });
93
136
  }
94
- });
137
+ };
95
138
 
96
- function toggle() {
97
- if (isOpen.value) {
98
- hide();
139
+ const destroyPopper = () => {
140
+ if (popperInstance.value) {
141
+ popperInstance.value.destroy();
142
+ popperInstance.value = null;
143
+ }
144
+ };
145
+
146
+ const show = () => {
147
+ if (isOpen.value) return;
148
+ clearTimeout(hideTimer.value!);
149
+ isOpen.value = true;
150
+ emit('show');
151
+ nextTick(() => {
152
+ if (!popperInstance.value) {
153
+ initializePopper();
154
+ }
155
+ popperInstance.value?.update();
156
+ popperInstance.value?.forceUpdate();
157
+ });
158
+ };
159
+
160
+ // Hide the popover
161
+ const hide = (immediate = false) => {
162
+ if (!isOpen.value) return; // Already closed
163
+
164
+ if (props.trigger === 'hover' && !immediate) {
165
+ clearTimeout(hideTimer.value!);
166
+ hideTimer.value = setTimeout(() => {
167
+ isOpen.value = false;
168
+ emit('hide');
169
+ }, props.hoverHideDelay);
99
170
  } else {
171
+ clearTimeout(hideTimer.value!);
172
+ isOpen.value = false;
173
+ emit('hide');
174
+ }
175
+ };
176
+
177
+ const handleClick = () => {
178
+ if (props.trigger === 'click') {
179
+ if (isOpen.value) {
180
+ hide(true);
181
+ } else {
182
+ show();
183
+ }
184
+ }
185
+ };
186
+
187
+ const handleMouseEnter = () => {
188
+ if (props.trigger === 'hover') {
100
189
  show();
101
190
  }
102
- }
191
+ };
103
192
 
104
- function show() {
105
- tooltip.value?.setAttribute("data-show", "");
106
- isOpen.value = true;
107
- emit("showPopover");
108
- popperInstance.value?.update();
109
- }
193
+ const handleMouseLeave = () => {
194
+ if (props.trigger === 'hover') {
195
+ hide(false);
196
+ }
197
+ };
110
198
 
111
- function hide() {
112
- tooltip.value?.removeAttribute("data-show");
113
- isOpen.value = false;
114
- emit("hidePopover");
115
- }
199
+ const handleFocus = () => {
200
+ if (props.trigger === 'hover') {
201
+ show();
202
+ }
203
+ };
204
+
205
+ const handleBlur = () => {
206
+ if (props.trigger === 'hover') {
207
+ hide(false);
208
+ }
209
+ };
210
+
211
+ const handleTooltipMouseEnter = () => {
212
+ if (props.trigger === 'hover') {
213
+ clearTimeout(hideTimer.value!);
214
+ }
215
+ };
216
+
217
+ const handleTooltipMouseLeave = () => {
218
+ if (props.trigger === 'hover') {
219
+ hide(false);
220
+ }
221
+ };
222
+
223
+ onMounted(() => {
224
+ if (isOpen.value) {
225
+ nextTick(() => {
226
+ initializePopper();
227
+ setTimeout(() => popperInstance.value?.forceUpdate(), 50);
228
+ });
229
+ }
230
+
231
+ if (props.trigger === 'click') {
232
+ onClickOutside(popperWrapper.value, () => {
233
+ if (isOpen.value) {
234
+ hide(true);
235
+ }
236
+ }, { ignore: [popperButton] });
237
+ }
238
+ });
239
+
240
+ onBeforeUnmount(() => {
241
+ destroyPopper();
242
+ clearTimeout(hideTimer.value!); // Clear any running timers
243
+ });
116
244
  </script>
117
245
 
118
246
  <style lang="scss" scoped>