@featherk/composables 0.10.2 → 0.10.4

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.
@@ -6,35 +6,16 @@ Composable for accessible Kendo UI for Vue `Popup` + `Menu` action menus. It man
6
6
 
7
7
  ## Quick Start
8
8
 
9
- ```ts
10
- <script setup lang="ts">
11
- import { computed, ref } from "vue";
12
- import { usePopupMenu } from "@featherk/composables/menu";
13
-
14
- const isOpen = ref(false);
15
- const triggerRef = ref<HTMLElement | null>(null);
16
- const menuRef = ref<HTMLElement | null>(null);
17
- const panelRef = ref<HTMLElement | null>(null);
18
-
19
- const menu = usePopupMenu({
20
- isOpen,
21
- triggerRef,
22
- menuRef,
23
- focusTargetRef: panelRef,
24
- requestShow: () => (isOpen.value = true),
25
- requestHide: () => (isOpen.value = false),
26
- });
27
-
28
- const onSelect = (event: Parameters<typeof menu.handleMenuSelect>[0]) => {
29
- menu.handleMenuSelect(event, (selected) => {
30
- // Keep application-specific actions in the consuming component.
31
- if (selected.item?.text === "Archive") archiveRecord();
32
- });
33
- };
34
- </script>
9
+ 1. Create parent-owned refs for `isOpen`, `triggerRef`, `menuRef`, and an optional keyboard focus target.
10
+ 2. Call `usePopupMenu(...)` with `requestShow` and `requestHide` that update the parent-owned `isOpen` state.
11
+ 3. Route Kendo Menu `@select` through `handleMenuSelect(event, onAction)` so the composable closes first and your business action runs second.
12
+ 4. Bind trigger button `@click` and `@keydown` to the composable handlers.
13
+ 5. Bind Popup `@close` and Menu `@keydown.escape` so keyboard and mouse close paths restore focus correctly.
35
14
 
15
+ ```vue
36
16
  <template>
37
17
  <section ref="panelRef">
18
+ <!-- Step 4: bind trigger handlers -->
38
19
  <button
39
20
  ref="triggerRef"
40
21
  type="button"
@@ -45,6 +26,7 @@ const onSelect = (event: Parameters<typeof menu.handleMenuSelect>[0]) => {
45
26
  Actions
46
27
  </button>
47
28
 
29
+ <!-- Step 5: bind popup/menu close hooks for modality-aware focus restoration -->
48
30
  <Popup :show="isOpen" @close="menu.handlePopupClose">
49
31
  <Menu
50
32
  ref="menuRef"
@@ -54,16 +36,57 @@ const onSelect = (event: Parameters<typeof menu.handleMenuSelect>[0]) => {
54
36
  </Popup>
55
37
  </section>
56
38
  </template>
39
+
40
+ <script setup lang="ts">
41
+ import { ref } from "vue";
42
+ import {
43
+ usePopupMenu,
44
+ type KendoMenuSelectEvent,
45
+ } from "@featherk/composables/menu";
46
+
47
+ // Step 1: parent-owned menu state and element refs
48
+ const isOpen = ref(false);
49
+ const triggerRef = ref<HTMLElement | null>(null);
50
+ const menuRef = ref<HTMLElement | null>(null);
51
+ const panelRef = ref<HTMLElement | null>(null);
52
+
53
+ // Step 2: wire composable show/hide ownership to parent state
54
+ const menu = usePopupMenu({
55
+ isOpen,
56
+ triggerRef,
57
+ menuRef,
58
+ focusTargetRef: panelRef,
59
+ requestShow: () => (isOpen.value = true),
60
+ requestHide: () => (isOpen.value = false),
61
+ });
62
+
63
+ // Step 3: run close+modality handling first, then app-specific action logic
64
+ const onSelect = (event: KendoMenuSelectEvent) => {
65
+ menu.handleMenuSelect(event, (selected) => {
66
+ // Keep application-specific actions in the consuming component.
67
+ if (selected.item?.text === "Archive") archiveRecord();
68
+ });
69
+ };
70
+ </script>
57
71
  ```
58
72
 
59
73
  ## Grid Action Cell
60
74
 
61
75
  For a virtualized Kendo Grid, use `resolveFocusTarget` to locate the current row when the popup closes by keyboard. The resolver is evaluated at focus time, after the grid and popup have settled.
62
76
 
77
+ 1. Read `showMenu` from the row item and keep open/close state parent-owned.
78
+ 2. Create trigger and menu refs as component refs (`ComponentPublicInstance`) for Kendo wrappers.
79
+ 3. Call `usePopupMenu(...)` and map `requestShow`/`requestHide` to row-specific emits.
80
+ 4. Provide `resolveFocusTarget` so keyboard close restores focus to the current virtualized row.
81
+ 5. Route menu selection through `handleMenuSelect(event, onAction)` so close/focus behavior runs before row action logic.
82
+
63
83
  ```ts
64
84
  <script setup lang="ts">
65
85
  import { computed, ref, type ComponentPublicInstance } from "vue";
66
- import { usePopupMenu } from "@featherk/composables/menu";
86
+ import {
87
+ usePopupMenu,
88
+ type KendoMenuSelectEvent,
89
+ } from "@featherk/composables/menu";
67
90
 
68
91
  const props = defineProps<{ dataItem: { id: string; showMenu: boolean } }>();
69
92
  const emit = defineEmits<{
@@ -71,15 +94,21 @@ const emit = defineEmits<{
71
94
  "update:hideMenu": [id: string];
72
95
  }>();
73
96
 
97
+ // Step 1: read row-owned open state from the data item
98
+ const isOpen = computed(() => props.dataItem.showMenu);
99
+
100
+ // Step 2: Kendo refs are component instances; usePopupMenu resolves $el internally
74
101
  const triggerRef = ref<ComponentPublicInstance | null>(null);
75
102
  const menuRef = ref<ComponentPublicInstance | null>(null);
76
103
 
104
+ // Step 3: keep popup ownership in parent row state via emits
77
105
  const menu = usePopupMenu({
78
- isOpen: computed(() => props.dataItem.showMenu),
106
+ isOpen,
79
107
  triggerRef,
80
108
  menuRef,
81
109
  requestShow: () => emit("update:showMenu", props.dataItem.id),
82
110
  requestHide: () => emit("update:hideMenu", props.dataItem.id),
111
+ // Step 4: keyboard close restores focus to current virtualized grid row
83
112
  resolveFocusTarget: () => {
84
113
  const trigger = triggerRef.value?.$el as HTMLElement | undefined;
85
114
  return (
@@ -88,7 +117,8 @@ const menu = usePopupMenu({
88
117
  },
89
118
  });
90
119
 
91
- const onSelect = (event: Parameters<typeof menu.handleMenuSelect>[0]) => {
120
+ // Step 5: let usePopupMenu close first, then run row-specific business action
121
+ const onSelect = (event: KendoMenuSelectEvent) => {
92
122
  menu.handleMenuSelect(event, (selected) => {
93
123
  // Row-specific action logic remains here.
94
124
  runGridAction(props.dataItem.id, selected.item?.text);
@@ -126,8 +156,13 @@ const onSelect = (event: Parameters<typeof menu.handleMenuSelect>[0]) => {
126
156
  #### Types
127
157
 
128
158
  ```ts
129
- export type PopupMenuCloseReason = "keyboard" | "mouse" | null;
130
- export type PopupMenuCloseTrigger = "keyboard" | "mouse";
159
+ export type PopupMenuCloseReason =
160
+ | "keyboard"
161
+ | "mouse-selection"
162
+ | "outside-click"
163
+ | "trigger-click"
164
+ | null;
165
+ export type PopupMenuCloseTrigger = Exclude<PopupMenuCloseReason, null>;
131
166
  export type KendoMenuSelectEvent = {
132
167
  item?: { text?: string };
133
168
  event?: { type?: string } | null;
@@ -140,7 +175,9 @@ export type KendoMenuSelectEvent = {
140
175
  - `aria-expanded` is written to the trigger whenever it is a `<button>`, has `role="button"`, or declares `aria-haspopup`.
141
176
  - Opening focuses the first enabled Kendo Menu item after Vue renders it.
142
177
  - Outside clicks close the menu, while the trigger is ignored to prevent a close/reopen race.
143
- - Selection and Escape preserve keyboard versus mouse modality.
144
- - Mouse-driven closes restore focus to the trigger.
178
+ - Menu selection records keyboard or mouse modality from the selection event type.
179
+ - `Escape` on the trigger or inside the menu uses the keyboard close path.
145
180
  - Keyboard-driven closes focus `resolveFocusTarget()` or `focusTargetRef`, then fall back to the trigger.
181
+ - Mouse selection closes restore focus to the trigger.
182
+ - Outside-click and trigger-click closes do not force focus restoration.
146
183
  - Business-specific actions are never implemented by the composable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/composables",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "main": "dist/featherk-composables.umd.js",
5
5
  "module": "dist/featherk-composables.es.js",
6
6
  "types": "dist/index.d.ts",