@xy-planning-network/trees 0.10.4 → 0.10.6-rc1
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/style.css +1 -1
- package/dist/trees.es.js +4085 -4179
- package/dist/trees.umd.js +10 -10
- package/package.json +5 -5
- package/src/lib-components/forms/BaseInput.vue +18 -29
- package/src/lib-components/forms/Checkbox.vue +8 -9
- package/src/lib-components/forms/DateRangePicker.vue +21 -22
- package/src/lib-components/forms/DateTimeInput.vue +14 -24
- package/src/lib-components/forms/MultiCheckboxes.vue +39 -44
- package/src/lib-components/forms/Radio.vue +8 -11
- package/src/lib-components/forms/RadioCards.vue +8 -12
- package/src/lib-components/forms/Select.vue +10 -26
- package/src/lib-components/forms/TextArea.vue +11 -17
- package/src/lib-components/forms/Toggle.vue +6 -7
- package/src/lib-components/forms/YesOrNoRadio.vue +10 -12
- package/src/lib-components/layout/DateFilter.vue +8 -4
- package/src/lib-components/lists/DynamicTable.vue +7 -8
- package/src/lib-components/navigation/ActionsDropdown.vue +2 -0
- package/src/lib-components/navigation/Paginator.vue +17 -20
- package/src/lib-components/navigation/Tabs.vue +6 -18
- package/src/lib-components/overlays/ContentModal.vue +16 -10
- package/src/lib-components/overlays/Modal.vue +14 -11
- package/src/lib-components/overlays/Popover/Popover.vue +2 -0
- package/src/lib-components/overlays/Slideover.vue +13 -15
- package/types/composables/forms.d.ts +16 -2
- package/types/composables/nav.d.ts +1 -1
- package/types/composables/useActionItems.d.ts +1 -1
- package/types/composables/useFlashes.d.ts +1 -1
- package/types/composables/useSpinner.d.ts +6 -6
- package/types/composables/useTable.d.ts +1 -1
- package/types/entry.d.ts +0 -2
- package/types/helpers/Slots.d.ts +2 -0
- package/types/lib-components/forms/BaseInput.vue.d.ts +13 -36
- package/types/lib-components/forms/Checkbox.vue.d.ts +10 -35
- package/types/lib-components/forms/DateRangePicker.vue.d.ts +15 -38
- package/types/lib-components/forms/DateTimeInput.vue.d.ts +10 -35
- package/types/lib-components/forms/FieldsetLegend.vue.d.ts +4 -29
- package/types/lib-components/forms/InputError.vue.d.ts +4 -25
- package/types/lib-components/forms/InputHelp.vue.d.ts +4 -27
- package/types/lib-components/forms/InputLabel.vue.d.ts +4 -29
- package/types/lib-components/forms/MultiCheckboxes.vue.d.ts +13 -36
- package/types/lib-components/forms/Radio.vue.d.ts +10 -35
- package/types/lib-components/forms/RadioCards.vue.d.ts +16 -13
- package/types/lib-components/forms/Select.vue.d.ts +10 -35
- package/types/lib-components/forms/TextArea.vue.d.ts +10 -35
- package/types/lib-components/forms/Toggle.vue.d.ts +12 -36
- package/types/lib-components/forms/YesOrNoRadio.vue.d.ts +10 -35
- package/types/lib-components/indicators/InlineAlert.vue.d.ts +12 -43
- package/types/lib-components/indicators/ProgressCircles.vue.d.ts +3 -12
- package/types/lib-components/indicators/ProgressCirclesLabeled.vue.d.ts +3 -12
- package/types/lib-components/indicators/XYSpinner.vue.d.ts +1 -1
- package/types/lib-components/layout/DateFilter.vue.d.ts +7 -16
- package/types/lib-components/layout/SidebarLayout.vue.d.ts +4 -25
- package/types/lib-components/layout/StackedLayout.vue.d.ts +4 -25
- package/types/lib-components/lists/Cards.vue.d.ts +3 -12
- package/types/lib-components/lists/DataTable.vue.d.ts +4 -31
- package/types/lib-components/lists/DetailList.vue.d.ts +5 -36
- package/types/lib-components/lists/DownloadCell.vue.d.ts +3 -12
- package/types/lib-components/lists/DynamicTable.vue.d.ts +8 -35
- package/types/lib-components/lists/StaticTable.vue.d.ts +21 -0
- package/types/lib-components/lists/StaticTableActionSlot.vue.d.ts +27 -0
- package/types/lib-components/lists/Table.vue.d.ts +39 -0
- package/types/lib-components/lists/TableActionButtons.vue.d.ts +4 -25
- package/types/lib-components/navigation/ActionsDropdown.vue.d.ts +4 -26
- package/types/lib-components/navigation/ActionsDropdownCallback.vue.d.ts +18 -0
- package/types/lib-components/navigation/ActionsDropdownEmit.vue.d.ts +22 -0
- package/types/lib-components/navigation/Paginator.vue.d.ts +7 -17
- package/types/lib-components/navigation/Steps.vue.d.ts +6 -33
- package/types/lib-components/navigation/Tabs.vue.d.ts +12 -37
- package/types/lib-components/overlays/AlertModal.vue.d.ts +68 -0
- package/types/lib-components/overlays/ContentModal.vue.d.ts +15 -31
- package/types/lib-components/overlays/Flash.vue.d.ts +1 -1
- package/types/lib-components/overlays/Modal.vue.d.ts +19 -49
- package/types/lib-components/overlays/Popover/Popover.vue.d.ts +4 -27
- package/types/lib-components/overlays/Popover/PopoverContent.vue.d.ts +1 -1
- package/types/lib-components/overlays/Slideover.vue.d.ts +12 -21
- package/types/lib-components/overlays/Spinner.vue.d.ts +1 -1
- package/types/lib-components/overlays/Tooltip.vue.d.ts +4 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xy-planning-network/trees",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6-rc1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "github:xy-planning-network/trees",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@tailwindcss/forms": "^0.5.3",
|
|
39
39
|
"@tailwindcss/typography": "^0.5.7",
|
|
40
40
|
"@types/node": "^18.15.11",
|
|
41
|
-
"@vitejs/plugin-vue": "^
|
|
41
|
+
"@vitejs/plugin-vue": "^5.1.4",
|
|
42
42
|
"@vue/eslint-config-prettier": "~8.0.0",
|
|
43
43
|
"@vue/eslint-config-typescript": "~13.0.0",
|
|
44
44
|
"autoprefixer": "^10.4.14",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"typescript": "~5.4.5",
|
|
54
54
|
"unplugin-vue-components": "^0.27.4",
|
|
55
55
|
"unplugin-vue-markdown": "^0.25.2",
|
|
56
|
-
"vite": "^4.
|
|
57
|
-
"vue-tsc": "^2.
|
|
56
|
+
"vite": "^5.4.10",
|
|
57
|
+
"vue-tsc": "^2.1.6"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@floating-ui/vue": "^1.0.1",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"autoprefixer": "^10.4.7",
|
|
70
70
|
"postcss": "^8.4.14",
|
|
71
71
|
"tailwindcss": "^3.1.4",
|
|
72
|
-
"vue": "^3.
|
|
72
|
+
"vue": "^3.5.12"
|
|
73
73
|
},
|
|
74
74
|
"engines": {
|
|
75
75
|
"node": ">=20"
|
|
@@ -5,29 +5,34 @@ import InputError from "./InputError.vue"
|
|
|
5
5
|
import {
|
|
6
6
|
useInputField,
|
|
7
7
|
defaultInputProps,
|
|
8
|
+
defaultModelOpts,
|
|
8
9
|
emailPattern,
|
|
9
10
|
looseToNumber,
|
|
10
11
|
phonePattern,
|
|
11
12
|
} from "@/composables/forms"
|
|
12
13
|
import type { TextLikeInput } from "@/composables/forms"
|
|
13
|
-
import { computed,
|
|
14
|
+
import { computed, useTemplateRef } from "vue"
|
|
14
15
|
|
|
15
16
|
defineOptions({
|
|
16
17
|
inheritAttrs: false,
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
const props = withDefaults(defineProps<TextLikeInput>(), defaultInputProps)
|
|
21
|
+
const modelState = defineModel<TextLikeInput["modelValue"]>({
|
|
22
|
+
...defaultModelOpts,
|
|
23
|
+
set: (v) => {
|
|
24
|
+
if (props.type === "number") {
|
|
25
|
+
return looseToNumber(v)
|
|
26
|
+
}
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
inputValidation,
|
|
30
|
-
} = useInputField(props)
|
|
28
|
+
if (typeof v === "string") {
|
|
29
|
+
return v.trim()
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
const input = useTemplateRef("input")
|
|
34
|
+
const { errorState, inputID, isRequired, onInvalid, inputValidation } =
|
|
35
|
+
useInputField(props)
|
|
31
36
|
|
|
32
37
|
// A wrapper component may need to have direct access
|
|
33
38
|
// to the underlying HTMLInputElement that BaseInput binds to
|
|
@@ -53,22 +58,6 @@ const typeAttributes = computed(() => {
|
|
|
53
58
|
return {}
|
|
54
59
|
}
|
|
55
60
|
})
|
|
56
|
-
|
|
57
|
-
const onInput = (e: Event) => {
|
|
58
|
-
let val = (e.target as HTMLInputElement).value
|
|
59
|
-
|
|
60
|
-
if (props.type === "number") {
|
|
61
|
-
val = looseToNumber(val)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (typeof val === "string") {
|
|
65
|
-
val = val.trim()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
modelState.value = val
|
|
69
|
-
|
|
70
|
-
inputValidation(e)
|
|
71
|
-
}
|
|
72
61
|
</script>
|
|
73
62
|
|
|
74
63
|
<template>
|
|
@@ -83,6 +72,7 @@ const onInput = (e: Event) => {
|
|
|
83
72
|
<input
|
|
84
73
|
:id="inputID"
|
|
85
74
|
ref="input"
|
|
75
|
+
v-model="modelState"
|
|
86
76
|
:aria-labelledby="label ? `${inputID}-label` : undefined"
|
|
87
77
|
:aria-describedby="help ? `${inputID}-help` : undefined"
|
|
88
78
|
:aria-errormessage="errorState ? `${inputID}-error` : undefined"
|
|
@@ -95,9 +85,8 @@ const onInput = (e: Event) => {
|
|
|
95
85
|
]"
|
|
96
86
|
:placeholder="placeholder"
|
|
97
87
|
:type="type"
|
|
98
|
-
:value="modelState"
|
|
99
88
|
v-bind="{ ...typeAttributes, ...$attrs }"
|
|
100
|
-
@input="
|
|
89
|
+
@input="inputValidation"
|
|
101
90
|
@invalid="onInvalid"
|
|
102
91
|
/>
|
|
103
92
|
<InputHelp :id="`${inputID}-help`" class="mt-1" :text="help" />
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import InputLabel from "./InputLabel.vue"
|
|
3
3
|
import InputHelp from "./InputHelp.vue"
|
|
4
4
|
import InputError from "./InputError.vue"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
useInputField,
|
|
7
|
+
defaultInputProps,
|
|
8
|
+
defaultModelOpts,
|
|
9
|
+
} from "@/composables/forms"
|
|
6
10
|
import type { BooleanInput } from "@/composables/forms"
|
|
7
11
|
|
|
8
12
|
defineOptions({
|
|
@@ -10,23 +14,17 @@ defineOptions({
|
|
|
10
14
|
})
|
|
11
15
|
|
|
12
16
|
const props = withDefaults(defineProps<BooleanInput>(), defaultInputProps)
|
|
17
|
+
const modelState = defineModel<BooleanInput["modelValue"]>(defaultModelOpts)
|
|
13
18
|
|
|
14
|
-
defineEmits(["update:modelValue", "update:error"])
|
|
15
19
|
const {
|
|
16
20
|
aria,
|
|
17
21
|
inputID,
|
|
18
22
|
isDisabled,
|
|
19
23
|
isRequired,
|
|
20
24
|
errorState,
|
|
21
|
-
modelState,
|
|
22
25
|
validate,
|
|
23
26
|
onInvalid,
|
|
24
27
|
} = useInputField(props)
|
|
25
|
-
|
|
26
|
-
const onChange = (e: Event) => {
|
|
27
|
-
modelState.value = (e.target as HTMLInputElement).checked
|
|
28
|
-
validate(e)
|
|
29
|
-
}
|
|
30
28
|
</script>
|
|
31
29
|
|
|
32
30
|
<template>
|
|
@@ -34,6 +32,7 @@ const onChange = (e: Event) => {
|
|
|
34
32
|
<div class="flex items-center h-5">
|
|
35
33
|
<input
|
|
36
34
|
:id="inputID"
|
|
35
|
+
v-model="modelState"
|
|
37
36
|
:aria-labelledby="aria.labelledby"
|
|
38
37
|
:aria-describedby="aria.describedby"
|
|
39
38
|
:aria-errormessage="aria.errormessage"
|
|
@@ -48,7 +47,7 @@ const onChange = (e: Event) => {
|
|
|
48
47
|
]"
|
|
49
48
|
type="checkbox"
|
|
50
49
|
v-bind="$attrs"
|
|
51
|
-
@change="
|
|
50
|
+
@change="validate"
|
|
52
51
|
@invalid="onInvalid"
|
|
53
52
|
/>
|
|
54
53
|
</div>
|
|
@@ -5,7 +5,11 @@ import InputError from "./InputError.vue"
|
|
|
5
5
|
import flatpickr from "flatpickr"
|
|
6
6
|
import "flatpickr/dist/flatpickr.min.css"
|
|
7
7
|
import { onMounted } from "vue"
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
defaultInputProps,
|
|
10
|
+
defaultModelOpts,
|
|
11
|
+
useInputField,
|
|
12
|
+
} from "@/composables/forms"
|
|
9
13
|
import type { DateRangeInput } from "@/composables/forms"
|
|
10
14
|
|
|
11
15
|
defineOptions({
|
|
@@ -15,27 +19,18 @@ defineOptions({
|
|
|
15
19
|
const props = withDefaults(defineProps<DateRangeInput>(), {
|
|
16
20
|
...defaultInputProps,
|
|
17
21
|
maxRange: 0,
|
|
18
|
-
modelValue: () => {
|
|
19
|
-
return {
|
|
20
|
-
maxDate: 0,
|
|
21
|
-
minDate: 0,
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
22
|
placeholder: "mm-dd-yyyy range",
|
|
25
23
|
startDate: 0,
|
|
26
24
|
})
|
|
25
|
+
const modelState = defineModel<DateRangeInput["modelValue"]>({
|
|
26
|
+
...defaultModelOpts,
|
|
27
|
+
default: { maxDate: 0, minDate: 0 },
|
|
28
|
+
})
|
|
27
29
|
|
|
28
|
-
const {
|
|
29
|
-
|
|
30
|
-
errorState,
|
|
31
|
-
modelState,
|
|
32
|
-
inputID,
|
|
33
|
-
isRequired,
|
|
34
|
-
onInvalid,
|
|
35
|
-
validate,
|
|
36
|
-
} = useInputField(props)
|
|
30
|
+
const { aria, errorState, inputID, isRequired, onInvalid, validate } =
|
|
31
|
+
useInputField(props)
|
|
37
32
|
|
|
38
|
-
const
|
|
33
|
+
const updateModelState = (value: { minDate: number; maxDate: number }) => {
|
|
39
34
|
modelState.value = value
|
|
40
35
|
}
|
|
41
36
|
|
|
@@ -48,14 +43,14 @@ onMounted(() => {
|
|
|
48
43
|
minDate: props.startDate,
|
|
49
44
|
onClose: (selectedDates) => {
|
|
50
45
|
if (selectedDates.length === 2) {
|
|
51
|
-
|
|
46
|
+
updateModelState({
|
|
52
47
|
minDate: selectedDates[0].setUTCHours(0, 0, 0, 0) / 1000,
|
|
53
48
|
maxDate: Math.floor(
|
|
54
49
|
selectedDates[1].setUTCHours(23, 59, 59, 999) / 1000
|
|
55
50
|
),
|
|
56
51
|
})
|
|
57
52
|
} else if (selectedDates.length === 0) {
|
|
58
|
-
|
|
53
|
+
updateModelState({
|
|
59
54
|
minDate: 0,
|
|
60
55
|
maxDate: 0,
|
|
61
56
|
})
|
|
@@ -64,10 +59,14 @@ onMounted(() => {
|
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
// Handle initial values if set
|
|
67
|
-
if (
|
|
62
|
+
if (
|
|
63
|
+
modelState.value && // NOTE(spk): even with a "default" value this may be a literal null
|
|
64
|
+
modelState.value.minDate != 0 &&
|
|
65
|
+
modelState.value.maxDate != 0
|
|
66
|
+
) {
|
|
68
67
|
opts.defaultDate = [
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
modelState.value.minDate * 1000,
|
|
69
|
+
modelState.value.maxDate * 1000,
|
|
71
70
|
]
|
|
72
71
|
}
|
|
73
72
|
|
|
@@ -6,36 +6,27 @@ import {
|
|
|
6
6
|
useInputField,
|
|
7
7
|
defaultInputProps,
|
|
8
8
|
toDatetimeLocal,
|
|
9
|
+
defaultModelOpts,
|
|
9
10
|
} from "@/composables/forms"
|
|
10
11
|
import type { DateTimeInput } from "@/composables/forms"
|
|
11
|
-
import { computed, ref } from "vue"
|
|
12
12
|
|
|
13
13
|
defineOptions({
|
|
14
14
|
inheritAttrs: false,
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const props = withDefaults(defineProps<DateTimeInput>(), defaultInputProps)
|
|
18
|
+
const modelState = defineModel<DateTimeInput["modelValue"]>({
|
|
19
|
+
...defaultModelOpts,
|
|
20
|
+
get: (v) => {
|
|
21
|
+
return v ? toDatetimeLocal(v) : v
|
|
22
|
+
},
|
|
23
|
+
set: (v) => {
|
|
24
|
+
return v ? new Date(v).toISOString() : v
|
|
25
|
+
},
|
|
26
|
+
})
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
errorState,
|
|
23
|
-
modelState,
|
|
24
|
-
inputID,
|
|
25
|
-
isRequired,
|
|
26
|
-
onInvalid,
|
|
27
|
-
inputValidation,
|
|
28
|
-
} = useInputField(props)
|
|
29
|
-
|
|
30
|
-
const inputValue = computed(() => toDatetimeLocal(modelState.value))
|
|
31
|
-
|
|
32
|
-
const onInput = (e: Event) => {
|
|
33
|
-
let val = (e.target as HTMLInputElement).value
|
|
34
|
-
|
|
35
|
-
modelState.value = val ? new Date(val).toISOString() : val
|
|
36
|
-
|
|
37
|
-
inputValidation(e)
|
|
38
|
-
}
|
|
28
|
+
const { errorState, inputID, isRequired, onInvalid, inputValidation } =
|
|
29
|
+
useInputField(props)
|
|
39
30
|
</script>
|
|
40
31
|
|
|
41
32
|
<template>
|
|
@@ -49,7 +40,7 @@ const onInput = (e: Event) => {
|
|
|
49
40
|
/>
|
|
50
41
|
<input
|
|
51
42
|
:id="inputID"
|
|
52
|
-
|
|
43
|
+
v-model="modelState"
|
|
53
44
|
:aria-labelledby="label ? `${inputID}-label` : undefined"
|
|
54
45
|
:aria-describedby="help ? `${inputID}-help` : undefined"
|
|
55
46
|
:aria-errormessage="errorState ? `${inputID}-error` : undefined"
|
|
@@ -62,9 +53,8 @@ const onInput = (e: Event) => {
|
|
|
62
53
|
]"
|
|
63
54
|
:placeholder="placeholder"
|
|
64
55
|
type="datetime-local"
|
|
65
|
-
:value="inputValue"
|
|
66
56
|
v-bind="$attrs"
|
|
67
|
-
@input="
|
|
57
|
+
@input="inputValidation"
|
|
68
58
|
@invalid="onInvalid"
|
|
69
59
|
/>
|
|
70
60
|
<InputHelp :id="`${inputID}-help`" class="mt-1" :text="help" />
|
|
@@ -4,8 +4,12 @@ import InputLabel from "./InputLabel.vue"
|
|
|
4
4
|
import InputHelp from "./InputHelp.vue"
|
|
5
5
|
import InputError from "./InputError.vue"
|
|
6
6
|
import { useInputField, defaultInputProps } from "@/composables/forms"
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
MultiChoiceInput,
|
|
9
|
+
ColumnedInput,
|
|
10
|
+
defaultModelOpts,
|
|
11
|
+
} from "@/composables/forms"
|
|
12
|
+
import { computed, useTemplateRef } from "vue"
|
|
9
13
|
|
|
10
14
|
defineOptions({
|
|
11
15
|
inheritAttrs: false,
|
|
@@ -16,62 +20,52 @@ const props = withDefaults(
|
|
|
16
20
|
defaultInputProps
|
|
17
21
|
)
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
validate(e)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const selectionCount = computed(() => {
|
|
40
|
-
if (Array.isArray(modelState.value)) {
|
|
41
|
-
return modelState.value.length
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return 0
|
|
23
|
+
const modelState = defineModel<MultiChoiceInput["modelValue"]>({
|
|
24
|
+
...defaultModelOpts,
|
|
25
|
+
// NOTE(spk): Vue handling of explicit null props values vs undefined props values
|
|
26
|
+
// means we can't rely on the the "default" param of defineModel here. Ensuring the
|
|
27
|
+
// getter returns an array type allows for a consistent checkbox v-model binding similar to
|
|
28
|
+
// the example in the official Vue docs.
|
|
29
|
+
//
|
|
30
|
+
// When a parent component passes a null v-model the parent ref stays null until a mutation occurs
|
|
31
|
+
// this is consistent with current input components.
|
|
32
|
+
get: (v) => {
|
|
33
|
+
if (!Array.isArray(v)) {
|
|
34
|
+
return []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return v
|
|
38
|
+
},
|
|
45
39
|
})
|
|
46
40
|
|
|
41
|
+
const { aria, inputID, isDisabled, errorState, validate } = useInputField(props)
|
|
42
|
+
|
|
47
43
|
const minCount = computed(() => {
|
|
48
|
-
return props.min ||
|
|
44
|
+
return props.min || null
|
|
49
45
|
})
|
|
50
46
|
|
|
51
47
|
const maxCount = computed(() => {
|
|
52
|
-
return
|
|
53
|
-
props.max ||
|
|
54
|
-
props.options.filter((opt) => {
|
|
55
|
-
return !opt.disabled
|
|
56
|
-
}).length
|
|
57
|
-
)
|
|
48
|
+
return props.max || null
|
|
58
49
|
})
|
|
59
50
|
|
|
60
51
|
const countError = computed(() => {
|
|
52
|
+
const count = modelState.value?.length || 0
|
|
53
|
+
|
|
61
54
|
// min not reached, no max is set
|
|
62
|
-
if (
|
|
55
|
+
if (minCount.value !== null && count < minCount.value) {
|
|
63
56
|
return `Please select at least ${minCount.value} of these options.`
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
// max is reached, no min set
|
|
67
|
-
if (
|
|
60
|
+
if (maxCount.value !== null && count > maxCount.value) {
|
|
68
61
|
return `Please limit your selection to ${maxCount.value} of these options.`
|
|
69
62
|
}
|
|
70
63
|
|
|
71
|
-
// min and max are both set and out of range
|
|
64
|
+
// min and max are both set and at least on of them is out of range
|
|
72
65
|
if (
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
minCount.value !== null &&
|
|
67
|
+
maxCount.value !== null &&
|
|
68
|
+
(count < minCount.value || count > maxCount.value)
|
|
75
69
|
) {
|
|
76
70
|
return `Please select between ${minCount.value} and ${maxCount.value} of these options.`
|
|
77
71
|
}
|
|
@@ -79,7 +73,7 @@ const countError = computed(() => {
|
|
|
79
73
|
return ""
|
|
80
74
|
})
|
|
81
75
|
|
|
82
|
-
const errorInput =
|
|
76
|
+
const errorInput = useTemplateRef("errorInput")
|
|
83
77
|
const setValidationError = () => {
|
|
84
78
|
if (!errorState.value) {
|
|
85
79
|
errorState.value = countError.value
|
|
@@ -100,7 +94,7 @@ const setValidationError = () => {
|
|
|
100
94
|
<FieldsetLegend
|
|
101
95
|
:id="aria.labelledby"
|
|
102
96
|
:label="label"
|
|
103
|
-
:required="minCount
|
|
97
|
+
:required="minCount ? true : false"
|
|
104
98
|
/>
|
|
105
99
|
<InputHelp
|
|
106
100
|
v-if="help"
|
|
@@ -129,11 +123,11 @@ const setValidationError = () => {
|
|
|
129
123
|
<div class="flex items-center h-5">
|
|
130
124
|
<input
|
|
131
125
|
:id="`${inputID}-${index}`"
|
|
126
|
+
v-model="modelState"
|
|
132
127
|
:aria-labelledby="`${inputID}-${index}-label`"
|
|
133
128
|
:aria-describedby="
|
|
134
129
|
option.help ? `${inputID}-${index}-help` : undefined
|
|
135
130
|
"
|
|
136
|
-
:checked="modelValue?.includes(option.value)"
|
|
137
131
|
:disabled="option.disabled"
|
|
138
132
|
:class="[
|
|
139
133
|
'h-4 w-4 rounded cursor-pointer',
|
|
@@ -144,8 +138,9 @@ const setValidationError = () => {
|
|
|
144
138
|
: 'border-gray-300 focus:ring-xy-blue-500',
|
|
145
139
|
]"
|
|
146
140
|
type="checkbox"
|
|
141
|
+
:value="option.value"
|
|
147
142
|
v-bind="$attrs"
|
|
148
|
-
@change="
|
|
143
|
+
@change="validate"
|
|
149
144
|
/>
|
|
150
145
|
</div>
|
|
151
146
|
<div class="ml-3">
|
|
@@ -3,7 +3,11 @@ import FieldsetLegend from "./FieldsetLegend.vue"
|
|
|
3
3
|
import InputLabel from "./InputLabel.vue"
|
|
4
4
|
import InputHelp from "./InputHelp.vue"
|
|
5
5
|
import InputError from "./InputError.vue"
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
useInputField,
|
|
8
|
+
defaultInputProps,
|
|
9
|
+
defaultModelOpts,
|
|
10
|
+
} from "@/composables/forms"
|
|
7
11
|
import type { OptionsInput, ColumnedInput } from "@/composables/forms"
|
|
8
12
|
|
|
9
13
|
defineOptions({
|
|
@@ -14,24 +18,17 @@ const props = withDefaults(
|
|
|
14
18
|
defineProps<OptionsInput & ColumnedInput>(),
|
|
15
19
|
defaultInputProps
|
|
16
20
|
)
|
|
17
|
-
|
|
18
|
-
defineEmits(["update:modelValue", "update:error"])
|
|
21
|
+
const modelState = defineModel<OptionsInput["modelValue"]>(defaultModelOpts)
|
|
19
22
|
|
|
20
23
|
const {
|
|
21
24
|
aria,
|
|
22
25
|
errorState,
|
|
23
|
-
modelState,
|
|
24
26
|
inputID,
|
|
25
27
|
isDisabled,
|
|
26
28
|
isRequired,
|
|
27
29
|
onInvalid,
|
|
28
30
|
validate,
|
|
29
31
|
} = useInputField(props)
|
|
30
|
-
|
|
31
|
-
const onChange = (e: Event, val: string | number) => {
|
|
32
|
-
modelState.value = val
|
|
33
|
-
validate(e)
|
|
34
|
-
}
|
|
35
32
|
</script>
|
|
36
33
|
|
|
37
34
|
<template>
|
|
@@ -74,11 +71,11 @@ const onChange = (e: Event, val: string | number) => {
|
|
|
74
71
|
<div class="flex items-center h-5">
|
|
75
72
|
<input
|
|
76
73
|
:id="`${inputID}-${index}`"
|
|
74
|
+
v-model="modelState"
|
|
77
75
|
:aria-describedby="
|
|
78
76
|
option.help ? `${inputID}-${index}-help` : undefined
|
|
79
77
|
"
|
|
80
78
|
:aria-labelledby="`${inputID}-${index}-label`"
|
|
81
|
-
:checked="modelState === option.value"
|
|
82
79
|
:class="[
|
|
83
80
|
'h-4 w-4 cursor-pointer text-xy-blue',
|
|
84
81
|
'disabled:bg-gray-100 disabled:border-gray-200 disabled:cursor-not-allowed disabled:opacity-100',
|
|
@@ -92,7 +89,7 @@ const onChange = (e: Event, val: string | number) => {
|
|
|
92
89
|
type="radio"
|
|
93
90
|
:value="option.value"
|
|
94
91
|
v-bind="$attrs"
|
|
95
|
-
@change="
|
|
92
|
+
@change="validate"
|
|
96
93
|
@invalid="onInvalid"
|
|
97
94
|
/>
|
|
98
95
|
</div>
|
|
@@ -10,7 +10,11 @@ import InputLabel from "./InputLabel.vue"
|
|
|
10
10
|
import InputHelp from "./InputHelp.vue"
|
|
11
11
|
import InputError from "./InputError.vue"
|
|
12
12
|
import FieldsetLegend from "./FieldsetLegend.vue"
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
defaultInputProps,
|
|
15
|
+
defaultModelOpts,
|
|
16
|
+
useInputField,
|
|
17
|
+
} from "@/composables/forms"
|
|
14
18
|
import type {
|
|
15
19
|
ColumnedInput,
|
|
16
20
|
InputOption,
|
|
@@ -26,7 +30,6 @@ defineOptions({
|
|
|
26
30
|
* to use the modelValue inside of forms. It does not however resolve the issue of
|
|
27
31
|
* supporting HTML5 form validation, so we'll add our own hidden radio buttons to support both.
|
|
28
32
|
*/
|
|
29
|
-
|
|
30
33
|
interface RadioCards extends OptionsInput {
|
|
31
34
|
options: T[]
|
|
32
35
|
}
|
|
@@ -35,17 +38,10 @@ const props = withDefaults(
|
|
|
35
38
|
defineProps<RadioCards & ColumnedInput>(),
|
|
36
39
|
defaultInputProps
|
|
37
40
|
)
|
|
41
|
+
const modelState = defineModel<RadioCards["modelValue"]>(defaultModelOpts)
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
aria,
|
|
42
|
-
isDisabled,
|
|
43
|
-
isRequired,
|
|
44
|
-
nameAttr,
|
|
45
|
-
modelState,
|
|
46
|
-
errorState,
|
|
47
|
-
onInvalid,
|
|
48
|
-
} = useInputField(props)
|
|
43
|
+
const { aria, isDisabled, isRequired, nameAttr, errorState, onInvalid } =
|
|
44
|
+
useInputField(props)
|
|
49
45
|
|
|
50
46
|
const onUpdate = (val: unknown) => {
|
|
51
47
|
if (val) {
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import InputLabel from "./InputLabel.vue"
|
|
3
3
|
import InputHelp from "./InputHelp.vue"
|
|
4
4
|
import InputError from "./InputError.vue"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
defaultInputProps,
|
|
7
|
+
defaultModelOpts,
|
|
8
|
+
useInputField,
|
|
9
|
+
} from "@/composables/forms"
|
|
6
10
|
import type { OptionsInput } from "@/composables/forms"
|
|
7
11
|
|
|
8
12
|
defineOptions({
|
|
@@ -14,30 +18,10 @@ const props = withDefaults(defineProps<OptionsInput>(), {
|
|
|
14
18
|
placeholder: "Select an option",
|
|
15
19
|
})
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
const {
|
|
19
|
-
aria,
|
|
20
|
-
inputID,
|
|
21
|
-
isRequired,
|
|
22
|
-
validate,
|
|
23
|
-
modelState,
|
|
24
|
-
errorState,
|
|
25
|
-
onInvalid,
|
|
26
|
-
} = useInputField(props)
|
|
21
|
+
const modelState = defineModel<OptionsInput["modelValue"]>(defaultModelOpts)
|
|
27
22
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-select-selectedindex
|
|
31
|
-
const selectedIndex = (e.target as HTMLSelectElement).selectedIndex
|
|
32
|
-
|
|
33
|
-
// NOTE(spk): we disable the 0 index option in the list, so there's
|
|
34
|
-
// no expectation it will trigger a change event. Likewise, dynamic updates to
|
|
35
|
-
// a modelValue prop in a parent do not trigger the change event.
|
|
36
|
-
if (selectedIndex > 0) {
|
|
37
|
-
modelState.value = props.options[selectedIndex - 1].value
|
|
38
|
-
validate(e)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
23
|
+
const { aria, inputID, isRequired, validate, errorState, onInvalid } =
|
|
24
|
+
useInputField(props)
|
|
41
25
|
</script>
|
|
42
26
|
|
|
43
27
|
<template>
|
|
@@ -51,6 +35,7 @@ const onChange = (e: Event) => {
|
|
|
51
35
|
/>
|
|
52
36
|
<select
|
|
53
37
|
:id="inputID"
|
|
38
|
+
v-model="modelState"
|
|
54
39
|
:aria-labelledby="aria.labelledby"
|
|
55
40
|
:aria-describedby="aria.describedby"
|
|
56
41
|
:aria-errormessage="aria.errormessage"
|
|
@@ -61,9 +46,8 @@ const onChange = (e: Event) => {
|
|
|
61
46
|
? 'text-red-900 ring-red-700 placeholder:text-red-300 focus:ring-red-700'
|
|
62
47
|
: 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-xy-blue-500',
|
|
63
48
|
]"
|
|
64
|
-
:value="modelState"
|
|
65
49
|
v-bind="$attrs"
|
|
66
|
-
@change="
|
|
50
|
+
@change="validate"
|
|
67
51
|
@invalid="onInvalid"
|
|
68
52
|
>
|
|
69
53
|
<option value="" disabled selected v-text="placeholder" />
|