@fiscozen/input 3.2.0 → 3.3.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/CHANGELOG.md +14 -0
- package/coverage/FzCurrencyInput.vue.html +640 -0
- package/coverage/FzInput.vue.html +709 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +494 -0
- package/coverage/coverage-final.json +4 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/useInputStyle.ts.html +343 -0
- package/dist/input.js +267 -8459
- package/dist/input.umd.cjs +1 -557
- package/dist/src/FzCurrencyInput.vue.d.ts +26 -4
- package/dist/src/FzInput.vue.d.ts +4 -0
- package/dist/src/types.d.ts +11 -0
- package/package.json +6 -6
- package/src/FzCurrencyInput.vue +7 -0
- package/src/FzInput.vue +482 -573
- package/src/__tests__/FzCurrencyInput.spec.ts +1169 -1068
- package/src/__tests__/FzInput.spec.ts +125 -0
- package/src/types.ts +11 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +1 -1
- package/dist/input.css +0 -2
package/src/FzInput.vue
CHANGED
|
@@ -1,615 +1,524 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { computed, toRefs, Ref, ref, watch, useSlots, useAttrs } from "vue";
|
|
15
|
-
import { FzInputProps, type InputEnvironment } from "./types";
|
|
16
|
-
import { FzAlert } from "@fiscozen/alert";
|
|
17
|
-
import { FzIcon } from "@fiscozen/icons";
|
|
18
|
-
import { FzIconButton } from "@fiscozen/button";
|
|
19
|
-
import useInputStyle from "./useInputStyle";
|
|
20
|
-
import { generateInputId, sizeToEnvironmentMapping } from "./utils";
|
|
21
|
-
|
|
22
|
-
const props = withDefaults(defineProps<FzInputProps>(), {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
2
|
+
/**
|
|
3
|
+
* FzInput Component
|
|
4
|
+
*
|
|
5
|
+
* Flexible input component with icon support, validation states, and multiple variants.
|
|
6
|
+
* Supports left/right icons (static or clickable buttons), floating label variant,
|
|
7
|
+
* error/valid states, and full accessibility features.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @example
|
|
11
|
+
* <FzInput label="Email" type="email" v-model="email" />
|
|
12
|
+
* <FzInput label="Password" type="password" rightIcon="eye" @fzinput:right-icon-click="toggleVisibility" />
|
|
13
|
+
*/
|
|
14
|
+
import { computed, toRefs, Ref, ref, watch, useSlots, useAttrs } from "vue";
|
|
15
|
+
import { FzInputProps, type InputEnvironment } from "./types";
|
|
16
|
+
import { FzAlert } from "@fiscozen/alert";
|
|
17
|
+
import { FzIcon } from "@fiscozen/icons";
|
|
18
|
+
import { FzIconButton } from "@fiscozen/button";
|
|
19
|
+
import useInputStyle from "./useInputStyle";
|
|
20
|
+
import { generateInputId, sizeToEnvironmentMapping } from "./utils";
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(defineProps<FzInputProps>(), {
|
|
23
|
+
error: false,
|
|
24
|
+
type: "text",
|
|
25
|
+
rightIconButtonVariant: "invisible",
|
|
26
|
+
secondRightIconButtonVariant: "invisible",
|
|
27
|
+
variant: "normal",
|
|
28
|
+
environment: "frontoffice",
|
|
29
|
+
autocomplete: false,
|
|
30
|
+
highlighted: false,
|
|
31
|
+
highlightedDescription: "Campo in evidenza",
|
|
32
|
+
aiReasoning: false,
|
|
33
|
+
aiReasoningDescription: "Suggerito dall'intelligenza artificiale",
|
|
34
|
+
disableEmphasisReset: false,
|
|
35
|
+
clearable: false,
|
|
36
|
+
clearAriaLabel: "Cancella",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
defineOptions({
|
|
40
|
+
inheritAttrs: false,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const attrs = useAttrs();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Attrs forwarded to the native input element.
|
|
47
|
+
* Excludes class which are applied to the root wrapper div
|
|
48
|
+
* so that consumers can control layout/positioning of the component.
|
|
49
|
+
*/
|
|
50
|
+
const inputAttrs = computed(() => {
|
|
51
|
+
return {
|
|
52
|
+
...attrs,
|
|
53
|
+
class: undefined,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const rootClass = computed(() => attrs.class);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Deprecation warning and normalization for size prop.
|
|
61
|
+
* Watches the size prop and warns once on mount if it's provided.
|
|
62
|
+
* Normalizes size values to environment for backward compatibility.
|
|
63
|
+
*/
|
|
64
|
+
watch(
|
|
65
|
+
() => props.size,
|
|
66
|
+
(size) => {
|
|
67
|
+
if (size !== undefined) {
|
|
68
|
+
const mappedEnvironment = sizeToEnvironmentMapping[size];
|
|
69
|
+
|
|
70
|
+
// Check if both environment and size are provided and conflict
|
|
71
|
+
if (props.environment && props.environment !== mappedEnvironment) {
|
|
72
|
+
console.warn(
|
|
73
|
+
`[FzInput] Both "size" and "environment" props are provided. ` +
|
|
72
74
|
`"environment=${props.environment}" will be used and "size=${size}" will be ignored. ` +
|
|
73
75
|
`Please remove the deprecated "size" prop.`,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[FzInput] The "size" prop is deprecated and will be removed in a future version. ` +
|
|
78
80
|
`Please use environment="${mappedEnvironment}" instead of size="${size}".`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{ immediate: true },
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Deprecation warning for rightIconSize prop.
|
|
90
|
+
* Icons now have a fixed size of "md" and this prop is ignored.
|
|
91
|
+
*/
|
|
92
|
+
watch(
|
|
93
|
+
() => props.rightIconSize,
|
|
94
|
+
(rightIconSize) => {
|
|
95
|
+
if (rightIconSize !== undefined) {
|
|
96
|
+
console.warn(
|
|
97
|
+
`[FzInput] The "rightIconSize" prop is deprecated and will be removed in a future version. ` +
|
|
98
|
+
`Icons now have a fixed size of "md". The provided value "${rightIconSize}" will be ignored.`,
|
|
79
99
|
);
|
|
80
100
|
}
|
|
101
|
+
},
|
|
102
|
+
{ immediate: true },
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Determines the effective environment based on environment or size prop
|
|
107
|
+
*
|
|
108
|
+
* Priority: environment prop > size prop mapped to environment > default 'frontoffice'.
|
|
109
|
+
* The size prop is deprecated and only used for backward compatibility.
|
|
110
|
+
*/
|
|
111
|
+
const effectiveEnvironment = computed((): InputEnvironment => {
|
|
112
|
+
if (props.environment) {
|
|
113
|
+
return props.environment;
|
|
81
114
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
()
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
if (props.size) {
|
|
116
|
+
return sizeToEnvironmentMapping[props.size];
|
|
117
|
+
}
|
|
118
|
+
return "frontoffice";
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const model = defineModel<string>();
|
|
122
|
+
const containerRef: Ref<HTMLElement | null> = ref(null);
|
|
123
|
+
const inputRef: Ref<HTMLInputElement | null> = ref(null);
|
|
124
|
+
const uniqueId = generateInputId();
|
|
125
|
+
const isFocused = ref(false);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Internal visual state for emphasis props.
|
|
129
|
+
* These track the effective visual state which can differ from props when
|
|
130
|
+
* the user types into a highlighted/aiReasoning input (user input resets emphasis).
|
|
131
|
+
* Programmatic value changes (via v-model) do not affect these states.
|
|
132
|
+
*/
|
|
133
|
+
const effectiveHighlighted = ref(props.highlighted);
|
|
134
|
+
const effectiveAiReasoning = ref(props.aiReasoning);
|
|
135
|
+
|
|
136
|
+
watch(
|
|
137
|
+
() => props.highlighted,
|
|
138
|
+
(val) => {
|
|
139
|
+
effectiveHighlighted.value = val;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
watch(
|
|
143
|
+
() => props.aiReasoning,
|
|
144
|
+
(val) => {
|
|
145
|
+
effectiveAiReasoning.value = val;
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resets visual emphasis (highlighted/aiReasoning) when user physically types.
|
|
151
|
+
* Only triggered by native input events (user interaction), not programmatic v-model updates.
|
|
152
|
+
* Emits update events so parents using v-model:highlighted / v-model:aiReasoning stay in sync.
|
|
153
|
+
*/
|
|
154
|
+
const handleUserInput = () => {
|
|
155
|
+
if (props.disableEmphasisReset) return;
|
|
156
|
+
if (effectiveHighlighted.value) {
|
|
157
|
+
effectiveHighlighted.value = false;
|
|
158
|
+
emit("update:highlighted", false);
|
|
159
|
+
}
|
|
160
|
+
if (effectiveAiReasoning.value) {
|
|
161
|
+
effectiveAiReasoning.value = false;
|
|
162
|
+
emit("update:aiReasoning", false);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const propsRefs = toRefs(props);
|
|
167
|
+
|
|
168
|
+
const {
|
|
169
|
+
staticContainerClass,
|
|
170
|
+
computedContainerClass,
|
|
171
|
+
computedLabelClass,
|
|
172
|
+
staticInputClass,
|
|
173
|
+
computedInputClass,
|
|
174
|
+
computedHelpClass,
|
|
175
|
+
containerWidth,
|
|
176
|
+
showNormalPlaceholder,
|
|
177
|
+
} = useInputStyle(
|
|
178
|
+
{
|
|
179
|
+
...propsRefs,
|
|
180
|
+
highlighted: effectiveHighlighted,
|
|
181
|
+
aiReasoning: effectiveAiReasoning,
|
|
182
|
+
},
|
|
183
|
+
containerRef,
|
|
184
|
+
model,
|
|
185
|
+
effectiveEnvironment,
|
|
186
|
+
isFocused,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const slots = defineSlots<{
|
|
190
|
+
label?: () => unknown;
|
|
191
|
+
"left-icon"?: () => unknown;
|
|
192
|
+
"right-icon"?: () => unknown;
|
|
193
|
+
errorMessage?: () => unknown;
|
|
194
|
+
helpText?: () => unknown;
|
|
195
|
+
}>();
|
|
196
|
+
|
|
197
|
+
const runtimeSlots = useSlots();
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Computes aria-labelledby value linking input to label element
|
|
201
|
+
*
|
|
202
|
+
* Only set when default label element is rendered. Custom label slot replaces default label,
|
|
203
|
+
* so the ID doesn't exist and aria-labelledby would reference a non-existent element.
|
|
204
|
+
*/
|
|
205
|
+
const ariaLabelledBy = computed(() => {
|
|
206
|
+
const hasLabelProp = !!props.label;
|
|
207
|
+
const hasCustomLabelSlot = !!runtimeSlots.label;
|
|
208
|
+
|
|
209
|
+
if (hasLabelProp && !hasCustomLabelSlot) {
|
|
210
|
+
return `${uniqueId}-label`;
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Computes aria-describedby value linking input to help text or error message
|
|
217
|
+
*
|
|
218
|
+
* Uses runtimeSlots (not slots from defineSlots) because defineSlots is only for TypeScript typing.
|
|
219
|
+
*/
|
|
220
|
+
const ariaDescribedBy = computed(() => {
|
|
221
|
+
const ids: string[] = [];
|
|
222
|
+
if (props.error && runtimeSlots.errorMessage) {
|
|
223
|
+
ids.push(`${uniqueId}-error`);
|
|
98
224
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
() =>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
computedContainerClass,
|
|
169
|
-
computedLabelClass,
|
|
170
|
-
staticInputClass,
|
|
171
|
-
computedInputClass,
|
|
172
|
-
computedHelpClass,
|
|
173
|
-
containerWidth,
|
|
174
|
-
showNormalPlaceholder,
|
|
175
|
-
} = useInputStyle(
|
|
176
|
-
{
|
|
177
|
-
...propsRefs,
|
|
178
|
-
highlighted: effectiveHighlighted,
|
|
179
|
-
aiReasoning: effectiveAiReasoning,
|
|
180
|
-
},
|
|
181
|
-
containerRef,
|
|
182
|
-
model,
|
|
183
|
-
effectiveEnvironment,
|
|
184
|
-
isFocused,
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
const slots = defineSlots<{
|
|
188
|
-
label?: () => unknown;
|
|
189
|
-
"left-icon"?: () => unknown;
|
|
190
|
-
"right-icon"?: () => unknown;
|
|
191
|
-
errorMessage?: () => unknown;
|
|
192
|
-
helpText?: () => unknown;
|
|
193
|
-
}>();
|
|
194
|
-
|
|
195
|
-
const runtimeSlots = useSlots();
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Computes aria-labelledby value linking input to label element
|
|
199
|
-
*
|
|
200
|
-
* Only set when default label element is rendered. Custom label slot replaces default label,
|
|
201
|
-
* so the ID doesn't exist and aria-labelledby would reference a non-existent element.
|
|
202
|
-
*/
|
|
203
|
-
const ariaLabelledBy = computed(() => {
|
|
204
|
-
const hasLabelProp = !!props.label;
|
|
205
|
-
const hasCustomLabelSlot = !!runtimeSlots.label;
|
|
206
|
-
|
|
207
|
-
if (hasLabelProp && !hasCustomLabelSlot) {
|
|
208
|
-
return `${uniqueId}-label`;
|
|
209
|
-
}
|
|
210
|
-
return undefined;
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Computes aria-describedby value linking input to help text or error message
|
|
215
|
-
*
|
|
216
|
-
* Uses runtimeSlots (not slots from defineSlots) because defineSlots is only for TypeScript typing.
|
|
217
|
-
*/
|
|
218
|
-
const ariaDescribedBy = computed(() => {
|
|
219
|
-
const ids: string[] = [];
|
|
220
|
-
if (props.error && runtimeSlots.errorMessage) {
|
|
221
|
-
ids.push(`${uniqueId}-error`);
|
|
222
|
-
}
|
|
223
|
-
if (!props.error && runtimeSlots.helpText) {
|
|
224
|
-
ids.push(`${uniqueId}-help`);
|
|
225
|
-
}
|
|
226
|
-
return ids.length > 0 ? ids.join(" ") : undefined;
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const emit = defineEmits<{
|
|
230
|
-
focus: [event: FocusEvent];
|
|
231
|
-
blur: [event: FocusEvent];
|
|
232
|
-
// Other DOM events (keydown, keyup, paste, input, change, etc.) are automatically
|
|
233
|
-
// forwarded to the native input element via v-bind="$attrs" and don't need to be
|
|
234
|
-
// explicitly declared here. They will work automatically when used on FzInput.
|
|
235
|
-
"fzinput:left-icon-click": [];
|
|
236
|
-
"fzinput:right-icon-click": [];
|
|
237
|
-
"fzinput:second-right-icon-click": [];
|
|
238
|
-
"update:highlighted": [value: boolean];
|
|
239
|
-
"update:aiReasoning": [value: boolean];
|
|
240
|
-
}>();
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Handles container interaction (click or keyboard) to focus the input
|
|
244
|
-
*
|
|
245
|
-
* Makes the entire container area clickable and keyboard-accessible for better UX,
|
|
246
|
-
* especially useful for floating-label variant and mobile devices.
|
|
247
|
-
* Respects disabled and readonly states.
|
|
248
|
-
*/
|
|
249
|
-
const handleContainerInteraction = () => {
|
|
250
|
-
if (!props.disabled && !props.readonly && inputRef.value) {
|
|
251
|
-
inputRef.value.focus();
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Handles keyboard events on container to focus input
|
|
257
|
-
*
|
|
258
|
-
* Supports Enter and Space keys following accessibility best practices.
|
|
259
|
-
* Only prevents default when event originates from container itself (not from child elements like input),
|
|
260
|
-
* allowing form submission when Enter is pressed inside the input field.
|
|
261
|
-
*
|
|
262
|
-
* @param e - Keyboard event
|
|
263
|
-
*/
|
|
264
|
-
const handleContainerKeydown = (e: KeyboardEvent) => {
|
|
265
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
266
|
-
// Only prevent default if event originated from container itself, not from child elements
|
|
267
|
-
// This allows Enter key presses in the input to trigger form submission
|
|
268
|
-
if (e.target === e.currentTarget || e.target === containerRef.value) {
|
|
225
|
+
if (!props.error && runtimeSlots.helpText) {
|
|
226
|
+
ids.push(`${uniqueId}-help`);
|
|
227
|
+
}
|
|
228
|
+
return ids.length > 0 ? ids.join(" ") : undefined;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const emit = defineEmits<{
|
|
232
|
+
focus: [event: FocusEvent];
|
|
233
|
+
blur: [event: FocusEvent];
|
|
234
|
+
// Other DOM events (keydown, keyup, paste, input, change, etc.) are automatically
|
|
235
|
+
// forwarded to the native input element via v-bind="$attrs" and don't need to be
|
|
236
|
+
// explicitly declared here. They will work automatically when used on FzInput.
|
|
237
|
+
"fzinput:left-icon-click": [];
|
|
238
|
+
"fzinput:right-icon-click": [];
|
|
239
|
+
"fzinput:second-right-icon-click": [];
|
|
240
|
+
"update:highlighted": [value: boolean];
|
|
241
|
+
"update:aiReasoning": [value: boolean];
|
|
242
|
+
"fzinput:clear": [];
|
|
243
|
+
}>();
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Handles container interaction (click or keyboard) to focus the input
|
|
247
|
+
*
|
|
248
|
+
* Makes the entire container area clickable and keyboard-accessible for better UX,
|
|
249
|
+
* especially useful for floating-label variant and mobile devices.
|
|
250
|
+
* Respects disabled and readonly states.
|
|
251
|
+
*/
|
|
252
|
+
const handleContainerInteraction = () => {
|
|
253
|
+
if (!props.disabled && !props.readonly && inputRef.value) {
|
|
254
|
+
inputRef.value.focus();
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Handles keyboard events on container to focus input
|
|
260
|
+
*
|
|
261
|
+
* Supports Enter and Space keys following accessibility best practices.
|
|
262
|
+
* Only prevents default when event originates from container itself (not from child elements like input),
|
|
263
|
+
* allowing form submission when Enter is pressed inside the input field.
|
|
264
|
+
*
|
|
265
|
+
* @param e - Keyboard event
|
|
266
|
+
*/
|
|
267
|
+
const handleContainerKeydown = (e: KeyboardEvent) => {
|
|
268
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
269
|
+
// Only prevent default if event originated from container itself, not from child elements
|
|
270
|
+
// This allows Enter key presses in the input to trigger form submission
|
|
271
|
+
if (e.target === e.currentTarget || e.target === containerRef.value) {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
handleContainerInteraction();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Handles keyboard events on clickable icons
|
|
280
|
+
*
|
|
281
|
+
* Supports Enter and Space keys following accessibility best practices.
|
|
282
|
+
*
|
|
283
|
+
* @param e - Keyboard event
|
|
284
|
+
* @param emitEvent - Event name to emit when key is pressed
|
|
285
|
+
*/
|
|
286
|
+
const handleIconKeydown = (
|
|
287
|
+
e: KeyboardEvent,
|
|
288
|
+
emitEvent:
|
|
289
|
+
| "fzinput:left-icon-click"
|
|
290
|
+
| "fzinput:right-icon-click"
|
|
291
|
+
| "fzinput:second-right-icon-click",
|
|
292
|
+
) => {
|
|
293
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
269
294
|
e.preventDefault();
|
|
270
|
-
|
|
295
|
+
if (!isReadonlyOrDisabled.value) {
|
|
296
|
+
if (emitEvent === "fzinput:left-icon-click") {
|
|
297
|
+
emit("fzinput:left-icon-click");
|
|
298
|
+
} else if (emitEvent === "fzinput:right-icon-click") {
|
|
299
|
+
emit("fzinput:right-icon-click");
|
|
300
|
+
} else {
|
|
301
|
+
emit("fzinput:second-right-icon-click");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
271
304
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
* @param e - Keyboard event
|
|
281
|
-
* @param emitEvent - Event name to emit when key is pressed
|
|
282
|
-
*/
|
|
283
|
-
const handleIconKeydown = (
|
|
284
|
-
e: KeyboardEvent,
|
|
285
|
-
emitEvent:
|
|
286
|
-
| "fzinput:left-icon-click"
|
|
287
|
-
| "fzinput:right-icon-click"
|
|
288
|
-
| "fzinput:second-right-icon-click",
|
|
289
|
-
) => {
|
|
290
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
291
|
-
e.preventDefault();
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Handles left icon click events
|
|
309
|
+
*
|
|
310
|
+
* Respects disabled and readonly states - does not emit if input is disabled or readonly.
|
|
311
|
+
*/
|
|
312
|
+
const handleLeftIconClick = () => {
|
|
292
313
|
if (!isReadonlyOrDisabled.value) {
|
|
293
|
-
|
|
294
|
-
emit("fzinput:left-icon-click");
|
|
295
|
-
} else if (emitEvent === "fzinput:right-icon-click") {
|
|
296
|
-
emit("fzinput:right-icon-click");
|
|
297
|
-
} else {
|
|
298
|
-
emit("fzinput:second-right-icon-click");
|
|
299
|
-
}
|
|
314
|
+
emit("fzinput:left-icon-click");
|
|
300
315
|
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
() =>
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
() =>
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
containerRef,
|
|
411
|
-
});
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Handles right icon click events
|
|
320
|
+
*
|
|
321
|
+
* Respects disabled and readonly states - does not emit if input is disabled or readonly.
|
|
322
|
+
*/
|
|
323
|
+
const handleRightIconClick = () => {
|
|
324
|
+
if (!isReadonlyOrDisabled.value) {
|
|
325
|
+
emit("fzinput:right-icon-click");
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Handles second right icon click events
|
|
331
|
+
*
|
|
332
|
+
* Respects disabled and readonly states - does not emit if input is disabled or readonly.
|
|
333
|
+
*/
|
|
334
|
+
const handleSecondRightIconClick = () => {
|
|
335
|
+
if (!isReadonlyOrDisabled.value) {
|
|
336
|
+
emit("fzinput:second-right-icon-click");
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Determines if left icon is clickable (has click handler)
|
|
342
|
+
*/
|
|
343
|
+
const isLeftIconClickable = computed(() => !!props.leftIcon);
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Determines if left icon is keyboard-accessible (has aria-label)
|
|
347
|
+
*
|
|
348
|
+
* Icons are only accessible via keyboard when aria-label is provided.
|
|
349
|
+
*/
|
|
350
|
+
const isLeftIconAccessible = computed(
|
|
351
|
+
() => isLeftIconClickable.value && !!props.leftIconAriaLabel,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Determines if input is disabled or readonly
|
|
356
|
+
*
|
|
357
|
+
* Readonly inputs have the same visual styling and behavior as disabled inputs.
|
|
358
|
+
*/
|
|
359
|
+
const isReadonlyOrDisabled = computed(
|
|
360
|
+
() => !!props.disabled || !!props.readonly,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Computed class for the auto-rendered AI sparkles icon.
|
|
365
|
+
* Muted when the input is in error, disabled, or readonly state.
|
|
366
|
+
*/
|
|
367
|
+
const aiIconClass = computed(() => {
|
|
368
|
+
if (isReadonlyOrDisabled.value || props.error) return "text-grey-300";
|
|
369
|
+
return "text-purple-600";
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const emphasisDescription = computed(() => {
|
|
373
|
+
if (isReadonlyOrDisabled.value || props.error) return undefined;
|
|
374
|
+
if (effectiveHighlighted.value) return props.highlightedDescription;
|
|
375
|
+
if (effectiveAiReasoning.value) return props.aiReasoningDescription;
|
|
376
|
+
return undefined;
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Determines if right icon is clickable (not rendered as button)
|
|
381
|
+
*/
|
|
382
|
+
const isRightIconClickable = computed(
|
|
383
|
+
() => !!props.rightIcon && !props.rightIconButton,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Determines if right icon is keyboard-accessible (has aria-label)
|
|
388
|
+
*
|
|
389
|
+
* Icons are only accessible via keyboard when aria-label is provided.
|
|
390
|
+
*/
|
|
391
|
+
const isRightIconAccessible = computed(
|
|
392
|
+
() => isRightIconClickable.value && !!props.rightIconAriaLabel,
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Determines if second right icon is clickable (not rendered as button)
|
|
397
|
+
*/
|
|
398
|
+
const isSecondRightIconClickable = computed(
|
|
399
|
+
() => !!props.secondRightIcon && !props.secondRightIconButton,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Determines if second right icon is keyboard-accessible (has aria-label)
|
|
404
|
+
*
|
|
405
|
+
* Icons are only accessible via keyboard when aria-label is provided.
|
|
406
|
+
*/
|
|
407
|
+
const isSecondRightIconAccessible = computed(
|
|
408
|
+
() => isSecondRightIconClickable.value && !!props.secondRightIconAriaLabel,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const shouldShowClearIcon = computed(() => {
|
|
412
|
+
return props.clearable && !!model.value && !isReadonlyOrDisabled.value;
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const handleClear = () => {
|
|
416
|
+
model.value = "";
|
|
417
|
+
emit("fzinput:clear");
|
|
418
|
+
inputRef.value?.focus();
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
defineExpose({
|
|
422
|
+
inputRef,
|
|
423
|
+
containerRef,
|
|
424
|
+
});
|
|
412
425
|
</script>
|
|
413
426
|
|
|
414
427
|
<template>
|
|
415
428
|
<div class="fz-input w-full flex flex-col gap-8" :class="rootClass">
|
|
416
429
|
<slot name="label">
|
|
417
|
-
<label
|
|
418
|
-
v-if="label"
|
|
419
|
-
:id="`${uniqueId}-label`"
|
|
420
|
-
:class="computedLabelClass"
|
|
421
|
-
:for="uniqueId"
|
|
422
|
-
>
|
|
430
|
+
<label v-if="label" :id="`${uniqueId}-label`" :class="computedLabelClass" :for="uniqueId">
|
|
423
431
|
{{ label }}{{ required ? " *" : "" }}
|
|
424
432
|
</label>
|
|
425
433
|
</slot>
|
|
426
|
-
<div
|
|
427
|
-
:
|
|
428
|
-
|
|
429
|
-
:tabindex="isReadonlyOrDisabled ? undefined : 0"
|
|
430
|
-
@click="handleContainerInteraction"
|
|
431
|
-
@keydown="handleContainerKeydown"
|
|
432
|
-
>
|
|
434
|
+
<div :class="[staticContainerClass, computedContainerClass]" ref="containerRef"
|
|
435
|
+
:tabindex="isReadonlyOrDisabled ? undefined : 0" @click="handleContainerInteraction"
|
|
436
|
+
@keydown="handleContainerKeydown">
|
|
433
437
|
<slot name="left-icon">
|
|
434
|
-
<FzIcon
|
|
435
|
-
v-if="leftIcon"
|
|
436
|
-
:name="leftIcon"
|
|
437
|
-
size="md"
|
|
438
|
-
:variant="leftIconVariant"
|
|
438
|
+
<FzIcon v-if="leftIcon" :name="leftIcon" size="md" :variant="leftIconVariant"
|
|
439
439
|
:role="isLeftIconAccessible ? 'button' : undefined"
|
|
440
|
-
:aria-label="isLeftIconAccessible ? leftIconAriaLabel : undefined"
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
isLeftIconAccessible
|
|
451
|
-
? (e: KeyboardEvent) =>
|
|
452
|
-
handleIconKeydown(e, 'fzinput:left-icon-click')
|
|
453
|
-
: undefined
|
|
454
|
-
"
|
|
455
|
-
/>
|
|
456
|
-
<FzIcon
|
|
457
|
-
v-else-if="effectiveAiReasoning && !effectiveHighlighted"
|
|
458
|
-
name="sparkles"
|
|
459
|
-
variant="fas"
|
|
460
|
-
size="md"
|
|
461
|
-
aria-hidden="true"
|
|
462
|
-
:class="aiIconClass"
|
|
463
|
-
/>
|
|
440
|
+
:aria-label="isLeftIconAccessible ? leftIconAriaLabel : undefined" :aria-disabled="isLeftIconAccessible && isReadonlyOrDisabled ? 'true' : undefined
|
|
441
|
+
" :tabindex="isLeftIconAccessible && !isReadonlyOrDisabled ? 0 : undefined
|
|
442
|
+
" :class="leftIconClass" @click.stop="handleLeftIconClick" @keydown="
|
|
443
|
+
isLeftIconAccessible
|
|
444
|
+
? (e: KeyboardEvent) =>
|
|
445
|
+
handleIconKeydown(e, 'fzinput:left-icon-click')
|
|
446
|
+
: undefined
|
|
447
|
+
" />
|
|
448
|
+
<FzIcon v-else-if="effectiveAiReasoning && !effectiveHighlighted" name="sparkles" variant="fas" size="md"
|
|
449
|
+
aria-hidden="true" :class="aiIconClass" />
|
|
464
450
|
</slot>
|
|
465
451
|
<div class="flex flex-col justify-around min-w-0 grow">
|
|
466
|
-
<span
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
:
|
|
473
|
-
:
|
|
474
|
-
|
|
475
|
-
:readonly="readonly"
|
|
476
|
-
:placeholder="showNormalPlaceholder ? placeholder : ''"
|
|
477
|
-
v-model="model"
|
|
478
|
-
:id="uniqueId"
|
|
479
|
-
ref="inputRef"
|
|
480
|
-
:class="[staticInputClass, computedInputClass]"
|
|
481
|
-
:pattern="pattern"
|
|
482
|
-
:name
|
|
483
|
-
:maxlength
|
|
484
|
-
:autocomplete="autocomplete ? 'on' : 'off'"
|
|
485
|
-
:aria-required="required ? 'true' : 'false'"
|
|
486
|
-
:aria-invalid="error ? 'true' : 'false'"
|
|
487
|
-
:aria-disabled="isReadonlyOrDisabled ? 'true' : 'false'"
|
|
488
|
-
:aria-labelledby="ariaLabelledBy"
|
|
489
|
-
:aria-describedby="ariaDescribedBy"
|
|
490
|
-
:aria-description="emphasisDescription"
|
|
491
|
-
v-bind="inputAttrs"
|
|
492
|
-
@input="handleUserInput"
|
|
493
|
-
@blur="
|
|
452
|
+
<span v-if="!showNormalPlaceholder"
|
|
453
|
+
class="text-xs text-grey-300 grow-0 overflow-hidden text-ellipsis whitespace-nowrap">{{ placeholder }}</span>
|
|
454
|
+
<input :type="type" :required="required" :disabled="disabled" :readonly="readonly"
|
|
455
|
+
:placeholder="showNormalPlaceholder ? placeholder : ''" v-model="model" :id="uniqueId" ref="inputRef"
|
|
456
|
+
:class="[staticInputClass, computedInputClass]" :pattern="pattern" :name :maxlength
|
|
457
|
+
:autocomplete="autocomplete ? 'on' : 'off'" :aria-required="required ? 'true' : 'false'"
|
|
458
|
+
:aria-invalid="error ? 'true' : 'false'" :aria-disabled="isReadonlyOrDisabled ? 'true' : 'false'"
|
|
459
|
+
:aria-labelledby="ariaLabelledBy" :aria-describedby="ariaDescribedBy" :aria-description="emphasisDescription"
|
|
460
|
+
v-bind="inputAttrs" @input="handleUserInput" @blur="
|
|
494
461
|
(e) => {
|
|
495
462
|
isFocused = false;
|
|
496
463
|
$emit('blur', e);
|
|
497
464
|
}
|
|
498
|
-
"
|
|
499
|
-
@focus="
|
|
465
|
+
" @focus="
|
|
500
466
|
(e) => {
|
|
501
467
|
isFocused = true;
|
|
502
468
|
$emit('focus', e);
|
|
503
469
|
}
|
|
504
|
-
"
|
|
505
|
-
/>
|
|
470
|
+
" />
|
|
506
471
|
</div>
|
|
507
|
-
<
|
|
508
|
-
<
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
:role="isSecondRightIconAccessible ? 'button' : undefined"
|
|
515
|
-
:aria-label="
|
|
516
|
-
isSecondRightIconAccessible ? secondRightIconAriaLabel : undefined
|
|
517
|
-
"
|
|
518
|
-
:aria-disabled="
|
|
519
|
-
isSecondRightIconAccessible && isReadonlyOrDisabled
|
|
472
|
+
<div class="flex items-center gap-4">
|
|
473
|
+
<FzIconButton v-if="shouldShowClearIcon" iconName="xmark" size="md" variant="invisible"
|
|
474
|
+
:ariaLabel="clearAriaLabel" @click.stop="handleClear" />
|
|
475
|
+
<slot name="right-icon">
|
|
476
|
+
<FzIcon v-if="secondRightIcon && !secondRightIconButton" :name="secondRightIcon" size="md"
|
|
477
|
+
:variant="secondRightIconVariant" :role="isSecondRightIconAccessible ? 'button' : undefined" :aria-label="isSecondRightIconAccessible ? secondRightIconAriaLabel : undefined
|
|
478
|
+
" :aria-disabled="isSecondRightIconAccessible && isReadonlyOrDisabled
|
|
520
479
|
? 'true'
|
|
521
480
|
: undefined
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
<
|
|
538
|
-
v-if="secondRightIcon && secondRightIconButton"
|
|
539
|
-
:iconName="secondRightIcon"
|
|
540
|
-
size="md"
|
|
541
|
-
:iconVariant="secondRightIconVariant"
|
|
542
|
-
:variant="
|
|
543
|
-
isReadonlyOrDisabled ? 'invisible' : secondRightIconButtonVariant
|
|
544
|
-
"
|
|
545
|
-
@click.stop="handleSecondRightIconClick"
|
|
546
|
-
:class="[
|
|
547
|
-
{ 'bg-grey-100 !text-grey-300': isReadonlyOrDisabled },
|
|
548
|
-
secondRightIconClass,
|
|
549
|
-
]"
|
|
550
|
-
/>
|
|
551
|
-
<FzIcon
|
|
552
|
-
v-if="rightIcon && !rightIconButton"
|
|
553
|
-
:name="rightIcon"
|
|
554
|
-
size="md"
|
|
555
|
-
:variant="rightIconVariant"
|
|
481
|
+
" :tabindex="isSecondRightIconAccessible && !isReadonlyOrDisabled
|
|
482
|
+
? 0
|
|
483
|
+
: undefined
|
|
484
|
+
" :class="secondRightIconClass" @click.stop="handleSecondRightIconClick" @keydown="
|
|
485
|
+
isSecondRightIconAccessible
|
|
486
|
+
? (e: KeyboardEvent) =>
|
|
487
|
+
handleIconKeydown(e, 'fzinput:second-right-icon-click')
|
|
488
|
+
: undefined
|
|
489
|
+
" />
|
|
490
|
+
<FzIconButton v-if="secondRightIcon && secondRightIconButton" :iconName="secondRightIcon" size="md"
|
|
491
|
+
:iconVariant="secondRightIconVariant" :variant="isReadonlyOrDisabled ? 'invisible' : secondRightIconButtonVariant
|
|
492
|
+
" @click.stop="handleSecondRightIconClick" :class="[
|
|
493
|
+
{ 'bg-grey-100 !text-grey-300': isReadonlyOrDisabled },
|
|
494
|
+
secondRightIconClass,
|
|
495
|
+
]" />
|
|
496
|
+
<FzIcon v-if="rightIcon && !rightIconButton" :name="rightIcon" size="md" :variant="rightIconVariant"
|
|
556
497
|
:role="isRightIconAccessible ? 'button' : undefined"
|
|
557
|
-
:aria-label="isRightIconAccessible ? rightIconAriaLabel : undefined"
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
v-if="rightIcon && rightIconButton"
|
|
575
|
-
:iconName="rightIcon"
|
|
576
|
-
size="md"
|
|
577
|
-
:iconVariant="rightIconVariant"
|
|
578
|
-
:variant="
|
|
579
|
-
isReadonlyOrDisabled ? 'invisible' : rightIconButtonVariant
|
|
580
|
-
"
|
|
581
|
-
@click.stop="handleRightIconClick"
|
|
582
|
-
:class="[
|
|
583
|
-
{ 'bg-grey-100 !text-grey-300': isReadonlyOrDisabled },
|
|
584
|
-
rightIconClass,
|
|
585
|
-
]"
|
|
586
|
-
/>
|
|
587
|
-
<FzIcon
|
|
588
|
-
v-if="valid"
|
|
589
|
-
name="check"
|
|
590
|
-
size="md"
|
|
591
|
-
class="text-semantic-success"
|
|
592
|
-
aria-hidden="true"
|
|
593
|
-
/>
|
|
594
|
-
</div>
|
|
595
|
-
</slot>
|
|
498
|
+
:aria-label="isRightIconAccessible ? rightIconAriaLabel : undefined" :aria-disabled="isRightIconAccessible && isReadonlyOrDisabled ? 'true' : undefined
|
|
499
|
+
" :tabindex="isRightIconAccessible && !isReadonlyOrDisabled ? 0 : undefined
|
|
500
|
+
" :class="rightIconClass" @click.stop="handleRightIconClick" @keydown="
|
|
501
|
+
isRightIconAccessible
|
|
502
|
+
? (e: KeyboardEvent) =>
|
|
503
|
+
handleIconKeydown(e, 'fzinput:right-icon-click')
|
|
504
|
+
: undefined
|
|
505
|
+
" />
|
|
506
|
+
<FzIconButton v-if="rightIcon && rightIconButton" :iconName="rightIcon" size="md"
|
|
507
|
+
:iconVariant="rightIconVariant" :variant="isReadonlyOrDisabled ? 'invisible' : rightIconButtonVariant
|
|
508
|
+
" @click.stop="handleRightIconClick" :class="[
|
|
509
|
+
{ 'bg-grey-100 !text-grey-300': isReadonlyOrDisabled },
|
|
510
|
+
rightIconClass,
|
|
511
|
+
]" />
|
|
512
|
+
<FzIcon v-if="valid" name="check" size="md" class="text-semantic-success" aria-hidden="true" />
|
|
513
|
+
</slot>
|
|
514
|
+
</div>
|
|
596
515
|
</div>
|
|
597
|
-
<FzAlert
|
|
598
|
-
|
|
599
|
-
:id="`${uniqueId}-error`"
|
|
600
|
-
role="alert"
|
|
601
|
-
tone="error"
|
|
602
|
-
variant="text"
|
|
603
|
-
:style="{ width: containerWidth }"
|
|
604
|
-
>
|
|
516
|
+
<FzAlert v-if="error && $slots.errorMessage" :id="`${uniqueId}-error`" role="alert" tone="error" variant="text"
|
|
517
|
+
:style="{ width: containerWidth }">
|
|
605
518
|
<slot name="errorMessage"></slot>
|
|
606
519
|
</FzAlert>
|
|
607
|
-
<span
|
|
608
|
-
|
|
609
|
-
:id="`${uniqueId}-help`"
|
|
610
|
-
:class="[computedHelpClass]"
|
|
611
|
-
:style="{ width: containerWidth }"
|
|
612
|
-
>
|
|
520
|
+
<span v-else-if="$slots.helpText" :id="`${uniqueId}-help`" :class="[computedHelpClass]"
|
|
521
|
+
:style="{ width: containerWidth }">
|
|
613
522
|
<slot name="helpText"></slot>
|
|
614
523
|
</span>
|
|
615
524
|
</div>
|