@featherk/composables 0.16.0 → 0.16.1

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.
@@ -1,7 +1,7 @@
1
1
  // Generated by scripts/generate-docs-manifest.mjs. Do not edit.
2
2
  export const packageName = "@featherk/composables";
3
3
 
4
- export const version = "0.16.0";
4
+ export const version = "0.16.1";
5
5
 
6
6
  export const paths = {
7
7
  useActiveIdRegistry: "registry/useActiveIdRegistry.md",
@@ -30,29 +30,29 @@ export const paths = {
30
30
  };
31
31
 
32
32
  export const docs = {
33
- useActiveIdRegistry: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/registry/useActiveIdRegistry.md",
34
- useCompositeId: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/id/useCompositeId.md",
35
- useExclusiveGroup: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/registry/useExclusiveGroup.md",
36
- useFieldsetTouchTracker: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/form/useFieldsetTouchTracker.md",
37
- useFieldsetValidationKit: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/form/useFieldsetValidationKit.md",
38
- useGridA11y: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/grid/useGridA11y.md",
39
- useGridActiveFilter: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/grid/useGridActiveFilter.md",
40
- useGridRowAction: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/grid/useGridRowAction.md",
41
- useIntersectionObserver: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/observer/useIntersectionObserver.md",
42
- useMaskedDateInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/date/useMaskedDateInput.md",
43
- useMaskedDateRangeInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/range/useMaskedDateRangeInput.md",
44
- useMaskedTimeInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/time/useMaskedTimeInput.md",
45
- usePopupMenu: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenu.md",
46
- usePopupMenuActivationAnchor: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenuActivationAnchor.md",
47
- usePopupMenuAndActiveIdRegistry: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenuAndActiveIdRegistry.md",
48
- usePopupMenuFocusAndExclusivity: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenuFocusAndExclusivity.md",
49
- usePopupMenuGridOrchestration: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenuGridOrchestration.md",
50
- usePopupMenuSharedInstance: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenuSharedInstance.md",
51
- usePopupMenuUpgrade0130: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/menu/usePopupMenuUpgrade0130.md",
52
- usePopupTrap: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/trap/usePopupTrap.md",
53
- useUSAddress: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/address/useUSAddress.md",
54
- useWindow: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/window/useWindow.md",
55
- useZipTextBox: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.0/docs/address/useZipTextBox.md",
33
+ useActiveIdRegistry: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/registry/useActiveIdRegistry.md",
34
+ useCompositeId: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/id/useCompositeId.md",
35
+ useExclusiveGroup: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/registry/useExclusiveGroup.md",
36
+ useFieldsetTouchTracker: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/form/useFieldsetTouchTracker.md",
37
+ useFieldsetValidationKit: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/form/useFieldsetValidationKit.md",
38
+ useGridA11y: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/grid/useGridA11y.md",
39
+ useGridActiveFilter: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/grid/useGridActiveFilter.md",
40
+ useGridRowAction: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/grid/useGridRowAction.md",
41
+ useIntersectionObserver: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/observer/useIntersectionObserver.md",
42
+ useMaskedDateInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/date/useMaskedDateInput.md",
43
+ useMaskedDateRangeInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/range/useMaskedDateRangeInput.md",
44
+ useMaskedTimeInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/time/useMaskedTimeInput.md",
45
+ usePopupMenu: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenu.md",
46
+ usePopupMenuActivationAnchor: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenuActivationAnchor.md",
47
+ usePopupMenuAndActiveIdRegistry: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenuAndActiveIdRegistry.md",
48
+ usePopupMenuFocusAndExclusivity: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenuFocusAndExclusivity.md",
49
+ usePopupMenuGridOrchestration: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenuGridOrchestration.md",
50
+ usePopupMenuSharedInstance: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenuSharedInstance.md",
51
+ usePopupMenuUpgrade0130: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/menu/usePopupMenuUpgrade0130.md",
52
+ usePopupTrap: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/trap/usePopupTrap.md",
53
+ useUSAddress: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/address/useUSAddress.md",
54
+ useWindow: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/window/useWindow.md",
55
+ useZipTextBox: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.16.1/docs/address/useZipTextBox.md",
56
56
  };
57
57
 
58
58
  export const docUrl = (name, host = "https://cdn.jsdelivr.net/npm") =>
@@ -57,7 +57,7 @@ export interface USAddressSubmitPayload {
57
57
  address2: string;
58
58
  city: string;
59
59
  state: string; // upper-cased
60
- zip: string; // digits only
60
+ zip: string; // digits only
61
61
  }
62
62
  ```
63
63
 
@@ -17,7 +17,7 @@ Kendo Vue's `MaskedTextBox` does not handle dynamic mask switching between a 5-d
17
17
 
18
18
  ## Quick Start
19
19
 
20
- ```vue
20
+ ```html
21
21
  <script setup lang="ts">
22
22
  import { TextBox } from "@progress/kendo-vue-inputs";
23
23
  import { Error } from "@progress/kendo-vue-labels";
@@ -34,9 +34,9 @@ const {
34
34
  submitAndValidate,
35
35
  handleZipInput,
36
36
  } = useZipTextBox({
37
- id: "zip", // required — must match the TextBox id attribute
38
- initialValue: "", // optional pre-populated value (digits or formatted)
39
- errorMessage: "Enter a valid ZIP code.", // optional custom message
37
+ id: "zip", // required — must match the TextBox id attribute
38
+ initialValue: "", // optional pre-populated value (digits or formatted)
39
+ errorMessage: "Enter a valid ZIP code.", // optional custom message
40
40
  });
41
41
 
42
42
  const handleSubmit = () => {
@@ -65,45 +65,45 @@ const handleSubmit = () => {
65
65
 
66
66
  ## Options
67
67
 
68
- | Option | Type | Required | Default | Description |
69
- |--------|------|----------|---------|-------------|
70
- | `id` | `string` | **Yes** | — | The `id` attribute of the `TextBox` input. Must match the `id` prop passed to `<TextBox>`. Used by `submitAndValidate({ focus: true })` to focus the field on validation failure. |
71
- | `initialValue` | `string` | No | `""` | Pre-populate the field. Digits and formatted strings are both accepted; non-digit characters are stripped. |
72
- | `errorMessage` | `string` | No | `"Enter a valid ZIP code with exactly 5 or 9 digits."` | Custom validation error message. Whitespace-only values fall back to the default. |
68
+ | Option | Type | Required | Default | Description |
69
+ | -------------- | -------- | -------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `id` | `string` | **Yes** | — | The `id` attribute of the `TextBox` input. Must match the `id` prop passed to `<TextBox>`. Used by `submitAndValidate({ focus: true })` to focus the field on validation failure. |
71
+ | `initialValue` | `string` | No | `""` | Pre-populate the field. Digits and formatted strings are both accepted; non-digit characters are stripped. |
72
+ | `errorMessage` | `string` | No | `"Enter a valid ZIP code with exactly 5 or 9 digits."` | Custom validation error message. Whitespace-only values fall back to the default. |
73
73
 
74
74
  ## Return Value
75
75
 
76
76
  ### State (writable `Ref`)
77
77
 
78
- | Property | Type | Description |
79
- |----------|------|-------------|
80
- | `zipDisplay` | `Ref<string>` | The formatted display value bound to the `TextBox`. Either `XXXXX` or `XXXXX-XXXX`. |
81
- | `submitted` | `Ref<boolean>` | Whether `submitAndValidate()` has been called. Drives error visibility. Use `submitted.value` directly (e.g. to reset) — not typically bound in the template. |
82
- | `errorMessage` | `Ref<string>` | Active error message (custom or default). |
78
+ | Property | Type | Description |
79
+ | -------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
80
+ | `zipDisplay` | `Ref<string>` | The formatted display value bound to the `TextBox`. Either `XXXXX` or `XXXXX-XXXX`. |
81
+ | `submitted` | `Ref<boolean>` | Whether `submitAndValidate()` has been called. Drives error visibility. Use `submitted.value` directly (e.g. to reset) — not typically bound in the template. |
82
+ | `errorMessage` | `Ref<string>` | Active error message (custom or default). |
83
83
 
84
84
  ### Bindings (readonly `string`)
85
85
 
86
- | Property | Type | Description |
87
- |----------|------|-------------|
86
+ | Property | Type | Description |
87
+ | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
88
  | `wrapperClass` | `string` | Bind to `:wrapperClass` on `<TextBox>`. Kendo applies it to its `.k-textbox` wrapper element on every render, so it survives validity state changes. Value is always `"fk-zip-textbox"`. |
89
89
 
90
90
  ### Derived (readonly `ComputedRef`)
91
91
 
92
- | Property | Type | Description |
93
- |----------|------|-------------|
94
- | `zipRaw` | `ComputedRef<string>` | Digits-only ZIP (strips the dash). Use this for form payloads and API calls. |
95
- | `isZipValid` | `ComputedRef<boolean>` | `true` when `zipRaw` is exactly 5 or 9 digits. |
96
- | `showZipError` | `ComputedRef<boolean>` | `true` when submitted and the value is invalid. Bind to `v-if` on an `<Error>`. |
97
- | `textBoxValid` | `ComputedRef<boolean>` | Bind to `:valid` on `TextBox`. `false` only after submit with an invalid value. |
92
+ | Property | Type | Description |
93
+ | ----------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | `zipRaw` | `ComputedRef<string>` | Digits-only ZIP (strips the dash). Use this for form payloads and API calls. |
95
+ | `isZipValid` | `ComputedRef<boolean>` | `true` when `zipRaw` is exactly 5 or 9 digits. |
96
+ | `showZipError` | `ComputedRef<boolean>` | `true` when submitted and the value is invalid. Bind to `v-if` on an `<Error>`. |
97
+ | `textBoxValid` | `ComputedRef<boolean>` | Bind to `:valid` on `TextBox`. `false` only after submit with an invalid value. |
98
98
  | `textBoxValidate` | `ComputedRef<boolean>` | `true` once `submitAndValidate()` has been called. Bind to both `:validate` and `:validityStyles` on `TextBox` — covers both props since they share the same semantic (has the user attempted submission?). |
99
99
 
100
100
  ### Actions
101
101
 
102
- | Method | Signature | Description |
103
- |--------|-----------|-------------|
104
- | `handleZipInput` | `(...args: unknown[]) => void` | Bind to `@input` on `TextBox`. Reformats the value, strips non-digits, inserts/removes the dash, and restores the caret. |
102
+ | Method | Signature | Description |
103
+ | ------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
104
+ | `handleZipInput` | `(...args: unknown[]) => void` | Bind to `@input` on `TextBox`. Reformats the value, strips non-digits, inserts/removes the dash, and restores the caret. |
105
105
  | `submitAndValidate` | `(opts?: { focus?: boolean }) => boolean` | Sets `submitted` to `true` and returns `isZipValid`. Pass `{ focus: true }` to automatically focus the input on failure — useful when this is the first (or only) invalid field. See multi-field note below. |
106
- | `buildPayload` | `() => ZipPayload` | Returns `{ zip: string }` with the raw digits-only value. Safe to pass directly to an API. |
106
+ | `buildPayload` | `() => ZipPayload` | Returns `{ zip: string }` with the raw digits-only value. Safe to pass directly to an API. |
107
107
 
108
108
  > **Multi-field forms:** When validating several ZIP fields, call `submitAndValidate()` on all of them first (so every field shows its error state simultaneously), then call `submitAndValidate({ focus: true })` on the first failure to direct the user:
109
109
  >
@@ -124,7 +124,7 @@ export type ZipPayload = {
124
124
  };
125
125
 
126
126
  export type UseZipTextBoxOptions = {
127
- id: string; // required
127
+ id: string; // required
128
128
  initialValue?: string;
129
129
  errorMessage?: string;
130
130
  };
@@ -136,14 +136,14 @@ export type UseZipTextBoxBindings = {
136
136
 
137
137
  ## Formatting Behaviour
138
138
 
139
- | User input | `zipDisplay` | `zipRaw` | `isZipValid` |
140
- |------------|-------------|----------|--------------|
141
- | `""` | `""` | `""` | `false` |
142
- | `"1234"` | `"1234"` | `"1234"` | `false` |
143
- | `"12345"` | `"12345"` | `"12345"` | `true` |
144
- | `"123456"` | `"12345-6"` | `"123456"` | `false` |
145
- | `"123456789"` | `"12345-6789"` | `"123456789"` | `true` |
146
- | `"12345-6789"` | `"12345-6789"` | `"123456789"` | `true` |
139
+ | User input | `zipDisplay` | `zipRaw` | `isZipValid` |
140
+ | -------------------------- | -------------- | ------------- | ----------------------- |
141
+ | `""` | `""` | `""` | `false` |
142
+ | `"1234"` | `"1234"` | `"1234"` | `false` |
143
+ | `"12345"` | `"12345"` | `"12345"` | `true` |
144
+ | `"123456"` | `"12345-6"` | `"123456"` | `false` |
145
+ | `"123456789"` | `"12345-6789"` | `"123456789"` | `true` |
146
+ | `"12345-6789"` | `"12345-6789"` | `"123456789"` | `true` |
147
147
  | `"1234567890"` (10 digits) | `"12345-6789"` | `"123456789"` | `true` (truncated to 9) |
148
148
 
149
149
  ## Caret Behaviour
@@ -16,7 +16,7 @@ When `required` is enabled, the composable defers required-validation until afte
16
16
 
17
17
  ## Quick Start
18
18
 
19
- ```vue
19
+ ```html
20
20
  <script setup lang="ts">
21
21
  import { ref } from "vue";
22
22
  import { DatePicker } from "@progress/kendo-vue-dateinputs";
@@ -34,7 +34,9 @@ const MAX_DATE = new Date(2026, 11, 31);
34
34
 
35
35
  const masked = useMaskedDateInput({
36
36
  id: "startDate",
37
- onChange: ({ value }) => { selectedDate.value = value ?? undefined; },
37
+ onChange: ({ value }) => {
38
+ selectedDate.value = value ?? undefined;
39
+ },
38
40
  onShowCalendar: () => (showPicker.value = true),
39
41
  externalValue: selectedDate,
40
42
  min: MIN_DATE,
@@ -45,7 +47,14 @@ const masked = useMaskedDateInput({
45
47
 
46
48
  // Destructure only values used as component props
47
49
  // (avoids .value in templates while keeping handlers namespaced)
48
- const { debugEnabled, debugLines, isValid, placeholder, raw, validationMessage } = masked;
50
+ const {
51
+ debugEnabled,
52
+ debugLines,
53
+ isValid,
54
+ placeholder,
55
+ raw,
56
+ validationMessage,
57
+ } = masked;
49
58
 
50
59
  // Integrate focus trap for the DatePicker popup
51
60
  usePopupTrap({
@@ -96,7 +105,6 @@ usePopupTrap({
96
105
  </div>
97
106
 
98
107
  <Error for="startDate">{{ validationMessage }}</Error>
99
-
100
108
  </template>
101
109
  ```
102
110
 
@@ -151,7 +159,7 @@ Creates a controller for a masked single-date input.
151
159
  - Range messages (between/on or after/on or before) when `reason === "out-of-bounds"`.
152
160
  - `datePart(pos: number): 'mm' | 'dd' | 'yyyy'`: Helper to determine focused part from caret position.
153
161
  - `datePart(pos: number): 'mm' | 'dd' | 'yyyy'`: Helper to determine focused part from caret position.
154
- - `initStyling(): void`: Re-applies the `fk-datepicker` class to the parent of the closest `.k-datepicker` element. This is exposed so consumers can re-run theming after Kendo manipulates the DOM; the function is idempotent. Use this to apply the fk-datepicker style hook when the date picker isn't available on initial load.
162
+ - `initStyling(): void`: Re-applies the `fk-datepicker` class to the parent of the closest `.k-datepicker` element. This is exposed so consumers can re-run theming after Kendo manipulates the DOM; the function is idempotent. Use this to apply the fk-datepicker style hook when the date picker isn't available on initial load.
155
163
  - Event handlers to wire to the input:
156
164
  - `handleChange(event)`
157
165
  - `handleKeyDown(event)` — Space opens calendar via `onShowCalendar`; ArrowUp/ArrowDown act as steppers.
@@ -12,7 +12,7 @@ This is a primitive used by `useFieldsetValidationKit`. Most app-level form work
12
12
 
13
13
  ## Quick Start
14
14
 
15
- ```vue
15
+ ```html
16
16
  <script setup lang="ts">
17
17
  import { ref } from "vue";
18
18
  import { useFieldsetTouchTracker } from "@featherk/composables/form";
@@ -43,18 +43,18 @@ Tracks focus transitions for one fieldset.
43
43
 
44
44
  #### Parameters
45
45
 
46
- | Parameter | Type | Description |
47
- |----------|------|-------------|
48
- | `fieldsetRef` | `Ref<HTMLFieldSetElement \| null>` | Ref for the target fieldset. |
49
- | `options.isValid` | `() => boolean` | Current validity function used to set `aria-invalid`. |
46
+ | Parameter | Type | Description |
47
+ | ----------------- | ---------------------------------- | ----------------------------------------------------- |
48
+ | `fieldsetRef` | `Ref<HTMLFieldSetElement \| null>` | Ref for the target fieldset. |
49
+ | `options.isValid` | `() => boolean` | Current validity function used to set `aria-invalid`. |
50
50
 
51
51
  #### Returns
52
52
 
53
- | Property | Type | Description |
54
- |----------|------|-------------|
55
- | `touched` | `Ref<boolean>` | Becomes `true` after a focus transition that leaves the fieldset. |
56
- | `onFocusout` | `(event: FocusEvent) => void` | Focusout handler to bind on the fieldset. |
57
- | `reset` | `() => void` | Sets `touched` back to `false`. |
53
+ | Property | Type | Description |
54
+ | ------------ | ----------------------------- | ----------------------------------------------------------------- |
55
+ | `touched` | `Ref<boolean>` | Becomes `true` after a focus transition that leaves the fieldset. |
56
+ | `onFocusout` | `(event: FocusEvent) => void` | Focusout handler to bind on the fieldset. |
57
+ | `reset` | `() => void` | Sets `touched` back to `false`. |
58
58
 
59
59
  ## Behavior Notes
60
60
 
@@ -21,7 +21,7 @@ Form orchestration composable for delayed fieldset validation UX.
21
21
 
22
22
  ## Quick Start
23
23
 
24
- ```vue
24
+ ```html
25
25
  <script setup lang="ts">
26
26
  import { computed, ref } from "vue";
27
27
  import { useFieldsetValidationKit } from "@featherk/composables/form";
@@ -115,22 +115,22 @@ Creates delayed-validation behavior for a fieldset.
115
115
 
116
116
  #### Options
117
117
 
118
- | Option | Type | Required | Description |
119
- |--------|------|----------|-------------|
120
- | `submitted` | `MaybeRefOrGetter<boolean>` | Yes | Consumer-owned submit state. Never mutated by the kit. |
121
- | `isValid` | `() => boolean` | Yes | Returns the entire fieldset's current true validity. |
118
+ | Option | Type | Required | Description |
119
+ | ----------- | --------------------------- | -------- | ------------------------------------------------------ |
120
+ | `submitted` | `MaybeRefOrGetter<boolean>` | Yes | Consumer-owned submit state. Never mutated by the kit. |
121
+ | `isValid` | `() => boolean` | Yes | Returns the entire fieldset's current true validity. |
122
122
 
123
123
  #### Returns
124
124
 
125
- | Property | Type | Description |
126
- |----------|------|-------------|
127
- | `touched` | `Ref<boolean>` | Becomes `true` once focus leaves the fieldset. |
128
- | `fieldsetValidate` | `ComputedRef<boolean>` | Delayed validation gate: `submitted || touched`. |
129
- | `onFocusout` | `(event: FocusEvent) => void` | Bind to fieldset `focusout`. Handles touch tracking. |
130
- | `reset` | `() => void` | Clears touch state and suppresses one immediate post-reset focusout. |
131
- | `createFieldValidity` | `(isValid: MaybeRefOrGetter<boolean>) => ComputedRef<boolean>` | Wraps a field rule with delayed-validation gate behavior. |
132
- | `createFieldValidationsMap` | `<T>(isValidMap: T) => { [K in keyof T]: ComputedRef<boolean> }` | Accepts one object argument of named field rules and returns the same shape with delayed `ComputedRef<boolean>` values. |
133
- | `focusFirstInvalidField` | `(formRef: MaybeRefOrGetter<HTMLFormElement \| null>) => void` | Convenience helper that focuses the first invalid Kendo control in the form using `.k-invalid` selectors. This is expected to move out of the kit in a future release. |
125
+ | Property | Type | Description |
126
+ | --------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
127
+ | `touched` | `Ref<boolean>` | Becomes `true` once focus leaves the fieldset. |
128
+ | `fieldsetValidate` | `ComputedRef<boolean>` | Delayed validation gate: true once `submitted` or `touched` is true. |
129
+ | `onFocusout` | `(event: FocusEvent) => void` | Bind to fieldset `focusout`. Handles touch tracking. |
130
+ | `reset` | `() => void` | Clears touch state and suppresses one immediate post-reset focusout. |
131
+ | `createFieldValidity` | `(isValid: MaybeRefOrGetter<boolean>) => ComputedRef<boolean>` | Wraps a field rule with delayed-validation gate behavior. |
132
+ | `createFieldValidationsMap` | `<T>(isValidMap: T) => { [K in keyof T]: ComputedRef<boolean> }` | Accepts one object argument of named field rules and returns the same shape with delayed `ComputedRef<boolean>` values. |
133
+ | `focusFirstInvalidField` | `(formRef: MaybeRefOrGetter<HTMLFormElement \| null>) => void` | Convenience helper that focuses the first invalid Kendo control in the form using `.k-invalid` selectors. This is expected to move out of the kit in a future release. |
134
134
 
135
135
  ## Behavior Notes
136
136
 
@@ -27,8 +27,8 @@ npm install @featherk/composables
27
27
  Place inside a `<script setup lang="ts">` block. Provide a Grid ref and call the composable.
28
28
 
29
29
  ```ts
30
- import { ref, onMounted, watch } from 'vue';
31
- import { useGridA11y } from '@featherk/composables/grid';
30
+ import { ref, onMounted, watch } from "vue";
31
+ import { useGridA11y } from "@featherk/composables/grid";
32
32
 
33
33
  const gridRef = ref(null);
34
34
  const dataResult = ref({ data: [] }); // example data container
@@ -37,7 +37,7 @@ const {
37
37
  activeFilterButton,
38
38
  handleGridKeyDown,
39
39
  handleSortChange,
40
- initA11y // NEW: must be called after Grid + data are present in DOM
40
+ initA11y, // NEW: must be called after Grid + data are present in DOM
41
41
  } = useGridA11y(gridRef);
42
42
  ```
43
43
 
@@ -52,10 +52,10 @@ onMounted(() => {
52
52
  () => dataResult.value.data,
53
53
  (rows) => {
54
54
  if (rows && rows.length && gridRef.value) {
55
- initA11y(); // safe to call again; will no-op after first successful init
55
+ initA11y(); // safe to call again; will no-op after first successful init
56
56
  }
57
57
  },
58
- { immediate: true }
58
+ { immediate: true },
59
59
  );
60
60
  });
61
61
  ```
@@ -87,15 +87,15 @@ Kendo Grid `rowRender` allows you to add an `aria-label` so screen readers annou
87
87
  ```ts
88
88
  const renderRow = (h: any, trElement: any, defaultSlots: any, props: any) => {
89
89
  const ariaLabel = `Name: ${props.dataItem.name}, Price: ${props.dataItem.price}`;
90
- const merged = { ...trElement.props, 'aria-label': ariaLabel };
91
- return h('tr', merged, defaultSlots);
90
+ const merged = { ...trElement.props, "aria-label": ariaLabel };
91
+ return h("tr", merged, defaultSlots);
92
92
  };
93
93
  ```
94
94
 
95
95
  ### 5) Focus the active filter button after filter changes
96
96
 
97
97
  ```ts
98
- import { nextTick } from 'vue';
98
+ import { nextTick } from "vue";
99
99
 
100
100
  function onFilterChange(event: any) {
101
101
  // update filter state + reload data
@@ -137,18 +137,14 @@ function onSortChange(event: any) {
137
137
 
138
138
  ```ts
139
139
  // <script setup lang="ts">
140
- import { ref, onMounted, watch } from 'vue';
141
- import { useGridA11y } from '@featherk/composables/grid';
140
+ import { ref, onMounted, watch } from "vue";
141
+ import { useGridA11y } from "@featherk/composables/grid";
142
142
 
143
143
  const gridRef = ref<any>(null);
144
144
  const dataResult = ref<{ data: any[] }>({ data: [] });
145
145
 
146
- const {
147
- activeFilterButton,
148
- handleGridKeyDown,
149
- handleSortChange,
150
- initA11y,
151
- } = useGridA11y(gridRef);
146
+ const { activeFilterButton, handleGridKeyDown, handleSortChange, initA11y } =
147
+ useGridA11y(gridRef);
152
148
 
153
149
  onMounted(() => {
154
150
  watch(
@@ -158,7 +154,7 @@ onMounted(() => {
158
154
  initA11y(); // safe to call; no-ops after first successful init
159
155
  }
160
156
  },
161
- { immediate: true }
157
+ { immediate: true },
162
158
  );
163
159
  });
164
160
  // </script>
@@ -173,7 +169,8 @@ Template bindings (essentials shown):
173
169
  :dataItemKey="'id'"
174
170
  @keydown="handleGridKeyDown"
175
171
  @sortchange="handleSortChange"
176
- navigatable="false" />
172
+ navigatable="false"
173
+ />
177
174
  ```
178
175
 
179
176
  Optional row renderer for screen readers:
@@ -181,14 +178,14 @@ Optional row renderer for screen readers:
181
178
  ```ts
182
179
  const renderRow = (h: any, trEl: any, slots: any, props: any) => {
183
180
  const ariaLabel = `Name: ${props.dataItem.name}, Price: ${props.dataItem.price}`;
184
- return h('tr', { ...trEl.props, 'aria-label': ariaLabel }, slots);
181
+ return h("tr", { ...trEl.props, "aria-label": ariaLabel }, slots);
185
182
  };
186
183
  ```
187
184
 
188
185
  Restore focus after filter changes:
189
186
 
190
187
  ```ts
191
- import { nextTick } from 'vue';
188
+ import { nextTick } from "vue";
192
189
 
193
190
  function onFilterChange(event: any) {
194
191
  // update filter state + reload data
@@ -218,7 +215,6 @@ Returns helpers to initialize accessibility, manage focus, and handle keyboard a
218
215
  ### Options
219
216
 
220
217
  - `options.focusableMenuSelectors?: string[]` — Optional array of CSS selectors used to determine which elements inside a column filter form should be considered focusable for Tab-trapping. If omitted, the composable uses the following built-in defaults targeting common Kendo filter form controls:
221
-
222
218
  - `.k-filter-menu-container .k-dropdownlist[tabindex='0']`
223
219
  - `.k-filter-menu-container input.k-input-inner:not([tabindex='-1']):not([disabled])`
224
220
  - `.k-filter-menu-container button:not([tabindex='-1']):not([disabled])`
@@ -285,13 +281,8 @@ This pattern works without extra keydown propagation workarounds:
285
281
  <GridToolbar>
286
282
  <!-- These form controls now work naturally -->
287
283
  <!-- ArrowLeft/Right move cursor, ArrowUp/Down increment spinners, etc. -->
288
- <MaskedTextBox
289
- mask="00/00/0000"
290
- @keydown="maskedInput.handleKeyDown"
291
- />
292
- <DatePicker
293
- @keydown.capture="datePicker.handleKeyDown"
294
- />
284
+ <MaskedTextBox mask="00/00/0000" @keydown="maskedInput.handleKeyDown" />
285
+ <DatePicker @keydown.capture="datePicker.handleKeyDown" />
295
286
  <TextBox placeholder="Search..." />
296
287
  </GridToolbar>
297
288
  <!-- ... grid columns ... -->
@@ -349,9 +340,7 @@ The `handleGridKeyDown` handler uses an internal `isFormControl()` helper functi
349
340
  In rare cases where form controls don't match the standard patterns detected by `isFormControl()`, you can add `.stop` to individual event handlers to prevent event bubbling:
350
341
 
351
342
  ```html
352
- <CustomFormComponent
353
- @keydown.stop="customComponent.handleKeyDown"
354
- />
343
+ <CustomFormComponent @keydown.stop="customComponent.handleKeyDown" />
355
344
  ```
356
345
 
357
346
  ## Limitations & Assumptions
@@ -34,7 +34,10 @@ import { useGridActiveFilter } from "@featherk/composables/grid";
34
34
  ```ts
35
35
  import { computed, ref } from "vue";
36
36
  import type { CompositeFilterDescriptor } from "@progress/kendo-data-query";
37
- import type { GridFilterChangeEvent, GridColumnProps } from "@progress/kendo-vue-grid";
37
+ import type {
38
+ GridFilterChangeEvent,
39
+ GridColumnProps,
40
+ } from "@progress/kendo-vue-grid";
38
41
  import { useGridActiveFilter } from "@featherk/composables/grid";
39
42
 
40
43
  const filter = ref<CompositeFilterDescriptor | undefined>(undefined);
@@ -64,10 +67,7 @@ const filterChange = (event: GridFilterChangeEvent) => {
64
67
  Template bindings (essentials shown):
65
68
 
66
69
  ```html
67
- <Grid
68
- :columns="columns"
69
- :filter="filter"
70
- @filterchange="filterChange" />
70
+ <Grid :columns="columns" :filter="filter" @filterchange="filterChange" />
71
71
  ```
72
72
 
73
73
  ## API
@@ -33,7 +33,7 @@ npm install @featherk/composables
33
33
  2. Bind `@rowclick` on the Kendo Grid to `handleRowClick`.
34
34
  3. Bind `@keydown` on the Kendo Grid to `handleRowKeyDown`.
35
35
 
36
- ```vue
36
+ ```html
37
37
  <template>
38
38
  <!-- Step 2: Wire @rowclick to handleRowClick -->
39
39
  <!-- Step 3: Wire @keydown to handleRowKeyDown -->
@@ -48,7 +48,10 @@ 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/grid";
51
+ import {
52
+ useGridRowAction,
53
+ type RowActionContext,
54
+ } from "@featherk/composables/grid";
52
55
 
53
56
  interface Patient {
54
57
  id: number;
@@ -82,7 +85,7 @@ const { handleRowClick, handleRowKeyDown } = useGridRowAction<Patient>({
82
85
 
83
86
  When combining `useGridA11y` (for `ArrowUp`/`ArrowDown` row focus navigation) with `useGridRowAction` (for `Enter`/`Space` row activation), delegate both handlers in a single `@keydown` listener:
84
87
 
85
- ```vue
88
+ ```html
86
89
  <template>
87
90
  <Grid
88
91
  ref="gridRef"
@@ -26,4 +26,4 @@ const menuId = ids.compose("patient", "menu");
26
26
 
27
27
  The default prefix is `fkid`, producing IDs like `fkid-v-0-patient-menu`. Pass a custom prefix when a component-specific DOM prefix improves inspection or debugging.
28
28
 
29
- `useCompositeId` is for component-scoped DOM identifiers. Keep deterministic row/trigger IDs used with `useActiveIdRegistry` consumer-owned, for example `patient:42`.
29
+ `useCompositeId` is for component-scoped DOM identifiers. Keep deterministic row/trigger IDs used with `useActiveIdRegistry` consumer-owned, for example `patient:42`.
@@ -15,7 +15,7 @@ Composable for accessible Kendo UI for Vue `Popup` + `Menu` action menus. It man
15
15
  5. Bind trigger button `@click` and `@keydown` to the composable handlers.
16
16
  6. Bind Popup `@close` and Menu `@keydown.escape` so keyboard and mouse close paths restore focus correctly.
17
17
 
18
- ```vue
18
+ ```html
19
19
  <template>
20
20
  <section ref="panelRef">
21
21
  <!-- Step 1: static aria-haspopup/aria-expanded baseline; Step 5: bind trigger handlers -->
@@ -175,9 +175,9 @@ For a complete shared-instance implementation, see [Shared Instance Menus](https
175
175
 
176
176
  `triggerMode: "row"` and `triggerMode: "button"` share the same `handleTriggerClick`/`handleTriggerKeydown`/`handleMenuSelect` code paths - opening, closing, and selection logic is identical either way. What actually differs:
177
177
 
178
- - **Anchor positioning convention (not enforced, just the natural fit).** A row spans the full grid width with no single natural corner to anchor to, and the same row can be clicked at a different point every time - so row triggers conventionally use `anchor.activationPoint` (dynamic, positioned at the click/keyboard point). A button is a small, fixed-size element with an obvious anchor corner, so button triggers conventionally use plain rect-based anchoring (`anchor: true`, or `anchor` omitted) - the menu simply drops below the button every time, in the same spot. Nothing stops flipping this (e.g. `activationPoint` on a right-click-anywhere button, or a fixed rect anchor for a row menu that should always appear in one corner) - but for a shared/registry-driven trigger, `activationPoint` always requires the consumer to call `setAnchorActivation(trigger, activation)` with real coordinates before activating (see "Cursor-Positioned Row Menus" above), regardless of which mode it's paired with. `usePopupMenu` cannot capture this automatically for a shared instance, because it only ever sees the *currently active* trigger, never the one about to become active.
178
+ - **Anchor positioning convention (not enforced, just the natural fit).** A row spans the full grid width with no single natural corner to anchor to, and the same row can be clicked at a different point every time - so row triggers conventionally use `anchor.activationPoint` (dynamic, positioned at the click/keyboard point). A button is a small, fixed-size element with an obvious anchor corner, so button triggers conventionally use plain rect-based anchoring (`anchor: true`, or `anchor` omitted) - the menu simply drops below the button every time, in the same spot. Nothing stops flipping this (e.g. `activationPoint` on a right-click-anywhere button, or a fixed rect anchor for a row menu that should always appear in one corner) - but for a shared/registry-driven trigger, `activationPoint` always requires the consumer to call `setAnchorActivation(trigger, activation)` with real coordinates before activating (see "Cursor-Positioned Row Menus" above), regardless of which mode it's paired with. `usePopupMenu` cannot capture this automatically for a shared instance, because it only ever sees the _currently active_ trigger, never the one about to become active.
179
179
  - **ARIA element matching is enforced, not just conventional.** `triggerMode: "row"` only manages an element matching `tr.k-table-row`; `triggerMode: "button"` only manages an element that does **not** match `tr.k-table-row`. If the resolved `triggerRef` doesn't match the declared mode (e.g. `triggerMode: "button"` resolves to a `tr`), the composable logs a `console.warn` and skips ARIA management entirely for that resolution, rather than silently managing the wrong element shape.
180
- - **Escape fallback target differs.** `triggerMode: "button"` defaults `escapeFocusContainerSelector` to `.k-table-row` - pressing Escape with no closer configured focus target walks up from the button to its containing row and focuses that, keeping keyboard users in the grid's row navigation instead of stranding them on a small nested cell. `triggerMode: "row"` has no such default: the trigger *is* the row already, so Escape with no configured target simply refocuses it directly - there's nothing to "walk up" to.
180
+ - **Escape fallback target differs.** `triggerMode: "button"` defaults `escapeFocusContainerSelector` to `.k-table-row` - pressing Escape with no closer configured focus target walks up from the button to its containing row and focuses that, keeping keyboard users in the grid's row navigation instead of stranding them on a small nested cell. `triggerMode: "row"` has no such default: the trigger _is_ the row already, so Escape with no configured target simply refocuses it directly - there's nothing to "walk up" to.
181
181
 
182
182
  ## Sharing One Instance Across Many Triggers (Grid-Wide)
183
183
 
@@ -395,7 +395,7 @@ const buttonMenu = usePopupMenu({
395
395
  });
396
396
  ```
397
397
 
398
- ```vue
398
+ ```html
399
399
  <Popup :show="isOpen" :offset="buttonMenu.offset.value">
400
400
  <strong v-if="activeTitle">{{ activeTitle }}</strong>
401
401
  <Menu ref="menuRef" :items="activeMenuItems" />
@@ -11,7 +11,7 @@ Use an activation-point anchor when a context menu must open near a pointer or a
11
11
  3. Capture the activation against the trigger before activating its id.
12
12
  4. Keep visibility parent-owned through the registry.
13
13
 
14
- ```vue
14
+ ```html
15
15
  <template>
16
16
  <table>
17
17
  <tbody>
@@ -408,7 +408,7 @@ const getButtonTriggerId = (
408
408
 
409
409
  Register and activate each button with that composite id:
410
410
 
411
- ```vue
411
+ ```html
412
412
  <Button
413
413
  :ref="
414
414
  (element) =>
@@ -518,7 +518,7 @@ usePopupTrap({
518
518
  ```
519
519
 
520
520
  `usePopupTrap` already defaults `allowOutsideClick` to `true` - without it, a
521
- genuinely active trap on one menu would swallow clicks on the *other* trigger
521
+ genuinely active trap on one menu would swallow clicks on the _other_ trigger
522
522
  in the same row (e.g. the row menu trapping Tab while the user tries to click
523
523
  the action button). See `usePopupMenu.md`'s "Pairing with `usePopupTrap`"
524
524
  section for why each of these options is required.
@@ -11,7 +11,7 @@ Use one `usePopupMenu` instance for many equivalent triggers when only one item
11
11
  3. Activate or deactivate ids in a consumer-owned toggle function.
12
12
  4. Render one Popup and bind its `show`, `offset`, and close handler.
13
13
 
14
- ```vue
14
+ ```html
15
15
  <template>
16
16
  <ul>
17
17
  <li v-for="record in records" :key="record.id">
@@ -16,7 +16,7 @@ Version 0.13.0 renames three returned handlers so their names apply equally to b
16
16
 
17
17
  Before:
18
18
 
19
- ```vue
19
+ ```html
20
20
  <button
21
21
  @click="menu.handleActionButtonClick"
22
22
  @keydown="menu.handleActionButtonKeydown"
@@ -26,7 +26,7 @@ Before:
26
26
 
27
27
  After:
28
28
 
29
- ```vue
29
+ ```html
30
30
  <button @click="menu.handleTriggerClick" @keydown="menu.handleTriggerKeydown" />
31
31
  <Menu @keydown.escape="menu.handleMenuEscape" />
32
32
  ```
@@ -18,7 +18,7 @@ Do not use the outer Grid component element unless it is the element that actual
18
18
 
19
19
  The composable works the same way with a regular HTML table. Place the table in a scrolling wrapper and use that wrapper as `root`; the `<tr>` is the observed target.
20
20
 
21
- ```vue
21
+ ```html
22
22
  <template>
23
23
  <div ref="tableViewportRef" class="table-viewport">
24
24
  <table>
@@ -92,4 +92,4 @@ useIntersectionObserver({
92
92
  - `1`: callback runs when the target becomes fully visible or stops being fully visible. This is the default.
93
93
  - `[0, 0.5, 1]`: callback runs when the target crosses any listed visibility boundary.
94
94
 
95
- `entry.isIntersecting` becomes `false` only after the target leaves the root entirely, regardless of the configured threshold. The composable disconnects automatically on component unmount and re-observes when `target` changes.
95
+ `entry.isIntersecting` becomes `false` only after the target leaves the root entirely, regardless of the configured threshold. The composable disconnects automatically on component unmount and re-observes when `target` changes.
@@ -15,14 +15,17 @@ This composable is designed to preserve the user's raw input: it does not coerce
15
15
 
16
16
  ## Quick Start
17
17
 
18
- ```vue
18
+ ```html
19
19
  <script setup lang="ts">
20
20
  import { ref } from "vue";
21
21
  import { MaskedTextBox } from "@progress/kendo-vue-inputs";
22
22
  import { SvgIcon } from "@progress/kendo-vue-common";
23
23
  import { Calendar, type SelectionRange } from "@progress/kendo-vue-dateinputs";
24
24
  import { Popup } from "@progress/kendo-vue-popup";
25
- import { useMaskedDateRangeInput, usePopupTrap } from "@featherk/composables/range";
25
+ import {
26
+ useMaskedDateRangeInput,
27
+ usePopupTrap,
28
+ } from "@featherk/composables/range";
26
29
 
27
30
  const showCal = ref(false);
28
31
  const dateRange = ref<SelectionRange | null | undefined>();
@@ -33,12 +36,18 @@ const ONE_YEAR = 365 * 24 * 60 * 60 * 1000;
33
36
  const MIN_DATE = new Date(Date.now() - ONE_YEAR);
34
37
  const MAX_DATE = new Date(Date.now() + ONE_YEAR);
35
38
 
36
- const openCalendar = () => { showCal.value = true; };
37
- const closeCalendar = () => { showCal.value = false; };
39
+ const openCalendar = () => {
40
+ showCal.value = true;
41
+ };
42
+ const closeCalendar = () => {
43
+ showCal.value = false;
44
+ };
38
45
 
39
46
  const range = useMaskedDateRangeInput({
40
47
  id: "date-range-input",
41
- onChange: ({ value }) => { dateRange.value = value ?? undefined; },
48
+ onChange: ({ value }) => {
49
+ dateRange.value = value ?? undefined;
50
+ },
42
51
  onShowCalendar: openCalendar,
43
52
  externalValue: dateRange,
44
53
  // Validation bounds
@@ -56,7 +65,9 @@ const range = useMaskedDateRangeInput({
56
65
  const dateRangeIcon = { name: "calendar" } as any; // use @featherk/icons getCustomIcon here
57
66
 
58
67
  const rawInput = range.raw;
59
- const clickAdornment = () => { showCal.value ? closeCalendar() : openCalendar(); };
68
+ const clickAdornment = () => {
69
+ showCal.value ? closeCalendar() : openCalendar();
70
+ };
60
71
 
61
72
  // Focus trap for the calendar popup
62
73
  usePopupTrap({
@@ -103,14 +114,15 @@ usePopupTrap({
103
114
 
104
115
  <!-- Example debug line showing validation info -->
105
116
  <div style="margin-top: 0.5rem; font-size: 0.875rem;">
106
- Span (days): {{ range.spanDays ?? '-' }}
107
- • Valid: {{ range.valid ?? '-' }}
108
- • Reason: {{ range.reason ?? '-' }}
117
+ Span (days): {{ range.spanDays ?? "-" }} • Valid: {{ range.valid ?? "-" }} •
118
+ Reason: {{ range.reason ?? "-" }}
109
119
  </div>
110
120
  </template>
111
121
 
112
122
  <style scoped>
113
- .calendar-adornment-icon { cursor: pointer; }
123
+ .calendar-adornment-icon {
124
+ cursor: pointer;
125
+ }
114
126
  </style>
115
127
  ```
116
128
 
@@ -150,7 +162,7 @@ Creates a controller for a masked date range input.
150
162
  - **`validationMessage: Readonly<ComputedRef<string>>`**: User-facing message derived from `reason`; always in sync with `validComputed`.
151
163
  - **`spanDays: Readonly<ComputedRef<number | undefined>>`**: Span in days between clamped start/end; `undefined` when incomplete or invalid.
152
164
  - Readonly computed parts: **`month1`**, **`day1`**, **`year1`**, **`month2`**, **`day2`**, **`year2`** (strings). Useful for diagnostics.
153
- - **`initStyling(): void`**: Re-applies the `fk-daterangepicker` class to the input's parent container. Exposed so consumers can re-run theming after Kendo manipulates the DOM; the function is idempotent. Use this to apply the fk-daterangepicker style hook when the date range picker isn't available on initial load
165
+ - **`initStyling(): void`**: Re-applies the `fk-daterangepicker` class to the input's parent container. Exposed so consumers can re-run theming after Kendo manipulates the DOM; the function is idempotent. Use this to apply the fk-daterangepicker style hook when the date range picker isn't available on initial load
154
166
  - Event handlers to wire to the input:
155
167
  - **`handleChange(event)`**: Assigns `raw` from the input event and emits parsed range when complete and valid.
156
168
  - **`handleKeyDown(event)`**:
@@ -203,6 +215,7 @@ Calendar coordination:
203
215
  - Provide `isOpen`, `onRequestClose`, and `closeDelay` for smooth auto-closing after calendar selection.
204
216
 
205
217
  <!-- Example moved above into Quick Start to reduce duplication. Full reference implementation: -->
218
+
206
219
  The project’s full reference implementation is in [src/components/custom-date-range-picker/CustomDateRangePicker.vue](../src/components/custom-date-range-picker/CustomDateRangePicker.vue), which includes advanced styling, focus-trap, and accessibility tweaks.
207
220
 
208
221
  ## Accessibility Notes
@@ -66,8 +66,8 @@ registers its own active-id registry and deactivates group peers before it opens
66
66
 
67
67
  ## API
68
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. |
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. |
@@ -19,7 +19,7 @@ This composable preserves the user's raw input: it does not coerce or rewrite wh
19
19
  3. Connect `usePopupTrap` to the time popup's state and wrapper.
20
20
  4. Replace the built-in input with a wrapped `MaskedTextBox`; wire input handlers and TimePicker popup actions.
21
21
 
22
- ```vue
22
+ ```html
23
23
  <template>
24
24
  <!-- Step 4: replace TimePicker's input through dateInput, not timeInput. -->
25
25
  <div ref="timePickerRef">
@@ -79,9 +79,11 @@ const maxTime = new Date(new Date().setHours(17, 0, 0, 0)); // 5:00 PM
79
79
  // Step 2: synchronize typed values and popup selections.
80
80
  const masked = useMaskedTimeInput({
81
81
  id: "startTime",
82
- onChange: ({ value }) => { selectedTime.value = value ?? undefined; },
82
+ onChange: ({ value }) => {
83
+ selectedTime.value = value ?? undefined;
84
+ },
83
85
  onShowPicker: () => (showPicker.value = true), // Space opens the time menu
84
- externalValue: selectedTime, // Mirror TimePicker selection into the mask
86
+ externalValue: selectedTime, // Mirror TimePicker selection into the mask
85
87
  minuteStep: 5,
86
88
  min: minTime,
87
89
  max: maxTime,
@@ -94,9 +96,9 @@ const {
94
96
  placeholder,
95
97
  isValid,
96
98
  validationMessage,
97
- hour24, // 24-hour format (0-23)
98
- time24, // HH:MM:SS format for database
99
- parsedRawTime, // Parsed Date object
99
+ hour24, // 24-hour format (0-23)
100
+ time24, // HH:MM:SS format for database
101
+ parsedRawTime, // Parsed Date object
100
102
  debugEnabled,
101
103
  debugLines,
102
104
  } = masked;
@@ -221,7 +223,7 @@ When replacing the built‑in input inside Kendo Vue `TimePicker`, the component
221
223
 
222
224
  Example:
223
225
 
224
- ```vue
226
+ ```html
225
227
  <TimePicker v-model="timeValue" dateInput="masked">
226
228
  <template #masked="{ props }">
227
229
  <!-- your custom MaskedTextBox here -->
@@ -17,7 +17,7 @@ Composable for managing focus trapping and close behavior for popup UIs (e.g., K
17
17
  3. Add `usePopupTrap` with `POPUP_MENU_TRAP_OPTIONS` for focus containment only.
18
18
  4. Bind the trigger, Popup, and Menu to the menu controller's handlers.
19
19
 
20
- ```vue
20
+ ```html
21
21
  <template>
22
22
  <!-- Step 4: route interactions through the menu controller. -->
23
23
  <button
@@ -50,7 +50,10 @@ import { ref, type ComponentPublicInstance } from "vue";
50
50
  import { Popup } from "@progress/kendo-vue-popup";
51
51
  import { Menu } from "@progress/kendo-vue-layout";
52
52
  import { usePopupMenu } from "@featherk/composables/menu";
53
- import { POPUP_MENU_TRAP_OPTIONS, usePopupTrap } from "@featherk/composables/trap";
53
+ import {
54
+ POPUP_MENU_TRAP_OPTIONS,
55
+ usePopupTrap,
56
+ } from "@featherk/composables/trap";
54
57
 
55
58
  // Step 1: create consumer-owned visibility and the DOM/component refs.
56
59
  const open = ref(false);
@@ -64,8 +67,12 @@ const popupMenu = usePopupMenu({
64
67
  triggerRef,
65
68
  menuRef,
66
69
  triggerMode: "button",
67
- requestShow: () => { open.value = true; },
68
- requestHide: () => { open.value = false; },
70
+ requestShow: () => {
71
+ open.value = true;
72
+ },
73
+ requestHide: () => {
74
+ open.value = false;
75
+ },
69
76
  });
70
77
 
71
78
  // Step 3: preserve menu focus and delegate dismissal through the preset.
@@ -91,7 +98,7 @@ trap: the menu's handlers remain the dismissal owner.
91
98
  3. Connect `usePopupTrap` to the visibility and trigger refs.
92
99
  4. Replace the DatePicker input with a wrapped `MaskedTextBox` and wire its handlers.
93
100
 
94
- ```vue
101
+ ```html
95
102
  <template>
96
103
  <!-- Step 4: use the controller's custom input, not Kendo's built-in DateInput. -->
97
104
  <div ref="datePickerRef">
@@ -141,8 +148,12 @@ const datePickerRef = shallowRef<HTMLElement | null>(null);
141
148
  const masked = useMaskedDateInput({
142
149
  id: "popup-date",
143
150
  externalValue: selectedDate,
144
- onChange: ({ value }) => { selectedDate.value = value ?? undefined; },
145
- onShowCalendar: () => { open.value = true; },
151
+ onChange: ({ value }) => {
152
+ selectedDate.value = value ?? undefined;
153
+ },
154
+ onShowCalendar: () => {
155
+ open.value = true;
156
+ },
146
157
  });
147
158
  const { raw, placeholder, isValid, validationMessage } = masked;
148
159
 
@@ -207,7 +218,9 @@ usePopupTrap({
207
218
  usePopupTrap({
208
219
  isOpen: showPicker,
209
220
  triggerEl: datePickerRef,
210
- onRequestClose: () => { showPicker.value = false; },
221
+ onRequestClose: () => {
222
+ showPicker.value = false;
223
+ },
211
224
  });
212
225
  ```
213
226
 
@@ -227,7 +240,9 @@ child trap first so its ref can be passed directly:
227
240
  const calendarTrap = usePopupTrap({
228
241
  isOpen: showPicker,
229
242
  triggerEl: datePickerRef,
230
- onRequestClose: () => { showPicker.value = false; },
243
+ onRequestClose: () => {
244
+ showPicker.value = false;
245
+ },
231
246
  });
232
247
 
233
248
  usePopupTrap({
@@ -254,7 +269,7 @@ children still need their own working focus containment and keyboard behavior.
254
269
  3. Connect `usePopupTrap` using the TimePicker popup selectors.
255
270
  4. Render the wrapped `MaskedTextBox` in the `dateInput` slot and wire both input and picker changes.
256
271
 
257
- ```vue
272
+ ```html
258
273
  <template>
259
274
  <!-- Step 4: TimePicker uses dateInput, not timeInput, for its custom input. -->
260
275
  <div ref="timePickerRef">
@@ -307,8 +322,12 @@ const timePickerRef = ref<HTMLElement | null>(null);
307
322
  const masked = useMaskedTimeInput({
308
323
  id: "popup-time",
309
324
  externalValue: selectedTime,
310
- onChange: ({ value }) => { selectedTime.value = value ?? undefined; },
311
- onShowPicker: () => { showPicker.value = true; },
325
+ onChange: ({ value }) => {
326
+ selectedTime.value = value ?? undefined;
327
+ },
328
+ onShowPicker: () => {
329
+ showPicker.value = true;
330
+ },
312
331
  minuteStep: 5,
313
332
  });
314
333
  const { raw, rules, placeholder, isValid, validationMessage } = masked;
@@ -368,8 +387,13 @@ The presets and `PopupTrapOutsideClickOptions` are exported from both
368
387
  import type { OnClickOutsideOptions } from "@vueuse/core";
369
388
 
370
389
  export type CloseReason = "escape" | "outside";
371
- export type PopupTrapOutsideClickOptions = Pick<OnClickOutsideOptions, "ignore">;
372
- export type InitialFocus = string | ((root: HTMLElement) => HTMLElement | null | undefined);
390
+ export type PopupTrapOutsideClickOptions = Pick<
391
+ OnClickOutsideOptions,
392
+ "ignore"
393
+ >;
394
+ export type InitialFocus =
395
+ | string
396
+ | ((root: HTMLElement) => HTMLElement | null | undefined);
373
397
  ```
374
398
 
375
399
  ## Behavior Details
@@ -379,7 +403,7 @@ export type InitialFocus = string | ((root: HTMLElement) => HTMLElement | null |
379
403
  - `.k-popup`
380
404
  - `.k-timepicker-popup`
381
405
  - `.k-menu-popup`
382
- - **Focus trap**: Uses `useFocusTrap(popupRef)` with a fallback/initial focus inside the popup. `escapeDeactivates` and `clickOutsideDeactivates` are disabled; closing is managed explicitly. `allowOutsideClick` defaults to `true` - without it, `focus-trap`'s own default (`false`) `preventDefault()`s and swallows *every* outside pointerdown/click while the trap is active, not just ones that would close it, blocking interaction with anything else on the page (e.g. a second trigger in the same grid row) until the trap deactivates. Override via `focusTrapOptions` only if a specific popup genuinely needs that stricter modal-style behavior.
406
+ - **Focus trap**: Uses `useFocusTrap(popupRef)` with a fallback/initial focus inside the popup. `escapeDeactivates` and `clickOutsideDeactivates` are disabled; closing is managed explicitly. `allowOutsideClick` defaults to `true` - without it, `focus-trap`'s own default (`false`) `preventDefault()`s and swallows _every_ outside pointerdown/click while the trap is active, not just ones that would close it, blocking interaction with anything else on the page (e.g. a second trigger in the same grid row) until the trap deactivates. Override via `focusTrapOptions` only if a specific popup genuinely needs that stricter modal-style behavior.
383
407
  - **Escape to close**: Adds a capture-phase `keydown` listener on `document` when opened; pressing Escape calls `onRequestClose('escape', event)`. The composable does not stop propagation; consumers retain ownership of nested Escape policy.
384
408
  - **Outside click to close**: Unless disabled, uses `onClickOutside(popupRef, ...)` with VueUse's capture default to request closure when open. There should be one outside-dismissal owner per popup. Focus-trap stacking pauses containment, not this independent listener.
385
409
  - **Separate policies**: `allowOutsideClick` permits interactions and `clickOutsideDeactivates` controls containment; neither configures FeatherK's independent close requests. Do not set `allowOutsideClick: false` to prevent dialog dismissal: it can block teleported child interactions. Modal background isolation remains the dialog component's responsibility.
@@ -13,7 +13,7 @@ Composable for a zero/low-config Kendo `Window`: viewport-clamped dragging and r
13
13
  3. Wire a trigger (e.g. a Button) to `open()`.
14
14
  4. Bind `<Window>` via `v-if`, `ref`, `v-bind`, and `v-on` - never wire individual Window props by hand.
15
15
 
16
- ```vue
16
+ ```html
17
17
  <template>
18
18
  <!-- Step 3: trigger opens the Window using its defaults -->
19
19
  <Button @click="open">Open Window</Button>
@@ -118,7 +118,7 @@ maximize button does not automatically disable titlebar double-click behavior.
118
118
 
119
119
  Keep binding `v-on="events"` on `<Window>` so viewport clamping (drag and resize), stage handling, and Kendo workarounds remain active:
120
120
 
121
- ```vue
121
+ ```html
122
122
  <Window
123
123
  v-if="win.visible.value"
124
124
  ref="windowRef"
@@ -133,16 +133,16 @@ Do not replace entries on `events` unless you intentionally opt out of the corre
133
133
 
134
134
  ### `UseWindowOptions`
135
135
 
136
- | Option | Type | Description |
137
- | --------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
138
- | `settings` | `Partial<WindowSettings> \| Ref<Partial<WindowSettings>>` | Overrides merged over `WINDOW_SETTINGS_DEFAULTS` internally. Pass a `Ref` for externally-owned/persisted settings. |
139
- | `initialStage` | `WindowStage` | Initial `stage` value. Defaults to `"DEFAULT"`. |
140
- | `titlebarDoubleClick` | `MaybeRefOrGetter<boolean>` | Enables titlebar double-click stage changes. Defaults to `true`; pass `false` or a reactive ref/getter returning `false` to disable both maximize and restore transitions. |
141
- | `onMove` | `(payload) => void` | Called after internal move handling. |
142
- | `onResize` | `(payload) => void` | Called after internal resize handling, with the viewport-clamped `left`/`top`/`width`/`height`. |
143
- | `onStageChange` | `(stage) => void` | Called after a valid stage transition. |
144
- | `onOpen` | `() => void` | Called after `open()` finishes sizing the Window. |
145
- | `onClose` | `() => void` | Called after `close()` hides the Window. |
136
+ | Option | Type | Description |
137
+ | --------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138
+ | `settings` | `Partial<WindowSettings> \| Ref<Partial<WindowSettings>>` | Overrides merged over `WINDOW_SETTINGS_DEFAULTS` internally. Pass a `Ref` for externally-owned/persisted settings. |
139
+ | `initialStage` | `WindowStage` | Initial `stage` value. Defaults to `"DEFAULT"`. |
140
+ | `titlebarDoubleClick` | `MaybeRefOrGetter<boolean>` | Enables titlebar double-click stage changes. Defaults to `true`; pass `false` or a reactive ref/getter returning `false` to disable both maximize and restore transitions. |
141
+ | `onMove` | `(payload) => void` | Called after internal move handling. |
142
+ | `onResize` | `(payload) => void` | Called after internal resize handling, with the viewport-clamped `left`/`top`/`width`/`height`. |
143
+ | `onStageChange` | `(stage) => void` | Called after a valid stage transition. |
144
+ | `onOpen` | `() => void` | Called after `open()` finishes sizing the Window. |
145
+ | `onClose` | `() => void` | Called after `close()` hides the Window. |
146
146
 
147
147
  ### `UseWindowReturn`
148
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/composables",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "main": "dist/featherk-composables.umd.js",
5
5
  "module": "dist/featherk-composables.es.js",
6
6
  "types": "dist/index.d.ts",