@featherk/composables 0.0.6 → 0.0.8

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/README.md CHANGED
@@ -1,19 +1,23 @@
1
+ <!-- # Summary -->
1
2
 
2
- # useGridA11y — Integration Quick Reference
3
+ # `@featherk/composables`
3
4
 
4
- ## BIG DISCLAIMER
5
+ > This pacakge provides Vue 3 composables intended to augment and improve Kendo UI for Vue (Telerik) component behavior.
6
+ >
7
+ > *This package is not affiliated with or approved by Telerik.*
5
8
 
6
- > This package is experimental and NOT READY FOR PRODUCTION. Do not use this package in production environments. The API, types, and build output are unstable. Tests and documentation are incomplete. You may encounter breaking changes, missing features, or rough edges. Use only for local experimentation or as a reference.
9
+ ## useGridA11y Integration Quick Reference
7
10
 
8
- ## Compatibility
11
+ ### BIG DISCLAIMER
9
12
 
10
- > The current version of `useGridA11y` composable was developed targeting Kendo UI for Vue version 6.4.1.
11
- >
12
- > *This library is not affiliated with or approved by Telerik.*
13
+ > This package is experimental and **NOT READY FOR PRODUCTION**. Do not use this package in production environments. The API, types, and build output are unstable. Tests and documentation are incomplete. You may encounter breaking changes, missing features, or rough edges. Use only for local experimentation or as a reference.
13
14
 
14
- ## Short description
15
+ ### Compatibility
15
16
 
16
- `@featherk/composables` provides small Vue 3 composables intended to augment and improve accessibility and behavior for Kendo UI components (Telerik). The library is developed alongside a demo app that shows usage patterns and integration points.
17
+ > The current version of `useGridA11y` composable was developed targeting Kendo UI for Vue version 6.4.1.
18
+ >*Use of Kendo UI for Vue Grid requires a paid [license](https://www.telerik.com/purchase/kendo-ui) from Telerik.*
19
+ >
20
+ >See [Kendo UI for Vue Grid Documentation](https://www.telerik.com/kendo-vue-ui/components/grid)
17
21
 
18
22
  ### Notes
19
23
 
@@ -21,7 +25,13 @@
21
25
  - For row-level navigation, disable the Grid's cell-level dynamic tabindex behavior (omit or set `navigatable="false"` on the Grid).
22
26
  - The composable returns helpers for keyboard handling, focus management, and sort/filter interactions.
23
27
 
24
- ## Prerequisites
28
+ ### Styling and the `.fk-grid` class
29
+
30
+ - The composable will add the CSS class `.fk-grid` to the Grid's root element (see `setupGridStyling()` in the source). The composable itself does NOT include any CSS (no inline styles or stylesheet).
31
+ - The `.fk-grid` class is a hook used by the FeatherK stylesheet to apply visual styles. To see visual indicators (for example, the active/filtered status), you must include the appropriate FeatherK stylesheet for Kendo in your application.
32
+ - Ensure you are using the matching FeatherK styling release for correct visuals — e.g. `featherk-q3-2024-v#.css` (replace `#` with the patch version you are using).
33
+
34
+ ### Prerequisites
25
35
 
26
36
  - Vue 3 (script setup)
27
37
  - `@progress/kendo-vue-grid` installed
@@ -33,7 +43,7 @@ Install (if needed)
33
43
  npm install @featherk/composables
34
44
  ```
35
45
 
36
- ## 1) Minimal import + setup
46
+ ### 1) Minimal import + setup
37
47
 
38
48
  Place inside a `<script setup lang="ts">` block. Provide a Grid ref and call the composable.
39
49
 
@@ -43,26 +53,27 @@ import { useGridA11y } from '@featherk/composables';
43
53
 
44
54
  const gridRef = ref(null);
45
55
 
46
- const { activeFilterButton, handleGridKeyDown, handleSortChange } = useGridA11y(gridRef);
56
+ const { activeFilterButton, handleGridKeyDown, handleSortChange } =
57
+ useGridA11y(gridRef);
47
58
  ```
48
59
 
49
- ## 2) Wire keyboard handler on the Grid
60
+ ### 2) Wire keyboard handler on the Grid
50
61
 
51
- Template snippet showing essential bindings (keep other Grid props as required by your app):
62
+ Template snippet showing essential bindings (keep other `Grid` props as required by your app):
52
63
 
53
64
  ```html
54
65
  <Grid
55
66
  ref="gridRef"
56
67
  :dataItems="dataResult.data"
57
68
  :dataItemKey="'id'"
58
- :rowRender="renderRow"
59
- @keydown="handleGridKeyDown" <!-- keyboard navigation -->
60
- @sortchange="handleSortChange" <!-- composable-aware sort handling -->
61
- navigatable="false" <!-- optional: prefer row-level nav -->
69
+ :rowRender="renderRow" // optional: aria-label for screen reader
70
+ @keydown="handleGridKeyDown" // keyboard navigation
71
+ @sortchange="handleSortChange" // composable-aware sort handling
72
+ navigatable="false" // turn off cell to cell navigation
62
73
  />
63
74
  ```
64
75
 
65
- ## 3) Provide an accessible row renderer (`aria-label`)
76
+ ### 3) Provide an accessible row renderer (`aria-label`)
66
77
 
67
78
  > Not part of `@featherk/composable`, but good practice
68
79
 
@@ -77,7 +88,7 @@ const renderRow = (h: any, trElement: any, defaultSlots: any, props: any) => {
77
88
  };
78
89
  ```
79
90
 
80
- ## 4) Focus the active filter button after filter changes
91
+ ### 4) Focus the active filter button after filter changes
81
92
 
82
93
  The composable returns `activeFilterButton` (a ref) which you can focus after DOM updates.
83
94
 
@@ -95,7 +106,7 @@ function onFilterChange(event: any) {
95
106
  }
96
107
  ```
97
108
 
98
- ## 5) Custom sort handling with composable helper
109
+ ### 5) Custom sort handling with composable helper
99
110
 
100
111
  If you need to show a loader or call an API, pass a custom callback into the composable's sort helper so the composable does its internal work and your app performs side effects.
101
112
 
@@ -114,11 +125,11 @@ function onSortChange(event: any) {
114
125
  }
115
126
  ```
116
127
 
117
- ## 6) Summary checklist
128
+ ### 6) Summary checklist
118
129
 
119
130
  - Import and call `useGridA11y(gridRef)`
120
- - Bind returned keyboard handler to Grid `@keydown`
121
- - Bind returned sort handler to Grid `@sortchange` (and optionally pass a custom callback)
131
+ - Bind returned keyboard handler to `Grid` `@keydown`
132
+ - Bind returned sort handler to `Grid` `@sortchange` (and optionally pass a custom callback)
122
133
  - Use returned `activeFilterButton` to manage focus after filter updates
123
134
  - Provide a `rowRender` that adds a descriptive `aria-label` for each row
124
- - Set `navigatable="false"` on the Grid to prefer row-level navigation
135
+ - Set `navigatable="false"` on the `Grid` to prefer row-level navigation
@@ -1,8 +1,8 @@
1
1
  import { ref as E, onMounted as S, onBeforeUnmount as M, nextTick as p, onUnmounted as N } from "vue";
2
- const G = (b) => {
2
+ const G = (f) => {
3
3
  const s = E(null);
4
- let f = null, u = null;
5
- const m = [], v = ".k-table-row[data-grid-row-index] [tabindex]", k = () => b?.value?.columns, h = (t) => {
4
+ let m = null, u = null;
5
+ const b = [], y = ".k-table-row[data-grid-row-index] [tabindex]", k = () => f?.value?.columns, h = (t) => {
6
6
  const o = t.key || t.code;
7
7
  [" ", "Spacebar", "Space", "Enter"].includes(o) && (t.preventDefault(), t.stopPropagation(), s.value = t.target, t.target.click());
8
8
  }, g = (t) => {
@@ -18,52 +18,52 @@ const G = (b) => {
18
18
  ), e = document.querySelector(".k-filter-menu-container");
19
19
  if (e) {
20
20
  if (t.code === "Tab") {
21
- const n = [
21
+ const r = [
22
22
  ".k-filter-menu-container .k-dropdownlist[tabindex='0']",
23
23
  ".k-filter-menu-container input.k-input-inner:not([tabindex='-1']):not([disabled])",
24
24
  ".k-filter-menu-container button:not([tabindex='-1']):not([disabled])"
25
- ], r = Array.from(
26
- e.querySelectorAll(n.join(","))
25
+ ], n = Array.from(
26
+ e.querySelectorAll(r.join(","))
27
27
  );
28
- if (r.length === 0) return;
29
- const a = r.findIndex(
28
+ if (n.length === 0) return;
29
+ const a = n.findIndex(
30
30
  (l) => l === document.activeElement
31
31
  );
32
32
  let i;
33
- a === -1 ? i = 0 : t.shiftKey ? i = (a - 1 + r.length) % r.length : i = (a + 1) % r.length, t.preventDefault(), t.stopPropagation(), r[i]?.focus();
33
+ a === -1 ? i = 0 : t.shiftKey ? i = (a - 1 + n.length) % n.length : i = (a + 1) % n.length, t.preventDefault(), t.stopPropagation(), n[i]?.focus();
34
34
  return;
35
35
  }
36
36
  } else if (t.code === "ArrowUp" || t.code === "ArrowDown") {
37
37
  t.preventDefault(), t.stopPropagation();
38
- const n = o.findIndex(
38
+ const r = o.findIndex(
39
39
  (a) => a === document.activeElement
40
40
  );
41
- let r = n;
42
- t.code === "ArrowUp" ? r = n > 0 ? n - 1 : o.length - 1 : t.code === "ArrowDown" && (r = n < o.length - 1 ? n + 1 : 0), o[r]?.focus();
41
+ let n = r;
42
+ t.code === "ArrowUp" ? n = r > 0 ? r - 1 : o.length - 1 : t.code === "ArrowDown" && (n = r < o.length - 1 ? r + 1 : 0), o[n]?.focus();
43
43
  return;
44
44
  }
45
45
  t.code === "Tab" && (t.preventDefault(), t.stopPropagation(), t.shiftKey ? (s.value?.previousElementSibling).focus() : (s.value?.nextElementSibling).focus());
46
46
  }, x = () => {
47
- f = new MutationObserver((t) => {
47
+ m = new MutationObserver((t) => {
48
48
  t.forEach((o) => {
49
49
  o.addedNodes.forEach((e) => {
50
50
  if (e.nodeType === Node.ELEMENT_NODE) {
51
- const n = e;
52
- if (n.classList.contains("k-animation-container")) {
51
+ const r = e;
52
+ if (r.classList.contains("k-animation-container")) {
53
53
  const a = s.value;
54
54
  a && (a.dataset.featherKSortable === "true" || p(() => {
55
- n.querySelectorAll(
55
+ r.querySelectorAll(
56
56
  ".k-columnmenu-item-wrapper"
57
57
  ).forEach((d) => {
58
58
  d.textContent?.toLowerCase().includes("sort") && d.remove();
59
59
  });
60
- })), n.addEventListener(
60
+ })), r.addEventListener(
61
61
  "keydown",
62
62
  g
63
63
  ), p(() => {
64
64
  const i = () => {
65
65
  const l = Array.from(
66
- n.querySelectorAll(
66
+ r.querySelectorAll(
67
67
  ".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"
68
68
  )
69
69
  );
@@ -78,7 +78,7 @@ const G = (b) => {
78
78
  i();
79
79
  });
80
80
  }
81
- n.querySelectorAll(
81
+ r.querySelectorAll(
82
82
  ".k-animation-container"
83
83
  ).forEach((a) => {
84
84
  a.addEventListener(
@@ -89,11 +89,11 @@ const G = (b) => {
89
89
  }
90
90
  }), o.removedNodes.forEach((e) => {
91
91
  if (e.nodeType === Node.ELEMENT_NODE) {
92
- const n = e;
93
- n.classList.contains("k-animation-container") && n.removeEventListener(
92
+ const r = e;
93
+ r.classList.contains("k-animation-container") && r.removeEventListener(
94
94
  "keydown",
95
95
  g
96
- ), n.querySelectorAll(
96
+ ), r.querySelectorAll(
97
97
  ".k-animation-container"
98
98
  ).forEach((a) => {
99
99
  a.removeEventListener(
@@ -104,7 +104,7 @@ const G = (b) => {
104
104
  }
105
105
  });
106
106
  });
107
- }), f.observe(document.body, {
107
+ }), m.observe(document.body, {
108
108
  childList: !0,
109
109
  subtree: !0
110
110
  });
@@ -113,101 +113,119 @@ const G = (b) => {
113
113
  return;
114
114
  console.log("handleGridKeyDown", t, t.code);
115
115
  const o = t.target;
116
- if (o && [
117
- "ArrowDown",
118
- "ArrowLeft",
119
- "ArrowRight",
120
- "ArrowUp",
121
- "Enter",
122
- "Space"
123
- ].includes(t.code)) {
124
- if (t.preventDefault(), o.classList.contains("k-grid-header-menu") && o.classList.contains("k-grid-column-menu")) {
125
- s.value = o;
126
- return;
116
+ if (o) {
117
+ if (t.code === "Escape") {
118
+ const e = document.activeElement?.closest(".k-table-row[data-grid-row-index]");
119
+ if (e) {
120
+ t.preventDefault(), t.stopPropagation();
121
+ try {
122
+ Array.from(
123
+ f?.value.$el.querySelectorAll(
124
+ ".k-table-row[data-grid-row-index]"
125
+ )
126
+ ).forEach((n) => n.setAttribute("tabindex", "-1"));
127
+ } catch {
128
+ }
129
+ e.setAttribute("tabindex", "0"), e.focus();
130
+ return;
131
+ }
127
132
  }
128
- const e = o.closest(
129
- ".k-table-row[data-grid-row-index]"
130
- );
131
- if (e) {
132
- if (["ArrowDown", "ArrowUp"].includes(t.code)) {
133
- const n = (a, i) => {
134
- let l = i === "next" ? a.nextElementSibling : a.previousElementSibling;
135
- for (; l; ) {
136
- const d = l;
137
- try {
138
- if (d.hasAttribute && d.hasAttribute("tabindex"))
139
- return d;
140
- } catch {
141
- }
142
- l = i === "next" ? l.nextElementSibling : l.previousElementSibling;
143
- }
144
- return null;
145
- }, r = t.code === "ArrowDown" ? n(e, "next") : n(e, "previous");
146
- r && (e.setAttribute("tabindex", "-1"), r.setAttribute("tabindex", "0"), r.focus());
133
+ if ([
134
+ "ArrowDown",
135
+ "ArrowLeft",
136
+ "ArrowRight",
137
+ "ArrowUp",
138
+ "Enter",
139
+ "Space"
140
+ ].includes(t.code)) {
141
+ if (t.preventDefault(), o.classList.contains("k-grid-header-menu") && o.classList.contains("k-grid-column-menu")) {
142
+ s.value = o;
147
143
  return;
148
144
  }
149
- if (["ArrowLeft", "ArrowRight"].includes(t.code)) {
150
- t.preventDefault();
151
- const n = e.querySelectorAll(
152
- v
153
- );
154
- if (n.length === 0) return;
155
- let r = Array.from(n).findIndex(
156
- (a) => a === document.activeElement
157
- );
158
- if (r === -1 && document.activeElement === e) {
159
- n[0].focus();
145
+ const e = o.closest(
146
+ ".k-table-row[data-grid-row-index]"
147
+ );
148
+ if (e) {
149
+ if (["ArrowDown", "ArrowUp"].includes(t.code)) {
150
+ const r = (a, i) => {
151
+ let l = i === "next" ? a.nextElementSibling : a.previousElementSibling;
152
+ for (; l; ) {
153
+ const d = l;
154
+ try {
155
+ if (d.hasAttribute && d.classList.contains("k-table-row"))
156
+ return d;
157
+ } catch {
158
+ }
159
+ l = i === "next" ? l.nextElementSibling : l.previousElementSibling;
160
+ }
161
+ return null;
162
+ }, n = t.code === "ArrowDown" ? r(e, "next") : r(e, "previous");
163
+ n && (e.setAttribute("tabindex", "-1"), n.setAttribute("tabindex", "0"), n.focus());
164
+ return;
165
+ }
166
+ if (["ArrowLeft", "ArrowRight"].includes(t.code)) {
167
+ t.preventDefault();
168
+ const r = e.querySelectorAll(
169
+ y
170
+ );
171
+ if (r.length === 0) return;
172
+ let n = Array.from(r).findIndex(
173
+ (a) => a === document.activeElement
174
+ );
175
+ if (n === -1 && document.activeElement === e) {
176
+ r[0].focus();
177
+ return;
178
+ }
179
+ t.code === "ArrowRight" ? n = n === r.length - 1 ? 0 : n + 1 : t.code === "ArrowLeft" && (n = n === r.length - 1 ? n - 1 : r.length - 1), r[n].focus();
160
180
  return;
161
181
  }
162
- t.code === "ArrowRight" ? r = r === n.length - 1 ? 0 : r + 1 : t.code === "ArrowLeft" && (r = r === n.length - 1 ? r - 1 : n.length - 1), n[r].focus();
163
- return;
164
182
  }
165
183
  }
166
184
  }
167
185
  }, D = () => {
168
186
  p(() => {
169
- const t = b.value.$el.closest(".k-grid");
187
+ const t = f.value.$el.closest(".k-grid");
170
188
  t && t.classList.add("fk-grid");
171
189
  });
172
- }, T = () => {
190
+ }, q = () => {
173
191
  try {
174
192
  const t = () => {
175
193
  try {
176
- const n = Array.from(
177
- b.value.$el.querySelectorAll(
194
+ const r = Array.from(
195
+ f.value.$el.querySelectorAll(
178
196
  ".k-table-row[data-grid-row-index]"
179
197
  )
180
198
  );
181
- if (!n || n.length === 0 || n.filter(
199
+ if (!r || r.length === 0 || r.filter(
182
200
  (i) => i.getAttribute("tabindex") === "0"
183
201
  ).length === 1) return;
184
- const a = n.find(
202
+ const a = r.find(
185
203
  (i) => i === document.activeElement || i.contains(document.activeElement)
186
204
  );
187
- if (n.forEach((i) => i.setAttribute("tabindex", "-1")), a) {
205
+ if (r.forEach((i) => i.setAttribute("tabindex", "-1")), a) {
188
206
  a.setAttribute("tabindex", "0");
189
207
  return;
190
208
  }
191
- n[0].setAttribute("tabindex", "0");
192
- } catch (n) {
193
- console.error("ensureSingleTabindex error:", n);
209
+ r[0].setAttribute("tabindex", "0");
210
+ } catch (r) {
211
+ console.error("ensureSingleTabindex error:", r);
194
212
  }
195
213
  }, o = Array.from(
196
- b.value.$el.querySelectorAll(
214
+ f.value.$el.querySelectorAll(
197
215
  ".k-table-row[data-grid-row-index]"
198
216
  )
199
217
  );
200
- o.length > 0 && o.forEach((n, r) => {
201
- n.setAttribute("tabindex", r === 0 ? "0" : "-1");
218
+ o.length > 0 && o.forEach((r, n) => {
219
+ r.setAttribute("tabindex", n === 0 ? "0" : "-1");
202
220
  });
203
- const e = b.value.$el.querySelector(".k-table-tbody");
221
+ const e = f.value.$el.querySelector(".k-table-tbody");
204
222
  e && (u = new MutationObserver(() => {
205
223
  t();
206
224
  }), u.observe(e, { childList: !0, subtree: !0 }));
207
225
  } catch (t) {
208
226
  console.error("Error setting up row navigation:", t);
209
227
  }
210
- }, q = () => {
228
+ }, T = () => {
211
229
  p(() => {
212
230
  const o = document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");
213
231
  o && o.forEach((e) => {
@@ -224,36 +242,36 @@ const G = (b) => {
224
242
  "keydown",
225
243
  h
226
244
  );
227
- }), f && (f.disconnect(), f = null), u && (u.disconnect(), u = null), m.forEach((e) => e()), m.length = 0;
245
+ }), m && (m.disconnect(), m = null), u && (u.disconnect(), u = null), b.forEach((e) => e()), b.length = 0;
228
246
  }, I = () => {
229
247
  document.querySelectorAll(
230
248
  ".k-grid-header .k-table-thead th"
231
- ).forEach((e, n) => {
232
- const r = e.querySelector(
249
+ ).forEach((e, r) => {
250
+ const n = e.querySelector(
233
251
  ".k-grid-header-menu.k-grid-column-menu"
234
252
  );
235
- if (!r) return;
253
+ if (!n) return;
236
254
  const a = k();
237
- if (a && a[n]) {
238
- const c = a[n].field ?? "";
255
+ if (a && a[r]) {
256
+ const c = a[r].field ?? "";
239
257
  e.setAttribute("data-feather-k-field", c), e.setAttribute(
240
258
  "data-feather-k-filterable",
241
- a[n].filterable === !1 ? "false" : "true"
259
+ a[r].filterable === !1 ? "false" : "true"
242
260
  ), e.setAttribute(
243
261
  "data-feather-k-sortable",
244
- a[n].sortable === !1 ? "false" : "true"
262
+ a[r].sortable === !1 ? "false" : "true"
245
263
  );
246
264
  }
247
265
  const i = e.dataset.featherKFilterable !== "false", l = e.dataset.featherKSortable !== "false";
248
- r.setAttribute("tabindex", "-1"), i ? (e.setAttribute("tabindex", "0"), e.setAttribute("role", "button"), e.setAttribute("aria-haspopup", "menu"), e.style.cursor = "pointer") : (e.setAttribute("tabindex", "0"), e.setAttribute("role", "columnheader"), e.removeAttribute("aria-haspopup"), e.style.cursor = "default");
266
+ n.setAttribute("tabindex", "-1"), i || l ? e.style.cursor = "pointer" : e.style.cursor = "default", i ? (e.setAttribute("tabindex", "0"), e.setAttribute("role", "button"), e.setAttribute("aria-haspopup", "menu")) : (e.setAttribute("tabindex", "0"), e.setAttribute("role", "columnheader"), e.removeAttribute("aria-haspopup"));
249
267
  const d = (c) => {
250
- c.target?.closest(".k-column-resizer") || (s.value = e, r.click());
251
- }, w = (c) => {
268
+ c.target?.closest(".k-column-resizer") || (s.value = e, n.click());
269
+ }, v = (c) => {
252
270
  if (i)
253
271
  s.value = e, c.preventDefault(), c.stopPropagation(), d(c);
254
272
  else if (l) {
255
273
  s.value = e;
256
- const y = new KeyboardEvent("keydown", {
274
+ const w = new KeyboardEvent("keydown", {
257
275
  key: "Enter",
258
276
  code: "Enter",
259
277
  keyCode: 13,
@@ -261,38 +279,38 @@ const G = (b) => {
261
279
  bubbles: !0,
262
280
  cancelable: !0
263
281
  });
264
- e.dispatchEvent(y);
282
+ e.dispatchEvent(w);
265
283
  }
266
284
  };
267
- e.addEventListener("click", w), m.push(() => {
268
- e.removeEventListener("click", w);
285
+ e.addEventListener("click", v), b.push(() => {
286
+ e.removeEventListener("click", v);
269
287
  });
270
288
  const A = (c) => {
271
289
  if ((c.code === "Enter" || c.code === "Space") && (i || l)) {
272
290
  if (s.value = e, s.value.focus(), i)
273
291
  c.preventDefault(), c.stopPropagation(), d(c);
274
292
  else if (l) {
275
- const y = e.querySelector(".k-link");
276
- y && y.click();
293
+ const w = e.querySelector(".k-link");
294
+ w && w.click();
277
295
  }
278
296
  }
279
297
  };
280
- e.addEventListener("keydown", A, !0), m.push(() => {
298
+ e.addEventListener("keydown", A, !0), b.push(() => {
281
299
  e.removeEventListener("keydown", A, !0);
282
300
  });
283
301
  });
284
302
  const o = document.querySelector(".k-grid-header .k-table-thead");
285
303
  if (o) {
286
- const e = (n) => {
287
- const r = n.target.closest("th");
288
- r && (n.code === "Enter" || n.code === "Space") && r.dataset.featherKFilterable === "false" && r.dataset.featherKSortable === "false" && (n.preventDefault(), n.stopImmediatePropagation());
304
+ const e = (r) => {
305
+ const n = r.target.closest("th");
306
+ n && (r.code === "Enter" || r.code === "Space") && n.dataset.featherKFilterable === "false" && n.dataset.featherKSortable === "false" && (r.preventDefault(), r.stopImmediatePropagation());
289
307
  };
290
308
  o.addEventListener(
291
309
  "keydown",
292
310
  e,
293
311
  !0
294
312
  // NOTE: capture phase
295
- ), m.push(() => {
313
+ ), b.push(() => {
296
314
  o.removeEventListener(
297
315
  "keydown",
298
316
  e,
@@ -301,10 +319,10 @@ const G = (b) => {
301
319
  });
302
320
  }
303
321
  }, K = function(t, o) {
304
- const e = t?.event.event.target, n = k();
305
- if (!e || !n) return;
306
- const r = e.classList.contains("k-link"), a = e.classList.contains("k-columnmenu-item"), i = n.find((l) => l.field === t.event.field)?.sortable && !0;
307
- if (!r) {
322
+ const e = t?.event.event.target, r = k();
323
+ if (!e || !r) return;
324
+ const n = e.classList.contains("k-link"), a = e.classList.contains("k-columnmenu-item"), i = r.find((l) => l.field === t.event.field)?.sortable && !0;
325
+ if (!n) {
308
326
  if (a && !i) {
309
327
  (t.event.sort && void 0)?.filter(
310
328
  (d) => d.field !== t.event.field
@@ -317,7 +335,7 @@ const G = (b) => {
317
335
  }
318
336
  };
319
337
  return S(() => {
320
- D(), T(), q();
338
+ D(), q(), T();
321
339
  }), M(() => {
322
340
  C();
323
341
  }), {
@@ -326,30 +344,30 @@ const G = (b) => {
326
344
  handleSortChange: K
327
345
  };
328
346
  };
329
- function B(b = {}) {
330
- const { activeByDefault: s = !1, autoActivateDelay: f = 0 } = b, u = E(s);
331
- function m() {
347
+ function P(f = {}) {
348
+ const { activeByDefault: s = !1, autoActivateDelay: m = 0 } = f, u = E(s);
349
+ function b() {
332
350
  u.value = !0;
333
351
  }
334
- function v() {
352
+ function y() {
335
353
  u.value = !1;
336
354
  }
337
355
  function k() {
338
356
  u.value = !u.value;
339
357
  }
340
358
  return S(() => {
341
- f > 0 && !s && setTimeout(() => {
342
- m();
343
- }, f);
359
+ m > 0 && !s && setTimeout(() => {
360
+ b();
361
+ }, m);
344
362
  }), N(() => {
345
363
  }), {
346
364
  isGridActive: u,
347
- activateGrid: m,
348
- deactivateGrid: v,
365
+ activateGrid: b,
366
+ deactivateGrid: y,
349
367
  toggleGrid: k
350
368
  };
351
369
  }
352
370
  export {
353
371
  G as useGridA11y,
354
- B as useGridComposableEx
372
+ P as useGridComposableEx
355
373
  };
@@ -1 +1 @@
1
- (function(m,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],c):(m=typeof globalThis<"u"?globalThis:m||self,c(m.FeatherKComposables={},m.Vue))})(this,(function(m,c){"use strict";const S=k=>{const s=c.ref(null);let b=null,d=null;const p=[],w=".k-table-row[data-grid-row-index] [tabindex]",g=()=>k?.value?.columns,v=t=>{const o=t.key||t.code;[" ","Spacebar","Space","Enter"].includes(o)&&(t.preventDefault(),t.stopPropagation(),s.value=t.target,t.target.click())},y=t=>{if(!s.value)return;if(t.code==="Escape"){t.preventDefault(),t.stopPropagation(),s.value&&s.value.focus();return}const o=Array.from(document.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item")),e=document.querySelector(".k-filter-menu-container");if(e){if(t.code==="Tab"){const n=[".k-filter-menu-container .k-dropdownlist[tabindex='0']",".k-filter-menu-container input.k-input-inner:not([tabindex='-1']):not([disabled])",".k-filter-menu-container button:not([tabindex='-1']):not([disabled])"],r=Array.from(e.querySelectorAll(n.join(",")));if(r.length===0)return;const a=r.findIndex(l=>l===document.activeElement);let i;a===-1?i=0:t.shiftKey?i=(a-1+r.length)%r.length:i=(a+1)%r.length,t.preventDefault(),t.stopPropagation(),r[i]?.focus();return}}else if(t.code==="ArrowUp"||t.code==="ArrowDown"){t.preventDefault(),t.stopPropagation();const n=o.findIndex(a=>a===document.activeElement);let r=n;t.code==="ArrowUp"?r=n>0?n-1:o.length-1:t.code==="ArrowDown"&&(r=n<o.length-1?n+1:0),o[r]?.focus();return}t.code==="Tab"&&(t.preventDefault(),t.stopPropagation(),t.shiftKey?(s.value?.previousElementSibling).focus():(s.value?.nextElementSibling).focus())},L=()=>{b=new MutationObserver(t=>{t.forEach(o=>{o.addedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const n=e;if(n.classList.contains("k-animation-container")){const a=s.value;a&&(a.dataset.featherKSortable==="true"||c.nextTick(()=>{n.querySelectorAll(".k-columnmenu-item-wrapper").forEach(f=>{f.textContent?.toLowerCase().includes("sort")&&f.remove()})})),n.addEventListener("keydown",y),c.nextTick(()=>{const i=()=>{const l=Array.from(n.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"));if(l.length===1)l[0].focus(),l[0].click(),i.attempts=0;else if(l.length>1){i.attempts=0;return}else i.attempts===void 0&&(i.attempts=0),i.attempts++<3&&setTimeout(i,200)};i()})}n.querySelectorAll(".k-animation-container").forEach(a=>{a.addEventListener("keydown",y)})}}),o.removedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const n=e;n.classList.contains("k-animation-container")&&n.removeEventListener("keydown",y),n.querySelectorAll(".k-animation-container").forEach(a=>{a.removeEventListener("keydown",y)})}})})}),b.observe(document.body,{childList:!0,subtree:!0})},T=t=>{if(!t.type||!t)return;console.log("handleGridKeyDown",t,t.code);const o=t.target;if(o&&["ArrowDown","ArrowLeft","ArrowRight","ArrowUp","Enter","Space"].includes(t.code)){if(t.preventDefault(),o.classList.contains("k-grid-header-menu")&&o.classList.contains("k-grid-column-menu")){s.value=o;return}const e=o.closest(".k-table-row[data-grid-row-index]");if(e){if(["ArrowDown","ArrowUp"].includes(t.code)){const n=(a,i)=>{let l=i==="next"?a.nextElementSibling:a.previousElementSibling;for(;l;){const f=l;try{if(f.hasAttribute&&f.hasAttribute("tabindex"))return f}catch{}l=i==="next"?l.nextElementSibling:l.previousElementSibling}return null},r=t.code==="ArrowDown"?n(e,"next"):n(e,"previous");r&&(e.setAttribute("tabindex","-1"),r.setAttribute("tabindex","0"),r.focus());return}if(["ArrowLeft","ArrowRight"].includes(t.code)){t.preventDefault();const n=e.querySelectorAll(w);if(n.length===0)return;let r=Array.from(n).findIndex(a=>a===document.activeElement);if(r===-1&&document.activeElement===e){n[0].focus();return}t.code==="ArrowRight"?r=r===n.length-1?0:r+1:t.code==="ArrowLeft"&&(r=r===n.length-1?r-1:n.length-1),n[r].focus();return}}}},D=()=>{c.nextTick(()=>{const t=k.value.$el.closest(".k-grid");t&&t.classList.add("fk-grid")})},q=()=>{try{const t=()=>{try{const n=Array.from(k.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));if(!n||n.length===0||n.filter(i=>i.getAttribute("tabindex")==="0").length===1)return;const a=n.find(i=>i===document.activeElement||i.contains(document.activeElement));if(n.forEach(i=>i.setAttribute("tabindex","-1")),a){a.setAttribute("tabindex","0");return}n[0].setAttribute("tabindex","0")}catch(n){console.error("ensureSingleTabindex error:",n)}},o=Array.from(k.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));o.length>0&&o.forEach((n,r)=>{n.setAttribute("tabindex",r===0?"0":"-1")});const e=k.value.$el.querySelector(".k-table-tbody");e&&(d=new MutationObserver(()=>{t()}),d.observe(e,{childList:!0,subtree:!0}))}catch(t){console.error("Error setting up row navigation:",t)}},C=()=>{c.nextTick(()=>{const o=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");o&&o.forEach(e=>{e.setAttribute("role","button"),e.addEventListener("keydown",v)}),L(),K()})},I=()=>{const o=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");o&&o.forEach(e=>{e.removeEventListener("keydown",v)}),b&&(b.disconnect(),b=null),d&&(d.disconnect(),d=null),p.forEach(e=>e()),p.length=0},K=()=>{document.querySelectorAll(".k-grid-header .k-table-thead th").forEach((e,n)=>{const r=e.querySelector(".k-grid-header-menu.k-grid-column-menu");if(!r)return;const a=g();if(a&&a[n]){const u=a[n].field??"";e.setAttribute("data-feather-k-field",u),e.setAttribute("data-feather-k-filterable",a[n].filterable===!1?"false":"true"),e.setAttribute("data-feather-k-sortable",a[n].sortable===!1?"false":"true")}const i=e.dataset.featherKFilterable!=="false",l=e.dataset.featherKSortable!=="false";r.setAttribute("tabindex","-1"),i?(e.setAttribute("tabindex","0"),e.setAttribute("role","button"),e.setAttribute("aria-haspopup","menu"),e.style.cursor="pointer"):(e.setAttribute("tabindex","0"),e.setAttribute("role","columnheader"),e.removeAttribute("aria-haspopup"),e.style.cursor="default");const f=u=>{u.target?.closest(".k-column-resizer")||(s.value=e,r.click())},A=u=>{if(i)s.value=e,u.preventDefault(),u.stopPropagation(),f(u);else if(l){s.value=e;const h=new KeyboardEvent("keydown",{key:"Enter",code:"Enter",keyCode:13,which:13,bubbles:!0,cancelable:!0});e.dispatchEvent(h)}};e.addEventListener("click",A),p.push(()=>{e.removeEventListener("click",A)});const E=u=>{if((u.code==="Enter"||u.code==="Space")&&(i||l)){if(s.value=e,s.value.focus(),i)u.preventDefault(),u.stopPropagation(),f(u);else if(l){const h=e.querySelector(".k-link");h&&h.click()}}};e.addEventListener("keydown",E,!0),p.push(()=>{e.removeEventListener("keydown",E,!0)})});const o=document.querySelector(".k-grid-header .k-table-thead");if(o){const e=n=>{const r=n.target.closest("th");r&&(n.code==="Enter"||n.code==="Space")&&r.dataset.featherKFilterable==="false"&&r.dataset.featherKSortable==="false"&&(n.preventDefault(),n.stopImmediatePropagation())};o.addEventListener("keydown",e,!0),p.push(()=>{o.removeEventListener("keydown",e,!0)})}},M=function(t,o){const e=t?.event.event.target,n=g();if(!e||!n)return;const r=e.classList.contains("k-link"),a=e.classList.contains("k-columnmenu-item"),i=n.find(l=>l.field===t.event.field)?.sortable&&!0;if(!r){if(a&&!i){(t.event.sort&&void 0)?.filter(f=>f.field!==t.event.field);return}typeof o=="function"&&c.nextTick(()=>{s.value&&s.value.focus(),o(t)})}};return c.onMounted(()=>{D(),q(),C()}),c.onBeforeUnmount(()=>{I()}),{activeFilterButton:s,handleGridKeyDown:T,handleSortChange:M}};function x(k={}){const{activeByDefault:s=!1,autoActivateDelay:b=0}=k,d=c.ref(s);function p(){d.value=!0}function w(){d.value=!1}function g(){d.value=!d.value}return c.onMounted(()=>{b>0&&!s&&setTimeout(()=>{p()},b)}),c.onUnmounted(()=>{}),{isGridActive:d,activateGrid:p,deactivateGrid:w,toggleGrid:g}}m.useGridA11y=S,m.useGridComposableEx=x,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(m,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],c):(m=typeof globalThis<"u"?globalThis:m||self,c(m.FeatherKComposables={},m.Vue))})(this,(function(m,c){"use strict";const S=b=>{const s=c.ref(null);let p=null,d=null;const k=[],h=".k-table-row[data-grid-row-index] [tabindex]",g=()=>b?.value?.columns,v=t=>{const o=t.key||t.code;[" ","Spacebar","Space","Enter"].includes(o)&&(t.preventDefault(),t.stopPropagation(),s.value=t.target,t.target.click())},y=t=>{if(!s.value)return;if(t.code==="Escape"){t.preventDefault(),t.stopPropagation(),s.value&&s.value.focus();return}const o=Array.from(document.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item")),e=document.querySelector(".k-filter-menu-container");if(e){if(t.code==="Tab"){const n=[".k-filter-menu-container .k-dropdownlist[tabindex='0']",".k-filter-menu-container input.k-input-inner:not([tabindex='-1']):not([disabled])",".k-filter-menu-container button:not([tabindex='-1']):not([disabled])"],r=Array.from(e.querySelectorAll(n.join(",")));if(r.length===0)return;const a=r.findIndex(l=>l===document.activeElement);let i;a===-1?i=0:t.shiftKey?i=(a-1+r.length)%r.length:i=(a+1)%r.length,t.preventDefault(),t.stopPropagation(),r[i]?.focus();return}}else if(t.code==="ArrowUp"||t.code==="ArrowDown"){t.preventDefault(),t.stopPropagation();const n=o.findIndex(a=>a===document.activeElement);let r=n;t.code==="ArrowUp"?r=n>0?n-1:o.length-1:t.code==="ArrowDown"&&(r=n<o.length-1?n+1:0),o[r]?.focus();return}t.code==="Tab"&&(t.preventDefault(),t.stopPropagation(),t.shiftKey?(s.value?.previousElementSibling).focus():(s.value?.nextElementSibling).focus())},L=()=>{p=new MutationObserver(t=>{t.forEach(o=>{o.addedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const n=e;if(n.classList.contains("k-animation-container")){const a=s.value;a&&(a.dataset.featherKSortable==="true"||c.nextTick(()=>{n.querySelectorAll(".k-columnmenu-item-wrapper").forEach(f=>{f.textContent?.toLowerCase().includes("sort")&&f.remove()})})),n.addEventListener("keydown",y),c.nextTick(()=>{const i=()=>{const l=Array.from(n.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"));if(l.length===1)l[0].focus(),l[0].click(),i.attempts=0;else if(l.length>1){i.attempts=0;return}else i.attempts===void 0&&(i.attempts=0),i.attempts++<3&&setTimeout(i,200)};i()})}n.querySelectorAll(".k-animation-container").forEach(a=>{a.addEventListener("keydown",y)})}}),o.removedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const n=e;n.classList.contains("k-animation-container")&&n.removeEventListener("keydown",y),n.querySelectorAll(".k-animation-container").forEach(a=>{a.removeEventListener("keydown",y)})}})})}),p.observe(document.body,{childList:!0,subtree:!0})},T=t=>{if(!t.type||!t)return;console.log("handleGridKeyDown",t,t.code);const o=t.target;if(o){if(t.code==="Escape"){const e=document.activeElement?.closest(".k-table-row[data-grid-row-index]");if(e){t.preventDefault(),t.stopPropagation();try{Array.from(b?.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]")).forEach(r=>r.setAttribute("tabindex","-1"))}catch{}e.setAttribute("tabindex","0"),e.focus();return}}if(["ArrowDown","ArrowLeft","ArrowRight","ArrowUp","Enter","Space"].includes(t.code)){if(t.preventDefault(),o.classList.contains("k-grid-header-menu")&&o.classList.contains("k-grid-column-menu")){s.value=o;return}const e=o.closest(".k-table-row[data-grid-row-index]");if(e){if(["ArrowDown","ArrowUp"].includes(t.code)){const n=(a,i)=>{let l=i==="next"?a.nextElementSibling:a.previousElementSibling;for(;l;){const f=l;try{if(f.hasAttribute&&f.classList.contains("k-table-row"))return f}catch{}l=i==="next"?l.nextElementSibling:l.previousElementSibling}return null},r=t.code==="ArrowDown"?n(e,"next"):n(e,"previous");r&&(e.setAttribute("tabindex","-1"),r.setAttribute("tabindex","0"),r.focus());return}if(["ArrowLeft","ArrowRight"].includes(t.code)){t.preventDefault();const n=e.querySelectorAll(h);if(n.length===0)return;let r=Array.from(n).findIndex(a=>a===document.activeElement);if(r===-1&&document.activeElement===e){n[0].focus();return}t.code==="ArrowRight"?r=r===n.length-1?0:r+1:t.code==="ArrowLeft"&&(r=r===n.length-1?r-1:n.length-1),n[r].focus();return}}}}},D=()=>{c.nextTick(()=>{const t=b.value.$el.closest(".k-grid");t&&t.classList.add("fk-grid")})},q=()=>{try{const t=()=>{try{const n=Array.from(b.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));if(!n||n.length===0||n.filter(i=>i.getAttribute("tabindex")==="0").length===1)return;const a=n.find(i=>i===document.activeElement||i.contains(document.activeElement));if(n.forEach(i=>i.setAttribute("tabindex","-1")),a){a.setAttribute("tabindex","0");return}n[0].setAttribute("tabindex","0")}catch(n){console.error("ensureSingleTabindex error:",n)}},o=Array.from(b.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));o.length>0&&o.forEach((n,r)=>{n.setAttribute("tabindex",r===0?"0":"-1")});const e=b.value.$el.querySelector(".k-table-tbody");e&&(d=new MutationObserver(()=>{t()}),d.observe(e,{childList:!0,subtree:!0}))}catch(t){console.error("Error setting up row navigation:",t)}},C=()=>{c.nextTick(()=>{const o=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");o&&o.forEach(e=>{e.setAttribute("role","button"),e.addEventListener("keydown",v)}),L(),K()})},I=()=>{const o=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");o&&o.forEach(e=>{e.removeEventListener("keydown",v)}),p&&(p.disconnect(),p=null),d&&(d.disconnect(),d=null),k.forEach(e=>e()),k.length=0},K=()=>{document.querySelectorAll(".k-grid-header .k-table-thead th").forEach((e,n)=>{const r=e.querySelector(".k-grid-header-menu.k-grid-column-menu");if(!r)return;const a=g();if(a&&a[n]){const u=a[n].field??"";e.setAttribute("data-feather-k-field",u),e.setAttribute("data-feather-k-filterable",a[n].filterable===!1?"false":"true"),e.setAttribute("data-feather-k-sortable",a[n].sortable===!1?"false":"true")}const i=e.dataset.featherKFilterable!=="false",l=e.dataset.featherKSortable!=="false";r.setAttribute("tabindex","-1"),i||l?e.style.cursor="pointer":e.style.cursor="default",i?(e.setAttribute("tabindex","0"),e.setAttribute("role","button"),e.setAttribute("aria-haspopup","menu")):(e.setAttribute("tabindex","0"),e.setAttribute("role","columnheader"),e.removeAttribute("aria-haspopup"));const f=u=>{u.target?.closest(".k-column-resizer")||(s.value=e,r.click())},A=u=>{if(i)s.value=e,u.preventDefault(),u.stopPropagation(),f(u);else if(l){s.value=e;const w=new KeyboardEvent("keydown",{key:"Enter",code:"Enter",keyCode:13,which:13,bubbles:!0,cancelable:!0});e.dispatchEvent(w)}};e.addEventListener("click",A),k.push(()=>{e.removeEventListener("click",A)});const E=u=>{if((u.code==="Enter"||u.code==="Space")&&(i||l)){if(s.value=e,s.value.focus(),i)u.preventDefault(),u.stopPropagation(),f(u);else if(l){const w=e.querySelector(".k-link");w&&w.click()}}};e.addEventListener("keydown",E,!0),k.push(()=>{e.removeEventListener("keydown",E,!0)})});const o=document.querySelector(".k-grid-header .k-table-thead");if(o){const e=n=>{const r=n.target.closest("th");r&&(n.code==="Enter"||n.code==="Space")&&r.dataset.featherKFilterable==="false"&&r.dataset.featherKSortable==="false"&&(n.preventDefault(),n.stopImmediatePropagation())};o.addEventListener("keydown",e,!0),k.push(()=>{o.removeEventListener("keydown",e,!0)})}},M=function(t,o){const e=t?.event.event.target,n=g();if(!e||!n)return;const r=e.classList.contains("k-link"),a=e.classList.contains("k-columnmenu-item"),i=n.find(l=>l.field===t.event.field)?.sortable&&!0;if(!r){if(a&&!i){(t.event.sort&&void 0)?.filter(f=>f.field!==t.event.field);return}typeof o=="function"&&c.nextTick(()=>{s.value&&s.value.focus(),o(t)})}};return c.onMounted(()=>{D(),q(),C()}),c.onBeforeUnmount(()=>{I()}),{activeFilterButton:s,handleGridKeyDown:T,handleSortChange:M}};function x(b={}){const{activeByDefault:s=!1,autoActivateDelay:p=0}=b,d=c.ref(s);function k(){d.value=!0}function h(){d.value=!1}function g(){d.value=!d.value}return c.onMounted(()=>{p>0&&!s&&setTimeout(()=>{k()},p)}),c.onUnmounted(()=>{}),{isGridActive:d,activateGrid:k,deactivateGrid:h,toggleGrid:g}}m.useGridA11y=S,m.useGridComposableEx=x,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
@@ -3,9 +3,6 @@
3
3
  *
4
4
  * Composable that augments a Kendo-based grid with accessible keyboard navigation,
5
5
  * focus management, and custom behaviors for column header menus (filter/sort).
6
- *
7
- * (Contents preserved from previous grid-keyboard-navigation.ts; only the export name
8
- * and filename were changed to reflect the new composable name.)
9
6
  */
10
7
  import { type Ref } from "vue";
11
8
  import type { GridSortChangeEvent } from "./types/kendo";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/composables",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "main": "dist/featherk-composables.umd.js",
5
5
  "module": "dist/featherk-composables.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,4 +29,4 @@
29
29
  "access": "public"
30
30
  },
31
31
  "license": "MIT"
32
- }
32
+ }