@placeholderco/placeholder-ui 2.5.5 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/ui/ConfirmDialog.svelte +191 -0
- package/dist/ui/ConfirmDialog.svelte.d.ts +52 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { default as Badge } from './ui/Badge.svelte';
|
|
|
10
10
|
export type { BadgeProps } from './ui/Badge.svelte';
|
|
11
11
|
export { default as Dialog } from './ui/Dialog.svelte';
|
|
12
12
|
export type { DialogProps, DialogContext } from './ui/Dialog.svelte';
|
|
13
|
+
export { default as ConfirmDialog } from './ui/ConfirmDialog.svelte';
|
|
14
|
+
export type { ConfirmDialogProps } from './ui/ConfirmDialog.svelte';
|
|
13
15
|
export { default as Drawer } from './ui/Drawer.svelte';
|
|
14
16
|
export type { DrawerProps } from './ui/Drawer.svelte';
|
|
15
17
|
export { default as Dropdown } from './ui/Dropdown.svelte';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { default as Button } from './ui/Button.svelte';
|
|
|
4
4
|
export { default as ActionIcon } from './ui/ActionIcon.svelte';
|
|
5
5
|
export { default as Badge } from './ui/Badge.svelte';
|
|
6
6
|
export { default as Dialog } from './ui/Dialog.svelte';
|
|
7
|
+
export { default as ConfirmDialog } from './ui/ConfirmDialog.svelte';
|
|
7
8
|
export { default as Drawer } from './ui/Drawer.svelte';
|
|
8
9
|
export { default as Dropdown } from './ui/Dropdown.svelte';
|
|
9
10
|
export { default as Dropzone } from './ui/Dropzone.svelte';
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import Button from './Button.svelte';
|
|
4
|
+
import Dialog from './Dialog.svelte';
|
|
5
|
+
import type { ButtonVariant } from './ButtonVariant.js';
|
|
6
|
+
|
|
7
|
+
export interface ConfirmDialogProps {
|
|
8
|
+
/** Whether the dialog is visible (bindable) */
|
|
9
|
+
show: boolean;
|
|
10
|
+
/** Dialog title displayed in the header */
|
|
11
|
+
title?: string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Plain-text body. Ignored when `children` is provided, so pass a snippet
|
|
14
|
+
* for richer content.
|
|
15
|
+
*/
|
|
16
|
+
message?: string | undefined;
|
|
17
|
+
/** Label for the confirming button */
|
|
18
|
+
okText?: string;
|
|
19
|
+
/** Label for the dismissing button */
|
|
20
|
+
cancelText?: string;
|
|
21
|
+
/** Visual variant of the confirming button (use `danger` for destructive actions) */
|
|
22
|
+
okVariant?: ButtonVariant;
|
|
23
|
+
/** Visual variant of the dismissing button */
|
|
24
|
+
cancelVariant?: ButtonVariant;
|
|
25
|
+
/** Icon shown on the left of the confirming button */
|
|
26
|
+
okSvg?: string;
|
|
27
|
+
/** Disable the confirming button (e.g. while a form is invalid) */
|
|
28
|
+
okDisabled?: boolean;
|
|
29
|
+
/** Hide the Cancel button entirely, leaving an acknowledgement-style dialog */
|
|
30
|
+
hideCancel?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Which button receives focus when the dialog opens. Defaults to the
|
|
33
|
+
* confirming button, or to Cancel when `okVariant` is a danger variant so
|
|
34
|
+
* a stray Enter cannot trigger a destructive action.
|
|
35
|
+
*/
|
|
36
|
+
initialFocus?: 'ok' | 'cancel' | 'none';
|
|
37
|
+
/**
|
|
38
|
+
* Called when OK is pressed. Returning a promise puts the OK button into a
|
|
39
|
+
* loading state and keeps the dialog open until it settles. Return `false`
|
|
40
|
+
* (or resolve to `false`) to keep the dialog open.
|
|
41
|
+
*/
|
|
42
|
+
onConfirm?: () => void | boolean | Promise<void | boolean>;
|
|
43
|
+
/** Called when the dialog is dismissed via Cancel, the close icon, Escape or the backdrop */
|
|
44
|
+
onCancel?: () => void;
|
|
45
|
+
/** Allow closing by clicking backdrop or pressing Escape */
|
|
46
|
+
allowImplicitClose?: boolean;
|
|
47
|
+
/** Size preset for the dialog width */
|
|
48
|
+
size?: 'md' | 'lg' | 'xl' | 'full';
|
|
49
|
+
/** Additional CSS classes passed through to the dialog */
|
|
50
|
+
class?: string;
|
|
51
|
+
/** Custom body content; overrides `message` */
|
|
52
|
+
children?: Snippet;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let {
|
|
56
|
+
show = $bindable(false),
|
|
57
|
+
title = undefined,
|
|
58
|
+
message = undefined,
|
|
59
|
+
okText = 'Save',
|
|
60
|
+
cancelText = 'Cancel',
|
|
61
|
+
okVariant = 'primary',
|
|
62
|
+
cancelVariant = 'secondary',
|
|
63
|
+
okSvg = undefined,
|
|
64
|
+
okDisabled = false,
|
|
65
|
+
hideCancel = false,
|
|
66
|
+
initialFocus = undefined,
|
|
67
|
+
onConfirm = undefined,
|
|
68
|
+
onCancel = undefined,
|
|
69
|
+
allowImplicitClose = true,
|
|
70
|
+
size = 'md',
|
|
71
|
+
class: classes = '',
|
|
72
|
+
children
|
|
73
|
+
}: ConfirmDialogProps = $props();
|
|
74
|
+
|
|
75
|
+
let loading = $state(false);
|
|
76
|
+
let footerElement: HTMLDivElement | undefined = $state(undefined);
|
|
77
|
+
|
|
78
|
+
// Track whether the close was the result of OK so the cancel callback only
|
|
79
|
+
// fires for genuine dismissals (Cancel button, close icon, Escape, backdrop)
|
|
80
|
+
let confirmed = false;
|
|
81
|
+
let wasOpen = false;
|
|
82
|
+
|
|
83
|
+
const resolvedFocus = $derived(
|
|
84
|
+
initialFocus ?? (okVariant.startsWith('danger') && !hideCancel ? 'cancel' : 'ok')
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
$effect(() => {
|
|
88
|
+
if (show && !wasOpen) {
|
|
89
|
+
wasOpen = true;
|
|
90
|
+
confirmed = false;
|
|
91
|
+
loading = false;
|
|
92
|
+
focusInitialButton();
|
|
93
|
+
} else if (!show && wasOpen) {
|
|
94
|
+
wasOpen = false;
|
|
95
|
+
loading = false;
|
|
96
|
+
if (!confirmed) onCancel?.();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
function focusInitialButton() {
|
|
101
|
+
if (resolvedFocus === 'none') return;
|
|
102
|
+
// showModal() runs in the parent Dialog's effect and moves focus to the
|
|
103
|
+
// first focusable element; wait a frame so this focus call lands after it
|
|
104
|
+
requestAnimationFrame(() => {
|
|
105
|
+
const target = footerElement?.querySelector<HTMLButtonElement>(
|
|
106
|
+
`.confirm-dialog-${resolvedFocus} button`
|
|
107
|
+
);
|
|
108
|
+
target?.focus();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function handleConfirm() {
|
|
113
|
+
if (loading) return;
|
|
114
|
+
let result: void | boolean;
|
|
115
|
+
try {
|
|
116
|
+
const pending = onConfirm?.();
|
|
117
|
+
if (pending instanceof Promise) {
|
|
118
|
+
loading = true;
|
|
119
|
+
result = await pending;
|
|
120
|
+
} else {
|
|
121
|
+
result = pending;
|
|
122
|
+
}
|
|
123
|
+
} finally {
|
|
124
|
+
loading = false;
|
|
125
|
+
}
|
|
126
|
+
if (result === false) return;
|
|
127
|
+
confirmed = true;
|
|
128
|
+
show = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function handleCancel() {
|
|
132
|
+
if (loading) return;
|
|
133
|
+
show = false;
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<Dialog
|
|
138
|
+
bind:show
|
|
139
|
+
{title}
|
|
140
|
+
{size}
|
|
141
|
+
class={classes}
|
|
142
|
+
allowImplicitClose={allowImplicitClose && !loading}
|
|
143
|
+
>
|
|
144
|
+
{#if children}
|
|
145
|
+
{@render children()}
|
|
146
|
+
{:else if message}
|
|
147
|
+
<p class="confirm-dialog-message">{message}</p>
|
|
148
|
+
{/if}
|
|
149
|
+
{#snippet footer()}
|
|
150
|
+
<div class="confirm-dialog-actions" bind:this={footerElement}>
|
|
151
|
+
{#if !hideCancel}
|
|
152
|
+
<span class="confirm-dialog-cancel">
|
|
153
|
+
<Button variant={cancelVariant} disabled={loading} onclick={handleCancel}>
|
|
154
|
+
{cancelText}
|
|
155
|
+
</Button>
|
|
156
|
+
</span>
|
|
157
|
+
{/if}
|
|
158
|
+
<span class="confirm-dialog-ok">
|
|
159
|
+
<Button
|
|
160
|
+
variant={okVariant}
|
|
161
|
+
svg={okSvg}
|
|
162
|
+
{loading}
|
|
163
|
+
disabled={okDisabled}
|
|
164
|
+
onclick={handleConfirm}
|
|
165
|
+
>
|
|
166
|
+
{okText}
|
|
167
|
+
</Button>
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
{/snippet}
|
|
171
|
+
</Dialog>
|
|
172
|
+
|
|
173
|
+
<style>
|
|
174
|
+
.confirm-dialog-message {
|
|
175
|
+
margin: 0;
|
|
176
|
+
white-space: pre-line;
|
|
177
|
+
overflow-wrap: anywhere;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.confirm-dialog-actions {
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
justify-content: flex-end;
|
|
184
|
+
gap: 0.5rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.confirm-dialog-cancel,
|
|
188
|
+
.confirm-dialog-ok {
|
|
189
|
+
display: contents;
|
|
190
|
+
}
|
|
191
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ButtonVariant } from './ButtonVariant.js';
|
|
3
|
+
export interface ConfirmDialogProps {
|
|
4
|
+
/** Whether the dialog is visible (bindable) */
|
|
5
|
+
show: boolean;
|
|
6
|
+
/** Dialog title displayed in the header */
|
|
7
|
+
title?: string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Plain-text body. Ignored when `children` is provided, so pass a snippet
|
|
10
|
+
* for richer content.
|
|
11
|
+
*/
|
|
12
|
+
message?: string | undefined;
|
|
13
|
+
/** Label for the confirming button */
|
|
14
|
+
okText?: string;
|
|
15
|
+
/** Label for the dismissing button */
|
|
16
|
+
cancelText?: string;
|
|
17
|
+
/** Visual variant of the confirming button (use `danger` for destructive actions) */
|
|
18
|
+
okVariant?: ButtonVariant;
|
|
19
|
+
/** Visual variant of the dismissing button */
|
|
20
|
+
cancelVariant?: ButtonVariant;
|
|
21
|
+
/** Icon shown on the left of the confirming button */
|
|
22
|
+
okSvg?: string;
|
|
23
|
+
/** Disable the confirming button (e.g. while a form is invalid) */
|
|
24
|
+
okDisabled?: boolean;
|
|
25
|
+
/** Hide the Cancel button entirely, leaving an acknowledgement-style dialog */
|
|
26
|
+
hideCancel?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Which button receives focus when the dialog opens. Defaults to the
|
|
29
|
+
* confirming button, or to Cancel when `okVariant` is a danger variant so
|
|
30
|
+
* a stray Enter cannot trigger a destructive action.
|
|
31
|
+
*/
|
|
32
|
+
initialFocus?: 'ok' | 'cancel' | 'none';
|
|
33
|
+
/**
|
|
34
|
+
* Called when OK is pressed. Returning a promise puts the OK button into a
|
|
35
|
+
* loading state and keeps the dialog open until it settles. Return `false`
|
|
36
|
+
* (or resolve to `false`) to keep the dialog open.
|
|
37
|
+
*/
|
|
38
|
+
onConfirm?: () => void | boolean | Promise<void | boolean>;
|
|
39
|
+
/** Called when the dialog is dismissed via Cancel, the close icon, Escape or the backdrop */
|
|
40
|
+
onCancel?: () => void;
|
|
41
|
+
/** Allow closing by clicking backdrop or pressing Escape */
|
|
42
|
+
allowImplicitClose?: boolean;
|
|
43
|
+
/** Size preset for the dialog width */
|
|
44
|
+
size?: 'md' | 'lg' | 'xl' | 'full';
|
|
45
|
+
/** Additional CSS classes passed through to the dialog */
|
|
46
|
+
class?: string;
|
|
47
|
+
/** Custom body content; overrides `message` */
|
|
48
|
+
children?: Snippet;
|
|
49
|
+
}
|
|
50
|
+
declare const ConfirmDialog: import("svelte").Component<ConfirmDialogProps, {}, "show">;
|
|
51
|
+
type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
|
|
52
|
+
export default ConfirmDialog;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@placeholderco/placeholder-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "A modern, customizable Svelte 5 UI component library with comprehensive theming support. Re-brand it with a single config object.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Placeholderco",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@sveltejs/kit": "^2.70.3",
|
|
57
57
|
"@sveltejs/package": "^2.5.8",
|
|
58
58
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
59
|
-
"@types/node": "^26.
|
|
59
|
+
"@types/node": "^26.5.1",
|
|
60
60
|
"globals": "^17.12.0",
|
|
61
61
|
"prettier": "^3.9.6",
|
|
62
62
|
"prettier-plugin-svelte": "^4.1.1",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"svelte": "^5.57.0",
|
|
66
66
|
"svelte-check": "^4.7.6",
|
|
67
67
|
"typescript": "^6.0.3",
|
|
68
|
-
"vite": "^8.
|
|
68
|
+
"vite": "^8.3.0"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@codemirror/autocomplete": "^6.20.3",
|