@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.
@@ -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
- item?: {
38
- text?: string;
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
@@ -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,
@@ -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 package root:
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, usePopupTrap } from "@featherk/composables";
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>();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/composables",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "main": "dist/featherk-composables.umd.js",
5
5
  "module": "dist/featherk-composables.es.js",
6
6
  "types": "dist/index.d.ts",