@fuzdev/fuz_ui 0.200.0 → 0.201.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/README.md +2 -2
- package/dist/ApiDeclarationList.svelte +2 -2
- package/dist/DeclarationDetail.svelte +40 -35
- package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
- package/dist/Dialog.svelte +174 -165
- package/dist/Dialog.svelte.d.ts +44 -13
- package/dist/Dialog.svelte.d.ts.map +1 -1
- package/dist/DialogContent.svelte +140 -0
- package/dist/DialogContent.svelte.d.ts +69 -0
- package/dist/DialogContent.svelte.d.ts.map +1 -0
- package/dist/dialog.d.ts +32 -18
- package/dist/dialog.d.ts.map +1 -1
- package/dist/dialog.js +3 -12
- package/package.json +2 -2
- package/src/lib/dialog.ts +30 -27
- package/dist/Dialogs.svelte +0 -28
- package/dist/Dialogs.svelte.d.ts +0 -11
- package/dist/Dialogs.svelte.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# fuz_ui
|
|
2
2
|
|
|
3
3
|
[<img src="static/logo.svg" alt="a friendly brown spider facing you" align="right" width="192" height="192">](https://ui.fuz.dev/)
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
fuz_ui is a Svelte UI library with components and helpers for making zippy
|
|
8
8
|
websites. It's built on [fuz_css](https://css.fuz.dev/)
|
|
9
|
-
and provides
|
|
9
|
+
and provides a documentation system built on
|
|
10
10
|
[svelte-docinfo](https://svelte-docinfo.fuz.dev/).
|
|
11
11
|
It's in early alpha with breaking changes ahead.
|
|
12
12
|
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
{:else}
|
|
25
25
|
{#each declarations as declaration (`${declaration.module_path}:${declaration.name}`)}
|
|
26
26
|
<TomeSection>
|
|
27
|
-
<!--
|
|
27
|
+
<!-- Show the bare name; generic parameters are documented in the detail below (generics/type signature). -->
|
|
28
28
|
<TomeSectionHeader text={declaration.name}>
|
|
29
|
-
<div class="word-break:break-all">{declaration.
|
|
29
|
+
<div class="word-break:break-all">{declaration.name}</div>
|
|
30
30
|
</TomeSectionHeader>
|
|
31
31
|
<article id={declaration.name}>
|
|
32
32
|
<DeclarationDetail {declaration} />
|
|
@@ -62,6 +62,16 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
62
62
|
</section>
|
|
63
63
|
{/snippet}
|
|
64
64
|
|
|
65
|
+
<!-- A compact parameter row: name (with optional marker) and type, for snippet params and overloads. -->
|
|
66
|
+
{#snippet param_row(param: ParameterJsonInput)}
|
|
67
|
+
<div class="row gap_md">
|
|
68
|
+
<code
|
|
69
|
+
>{param.name}{#if param.optional}?{/if}</code
|
|
70
|
+
>
|
|
71
|
+
<TypeLink type={param.type} />
|
|
72
|
+
</div>
|
|
73
|
+
{/snippet}
|
|
74
|
+
|
|
65
75
|
<!-- Compact prose metadata for props and members (deprecated, since, examples, see also, throws). -->
|
|
66
76
|
{#snippet doc_extras(item: DocExtras)}
|
|
67
77
|
<!-- eslint-disable-next-line @typescript-eslint/no-deprecated -->
|
|
@@ -104,6 +114,12 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
104
114
|
{/if}
|
|
105
115
|
{/snippet}
|
|
106
116
|
|
|
117
|
+
<!-- A comma-separated inline list of type links (extends, implements, intersects are rarely more than one). -->
|
|
118
|
+
{#snippet type_list(types: Array<string>)}
|
|
119
|
+
{#each types as type, i (type)}{#if i > 0},
|
|
120
|
+
{/if}<TypeLink {type} />{/each}
|
|
121
|
+
{/snippet}
|
|
122
|
+
|
|
107
123
|
<!-- Metadata -->
|
|
108
124
|
<p class="row justify-content:space-between">
|
|
109
125
|
<ModuleLink module_path={declaration.module_path} />
|
|
@@ -211,12 +227,7 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
211
227
|
<section>
|
|
212
228
|
<h5>snippet parameters</h5>
|
|
213
229
|
{#each prop.parameters as param (param)}
|
|
214
|
-
|
|
215
|
-
<code
|
|
216
|
-
>{param.name}{#if param.optional}?{/if}</code
|
|
217
|
-
>
|
|
218
|
-
<TypeLink type={param.type} />
|
|
219
|
-
</div>
|
|
230
|
+
{@render param_row(param)}
|
|
220
231
|
{/each}
|
|
221
232
|
</section>
|
|
222
233
|
{/if}
|
|
@@ -238,12 +249,7 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
238
249
|
{/if}
|
|
239
250
|
{#if overload.parameters?.length}
|
|
240
251
|
{#each overload.parameters as param (param)}
|
|
241
|
-
|
|
242
|
-
<code
|
|
243
|
-
>{param.name}{#if param.optional}?{/if}</code
|
|
244
|
-
>
|
|
245
|
-
<TypeLink type={param.type} />
|
|
246
|
-
</div>
|
|
252
|
+
{@render param_row(param)}
|
|
247
253
|
{/each}
|
|
248
254
|
{/if}
|
|
249
255
|
{#if overload.returnType}
|
|
@@ -264,11 +270,9 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
264
270
|
{#if declaration.intersects?.length}
|
|
265
271
|
<section>
|
|
266
272
|
<h4>intersects</h4>
|
|
267
|
-
<
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
{/each}
|
|
271
|
-
</ul>
|
|
273
|
+
<div class="row gap_md flex-wrap:wrap">
|
|
274
|
+
{@render type_list(declaration.intersects)}
|
|
275
|
+
</div>
|
|
272
276
|
</section>
|
|
273
277
|
{/if}
|
|
274
278
|
|
|
@@ -286,10 +290,13 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
286
290
|
<!-- generics -->
|
|
287
291
|
{#if declaration.generic_params.length}
|
|
288
292
|
<section>
|
|
289
|
-
<
|
|
293
|
+
<div class="row gap_md">
|
|
294
|
+
<h4>generics</h4>
|
|
295
|
+
<TypeLink type={declaration.display_name} />
|
|
296
|
+
</div>
|
|
290
297
|
{#each declaration.generic_params as generic (generic)}
|
|
291
298
|
<section>
|
|
292
|
-
<
|
|
299
|
+
<h5><code>{generic.name}</code></h5>
|
|
293
300
|
{#if generic.constraint}
|
|
294
301
|
<div class="row gap_md">
|
|
295
302
|
<strong>constraint</strong>
|
|
@@ -312,27 +319,19 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
312
319
|
<section>
|
|
313
320
|
<h4>inheritance</h4>
|
|
314
321
|
{#if declaration.extends_type}
|
|
315
|
-
<div>
|
|
322
|
+
<div class="row gap_md flex-wrap:wrap">
|
|
316
323
|
<strong>extends:</strong>
|
|
317
|
-
{
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
</ul>
|
|
323
|
-
{:else}
|
|
324
|
-
<TypeLink type={declaration.extends_type} />
|
|
325
|
-
{/if}
|
|
324
|
+
{@render type_list(
|
|
325
|
+
Array.isArray(declaration.extends_type)
|
|
326
|
+
? declaration.extends_type
|
|
327
|
+
: [declaration.extends_type],
|
|
328
|
+
)}
|
|
326
329
|
</div>
|
|
327
330
|
{/if}
|
|
328
331
|
{#if declaration.implements_types?.length}
|
|
329
|
-
<div>
|
|
332
|
+
<div class="row gap_md flex-wrap:wrap">
|
|
330
333
|
<strong>implements:</strong>
|
|
331
|
-
|
|
332
|
-
{#each declaration.implements_types as impl (impl)}
|
|
333
|
-
<li><TypeLink type={impl} /></li>
|
|
334
|
-
{/each}
|
|
335
|
-
</ul>
|
|
334
|
+
{@render type_list(declaration.implements_types)}
|
|
336
335
|
</div>
|
|
337
336
|
{/if}
|
|
338
337
|
</section>
|
|
@@ -465,3 +464,9 @@ including parameters, props, members, overloads, intersects, and more.
|
|
|
465
464
|
{/each}
|
|
466
465
|
</section>
|
|
467
466
|
{/if}
|
|
467
|
+
|
|
468
|
+
<style>
|
|
469
|
+
section section:not(:last-child) {
|
|
470
|
+
margin-bottom: var(--space_xl4);
|
|
471
|
+
}
|
|
472
|
+
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeclarationDetail.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DeclarationDetail.svelte"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yBAAyB,CAAC;AAiBxD,KAAK,gBAAgB,GAAI;IAAC,WAAW,EAAE,WAAW,CAAA;CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"DeclarationDetail.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DeclarationDetail.svelte"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yBAAyB,CAAC;AAiBxD,KAAK,gBAAgB,GAAI;IAAC,WAAW,EAAE,WAAW,CAAA;CAAC,CAAC;AAqbrD;;;;;;;;GAQG;AACH,QAAA,MAAM,iBAAiB,sDAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
package/dist/Dialog.svelte
CHANGED
|
@@ -1,167 +1,177 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type {Snippet} from 'svelte';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
3
|
+
import type {SvelteHTMLElements} from 'svelte/elements';
|
|
4
|
+
import {SvelteSet} from 'svelte/reactivity';
|
|
5
|
+
import {swallow} from '@fuzdev/fuz_util/dom.js';
|
|
6
|
+
|
|
7
|
+
import {dialog_context, type DialogContext, type DialogAlign} from './dialog.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This component renders a native `<dialog>` opened with `showModal()`, which
|
|
11
|
+
* puts it in the top layer. The top layer escapes ancestor stacking/overflow
|
|
12
|
+
* contexts (so no `Teleport` is needed), traps focus, makes the rest of the page
|
|
13
|
+
* inert, closes on Escape, and restores focus to the previously focused element
|
|
14
|
+
* on close -- all natively. The dim background is the native `::backdrop`.
|
|
15
|
+
*
|
|
16
|
+
* We render a full-viewport overlay inside the dialog rather than a content-sized
|
|
17
|
+
* box, to preserve the scrolling and `align="top"` behaviors. The content
|
|
18
|
+
* surface itself is the consumer's -- pair this with `DialogContent` for the
|
|
19
|
+
* default `.pane` card and gutter, or render your own surface in `children`.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
23
|
|
|
24
24
|
const {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
active = true,
|
|
25
|
+
show = true,
|
|
26
|
+
align = 'center',
|
|
27
|
+
dismissable = true,
|
|
29
28
|
content_selector = '.pane',
|
|
29
|
+
onbeforeclose,
|
|
30
30
|
onclose,
|
|
31
31
|
children,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
container?: HTMLElement;
|
|
32
|
+
...rest
|
|
33
|
+
}: Omit<SvelteHTMLElements['dialog'], 'children' | 'onclose'> & {
|
|
35
34
|
/**
|
|
36
|
-
*
|
|
35
|
+
* Whether the dialog is shown. When the `<dialog>` mounts it opens via
|
|
36
|
+
* `showModal()`; when it unmounts it closes.
|
|
37
|
+
* Defaults to `true` so the `{#if opened}<Dialog>...</Dialog>{/if}` pattern
|
|
38
|
+
* works without passing `show` -- mounting the component opens the dialog.
|
|
39
|
+
* Pass `show={opened}` to skip the outer `{#if}` and let the component manage
|
|
40
|
+
* its own conditional rendering.
|
|
41
|
+
* @default true
|
|
37
42
|
*/
|
|
38
|
-
|
|
43
|
+
show?: boolean;
|
|
39
44
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
45
|
+
* How the content is aligned in the viewport. `center` vertically centers it;
|
|
46
|
+
* `top` aligns it to the top and grows downward, which avoids jank when the
|
|
47
|
+
* content's height changes.
|
|
48
|
+
* @default 'center'
|
|
42
49
|
*/
|
|
43
|
-
|
|
50
|
+
align?: DialogAlign;
|
|
44
51
|
/**
|
|
52
|
+
* Whether clicking outside the content (see `content_selector`) closes the
|
|
53
|
+
* dialog. `Escape` closes it regardless of this.
|
|
45
54
|
* @default true
|
|
46
55
|
*/
|
|
47
|
-
|
|
56
|
+
dismissable?: boolean;
|
|
48
57
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
58
|
+
* Fallback selector for a content surface you render in `children` yourself
|
|
59
|
+
* (rather than via `DialogContent`, which self-registers).
|
|
60
|
+
* When `dismissable`, a press that isn't inside a registered surface (e.g.
|
|
61
|
+
* `DialogContent`, which self-registers) and doesn't match this selector
|
|
62
|
+
* closes the dialog. Defaults to the fuz_css `.pane` card; set it to match
|
|
63
|
+
* your surface's outermost element -- with no registered surface and no
|
|
64
|
+
* match, presses anywhere close the dialog.
|
|
51
65
|
* @default '.pane'
|
|
52
66
|
*/
|
|
53
|
-
content_selector?: string
|
|
67
|
+
content_selector?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Called before a user-initiated close (`Escape`, click-outside, or `close`).
|
|
70
|
+
* Return `false` to veto and keep the dialog open -- e.g. to confirm
|
|
71
|
+
* discarding unsaved changes. Programmatic close via `show={false}` bypasses
|
|
72
|
+
* this.
|
|
73
|
+
*/
|
|
74
|
+
onbeforeclose?: () => boolean | void;
|
|
75
|
+
/**
|
|
76
|
+
* Called when the dialog closes -- via `Escape`, click-outside, or `close`.
|
|
77
|
+
* Use it to sync your own open state, e.g. `onclose={() => (opened = false)}`.
|
|
78
|
+
* Like `onbeforeclose`, programmatic close via `show={false}` bypasses this.
|
|
79
|
+
*/
|
|
54
80
|
onclose?: () => void;
|
|
55
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Rendered inside the dialog overlay. Receives the `DialogContext` (e.g.
|
|
83
|
+
* `{close}`); pair with `DialogContent` or render your own surface.
|
|
84
|
+
*/
|
|
85
|
+
children: Snippet<[dialog: DialogContext]>;
|
|
56
86
|
} = $props();
|
|
57
87
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const root_el = document.querySelector(ROOT_SELECTOR);
|
|
75
|
-
if (!root_el) {
|
|
76
|
-
throw Error(`Cannot find dialog root element with selector '${ROOT_SELECTOR}'`);
|
|
77
|
-
}
|
|
78
|
-
container_el = document.createElement('div');
|
|
79
|
-
container_el.id = CONTAINER_ID;
|
|
80
|
-
container_el.style.display = 'contents';
|
|
81
|
-
root_el.appendChild(container_el);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
88
|
+
let dialog_el: HTMLDialogElement | undefined;
|
|
89
|
+
|
|
90
|
+
// Guards against a single dismissal firing `onclose` twice. Reset on mount.
|
|
91
|
+
let closing = false;
|
|
92
|
+
|
|
93
|
+
// Closes the native dialog (which restores focus to the trigger) and notifies
|
|
94
|
+
// the consumer to unmount us. Closing here, while still mounted and connected,
|
|
95
|
+
// is what makes focus restoration work -- closing on unmount would run on a
|
|
96
|
+
// detached node, which doesn't restore focus.
|
|
97
|
+
const request_close = () => {
|
|
98
|
+
if (closing) return;
|
|
99
|
+
// let the consumer veto a user-initiated dismissal (e.g. confirm unsaved changes)
|
|
100
|
+
if (onbeforeclose?.() === false) return;
|
|
101
|
+
closing = true;
|
|
102
|
+
dialog_el?.close();
|
|
103
|
+
onclose?.();
|
|
84
104
|
};
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
let content_el: HTMLElement | undefined = $state.raw();
|
|
88
|
-
|
|
106
|
+
// The `close` passed to children also swallows the triggering event.
|
|
89
107
|
const close = (e?: Event) => {
|
|
90
108
|
if (e) swallow(e);
|
|
91
|
-
|
|
109
|
+
request_close();
|
|
92
110
|
};
|
|
93
111
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
}
|
|
112
|
+
// Content surfaces (e.g. `DialogContent`) register here so a press inside one
|
|
113
|
+
// isn't an outside-dismiss. Identity-based, so it works regardless of classes;
|
|
114
|
+
// `content_selector` stays the fallback for surfaces rendered in `children`.
|
|
115
|
+
const surfaces = new SvelteSet<Element>();
|
|
116
|
+
const register_surface = (element: Element): (() => void) => {
|
|
117
|
+
surfaces.add(element);
|
|
118
|
+
return () => {
|
|
119
|
+
surfaces.delete(element);
|
|
120
|
+
};
|
|
105
121
|
};
|
|
106
122
|
|
|
107
|
-
// The
|
|
108
|
-
//
|
|
109
|
-
|
|
123
|
+
// The context is both passed to `children` and set for descendants (e.g.
|
|
124
|
+
// `DialogContent`), so either path can close the dialog.
|
|
125
|
+
const context: DialogContext = {close, register_surface};
|
|
126
|
+
dialog_context.set(context);
|
|
127
|
+
|
|
128
|
+
const setup_dialog = (el: HTMLDialogElement) => {
|
|
129
|
+
dialog_el = el;
|
|
130
|
+
closing = false;
|
|
131
|
+
// Esc (and any platform close request) fires `cancel`. We swallow it and
|
|
132
|
+
// close the dialog ourselves (above, while connected) so focus is restored,
|
|
133
|
+
// and so the consumer's state stays in sync and we unmount.
|
|
134
|
+
const oncancel = (e: Event) => {
|
|
135
|
+
swallow(e);
|
|
136
|
+
request_close();
|
|
137
|
+
};
|
|
138
|
+
el.addEventListener('cancel', oncancel);
|
|
139
|
+
if (!el.open) el.showModal();
|
|
140
|
+
return () => {
|
|
141
|
+
el.removeEventListener('cancel', oncancel);
|
|
142
|
+
dialog_el = undefined;
|
|
143
|
+
// fallback for programmatic unmount (e.g. `show` set false directly)
|
|
144
|
+
if (el.open) el.close();
|
|
145
|
+
};
|
|
146
|
+
};
|
|
110
147
|
</script>
|
|
111
148
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
For more see: https://www.w3.org/TR/wai-aria-practices-1.1/#dialog_modal
|
|
119
|
-
and https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets
|
|
120
|
-
-->
|
|
121
|
-
<Teleport
|
|
122
|
-
to={container_el}
|
|
123
|
-
onmove={async () => {
|
|
124
|
-
await wait(); // TODO this is a hack to get animations working, `Teleport` now mounts synchronously?!
|
|
125
|
-
ready = true;
|
|
126
|
-
dialog_el?.focus(); // TODO make this more declarative? probably want to focus only after moving though, not on mount, which makes an action trickier
|
|
127
|
-
}}
|
|
128
|
-
>
|
|
129
|
-
<div
|
|
130
|
-
class="dialog"
|
|
131
|
-
class:ready
|
|
132
|
-
class:layout-page={layout === 'page'}
|
|
133
|
-
role="dialog"
|
|
134
|
-
aria-modal="true"
|
|
135
|
-
bind:this={dialog_el}
|
|
136
|
-
tabindex="-1"
|
|
137
|
-
style:z-index={100 + index}
|
|
149
|
+
{#if show}
|
|
150
|
+
<dialog
|
|
151
|
+
{...rest}
|
|
152
|
+
class="dialog {rest.class}"
|
|
153
|
+
class:align-top={align === 'top'}
|
|
154
|
+
{@attach setup_dialog}
|
|
138
155
|
>
|
|
139
|
-
<div
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
>
|
|
156
|
-
<div class="dialog-bg" aria-hidden="true"></div>
|
|
157
|
-
<div class="dialog-content" bind:this={content_el}>
|
|
158
|
-
<!-- mount the content only after teleporting to avoid issues -->
|
|
159
|
-
{#if ready}{@render children(close)}{/if}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
156
|
+
<div
|
|
157
|
+
class="dialog-wrapper"
|
|
158
|
+
role="none"
|
|
159
|
+
onmousedown={(e) => {
|
|
160
|
+
if (!dismissable) return;
|
|
161
|
+
const target = e.target as Element;
|
|
162
|
+
// inside a registered surface (e.g. DialogContent) -> not an outside dismiss
|
|
163
|
+
for (const surface of surfaces) {
|
|
164
|
+
if (surface.contains(target)) return;
|
|
165
|
+
}
|
|
166
|
+
// fallback for surfaces rendered in `children`: match `content_selector`
|
|
167
|
+
if (content_selector && target.closest(content_selector)) return;
|
|
168
|
+
close(e);
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
{@render children(context)}
|
|
162
172
|
</div>
|
|
163
|
-
</
|
|
164
|
-
|
|
173
|
+
</dialog>
|
|
174
|
+
{/if}
|
|
165
175
|
|
|
166
176
|
<style>
|
|
167
177
|
.dialog {
|
|
@@ -171,51 +181,50 @@
|
|
|
171
181
|
var(--shadow_color, var(--shadow_color_umbra)) var(--shadow_alpha_70),
|
|
172
182
|
transparent
|
|
173
183
|
);
|
|
184
|
+
/* reset the user-agent dialog styles; we render a full-viewport overlay */
|
|
185
|
+
max-width: none;
|
|
186
|
+
max-height: none;
|
|
187
|
+
/* width/height are needed despite `inset: 0` to make the dialog element fill the viewport */
|
|
188
|
+
width: 100%;
|
|
189
|
+
height: 100%;
|
|
190
|
+
margin: 0;
|
|
191
|
+
padding: 0;
|
|
192
|
+
border: none;
|
|
193
|
+
background: transparent;
|
|
194
|
+
color: inherit;
|
|
174
195
|
position: fixed;
|
|
175
196
|
inset: 0;
|
|
176
197
|
overflow: auto;
|
|
177
|
-
/* this simplifies the code a lot but doesn't prevent scrolling
|
|
178
|
-
the underlying content when the dialog doesn't overflow, even when `overflow: scroll`
|
|
179
|
-
TODO check if this behaves as desired after switching to use the `dialog` element */
|
|
180
198
|
overscroll-behavior: contain;
|
|
181
199
|
}
|
|
200
|
+
/* the native backdrop is the dim background; fade it in on open */
|
|
201
|
+
.dialog::backdrop {
|
|
202
|
+
background-color: var(--dialog_bg, var(--darken_60));
|
|
203
|
+
transition: background-color var(--duration_2) ease;
|
|
204
|
+
}
|
|
205
|
+
@starting-style {
|
|
206
|
+
.dialog::backdrop {
|
|
207
|
+
background-color: transparent;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
182
210
|
|
|
211
|
+
/* Lock background scroll while a modal dialog is open. `scrollbar-gutter`
|
|
212
|
+
reserves the scrollbar space so toggling the lock doesn't shift the layout. */
|
|
213
|
+
:global(html:has(dialog.dialog[open])) {
|
|
214
|
+
overflow: hidden;
|
|
215
|
+
scrollbar-gutter: stable;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* `min-height: 100%` (not `height`) makes tall content overflow downward only: the
|
|
219
|
+
wrapper grows to its content, so the centered top stays reachable in the dialog's
|
|
220
|
+
scroll. A fixed-height centering box would strand the top above the scroll origin. */
|
|
183
221
|
.dialog-wrapper {
|
|
184
|
-
position: relative; /* for the surface */
|
|
185
222
|
min-height: 100%;
|
|
186
223
|
display: flex;
|
|
187
224
|
align-items: center;
|
|
188
225
|
justify-content: center;
|
|
189
226
|
}
|
|
190
|
-
.
|
|
227
|
+
.align-top .dialog-wrapper {
|
|
191
228
|
align-items: start;
|
|
192
229
|
}
|
|
193
|
-
|
|
194
|
-
.dialog-bg {
|
|
195
|
-
position: absolute;
|
|
196
|
-
inset: 0;
|
|
197
|
-
z-index: 0;
|
|
198
|
-
opacity: 0;
|
|
199
|
-
transition: opacity var(--duration_3) ease;
|
|
200
|
-
background-color: var(--dialog_bg, var(--darken_60));
|
|
201
|
-
}
|
|
202
|
-
.ready .dialog-bg {
|
|
203
|
-
opacity: 1;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.dialog-layout {
|
|
207
|
-
height: 100%;
|
|
208
|
-
/* makes the content overflow downwards instead of upwards+downwards because it's centered */
|
|
209
|
-
max-height: 100%;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.dialog-content {
|
|
213
|
-
width: 100%;
|
|
214
|
-
transform: scale(0.99);
|
|
215
|
-
transition: transform var(--duration_1) ease;
|
|
216
|
-
padding: 40px;
|
|
217
|
-
}
|
|
218
|
-
.ready .dialog-content {
|
|
219
|
-
transform: scale(1);
|
|
220
|
-
}
|
|
221
230
|
</style>
|
package/dist/Dialog.svelte.d.ts
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type {
|
|
3
|
-
type
|
|
4
|
-
|
|
2
|
+
import type { SvelteHTMLElements } from 'svelte/elements';
|
|
3
|
+
import { type DialogContext, type DialogAlign } from './dialog.js';
|
|
4
|
+
type $$ComponentProps = Omit<SvelteHTMLElements['dialog'], 'children' | 'onclose'> & {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Whether the dialog is shown. When the `<dialog>` mounts it opens via
|
|
7
|
+
* `showModal()`; when it unmounts it closes.
|
|
8
|
+
* Defaults to `true` so the `{#if opened}<Dialog>...</Dialog>{/if}` pattern
|
|
9
|
+
* works without passing `show` -- mounting the component opens the dialog.
|
|
10
|
+
* Pass `show={opened}` to skip the outer `{#if}` and let the component manage
|
|
11
|
+
* its own conditional rendering.
|
|
12
|
+
* @default true
|
|
7
13
|
*/
|
|
8
|
-
|
|
14
|
+
show?: boolean;
|
|
9
15
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
16
|
+
* How the content is aligned in the viewport. `center` vertically centers it;
|
|
17
|
+
* `top` aligns it to the top and grows downward, which avoids jank when the
|
|
18
|
+
* content's height changes.
|
|
19
|
+
* @default 'center'
|
|
12
20
|
*/
|
|
13
|
-
|
|
21
|
+
align?: DialogAlign;
|
|
14
22
|
/**
|
|
23
|
+
* Whether clicking outside the content (see `content_selector`) closes the
|
|
24
|
+
* dialog. `Escape` closes it regardless of this.
|
|
15
25
|
* @default true
|
|
16
26
|
*/
|
|
17
|
-
|
|
27
|
+
dismissable?: boolean;
|
|
18
28
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
29
|
+
* Fallback selector for a content surface you render in `children` yourself
|
|
30
|
+
* (rather than via `DialogContent`, which self-registers).
|
|
31
|
+
* When `dismissable`, a press that isn't inside a registered surface (e.g.
|
|
32
|
+
* `DialogContent`, which self-registers) and doesn't match this selector
|
|
33
|
+
* closes the dialog. Defaults to the fuz_css `.pane` card; set it to match
|
|
34
|
+
* your surface's outermost element -- with no registered surface and no
|
|
35
|
+
* match, presses anywhere close the dialog.
|
|
21
36
|
* @default '.pane'
|
|
22
37
|
*/
|
|
23
|
-
content_selector?: string
|
|
38
|
+
content_selector?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Called before a user-initiated close (`Escape`, click-outside, or `close`).
|
|
41
|
+
* Return `false` to veto and keep the dialog open -- e.g. to confirm
|
|
42
|
+
* discarding unsaved changes. Programmatic close via `show={false}` bypasses
|
|
43
|
+
* this.
|
|
44
|
+
*/
|
|
45
|
+
onbeforeclose?: () => boolean | void;
|
|
46
|
+
/**
|
|
47
|
+
* Called when the dialog closes -- via `Escape`, click-outside, or `close`.
|
|
48
|
+
* Use it to sync your own open state, e.g. `onclose={() => (opened = false)}`.
|
|
49
|
+
* Like `onbeforeclose`, programmatic close via `show={false}` bypasses this.
|
|
50
|
+
*/
|
|
24
51
|
onclose?: () => void;
|
|
25
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Rendered inside the dialog overlay. Receives the `DialogContext` (e.g.
|
|
54
|
+
* `{close}`); pair with `DialogContent` or render your own surface.
|
|
55
|
+
*/
|
|
56
|
+
children: Snippet<[dialog: DialogContext]>;
|
|
26
57
|
};
|
|
27
58
|
declare const Dialog: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
28
59
|
type Dialog = ReturnType<typeof Dialog>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Dialog.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Dialog.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Dialog.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAIxD,OAAO,EAAiB,KAAK,aAAa,EAAE,KAAK,WAAW,EAAC,MAAM,aAAa,CAAC;AAEhF,KAAK,gBAAgB,GAAI,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG;IACrF;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;CAC3C,CAAC;AAsHH,QAAA,MAAM,MAAM,sDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {Snippet} from 'svelte';
|
|
3
|
+
import type {HTMLAttributes, SvelteHTMLElements} from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
import {dialog_context, type DialogContext, type DialogCloseButtonAttrs} from './dialog.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The default content surface for `Dialog`: a fuz_css `.pane` card with a
|
|
9
|
+
* `gutter` margin (the dismiss zone), centered in the viewport by `Dialog`'s
|
|
10
|
+
* own layout. It reads `close` from `dialog_context` (set by `Dialog`) and
|
|
11
|
+
* passes it to `children`, so content can close the dialog without the consumer
|
|
12
|
+
* threading `close` down from `Dialog`'s own `children` snippet.
|
|
13
|
+
*
|
|
14
|
+
* Must be rendered inside a `Dialog`, with which it registers its surface, so
|
|
15
|
+
* click-outside-to-close treats presses inside the card as inside regardless of
|
|
16
|
+
* classes -- `pane={false}` works without further setup.
|
|
17
|
+
*
|
|
18
|
+
* By default it renders a `close_button` floating just outside the surface's
|
|
19
|
+
* top-right corner. The surface is a containing block (`position: relative`) for
|
|
20
|
+
* that button and any absolutely-positioned content in `children`.
|
|
21
|
+
*
|
|
22
|
+
* The surface has no layout of its own -- it's a plain block, so children flow
|
|
23
|
+
* top-to-bottom in normal document order. Pass `class` through (e.g. `box` for a
|
|
24
|
+
* centered column, `column` for an uncentered one) to add one.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const {
|
|
30
|
+
padding = 'var(--space_xl)',
|
|
31
|
+
pane = true,
|
|
32
|
+
gutter = 'var(--space_xl3)',
|
|
33
|
+
max_width = 'var(--distance_md)',
|
|
34
|
+
close_button = true,
|
|
35
|
+
children,
|
|
36
|
+
...rest
|
|
37
|
+
}: Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
38
|
+
/**
|
|
39
|
+
* The content surface's padding, set as an inline style. Defaults to
|
|
40
|
+
* `var(--space_xl)` for a comfortable gutter between the card edge and its
|
|
41
|
+
* content. Set to `''` or `'0'` for no padding (e.g. a flush image or a
|
|
42
|
+
* surface that manages its own spacing).
|
|
43
|
+
* @default 'var(--space_xl)'
|
|
44
|
+
*/
|
|
45
|
+
padding?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Whether to apply the fuz_css `.pane` card class to the content surface --
|
|
48
|
+
* its opaque background, shadow, and rounded corners. `true` (the default) is
|
|
49
|
+
* the standard dialog card; `false` gives a chromeless surface. Either way the
|
|
50
|
+
* surface registers with `Dialog`, so click-outside-to-close keeps working.
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
pane?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* The gutter around the `.pane` card -- its outer margin, and so the area
|
|
56
|
+
* outside the card where a press dismisses the dialog. Set to `''` or `'0'` to remove.
|
|
57
|
+
* @default 'var(--space_xl3)'
|
|
58
|
+
*/
|
|
59
|
+
gutter?: string;
|
|
60
|
+
/**
|
|
61
|
+
* The card's max width. The card shrinks to its content and is capped here,
|
|
62
|
+
* so narrow content stays narrow while wide content doesn't sprawl. Set to
|
|
63
|
+
* `''` for no cap (pure shrink-to-content).
|
|
64
|
+
* @default 'var(--distance_md)'
|
|
65
|
+
*/
|
|
66
|
+
max_width?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The close button floating just outside the content surface's top-right
|
|
69
|
+
* corner. `true` (the default) renders an absolutely-positioned `.sm.plain.icon_button`
|
|
70
|
+
* that closes the dialog; `false` removes it. It renders after
|
|
71
|
+
* `children`, so a content control (or an `autofocus` element) takes initial
|
|
72
|
+
* focus on open rather than the close button.
|
|
73
|
+
*
|
|
74
|
+
* Pass a `Snippet` to render your own. It receives `attrs`
|
|
75
|
+
* (`DialogCloseButtonAttrs`) carrying the default's placement, styling, a11y,
|
|
76
|
+
* and `onclick` (closes the dialog), plus the `DialogContext` (e.g. `{close}`).
|
|
77
|
+
* Spread `attrs` onto a `<button>` to inherit the corner anchoring and override
|
|
78
|
+
* only what differs (e.g. the glyph), or drop it to place the button freely. The
|
|
79
|
+
* surface is a containing block (`position: relative`), so an absolutely-positioned
|
|
80
|
+
* custom button anchors to it.
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
close_button?: boolean | Snippet<[attrs: DialogCloseButtonAttrs, dialog: DialogContext]>;
|
|
84
|
+
/**
|
|
85
|
+
* Rendered inside the content surface. Receives the `DialogContext` (e.g.
|
|
86
|
+
* `{close}`) so content can close the dialog without reaching into `Dialog`'s
|
|
87
|
+
* `children` snippet.
|
|
88
|
+
*
|
|
89
|
+
* The default `close_button` renders as the surface's last child (so a content
|
|
90
|
+
* control or `autofocus` element wins initial focus over it). Because it's a
|
|
91
|
+
* real child, it throws off universal child-spacing selectors: your content's
|
|
92
|
+
* last element is no longer `:last-child` (so `> :not(:last-child)` matches it
|
|
93
|
+
* and `> :last-child` skips it), and `> * + *` matches the button itself. Either
|
|
94
|
+
* scope spacing to your own elements or a class, or wrap your content in a single
|
|
95
|
+
* element so its child selectors no longer see the button.
|
|
96
|
+
*/
|
|
97
|
+
children: Snippet<[dialog: DialogContext]>;
|
|
98
|
+
} = $props();
|
|
99
|
+
|
|
100
|
+
const dialog = dialog_context.get('DialogContent must be rendered inside a Dialog');
|
|
101
|
+
|
|
102
|
+
// the default close button's attributes; `onclick` closes the dialog and the rest
|
|
103
|
+
// is handed to a custom `close_button` snippet so it can inherit the corner-anchored
|
|
104
|
+
// button by spreading them. positioning is inline (not a scoped class) so it travels
|
|
105
|
+
// into the consumer's snippet, which carries its own style scope
|
|
106
|
+
const close_button_attrs: DialogCloseButtonAttrs = {
|
|
107
|
+
type: 'button',
|
|
108
|
+
class: 'sm plain icon_button',
|
|
109
|
+
style: 'position: absolute; top: 0; right: 0;',
|
|
110
|
+
onclick: dialog.close,
|
|
111
|
+
title: 'close',
|
|
112
|
+
'aria-label': 'close',
|
|
113
|
+
} satisfies SvelteHTMLElements['button'];
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<!-- The surface is centered by `Dialog`'s `.dialog-wrapper` flex (no wrapper of its own
|
|
117
|
+
needed) and is a containing block (`position: relative`) for the close button and any
|
|
118
|
+
absolutely-positioned content in `children`. `gutter` is the surface's margin -- the
|
|
119
|
+
dismiss zone outside the card, where a press lands on the wrapper and closes the dialog.
|
|
120
|
+
`min-width: 0` lets this flex-item surface shrink below its content's intrinsic width, so
|
|
121
|
+
content with its own overflow (e.g. `Code`) scrolls instead of forcing the card wider. -->
|
|
122
|
+
<div
|
|
123
|
+
{...rest}
|
|
124
|
+
class:pane
|
|
125
|
+
style:padding
|
|
126
|
+
style:margin={gutter}
|
|
127
|
+
style:max-width={max_width}
|
|
128
|
+
style:min-width="0"
|
|
129
|
+
style:position="relative"
|
|
130
|
+
{@attach dialog.register_surface}
|
|
131
|
+
>
|
|
132
|
+
{@render children(dialog)}
|
|
133
|
+
<!-- rendered after `children` so a content control (or an `autofocus` element)
|
|
134
|
+
takes initial focus on open, not the close button -->
|
|
135
|
+
{#if close_button === true}
|
|
136
|
+
<button {...close_button_attrs}>✕</button>
|
|
137
|
+
{:else if close_button}
|
|
138
|
+
{@render close_button(close_button_attrs, dialog)}
|
|
139
|
+
{/if}
|
|
140
|
+
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type DialogContext, type DialogCloseButtonAttrs } from './dialog.js';
|
|
4
|
+
type $$ComponentProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
5
|
+
/**
|
|
6
|
+
* The content surface's padding, set as an inline style. Defaults to
|
|
7
|
+
* `var(--space_xl)` for a comfortable gutter between the card edge and its
|
|
8
|
+
* content. Set to `''` or `'0'` for no padding (e.g. a flush image or a
|
|
9
|
+
* surface that manages its own spacing).
|
|
10
|
+
* @default 'var(--space_xl)'
|
|
11
|
+
*/
|
|
12
|
+
padding?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to apply the fuz_css `.pane` card class to the content surface --
|
|
15
|
+
* its opaque background, shadow, and rounded corners. `true` (the default) is
|
|
16
|
+
* the standard dialog card; `false` gives a chromeless surface. Either way the
|
|
17
|
+
* surface registers with `Dialog`, so click-outside-to-close keeps working.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
pane?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The gutter around the `.pane` card -- its outer margin, and so the area
|
|
23
|
+
* outside the card where a press dismisses the dialog. Set to `''` or `'0'` to remove.
|
|
24
|
+
* @default 'var(--space_xl3)'
|
|
25
|
+
*/
|
|
26
|
+
gutter?: string;
|
|
27
|
+
/**
|
|
28
|
+
* The card's max width. The card shrinks to its content and is capped here,
|
|
29
|
+
* so narrow content stays narrow while wide content doesn't sprawl. Set to
|
|
30
|
+
* `''` for no cap (pure shrink-to-content).
|
|
31
|
+
* @default 'var(--distance_md)'
|
|
32
|
+
*/
|
|
33
|
+
max_width?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The close button floating just outside the content surface's top-right
|
|
36
|
+
* corner. `true` (the default) renders an absolutely-positioned `.sm.plain.icon_button`
|
|
37
|
+
* that closes the dialog; `false` removes it. It renders after
|
|
38
|
+
* `children`, so a content control (or an `autofocus` element) takes initial
|
|
39
|
+
* focus on open rather than the close button.
|
|
40
|
+
*
|
|
41
|
+
* Pass a `Snippet` to render your own. It receives `attrs`
|
|
42
|
+
* (`DialogCloseButtonAttrs`) carrying the default's placement, styling, a11y,
|
|
43
|
+
* and `onclick` (closes the dialog), plus the `DialogContext` (e.g. `{close}`).
|
|
44
|
+
* Spread `attrs` onto a `<button>` to inherit the corner anchoring and override
|
|
45
|
+
* only what differs (e.g. the glyph), or drop it to place the button freely. The
|
|
46
|
+
* surface is a containing block (`position: relative`), so an absolutely-positioned
|
|
47
|
+
* custom button anchors to it.
|
|
48
|
+
* @default true
|
|
49
|
+
*/
|
|
50
|
+
close_button?: boolean | Snippet<[attrs: DialogCloseButtonAttrs, dialog: DialogContext]>;
|
|
51
|
+
/**
|
|
52
|
+
* Rendered inside the content surface. Receives the `DialogContext` (e.g.
|
|
53
|
+
* `{close}`) so content can close the dialog without reaching into `Dialog`'s
|
|
54
|
+
* `children` snippet.
|
|
55
|
+
*
|
|
56
|
+
* The default `close_button` renders as the surface's last child (so a content
|
|
57
|
+
* control or `autofocus` element wins initial focus over it). Because it's a
|
|
58
|
+
* real child, it throws off universal child-spacing selectors: your content's
|
|
59
|
+
* last element is no longer `:last-child` (so `> :not(:last-child)` matches it
|
|
60
|
+
* and `> :last-child` skips it), and `> * + *` matches the button itself. Either
|
|
61
|
+
* scope spacing to your own elements or a class, or wrap your content in a single
|
|
62
|
+
* element so its child selectors no longer see the button.
|
|
63
|
+
*/
|
|
64
|
+
children: Snippet<[dialog: DialogContext]>;
|
|
65
|
+
};
|
|
66
|
+
declare const DialogContent: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
67
|
+
type DialogContent = ReturnType<typeof DialogContent>;
|
|
68
|
+
export default DialogContent;
|
|
69
|
+
//# sourceMappingURL=DialogContent.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DialogContent.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DialogContent.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAC,cAAc,EAAqB,MAAM,iBAAiB,CAAC;AAExE,OAAO,EAAiB,KAAK,aAAa,EAAE,KAAK,sBAAsB,EAAC,MAAM,aAAa,CAAC;AAE3F,KAAK,gBAAgB,GAAI,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IAC3E;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,KAAK,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IACzF;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;CAC3C,CAAC;AAoEH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
package/dist/dialog.d.ts
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export type DialogAlign = 'center' | 'top';
|
|
2
|
+
export declare const dialog_aligns: Array<DialogAlign>;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* which do not work for plain objects as of v5.0.4.
|
|
7
|
-
* * `ContextmenuParams` uses a similar strategy.
|
|
4
|
+
* Set by `Dialog`, read by `DialogContent`. Lets the content surface close the
|
|
5
|
+
* dialog without the consumer threading `close` through the `children` snippet.
|
|
8
6
|
*/
|
|
9
|
-
export
|
|
7
|
+
export interface DialogContext {
|
|
8
|
+
/**
|
|
9
|
+
* Closes the dialog. When passed an event it's swallowed (default-prevented
|
|
10
|
+
* and propagation-stopped) before closing.
|
|
11
|
+
*/
|
|
12
|
+
close: (e?: Event) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Registers a content-surface element so a press inside it isn't treated as an
|
|
15
|
+
* outside-dismiss. The surface is known by node identity, independent of any
|
|
16
|
+
* class. Use as an attachment on the surface: `{@attach register_surface}`.
|
|
17
|
+
* Returns a cleanup that unregisters on unmount.
|
|
18
|
+
*/
|
|
19
|
+
register_surface: (element: Element) => () => void;
|
|
20
|
+
}
|
|
21
|
+
export declare const dialog_context: {
|
|
22
|
+
get: (error_message?: string) => DialogContext;
|
|
23
|
+
get_maybe: () => DialogContext | undefined;
|
|
24
|
+
set: (value: DialogContext) => DialogContext;
|
|
25
|
+
};
|
|
10
26
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* The main limitation is that the generic cannot be inferred automatically,
|
|
14
|
-
* so we use `to_dialog_params` to construct instances in most cases.
|
|
15
|
-
* Definining `DialogParams` with no concrete `T` lacks typechecking for `props`.
|
|
27
|
+
* The `attrs` a custom `DialogContent` `close_button` snippet receives.
|
|
28
|
+
* Spread onto a `<button>` to inherit the corner-anchored close button.
|
|
16
29
|
*/
|
|
17
|
-
export interface
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
export interface DialogCloseButtonAttrs {
|
|
31
|
+
type: 'button';
|
|
32
|
+
class: string;
|
|
33
|
+
style: string;
|
|
34
|
+
onclick: DialogContext['close'];
|
|
35
|
+
title: string;
|
|
36
|
+
'aria-label': string;
|
|
21
37
|
}
|
|
22
|
-
export type DialogLayout = 'centered' | 'page';
|
|
23
|
-
export declare const dialog_layouts: Array<DialogLayout>;
|
|
24
38
|
//# sourceMappingURL=dialog.d.ts.map
|
package/dist/dialog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dialog.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dialog.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dialog.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,CAAC;AAC3C,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,WAAW,CAAqB,CAAC;AAEnE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B;;;OAGG;IACH,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC3B;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,cAAc;;;;CAAkC,CAAC;AAE9D;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACrB"}
|
package/dist/dialog.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* which do not work for plain objects as of v5.0.4.
|
|
5
|
-
* * `ContextmenuParams` uses a similar strategy.
|
|
6
|
-
*/
|
|
7
|
-
export const to_dialog_params = (Component, props, dialog_props) => ({
|
|
8
|
-
Component,
|
|
9
|
-
props,
|
|
10
|
-
dialog_props,
|
|
11
|
-
});
|
|
12
|
-
export const dialog_layouts = ['centered', 'page'];
|
|
1
|
+
import { create_context } from './context_helpers.js';
|
|
2
|
+
export const dialog_aligns = ['center', 'top'];
|
|
3
|
+
export const dialog_context = create_context();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.201.0",
|
|
4
4
|
"description": "Svelte UI library",
|
|
5
5
|
"tagline": "friendly user zystem",
|
|
6
6
|
"glyph": "🧶",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"eslint": "^9.39.4",
|
|
100
100
|
"eslint-plugin-svelte": "^3.19.0",
|
|
101
101
|
"esm-env": "^1.2.2",
|
|
102
|
-
"jsdom": "^
|
|
102
|
+
"jsdom": "^29.1.1",
|
|
103
103
|
"magic-string": "^0.30.21",
|
|
104
104
|
"prettier": "^3.7.4",
|
|
105
105
|
"prettier-plugin-svelte": "^3.5.1",
|
package/src/lib/dialog.ts
CHANGED
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {create_context} from './context_helpers.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export type DialogAlign = 'center' | 'top';
|
|
4
|
+
export const dialog_aligns: Array<DialogAlign> = ['center', 'top'];
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* which do not work for plain objects as of v5.0.4.
|
|
9
|
-
* * `ContextmenuParams` uses a similar strategy.
|
|
7
|
+
* Set by `Dialog`, read by `DialogContent`. Lets the content surface close the
|
|
8
|
+
* dialog without the consumer threading `close` through the `children` snippet.
|
|
10
9
|
*/
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
export interface DialogContext {
|
|
11
|
+
/**
|
|
12
|
+
* Closes the dialog. When passed an event it's swallowed (default-prevented
|
|
13
|
+
* and propagation-stopped) before closing.
|
|
14
|
+
*/
|
|
15
|
+
close: (e?: Event) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Registers a content-surface element so a press inside it isn't treated as an
|
|
18
|
+
* outside-dismiss. The surface is known by node identity, independent of any
|
|
19
|
+
* class. Use as an attachment on the surface: `{@attach register_surface}`.
|
|
20
|
+
* Returns a cleanup that unregisters on unmount.
|
|
21
|
+
*/
|
|
22
|
+
register_surface: (element: Element) => () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const dialog_context = create_context<DialogContext>();
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* The main limitation is that the generic cannot be inferred automatically,
|
|
25
|
-
* so we use `to_dialog_params` to construct instances in most cases.
|
|
26
|
-
* Definining `DialogParams` with no concrete `T` lacks typechecking for `props`.
|
|
28
|
+
* The `attrs` a custom `DialogContent` `close_button` snippet receives.
|
|
29
|
+
* Spread onto a `<button>` to inherit the corner-anchored close button.
|
|
27
30
|
*/
|
|
28
|
-
export interface
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
export interface DialogCloseButtonAttrs {
|
|
32
|
+
type: 'button';
|
|
33
|
+
class: string;
|
|
34
|
+
style: string;
|
|
35
|
+
onclick: DialogContext['close'];
|
|
36
|
+
title: string;
|
|
37
|
+
'aria-label': string;
|
|
32
38
|
}
|
|
33
|
-
|
|
34
|
-
export type DialogLayout = 'centered' | 'page';
|
|
35
|
-
export const dialog_layouts: Array<DialogLayout> = ['centered', 'page'];
|
package/dist/Dialogs.svelte
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type {Snippet} from 'svelte';
|
|
3
|
-
|
|
4
|
-
import type {DialogParams} from './dialog.js';
|
|
5
|
-
import Dialog from './Dialog.svelte';
|
|
6
|
-
|
|
7
|
-
// TODO this is experimental
|
|
8
|
-
|
|
9
|
-
const {
|
|
10
|
-
dialogs,
|
|
11
|
-
onclose,
|
|
12
|
-
children,
|
|
13
|
-
}: {
|
|
14
|
-
dialogs: Array<DialogParams>;
|
|
15
|
-
onclose?: () => void;
|
|
16
|
-
children?: Snippet<[dialog: DialogParams]>;
|
|
17
|
-
} = $props();
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
{#each dialogs as dialog, index (dialog)}<Dialog
|
|
21
|
-
{onclose}
|
|
22
|
-
{...dialog.dialog_props}
|
|
23
|
-
{index}
|
|
24
|
-
active={index === dialogs.length - 1}
|
|
25
|
-
>{#if children}{@render children(dialog)}{:else}<dialog.Component
|
|
26
|
-
{...dialog.props}
|
|
27
|
-
/>{/if}</Dialog
|
|
28
|
-
>{/each}
|
package/dist/Dialogs.svelte.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { DialogParams } from './dialog.js';
|
|
3
|
-
type $$ComponentProps = {
|
|
4
|
-
dialogs: Array<DialogParams>;
|
|
5
|
-
onclose?: () => void;
|
|
6
|
-
children?: Snippet<[dialog: DialogParams]>;
|
|
7
|
-
};
|
|
8
|
-
declare const Dialogs: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
-
type Dialogs = ReturnType<typeof Dialogs>;
|
|
10
|
-
export default Dialogs;
|
|
11
|
-
//# sourceMappingURL=Dialogs.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Dialogs.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Dialogs.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAEpC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAG7C,KAAK,gBAAgB,GAAI;IACxB,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC3C,CAAC;AAoBH,QAAA,MAAM,OAAO,sDAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|