dialog: allow disabling controls from rendering

This commit is contained in:
Elijah Duffy
2025-12-17 22:17:42 -08:00
parent 6630051d67
commit 3cdda64686
2 changed files with 82 additions and 56 deletions

View File

@@ -41,24 +41,29 @@
title: (title: string) => void; title: (title: string) => void;
} }
type DialogControlButton = {
label?: string;
class?: ClassValue;
action?: (dialog: DialogAPI) => void;
};
/** /**
* Configures the default dialog controls. * Configures the default dialog controls.
*/ */
export type DialogControlOpts = { export type DialogControls = {
cancel?: { cancel?: DialogControlButton | null;
label?: string; ok?: DialogControlButton | null;
class?: ClassValue; close?: Omit<DialogControlButton, 'label'> | null;
action?: (dialog: DialogAPI) => void; };
};
ok?: { const defaultDialogControls: DialogControls = {
label?: string; cancel: {
class?: ClassValue; label: 'Cancel'
action?: (dialog: DialogAPI) => void; },
}; ok: {
close?: { label: 'OK'
class?: ClassValue; },
action?: (dialog: DialogAPI) => void; close: {}
};
}; };
</script> </script>
@@ -72,6 +77,7 @@
import { X } from 'phosphor-svelte'; import { X } from 'phosphor-svelte';
import { ErrorMessage, type RawError } from './error'; import { ErrorMessage, type RawError } from './error';
import ErrorBox from './ErrorBox.svelte'; import ErrorBox from './ErrorBox.svelte';
import { mergeOverrideObject } from './util';
interface Props { interface Props {
open?: boolean; open?: boolean;
@@ -80,7 +86,7 @@
size?: 'sm' | 'md' | 'lg' | 'max'; size?: 'sm' | 'md' | 'lg' | 'max';
class?: ClassValue; class?: ClassValue;
children?: Snippet; children?: Snippet;
controls?: Snippet | DialogControlOpts; controls?: Snippet | DialogControls;
onopen?: (dialog: DialogAPI) => void; onopen?: (dialog: DialogAPI) => void;
onclose?: (dialog: DialogAPI) => void; onclose?: (dialog: DialogAPI) => void;
loading?: boolean; loading?: boolean;
@@ -95,7 +101,7 @@
size = 'sm', size = 'sm',
class: classValue, class: classValue,
children, children,
controls, controls: rawControls = defaultDialogControls,
onopen, onopen,
onclose, onclose,
loading = $bindable(false), loading = $bindable(false),
@@ -103,6 +109,12 @@
disabled = $bindable(false) disabled = $bindable(false)
}: Props = $props(); }: Props = $props();
let controls = $derived(
typeof rawControls === 'function'
? rawControls
: mergeOverrideObject(defaultDialogControls, rawControls)
);
let dialogContainer = $state<HTMLDivElement | null>(null); let dialogContainer = $state<HTMLDivElement | null>(null);
let error = $state<ErrorMessage | null>(null); let error = $state<ErrorMessage | null>(null);
@@ -192,49 +204,62 @@
{#if children}{@render children()}{:else}Dialog is empty{/if} {#if children}{@render children()}{:else}Dialog is empty{/if}
<!-- Dialog Controls -->
<div class="mt-6 flex justify-end gap-4"> <div class="mt-6 flex justify-end gap-4">
{#if controls && typeof controls === 'function'}{@render controls()}{:else} {#if controls && typeof controls === 'function'}{@render controls()}{:else}
<Button {#if controls.cancel !== null}
class={controls?.cancel?.class} <Button
onclick={() => { class={controls?.cancel?.class}
if (controls?.cancel?.action) { onclick={() => {
controls.cancel.action(dialogAPI); if (controls?.cancel?.action) {
} else if (!frozen) { controls.cancel.action(dialogAPI);
open = false; } else if (!frozen) {
} open = false;
}} }
disabled={frozen} }}
> disabled={frozen}
{controls?.cancel?.label || 'Cancel'} >
</Button> {controls?.cancel?.label || 'Cancel'}
<Button </Button>
class={controls?.ok?.class} {/if}
onclick={() => { {#if controls.ok !== null}
if (controls?.ok?.action) { <Button
controls.ok.action(dialogAPI); class={controls?.ok?.class}
} else if (!frozen && !loading && !disabled) { onclick={() => {
open = false; if (controls?.ok?.action) {
} controls.ok.action(dialogAPI);
}} } else if (!frozen && !loading && !disabled) {
disabled={frozen || loading || disabled} open = false;
{loading} }
> }}
{controls?.ok?.label || 'OK'} disabled={frozen || loading || disabled}
</Button> {loading}
>
{controls?.ok?.label || 'OK'}
</Button>
{/if}
{/if} {/if}
</div> </div>
<button
type="button" <!-- Close Button -->
aria-label="close" {#if typeof controls === 'function' || controls?.close !== null}
class="absolute top-4 right-4 inline-flex cursor-pointer items-center <button
type="button"
aria-label="close"
class="absolute top-4 right-4 inline-flex cursor-pointer items-center
justify-center disabled:cursor-not-allowed disabled:opacity-50" justify-center disabled:cursor-not-allowed disabled:opacity-50"
onclick={() => { onclick={() => {
if (!frozen) open = false; if (typeof controls !== 'function' && controls?.close?.action) {
}} controls?.close?.action?.(dialogAPI);
disabled={frozen} } else if (!frozen) {
> open = false;
<X size="1.5em" weight="bold" /> }
</button> }}
disabled={frozen}
>
<X size="1.5em" weight="bold" />
</button>
{/if}
</div> </div>
</div> </div>
{/snippet} {/snippet}

View File

@@ -523,7 +523,8 @@
dialog.close(); dialog.close();
alert('Dialog submitted!'); alert('Dialog submitted!');
} }
} },
cancel: null
}} }}
onopen={(dialog) => { onopen={(dialog) => {
dialog.error('Example error message!'); dialog.error('Example error message!');