@aurea-uds/react 0.18.0 → 0.22.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/actions.d.ts +2 -3
- package/dist/actions.js +5 -5
- package/dist/agents.js +35 -35
- package/dist/calendar.js +2 -2
- package/dist/chart.js +3 -3
- package/dist/communication.d.ts +57 -4
- package/dist/communication.js +237 -14
- package/dist/data-grid.js +3 -3
- package/dist/file-input.d.ts +1 -0
- package/dist/file-input.js +3 -1
- package/dist/graph.js +11 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inputs-client.d.ts +3 -2
- package/dist/inputs-client.js +9 -5
- package/dist/internal.js +1 -1
- package/dist/layout-client.js +2 -2
- package/dist/layout.d.ts +2 -0
- package/dist/layout.js +2 -0
- package/dist/markup.d.ts +55 -5
- package/dist/markup.js +26 -5
- package/dist/media-client.d.ts +10 -1
- package/dist/media-client.js +40 -17
- package/dist/navigation-client.d.ts +7 -2
- package/dist/navigation-client.js +38 -21
- package/dist/overlays.js +2 -2
- package/dist/pure.d.ts +10 -0
- package/dist/pure.js +4 -4
- package/package.json +1 -1
package/dist/actions.d.ts
CHANGED
|
@@ -39,9 +39,8 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Re
|
|
|
39
39
|
rel?: string;
|
|
40
40
|
download?: boolean | string;
|
|
41
41
|
/**
|
|
42
|
-
* @deprecated Use `<Toggle>` instead. Pesquisado em 18/08/2026 nas doze referências (
|
|
43
|
-
*
|
|
44
|
-
* Cedar, APG): **nenhuma** põe o estado de pressionado no botão comum — todas têm um
|
|
42
|
+
* @deprecated Use `<Toggle>` instead. Pesquisado em 18/08/2026 nas doze referências (entre
|
|
43
|
+
* elas o motor Base UI e o APG): **nenhuma** põe o estado de pressionado no botão comum — todas têm um
|
|
45
44
|
* componente separado, e o nosso é o `Toggle`. Manter os dois é dois caminhos para a mesma
|
|
46
45
|
* coisa, que é o "qual eu uso?" que denuncia recurso duplicado.
|
|
47
46
|
* A diferença de verdade: aqui VOCÊ guarda o estado e isto só pinta e anuncia; o `Toggle`
|
package/dist/actions.js
CHANGED
|
@@ -58,8 +58,8 @@ export const Button = forwardRef(function Button({ variant, appearance, tone, si
|
|
|
58
58
|
// 🔴 OCUPADO TROCA, NÃO ACRESCENTA (01/10/2026, achado pelo Victor no aureauds.dev): a rodinha
|
|
59
59
|
// entrava ANTES do ícone, e no `IconButton` — 36 de largura fixa — rodinha e glifo não cabiam:
|
|
60
60
|
// medido no catálogo, a rodinha começava 4px FORA do botão, à esquerda, com o disquete ainda ao
|
|
61
|
-
// lado.
|
|
62
|
-
//
|
|
61
|
+
// lado. A referência troca (no exemplo de estado ocupado da doc do botão dela, a rodinha
|
|
62
|
+
// entra no lugar do ícone). Aqui também: ocupado, a rodinha toma o lugar do ícone
|
|
63
63
|
// da frente; e sem conteúdo (o `IconButton` ocupado) o invólucro vazio some, senão o `gap` dele
|
|
64
64
|
// empurraria a rodinha para fora do centro. Fora do estado ocupado o HTML é o mesmo de antes.
|
|
65
65
|
const temConteudo = children != null && children !== false;
|
|
@@ -67,7 +67,7 @@ export const Button = forwardRef(function Button({ variant, appearance, tone, si
|
|
|
67
67
|
// INERTE ≠ DESABILITADO, e a diferença é medida (M4, 13/08/2026): `disabled` tira o botão da
|
|
68
68
|
// ordem de foco, então quem navega por teclado nunca alcança a explicação de POR QUE não dá — e
|
|
69
69
|
// "não dá porque você não tem permissão" é justamente o caso em que a explicação é tudo. O
|
|
70
|
-
// embrulho de <span> que
|
|
70
|
+
// embrulho de <span> que uma das referências documenta resolve o ponteiro e não resolve o teclado (span nasce
|
|
71
71
|
// com tabIndex -1, medido). Com `aria-disabled` o botão continua focável e anunciado como
|
|
72
72
|
// desabilitado, e é ESTE componente que tem de barrar a ativação — o atributo é só semântica.
|
|
73
73
|
// Mesmo remendo do AUD-0004, que já barrava o link desabilitado; aqui ele alcança o <button>.
|
|
@@ -118,7 +118,7 @@ function temConteudoVisivel(children) {
|
|
|
118
118
|
// ESTE é o toggle da Aurea. O `pressed` do `Button` está DEPRECIADO em favor dele (0.4.0,
|
|
119
119
|
// sai na 1.0): as doze referências pesquisadas em 18/08/2026 têm componente separado, e
|
|
120
120
|
// nenhuma põe o estado no botão comum.
|
|
121
|
-
// E a REGRA QUE FALTAVA ESTAR ESCRITA AQUI,
|
|
121
|
+
// E a REGRA QUE FALTAVA ESTAR ESCRITA AQUI, de uma das referências e do APG: **o rótulo não muda
|
|
122
122
|
// entre os estados**. Se o texto vira "Mute"/"Unmute" ou "Play"/"Pause", não é toggle — é
|
|
123
123
|
// botão de ação, porque quem lê tela ouve o rótulo NOVO e o estado ao mesmo tempo e não sabe
|
|
124
124
|
// se o botão descreve o que é ou o que fará.
|
|
@@ -145,7 +145,7 @@ export const ThemeToggle = forwardRef(function ThemeToggle({ className, ...props
|
|
|
145
145
|
const s = useAureaStrings();
|
|
146
146
|
const escuro = theme === "dark";
|
|
147
147
|
// O corpo do `IconButton`, escrito aqui só para o glifo sair na forma CHEIA (ADR-0053: a lua e o
|
|
148
|
-
// sol cheios, como os do
|
|
148
|
+
// sol cheios, como os do conjunto de ícones anterior, aprovados pela imagem em 25/09/2026). O `IconButton` público não
|
|
149
149
|
// tem peso de ícone.
|
|
150
150
|
return _jsx(Button, { ref: ref, variant: "ghost", className: cx("btn-icon", escuro ? "theme-toggle-sun" : "theme-toggle-moon", className), "aria-label": escuro ? s.themeToLight : s.themeToDark, onClick: toggleTheme, ...props, children: _jsx(Icon, { name: escuro ? "sun" : "moon", weight: "fill" }) });
|
|
151
151
|
});
|
package/dist/agents.js
CHANGED
|
@@ -9,13 +9,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
9
9
|
// `DIRECTION.md` se declara "ilustrativo, não-vinculante", então tirar dali uma
|
|
10
10
|
// categoria é decisão, não dedução.
|
|
11
11
|
//
|
|
12
|
-
// PROCEDÊNCIA (BUILDING.md passo 2): estes três saem de
|
|
13
|
-
//
|
|
14
|
-
//
|
|
12
|
+
// PROCEDÊNCIA (BUILDING.md passo 2): estes três saem de uma referência de agentes (o cartão
|
|
13
|
+
// de agente e o painel de estado) e da vista de detalhes
|
|
14
|
+
// de outra. As sete referências antigas NÃO têm esta família — foi por
|
|
15
15
|
// isso que a Parte H ficou bloqueada até 08/08/2026.
|
|
16
16
|
//
|
|
17
|
-
// Escopo menor que o da referência (passo 5): o
|
|
18
|
-
// STRING (
|
|
17
|
+
// Escopo menor que o da referência (passo 5): o cartão de lá despacha ações por
|
|
18
|
+
// STRING (uma função que recebe o nome da ação como texto), o que troca o compilador por um acordo
|
|
19
19
|
// verbal. Aqui as ações entram como nó, e quem monta o botão sabe o que ele faz.
|
|
20
20
|
import React from "react";
|
|
21
21
|
import { cx, useAureaStrings } from "./internal.js";
|
|
@@ -45,7 +45,7 @@ export function AgentCard({ name, description, avatarSrc, model, state, capabili
|
|
|
45
45
|
return _jsxs("article", { className: cx("card", "agent-card", className), ...props, children: [_jsxs("header", { className: "agent-card-head", children: [_jsx(Avatar, { src: avatarSrc, alt: name, fallback: name.slice(0, 2), size: "md" }), _jsxs("div", { className: "agent-card-id", children: [_jsx("strong", { children: name }), model && _jsx("span", { className: "hint", children: model })] }), state && _jsx(AgentStatus, { state: state })] }), description && _jsx("p", { className: "agent-card-desc", children: description }), !!capabilities?.length && _jsx("ul", { className: "agent-caps", children: capabilities.map(c => _jsxs("li", { className: "agent-cap", title: c.description, children: [c.icon && _jsx(Icon, { name: c.icon, size: "sm" }), _jsx("span", { children: c.name })] }, c.name)) }), actions && _jsx("footer", { className: "agent-card-actions", children: actions })] });
|
|
46
46
|
}
|
|
47
47
|
// AgentInspector — o detalhe: configuração, ferramentas, instruções.
|
|
48
|
-
// A anatomia é a
|
|
48
|
+
// A anatomia é a da vista de detalhes de uma das referências de agentes: cabeçalho + seções rotuladas, com
|
|
49
49
|
// o valor cru disponível. Aqui as seções são `DataList`, que já existe e já tem pele —
|
|
50
50
|
// e o corpo livre entra como filho, para inspecionar o que a ficha não previu.
|
|
51
51
|
//
|
|
@@ -64,10 +64,10 @@ export function AgentInspector({ title, sections, children, className, ...props
|
|
|
64
64
|
//
|
|
65
65
|
// O passo que tem corpo é um `<details>` nativo, o mesmo idioma do `Accordion`: dobrar
|
|
66
66
|
// e desdobrar já é comportamento de plataforma, com teclado e ARIA, e a referência
|
|
67
|
-
// gasta um
|
|
67
|
+
// gasta um componente dobrável de biblioteca para chegar no mesmo lugar.
|
|
68
68
|
//
|
|
69
69
|
// `running` marca `aria-busy` na região INTEIRA em vez de animar o texto. A referência
|
|
70
|
-
// usa um
|
|
70
|
+
// usa um brilho que corre pelo texto — animação em cima do rótulo — e isso é decoração que um leitor
|
|
71
71
|
// de tela não alcança e que `prefers-reduced-motion` teria de desfazer.
|
|
72
72
|
//
|
|
73
73
|
// Entrada e saída são `group` NOMEADO, e não `<h3>` — mesmo defeito e mesma correção do
|
|
@@ -86,10 +86,10 @@ const TAREFA = {
|
|
|
86
86
|
};
|
|
87
87
|
// TaskQueue — a fila de trabalho.
|
|
88
88
|
//
|
|
89
|
-
// ESCOPO MENOR QUE O DA REFERÊNCIA, e por fronteira, não por preguiça:
|
|
89
|
+
// ESCOPO MENOR QUE O DA REFERÊNCIA, e por fronteira, não por preguiça: a tarefa de
|
|
90
90
|
// lá tem 15 campos, e cinco deles pertencem a OUTROS componentes desta mesma parte —
|
|
91
|
-
//
|
|
92
|
-
//
|
|
91
|
+
// a contagem de tokens e o custo são o `ModelUsage` (H11) e o `CostMeter` (H12),
|
|
92
|
+
// os pontos de controle são o `TraceTimeline` (H9), o responsável é o `AgentCard` (H1). Absorvê-los
|
|
93
93
|
// aqui faria a fila responder por quatro contratos e nenhum deles direito.
|
|
94
94
|
export function TaskQueue({ tasks, label, onRetry, className, ...props }) {
|
|
95
95
|
const s = useAureaStrings();
|
|
@@ -150,7 +150,7 @@ export function EventStream({ events, label, follow, className, ...props }) {
|
|
|
150
150
|
// (end-start)/(max-min) de largura.
|
|
151
151
|
//
|
|
152
152
|
// O que NÃO entrou: as ONZE cores por categoria de span da referência
|
|
153
|
-
// (
|
|
153
|
+
// (chamada de modelo, execução de ferramenta, busca…). Cor por categoria numa paleta travada é
|
|
154
154
|
// o mesmo defeito que o H.a recusou; `kind` entra como TEXTO, que é legível também por
|
|
155
155
|
// quem não distingue as cores.
|
|
156
156
|
export function TraceTimeline({ spans, label, className, ...props }) {
|
|
@@ -174,24 +174,24 @@ export function HealthMatrix({ entries, label, className, ...props }) {
|
|
|
174
174
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
175
|
// H.e — custo e memória: quanto se consumiu, contra que teto, e o que ficou guardado.
|
|
176
176
|
//
|
|
177
|
-
// PROCEDÊNCIA:
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// Mais
|
|
183
|
-
// de unidades desta família, contagem e preço — e
|
|
184
|
-
//
|
|
177
|
+
// PROCEDÊNCIA: o painel de uma referência de observabilidade de LLM — o
|
|
178
|
+
// gráfico de uso por modelo (a métrica total no topo e a quebra por modelo), a
|
|
179
|
+
// tabela de custo por modelo (as colunas modelo × tokens × custo, com o número à direita), a
|
|
180
|
+
// métrica total (número grande + descrição, e é UMA linha de arranjo) e a
|
|
181
|
+
// lista de barras (a lista de barras horizontais com rótulo de valor).
|
|
182
|
+
// Mais os selos de tokens e de preço de uma referência de agentes — o par
|
|
183
|
+
// de unidades desta família, contagem e preço — e o painel de estado de agente de outra,
|
|
184
|
+
// que traz a tarifa por 1k tokens com entrada e saída separadas.
|
|
185
185
|
//
|
|
186
186
|
// O `MemoryLedger` NÃO TEM REFERÊNCIA, e é o caso declarado no `BUILDING.md` §1: nasce de
|
|
187
187
|
// pesquisa registrada no `REFERENCES.md` e de composição. A pesquisa está lá com data.
|
|
188
188
|
// Formatação de número: `Intl` é a plataforma, e o locale entra como prop com default
|
|
189
189
|
// FIXO. O default fixo é o que mantém a saída determinística no SSR do catálogo e no gate
|
|
190
190
|
// de pixel — locale do ambiente mudaria o texto entre a máquina e a CI. A referência faz
|
|
191
|
-
// igual: o
|
|
192
|
-
//
|
|
191
|
+
// igual: o formatador de custo dela é `Intl.NumberFormat("en-US")` pregado, medido no
|
|
192
|
+
// fonte dela.
|
|
193
193
|
const compacto = (n, locale) => new Intl.NumberFormat(locale, { notation: "compact", maximumFractionDigits: 2 }).format(n);
|
|
194
|
-
// Duas casas fixas é o que o
|
|
194
|
+
// Duas casas fixas é o que o formatador de custo da referência usa, e aqui isso MENTE: custo de
|
|
195
195
|
// modelo cai em fração de centavo o tempo todo, e "$0.00" para um gasto real de US$ 0,004
|
|
196
196
|
// apaga o número justamente no componente que existe para vigiá-lo.
|
|
197
197
|
const dinheiro = (n, currency, locale) => new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: 2, maximumFractionDigits: n !== 0 && Math.abs(n) < 0.01 ? 6 : 2 }).format(n);
|
|
@@ -202,8 +202,8 @@ const dinheiro = (n, currency, locale) => new Intl.NumberFormat(locale, { style:
|
|
|
202
202
|
// levou quanto". No `CostMeter` o teto vem de fora, e a pergunta é "quanto ainda cabe".
|
|
203
203
|
// Trocar um pelo outro é mostrar orçamento onde não existe orçamento nenhum.
|
|
204
204
|
//
|
|
205
|
-
// ESCOPO MENOR QUE O DA REFERÊNCIA (passo 5): as quatro abas de lá (
|
|
206
|
-
//
|
|
205
|
+
// ESCOPO MENOR QUE O DA REFERÊNCIA (passo 5): as quatro abas de lá (custo e uso,
|
|
206
|
+
// cada um por modelo e por tipo) existem porque são quatro CONSULTAS
|
|
207
207
|
// diferentes no produto dela. Aqui a dimensão é uma prop e os dados chegam prontos. Também
|
|
208
208
|
// ficaram fora o seletor de modelos em popover e a série temporal — a série é o `Chart`,
|
|
209
209
|
// que já existe, e embutir um segundo gráfico aqui duplicaria aquele contrato.
|
|
@@ -212,7 +212,7 @@ export function ModelUsage({ entries, metric = "tokens", label, currency = "USD"
|
|
|
212
212
|
const valor = (e) => (metric === "cost" ? e.cost : metric === "requests" ? e.requests : e.tokens) ?? 0;
|
|
213
213
|
const fmt = (n) => metric === "cost" ? dinheiro(n, currency, locale) : compacto(n, locale);
|
|
214
214
|
const total = entries.reduce((a, e) => a + valor(e), 0);
|
|
215
|
-
// Ordem decrescente pela métrica, como a referência (
|
|
215
|
+
// Ordem decrescente pela métrica, como a referência (a ordenação da tabela de custo dela): uma
|
|
216
216
|
// lista de barras fora de ordem obriga a varrer tudo para achar o maior, que é a única
|
|
217
217
|
// coisa que ela existe para responder de relance. Cópia, não `sort` no array de quem chamou.
|
|
218
218
|
const linhas = [...entries].sort((a, b) => valor(b) - valor(a));
|
|
@@ -223,10 +223,10 @@ export function ModelUsage({ entries, metric = "tokens", label, currency = "USD"
|
|
|
223
223
|
}
|
|
224
224
|
// CostMeter — o gasto contra um TETO.
|
|
225
225
|
//
|
|
226
|
-
// Os dois limites são os do
|
|
226
|
+
// Os dois limites são os do padrão de mercado de controle de custo de LLM,
|
|
227
227
|
// medido em 09/08/2026 (pesquisa registrada no `REFERENCES.md`): o limite RÍGIDO bloqueia,
|
|
228
|
-
// o limite BRANDO só avisa. Nenhuma das nove referências locais tem orçamento —
|
|
229
|
-
//
|
|
228
|
+
// o limite BRANDO só avisa. Nenhuma das nove referências locais tem orçamento — a referência
|
|
229
|
+
// de observabilidade, na versão aberta, mostra custo e não teto —, então isto veio pelo passo 4 do `BUILDING.md`, pesquisado
|
|
230
230
|
// antes de escrever, e não de memória.
|
|
231
231
|
//
|
|
232
232
|
// Sem `softLimit` NÃO EXISTE estado de aviso. A tentação era derivar um em 80% do teto;
|
|
@@ -244,9 +244,9 @@ const LANCAMENTO = {
|
|
|
244
244
|
};
|
|
245
245
|
// MemoryLedger — o que o agente guardou, esqueceu ou lembrou, e DE ONDE veio.
|
|
246
246
|
//
|
|
247
|
-
// É o único da Parte H sem referência no mundo aberto:
|
|
248
|
-
//
|
|
249
|
-
// 08/08/2026 e reconfirmado em 09/08.
|
|
247
|
+
// É o único da Parte H sem referência no mundo aberto: dos produtos de memória de agentes
|
|
248
|
+
// pesquisados, um só abre o servidor, e o outro tem a versão aberta sendo descontinuada e o
|
|
249
|
+
// painel só na nuvem. Medido em 08/08/2026 e reconfirmado em 09/08.
|
|
250
250
|
//
|
|
251
251
|
// Livro-razão é APENDE-SÓ, e é isso que o separa de uma lista de memórias: `forgotten` não
|
|
252
252
|
// tira a linha, registra o esquecimento. Uma tela que apaga a linha ao esquecer perde
|
|
@@ -271,8 +271,8 @@ const RECADO = {
|
|
|
271
271
|
// tem um remetente e um destinatário, e é a passagem entre os dois que se está lendo. Um
|
|
272
272
|
// `MessageList` com dois autores não diz quem entregou o quê a quem.
|
|
273
273
|
//
|
|
274
|
-
// O que NÃO entrou: as seis cores por intenção do
|
|
275
|
-
//
|
|
274
|
+
// O que NÃO entrou: as seis cores por intenção do painel de encaminhamento da referência (ciano,
|
|
275
|
+
// âmbar, ardósia…). É a TERCEIRA vez que esta parte recusa a mesma coisa —
|
|
276
276
|
// depois das onze de span no H.d e das seis de estado no H.a. Cor por categoria numa paleta
|
|
277
277
|
// travada não é legível para quem não separa as cores. A `kind` entra como palavra, num selo.
|
|
278
278
|
export function InterAgentMessage({ messages, label, className, ...props }) {
|
|
@@ -284,7 +284,7 @@ export function InterAgentMessage({ messages, label, className, ...props }) {
|
|
|
284
284
|
}
|
|
285
285
|
// AutomationCard — a regra que roda sozinha: QUANDO isto, ENTÃO aquilo.
|
|
286
286
|
//
|
|
287
|
-
// A anatomia é a
|
|
287
|
+
// A anatomia é a da lateral de automações da referência de observabilidade, medida: nome, gatilho (fonte de evento) e
|
|
288
288
|
// ação (tipo), mais o estado ativo/inativo. É isso que separa este cartão do `AgentCard`, que é
|
|
289
289
|
// identidade, e da `TaskQueue`, que é trabalho enfileirado: uma automação é uma REGRA, e regra
|
|
290
290
|
// é uma condição e uma consequência.
|
package/dist/calendar.js
CHANGED
|
@@ -13,9 +13,9 @@ import { cx } from "./internal.js";
|
|
|
13
13
|
const NOMES = { months: "calendar-months", month_caption: "calendar-caption" };
|
|
14
14
|
export function Calendar({ label, className, classNames, ...props }) {
|
|
15
15
|
const grade = _jsx(DayPicker
|
|
16
|
-
// Grade cheia por padrão, como
|
|
16
|
+
// Grade cheia por padrão, como naquela referência: mês com buraco nas pontas treme ao trocar de mês.
|
|
17
17
|
, {
|
|
18
|
-
// Grade cheia por padrão, como
|
|
18
|
+
// Grade cheia por padrão, como naquela referência: mês com buraco nas pontas treme ao trocar de mês.
|
|
19
19
|
showOutsideDays: true, ...props, classNames: { ...NOMES, root: cx("calendar", className), ...classNames } });
|
|
20
20
|
// O motor já nomeia a GRADE com o mês ("August 2026"). O que ele não sabe é para que serve
|
|
21
21
|
// ESTE calendário — "de" e "até" lado a lado soariam os dois "August 2026". É o que `label`
|
package/dist/chart.js
CHANGED
|
@@ -4,17 +4,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
// componente que precisa do `recharts`, que é peer OPCIONAL. Se morasse em data-display.tsx,
|
|
5
5
|
// quem importa Table baixaria um motor de gráfico inteiro — que é o achado A5 visto de perto.
|
|
6
6
|
//
|
|
7
|
-
// NÃO ESCREVEMOS MOTOR DE GRÁFICO. Duas das quatro referências
|
|
7
|
+
// NÃO ESCREVEMOS MOTOR DE GRÁFICO. Duas das quatro referências
|
|
8
8
|
// envelopam o MESMO motor, o Recharts, e nenhuma das quatro desenha eixo à mão. O que é nosso
|
|
9
9
|
// aqui é só a pele: a caixa, o tooltip e a legenda. Escala, eixo, curva e interação são dele.
|
|
10
10
|
//
|
|
11
11
|
// O QUE FICOU DE FORA, e por quê (BUILDING.md passo 5 — escopo menor que o da referência):
|
|
12
|
-
// • o `ChartConfig`
|
|
12
|
+
// • o `ChartConfig` de uma delas (mapa dataKey → {label, color, theme}). MEDIDO em 01/08/2026: o
|
|
13
13
|
// payload que o Recharts entrega ao `content` já traz `name` (do prop `name` da série) e
|
|
14
14
|
// `color` resolvido. O objeto de configuração é indireção para reconstruir o que já chega.
|
|
15
15
|
// E o eixo `theme:{light,dark}` não existe aqui: `var(--chart-2)` já troca com o data-theme.
|
|
16
16
|
// • o par `ChartTooltip`/`ChartTooltipContent`. No Recharts 2 o filho tinha de ser o
|
|
17
|
-
// componente DELES, o que forçava
|
|
17
|
+
// componente DELES, o que forçava essa referência a reexportar o primitivo e pôr a pele no
|
|
18
18
|
// `content`. MEDIDO no 3.10.1: um componente NOSSO como filho é reconhecido. Então é uma
|
|
19
19
|
// peça só, não duas.
|
|
20
20
|
// • prop de altura. Dimensão não é número (check 23): a caixa mede pelo CSS (`.chart`), e
|
package/dist/communication.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
2
|
import { type IconName } from "./system.js";
|
|
3
3
|
import { type BadgeVariant } from "./feedback.js";
|
|
4
|
+
export type ChatDirection = "incoming" | "outgoing";
|
|
5
|
+
export interface MessageAttachment {
|
|
6
|
+
id: string;
|
|
7
|
+
kind: "image" | "video" | "file";
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
bytes?: number;
|
|
12
|
+
duration?: number;
|
|
13
|
+
href?: string;
|
|
14
|
+
}
|
|
4
15
|
export interface ChatMessage {
|
|
5
16
|
id: string;
|
|
6
17
|
body: ReactNode;
|
|
@@ -14,18 +25,60 @@ export interface ChatMessage {
|
|
|
14
25
|
label: ReactNode;
|
|
15
26
|
variant?: BadgeVariant;
|
|
16
27
|
};
|
|
28
|
+
direction?: ChatDirection;
|
|
29
|
+
attachments?: MessageAttachment[];
|
|
30
|
+
replyTo?: {
|
|
31
|
+
id: string;
|
|
32
|
+
author?: ReactNode;
|
|
33
|
+
body: ReactNode;
|
|
34
|
+
};
|
|
35
|
+
forwardedFrom?: ReactNode;
|
|
36
|
+
edited?: boolean;
|
|
37
|
+
day?: string;
|
|
17
38
|
}
|
|
18
|
-
export
|
|
39
|
+
export interface MessageListProps {
|
|
19
40
|
messages: ChatMessage[];
|
|
20
41
|
label?: string;
|
|
21
42
|
className?: string;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
hasMoreBefore?: boolean;
|
|
44
|
+
hasMoreAfter?: boolean;
|
|
45
|
+
loadingBefore?: boolean;
|
|
46
|
+
loadingAfter?: boolean;
|
|
47
|
+
onReachStart?: () => void;
|
|
48
|
+
onReachEnd?: () => void;
|
|
49
|
+
onOpenAttachment?: (message: ChatMessage, attachment: MessageAttachment) => void;
|
|
50
|
+
}
|
|
51
|
+
export declare function MessageList({ messages, label, className, hasMoreBefore, hasMoreAfter, loadingBefore, loadingAfter, onReachStart, onReachEnd, onOpenAttachment }: MessageListProps): React.JSX.Element;
|
|
52
|
+
export interface ComposerReply {
|
|
53
|
+
id: string;
|
|
54
|
+
author?: ReactNode;
|
|
55
|
+
body: ReactNode;
|
|
56
|
+
}
|
|
57
|
+
export interface ComposerEdit {
|
|
58
|
+
id: string;
|
|
59
|
+
body: string;
|
|
60
|
+
}
|
|
61
|
+
export interface ComposerAttach {
|
|
62
|
+
accept?: string;
|
|
63
|
+
maxSize?: number;
|
|
64
|
+
multiple?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface MessageSendDetails {
|
|
67
|
+
files: File[];
|
|
68
|
+
replyToId?: string;
|
|
69
|
+
editingId?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className, attach, replyTo, onCancelReply, editing, onCancelEdit }: {
|
|
72
|
+
onSend: (text: string, details?: MessageSendDetails) => void;
|
|
25
73
|
placeholder?: string;
|
|
26
74
|
label?: string;
|
|
27
75
|
icon?: IconName;
|
|
28
76
|
sendLabel?: string;
|
|
29
77
|
disabled?: boolean;
|
|
30
78
|
className?: string;
|
|
79
|
+
attach?: boolean | ComposerAttach;
|
|
80
|
+
replyTo?: ComposerReply | null;
|
|
81
|
+
onCancelReply?: () => void;
|
|
82
|
+
editing?: ComposerEdit | null;
|
|
83
|
+
onCancelEdit?: () => void;
|
|
31
84
|
}): React.JSX.Element;
|
package/dist/communication.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
// Fase 9 (achado A5): este arquivo saiu do index.tsx de 970 linhas. Um módulo por categoria
|
|
4
4
|
// do registry — a taxonomia já existia e é gateada. A ordem de import entre eles é um DAG:
|
|
5
5
|
// internal → system → actions → feedback → inputs → navigation → layout → data-display → resto.
|
|
@@ -9,21 +9,244 @@ import { InputGroup, InputGroupAddon } from "./inputs.js";
|
|
|
9
9
|
import { Icon } from "./system.js";
|
|
10
10
|
import { IconButton } from "./actions.js";
|
|
11
11
|
import { Avatar } from "./identity.js";
|
|
12
|
-
import { Badge } from "./feedback.js";
|
|
13
|
-
|
|
12
|
+
import { Badge, Spinner } from "./feedback.js";
|
|
13
|
+
import { formatSize, matchesAccept } from "./file-input.js";
|
|
14
|
+
// A AN-02 põe álbum na mensagem com a `Gallery` que já existe. `media` mora no "resto" do DAG,
|
|
15
|
+
// como este módulo, e não importa `communication` — não há ciclo.
|
|
16
|
+
import { Gallery } from "./media.js";
|
|
17
|
+
// O contêiner que rola é o ancestral com `overflow` de rolagem, ou a página.
|
|
18
|
+
function rolador(el) {
|
|
19
|
+
for (let p = el.parentElement; p; p = p.parentElement) {
|
|
20
|
+
const o = getComputedStyle(p).overflowY;
|
|
21
|
+
if (o === "auto" || o === "scroll" || o === "overlay")
|
|
22
|
+
return p;
|
|
23
|
+
}
|
|
24
|
+
return document.scrollingElement ?? document.documentElement;
|
|
25
|
+
}
|
|
26
|
+
const ehPagina = (sc) => sc === document.scrollingElement || sc === document.documentElement;
|
|
27
|
+
function useVigia(alvo, ligado, avisar, deps) {
|
|
28
|
+
const cb = React.useRef(avisar);
|
|
29
|
+
cb.current = avisar;
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
const el = alvo.current;
|
|
32
|
+
if (!el || !ligado || typeof IntersectionObserver === "undefined")
|
|
33
|
+
return;
|
|
34
|
+
const sc = rolador(el);
|
|
35
|
+
const obs = new IntersectionObserver(es => { if (es.some(e => e.isIntersecting))
|
|
36
|
+
cb.current?.(); }, { root: ehPagina(sc) ? null : sc });
|
|
37
|
+
obs.observe(el);
|
|
38
|
+
return () => obs.disconnect();
|
|
39
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
+
}, [ligado, ...deps]);
|
|
41
|
+
}
|
|
42
|
+
export function MessageList({ messages, label, className, hasMoreBefore, hasMoreAfter, loadingBefore, loadingAfter, onReachStart, onReachEnd, onOpenAttachment }) {
|
|
14
43
|
const s = useAureaStrings();
|
|
15
|
-
|
|
44
|
+
const janela = !!onReachStart || !!onReachEnd;
|
|
45
|
+
const raiz = React.useRef(null);
|
|
46
|
+
const topo = React.useRef(null);
|
|
47
|
+
const fim = React.useRef(null);
|
|
48
|
+
const ancora = React.useRef(null);
|
|
49
|
+
const noFim = React.useRef(true);
|
|
50
|
+
const ultimoAntes = React.useRef(undefined);
|
|
51
|
+
const montou = React.useRef(false);
|
|
52
|
+
const [anuncio, setAnuncio] = React.useState("");
|
|
53
|
+
const hasMoreAfterRef = React.useRef(hasMoreAfter);
|
|
54
|
+
hasMoreAfterRef.current = hasMoreAfter;
|
|
55
|
+
// A posição de quem está no alto da tela, e se a última está à vista. Lida a cada rolagem e
|
|
56
|
+
// depois de cada troca: entre uma troca e outra, só a rolagem move as mensagens.
|
|
57
|
+
const medir = React.useCallback(() => {
|
|
58
|
+
const el = raiz.current;
|
|
59
|
+
if (!el)
|
|
60
|
+
return;
|
|
61
|
+
const sc = rolador(el), pag = ehPagina(sc);
|
|
62
|
+
const alto = pag ? 0 : sc.getBoundingClientRect().top, baixo = pag ? window.innerHeight : sc.getBoundingClientRect().bottom;
|
|
63
|
+
const itens = el.querySelectorAll("[data-message-id]");
|
|
64
|
+
// A âncora é a primeira que COMEÇA dentro da tela, e não a primeira que aparece: medido no banco
|
|
65
|
+
// em 04/10/2026, um álbum cortado no alto cresceu 480 px quando as fotos chegaram — para baixo
|
|
66
|
+
// dele —, o topo dele não andou, e a conversa inteira desceu sem a conta perceber. Se nenhuma
|
|
67
|
+
// começa na tela (uma mensagem maior que ela), vale a que está à vista.
|
|
68
|
+
ancora.current = null;
|
|
69
|
+
let vista;
|
|
70
|
+
for (const it of Array.from(itens)) {
|
|
71
|
+
const r = it.getBoundingClientRect();
|
|
72
|
+
if (r.bottom <= alto)
|
|
73
|
+
continue;
|
|
74
|
+
vista ??= it;
|
|
75
|
+
if (r.top >= alto) {
|
|
76
|
+
ancora.current = { id: it.dataset.messageId, top: r.top };
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!ancora.current && vista)
|
|
81
|
+
ancora.current = { id: vista.dataset.messageId, top: vista.getBoundingClientRect().top };
|
|
82
|
+
const ultimo = itens[itens.length - 1];
|
|
83
|
+
noFim.current = !ultimo || ultimo.getBoundingClientRect().bottom <= baixo + 1;
|
|
84
|
+
}, []);
|
|
85
|
+
React.useEffect(() => {
|
|
86
|
+
if (!janela || !raiz.current)
|
|
87
|
+
return;
|
|
88
|
+
const sc = rolador(raiz.current), alvo = ehPagina(sc) ? window : sc;
|
|
89
|
+
alvo.addEventListener("scroll", medir, { passive: true });
|
|
90
|
+
return () => alvo.removeEventListener("scroll", medir);
|
|
91
|
+
}, [janela, medir]);
|
|
92
|
+
const ultimoId = messages[messages.length - 1]?.id;
|
|
93
|
+
React.useLayoutEffect(() => {
|
|
94
|
+
const el = raiz.current;
|
|
95
|
+
if (!janela || !el) {
|
|
96
|
+
ultimoAntes.current = ultimoId;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const sc = rolador(el);
|
|
100
|
+
const irAoFim = () => { sc.scrollTop = sc.scrollHeight; };
|
|
101
|
+
const antes = ultimoAntes.current;
|
|
102
|
+
// Chegou mensagem no fim: a última de antes continua na lista, e não é mais a última.
|
|
103
|
+
const ia = antes === undefined ? -1 : messages.findIndex(m => m.id === antes);
|
|
104
|
+
const chegou = montou.current && ia >= 0 && ia < messages.length - 1;
|
|
105
|
+
if (!montou.current) {
|
|
106
|
+
irAoFim();
|
|
107
|
+
montou.current = true;
|
|
108
|
+
}
|
|
109
|
+
else if (chegou && noFim.current && !hasMoreAfter)
|
|
110
|
+
irAoFim();
|
|
111
|
+
else if (ancora.current) {
|
|
112
|
+
const it = el.querySelector(`[data-message-id="${CSS.escape(ancora.current.id)}"]`);
|
|
113
|
+
if (it)
|
|
114
|
+
sc.scrollTop += it.getBoundingClientRect().top - ancora.current.top;
|
|
115
|
+
}
|
|
116
|
+
if (chegou) {
|
|
117
|
+
const novo = el.querySelector(`[data-message-id="${CSS.escape(ultimoId)}"]`);
|
|
118
|
+
setAnuncio(novo?.textContent ?? "");
|
|
119
|
+
}
|
|
120
|
+
ultimoAntes.current = ultimoId;
|
|
121
|
+
medir();
|
|
122
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
123
|
+
}, [messages]);
|
|
124
|
+
// A ALTURA MUDA DEPOIS DE MONTAR — a fonte chega, a foto decodifica, o app troca um texto — e
|
|
125
|
+
// nenhuma rolagem acontece para avisar. Medido no banco em 04/10/2026: a conversa abria na #4992
|
|
126
|
+
// e não na última. Então se observa o tamanho da lista: quem estava no fim continua no fim; quem
|
|
127
|
+
// não estava mantém a mensagem do alto onde ela estava.
|
|
128
|
+
React.useEffect(() => {
|
|
129
|
+
const el = raiz.current;
|
|
130
|
+
if (!janela || !el || typeof ResizeObserver === "undefined")
|
|
131
|
+
return;
|
|
132
|
+
const ro = new ResizeObserver(() => {
|
|
133
|
+
const sc = rolador(el);
|
|
134
|
+
if (noFim.current && !hasMoreAfterRef.current)
|
|
135
|
+
sc.scrollTop = sc.scrollHeight;
|
|
136
|
+
else if (ancora.current) {
|
|
137
|
+
const it = el.querySelector(`[data-message-id="${CSS.escape(ancora.current.id)}"]`);
|
|
138
|
+
if (it)
|
|
139
|
+
sc.scrollTop += it.getBoundingClientRect().top - ancora.current.top;
|
|
140
|
+
}
|
|
141
|
+
medir();
|
|
142
|
+
});
|
|
143
|
+
ro.observe(el);
|
|
144
|
+
return () => ro.disconnect();
|
|
145
|
+
}, [janela, medir]);
|
|
146
|
+
useVigia(topo, janela && !!hasMoreBefore && !loadingBefore, onReachStart, [messages.length]);
|
|
147
|
+
useVigia(fim, janela && !!hasMoreAfter && !loadingAfter, onReachEnd, [messages.length]);
|
|
148
|
+
return _jsxs(_Fragment, { children: [_jsxs("div", { ref: raiz, className: cx("stack", className), role: "log", "aria-live": janela ? "off" : "polite", "aria-busy": (janela && (loadingBefore || loadingAfter)) || undefined, "aria-label": label ?? s.chatLabel, children: [janela && hasMoreBefore && _jsx("div", { ref: topo, className: "message-more", children: loadingBefore && _jsx(Spinner, {}) }), messages.map((m, i) => {
|
|
149
|
+
const lado = m.direction;
|
|
150
|
+
const midia = (m.attachments ?? []).filter(a => a.kind !== "file" && a.src);
|
|
151
|
+
const arquivos = (m.attachments ?? []).filter(a => a.kind === "file");
|
|
152
|
+
const dia = m.day != null && m.day !== messages[i - 1]?.day;
|
|
153
|
+
return _jsxs(React.Fragment, { children: [dia && _jsx("div", { className: "message-day", children: _jsx(Badge, { children: m.day }) }), _jsxs("div", { className: cx("message", lado === "outgoing" && "message-out", lado === "incoming" && "message-in"), "data-message-id": m.id, children: [lado === "outgoing" ? null : m.avatar ? _jsx(Avatar, { src: m.avatar.src, fallback: m.avatar.fallback }) : _jsx("span", {}), _jsxs("div", { className: "message-bubble", children: [(m.author || m.time || m.edited) && _jsxs("div", { className: "label", children: [_jsx("span", { children: m.author }), (m.time || m.edited) && _jsxs("span", { className: "hint", children: [m.edited && _jsxs(_Fragment, { children: [s.chatEdited, m.time != null && " · "] }), m.time] })] }), m.forwardedFrom != null && _jsxs("div", { className: "message-forwarded", children: [_jsx(Icon, { name: "share-fat", size: "sm" }), _jsxs("span", { children: [s.chatForwardedFrom, " ", m.forwardedFrom] })] }), m.replyTo && _jsx("div", { className: "message-quote", children: _jsxs("span", { className: "message-quote-text", children: [_jsx("span", { className: "message-quote-title", children: m.replyTo.author }), _jsx("span", { className: "message-quote-body", children: m.replyTo.body })] }) }), midia.length > 0 && _jsx(Gallery, { className: "message-album", label: s.chatAttachments, zoom: !onOpenAttachment, ratio: midia.length === 1 ? "4/3" : "1/1", items: midia.map(a => ({ id: a.id, src: a.src, alt: a.alt ?? a.name ?? "", kind: a.kind === "video" ? "video" : "image", duration: a.duration })), onSelect: onOpenAttachment ? (id => { const a = midia.find(x => x.id === id); if (a)
|
|
154
|
+
onOpenAttachment(m, a); }) : undefined }), arquivos.length > 0 && _jsx("ul", { className: "file-list", "aria-label": s.chatAttachments, children: arquivos.map(a => _jsxs("li", { className: "file-item", children: [_jsx(Icon, { name: "file-text" }), a.href ? _jsx("a", { className: "file-name", href: a.href, download: a.name ?? true, children: a.name ?? a.href }) : _jsx("span", { className: "file-name", children: a.name }), a.bytes != null && _jsx("span", { className: "file-size", children: formatSize(a.bytes) })] }, a.id)) }), m.body, m.status && _jsx("div", { className: "message-status", children: _jsxs(Badge, { variant: m.status.variant, children: [_jsx("i", { className: "status-dot" }), m.status.label] }) })] })] })] }, m.id);
|
|
155
|
+
}), janela && hasMoreAfter && _jsx("div", { ref: fim, className: "message-more", children: loadingAfter && _jsx(Spinner, {}) })] }), janela && _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: anuncio })] });
|
|
16
156
|
}
|
|
17
|
-
|
|
18
|
-
// Texto é estado interno (não controlado — controlar de fora só com demanda real,
|
|
19
|
-
// precedente MediaPlayer/FileInput). Não envia texto vazio/só-espaço; o botão de
|
|
20
|
-
// envio desabilita enquanto vazio. O input sempre tem nome acessível (aria-label),
|
|
21
|
-
// pois placeholder não serve de nome. ponytail: input de uma linha; textarea +
|
|
22
|
-
// Shift+Enter quando surgir demanda de multilinha.
|
|
23
|
-
export function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className }) {
|
|
157
|
+
export function MessageComposer({ onSend, placeholder, label, icon, sendLabel, disabled, className, attach, replyTo, onCancelReply, editing, onCancelEdit }) {
|
|
24
158
|
const s = useAureaStrings();
|
|
25
159
|
const [text, setText] = React.useState("");
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
160
|
+
const [anexos, setAnexos] = React.useState([]);
|
|
161
|
+
const [recusados, setRecusados] = React.useState([]);
|
|
162
|
+
const [aviso, setAviso] = React.useState("");
|
|
163
|
+
const campo = React.useRef(null);
|
|
164
|
+
const seletor = React.useRef(null);
|
|
165
|
+
const seq = React.useRef(0);
|
|
166
|
+
const opcoes = attach === true ? {} : attach || undefined;
|
|
167
|
+
const editandoId = editing?.id;
|
|
168
|
+
const respondendoId = replyTo?.id;
|
|
169
|
+
// O texto antigo entra quando a edição COMEÇA (muda o id), e sai quando ela acaba — sem isso o
|
|
170
|
+
// texto da mensagem editada ficava no campo como rascunho de mensagem nova.
|
|
171
|
+
const editouAntes = React.useRef(undefined);
|
|
172
|
+
React.useEffect(() => {
|
|
173
|
+
if (editandoId !== undefined) {
|
|
174
|
+
setText(editing?.body ?? "");
|
|
175
|
+
campo.current?.focus();
|
|
176
|
+
}
|
|
177
|
+
else if (editouAntes.current !== undefined)
|
|
178
|
+
setText("");
|
|
179
|
+
editouAntes.current = editandoId;
|
|
180
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
181
|
+
}, [editandoId]);
|
|
182
|
+
React.useEffect(() => { if (respondendoId !== undefined)
|
|
183
|
+
campo.current?.focus(); }, [respondendoId]);
|
|
184
|
+
// As URLs das miniaturas vivem enquanto o anexo vive; desmontar solta todas (o FileInput faz igual).
|
|
185
|
+
const anexosRef = React.useRef(anexos);
|
|
186
|
+
anexosRef.current = anexos;
|
|
187
|
+
React.useEffect(() => () => anexosRef.current.forEach(a => a.url && URL.revokeObjectURL(a.url)), []);
|
|
188
|
+
const soltar = (lista) => lista.forEach(a => a.url && URL.revokeObjectURL(a.url));
|
|
189
|
+
const escolher = (lista) => {
|
|
190
|
+
if (!lista)
|
|
191
|
+
return;
|
|
192
|
+
const novos = [], fora = [];
|
|
193
|
+
for (const file of Array.from(lista)) {
|
|
194
|
+
if (!matchesAccept(file, opcoes?.accept))
|
|
195
|
+
fora.push({ name: file.name, reason: s.fileWrongType });
|
|
196
|
+
else if (opcoes?.maxSize != null && file.size > opcoes.maxSize)
|
|
197
|
+
fora.push({ name: file.name, reason: s.fileTooLarge });
|
|
198
|
+
else
|
|
199
|
+
novos.push({ id: `a${seq.current++}`, file, url: file.type.startsWith("image/") && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : undefined });
|
|
200
|
+
}
|
|
201
|
+
setRecusados(fora);
|
|
202
|
+
if (novos.length) {
|
|
203
|
+
if (opcoes?.multiple === false) {
|
|
204
|
+
soltar(anexos);
|
|
205
|
+
setAnexos(novos.slice(0, 1));
|
|
206
|
+
}
|
|
207
|
+
else
|
|
208
|
+
setAnexos(prev => [...prev, ...novos]);
|
|
209
|
+
setAviso(`${s.fileAdded}: ${novos.map(a => a.file.name).join(", ")}`);
|
|
210
|
+
}
|
|
211
|
+
if (seletor.current)
|
|
212
|
+
seletor.current.value = "";
|
|
213
|
+
};
|
|
214
|
+
const remover = (a) => { soltar([a]); setAnexos(prev => prev.filter(x => x.id !== a.id)); setAviso(`${s.fileRemoved}: ${a.file.name}`); campo.current?.focus(); };
|
|
215
|
+
const podeAnexar = !!opcoes && editandoId === undefined;
|
|
216
|
+
const vazio = !text.trim() && !(podeAnexar && anexos.length);
|
|
217
|
+
const submit = (e) => {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
if (vazio)
|
|
220
|
+
return;
|
|
221
|
+
const t = text.trim();
|
|
222
|
+
// O segundo argumento só vai quando o app usa uma prop nova: quem chamava `onSend(texto)` e
|
|
223
|
+
// conferia a chamada exata continua recebendo exatamente isso.
|
|
224
|
+
if (!opcoes && replyTo == null && editing == null)
|
|
225
|
+
onSend(t);
|
|
226
|
+
else
|
|
227
|
+
onSend(t, { files: podeAnexar ? anexos.map(a => a.file) : [], replyToId: editandoId === undefined ? respondendoId : undefined, editingId: editandoId });
|
|
228
|
+
setText("");
|
|
229
|
+
soltar(anexos);
|
|
230
|
+
setAnexos([]);
|
|
231
|
+
setRecusados([]);
|
|
232
|
+
};
|
|
233
|
+
const aoTeclar = (e) => {
|
|
234
|
+
if (e.key !== "Escape")
|
|
235
|
+
return;
|
|
236
|
+
if (editandoId !== undefined && onCancelEdit) {
|
|
237
|
+
e.preventDefault();
|
|
238
|
+
onCancelEdit();
|
|
239
|
+
}
|
|
240
|
+
else if (respondendoId !== undefined && onCancelReply) {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
onCancelReply();
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const citacao = editing
|
|
246
|
+
? _jsxs("div", { className: "message-quote", children: [_jsx(Icon, { name: "pencil-simple", size: "sm" }), _jsxs("span", { className: "message-quote-text", children: [_jsx("span", { className: "message-quote-title", children: s.chatEditing }), _jsx("span", { className: "message-quote-body", children: editing.body })] }), onCancelEdit && _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.chatEditCancel, onClick: onCancelEdit })] })
|
|
247
|
+
: replyTo
|
|
248
|
+
? _jsxs("div", { className: "message-quote", children: [_jsx(Icon, { name: "arrow-bend-up-left", size: "sm" }), _jsxs("span", { className: "message-quote-text", children: [_jsxs("span", { className: "message-quote-title", children: [s.chatReplyTo, replyTo.author != null && _jsxs(_Fragment, { children: [" ", replyTo.author] })] }), _jsx("span", { className: "message-quote-body", children: replyTo.body })] }), onCancelReply && _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: s.chatReplyCancel, onClick: onCancelReply })] })
|
|
249
|
+
: null;
|
|
250
|
+
const temFaixa = !!citacao || (podeAnexar && (anexos.length > 0 || recusados.length > 0));
|
|
251
|
+
return _jsxs("form", { className: cx("message-composer", className), onSubmit: submit, children: [_jsxs(InputGroup, { children: [icon && _jsx(InputGroupAddon, { children: _jsx(Icon, { name: icon }) }), temFaixa && _jsxs("div", { className: "input-group-addon input-group-addon-start input-group-addon-block message-composer-context", children: [citacao, podeAnexar && anexos.length > 0 && _jsx("ul", { className: "file-list", "aria-label": s.chatAttachments, children: anexos.map(a => _jsxs("li", { className: "file-item", children: [a.url ? _jsx("img", { className: "file-thumb", src: a.url, alt: "" }) : _jsx(Icon, { name: "file-text" }), _jsx("span", { className: "file-name", children: a.file.name }), _jsx("span", { className: "file-size", children: formatSize(a.file.size) }), _jsx(IconButton, { variant: "ghost", size: "sm", icon: "x", label: `${s.fileRemove} ${a.file.name}`, onClick: () => remover(a) })] }, a.id)) }), podeAnexar && recusados.map((r, n) => _jsxs("span", { className: "field-error", children: [r.reason, ": ", r.name] }, n))] }), _jsx("input", { ref: campo, className: "input", value: text, disabled: disabled, placeholder: placeholder, "aria-label": label ?? s.chatMessage, onChange: e => setText(e.target.value), onKeyDown: aoTeclar }), podeAnexar && _jsx(InputGroupAddon, { side: "end", children: _jsx(IconButton, { variant: "ghost", size: "sm", icon: "paperclip", label: s.chatAttach, disabled: disabled, onClick: () => seletor.current?.click() }) })] }), podeAnexar && _jsx("input", { ref: seletor, type: "file", hidden: true, accept: opcoes?.accept, multiple: opcoes?.multiple !== false, onChange: e => escolher(e.target.files) }), _jsx(IconButton, { type: "submit", variant: "primary", icon: editing ? "check" : "paper-plane-tilt", label: editing ? s.chatSave : (sendLabel ?? s.chatSend), disabled: disabled || vazio }), podeAnexar && _jsx("span", { className: "sr-only", role: "status", "aria-live": "polite", children: aviso })] });
|
|
29
252
|
}
|