annotate design_system/src/components/notifications.js @ 265:056790c4fb0d

add role-aware Epi assistant prompts Add verified June knowledge, guest/member/admin Copilot profiles, profile-isolated session recovery, animated Epi greetings, and a single authoritative runtime config workflow for inference. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 10:50:30 -0700
parents 60a876c4587a
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
1 const MAX_VISIBLE = 3;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
2 const MAX_QUEUED = 20;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
3 const MAX_RECORDS = MAX_VISIBLE + MAX_QUEUED;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
4 const DEFAULT_DURATION_MS = 5000;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
5 const MIN_DURATION_MS = 250;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
6 const MAX_DURATION_MS = 120000;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
7 const TONES = new Set(["info", "success", "warning", "error"]);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
8 const ANNOUNCEMENTS = new Set(["none", "polite", "assertive"]);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
9
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
10 function isShortString(value, maximum, allowEmpty = false) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
11 return typeof value === "string" &&
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
12 value.length <= maximum &&
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
13 (allowEmpty || value.trim().length > 0);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
14 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
15
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
16 function resolveCssLength(element, property, fallback) {
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
17 const probe = document.createElement("span");
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
18 probe.style.position = "absolute";
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
19 probe.style.inlineSize = `var(${property})`;
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
20 probe.style.visibility = "hidden";
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
21 element.append(probe);
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
22 const value = Number.parseFloat(getComputedStyle(probe).inlineSize);
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
23 probe.remove();
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
24 return Number.isFinite(value) ? value : fallback;
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
25 }
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
26
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
27 /**
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
28 * Validates and normalizes a notification event record.
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
29 *
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
30 * @param {*} value Candidate event detail.
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
31 * @return {?Object} Frozen normalized record, or null when invalid.
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
32 */
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
33 export function validateNotification(value) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
34 if (!value ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
35 value.version !== 1 ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
36 !isShortString(value.id, 128) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
37 !TONES.has(value.tone) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
38 !isShortString(value.message, 500) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
39 !ANNOUNCEMENTS.has(value.announcement)) return null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
40 if (value.description !== undefined &&
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
41 !isShortString(value.description, 1000, true)) return null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
42 if (value.persistent !== undefined &&
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
43 typeof value.persistent !== "boolean") return null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
44 if (value.durationMs !== undefined &&
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
45 (!Number.isFinite(value.durationMs) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
46 value.durationMs < MIN_DURATION_MS ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
47 value.durationMs > MAX_DURATION_MS)) return null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
48
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
49 let action;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
50 if (value.action !== undefined) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
51 if (!value.action ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
52 !isShortString(value.action.token, 256) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
53 !isShortString(value.action.label, 120)) return null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
54 action = Object.freeze({
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
55 token: value.action.token,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
56 label: value.action.label,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
57 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
58 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
59
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
60 return Object.freeze({
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
61 version: 1,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
62 id: value.id,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
63 tone: value.tone,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
64 message: value.message,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
65 description: value.description || "",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
66 announcement: value.announcement,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
67 durationMs: value.durationMs ?? DEFAULT_DURATION_MS,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
68 persistent: value.persistent === true,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
69 action,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
70 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
71 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
72
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
73 /**
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
74 * Validates a programmatic notification dismissal record.
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
75 *
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
76 * @param {*} value Candidate event detail.
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
77 * @return {?string} Valid notification ID, or null when invalid.
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
78 */
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
79 export function validateNotificationDismiss(value) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
80 if (!value ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
81 value.version !== 1 ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
82 !isShortString(value.id, 128)) return null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
83 return value.id;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
84 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
85
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
86 function makeRecord(notification, announced = false) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
87 return {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
88 ...notification,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
89 announced,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
90 pauseReasons: new Set(),
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
91 remainingMs: notification.durationMs,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
92 startedAt: 0,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
93 timer: null,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
94 view: null,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
95 };
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
96 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
97
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
98 /**
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
99 * Renders a bounded, scoped notification queue.
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
100 *
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
101 * @extends {HTMLElement}
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
102 */
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
103 export class ZenNotifications extends HTMLElement {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
104 static version = 1;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
105
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
106 #records = new Map();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
107 #announcementQueues = {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
108 polite: [],
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
109 assertive: [],
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
110 };
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
111 #announcementTimers = {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
112 polite: null,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
113 assertive: null,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
114 };
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
115 #announcementRunning = {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
116 polite: false,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
117 assertive: false,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
118 };
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
119 #exitTimers = new Map();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
120 #exitingElements = new Set();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
121 #layoutFrame = null;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
122 #resizeObserver = null;
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
123
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
124 constructor() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
125 super();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
126 this.handleNotify = event => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
127 if (!(event instanceof CustomEvent) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
128 !event.bubbles ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
129 event.composed) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
130 event.stopPropagation();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
131 this.notify(event.detail);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
132 };
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
133 this.handleDismiss = event => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
134 if (!(event instanceof CustomEvent) ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
135 !event.bubbles ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
136 event.composed) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
137 event.stopPropagation();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
138 const id = validateNotificationDismiss(event.detail);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
139 if (id) this.dismiss(id, "programmatic");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
140 };
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
141 this.handleVisibility = () => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
142 for (const record of this.#records.values()) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
143 if (document.hidden) this.pause(record, "hidden");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
144 else this.resume(record, "hidden");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
145 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
146 };
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
147 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
148
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
149 connectedCallback() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
150 if (!this.stack) this.createInternalElements();
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
151 if (!this.#resizeObserver &&
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
152 typeof ResizeObserver === "function") {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
153 this.#resizeObserver = new ResizeObserver(() => {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
154 this.requestLayout();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
155 });
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
156 }
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
157 this.addEventListener("zen-notify", this.handleNotify);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
158 this.addEventListener(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
159 "zen-dismiss-notification",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
160 this.handleDismiss,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
161 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
162 document.addEventListener(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
163 "visibilitychange",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
164 this.handleVisibility,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
165 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
166 this.render();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
167 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
168
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
169 disconnectedCallback() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
170 this.removeEventListener("zen-notify", this.handleNotify);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
171 this.removeEventListener(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
172 "zen-dismiss-notification",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
173 this.handleDismiss,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
174 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
175 document.removeEventListener(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
176 "visibilitychange",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
177 this.handleVisibility,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
178 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
179 for (const record of this.#records.values()) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
180 this.clearTimer(record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
181 }
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
182 cancelAnimationFrame(this.#layoutFrame);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
183 this.#layoutFrame = null;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
184 this.#resizeObserver?.disconnect();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
185 for (const timer of this.#exitTimers.values()) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
186 clearTimeout(timer);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
187 }
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
188 this.#exitTimers.clear();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
189 this.#exitingElements.clear();
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
190 this.#records.clear();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
191 for (const priority of ["polite", "assertive"]) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
192 clearTimeout(this.#announcementTimers[priority]);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
193 this.#announcementTimers[priority] = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
194 this.#announcementQueues[priority].length = 0;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
195 this.#announcementRunning[priority] = false;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
196 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
197 for (const element of this.querySelectorAll(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
198 ":scope > [data-zen-notifications-internal]",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
199 )) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
200 element.remove();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
201 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
202 this.stack = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
203 this.politeRegion = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
204 this.assertiveRegion = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
205 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
206
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
207 createInternalElements() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
208 const label = this.getAttribute("aria-label") || "Notifications";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
209 this.stack = document.createElement("section");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
210 this.stack.dataset.zenNotificationStack = "";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
211 this.stack.dataset.zenNotificationsInternal = "";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
212 this.stack.setAttribute("role", "region");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
213 this.stack.setAttribute("aria-label", label);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
214
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
215 this.politeRegion = document.createElement("span");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
216 this.politeRegion.dataset.zenLive = "polite";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
217 this.politeRegion.dataset.zenNotificationsInternal = "";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
218 this.politeRegion.className = "zen-visually-hidden";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
219 this.politeRegion.setAttribute("aria-live", "polite");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
220 this.politeRegion.setAttribute("aria-atomic", "true");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
221
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
222 this.assertiveRegion = document.createElement("span");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
223 this.assertiveRegion.dataset.zenLive = "assertive";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
224 this.assertiveRegion.dataset.zenNotificationsInternal = "";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
225 this.assertiveRegion.className = "zen-visually-hidden";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
226 this.assertiveRegion.setAttribute("aria-live", "assertive");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
227 this.assertiveRegion.setAttribute("aria-atomic", "true");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
228
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
229 this.append(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
230 this.stack,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
231 this.politeRegion,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
232 this.assertiveRegion,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
233 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
234 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
235
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
236 get size() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
237 return this.#records.size;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
238 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
239
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
240 get visibleCount() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
241 return Math.min(this.#records.size, MAX_VISIBLE);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
242 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
243
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
244 notify(value) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
245 const notification = validateNotification(value);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
246 if (!notification) return false;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
247
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
248 const previous = this.#records.get(notification.id);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
249 if (!previous && this.#records.size >= MAX_RECORDS) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
250 const oldestFinite = [...this.#records.values()].find(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
251 record => !record.persistent,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
252 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
253 if (!oldestFinite) return false;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
254 this.removeRecord(oldestFinite.id, "overflow", false);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
255 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
256
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
257 let record;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
258 if (previous) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
259 this.clearTimer(previous);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
260 Object.assign(previous, notification);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
261 previous.remainingMs = notification.durationMs;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
262 this.#records.delete(notification.id);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
263 record = previous;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
264 } else {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
265 record = makeRecord(notification);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
266 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
267 this.#records.set(notification.id, record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
268 this.render();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
269 return true;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
270 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
271
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
272 dismiss(id, reason = "dismissed") {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
273 if (!isShortString(id, 128) || !this.#records.has(id)) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
274 return false;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
275 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
276 this.removeRecord(id, reason, true);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
277 return true;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
278 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
279
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
280 removeRecord(id, reason, render) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
281 const record = this.#records.get(id);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
282 if (!record) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
283 this.clearTimer(record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
284 this.#records.delete(id);
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
285 const article = record.view?.article;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
286 if (article && render && article.isConnected) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
287 article.dataset.removing = "true";
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
288 this.#exitingElements.add(article);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
289 const exitTimer = setTimeout(() => {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
290 this.#resizeObserver?.unobserve(article);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
291 article.remove();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
292 this.#exitingElements.delete(article);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
293 this.#exitTimers.delete(article);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
294 this.requestLayout();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
295 }, 180);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
296 this.#exitTimers.set(article, exitTimer);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
297 } else if (article) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
298 this.#resizeObserver?.unobserve(article);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
299 article.remove();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
300 }
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
301 record.view = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
302 this.dispatchEvent(new CustomEvent("zen-notification-removed", {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
303 bubbles: true,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
304 composed: false,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
305 detail: Object.freeze({
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
306 version: 1,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
307 id,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
308 reason,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
309 }),
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
310 }));
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
311 if (render) this.render();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
312 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
313
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
314 clearTimer(record) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
315 if (!record.timer) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
316 clearTimeout(record.timer);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
317 record.remainingMs = Math.max(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
318 0,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
319 record.remainingMs - (performance.now() - record.startedAt),
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
320 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
321 record.timer = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
322 record.startedAt = 0;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
323 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
324
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
325 pause(record, reason) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
326 if (record.persistent || record.pauseReasons.has(reason)) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
327 record.pauseReasons.add(reason);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
328 this.clearTimer(record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
329 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
330
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
331 resume(record, reason) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
332 if (record.persistent) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
333 record.pauseReasons.delete(reason);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
334 this.schedule(record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
335 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
336
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
337 schedule(record) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
338 if (record.persistent ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
339 record.timer ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
340 record.pauseReasons.size > 0 ||
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
341 !this.#records.has(record.id)) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
342 if (record.remainingMs <= 0) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
343 this.dismiss(record.id, "timeout");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
344 return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
345 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
346 record.startedAt = performance.now();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
347 record.timer = setTimeout(() => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
348 record.timer = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
349 record.remainingMs = 0;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
350 this.dismiss(record.id, "timeout");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
351 }, record.remainingMs);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
352 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
353
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
354 announce(record) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
355 if (record.announced) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
356 record.announced = true;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
357 if (record.announcement === "none") return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
358 this.#announcementQueues[record.announcement].push({
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
359 id: record.id,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
360 message: record.message,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
361 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
362 this.runAnnouncementQueue(record.announcement);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
363 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
364
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
365 runAnnouncementQueue(priority) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
366 if (this.#announcementRunning[priority]) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
367 const next = this.#announcementQueues[priority].shift();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
368 if (!next) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
369 this.#announcementRunning[priority] = true;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
370 const region = priority === "assertive"
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
371 ? this.assertiveRegion
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
372 : this.politeRegion;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
373 region.textContent = "";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
374 this.#announcementTimers[priority] = setTimeout(() => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
375 if (this.isConnected && this.#records.has(next.id)) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
376 region.textContent = next.message;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
377 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
378 this.#announcementTimers[priority] = setTimeout(() => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
379 this.#announcementTimers[priority] = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
380 this.#announcementRunning[priority] = false;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
381 this.runAnnouncementQueue(priority);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
382 }, 120);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
383 }, 16);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
384 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
385
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
386 createNotificationElement(record) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
387 if (!record.view) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
388 const article = document.createElement("article");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
389 article.dataset.zenNotificationId = record.id;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
390
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
391 const tone = document.createElement("span");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
392 tone.className = "zen-notification-tone";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
393 tone.setAttribute("aria-hidden", "true");
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
394 const toneIcon = document.createElement("zen-icon");
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
395 tone.append(toneIcon);
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
396
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
397 const content = document.createElement("div");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
398 content.className = "zen-notification-content";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
399 const message = document.createElement("p");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
400 message.className = "zen-notification-message";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
401 content.append(message);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
402
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
403 const dismiss = document.createElement("button");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
404 dismiss.type = "button";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
405 dismiss.className = "zen-notification-dismiss";
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
406 const dismissIcon = document.createElement("zen-icon");
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
407 dismissIcon.setAttribute("name", "close");
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
408 dismiss.append(dismissIcon);
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
409 dismiss.addEventListener("click", () => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
410 this.dismiss(record.id, "dismissed");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
411 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
412
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
413 article.addEventListener("pointerenter", () => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
414 this.pause(record, "hover");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
415 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
416 article.addEventListener("pointerleave", () => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
417 this.resume(record, "hover");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
418 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
419 article.addEventListener("focusin", () => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
420 this.pause(record, "focus");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
421 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
422 article.addEventListener("focusout", event => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
423 if (!article.contains(event.relatedTarget)) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
424 this.resume(record, "focus");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
425 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
426 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
427
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
428 article.append(tone, content, dismiss);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
429 record.view = {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
430 action: null,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
431 article,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
432 content,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
433 description: null,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
434 dismiss,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
435 message,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
436 tone,
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
437 toneIcon,
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
438 };
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
439 this.#resizeObserver?.observe(article);
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
440 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
441
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
442 const view = record.view;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
443 view.article.dataset.tone = record.tone;
254
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
444 view.toneIcon.setAttribute("name", {
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
445 info: "info",
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
446 success: "check",
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
447 warning: "alert",
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
448 error: "error",
2b6e732087ff [ui] Add complete native component catalog
MrJuneJune <me@mrjunejune.com>
parents: 253
diff changeset
449 }[record.tone]);
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
450 view.message.textContent = record.message;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
451 view.dismiss.setAttribute(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
452 "aria-label",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
453 this.getAttribute("dismiss-label") || "Dismiss notification",
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
454 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
455
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
456 if (record.description) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
457 if (!view.description) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
458 view.description = document.createElement("p");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
459 view.description.className = "zen-notification-description";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
460 view.content.insertBefore(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
461 view.description,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
462 view.action,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
463 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
464 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
465 view.description.textContent = record.description;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
466 } else if (view.description) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
467 view.description.remove();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
468 view.description = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
469 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
470
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
471 if (record.action) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
472 if (!view.action) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
473 view.action = document.createElement("button");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
474 view.action.type = "button";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
475 view.action.className = "zen-notification-action";
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
476 view.action.addEventListener("click", () => {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
477 this.dispatchEvent(new CustomEvent("zen-notification-action", {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
478 bubbles: true,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
479 composed: false,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
480 detail: Object.freeze({
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
481 version: 1,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
482 id: record.id,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
483 token: record.action.token,
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
484 }),
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
485 }));
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
486 });
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
487 view.content.append(view.action);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
488 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
489 view.action.textContent = record.action.label;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
490 } else if (view.action) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
491 view.action.remove();
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
492 view.action = null;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
493 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
494 return view.article;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
495 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
496
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
497 requestLayout() {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
498 cancelAnimationFrame(this.#layoutFrame);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
499 this.#layoutFrame = requestAnimationFrame(() => {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
500 this.#layoutFrame = null;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
501 this.layoutStack();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
502 });
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
503 }
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
504
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
505 layoutStack() {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
506 if (!this.stack) return;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
507 const visible = [...this.#records.values()]
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
508 .slice(-MAX_VISIBLE)
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
509 .filter(record => record.view?.article.isConnected);
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
510 const gap = resolveCssLength(
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
511 this.stack,
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
512 "--zenbu-notification-gap",
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
513 12,
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
514 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
515 let expandedHeight = 0;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
516 let newestHeight = 0;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
517
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
518 for (let index = visible.length - 1; index >= 0; index--) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
519 const record = visible[index];
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
520 const article = record.view.article;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
521 const depth = visible.length - 1 - index;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
522 const height = article.offsetHeight;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
523 if (depth === 0) newestHeight = height;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
524 article.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
525 "--zenbu-notification-depth",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
526 String(depth),
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
527 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
528 article.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
529 "--zenbu-notification-collapsed-offset",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
530 `${-depth * 10}px`,
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
531 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
532 article.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
533 "--zenbu-notification-collapsed-opacity",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
534 String(Math.max(0.55, 1 - depth * 0.18)),
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
535 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
536 article.dataset.depth = String(depth);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
537 article.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
538 "--zenbu-notification-offset",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
539 `${-expandedHeight}px`,
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
540 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
541 article.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
542 "--zenbu-notification-scale",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
543 String(Math.max(0.88, 1 - depth * 0.055)),
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
544 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
545 article.style.zIndex = String(visible.length - depth);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
546 expandedHeight += height + gap;
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
547 }
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
548
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
549 this.stack.dataset.count = String(visible.length);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
550 this.stack.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
551 "--zenbu-notification-collapsed-height",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
552 `${newestHeight + Math.max(0, visible.length - 1) * 10}px`,
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
553 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
554 this.stack.style.setProperty(
258
60a876c4587a [ui] Add semantic primitive ownership
MrJuneJune <me@mrjunejune.com>
parents: 254
diff changeset
555 "--zenbu-notification-expanded-height",
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
556 `${Math.max(0, expandedHeight - gap)}px`,
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
557 );
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
558 }
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
559
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
560 render() {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
561 if (!this.stack) return;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
562 const records = [...this.#records.values()];
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
563 const visible = records.slice(-MAX_VISIBLE);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
564 const visibleIds = new Set(visible.map(record => record.id));
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
565
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
566 for (const record of records) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
567 if (visibleIds.has(record.id)) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
568 this.resume(record, "queued");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
569 } else {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
570 this.pause(record, "queued");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
571 this.resume(record, "hover");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
572 this.resume(record, "focus");
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
573 if (record.view?.article) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
574 this.#resizeObserver?.unobserve(record.view.article);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
575 record.view.article.remove();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
576 }
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
577 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
578 if (document.hidden) this.pause(record, "hidden");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
579 else this.resume(record, "hidden");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
580 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
581
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
582 const elements = visible.map(record =>
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
583 this.createNotificationElement(record)
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
584 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
585 for (let index = 0; index < elements.length; index++) {
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
586 const current = [...this.stack.children]
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
587 .filter(element => !this.#exitingElements.has(element))[index];
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
588 if (current !== elements[index]) {
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
589 this.stack.insertBefore(
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
590 elements[index],
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
591 current || null,
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
592 );
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
593 }
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
594 this.#resizeObserver?.observe(elements[index]);
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
595 }
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
596 const desired = new Set(elements);
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
597 for (const element of [...this.stack.children]) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
598 if (!desired.has(element) &&
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
599 !this.#exitingElements.has(element)) {
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
600 element.remove();
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
601 }
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
602 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
603
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
604 for (const record of visible) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
605 const element = record.view.article;
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
606 if (element.matches(":hover")) this.pause(record, "hover");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
607 else this.resume(record, "hover");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
608 if (element.contains(document.activeElement)) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
609 this.pause(record, "focus");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
610 } else this.resume(record, "focus");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
611 this.announce(record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
612 this.schedule(record);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
613 }
253
fdf3816959cb [ui] Add Sonner-like notification stack
MrJuneJune <me@mrjunejune.com>
parents: 252
diff changeset
614 this.requestLayout();
252
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
615 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
616 }
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
617
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
618 const existing = customElements.get("zen-notifications");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
619 if (!existing) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
620 customElements.define("zen-notifications", ZenNotifications);
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
621 } else if (existing.version !== ZenNotifications.version) {
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
622 throw new Error("Incompatible zen-notifications component");
7a7581f040e8 [ui] Add scoped notification component
MrJuneJune <me@mrjunejune.com>
parents:
diff changeset
623 }