@praxisui/list 8.0.0-beta.19 → 8.0.0-beta.20

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.
@@ -0,0 +1,309 @@
1
+ # ADR: Expansão Inline V1 para o `praxis-list`
2
+
3
+ ## Status
4
+
5
+ Proposto.
6
+
7
+ Nao implementado no runtime atual.
8
+
9
+ ## Problema
10
+
11
+ O `praxis-list` ja atende bem leituras fechadas em `list`, `cards` e `tiles`, mas nao oferece um mecanismo oficial para `detail row` ou expansao inline por item.
12
+
13
+ Essa lacuna ficou explicita ao tentar reproduzir referencias corporativas com duas camadas:
14
+
15
+ 1. uma linha executiva resumida;
16
+ 2. um painel expandido inline com secoes como alertas, produtos contratados e proximos eventos.
17
+
18
+ Hoje isso nao cabe no contrato publico atual de `PraxisListConfig`, nem no template do runtime.
19
+
20
+ Consequencias da ausencia de contrato formal:
21
+
22
+ - playground pode cair em hacks de apresentacao que parecem suportar algo que o componente nao suporta;
23
+ - integradores nao tem boundary claro para estado expandido, trigger e acessibilidade;
24
+ - UX corporativa fica inconsistente entre hosts;
25
+ - qualquer tentativa de resolver com `html` livre degrada governanca, consistencia visual e testabilidade.
26
+
27
+ ## Decisao
28
+
29
+ Tratar expansao inline como evolucao formal do contrato do `praxis-list`, com escopo reduzido e governado.
30
+
31
+ Regras normativas da proposta V1:
32
+
33
+ 1. A V1 cobre apenas `layout.variant = 'list'`.
34
+ 2. A V1 adiciona dois blocos novos ao contrato: `interaction` e `expansion`.
35
+ 3. `interaction` governa habilitacao, trigger e politica de estado expandido.
36
+ 4. `expansion` governa apenas conteudo expandido inline estruturado, nao markup arbitrario.
37
+ 5. O conteudo expandido e composto por secoes com tipos fechados e previsiveis.
38
+ 6. `cards` e `tiles` ficam fora do escopo da V1.
39
+ 7. `selection` e expansao podem coexistir, mas o trigger padrao deve favorecer icone dedicado quando houver selecao ativa.
40
+ 8. A API canônica so deve promover esse contrato para `Active` depois de implementacao, testes de acessibilidade e exemplos oficiais.
41
+
42
+ ## Motivacao
43
+
44
+ Essa decisao existe para resolver um problema real de produto sem quebrar o perfil enterprise do componente.
45
+
46
+ Objetivos:
47
+
48
+ - manter integridade do contrato;
49
+ - evitar hacks de playground como falsa demostracao de capacidade;
50
+ - preservar acessibilidade e navegacao por teclado;
51
+ - limitar a V1 a um conjunto pequeno de padroes forte, em vez de abrir um renderer arbitrario;
52
+ - dar base formal para examples, authoring e documentacao futura.
53
+
54
+ Referencia visual-alvo desta evolucao:
55
+
56
+ - `praxis-ui-landing-page/issues/playground/problemas/referencia-list/img.png`
57
+ - `praxis-ui-landing-page/issues/playground/problemas/referencia-list/img_1.png`
58
+
59
+ Essas imagens representam o objetivo funcional e visual da combinacao:
60
+
61
+ 1. linha executiva fechada com KPIs e acoes;
62
+ 2. expansao inline governada com secoes semanticas abaixo da linha principal.
63
+
64
+ ## Proposta de Contrato V1
65
+
66
+ ### Bloco `interaction`
67
+
68
+ ```ts
69
+ interaction?: {
70
+ expandable?: boolean;
71
+ expandTrigger?: 'row' | 'icon' | 'row+icon';
72
+ expandMode?: 'single' | 'multiple';
73
+ collapseOnPageChange?: boolean;
74
+ };
75
+ ```
76
+
77
+ Semantica:
78
+
79
+ - `expandable`
80
+ - default: `false`
81
+ - habilita ou nao o estado expandido por item.
82
+ - `expandTrigger`
83
+ - default proposto: `'row'`
84
+ - quando `selection.mode !== 'none'`, default recomendado: `'icon'`
85
+ - evita conflito entre selecao e expansao na mesma superficie.
86
+ - `expandMode`
87
+ - default: `'single'`
88
+ - `'single'`: uma linha aberta por vez.
89
+ - `'multiple'`: varias linhas abertas ao mesmo tempo.
90
+ - `collapseOnPageChange`
91
+ - default: `true`
92
+ - simplifica comportamento em listas remotas e reduz risco de estado invisivel apos paginação.
93
+
94
+ ### Bloco `expansion`
95
+
96
+ ```ts
97
+ interaction?: {
98
+ expandable?: boolean;
99
+ expandTrigger?: 'row' | 'icon' | 'row+icon';
100
+ expandMode?: 'single' | 'multiple';
101
+ collapseOnPageChange?: boolean;
102
+ };
103
+
104
+ expansion?: {
105
+ emptyLabel?: string;
106
+ sections: Array<{
107
+ id: string;
108
+ title?: string;
109
+ type: 'info-list' | 'chip-list' | 'timeline' | 'key-value';
110
+ itemsExpr: string;
111
+ emptyLabel?: string;
112
+ }>;
113
+ };
114
+ ```
115
+
116
+ Semantica:
117
+
118
+ - `expansion.sections`
119
+ - obrigatorio quando `interaction.expandable = true`
120
+ - define as secoes renderizadas abaixo da linha principal.
121
+ - `id`
122
+ - chave estavel da secao.
123
+ - `title`
124
+ - rotulo visual e acessivel da secao.
125
+ - `type`
126
+ - renderer fechado e governado da secao.
127
+ - `itemsExpr`
128
+ - expressao que aponta para a colecao renderizada a partir do item atual.
129
+ - `emptyLabel`
130
+ - fallback local ou global quando a secao nao tiver conteudo.
131
+
132
+ ## Tipos de Secao da V1
133
+
134
+ ### `info-list`
135
+
136
+ Uso:
137
+
138
+ - alertas;
139
+ - observacoes;
140
+ - pendencias simples.
141
+
142
+ Shape esperado do array:
143
+
144
+ ```ts
145
+ Array<{
146
+ label?: string;
147
+ value: string;
148
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
149
+ icon?: string;
150
+ }>
151
+ ```
152
+
153
+ ### `chip-list`
154
+
155
+ Uso:
156
+
157
+ - produtos contratados;
158
+ - tags operacionais;
159
+ - capacidades ativas.
160
+
161
+ Shape esperado do array:
162
+
163
+ ```ts
164
+ Array<{
165
+ label: string;
166
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
167
+ }>
168
+ ```
169
+
170
+ ### `timeline`
171
+
172
+ Uso:
173
+
174
+ - proximos eventos;
175
+ - vencimentos;
176
+ - marcos operacionais.
177
+
178
+ Shape esperado do array:
179
+
180
+ ```ts
181
+ Array<{
182
+ date?: string;
183
+ label: string;
184
+ helper?: string;
185
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
186
+ }>
187
+ ```
188
+
189
+ ### `key-value`
190
+
191
+ Uso:
192
+
193
+ - bloco resumido de fatos;
194
+ - indicadores secundarios;
195
+ - metadados regulatórios.
196
+
197
+ Shape esperado do array:
198
+
199
+ ```ts
200
+ Array<{
201
+ key: string;
202
+ value: string;
203
+ }>
204
+ ```
205
+
206
+ ## Exemplo V1
207
+
208
+ ```ts
209
+ const config = {
210
+ dataSource: {
211
+ resourcePath: 'api/customer-accounts/vw-account-portfolio',
212
+ sort: ['saldoDisponivel,desc'],
213
+ },
214
+ layout: {
215
+ variant: 'list',
216
+ lines: 2,
217
+ density: 'comfortable',
218
+ dividers: 'between',
219
+ pageSize: 8,
220
+ },
221
+ interaction: {
222
+ expandable: true,
223
+ expandTrigger: 'icon',
224
+ expandMode: 'single',
225
+ collapseOnPageChange: true,
226
+ },
227
+ templating: {
228
+ leading: { type: 'text', expr: '${item.sigla}' },
229
+ primary: { type: 'text', expr: '${item.nomeConta}' },
230
+ secondary: { type: 'text', expr: '${item.segmento} • ${item.tipoConta}' },
231
+ meta: { type: 'currency', expr: '${item.saldoDisponivel}|BRL:pt-BR' },
232
+ trailing: { type: 'chip', expr: '${item.risco}', variant: 'outlined' },
233
+ features: [
234
+ { icon: 'account_balance_wallet', expr: 'Saldo ${item.saldo|currency:BRL:pt-BR}' },
235
+ { icon: 'credit_card', expr: 'Limite ${item.limite|currency:BRL:pt-BR}' },
236
+ { icon: 'percent', expr: 'Uso ${item.usoPercentual}%' },
237
+ ],
238
+ },
239
+ expansion: {
240
+ emptyLabel: 'Nenhum detalhe complementar disponivel.',
241
+ sections: [
242
+ {
243
+ id: 'alerts',
244
+ title: 'Alertas',
245
+ type: 'info-list',
246
+ itemsExpr: '${item.alertas}',
247
+ emptyLabel: 'Sem alertas ativos.',
248
+ },
249
+ {
250
+ id: 'products',
251
+ title: 'Produtos contratados',
252
+ type: 'chip-list',
253
+ itemsExpr: '${item.produtosContratados}',
254
+ },
255
+ {
256
+ id: 'events',
257
+ title: 'Próximos eventos',
258
+ type: 'timeline',
259
+ itemsExpr: '${item.proximosEventos}',
260
+ emptyLabel: 'Sem eventos futuros.',
261
+ },
262
+ ],
263
+ },
264
+ };
265
+ ```
266
+
267
+ ## Fora de Escopo na V1
268
+
269
+ - expansao em `cards` e `tiles`;
270
+ - conteudo arbitrario via `html` como surface principal da expansao;
271
+ - tabs dentro da expansao;
272
+ - formulários inline dentro da detail row;
273
+ - persistencia cross-page do estado aberto;
274
+ - nesting de expansao dentro de expansao.
275
+
276
+ ## Acessibilidade e WCAG AA
277
+
278
+ Requisitos minimos da implementacao:
279
+
280
+ 1. item expansivel deve expor `aria-expanded`;
281
+ 2. trigger por icone deve expor `aria-controls`;
282
+ 3. a regiao expandida deve ter `role="region"` e rotulo associado;
283
+ 4. foco por teclado deve permitir abrir/fechar sem mouse;
284
+ 5. contraste visual da area expandida deve respeitar AA;
285
+ 6. expansao nao deve depender apenas de cor para indicar estado.
286
+
287
+ ## Impacto Esperado
288
+
289
+ Beneficios:
290
+
291
+ - capacidade oficial para padroes de “linha + detalhe inline”;
292
+ - melhor aderencia a cenarios enterprise densos;
293
+ - playground, editor e examples passam a demonstrar capacidade real;
294
+ - reduz pressao para criar componentes duplicados apenas por detail row.
295
+
296
+ Tradeoffs:
297
+
298
+ - aumenta a superficie do contrato publico;
299
+ - exige testes adicionais de keyboard/focus/ARIA;
300
+ - exige governanca de interacao com `selection`, `actions` e paginação remota.
301
+
302
+ ## Referencias
303
+
304
+ - `projects/praxis-list/src/lib/models/list-config.model.ts`
305
+ - `projects/praxis-list/src/lib/components/praxis-list.component.html`
306
+ - `projects/praxis-list/src/lib/components/praxis-list.component.scss`
307
+ - `projects/praxis-list/docs/adr/2026-03-list-authoring-protocol.md`
308
+ - `praxis-ui-landing-page/issues/playground/problemas/referencia-list/img.png`
309
+ - `praxis-ui-landing-page/issues/playground/problemas/referencia-list/img_1.png`
@@ -6940,13 +6940,13 @@ const LIST_AI_CAPABILITIES = {
6940
6940
  path: 'actions[].globalAction.[actionId]',
6941
6941
  category: 'actions',
6942
6942
  valueKind: 'string',
6943
- description: 'Canonical global app action id (ex.: "toast.success").',
6943
+ description: 'Canonical global app action id (ex.: "toast.success" ou "navigation.openRoute").',
6944
6944
  },
6945
6945
  {
6946
6946
  path: 'actions[].globalAction.payload',
6947
6947
  category: 'actions',
6948
6948
  valueKind: 'object',
6949
- description: 'Structured payload for the global app action (JSON/template).',
6949
+ description: 'Structured payload for the global app action (JSON/template), including internal route payloads resolved from item context.',
6950
6950
  },
6951
6951
  {
6952
6952
  path: 'actions[].emitLocal',
@@ -10267,11 +10267,11 @@ class PraxisList {
10267
10267
  syncPaginatorIntl() {
10268
10268
  const locale = this.config?.i18n?.locale || this.hostLocale || 'en-US';
10269
10269
  const english = this.isEnglishLocaleValue(locale);
10270
- this.paginatorIntl.itemsPerPageLabel = this.t('pagination.itemsPerPage', english ? 'Items per page:' : 'Itens por página:');
10271
- this.paginatorIntl.nextPageLabel = this.t('pagination.nextPage', english ? 'Next page' : 'Próxima página');
10272
- this.paginatorIntl.previousPageLabel = this.t('pagination.previousPage', english ? 'Previous page' : 'Página anterior');
10273
- this.paginatorIntl.firstPageLabel = this.t('pagination.firstPage', english ? 'First page' : 'Primeira página');
10274
- this.paginatorIntl.lastPageLabel = this.t('pagination.lastPage', english ? 'Last page' : 'Última página');
10270
+ this.paginatorIntl.itemsPerPageLabel = this.tWithLocale(locale, 'pagination.itemsPerPage', english ? 'Items per page:' : 'Itens por página:');
10271
+ this.paginatorIntl.nextPageLabel = this.tWithLocale(locale, 'pagination.nextPage', english ? 'Next page' : 'Próxima página');
10272
+ this.paginatorIntl.previousPageLabel = this.tWithLocale(locale, 'pagination.previousPage', english ? 'Previous page' : 'Página anterior');
10273
+ this.paginatorIntl.firstPageLabel = this.tWithLocale(locale, 'pagination.firstPage', english ? 'First page' : 'Primeira página');
10274
+ this.paginatorIntl.lastPageLabel = this.tWithLocale(locale, 'pagination.lastPage', english ? 'Last page' : 'Última página');
10275
10275
  this.paginatorIntl.getRangeLabel = (page, pageSize, length) => this.formatPaginatorRangeLabel(page, pageSize, length, locale);
10276
10276
  this.paginatorIntl.changes.next();
10277
10277
  }
@@ -10317,6 +10317,7 @@ class PraxisList {
10317
10317
  this.initializeDataStreams();
10318
10318
  this.config = normalizeListConfig(plan.canonicalConfig);
10319
10319
  this.appliedRuntimeConfig = this.config;
10320
+ this.syncPaginatorIntl();
10320
10321
  this.syncExpansionState();
10321
10322
  if (plan.runtime?.applyConfig) {
10322
10323
  this.data.setConfig(this.config);
@@ -11041,6 +11042,9 @@ class PraxisList {
11041
11042
  t(key, fallback) {
11042
11043
  return this.i18n.t(key, undefined, fallback, PRAXIS_LIST_I18N_NAMESPACE);
11043
11044
  }
11045
+ tWithLocale(locale, key, fallback) {
11046
+ return this.i18n.tForLocale(locale, key, undefined, fallback, PRAXIS_LIST_I18N_NAMESPACE);
11047
+ }
11044
11048
  ensureExpansionItemObjectId(value) {
11045
11049
  const current = this.expansionItemObjectIds.get(value);
11046
11050
  if (current)
@@ -12729,6 +12733,29 @@ const PRAXIS_LIST_AUTHORING_MANIFEST = {
12729
12733
  params: { id: 'edit', placement: 'actions', showLoading: true },
12730
12734
  isPositive: true,
12731
12735
  },
12736
+ {
12737
+ id: 'add-navigation-open-route-action',
12738
+ request: 'Add an item action that opens the guide route with the selected employee id.',
12739
+ operationId: 'item.action.add',
12740
+ params: {
12741
+ id: 'open-guide-route',
12742
+ label: 'Abrir Guia',
12743
+ icon: 'travel_explore',
12744
+ kind: 'button',
12745
+ buttonVariant: 'stroked',
12746
+ globalAction: {
12747
+ actionId: 'navigation.openRoute',
12748
+ payload: {
12749
+ path: '/components-showcase/surface-open-guide',
12750
+ query: { funcionarioId: '${item.id}' },
12751
+ state: { funcionarioId: '${item.id}', source: 'surface-open-list-demo' },
12752
+ },
12753
+ },
12754
+ emitLocal: false,
12755
+ showLoading: true,
12756
+ },
12757
+ isPositive: true,
12758
+ },
12732
12759
  {
12733
12760
  id: 'remove-item-action-with-confirmation',
12734
12761
  request: 'Remove the edit action after confirmation.',
package/index.d.ts CHANGED
@@ -730,6 +730,7 @@ declare class PraxisList implements OnInit, OnChanges, OnDestroy {
730
730
  private syncExpansionState;
731
731
  private itemExpansionKey;
732
732
  t(key: string, fallback: string): string;
733
+ private tWithLocale;
733
734
  private ensureExpansionItemObjectId;
734
735
  private sanitizeDomId;
735
736
  private evaluateExpansionExpr;
package/package.json CHANGED
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "name": "@praxisui/list",
3
- "version": "8.0.0-beta.19",
3
+ "version": "8.0.0-beta.20",
4
4
  "description": "List components and helpers for Praxis UI.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": ">=16 <21",
7
7
  "@angular/core": ">=16 <21",
8
8
  "@angular/material": ">=16 <21",
9
- "@praxisui/dynamic-fields": "^8.0.0-beta.19",
10
- "rxjs": ">=7 <9"
9
+ "@praxisui/dynamic-fields": "^8.0.0-beta.20",
10
+ "rxjs": ">=7 <9",
11
+ "@angular/forms": ">=16 <21",
12
+ "@angular/router": ">=16 <21",
13
+ "@praxisui/ai": "^8.0.0-beta.20",
14
+ "@praxisui/core": "^8.0.0-beta.20",
15
+ "@praxisui/rich-content": "^8.0.0-beta.20",
16
+ "@praxisui/settings-panel": "^8.0.0-beta.20"
11
17
  },
12
18
  "dependencies": {
13
19
  "tslib": "^2.3.0",