@featherk/composables 0.12.1 → 0.12.2
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 +7 -1
- package/dist/featherk-composables.es.js +764 -743
- package/dist/featherk-composables.es.js.map +1 -1
- package/dist/featherk-composables.umd.js +1 -1
- package/dist/featherk-composables.umd.js.map +1 -1
- package/dist/menu/usePopupMenu.d.ts +9 -4
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/useExclusiveGroup.d.ts +20 -0
- package/dist/registry/useExclusiveGroup.test.d.ts +1 -0
- package/docs/address/useZipTextBox.md +1 -1
- package/docs/grid/useGridA11y.md +1 -1
- package/docs/grid/useGridActiveFilter.md +3 -3
- package/docs/grid/useGridRowAction.md +2 -2
- package/docs/id/useCompositeId.md +1 -1
- package/docs/menu/usePopupMenu.md +6 -0
- package/docs/observer/useIntersectionObserver.md +2 -2
- package/docs/registry/useExclusiveGroup.md +73 -0
- package/docs/time/useMaskedTimeInput.md +2 -1
- package/package.json +1 -1
|
@@ -33,10 +33,13 @@ export type PopupMenuCloseReason =
|
|
|
33
33
|
| null;
|
|
34
34
|
export type PopupMenuCloseTrigger = Exclude<PopupMenuCloseReason, null>;
|
|
35
35
|
/** Minimal event shape emitted by Kendo Menu `@select`. */
|
|
36
|
-
export type KendoMenuSelectEvent = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
export type KendoMenuSelectEvent<TItem = {
|
|
37
|
+
text?: string;
|
|
38
|
+
id?: string;
|
|
39
|
+
cssClass?: string;
|
|
40
|
+
data?: unknown;
|
|
41
|
+
}> = {
|
|
42
|
+
item?: TItem;
|
|
40
43
|
event?: {
|
|
41
44
|
type?: string;
|
|
42
45
|
} | null;
|
|
@@ -92,6 +95,8 @@ export type UsePopupMenuOptions = {
|
|
|
92
95
|
* `aria-haspopup="menu"` and baseline `aria-expanded="false"` attributes.
|
|
93
96
|
*/
|
|
94
97
|
manageMenuTriggerAria?: boolean;
|
|
98
|
+
/** Applies an accessible name to the rendered `role="menubar"` when provided. */
|
|
99
|
+
menuLabel?: MaybeRefOrGetter<string | null | undefined>;
|
|
95
100
|
/**
|
|
96
101
|
* Declares whether `triggerRef` is a button or a `tr.k-table-row`. Required so
|
|
97
102
|
* misrouted refs (e.g. a button-mode ref that resolves to a row) are never
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export { useExclusiveGroup } from "./useExclusiveGroup";
|
|
2
|
+
export type { ExclusiveGroupMember, UseExclusiveGroupReturn, } from "./useExclusiveGroup";
|
|
1
3
|
export { useActiveIdRegistry } from "./useActiveIdRegistry";
|
|
2
4
|
export type { RegisterableElement, UseActiveIdRegistryReturn, } from "./useActiveIdRegistry";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A participant that can be deactivated when another group member becomes active. */
|
|
2
|
+
export type ExclusiveGroupMember = {
|
|
3
|
+
deactivate: () => void;
|
|
4
|
+
};
|
|
5
|
+
/** Coordinates mutually exclusive activation across registered members. */
|
|
6
|
+
export type UseExclusiveGroupReturn = {
|
|
7
|
+
/** Adds a member and unregisters it automatically when the current owner unmounts. */
|
|
8
|
+
register: (member: ExclusiveGroupMember) => void;
|
|
9
|
+
/** Deactivates every registered member except the activating member. */
|
|
10
|
+
deactivateOthers: (activeMember: ExclusiveGroupMember) => void;
|
|
11
|
+
/** Deactivates every registered member. */
|
|
12
|
+
deactivateAll: () => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Coordinates mutually exclusive active-id registries within a Vue owner.
|
|
16
|
+
*
|
|
17
|
+
* Registered members are removed automatically when their owning component or composable
|
|
18
|
+
* unmounts. Call `register` from setup so Vue can associate cleanup with that owner.
|
|
19
|
+
*/
|
|
20
|
+
export declare const useExclusiveGroup: () => UseExclusiveGroupReturn;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -21,7 +21,7 @@ Kendo Vue's `MaskedTextBox` does not handle dynamic mask switching between a 5-d
|
|
|
21
21
|
<script setup lang="ts">
|
|
22
22
|
import { TextBox } from "@progress/kendo-vue-inputs";
|
|
23
23
|
import { Error } from "@progress/kendo-vue-labels";
|
|
24
|
-
import { useZipTextBox } from "@featherk/composables";
|
|
24
|
+
import { useZipTextBox } from "@featherk/composables/address";
|
|
25
25
|
|
|
26
26
|
const {
|
|
27
27
|
zipDisplay,
|
package/docs/grid/useGridA11y.md
CHANGED
|
@@ -28,7 +28,7 @@ Place inside a `<script setup lang="ts">` block. Provide a Grid ref and call the
|
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
import { ref, onMounted, watch } from 'vue';
|
|
31
|
-
import { useGridA11y } from '@featherk/composables';
|
|
31
|
+
import { useGridA11y } from '@featherk/composables/grid';
|
|
32
32
|
|
|
33
33
|
const gridRef = ref(null);
|
|
34
34
|
const dataResult = ref({ data: [] }); // example data container
|
|
@@ -23,10 +23,10 @@ npm install @featherk/composables
|
|
|
23
23
|
|
|
24
24
|
## Import
|
|
25
25
|
|
|
26
|
-
`useGridActiveFilter` is exported from the
|
|
26
|
+
`useGridActiveFilter` is exported from the grid feature barrel:
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
|
-
import { useGridActiveFilter } from "@featherk/composables";
|
|
29
|
+
import { useGridActiveFilter } from "@featherk/composables/grid";
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Quick Start
|
|
@@ -35,7 +35,7 @@ import { useGridActiveFilter } from "@featherk/composables";
|
|
|
35
35
|
import { computed, ref } from "vue";
|
|
36
36
|
import type { CompositeFilterDescriptor } from "@progress/kendo-data-query";
|
|
37
37
|
import type { GridFilterChangeEvent, GridColumnProps } from "@progress/kendo-vue-grid";
|
|
38
|
-
import { useGridActiveFilter } from "@featherk/composables";
|
|
38
|
+
import { useGridActiveFilter } from "@featherk/composables/grid";
|
|
39
39
|
|
|
40
40
|
const filter = ref<CompositeFilterDescriptor | undefined>(undefined);
|
|
41
41
|
|
|
@@ -48,7 +48,7 @@ npm install @featherk/composables
|
|
|
48
48
|
<script setup lang="ts">
|
|
49
49
|
import { ref } from "vue";
|
|
50
50
|
import { Grid } from "@progress/kendo-vue-grid";
|
|
51
|
-
import { useGridRowAction, type RowActionContext } from "@featherk/composables";
|
|
51
|
+
import { useGridRowAction, type RowActionContext } from "@featherk/composables/grid";
|
|
52
52
|
|
|
53
53
|
interface Patient {
|
|
54
54
|
id: number;
|
|
@@ -96,7 +96,7 @@ When combining `useGridA11y` (for `ArrowUp`/`ArrowDown` row focus navigation) wi
|
|
|
96
96
|
<script setup lang="ts">
|
|
97
97
|
import { onMounted, ref } from "vue";
|
|
98
98
|
import { Grid } from "@progress/kendo-vue-grid";
|
|
99
|
-
import { useGridA11y, useGridRowAction } from "@featherk/composables";
|
|
99
|
+
import { useGridA11y, useGridRowAction } from "@featherk/composables/grid";
|
|
100
100
|
|
|
101
101
|
const gridRef = ref(null);
|
|
102
102
|
const patients = ref([{ id: 1, name: "Alice Johnson" }]);
|
|
@@ -11,7 +11,7 @@ Creates SSR-safe, component-scoped DOM IDs using Vue's `useId()`. Use it for rel
|
|
|
11
11
|
3. Use `compose(...)` for related IDs with stable logical segments.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { useCompositeId } from "@featherk/composables";
|
|
14
|
+
import { useCompositeId } from "@featherk/composables/id";
|
|
15
15
|
|
|
16
16
|
// Step 1: Vue creates a unique ID for this rendered component instance.
|
|
17
17
|
const ids = useCompositeId("action-cell");
|
|
@@ -30,6 +30,9 @@ Composable for accessible Kendo UI for Vue `Popup` + `Menu` action menus. It man
|
|
|
30
30
|
|
|
31
31
|
<!-- Step 6: bind popup/menu close hooks for modality-aware focus restoration -->
|
|
32
32
|
<Popup :show="isOpen" @close="menu.handlePopupClose">
|
|
33
|
+
<strong v-if="menuTitle" class="fk-menu-title">
|
|
34
|
+
{{ menuTitle }}
|
|
35
|
+
</strong>
|
|
33
36
|
<Menu
|
|
34
37
|
ref="menuRef"
|
|
35
38
|
@keydown.escape="menu.handleActionMenuEscape"
|
|
@@ -51,6 +54,7 @@ const isOpen = ref(false);
|
|
|
51
54
|
const triggerRef = ref<HTMLElement | null>(null);
|
|
52
55
|
const menuRef = ref<HTMLElement | null>(null);
|
|
53
56
|
const panelRef = ref<HTMLElement | null>(null);
|
|
57
|
+
const menuTitle = ref("Record actions");
|
|
54
58
|
|
|
55
59
|
// Step 3: wire composable show/hide ownership to parent state
|
|
56
60
|
const menu = usePopupMenu({
|
|
@@ -58,6 +62,7 @@ const menu = usePopupMenu({
|
|
|
58
62
|
triggerRef,
|
|
59
63
|
menuRef,
|
|
60
64
|
triggerMode: "button",
|
|
65
|
+
menuLabel: menuTitle,
|
|
61
66
|
focusTargetRef: panelRef,
|
|
62
67
|
requestShow: () => (isOpen.value = true),
|
|
63
68
|
requestHide: () => (isOpen.value = false),
|
|
@@ -240,6 +245,7 @@ See [UsePopupMenu.vue](https://github.com/NantHealth/featherk/blob/integration/d
|
|
|
240
245
|
- `resolveFocusTarget?: () => HTMLElement | null`: Dynamic focus-target resolver. It takes precedence over `focusTargetRef` and is useful for virtualized grid rows.
|
|
241
246
|
- `menuItemSelector?: string`: Selector for the first enabled item. Defaults to `.k-menu-item:not(.k-disabled)`.
|
|
242
247
|
- `manageMenuTriggerAria?: boolean`: Keeps `aria-expanded` in sync with `isOpen` on the resolved `triggerRef` element. Defaults to `true`. The consumer always owns the static `aria-haspopup="menu"` attribute and the baseline `aria-expanded="false"`; the composable only ever writes `aria-expanded` and never removes it. Set to `false` to own `aria-expanded` yourself too.
|
|
248
|
+
- `menuLabel?: MaybeRefOrGetter<string | null | undefined>`: Optional accessible name applied as `aria-label` to the rendered `ul[role="menubar"]`. Empty labels remove the managed attribute.
|
|
243
249
|
- `triggerMode: "button" | "row"`: Required. Declares the trigger semantics so a misrouted `triggerRef` (e.g. one that unexpectedly resolves to a `tr.k-table-row` while `triggerMode: "button"` is set) is never silently managed; a mismatch logs a `console.warn` and skips ARIA management for that resolution. Use `"row"` only when a `tr.k-table-row` owns the menu trigger. Neither mode adds `role`, `tabindex`, or keyboard behavior.
|
|
244
250
|
- `anchor?: true | PopupMenuAnchorOptions`: Enables document-relative `offset` tracking for dynamically retargeted Popups. `true` uses the resolved trigger's rectangle and nearest scrollable ancestor. `PopupMenuAnchorOptions` accepts `clipRoot`, `hideWhenAnchorClipped`, and `getRect` for pointer-positioned menus. Internally, the anchor-hidden policy uses `useIntersectionObserver` with `threshold: 1`; a clipped trigger closes with `"anchor-hidden"` without restoring focus.
|
|
245
251
|
|
|
@@ -33,7 +33,7 @@ The composable works the same way with a regular HTML table. Place the table in
|
|
|
33
33
|
|
|
34
34
|
<script setup lang="ts">
|
|
35
35
|
import { ref } from "vue";
|
|
36
|
-
import { useIntersectionObserver } from "@featherk/composables";
|
|
36
|
+
import { useIntersectionObserver } from "@featherk/composables/observer";
|
|
37
37
|
|
|
38
38
|
// Step 1: bind refs to the native scroll wrapper and target table row.
|
|
39
39
|
const tableViewportRef = ref<HTMLElement | null>(null);
|
|
@@ -66,7 +66,7 @@ useIntersectionObserver({
|
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
68
|
import { ref } from "vue";
|
|
69
|
-
import { useIntersectionObserver } from "@featherk/composables";
|
|
69
|
+
import { useIntersectionObserver } from "@featherk/composables/observer";
|
|
70
70
|
|
|
71
71
|
// Step 1: bind this ref to the target element.
|
|
72
72
|
const cellRef = ref<Element | null>(null);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# useExclusiveGroup
|
|
2
|
+
|
|
3
|
+
[Back to Composables README](https://github.com/NantHealth/featherk/blob/integration/packages/composables/README.md)
|
|
4
|
+
|
|
5
|
+
`useExclusiveGroup` coordinates a set of independently owned active-id registries so that
|
|
6
|
+
activating one can deactivate the others.
|
|
7
|
+
|
|
8
|
+
This is a Vue composable: `register()` automatically removes each member when the component or
|
|
9
|
+
composable that called it unmounts. Create the group and register members from Vue setup.
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
1. Create one group for the interactions that must be mutually exclusive.
|
|
14
|
+
2. Create each active-id registry and register it with the group.
|
|
15
|
+
3. Before activating a registry, deactivate the other group members.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
useActiveIdRegistry,
|
|
20
|
+
useExclusiveGroup,
|
|
21
|
+
} from "@featherk/composables/registry";
|
|
22
|
+
|
|
23
|
+
// Step 1: create one group for the related interactions.
|
|
24
|
+
const menuGroup = useExclusiveGroup();
|
|
25
|
+
|
|
26
|
+
// Step 2: create and register the related registries.
|
|
27
|
+
const actionRegistry = useActiveIdRegistry<string>();
|
|
28
|
+
const rowRegistry = useActiveIdRegistry<number>();
|
|
29
|
+
menuGroup.register(actionRegistry);
|
|
30
|
+
menuGroup.register(rowRegistry);
|
|
31
|
+
|
|
32
|
+
const openAction = (id: string) => {
|
|
33
|
+
// Step 3: close sibling interactions before activating this registry.
|
|
34
|
+
menuGroup.deactivateOthers(actionRegistry);
|
|
35
|
+
actionRegistry.activate(id);
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## With usePopupMenu
|
|
40
|
+
|
|
41
|
+
`usePopupMenu` stays independent of exclusivity groups. A standalone popup joins through the
|
|
42
|
+
registry that owns its `isOpen`, `requestShow`, and `requestHide` state:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const rowRegistry = useActiveIdRegistry<number>();
|
|
46
|
+
const menuGroup = useExclusiveGroup();
|
|
47
|
+
menuGroup.register(rowRegistry);
|
|
48
|
+
|
|
49
|
+
const rowMenu = usePopupMenu({
|
|
50
|
+
isOpen: rowRegistry.isActive,
|
|
51
|
+
triggerRef: rowRegistry.activeElement,
|
|
52
|
+
menuRef,
|
|
53
|
+
triggerMode: "row",
|
|
54
|
+
requestShow: () => {},
|
|
55
|
+
requestHide: () => rowRegistry.deactivate(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const openRowMenu = (rowId: number) => {
|
|
59
|
+
menuGroup.deactivateOthers(rowRegistry);
|
|
60
|
+
rowRegistry.activate(rowId);
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`useActionCellMenu` follows this same pattern internally when its optional `group` is set: it
|
|
65
|
+
registers its own active-id registry and deactivates group peers before it opens its popup.
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
| Method | Description |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `register(member)` | Adds a `{ deactivate(): void }` member and removes it automatically when the current Vue owner unmounts. |
|
|
72
|
+
| `deactivateOthers(activeMember)` | Deactivates all registered members except `activeMember`. |
|
|
73
|
+
| `deactivateAll()` | Deactivates all registered members. |
|
|
@@ -20,7 +20,8 @@ import { ref } from "vue";
|
|
|
20
20
|
import { TimePicker } from "@progress/kendo-vue-dateinputs";
|
|
21
21
|
import { MaskedTextBox } from "@progress/kendo-vue-inputs";
|
|
22
22
|
import { Error } from "@progress/kendo-vue-labels";
|
|
23
|
-
import { useMaskedTimeInput
|
|
23
|
+
import { useMaskedTimeInput } from "@featherk/composables/time";
|
|
24
|
+
import { usePopupTrap } from "@featherk/composables/trap";
|
|
24
25
|
|
|
25
26
|
// Shared model for TimePicker and composable
|
|
26
27
|
const selectedTime = ref<Date | null | undefined>();
|