diff --git a/eslint.config.mjs b/eslint.config.mjs index b123358..92b6ece 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -32,8 +32,6 @@ export default ts.config( }, { rules: { - // expressions are often used to check if a props function is defined before calling it - '@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-explicit-any': 'off' } } diff --git a/src/lib/components/Input/Input.svelte b/src/lib/components/Input/Input.svelte index e471464..ba32f03 100644 --- a/src/lib/components/Input/Input.svelte +++ b/src/lib/components/Input/Input.svelte @@ -14,7 +14,7 @@ required = false, disabled = false, readonly = false, - checked = false, + checked = $bindable(false), size = 'md', pickyWidth = true, containerClass = '', @@ -109,10 +109,15 @@ {readonly} bind:this={inputElement} autocomplete="off" + onchange={() => { + if (type === 'checkbox') { + checked = !checked; + } + }} oninput={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => { value = e.currentTarget.value; if (pattern && !pattern.test(value)) return; - oninput && oninput(e); + oninput?.(e); }} onpaste={(e) => { if (pattern && e.clipboardData && !pattern.test(e.clipboardData.getData('text'))) { diff --git a/src/lib/components/Input/Search.svelte b/src/lib/components/Input/Search.svelte index c83be89..c167129 100644 --- a/src/lib/components/Input/Search.svelte +++ b/src/lib/components/Input/Search.svelte @@ -63,14 +63,14 @@ value = searchSuggestion.value; searchSuggestions = []; e.currentTarget.setCustomValidity(''); - onsubmit && onsubmit(Object.assign(e, { input: inputValue, value: value })); + onsubmit?.(Object.assign(e, { input: inputValue, value: value })); } else if (inputValue === '' && emptyAllowed) { - onsubmit && onsubmit(Object.assign(e, { input: '', value: '' })); + onsubmit?.(Object.assign(e, { input: '', value: '' })); } }); }} oninvalid={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => { - invalidMessage && e.currentTarget.setCustomValidity(invalidMessage); + if (invalidMessage) e.currentTarget.setCustomValidity(invalidMessage); }} pattern={suggestionRequired ? `${value ? inputValue : 'a^' + (emptyAllowed ? '|$^' : '')}` @@ -89,7 +89,7 @@ inputValue = searchSuggestion.name; value = searchSuggestion.value; searchSuggestions = []; - onsubmit && onsubmit(Object.assign(e, { input: inputValue, value: value })); + onsubmit?.(Object.assign(e, { input: inputValue, value: value })); }}>{searchSuggestion.name} diff --git a/src/lib/context.ts b/src/lib/context.ts new file mode 100644 index 0000000..5e33a49 --- /dev/null +++ b/src/lib/context.ts @@ -0,0 +1,18 @@ +import { getContext } from 'svelte'; + +export type PopupModalContextArgs = { + title: string; + text?: string; + actions?: { text: string; action?: (modal: Event) => void }[]; + onClose?: () => void; +}; +export function getPopupModalShowFn(): ({ + title, + text, + actions, + onClose +}: PopupModalContextArgs) => void { + const { set }: { set: ({ title, text, actions, onClose }: PopupModalContextArgs) => void } = + getContext('globalPopupModal'); + return set; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3406f91..1150521 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -3,6 +3,9 @@ import { env } from '$env/dynamic/public'; import { goto } from '$app/navigation'; import { page } from '$app/stores'; + import Input from '$lib/components/Input/Input.svelte'; + import { setContext, tick } from 'svelte'; + import type { PopupModalContextArgs } from '$lib/context'; let { children } = $props(); @@ -60,7 +63,20 @@ } }); + let popupModalState: PopupModalContextArgs | null = $state(null); + // eslint-disable-next-line no-undef + let popupModalNullTimeout: number | NodeJS.Timeout | null = null; + setContext('globalPopupModal', { + set: async ({ title, text, actions, onClose }: PopupModalContextArgs) => { + if (popupModalNullTimeout) clearTimeout(popupModalNullTimeout); + popupModalState = { title, text, actions, onClose }; + await tick(); + popupModalElem.showModal(); + } + }); + let navElem: HTMLDivElement; + let popupModalElem: HTMLDialogElement; @@ -179,6 +195,48 @@ {/if} + + {#if popupModalState} + { + popupModalNullTimeout = setTimeout(() => { + popupModalState = null; + popupModalNullTimeout = null; + }, 200); + popupModalState?.onClose?.(); + }} + > + ✕ + {popupModalState.title} + {#if popupModalState.text} + {popupModalState.text} + {/if} + {#if popupModalState.actions} + + {#each popupModalState.actions as action} + action.action?.(e)} /> + {/each} + + {/if} + + { + popupModalNullTimeout = setTimeout(() => { + popupModalState = null; + popupModalNullTimeout = null; + }, 200); + popupModalState?.onClose?.(); + }} + > + close + + {/if} + +
{popupModalState.text}