@xy-planning-network/trees 0.10.5 → 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.
Files changed (78) hide show
  1. package/dist/style.css +1 -1
  2. package/dist/trees.es.js +4084 -4179
  3. package/dist/trees.umd.js +10 -10
  4. package/package.json +5 -5
  5. package/src/lib-components/forms/BaseInput.vue +18 -29
  6. package/src/lib-components/forms/Checkbox.vue +8 -9
  7. package/src/lib-components/forms/DateRangePicker.vue +21 -22
  8. package/src/lib-components/forms/DateTimeInput.vue +14 -24
  9. package/src/lib-components/forms/MultiCheckboxes.vue +33 -35
  10. package/src/lib-components/forms/Radio.vue +8 -11
  11. package/src/lib-components/forms/RadioCards.vue +8 -12
  12. package/src/lib-components/forms/Select.vue +10 -26
  13. package/src/lib-components/forms/TextArea.vue +11 -17
  14. package/src/lib-components/forms/Toggle.vue +6 -7
  15. package/src/lib-components/forms/YesOrNoRadio.vue +10 -12
  16. package/src/lib-components/layout/DateFilter.vue +8 -4
  17. package/src/lib-components/lists/DynamicTable.vue +7 -8
  18. package/src/lib-components/navigation/ActionsDropdown.vue +2 -0
  19. package/src/lib-components/navigation/Paginator.vue +17 -20
  20. package/src/lib-components/navigation/Tabs.vue +6 -18
  21. package/src/lib-components/overlays/ContentModal.vue +13 -9
  22. package/src/lib-components/overlays/Modal.vue +14 -11
  23. package/src/lib-components/overlays/Popover/Popover.vue +2 -0
  24. package/src/lib-components/overlays/Slideover.vue +13 -15
  25. package/types/composables/forms.d.ts +16 -2
  26. package/types/composables/nav.d.ts +1 -1
  27. package/types/composables/useActionItems.d.ts +1 -1
  28. package/types/composables/useFlashes.d.ts +1 -1
  29. package/types/composables/useSpinner.d.ts +6 -6
  30. package/types/composables/useTable.d.ts +1 -1
  31. package/types/entry.d.ts +0 -2
  32. package/types/helpers/Slots.d.ts +2 -0
  33. package/types/lib-components/forms/BaseInput.vue.d.ts +13 -36
  34. package/types/lib-components/forms/Checkbox.vue.d.ts +10 -35
  35. package/types/lib-components/forms/DateRangePicker.vue.d.ts +15 -38
  36. package/types/lib-components/forms/DateTimeInput.vue.d.ts +10 -35
  37. package/types/lib-components/forms/FieldsetLegend.vue.d.ts +4 -29
  38. package/types/lib-components/forms/InputError.vue.d.ts +4 -25
  39. package/types/lib-components/forms/InputHelp.vue.d.ts +4 -27
  40. package/types/lib-components/forms/InputLabel.vue.d.ts +4 -29
  41. package/types/lib-components/forms/MultiCheckboxes.vue.d.ts +13 -36
  42. package/types/lib-components/forms/Radio.vue.d.ts +10 -35
  43. package/types/lib-components/forms/RadioCards.vue.d.ts +16 -13
  44. package/types/lib-components/forms/Select.vue.d.ts +10 -35
  45. package/types/lib-components/forms/TextArea.vue.d.ts +10 -35
  46. package/types/lib-components/forms/Toggle.vue.d.ts +12 -36
  47. package/types/lib-components/forms/YesOrNoRadio.vue.d.ts +10 -35
  48. package/types/lib-components/indicators/InlineAlert.vue.d.ts +11 -42
  49. package/types/lib-components/indicators/ProgressCircles.vue.d.ts +3 -12
  50. package/types/lib-components/indicators/ProgressCirclesLabeled.vue.d.ts +3 -12
  51. package/types/lib-components/indicators/XYSpinner.vue.d.ts +1 -1
  52. package/types/lib-components/layout/DateFilter.vue.d.ts +7 -16
  53. package/types/lib-components/layout/SidebarLayout.vue.d.ts +4 -25
  54. package/types/lib-components/layout/StackedLayout.vue.d.ts +4 -25
  55. package/types/lib-components/lists/Cards.vue.d.ts +3 -12
  56. package/types/lib-components/lists/DataTable.vue.d.ts +4 -31
  57. package/types/lib-components/lists/DetailList.vue.d.ts +5 -36
  58. package/types/lib-components/lists/DownloadCell.vue.d.ts +3 -12
  59. package/types/lib-components/lists/DynamicTable.vue.d.ts +8 -35
  60. package/types/lib-components/lists/StaticTable.vue.d.ts +21 -0
  61. package/types/lib-components/lists/StaticTableActionSlot.vue.d.ts +27 -0
  62. package/types/lib-components/lists/Table.vue.d.ts +39 -0
  63. package/types/lib-components/lists/TableActionButtons.vue.d.ts +4 -25
  64. package/types/lib-components/navigation/ActionsDropdown.vue.d.ts +4 -26
  65. package/types/lib-components/navigation/ActionsDropdownCallback.vue.d.ts +18 -0
  66. package/types/lib-components/navigation/ActionsDropdownEmit.vue.d.ts +22 -0
  67. package/types/lib-components/navigation/Paginator.vue.d.ts +7 -17
  68. package/types/lib-components/navigation/Steps.vue.d.ts +6 -33
  69. package/types/lib-components/navigation/Tabs.vue.d.ts +12 -37
  70. package/types/lib-components/overlays/AlertModal.vue.d.ts +68 -0
  71. package/types/lib-components/overlays/ContentModal.vue.d.ts +14 -35
  72. package/types/lib-components/overlays/Flash.vue.d.ts +1 -1
  73. package/types/lib-components/overlays/Modal.vue.d.ts +19 -49
  74. package/types/lib-components/overlays/Popover/Popover.vue.d.ts +4 -27
  75. package/types/lib-components/overlays/Popover/PopoverContent.vue.d.ts +1 -1
  76. package/types/lib-components/overlays/Slideover.vue.d.ts +12 -21
  77. package/types/lib-components/overlays/Spinner.vue.d.ts +1 -1
  78. 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.5",
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": "^4.2.3",
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.3.9",
57
- "vue-tsc": "^2.0.29"
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.3.4"
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, ref } from "vue"
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
- defineEmits(["update:modelValue", "update:error"])
22
- const input = ref<HTMLInputElement | null>(null)
23
- const {
24
- errorState,
25
- modelState,
26
- inputID,
27
- isRequired,
28
- onInvalid,
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="onInput"
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 { useInputField, defaultInputProps } from "@/composables/forms"
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="onChange"
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 { defaultInputProps, useInputField } from "@/composables/forms"
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
- aria,
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 updateModelValue = (value: { minDate: number; maxDate: number }) => {
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
- updateModelValue({
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
- updateModelValue({
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 (props.modelValue.minDate != 0 && props.modelValue.maxDate != 0) {
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
- props.modelValue.minDate * 1000,
70
- props.modelValue.maxDate * 1000,
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
- defineEmits(["update:modelValue", "update:error"])
20
- const input = ref<HTMLInputElement | null>(null)
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
- ref="input"
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="onInput"
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 type { MultiChoiceInput, ColumnedInput } from "@/composables/forms"
8
- import { computed, ref } from "vue"
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,34 +20,26 @@ const props = withDefaults(
16
20
  defaultInputProps
17
21
  )
18
22
 
19
- defineEmits(["update:modelValue", "update:error"])
20
- const { aria, inputID, isDisabled, modelState, errorState, validate } =
21
- useInputField(props)
22
-
23
- const onChange = (e: Event, val: string | number) => {
24
- const checked = (e.target as HTMLInputElement).checked
25
-
26
- if (!Array.isArray(modelState.value)) {
27
- modelState.value = []
28
- }
29
-
30
- if (checked) {
31
- modelState.value.push(val)
32
- } else {
33
- modelState.value.splice(modelState.value.indexOf(val), 1)
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
44
  return props.min || null
49
45
  })
@@ -53,13 +49,15 @@ const maxCount = computed(() => {
53
49
  })
54
50
 
55
51
  const countError = computed(() => {
52
+ const count = modelState.value?.length || 0
53
+
56
54
  // min not reached, no max is set
57
- if (minCount.value !== null && selectionCount.value < minCount.value) {
55
+ if (minCount.value !== null && count < minCount.value) {
58
56
  return `Please select at least ${minCount.value} of these options.`
59
57
  }
60
58
 
61
59
  // max is reached, no min set
62
- if (maxCount.value !== null && selectionCount.value > maxCount.value) {
60
+ if (maxCount.value !== null && count > maxCount.value) {
63
61
  return `Please limit your selection to ${maxCount.value} of these options.`
64
62
  }
65
63
 
@@ -67,8 +65,7 @@ const countError = computed(() => {
67
65
  if (
68
66
  minCount.value !== null &&
69
67
  maxCount.value !== null &&
70
- (selectionCount.value < minCount.value ||
71
- selectionCount.value > maxCount.value)
68
+ (count < minCount.value || count > maxCount.value)
72
69
  ) {
73
70
  return `Please select between ${minCount.value} and ${maxCount.value} of these options.`
74
71
  }
@@ -76,7 +73,7 @@ const countError = computed(() => {
76
73
  return ""
77
74
  })
78
75
 
79
- const errorInput = ref<HTMLInputElement | null>(null)
76
+ const errorInput = useTemplateRef("errorInput")
80
77
  const setValidationError = () => {
81
78
  if (!errorState.value) {
82
79
  errorState.value = countError.value
@@ -126,11 +123,11 @@ const setValidationError = () => {
126
123
  <div class="flex items-center h-5">
127
124
  <input
128
125
  :id="`${inputID}-${index}`"
126
+ v-model="modelState"
129
127
  :aria-labelledby="`${inputID}-${index}-label`"
130
128
  :aria-describedby="
131
129
  option.help ? `${inputID}-${index}-help` : undefined
132
130
  "
133
- :checked="modelValue?.includes(option.value)"
134
131
  :disabled="option.disabled"
135
132
  :class="[
136
133
  'h-4 w-4 rounded cursor-pointer',
@@ -141,8 +138,9 @@ const setValidationError = () => {
141
138
  : 'border-gray-300 focus:ring-xy-blue-500',
142
139
  ]"
143
140
  type="checkbox"
141
+ :value="option.value"
144
142
  v-bind="$attrs"
145
- @change="onChange($event, option.value)"
143
+ @change="validate"
146
144
  />
147
145
  </div>
148
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 { useInputField, defaultInputProps } from "@/composables/forms"
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="onChange($event, option.value)"
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 { defaultInputProps, useInputField } from "@/composables/forms"
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
- defineEmits(["update:modelValue", "update:error"])
40
- const {
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 { defaultInputProps, useInputField } from "@/composables/forms"
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
- defineEmits(["update:modelValue", "update:error"])
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 onChange = (e: Event) => {
29
- // NOTE(spk): The selectedIndex references the list of options in tree order
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="onChange"
50
+ @change="validate"
67
51
  @invalid="onInvalid"
68
52
  >
69
53
  <option value="" disabled selected v-text="placeholder" />
@@ -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 { useInputField, defaultInputProps } from "@/composables/forms"
5
+ import {
6
+ useInputField,
7
+ defaultInputProps,
8
+ defaultModelOpts,
9
+ } from "@/composables/forms"
6
10
  import type { TextareaInput } from "@/composables/forms"
7
11
 
8
12
  defineOptions({
@@ -10,21 +14,11 @@ defineOptions({
10
14
  })
11
15
 
12
16
  const props = withDefaults(defineProps<TextareaInput>(), defaultInputProps)
13
- defineEmits(["update:modelValue", "update:error"])
14
- const {
15
- aria,
16
- inputID,
17
- isRequired,
18
- modelState,
19
- errorState,
20
- onInvalid,
21
- inputValidation,
22
- } = useInputField(props)
23
17
 
24
- const onInput = (e: Event) => {
25
- modelState.value = (e.target as HTMLInputElement).value.trim()
26
- inputValidation(e)
27
- }
18
+ const modelState = defineModel<TextareaInput["modelValue"]>(defaultModelOpts)
19
+
20
+ const { aria, inputID, isRequired, errorState, onInvalid, inputValidation } =
21
+ useInputField(props)
28
22
  </script>
29
23
 
30
24
  <template>
@@ -38,6 +32,7 @@ const onInput = (e: Event) => {
38
32
  />
39
33
  <textarea
40
34
  :id="inputID"
35
+ v-model="modelState"
41
36
  :aria-labelledby="aria.labelledby"
42
37
  :aria-describedby="aria.describedby"
43
38
  :aria-errormessage="aria.errormessage"
@@ -49,9 +44,8 @@ const onInput = (e: Event) => {
49
44
  : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-xy-blue-500',
50
45
  ]"
51
46
  :placeholder="placeholder"
52
- :value="modelState || undefined"
53
47
  v-bind="$attrs"
54
- @input="onInput"
48
+ @input="inputValidation"
55
49
  @invalid="onInvalid"
56
50
  />
57
51
  <InputHelp :id="aria.describedby" class="mt-1" :text="help" />