@praxisui/table 9.0.0-beta.9 → 9.0.0-beta.90

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.
@@ -7,7 +7,7 @@ Allow users to resize and move columns in `praxis-table` without conflicting wit
7
7
  ## Guiding Principles
8
8
 
9
9
  - `sort` belongs to a dedicated sortable label/container, not to the entire `th`
10
- - `reorder` belongs to a dedicated drag handle
10
+ - `reorder` belongs to the draggable header cell, without a permanent move icon
11
11
  - `resize` belongs to a dedicated handle on the right edge of the header cell
12
12
  - runtime mutations should reuse the existing persistence pipeline through a shared column-mutation coordinator
13
13
  - width persistence should normalize to `px`
@@ -33,15 +33,17 @@ This is the main reason resize must not be introduced as another gesture on the
33
33
 
34
34
  Additional constraints from the current implementation:
35
35
 
36
- - reorder keyboard parity is attached to the `th`, not to a standalone handle
37
- - reorder aria-label and tooltip are currently attached to the `th`
36
+ - reorder keyboard parity is attached to the dedicated header sort trigger through Ctrl/Alt + arrows
37
+ - runtime reorder does not render a visible move icon/handle, matching SAP Fiori and MUI Data Grid patterns
38
38
  - persistence currently saves the full `config` snapshot for each runtime mutation
39
39
  - width styling may already come from `column.width`, `headerStyle`, or `style`
40
40
 
41
41
  Current reorder affordance note:
42
42
 
43
- - the drag handle is rendered before the header text in draggable headers so the grip appears first visually without changing the current `cdkDrag` root on the `th`
44
- - this is a visual/layout choice only in the current implementation slice; the handle is still non-interactive and the full draggable header remains the active drag target
43
+ - reorder is implicit on the draggable header cell, matching enterprise grids such as SAP Fiori and MUI Data Grid
44
+ - the current `cdkDrag` root remains on the `th` so preview, placeholder sizing, and index semantics stay stable
45
+ - `mat-sort-header` is mounted on `.praxis-header-sort-trigger`, not on the full `th`, so sort remains a focused label interaction while header drag remains available
46
+ - keyboard reorder belongs to the focused header sort trigger via Ctrl/Alt + arrow keys
45
47
 
46
48
  The plan must explicitly unwind those couplings instead of treating them as incidental details.
47
49
 
@@ -52,37 +54,32 @@ The plan must explicitly unwind those couplings instead of treating them as inci
52
54
  Refactor the header so each interaction has an explicit zone:
53
55
 
54
56
  - sortable label container: sort
55
- - drag icon/handle: reorder
57
+ - draggable header cell: reorder, with no permanent move icon
56
58
  - right-edge handle: resize
57
59
 
58
60
  Changes in `praxis-table.html`:
59
61
 
60
62
  - keep `.praxis-header-label-text`
61
- - keep the drag handle rendered before the label text while the current reorder UX still relies on the full `th` as drag root
62
- - add a sortable wrapper such as `.praxis-header-sort-trigger`
63
- - keep `.praxis-column-drag-handle`
64
- - add `.praxis-column-resize-handle`
63
+ - add a sortable wrapper `.praxis-header-sort-trigger`
65
64
  - keep `cdkDrag` on the `th`
66
- - add `cdkDragHandle` to the reorder handle so the existing drag root, preview, placeholder sizing, and index semantics stay intact
67
- - move reorder `aria-label`, tooltip, and keyboard listeners from the `th` to the reorder handle
68
- - ensure the resize handle and reorder handle both stop `click`, `pointerdown`, and keyboard bubbling into the sort trigger
65
+ - do not render a visible reorder icon/handle in the runtime header
66
+ - add `.praxis-column-resize-handle`
67
+ - keep keyboard reorder on the header sort trigger without blocking sort Enter/Space behavior
68
+ - ensure the resize handle stops `click`, `pointerdown`, and keyboard bubbling into the sort trigger
69
69
 
70
70
  Changes in `praxis-table.scss`:
71
71
 
72
72
  - make the sortable label container the only area that shows sort cursor/hover treatment
73
- - keep drag handle aligned next to the label text
74
73
  - add resize handle positioned at the far right edge
75
- - use `cursor: grab` for reorder
76
74
  - use `cursor: col-resize` for resize
77
75
  - use a hit area around `8px` to `12px` for resize
78
- - remove `pointer-events: none` from the drag handle and make it a real interactive control
79
76
 
80
77
  Sorting strategy:
81
78
 
82
79
  - do not keep the full `th` as the primary sort interaction target
83
80
  - move the sort trigger to the label/container inside the header cell
84
81
  - preserve keyboard sort semantics on that dedicated sort target
85
- - treat suppression of `click` on reorder/resize handles as a hard requirement, not a best-effort safeguard
82
+ - treat suppression of `click` on resize handles as a hard requirement, not a best-effort safeguard
86
83
 
87
84
  ### Phase 2: Add Runtime Resize State
88
85
 
@@ -125,7 +122,7 @@ On resize start:
125
122
 
126
123
  Behavioral rule:
127
124
 
128
- - dragging only starts from the reorder handle
125
+ - dragging starts from the header cell, except the resize handle and sortable label interactions
129
126
  - sorting only starts from the dedicated sortable label/container
130
127
  - resizing only starts from the right-edge resize handle
131
128
 
@@ -151,6 +148,12 @@ Width rules:
151
148
  - if `column.width` is already in `px`, use it directly
152
149
  - if width is `%`, `auto`, or empty, measure the current header width at first resize and convert to `px`
153
150
  - persist resized widths as `px`
151
+ - resizing changes the internal table content width, not the host/card/viewport width
152
+ - when the summed column widths exceed the viewport, the scroll container owns horizontal overflow
153
+ - when the summed column widths are smaller than the viewport, empty space may remain after the last column instead of redistributing widths implicitly
154
+ - do not resize neighboring columns in `px` mode; the resized column changes and following columns move accordingly
155
+ - when resize is enabled, persisted `px` widths below the effective readable minimum are normalized back into `config.columns[]`, not only corrected during rendering
156
+ - the effective readable minimum includes the column header text, the resize separator gutter, and the sort gutter only when that concrete column is sortable
154
157
  - explicitly define precedence when `headerStyle` or `style` also carry width declarations
155
158
  - document that first user resize converts responsive width intent into fixed runtime width intent
156
159
  - validate width behavior under sticky start/end columns and horizontal scroll mode
@@ -186,9 +189,10 @@ Resize persistence policy:
186
189
 
187
190
  - persist on commit only, not continuously during pointer move
188
191
  - commit point is `pointerup` for pointer interactions
189
- - keyboard resize is not part of the first delivery slice
190
- - when keyboard resize is added later, commit semantics must be explicit (`Enter`, `blur`, or stepwise save), not implied
192
+ - keyboard resize commits stepwise on each handled key event
193
+ - keyboard resize uses `ArrowLeft`, `ArrowRight`, `Shift + Arrow`, `Home`, and `End`
191
194
  - define stale-callback protection for resize and reorder through the same shared coordinator
195
+ - derived table width used during/after resize must be cleared when the visible column contract changes externally
192
196
  - do not add undo in the first slice unless a concrete UX requirement appears, but keep the runtime contract compatible with future undo support
193
197
 
194
198
  Emit a resize event after persistence:
@@ -240,16 +244,18 @@ Resize handle:
240
244
 
241
245
  Keyboard behavior for resize handle:
242
246
 
243
- - defer keyboard resize to a follow-up slice after pointer behavior and persistence are stable
244
- - when implemented, define `ArrowLeft`, `ArrowRight`, `Shift + Arrow`, `Home`, `End`, and `Escape`
245
- - define exactly when the resize is committed and when it is cancelled
247
+ - `ArrowLeft` and `ArrowRight` resize by the default step
248
+ - `Shift + ArrowLeft` and `Shift + ArrowRight` resize by the larger step
249
+ - `Home` applies the configured minimum width
250
+ - `End` applies the configured maximum width when one is configured
251
+ - `Escape` cancels an active pointer resize before commit
252
+ - keyboard resize commits stepwise on each handled key event
246
253
 
247
- Reorder handle:
254
+ Reorder accessibility:
248
255
 
249
- - make the reorder handle a real focusable control
250
- - move existing Alt/Ctrl+Arrow reorder behavior onto the handle
251
- - move existing reorder aria-label and tooltip onto the handle
252
- - preserve visible focus styling and screen-reader parity after removing those affordances from the `th`
256
+ - keep keyboard reorder on the focused header sort trigger with Alt/Ctrl + arrow keys
257
+ - do not add a permanent focusable move icon unless a future accessibility review proves the header-trigger model insufficient
258
+ - preserve visible focus styling and screen-reader parity on the header label/sort trigger
253
259
 
254
260
  ### Phase 8: Testing
255
261
 
@@ -260,7 +266,7 @@ Add focused tests for interaction conflicts:
260
266
  - resize handle click does not bubble into sort click
261
267
  - reorder does not alter width
262
268
  - clicking the sortable label still sorts
263
- - reorder still works through the dedicated `cdkDragHandle`
269
+ - reorder still works through the draggable header cell
264
270
  - keyboard reorder still works after moving intent to the handle
265
271
  - sort keyboard interaction still works after sort leaves the `th`
266
272
 
@@ -300,18 +306,18 @@ Add visual/e2e coverage later:
300
306
  ## Recommended Delivery Sequence
301
307
 
302
308
  1. Refactor header structure so sort is isolated to a dedicated sortable label/container
303
- 2. Move reorder affordance, keyboard handling, aria-label, and tooltip to a real drag handle
304
- 3. Add resize handle with runtime-only pointer support and explicit cancel paths
305
- 4. Validate sticky and horizontal-scroll behavior in the same slice
309
+ 2. Keep reorder implicit on the header and remove stale move-icon guidance from docs/editor copy
310
+ 3. Add resize handle with pointer support, keyboard support, and explicit cancel paths
311
+ 4. Validate sticky and horizontal-scroll behavior in a follow-up hardening slice
306
312
  5. Introduce shared column runtime mutation coordination for reorder + resize persistence
307
313
  6. Persist column width on commit only
308
314
  7. Add focused conflict and persistence tests
309
- 8. Add keyboard resize and final resize a11y semantics in a follow-up slice
315
+ 8. Add broader sticky, horizontal-scroll, and non-data-column E2E coverage
310
316
  9. Enable only through config/feature flag
311
317
 
312
318
  ## Definition of Done
313
319
 
314
- - users can reorder only from the drag handle
320
+ - users can reorder from the header without a permanent move icon
315
321
  - users can resize only from the right-edge handle
316
322
  - clicking the sortable label still sorts
317
323
  - reorder, resize, and sort do not interfere with each other
@@ -326,6 +332,8 @@ Add visual/e2e coverage later:
326
332
  - first resize may intentionally convert responsive width behavior into fixed pixel width behavior
327
333
  - sticky start/end columns and horizontal scroll are immediate implementation concerns
328
334
  - resize and reorder mutations need a shared commit/stale-callback policy, not parallel ad hoc logic
335
+ - table-level width is runtime-derived state; it must not outlive the column contract that produced it
336
+ - the canonical enterprise default follows the SAP/MUI model for fixed-width columns: resize the column and the internal content width, keep the outer viewport stable, and use horizontal overflow when needed
329
337
 
330
338
  ## Suggested Follow-Up
331
339
 
@@ -466,24 +466,24 @@ Todos os itens abaixo devem estar `true`:
466
466
  14. Revalidacao complementar do wrapper CRUD apos o hardening:
467
467
  - `ng test praxis-crud --watch=false --browsers=ChromeHeadless --polyfills=zone.js --polyfills=zone.js/testing`;
468
468
  - `TOTAL: 30 SUCCESS`.
469
- 15. Validacao da lane dedicada no WSL:
469
+ 15. Validacao da lane dedicada:
470
470
  - `bash ./scripts/test-table-preimpl-lane.sh`: bloqueada por infraestrutura (`No usable Chromium binary found for headless capture`).
471
471
  - fallback operacional via Windows Chrome com os mesmos includes da lane:
472
472
  `ng test praxis-table --watch=false --browsers=ChromeHeadless --polyfills=zone.js --polyfills=zone.js/testing --include ...`;
473
473
  - resultado do fallback: `TOTAL: 48 SUCCESS`.
474
- 16. Mitigacao operacional no WSL validada sem alterar o script da lane:
474
+ 16. Mitigacao operacional validada sem alterar o script da lane:
475
475
  - wrapper: `tools/karma/chromium-bin-wrapper.sh` (contorna apenas probe `--dump-dom`);
476
476
  - execucao:
477
477
  `CHROMIUM_REAL_BIN=<puppeteer-executablePath> CHROMIUM_BIN=tools/karma/chromium-bin-wrapper.sh CHROME_BIN=tools/karma/chromium-bin-wrapper.sh bash ./scripts/test-table-preimpl-lane.sh`;
478
478
  - resultado: `TOTAL: 48 SUCCESS`.
479
- 17. Revalidacao integrada do gate seguro apos mitigacao WSL:
480
- - `CHROMIUM_REAL_BIN=<puppeteer-executablePath> CHROMIUM_BIN=tools/karma/chromium-bin-wrapper.sh CHROME_BIN=tools/karma/chromium-bin-wrapper.sh bash tools/karma/run-safe-gate.sh`;
479
+ 17. Revalidacao integrada do gate seguro apos mitigacao:
480
+ - gate legado removido; usar `npm run test:table` e, quando aplicavel, `bash tools/karma/run-table-dsl-compat-gate.sh`;
481
481
  - `praxis-table lane`: `PASS` (`TOTAL: 48 SUCCESS`);
482
482
  - `praxis-crud full`: `PASS` (`TOTAL: 30 SUCCESS`).
483
- 18. Automacao do gate seguro no WSL concluida:
484
- - `tools/karma/run-safe-gate.sh` agora auto-configura `chromium-bin-wrapper.sh` + Puppeteer Chromium quando `CHROMIUM_BIN/CHROME_BIN` nao sao informados;
483
+ 18. Automacao do gate seguro concluida:
484
+ - runner legado removido; `tools/karma/karma.headless.conf.cjs` agora centraliza a configuracao headless;
485
485
  - revalidacao sem env manual:
486
- `env -u CHROMIUM_BIN -u CHROME_BIN -u CHROMIUM_REAL_BIN bash tools/karma/run-safe-gate.sh`;
486
+ `npm run test:table`;
487
487
  - resultado: `praxis-table lane PASS (48 SUCCESS)` e `praxis-crud full PASS (30 SUCCESS)`.
488
488
  19. Cobertura adicional no wrapper `praxis-crud` para precedencia remoto > local concluida:
489
489
  - novo teste para `metadata.table.resourcePath` prevalecer sobre `metadata.data`;
@@ -492,9 +492,9 @@ Todos os itens abaixo devem estar `true`:
492
492
  - validacao dirigida executada com tsconfig temporario isolado (evitando erro externo de compilacao em `praxis-dynamic-fields`):
493
493
  `ng test praxis-crud --watch=false --browsers=ChromeHeadless --polyfills=zone.js --polyfills=zone.js/testing --ts-config .tmp-preimpl-lane/tsconfig.crud-isolated.json --include projects/praxis-crud/src/lib/praxis-crud.component.spec.ts`;
494
494
  - resultado: `TOTAL: 11 SUCCESS`.
495
- 20. Revalidacao do `safe-gate` apos ampliar cobertura do CRUD:
496
- - comando: `bash tools/karma/run-safe-gate.sh`;
497
- - tentativa WSL da lane `praxis-table` permaneceu bloqueada por deteccao de Chromium (`No usable Chromium binary found for headless capture`);
495
+ 20. Revalidacao do gate apos ampliar cobertura do CRUD:
496
+ - comando atual: `npm run test:table`;
497
+ - tentativa legada da lane `praxis-table` permaneceu bloqueada por deteccao de Chromium (`No usable Chromium binary found for headless capture`);
498
498
  - fallback automatico via `cmd.exe + ChromeHeadless` executou com sucesso;
499
499
  - resultado final:
500
500
  - `praxis-table lane`: `PASS` (`TOTAL: 48 SUCCESS`);
@@ -502,23 +502,23 @@ Todos os itens abaixo devem estar `true`:
502
502
  21. Revalidacao full da `praxis-table` apos a ampliacao de cobertura no CRUD:
503
503
  - comando: `ng test praxis-table --watch=false --browsers=ChromeHeadless --polyfills=zone.js --polyfills=zone.js/testing`;
504
504
  - resultado: `TOTAL: 372 SUCCESS`.
505
- 22. Hardening da lane WSL no script dedicado concluido:
506
- - `scripts/test-table-preimpl-lane.sh` agora aplica automaticamente `tools/karma/chromium-bin-wrapper.sh` quando o probe direto de Chromium falha no WSL;
505
+ 22. Hardening da lane no script dedicado concluido:
506
+ - `scripts/test-table-preimpl-lane.sh` agora aplica automaticamente `tools/karma/chromium-bin-wrapper.sh` quando o probe direto de Chromium falha;
507
507
  - revalidacao direta da lane, sem env manual:
508
508
  `bash ./scripts/test-table-preimpl-lane.sh`;
509
509
  - resultado: `TOTAL: 48 SUCCESS`.
510
510
  - revalidacao adicional com ambiente limpo de browser vars:
511
511
  `env -u CHROMIUM_BIN -u CHROME_BIN -u CHROMIUM_REAL_BIN bash ./scripts/test-table-preimpl-lane.sh`;
512
512
  - resultado: `TOTAL: 48 SUCCESS`.
513
- 23. Revalidacao do gate seguro apos hardening da lane WSL:
514
- - comando: `bash tools/karma/run-safe-gate.sh`;
513
+ 23. Revalidacao do gate seguro apos hardening da lane:
514
+ - comando atual: `npm run test:table`;
515
515
  - resultado final: `praxis-table lane PASS (48 SUCCESS)` e `praxis-crud full PASS (32 SUCCESS)`;
516
- - nesta execucao, a lane WSL concluiu na trilha primaria (sem fallback para `cmd.exe`).
516
+ - nesta execucao, a lane concluiu na trilha primaria.
517
517
  24. Revalidacao completa do gate seguro em modo estrito + full suite:
518
518
  - comando:
519
- `SAFE_GATE_STRICT_WSL_PRIMARY=true SAFE_GATE_INCLUDE_TABLE_FULL=true bash tools/karma/run-safe-gate.sh`;
519
+ `npm run test:table`;
520
520
  - resultado final:
521
521
  - `praxis-table lane`: `PASS` (`TOTAL: 48 SUCCESS`);
522
- - `praxis-table lane path`: `wsl-primary`;
522
+ - `praxis-table lane path`: `headless`;
523
523
  - `praxis-crud full`: `PASS` (`TOTAL: 32 SUCCESS`);
524
524
  - `praxis-table full`: `PASS` (`TOTAL: 372 SUCCESS`).
@@ -1,13 +1,13 @@
1
1
  # Pacote de PR - Local Data Support + Gate Karma
2
2
 
3
3
  ## Objetivo do pacote
4
- Consolidar o estado pronto para PR da trilha de suporte local/remote da `praxis-table`, com evidencias de validacao e operacao de testes no WSL/Windows.
4
+ Consolidar o estado pronto para PR da trilha de suporte local/remote da `praxis-table`, com evidencias de validacao e operacao de testes locais.
5
5
 
6
6
  ## Escopo consolidado
7
7
  1. `praxis-crud` com suporte a `metadata.data` sem `resource.path`, preservando precedencia remoto > local.
8
- 2. Hardening da lane isolada da `praxis-table` no WSL (wrapper/probe Chromium).
9
- 3. Runner de gate seguro (`tools/karma/run-safe-gate.sh`) com modo estrito WSL e opcao de suite full da `praxis-table`.
10
- 4. Revalidacao final do gate com trilha primaria WSL e suites completas verdes.
8
+ 2. Hardening da lane isolada da `praxis-table`.
9
+ 3. Runner de gate seguro legado substituido pelos comandos canônicos atuais.
10
+ 4. Revalidacao final do gate com suites completas verdes.
11
11
 
12
12
  ## Commits da etapa
13
13
  1. `7855fb9` feat(praxis-crud): suportar modo local via metadata.data sem resource.path
@@ -19,24 +19,24 @@ Consolidar o estado pronto para PR da trilha de suporte local/remote da `praxis-
19
19
  7. `0086e4c` test(praxis-table): estabiliza specs pendentes e fecha suíte
20
20
  8. `4e95076` test(praxis-table): expand shared TestBed provider coverage
21
21
  9. `e545355` docs(praxis-table): atualizar status de validacao pos-hardening
22
- 10. `186e367` chore(karma): add WSL chromium probe wrapper for preimpl lane
22
+ 10. `186e367` chore(karma): add chromium probe wrapper for preimpl lane
23
23
  11. `3951fab` docs(praxis-table): registrar revalidacao do safe gate
24
- 12. `73e7160` chore(karma): auto-configure WSL chromium wrapper in safe gate
24
+ 12. `73e7160` chore(karma): auto-configure chromium wrapper in safe gate
25
25
  13. `7a8e0d4` test(praxis-crud): cobrir precedencia de resourcePath fallback
26
26
  14. `d84c49d` docs(praxis-table): registrar safe-gate com crud 32 success
27
27
  15. `5d0a335` docs(praxis-table): registrar full suite 372 success apos safe-gate
28
28
  16. `0c0f55a` chore(karma): auto-fallback to chromium wrapper in preimpl lane
29
29
  17. `f7fe073` chore(karma): log resolved real chromium in preimpl lane
30
30
  18. `1857a87` docs(praxis-table): registrar gate estrito com full suite verde
31
- 19. `b7d6782` docs(karma): document strict WSL safe-gate modes
31
+ 19. `b7d6782` docs(karma): document strict safe-gate modes
32
32
 
33
33
  ## Validacoes executadas (gate operacional)
34
34
  1. `bash ./scripts/test-table-preimpl-lane.sh`
35
35
  - Resultado: `TOTAL: 48 SUCCESS`.
36
- 2. `bash tools/karma/run-safe-gate.sh`
37
- - Resultado: `praxis-table lane PASS (48 SUCCESS)` e `praxis-crud full PASS (32 SUCCESS)`.
38
- 3. `SAFE_GATE_STRICT_WSL_PRIMARY=true SAFE_GATE_INCLUDE_TABLE_FULL=true bash tools/karma/run-safe-gate.sh`
39
- - Resultado: `praxis-table lane PASS (48 SUCCESS)`, `praxis-table lane path: wsl-primary`, `praxis-crud full PASS (32 SUCCESS)`, `praxis-table full PASS (372 SUCCESS)`.
36
+ 2. Gate canônico atual: `npm run test:table`
37
+ - Substitui o runner legado removido.
38
+ 3. Gate ordenado de compatibilidade DSL quando a mudanca tocar contratos da tabela: `bash tools/karma/run-table-dsl-compat-gate.sh`
39
+ - Usar junto com build/test focal conforme o `AGENTS.md` da subarea.
40
40
 
41
41
  ## Cobertura critica confirmada
42
42
  1. Preservacao de `pageSize` local (override do usuario)
@@ -57,7 +57,7 @@ Consolidar o estado pronto para PR da trilha de suporte local/remote da `praxis-
57
57
  - manter validacao focada e reproduzivel via `ng test` com `--include` por suite/arquivo;
58
58
  - preferir setup de testes centralizado e reutilizavel (consolidacao de providers compartilhados no hardening dos integration tests);
59
59
  - manter contratos e comportamento com regressao protegida por specs dedicadas (precedencia `resourcePath > data`, transicoes local/remote/empty);
60
- - executar gate com comandos explicitos e deterministicos (modo estrito WSL + suite full opcional).
60
+ - executar gate com comandos explicitos e deterministicos.
61
61
  3. Referencias oficiais retornadas por `search_documentation`:
62
62
  - https://angular.dev/cli/test#options
63
63
  - https://angular.dev/guide/testing/utility-apis
@@ -11,9 +11,7 @@ Nota historica: esta baseline e anterior a canonicalizacao de Json Logic. As ref
11
11
  - Helpers anteriores mantidos (`jsonGet/jsonEq/jsonIn`, `isBlank`, `today/now`, `len*`, etc.).
12
12
  - Normalização legada preservada para expressões antigas.
13
13
  - Limitação documentada: parser atual não suporta comparação direta de retorno de função (`fn(...) == valor`).
14
- - `toolbar.actions.visibleWhen` com politica por ambiente:
15
- - `prod/qa`: fail-closed.
16
- - `dev/hml`: fail-open para diagnostico.
14
+ - `toolbar.actions.visibleWhen` hoje falha fechado em todos os ambientes quando a condicao nao e Json Logic valida. A baseline historica de `2026-02-26` registrava comportamento diagnostico legado, mas esse comportamento foi removido na canonicalizacao.
17
15
  - `shortcutScope` canonico com validacao (`toolbar|global`) e fallback seguro.
18
16
  - Guards schema-only para:
19
17
  - `behavior.sorting.multiSort`
@@ -0,0 +1,36 @@
1
+ # Praxis Table Resource Events
2
+
3
+ `PraxisTable` emits `resourceEvent` in parallel with its existing public outputs. The envelope is intended for enterprise hosts that need one governed stream for audit, analytics, shell orchestration, and related-resource surfaces.
4
+
5
+ ## Event Mapping
6
+
7
+ | Table interaction | `kind` | `sourceOutput` | `phase` |
8
+ | --- | --- | --- | --- |
9
+ | Row click | `row-click` | `rowClick` | `notification` |
10
+ | Row double click | `row-double-click` | `rowDoubleClick` | `notification` |
11
+ | Selection change | `selection-change` | `selectionChange` | `notification` |
12
+ | Row action | `row-action` | `rowAction` | `notification` |
13
+ | Toolbar action | `toolbar-action` | `toolbarAction` | `notification` |
14
+ | Bulk action | `bulk-action` | `bulkAction` | `notification` |
15
+ | Export action | `export-action` | `exportAction` | `success` or `error` |
16
+ | Dynamic record surface fallback | `surface-open` | `recordSurfaceOpen` | `request` |
17
+ | Catalog-backed record surface | `surface-open` | `recordSurfaceOpen` | `request`, then `success` or `error` |
18
+ | Catalog-backed collection surface | `surface-open` | `toolbarAction` | `request`, then `success` or `error` |
19
+
20
+ `notification` events preserve the old output semantics and should not be interpreted as business completion. Host-mediated operations such as `surface.open` emit `request` before invoking `GlobalActionService` and then emit `success` or `error` after the result is known.
21
+
22
+ `selectionChange` can also be emitted with `trigger: "data-reconcile"` when a data update removes identities from the effective selection. Rehydrating the same selected identities does not emit a new selection event. With `persistSelection`, identities may remain selected outside the current page; `persistOnDataUpdate` only reconciles selection against the current visible dataset.
23
+
24
+ Record-oriented events may carry `resourceIdentity`. Resolution prefers a valid
25
+ explicit `x-ui.resource.identity`, then a key-only fallback from a valid
26
+ `x-ui.resource.idField`, then the effective host `idField` when that field is
27
+ present in the filtered response schema. Fallback identities expose `source`
28
+ and structured `diagnostics`; they never infer a title from property names. If
29
+ the configured field has no displayable row value, the event carries
30
+ `resourceIdentity: null` and the runtime emits a deduplicated diagnostic.
31
+
32
+ ## Surface Rules
33
+
34
+ For catalog-backed surfaces, `resourceEvent.surface` carries the canonical `ResourceSurfaceCatalogItem`, including `relatedResource` when the backend publishes it. For fallback dynamic-page requests, the payload still carries the requested surface context, but `surface` may be `null` because no full catalog item is available.
35
+
36
+ Consumers must keep legacy outputs wired where they already depend on them. `resourceEvent` is the canonical cross-component event stream, not a breaking replacement for existing outputs.
@@ -4,13 +4,14 @@ import * as i1 from '@angular/material/dialog';
4
4
  import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
5
5
  import * as i3 from '@angular/material/button';
6
6
  import { MatButtonModule } from '@angular/material/button';
7
- import * as i14 from '@angular/material/progress-bar';
7
+ import * as i13 from '@angular/material/progress-bar';
8
8
  import { MatProgressBarModule } from '@angular/material/progress-bar';
9
- import * as i4 from '@angular/material/icon';
9
+ import * as i7 from '@angular/material/icon';
10
10
  import { MatIconModule } from '@angular/material/icon';
11
- import * as i10 from '@angular/material/tooltip';
11
+ import * as i14 from '@angular/material/tooltip';
12
12
  import { MatTooltipModule } from '@angular/material/tooltip';
13
13
  import { PraxisFilterForm } from '@praxisui/dynamic-form';
14
+ import { PraxisIconButtonComponent } from '@praxisui/core';
14
15
 
15
16
  class FilterFormDialogHostComponent {
16
17
  data;
@@ -109,10 +110,8 @@ class FilterFormDialogHostComponent {
109
110
  <mat-icon>tune</mat-icon>
110
111
  <span>{{ data.title || 'Filtro avançado' }}</span>
111
112
  </div>
112
- <button mat-icon-button type="button" class="pfx-dialog-close" (click)="close()"
113
- [attr.aria-label]="data.i18n?.cancel || 'Fechar'">
114
- <mat-icon>close</mat-icon>
115
- </button>
113
+ <button praxisIconButton="close" size="compact" type="button" class="pfx-dialog-close" (click)="close()"
114
+ [attr.aria-label]="data.i18n?.cancel || 'Fechar'"></button>
116
115
  </div>
117
116
  <mat-dialog-content class="pfx-filter-dialog-content" aria-labelledby="filterDialogTitle">
118
117
  @if (data?.schemaLoading) {
@@ -161,20 +160,18 @@ class FilterFormDialogHostComponent {
161
160
  {{ data.i18n?.apply || 'Aplicar' }}
162
161
  </button>
163
162
  </mat-dialog-actions>
164
- `, isInline: true, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--mdc-dialog-subhead-color, var(--md-sys-color-on-surface))}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--mdc-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant))}.pfx-dialog-loading{display:flex;flex-direction:column;gap:10px;min-height:128px;justify-content:center;color:var(--mdc-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant));font-size:.875rem}.pfx-form-pending{height:0;overflow:hidden;visibility:hidden;pointer-events:none}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}.pfx-save-shortcut-button{margin-right:auto}.pfx-save-shortcut-button mat-icon{margin-right:6px;width:18px;height:18px;font-size:18px}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i14.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i10.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisFilterForm, selector: "praxis-filter-form", inputs: ["config", "formId", "resourcePath", "mode"], outputs: ["formReady", "valueChange", "submit", "requestSearch", "validityChange"] }] });
163
+ `, isInline: true, styles: [".pfx-dialog-title{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-right:8px}.pfx-dialog-title-text{display:inline-flex;align-items:center;gap:8px;font-weight:600;color:var(--mdc-dialog-subhead-color, var(--md-sys-color-on-surface))}.pfx-dialog-close{margin-left:auto}.pfx-filter-dialog-content{display:flex;flex-direction:column;gap:12px;padding-top:8px}.pfx-empty-state{margin:8px 0 0;color:var(--mdc-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant))}.pfx-dialog-loading{display:flex;flex-direction:column;gap:10px;min-height:128px;justify-content:center;color:var(--mdc-dialog-supporting-text-color, var(--md-sys-color-on-surface-variant));font-size:.875rem}.pfx-form-pending{height:0;overflow:hidden;visibility:hidden;pointer-events:none}.pfx-dialog-actions{padding:var(--pdx-dialog-actions-padding, 12px 24px 16px);border-top:1px solid var(--md-sys-color-outline-variant);background:transparent;display:flex;align-items:center;gap:8px}.pfx-save-shortcut-button{margin-right:auto}.pfx-save-shortcut-button mat-icon{margin-right:6px;width:18px;height:18px;font-size:18px}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i13.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i14.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisFilterForm, selector: "praxis-filter-form", inputs: ["config", "formId", "resourcePath", "mode"], outputs: ["formReady", "valueChange", "submit", "requestSearch", "validityChange"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }] });
165
164
  }
166
165
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: FilterFormDialogHostComponent, decorators: [{
167
166
  type: Component,
168
- args: [{ selector: 'praxis-filter-form-dialog-host', standalone: true, imports: [MatDialogModule, MatButtonModule, MatProgressBarModule, MatIconModule, MatTooltipModule, PraxisFilterForm], template: `
167
+ args: [{ selector: 'praxis-filter-form-dialog-host', standalone: true, imports: [MatDialogModule, MatButtonModule, MatProgressBarModule, MatIconModule, MatTooltipModule, PraxisFilterForm, PraxisIconButtonComponent], template: `
169
168
  <div mat-dialog-title class="pfx-dialog-title" id="filterDialogTitle">
170
169
  <div class="pfx-dialog-title-text">
171
170
  <mat-icon>tune</mat-icon>
172
171
  <span>{{ data.title || 'Filtro avançado' }}</span>
173
172
  </div>
174
- <button mat-icon-button type="button" class="pfx-dialog-close" (click)="close()"
175
- [attr.aria-label]="data.i18n?.cancel || 'Fechar'">
176
- <mat-icon>close</mat-icon>
177
- </button>
173
+ <button praxisIconButton="close" size="compact" type="button" class="pfx-dialog-close" (click)="close()"
174
+ [attr.aria-label]="data.i18n?.cancel || 'Fechar'"></button>
178
175
  </div>
179
176
  <mat-dialog-content class="pfx-filter-dialog-content" aria-labelledby="filterDialogTitle">
180
177
  @if (data?.schemaLoading) {