edvoyui-component-library-test-flight 0.0.222 → 0.0.224

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.
@@ -19,6 +19,7 @@ declare function __VLS_template(): {
19
19
  }): any;
20
20
  };
21
21
  refs: {
22
+ dropdownRoot: HTMLDivElement;
22
23
  dropdownButton: HTMLButtonElement;
23
24
  dropdownMenu: HTMLDivElement;
24
25
  };
@@ -67,6 +68,15 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
67
68
  type: PropType<"top" | "bottom" | "left" | "right">;
68
69
  default: string;
69
70
  };
71
+ /**
72
+ * Keep the dropdown open while the user interacts with content inside the menu
73
+ * (search fields, row actions). Selecting an item and clicking outside still close it.
74
+ * Off by default so existing menus keep dismissing on any click inside them.
75
+ */
76
+ keepOpenOnContentClick: {
77
+ type: BooleanConstructor;
78
+ default: boolean;
79
+ };
70
80
  }>, {}, {}, {}, {}, import('../../../node_modules/vue').ComponentOptionsMixin, import('../../../node_modules/vue').ComponentOptionsMixin, {
71
81
  subMenuItem: (...args: any[]) => void;
72
82
  menuItem: (...args: any[]) => void;
@@ -113,6 +123,15 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
113
123
  type: PropType<"top" | "bottom" | "left" | "right">;
114
124
  default: string;
115
125
  };
126
+ /**
127
+ * Keep the dropdown open while the user interacts with content inside the menu
128
+ * (search fields, row actions). Selecting an item and clicking outside still close it.
129
+ * Off by default so existing menus keep dismissing on any click inside them.
130
+ */
131
+ keepOpenOnContentClick: {
132
+ type: BooleanConstructor;
133
+ default: boolean;
134
+ };
116
135
  }>> & Readonly<{
117
136
  onSubMenuItem?: ((...args: any[]) => any) | undefined;
118
137
  onMenuItem?: ((...args: any[]) => any) | undefined;
@@ -123,7 +142,9 @@ declare const __VLS_component: import('../../../node_modules/vue').DefineCompone
123
142
  placement: "top" | "bottom" | "right" | "left";
124
143
  isSolidArrow: boolean;
125
144
  menuItems: MenuItem[];
145
+ keepOpenOnContentClick: boolean;
126
146
  }, {}, {}, {}, string, import('../../../node_modules/vue').ComponentProvideOptions, true, {
147
+ dropdownRoot: HTMLDivElement;
127
148
  dropdownButton: HTMLButtonElement;
128
149
  dropdownMenu: HTMLDivElement;
129
150
  }, HTMLDivElement>;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="relative">
2
+ <div ref="dropdownRoot" class="relative">
3
3
  <!-- Main Dropdown Button -->
4
4
  <button
5
5
  type="button"
@@ -35,7 +35,7 @@
35
35
  class="relative flex items-center px-4 py-2 rounded-lg cursor-pointer hover:bg-gray-100"
36
36
  @mouseenter="setActiveMenuItem(item.text)"
37
37
  @mouseleave="clearActiveMenuItem"
38
- @click.stop="$emit('menuItem', item)"
38
+ @click.stop="selectMenuItem(item)"
39
39
  >
40
40
  <div
41
41
  class="flex items-center justify-between w-full gap-2 text-sm font-medium text-gray-800 break-words hover:text-gray-900"
@@ -66,7 +66,7 @@
66
66
  <div
67
67
  v-if="item.enableAction"
68
68
  class="flex items-center justify-center w-full gap-1 px-6 py-3 text-sm font-medium text-gray-900 bg-purple-100 rounded-t-md"
69
- @click.prevent="$emit('actionItem', 'action')"
69
+ @click.stop.prevent="triggerActionItem()"
70
70
  >
71
71
  <slot name="actionName">{{ "+ Action Name" }}</slot>
72
72
  </div>
@@ -77,7 +77,7 @@
77
77
  v-for="subItem in item.subMenu"
78
78
  :key="subItem.text"
79
79
  class="flex items-center justify-between gap-2 px-3 py-2 text-sm font-medium text-gray-700 rounded-lg cursor-pointer hover:bg-gray-100 hover:text-gray-900"
80
- @click.stop="$emit('subMenuItem', subItem)"
80
+ @click.stop="selectSubMenuItem(subItem)"
81
81
  >
82
82
  <slot name="submenu" :subItem="subItem">
83
83
  {{ subItem.text }}
@@ -156,22 +156,37 @@ const props = defineProps({
156
156
  type: String as PropType<"top" | "bottom" | "left" | "right">,
157
157
  default: "bottom",
158
158
  },
159
+ /**
160
+ * Keep the dropdown open while the user interacts with content inside the menu
161
+ * (search fields, row actions). Selecting an item and clicking outside still close it.
162
+ * Off by default so existing menus keep dismissing on any click inside them.
163
+ */
164
+ keepOpenOnContentClick: {
165
+ type: Boolean,
166
+ default: false,
167
+ },
159
168
  });
160
169
 
161
170
  const isOpen = ref(false);
162
171
  const activeMenuItem = ref<string | null>(null);
172
+ const dropdownRoot = ref<HTMLElement | null>(null);
163
173
  const dropdownButton = ref<HTMLElement | null>(null);
164
174
  const dropdownMenu = ref<HTMLElement | null>(null);
165
175
  const resolvedPlacement = ref<"top" | "bottom" | "left" | "right">(
166
176
  props.placement
167
177
  );
168
178
 
169
- defineEmits(["subMenuItem", "menuItem", "actionItem"]);
179
+ const emit = defineEmits(["subMenuItem", "menuItem", "actionItem"]);
170
180
 
171
181
  const toggleDropdown = () => {
172
182
  isOpen.value = !isOpen.value;
173
183
  };
174
184
 
185
+ const closeDropdown = () => {
186
+ isOpen.value = false;
187
+ activeMenuItem.value = null;
188
+ };
189
+
175
190
  const setActiveMenuItem = (text: string) => {
176
191
  activeMenuItem.value = text;
177
192
  };
@@ -180,8 +195,32 @@ const clearActiveMenuItem = () => {
180
195
  activeMenuItem.value = null;
181
196
  };
182
197
 
198
+ const selectMenuItem = (item: MenuItem) => {
199
+ emit("menuItem", item);
200
+ closeDropdown();
201
+ };
202
+
203
+ const selectSubMenuItem = (subItem: MenuItem) => {
204
+ emit("subMenuItem", subItem);
205
+ closeDropdown();
206
+ };
207
+
208
+ const triggerActionItem = () => {
209
+ emit("actionItem", "action");
210
+ closeDropdown();
211
+ };
212
+
213
+ // A click anywhere outside the dropdown always closes it.
214
+ onClickOutside(dropdownRoot, () => {
215
+ closeDropdown();
216
+ });
217
+
218
+ // Legacy behaviour: the watcher used to sit on the trigger button alone, so clicks on the
219
+ // menu itself counted as "outside" and dismissed it. Menus that host interactive content
220
+ // opt out with `keepOpenOnContentClick` - they close on selection instead.
183
221
  onClickOutside(dropdownButton, () => {
184
- isOpen.value = false;
222
+ if (props.keepOpenOnContentClick) return;
223
+ closeDropdown();
185
224
  });
186
225
 
187
226
  const updateDropdownPlacement = () => {
@@ -620,7 +620,7 @@ const offsetValue = computed(() => props.customOffset);
620
620
 
621
621
  const withPopper = (
622
622
  dropdownList: HTMLElement,
623
- component: { $refs: { toggle: Element | VirtualElement } },
623
+ component: { $refs: { toggle: Element | VirtualElement }; open: boolean },
624
624
  { width, maxHeight }: any,
625
625
  ) => {
626
626
  const newwidth = selectWidth.value || width; //setcustom width '400px'
@@ -645,7 +645,23 @@ const withPopper = (
645
645
  ],
646
646
  });
647
647
  dropdownList.classList.add("toggle-dropdown");
648
- return () => popper.destroy();
648
+
649
+ // The dropdown is appended to <body>, so it only tracks its trigger via
650
+ // Popper's rAF-scheduled scroll listener, which visibly lags the trigger
651
+ // during a fast/continuous scroll of an ancestor container until scrolling
652
+ // stops. Closing immediately on any outside scroll (capture phase catches
653
+ // non-bubbling scroll events from nested containers) avoids that detached,
654
+ // floating state entirely.
655
+ const closeOnScroll = (event: Event) => {
656
+ if (dropdownList.contains(event.target as Node)) return;
657
+ component.open = false;
658
+ };
659
+ document.addEventListener("scroll", closeOnScroll, true);
660
+
661
+ return () => {
662
+ document.removeEventListener("scroll", closeOnScroll, true);
663
+ popper.destroy();
664
+ };
649
665
  };
650
666
 
651
667
  const fuseSearch = (