@featherk/composables 0.0.5 → 0.0.7
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 +104 -44
- package/dist/featherk-composables.es.js +166 -136
- package/dist/featherk-composables.umd.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/types/kendo.d.ts +1 -1
- package/dist/useGridA11y.d.ts +0 -3
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -1,70 +1,130 @@
|
|
|
1
|
-
# @featherk/composables — Summary
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
# useGridA11y — Integration Quick Reference
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
## BIG DISCLAIMER
|
|
6
5
|
|
|
7
|
-
|
|
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.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
## Compatibility
|
|
10
9
|
|
|
11
|
-
|
|
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.*
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
- TypeScript (partial coverage; some types may be missing or incomplete)
|
|
15
|
-
- Designed to be used inside a monorepo (Turborepo) with a demo app at `demos/` and package sources in `packages/composables/src/`
|
|
14
|
+
## Short description
|
|
16
15
|
|
|
17
|
-
|
|
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.
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
- Current stability/status and the critical disclaimer
|
|
21
|
-
- Quick local build / demo steps to try the code (for contributors)
|
|
22
|
-
- Where to look in the repo for implementation and exports
|
|
18
|
+
### Notes
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
- The composable expects a Grid ref (a Vue ref to the Grid component).
|
|
21
|
+
- For row-level navigation, disable the Grid's cell-level dynamic tabindex behavior (omit or set `navigatable="false"` on the Grid).
|
|
22
|
+
- The composable returns helpers for keyboard handling, focus management, and sort/filter interactions.
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
### Styling and the .fk-grid class
|
|
27
25
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- Documentation: Sample usage is mainly in the demo app; inline documentation is minimal.
|
|
32
|
-
- Compatibility: Not officially supported by or affiliated with Telerik.
|
|
26
|
+
- 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).
|
|
27
|
+
- 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.
|
|
28
|
+
- 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
29
|
|
|
34
|
-
|
|
30
|
+
## Prerequisites
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
- Vue 3 (script setup)
|
|
33
|
+
- `@progress/kendo-vue-grid` installed
|
|
34
|
+
- `@featherk/composables` installed
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
Install (if needed)
|
|
39
37
|
|
|
40
38
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
npm install @featherk/composables
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 1) Minimal import + setup
|
|
43
|
+
|
|
44
|
+
Place inside a `<script setup lang="ts">` block. Provide a Grid ref and call the composable.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { ref } from 'vue';
|
|
48
|
+
import { useGridA11y } from '@featherk/composables';
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
const gridRef = ref(null);
|
|
51
|
+
|
|
52
|
+
const { activeFilterButton, handleGridKeyDown, handleSortChange } = useGridA11y(gridRef);
|
|
46
53
|
```
|
|
47
54
|
|
|
48
|
-
##
|
|
55
|
+
## 2) Wire keyboard handler on the Grid
|
|
56
|
+
|
|
57
|
+
Template snippet showing essential bindings (keep other Grid props as required by your app):
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
```html
|
|
60
|
+
<Grid
|
|
61
|
+
ref="gridRef"
|
|
62
|
+
:dataItems="dataResult.data"
|
|
63
|
+
:dataItemKey="'id'"
|
|
64
|
+
:rowRender="renderRow"
|
|
65
|
+
@keydown="handleGridKeyDown" <!-- keyboard navigation -->
|
|
66
|
+
@sortchange="handleSortChange" <!-- composable-aware sort handling -->
|
|
67
|
+
navigatable="false" <!-- optional: prefer row-level nav -->
|
|
68
|
+
/>
|
|
69
|
+
```
|
|
54
70
|
|
|
55
|
-
##
|
|
71
|
+
## 3) Provide an accessible row renderer (`aria-label`)
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
- Keep changes small and documented. Add tests for new behavior where reasonable.
|
|
59
|
-
- Build the composables package before running the demo to ensure the demo picks up local changes.
|
|
73
|
+
> Not part of `@featherk/composable`, but good practice
|
|
60
74
|
|
|
61
|
-
|
|
75
|
+
Kendo Grid `rowRender` allows you to add an `aria-label` so screen readers announce row contents.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const renderRow = (h: any, trElement: any, defaultSlots: any, props: any) => {
|
|
79
|
+
const ariaLabel = `Name: ${props.dataItem.name}, Price: ${props.dataItem.price}`;
|
|
80
|
+
// merge existing props and add aria-label
|
|
81
|
+
const merged = { ...trElement.props, 'aria-label': ariaLabel };
|
|
82
|
+
return h('tr', merged, defaultSlots);
|
|
83
|
+
};
|
|
84
|
+
```
|
|
62
85
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
86
|
+
## 4) Focus the active filter button after filter changes
|
|
87
|
+
|
|
88
|
+
The composable returns `activeFilterButton` (a ref) which you can focus after DOM updates.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { nextTick } from 'vue';
|
|
92
|
+
|
|
93
|
+
function onFilterChange(event: any) {
|
|
94
|
+
// update your filter state and data here
|
|
95
|
+
|
|
96
|
+
nextTick(() => {
|
|
97
|
+
if (activeFilterButton.value) {
|
|
98
|
+
activeFilterButton.value.focus();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 5) Custom sort handling with composable helper
|
|
105
|
+
|
|
106
|
+
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.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const optionalCustomSort = (event: any) => {
|
|
110
|
+
loader.value = true;
|
|
111
|
+
// example async
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
loader.value = false;
|
|
114
|
+
// apply sort state and reload data
|
|
115
|
+
}, 200);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
function onSortChange(event: any) {
|
|
119
|
+
handleSortChange(event, optionalCustomSort);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
67
122
|
|
|
68
|
-
##
|
|
123
|
+
## 6) Summary checklist
|
|
69
124
|
|
|
70
|
-
|
|
125
|
+
- Import and call `useGridA11y(gridRef)`
|
|
126
|
+
- Bind returned keyboard handler to Grid `@keydown`
|
|
127
|
+
- Bind returned sort handler to Grid `@sortchange` (and optionally pass a custom callback)
|
|
128
|
+
- Use returned `activeFilterButton` to manage focus after filter updates
|
|
129
|
+
- Provide a `rowRender` that adds a descriptive `aria-label` for each row
|
|
130
|
+
- Set `navigatable="false"` on the Grid to prefer row-level navigation
|
|
@@ -1,75 +1,75 @@
|
|
|
1
1
|
import { ref as E, onMounted as S, onBeforeUnmount as M, nextTick as p, onUnmounted as N } from "vue";
|
|
2
|
-
const G = (
|
|
3
|
-
const
|
|
4
|
-
let
|
|
5
|
-
const
|
|
6
|
-
const o =
|
|
7
|
-
[" ", "Spacebar", "Space", "Enter"].includes(o) && (
|
|
8
|
-
}, g = (
|
|
9
|
-
if (!
|
|
10
|
-
if (
|
|
11
|
-
|
|
2
|
+
const G = (f) => {
|
|
3
|
+
const s = E(null);
|
|
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
|
+
const o = t.key || t.code;
|
|
7
|
+
[" ", "Spacebar", "Space", "Enter"].includes(o) && (t.preventDefault(), t.stopPropagation(), s.value = t.target, t.target.click());
|
|
8
|
+
}, g = (t) => {
|
|
9
|
+
if (!s.value) return;
|
|
10
|
+
if (t.code === "Escape") {
|
|
11
|
+
t.preventDefault(), t.stopPropagation(), s.value && s.value.focus();
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
const o = Array.from(
|
|
15
15
|
document.querySelectorAll(
|
|
16
16
|
".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"
|
|
17
17
|
)
|
|
18
|
-
),
|
|
19
|
-
if (
|
|
20
|
-
if (
|
|
18
|
+
), e = document.querySelector(".k-filter-menu-container");
|
|
19
|
+
if (e) {
|
|
20
|
+
if (t.code === "Tab") {
|
|
21
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
25
|
], n = Array.from(
|
|
26
|
-
|
|
26
|
+
e.querySelectorAll(r.join(","))
|
|
27
27
|
);
|
|
28
28
|
if (n.length === 0) return;
|
|
29
29
|
const a = n.findIndex(
|
|
30
|
-
(
|
|
30
|
+
(l) => l === document.activeElement
|
|
31
31
|
);
|
|
32
32
|
let i;
|
|
33
|
-
a === -1 ? i = 0 :
|
|
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
|
-
} else if (
|
|
37
|
-
|
|
36
|
+
} else if (t.code === "ArrowUp" || t.code === "ArrowDown") {
|
|
37
|
+
t.preventDefault(), t.stopPropagation();
|
|
38
38
|
const r = o.findIndex(
|
|
39
39
|
(a) => a === document.activeElement
|
|
40
40
|
);
|
|
41
41
|
let n = r;
|
|
42
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
o.addedNodes.forEach((
|
|
50
|
-
if (
|
|
51
|
-
const r =
|
|
47
|
+
m = new MutationObserver((t) => {
|
|
48
|
+
t.forEach((o) => {
|
|
49
|
+
o.addedNodes.forEach((e) => {
|
|
50
|
+
if (e.nodeType === Node.ELEMENT_NODE) {
|
|
51
|
+
const r = e;
|
|
52
52
|
if (r.classList.contains("k-animation-container")) {
|
|
53
|
-
const a =
|
|
53
|
+
const a = s.value;
|
|
54
54
|
a && (a.dataset.featherKSortable === "true" || p(() => {
|
|
55
55
|
r.querySelectorAll(
|
|
56
56
|
".k-columnmenu-item-wrapper"
|
|
57
|
-
).forEach((
|
|
58
|
-
|
|
57
|
+
).forEach((d) => {
|
|
58
|
+
d.textContent?.toLowerCase().includes("sort") && d.remove();
|
|
59
59
|
});
|
|
60
60
|
})), r.addEventListener(
|
|
61
61
|
"keydown",
|
|
62
62
|
g
|
|
63
63
|
), p(() => {
|
|
64
64
|
const i = () => {
|
|
65
|
-
const
|
|
65
|
+
const l = Array.from(
|
|
66
66
|
r.querySelectorAll(
|
|
67
67
|
".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"
|
|
68
68
|
)
|
|
69
69
|
);
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
else if (
|
|
70
|
+
if (l.length === 1)
|
|
71
|
+
l[0].focus(), l[0].click(), i.attempts = 0;
|
|
72
|
+
else if (l.length > 1) {
|
|
73
73
|
i.attempts = 0;
|
|
74
74
|
return;
|
|
75
75
|
} else
|
|
@@ -87,9 +87,9 @@ const G = (m) => {
|
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
-
}), o.removedNodes.forEach((
|
|
91
|
-
if (
|
|
92
|
-
const r =
|
|
90
|
+
}), o.removedNodes.forEach((e) => {
|
|
91
|
+
if (e.nodeType === Node.ELEMENT_NODE) {
|
|
92
|
+
const r = e;
|
|
93
93
|
r.classList.contains("k-animation-container") && r.removeEventListener(
|
|
94
94
|
"keydown",
|
|
95
95
|
g
|
|
@@ -104,65 +104,95 @@ const G = (m) => {
|
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
|
-
}),
|
|
107
|
+
}), m.observe(document.body, {
|
|
108
108
|
childList: !0,
|
|
109
109
|
subtree: !0
|
|
110
110
|
});
|
|
111
|
-
}, L = (
|
|
112
|
-
if (!
|
|
111
|
+
}, L = (t) => {
|
|
112
|
+
if (!t.type || !t)
|
|
113
113
|
return;
|
|
114
|
-
console.log("handleGridKeyDown",
|
|
115
|
-
const o =
|
|
116
|
-
if (o
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
console.log("handleGridKeyDown", t, t.code);
|
|
115
|
+
const o = t.target;
|
|
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
|
-
|
|
129
|
-
"
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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;
|
|
135
143
|
return;
|
|
136
144
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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();
|
|
148
180
|
return;
|
|
149
181
|
}
|
|
150
|
-
e.code === "ArrowRight" ? n = n === r.length - 1 ? 0 : n + 1 : e.code === "ArrowLeft" && (n = n === r.length - 1 ? n - 1 : r.length - 1), r[n].focus();
|
|
151
|
-
return;
|
|
152
182
|
}
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
185
|
}, D = () => {
|
|
156
186
|
p(() => {
|
|
157
|
-
const
|
|
158
|
-
|
|
187
|
+
const t = f.value.$el.closest(".k-grid");
|
|
188
|
+
t && t.classList.add("fk-grid");
|
|
159
189
|
});
|
|
160
|
-
},
|
|
190
|
+
}, q = () => {
|
|
161
191
|
try {
|
|
162
|
-
const
|
|
192
|
+
const t = () => {
|
|
163
193
|
try {
|
|
164
194
|
const r = Array.from(
|
|
165
|
-
|
|
195
|
+
f.value.$el.querySelectorAll(
|
|
166
196
|
".k-table-row[data-grid-row-index]"
|
|
167
197
|
)
|
|
168
198
|
);
|
|
@@ -181,25 +211,25 @@ const G = (m) => {
|
|
|
181
211
|
console.error("ensureSingleTabindex error:", r);
|
|
182
212
|
}
|
|
183
213
|
}, o = Array.from(
|
|
184
|
-
|
|
214
|
+
f.value.$el.querySelectorAll(
|
|
185
215
|
".k-table-row[data-grid-row-index]"
|
|
186
216
|
)
|
|
187
217
|
);
|
|
188
218
|
o.length > 0 && o.forEach((r, n) => {
|
|
189
219
|
r.setAttribute("tabindex", n === 0 ? "0" : "-1");
|
|
190
220
|
});
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}), u.observe(
|
|
195
|
-
} catch (
|
|
196
|
-
console.error("Error setting up row navigation:",
|
|
221
|
+
const e = f.value.$el.querySelector(".k-table-tbody");
|
|
222
|
+
e && (u = new MutationObserver(() => {
|
|
223
|
+
t();
|
|
224
|
+
}), u.observe(e, { childList: !0, subtree: !0 }));
|
|
225
|
+
} catch (t) {
|
|
226
|
+
console.error("Error setting up row navigation:", t);
|
|
197
227
|
}
|
|
198
|
-
},
|
|
228
|
+
}, T = () => {
|
|
199
229
|
p(() => {
|
|
200
230
|
const o = document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");
|
|
201
|
-
o && o.forEach((
|
|
202
|
-
|
|
231
|
+
o && o.forEach((e) => {
|
|
232
|
+
e.setAttribute("role", "button"), e.addEventListener(
|
|
203
233
|
"keydown",
|
|
204
234
|
h
|
|
205
235
|
);
|
|
@@ -207,41 +237,41 @@ const G = (m) => {
|
|
|
207
237
|
});
|
|
208
238
|
}, C = () => {
|
|
209
239
|
const o = document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");
|
|
210
|
-
o && o.forEach((
|
|
211
|
-
|
|
240
|
+
o && o.forEach((e) => {
|
|
241
|
+
e.removeEventListener(
|
|
212
242
|
"keydown",
|
|
213
243
|
h
|
|
214
244
|
);
|
|
215
|
-
}),
|
|
245
|
+
}), m && (m.disconnect(), m = null), u && (u.disconnect(), u = null), b.forEach((e) => e()), b.length = 0;
|
|
216
246
|
}, I = () => {
|
|
217
247
|
document.querySelectorAll(
|
|
218
248
|
".k-grid-header .k-table-thead th"
|
|
219
|
-
).forEach((
|
|
220
|
-
const n =
|
|
249
|
+
).forEach((e, r) => {
|
|
250
|
+
const n = e.querySelector(
|
|
221
251
|
".k-grid-header-menu.k-grid-column-menu"
|
|
222
252
|
);
|
|
223
253
|
if (!n) return;
|
|
224
254
|
const a = k();
|
|
225
255
|
if (a && a[r]) {
|
|
226
256
|
const c = a[r].field ?? "";
|
|
227
|
-
|
|
257
|
+
e.setAttribute("data-feather-k-field", c), e.setAttribute(
|
|
228
258
|
"data-feather-k-filterable",
|
|
229
259
|
a[r].filterable === !1 ? "false" : "true"
|
|
230
|
-
),
|
|
260
|
+
), e.setAttribute(
|
|
231
261
|
"data-feather-k-sortable",
|
|
232
262
|
a[r].sortable === !1 ? "false" : "true"
|
|
233
263
|
);
|
|
234
264
|
}
|
|
235
|
-
const i =
|
|
236
|
-
n.setAttribute("tabindex", "-1"), i ? (
|
|
237
|
-
const
|
|
238
|
-
c.target?.closest(".k-column-resizer") || (
|
|
239
|
-
},
|
|
265
|
+
const i = e.dataset.featherKFilterable !== "false", l = e.dataset.featherKSortable !== "false";
|
|
266
|
+
n.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");
|
|
267
|
+
const d = (c) => {
|
|
268
|
+
c.target?.closest(".k-column-resizer") || (s.value = e, n.click());
|
|
269
|
+
}, v = (c) => {
|
|
240
270
|
if (i)
|
|
241
|
-
|
|
242
|
-
else if (
|
|
243
|
-
|
|
244
|
-
const
|
|
271
|
+
s.value = e, c.preventDefault(), c.stopPropagation(), d(c);
|
|
272
|
+
else if (l) {
|
|
273
|
+
s.value = e;
|
|
274
|
+
const w = new KeyboardEvent("keydown", {
|
|
245
275
|
key: "Enter",
|
|
246
276
|
code: "Enter",
|
|
247
277
|
keyCode: 13,
|
|
@@ -249,95 +279,95 @@ const G = (m) => {
|
|
|
249
279
|
bubbles: !0,
|
|
250
280
|
cancelable: !0
|
|
251
281
|
});
|
|
252
|
-
|
|
282
|
+
e.dispatchEvent(w);
|
|
253
283
|
}
|
|
254
284
|
};
|
|
255
|
-
|
|
256
|
-
|
|
285
|
+
e.addEventListener("click", v), b.push(() => {
|
|
286
|
+
e.removeEventListener("click", v);
|
|
257
287
|
});
|
|
258
288
|
const A = (c) => {
|
|
259
|
-
if ((c.code === "Enter" || c.code === "Space") && (i ||
|
|
260
|
-
if (
|
|
261
|
-
c.preventDefault(), c.stopPropagation(),
|
|
262
|
-
else if (
|
|
263
|
-
const
|
|
264
|
-
|
|
289
|
+
if ((c.code === "Enter" || c.code === "Space") && (i || l)) {
|
|
290
|
+
if (s.value = e, s.value.focus(), i)
|
|
291
|
+
c.preventDefault(), c.stopPropagation(), d(c);
|
|
292
|
+
else if (l) {
|
|
293
|
+
const w = e.querySelector(".k-link");
|
|
294
|
+
w && w.click();
|
|
265
295
|
}
|
|
266
296
|
}
|
|
267
297
|
};
|
|
268
|
-
|
|
269
|
-
|
|
298
|
+
e.addEventListener("keydown", A, !0), b.push(() => {
|
|
299
|
+
e.removeEventListener("keydown", A, !0);
|
|
270
300
|
});
|
|
271
301
|
});
|
|
272
302
|
const o = document.querySelector(".k-grid-header .k-table-thead");
|
|
273
303
|
if (o) {
|
|
274
|
-
const
|
|
304
|
+
const e = (r) => {
|
|
275
305
|
const n = r.target.closest("th");
|
|
276
306
|
n && (r.code === "Enter" || r.code === "Space") && n.dataset.featherKFilterable === "false" && n.dataset.featherKSortable === "false" && (r.preventDefault(), r.stopImmediatePropagation());
|
|
277
307
|
};
|
|
278
308
|
o.addEventListener(
|
|
279
309
|
"keydown",
|
|
280
|
-
|
|
310
|
+
e,
|
|
281
311
|
!0
|
|
282
312
|
// NOTE: capture phase
|
|
283
|
-
),
|
|
313
|
+
), b.push(() => {
|
|
284
314
|
o.removeEventListener(
|
|
285
315
|
"keydown",
|
|
286
|
-
|
|
316
|
+
e,
|
|
287
317
|
!0
|
|
288
318
|
);
|
|
289
319
|
});
|
|
290
320
|
}
|
|
291
|
-
}, K = function(
|
|
292
|
-
const
|
|
293
|
-
if (!
|
|
294
|
-
const n =
|
|
321
|
+
}, K = function(t, o) {
|
|
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;
|
|
295
325
|
if (!n) {
|
|
296
326
|
if (a && !i) {
|
|
297
|
-
(
|
|
298
|
-
(
|
|
327
|
+
(t.event.sort && void 0)?.filter(
|
|
328
|
+
(d) => d.field !== t.event.field
|
|
299
329
|
);
|
|
300
330
|
return;
|
|
301
331
|
}
|
|
302
332
|
typeof o == "function" && p(() => {
|
|
303
|
-
|
|
333
|
+
s.value && s.value.focus(), o(t);
|
|
304
334
|
});
|
|
305
335
|
}
|
|
306
336
|
};
|
|
307
337
|
return S(() => {
|
|
308
|
-
D(),
|
|
338
|
+
D(), q(), T();
|
|
309
339
|
}), M(() => {
|
|
310
340
|
C();
|
|
311
341
|
}), {
|
|
312
|
-
activeFilterButton:
|
|
342
|
+
activeFilterButton: s,
|
|
313
343
|
handleGridKeyDown: L,
|
|
314
344
|
handleSortChange: K
|
|
315
345
|
};
|
|
316
346
|
};
|
|
317
|
-
function
|
|
318
|
-
const { activeByDefault:
|
|
319
|
-
function
|
|
347
|
+
function P(f = {}) {
|
|
348
|
+
const { activeByDefault: s = !1, autoActivateDelay: m = 0 } = f, u = E(s);
|
|
349
|
+
function b() {
|
|
320
350
|
u.value = !0;
|
|
321
351
|
}
|
|
322
|
-
function
|
|
352
|
+
function y() {
|
|
323
353
|
u.value = !1;
|
|
324
354
|
}
|
|
325
355
|
function k() {
|
|
326
356
|
u.value = !u.value;
|
|
327
357
|
}
|
|
328
358
|
return S(() => {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
},
|
|
359
|
+
m > 0 && !s && setTimeout(() => {
|
|
360
|
+
b();
|
|
361
|
+
}, m);
|
|
332
362
|
}), N(() => {
|
|
333
363
|
}), {
|
|
334
364
|
isGridActive: u,
|
|
335
|
-
activateGrid:
|
|
336
|
-
deactivateGrid:
|
|
365
|
+
activateGrid: b,
|
|
366
|
+
deactivateGrid: y,
|
|
337
367
|
toggleGrid: k
|
|
338
368
|
};
|
|
339
369
|
}
|
|
340
370
|
export {
|
|
341
371
|
G as useGridA11y,
|
|
342
|
-
|
|
372
|
+
P as useGridComposableEx
|
|
343
373
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
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?(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 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"})}));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { useGridA11y } from
|
|
2
|
-
export { useGridComposableEx } from
|
|
1
|
+
export { useGridA11y } from "./useGridA11y";
|
|
2
|
+
export { useGridComposableEx } from "./useGridComposableEx";
|
package/dist/types/kendo.d.ts
CHANGED
package/dist/useGridA11y.d.ts
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"main": "dist/featherk-composables.umd.js",
|
|
5
5
|
"module": "dist/featherk-composables.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"build": "vite build && npm run build:types",
|
|
12
12
|
"build:types": "vue-tsc --emitDeclarationOnly --declaration --declarationDir dist",
|
|
13
13
|
"test": "vitest",
|
|
14
|
-
"lint": "eslint src --ext .ts,.vue",
|
|
15
14
|
"clean": "rm -rf dist",
|
|
16
15
|
"prepublishOnly": "npm run build"
|
|
17
16
|
},
|
|
@@ -21,7 +20,6 @@
|
|
|
21
20
|
"devDependencies": {
|
|
22
21
|
"@types/node": "^24.5.2",
|
|
23
22
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
24
|
-
"eslint": "^8.0.0",
|
|
25
23
|
"typescript": "~5.8.3",
|
|
26
24
|
"vite": "^7.1.7",
|
|
27
25
|
"vitest": "^3.2.4",
|