diff --git a/registration/src/lib/components/forms/steps/registration/CheckNickNameStep.svelte b/registration/src/lib/components/forms/steps/registration/CheckNickNameStep.svelte index 85640e0..681fa0f 100644 --- a/registration/src/lib/components/forms/steps/registration/CheckNickNameStep.svelte +++ b/registration/src/lib/components/forms/steps/registration/CheckNickNameStep.svelte @@ -53,7 +53,10 @@ lastName, nickName, accepted - }).success || nickNameTaken || formLoading || !nickNamechecked; + }).success || + nickNameTaken || + formLoading || + !nickNamechecked; const handler = async () => { if (disabled) return; @@ -117,6 +120,7 @@ info={true} infoTitle={$t('Matrix ID/Email')} infoDescription={$t('username_info_tooltip')} + onStopTyping={checkNickName} /> {#if nickNameTaken === true} - import { clickOutside } from '$utils/html'; + import { clickOutside, stopTyping } from '$utils/html'; import ErrorIcon from '$components/icons/ErrorIcon.svelte'; import InfoIcon from '$components/icons/InfoIcon.svelte'; import Spin from '$components/icons/SpinnerIcon.svelte'; import Valid from '$components/icons/ValidIcon.svelte'; import InfoTooltip from '$components/display/InfoTooltip.svelte'; + import { onMount } from 'svelte'; export let label: string; export let placeholder: string; @@ -19,8 +20,17 @@ export let info: boolean = false; export let infoTitle: string = ''; export let infoDescription: string = ''; + export let onStopTyping: (() => void) | undefined = undefined; + let inputRef: HTMLInputElement; let showInfo = false; + const action = onStopTyping ? stopTyping : () => {}; + + onMount(() => { + if (onStopTyping) { + inputRef.addEventListener('stopTyping', onStopTyping); + } + }); $: notValid = value.length > 0 && isInValid; @@ -30,6 +40,8 @@ {/if} void) => { +import { debounce } from '$utils/timeout'; + +type ActionType = { + destroy: () => void; +}; + +/** + * Click outside svelte action. + * + * calls a handler when the user clicks outside + * + * @param {HTMLElement} node - The node to listen to. + * @param {Function} onEventFunction - The function to call when the event is triggered. + * @example + *
+ * @returns {ActionType} + */ +export const clickOutside = (node: HTMLElement, onEventFunction: () => void): ActionType => { const handleClick = (event: Event) => { const path = event.composedPath(); @@ -15,3 +32,29 @@ export const clickOutside = (node: HTMLElement, onEventFunction: () => void) => } }; }; + +/** + * Stop typing svelte action. + * + * fires a custom event when the user stops typing after some delay. + * + * @param {HTMLElement} node - The node to listen to. + * @example + * + * @returns {ActionType} + */ +export const stopTyping = (node: HTMLElement): ActionType => { + const handler = debounce((event: Event) => { + if (node.contains(event.target as Node)) { + node.dispatchEvent(new CustomEvent('stopTyping')); + } + }); + + node.addEventListener('input', handler); + + return { + destroy() { + node.removeEventListener('input', handler); + } + }; +}; diff --git a/registration/src/lib/utils/timeout.ts b/registration/src/lib/utils/timeout.ts new file mode 100644 index 0000000..bb232fe --- /dev/null +++ b/registration/src/lib/utils/timeout.ts @@ -0,0 +1,21 @@ +/** + * Debounce function + * + * @param fn Function to debounce + * @param waitFor Time to wait before executing the function + * + * @example + * const fn = () => console.log('Debounced!'); + * const debounced = debounce(fn, 1000); + */ +export const debounce = ( + fn: (...args: Params) => void, + waitFor = 800 +) => { + let timer: NodeJS.Timeout; + + return (...args: Params) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), waitFor); + }; +};