@elliemae/ds-data-table 3.70.0-next.3 → 3.70.0-next.4

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.
Files changed (43) hide show
  1. package/dist/cjs/TruncatedTooltipText.js +94 -0
  2. package/dist/cjs/TruncatedTooltipText.js.map +7 -0
  3. package/dist/cjs/exported-related/EditableCell.js +1 -1
  4. package/dist/cjs/exported-related/EditableCell.js.map +2 -2
  5. package/dist/cjs/parts/Cells/Cell.js +2 -2
  6. package/dist/cjs/parts/Cells/Cell.js.map +2 -2
  7. package/dist/cjs/parts/Cells/CellFactory.js +2 -2
  8. package/dist/cjs/parts/Cells/CellFactory.js.map +2 -2
  9. package/dist/cjs/parts/Headers/HeaderCellTitle.js +2 -2
  10. package/dist/cjs/parts/Headers/HeaderCellTitle.js.map +2 -2
  11. package/dist/cjs/react-desc-prop-types.js +2 -2
  12. package/dist/cjs/react-desc-prop-types.js.map +1 -1
  13. package/dist/esm/TruncatedTooltipText.js +68 -0
  14. package/dist/esm/TruncatedTooltipText.js.map +7 -0
  15. package/dist/esm/exported-related/EditableCell.js +1 -1
  16. package/dist/esm/exported-related/EditableCell.js.map +2 -2
  17. package/dist/esm/parts/Cells/Cell.js +2 -2
  18. package/dist/esm/parts/Cells/Cell.js.map +2 -2
  19. package/dist/esm/parts/Cells/CellFactory.js +2 -2
  20. package/dist/esm/parts/Cells/CellFactory.js.map +2 -2
  21. package/dist/esm/parts/Headers/HeaderCellTitle.js +2 -2
  22. package/dist/esm/parts/Headers/HeaderCellTitle.js.map +2 -2
  23. package/dist/esm/react-desc-prop-types.js +2 -2
  24. package/dist/esm/react-desc-prop-types.js.map +1 -1
  25. package/dist/types/TruncatedTooltipText.d.ts +9 -0
  26. package/dist/types/tests/DSDataTable.get-owner-props-arguments-slots.test.d.ts +1 -0
  27. package/dist/types/tests/callbacks/editableCell.events.test.d.ts +1 -0
  28. package/dist/types/tests/playwright/DSDataTable.slot-contracts-dynamic.test.playwright.d.ts +1 -0
  29. package/dist/types/tests/playwright/DSDataTableDropIndicatorTestRenderer.d.ts +1 -0
  30. package/dist/types/tests/render/cellStyle.test.d.ts +1 -0
  31. package/package.json +33 -33
  32. package/skills/ds-data-table-boundaries/SKILL.md +363 -0
  33. package/skills/ds-data-table-columns/SKILL.md +273 -0
  34. package/skills/ds-data-table-expandable/SKILL.md +235 -0
  35. package/skills/ds-data-table-feedback/SKILL.md +190 -0
  36. package/skills/ds-data-table-filtering/SKILL.md +322 -0
  37. package/skills/ds-data-table-health-check/SKILL.md +172 -0
  38. package/skills/ds-data-table-migration/SKILL.md +201 -0
  39. package/skills/ds-data-table-pagination/SKILL.md +182 -0
  40. package/skills/ds-data-table-row-variants/SKILL.md +260 -0
  41. package/skills/ds-data-table-selection/SKILL.md +449 -0
  42. package/skills/ds-data-table-setup/SKILL.md +229 -0
  43. package/skills/ds-data-table-slots/SKILL.md +257 -0
@@ -0,0 +1,257 @@
1
+ ---
2
+ name: ds-data-table-slots
3
+ description: >
4
+ Low-level slot injection surface for @elliemae/ds-data-table. Reach for this only after OOB
5
+ features cannot satisfy the requirement. Valid uses: data-* attribute injection (analytics,
6
+ tracking), aria-* overrides, and conditional styling. Full slot surface in DATA_TABLE_SLOTS /
7
+ DSDataTableSlots. App side owns long-term maintenance of anything injected via slots.
8
+ type: core
9
+ library: ds-data-table
10
+ library_version: '3.60.0'
11
+ requires:
12
+ - ds-data-table-setup
13
+ sources:
14
+ - '@elliemae/ds-data-table:dist/types/constants/index.d.ts'
15
+ - '@elliemae/ds-data-table:dist/types/react-desc-prop-types.d.ts'
16
+ ---
17
+
18
+ ## Slot surface — DOM hierarchy
19
+
20
+ Slots are injection points into specific rendered DOM nodes. The `data-dimsum-slot` attribute value on each node identifies the slot — values use camelCase with the component prefix, e.g. `dsDatatableCellsContainer`. Understanding the hierarchy is required to inject props into the right element — injecting `aria-*` attributes at the wrong level produces silent accessibility failures.
21
+
22
+ **About this map:** Verified against `@elliemae/ds-data-table@3.60.0` via probe tests. Slot names and positions are governed by the Dimsum breaking change protocol — renames and structural moves are intentional, coordinated, and documented in the official resources; they do not happen silently. The map is likely accurate for nearby versions, but not guaranteed for all time.
23
+
24
+ **If something doesn't match the version you're integrating with:** Find a way to inspect the actual rendered HTML of the component — how you do that depends on your environment and what tools are available to you. If you cannot do it autonomously, ask the human in the loop to render the component with synthetic or placeholder data and share the resulting HTML structure. **Important:** the human must not copy HTML from a production environment or any page containing real user data, real loan information, or any personally identifiable information — the goal is slot position verification, which requires only the DOM structure, not real content. Placeholder text and fake IDs are sufficient and correct for this purpose.
25
+
26
+ Each entry below shows: `SLOT_KEY (data-dimsum-slot="value") · tag · notes`.
27
+
28
+ ```
29
+ ROOT (dsDatatableRoot) · div
30
+ ├── FILTER_BAR_WRAPPER (dsDatatableFilterBarWrapper) · div ← only when withFilterBar=true
31
+ │ └── FILTER_BAR_MENU_BUTTON ← static, no interaction needed
32
+ │ wraps DSMenuButton → DOM shows dsButtonRoot (no dsDatatableFilterBarMenuButton attr)
33
+ └── CONTENT_WRAPPER (dsDatatableContentWrapper) · div
34
+ ├── TABLE_WRAPPER (dsDatatableTableWrapper) · div[role="table"]
35
+ │ └── VIRTUAL_LIST_WRAPPER (dsDatatableVirtualListWrapper) · div[role="rowgroup" tabIndex="-1"]
36
+ │ └── TABLE_CONTENT_WRAPPER (dsDatatableTableContentWrapper) · div
37
+ │ ├── HEAD_WRAPPER (dsDatatableHeadWrapper) · div
38
+ │ │ └── HEAD_TR (dsDatatableHeadTr) · div[role="row"]
39
+ │ │ └── HEADER_CELL_WRAPPER (dsDatatableHeaderCellWrapper) · div (one per column)
40
+ │ │ └── HEAD_TH (dsDatatableHeadTh) · div[role="columnheader"]
41
+ │ │ ║ prop: dsDatatableHeadTh
42
+ │ │ ║ callback: ({ columnId, firstFocuseableColumnHeaderId }) => props
43
+ │ │ ├── HEAD_RIGHT_ICONS_WRAPPER (dsDatatableHeadRightIconsWrapper) · div
44
+ │ │ │ present when: canSort=true OR column.filter is set
45
+ │ │ │ ├── sort button (dsButtonRoot — no DataTable slot attr)
46
+ │ │ │ └── FILTER_POPOVER_BUTTON (dsDatatableFilterPopoverButton) · span
47
+ │ │ │ present when: column.filter is set
48
+ │ │ │ └── filter trigger button (dsButtonRoot — no DataTable slot attr)
49
+ │ │ │ clicking this opens the filter popover (portal — see below)
50
+ │ │ ├── EXPAND_CELL_CONTAINER (dsDatatableExpandCellContainer) · span[role="button"]
51
+ │ │ │ present in: the expand column header cell only
52
+ │ │ └── RESIZER (dsDatatableResizer) ← isResizeable=true only
53
+ │ │ prop: dsDatatableResizer · wraps DSInputText
54
+ │ │ DSInputText root shows dsInputtextRoot · data-dimsum-slot="dsDatatableResizer"
55
+ │ │ and data-testid="ds-datatable-resizer" land on the inner input element
56
+ │ │
57
+ │ ├── LOADER_WRAPPER (dsDatatableLoaderWrapper) · div[role="row"]
58
+ │ │ present when: isLoading=true (sibling of HEAD_WRAPPER)
59
+ │ │
60
+ │ ├── CENTER_CONTENT_FLEX_WRAPPER (dsDatatableCenterContentFlexWrapper) · div[role="row"]
61
+ │ │ present when: data=[] (sibling of HEAD_WRAPPER)
62
+ │ │ └── EMPTY_STATE_WRAPPER (dsDatatableEmptyStateWrapper) · div[role="cell"]
63
+ │ │ ├── warning icon (dsIconRoot — no DataTable slot attr)
64
+ │ │ ├── EMPTY_PRIMARY_MESSAGE (dsDatatableEmptyPrimaryMessage) · p
65
+ │ │ ├── EMPTY_SECONDARY_MESSAGE (dsDatatableEmptySecondaryMessage) · p
66
+ │ │ └── CTA button (dsButtonRoot — no DataTable slot attr)
67
+ │ │ present when: noResultsButtonLabel + onNoResultsButtonClick provided
68
+ │ │
69
+ │ └── ROW (dsDatatableRow) · div[position:absolute] (one per visible row)
70
+ │ ║ prop: dsDatatableRow · layout wrapper — not the semantic row element
71
+ │ └── FULLSIZE_GRID (dsDatatableFullsizeGrid) · div
72
+ │ ║ prop: dsDatatableFullsizeGrid · owns onClick/onKeyDown row interaction handlers
73
+ │ └── CELL_CONTAINER (dsDatatableCellsContainer) · div[role="row"]
74
+ │ ║ ★ THE semantic row element — what screen readers traverse
75
+ │ ║ prop: dsDatatableCellsContainer · callback: (row) => props
76
+ │ ║ inject aria-*, data-* attributes, bg/color xstyled props here
77
+ │ │
78
+ │ ├── GROUP_HEADER_CONTAINER (dsDatatableGroupHeaderContainer) · div[role="cell"]
79
+ │ │ present in: group header rows only (requires getRowVariant returning 'ds-header-group-row')
80
+ │ │ └── GROUP_HEADER_TITLE (dsDatatableHeaderTitle) · span
81
+ │ │
82
+ │ ├── CELL (dsDatatableCell) · div[role="cell"] (one per data column)
83
+ │ │ ║ prop: dsDatatableCell · callback: (cell) => props
84
+ │ │ ├── EDITABLE_CONTAINER (dsDatatableEditableContainer) · div[role="group"]
85
+ │ │ │ present when: column.editable is set
86
+ │ │ │ ├── CELL_CONTENT (dsDatatableCellContent) · div
87
+ │ │ │ ├── pencil icon (dsIconRoot — no DataTable slot attr)
88
+ │ │ │ └── TEXT_EDITABLE_CELL_INPUT (dsDatatableTextEditableCellInput) · input
89
+ │ │ │ present after user clicks to enter edit mode (autoFocus on mount)
90
+ │ │ └── CELL_CONTENT (dsDatatableCellContent) · div (standard columns)
91
+ │ │ ├── EXPAND_CELL_CONTAINER (dsDatatableExpandCellContainer) · span[role="button"]
92
+ │ │ │ present in: expand column data cells only
93
+ │ │ ├── SINGLE_CELL_CONTAINER (dsDatatableSingleCellContainer) · div
94
+ │ │ │ present in: single-select column data cells only
95
+ │ │ └── DRAG_AND_DROP_GRIPPER (dsDatatableDragAndDropGripper) · div[role="button"]
96
+ │ │ present in: DnD column data cells (dragAndDropRows=true)
97
+ │ │
98
+ │ └── ACTION_CELL (dsDatatableActionCell) · div[role="cell"]
99
+ │ present when: renderRowActions prop is set
100
+ │ └── TOOLBAR_POSITION (dsDatatableToolbarPosition) · div
101
+ │ └── TOOLBAR_WRAPPER (dsDatatableToolbarWrapper) · div
102
+ │ ├── TOOLBAR_BUTTONS_WRAPPER (dsDatatableToolbarButtonsWrapper) · div
103
+ │ │ present after user opens the toolbar
104
+ │ └── trigger button (dsButtonRoot — no DataTable slot attr)
105
+ │ data-testid="data-table-toolbar-trigger"
106
+
107
+ └── PAGINATION_WRAPPER (dsDatatablePaginationWrapper) · div
108
+ present when: pagination prop is set
109
+ └── (ds-pagination component — dsPaginationWrapper etc.)
110
+
111
+ ── FILTER_POPOVER portal ← rendered at document body level, not inside DataTable DOM
112
+ present after user clicks a column's FILTER_POPOVER_BUTTON trigger
113
+ DOM shows: dsFloatingwrapperRoot[role="dialog"] · (root)
114
+ └── dsFloatingwrapperContent
115
+ └── FILTER_POPOVER_CONTENT (dsDatatableFilterPopoverContent) · div
116
+ ├── FREE_TEXT_SEARCH_WRAPPER (dsDatatableFreeTextSearchWrapper) · div
117
+ │ └── FREE_TEXT_SEARCH_FILTER (dsDatatableFreeTextSearchFilter)
118
+ │ prop: dsDatatableFreeTextSearchFilter · wraps DSInputText
119
+ │ data-dimsum-slot="dsDatatableFreeTextSearchFilter" and data-testid="data-table-filter-free-text-search"
120
+ │ land on the inner input element
121
+ └── FILTER_POPOVER_FOOTER — no dsDatatableFilterPopoverFooter attr in DOM
122
+ └── submit/reset buttons (dsButtonRoot)
123
+ ```
124
+
125
+ **Slots that are injectable but do not appear as `dsDatatable*` in the DOM:**
126
+
127
+ These slot props work for injection, but when you inspect the DOM you will not see a `dsDatatableXxx` attribute. The table below shows what you will see instead. This matters if you are debugging injection or writing DOM-based test assertions.
128
+
129
+ | Slot key | What the DOM shows | Where to find it |
130
+ | ------------------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------- |
131
+ | `PENCIL_ICON` | `dsIconRoot` + `dsIconSvg` | Inside `EDITABLE_CONTAINER`; present in DOM but CSS-hidden until hover/focus |
132
+ | `TOOLBAR_BUTTON` | `dsButtonRoot` | Inside `TOOLBAR_BUTTONS_WRAPPER`, visible after toolbar is opened |
133
+ | `EMPTY_BUTTON` | `dsButtonRoot` | Inside `EMPTY_STATE_WRAPPER`, the CTA button |
134
+ | `FILTER_POPOVER` | `dsFloatingwrapperRoot` + `dsFloatingwrapperContent` | Portal at document body — `role="dialog"`, not inside the DataTable DOM subtree |
135
+ | `FILTER_POPOVER_FOOTER` | `dsDialogFooter` | Inside `FILTER_POPOVER_CONTENT` |
136
+ | `FILTER_BAR_MENU_BUTTON` | `dsButtonRoot` | Inside `FILTER_BAR_WRAPPER`, always visible when `withFilterBar=true` |
137
+
138
+ **Slots only visible in interactive states (not in static render):**
139
+
140
+ | Slot key | Required interaction | How to trigger in a test |
141
+ | ----------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------- |
142
+ | `TOOLBAR_BUTTONS_WRAPPER` | Click toolbar trigger | `userEvent.click(screen.getByTestId('data-table-toolbar-trigger'))` |
143
+ | `TEXT_EDITABLE_CELL_INPUT` | Click editable cell to enter edit mode | `userEvent.click(screen.getByTestId('ds-datatable-editable-container'))` |
144
+ | `FILTER_POPOVER_CONTENT` / `FREE_TEXT_SEARCH_WRAPPER` | Click column filter button | `fireEvent.click(screen.getByTestId('data-table-filter-menu-button').querySelector('button'))` |
145
+ | `DROP_INDICATOR` | Active drag in progress | Requires a real browser drag interaction — not reliably reproducible in JSDOM |
146
+
147
+ **Critical distinction — `CELL_CONTAINER` vs `ROW` vs `FULLSIZE_GRID`:**
148
+
149
+ Three slots wrap each row. They are not interchangeable:
150
+
151
+ - `ROW` (`dsDatatableRow`) — the absolute-positioned layout div. Inject layout-level CSS only. Not a semantic element.
152
+ - `FULLSIZE_GRID` (`dsDatatableFullsizeGrid`) — the grid div that owns the click/keyboard handlers. Injectable via `dsDatatableFullsizeGrid` slot prop.
153
+ - `CELL_CONTAINER` (`dsDatatableCellsContainer`) — the `role="row"` div. **This is the semantic row.** Screen readers traverse this. `aria-rowindex`, `aria-label`, `aria-selected`, and background color belong here.
154
+
155
+ Injecting `aria-selected` or a background color into `ROW` instead of `CELL_CONTAINER` is a silent failure — the attribute exists in the DOM but attaches to a layout wrapper that screen readers skip.
156
+
157
+ **Header slots:**
158
+
159
+ - `HEAD_TR` (`dsDatatableHeadTr`) — the `role="row"` header row container. Injectable via `dsDatatableHeadTr` slot prop.
160
+ - `HEAD_TH` (`dsDatatableHeadTh`) — the `role="columnheader"` element. Use for `innerRef` focus-bridge wiring and column-level aria overrides.
161
+
162
+ `DATA_TABLE_SLOTS.HEAD_TH` (prop: `dsDatatableHeadTh`) has a distinct callback argument shape from row and cell slots. It receives `{ columnId: string, firstFocuseableColumnHeaderId: string | undefined }` — the ID of the current header cell and the ID of the first focusable column header in the table. This is used for focus-bridge patterns such as wiring a `fallbackRef` from an external filter bar to the first column header when the last filter pill is removed.
163
+
164
+ ```jsx
165
+ const dsDatatableHeadTh = useCallback(
166
+ ({ columnId, firstFocuseableColumnHeaderId }) => {
167
+ if (columnId === firstFocuseableColumnHeaderId) return { innerRef: fallbackRef };
168
+ return {};
169
+ },
170
+ [fallbackRef],
171
+ );
172
+ ```
173
+
174
+ ## When to reach for slots
175
+
176
+ **Try OOB first.** The DataTable exposes first-class callbacks for every stateful interaction — `onSelectionChange`, `onFiltersChange`, `onColumnSort`, `onRowExpand`, `onCellValueChange`. These are the correct and complete sites for application logic. Slots are not a shortcut to those.
177
+
178
+ Slots are a low-level escape hatch. They inject props directly into internal styled components. The application side owns long-term maintenance of everything injected this way — if the DataTable's internal structure changes, slot-based injections break silently.
179
+
180
+ **Valid uses:**
181
+
182
+ - `data-*` attributes — analytics tracking, feature flags, test selectors that the DataTable doesn't expose natively. Example: `data-private="true"`, `data-loan-id={row.original.id}`.
183
+ - `aria-*` overrides — accessibility attributes the DataTable doesn't set automatically for your specific use case.
184
+ - Conditional styling — `bg`, `color`, `style` driven by row/cell data. The app team accepts ownership of visual maintenance.
185
+
186
+ **Red flag — event listeners via slots.** If you find yourself attaching `onClick`, `onKeyDown`, or other event listeners through `dsDatatableCellsContainer` or `dsDatatableCell`, stop. The DataTable already manages its own interaction model. Before proceeding:
187
+
188
+ 1. Check whether an OOB callback already handles what you need.
189
+ 2. Re-examine the UX requirement — if the system provides no first-class support for this interaction, that is a signal the design may be solving a problem in a non-standard way.
190
+ 3. Engage the Dimsum team (ICE engineers: Teams/Jira; partners: your org's Dimsum contact) to verify whether an OOB solution exists or should be added.
191
+
192
+ ## Setup
193
+
194
+ The two primary consumer props. Both receive a memoized callback returning a plain props object — not a React component, no hooks inside.
195
+
196
+ ```jsx
197
+ import { DataTable } from '@elliemae/ds-data-table';
198
+
199
+ // Row level — targets CELL_CONTAINER (the semantic role="row" element)
200
+ const rowContainerProps = useCallback((row) => {
201
+ if (row.original.status === 'error') {
202
+ return {
203
+ 'data-error-row': 'true', // data-* injection
204
+ 'aria-invalid': 'true', // aria-* injection
205
+ bg: { _: 'red.100', hover: 'red.200 !important' }, // xstyled
206
+ };
207
+ }
208
+ return {};
209
+ }, []);
210
+
211
+ // Cell level — targets CELL (individual role="cell" element)
212
+ const cellProps = useCallback((cell) => {
213
+ if (cell.row.original.status === 'error') {
214
+ return { style: { color: 'red' } }; // React CSSProperties
215
+ }
216
+ return {};
217
+ }, []);
218
+
219
+ <DataTable
220
+ columns={columns}
221
+ data={rows}
222
+ height={500}
223
+ dsDatatableCellsContainer={rowContainerProps}
224
+ dsDatatableCell={cellProps}
225
+ />;
226
+ ```
227
+
228
+ Always wrap callbacks in `useCallback`. Inline callbacks create a new reference on every render, causing all virtualized rows to re-render unnecessarily.
229
+
230
+ In `dsDatatableCell`, access row data via `cell.row.original` (raw data) or `cell.row.uid` (UID from `uniqueRowAccessor`). Do not use `cell.row.id` — that is react-table's internal positional index and shifts when data is reordered.
231
+
232
+ ## `column.cellStyle` vs `dsDatatableCell`
233
+
234
+ `column.cellStyle` (`React.CSSProperties`) applies the same CSS to every cell in that column. Use it for uniform column-level layout: sticky positioning, fixed-width overrides. It requires no callback.
235
+
236
+ `dsDatatableCell` varies per row based on data. Use it when the styling depends on row state (error, highlight, selection). Reaching for `dsDatatableCell` to apply the same style to all rows in a column is the wrong tool — `column.cellStyle` is simpler and sufficient for that case.
237
+
238
+ ## Critical constraint — no hooks inside slot callbacks
239
+
240
+ `dsDatatableCellsContainer` and `dsDatatableCell` are JavaScript functions called at render time inside the DataTable's own rendering context. They are not React components. Hook calls inside them register against the calling component's hook list — a rules-of-hooks violation. Obtain all external values (theme tokens, feature flags, state) at component level and close over them:
241
+
242
+ ```jsx
243
+ const theme = useTheme();
244
+ const errorBg = theme.colors.danger[50];
245
+
246
+ const rowContainerProps = useCallback(
247
+ (row) => {
248
+ if (row.original.status === 'error') return { bg: errorBg };
249
+ return {};
250
+ },
251
+ [errorBg],
252
+ );
253
+ ```
254
+
255
+ ---
256
+
257
+ See also: ds-data-table-boundaries/SKILL.md — when slots cannot satisfy the requirement, escalation path