Files
Automata/packages/design-system/src/components/N8nFormInput/FormInput.vue
Milorad FIlipović 4e491b754f fix(editor): Vue3 - Fix modal positioning and multi-select tag sizing (#6783)
*  Updating modals positioning within the overlay
* 💄 Implemented multi-select variant with small tabs
* ✔️ Removing password link clicks while modal is open in e2e tests
* Set generous timeout for $paramter resolve
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
---------
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Oleg Ivaniv <me@olegivaniv.com>
2023-07-28 17:57:25 +02:00

284 lines
6.4 KiB
Vue

<template>
<n8n-checkbox
v-if="type === 'checkbox'"
v-bind="$props"
@update:modelValue="onUpdateModelValue"
@focus="onFocus"
ref="inputRef"
/>
<n8n-input-label
v-else-if="type === 'toggle'"
:inputName="name"
:label="label"
:tooltipText="tooltipText"
:required="required && showRequiredAsterisk"
>
<template #content>
{{ tooltipText }}
</template>
<el-switch
:modelValue="modelValue"
:active-color="activeColor"
:inactive-color="inactiveColor"
@update:modelValue="onUpdateModelValue"
></el-switch>
</n8n-input-label>
<n8n-input-label
v-else
:inputName="name"
:label="label"
:tooltipText="tooltipText"
:required="required && showRequiredAsterisk"
>
<div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter">
<slot v-if="hasDefaultSlot" />
<n8n-select
:class="{ [$style.multiSelectSmallTags]: tagSize === 'small' }"
v-else-if="type === 'select' || type === 'multi-select'"
:modelValue="modelValue"
:placeholder="placeholder"
:multiple="type === 'multi-select'"
:disabled="disabled"
@update:modelValue="onUpdateModelValue"
@focus="onFocus"
@blur="onBlur"
:name="name"
:teleported="teleported"
ref="inputRef"
>
<n8n-option
v-for="option in options || []"
:key="option.value"
:value="option.value"
:label="option.label"
size="small"
/>
</n8n-select>
<n8n-input
v-else
:name="name"
:type="type"
:placeholder="placeholder"
:modelValue="modelValue"
:maxlength="maxlength"
:autocomplete="autocomplete"
:disabled="disabled"
@update:modelValue="onUpdateModelValue"
@blur="onBlur"
@focus="onFocus"
ref="inputRef"
/>
</div>
<div :class="$style.errors" v-if="showErrors">
<span v-text="validationError" />
<n8n-link
v-if="documentationUrl && documentationText"
:to="documentationUrl"
:newWindow="true"
size="small"
theme="danger"
>
{{ documentationText }}
</n8n-link>
</div>
<div :class="$style.infoText" v-else-if="infoText">
<span size="small" v-text="infoText" />
</div>
</n8n-input-label>
</template>
<script lang="ts" setup>
import { computed, reactive, onMounted, ref, watch, useSlots } from 'vue';
import N8nInput from '../N8nInput';
import N8nSelect from '../N8nSelect';
import N8nOption from '../N8nOption';
import N8nInputLabel from '../N8nInputLabel';
import N8nCheckbox from '../N8nCheckbox';
import { ElSwitch } from 'element-plus';
import { getValidationError, VALIDATORS } from './validators';
import type { Rule, RuleGroup, IValidator, Validatable, FormState } from '../../types';
import { t } from '../../locale';
export interface Props {
modelValue: Validatable;
label: string;
infoText?: string;
required?: boolean;
showRequiredAsterisk?: boolean;
type?: string;
placeholder?: string;
tooltipText?: string;
showValidationWarnings?: boolean;
validateOnBlur?: boolean;
documentationUrl?: string;
documentationText?: string;
validationRules?: Array<Rule | RuleGroup>;
validators?: { [key: string]: IValidator | RuleGroup };
maxlength?: number;
options?: Array<{ value: string | number; label: string }>;
autocomplete?: string;
name?: string;
focusInitially?: boolean;
labelSize?: 'small' | 'medium';
disabled?: boolean;
activeLabel?: string;
activeColor?: string;
inactiveLabel?: string;
inactiveColor?: string;
teleported?: boolean;
tagSize?: 'small' | 'medium';
}
const props = withDefaults(defineProps<Props>(), {
documentationText: 'Open docs',
labelSize: 'medium',
type: 'text',
showRequiredAsterisk: true,
validateOnBlur: true,
teleported: true,
tagSize: 'small',
});
const emit = defineEmits<{
(event: 'validate', shouldValidate: boolean): void;
(event: 'update:modelValue', value: unknown): void;
(event: 'focus'): void;
(event: 'blur'): void;
(event: 'enter'): void;
}>();
const state = reactive({
hasBlurred: false,
isTyping: false,
});
const slots = useSlots();
const inputRef = ref<HTMLTextAreaElement | null>(null);
function getInputValidationError(): ReturnType<IValidator['validate']> {
const rules = props.validationRules || [];
const validators = {
...VALIDATORS,
...(props.validators || {}),
} as { [key: string]: IValidator | RuleGroup };
if (props.required) {
const error = getValidationError(
props.modelValue,
validators,
validators.REQUIRED as IValidator,
);
if (error) return error;
}
for (let i = 0; i < rules.length; i++) {
if (rules[i].hasOwnProperty('name')) {
const rule = rules[i] as Rule;
if (validators[rule.name]) {
const error = getValidationError(
props.modelValue,
validators,
validators[rule.name] as IValidator,
rule.config,
);
if (error) return error;
}
}
if (rules[i].hasOwnProperty('rules')) {
const rule = rules[i] as RuleGroup;
const error = getValidationError(props.modelValue, validators, rule);
if (error) return error;
}
}
return null;
}
function onBlur() {
state.hasBlurred = true;
state.isTyping = false;
emit('blur');
}
function onUpdateModelValue(value: FormState) {
state.isTyping = true;
emit('update:modelValue', value);
}
function onFocus() {
emit('focus');
}
function onEnter(event: Event) {
event.stopPropagation();
event.preventDefault();
emit('enter');
}
const validationError = computed<string | null>(() => {
const error = getInputValidationError();
if (error) {
if (error.messageKey) {
return t(error.messageKey, error.options);
} else {
return error.message;
}
}
return null;
});
const hasDefaultSlot = computed(() => !!slots.default);
const showErrors = computed(
() =>
!!validationError.value &&
((props.validateOnBlur && state.hasBlurred && !state.isTyping) || props.showValidationWarnings),
);
onMounted(() => {
emit('validate', !validationError.value);
if (props.focusInitially && inputRef.value) inputRef.value.focus();
});
watch(
() => validationError.value,
(error) => emit('validate', !error),
);
defineExpose({ inputRef });
</script>
<style lang="scss" module>
.infoText {
margin-top: var(--spacing-2xs);
font-size: var(--font-size-2xs);
font-weight: var(--font-weight-regular);
color: var(--color-text-base);
}
.errors {
composes: infoText;
color: var(--color-danger);
}
.errorInput {
--input-border-color: var(--color-danger);
}
.multiSelectSmallTags {
:global(.el-tag) {
height: 24px;
padding: 0 8px;
line-height: 22px;
}
}
</style>