@rtzll/reflow 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/basic.js +2 -7
- package/dist/compositions.css +3 -3
- package/dist/drawer.css +240 -0
- package/dist/drawer.d.ts +45 -0
- package/dist/drawer.js +228 -0
- package/dist/foundations.css +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/sidebar.css +14 -57
- package/dist/sidebar.js +9 -13
- package/dist/status-icon.d.ts +3 -0
- package/dist/status-icon.js +7 -0
- package/dist/styles.css +2 -0
- package/dist/toaster.css +268 -0
- package/dist/toaster.d.ts +79 -0
- package/dist/toaster.js +418 -0
- package/package.json +1 -1
package/dist/toaster.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "remix/component/jsx-runtime";
|
|
2
|
+
import { on, ref } from 'remix/component';
|
|
3
|
+
import { Button } from './basic.js';
|
|
4
|
+
import { Icon } from './icon.js';
|
|
5
|
+
import { matchesShortcut, parseShortcut } from './shortcut.js';
|
|
6
|
+
import { statusIcon } from './status-icon.js';
|
|
7
|
+
// The module-level store behind toast(). Toasters subscribe; nothing renders
|
|
8
|
+
// on the server, where no toast is ever raised.
|
|
9
|
+
let entries = [];
|
|
10
|
+
let counter = 0;
|
|
11
|
+
const listeners = new Set();
|
|
12
|
+
function emit() {
|
|
13
|
+
for (const listener of listeners)
|
|
14
|
+
listener();
|
|
15
|
+
}
|
|
16
|
+
function upsert(title, status, options = {}) {
|
|
17
|
+
const id = options.id ?? `rf-toast-${++counter}`;
|
|
18
|
+
const index = entries.findIndex((entry) => entry.id === id && !entry.dismissed);
|
|
19
|
+
const previous = index >= 0 ? entries[index] : undefined;
|
|
20
|
+
const entry = {
|
|
21
|
+
...previous,
|
|
22
|
+
...options,
|
|
23
|
+
id,
|
|
24
|
+
title,
|
|
25
|
+
status,
|
|
26
|
+
dismissed: false,
|
|
27
|
+
version: (previous?.version ?? 0) + 1,
|
|
28
|
+
};
|
|
29
|
+
if (previous)
|
|
30
|
+
entries = entries.with(index, entry);
|
|
31
|
+
else
|
|
32
|
+
entries = [entry, ...entries.filter((item) => item.id !== id)];
|
|
33
|
+
emit();
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
36
|
+
function close(id, reason) {
|
|
37
|
+
const closing = entries.filter((entry) => !entry.dismissed && (id === undefined || entry.id === id));
|
|
38
|
+
if (!closing.length)
|
|
39
|
+
return;
|
|
40
|
+
entries = entries.map((entry) => closing.includes(entry) ? { ...entry, dismissed: true } : entry);
|
|
41
|
+
// Without a Toaster to play the exit, a closed toast leaves at once.
|
|
42
|
+
if (!listeners.size)
|
|
43
|
+
entries = entries.filter((entry) => !entry.dismissed);
|
|
44
|
+
emit();
|
|
45
|
+
for (const entry of closing) {
|
|
46
|
+
if (reason === 'auto')
|
|
47
|
+
entry.onAutoClose?.(entry);
|
|
48
|
+
else
|
|
49
|
+
entry.onDismiss?.(entry);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function remove(id) {
|
|
53
|
+
const next = entries.filter((entry) => !(entry.id === id && entry.dismissed));
|
|
54
|
+
if (next.length === entries.length)
|
|
55
|
+
return;
|
|
56
|
+
entries = next;
|
|
57
|
+
emit();
|
|
58
|
+
}
|
|
59
|
+
function resolveMessage(message, value) {
|
|
60
|
+
return typeof message === 'function' ? message(value) : message;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Raises a toast in the app's `<Toaster>` and returns its id. `toast.success`,
|
|
64
|
+
* `toast.info`, `toast.warning`, `toast.error` and `toast.loading` set its
|
|
65
|
+
* status; `toast.promise` follows a promise from loading to its outcome;
|
|
66
|
+
* `toast.dismiss` closes one toast, or all of them.
|
|
67
|
+
*/
|
|
68
|
+
export const toast = Object.assign((title, options) => upsert(title, 'default', options), {
|
|
69
|
+
success: (title, options) => upsert(title, 'success', options),
|
|
70
|
+
info: (title, options) => upsert(title, 'info', options),
|
|
71
|
+
warning: (title, options) => upsert(title, 'warning', options),
|
|
72
|
+
error: (title, options) => upsert(title, 'error', options),
|
|
73
|
+
loading: (title, options) => upsert(title, 'loading', { duration: Infinity, ...options }),
|
|
74
|
+
promise(promise, messages, options = {}) {
|
|
75
|
+
const id = upsert(messages.loading, 'loading', {
|
|
76
|
+
description: messages.description,
|
|
77
|
+
...options,
|
|
78
|
+
duration: Infinity,
|
|
79
|
+
});
|
|
80
|
+
const settle = (status, title) => {
|
|
81
|
+
// A toast closed while it waited stays closed.
|
|
82
|
+
if (!entries.some((entry) => entry.id === id && !entry.dismissed))
|
|
83
|
+
return;
|
|
84
|
+
upsert(title, status, { ...options, id, description: options.description });
|
|
85
|
+
};
|
|
86
|
+
Promise.resolve()
|
|
87
|
+
.then(() => (typeof promise === 'function' ? promise() : promise))
|
|
88
|
+
.then((value) => settle('success', resolveMessage(messages.success, value)), (error) => settle('error', resolveMessage(messages.error, error)));
|
|
89
|
+
return id;
|
|
90
|
+
},
|
|
91
|
+
dismiss: (id) => close(id, 'dismiss'),
|
|
92
|
+
});
|
|
93
|
+
// Below these, a released swipe springs back.
|
|
94
|
+
const swipeDistance = 45;
|
|
95
|
+
const swipeVelocity = 0.11;
|
|
96
|
+
// How long a closed toast plays its exit before it leaves the DOM.
|
|
97
|
+
const exitDuration = 200;
|
|
98
|
+
const interactive = 'button, a, input, select, textarea, [contenteditable]';
|
|
99
|
+
/**
|
|
100
|
+
* Shows the toasts raised with `toast()`. Place one in the app, outside
|
|
101
|
+
* anything that scrolls. The newest toast sits in front with up to two
|
|
102
|
+
* peeking behind it, on QueuedStack's stacking values; pointing at or
|
|
103
|
+
* focusing the stack spreads it out and pauses the timers. A swipe toward
|
|
104
|
+
* the nearest edge, Escape, or the close button dismisses a toast, and the
|
|
105
|
+
* hotkey moves focus to the newest one.
|
|
106
|
+
*/
|
|
107
|
+
export function Toaster(handle) {
|
|
108
|
+
let list = null;
|
|
109
|
+
let hovered = false;
|
|
110
|
+
let focused = false;
|
|
111
|
+
let swipe;
|
|
112
|
+
let returnFocus = null;
|
|
113
|
+
const timers = new Map();
|
|
114
|
+
const leaving = new Map();
|
|
115
|
+
let resizeObserver;
|
|
116
|
+
const position = () => handle.props.position ?? 'bottom-right';
|
|
117
|
+
const expanded = () => Boolean(handle.props.expand) || hovered || focused;
|
|
118
|
+
const paused = () => hovered || focused || Boolean(swipe) || document.visibilityState === 'hidden';
|
|
119
|
+
const sync = () => void handle.update();
|
|
120
|
+
listeners.add(sync);
|
|
121
|
+
// Keeps each toast's close timer running, paused or cleared to match.
|
|
122
|
+
function schedule() {
|
|
123
|
+
const now = performance.now();
|
|
124
|
+
const live = new Set();
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (entry.dismissed)
|
|
127
|
+
continue;
|
|
128
|
+
const duration = entry.duration ?? handle.props.duration ?? 4000;
|
|
129
|
+
if (entry.status === 'loading' || !Number.isFinite(duration))
|
|
130
|
+
continue;
|
|
131
|
+
live.add(entry.id);
|
|
132
|
+
let timer = timers.get(entry.id);
|
|
133
|
+
if (!timer || timer.version !== entry.version) {
|
|
134
|
+
if (timer)
|
|
135
|
+
clearTimeout(timer.timeout);
|
|
136
|
+
timer = { version: entry.version, remaining: Math.max(0, duration), started: now };
|
|
137
|
+
timers.set(entry.id, timer);
|
|
138
|
+
}
|
|
139
|
+
if (paused()) {
|
|
140
|
+
if (timer.timeout !== undefined) {
|
|
141
|
+
clearTimeout(timer.timeout);
|
|
142
|
+
timer.timeout = undefined;
|
|
143
|
+
timer.remaining -= now - timer.started;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (timer.timeout === undefined) {
|
|
147
|
+
timer.started = now;
|
|
148
|
+
timer.timeout = setTimeout(() => {
|
|
149
|
+
timers.delete(entry.id);
|
|
150
|
+
close(entry.id, 'auto');
|
|
151
|
+
}, timer.remaining);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const [id, timer] of timers) {
|
|
155
|
+
if (live.has(id))
|
|
156
|
+
continue;
|
|
157
|
+
clearTimeout(timer.timeout);
|
|
158
|
+
timers.delete(id);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Hands focus on before a focused toast leaves.
|
|
162
|
+
function handOff(item) {
|
|
163
|
+
if (!item.contains(document.activeElement))
|
|
164
|
+
return;
|
|
165
|
+
const next = list?.querySelector(':scope > .rf-toast:not([data-removed], [data-hidden])');
|
|
166
|
+
if (next && next !== item)
|
|
167
|
+
next.focus({ preventScroll: true });
|
|
168
|
+
else if (returnFocus?.isConnected)
|
|
169
|
+
returnFocus.focus({ preventScroll: true });
|
|
170
|
+
else
|
|
171
|
+
item.blur();
|
|
172
|
+
}
|
|
173
|
+
// Writes the stack's measurements: each toast's depth, its own height and
|
|
174
|
+
// its offset once spread out, and the height of the toast in front.
|
|
175
|
+
function layout() {
|
|
176
|
+
if (!list)
|
|
177
|
+
return;
|
|
178
|
+
const items = Array.from(list.querySelectorAll(':scope > .rf-toast:not([data-removed])'));
|
|
179
|
+
const style = getComputedStyle(list);
|
|
180
|
+
const gap = parseFloat(style.getPropertyValue('--rf-stack-gap')) || 5;
|
|
181
|
+
const peek = parseFloat(style.getPropertyValue('--rf-stack-peek')) || 6;
|
|
182
|
+
const visible = Math.max(1, handle.props.visibleToasts ?? 3);
|
|
183
|
+
let offset = 0;
|
|
184
|
+
let spread = 0;
|
|
185
|
+
for (const [depth, item] of items.entries()) {
|
|
186
|
+
const height = item.querySelector('.rf-toast-inner')?.offsetHeight ?? 0;
|
|
187
|
+
item.style.setProperty('--rf-toast-depth', String(depth));
|
|
188
|
+
item.style.setProperty('--rf-toast-height', `${height}px`);
|
|
189
|
+
item.style.setProperty('--rf-toast-offset', `${offset}px`);
|
|
190
|
+
item.toggleAttribute('data-front', depth === 0);
|
|
191
|
+
item.toggleAttribute('data-hidden', depth >= visible);
|
|
192
|
+
if (depth < visible)
|
|
193
|
+
spread = offset + height;
|
|
194
|
+
offset += height + gap;
|
|
195
|
+
}
|
|
196
|
+
const front = items[0]?.querySelector('.rf-toast-inner')?.offsetHeight ?? 0;
|
|
197
|
+
const shown = Math.min(items.length, visible);
|
|
198
|
+
const edge = handle.props.offset;
|
|
199
|
+
if (edge === undefined)
|
|
200
|
+
list.style.removeProperty('--rf-toaster-offset');
|
|
201
|
+
else
|
|
202
|
+
list.style.setProperty('--rf-toaster-offset', `${edge}px`);
|
|
203
|
+
list.style.setProperty('--rf-toast-front-height', `${front}px`);
|
|
204
|
+
list.style.setProperty('--rf-toaster-piled-height', `${front + Math.max(0, shown - 1) * peek}px`);
|
|
205
|
+
list.style.setProperty('--rf-toaster-spread-height', `${spread}px`);
|
|
206
|
+
}
|
|
207
|
+
function afterRender() {
|
|
208
|
+
if (!list)
|
|
209
|
+
return;
|
|
210
|
+
for (const item of list.querySelectorAll(':scope > .rf-toast')) {
|
|
211
|
+
const inner = item.querySelector('.rf-toast-inner');
|
|
212
|
+
if (inner)
|
|
213
|
+
resizeObserver?.observe(inner);
|
|
214
|
+
const id = item.dataset.toastId;
|
|
215
|
+
if (item.hasAttribute('data-removed') && !leaving.has(id)) {
|
|
216
|
+
handOff(item);
|
|
217
|
+
leaving.set(id, setTimeout(() => {
|
|
218
|
+
leaving.delete(id);
|
|
219
|
+
remove(id);
|
|
220
|
+
}, exitDuration));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
layout();
|
|
224
|
+
schedule();
|
|
225
|
+
}
|
|
226
|
+
function endSwipe(event, item) {
|
|
227
|
+
if (!swipe || swipe.pointer !== event.pointerId)
|
|
228
|
+
return;
|
|
229
|
+
const current = swipe;
|
|
230
|
+
swipe = undefined;
|
|
231
|
+
item.removeAttribute('data-swiping');
|
|
232
|
+
const [y, x] = position().split('-');
|
|
233
|
+
const signY = y === 'bottom' ? 1 : -1;
|
|
234
|
+
const signX = x === 'right' ? 1 : x === 'left' ? -1 : 0;
|
|
235
|
+
const travel = current.axis === 'x' ? current.dx * signX : current.axis === 'y' ? current.dy * signY : 0;
|
|
236
|
+
const elapsed = Math.max(1, performance.now() - current.time);
|
|
237
|
+
const entry = entries.find((item) => item.id === current.id);
|
|
238
|
+
if (entry && travel > 0 && (travel >= swipeDistance || travel / elapsed > swipeVelocity)) {
|
|
239
|
+
// Carry on in the swipe's direction as it leaves.
|
|
240
|
+
const width = item.offsetWidth;
|
|
241
|
+
const height = item.offsetHeight;
|
|
242
|
+
item.dataset.swipeOut = current.axis;
|
|
243
|
+
if (current.axis === 'x')
|
|
244
|
+
item.style.setProperty('--rf-swipe-x', `${signX * width}px`);
|
|
245
|
+
else
|
|
246
|
+
item.style.setProperty('--rf-swipe-y', `${signY * height}px`);
|
|
247
|
+
close(entry.id, 'dismiss');
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
item.style.setProperty('--rf-swipe-x', '0px');
|
|
251
|
+
item.style.setProperty('--rf-swipe-y', '0px');
|
|
252
|
+
void handle.update();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const listMix = ref((element, signal) => {
|
|
256
|
+
if (!(element instanceof HTMLOListElement))
|
|
257
|
+
return;
|
|
258
|
+
list = element;
|
|
259
|
+
const options = { signal };
|
|
260
|
+
resizeObserver = new ResizeObserver(() => layout());
|
|
261
|
+
element.addEventListener('pointerenter', (event) => {
|
|
262
|
+
if (event.pointerType === 'touch')
|
|
263
|
+
return;
|
|
264
|
+
hovered = true;
|
|
265
|
+
void handle.update();
|
|
266
|
+
}, options);
|
|
267
|
+
element.addEventListener('pointerleave', () => {
|
|
268
|
+
if (!hovered)
|
|
269
|
+
return;
|
|
270
|
+
hovered = false;
|
|
271
|
+
void handle.update();
|
|
272
|
+
}, options);
|
|
273
|
+
element.addEventListener('focusin', (event) => {
|
|
274
|
+
const from = event.relatedTarget;
|
|
275
|
+
if (from instanceof HTMLElement && !element.contains(from))
|
|
276
|
+
returnFocus = from;
|
|
277
|
+
if (focused)
|
|
278
|
+
return;
|
|
279
|
+
focused = true;
|
|
280
|
+
void handle.update();
|
|
281
|
+
}, options);
|
|
282
|
+
element.addEventListener('focusout', (event) => {
|
|
283
|
+
const next = event.relatedTarget;
|
|
284
|
+
if (next instanceof Node && element.contains(next))
|
|
285
|
+
return;
|
|
286
|
+
// A toast that leaves with focus inside hands it on in the same frame.
|
|
287
|
+
queueMicrotask(() => {
|
|
288
|
+
if (element.contains(document.activeElement))
|
|
289
|
+
return;
|
|
290
|
+
focused = false;
|
|
291
|
+
void handle.update();
|
|
292
|
+
});
|
|
293
|
+
}, options);
|
|
294
|
+
element.ownerDocument.addEventListener('keydown', (event) => {
|
|
295
|
+
if (!matchesShortcut(event, parseShortcut(handle.props.hotkey ?? 'alt+t')))
|
|
296
|
+
return;
|
|
297
|
+
const front = element.querySelector(':scope > .rf-toast:not([data-removed])');
|
|
298
|
+
if (!front)
|
|
299
|
+
return;
|
|
300
|
+
event.preventDefault();
|
|
301
|
+
const active = document.activeElement;
|
|
302
|
+
if (active instanceof HTMLElement && !element.contains(active))
|
|
303
|
+
returnFocus = active;
|
|
304
|
+
front.focus();
|
|
305
|
+
}, options);
|
|
306
|
+
element.ownerDocument.addEventListener('visibilitychange', () => schedule(), options);
|
|
307
|
+
signal.addEventListener('abort', () => {
|
|
308
|
+
resizeObserver?.disconnect();
|
|
309
|
+
if (list === element)
|
|
310
|
+
list = null;
|
|
311
|
+
}, { once: true });
|
|
312
|
+
});
|
|
313
|
+
handle.signal.addEventListener('abort', () => {
|
|
314
|
+
listeners.delete(sync);
|
|
315
|
+
// With no Toaster left to play their exit, closed toasts leave now.
|
|
316
|
+
if (!listeners.size)
|
|
317
|
+
entries = entries.filter((entry) => !entry.dismissed);
|
|
318
|
+
for (const timer of timers.values())
|
|
319
|
+
clearTimeout(timer.timeout);
|
|
320
|
+
for (const timeout of leaving.values())
|
|
321
|
+
clearTimeout(timeout);
|
|
322
|
+
}, { once: true });
|
|
323
|
+
function renderToast(entry) {
|
|
324
|
+
const dismissible = entry.dismissible ?? true;
|
|
325
|
+
const icon = entry.icon ??
|
|
326
|
+
(entry.status === 'loading' ? (_jsx("svg", { class: "rf-toast-spinner", viewBox: "0 0 16 16", fill: "none", children: _jsx("g", { children: _jsx("circle", { cx: "8", cy: "8", r: "6.25", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", pathLength: "100", transform: "rotate(-90 8 8)" }) }) })) : entry.status === 'default' ? undefined : (statusIcon(entry.status)));
|
|
327
|
+
return (_jsx("li", { class: "rf-toast", tabIndex: 0, "data-toast-id": entry.id, "data-status": entry.status, "data-removed": entry.dismissed || undefined, "data-dismissible": dismissible ? undefined : 'false', "aria-atomic": "true", mix: [
|
|
328
|
+
on('keydown', (event) => {
|
|
329
|
+
if (event.key !== 'Escape' || !dismissible || entry.dismissed)
|
|
330
|
+
return;
|
|
331
|
+
event.preventDefault();
|
|
332
|
+
event.stopPropagation();
|
|
333
|
+
close(entry.id, 'dismiss');
|
|
334
|
+
}),
|
|
335
|
+
on('pointerdown', (event) => {
|
|
336
|
+
if (!dismissible || entry.dismissed || event.button !== 0)
|
|
337
|
+
return;
|
|
338
|
+
if (event.target instanceof Element && event.target.closest(interactive))
|
|
339
|
+
return;
|
|
340
|
+
swipe = {
|
|
341
|
+
id: entry.id,
|
|
342
|
+
pointer: event.pointerId,
|
|
343
|
+
x: event.clientX,
|
|
344
|
+
y: event.clientY,
|
|
345
|
+
time: performance.now(),
|
|
346
|
+
dx: 0,
|
|
347
|
+
dy: 0,
|
|
348
|
+
};
|
|
349
|
+
try {
|
|
350
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Synthetic pointers have nothing to capture.
|
|
354
|
+
}
|
|
355
|
+
void handle.update();
|
|
356
|
+
}),
|
|
357
|
+
on('pointermove', (event) => {
|
|
358
|
+
if (!swipe || swipe.pointer !== event.pointerId || swipe.id !== entry.id)
|
|
359
|
+
return;
|
|
360
|
+
const item = event.currentTarget;
|
|
361
|
+
const dx = event.clientX - swipe.x;
|
|
362
|
+
const dy = event.clientY - swipe.y;
|
|
363
|
+
if (!swipe.axis) {
|
|
364
|
+
if (Math.max(Math.abs(dx), Math.abs(dy)) < 2)
|
|
365
|
+
return;
|
|
366
|
+
swipe.axis = Math.abs(dx) > Math.abs(dy) ? 'x' : 'y';
|
|
367
|
+
item.setAttribute('data-swiping', '');
|
|
368
|
+
}
|
|
369
|
+
const [y, x] = position().split('-');
|
|
370
|
+
const toward = swipe.axis === 'x'
|
|
371
|
+
? x === 'right'
|
|
372
|
+
? 1
|
|
373
|
+
: x === 'left'
|
|
374
|
+
? -1
|
|
375
|
+
: 0
|
|
376
|
+
: y === 'bottom'
|
|
377
|
+
? 1
|
|
378
|
+
: -1;
|
|
379
|
+
const delta = swipe.axis === 'x' ? dx : dy;
|
|
380
|
+
// Toward the edge it follows the pointer; away from it, it resists.
|
|
381
|
+
const moved = Math.sign(delta) === toward ? delta : delta / (1 + Math.abs(delta) / 24);
|
|
382
|
+
swipe.dx = swipe.axis === 'x' ? moved : 0;
|
|
383
|
+
swipe.dy = swipe.axis === 'y' ? moved : 0;
|
|
384
|
+
item.style.setProperty('--rf-swipe-x', `${Math.round(swipe.dx)}px`);
|
|
385
|
+
item.style.setProperty('--rf-swipe-y', `${Math.round(swipe.dy)}px`);
|
|
386
|
+
}),
|
|
387
|
+
on('pointerup', (event) => endSwipe(event, event.currentTarget)),
|
|
388
|
+
on('pointercancel', (event) => endSwipe(event, event.currentTarget)),
|
|
389
|
+
], children: _jsxs("div", { class: "rf-toast-inner", children: [icon && (_jsx("span", { class: "rf-toast-icon", "aria-hidden": "true", children: icon })), _jsxs("div", { class: "rf-toast-content", children: [_jsx("div", { class: "rf-toast-title", children: entry.title }), entry.description && _jsx("div", { class: "rf-toast-description", children: entry.description })] }), entry.action && (_jsx(Button, { variant: "secondary", size: "compact", class: "rf-toast-action", mix: on('click', (event) => {
|
|
390
|
+
entry.action?.onClick(event);
|
|
391
|
+
if (!event.defaultPrevented)
|
|
392
|
+
close(entry.id, 'dismiss');
|
|
393
|
+
}), children: entry.action.label })), handle.props.closeButton && dismissible && (_jsx("button", { type: "button", class: "rf-toast-close", "aria-label": "Close", mix: on('click', () => close(entry.id, 'dismiss')), children: _jsx(Icon, { name: "x" }) }))] }) }, entry.id));
|
|
394
|
+
}
|
|
395
|
+
return () => {
|
|
396
|
+
handle.queueTask(afterRender);
|
|
397
|
+
const { label = 'Notifications', size, hotkey = 'alt+t' } = handle.props;
|
|
398
|
+
return (_jsx("section", { class: handle.props.class ? `rf-toaster-region ${handle.props.class}` : 'rf-toaster-region', "aria-label": label, "aria-keyshortcuts": ariaShortcut(hotkey), "aria-live": "polite", "aria-relevant": "additions text", "aria-atomic": "false", tabIndex: -1, "data-density": size, children: _jsx("ol", { class: "rf-toaster", "data-position": position(), "data-expanded": expanded() || undefined, mix: listMix, children: entries.map(renderToast) }) }));
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
// alt+t reads as Alt+T, the form aria-keyshortcuts expects.
|
|
402
|
+
function ariaShortcut(hotkey) {
|
|
403
|
+
return hotkey
|
|
404
|
+
.split('+')
|
|
405
|
+
.map((part) => {
|
|
406
|
+
const lower = part.trim().toLowerCase();
|
|
407
|
+
if (lower === 'mod' || lower === 'ctrl' || lower === 'control')
|
|
408
|
+
return 'Control';
|
|
409
|
+
if (lower === 'cmd' || lower === 'meta')
|
|
410
|
+
return 'Meta';
|
|
411
|
+
if (lower === 'option' || lower === 'opt')
|
|
412
|
+
return 'Alt';
|
|
413
|
+
return lower.length === 1
|
|
414
|
+
? lower.toUpperCase()
|
|
415
|
+
: lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
416
|
+
})
|
|
417
|
+
.join('+');
|
|
418
|
+
}
|