@happyvertical/smrt-svelte 0.57.46 → 0.57.48
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/agents/components.md
CHANGED
|
@@ -108,3 +108,10 @@ and `limit`. `iconOnly` (default false) swaps the text trigger for an
|
|
|
108
108
|
panel under the trigger) or `'beside'` (panel opens at the trigger's inline
|
|
109
109
|
end, top-aligned, for a collapsed rail; `data-placement` on the root).
|
|
110
110
|
`variant` is passed to the trigger `Button` (default `'ghost'`).
|
|
111
|
+
`open` is bindable (`bind:open`) and `onopenchange(open)` reports the bell's
|
|
112
|
+
own changes, so a host can close the panel itself. The panel light-dismisses
|
|
113
|
+
(#4566): a pointer-down outside the bell (document capture, like `Popover`),
|
|
114
|
+
Escape at the document level wherever focus is (returns focus to the trigger),
|
|
115
|
+
and activating a notice link (closes without refocusing, so navigation keeps
|
|
116
|
+
focus). Notice titles are links styled in `--smrt-color-primary` with an
|
|
117
|
+
underline, not the browser default (#4567).
|
|
@@ -18,6 +18,14 @@ interface Props {
|
|
|
18
18
|
placement?: 'below' | 'beside';
|
|
19
19
|
/** Trigger button variant. */
|
|
20
20
|
variant?: ComponentProps<typeof Button>['variant'];
|
|
21
|
+
/**
|
|
22
|
+
* Whether the panel is open. Bindable, so a host can close it (or open it)
|
|
23
|
+
* itself; the bell still toggles it from the trigger and closes it on light
|
|
24
|
+
* dismiss (#4566).
|
|
25
|
+
*/
|
|
26
|
+
open?: boolean;
|
|
27
|
+
/** Called after the panel opens or closes, with the new state. */
|
|
28
|
+
onopenchange?: (open: boolean) => void;
|
|
21
29
|
}
|
|
22
30
|
const { t } = useI18n();
|
|
23
31
|
let {
|
|
@@ -27,14 +35,33 @@ let {
|
|
|
27
35
|
iconOnly = false,
|
|
28
36
|
placement = 'below',
|
|
29
37
|
variant = 'ghost',
|
|
38
|
+
open = $bindable(false),
|
|
39
|
+
onopenchange,
|
|
30
40
|
}: Props = $props();
|
|
31
41
|
const heading = $derived(label ?? t(M['ui.notification_bell.label']));
|
|
32
42
|
const instanceId = $props.id();
|
|
43
|
+
let root = $state<HTMLDivElement>();
|
|
33
44
|
let panel = $state<HTMLDivElement>();
|
|
34
|
-
let open = $state(false);
|
|
35
45
|
$effect(() => {
|
|
36
46
|
if (open) panel?.focus();
|
|
37
47
|
});
|
|
48
|
+
function setOpen(next: boolean) {
|
|
49
|
+
if (open === next) return;
|
|
50
|
+
open = next;
|
|
51
|
+
onopenchange?.(next);
|
|
52
|
+
}
|
|
53
|
+
// Light dismiss (#4566): a pointer-down anywhere outside the bell closes the
|
|
54
|
+
// panel, like `Popover`. Captured on the document so a click that lands on a
|
|
55
|
+
// control which stops propagation still counts as "outside".
|
|
56
|
+
$effect(() => {
|
|
57
|
+
if (!open || typeof document === 'undefined') return;
|
|
58
|
+
const dismiss = (event: PointerEvent) => {
|
|
59
|
+
if (event.target instanceof Node && !root?.contains(event.target))
|
|
60
|
+
setOpen(false);
|
|
61
|
+
};
|
|
62
|
+
document.addEventListener('pointerdown', dismiss, true);
|
|
63
|
+
return () => document.removeEventListener('pointerdown', dismiss, true);
|
|
64
|
+
});
|
|
38
65
|
let loading = $state(true);
|
|
39
66
|
let error = $state<string | null>(null);
|
|
40
67
|
let unread = $state(0);
|
|
@@ -79,11 +106,14 @@ function safeHref(value: string | null | undefined): string | null {
|
|
|
79
106
|
}
|
|
80
107
|
}
|
|
81
108
|
function close() {
|
|
82
|
-
|
|
109
|
+
setOpen(false);
|
|
83
110
|
document.getElementById(`${instanceId}-trigger`)?.focus();
|
|
84
111
|
}
|
|
112
|
+
// Escape closes the panel wherever focus is (the body after following a
|
|
113
|
+
// notice, a control elsewhere in the shell), not only inside the panel. A
|
|
114
|
+
// dialog or palette that owns Escape already stops it before it gets here.
|
|
85
115
|
function keydown(event: KeyboardEvent) {
|
|
86
|
-
if (event.key === 'Escape' && open) {
|
|
116
|
+
if (event.key === 'Escape' && open && !event.defaultPrevented) {
|
|
87
117
|
event.preventDefault();
|
|
88
118
|
close();
|
|
89
119
|
}
|
|
@@ -123,8 +153,10 @@ $effect(() => {
|
|
|
123
153
|
});
|
|
124
154
|
</script>
|
|
125
155
|
|
|
126
|
-
<
|
|
127
|
-
|
|
156
|
+
<svelte:document onkeydown={keydown} />
|
|
157
|
+
|
|
158
|
+
<div bind:this={root} class="smrt-notification-bell" data-placement={placement} data-smrt-component="NotificationBell">
|
|
159
|
+
<Button id={`${instanceId}-trigger`} aria-controls={`${instanceId}-panel`} {variant} aria-haspopup="dialog" aria-expanded={open} aria-label={iconOnly ? (unread > 0 ? `${heading}, ${unreadText}` : heading) : undefined} onclick={() => setOpen(!open)}>
|
|
128
160
|
{#if iconOnly}
|
|
129
161
|
<span class="smrt-notification-bell__icon" aria-hidden="true">
|
|
130
162
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" focusable="false"><path d="M12 22c1.1 0 2-.9 2-2h-4a2 2 0 0 0 2 2m6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4a1.5 1.5 0 0 0-3 0v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1z" /></svg>
|
|
@@ -135,12 +167,12 @@ $effect(() => {
|
|
|
135
167
|
{/if}
|
|
136
168
|
</Button>
|
|
137
169
|
{#if open}
|
|
138
|
-
<div bind:this={panel} id={`${instanceId}-panel`} role="dialog" aria-label={heading} tabindex="-1"
|
|
170
|
+
<div bind:this={panel} id={`${instanceId}-panel`} role="dialog" aria-label={heading} tabindex="-1" class="smrt-notification-bell__panel">
|
|
139
171
|
<header><strong>{heading}</strong><Button variant="ghost" size="sm" disabled={unread === 0 || loading} onclick={() => mutate()}>{t(M['ui.notification_bell.mark_all'])}</Button></header>
|
|
140
172
|
{#if error}<p role="alert">{t(error)}</p>{/if}
|
|
141
173
|
{#if loading}<p role="status">{t(M['ui.notification_bell.loading'])}</p>{:else if items.length === 0}<p>{t(M['ui.notification_bell.empty'])}</p>{:else}<ol>
|
|
142
174
|
{#each items as item (item.id)}<li data-read={item.readAt ? 'true' : 'false'}>
|
|
143
|
-
{#if safeHref(item.href)}<a href={safeHref(item.href) ?? undefined}>{item.title}</a>{:else}<strong>{item.title}</strong>{/if}
|
|
175
|
+
{#if safeHref(item.href)}<a href={safeHref(item.href) ?? undefined} onclick={() => setOpen(false)}>{item.title}</a>{:else}<strong>{item.title}</strong>{/if}
|
|
144
176
|
{#if item.body}<p>{item.body}</p>{/if}
|
|
145
177
|
{#if !item.readAt}<Button variant="ghost" size="sm" onclick={() => mutate(item.id)}>{t(M['ui.notification_bell.mark_one'])}</Button>{/if}
|
|
146
178
|
</li>{/each}
|
|
@@ -161,6 +193,9 @@ $effect(() => {
|
|
|
161
193
|
ol { display: grid; gap: var(--smrt-spacing-2); padding: 0; list-style: none; }
|
|
162
194
|
li { padding-block: var(--smrt-spacing-2); border-block-end: var(--smrt-hairline); }
|
|
163
195
|
li[data-read='false'] { border-inline-start: 3px solid var(--smrt-color-primary); padding-inline-start: var(--smrt-spacing-2); }
|
|
196
|
+
/* Notice titles are links: themed like the panel's other text, not the browser's default link blue (#4567). */
|
|
197
|
+
li a { color: var(--smrt-color-primary); font-weight: var(--smrt-typography-weight-semibold, 600); text-decoration: underline; text-underline-offset: 0.15em; }
|
|
198
|
+
li a:hover, li a:focus-visible { color: var(--smrt-color-on-surface); }
|
|
164
199
|
p { overflow-wrap: anywhere; }
|
|
165
200
|
}
|
|
166
201
|
</style>
|
|
@@ -14,8 +14,16 @@ interface Props {
|
|
|
14
14
|
placement?: 'below' | 'beside';
|
|
15
15
|
/** Trigger button variant. */
|
|
16
16
|
variant?: ComponentProps<typeof Button>['variant'];
|
|
17
|
+
/**
|
|
18
|
+
* Whether the panel is open. Bindable, so a host can close it (or open it)
|
|
19
|
+
* itself; the bell still toggles it from the trigger and closes it on light
|
|
20
|
+
* dismiss (#4566).
|
|
21
|
+
*/
|
|
22
|
+
open?: boolean;
|
|
23
|
+
/** Called after the panel opens or closes, with the new state. */
|
|
24
|
+
onopenchange?: (open: boolean) => void;
|
|
17
25
|
}
|
|
18
|
-
declare const NotificationBell: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
declare const NotificationBell: import("svelte").Component<Props, {}, "open">;
|
|
19
27
|
type NotificationBell = ReturnType<typeof NotificationBell>;
|
|
20
28
|
export default NotificationBell;
|
|
21
29
|
//# sourceMappingURL=NotificationBell.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotificationBell.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/notifications/NotificationBell.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,UAAU,KAAK;IACb,wEAAwE;IACxE,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC/B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"NotificationBell.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/notifications/NotificationBell.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGvD,UAAU,KAAK;IACb,wEAAwE;IACxE,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC/B,8BAA8B;IAC9B,OAAO,CAAC,EAAE,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC;IACnD;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC;AAsKD,QAAA,MAAM,gBAAgB,+CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-svelte",
|
|
3
|
-
"version": "0.57.
|
|
3
|
+
"version": "0.57.48",
|
|
4
4
|
"smrtJsdoc": "strict",
|
|
5
5
|
"description": "Svelte 5 components for SMRT user management - auth, users, tenants, roles, permissions, groups",
|
|
6
6
|
"type": "module",
|
|
@@ -209,12 +209,12 @@
|
|
|
209
209
|
"access": "public"
|
|
210
210
|
},
|
|
211
211
|
"dependencies": {
|
|
212
|
-
"@happyvertical/logger": "^0.111.
|
|
213
|
-
"@happyvertical/smrt-languages": "0.57.
|
|
214
|
-
"@happyvertical/smrt-mcp-apps": "0.57.
|
|
215
|
-
"@happyvertical/smrt-types": "0.57.
|
|
216
|
-
"@happyvertical/smrt-ui": "0.57.
|
|
217
|
-
"@happyvertical/smrt-web": "0.57.
|
|
212
|
+
"@happyvertical/logger": "^0.111.3",
|
|
213
|
+
"@happyvertical/smrt-languages": "0.57.48",
|
|
214
|
+
"@happyvertical/smrt-mcp-apps": "0.57.48",
|
|
215
|
+
"@happyvertical/smrt-types": "0.57.48",
|
|
216
|
+
"@happyvertical/smrt-ui": "0.57.48",
|
|
217
|
+
"@happyvertical/smrt-web": "0.57.48",
|
|
218
218
|
"@tanstack/db": "^1.0.0-rc.1",
|
|
219
219
|
"@tanstack/svelte-db": "^1.0.0-rc.1",
|
|
220
220
|
"esm-env": "^1.2.2"
|
|
@@ -249,15 +249,15 @@
|
|
|
249
249
|
}
|
|
250
250
|
},
|
|
251
251
|
"devDependencies": {
|
|
252
|
-
"@happyvertical/smrt-agents": "0.57.
|
|
253
|
-
"@happyvertical/smrt-chat": "0.57.
|
|
254
|
-
"@happyvertical/smrt-content": "0.57.
|
|
255
|
-
"@happyvertical/smrt-core": "0.57.
|
|
256
|
-
"@happyvertical/smrt-reports": "0.57.
|
|
257
|
-
"@happyvertical/smrt-scanner": "0.57.
|
|
258
|
-
"@happyvertical/smrt-tenancy": "0.57.
|
|
259
|
-
"@happyvertical/smrt-users": "0.57.
|
|
260
|
-
"@happyvertical/speech": "^0.111.
|
|
252
|
+
"@happyvertical/smrt-agents": "0.57.48",
|
|
253
|
+
"@happyvertical/smrt-chat": "0.57.48",
|
|
254
|
+
"@happyvertical/smrt-content": "0.57.48",
|
|
255
|
+
"@happyvertical/smrt-core": "0.57.48",
|
|
256
|
+
"@happyvertical/smrt-reports": "0.57.48",
|
|
257
|
+
"@happyvertical/smrt-scanner": "0.57.48",
|
|
258
|
+
"@happyvertical/smrt-tenancy": "0.57.48",
|
|
259
|
+
"@happyvertical/smrt-users": "0.57.48",
|
|
260
|
+
"@happyvertical/speech": "^0.111.3",
|
|
261
261
|
"@sveltejs/package": "^2.5.8",
|
|
262
262
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
263
263
|
"@testing-library/jest-dom": "^7.0.1",
|