@featherk/composables 0.15.8 → 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.
- package/dist/docs-manifest.js +24 -24
- package/docs/address/useUSAddress.md +1 -1
- package/docs/address/useZipTextBox.md +35 -35
- package/docs/date/useMaskedDateInput.md +13 -5
- package/docs/form/useFieldsetTouchTracker.md +10 -10
- package/docs/form/useFieldsetValidationKit.md +14 -14
- package/docs/grid/useGridA11y.md +20 -31
- package/docs/grid/useGridActiveFilter.md +5 -5
- package/docs/grid/useGridRowAction.md +6 -3
- package/docs/id/useCompositeId.md +1 -1
- package/docs/menu/usePopupMenu.md +4 -4
- package/docs/menu/usePopupMenuActivationAnchor.md +1 -1
- package/docs/menu/usePopupMenuGridOrchestration.md +2 -2
- package/docs/menu/usePopupMenuSharedInstance.md +1 -1
- package/docs/menu/usePopupMenuUpgrade0130.md +2 -2
- package/docs/observer/useIntersectionObserver.md +2 -2
- package/docs/range/useMaskedDateRangeInput.md +24 -11
- package/docs/registry/useExclusiveGroup.md +5 -5
- package/docs/time/useMaskedTimeInput.md +9 -7
- package/docs/trap/usePopupTrap.md +39 -15
- package/docs/window/useWindow.md +12 -12
- package/package.json +1 -1
package/dist/docs-manifest.js
CHANGED
|
@@ -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.
|
|
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.
|
|
34
|
-
useCompositeId: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
35
|
-
useExclusiveGroup: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
36
|
-
useFieldsetTouchTracker: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
37
|
-
useFieldsetValidationKit: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
38
|
-
useGridA11y: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
39
|
-
useGridActiveFilter: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
40
|
-
useGridRowAction: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
41
|
-
useIntersectionObserver: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
42
|
-
useMaskedDateInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
43
|
-
useMaskedDateRangeInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
44
|
-
useMaskedTimeInput: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
45
|
-
usePopupMenu: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
46
|
-
usePopupMenuActivationAnchor: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
47
|
-
usePopupMenuAndActiveIdRegistry: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
48
|
-
usePopupMenuFocusAndExclusivity: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
49
|
-
usePopupMenuGridOrchestration: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
50
|
-
usePopupMenuSharedInstance: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
51
|
-
usePopupMenuUpgrade0130: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
52
|
-
usePopupTrap: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
53
|
-
useUSAddress: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
54
|
-
useWindow: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
55
|
-
useZipTextBox: "https://cdn.jsdelivr.net/npm/@featherk/composables@0.
|
|
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") =>
|
|
@@ -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
|
-
```
|
|
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",
|
|
38
|
-
initialValue: "",
|
|
39
|
-
errorMessage: "Enter a valid ZIP code.",
|
|
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
|
|
69
|
-
|
|
70
|
-
| `id`
|
|
71
|
-
| `initialValue` | `string` | No
|
|
72
|
-
| `errorMessage` | `string` | No
|
|
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
|
|
79
|
-
|
|
80
|
-
| `zipDisplay`
|
|
81
|
-
| `submitted`
|
|
82
|
-
| `errorMessage` | `Ref<string>`
|
|
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
|
|
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
|
|
93
|
-
|
|
94
|
-
| `zipRaw`
|
|
95
|
-
| `isZipValid`
|
|
96
|
-
| `showZipError`
|
|
97
|
-
| `textBoxValid`
|
|
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
|
|
103
|
-
|
|
104
|
-
| `handleZipInput`
|
|
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`
|
|
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;
|
|
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
|
|
140
|
-
|
|
141
|
-
| `""`
|
|
142
|
-
| `"1234"`
|
|
143
|
-
| `"12345"`
|
|
144
|
-
| `"123456"`
|
|
145
|
-
| `"123456789"`
|
|
146
|
-
| `"12345-6789"`
|
|
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
|
-
```
|
|
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 }) => {
|
|
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 {
|
|
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.
|
|
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
|
-
```
|
|
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
|
|
47
|
-
|
|
48
|
-
| `fieldsetRef`
|
|
49
|
-
| `options.isValid` | `() => boolean`
|
|
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
|
|
54
|
-
|
|
55
|
-
| `touched`
|
|
56
|
-
| `onFocusout` | `(event: FocusEvent) => void` | Focusout handler to bind on the fieldset.
|
|
57
|
-
| `reset`
|
|
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
|
-
```
|
|
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
|
|
119
|
-
|
|
120
|
-
| `submitted` | `MaybeRefOrGetter<boolean>` | Yes
|
|
121
|
-
| `isValid`
|
|
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
|
|
126
|
-
|
|
127
|
-
| `touched`
|
|
128
|
-
| `fieldsetValidate`
|
|
129
|
-
| `onFocusout`
|
|
130
|
-
| `reset`
|
|
131
|
-
| `createFieldValidity`
|
|
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`
|
|
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
|
|
package/docs/grid/useGridA11y.md
CHANGED
|
@@ -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
|
|
31
|
-
import { useGridA11y } from
|
|
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();
|
|
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,
|
|
91
|
-
return h(
|
|
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
|
|
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
|
|
141
|
-
import { useGridA11y } from
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
```
|
|
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 {
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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 {
|
|
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 = () => {
|
|
37
|
-
|
|
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 }) => {
|
|
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 = () => {
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
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
|
|
70
|
-
|
|
|
71
|
-
| `register(member)`
|
|
72
|
-
| `deactivateOthers(activeMember)` | Deactivates all registered members except `activeMember`.
|
|
73
|
-
| `deactivateAll()`
|
|
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
|
-
```
|
|
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 }) => {
|
|
82
|
+
onChange: ({ value }) => {
|
|
83
|
+
selectedTime.value = value ?? undefined;
|
|
84
|
+
},
|
|
83
85
|
onShowPicker: () => (showPicker.value = true), // Space opens the time menu
|
|
84
|
-
externalValue: selectedTime,
|
|
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,
|
|
98
|
-
time24,
|
|
99
|
-
parsedRawTime,
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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 {
|
|
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: () => {
|
|
68
|
-
|
|
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
|
-
```
|
|
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 }) => {
|
|
145
|
-
|
|
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: () => {
|
|
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: () => {
|
|
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
|
-
```
|
|
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 }) => {
|
|
311
|
-
|
|
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<
|
|
372
|
-
|
|
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
|
|
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.
|
package/docs/window/useWindow.md
CHANGED
|
@@ -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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
137
|
-
|
|
|
138
|
-
| `settings`
|
|
139
|
-
| `initialStage`
|
|
140
|
-
| `titlebarDoubleClick` | `MaybeRefOrGetter<boolean>`
|
|
141
|
-
| `onMove`
|
|
142
|
-
| `onResize`
|
|
143
|
-
| `onStageChange`
|
|
144
|
-
| `onOpen`
|
|
145
|
-
| `onClose`
|
|
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
|
|