edvoyui-component-library-test-flight 0.0.248 → 0.0.251

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.
@@ -37,6 +37,8 @@ interface Props {
37
37
  maxVisibleTags?: number;
38
38
  tagSize?: "sm" | "md" | "lg";
39
39
  dropdownWidth?: string;
40
+ /** Render the menu at body level to escape clipping scroll containers. */
41
+ teleported?: boolean;
40
42
  }
41
43
  export type SelectItemKey = string | (string | number)[] | ((item: object, fallback?: any) => any);
42
44
  declare function __VLS_template(): {
@@ -59,6 +61,7 @@ declare function __VLS_template(): {
59
61
  };
60
62
  refs: {
61
63
  componentRef: HTMLDivElement;
64
+ triggerRef: HTMLDivElement;
62
65
  searchInputRef: import('../../../node_modules/vue').CreateComponentPublicInstanceWithMixins<Readonly<import('../../../node_modules/vue').ExtractPropTypes<{
63
66
  modelValue: {
64
67
  type: (StringConstructor | NumberConstructor)[];
@@ -194,6 +197,7 @@ declare function __VLS_template(): {
194
197
  activeOptionId: string;
195
198
  expanded: boolean;
196
199
  }> | null;
200
+ dropdownContentRef: HTMLDivElement;
197
201
  options: HTMLUListElement;
198
202
  };
199
203
  rootEl: HTMLDivElement;
@@ -231,6 +235,7 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
231
235
  clearable: boolean;
232
236
  searchable: boolean;
233
237
  placement: "auto" | "auto-start" | "auto-end" | "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "right" | "right-start" | "right-end" | "left" | "left-start" | "left-end";
238
+ teleported: boolean;
234
239
  classes: string;
235
240
  backendPagination: boolean;
236
241
  itemText: string;
@@ -252,6 +257,7 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
252
257
  dropdownWidth: string;
253
258
  }, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, false, {
254
259
  componentRef: HTMLDivElement;
260
+ triggerRef: HTMLDivElement;
255
261
  searchInputRef: import('../../../node_modules/vue').CreateComponentPublicInstanceWithMixins<Readonly<import('../../../node_modules/vue').ExtractPropTypes<{
256
262
  modelValue: {
257
263
  type: (StringConstructor | NumberConstructor)[];
@@ -387,6 +393,7 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
387
393
  activeOptionId: string;
388
394
  expanded: boolean;
389
395
  }> | null;
396
+ dropdownContentRef: HTMLDivElement;
390
397
  options: HTMLUListElement;
391
398
  }, HTMLDivElement>;
392
399
  declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
@@ -9,20 +9,7 @@
9
9
  {{ label }} <span v-if="required" class="text-red-500">*</span>
10
10
  </label>
11
11
  </slot>
12
- <Popper
13
- :class="[`popper-defaultstyle w-full ${popperClasses}`]"
14
- v-bind="$attrs"
15
- :show="showDropDown"
16
- arrow
17
- closeDelay="100"
18
- :offsetSkid="offsetSkid"
19
- :offsetDistance="offsetDistance"
20
- :placement="placement"
21
- :interactive="true"
22
- :auto-hide="true"
23
- @open:popper="handleOpen"
24
- @close:popper="handleClose"
25
- >
12
+ <div ref="triggerRef" class="w-full" v-bind="$attrs">
26
13
  <SearchInput
27
14
  v-if="showDropDown || selectedValues.length === 0"
28
15
  ref="searchInputRef"
@@ -89,11 +76,20 @@
89
76
  />
90
77
  </div>
91
78
  </button>
92
- <template #content>
93
- <div
94
- v-if="showDropDown"
95
- :class="['w-full p-3 text-sm bg-white border-2 border-gray-100 border-solid shadow-lg rounded-3xl min-w-28', confirmSelection && selectType === 'multiple' ? '' : 'overflow-y-auto max-h-72 scrollbar--hide', dropdownWidth]"
96
- >
79
+ </div>
80
+ <Teleport to="body" :disabled="!teleported">
81
+ <div
82
+ v-if="showDropDown"
83
+ ref="dropdownContentRef"
84
+ :class="[
85
+ 'p-3 text-sm bg-white border-2 border-gray-100 border-solid shadow-lg rounded-3xl min-w-28',
86
+ confirmSelection && selectType === 'multiple' ? '' : 'overflow-y-auto max-h-72 scrollbar--hide',
87
+ dropdownWidth,
88
+ teleported ? 'z-[60]' : 'z-50',
89
+ 'transition-opacity duration-150 ease-out',
90
+ dropdownReady ? 'opacity-100' : 'opacity-0 pointer-events-none',
91
+ ]"
92
+ >
97
93
  <div class="flex items-center justify-between p-2 pb-0 mb-1 -mx-px -mt-3">
98
94
  <h3
99
95
  class="mb-0 text-xs font-semibold tracking-wide text-gray-500 uppercase bg-white "
@@ -120,7 +116,7 @@
120
116
  tabindex="-1"
121
117
  role="listbox"
122
118
  aria-label="Search results"
123
- :class="['space-y-0.5', confirmSelection && selectType === 'multiple' ? 'max-h-52 overflow-y-auto scrollbar--hide' : '']"
119
+ :class="['space-y-0.5', confirmSelection && selectType === 'multiple' ? 'max-h-48 overflow-y-auto scrollbar--hide' : '']"
124
120
  >
125
121
  <li
126
122
  v-if="showAllOption"
@@ -268,9 +264,8 @@
268
264
  Apply
269
265
  </EUIButton>
270
266
  </div>
271
- </div>
272
- </template>
273
- </Popper>
267
+ </div>
268
+ </Teleport>
274
269
  <template v-if="errors && errors.length && !showDropDown">
275
270
  <EUIErrorMessage :errors="errors" :name="name" class="mt-2" />
276
271
  </template>
@@ -350,7 +345,7 @@ import ChevronDownStroke from "../../assets/svg/ChevronDownStroke.vue";
350
345
  import EUICheckbox from "../checkbox/EUICheckbox.vue";
351
346
  import EUITag from "../tag/EUITag.vue";
352
347
  import EUIButton from "../button/EUIButton.vue";
353
- import Popper from "vue3-popper";
348
+ import { createPopper, type Instance, type Placement } from "@popperjs/core";
354
349
  import CheckTick from "../../assets/svg/CheckTick.vue";
355
350
  import { XMarkIcon } from "@heroicons/vue/20/solid";
356
351
  import EUIErrorMessage from "../errorMessage/EUIErrorMessage.vue";
@@ -409,6 +404,8 @@ interface Props {
409
404
  maxVisibleTags?: number;
410
405
  tagSize?: "sm" | "md" | "lg";
411
406
  dropdownWidth?: string,
407
+ /** Render the menu at body level to escape clipping scroll containers. */
408
+ teleported?: boolean;
412
409
  }
413
410
  const props = withDefaults(defineProps<Props>(), {
414
411
  items: () => [],
@@ -440,7 +437,8 @@ const props = withDefaults(defineProps<Props>(), {
440
437
  selectAllLabel: "All",
441
438
  maxVisibleTags: 0,
442
439
  tagSize: "md",
443
- dropdownWidth: ''
440
+ dropdownWidth: '',
441
+ teleported: false,
444
442
  });
445
443
 
446
444
 
@@ -464,6 +462,10 @@ const draftValues = ref<any[]>([]);
464
462
  const options = ref<HTMLElement>();
465
463
  const searchInputRef = ref<any>();
466
464
  const componentRef = ref<HTMLElement>();
465
+ const triggerRef = ref<HTMLElement>();
466
+ const dropdownContentRef = ref<HTMLElement>();
467
+ const dropdownReady = ref(false);
468
+ let popperInstance: Instance | null = null;
467
469
  const tagsExpanded = ref(false);
468
470
  const highlightedIndex = ref(-1);
469
471
  const listboxId = `search-tag-listbox-${getCurrentInstance()?.uid ?? 0}`;
@@ -828,7 +830,58 @@ const handleSearchKeydown = (event: KeyboardEvent) => {
828
830
  else handleSelect(item);
829
831
  };
830
832
 
831
- const handleOpen = () => {
833
+ const destroyPopper = () => {
834
+ popperInstance?.destroy();
835
+ popperInstance = null;
836
+ };
837
+
838
+ const createDropdownPopper = async () => {
839
+ await nextTick();
840
+ if (!showDropDown.value || !triggerRef.value || !dropdownContentRef.value)
841
+ return;
842
+
843
+ destroyPopper();
844
+ popperInstance = createPopper(triggerRef.value, dropdownContentRef.value, {
845
+ placement: props.placement as Placement,
846
+ strategy: props.teleported ? "fixed" : "absolute",
847
+ modifiers: [
848
+ {
849
+ name: "offset",
850
+ options: { offset: [Number(props.offsetSkid), Number(props.offsetDistance)] },
851
+ },
852
+ {
853
+ name: "flip",
854
+ options: {
855
+ padding: 8,
856
+ fallbackPlacements: ["bottom-start", "top-start", "bottom-end", "top-end"],
857
+ },
858
+ },
859
+ {
860
+ name: "preventOverflow",
861
+ options: {
862
+ boundary: "viewport",
863
+ rootBoundary: "viewport",
864
+ padding: 8,
865
+ altAxis: true,
866
+ tether: true,
867
+ },
868
+ },
869
+ {
870
+ name: "sameWidth",
871
+ enabled: !props.dropdownWidth,
872
+ phase: "beforeWrite",
873
+ requires: ["computeStyles"],
874
+ fn: ({ state }) => {
875
+ state.styles.popper.width = `${state.rects.reference.width}px`;
876
+ },
877
+ },
878
+ { name: "eventListeners", options: { scroll: true, resize: true } },
879
+ ],
880
+ });
881
+ await popperInstance.update();
882
+ };
883
+
884
+ const handleOpen = async () => {
832
885
  if (props.disabled || showDropDown.value) return;
833
886
 
834
887
  highlightedIndex.value = -1;
@@ -841,8 +894,11 @@ const handleOpen = () => {
841
894
  if (props.selectType === "single") {
842
895
  internalSearch.value = "";
843
896
  }
897
+ dropdownReady.value = false;
844
898
  showDropDown.value = true;
845
- window.addEventListener("scroll", handleScroll, true);
899
+ await createDropdownPopper();
900
+ if (!showDropDown.value) return;
901
+ dropdownReady.value = true;
846
902
  emit("open");
847
903
 
848
904
  nextTick(() => {
@@ -877,7 +933,8 @@ const handleApply = () => {
877
933
 
878
934
  const handleClose = () => {
879
935
  highlightedIndex.value = -1;
880
- window.removeEventListener("scroll", handleScroll, true);
936
+ destroyPopper();
937
+ dropdownReady.value = false;
881
938
  if (focusTimer) {
882
939
  clearTimeout(focusTimer);
883
940
  focusTimer = undefined;
@@ -908,34 +965,25 @@ const handleSearchFocus = () => {
908
965
  };
909
966
 
910
967
  // Document click handler for outside clicks
911
- const handleDocumentClick = (event: MouseEvent) => {
912
- // The trigger can be replaced during this click. composedPath retains its
913
- // original ancestors, so an inside click stays inside after that DOM update.
968
+ const handleDocumentPointerDown = (event: PointerEvent) => {
969
+ if (!showDropDown.value) return;
970
+ const target = event.target as Node;
914
971
  if (
915
- showDropDown.value &&
916
- componentRef.value &&
917
- !event.composedPath().includes(componentRef.value)
918
- ) {
919
- handleDismiss();
920
- }
921
- };
972
+ componentRef.value?.contains(target) ||
973
+ dropdownContentRef.value?.contains(target)
974
+ ) return;
922
975
 
923
- const handleScroll = (event: Event) => {
924
- if (!showDropDown.value) return;
925
- const target = event.target;
926
- // Keep the options usable; scrolling any ancestor or the page closes them.
927
- if (target instanceof Node && componentRef.value?.contains(target)) return;
928
976
  handleDismiss();
929
977
  };
930
978
 
931
979
  // Lifecycle hooks
932
980
  onMounted(() => {
933
- document.addEventListener("click", handleDocumentClick);
981
+ document.addEventListener("pointerdown", handleDocumentPointerDown);
934
982
  });
935
983
 
936
984
  onUnmounted(() => {
937
- document.removeEventListener("click", handleDocumentClick);
938
- window.removeEventListener("scroll", handleScroll, true);
985
+ document.removeEventListener("pointerdown", handleDocumentPointerDown);
986
+ destroyPopper();
939
987
  if (focusTimer) clearTimeout(focusTimer);
940
988
  });
941
989
 
@@ -944,19 +992,19 @@ watch(internalSearch, (value: string) => {
944
992
  emit("search", value);
945
993
  });
946
994
 
995
+ watch(
996
+ () => [props.placement, props.offsetDistance, props.offsetSkid, props.teleported],
997
+ async () => {
998
+ if (!showDropDown.value) return;
999
+ dropdownReady.value = false;
1000
+ await createDropdownPopper();
1001
+ if (showDropDown.value) dropdownReady.value = true;
1002
+ },
1003
+ );
1004
+
947
1005
  </script>
948
1006
 
949
1007
  <style lang="scss">
950
- .popper-defaultstyle {
951
- border: 0px solid transparent !important;
952
- margin: 0px !important;
953
- @apply w-full;
954
-
955
- & > div.popper {
956
- width: 100% !important;
957
- }
958
- }
959
-
960
1008
  .show-count {
961
1009
  @apply min-w-5 h-5 inline-flex items-center justify-center text-xss font-semibold bg-white rounded-xl;
962
1010
  }
@@ -470,7 +470,7 @@ const headerStyle = (header: Header) => ({
470
470
  });
471
471
 
472
472
  const bodyStyle = (header: Header) => ({
473
- width: `${header.width || 300}px`,
473
+ minWidth: `${header.width || 300}px`,
474
474
  maxWidth: `${header.width || 300}px`,
475
475
  // flex: `${header.width || 300} 0 auto`,
476
476
  });