Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(OnyxForm): Add successMessage to useCustomValidity composable #2031

Merged
merged 7 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import { type FormErrorMessages, getCustomErrorText } from "../../composables/useCustomValidity";
import { type FormMessages, getCustomText } from "../../composables/useCustomValidity";
import OnyxTooltip from "../OnyxTooltip/OnyxTooltip.vue";

const props = defineProps<{
Expand All @@ -9,7 +9,7 @@ const props = defineProps<{
* errorMessages are provided. Without errorMessages, the
* component will not be rendered inside a slot.
*/
errorMessages?: FormErrorMessages;
errorMessages?: FormMessages;
/** We don't show an error if the content is not interactive */
disabled?: boolean;
}>();
Expand All @@ -21,7 +21,7 @@ defineSlots<{
default(): unknown;
}>();

const tooltipError = computed(() => getCustomErrorText(props.errorMessages));
const tooltipError = computed(() => getCustomText(props.errorMessages));

const targetRef = ref<HTMLDivElement>();
</script>
Expand Down
4 changes: 2 additions & 2 deletions packages/sit-onyx/src/components/OnyxFormElement/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RequiredMarkerProp } from "../../composables/required";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";

export type OnyxFormElementProps = RequiredMarkerProp & {
/**
Expand Down Expand Up @@ -40,7 +40,7 @@ export type OnyxFormElementProps = RequiredMarkerProp & {
/**
* Error messages that inform about causes for invalidity of form components
*/
errorMessages?: FormErrorMessages;
errorMessages?: FormMessages;
/**
* Maximum number of characters that are allowed to be entered.
* Warning: when the value is (pre)set programmatically,
Expand Down
4 changes: 2 additions & 2 deletions packages/sit-onyx/src/components/OnyxInput/OnyxInput.ct.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DENSITIES } from "../../composables/density";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";
import { expect, test } from "../../playwright/a11y";
import { executeMatrixScreenshotTest } from "../../playwright/screenshots";
import { createFormElementUtils } from "../OnyxFormElement/OnyxFormElement.ct-utils";
Expand Down Expand Up @@ -108,7 +108,7 @@ test.describe("Screenshot tests", () => {
const message = showLongMessage
? "Very long message that should be truncated"
: "Test message";
const errorMessages: FormErrorMessages = {
const errorMessages: FormMessages = {
shortMessage: showLongMessage
? "Very long error preview that should be truncated"
: "Test error",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { MountResultJsx } from "@playwright/experimental-ct-vue";
import { comboboxSelectOnlyTesting, comboboxTesting } from "@sit-onyx/headless/playwright";
import { DENSITIES } from "../../composables/density";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";
import { expect, test } from "../../playwright/a11y";
import {
adjustAbsolutePositionScreenshot,
Expand Down Expand Up @@ -315,7 +315,7 @@ test.describe("Invalidity handling screenshots", () => {
const message = showLongMessage
? "Very long message that should be truncated"
: "Test message";
const errorMessages: FormErrorMessages = {
const errorMessages: FormMessages = {
shortMessage: showLongMessage
? "Very long error preview that should be truncated"
: "Test error",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DENSITIES } from "../../composables/density";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";
import { expect, test } from "../../playwright/a11y";
import { executeMatrixScreenshotTest } from "../../playwright/screenshots";
import { createFormElementUtils } from "../OnyxFormElement/OnyxFormElement.ct-utils";
Expand Down Expand Up @@ -100,7 +100,7 @@ test.describe("Screenshot tests", () => {
const message = showLongMessage
? "Very long message that should be truncated"
: "Test message";
const errorMessages: FormErrorMessages = {
const errorMessages: FormMessages = {
shortMessage: showLongMessage
? "Very long error preview that should be truncated"
: "Test error",
Expand Down
63 changes: 43 additions & 20 deletions packages/sit-onyx/src/composables/useCustomValidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import type { BaseSelectOption } from "../types";
import { areObjectsFlatEqual } from "../utils/objects";
import { getFirstInvalidType, transformValidityStateToObject } from "../utils/validity";

export type CustomErrorType = string | FormErrorMessages;
export type CustomMessageType = string | FormMessages;

export type CustomValidityProp = {
/**
* Custom error message to show. Will only show up after the user has interacted with the input.
*/
customError?: CustomErrorType;
customError?: CustomMessageType;
/**
* Custom success message to show. Will only show up after the user has interacted with the input.
*/
customSuccess?: CustomMessageType;
MajaZarkova marked this conversation as resolved.
Show resolved Hide resolved
};

export type UseCustomValidityOptions = {
Expand Down Expand Up @@ -45,39 +49,38 @@ export const TRANSLATED_INPUT_TYPES = Object.keys(
export type TranslatedInputType = (typeof TRANSLATED_INPUT_TYPES)[number];

/**
* Translated error messages that inform about causes for invalidity of form components
* Translated messages that inform about the validity state of form components
*/
export type FormErrorMessages = {
export type FormMessages = {
/**
* A short error message preview to inform the user about the cause of the error
* A short message preview to inform the user about the validity state
*/
shortMessage: string;
/**
* An extended informative error message to provide more info
* how the error cause can be resolved
* An extended informative message to provide more info
*/
longMessage?: string;
};

/**
* Transforms a customError into the format needed to display an error preview and extended message
* Transforms a customMessage into the format needed to display an error preview and extended message
*/
export const getCustomErrors = (customError?: CustomErrorType): FormErrorMessages | undefined => {
if (!customError) return;
if (typeof customError === "string") {
// we can't guarantee a custom error message will be short,
export const getCustomMessages = (customMessage?: CustomMessageType): FormMessages | undefined => {
if (!customMessage) return;
if (typeof customMessage === "string") {
// we can't guarantee a custom message will be short,
// so in case it overflows, by adding it to "longMessage",
// it will still be visible in a tooltip
return { shortMessage: customError, longMessage: customError };
return { shortMessage: customMessage, longMessage: customMessage };
}
return customError;
return customMessage;
};

/**
* Returns a string combining short + long message or just the customError if it was provided as single string.
* Returns a string combining short + long message or just the customMessage if it was provided as single string.
* Will be used e.g. for customInvalidity and showing a tooltip e.g. in RadioButtons
*/
export const getCustomErrorText = (customError?: CustomErrorType): string | undefined => {
export const getCustomText = (customError?: CustomMessageType): string | undefined => {
MajaZarkova marked this conversation as resolved.
Show resolved Hide resolved
if (!customError) return;
if (typeof customError === "string") {
return customError;
Expand All @@ -90,7 +93,7 @@ export const getCustomErrorText = (customError?: CustomErrorType): string | unde
};

/**
* Composable for unified handling of custom error messages for form components.
* Composable for unified handling of custom messages for form components.
* Will call `setCustomValidity()` accordingly and emit the "validityChange" event
* whenever the input value / error changes.
*
Expand Down Expand Up @@ -128,7 +131,7 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
/**
* Sync custom error with the native input validity.
*/
watchEffect(() => el.setCustomValidity(getCustomErrorText(options.props.customError) ?? ""));
watchEffect(() => el.setCustomValidity(getCustomText(options.props.customError) ?? ""));

watch(
// we need to watch all props instead of only modelValue so the validity is re-checked
Expand Down Expand Up @@ -166,11 +169,11 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
},
} satisfies Directive<InputValidationElement, undefined>;

const errorMessages = computed<FormErrorMessages | undefined>(() => {
const errorMessages = computed<FormMessages | undefined>(() => {
if (!validityState.value || validityState.value.valid) return;

const errorType = getFirstInvalidType(validityState.value);
const customErrors = getCustomErrors(options.props.customError);
const customErrors = getCustomMessages(options.props.customError);
// a custom error message always is considered first
if (customErrors || errorType === "customError") {
if (!customErrors) return;
Expand Down Expand Up @@ -207,6 +210,22 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
};
});

const successMessages = computed<FormMessages | undefined>(() => {
if (validityState.value === undefined || !validityState.value.valid) return;

const errorType = getFirstInvalidType(validityState.value);
const customErrors = getCustomMessages(options.props.customError);

// a custom error message always is considered first
if (customErrors || errorType === "customError") {
if (customErrors) return;
}
MajaZarkova marked this conversation as resolved.
Show resolved Hide resolved

const customSuccess = getCustomMessages(options.props.customSuccess);

return customSuccess;
});

return {
/**
* Directive to set the custom error message and emit validityChange event.
Expand All @@ -216,5 +235,9 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
* A custom error or the default translation of the first invalid state if one exists.
*/
errorMessages,
/**
* A custom success message if provided by the user.
*/
successMessages,
};
};
Loading