@fuzdev/fuz_ui 0.200.0 → 0.202.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.
@@ -1,28 +1,59 @@
1
1
  import type { Snippet } from 'svelte';
2
- import type { DialogLayout } from './dialog.js';
3
- type $$ComponentProps = {
4
- container?: HTMLElement;
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
- * @default 'centered'
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
- layout?: DialogLayout;
14
+ show?: boolean;
9
15
  /**
10
- * index 0 is under 1 is under 2 etc -- the topmost dialog is the last in the array
11
- * @default 0
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
- index?: number;
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
- active?: boolean;
27
+ dismissable?: boolean;
18
28
  /**
19
- * If provided, prevents clicks that would close the dialog
20
- * from bubbling past any elements matching this selector.
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 | null;
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
- children: Snippet<[close: (e?: Event) => void]>;
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;AAKpC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAE7C,KAAK,gBAAgB,GAAI;IAExB,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC;CAChD,CAAC;AA4HH,QAAA,MAAM,MAAM,sDAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,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,142 @@
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
+ import {icon_close} from './icons.js';
7
+ import Svg from './Svg.svelte';
8
+
9
+ /**
10
+ * The default content surface for `Dialog`: a fuz_css `.pane` card with a
11
+ * `gutter` margin (the dismiss zone), centered in the viewport by `Dialog`'s
12
+ * own layout. It reads `close` from `dialog_context` (set by `Dialog`) and
13
+ * passes it to `children`, so content can close the dialog without the consumer
14
+ * threading `close` down from `Dialog`'s own `children` snippet.
15
+ *
16
+ * Must be rendered inside a `Dialog`, with which it registers its surface, so
17
+ * click-outside-to-close treats presses inside the card as inside regardless of
18
+ * classes -- `pane={false}` works without further setup.
19
+ *
20
+ * By default it renders a `close_button` floating just outside the surface's
21
+ * top-right corner. The surface is a containing block (`position: relative`) for
22
+ * that button and any absolutely-positioned content in `children`.
23
+ *
24
+ * The surface has no layout of its own -- it's a plain block, so children flow
25
+ * top-to-bottom in normal document order. Pass `class` through (e.g. `box` for a
26
+ * centered column, `column` for an uncentered one) to add one.
27
+ *
28
+ * @module
29
+ */
30
+
31
+ const {
32
+ padding = 'var(--space_xl)',
33
+ pane = true,
34
+ gutter = 'var(--space_xl3)',
35
+ max_width = 'var(--distance_md)',
36
+ close_button = true,
37
+ children,
38
+ ...rest
39
+ }: Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
40
+ /**
41
+ * The content surface's padding, set as an inline style. Defaults to
42
+ * `var(--space_xl)` for a comfortable gutter between the card edge and its
43
+ * content. Set to `''` or `'0'` for no padding (e.g. a flush image or a
44
+ * surface that manages its own spacing).
45
+ * @default 'var(--space_xl)'
46
+ */
47
+ padding?: string;
48
+ /**
49
+ * Whether to apply the fuz_css `.pane` card class to the content surface --
50
+ * its opaque background, shadow, and rounded corners. `true` (the default) is
51
+ * the standard dialog card; `false` gives a chromeless surface. Either way the
52
+ * surface registers with `Dialog`, so click-outside-to-close keeps working.
53
+ * @default true
54
+ */
55
+ pane?: boolean;
56
+ /**
57
+ * The gutter around the `.pane` card -- its outer margin, and so the area
58
+ * outside the card where a press dismisses the dialog. Set to `''` or `'0'` to remove.
59
+ * @default 'var(--space_xl3)'
60
+ */
61
+ gutter?: string;
62
+ /**
63
+ * The card's max width. The card shrinks to its content and is capped here,
64
+ * so narrow content stays narrow while wide content doesn't sprawl. Set to
65
+ * `''` for no cap (pure shrink-to-content).
66
+ * @default 'var(--distance_md)'
67
+ */
68
+ max_width?: string;
69
+ /**
70
+ * The close button floating just outside the content surface's top-right
71
+ * corner. `true` (the default) renders an absolutely-positioned `.sm.plain.icon_button`
72
+ * that closes the dialog; `false` removes it. It renders after
73
+ * `children`, so a content control (or an `autofocus` element) takes initial
74
+ * focus on open rather than the close button.
75
+ *
76
+ * Pass a `Snippet` to render your own. It receives `attrs`
77
+ * (`DialogCloseButtonAttrs`) carrying the default's placement, styling, a11y,
78
+ * and `onclick` (closes the dialog), plus the `DialogContext` (e.g. `{close}`).
79
+ * Spread `attrs` onto a `<button>` to inherit the corner anchoring and override
80
+ * only what differs (e.g. the glyph), or drop it to place the button freely. The
81
+ * surface is a containing block (`position: relative`), so an absolutely-positioned
82
+ * custom button anchors to it.
83
+ * @default true
84
+ */
85
+ close_button?: boolean | Snippet<[attrs: DialogCloseButtonAttrs, dialog: DialogContext]>;
86
+ /**
87
+ * Rendered inside the content surface. Receives the `DialogContext` (e.g.
88
+ * `{close}`) so content can close the dialog without reaching into `Dialog`'s
89
+ * `children` snippet.
90
+ *
91
+ * The default `close_button` renders as the surface's last child (so a content
92
+ * control or `autofocus` element wins initial focus over it). Because it's a
93
+ * real child, it throws off universal child-spacing selectors: your content's
94
+ * last element is no longer `:last-child` (so `> :not(:last-child)` matches it
95
+ * and `> :last-child` skips it), and `> * + *` matches the button itself. Either
96
+ * scope spacing to your own elements or a class, or wrap your content in a single
97
+ * element so its child selectors no longer see the button.
98
+ */
99
+ children: Snippet<[dialog: DialogContext]>;
100
+ } = $props();
101
+
102
+ const dialog = dialog_context.get('DialogContent must be rendered inside a Dialog');
103
+
104
+ // the default close button's attributes; `onclick` closes the dialog and the rest
105
+ // is handed to a custom `close_button` snippet so it can inherit the corner-anchored
106
+ // button by spreading them. positioning is inline (not a scoped class) so it travels
107
+ // into the consumer's snippet, which carries its own style scope
108
+ const close_button_attrs: DialogCloseButtonAttrs = {
109
+ type: 'button',
110
+ class: 'sm plain icon_button',
111
+ style: 'position: absolute; top: 0; right: 0;',
112
+ onclick: dialog.close,
113
+ title: 'close',
114
+ 'aria-label': 'close',
115
+ } satisfies SvelteHTMLElements['button'];
116
+ </script>
117
+
118
+ <!-- The surface is centered by `Dialog`'s `.dialog-wrapper` flex (no wrapper of its own
119
+ needed) and is a containing block (`position: relative`) for the close button and any
120
+ absolutely-positioned content in `children`. `gutter` is the surface's margin -- the
121
+ dismiss zone outside the card, where a press lands on the wrapper and closes the dialog.
122
+ `min-width: 0` lets this flex-item surface shrink below its content's intrinsic width, so
123
+ content with its own overflow (e.g. `Code`) scrolls instead of forcing the card wider. -->
124
+ <div
125
+ {...rest}
126
+ class:pane
127
+ style:padding
128
+ style:margin={gutter}
129
+ style:max-width={max_width}
130
+ style:min-width="0"
131
+ style:position="relative"
132
+ {@attach dialog.register_surface}
133
+ >
134
+ {@render children(dialog)}
135
+ <!-- rendered after `children` so a content control (or an `autofocus` element)
136
+ takes initial focus on open, not the close button -->
137
+ {#if close_button === true}
138
+ <button {...close_button_attrs}><Svg data={icon_close} /></button>
139
+ {:else if close_button}
140
+ {@render close_button(close_button_attrs, dialog)}
141
+ {/if}
142
+ </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;AAI3F,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;AAsEH,QAAA,MAAM,aAAa,sDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
package/dist/Docs.svelte CHANGED
@@ -10,6 +10,7 @@
10
10
  import DocsSecondaryNav from './DocsSecondaryNav.svelte';
11
11
  import DocsTertiaryNav from './DocsTertiaryNav.svelte';
12
12
  import Dialog from './Dialog.svelte';
13
+ import DialogContent from './DialogContent.svelte';
13
14
  import DocsFooter from './DocsFooter.svelte';
14
15
  import {site_context} from './site.svelte.js';
15
16
  import {FUZ_DEV_URL} from './constants.js';
@@ -83,15 +84,17 @@
83
84
  <!-- TODO this is messy rendering `DocsSecondaryNav` twice to handle responsive states with SSR correctly -->
84
85
  {#if show_secondary_nav_dialog && innerWidth.current && innerWidth.current <= TERTIARY_NAV_BREAKPOINT}
85
86
  <Dialog onclose={() => (show_secondary_nav_dialog = false)}>
86
- <div class="pane" style:--docs_menu_width={docs_menu_width}>
87
- <div class="p_xl pb_0">
88
- <Breadcrumb />
89
- </div>
90
- <div class="px_lg pb_xl">
91
- <DocsSecondaryNav {tomes} sidebar={false} />
92
- <DocsTertiaryNav {tomes} {tomes_by_slug} sidebar={false} />
87
+ <DialogContent padding="">
88
+ <div style:--docs_menu_width={docs_menu_width}>
89
+ <div class="p_xl pb_0">
90
+ <Breadcrumb />
91
+ </div>
92
+ <div class="px_lg pb_xl">
93
+ <DocsSecondaryNav {tomes} sidebar={false} />
94
+ <DocsTertiaryNav {tomes} {tomes_by_slug} sidebar={false} />
95
+ </div>
93
96
  </div>
94
- </div>
97
+ </DialogContent>
95
98
  </Dialog>
96
99
  {/if}
97
100
 
@@ -1 +1 @@
1
- {"version":3,"file":"Docs.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Docs.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAMpC,OAAO,EAAC,IAAI,EAAgB,MAAM,WAAW,CAAC;AAU7C,KAAK,gBAAgB,GAAI;IACxB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAkGH,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"Docs.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Docs.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAMpC,OAAO,EAAC,IAAI,EAAgB,MAAM,WAAW,CAAC;AAW7C,KAAK,gBAAgB,GAAI;IACxB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CAClB,CAAC;AAqGH,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
@@ -1,6 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type {SvelteHTMLElements} from 'svelte/elements';
3
3
 
4
+ import {icon_close} from './icons.js';
5
+ import Svg from './Svg.svelte';
6
+
4
7
  let {
5
8
  placeholder = 'search modules and declarations...',
6
9
  module_count,
@@ -71,7 +74,7 @@
71
74
  title="clear search"
72
75
  aria-label="clear search"
73
76
  >
74
- 🗙
77
+ <Svg data={icon_close} />
75
78
  </button>
76
79
  {/if}
77
80
  </label>
@@ -1 +1 @@
1
- {"version":3,"file":"DocsSearch.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DocsSearch.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAEvD,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,OAAO,CAAC,GAAG;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACrB,CAAC;AA+DH,QAAA,MAAM,UAAU,kEAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"DocsSearch.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DocsSearch.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAKvD,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,OAAO,CAAC,GAAG;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACrB,CAAC;AAkEH,QAAA,MAAM,UAAU,kEAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
package/dist/Svg.svelte CHANGED
@@ -4,6 +4,29 @@
4
4
 
5
5
  import type {SvgData} from './svg.js';
6
6
 
7
+ /**
8
+ * Renders an `SvgData` icon definition to inline `svg` markup, never via `@html`.
9
+ *
10
+ * ## Security
11
+ *
12
+ * `SvgData` is rendered structurally into a fixed element set - `svg`, `defs`,
13
+ * `linearGradient`/`radialGradient`, `stop`, and `path`. None of these expose a
14
+ * navigation or script sink (no `src` or `foreignObject`, and a gradient `href`
15
+ * only references another gradient as a template, never a document location),
16
+ * and Svelte routes `on*` spread keys through `addEventListener` rather than
17
+ * writing inline handler attributes. So even an untrusted `SvgData` cannot
18
+ * introduce script execution or navigation - which is why the data is rendered
19
+ * through typed fields (`paths`, `gradients`) instead of a raw-markup escape
20
+ * hatch.
21
+ *
22
+ * The one residual: a `style` value (`data.style`, a path's `style`, or a
23
+ * gradient's `attrs.style`) can still issue network requests via `url(...)`, so
24
+ * sanitize styles if you render untrusted data and that matters. Per-instance
25
+ * `svg` attributes come from this component's own props, not from `data`.
26
+ *
27
+ * @module
28
+ */
29
+
7
30
  const {
8
31
  data,
9
32
  fill,
@@ -44,29 +67,43 @@
44
67
  const final_width = $derived(width ?? size); // TODO @many default value? `100%` or omitted or something else?
45
68
  const final_height = $derived(height ?? size); // TODO @many default value? `100%` or omitted or something else?
46
69
 
47
- // merge `style` so users don't accidentally clobber any style data - maybe support other attrs or somehow clean this up?
70
+ // merge `style` so the icon's own style and a per-instance `style` prop don't clobber each other
48
71
  const style = $derived(
49
- data.attrs?.style && rest.style
50
- ? ensure_end(data.attrs.style, ';') + ' ' + rest.style
51
- : (data.attrs?.style ?? rest.style),
72
+ data.style && rest.style
73
+ ? ensure_end(data.style, ';') + ' ' + rest.style
74
+ : (data.style ?? rest.style),
52
75
  );
53
-
54
- // TODO dont use @html
55
76
  </script>
56
77
 
57
78
  <svg
58
79
  viewBox={data.viewBox ?? '0 0 100 100'}
59
- {...data.attrs}
60
80
  {...rest}
61
81
  aria-label={label ?? data.label}
62
- class="{data.attrs?.class} {rest.class}"
63
82
  class:inline
64
83
  style:width={final_width}
65
84
  style:height={final_height}
66
85
  style:flex-shrink={shrink ? 1 : 0}
67
86
  {style}
68
87
  >
69
- {#if data.raw}{@html data.raw}{/if}<!-- eslint-disable-line svelte/no-at-html-tags -->
88
+ {#if data.gradients}
89
+ <defs>
90
+ {#each data.gradients as gradient (gradient.id)}
91
+ {#if gradient.type === 'radial'}
92
+ <radialGradient id={gradient.id} {...gradient.attrs}>
93
+ {#each gradient.stops as stop (stop)}
94
+ <stop offset={stop.offset} stop-color={stop.color} stop-opacity={stop.opacity} />
95
+ {/each}
96
+ </radialGradient>
97
+ {:else}
98
+ <linearGradient id={gradient.id} {...gradient.attrs}>
99
+ {#each gradient.stops as stop (stop)}
100
+ <stop offset={stop.offset} stop-color={stop.color} stop-opacity={stop.opacity} />
101
+ {/each}
102
+ </linearGradient>
103
+ {/if}
104
+ {/each}
105
+ </defs>
106
+ {/if}
70
107
  {#if data.paths}
71
108
  {#each data.paths as path (path)}
72
109
  <path fill={final_fill} {...path} />
@@ -1 +1 @@
1
- {"version":3,"file":"Svg.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Svg.svelte"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAExD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAErC,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG;IACpD,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AA8CH,QAAA,MAAM,GAAG,sDAAwC,CAAC;AAClD,KAAK,GAAG,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;AAClC,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"Svg.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Svg.svelte"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAExD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAErC,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG;IACpD,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAqFH,QAAA,MAAM,GAAG,sDAAwC,CAAC;AAClD,KAAK,GAAG,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,CAAC;AAClC,eAAe,GAAG,CAAC"}
package/dist/dialog.d.ts CHANGED
@@ -1,24 +1,38 @@
1
- import type { ComponentProps, Component } from 'svelte';
2
- import type Dialog from './Dialog.svelte';
1
+ export type DialogAlign = 'center' | 'top';
2
+ export declare const dialog_aligns: Array<DialogAlign>;
3
3
  /**
4
- * This helper function is needed to construct `DialogParams` with type safety.
5
- * It uses TypeScript's inferred generics for functions,
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 declare const to_dialog_params: <T extends Component<any>>(Component: T, props: ComponentProps<T>, dialog_props?: Partial<ComponentProps<typeof Dialog>>) => DialogParams<T>;
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
- * This pattern is based on:
12
- * https://github.com/ivanhofer/sveltekit-typescript-showcase/blob/main/src/01-props/09-svelte-component/Component.svelte
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 DialogParams<T extends Component<any> = Component<any>> {
18
- Component: T;
19
- props: ComponentProps<T>;
20
- dialog_props?: Partial<ComponentProps<typeof Dialog>> | undefined;
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
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAE,SAAS,EAAC,MAAM,QAAQ,CAAC;AAEtD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,SAAS,CAAC,GAAG,CAAC,EACxD,WAAW,CAAC,EACZ,OAAO,cAAc,CAAC,CAAC,CAAC,EACxB,eAAe,OAAO,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC,KACnD,YAAY,CAAC,CAAC,CAIf,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC;IACtE,SAAS,EAAE,CAAC,CAAC;IACb,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;CAClE;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAC/C,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,YAAY,CAAwB,CAAC"}
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
- * This helper function is needed to construct `DialogParams` with type safety.
3
- * It uses TypeScript's inferred generics for functions,
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();