@ryuzaki13/react-foundation-lib 2.17.0 → 2.18.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/notifications/index.js +21 -12
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/store.d.ts +3 -1
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/notifications/types.d.ts +2 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/notifications/README.mdx +40 -16
|
@@ -89,35 +89,44 @@ function s() {
|
|
|
89
89
|
createdAt: l,
|
|
90
90
|
ttlMs: t.ttlMs,
|
|
91
91
|
dismissible: t.dismissible ?? !0,
|
|
92
|
-
actions: t.actions
|
|
92
|
+
actions: t.actions,
|
|
93
|
+
...t.retainInHistory === void 0 ? {} : { retainInHistory: t.retainInHistory }
|
|
93
94
|
}, d = s().items, f = [u, ...d.filter((e) => e.id !== c)].slice(0, o), p = new Set(f.map((e) => e.id));
|
|
94
95
|
return d.forEach((e) => {
|
|
95
96
|
p.has(e.id) || n(e.id);
|
|
96
|
-
}), e((e) =>
|
|
97
|
-
|
|
98
|
-
history: [u, ...e.history.filter((e) => e.id !== c)]
|
|
99
|
-
|
|
97
|
+
}), e((e) => {
|
|
98
|
+
let t = e.history;
|
|
99
|
+
return u.retainInHistory === !1 ? e.history.some((e) => e.id === c) && (t = e.history.filter((e) => e.id !== c)) : t = [u, ...e.history.filter((e) => e.id !== c)], {
|
|
100
|
+
items: f,
|
|
101
|
+
history: t
|
|
102
|
+
};
|
|
103
|
+
}), r(i, c, u.ttlMs), c;
|
|
100
104
|
},
|
|
101
105
|
update: (t, n) => {
|
|
102
106
|
let a = s().items.find((e) => e.id === t);
|
|
103
107
|
if (!a) return !1;
|
|
104
|
-
let o = {
|
|
108
|
+
let { retainInHistory: o, ...c } = n, l = {
|
|
105
109
|
...a,
|
|
106
|
-
...
|
|
110
|
+
...c,
|
|
111
|
+
...o === void 0 ? {} : { retainInHistory: o },
|
|
107
112
|
id: a.id,
|
|
108
113
|
createdAt: a.createdAt
|
|
109
114
|
};
|
|
110
|
-
return e((e) =>
|
|
111
|
-
|
|
112
|
-
history: e.history.map((e) => e.id === t ?
|
|
113
|
-
|
|
115
|
+
return e((e) => {
|
|
116
|
+
let n = e.history.some((e) => e.id === t), r = e.history;
|
|
117
|
+
return l.retainInHistory === !1 ? n && (r = e.history.filter((e) => e.id !== t)) : n ? r = e.history.map((e) => e.id === t ? l : e) : o === !0 && (r = [l, ...e.history]), {
|
|
118
|
+
items: e.items.map((e) => e.id === t ? l : e),
|
|
119
|
+
history: r
|
|
120
|
+
};
|
|
121
|
+
}), "ttlMs" in n && r(i, t, l.ttlMs), !0;
|
|
114
122
|
},
|
|
115
123
|
upsert: (e) => s().actions.update(e.id, {
|
|
116
124
|
type: e.type,
|
|
117
125
|
title: e.title,
|
|
118
126
|
message: e.message,
|
|
119
127
|
ttlMs: e.ttlMs,
|
|
120
|
-
dismissible: e.dismissible
|
|
128
|
+
dismissible: e.dismissible,
|
|
129
|
+
...e.retainInHistory === void 0 ? {} : { retainInHistory: e.retainInHistory }
|
|
121
130
|
}) ? e.id : s().actions.push(e),
|
|
122
131
|
dismiss: (t) => {
|
|
123
132
|
n(t), e((e) => ({ items: e.items.filter((e) => e.id !== t) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/notifications/notify.ts","../../src/notifications/store.ts"],"sourcesContent":["import { type NotificationPushInput, type NotificationsStoreApi, type NotificationUpdatePatch } from \"./store\";\nimport { type NotificationId } from \"./types\";\n\nlet boundStore: NotificationsStoreApi | null = null;\n\nexport const bindNotifications = (store: NotificationsStoreApi) => {\n\tboundStore = store;\n\n\treturn () => {\n\t\tif (boundStore === store) {\n\t\t\tboundStore = null;\n\t\t}\n\t};\n};\n\nconst requireStore = (): NotificationsStoreApi => {\n\tif (!boundStore) throw new Error(\"Notifications store is not bound. Wrap app with NotificationsProvider.\");\n\treturn boundStore;\n};\n\nexport const notify = Object.freeze({\n\tpush: (input: NotificationPushInput) => requireStore().getState().actions.push(input),\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => requireStore().getState().actions.upsert(input),\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => requireStore().getState().actions.update(id, patch),\n\n\tdismiss: (id: NotificationId) => requireStore().getState().actions.dismiss(id),\n\tclear: () => requireStore().getState().actions.clear(),\n\tclearHistory: () => requireStore().getState().actions.clearHistory(),\n\n\tsuccess: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"success\", message, ...opts }),\n\tinfo: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"info\", message, ...opts }),\n\twarning: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"warning\", message, ...opts }),\n\terror: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"error\", message, ...opts }),\n\n\t/**\n\t * Удобный паттерн \"долгой операции\":\n\t * const op = notify.progress(\"Сохраняю...\");\n\t * ... op.success(\"Готово\") или op.error(\"Ошибка\")\n\t */\n\tprogress: (message: string, opts?: { id?: NotificationId; title?: string }) => {\n\t\tconst store = requireStore();\n\t\tconst id = opts?.id;\n\n\t\tif (id) {\n\t\t\tstore.getState().actions.upsert({\n\t\t\t\tid,\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\t\t}\n\n\t\tconst notificationId =\n\t\t\tid ??\n\t\t\tstore.getState().actions.push({\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\n\t\treturn {\n\t\t\tid: notificationId,\n\t\t\tupdate: (nextMessage: string) => notify.update(notificationId, { message: nextMessage }),\n\t\t\tsuccess: (nextMessage: string, ttlMs = 2500) =>\n\t\t\t\tnotify.update(notificationId, { type: \"success\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\terror: (nextMessage: string, ttlMs = 5000) =>\n\t\t\t\tnotify.update(notificationId, { type: \"error\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\tdismiss: () => notify.dismiss(notificationId)\n\t\t};\n\t}\n});\n","import { createStore, type StoreApi } from \"zustand\";\n\nimport { type Notification, type NotificationAction, type NotificationId, type NotificationType } from \"./types\";\n\nexport type NotificationPushInput = {\n\tid?: NotificationId;\n\ttype: NotificationType;\n\ttitle?: string;\n\tmessage: string;\n\tttlMs?: number;\n\tdismissible?: boolean;\n\tactions?: NotificationAction[];\n};\n\nexport type NotificationUpdatePatch = Partial<Omit<Notification, \"id\" | \"createdAt\">> & {\n\t// ttlMs можно менять; createdAt — нет\n};\n\nexport type NotificationsState = {\n\t/** Активные toast-уведомления, которые должен отображать краткоживущий host. */\n\titems: Notification[];\n\t/** Полная история за время жизни store; TTL, dismiss и переполнение toast-стека её не сокращают. */\n\thistory: Notification[];\n};\n\nexport type NotificationsActions = {\n\tpush: (input: NotificationPushInput) => NotificationId;\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => boolean;\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => NotificationId;\n\tdismiss: (id: NotificationId) => void;\n\tclear: () => void;\n\tclearHistory: () => void;\n};\n\nexport type NotificationsStore = NotificationsState & {\n\tactions: NotificationsActions;\n};\n\nconst genId = (): NotificationId => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n\nconst MAX_VISIBLE_ITEMS = 6;\n\nexport function createNotificationsStore() {\n\tconst timers = new Map<NotificationId, number>();\n\n\tconst clearTimer = (id: NotificationId) => {\n\t\tconst t = timers.get(id);\n\t\tif (t) {\n\t\t\twindow.clearTimeout(t);\n\t\t\ttimers.delete(id);\n\t\t}\n\t};\n\n\tconst armTimer = (store: StoreApi<NotificationsStore>, id: NotificationId, ttlMs: number = 10_000) => {\n\t\tclearTimer(id);\n\t\tif (ttlMs <= 0) return;\n\n\t\tconst t = window.setTimeout(() => {\n\t\t\tstore.getState().actions.dismiss(id);\n\t\t}, ttlMs);\n\n\t\ttimers.set(id, t);\n\t};\n\n\tconst store = createStore<NotificationsStore>()((set, get) => ({\n\t\titems: [],\n\t\thistory: [],\n\n\t\tactions: {\n\t\t\tpush: (input) => {\n\t\t\t\tconst id = input.id ?? genId();\n\t\t\t\tconst now = Date.now();\n\n\t\t\t\tconst notif: Notification = {\n\t\t\t\t\tid,\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible ?? true,\n\t\t\t\t\tactions: input.actions\n\t\t\t\t};\n\n\t\t\t\tconst currentItems = get().items;\n\t\t\t\tconst nextItems = [notif, ...currentItems.filter((notification) => notification.id !== id)].slice(0, MAX_VISIBLE_ITEMS);\n\t\t\t\tconst visibleIds = new Set(nextItems.map((notification) => notification.id));\n\n\t\t\t\t// Для вытесненного toast таймер больше не нужен: запись уже останется в history.\n\t\t\t\tcurrentItems.forEach((notification) => {\n\t\t\t\t\tif (!visibleIds.has(notification.id)) clearTimer(notification.id);\n\t\t\t\t});\n\n\t\t\t\tset((state) => ({\n\t\t\t\t\titems: nextItems,\n\t\t\t\t\thistory: [notif, ...state.history.filter((notification) => notification.id !== id)]\n\t\t\t\t}));\n\n\t\t\t\tarmTimer(store, id, notif.ttlMs);\n\t\t\t\treturn id;\n\t\t\t},\n\n\t\t\tupdate: (id, patch) => {\n\t\t\t\tconst previous = get().items.find((notification) => notification.id === id);\n\t\t\t\tif (!previous) return false;\n\n\t\t\t\tconst next: Notification = {\n\t\t\t\t\t...previous,\n\t\t\t\t\t...patch,\n\t\t\t\t\tid: previous.id,\n\t\t\t\t\tcreatedAt: previous.createdAt\n\t\t\t\t};\n\n\t\t\t\tset((state) => ({\n\t\t\t\t\titems: state.items.map((notification) => (notification.id === id ? next : notification)),\n\t\t\t\t\thistory: state.history.map((notification) => (notification.id === id ? next : notification))\n\t\t\t\t}));\n\n\t\t\t\t// если ttlMs изменили — пере-армим таймер\n\t\t\t\tif (\"ttlMs\" in patch) {\n\t\t\t\t\tarmTimer(store, id, next.ttlMs);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tupsert: (input) => {\n\t\t\t\tconst ok = get().actions.update(input.id, {\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible\n\t\t\t\t});\n\n\t\t\t\tif (ok) return input.id;\n\t\t\t\treturn get().actions.push(input);\n\t\t\t},\n\n\t\t\tdismiss: (id) => {\n\t\t\t\tclearTimer(id);\n\t\t\t\tset((s) => ({ items: s.items.filter((n) => n.id !== id) }));\n\t\t\t},\n\n\t\t\tclear: () => {\n\t\t\t\ttimers.forEach((t) => window.clearTimeout(t));\n\t\t\t\ttimers.clear();\n\t\t\t\tset({ items: [] });\n\t\t\t},\n\n\t\t\tclearHistory: () => {\n\t\t\t\tset({ history: [] });\n\t\t\t}\n\t\t}\n\t}));\n\n\treturn store;\n}\n\nexport type NotificationsStoreApi = StoreApi<NotificationsStore>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/notifications/notify.ts","../../src/notifications/store.ts"],"sourcesContent":["import { type NotificationPushInput, type NotificationsStoreApi, type NotificationUpdatePatch } from \"./store\";\nimport { type NotificationId } from \"./types\";\n\nlet boundStore: NotificationsStoreApi | null = null;\n\nexport const bindNotifications = (store: NotificationsStoreApi) => {\n\tboundStore = store;\n\n\treturn () => {\n\t\tif (boundStore === store) {\n\t\t\tboundStore = null;\n\t\t}\n\t};\n};\n\nconst requireStore = (): NotificationsStoreApi => {\n\tif (!boundStore) throw new Error(\"Notifications store is not bound. Wrap app with NotificationsProvider.\");\n\treturn boundStore;\n};\n\nexport const notify = Object.freeze({\n\tpush: (input: NotificationPushInput) => requireStore().getState().actions.push(input),\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => requireStore().getState().actions.upsert(input),\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => requireStore().getState().actions.update(id, patch),\n\n\tdismiss: (id: NotificationId) => requireStore().getState().actions.dismiss(id),\n\tclear: () => requireStore().getState().actions.clear(),\n\tclearHistory: () => requireStore().getState().actions.clearHistory(),\n\n\tsuccess: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"success\", message, ...opts }),\n\tinfo: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"info\", message, ...opts }),\n\twarning: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"warning\", message, ...opts }),\n\terror: (message: string, opts?: Omit<NotificationPushInput, \"type\" | \"message\">) =>\n\t\trequireStore()\n\t\t\t.getState()\n\t\t\t.actions.push({ type: \"error\", message, ...opts }),\n\n\t/**\n\t * Удобный паттерн \"долгой операции\":\n\t * const op = notify.progress(\"Сохраняю...\");\n\t * ... op.success(\"Готово\") или op.error(\"Ошибка\")\n\t */\n\tprogress: (message: string, opts?: { id?: NotificationId; title?: string }) => {\n\t\tconst store = requireStore();\n\t\tconst id = opts?.id;\n\n\t\tif (id) {\n\t\t\tstore.getState().actions.upsert({\n\t\t\t\tid,\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\t\t}\n\n\t\tconst notificationId =\n\t\t\tid ??\n\t\t\tstore.getState().actions.push({\n\t\t\t\ttype: \"info\",\n\t\t\t\ttitle: opts?.title,\n\t\t\t\tmessage,\n\t\t\t\tdismissible: false\n\t\t\t});\n\n\t\treturn {\n\t\t\tid: notificationId,\n\t\t\tupdate: (nextMessage: string) => notify.update(notificationId, { message: nextMessage }),\n\t\t\tsuccess: (nextMessage: string, ttlMs = 2500) =>\n\t\t\t\tnotify.update(notificationId, { type: \"success\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\terror: (nextMessage: string, ttlMs = 5000) =>\n\t\t\t\tnotify.update(notificationId, { type: \"error\", message: nextMessage, dismissible: true, ttlMs }),\n\t\t\tdismiss: () => notify.dismiss(notificationId)\n\t\t};\n\t}\n});\n","import { createStore, type StoreApi } from \"zustand\";\n\nimport { type Notification, type NotificationAction, type NotificationId, type NotificationType } from \"./types\";\n\nexport type NotificationPushInput = {\n\tid?: NotificationId;\n\ttype: NotificationType;\n\ttitle?: string;\n\tmessage: string;\n\tttlMs?: number;\n\tdismissible?: boolean;\n\tactions?: NotificationAction[];\n\t/** false исключает текст из истории, не меняя отображение и срок жизни активного toast. */\n\tretainInHistory?: boolean;\n};\n\nexport type NotificationUpdatePatch = Partial<Omit<Notification, \"id\" | \"createdAt\">> & {\n\t// ttlMs можно менять; createdAt — нет\n};\n\nexport type NotificationsState = {\n\t/** Активные toast-уведомления, которые должен отображать краткоживущий host. */\n\titems: Notification[];\n\t/** История сохраняемых уведомлений; TTL, dismiss и переполнение toast-стека её не сокращают. */\n\thistory: Notification[];\n};\n\nexport type NotificationsActions = {\n\tpush: (input: NotificationPushInput) => NotificationId;\n\tupdate: (id: NotificationId, patch: NotificationUpdatePatch) => boolean;\n\tupsert: (input: NotificationPushInput & { id: NotificationId }) => NotificationId;\n\tdismiss: (id: NotificationId) => void;\n\tclear: () => void;\n\tclearHistory: () => void;\n};\n\nexport type NotificationsStore = NotificationsState & {\n\tactions: NotificationsActions;\n};\n\nconst genId = (): NotificationId => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n\nconst MAX_VISIBLE_ITEMS = 6;\n\nexport function createNotificationsStore() {\n\tconst timers = new Map<NotificationId, number>();\n\n\tconst clearTimer = (id: NotificationId) => {\n\t\tconst t = timers.get(id);\n\t\tif (t) {\n\t\t\twindow.clearTimeout(t);\n\t\t\ttimers.delete(id);\n\t\t}\n\t};\n\n\tconst armTimer = (store: StoreApi<NotificationsStore>, id: NotificationId, ttlMs: number = 10_000) => {\n\t\tclearTimer(id);\n\t\tif (ttlMs <= 0) return;\n\n\t\tconst t = window.setTimeout(() => {\n\t\t\tstore.getState().actions.dismiss(id);\n\t\t}, ttlMs);\n\n\t\ttimers.set(id, t);\n\t};\n\n\tconst store = createStore<NotificationsStore>()((set, get) => ({\n\t\titems: [],\n\t\thistory: [],\n\n\t\tactions: {\n\t\t\tpush: (input) => {\n\t\t\t\tconst id = input.id ?? genId();\n\t\t\t\tconst now = Date.now();\n\n\t\t\t\tconst notif: Notification = {\n\t\t\t\t\tid,\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tcreatedAt: now,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible ?? true,\n\t\t\t\t\tactions: input.actions,\n\t\t\t\t\t// Не добавляем поле старым consumers, если они не задали новую политику.\n\t\t\t\t\t...(input.retainInHistory !== undefined ? { retainInHistory: input.retainInHistory } : {})\n\t\t\t\t};\n\n\t\t\t\tconst currentItems = get().items;\n\t\t\t\tconst nextItems = [notif, ...currentItems.filter((notification) => notification.id !== id)].slice(0, MAX_VISIBLE_ITEMS);\n\t\t\t\tconst visibleIds = new Set(nextItems.map((notification) => notification.id));\n\n\t\t\t\t// Для вытесненного toast таймер больше не нужен; сохранение истории определяется его политикой.\n\t\t\t\tcurrentItems.forEach((notification) => {\n\t\t\t\t\tif (!visibleIds.has(notification.id)) clearTimer(notification.id);\n\t\t\t\t});\n\n\t\t\t\tset((state) => {\n\t\t\t\t\tlet history = state.history;\n\n\t\t\t\t\tif (notif.retainInHistory !== false) {\n\t\t\t\t\t\thistory = [notif, ...state.history.filter((notification) => notification.id !== id)];\n\t\t\t\t\t} else if (state.history.some((notification) => notification.id === id)) {\n\t\t\t\t\t\thistory = state.history.filter((notification) => notification.id !== id);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { items: nextItems, history };\n\t\t\t\t});\n\n\t\t\t\tarmTimer(store, id, notif.ttlMs);\n\t\t\t\treturn id;\n\t\t\t},\n\n\t\t\tupdate: (id, patch) => {\n\t\t\t\tconst previous = get().items.find((notification) => notification.id === id);\n\t\t\t\tif (!previous) return false;\n\n\t\t\t\tconst { retainInHistory, ...contentPatch } = patch;\n\t\t\t\tconst next: Notification = {\n\t\t\t\t\t...previous,\n\t\t\t\t\t...contentPatch,\n\t\t\t\t\t// undefined, в том числе переданный явно, не отменяет прежний отказ от истории.\n\t\t\t\t\t...(retainInHistory !== undefined ? { retainInHistory } : {}),\n\t\t\t\t\tid: previous.id,\n\t\t\t\t\tcreatedAt: previous.createdAt\n\t\t\t\t};\n\n\t\t\t\tset((state) => {\n\t\t\t\t\tconst existsInHistory = state.history.some((notification) => notification.id === id);\n\t\t\t\t\tlet history = state.history;\n\n\t\t\t\t\tif (next.retainInHistory === false) {\n\t\t\t\t\t\tif (existsInHistory) history = state.history.filter((notification) => notification.id !== id);\n\t\t\t\t\t} else if (existsInHistory) {\n\t\t\t\t\t\thistory = state.history.map((notification) => (notification.id === id ? next : notification));\n\t\t\t\t\t} else if (retainInHistory === true) {\n\t\t\t\t\t\t// После clearHistory вернуть активный toast можно только явным решением consumer.\n\t\t\t\t\t\thistory = [next, ...state.history];\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\titems: state.items.map((notification) => (notification.id === id ? next : notification)),\n\t\t\t\t\t\thistory\n\t\t\t\t\t};\n\t\t\t\t});\n\n\t\t\t\t// если ttlMs изменили — пере-армим таймер\n\t\t\t\tif (\"ttlMs\" in patch) {\n\t\t\t\t\tarmTimer(store, id, next.ttlMs);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tupsert: (input) => {\n\t\t\t\tconst ok = get().actions.update(input.id, {\n\t\t\t\t\ttype: input.type,\n\t\t\t\t\ttitle: input.title,\n\t\t\t\t\tmessage: input.message,\n\t\t\t\t\tttlMs: input.ttlMs,\n\t\t\t\t\tdismissible: input.dismissible,\n\t\t\t\t\t...(input.retainInHistory !== undefined ? { retainInHistory: input.retainInHistory } : {})\n\t\t\t\t});\n\n\t\t\t\tif (ok) return input.id;\n\t\t\t\treturn get().actions.push(input);\n\t\t\t},\n\n\t\t\tdismiss: (id) => {\n\t\t\t\tclearTimer(id);\n\t\t\t\tset((s) => ({ items: s.items.filter((n) => n.id !== id) }));\n\t\t\t},\n\n\t\t\tclear: () => {\n\t\t\t\ttimers.forEach((t) => window.clearTimeout(t));\n\t\t\t\ttimers.clear();\n\t\t\t\tset({ items: [] });\n\t\t\t},\n\n\t\t\tclearHistory: () => {\n\t\t\t\tset({ history: [] });\n\t\t\t}\n\t\t}\n\t}));\n\n\treturn store;\n}\n\nexport type NotificationsStoreApi = StoreApi<NotificationsStore>;\n"],"mappings":"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"}
|
|
@@ -8,12 +8,14 @@ export type NotificationPushInput = {
|
|
|
8
8
|
ttlMs?: number;
|
|
9
9
|
dismissible?: boolean;
|
|
10
10
|
actions?: NotificationAction[];
|
|
11
|
+
/** false исключает текст из истории, не меняя отображение и срок жизни активного toast. */
|
|
12
|
+
retainInHistory?: boolean;
|
|
11
13
|
};
|
|
12
14
|
export type NotificationUpdatePatch = Partial<Omit<Notification, "id" | "createdAt">> & {};
|
|
13
15
|
export type NotificationsState = {
|
|
14
16
|
/** Активные toast-уведомления, которые должен отображать краткоживущий host. */
|
|
15
17
|
items: Notification[];
|
|
16
|
-
/**
|
|
18
|
+
/** История сохраняемых уведомлений; TTL, dismiss и переполнение toast-стека её не сокращают. */
|
|
17
19
|
history: Notification[];
|
|
18
20
|
};
|
|
19
21
|
export type NotificationsActions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjH,MAAM,MAAM,qBAAqB,GAAG;IACnC,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/notifications/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjH,MAAM,MAAM,qBAAqB,GAAG;IACnC,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,WAAW,CAAC,CAAC,GAAG,EAEvF,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAChC,gFAAgF;IAChF,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,gGAAgG;IAChG,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,cAAc,CAAC;IACvD,MAAM,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC;IACxE,MAAM,EAAE,CAAC,KAAK,EAAE,qBAAqB,GAAG;QAAE,EAAE,EAAE,cAAc,CAAA;KAAE,KAAK,cAAc,CAAC;IAClF,OAAO,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,YAAY,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG;IACrD,OAAO,EAAE,oBAAoB,CAAC;CAC9B,CAAC;AAMF,wBAAgB,wBAAwB,iCA8IvC;AAED,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAAC"}
|
|
@@ -15,5 +15,7 @@ export type Notification = {
|
|
|
15
15
|
ttlMs?: number;
|
|
16
16
|
dismissible?: boolean;
|
|
17
17
|
actions?: NotificationAction[];
|
|
18
|
+
/** false оставляет текст только в активном toast; отсутствие поля сохраняет прежнее включение в историю. */
|
|
19
|
+
retainInHistory?: boolean;
|
|
18
20
|
};
|
|
19
21
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/notifications/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/notifications/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,4GAA4G;IAC5G,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
|
|
|
4
4
|
|
|
5
5
|
# Notifications
|
|
6
6
|
|
|
7
|
-
Модуль `@ryuzaki13/react-foundation-lib/notifications` хранит активный toast-стек и
|
|
7
|
+
Модуль `@ryuzaki13/react-foundation-lib/notifications` хранит активный toast-стек и историю сохраняемых уведомлений, а также предоставляет два способа управления ими:
|
|
8
8
|
|
|
9
9
|
- Zustand-store — для React-интерфейса, который отображает уведомления;
|
|
10
10
|
- объект `notify` — для императивных вызовов из обработчиков, сервисов и `try/catch`.
|
|
@@ -88,7 +88,7 @@ export function NotificationsViewport() {
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
`items` содержит только активные toast-уведомления и ограничен шестью элементами. Для отдельного центра уведомлений, страницы или содержимого диалога подписывайтесь на `state.history`: эта коллекция не сокращается при TTL, ручном закрытии и переполнении toast-стека.
|
|
91
|
+
`items` содержит только активные toast-уведомления и ограничен шестью элементами. Для отдельного центра уведомлений, страницы или содержимого диалога подписывайтесь на `state.history`: эта коллекция не сокращается при TTL, ручном закрытии и переполнении toast-стека. По умолчанию все уведомления сохраняются; явный `retainInHistory: false` оставляет уведомление только в активном стеке.
|
|
92
92
|
|
|
93
93
|
### 3. Показывайте уведомления
|
|
94
94
|
|
|
@@ -123,6 +123,7 @@ type Notification = {
|
|
|
123
123
|
ttlMs?: number;
|
|
124
124
|
dismissible?: boolean;
|
|
125
125
|
actions?: NotificationAction[];
|
|
126
|
+
retainInHistory?: boolean;
|
|
126
127
|
};
|
|
127
128
|
```
|
|
128
129
|
|
|
@@ -130,6 +131,8 @@ type Notification = {
|
|
|
130
131
|
|
|
131
132
|
`actions` содержат функции и потому не сериализуются. Не сохраняйте очередь уведомлений в JSON, localStorage или серверной базе.
|
|
132
133
|
|
|
134
|
+
`retainInHistory: false` запрещает хранить уведомление в `history`. Поле необязательно: его отсутствие сохраняет прежнее поведение и прежнюю форму объекта уведомления. `true` явно разрешает сохранение. Политика действует только на историю этого store, а не на сетевой транспорт, внешние журналы или другие хранилища.
|
|
135
|
+
|
|
133
136
|
## API store
|
|
134
137
|
|
|
135
138
|
### `createNotificationsStore()`
|
|
@@ -151,7 +154,7 @@ type NotificationsStore = {
|
|
|
151
154
|
};
|
|
152
155
|
```
|
|
153
156
|
|
|
154
|
-
`items` — стек видимых toast-уведомлений, ограниченный шестью элементами. `history` —
|
|
157
|
+
`items` — стек видимых toast-уведомлений, ограниченный шестью элементами. `history` — история сохраняемых уведомлений за время жизни store. Новое уведомление помещается в начало стека, а в историю — только если не задан `retainInHistory: false`. При добавлении с уже существующим `id` старый элемент заменяется, а новый получает новое `createdAt` и политику нового `push`; отказ от истории удаляет и прежнюю запись с этим ID.
|
|
155
158
|
|
|
156
159
|
История живёт в памяти и не является журналом аудита: она сбрасывается при пересоздании store или явном `clearHistory()`. Поскольку `actions` уведомлений содержат функции, модуль намеренно не сохраняет историю в `localStorage` и не сериализует её.
|
|
157
160
|
|
|
@@ -173,6 +176,23 @@ const id = notificationsStore.getState().actions.push({
|
|
|
173
176
|
|
|
174
177
|
Если `ttlMs` отсутствует, внутренний таймер всё равно запускается на 10 секунд. При этом в объекте уведомления поле остаётся `undefined`.
|
|
175
178
|
|
|
179
|
+
### Эфемерный toast без истории
|
|
180
|
+
|
|
181
|
+
Если текст нужен только во время краткого показа и не должен оставаться в общем центре уведомлений, задайте политику при добавлении:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const id = notificationsStore.getState().actions.push({
|
|
185
|
+
type: "info",
|
|
186
|
+
message: "Краткое уведомление",
|
|
187
|
+
retainInHistory: false
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Обновление без новой политики сохраняет отказ от истории.
|
|
191
|
+
notificationsStore.getState().actions.update(id, { message: "Обновлённое краткое уведомление" });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Видимость, действия и TTL работают как у обычного toast. Закрытие, истечение TTL и вытеснение из стека не добавляют его текст в историю. Отдельного реестра закрытых эфемерных уведомлений нет: после закрытия новый `push` или отсутствующий `upsert` с тем же ID создаёт новое уведомление с новой политикой.
|
|
195
|
+
|
|
176
196
|
### `update(id, patch)`
|
|
177
197
|
|
|
178
198
|
Частично меняет существующий элемент. Возвращает `true`, если элемент найден, иначе `false`.
|
|
@@ -187,6 +207,10 @@ const updated = notificationsStore.getState().actions.update(id, {
|
|
|
187
207
|
|
|
188
208
|
`id` и `createdAt` изменить нельзя. Таймер перестраивается только тогда, когда в patch явно присутствует ключ `ttlMs`. Изменение одного `message` не продлевает исходный срок жизни.
|
|
189
209
|
|
|
210
|
+
Если `retainInHistory` отсутствует или равен `undefined`, сохраняется текущая политика. Явный `false` удаляет из истории запись этого ID, не затрагивая остальные уведомления. Явный `true` возвращает активное уведомление в начало истории, если его там нет; существующая запись обновляется на прежнем месте без дубликата.
|
|
211
|
+
|
|
212
|
+
После `clearHistory()` обычное обновление не возвращает текст в историю, даже если у активного уведомления сохранился `retainInHistory: true`. Для возврата нужно снова явно передать `true` в patch. Если активного уведомления уже нет, `update` возвращает `false` и не создаёт запись истории.
|
|
213
|
+
|
|
190
214
|
### `upsert(input)`
|
|
191
215
|
|
|
192
216
|
Обновляет элемент с заданным `id`, а если его нет — добавляет новый.
|
|
@@ -200,11 +224,11 @@ notificationsStore.getState().actions.upsert({
|
|
|
200
224
|
});
|
|
201
225
|
```
|
|
202
226
|
|
|
203
|
-
Важная деталь: при обновлении существующего элемента `upsert` меняет `type`, `title`, `message`, `ttlMs` и `dismissible`, но не заменяет `actions`. Для явной смены действий используйте `update(id, { actions })`.
|
|
227
|
+
Важная деталь: при обновлении существующего элемента `upsert` меняет `type`, `title`, `message`, `ttlMs` и `dismissible`, но не заменяет `actions`. Для явной смены действий используйте `update(id, { actions })`. Поле `retainInHistory` следует правилам `update`: отсутствие или `undefined` сохраняет прежнюю политику, определённое значение меняет её явно. Если toast отсутствует, применяется обычный `push` с переданными параметрами.
|
|
204
228
|
|
|
205
229
|
### `dismiss(id)`, `clear()` и `clearHistory()`
|
|
206
230
|
|
|
207
|
-
`dismiss(id)` скрывает один toast и очищает его таймер. `clear()` скрывает весь toast-стек и очищает все таймеры. Обе операции сохраняют записи в `history
|
|
231
|
+
`dismiss(id)` скрывает один toast и очищает его таймер. `clear()` скрывает весь toast-стек и очищает все таймеры. Обе операции сохраняют существующие записи в `history` и не добавляют туда уведомления с отказом от истории.
|
|
208
232
|
|
|
209
233
|
`clearHistory()` явно очищает историю, не закрывая активные toast-уведомления. Чтобы полностью сбросить обе коллекции, последовательно вызовите `clear()` и `clearHistory()`.
|
|
210
234
|
|
|
@@ -239,7 +263,7 @@ notify.warning(message, options);
|
|
|
239
263
|
notify.error(message, options);
|
|
240
264
|
```
|
|
241
265
|
|
|
242
|
-
Сокращённые методы автоматически подставляют `type`, но принимают остальные параметры `push`, включая `id`, `title`, `ttlMs`, `dismissible` и `
|
|
266
|
+
Сокращённые методы автоматически подставляют `type`, но принимают остальные параметры `push`, включая `id`, `title`, `ttlMs`, `dismissible`, `actions` и `retainInHistory`.
|
|
243
267
|
|
|
244
268
|
### Долгая операция: `notify.progress()`
|
|
245
269
|
|
|
@@ -322,13 +346,13 @@ function App() {
|
|
|
322
346
|
|
|
323
347
|
| Экспорт | Назначение |
|
|
324
348
|
| ---------------------------- | -------------------------------------------------------- |
|
|
325
|
-
| `createNotificationsStore()` | Создать независимый Zustand-store toast-стека и истории
|
|
326
|
-
| `bindNotifications(store)` | Привязать store к глобальному фасаду `notify`
|
|
327
|
-
| `notify` | Императивно добавлять, обновлять и закрывать уведомления
|
|
328
|
-
| `Notification` | Полная модель сохранённого элемента
|
|
329
|
-
| `NotificationPushInput` | Параметры добавления элемента
|
|
330
|
-
| `NotificationUpdatePatch` | Допустимые поля частичного обновления
|
|
331
|
-
| `NotificationsState` |
|
|
332
|
-
| `NotificationsActions` | Действия toast-стека и явная очистка истории
|
|
333
|
-
| `NotificationsStoreApi` | Тип vanilla Zustand-store
|
|
334
|
-
| `NotificationAction` | Кнопка-действие уведомления
|
|
349
|
+
| `createNotificationsStore()` | Создать независимый Zustand-store toast-стека и истории |
|
|
350
|
+
| `bindNotifications(store)` | Привязать store к глобальному фасаду `notify` |
|
|
351
|
+
| `notify` | Императивно добавлять, обновлять и закрывать уведомления |
|
|
352
|
+
| `Notification` | Полная модель сохранённого элемента |
|
|
353
|
+
| `NotificationPushInput` | Параметры добавления элемента |
|
|
354
|
+
| `NotificationUpdatePatch` | Допустимые поля частичного обновления |
|
|
355
|
+
| `NotificationsState` | Активные `items` и история сохраняемых уведомлений |
|
|
356
|
+
| `NotificationsActions` | Действия toast-стека и явная очистка истории |
|
|
357
|
+
| `NotificationsStoreApi` | Тип vanilla Zustand-store |
|
|
358
|
+
| `NotificationAction` | Кнопка-действие уведомления |
|