@seedgrid/fe-components 2026.11.1 → 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 +1527 -89
- 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 +55 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +55 -0
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +55 -0
- package/dist/i18n/pt-BR.d.ts.map +1 -1
- package/dist/i18n/pt-BR.js +55 -0
- package/dist/i18n/pt-PT.d.ts.map +1 -1
- package/dist/i18n/pt-PT.js +55 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -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 +25 -0
- package/dist/inputs/SgCameraCapture.d.ts.map +1 -0
- package/dist/inputs/SgCameraCapture.js +114 -0
- package/dist/inputs/SgCameraCapture.meta.d.ts +4 -0
- package/dist/inputs/SgCameraCapture.meta.d.ts.map +1 -0
- package/dist/inputs/SgCameraCapture.meta.js +63 -0
- 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 +69 -0
- package/dist/inputs/SgFileUpload.d.ts.map +1 -0
- package/dist/inputs/SgFileUpload.js +234 -0
- package/dist/inputs/SgFileUpload.meta.d.ts +4 -0
- package/dist/inputs/SgFileUpload.meta.d.ts.map +1 -0
- package/dist/inputs/SgFileUpload.meta.js +71 -0
- package/dist/inputs/SgImageUpload.d.ts +20 -0
- package/dist/inputs/SgImageUpload.d.ts.map +1 -0
- package/dist/inputs/SgImageUpload.js +74 -0
- package/dist/inputs/SgImageUpload.meta.d.ts +4 -0
- package/dist/inputs/SgImageUpload.meta.d.ts.map +1 -0
- package/dist/inputs/SgImageUpload.meta.js +73 -0
- 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 +29 -0
- package/dist/inputs/SgInputDateTime.d.ts.map +1 -0
- package/dist/inputs/SgInputDateTime.js +141 -0
- package/dist/inputs/SgInputDateTime.meta.d.ts +4 -0
- package/dist/inputs/SgInputDateTime.meta.d.ts.map +1 -0
- package/dist/inputs/SgInputDateTime.meta.js +73 -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/cameraLogic.d.ts +27 -0
- package/dist/inputs/cameraLogic.d.ts.map +1 -0
- package/dist/inputs/cameraLogic.js +57 -0
- package/dist/inputs/dateTimeLogic.d.ts +28 -0
- package/dist/inputs/dateTimeLogic.d.ts.map +1 -0
- package/dist/inputs/dateTimeLogic.js +153 -0
- package/dist/inputs/fileUploadLogic.d.ts +62 -0
- package/dist/inputs/fileUploadLogic.d.ts.map +1 -0
- package/dist/inputs/fileUploadLogic.js +254 -0
- package/dist/inputs/imageUploadLogic.d.ts +31 -0
- package/dist/inputs/imageUploadLogic.d.ts.map +1 -0
- package/dist/inputs/imageUploadLogic.js +70 -0
- 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 +6 -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 +77 -66
- package/dist/style.css +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** Item da caixa do usuario, como a API devolve. `payload` so tem identificadores. */
|
|
2
|
+
export type SgNotification = {
|
|
3
|
+
publicId: string;
|
|
4
|
+
/** Livre (ex.: `report.ready`); o sino nao interpreta, so mapeia para icone e acao. */
|
|
5
|
+
type: string;
|
|
6
|
+
title: string;
|
|
7
|
+
message?: string | null;
|
|
8
|
+
payload?: Record<string, unknown> | null;
|
|
9
|
+
read: boolean;
|
|
10
|
+
readAt?: string | null;
|
|
11
|
+
/** ISO-8601 com offset. */
|
|
12
|
+
createdAt: string;
|
|
13
|
+
expiresAt?: string | null;
|
|
14
|
+
};
|
|
15
|
+
export declare const NOTIFICATIONS_CHANNEL = "user-notifications";
|
|
16
|
+
export declare const DEFAULT_EVENT_PATTERN = "user-notification.*";
|
|
17
|
+
export declare const DEFAULT_MAX_VISIBLE = 100;
|
|
18
|
+
export declare const BADGE_MAX = 99;
|
|
19
|
+
export declare const DEFAULT_POLL_INTERVAL_MS = 45000;
|
|
20
|
+
export declare const MIN_POLL_INTERVAL_MS = 30000;
|
|
21
|
+
export declare const MAX_POLL_INTERVAL_MS = 60000;
|
|
22
|
+
export declare const MAX_POLL_BACKOFF_MS = 300000;
|
|
23
|
+
/** Mais nova primeiro; empate pelo `publicId` decrescente (como o servidor: ORDER BY created_at DESC, id DESC). */
|
|
24
|
+
export declare function sortNotifications(items: readonly SgNotification[]): SgNotification[];
|
|
25
|
+
/** Teto da lista: de 1 a 100 (uma pagina da rota); sem valor ou invalido, 100. */
|
|
26
|
+
export declare function clampMaxVisible(value?: number | null): number;
|
|
27
|
+
/** Vencida (`expiresAt` ate agora): o servidor nao as lista, o sino tambem nao as mostra. */
|
|
28
|
+
export declare function isExpired(item: SgNotification, now: string): boolean;
|
|
29
|
+
/** Lista que o sino mostra: sem repetidos (vale o ultimo), sem vencidas (se `now` vier), ordenada, no maximo `max` itens. */
|
|
30
|
+
export declare function normalizeList(items: readonly SgNotification[], max?: number, now?: string): SgNotification[];
|
|
31
|
+
/** Contador mostrado: o do servidor (`GET /unread-count`) quando se sabe, senao as nao lidas da lista local. */
|
|
32
|
+
export declare function displayUnread(serverCount: number | null | undefined, items: readonly SgNotification[]): number;
|
|
33
|
+
/** So conta as nao lidas. */
|
|
34
|
+
export declare function unreadCount(items: readonly SgNotification[]): number;
|
|
35
|
+
/** Texto do contador: vazio com zero, `99+` acima de 99. */
|
|
36
|
+
export declare function badgeText(count: number): string;
|
|
37
|
+
export declare function markRead(items: readonly SgNotification[], publicId: string, readAt: string): SgNotification[];
|
|
38
|
+
export declare function markAllRead(items: readonly SgNotification[], readAt: string): SgNotification[];
|
|
39
|
+
export declare function removeNotification(items: readonly SgNotification[], publicId: string): SgNotification[];
|
|
40
|
+
/**
|
|
41
|
+
* Itens nao lidos que chegaram de verdade: desconhecidos e mais novos que o mais novo da lista anterior (uma nao lida
|
|
42
|
+
* antiga que so apareceu agora, por teto ou por reler, nao e noticia). Sao eles que fazem o sino animar.
|
|
43
|
+
*/
|
|
44
|
+
export declare function newUnread(previous: readonly SgNotification[], next: readonly SgNotification[]): SgNotification[];
|
|
45
|
+
/** `user-notification.*` casa `user-notification.created` e `user-notification.changed`. */
|
|
46
|
+
export declare function matchesEventPattern(pattern: string, event: string): boolean;
|
|
47
|
+
/** O que fazer com um aviso do Pulse. */
|
|
48
|
+
export type SgNotificationEventPlan = {
|
|
49
|
+
action: "fetch";
|
|
50
|
+
publicId: string;
|
|
51
|
+
} | {
|
|
52
|
+
action: "refresh";
|
|
53
|
+
} | {
|
|
54
|
+
action: "ignore";
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* `created` com o identificador: busca so a nova. `created` sem identificador e `changed` (ler, excluir, limpar em
|
|
58
|
+
* outra aba): le a lista de novo. Outro canal ou evento fora do padrao: ignora.
|
|
59
|
+
*/
|
|
60
|
+
export declare function planEvent(pattern: string, channel: string, event: string, data: unknown): SgNotificationEventPlan;
|
|
61
|
+
/** Estado da conexao em tempo real (o mesmo do `SgRealtimeProvider`). */
|
|
62
|
+
export type SgNotificationStatus = "idle" | "disabled" | "connecting" | "connected" | "reconnecting" | "unavailable" | "auth-error";
|
|
63
|
+
/** Por que o polling foi acionado: realtime desligado na configuracao, ou Pulse fora do ar. */
|
|
64
|
+
export type SgPollingReason = "disabled" | "unavailable";
|
|
65
|
+
/**
|
|
66
|
+
* Polling so quando nao ha tempo real nem esperanca de ter logo: `disabled` (enabled:false) e `unavailable` /
|
|
67
|
+
* `auth-error` (Pulse fora do ar). Conectando ou reconectando, o SDK resolve sozinho.
|
|
68
|
+
*/
|
|
69
|
+
export declare function pollingReason(status: SgNotificationStatus): SgPollingReason | null;
|
|
70
|
+
/** Ao (re)conectar, le a lista uma vez para recuperar o que passou enquanto estava fora. */
|
|
71
|
+
export declare function shouldRefreshOnStatus(previous: SgNotificationStatus, next: SgNotificationStatus): boolean;
|
|
72
|
+
/** Intervalo pedido, preso entre 30 e 60 s (padrao 45 s). */
|
|
73
|
+
export declare function clampPollInterval(ms?: number | null): number;
|
|
74
|
+
/**
|
|
75
|
+
* 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).
|
|
76
|
+
* `random` entra de fora (0 a 1) para o resultado ser testavel.
|
|
77
|
+
*/
|
|
78
|
+
export declare function nextPollDelay(baseMs: number | null | undefined, failures: number, random: number): number;
|
|
79
|
+
/** Metrica do polling, exposta por `onPollingTick`. */
|
|
80
|
+
export type SgPollingStats = {
|
|
81
|
+
count: number;
|
|
82
|
+
lastAt: string | null;
|
|
83
|
+
reason: SgPollingReason | null;
|
|
84
|
+
};
|
|
85
|
+
export declare const EMPTY_POLLING_STATS: SgPollingStats;
|
|
86
|
+
export declare function recordPollingTick(stats: SgPollingStats, at: string, reason: SgPollingReason): SgPollingStats;
|
|
87
|
+
export type SgNotificationViewState = "loading" | "error" | "empty" | "caughtUp" | "list";
|
|
88
|
+
/**
|
|
89
|
+
* Qual das telas mostrar. Com erro e lista ja carregada, a lista continua (a falha vira aviso); sem nada para
|
|
90
|
+
* mostrar, erro vence vazio.
|
|
91
|
+
*/
|
|
92
|
+
export declare function viewState(input: {
|
|
93
|
+
loaded: boolean;
|
|
94
|
+
failed: boolean;
|
|
95
|
+
count: number;
|
|
96
|
+
unreadOnly?: boolean;
|
|
97
|
+
}): SgNotificationViewState;
|
|
98
|
+
/** O que a lista mostra: em "Nao lidas" so as ainda nao clicadas; em "Todas", tudo. */
|
|
99
|
+
export declare function visibleItems(items: readonly SgNotification[], unreadOnly: boolean): SgNotification[];
|
|
100
|
+
/** Query da lista: em "Nao lidas" pede so as nao lidas ao servidor (`unreadOnly=true`, que ja entra na ETag). */
|
|
101
|
+
export declare function listQuery(page: number, size: number, unreadOnly: boolean): string;
|
|
102
|
+
/** Clicou numa notificacao: em "Nao lidas" ela sai da lista; em "Todas" so perde o destaque. */
|
|
103
|
+
export declare function afterOpen(items: readonly SgNotification[], publicId: string, unreadOnly: boolean, readAt: string): SgNotification[];
|
|
104
|
+
/** Valor inicial do interruptor: sem interruptor, o painel mostra tudo; com ele, vale `defaultUnreadOnly` (padrao true). */
|
|
105
|
+
export declare function initialUnreadOnly(showUnreadToggle?: boolean | null, defaultUnreadOnly?: boolean | null): boolean;
|
|
106
|
+
export type SgRelativeTime = {
|
|
107
|
+
unit: "now";
|
|
108
|
+
} | {
|
|
109
|
+
unit: "minute" | "hour" | "day";
|
|
110
|
+
value: number;
|
|
111
|
+
} | {
|
|
112
|
+
unit: "date";
|
|
113
|
+
};
|
|
114
|
+
/** Idade da notificacao em partes; o texto ("ha 5 min") e do i18n de cada plataforma. */
|
|
115
|
+
/** Data que da para mostrar (um `createdAt` invalido nao vira 1970 na tela). */
|
|
116
|
+
export declare function isValidInstant(iso: string): boolean;
|
|
117
|
+
export declare function relativeTime(createdAt: string, now: string): SgRelativeTime;
|
|
118
|
+
/** De que lado do sino o painel abre (os mesmos valores do SgPopup). `auto` escolhe o lado em que cabe. */
|
|
119
|
+
export type SgNotificationPlacement = "auto" | "top" | "right" | "bottom" | "left";
|
|
120
|
+
export type SgPanelSide = Exclude<SgNotificationPlacement, "auto">;
|
|
121
|
+
export type SgPanelPosition = {
|
|
122
|
+
top: number;
|
|
123
|
+
left: number;
|
|
124
|
+
side: SgPanelSide;
|
|
125
|
+
maxHeight: number;
|
|
126
|
+
};
|
|
127
|
+
/** Largura do painel (26rem) e altura maxima (32rem), em px. */
|
|
128
|
+
export declare const PANEL_WIDTH = 416;
|
|
129
|
+
export declare const PANEL_MAX_HEIGHT = 512;
|
|
130
|
+
/**
|
|
131
|
+
* Onde o painel fica, em coordenadas da janela, sem sair dela. `auto` tenta embaixo, em cima, a direita e a esquerda
|
|
132
|
+
* (nessa ordem) e fica com o primeiro lado em que o painel cabe inteiro; se nenhum couber, usa embaixo ou em cima,
|
|
133
|
+
* o que tiver mais espaco. O lado forcado (`top`, `right`...) e respeitado. O painel se alinha ao fim do sino e e
|
|
134
|
+
* empurrado para dentro da janela. `panelHeight` e a altura real do painel (ate PANEL_MAX_HEIGHT).
|
|
135
|
+
*/
|
|
136
|
+
export declare function panelPosition(anchor: {
|
|
137
|
+
left: number;
|
|
138
|
+
top: number;
|
|
139
|
+
right: number;
|
|
140
|
+
bottom: number;
|
|
141
|
+
}, panel: {
|
|
142
|
+
width: number;
|
|
143
|
+
height: number;
|
|
144
|
+
}, viewport: {
|
|
145
|
+
width: number;
|
|
146
|
+
height: number;
|
|
147
|
+
}, placement?: SgNotificationPlacement, offset?: number, padding?: number): SgPanelPosition;
|
|
148
|
+
//# sourceMappingURL=notificationBellLogic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notificationBellLogic.d.ts","sourceRoot":"","sources":["../../src/notifications/notificationBellLogic.ts"],"names":[],"mappings":"AAOA,sFAAsF;AACtF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAC1D,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAC3D,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,wBAAwB,QAAQ,CAAC;AAC9C,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAC1C,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAC1C,eAAO,MAAM,mBAAmB,SAAS,CAAC;AAQ1C,mHAAmH;AACnH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,cAAc,EAAE,CAMpF;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAG7D;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,6HAA6H;AAC7H,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,GAAG,GAAE,MAA4B,EACjC,GAAG,CAAC,EAAE,MAAM,GACX,cAAc,EAAE,CAIlB;AAED,gHAAgH;AAChH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAE9G;AAED,6BAA6B;AAC7B,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAEpE;AAED,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,CAE7G;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,CAE9F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAEvG;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,IAAI,EAAE,SAAS,cAAc,EAAE,GAAG,cAAc,EAAE,CAIhH;AAED,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI3E;AAED,yCAAyC;AACzC,MAAM,MAAM,uBAAuB,GAC/B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzB;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,uBAAuB,CAOjH;AAED,yEAAyE;AACzE,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,UAAU,GACV,YAAY,GACZ,WAAW,GACX,cAAc,GACd,aAAa,GACb,YAAY,CAAC;AAEjB,+FAA+F;AAC/F,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,aAAa,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,oBAAoB,GAAG,eAAe,GAAG,IAAI,CAIlF;AAED,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAEzG;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAG5D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMzG;AAED,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;CAAE,CAAC;AAEtG,eAAO,MAAM,mBAAmB,EAAE,cAAyD,CAAC;AAE5F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,cAAc,CAE5G;AAED,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAE1F;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,uBAAuB,CAKnI;AAED,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,UAAU,EAAE,OAAO,GAAG,cAAc,EAAE,CAEpG;AAED,iHAAiH;AACjH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CAEjF;AAED,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,CAEnI;AAED,4HAA4H;AAC5H,wBAAgB,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,OAAO,GAAG,IAAI,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAGhH;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GACf;IAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,yFAAyF;AACzF,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAW3E;AAED,2GAA2G;AAC3G,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnF,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,gEAAgE;AAChE,eAAO,MAAM,WAAW,MAAM,CAAC;AAC/B,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAMpC;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACpE,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACxC,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC3C,SAAS,GAAE,uBAAgC,EAC3C,MAAM,SAAI,EACV,OAAO,SAAI,GACV,eAAe,CAmCjB"}
|
|
@@ -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);
|