@seedgrid/fe-components 2026.11.2 → 2026.11.3
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/ai/seedgrid-components.manifest.json +323 -65
- package/dist/buttons/SgButton.d.ts +6 -2
- package/dist/buttons/SgButton.d.ts.map +1 -1
- package/dist/buttons/SgButton.js +2 -2
- package/dist/buttons/SgButton.meta.js +3 -3
- package/dist/buttons/SgFloatActionButton.d.ts +2 -2
- package/dist/buttons/SgFloatActionButton.d.ts.map +1 -1
- package/dist/buttons/SgFloatActionButton.js +7 -7
- package/dist/buttons/SgFloatActionButton.meta.js +2 -2
- package/dist/buttons/SgSplitButton.d.ts +2 -2
- package/dist/buttons/SgSplitButton.d.ts.map +1 -1
- package/dist/buttons/SgSplitButton.js +4 -4
- package/dist/buttons/SgSplitButton.meta.js +3 -3
- package/dist/commons/SgAvatar.d.ts +2 -2
- package/dist/commons/SgAvatar.d.ts.map +1 -1
- package/dist/commons/SgAvatar.js +3 -3
- package/dist/commons/SgBadge.d.ts +1 -1
- package/dist/commons/SgBadge.d.ts.map +1 -1
- package/dist/commons/SgBadge.js +3 -3
- package/dist/generated/compiledStyles.d.ts +1 -1
- package/dist/generated/compiledStyles.d.ts.map +1 -1
- package/dist/generated/compiledStyles.js +1 -1
- package/dist/i18n/en-US.d.ts.map +1 -1
- package/dist/i18n/en-US.js +26 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +26 -0
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +26 -0
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/pt-BR.js +26 -0
- package/dist/i18n/pt-PT.d.ts.map +1 -1
- package/dist/i18n/pt-PT.js +26 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/inputs/SgAutocomplete.d.ts +1 -1
- package/dist/inputs/SgAutocomplete.d.ts.map +1 -1
- package/dist/inputs/SgAutocomplete.js +3 -3
- package/dist/inputs/SgCameraCapture.d.ts +1 -1
- package/dist/inputs/SgCameraCapture.d.ts.map +1 -1
- package/dist/inputs/SgCameraCapture.js +4 -4
- package/dist/inputs/SgCameraCapture.meta.js +1 -1
- package/dist/inputs/SgCheckboxGroup.d.ts +2 -2
- package/dist/inputs/SgCheckboxGroup.d.ts.map +1 -1
- package/dist/inputs/SgCheckboxGroup.js +12 -12
- package/dist/inputs/SgCombobox.js +5 -5
- package/dist/inputs/SgDatatable.js +11 -11
- package/dist/inputs/SgDatatable.meta.d.ts.map +1 -1
- package/dist/inputs/SgDatatable.meta.js +4 -1
- package/dist/inputs/SgFileUpload.d.ts +1 -1
- package/dist/inputs/SgFileUpload.d.ts.map +1 -1
- package/dist/inputs/SgFileUpload.js +2 -2
- package/dist/inputs/SgFileUpload.meta.js +1 -1
- package/dist/inputs/SgImageUpload.meta.js +1 -1
- package/dist/inputs/SgInputCurrency.d.ts.map +1 -1
- package/dist/inputs/SgInputCurrency.js +6 -4
- package/dist/inputs/SgInputDate.d.ts.map +1 -1
- package/dist/inputs/SgInputDate.js +3 -4
- package/dist/inputs/SgInputDateTime.d.ts.map +1 -1
- package/dist/inputs/SgInputDateTime.js +11 -0
- package/dist/inputs/SgInputNumber.d.ts.map +1 -1
- package/dist/inputs/SgInputNumber.js +6 -4
- package/dist/inputs/SgInputText.d.ts.map +1 -1
- package/dist/inputs/SgInputText.js +6 -4
- package/dist/inputs/SgInputTextArea.d.ts.map +1 -1
- package/dist/inputs/SgInputTextArea.js +4 -2
- package/dist/inputs/SgMultiSelect.meta.js +1 -1
- package/dist/inputs/SgMultiSelectChips.meta.js +1 -1
- package/dist/inputs/SgMultiSelectDropdown.js +1 -1
- package/dist/inputs/SgOrderList.d.ts +3 -3
- package/dist/inputs/SgOrderList.d.ts.map +1 -1
- package/dist/inputs/SgOrderList.js +13 -13
- package/dist/inputs/SgOrderList.meta.js +1 -1
- package/dist/inputs/SgPeriodSelector.d.ts +1 -1
- package/dist/inputs/SgPeriodSelector.d.ts.map +1 -1
- package/dist/inputs/SgPeriodSelector.js +3 -3
- package/dist/inputs/SgPickList.d.ts +3 -3
- package/dist/inputs/SgPickList.d.ts.map +1 -1
- package/dist/inputs/SgPickList.js +28 -28
- package/dist/inputs/SgPickList.meta.js +1 -1
- package/dist/inputs/SgRadioGroup.d.ts +2 -2
- package/dist/inputs/SgRadioGroup.d.ts.map +1 -1
- package/dist/inputs/SgRadioGroup.js +5 -5
- package/dist/inputs/SgRating.d.ts +1 -1
- package/dist/inputs/SgRating.d.ts.map +1 -1
- package/dist/inputs/SgRating.js +7 -7
- package/dist/inputs/SgRating.meta.js +1 -1
- package/dist/inputs/SgSlider.d.ts +1 -1
- package/dist/inputs/SgSlider.d.ts.map +1 -1
- package/dist/inputs/SgSlider.js +3 -3
- package/dist/inputs/SgSlider.meta.js +1 -1
- package/dist/inputs/SgStepperInput.d.ts +2 -2
- package/dist/inputs/SgStepperInput.d.ts.map +1 -1
- package/dist/inputs/SgStepperInput.js +4 -4
- package/dist/inputs/SgStepperInput.meta.js +1 -1
- package/dist/inputs/SgTextEditor.d.ts +1 -1
- package/dist/inputs/SgTextEditor.d.ts.map +1 -1
- package/dist/inputs/SgTextEditor.js +14 -14
- package/dist/inputs/SgToggleSwitch.meta.js +1 -1
- package/dist/inputs/SgYearPartSelector.d.ts +1 -1
- package/dist/inputs/SgYearPartSelector.d.ts.map +1 -1
- package/dist/inputs/SgYearPartSelector.js +1 -2
- package/dist/inputs/SgYearPartSelector.meta.js +2 -2
- package/dist/inputs/dateTimeLogic.d.ts +7 -1
- package/dist/inputs/dateTimeLogic.d.ts.map +1 -1
- package/dist/inputs/dateTimeLogic.js +17 -4
- package/dist/inputs/useSgMultiSelect.d.ts +1 -1
- package/dist/inputs/useSgMultiSelect.d.ts.map +1 -1
- package/dist/inputs/useSgMultiSelect.js +1 -1
- package/dist/layout/SgAccordion.d.ts +1 -1
- package/dist/layout/SgAccordion.d.ts.map +1 -1
- package/dist/layout/SgAccordion.js +3 -3
- package/dist/layout/SgBreadcrumb.d.ts +1 -1
- package/dist/layout/SgBreadcrumb.d.ts.map +1 -1
- package/dist/layout/SgBreadcrumb.js +1 -1
- package/dist/layout/SgCard.d.ts +1 -1
- package/dist/layout/SgCard.d.ts.map +1 -1
- package/dist/layout/SgCard.js +14 -14
- package/dist/layout/SgMenu.d.ts +1 -1
- package/dist/layout/SgMenu.d.ts.map +1 -1
- package/dist/layout/SgMenu.js +15 -15
- package/dist/layout/SgPageControl.d.ts +1 -1
- package/dist/layout/SgPageControl.d.ts.map +1 -1
- package/dist/layout/SgPageControl.js +4 -4
- package/dist/layout/SgPageControlPage.meta.js +2 -2
- package/dist/layout/SgToolBar.d.ts +1 -1
- package/dist/layout/SgToolBar.d.ts.map +1 -1
- package/dist/layout/SgToolBar.js +4 -4
- package/dist/layout/SgToolbarIconButton.meta.js +2 -2
- package/dist/layout/SgTreeView.d.ts +4 -4
- package/dist/layout/SgTreeView.d.ts.map +1 -1
- package/dist/layout/SgTreeView.js +6 -6
- package/dist/menus/SgDockMenu.d.ts +1 -1
- package/dist/menus/SgDockMenu.d.ts.map +1 -1
- package/dist/menus/SgDockMenu.js +4 -4
- package/dist/notifications/SgNotificationBell.d.ts +51 -0
- package/dist/notifications/SgNotificationBell.d.ts.map +1 -0
- package/dist/notifications/SgNotificationBell.js +348 -0
- package/dist/notifications/SgNotificationBell.meta.d.ts +4 -0
- package/dist/notifications/SgNotificationBell.meta.d.ts.map +1 -0
- package/dist/notifications/SgNotificationBell.meta.js +75 -0
- package/dist/notifications/notificationBellLogic.d.ts +148 -0
- package/dist/notifications/notificationBellLogic.d.ts.map +1 -0
- package/dist/notifications/notificationBellLogic.js +244 -0
- package/dist/overlay/SgConfirmationDialog.d.ts +1 -1
- package/dist/overlay/SgConfirmationDialog.d.ts.map +1 -1
- package/dist/overlay/SgConfirmationDialog.js +1 -1
- package/dist/overlay/SgPopup.d.ts +1 -1
- package/dist/overlay/SgPopup.d.ts.map +1 -1
- package/dist/overlay/SgPopup.js +1 -1
- package/dist/realtime/SgRealtimeProvider.d.ts +5 -0
- package/dist/realtime/SgRealtimeProvider.d.ts.map +1 -1
- package/dist/realtime/SgRealtimeProvider.js +31 -8
- package/dist/sandbox.cjs +62 -62
- package/dist/style.css +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Logica pura do SgNotificationBell (P129). Sem React, sem rede, sem relogio: tudo recebe o que precisa por
|
|
2
|
+
// parametro, para que o Flutter (notification_bell_logic.dart) produza exatamente o mesmo resultado. Os vetores de
|
|
3
|
+
// paridade sao gerados DESTE arquivo (seedgrid-fe-flutter/.../notification_bell_vectors/generate.mjs).
|
|
4
|
+
//
|
|
5
|
+
// Contrato das rotas: extension `user-notifications` (P130). O aviso do Pulse so traz identificadores; o historico
|
|
6
|
+
// (fonte de verdade) vem da API.
|
|
7
|
+
export const NOTIFICATIONS_CHANNEL = "user-notifications";
|
|
8
|
+
export const DEFAULT_EVENT_PATTERN = "user-notification.*";
|
|
9
|
+
export const DEFAULT_MAX_VISIBLE = 100;
|
|
10
|
+
export const BADGE_MAX = 99;
|
|
11
|
+
export const DEFAULT_POLL_INTERVAL_MS = 45000;
|
|
12
|
+
export const MIN_POLL_INTERVAL_MS = 30000;
|
|
13
|
+
export const MAX_POLL_INTERVAL_MS = 60000;
|
|
14
|
+
export const MAX_POLL_BACKOFF_MS = 300000;
|
|
15
|
+
/** ISO-8601 em ms. Frações além de 3 dígitos (microssegundos do servidor) são truncadas, igual nas duas plataformas. */
|
|
16
|
+
function time(iso) {
|
|
17
|
+
const t = Date.parse(iso.replace(/(\.\d{3})\d+/, "$1"));
|
|
18
|
+
return Number.isNaN(t) ? 0 : t;
|
|
19
|
+
}
|
|
20
|
+
/** Mais nova primeiro; empate pelo `publicId` decrescente (como o servidor: ORDER BY created_at DESC, id DESC). */
|
|
21
|
+
export function sortNotifications(items) {
|
|
22
|
+
return [...items].sort((a, b) => {
|
|
23
|
+
const d = time(b.createdAt) - time(a.createdAt);
|
|
24
|
+
if (d !== 0)
|
|
25
|
+
return d;
|
|
26
|
+
return a.publicId < b.publicId ? 1 : a.publicId > b.publicId ? -1 : 0;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Teto da lista: de 1 a 100 (uma pagina da rota); sem valor ou invalido, 100. */
|
|
30
|
+
export function clampMaxVisible(value) {
|
|
31
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
32
|
+
return DEFAULT_MAX_VISIBLE;
|
|
33
|
+
return Math.min(DEFAULT_MAX_VISIBLE, Math.max(1, Math.trunc(value)));
|
|
34
|
+
}
|
|
35
|
+
/** Vencida (`expiresAt` ate agora): o servidor nao as lista, o sino tambem nao as mostra. */
|
|
36
|
+
export function isExpired(item, now) {
|
|
37
|
+
return !!item.expiresAt && time(item.expiresAt) <= time(now);
|
|
38
|
+
}
|
|
39
|
+
/** Lista que o sino mostra: sem repetidos (vale o ultimo), sem vencidas (se `now` vier), ordenada, no maximo `max` itens. */
|
|
40
|
+
export function normalizeList(items, max = DEFAULT_MAX_VISIBLE, now) {
|
|
41
|
+
const byId = new Map();
|
|
42
|
+
for (const item of items)
|
|
43
|
+
if (!now || !isExpired(item, now))
|
|
44
|
+
byId.set(item.publicId, item);
|
|
45
|
+
return sortNotifications([...byId.values()]).slice(0, Math.max(0, max));
|
|
46
|
+
}
|
|
47
|
+
/** Contador mostrado: o do servidor (`GET /unread-count`) quando se sabe, senao as nao lidas da lista local. */
|
|
48
|
+
export function displayUnread(serverCount, items) {
|
|
49
|
+
return typeof serverCount === "number" && serverCount >= 0 ? Math.trunc(serverCount) : unreadCount(items);
|
|
50
|
+
}
|
|
51
|
+
/** So conta as nao lidas. */
|
|
52
|
+
export function unreadCount(items) {
|
|
53
|
+
return items.reduce((n, item) => (item.read ? n : n + 1), 0);
|
|
54
|
+
}
|
|
55
|
+
/** Texto do contador: vazio com zero, `99+` acima de 99. */
|
|
56
|
+
export function badgeText(count) {
|
|
57
|
+
const n = Math.trunc(count);
|
|
58
|
+
if (!Number.isFinite(n) || n < 1)
|
|
59
|
+
return "";
|
|
60
|
+
return n > BADGE_MAX ? `${BADGE_MAX}+` : String(n);
|
|
61
|
+
}
|
|
62
|
+
export function markRead(items, publicId, readAt) {
|
|
63
|
+
return items.map((item) => (item.publicId === publicId && !item.read ? { ...item, read: true, readAt } : item));
|
|
64
|
+
}
|
|
65
|
+
export function markAllRead(items, readAt) {
|
|
66
|
+
return items.map((item) => (item.read ? item : { ...item, read: true, readAt }));
|
|
67
|
+
}
|
|
68
|
+
export function removeNotification(items, publicId) {
|
|
69
|
+
return items.filter((item) => item.publicId !== publicId);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Itens nao lidos que chegaram de verdade: desconhecidos e mais novos que o mais novo da lista anterior (uma nao lida
|
|
73
|
+
* antiga que so apareceu agora, por teto ou por reler, nao e noticia). Sao eles que fazem o sino animar.
|
|
74
|
+
*/
|
|
75
|
+
export function newUnread(previous, next) {
|
|
76
|
+
const known = new Set(previous.map((item) => item.publicId));
|
|
77
|
+
const newest = previous.reduce((max, item) => Math.max(max, time(item.createdAt)), Number.NEGATIVE_INFINITY);
|
|
78
|
+
return next.filter((item) => !item.read && !known.has(item.publicId) && time(item.createdAt) >= newest);
|
|
79
|
+
}
|
|
80
|
+
/** `user-notification.*` casa `user-notification.created` e `user-notification.changed`. */
|
|
81
|
+
export function matchesEventPattern(pattern, event) {
|
|
82
|
+
if (pattern === "*" || pattern === event)
|
|
83
|
+
return true;
|
|
84
|
+
if (pattern.endsWith(".*"))
|
|
85
|
+
return event.startsWith(pattern.slice(0, -1));
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* `created` com o identificador: busca so a nova. `created` sem identificador e `changed` (ler, excluir, limpar em
|
|
90
|
+
* outra aba): le a lista de novo. Outro canal ou evento fora do padrao: ignora.
|
|
91
|
+
*/
|
|
92
|
+
export function planEvent(pattern, channel, event, data) {
|
|
93
|
+
if (channel !== NOTIFICATIONS_CHANNEL || !matchesEventPattern(pattern, event))
|
|
94
|
+
return { action: "ignore" };
|
|
95
|
+
if (event === "user-notification.created") {
|
|
96
|
+
const id = data && typeof data === "object" ? data.notificationPublicId : undefined;
|
|
97
|
+
if (typeof id === "string" && id.length > 0)
|
|
98
|
+
return { action: "fetch", publicId: id };
|
|
99
|
+
}
|
|
100
|
+
return { action: "refresh" };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Polling so quando nao ha tempo real nem esperanca de ter logo: `disabled` (enabled:false) e `unavailable` /
|
|
104
|
+
* `auth-error` (Pulse fora do ar). Conectando ou reconectando, o SDK resolve sozinho.
|
|
105
|
+
*/
|
|
106
|
+
export function pollingReason(status) {
|
|
107
|
+
if (status === "disabled")
|
|
108
|
+
return "disabled";
|
|
109
|
+
if (status === "unavailable" || status === "auth-error")
|
|
110
|
+
return "unavailable";
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/** Ao (re)conectar, le a lista uma vez para recuperar o que passou enquanto estava fora. */
|
|
114
|
+
export function shouldRefreshOnStatus(previous, next) {
|
|
115
|
+
return next === "connected" && previous !== "connected";
|
|
116
|
+
}
|
|
117
|
+
/** Intervalo pedido, preso entre 30 e 60 s (padrao 45 s). */
|
|
118
|
+
export function clampPollInterval(ms) {
|
|
119
|
+
if (typeof ms !== "number" || !Number.isFinite(ms))
|
|
120
|
+
return DEFAULT_POLL_INTERVAL_MS;
|
|
121
|
+
return Math.min(MAX_POLL_INTERVAL_MS, Math.max(MIN_POLL_INTERVAL_MS, Math.round(ms)));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Espera ate a proxima consulta: intervalo, dobrado a cada falha seguida (ate 5 min), com jitter de -10% a +10%, e nunca acima de 5 min (o teto vale depois do jitter).
|
|
125
|
+
* `random` entra de fora (0 a 1) para o resultado ser testavel.
|
|
126
|
+
*/
|
|
127
|
+
export function nextPollDelay(baseMs, failures, random) {
|
|
128
|
+
const base = clampPollInterval(baseMs);
|
|
129
|
+
const doublings = Number.isFinite(failures) ? Math.min(Math.max(0, Math.trunc(failures)), 6) : 0;
|
|
130
|
+
const backoff = Math.min(base * 2 ** doublings, MAX_POLL_BACKOFF_MS);
|
|
131
|
+
const r = Number.isFinite(random) ? Math.min(1, Math.max(0, random)) : 0.5;
|
|
132
|
+
return Math.min(MAX_POLL_BACKOFF_MS, Math.round(backoff * (0.9 + 0.2 * r)));
|
|
133
|
+
}
|
|
134
|
+
export const EMPTY_POLLING_STATS = { count: 0, lastAt: null, reason: null };
|
|
135
|
+
export function recordPollingTick(stats, at, reason) {
|
|
136
|
+
return { count: stats.count + 1, lastAt: at, reason };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Qual das telas mostrar. Com erro e lista ja carregada, a lista continua (a falha vira aviso); sem nada para
|
|
140
|
+
* mostrar, erro vence vazio.
|
|
141
|
+
*/
|
|
142
|
+
export function viewState(input) {
|
|
143
|
+
if (input.count > 0)
|
|
144
|
+
return "list";
|
|
145
|
+
if (input.failed)
|
|
146
|
+
return "error";
|
|
147
|
+
if (!input.loaded)
|
|
148
|
+
return "loading";
|
|
149
|
+
return input.unreadOnly ? "caughtUp" : "empty";
|
|
150
|
+
}
|
|
151
|
+
/** O que a lista mostra: em "Nao lidas" so as ainda nao clicadas; em "Todas", tudo. */
|
|
152
|
+
export function visibleItems(items, unreadOnly) {
|
|
153
|
+
return unreadOnly ? items.filter((n) => !n.read) : [...items];
|
|
154
|
+
}
|
|
155
|
+
/** Query da lista: em "Nao lidas" pede so as nao lidas ao servidor (`unreadOnly=true`, que ja entra na ETag). */
|
|
156
|
+
export function listQuery(page, size, unreadOnly) {
|
|
157
|
+
return `?page=${page}&size=${size}${unreadOnly ? "&unreadOnly=true" : ""}`;
|
|
158
|
+
}
|
|
159
|
+
/** Clicou numa notificacao: em "Nao lidas" ela sai da lista; em "Todas" so perde o destaque. */
|
|
160
|
+
export function afterOpen(items, publicId, unreadOnly, readAt) {
|
|
161
|
+
return unreadOnly ? removeNotification(items, publicId) : markRead(items, publicId, readAt);
|
|
162
|
+
}
|
|
163
|
+
/** Valor inicial do interruptor: sem interruptor, o painel mostra tudo; com ele, vale `defaultUnreadOnly` (padrao true). */
|
|
164
|
+
export function initialUnreadOnly(showUnreadToggle, defaultUnreadOnly) {
|
|
165
|
+
if (showUnreadToggle === false)
|
|
166
|
+
return false;
|
|
167
|
+
return defaultUnreadOnly ?? true;
|
|
168
|
+
}
|
|
169
|
+
/** Idade da notificacao em partes; o texto ("ha 5 min") e do i18n de cada plataforma. */
|
|
170
|
+
/** Data que da para mostrar (um `createdAt` invalido nao vira 1970 na tela). */
|
|
171
|
+
export function isValidInstant(iso) {
|
|
172
|
+
return !Number.isNaN(Date.parse(iso.replace(/(.d{3})d+/, "$1")));
|
|
173
|
+
}
|
|
174
|
+
export function relativeTime(createdAt, now) {
|
|
175
|
+
if (!isValidInstant(createdAt) || !isValidInstant(now))
|
|
176
|
+
return { unit: "date" };
|
|
177
|
+
const seconds = Math.floor((time(now) - time(createdAt)) / 1000);
|
|
178
|
+
if (seconds < 60)
|
|
179
|
+
return { unit: "now" };
|
|
180
|
+
const minutes = Math.floor(seconds / 60);
|
|
181
|
+
if (minutes < 60)
|
|
182
|
+
return { unit: "minute", value: minutes };
|
|
183
|
+
const hours = Math.floor(minutes / 60);
|
|
184
|
+
if (hours < 24)
|
|
185
|
+
return { unit: "hour", value: hours };
|
|
186
|
+
const days = Math.floor(hours / 24);
|
|
187
|
+
if (days < 7)
|
|
188
|
+
return { unit: "day", value: days };
|
|
189
|
+
return { unit: "date" };
|
|
190
|
+
}
|
|
191
|
+
/** Largura do painel (26rem) e altura maxima (32rem), em px. */
|
|
192
|
+
export const PANEL_WIDTH = 416;
|
|
193
|
+
export const PANEL_MAX_HEIGHT = 512;
|
|
194
|
+
function clampNumber(value, min, max) {
|
|
195
|
+
return Math.max(min, Math.min(value, Math.max(min, max)));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Onde o painel fica, em coordenadas da janela, sem sair dela. `auto` tenta embaixo, em cima, a direita e a esquerda
|
|
199
|
+
* (nessa ordem) e fica com o primeiro lado em que o painel cabe inteiro; se nenhum couber, usa embaixo ou em cima,
|
|
200
|
+
* o que tiver mais espaco. O lado forcado (`top`, `right`...) e respeitado. O painel se alinha ao fim do sino e e
|
|
201
|
+
* empurrado para dentro da janela. `panelHeight` e a altura real do painel (ate PANEL_MAX_HEIGHT).
|
|
202
|
+
*/
|
|
203
|
+
export function panelPosition(anchor, panel, viewport, placement = "auto", offset = 8, padding = 8) {
|
|
204
|
+
const width = Math.min(panel.width, viewport.width - padding * 2);
|
|
205
|
+
const height = Math.min(panel.height, PANEL_MAX_HEIGHT, viewport.height - padding * 2);
|
|
206
|
+
const space = {
|
|
207
|
+
top: anchor.top - padding,
|
|
208
|
+
bottom: viewport.height - anchor.bottom - padding,
|
|
209
|
+
left: anchor.left - padding,
|
|
210
|
+
right: viewport.width - anchor.right - padding
|
|
211
|
+
};
|
|
212
|
+
const fits = {
|
|
213
|
+
bottom: space.bottom >= height + offset,
|
|
214
|
+
top: space.top >= height + offset,
|
|
215
|
+
right: space.right >= width + offset,
|
|
216
|
+
left: space.left >= width + offset
|
|
217
|
+
};
|
|
218
|
+
let side;
|
|
219
|
+
if (placement !== "auto")
|
|
220
|
+
side = placement;
|
|
221
|
+
else if (fits.bottom)
|
|
222
|
+
side = "bottom";
|
|
223
|
+
else if (fits.top)
|
|
224
|
+
side = "top";
|
|
225
|
+
else if (fits.right)
|
|
226
|
+
side = "right";
|
|
227
|
+
else if (fits.left)
|
|
228
|
+
side = "left";
|
|
229
|
+
else
|
|
230
|
+
side = space.bottom >= space.top ? "bottom" : "top";
|
|
231
|
+
let top;
|
|
232
|
+
let left;
|
|
233
|
+
if (side === "bottom" || side === "top") {
|
|
234
|
+
left = clampNumber(anchor.right - width, padding, viewport.width - width - padding);
|
|
235
|
+
top = side === "bottom" ? anchor.bottom + offset : anchor.top - offset - height;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
top = clampNumber(anchor.top, padding, viewport.height - height - padding);
|
|
239
|
+
left = side === "right" ? anchor.right + offset : anchor.left - offset - width;
|
|
240
|
+
}
|
|
241
|
+
top = clampNumber(top, padding, viewport.height - height - padding);
|
|
242
|
+
left = clampNumber(left, padding, viewport.width - width - padding);
|
|
243
|
+
return { top: Math.round(top), left: Math.round(left), side, maxHeight: Math.round(height) };
|
|
244
|
+
}
|
|
@@ -7,7 +7,7 @@ export type SgConfirmationDialogButtonConfig = {
|
|
|
7
7
|
severity?: SgButtonProps["severity"];
|
|
8
8
|
appearance?: SgButtonProps["appearance"];
|
|
9
9
|
shape?: SgButtonProps["shape"];
|
|
10
|
-
|
|
10
|
+
enabled?: boolean;
|
|
11
11
|
className?: string;
|
|
12
12
|
onClick?: () => void;
|
|
13
13
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgConfirmationDialog.d.ts","sourceRoot":"","sources":["../../src/overlay/SgConfirmationDialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAAY,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEjF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,
|
|
1
|
+
{"version":3,"file":"SgConfirmationDialog.d.ts","sourceRoot":"","sources":["../../src/overlay/SgConfirmationDialog.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAAY,KAAK,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEjF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,QAAQ,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,aAAa,EACb,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,eAAe,CAC1E,GAAG;IACF,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC/B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,WAAW,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACvC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,gCAAgC,CAAC;IAChD,aAAa,CAAC,EAAE,gCAAgC,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAoEF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,yBAAyB,CAAC,2CA2J9E;yBA3Je,oBAAoB"}
|
|
@@ -90,6 +90,6 @@ export function SgConfirmationDialog(props) {
|
|
|
90
90
|
}
|
|
91
91
|
: undefined;
|
|
92
92
|
const messageBlock = (_jsx("div", { className: "text-sm text-muted-foreground", children: message }));
|
|
93
|
-
return (_jsx(SgDialog, { ...dialogProps, open: open, onOpenChange: handleDialogOpenChange, onClose: onClose, closeable: closeable, severity: severity, showTopAccent: showSeverityAccent, customColor: customColor, elevation: elevation, title: title, size: size, footer: (_jsxs(_Fragment, { children: [_jsx(SgButton, { size: "sm", appearance: cancelButton?.appearance ?? "outline", severity: cancelSeverity, shape: cancelButton?.shape ?? "rounded",
|
|
93
|
+
return (_jsx(SgDialog, { ...dialogProps, open: open, onOpenChange: handleDialogOpenChange, onClose: onClose, closeable: closeable, severity: severity, showTopAccent: showSeverityAccent, customColor: customColor, elevation: elevation, title: title, size: size, footer: (_jsxs(_Fragment, { children: [_jsx(SgButton, { size: "sm", appearance: cancelButton?.appearance ?? "outline", severity: cancelSeverity, shape: cancelButton?.shape ?? "rounded", enabled: !((cancelButton?.enabled === false)), className: cancelButton?.className, customColors: resolvedCancelCustomColors, leftIcon: cancelIcon, onClick: handleCancel, children: cancelLabel }), _jsx(SgButton, { size: "sm", appearance: confirmButton?.appearance ?? "solid", severity: confirmSeverity, shape: confirmButton?.shape ?? "rounded", enabled: !((confirmButton?.enabled === false)), className: confirmButton?.className, leftIcon: confirmIcon, onClick: handleConfirm, children: confirmLabel })] })), children: resolvedIcon ? (iconPlacement === "top" ? (_jsxs("div", { className: "flex flex-col items-start gap-3", children: [_jsx("div", { className: "shrink-0", children: resolvedIcon }), messageBlock] })) : (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "shrink-0", children: resolvedIcon }), _jsx("div", { className: "min-w-0", children: messageBlock })] }))) : (messageBlock) }));
|
|
94
94
|
}
|
|
95
95
|
SgConfirmationDialog.displayName = "SgConfirmationDialog";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgPopup.d.ts","sourceRoot":"","sources":["../../src/overlay/SgPopup.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAoB/B,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC5E,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEtD,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,WAAW,GACX,SAAS,GACT,MAAM,GACN,SAAS,GACT,MAAM,GACN,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,
|
|
1
|
+
{"version":3,"file":"SgPopup.d.ts","sourceRoot":"","sources":["../../src/overlay/SgPopup.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAoB/B,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC5E,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzE,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEtD,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,WAAW,GACX,SAAS,GACT,MAAM,GACN,SAAS,GACT,MAAM,GACN,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAExC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3B,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAwHF,wBAAgB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,4BA4LpD;yBA5Le,OAAO"}
|
package/dist/overlay/SgPopup.js
CHANGED
|
@@ -210,7 +210,7 @@ export function SgPopup(props) {
|
|
|
210
210
|
}, role: "dialog", "aria-modal": "false", "aria-label": resolvedAriaLabel, children: [title ? (_jsx("div", { className: "bg-primary px-3 py-2 text-xs font-medium text-primary-foreground", children: title })) : null, subtitle ? (_jsx("div", { className: "border-b border-border bg-background px-3 py-2 text-xs text-muted-foreground", children: subtitle })) : null, children ? _jsx("div", { className: "p-2", children: children }) : null, actions && actions.length > 0 ? (_jsx("div", { className: cn(children ? "border-t border-border" : "", "p-1"), children: actions.map((a, idx) => {
|
|
211
211
|
const hasSeverity = a.severity !== undefined;
|
|
212
212
|
const sev = a.severity ?? "plain";
|
|
213
|
-
return (_jsx("button", { type: "button", disabled: a.
|
|
213
|
+
return (_jsx("button", { type: "button", disabled: a.enabled === false, title: a.hint, className: cn("w-full select-none rounded px-3 py-2 text-left text-sm", "transition-[filter,background-color] duration-150", "disabled:opacity-50 disabled:cursor-not-allowed", idx > 0 ? "border-t border-border" : "", hasSeverity ? ACTION_CLS[sev] : ACTION_DEFAULT_CLS), onClick: () => {
|
|
214
214
|
a.onClick?.();
|
|
215
215
|
if (a.closeOnClick ?? true)
|
|
216
216
|
close();
|
|
@@ -63,6 +63,11 @@ export declare function matchesSgRealtimeEvent(pattern: string, event: string):
|
|
|
63
63
|
* {@link SgRealtimeProvider}, não faz nada.
|
|
64
64
|
*/
|
|
65
65
|
export declare function useSgRealtimeEvent(channel: string, events: string | string[], handler: (event: SgRealtimeEvent) => void): void;
|
|
66
|
+
/**
|
|
67
|
+
* Ha um {@link SgRealtimeProvider} acima? Com `channel`, so conta se ele assina esse channel (o sino so reaproveita a
|
|
68
|
+
* conexao que ouve `user-notifications`; senao ficaria surdo e abre a propria).
|
|
69
|
+
*/
|
|
70
|
+
export declare function useHasSgRealtimeProvider(channel?: string): boolean;
|
|
66
71
|
/** Estado do realtime. `connected` falso ⇒ a tela usa o polling de reserva. */
|
|
67
72
|
export declare function useSgRealtimeStatus(): {
|
|
68
73
|
status: SgRealtimeStatus;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SgRealtimeProvider.d.ts","sourceRoot":"","sources":["../../src/realtime/SgRealtimeProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,uCAAuC,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC;AAEhI,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,4FAA4F;AAC5F,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACtD,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sFAAsF;IACtF,cAAc,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC,CAAC;CAC5E,CAAC;
|
|
1
|
+
{"version":3,"file":"SgRealtimeProvider.d.ts","sourceRoot":"","sources":["../../src/realtime/SgRealtimeProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,uCAAuC,CAAC;AAE/C;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC;AAEhI,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,4FAA4F;AAC5F,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACtD,kGAAkG;IAClG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sFAAsF;IACtF,cAAc,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC,CAAC;CAC5E,CAAC;AAyCF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,aAAsC,EACtC,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,OAAc,EACd,cAAc,EACf,EAAE,uBAAuB,2CA6GzB;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAO9E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,OAAO,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GACxC,IAAI,CAeN;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAIlE;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,IAAI;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAItF"}
|
|
@@ -22,7 +22,7 @@ function mapState(state) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
async function requestToken(endpoint, init) {
|
|
25
|
-
const response = await fetch(endpoint, {
|
|
25
|
+
const response = await fetch(endpoint, { credentials: "same-origin", ...init, method: "POST" });
|
|
26
26
|
if (!response.ok) {
|
|
27
27
|
throw new Error(`token de realtime indisponivel (HTTP ${response.status})`);
|
|
28
28
|
}
|
|
@@ -49,6 +49,7 @@ export function SgRealtimeProvider({ children, channels, tokenEndpoint = DEFAULT
|
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
51
51
|
let cancelled = false;
|
|
52
|
+
let retryTimer;
|
|
52
53
|
const client = new PulseSubscriberClient();
|
|
53
54
|
const getConnectResponse = () => {
|
|
54
55
|
const { tokenEndpoint: endpoint, tokenRequestInit: init, fetchToken: custom } = optionsRef.current;
|
|
@@ -77,14 +78,24 @@ export function SgRealtimeProvider({ children, channels, tokenEndpoint = DEFAULT
|
|
|
77
78
|
});
|
|
78
79
|
void (async () => {
|
|
79
80
|
setStatus("connecting");
|
|
81
|
+
// Falha temporaria (503, 429, rede) nao e definitiva: fica "unavailable" (a tela usa o polling) e tenta de novo
|
|
82
|
+
// com espera crescente (5 s ate 60 s). So `enabled:false` e definitivo.
|
|
80
83
|
let first;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
for (let attempt = 0; !first; attempt += 1) {
|
|
85
|
+
try {
|
|
86
|
+
first = await getConnectResponse();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
if (cancelled)
|
|
90
|
+
return;
|
|
86
91
|
setStatus("unavailable");
|
|
87
|
-
|
|
92
|
+
await new Promise((resolve) => {
|
|
93
|
+
retryTimer = setTimeout(resolve, Math.min(60000, 5000 * 2 ** attempt));
|
|
94
|
+
});
|
|
95
|
+
if (cancelled)
|
|
96
|
+
return;
|
|
97
|
+
setStatus("connecting");
|
|
98
|
+
}
|
|
88
99
|
}
|
|
89
100
|
if (cancelled)
|
|
90
101
|
return;
|
|
@@ -118,6 +129,8 @@ export function SgRealtimeProvider({ children, channels, tokenEndpoint = DEFAULT
|
|
|
118
129
|
})();
|
|
119
130
|
return () => {
|
|
120
131
|
cancelled = true;
|
|
132
|
+
if (retryTimer)
|
|
133
|
+
clearTimeout(retryTimer);
|
|
121
134
|
offState();
|
|
122
135
|
offMessage();
|
|
123
136
|
void client.disconnect().catch(() => undefined);
|
|
@@ -129,7 +142,7 @@ export function SgRealtimeProvider({ children, channels, tokenEndpoint = DEFAULT
|
|
|
129
142
|
listenersRef.current.delete(listener);
|
|
130
143
|
};
|
|
131
144
|
}, []);
|
|
132
|
-
const value = React.useMemo(() => ({ status, addListener }), [status, addListener]);
|
|
145
|
+
const value = React.useMemo(() => ({ status, channels, addListener }), [status, channelsKey, addListener]);
|
|
133
146
|
return _jsx(SgRealtimeContext.Provider, { value: value, children: children });
|
|
134
147
|
}
|
|
135
148
|
/** `backup.finished.*` casa `backup.finished.success` e `backup.finished.error`. */
|
|
@@ -162,6 +175,16 @@ export function useSgRealtimeEvent(channel, events, handler) {
|
|
|
162
175
|
});
|
|
163
176
|
}, [context, channel, patterns]);
|
|
164
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Ha um {@link SgRealtimeProvider} acima? Com `channel`, so conta se ele assina esse channel (o sino so reaproveita a
|
|
180
|
+
* conexao que ouve `user-notifications`; senao ficaria surdo e abre a propria).
|
|
181
|
+
*/
|
|
182
|
+
export function useHasSgRealtimeProvider(channel) {
|
|
183
|
+
const context = React.useContext(SgRealtimeContext);
|
|
184
|
+
if (!context)
|
|
185
|
+
return false;
|
|
186
|
+
return channel === undefined || context.channels.includes(channel);
|
|
187
|
+
}
|
|
165
188
|
/** Estado do realtime. `connected` falso ⇒ a tela usa o polling de reserva. */
|
|
166
189
|
export function useSgRealtimeStatus() {
|
|
167
190
|
const context = React.useContext(SgRealtimeContext);
|