@ablogcms/notify 3.2.28-beta.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/README.md +41 -0
- package/dist/acms.d.ts +6 -0
- package/dist/acms.mjs +1 -0
- package/dist/acms.mjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +431 -0
- package/dist/index.mjs.map +1 -0
- package/dist/notify-container.d.ts +3 -0
- package/dist/notify.d.ts +3 -0
- package/dist/presenters/toast-presenter.d.ts +11 -0
- package/dist/presenters/use-toast-stack.d.ts +10 -0
- package/dist/store/hook.d.ts +7 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/store.d.ts +17 -0
- package/dist/types/index.d.ts +76 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @ablogcms/notify
|
|
2
|
+
|
|
3
|
+
管理画面の通知(notify)ドメイン層。キュー・重複抑制・タイマー pause/resume を持つ store と、
|
|
4
|
+
`notify()` 公開 API、既定プレゼンター `ToastPresenter`(見た目は `@ablogcms/components/toast`)を提供する。
|
|
5
|
+
通知(notify)と表示コンポーネント(プレゼンター)は一対多の関係で分離されており、
|
|
6
|
+
`NotificationContainer` の `presenter` prop で表示形式を差し替えられる。
|
|
7
|
+
|
|
8
|
+
## 使い方
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { notify } from '@ablogcms/notify';
|
|
12
|
+
|
|
13
|
+
notify.info('保存しました');
|
|
14
|
+
notify.success('公開しました');
|
|
15
|
+
notify.danger('エラーが発生しました'); // 既定で自動消滅しない(duration: 0)
|
|
16
|
+
notify.warning('確認してください');
|
|
17
|
+
|
|
18
|
+
notify.success('削除しました', {
|
|
19
|
+
description: 'ゴミ箱に移動しました。',
|
|
20
|
+
action: { label: '元に戻す', onClick: ({ dismiss }) => { restore(); dismiss(); } },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
notify.promise(uploadFile(), {
|
|
24
|
+
loading: 'アップロード中…',
|
|
25
|
+
success: 'アップロードが完了しました',
|
|
26
|
+
error: (err) => `失敗しました: ${err.message}`,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
notify.update(id, { type: 'success', title: '完了しました' });
|
|
30
|
+
notify.dismiss(id); // id 省略で全消し
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `notify(content, options?)` / `.success` / `.info` / `.warning` / `.danger`: 通知を追加する(`options.id`
|
|
34
|
+
を指定すると同 id の既存通知を更新する重複抑制になる)。
|
|
35
|
+
- `notify.promise(promise, { loading, success, error })`: Promise の状態遷移に合わせて通知を更新する。
|
|
36
|
+
- `notify.update(id, options)` / `notify.dismiss(id?)`: 表示中の通知を更新・消去する。
|
|
37
|
+
- `options.autoHide` / `options.onHide` は `duration` / `onClose` の `@deprecated` エイリアスとして
|
|
38
|
+
引き続き動作する。
|
|
39
|
+
- `NotificationContainer`: 実際に通知を DOM へレンダリングするコンポーネント。ページに 1 度だけ
|
|
40
|
+
マウントすれば、以降の `notify.*` 呼び出しに反応して表示される。`presenter`(既定 `ToastPresenter`)・
|
|
41
|
+
`placement`・`limit`・`duration`・`overlap`・`label` を指定できる。
|
package/dist/acms.d.ts
ADDED
package/dist/acms.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=acms.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as notify } from './notify.js';
|
|
2
|
+
export { default as NotificationContainer } from './notify-container.js';
|
|
3
|
+
export { default as ToastPresenter } from './presenters/toast-presenter.js';
|
|
4
|
+
export type { ToastPresenterProps } from './presenters/toast-presenter.js';
|
|
5
|
+
export type * from './types/index.js';
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// src/store/store.ts
|
|
2
|
+
var DEFAULT_CONFIG = { limit: 3, duration: 5e3 };
|
|
3
|
+
var config = { ...DEFAULT_CONFIG };
|
|
4
|
+
var visible = [];
|
|
5
|
+
var queue = [];
|
|
6
|
+
var paused = false;
|
|
7
|
+
var seq = 0;
|
|
8
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
9
|
+
function emitChange() {
|
|
10
|
+
for (const listener of listeners) {
|
|
11
|
+
listener();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function genId() {
|
|
15
|
+
seq += 1;
|
|
16
|
+
return `notification-${seq}`;
|
|
17
|
+
}
|
|
18
|
+
function resolveDuration(type, duration) {
|
|
19
|
+
if (duration !== void 0) return duration;
|
|
20
|
+
return type === "danger" ? 0 : config.duration;
|
|
21
|
+
}
|
|
22
|
+
function clearTimer(notification) {
|
|
23
|
+
if (notification.timer) {
|
|
24
|
+
clearTimeout(notification.timer);
|
|
25
|
+
notification.timer = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function startTimer(notification) {
|
|
29
|
+
if (notification.duration === 0) return;
|
|
30
|
+
notification.startedAt = Date.now();
|
|
31
|
+
notification.timer = setTimeout(() => dismiss(notification.id), Math.max(notification.remaining, 0));
|
|
32
|
+
}
|
|
33
|
+
function buildInternal(id, options) {
|
|
34
|
+
const type = options.type ?? "info";
|
|
35
|
+
const duration = resolveDuration(type, options.duration);
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
type,
|
|
39
|
+
title: options.title,
|
|
40
|
+
description: options.description,
|
|
41
|
+
closable: options.closable ?? true,
|
|
42
|
+
action: options.action,
|
|
43
|
+
onClose: options.onClose,
|
|
44
|
+
duration,
|
|
45
|
+
remaining: duration,
|
|
46
|
+
startedAt: 0,
|
|
47
|
+
timer: null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function applyOptions(target, options) {
|
|
51
|
+
target.type = options.type ?? target.type;
|
|
52
|
+
target.title = options.title ?? target.title;
|
|
53
|
+
if ("description" in options) target.description = options.description;
|
|
54
|
+
if ("closable" in options) target.closable = options.closable ?? true;
|
|
55
|
+
if ("action" in options) target.action = options.action;
|
|
56
|
+
if ("onClose" in options) target.onClose = options.onClose;
|
|
57
|
+
target.duration = resolveDuration(target.type, options.duration);
|
|
58
|
+
target.remaining = target.duration;
|
|
59
|
+
}
|
|
60
|
+
function mergeNotification(existing, options) {
|
|
61
|
+
const next = { ...existing };
|
|
62
|
+
applyOptions(next, options);
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
function configure(next) {
|
|
66
|
+
config = { ...config, ...next };
|
|
67
|
+
}
|
|
68
|
+
function subscribe(listener) {
|
|
69
|
+
listeners.add(listener);
|
|
70
|
+
return () => {
|
|
71
|
+
listeners.delete(listener);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function getSnapshot() {
|
|
75
|
+
return visible;
|
|
76
|
+
}
|
|
77
|
+
function drainQueue() {
|
|
78
|
+
while (queue.length > 0 && visible.length < config.limit) {
|
|
79
|
+
const [next, ...rest] = queue;
|
|
80
|
+
queue = rest;
|
|
81
|
+
visible = [...visible, next];
|
|
82
|
+
if (!paused) startTimer(next);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function enqueue(options) {
|
|
86
|
+
const id = options.id ?? genId();
|
|
87
|
+
const existing = visible.find((n) => n.id === id);
|
|
88
|
+
if (existing) {
|
|
89
|
+
clearTimer(existing);
|
|
90
|
+
applyOptions(existing, options);
|
|
91
|
+
visible = [...visible];
|
|
92
|
+
if (!paused) startTimer(existing);
|
|
93
|
+
emitChange();
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
const queuedIndex = queue.findIndex((n) => n.id === id);
|
|
97
|
+
if (queuedIndex !== -1) {
|
|
98
|
+
queue = queue.map((n, i) => i === queuedIndex ? mergeNotification(n, options) : n);
|
|
99
|
+
return id;
|
|
100
|
+
}
|
|
101
|
+
const notification = buildInternal(id, options);
|
|
102
|
+
if (visible.length >= config.limit) {
|
|
103
|
+
queue = [...queue, notification];
|
|
104
|
+
return id;
|
|
105
|
+
}
|
|
106
|
+
visible = [...visible, notification];
|
|
107
|
+
if (!paused) startTimer(notification);
|
|
108
|
+
emitChange();
|
|
109
|
+
return id;
|
|
110
|
+
}
|
|
111
|
+
function update(id, options) {
|
|
112
|
+
const existing = visible.find((n) => n.id === id);
|
|
113
|
+
if (existing) {
|
|
114
|
+
clearTimer(existing);
|
|
115
|
+
applyOptions(existing, options);
|
|
116
|
+
visible = [...visible];
|
|
117
|
+
if (!paused) startTimer(existing);
|
|
118
|
+
emitChange();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const queuedIndex = queue.findIndex((n) => n.id === id);
|
|
122
|
+
if (queuedIndex !== -1) {
|
|
123
|
+
queue = queue.map((n, i) => i === queuedIndex ? mergeNotification(n, options) : n);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function dismiss(id) {
|
|
127
|
+
if (id === void 0) {
|
|
128
|
+
const closing = [...visible, ...queue];
|
|
129
|
+
closing.forEach(clearTimer);
|
|
130
|
+
visible = [];
|
|
131
|
+
queue = [];
|
|
132
|
+
emitChange();
|
|
133
|
+
closing.forEach((n) => n.onClose?.());
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const target = visible.find((n) => n.id === id);
|
|
137
|
+
if (target) {
|
|
138
|
+
clearTimer(target);
|
|
139
|
+
visible = visible.filter((n) => n.id !== id);
|
|
140
|
+
drainQueue();
|
|
141
|
+
emitChange();
|
|
142
|
+
target.onClose?.();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const queuedTarget = queue.find((n) => n.id === id);
|
|
146
|
+
if (queuedTarget) {
|
|
147
|
+
queue = queue.filter((n) => n.id !== id);
|
|
148
|
+
queuedTarget.onClose?.();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function pauseAll() {
|
|
152
|
+
if (paused) return;
|
|
153
|
+
paused = true;
|
|
154
|
+
visible.forEach((notification) => {
|
|
155
|
+
if (!notification.timer) return;
|
|
156
|
+
clearTimeout(notification.timer);
|
|
157
|
+
notification.timer = null;
|
|
158
|
+
notification.remaining = Math.max(notification.remaining - (Date.now() - notification.startedAt), 0);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function resumeAll() {
|
|
162
|
+
if (!paused) return;
|
|
163
|
+
paused = false;
|
|
164
|
+
visible.forEach((notification) => {
|
|
165
|
+
if (notification.duration === 0 || notification.timer) return;
|
|
166
|
+
startTimer(notification);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/notify.ts
|
|
171
|
+
function normalize(options) {
|
|
172
|
+
const { autoHide, onHide, duration, onClose, ...rest } = options;
|
|
173
|
+
return {
|
|
174
|
+
...rest,
|
|
175
|
+
duration: duration ?? autoHide,
|
|
176
|
+
onClose: onClose ?? onHide
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function show(content, options = {}, type) {
|
|
180
|
+
const normalized = normalize(options);
|
|
181
|
+
return enqueue({ ...normalized, type: type ?? normalized.type ?? "info", title: content });
|
|
182
|
+
}
|
|
183
|
+
function notify(content, options) {
|
|
184
|
+
return show(content, options);
|
|
185
|
+
}
|
|
186
|
+
notify.success = (content, options) => show(content, options, "success");
|
|
187
|
+
notify.info = (content, options) => show(content, options, "info");
|
|
188
|
+
notify.warning = (content, options) => show(content, options, "warning");
|
|
189
|
+
notify.danger = (content, options) => show(content, options, "danger");
|
|
190
|
+
notify.promise = function promise(promise, messages) {
|
|
191
|
+
const id = show(messages.loading, { duration: 0, closable: false }, "info");
|
|
192
|
+
promise.then(
|
|
193
|
+
(data) => {
|
|
194
|
+
const title = typeof messages.success === "function" ? messages.success(data) : messages.success;
|
|
195
|
+
update(id, { type: "success", title });
|
|
196
|
+
},
|
|
197
|
+
(error) => {
|
|
198
|
+
const title = typeof messages.error === "function" ? messages.error(error) : messages.error;
|
|
199
|
+
update(id, { type: "danger", title });
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
return promise;
|
|
203
|
+
};
|
|
204
|
+
notify.update = (id, options) => update(id, normalize(options));
|
|
205
|
+
notify.dismiss = (id) => dismiss(id);
|
|
206
|
+
var notify_default = notify;
|
|
207
|
+
|
|
208
|
+
// src/notify-container.tsx
|
|
209
|
+
import { useEffect as useEffect3 } from "react";
|
|
210
|
+
|
|
211
|
+
// src/presenters/toast-presenter.tsx
|
|
212
|
+
import { useCallback, useEffect as useEffect2, useLayoutEffect, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
213
|
+
import Toast from "@ablogcms/components/toast";
|
|
214
|
+
|
|
215
|
+
// src/presenters/use-toast-stack.ts
|
|
216
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
217
|
+
var LEAVE_FALLBACK_MS = 500;
|
|
218
|
+
function useToastStack(notifications) {
|
|
219
|
+
const [leaving, setLeaving] = useState([]);
|
|
220
|
+
const prevRef = useRef([]);
|
|
221
|
+
const timersRef = useRef(/* @__PURE__ */ new Map());
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
const currentIds = new Set(notifications.map((n) => n.id));
|
|
224
|
+
const newlyRemoved = prevRef.current.filter((n) => !currentIds.has(n.id));
|
|
225
|
+
prevRef.current = notifications;
|
|
226
|
+
if (newlyRemoved.length === 0) {
|
|
227
|
+
setLeaving((current) => current.filter((n) => !currentIds.has(n.id)));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
setLeaving((current) => [...current.filter((n) => !currentIds.has(n.id)), ...newlyRemoved]);
|
|
231
|
+
newlyRemoved.forEach((notification) => {
|
|
232
|
+
const timer = setTimeout(() => {
|
|
233
|
+
setLeaving((current) => current.filter((n) => n.id !== notification.id));
|
|
234
|
+
timersRef.current.delete(notification.id);
|
|
235
|
+
}, LEAVE_FALLBACK_MS);
|
|
236
|
+
timersRef.current.set(notification.id, timer);
|
|
237
|
+
});
|
|
238
|
+
}, [notifications]);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
const timers = timersRef.current;
|
|
241
|
+
return () => {
|
|
242
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
243
|
+
timers.clear();
|
|
244
|
+
};
|
|
245
|
+
}, []);
|
|
246
|
+
return useMemo(() => {
|
|
247
|
+
const activeIds = new Set(notifications.map((n) => n.id));
|
|
248
|
+
return [
|
|
249
|
+
...notifications.map((n) => ({ ...n, isLeaving: false })),
|
|
250
|
+
...leaving.filter((n) => !activeIds.has(n.id)).map((n) => ({ ...n, isLeaving: true }))
|
|
251
|
+
];
|
|
252
|
+
}, [notifications, leaving]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/presenters/toast-presenter.tsx
|
|
256
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
257
|
+
var GAP = 8;
|
|
258
|
+
var PEEK = 12;
|
|
259
|
+
function ToastPresenter({
|
|
260
|
+
notifications,
|
|
261
|
+
dismiss: dismiss2,
|
|
262
|
+
pause,
|
|
263
|
+
resume,
|
|
264
|
+
placement = "bottom-right",
|
|
265
|
+
overlap = true,
|
|
266
|
+
label
|
|
267
|
+
}) {
|
|
268
|
+
const regionRef = useRef2(null);
|
|
269
|
+
const itemRefs = useRef2(/* @__PURE__ */ new Map());
|
|
270
|
+
const [expanded, setExpanded] = useState2(false);
|
|
271
|
+
const stack = useToastStack(notifications);
|
|
272
|
+
const activeStacked = useMemo2(() => stack.filter((n) => !n.isLeaving).reverse(), [stack]);
|
|
273
|
+
const leavingStacked = useMemo2(() => stack.filter((n) => n.isLeaving), [stack]);
|
|
274
|
+
useLayoutEffect(() => {
|
|
275
|
+
const region = regionRef.current;
|
|
276
|
+
if (!region || !overlap) return;
|
|
277
|
+
let offset = 0;
|
|
278
|
+
let frontHeight = 0;
|
|
279
|
+
activeStacked.forEach((item, index) => {
|
|
280
|
+
const el = itemRefs.current.get(item.id);
|
|
281
|
+
if (!el) return;
|
|
282
|
+
el.style.setProperty("--acms-toast-index", String(index));
|
|
283
|
+
el.style.setProperty("--acms-toast-offset", `${offset}px`);
|
|
284
|
+
el.dataset.front = index === 0 ? "true" : "false";
|
|
285
|
+
const height = el.offsetHeight;
|
|
286
|
+
if (index === 0) frontHeight = height;
|
|
287
|
+
offset += height + GAP;
|
|
288
|
+
});
|
|
289
|
+
region.style.setProperty("--acms-toast-front-height", `${frontHeight}px`);
|
|
290
|
+
const totalHeight = expanded ? Math.max(offset - GAP, 0) : frontHeight + PEEK * Math.max(activeStacked.length - 1, 0);
|
|
291
|
+
region.style.setProperty("--acms-toast-region-height", `${totalHeight}px`);
|
|
292
|
+
}, [activeStacked, expanded, overlap]);
|
|
293
|
+
const expand = useCallback(() => {
|
|
294
|
+
pause();
|
|
295
|
+
if (overlap) setExpanded(true);
|
|
296
|
+
}, [pause, overlap]);
|
|
297
|
+
const collapse = useCallback(() => {
|
|
298
|
+
resume();
|
|
299
|
+
if (overlap) setExpanded(false);
|
|
300
|
+
}, [resume, overlap]);
|
|
301
|
+
const handleBlur = useCallback(
|
|
302
|
+
(event) => {
|
|
303
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
304
|
+
collapse();
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
[collapse]
|
|
308
|
+
);
|
|
309
|
+
const handleKeyDown = useCallback(
|
|
310
|
+
(event) => {
|
|
311
|
+
if (event.key === "Escape" && activeStacked.length > 0) {
|
|
312
|
+
dismiss2(activeStacked[0].id);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
[dismiss2, activeStacked]
|
|
316
|
+
);
|
|
317
|
+
useEffect2(() => {
|
|
318
|
+
function handleGlobalKeyDown(event) {
|
|
319
|
+
if (event.key === "F6" && notifications.length > 0) {
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
regionRef.current?.focus();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
document.addEventListener("keydown", handleGlobalKeyDown);
|
|
325
|
+
return () => document.removeEventListener("keydown", handleGlobalKeyDown);
|
|
326
|
+
}, [notifications.length]);
|
|
327
|
+
return (
|
|
328
|
+
// section は aria-label 付きで暗黙的に role=region を持つ(明示すると redundant-roles で警告される)。
|
|
329
|
+
// ホバー/フォーカスでの pause+展開、Esc での最前面クローズは Sonner / Base UI と同じ「常設ライブ
|
|
330
|
+
// リージョン」パターンの要件で、button/link 化できないため意図的に非対話要素へ付与している。
|
|
331
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
332
|
+
/* @__PURE__ */ jsxs(
|
|
333
|
+
"section",
|
|
334
|
+
{
|
|
335
|
+
ref: regionRef,
|
|
336
|
+
"aria-label": label ?? ACMS.i18n("notify.region"),
|
|
337
|
+
tabIndex: -1,
|
|
338
|
+
className: "acms-admin-toast-region",
|
|
339
|
+
"data-placement": placement,
|
|
340
|
+
"data-expanded": overlap ? expanded : true,
|
|
341
|
+
onMouseEnter: expand,
|
|
342
|
+
onMouseLeave: collapse,
|
|
343
|
+
onFocus: expand,
|
|
344
|
+
onBlur: handleBlur,
|
|
345
|
+
onKeyDown: handleKeyDown,
|
|
346
|
+
children: [
|
|
347
|
+
activeStacked.map((item, index) => /* @__PURE__ */ jsx(
|
|
348
|
+
Toast,
|
|
349
|
+
{
|
|
350
|
+
ref: (el) => {
|
|
351
|
+
if (el) itemRefs.current.set(item.id, el);
|
|
352
|
+
else itemRefs.current.delete(item.id);
|
|
353
|
+
},
|
|
354
|
+
type: item.type,
|
|
355
|
+
title: item.title,
|
|
356
|
+
description: item.description,
|
|
357
|
+
closable: item.closable,
|
|
358
|
+
"data-front": index === 0,
|
|
359
|
+
inert: overlap && !expanded && index !== 0,
|
|
360
|
+
onClose: () => dismiss2(item.id),
|
|
361
|
+
action: item.action ? {
|
|
362
|
+
label: item.action.label,
|
|
363
|
+
altText: item.action.altText,
|
|
364
|
+
onClick: () => item.action?.onClick({ dismiss: () => dismiss2(item.id) })
|
|
365
|
+
} : void 0
|
|
366
|
+
},
|
|
367
|
+
item.id
|
|
368
|
+
)),
|
|
369
|
+
leavingStacked.map((item) => /* @__PURE__ */ jsx(
|
|
370
|
+
Toast,
|
|
371
|
+
{
|
|
372
|
+
className: "is-leaving",
|
|
373
|
+
type: item.type,
|
|
374
|
+
title: item.title,
|
|
375
|
+
description: item.description,
|
|
376
|
+
closable: false,
|
|
377
|
+
inert: true
|
|
378
|
+
},
|
|
379
|
+
item.id
|
|
380
|
+
))
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
var toast_presenter_default = ToastPresenter;
|
|
387
|
+
|
|
388
|
+
// src/store/hook.ts
|
|
389
|
+
import { useSyncExternalStore } from "react";
|
|
390
|
+
function useNotificationStore() {
|
|
391
|
+
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
392
|
+
return { snapshot, dismiss, pause: pauseAll, resume: resumeAll };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/notify-container.tsx
|
|
396
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
397
|
+
function NotificationContainer({
|
|
398
|
+
presenter: Presenter = toast_presenter_default,
|
|
399
|
+
placement = "bottom-right",
|
|
400
|
+
limit = 3,
|
|
401
|
+
duration = 5e3,
|
|
402
|
+
overlap = true,
|
|
403
|
+
label
|
|
404
|
+
}) {
|
|
405
|
+
useEffect3(() => {
|
|
406
|
+
configure({ limit, duration });
|
|
407
|
+
}, [limit, duration]);
|
|
408
|
+
const { snapshot, dismiss: dismiss2, pause, resume } = useNotificationStore();
|
|
409
|
+
if (Presenter === toast_presenter_default) {
|
|
410
|
+
return /* @__PURE__ */ jsx2(
|
|
411
|
+
toast_presenter_default,
|
|
412
|
+
{
|
|
413
|
+
notifications: snapshot,
|
|
414
|
+
dismiss: dismiss2,
|
|
415
|
+
pause,
|
|
416
|
+
resume,
|
|
417
|
+
placement,
|
|
418
|
+
overlap,
|
|
419
|
+
label
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return /* @__PURE__ */ jsx2(Presenter, { notifications: snapshot, dismiss: dismiss2, pause, resume });
|
|
424
|
+
}
|
|
425
|
+
var notify_container_default = NotificationContainer;
|
|
426
|
+
export {
|
|
427
|
+
notify_container_default as NotificationContainer,
|
|
428
|
+
toast_presenter_default as ToastPresenter,
|
|
429
|
+
notify_default as notify
|
|
430
|
+
};
|
|
431
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/store.ts","../src/notify.ts","../src/notify-container.tsx","../src/presenters/toast-presenter.tsx","../src/presenters/use-toast-stack.ts","../src/store/hook.ts"],"sourcesContent":["import { NotificationItem, NotificationOptions } from '../types';\n\ninterface InternalNotification extends NotificationItem {\n duration: number;\n remaining: number;\n startedAt: number;\n timer: ReturnType<typeof setTimeout> | null;\n onClose?: () => void;\n}\n\ninterface StoreConfig {\n limit: number;\n duration: number;\n}\n\nconst DEFAULT_CONFIG: StoreConfig = { limit: 3, duration: 5000 };\n\nlet config: StoreConfig = { ...DEFAULT_CONFIG };\nlet visible: InternalNotification[] = [];\nlet queue: InternalNotification[] = [];\nlet paused = false;\nlet seq = 0;\n\ntype Listener = () => void;\nconst listeners = new Set<Listener>();\n\nfunction emitChange() {\n for (const listener of listeners) {\n listener();\n }\n}\n\nfunction genId(): string {\n seq += 1;\n return `notification-${seq}`;\n}\n\nfunction resolveDuration(type: NotificationOptions['type'], duration: number | undefined): number {\n if (duration !== undefined) return duration;\n return type === 'danger' ? 0 : config.duration;\n}\n\nfunction clearTimer(notification: InternalNotification): void {\n if (notification.timer) {\n clearTimeout(notification.timer);\n notification.timer = null;\n }\n}\n\nfunction startTimer(notification: InternalNotification): void {\n if (notification.duration === 0) return;\n notification.startedAt = Date.now();\n // startTimer -> dismiss -> drainQueue -> startTimer の相互再帰のため、定義順で解決できない\n // eslint-disable-next-line @typescript-eslint/no-use-before-define\n notification.timer = setTimeout(() => dismiss(notification.id), Math.max(notification.remaining, 0));\n}\n\nfunction buildInternal(id: string, options: NotificationOptions): InternalNotification {\n const type = options.type ?? 'info';\n const duration = resolveDuration(type, options.duration);\n return {\n id,\n type,\n title: options.title,\n description: options.description,\n closable: options.closable ?? true,\n action: options.action,\n onClose: options.onClose,\n duration,\n remaining: duration,\n startedAt: 0,\n timer: null,\n };\n}\n\nfunction applyOptions(target: InternalNotification, options: NotificationOptions): void {\n target.type = options.type ?? target.type;\n target.title = options.title ?? target.title;\n if ('description' in options) target.description = options.description;\n if ('closable' in options) target.closable = options.closable ?? true;\n if ('action' in options) target.action = options.action;\n if ('onClose' in options) target.onClose = options.onClose;\n target.duration = resolveDuration(target.type, options.duration);\n target.remaining = target.duration;\n}\n\n/** 既存の通知に options をマージした新しいオブジェクトを返す(未指定フィールドは既存値を維持) */\nfunction mergeNotification(existing: InternalNotification, options: NotificationOptions): InternalNotification {\n const next = { ...existing };\n applyOptions(next, options);\n return next;\n}\n\n/** テスト・HMR 用にストア状態を初期化する */\nexport function reset(): void {\n [...visible, ...queue].forEach(clearTimer);\n visible = [];\n queue = [];\n paused = false;\n seq = 0;\n config = { ...DEFAULT_CONFIG };\n}\n\nexport function configure(next: Partial<StoreConfig>): void {\n config = { ...config, ...next };\n}\n\nexport function subscribe(listener: Listener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\nexport function getSnapshot(): NotificationItem[] {\n return visible;\n}\n\nfunction drainQueue(): void {\n while (queue.length > 0 && visible.length < config.limit) {\n const [next, ...rest] = queue;\n queue = rest;\n visible = [...visible, next];\n if (!paused) startTimer(next);\n }\n}\n\nexport function enqueue(options: NotificationOptions): string {\n const id = options.id ?? genId();\n\n const existing = visible.find((n) => n.id === id);\n if (existing) {\n clearTimer(existing);\n applyOptions(existing, options);\n visible = [...visible];\n if (!paused) startTimer(existing);\n emitChange();\n return id;\n }\n\n const queuedIndex = queue.findIndex((n) => n.id === id);\n if (queuedIndex !== -1) {\n queue = queue.map((n, i) => (i === queuedIndex ? mergeNotification(n, options) : n));\n return id;\n }\n\n const notification = buildInternal(id, options);\n if (visible.length >= config.limit) {\n queue = [...queue, notification];\n return id;\n }\n\n visible = [...visible, notification];\n if (!paused) startTimer(notification);\n emitChange();\n return id;\n}\n\nexport function update(id: string, options: NotificationOptions): void {\n const existing = visible.find((n) => n.id === id);\n if (existing) {\n clearTimer(existing);\n applyOptions(existing, options);\n visible = [...visible];\n if (!paused) startTimer(existing);\n emitChange();\n return;\n }\n\n const queuedIndex = queue.findIndex((n) => n.id === id);\n if (queuedIndex !== -1) {\n queue = queue.map((n, i) => (i === queuedIndex ? mergeNotification(n, options) : n));\n }\n}\n\nexport function dismiss(id?: string): void {\n if (id === undefined) {\n const closing = [...visible, ...queue];\n closing.forEach(clearTimer);\n visible = [];\n queue = [];\n emitChange();\n closing.forEach((n) => n.onClose?.());\n return;\n }\n\n const target = visible.find((n) => n.id === id);\n if (target) {\n clearTimer(target);\n visible = visible.filter((n) => n.id !== id);\n drainQueue();\n emitChange();\n target.onClose?.();\n return;\n }\n\n const queuedTarget = queue.find((n) => n.id === id);\n if (queuedTarget) {\n queue = queue.filter((n) => n.id !== id);\n queuedTarget.onClose?.();\n }\n}\n\nexport function pauseAll(): void {\n if (paused) return;\n paused = true;\n visible.forEach((notification) => {\n if (!notification.timer) return;\n clearTimeout(notification.timer);\n notification.timer = null;\n notification.remaining = Math.max(notification.remaining - (Date.now() - notification.startedAt), 0);\n });\n}\n\nexport function resumeAll(): void {\n if (!paused) return;\n paused = false;\n visible.forEach((notification) => {\n if (notification.duration === 0 || notification.timer) return;\n startTimer(notification);\n });\n}\n","import { dismiss as dismissStore, enqueue, update as updateStore } from './store/store';\nimport { NotificationContent, NotificationOptions, NotificationType, NotifyApi, NotifyPromiseMessages } from './types';\n\n// 旧オプション名(autoHide/onHide)を新名称(duration/onClose)へ正規化する。\n// 明示された新名称があればそちらを優先する。\nfunction normalize(options: NotificationOptions): NotificationOptions {\n const { autoHide, onHide, duration, onClose, ...rest } = options;\n return {\n ...rest,\n duration: duration ?? autoHide,\n onClose: onClose ?? onHide,\n };\n}\n\nfunction show(content: NotificationContent, options: NotificationOptions = {}, type?: NotificationType): string {\n const normalized = normalize(options);\n return enqueue({ ...normalized, type: type ?? normalized.type ?? 'info', title: content });\n}\n\nfunction notify(content: NotificationContent, options?: NotificationOptions): string {\n return show(content, options);\n}\n\nnotify.success = (content: NotificationContent, options?: NotificationOptions) => show(content, options, 'success');\nnotify.info = (content: NotificationContent, options?: NotificationOptions) => show(content, options, 'info');\nnotify.warning = (content: NotificationContent, options?: NotificationOptions) => show(content, options, 'warning');\nnotify.danger = (content: NotificationContent, options?: NotificationOptions) => show(content, options, 'danger');\n\nnotify.promise = function promise<T>(promise: Promise<T>, messages: NotifyPromiseMessages<T>): Promise<T> {\n const id = show(messages.loading, { duration: 0, closable: false }, 'info');\n promise.then(\n (data) => {\n const title = typeof messages.success === 'function' ? messages.success(data) : messages.success;\n updateStore(id, { type: 'success', title });\n },\n (error) => {\n const title = typeof messages.error === 'function' ? messages.error(error) : messages.error;\n updateStore(id, { type: 'danger', title });\n }\n );\n return promise;\n};\n\nnotify.update = (id: string, options: NotificationOptions) => updateStore(id, normalize(options));\nnotify.dismiss = (id?: string) => dismissStore(id);\n\nexport default notify as NotifyApi;\n","import { useEffect } from 'react';\nimport ToastPresenter from './presenters/toast-presenter';\nimport { configure } from './store/store';\nimport { useNotificationStore } from './store';\nimport type { NotificationContainerProps } from './types';\n\nfunction NotificationContainer({\n presenter: Presenter = ToastPresenter,\n placement = 'bottom-right',\n limit = 3,\n duration = 5000,\n overlap = true,\n label,\n}: NotificationContainerProps) {\n useEffect(() => {\n configure({ limit, duration });\n }, [limit, duration]);\n\n const { snapshot, dismiss, pause, resume } = useNotificationStore();\n\n // 既定プレゼンター(ToastPresenter)にのみ placement/overlap/label を渡す。\n // NotificationPresenterProps 自体はプレゼンター間で共通の最小契約に留める。\n if (Presenter === ToastPresenter) {\n return (\n <ToastPresenter\n notifications={snapshot}\n dismiss={dismiss}\n pause={pause}\n resume={resume}\n placement={placement}\n overlap={overlap}\n label={label}\n />\n );\n }\n\n return <Presenter notifications={snapshot} dismiss={dismiss} pause={pause} resume={resume} />;\n}\n\nexport default NotificationContainer;\n","import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport Toast from '@ablogcms/components/toast';\nimport useToastStack from './use-toast-stack';\nimport type { NotificationPresenterProps, ToastPlacement } from '../types';\n\nexport interface ToastPresenterProps extends NotificationPresenterProps {\n /** 既定 bottom-right */\n placement?: ToastPlacement;\n /** 既定 true(Base UI 方式の重なり表示。false で常時リスト展開) */\n overlap?: boolean;\n /** リージョンの aria-label。既定 i18n('notify.region') */\n label?: string;\n}\n\nconst GAP = 8; // 展開時の toast 間隔($acms-spacers 2)\nconst PEEK = 12; // 折りたたみ時に背面がのぞく量\n\nfunction ToastPresenter({\n notifications,\n dismiss,\n pause,\n resume,\n placement = 'bottom-right',\n overlap = true,\n label,\n}: ToastPresenterProps) {\n const regionRef = useRef<HTMLElement>(null);\n const itemRefs = useRef(new Map<string, HTMLDivElement>());\n const [expanded, setExpanded] = useState(false);\n\n const stack = useToastStack(notifications);\n // 最新(配列末尾)が最前面になるよう反転する\n const activeStacked = useMemo(() => stack.filter((n) => !n.isLeaving).reverse(), [stack]);\n const leavingStacked = useMemo(() => stack.filter((n) => n.isLeaving), [stack]);\n\n useLayoutEffect(() => {\n const region = regionRef.current;\n if (!region || !overlap) return;\n\n let offset = 0;\n let frontHeight = 0;\n activeStacked.forEach((item, index) => {\n const el = itemRefs.current.get(item.id);\n if (!el) return;\n el.style.setProperty('--acms-toast-index', String(index));\n el.style.setProperty('--acms-toast-offset', `${offset}px`);\n el.dataset.front = index === 0 ? 'true' : 'false';\n const height = el.offsetHeight;\n if (index === 0) frontHeight = height;\n offset += height + GAP;\n });\n region.style.setProperty('--acms-toast-front-height', `${frontHeight}px`);\n const totalHeight = expanded\n ? Math.max(offset - GAP, 0)\n : frontHeight + PEEK * Math.max(activeStacked.length - 1, 0);\n region.style.setProperty('--acms-toast-region-height', `${totalHeight}px`);\n }, [activeStacked, expanded, overlap]);\n\n const expand = useCallback(() => {\n pause();\n if (overlap) setExpanded(true);\n }, [pause, overlap]);\n\n const collapse = useCallback(() => {\n resume();\n if (overlap) setExpanded(false);\n }, [resume, overlap]);\n\n const handleBlur = useCallback(\n (event: React.FocusEvent<HTMLElement>) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {\n collapse();\n }\n },\n [collapse]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (event.key === 'Escape' && activeStacked.length > 0) {\n dismiss(activeStacked[0].id);\n }\n },\n [dismiss, activeStacked]\n );\n\n // F6 でリージョンへフォーカス移動(表示中の通知がある場合のみ)\n useEffect(() => {\n function handleGlobalKeyDown(event: KeyboardEvent) {\n if (event.key === 'F6' && notifications.length > 0) {\n event.preventDefault();\n regionRef.current?.focus();\n }\n }\n document.addEventListener('keydown', handleGlobalKeyDown);\n return () => document.removeEventListener('keydown', handleGlobalKeyDown);\n }, [notifications.length]);\n\n return (\n // section は aria-label 付きで暗黙的に role=region を持つ(明示すると redundant-roles で警告される)。\n // ホバー/フォーカスでの pause+展開、Esc での最前面クローズは Sonner / Base UI と同じ「常設ライブ\n // リージョン」パターンの要件で、button/link 化できないため意図的に非対話要素へ付与している。\n // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions\n <section\n ref={regionRef}\n aria-label={label ?? ACMS.i18n('notify.region')}\n tabIndex={-1}\n className=\"acms-admin-toast-region\"\n data-placement={placement}\n data-expanded={overlap ? expanded : true}\n onMouseEnter={expand}\n onMouseLeave={collapse}\n onFocus={expand}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n >\n {activeStacked.map((item, index) => (\n <Toast\n key={item.id}\n ref={(el) => {\n if (el) itemRefs.current.set(item.id, el);\n else itemRefs.current.delete(item.id);\n }}\n type={item.type}\n title={item.title}\n description={item.description}\n closable={item.closable}\n data-front={index === 0}\n // 折りたたみ時、最前面以外は視覚的に隠れる(opacity:0)だけでなく、\n // inert でフォーカス・スクリーンリーダーからも除外する(展開時は解除)\n inert={overlap && !expanded && index !== 0}\n onClose={() => dismiss(item.id)}\n action={\n item.action\n ? {\n label: item.action.label,\n altText: item.action.altText,\n onClick: () => item.action?.onClick({ dismiss: () => dismiss(item.id) }),\n }\n : undefined\n }\n />\n ))}\n {leavingStacked.map((item) => (\n <Toast\n key={item.id}\n className=\"is-leaving\"\n type={item.type}\n title={item.title}\n description={item.description}\n closable={false}\n inert\n />\n ))}\n </section>\n );\n}\n\nexport default ToastPresenter;\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { NotificationItem } from '../types';\n\n// CSS 側の transitionend が来ない環境(prefers-reduced-motion 等)向けの保険。\n// --acms-toast-transition-duration(既定 0.3s)より長く取る。\nconst LEAVE_FALLBACK_MS = 500;\n\nexport interface StackedNotification extends NotificationItem {\n /** 退場アニメーション中(notifications からは既に外れているが DOM 上はまだ残す) */\n isLeaving: boolean;\n}\n\n/**\n * store から外れた通知を即座にアンマウントせず、退場アニメーションの間だけ\n * DOM 上に残す(is-leaving クラスでフェードアウトさせるため)。\n */\nexport default function useToastStack(notifications: NotificationItem[]): StackedNotification[] {\n const [leaving, setLeaving] = useState<NotificationItem[]>([]);\n const prevRef = useRef<NotificationItem[]>([]);\n const timersRef = useRef(new Map<string, ReturnType<typeof setTimeout>>());\n\n useEffect(() => {\n const currentIds = new Set(notifications.map((n) => n.id));\n const newlyRemoved = prevRef.current.filter((n) => !currentIds.has(n.id));\n prevRef.current = notifications;\n\n if (newlyRemoved.length === 0) {\n setLeaving((current) => current.filter((n) => !currentIds.has(n.id)));\n return;\n }\n\n setLeaving((current) => [...current.filter((n) => !currentIds.has(n.id)), ...newlyRemoved]);\n newlyRemoved.forEach((notification) => {\n const timer = setTimeout(() => {\n setLeaving((current) => current.filter((n) => n.id !== notification.id));\n timersRef.current.delete(notification.id);\n }, LEAVE_FALLBACK_MS);\n timersRef.current.set(notification.id, timer);\n });\n }, [notifications]);\n\n useEffect(() => {\n const timers = timersRef.current;\n return () => {\n timers.forEach((timer) => clearTimeout(timer));\n timers.clear();\n };\n }, []);\n\n return useMemo(() => {\n const activeIds = new Set(notifications.map((n) => n.id));\n return [\n ...notifications.map((n) => ({ ...n, isLeaving: false })),\n ...leaving.filter((n) => !activeIds.has(n.id)).map((n) => ({ ...n, isLeaving: true })),\n ];\n }, [notifications, leaving]);\n}\n","import { useSyncExternalStore } from 'react';\nimport { dismiss, getSnapshot, pauseAll, resumeAll, subscribe } from './store';\n\nexport default function useNotificationStore() {\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return { snapshot, dismiss, pause: pauseAll, resume: resumeAll };\n}\n"],"mappings":"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","names":["useEffect","useEffect","useMemo","useRef","useState","dismiss","useRef","useState","useMemo","useEffect","jsx","useEffect","dismiss"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { NotificationContainerProps } from './types/index.js';
|
|
2
|
+
declare function NotificationContainer({ presenter: Presenter, placement, limit, duration, overlap, label, }: NotificationContainerProps): import("react").JSX.Element;
|
|
3
|
+
export default NotificationContainer;
|
package/dist/notify.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NotificationPresenterProps, ToastPlacement } from '../types/index.js';
|
|
2
|
+
export interface ToastPresenterProps extends NotificationPresenterProps {
|
|
3
|
+
/** 既定 bottom-right */
|
|
4
|
+
placement?: ToastPlacement;
|
|
5
|
+
/** 既定 true(Base UI 方式の重なり表示。false で常時リスト展開) */
|
|
6
|
+
overlap?: boolean;
|
|
7
|
+
/** リージョンの aria-label。既定 i18n('notify.region') */
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
declare function ToastPresenter({ notifications, dismiss, pause, resume, placement, overlap, label, }: ToastPresenterProps): import("react").JSX.Element;
|
|
11
|
+
export default ToastPresenter;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NotificationItem } from '../types/index.js';
|
|
2
|
+
export interface StackedNotification extends NotificationItem {
|
|
3
|
+
/** 退場アニメーション中(notifications からは既に外れているが DOM 上はまだ残す) */
|
|
4
|
+
isLeaving: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* store から外れた通知を即座にアンマウントせず、退場アニメーションの間だけ
|
|
8
|
+
* DOM 上に残す(is-leaving クラスでフェードアウトさせるため)。
|
|
9
|
+
*/
|
|
10
|
+
export default function useToastStack(notifications: NotificationItem[]): StackedNotification[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NotificationItem, NotificationOptions } from '../types/index.js';
|
|
2
|
+
interface StoreConfig {
|
|
3
|
+
limit: number;
|
|
4
|
+
duration: number;
|
|
5
|
+
}
|
|
6
|
+
type Listener = () => void;
|
|
7
|
+
/** テスト・HMR 用にストア状態を初期化する */
|
|
8
|
+
export declare function reset(): void;
|
|
9
|
+
export declare function configure(next: Partial<StoreConfig>): void;
|
|
10
|
+
export declare function subscribe(listener: Listener): () => void;
|
|
11
|
+
export declare function getSnapshot(): NotificationItem[];
|
|
12
|
+
export declare function enqueue(options: NotificationOptions): string;
|
|
13
|
+
export declare function update(id: string, options: NotificationOptions): void;
|
|
14
|
+
export declare function dismiss(id?: string): void;
|
|
15
|
+
export declare function pauseAll(): void;
|
|
16
|
+
export declare function resumeAll(): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type NotificationType = 'success' | 'info' | 'warning' | 'danger';
|
|
2
|
+
export type NotificationContent = React.ReactNode;
|
|
3
|
+
export interface NotificationAction {
|
|
4
|
+
label: string;
|
|
5
|
+
/** スクリーンリーダー向けの代替説明 */
|
|
6
|
+
altText?: string;
|
|
7
|
+
onClick: (ctx: {
|
|
8
|
+
dismiss: () => void;
|
|
9
|
+
}) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface NotificationOptions {
|
|
12
|
+
/** 指定時、表示中の同 id の通知を update する(重複抑制) */
|
|
13
|
+
id?: string;
|
|
14
|
+
type?: NotificationType;
|
|
15
|
+
title?: NotificationContent;
|
|
16
|
+
description?: NotificationContent;
|
|
17
|
+
/** 既定 5000ms。0 = 手動で閉じるまで表示(danger の既定) */
|
|
18
|
+
duration?: number;
|
|
19
|
+
/** 既定 true */
|
|
20
|
+
closable?: boolean;
|
|
21
|
+
action?: NotificationAction;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
/** @deprecated duration を使う */
|
|
24
|
+
autoHide?: number;
|
|
25
|
+
/** @deprecated onClose を使う */
|
|
26
|
+
onHide?: () => void;
|
|
27
|
+
}
|
|
28
|
+
/** ストアの購読結果としてプレゼンターへ渡される、表示中の通知1件分の情報 */
|
|
29
|
+
export interface NotificationItem {
|
|
30
|
+
id: string;
|
|
31
|
+
type: NotificationType;
|
|
32
|
+
title: NotificationContent;
|
|
33
|
+
description?: NotificationContent;
|
|
34
|
+
closable: boolean;
|
|
35
|
+
action?: NotificationAction;
|
|
36
|
+
}
|
|
37
|
+
export interface NotifyPromiseMessages<T> {
|
|
38
|
+
loading: NotificationContent;
|
|
39
|
+
success: NotificationContent | ((data: T) => NotificationContent);
|
|
40
|
+
error: NotificationContent | ((error: unknown) => NotificationContent);
|
|
41
|
+
}
|
|
42
|
+
export interface NotifyApi {
|
|
43
|
+
(content: NotificationContent, options?: NotificationOptions): string;
|
|
44
|
+
success(content: NotificationContent, options?: NotificationOptions): string;
|
|
45
|
+
info(content: NotificationContent, options?: NotificationOptions): string;
|
|
46
|
+
warning(content: NotificationContent, options?: NotificationOptions): string;
|
|
47
|
+
danger(content: NotificationContent, options?: NotificationOptions): string;
|
|
48
|
+
promise<T>(promise: Promise<T>, messages: NotifyPromiseMessages<T>): Promise<T>;
|
|
49
|
+
update(id: string, options: NotificationOptions): void;
|
|
50
|
+
/** id 省略で全消し */
|
|
51
|
+
dismiss(id?: string): void;
|
|
52
|
+
}
|
|
53
|
+
export interface NotificationPresenterProps {
|
|
54
|
+
/** store の購読結果(表示対象のみ。キュー待ちは含まない) */
|
|
55
|
+
notifications: NotificationItem[];
|
|
56
|
+
dismiss: (id: string) => void;
|
|
57
|
+
/** 自動消滅タイマーの一括停止 */
|
|
58
|
+
pause: () => void;
|
|
59
|
+
resume: () => void;
|
|
60
|
+
}
|
|
61
|
+
export type NotificationPresenter = React.ComponentType<NotificationPresenterProps>;
|
|
62
|
+
export type ToastPlacement = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-center';
|
|
63
|
+
export interface NotificationContainerProps {
|
|
64
|
+
/** 既定: ToastPresenter。将来 AlertPresenter 等に差し替え可 */
|
|
65
|
+
presenter?: NotificationPresenter;
|
|
66
|
+
/** 既定 bottom-right(ToastPresenter 使用時のみ意味を持つ) */
|
|
67
|
+
placement?: ToastPlacement;
|
|
68
|
+
/** 同時表示上限。既定 3。超過は FIFO キュー */
|
|
69
|
+
limit?: number;
|
|
70
|
+
/** 既定 5000 */
|
|
71
|
+
duration?: number;
|
|
72
|
+
/** 既定 true(Base UI 方式の重なり表示。false で常時リスト展開) */
|
|
73
|
+
overlap?: boolean;
|
|
74
|
+
/** リージョンの aria-label(ToastPresenter 使用時のみ意味を持つ)。既定 i18n('notify.region') */
|
|
75
|
+
label?: string;
|
|
76
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ablogcms/notify",
|
|
3
|
+
"version": "3.2.28-beta.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "管理画面のトースト通知状態管理",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://www.a-blogcms.jp",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+ssh://git@bitbucket.org:appleple/ablogcms.git",
|
|
11
|
+
"directory": "packages/frontend/notify"
|
|
12
|
+
},
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@ablogcms/components": "3.2.28-beta.0",
|
|
16
|
+
"@ablogcms/types": "3.2.28-beta.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@testing-library/react": "^16.3.2",
|
|
20
|
+
"vitest": "^4.1.9"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "^19",
|
|
24
|
+
"@types/react": "^19.2.17"
|
|
25
|
+
},
|
|
26
|
+
"peerDependenciesMeta": {
|
|
27
|
+
"@types/react": {
|
|
28
|
+
"optional": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.mjs",
|
|
35
|
+
"default": "./dist/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./acms": {
|
|
38
|
+
"types": "./dist/acms.d.ts",
|
|
39
|
+
"import": "./dist/acms.mjs",
|
|
40
|
+
"default": "./dist/acms.mjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "node ../../../tools/build-package.mjs"
|
|
51
|
+
}
|
|
52
|
+
}
|