annotate design_system/src/components/notifications.js @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -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 }