@softize/opus 25.2.0 → 25.3.1

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.
@@ -215,6 +215,37 @@ periods: [
215
215
  // presetRange('last7') → { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' } — o mesmo cálculo do pattern.
216
216
  ```
217
217
 
218
+ ## Agrupamento
219
+
220
+ Declare `grouping.fields` quando uma dimensão fechada puder organizar a tabela sem mudar o recorte
221
+ de negócio. Cada campo precisa ser um filtro `select` com opções `static` ou `dictionary`; texto,
222
+ número, data e lookup não são dimensões de agrupamento. O filtro pode aceitar uma ou várias opções:
223
+ cada consulta de grupo envia um único valor na forma declarada. A lista continua plana por padrão,
224
+ e a pessoa escolhe **Agrupar por** em **Exibição**.
225
+
226
+ ```ts
227
+ filters: {
228
+ status: {
229
+ label: 'Status',
230
+ type: 'select',
231
+ options: { kind: 'dictionary', ref: 'workspace.status' },
232
+ },
233
+ },
234
+ grouping: { fields: ['status'] },
235
+ paginate: 'offset',
236
+ ```
237
+
238
+ O Opus consulta a mesma `ListAction` uma vez para cada opção, aplicando o valor ao filtro e
239
+ preservando busca, período, demais filtros e ordenação. Por isso, o handler precisa devolver
240
+ `total` e a action precisa usar `paginate: 'offset'`: o cabeçalho recolhível apresenta a contagem
241
+ autoritativa, e **Carregar mais** expande apenas aquele grupo. A soma aparece no rodapé, sem paginador
242
+ global. O limite é de doze opções por dimensão. Se um dicionário não estiver disponível ou exceder
243
+ esse limite, a dimensão não é oferecida em **Agrupar por**.
244
+
245
+ O agrupamento pertence à tabela derivada. Views alternativas e o modo `children` continuam planos.
246
+ `groupBy` integra `ActionListState`; `listStateToParams` o serializa como `group=<campo>`, e filtros
247
+ aplicados à mesma dimensão restringem os grupos exibidos.
248
+
218
249
  ## Paginação e carregamento
219
250
 
220
251
  `ActionList` envia `limit`, definido por `pageSize`, e `page` no input. O handler aplica o recorte e
@@ -481,21 +512,23 @@ render(
481
512
 
482
513
  ## Declaração na ListAction
483
514
 
484
- | Chave | O que declara |
485
- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
486
- | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
487
- | `filters` | `{ [nome]: { label, type, options?, multiple?, presentation?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `presentation: 'toggle'` mostra todas as opções curtas de um `select` de escolha única em um `ToggleGroup`; sem a propriedade, o controle continua sendo `Select`. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
488
- | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
489
- | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
490
- | `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
515
+ | Chave | O que declara |
516
+ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
517
+ | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` nasce oculta e pode ser exibida pelo seletor de colunas; `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
518
+ | `filters` | `{ [nome]: { label, type, options?, multiple?, presentation?, searchable?, allowEmpty?, placement?, section?, depends?, … } }` — a toolbar. `placement` classifica o controle como `inline`, `advanced` ou `external`; filtros avançados com `section` são agrupados no diálogo; `external` mantém estado e input, mas delega a apresentação ao consumidor. `presentation: 'toggle'` mostra todas as opções curtas de um `select` de escolha única em um `ToggleGroup`; sem a propriedade, o controle continua sendo `Select`. `searchable` habilita busca local em `select`; `allowEmpty: false` omite a opção vazia. `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
519
+ | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Ela aparece depois dos filtros inline e imediatamente antes do acesso aos filtros avançados. A busca participa da contagem de controles inline: com mais de um controle, a barra exibe as labels; quando ela é o único recorte, sua label é omitida. |
520
+ | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
521
+ | `periods` | `{ value, label, default? }[]` — o controle de período (presets + Personalizado com calendário); `default: true` marca o preset inicial, senão vale o primeiro; materializa em `from`/`to` no input. |
522
+ | `grouping` | `{ fields }` — dimensões fechadas disponíveis em **Agrupar por**. Cada nome referencia um filtro `select` com opções estáticas ou de dicionário. Não define agrupamento inicial. |
491
523
 
492
524
  ## useListAction
493
525
 
494
- Quando o layout não cabe no `ActionList`, `useListAction(action, input)` entrega o mesmo fetch
526
+ Quando o layout não cabe no `ActionList`, `useListAction(action, input, options?)` entrega o mesmo fetch
495
527
  declarativo (`items`, `total`, `error`, `isLoading`, `isFetching`, `refetch`) sob o
496
528
  `QueryClientProvider`, e `presetRange(preset)` materializa os presets de período (`today`, `last7`,
497
529
  `thisMonth`…) em `{ from, to }` no formato `YYYY-MM-DD` — o mesmo cálculo que o pattern faz antes de
498
- chamar a action.
530
+ chamar a action. `options.enabled` suspende a consulta enquanto for `false`; sem a opção, vale
531
+ `true`.
499
532
 
500
533
  ## Propriedades de ActionList
501
534
 
@@ -225,7 +225,12 @@ caminho que passa por uma lista vale para cada item, e um campo intermediário `
225
225
  endereço, e o nome já vai como `title`. Um caminho que termina num objeto ou numa lista leva tudo o
226
226
  que está abaixo dele, inclusive campos acrescentados depois à saída; para conferir num teste o que
227
227
  cada caminho alcança, use `assistantContextLeaves`, de `@softize/opus/assistant-schema`, sobre o
228
- schema de saída do manifest. O `title` precisa apontar para texto ou número.
228
+ schema de saída do manifest. Ela devolve, por caminho, as folhas em `leaves` e, em `open`, os pontos
229
+ que a conferência não alcança e por onde qualquer campo pode passar: um campo sem forma declarada
230
+ (`z.unknown()`, `z.any()`), uma estrutura recursiva, uma referência que não resolve e, como
231
+ `<caminho>.*`, um objeto que aceita chaves não declaradas (`.passthrough()`). Num teste que reprova
232
+ campo pessoal, trate também `open`: reprove cada ponto aberto ou justifique por que ele não leva dado
233
+ pessoal. O `title` precisa apontar para texto ou número.
229
234
 
230
235
  O `opus check` cobra a forma da declaração (regra `assistant-declaration`), o `opus gen` recusa
231
236
  `title`, caminho de `context` ou entrada que não conferem com os schemas, e o registro recusa duas
@@ -38,7 +38,7 @@ function AssistantDemo() {
38
38
  </DialogContent>
39
39
  </Dialog>
40
40
  {editing && (
41
- <FloatingLayer onEscapeKeyDown={() => setAssistant(false)}>
41
+ <FloatingLayer onEscapeKeyDown={assistant ? () => setAssistant(false) : undefined}>
42
42
  {assistant ? (
43
43
  <aside
44
44
  aria-label="Assistente"
@@ -126,6 +126,11 @@ não recebe foco, nenhum modal fecha e o Opus chama `onEscapeKeyDown` uma vez po
126
126
  no modal, o Esc fecha o modal, como sempre. Sem modal aberto, `onEscapeKeyDown` também é chamado
127
127
  quando a pessoa está na camada.
128
128
 
129
+ Passe `onEscapeKeyDown` só enquanto a camada tiver o que fechar. Sem ele, o Esc com a pessoa na
130
+ camada segue para o modal aberto, que fecha. É o que permite a quem chega ao botão do assistente
131
+ pelo Tab sair do modal com o Esc. No exemplo acima, o handler só existe com o painel aberto: com o
132
+ painel, o Esc fecha o painel; com o botão, fecha o formulário.
133
+
129
134
  Trate o Esc da camada só por `onEscapeKeyDown`. Para impedir que o modal feche, o Opus marca o evento
130
135
  com `preventDefault` antes de ele chegar aos elementos da camada; um `onKeyDown` próprio para Esc no
131
136
  painel trataria a tecla duas vezes ou leria um evento já marcado.
@@ -164,15 +169,17 @@ você.
164
169
  - O hospedeiro carrega `aria-live="off"`, o valor padrão do atributo: a camada não anuncia mudanças
165
170
  por conta própria, e regiões vivas de dentro dela, como a de um [`Ask`](/ui/ask), mantêm o próprio
166
171
  comportamento. O atributo é o que mantém a camada visível ao leitor de tela com um modal aberto.
167
- - O foco e o Tab descritos acima são verificados em Chromium por testes automatizados. Safari,
168
- Firefox e a leitura com VoiceOver ainda não foram verificados: o roteiro manual da ADR 0052 roda
169
- na adoção pela Grand Brasil, em Safari com VoiceOver e no Firefox, antes de ela chegar à versão
170
- principal da plataforma. Leitores de tela do Windows, como o NVDA, ficam fora dessa verificação.
172
+ - O foco, o Tab e o Esc descritos acima são verificados em Chromium por testes automatizados.
173
+ Safari, Firefox e a leitura com VoiceOver não foram verificados. Em 2026-10-05, na adoção pela
174
+ Grand Brasil, o dono do produto dispensou o roteiro manual da ADR 0052 para esses navegadores, e
175
+ ele não foi executado. Se surgir um problema de leitor de tela, de Safari ou de Firefox com a
176
+ camada sobre um modal, o roteiro volta a ser exigido. Leitores de tela do Windows, como o NVDA,
177
+ ficam fora dessa verificação.
171
178
 
172
179
  ## Propriedades de FloatingLayer
173
180
 
174
- | Propriedade | Tipo | Padrão | Descrição |
175
- | ----------------- | -------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
176
- | `children` | `ReactNode` | | Elementos da camada. Cada filho direto recebe clique e se posiciona contra a janela. |
177
- | `className` | `string` | | Classes do hospedeiro. A posição de cada filho continua sendo sua. |
178
- | `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | | Chamado uma vez por tecla quando a pessoa aperta Esc na camada, com ou sem modal aberto. Nenhum modal fecha nesse caso, e o Opus não fecha a camada. |
181
+ | Propriedade | Tipo | Padrão | Descrição |
182
+ | ----------------- | -------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
183
+ | `children` | `ReactNode` | | Elementos da camada. Cada filho direto recebe clique e se posiciona contra a janela. |
184
+ | `className` | `string` | | Classes do hospedeiro. A posição de cada filho continua sendo sua. |
185
+ | `onEscapeKeyDown` | `(event: KeyboardEvent) => void` | | Chamado uma vez por tecla quando a pessoa aperta Esc na camada, com ou sem modal aberto. Nenhum modal fecha nesse caso, e o Opus não fecha a camada. Sem ele, o Esc na camada fecha o modal aberto. |
@@ -0,0 +1,50 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from 'react'
2
+ import type { ClientAdapter } from '../../core/index.ts'
3
+
4
+ /**
5
+ * Dicionário estrutural pro resolvedor de opções: qualquer coisa com `options()`
6
+ * satisfaz o contrato sem este driver importar o driver de schema.
7
+ */
8
+ export interface DictLike {
9
+ options(): Array<{ value: string; label: string; icon?: string }>
10
+ }
11
+
12
+ interface OpusContextValue {
13
+ client: ClientAdapter
14
+ dicts: Record<string, DictLike>
15
+ }
16
+
17
+ const OpusContext = createContext<OpusContextValue | null>(null)
18
+
19
+ export interface OpusProviderProps {
20
+ client: ClientAdapter
21
+ /** Dicionários por ref usados por filtros e campos declarativos. */
22
+ dicts?: Record<string, DictLike>
23
+ children: ReactNode
24
+ }
25
+
26
+ export function OpusProvider({ client, dicts, children }: OpusProviderProps): ReactNode {
27
+ const value = useMemo<OpusContextValue>(() => ({ client, dicts: dicts ?? {} }), [client, dicts])
28
+ return <OpusContext.Provider value={value}>{children}</OpusContext.Provider>
29
+ }
30
+
31
+ /** Acesso interno ao adapter; não é reexportado por `@softize/opus/ui/react`. */
32
+ export function useClient(hook: string): ClientAdapter {
33
+ const ctx = useContext(OpusContext)
34
+ if (ctx === null) {
35
+ throw new Error(
36
+ `${hook} foi chamado fora de um <OpusProvider> e não tem como executar a action. ` +
37
+ 'Os hooks de action e os componentes que os usam, como ActionForm, ActionList e ActionView, ' +
38
+ 'precisam de um <OpusProvider client={...}> acima deles na árvore. Envolva a aplicação no provider; ' +
39
+ 'em testes e previews, envolva o componente renderizado.',
40
+ )
41
+ }
42
+ return ctx.client
43
+ }
44
+
45
+ const NO_DICTS: Record<string, DictLike> = {}
46
+
47
+ /** Dicionários do provider; fora dele, o resolvedor usa os fallbacks do contrato. */
48
+ export function useDicts(): Record<string, DictLike> {
49
+ return useContext(OpusContext)?.dicts ?? NO_DICTS
50
+ }
@@ -12,12 +12,9 @@
12
12
  */
13
13
 
14
14
  import {
15
- createContext,
16
15
  useCallback,
17
- useContext,
18
16
  useMemo,
19
17
  useState,
20
- type ReactNode,
21
18
  } from 'react'
22
19
  import {
23
20
  useForm,
@@ -32,7 +29,6 @@ import type {
32
29
  ActionContract,
33
30
  ActionDef,
34
31
  ActionError,
35
- ClientAdapter,
36
32
  FormContract,
37
33
  Paginated,
38
34
  } from '../../core/index.ts'
@@ -42,74 +38,16 @@ import {
42
38
  idleAction,
43
39
  idleLookup,
44
40
  } from '../index.ts'
41
+ import { useClient } from './context.tsx'
42
+ export {
43
+ OpusProvider,
44
+ useDicts,
45
+ type DictLike,
46
+ type OpusProviderProps,
47
+ } from './context.tsx'
45
48
  import { zodErrorMapPtBr } from '../lib/zod-pt-br.ts'
46
49
  import { objectSchemaShape } from '../lib/object-schema.ts'
47
50
 
48
- // =============================================================================
49
- // Provider
50
- // =============================================================================
51
-
52
- /**
53
- * Dicionário estrutural pro resolvedor de `options: { kind: 'dictionary', ref }`:
54
- * qualquer coisa com `options()` → `{ value, label, icon? }`. O `DictType` do `t.dict`
55
- * satisfaz direto — sem este driver importar o driver de schema. A metadata opcional
56
- * continua sendo declarada pelo dicionário; o provider não inventa apresentação.
57
- */
58
- export interface DictLike {
59
- options(): Array<{ value: string; label: string; icon?: string }>
60
- }
61
-
62
- interface OpusContextValue {
63
- client: ClientAdapter
64
- dicts: Record<string, DictLike>
65
- }
66
-
67
- const OpusContext = createContext<OpusContextValue | null>(null)
68
-
69
- export interface OpusProviderProps {
70
- client: ClientAdapter
71
- /** Dicionários por ref — resolvem `options: { kind: 'dictionary', ref }` de
72
- * filters/fields (ActionList/ActionForm). Campo de dict sem registro aqui ainda
73
- * resolve pelo fallback (a meta do `t.dict` viaja no schema do contrato). */
74
- dicts?: Record<string, DictLike>
75
- children: ReactNode
76
- }
77
-
78
- export function OpusProvider({
79
- client,
80
- dicts,
81
- children,
82
- }: OpusProviderProps): ReactNode {
83
- const value = useMemo<OpusContextValue>(() => ({ client, dicts: dicts ?? {} }), [client, dicts])
84
- return <OpusContext.Provider value={value}>{children}</OpusContext.Provider>
85
- }
86
-
87
- /**
88
- * O adapter do provider. `hook` nomeia quem chamou, para a mensagem apontar o hook que a
89
- * pessoa vê no stack — muitas vezes chamado por dentro de ActionForm, ActionList ou
90
- * ActionView, e não diretamente pela tela.
91
- */
92
- function useClient(hook: string): ClientAdapter {
93
- const ctx = useContext(OpusContext)
94
- if (ctx === null) {
95
- throw new Error(
96
- `${hook} foi chamado fora de um <OpusProvider> e não tem como executar a action. ` +
97
- 'Os hooks de action e os componentes que os usam, como ActionForm, ActionList e ActionView, ' +
98
- 'precisam de um <OpusProvider client={...}> acima deles na árvore. Envolva a aplicação no provider; ' +
99
- 'em testes e previews, envolva o componente renderizado.',
100
- )
101
- }
102
- return ctx.client
103
- }
104
-
105
- const NO_DICTS: Record<string, DictLike> = {}
106
-
107
- /** Dicts do provider. Default {} — inclusive fora do provider: o resolvedor cai
108
- * nos fallbacks (estáticas do spec, meta do `t.dict`) sem exigir registry. */
109
- export function useDicts(): Record<string, DictLike> {
110
- return useContext(OpusContext)?.dicts ?? NO_DICTS
111
- }
112
-
113
51
  // =============================================================================
114
52
  // useAction
115
53
  // =============================================================================
@@ -265,6 +203,7 @@ export interface UseListActionResult<TItem> {
265
203
  export function useListAction<TItem = unknown, TInput extends Record<string, unknown> = Record<string, unknown>>(
266
204
  action: { name: string; kind: 'list' },
267
205
  input?: TInput,
206
+ options?: { enabled?: boolean },
268
207
  ): UseListActionResult<TItem> {
269
208
  const client = useClient('useListAction')
270
209
 
@@ -278,6 +217,7 @@ export function useListAction<TItem = unknown, TInput extends Record<string, unk
278
217
  // Input mudou (filtro/página) → mantém os itens na tela enquanto busca: o
279
218
  // ActionList esmaece em vez de piscar skeleton. isPending = só o 1º load.
280
219
  placeholderData: keepPreviousData,
220
+ enabled: options?.enabled ?? true,
281
221
  })
282
222
 
283
223
  const refetch = useCallback(async () => {
@@ -12,7 +12,8 @@ import * as React from "react";
12
12
  * - um único ouvinte no `body`, em borbulha, impede que `focusin`, `focusout`, `wheel` e `touchmove`
13
13
  * ligados ao hospedeiro cheguem ao documento, onde a trava de foco do Radix e a trava de rolagem
14
14
  * os veriam;
15
- * - o Esc age onde a pessoa está: na camada, chama o `onEscapeKeyDown` dela e não fecha o modal;
15
+ * - o Esc age onde a pessoa está: na camada que tem `onEscapeKeyDown`, chama esse handler e não
16
+ * fecha o modal; na camada sem handler, segue para o modal, que fecha como sempre;
16
17
  * - o Tab passa do último elemento do modal para a camada e da camada de volta ao modal;
17
18
  * - quando o elemento focado da camada some, o foco volta ao modal;
18
19
  * - o hospedeiro carrega `aria-live="off"`, que faz o `hideOthers` do Radix preservá-lo.
@@ -37,9 +38,12 @@ interface ModalEntry {
37
38
  content: HTMLElement | null;
38
39
  }
39
40
 
41
+ /** O `onEscapeKeyDown` corrente da camada; `undefined` quando ela não tem o que fechar. */
42
+ export type LayerEscape = () => ((event: KeyboardEvent) => void) | undefined;
43
+
40
44
  interface LayerRecord {
41
45
  host: HTMLElement;
42
- escape: (event: KeyboardEvent) => void;
46
+ escape: LayerEscape;
43
47
  dispose: () => void;
44
48
  }
45
49
 
@@ -328,7 +332,7 @@ function removeGlobalListeners(): void {
328
332
  */
329
333
  export function registerFloatingLayer(
330
334
  host: HTMLElement,
331
- escape: (event: KeyboardEvent) => void,
335
+ escape: LayerEscape,
332
336
  ): () => void {
333
337
  const onPointerDown = (event: PointerEvent): void => {
334
338
  // Pertença decidida no aperto: no fim do clique, o elemento apertado pode já ter sumido.
@@ -354,7 +358,7 @@ export function registerFloatingLayer(
354
358
  const onDocumentKeyDown = (event: KeyboardEvent): void => {
355
359
  // Com um modal de trabalho aberto, a composição do modal já tratou o Esc e o marcou.
356
360
  if (event.key !== "Escape" || event.defaultPrevented) return;
357
- if (layerWherePersonIs() === record) escape(event);
361
+ if (layerWherePersonIs() === record) escape()?.(event);
358
362
  };
359
363
  const observer = new MutationObserver(() => {
360
364
  if (pointerAnchor !== null && pointerAnchor.parentNode !== pointerLayer?.host) {
@@ -424,16 +428,20 @@ export function isLayerInteraction(event: {
424
428
  }
425
429
 
426
430
  /**
427
- * Esc no modal de trabalho do topo com a pessoa na camada: nenhum modal fecha e a camada recebe a
428
- * tecla uma única vez. O `preventDefault` impede o Radix de fechar e marca o evento para o ouvinte
429
- * da própria camada, que ignora eventos já tratados.
431
+ * Esc no modal de trabalho do topo com a pessoa numa camada que tem `onEscapeKeyDown`: nenhum modal
432
+ * fecha e a camada recebe a tecla uma única vez. O `preventDefault` impede o Radix de fechar e marca
433
+ * o evento para o ouvinte da própria camada, que ignora eventos já tratados.
434
+ *
435
+ * Numa camada sem handler, a camada não tem o que fechar, e o Esc segue para o modal (nota de
436
+ * 2026-10-05 na seção 5 da ADR 0052): engolir a tecla deixaria quem chega ao FAB pelo Tab sem
437
+ * nenhum efeito.
430
438
  */
431
439
  export function routeEscapeToLayer(event: KeyboardEvent): boolean {
432
440
  if (!layerExemptionsActive()) return false;
433
- const layer = layerWherePersonIs();
434
- if (layer === null) return false;
441
+ const escape = layerWherePersonIs()?.escape();
442
+ if (escape === undefined) return false;
435
443
  event.preventDefault();
436
- layer.escape(event);
444
+ escape(event);
437
445
  return true;
438
446
  }
439
447
 
package/src/ui/meta.ts CHANGED
@@ -388,7 +388,7 @@ export const componentMeta = {
388
388
  name: "action-list",
389
389
  ancestry: "opus",
390
390
  whenToUse:
391
- "Apresente uma coleção pesquisável a partir de uma ListAction. O contrato declara colunas, filtros, período e busca; ActionList coordena visualizações, paginação, seleção em lote e sincronização com a URL. A tabela derivada não cria moldura própria. Em uma página, envolva a lista com Surface para manter título e criação junto da coleção. Para um único recurso, use ActionView.",
391
+ "Apresente uma coleção pesquisável a partir de uma ListAction. O contrato declara colunas, filtros, período, busca e dimensões fechadas de agrupamento; ActionList coordena visualizações, paginação, seleção em lote e sincronização com a URL. A tabela derivada não cria moldura própria. Em uma página, envolva a lista com Surface para manter título e criação junto da coleção. Para um único recurso, use ActionView.",
392
392
  },
393
393
  "action-trigger": {
394
394
  name: "action-trigger",