@praxisui/table 8.0.0-beta.2 → 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.
- package/README.md +81 -8
- package/docs/DSL-Extensions-Guide.md +23 -0
- package/docs/adr/2026-03-dynamic-filter-cross-lib-coupling.md +107 -0
- package/docs/adr/2026-03-filter-drawer-adapter-light-entrypoint.md +105 -0
- package/docs/adr/2026-03-table-editor-idfield-decision.md +85 -0
- package/docs/column-resize-reorder-implementation-plan.md +338 -0
- package/docs/column-resize-reorder-review-prompt.md +34 -0
- package/docs/dynamic-filter-architecture-overview.md +207 -0
- package/docs/dynamic-filter-backend-contract-cheatsheet.md +167 -0
- package/docs/dynamic-filter-editor-settings-guide.md +229 -0
- package/docs/dynamic-filter-host-integration-guide.md +217 -0
- package/docs/dynamic-filter-payload-contract.md +331 -0
- package/docs/dynamic-filter-range-filters-guide.md +289 -0
- package/docs/dynamic-filter-troubleshooting-guide.md +220 -0
- package/docs/dynamic-inline-filter-catalog.md +147 -0
- package/docs/e2e-column-drag-playwright.md +62 -0
- package/docs/expandable-rows-enterprise-big-leagues-plan.md +1080 -0
- package/docs/json-logic-operators-and-helpers.md +57 -0
- package/docs/local-data-mode-precedence.md +12 -0
- package/docs/local-data-pre-implementation-baseline.md +22 -0
- package/docs/local-data-preimplementation-go-no-go.md +39 -0
- package/docs/local-data-support-implementation-plan.md +524 -0
- package/docs/local-data-support-pr-package.md +66 -0
- package/docs/localization-persistence-merge.md +22 -0
- package/docs/performance-hardening-v2-implementation-plan.md +479 -0
- package/docs/playground-scenario-curation-plan.md +482 -0
- package/docs/playground-scenario-second-opinion-prompt.md +121 -0
- package/docs/playground-scenario-second-opinion-review.md +234 -0
- package/docs/release-notes-p1-hardening.md +76 -0
- package/docs/table-authoring-document-completeness-checklist.md +120 -0
- package/docs/table-editor-capability-review-prompt.md +349 -0
- package/docs/visual-rules-editor-transition.md +29 -0
- package/fesm2022/{praxisui-table-table-ai.adapter-DxjDaQqy.mjs → praxisui-table-table-ai.adapter-fS74fZ7o.mjs} +14 -5
- package/fesm2022/praxisui-table.mjs +3650 -324
- package/index.d.ts +120 -51
- package/package.json +15 -9
- package/src/lib/praxis-table.json-api.md +1315 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Column Resize + Reorder Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Objective
|
|
4
|
+
|
|
5
|
+
Allow users to resize and move columns in `praxis-table` without conflicting with sorting, while keeping behavior predictable, accessible, and persistent.
|
|
6
|
+
|
|
7
|
+
## Guiding Principles
|
|
8
|
+
|
|
9
|
+
- `sort` belongs to a dedicated sortable label/container, not to the entire `th`
|
|
10
|
+
- `reorder` belongs to a dedicated drag handle
|
|
11
|
+
- `resize` belongs to a dedicated handle on the right edge of the header cell
|
|
12
|
+
- runtime mutations should reuse the existing persistence pipeline through a shared column-mutation coordinator
|
|
13
|
+
- width persistence should normalize to `px`
|
|
14
|
+
- keep the existing config model and avoid introducing parallel API knobs
|
|
15
|
+
|
|
16
|
+
## Current Architecture Notes
|
|
17
|
+
|
|
18
|
+
Today the table header combines multiple behaviors on the same `th`:
|
|
19
|
+
|
|
20
|
+
- `mat-sort-header`
|
|
21
|
+
- `cdkDrag`
|
|
22
|
+
- reorder affordance/indicator
|
|
23
|
+
- sticky start/end support
|
|
24
|
+
- horizontal scroll modes already used in production
|
|
25
|
+
|
|
26
|
+
Relevant files:
|
|
27
|
+
|
|
28
|
+
- `/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-table/src/lib/praxis-table.html`
|
|
29
|
+
- `/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-table/src/lib/praxis-table.scss`
|
|
30
|
+
- `/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-table/src/lib/praxis-table.ts`
|
|
31
|
+
|
|
32
|
+
This is the main reason resize must not be introduced as another gesture on the same interaction zone. It also means sticky and horizontal scroll behavior must be treated as first-order implementation concerns, not deferred work.
|
|
33
|
+
|
|
34
|
+
Additional constraints from the current implementation:
|
|
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`
|
|
38
|
+
- persistence currently saves the full `config` snapshot for each runtime mutation
|
|
39
|
+
- width styling may already come from `column.width`, `headerStyle`, or `style`
|
|
40
|
+
|
|
41
|
+
Current reorder affordance note:
|
|
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
|
|
45
|
+
|
|
46
|
+
The plan must explicitly unwind those couplings instead of treating them as incidental details.
|
|
47
|
+
|
|
48
|
+
## Implementation Phases
|
|
49
|
+
|
|
50
|
+
### Phase 1: Split Header Interaction Zones
|
|
51
|
+
|
|
52
|
+
Refactor the header so each interaction has an explicit zone:
|
|
53
|
+
|
|
54
|
+
- sortable label container: sort
|
|
55
|
+
- drag icon/handle: reorder
|
|
56
|
+
- right-edge handle: resize
|
|
57
|
+
|
|
58
|
+
Changes in `praxis-table.html`:
|
|
59
|
+
|
|
60
|
+
- 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`
|
|
65
|
+
- 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
|
|
69
|
+
|
|
70
|
+
Changes in `praxis-table.scss`:
|
|
71
|
+
|
|
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
|
+
- add resize handle positioned at the far right edge
|
|
75
|
+
- use `cursor: grab` for reorder
|
|
76
|
+
- use `cursor: col-resize` for resize
|
|
77
|
+
- 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
|
+
|
|
80
|
+
Sorting strategy:
|
|
81
|
+
|
|
82
|
+
- do not keep the full `th` as the primary sort interaction target
|
|
83
|
+
- move the sort trigger to the label/container inside the header cell
|
|
84
|
+
- 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
|
|
86
|
+
|
|
87
|
+
### Phase 2: Add Runtime Resize State
|
|
88
|
+
|
|
89
|
+
Add state to `praxis-table.ts`:
|
|
90
|
+
|
|
91
|
+
- `activeResizeColumnField: string | null`
|
|
92
|
+
- `resizeStartX: number`
|
|
93
|
+
- `resizeStartWidthPx: number`
|
|
94
|
+
- `isResizingColumn: boolean`
|
|
95
|
+
- `resizePointerId: number | null`
|
|
96
|
+
|
|
97
|
+
Add internal defaults:
|
|
98
|
+
|
|
99
|
+
- derive defaults from the existing config model first
|
|
100
|
+
- use `behavior.resizing.minColumnWidth` and `behavior.resizing.maxColumnWidth` when present
|
|
101
|
+
- fall back to sane internal defaults only when config is absent
|
|
102
|
+
|
|
103
|
+
Add handlers:
|
|
104
|
+
|
|
105
|
+
- `onColumnResizePointerDown(event, column)`
|
|
106
|
+
- `onColumnResizePointerMove(event)`
|
|
107
|
+
- `onColumnResizePointerUp(event)`
|
|
108
|
+
- `onColumnResizePointerCancel(event)`
|
|
109
|
+
- `cancelColumnResize()`
|
|
110
|
+
|
|
111
|
+
Add runtime width state:
|
|
112
|
+
|
|
113
|
+
- keep transient width changes separate from persisted `config.columns[]` until commit
|
|
114
|
+
- resolve whether a column is resizable from both `behavior.resizing.enabled` and `column.resizable !== false`
|
|
115
|
+
- exclude non-data columns (`_select`, `_expander`, `_actions`) from resize by design
|
|
116
|
+
|
|
117
|
+
### Phase 3: Prevent Interaction Conflicts
|
|
118
|
+
|
|
119
|
+
On resize start:
|
|
120
|
+
|
|
121
|
+
- call `preventDefault()`
|
|
122
|
+
- call `stopPropagation()`
|
|
123
|
+
- capture the active pointer
|
|
124
|
+
- temporarily block sort and reorder while `isResizingColumn === true`
|
|
125
|
+
|
|
126
|
+
Behavioral rule:
|
|
127
|
+
|
|
128
|
+
- dragging only starts from the reorder handle
|
|
129
|
+
- sorting only starts from the dedicated sortable label/container
|
|
130
|
+
- resizing only starts from the right-edge resize handle
|
|
131
|
+
|
|
132
|
+
During resize:
|
|
133
|
+
|
|
134
|
+
- apply a class such as `pfx-column-resizing`
|
|
135
|
+
- temporarily disable `cdkDropList`
|
|
136
|
+
- ignore sort triggers from the header
|
|
137
|
+
- ensure sticky columns and resize handles preserve usable hit areas and z-index ordering
|
|
138
|
+
- preserve horizontal scroll behavior while pointer capture is active
|
|
139
|
+
- support abort paths: `Escape`, `pointercancel`, lost capture, and pointerup outside the viewport
|
|
140
|
+
|
|
141
|
+
### Phase 4: Normalize and Apply Widths
|
|
142
|
+
|
|
143
|
+
Add width utilities to `praxis-table.ts`:
|
|
144
|
+
|
|
145
|
+
- `resolveColumnWidthPx(column, headerEl): number`
|
|
146
|
+
- `clampColumnWidthPx(width: number): number`
|
|
147
|
+
- `formatColumnWidthPx(width: number): string`
|
|
148
|
+
|
|
149
|
+
Width rules:
|
|
150
|
+
|
|
151
|
+
- if `column.width` is already in `px`, use it directly
|
|
152
|
+
- if width is `%`, `auto`, or empty, measure the current header width at first resize and convert to `px`
|
|
153
|
+
- persist resized widths as `px`
|
|
154
|
+
- explicitly define precedence when `headerStyle` or `style` also carry width declarations
|
|
155
|
+
- document that first user resize converts responsive width intent into fixed runtime width intent
|
|
156
|
+
- validate width behavior under sticky start/end columns and horizontal scroll mode
|
|
157
|
+
|
|
158
|
+
Keep `getColumnWidthStyle(column)` as the main rendering output, now backed by updated runtime values.
|
|
159
|
+
|
|
160
|
+
### Phase 5: Persistence
|
|
161
|
+
|
|
162
|
+
Reuse the existing runtime mutation persistence flow in `praxis-table.ts`, but do not let resize and reorder persist independently against unrelated config snapshots.
|
|
163
|
+
|
|
164
|
+
Add a shared column runtime mutation coordinator:
|
|
165
|
+
|
|
166
|
+
- monotonic `columnMutationOperationId`
|
|
167
|
+
- mutation kind: `column-reorder` | `column-resize`
|
|
168
|
+
- per-operation snapshot merge before `saveConfig`
|
|
169
|
+
- stale-callback rejection shared by reorder and resize
|
|
170
|
+
- serialized commit semantics when reorder and resize overlap in time
|
|
171
|
+
|
|
172
|
+
Refactor `persistTableConfigAfterRuntimeMutation(...)` usage behind a narrower helper such as:
|
|
173
|
+
|
|
174
|
+
- `persistColumnRuntimeMutation(trigger, mutateConfig, options)`
|
|
175
|
+
|
|
176
|
+
Add a new trigger:
|
|
177
|
+
|
|
178
|
+
- `column-resize`
|
|
179
|
+
|
|
180
|
+
Persist width by updating the matching entry in `config.columns[]`:
|
|
181
|
+
|
|
182
|
+
- locate by `field`
|
|
183
|
+
- set `width: "${px}px"`
|
|
184
|
+
|
|
185
|
+
Resize persistence policy:
|
|
186
|
+
|
|
187
|
+
- persist on commit only, not continuously during pointer move
|
|
188
|
+
- 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
|
|
191
|
+
- define stale-callback protection for resize and reorder through the same shared coordinator
|
|
192
|
+
- 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
|
+
|
|
194
|
+
Emit a resize event after persistence:
|
|
195
|
+
|
|
196
|
+
- `columnResize`
|
|
197
|
+
|
|
198
|
+
Suggested payload:
|
|
199
|
+
|
|
200
|
+
- `tableId`
|
|
201
|
+
- `field`
|
|
202
|
+
- `header`
|
|
203
|
+
- `previousWidth`
|
|
204
|
+
- `currentWidth`
|
|
205
|
+
- `persisted`
|
|
206
|
+
|
|
207
|
+
### Phase 6: Public Configuration API
|
|
208
|
+
|
|
209
|
+
Align strictly with the existing config model. Do not introduce a second naming scheme.
|
|
210
|
+
|
|
211
|
+
Use the existing behavior flags in the core model:
|
|
212
|
+
|
|
213
|
+
- `behavior.dragging.columns`
|
|
214
|
+
- `behavior.resizing.enabled`
|
|
215
|
+
- `behavior.resizing.minColumnWidth`
|
|
216
|
+
- `behavior.resizing.maxColumnWidth`
|
|
217
|
+
- `behavior.resizing.persistWidths`
|
|
218
|
+
- `ColumnDefinition.resizable`
|
|
219
|
+
|
|
220
|
+
Optional future additions should only happen if the current model proves insufficient.
|
|
221
|
+
|
|
222
|
+
Recommended rollout:
|
|
223
|
+
|
|
224
|
+
- keep `dragging.columns = true`
|
|
225
|
+
- ship resize behind `behavior.resizing.enabled`
|
|
226
|
+
- honor `behavior.resizing.persistWidths` when deciding whether to save width runtime mutations
|
|
227
|
+
- keep `column.resizable !== false` as the per-column opt-out
|
|
228
|
+
- do not add new public API until the current model proves insufficient under real usage
|
|
229
|
+
|
|
230
|
+
### Phase 7: Accessibility
|
|
231
|
+
|
|
232
|
+
Resize handle:
|
|
233
|
+
|
|
234
|
+
- `role="separator"`
|
|
235
|
+
- `aria-orientation="vertical"`
|
|
236
|
+
- `aria-label="Resize column X"`
|
|
237
|
+
- `tabindex="0"`
|
|
238
|
+
- expose current/min/max width semantics explicitly when implemented
|
|
239
|
+
- announce committed width changes through the existing live-region pattern or an equivalent status channel
|
|
240
|
+
|
|
241
|
+
Keyboard behavior for resize handle:
|
|
242
|
+
|
|
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
|
|
246
|
+
|
|
247
|
+
Reorder handle:
|
|
248
|
+
|
|
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`
|
|
253
|
+
|
|
254
|
+
### Phase 8: Testing
|
|
255
|
+
|
|
256
|
+
Add focused tests for interaction conflicts:
|
|
257
|
+
|
|
258
|
+
- resize does not trigger reorder
|
|
259
|
+
- resize does not trigger sort
|
|
260
|
+
- resize handle click does not bubble into sort click
|
|
261
|
+
- reorder does not alter width
|
|
262
|
+
- clicking the sortable label still sorts
|
|
263
|
+
- reorder still works through the dedicated `cdkDragHandle`
|
|
264
|
+
- keyboard reorder still works after moving intent to the handle
|
|
265
|
+
- sort keyboard interaction still works after sort leaves the `th`
|
|
266
|
+
|
|
267
|
+
Add persistence tests:
|
|
268
|
+
|
|
269
|
+
- width updates `config.columns`
|
|
270
|
+
- width persists through the shared column mutation coordinator
|
|
271
|
+
- width survives rerender/reload flow
|
|
272
|
+
- width persistence respects `behavior.resizing.persistWidths`
|
|
273
|
+
- resize persists only on commit, not on every pointer move
|
|
274
|
+
- concurrent resize/reorder persistence does not leave stale width/order state
|
|
275
|
+
- out-of-order async persistence callbacks do not overwrite the latest width/order snapshot
|
|
276
|
+
|
|
277
|
+
Add limit tests:
|
|
278
|
+
|
|
279
|
+
- `minWidthPx` is respected
|
|
280
|
+
- `maxWidthPx` is respected
|
|
281
|
+
|
|
282
|
+
Add pointer/cancel coverage:
|
|
283
|
+
|
|
284
|
+
- resize aborts cleanly on `Escape`
|
|
285
|
+
- resize aborts cleanly on `pointercancel`
|
|
286
|
+
- resize completes correctly when pointerup happens outside the immediate handle area
|
|
287
|
+
|
|
288
|
+
Add visual/e2e coverage later:
|
|
289
|
+
|
|
290
|
+
- resize with horizontal scroll enabled
|
|
291
|
+
- resize under `scroll-auto`
|
|
292
|
+
- resize under `scroll-wrap`
|
|
293
|
+
- reorder still works after resize
|
|
294
|
+
- sticky start column resize
|
|
295
|
+
- sticky end column resize
|
|
296
|
+
- sticky columns do not produce overlap/z-index regression while resizing
|
|
297
|
+
- adjacent non-data columns (`_select`, `_expander`, `_actions`) do not regress
|
|
298
|
+
- responsive scroll modes remain aligned between header and body
|
|
299
|
+
|
|
300
|
+
## Recommended Delivery Sequence
|
|
301
|
+
|
|
302
|
+
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
|
|
306
|
+
5. Introduce shared column runtime mutation coordination for reorder + resize persistence
|
|
307
|
+
6. Persist column width on commit only
|
|
308
|
+
7. Add focused conflict and persistence tests
|
|
309
|
+
8. Add keyboard resize and final resize a11y semantics in a follow-up slice
|
|
310
|
+
9. Enable only through config/feature flag
|
|
311
|
+
|
|
312
|
+
## Definition of Done
|
|
313
|
+
|
|
314
|
+
- users can reorder only from the drag handle
|
|
315
|
+
- users can resize only from the right-edge handle
|
|
316
|
+
- clicking the sortable label still sorts
|
|
317
|
+
- reorder, resize, and sort do not interfere with each other
|
|
318
|
+
- width and order persist correctly
|
|
319
|
+
- stale async persistence callbacks cannot revert the latest width/order mutation
|
|
320
|
+
- existing horizontal scroll and sticky behavior remain intact after first resize
|
|
321
|
+
|
|
322
|
+
## Risk Notes
|
|
323
|
+
|
|
324
|
+
- gesture ambiguity is not solved by event suppression alone; sort must be structurally isolated from reorder/resize handles
|
|
325
|
+
- supporting `%` and `auto` widths requires careful first-resize normalization
|
|
326
|
+
- first resize may intentionally convert responsive width behavior into fixed pixel width behavior
|
|
327
|
+
- sticky start/end columns and horizontal scroll are immediate implementation concerns
|
|
328
|
+
- resize and reorder mutations need a shared commit/stale-callback policy, not parallel ad hoc logic
|
|
329
|
+
|
|
330
|
+
## Suggested Follow-Up
|
|
331
|
+
|
|
332
|
+
After review, break this plan into small implementation tasks by file:
|
|
333
|
+
|
|
334
|
+
- `praxis-table.html`
|
|
335
|
+
- `praxis-table.scss`
|
|
336
|
+
- `praxis-table.ts`
|
|
337
|
+
- relevant core config model files
|
|
338
|
+
- dedicated unit/integration specs
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Review Prompt For Another Agent
|
|
2
|
+
|
|
3
|
+
Review the implementation plan in:
|
|
4
|
+
|
|
5
|
+
- `/mnt/d/Developer/praxis-plataform/praxis-ui-angular/projects/praxis-table/docs/column-resize-reorder-implementation-plan.md`
|
|
6
|
+
|
|
7
|
+
Context:
|
|
8
|
+
|
|
9
|
+
- The goal is to support both column resizing and column reordering in `praxis-table`.
|
|
10
|
+
- The current table header already combines `mat-sort-header`, `cdkDrag`, and reorder UI.
|
|
11
|
+
- We want a robust solution where `sort`, `reorder`, and `resize` do not conflict.
|
|
12
|
+
- The plan assumes:
|
|
13
|
+
- sorting stays on the header text/content area
|
|
14
|
+
- reordering moves to a dedicated drag handle
|
|
15
|
+
- resizing uses a dedicated right-edge handle
|
|
16
|
+
- width persistence reuses the existing runtime mutation persistence flow
|
|
17
|
+
|
|
18
|
+
What to review:
|
|
19
|
+
|
|
20
|
+
1. Validate whether the interaction model is robust and aligned with mature grid implementations.
|
|
21
|
+
2. Identify technical risks in the proposed architecture, especially around Angular Material, CDK drag-drop, and pointer events.
|
|
22
|
+
3. Check whether the persistence strategy is coherent with the current `praxis-table` runtime mutation model.
|
|
23
|
+
4. Evaluate whether the accessibility plan is sufficient or missing critical keyboard/screen-reader behaviors.
|
|
24
|
+
5. Point out any missing test scenarios, especially conflict cases between resize, reorder, sort, sticky columns, and horizontal scroll.
|
|
25
|
+
6. Flag any assumptions that are too optimistic or likely to create regressions.
|
|
26
|
+
7. Suggest simplifications if the plan is over-engineered for the current codebase.
|
|
27
|
+
|
|
28
|
+
Expected output:
|
|
29
|
+
|
|
30
|
+
- Findings first, ordered by severity
|
|
31
|
+
- Then open questions or assumptions
|
|
32
|
+
- Then concrete recommendations
|
|
33
|
+
|
|
34
|
+
Please be critical. Focus on bugs, regressions, architectural risk, and implementation traps rather than rewriting the plan stylistically.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Dynamic Filter Architecture Overview"
|
|
3
|
+
slug: "dynamic-filter-architecture-overview"
|
|
4
|
+
description: "Visao arquitetural do filtro dinamico no ecossistema Praxis, cobrindo runtime Angular, campos inline, payload enviado e normalizacao no metadata-starter."
|
|
5
|
+
doc_type: "concept"
|
|
6
|
+
document_kind: "host-guide"
|
|
7
|
+
component: "table"
|
|
8
|
+
category: "architecture"
|
|
9
|
+
audience:
|
|
10
|
+
- "host"
|
|
11
|
+
- "frontend"
|
|
12
|
+
- "backend"
|
|
13
|
+
- "architect"
|
|
14
|
+
level: "advanced"
|
|
15
|
+
status: "active"
|
|
16
|
+
owner: "praxis-ui"
|
|
17
|
+
tags:
|
|
18
|
+
- "table"
|
|
19
|
+
- "dynamic-filter"
|
|
20
|
+
- "dynamic-fields"
|
|
21
|
+
- "metadata-starter"
|
|
22
|
+
- "range"
|
|
23
|
+
order: 26
|
|
24
|
+
icon: "account_tree"
|
|
25
|
+
toc: true
|
|
26
|
+
sidebar: true
|
|
27
|
+
search_boost: 1.15
|
|
28
|
+
reading_time: 16
|
|
29
|
+
estimated_setup_time: 35
|
|
30
|
+
version: "1.0"
|
|
31
|
+
related_docs:
|
|
32
|
+
- "table-overview"
|
|
33
|
+
- "dynamic-filter-payload-contract"
|
|
34
|
+
- "dynamic-filter-range-filters-guide"
|
|
35
|
+
- "dynamic-fields-inline-components-guide"
|
|
36
|
+
keywords:
|
|
37
|
+
- "praxis-filter"
|
|
38
|
+
- "GenericFilterDTO"
|
|
39
|
+
- "RangePayloadNormalizer"
|
|
40
|
+
- "filter settings"
|
|
41
|
+
source_of_truth:
|
|
42
|
+
- "projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.ts"
|
|
43
|
+
- "projects/praxis-table/src/lib/services/filter-config.service.ts"
|
|
44
|
+
- "projects/praxis-table/src/lib/filter-settings/filter-settings.component.ts"
|
|
45
|
+
- "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/filter/web/FilterRequestBodyAdvice.java"
|
|
46
|
+
- "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/filter/range/RangePayloadNormalizer.java"
|
|
47
|
+
- "../praxis-metadata-starter/src/main/java/org/praxisplatform/uischema/filter/specification/GenericSpecificationsBuilder.java"
|
|
48
|
+
last_updated: "2026-03-07"
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
# Dynamic Filter Architecture Overview
|
|
52
|
+
|
|
53
|
+
## Objetivo
|
|
54
|
+
|
|
55
|
+
Explicar o filtro dinâmico como uma feature transversal do ecossistema Praxis, não como um componente isolado, deixando claro como metadata, UI inline, editor/configuração, payload HTTP e interpretação backend trabalham juntos.
|
|
56
|
+
|
|
57
|
+
## Pré-requisitos
|
|
58
|
+
|
|
59
|
+
- Conhecimento básico de `@praxisui/table`, `@praxisui/dynamic-fields` e contratos de `FilterDTO`.
|
|
60
|
+
- Host Angular já integrado com `praxis-filter` e endpoints resource-oriented de filtro do backend.
|
|
61
|
+
- Familiaridade com metadados `controlType`, `@Filterable` e `@UISchema`.
|
|
62
|
+
|
|
63
|
+
## Quando usar
|
|
64
|
+
|
|
65
|
+
Use este documento quando precisar:
|
|
66
|
+
|
|
67
|
+
- entender o fluxo completo do filtro da tabela;
|
|
68
|
+
- planejar documentação funcional para hosts e squads backend;
|
|
69
|
+
- decidir onde documentar variantes inline, settings editor e contratos de range;
|
|
70
|
+
- investigar por que um payload válido na UI vira filtro inválido no backend.
|
|
71
|
+
|
|
72
|
+
## Visão geral da feature
|
|
73
|
+
|
|
74
|
+
No Praxis, o filtro dinâmico é uma pipeline de seis etapas:
|
|
75
|
+
|
|
76
|
+
1. O backend publica um `FilterDTO` anotado com `@Filterable` e `@UISchema`.
|
|
77
|
+
2. O host ou a tabela obtém schema/metadata para montar os campos do filtro.
|
|
78
|
+
3. O `PraxisFilter` decide o layout visível, a variante inline e o modo avançado.
|
|
79
|
+
4. O usuário interage com os campos, e o runtime emite `change`, `submit`, `clear`, `tagsChange` e eventos auxiliares.
|
|
80
|
+
5. O host envia o payload para endpoints como `POST /filter`, `POST /filter/cursor`, `POST /locate` ou `POST /options/filter`.
|
|
81
|
+
6. O `praxis-metadata-starter` normaliza ranges e constrói as predicates JPA a partir do `GenericFilterDTO`.
|
|
82
|
+
|
|
83
|
+
## Mapa de responsabilidades
|
|
84
|
+
|
|
85
|
+
### 1. `praxis-table`: runtime de orquestração
|
|
86
|
+
|
|
87
|
+
`PraxisFilter` é o núcleo do runtime na UI. Ele concentra:
|
|
88
|
+
|
|
89
|
+
- inputs de configuração e layout, como `alwaysVisibleFields`, `selectedFieldIds`, `allowSaveTags`, `changeDebounceMs` e variantes `useInline*Variant`;
|
|
90
|
+
- outputs que materializam o contrato com o host, principalmente `submit` e `change`;
|
|
91
|
+
- persistência local do estado do DTO e das preferências de exibição com chaves `filter-dto:<key>` e `filter-config:<key>`;
|
|
92
|
+
- heurísticas que convertem `controlType` genérico para inline canônico quando a feature pede experiência compacta.
|
|
93
|
+
|
|
94
|
+
Esse acoplamento existe em `projects/praxis-table/src/lib/components/praxis-filter/praxis-filter.component.ts` e `projects/praxis-table/src/lib/services/filter-config.service.ts`.
|
|
95
|
+
|
|
96
|
+
### 2. `praxis-dynamic-fields`: superfície dos controles
|
|
97
|
+
|
|
98
|
+
Os componentes `filter-*-inline` pertencem ao catálogo de `dynamic-fields`. Eles não definem sozinhos a jornada do filtro; eles implementam o comportamento visual e semântico de cada campo compacto:
|
|
99
|
+
|
|
100
|
+
- selects inline;
|
|
101
|
+
- async/searchable/entity lookup;
|
|
102
|
+
- intervalos numéricos, monetários, data e hora;
|
|
103
|
+
- controles especializados como rating, radius, pipeline status e score priority.
|
|
104
|
+
|
|
105
|
+
O contrato corporativo desses campos está em `projects/praxis-dynamic-fields/docs/dynamic-fields-inline-components-guide.md` (slug publicado: `dynamic-fields-inline-components-guide`).
|
|
106
|
+
|
|
107
|
+
### 3. `filter-settings`: governança editorial e operacional
|
|
108
|
+
|
|
109
|
+
`filter-settings.component.ts` é a ponte entre metadata, seleção de campos e overrides de UX. É onde o ecossistema define:
|
|
110
|
+
|
|
111
|
+
- quais campos ficam sempre visíveis;
|
|
112
|
+
- que `controlType` efetivo será usado na toolbar compacta;
|
|
113
|
+
- se a fonte da metadata vem do `filter-dto` ou de `columns`;
|
|
114
|
+
- quais conversões são aceitas para `rangeSlider`, `priceRange`, `dateRange`, `timeRange` e afins.
|
|
115
|
+
|
|
116
|
+
Em termos de documentação, isso significa que o editor do filtro precisa ser tratado como parte da feature, não como “extra”.
|
|
117
|
+
|
|
118
|
+
### 4. `praxis-metadata-starter`: normalização e interpretação
|
|
119
|
+
|
|
120
|
+
No backend, o payload recebido pelo controller não vai direto para a specification.
|
|
121
|
+
|
|
122
|
+
Antes disso:
|
|
123
|
+
|
|
124
|
+
- `FilterRequestBodyAdvice` intercepta qualquer request body compatível com `GenericFilterDTO`;
|
|
125
|
+
- `RangePayloadNormalizer` converte objetos e aliases em formato canônico de lista;
|
|
126
|
+
- `GenericSpecificationsBuilder` transforma o valor normalizado em predicates como `BETWEEN`, `BETWEEN_EXCLUSIVE`, `NOT_BETWEEN` e `OUTSIDE_RANGE`.
|
|
127
|
+
|
|
128
|
+
Isso é o motivo de a documentação do filtro precisar citar explicitamente o `praxis-metadata-starter`.
|
|
129
|
+
|
|
130
|
+
## Fluxo ponta a ponta
|
|
131
|
+
|
|
132
|
+
### Etapa 1. Schema e metadata
|
|
133
|
+
|
|
134
|
+
O backend descreve o filtro com:
|
|
135
|
+
|
|
136
|
+
- `@Filterable` para a operação semântica;
|
|
137
|
+
- `@UISchema` para `label`, `controlType`, `endpoint`, `numericFormat`, ordem e outras pistas visuais.
|
|
138
|
+
|
|
139
|
+
### Etapa 2. Resolução da UI
|
|
140
|
+
|
|
141
|
+
O `PraxisFilter` e o catálogo `dynamic-fields` decidem:
|
|
142
|
+
|
|
143
|
+
- se o campo vai para a barra compacta ou para a grade;
|
|
144
|
+
- se a variante será inline, dedicada ou avançada;
|
|
145
|
+
- se haverá clear button corporativo, tooltip contextual e presets rápidos.
|
|
146
|
+
|
|
147
|
+
### Etapa 3. Estado local
|
|
148
|
+
|
|
149
|
+
O runtime persiste:
|
|
150
|
+
|
|
151
|
+
- o DTO de filtro em `filter-dto:<key>`;
|
|
152
|
+
- a configuração de layout do filtro em `filter-config:<key>`.
|
|
153
|
+
|
|
154
|
+
Isso permite restaurar seleção de campos, preferências visuais e estado operacional do filtro.
|
|
155
|
+
|
|
156
|
+
### Etapa 4. Emissão de payload
|
|
157
|
+
|
|
158
|
+
No modo avançado, `onAdvancedChange(event)` trabalha sobre `event.formData`. Os eventos `change` e `submit` são o contrato que o host normalmente observa para disparar integração com a API.
|
|
159
|
+
|
|
160
|
+
Há uma nuance importante já coberta em teste: objeto de range vazio deve ser limpo antes de poluir o payload emitido.
|
|
161
|
+
|
|
162
|
+
### Etapa 5. Request HTTP
|
|
163
|
+
|
|
164
|
+
O host envia o DTO para endpoints como:
|
|
165
|
+
|
|
166
|
+
- `POST /filter`
|
|
167
|
+
- `POST /filter/cursor`
|
|
168
|
+
- `POST /locate`
|
|
169
|
+
- `POST /options/filter`
|
|
170
|
+
|
|
171
|
+
Todos eles fazem parte da superfície resource-oriented de filtro do metadata-starter.
|
|
172
|
+
|
|
173
|
+
### Etapa 6. Normalização backend
|
|
174
|
+
|
|
175
|
+
O backend aceita mais de um shape para intervalos, mas canonicaliza tudo para a lista esperada pelo `FilterDTO` tipado.
|
|
176
|
+
|
|
177
|
+
Exemplos:
|
|
178
|
+
|
|
179
|
+
- `{ minPrice: 10, maxPrice: 20 }` vira `[10, 20]`;
|
|
180
|
+
- `{ startDate: "2026-03-01", endDate: "2026-03-31" }` vira `["2026-03-01", "2026-03-31"]`;
|
|
181
|
+
- `[null, 100]` continua representando “somente teto superior”.
|
|
182
|
+
|
|
183
|
+
## Regras arquiteturais que a doc precisa preservar
|
|
184
|
+
|
|
185
|
+
1. O filtro é metadata-driven, mas não schema-only.
|
|
186
|
+
Runtime Angular, overrides do host e normalização backend influenciam o comportamento final.
|
|
187
|
+
|
|
188
|
+
2. O payload não é só responsabilidade do frontend.
|
|
189
|
+
O backend canonicaliza ranges aceitos pela plataforma e rejeita payloads inválidos.
|
|
190
|
+
|
|
191
|
+
3. O editor/configuração faz parte do contrato da feature.
|
|
192
|
+
Sempre documente `alwaysVisibleFields`, metadata overrides e variantes inline quando o componente depender deles.
|
|
193
|
+
|
|
194
|
+
4. A documentação deve separar duas perspectivas:
|
|
195
|
+
- experiência de uso do filtro no contexto da tabela;
|
|
196
|
+
- contrato técnico dos campos inline e do payload.
|
|
197
|
+
|
|
198
|
+
## Escopo da trilha documental recomendada
|
|
199
|
+
|
|
200
|
+
Para documentação extensa e didática, a feature deve ser desdobrada em pelo menos quatro documentos:
|
|
201
|
+
|
|
202
|
+
- arquitetura geral;
|
|
203
|
+
- contrato de payload;
|
|
204
|
+
- ranges e normalização;
|
|
205
|
+
- catálogo de campos inline e settings editor.
|
|
206
|
+
|
|
207
|
+
Os três primeiros já passam a existir nesta trilha. O quarto permanece ancorado no guia canônico de inline em `dynamic-fields`.
|