@sit-onyx/headless 1.0.0-beta.19 → 1.0.0-beta.20

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sit-onyx/headless",
3
3
  "description": "Headless composables for Vue",
4
- "version": "1.0.0-beta.19",
4
+ "version": "1.0.0-beta.20",
5
5
  "type": "module",
6
6
  "author": "Schwarz IT KG",
7
7
  "license": "Apache-2.0",
@@ -25,7 +25,7 @@ describe("useOutsideClick", () => {
25
25
  useOutsideClick({ inside, onOutsideClick });
26
26
 
27
27
  // ACT
28
- const event = new MouseEvent("click", { bubbles: true });
28
+ const event = new MouseEvent("mousedown", { bubbles: true });
29
29
  outside.value.dispatchEvent(event);
30
30
 
31
31
  // ASSERT
@@ -53,7 +53,7 @@ describe("useOutsideClick", () => {
53
53
  const onOutsideClick = vi.fn();
54
54
  useOutsideClick({ inside, onOutsideClick });
55
55
  // ACT
56
- const event = new MouseEvent("click", { bubbles: true });
56
+ const event = new MouseEvent("mousedown", { bubbles: true });
57
57
  inside[0].dispatchEvent(event);
58
58
  inside[1].dispatchEvent(event);
59
59
  // ASSERT
@@ -79,7 +79,7 @@ describe("useOutsideClick", () => {
79
79
  useOutsideClick({ inside, disabled, onOutsideClick });
80
80
 
81
81
  // ACT
82
- const event = new MouseEvent("click", { bubbles: true });
82
+ const event = new MouseEvent("mousedown", { bubbles: true });
83
83
  outside.value.dispatchEvent(event);
84
84
  // ASSERT
85
85
  expect(onOutsideClick).toHaveBeenCalledTimes(1);
@@ -88,7 +88,7 @@ describe("useOutsideClick", () => {
88
88
  // ACT
89
89
  disabled.value = true;
90
90
  await vi.runAllTimersAsync();
91
- const event2 = new MouseEvent("click", { bubbles: true });
91
+ const event2 = new MouseEvent("mousedown", { bubbles: true });
92
92
  outside.value.dispatchEvent(event2);
93
93
  // ASSERT
94
94
  expect(onOutsideClick).toHaveBeenCalledTimes(1);
@@ -49,7 +49,7 @@ export const useOutsideClick = <TCheckOnTab extends boolean | undefined>({
49
49
  if (isOutsideClick(event.target)) onOutsideClick(event);
50
50
  };
51
51
 
52
- useGlobalEventListener({ type: "click", listener: clickListener, disabled });
52
+ useGlobalEventListener({ type: "mousedown", listener: clickListener, disabled });
53
53
 
54
54
  if (checkOnTab) {
55
55
  const keydownListener = async (event: KeyboardEvent) => {
@@ -1,4 +1,4 @@
1
- import { computed, useId, watch, type Ref } from "vue";
1
+ import { computed, toValue, useId, watch, type MaybeRef, type Ref } from "vue";
2
2
  import { createBuilder, createElRef } from "../../utils/builder";
3
3
  import { debounce } from "../../utils/timer";
4
4
  import { useGlobalEventListener } from "../helpers/useGlobalListener";
@@ -6,9 +6,15 @@ import { useOutsideClick } from "../helpers/useOutsideClick";
6
6
 
7
7
  type CreateMenuButtonOptions = {
8
8
  isExpanded: Readonly<Ref<boolean>>;
9
- trigger: Readonly<Ref<"hover" | "click">>;
9
+ trigger: Readonly<MaybeRef<"hover" | "click">>;
10
10
  onToggle: () => void;
11
11
  disabled?: Readonly<Ref<boolean>>;
12
+ /**
13
+ * Whether the menu button opens to the top or bottom. Defines the keyboard navigation behavior (e.g. Arrow up and down).
14
+ *
15
+ * @default "bottom"
16
+ */
17
+ position?: MaybeRef<"top" | "bottom">;
12
18
  };
13
19
 
14
20
  /**
@@ -21,6 +27,8 @@ export const createMenuButton = createBuilder((options: CreateMenuButtonOptions)
21
27
  const menuRef = createElRef<HTMLElement>();
22
28
  const buttonId = useId();
23
29
 
30
+ const position = computed(() => toValue(options.position) ?? "bottom");
31
+
24
32
  useGlobalEventListener({
25
33
  type: "keydown",
26
34
  listener: (e) => e.key === "Escape" && setExpanded(false),
@@ -57,6 +65,7 @@ export const createMenuButton = createBuilder((options: CreateMenuButtonOptions)
57
65
  const menuItems = Array.from(currentMenu.querySelectorAll<HTMLElement>('[role="menuitem"]'))
58
66
  // filter out nested children
59
67
  .filter((item) => item.closest('[role="menu"]') === currentMenu);
68
+ if (position.value === "top") menuItems.reverse();
60
69
  let nextIndex = 0;
61
70
 
62
71
  if (currentMenuItem) {
@@ -85,11 +94,11 @@ export const createMenuButton = createBuilder((options: CreateMenuButtonOptions)
85
94
  switch (event.key) {
86
95
  case "ArrowDown":
87
96
  event.preventDefault();
88
- focusRelativeItem("next");
97
+ focusRelativeItem(position.value === "bottom" ? "next" : "prev");
89
98
  break;
90
99
  case "ArrowUp":
91
100
  event.preventDefault();
92
- focusRelativeItem("prev");
101
+ focusRelativeItem(position.value === "bottom" ? "prev" : "next");
93
102
  break;
94
103
  case "Home":
95
104
  event.preventDefault();
@@ -113,7 +122,7 @@ export const createMenuButton = createBuilder((options: CreateMenuButtonOptions)
113
122
  };
114
123
 
115
124
  const triggerEvents = computed(() => {
116
- if (options.trigger.value !== "hover") return;
125
+ if (toValue(options.trigger) !== "hover") return;
117
126
  return {
118
127
  onMouseenter: () => setExpanded(true),
119
128
  onMouseleave: () => setExpanded(false, true),
@@ -143,7 +152,9 @@ export const createMenuButton = createBuilder((options: CreateMenuButtonOptions)
143
152
  "aria-haspopup": true,
144
153
  onFocus: () => setExpanded(true, true),
145
154
  onClick: () =>
146
- options.trigger.value == "click" ? setExpanded(!options.isExpanded.value) : undefined,
155
+ toValue(options.trigger) == "click"
156
+ ? setExpanded(!options.isExpanded.value)
157
+ : undefined,
147
158
  id: buttonId,
148
159
  disabled: options.disabled?.value,
149
160
  }) as const,