Skip to content

Commit abbf669

Browse files
Improve type FormParameterValue by using a type map
This ensures the `currentValue` for a `FormElement` is the appropriate type given `props.type`. Fixes #19500
1 parent f7acf17 commit abbf669

File tree

9 files changed

+343
-193
lines changed

9 files changed

+343
-193
lines changed

client/src/components/Form/Elements/FormData/FormData.vue

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ const props = withDefaults(
3232
loading?: boolean;
3333
multiple?: boolean;
3434
optional?: boolean;
35-
options: Record<string, Array<DataOption>>;
35+
options?: Record<string, Array<DataOption>>;
3636
value?: {
37+
batch: boolean;
38+
product: boolean;
3739
values: Array<DataOption>;
3840
};
3941
extensions?: Array<string>;
@@ -48,6 +50,7 @@ const props = withDefaults(
4850
loading: false,
4951
multiple: false,
5052
optional: false,
53+
options: () => ({ dce: [], hda: [], hdca: [] }),
5154
value: undefined,
5255
extensions: () => [],
5356
type: "data",

client/src/components/Form/Elements/FormData/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ export type DataOption = {
66
batch: boolean;
77
map_over_type?: string;
88
name: string;
9-
src: string;
9+
src: "dce" | "hda" | "hdca" | "ldda" | "url";
1010
tags: Array<string>;
1111
};

client/src/components/Form/Elements/FormDrilldown/FormDrilldown.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ const props = withDefaults(
1010
defineProps<{
1111
id: string;
1212
value?: Value;
13-
options: Array<Option>;
13+
options?: Array<Option>;
1414
multiple: boolean;
1515
showIcons?: boolean;
1616
}>(),
1717
{
1818
value: null,
19+
options: () => [],
1920
multiple: true,
2021
showIcons: false,
2122
}

client/src/components/Form/Elements/FormOptionalText.vue

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default {
2626
},
2727
props: {
2828
value: {
29+
type: String,
2930
default: "",
3031
},
3132
id: {

client/src/components/Form/Elements/FormRadio.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { computed } from "vue";
44
const emit = defineEmits(["input"]);
55
const props = defineProps({
66
value: {
7-
default: null,
7+
type: String,
8+
default: "",
89
},
910
options: {
1011
type: Array,

client/src/components/Form/Elements/FormSelection.vue

+25-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
<script setup>
2-
import { library } from "@fortawesome/fontawesome-svg-core";
1+
<script setup lang="ts">
32
import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
43
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
54
import { BDropdown, BDropdownItemButton } from "bootstrap-vue";
@@ -8,43 +7,36 @@ import { computed, ref, watch } from "vue";
87
98
import { useUserFlagsStore } from "@/stores/userFlagsStore";
109
10+
import type { FormParameterValue } from "../parameterTypes";
11+
import type { DataOption } from "./FormData/types";
12+
1113
import FormCheck from "./FormCheck.vue";
1214
import FormRadio from "./FormRadio.vue";
1315
import FormSelect from "./FormSelect.vue";
1416
import FormSelectMany from "./FormSelectMany/FormSelectMany.vue";
1517
16-
library.add(faCaretDown);
17-
18-
const emit = defineEmits(["input"]);
19-
const props = defineProps({
20-
value: {
21-
default: null,
22-
},
23-
data: {
24-
type: Array,
25-
default: null,
26-
},
27-
display: {
28-
type: String,
29-
default: null,
30-
},
31-
optional: {
32-
type: Boolean,
33-
default: false,
34-
},
35-
options: {
36-
type: Array,
37-
default: null,
38-
},
39-
multiple: {
40-
type: Boolean,
41-
default: false,
42-
},
43-
});
18+
type FormValue = FormParameterValue | DataOption[];
19+
20+
const emit = defineEmits<{
21+
(e: "input", value: FormValue): void;
22+
}>();
23+
const props = defineProps<{
24+
/** TODO: It should be `value: FormValue` but thats giving:
25+
* `Type 'null' is not assignable to type 'PropConstructor<unknown>'.` */
26+
value: any;
27+
data?: {
28+
label: string;
29+
value: any;
30+
}[];
31+
display?: "checkboxes" | "radio" | "select" | "select-many";
32+
optional?: boolean;
33+
options?: [string, FormParameterValue | undefined][];
34+
multiple?: boolean;
35+
}>();
4436
4537
const currentValue = computed({
4638
get: () => {
47-
return props.value;
39+
return props.value as any;
4840
},
4941
set: (val) => {
5042
emit("input", val);
@@ -53,7 +45,7 @@ const currentValue = computed({
5345
5446
/** Provides formatted select options. */
5547
const currentOptions = computed(() => {
56-
let result = [];
48+
let result: { label: string; value: any }[] = [];
5749
const data = props.data;
5850
const options = props.options;
5951
if (options && options.length > 0) {
@@ -127,7 +119,7 @@ defineExpose({
127119

128120
<BDropdown toggle-class="inline-icon-button d-block px-1" variant="link" no-caret>
129121
<template v-slot:button-content>
130-
<FontAwesomeIcon icon="fa-caret-down"></FontAwesomeIcon>
122+
<FontAwesomeIcon :icon="faCaretDown" />
131123
<span class="sr-only">select element preferences</span>
132124
</template>
133125
<BDropdownItemButton

client/src/components/Form/Elements/FormText.vue

+69-99
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,78 @@
1+
<script setup lang="ts">
2+
import { BCol, BFormInput, BFormTextarea, BRow } from "bootstrap-vue";
3+
import { computed } from "vue";
4+
5+
import type { FormParameterTypeMap } from "../parameterTypes";
6+
7+
interface Props {
8+
value?: FormParameterTypeMap["text"];
9+
id?: string;
10+
type?: "text" | "password";
11+
/** <textarea> instead of <input> element */
12+
area?: boolean;
13+
/** Allow multiple entries to be created */
14+
multiple?: boolean;
15+
readonly?: boolean;
16+
placeholder?: string;
17+
optional?: boolean;
18+
showState?: boolean;
19+
color?: string;
20+
/** Refers to an optional custom css class name */
21+
cls?: string;
22+
/** Display list of suggestions in autocomplete dialog */
23+
datalist?: { label: string; value: string }[];
24+
}
25+
26+
const props = withDefaults(defineProps<Props>(), {
27+
value: "",
28+
id: "",
29+
type: "text",
30+
area: false,
31+
multiple: false,
32+
readonly: false,
33+
placeholder: "",
34+
optional: true,
35+
showState: false,
36+
color: undefined,
37+
cls: undefined,
38+
datalist: undefined,
39+
});
40+
const emit = defineEmits(["input"]);
41+
42+
const acceptedTypes = computed(() => (["text", "password"].includes(props.type) ? props.type : "text"));
43+
44+
const currentValue = computed({
45+
get() {
46+
const v = props.value ?? "";
47+
if (Array.isArray(v)) {
48+
if (v.length === 0) {
49+
return "";
50+
}
51+
return props.multiple ? v.reduce((str_value, v) => str_value + String(v) + "\n", "") : String(v[0]);
52+
}
53+
return String(v);
54+
},
55+
set(newVal: string) {
56+
emit("input", newVal);
57+
},
58+
});
59+
60+
const inputArea = computed(() => props.area || props.multiple);
61+
62+
const style = computed(() => (props.color ? { color: props.color, "border-color": props.color } : null));
63+
</script>
164
<template>
2-
<b-row align-v="center">
3-
<b-col>
4-
<b-form-textarea
65+
<BRow align-v="center">
66+
<BCol>
67+
<BFormTextarea
568
v-if="inputArea"
669
:id="id"
770
v-model="currentValue"
871
:class="['ui-text-area', cls]"
972
:readonly="readonly"
1073
:placeholder="placeholder"
1174
:style="style" />
12-
<b-form-input
75+
<BFormInput
1376
v-else
1477
:id="id"
1578
v-model="currentValue"
@@ -23,103 +86,10 @@
2386
<datalist v-if="datalist && !inputArea" :id="`${id}-datalist`">
2487
<option v-for="data in datalist" :key="data.value" :label="data.label" :value="data.value" />
2588
</datalist>
26-
</b-col>
27-
</b-row>
89+
</BCol>
90+
</BRow>
2891
</template>
2992

30-
<script>
31-
export default {
32-
props: {
33-
value: {
34-
// String; Array for multiple
35-
default: "",
36-
},
37-
id: {
38-
type: String,
39-
default: "",
40-
},
41-
type: {
42-
type: String,
43-
default: "text",
44-
},
45-
area: {
46-
// <textarea> instead of <input> element
47-
type: Boolean,
48-
default: false,
49-
},
50-
multiple: {
51-
// Allow multiple entries to be created
52-
type: Boolean,
53-
default: false,
54-
},
55-
readonly: {
56-
type: Boolean,
57-
default: false,
58-
},
59-
placeholder: {
60-
type: String,
61-
default: "",
62-
},
63-
optional: {
64-
type: Boolean,
65-
default: true,
66-
},
67-
showState: {
68-
type: Boolean,
69-
default: false,
70-
},
71-
color: {
72-
type: String,
73-
default: null,
74-
},
75-
cls: {
76-
// Refers to an optional custom css class name
77-
type: String,
78-
default: null,
79-
},
80-
datalist: {
81-
// Display list of suggestions in autocomplete dialog
82-
type: Array,
83-
default: null,
84-
},
85-
},
86-
computed: {
87-
acceptedTypes() {
88-
return ["text", "password"].includes(this.type) ? this.type : "text";
89-
},
90-
currentValue: {
91-
get() {
92-
const v = this.value ?? "";
93-
if (Array.isArray(v)) {
94-
if (v.length === 0) {
95-
return "";
96-
}
97-
return this.multiple
98-
? this.value.reduce((str_value, v) => str_value + String(v) + "\n", "")
99-
: String(this.value[0]);
100-
}
101-
return String(v);
102-
},
103-
set(newVal, oldVal) {
104-
if (newVal !== oldVal) {
105-
this.$emit("input", newVal);
106-
}
107-
},
108-
},
109-
inputArea() {
110-
return this.area || this.multiple;
111-
},
112-
style() {
113-
return this.color
114-
? {
115-
color: this.color,
116-
"border-color": this.color,
117-
}
118-
: null;
119-
},
120-
},
121-
};
122-
</script>
12393
<style scoped>
12494
.ui-input-linked {
12595
border-left-width: 0.5rem;

0 commit comments

Comments
 (0)