@elliemae/ds-filter-bar 3.70.0-next.3 → 3.70.0-next.30

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/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@elliemae/ds-filter-bar",
3
- "version": "3.70.0-next.3",
3
+ "version": "3.70.0-next.30",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Filter Bar",
6
6
  "files": [
7
- "dist"
7
+ "dist",
8
+ "skills"
8
9
  ],
9
10
  "module": "./dist/esm/index.js",
10
11
  "main": "./dist/cjs/index.js",
@@ -38,19 +39,19 @@
38
39
  "dependencies": {
39
40
  "@xstyled/system": "~3.8.1",
40
41
  "uid": "^2.0.2",
41
- "@elliemae/ds-button-v2": "3.70.0-next.3",
42
- "@elliemae/ds-grid": "3.70.0-next.3",
43
- "@elliemae/ds-menu-button": "3.70.0-next.3",
44
- "@elliemae/ds-props-helpers": "3.70.0-next.3",
45
- "@elliemae/ds-system": "3.70.0-next.3",
46
- "@elliemae/ds-icons": "3.70.0-next.3"
42
+ "@elliemae/ds-grid": "3.70.0-next.30",
43
+ "@elliemae/ds-button-v2": "3.70.0-next.30",
44
+ "@elliemae/ds-menu-button": "3.70.0-next.30",
45
+ "@elliemae/ds-props-helpers": "3.70.0-next.30",
46
+ "@elliemae/ds-system": "3.70.0-next.30",
47
+ "@elliemae/ds-icons": "3.70.0-next.30"
47
48
  },
48
49
  "devDependencies": {
49
50
  "jest": "^30.0.0",
50
51
  "styled-components": "~5.3.9",
51
- "@elliemae/ds-test-utils": "3.70.0-next.3",
52
- "@elliemae/ds-monorepo-devops": "3.70.0-next.3",
53
- "@elliemae/ds-typescript-helpers": "3.70.0-next.3"
52
+ "@elliemae/ds-test-utils": "3.70.0-next.30",
53
+ "@elliemae/ds-monorepo-devops": "3.70.0-next.30",
54
+ "@elliemae/ds-typescript-helpers": "3.70.0-next.30"
54
55
  },
55
56
  "peerDependencies": {
56
57
  "@testing-library/jest-dom": "^6.6.3",
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: ds-filter-bar-health-check
3
+ description: >
4
+ Audit the current state of a @elliemae/ds-filter-bar implementation. Use before starting
5
+ a task that depends on the filter bar, to verify a foundation before building on it, or
6
+ when diagnosing unexpected behavior. Produces a complete state report: what is correct and
7
+ sound, what requires fixing, what is suboptimal, and what gaps may affect the current task.
8
+ type: health-check
9
+ library: ds-filter-bar
10
+ library_version: '3.60.0'
11
+ requires:
12
+ - ds-filter-bar-setup
13
+ - ds-filter-bar-slots
14
+ ---
15
+
16
+ ## Purpose
17
+
18
+ This skill audits an existing `@elliemae/ds-filter-bar` integration. It is not a setup guide. It tells you the current state of a filter bar implementation that already exists.
19
+
20
+ **Use it:**
21
+
22
+ - Before starting a task that involves a filter bar — to understand what you are about to touch
23
+ - Before making filter bar interactions load-bearing for a new feature — to verify the foundation
24
+ - When focus, keyboard navigation, or screen reader feedback is broken — to surface what the implementation gets wrong
25
+
26
+ ---
27
+
28
+ ## Load the embedded knowledge
29
+
30
+ Load the following skills **as reference knowledge only**. You are reading their embedded understanding of correct patterns. You are not running them as tasks.
31
+
32
+ - `ds-filter-bar-setup` — assembly, focus management, `runAfterRemoval`, `ScreenReaderOnly`, `fallbackRef`, `filterBarRef`, state management
33
+ - `ds-filter-bar-slots` — slot injection surface, valid uses, event listener red flag
34
+
35
+ ---
36
+
37
+ ## Procedure
38
+
39
+ ### Step 1 — Locate and read the implementation
40
+
41
+ Find every file where `@elliemae/ds-filter-bar` components are rendered. For each instance:
42
+
43
+ - Record which components are used: `DSFilterBar`, `DSFilterBarContent`, `DSFilterBarActions`, `DSFilterBarMenuButton`
44
+ - Record whether `useFilterBarPrevNextFocus` is imported and used
45
+ - Read where filter state lives (active filters, pills) — state manager, store, atoms, or `useState`
46
+ - Read every `onRemove` handler on pill components — is each one wrapped in `runAfterRemoval`?
47
+ - Read the `onClearFilters` handler on `DSFilterBarMenuButton` — this one should NOT use `runAfterRemoval`
48
+ - Check whether `ScreenReaderOnly aria-live="polite"` is present inside `DSFilterBar`
49
+ - Check whether `filterBarRef` is passed to `DSFilterBar`
50
+ - Check whether `fallbackRef` (or equivalent) is shared between `useFilterBarPrevNextFocus` and `DSFilterBarMenuButton.innerRef`
51
+ - Check whether `RemovableFilterPill` (or equivalent pill component) is defined at module level
52
+ - Check whether the filter bar is inside a DataTable context — if so, `withFilterBar=true` on DataTable may have been the correct choice instead of standalone
53
+
54
+ ### Step 2 — Apply diagnostic checks
55
+
56
+ **CRITICAL** — prevents correct function or WCAG failure; fix before proceeding
57
+ **HIGH** — meaningful risk; should fix
58
+ **MEDIUM** — suboptimal; worth addressing
59
+ **SOUND** — correct; rely on it
60
+
61
+ #### Focus management checks
62
+
63
+ - [ ] `useFilterBarPrevNextFocus` is used — without it, focus management is manual and likely incorrect
64
+ - [ ] Every individual pill `onRemove` handler is wrapped in `runAfterRemoval` — omission is a silent WCAG 2.4.3 (Focus Order) failure
65
+ - [ ] `DSFilterBarMenuButton.onClearFilters` is NOT wrapped in `runAfterRemoval` — the menu button persists after clearing; this handler is the correct exception
66
+ - [ ] `filterBarRef` is passed to `DSFilterBar` — without it, the hook cannot scan focusable elements and immediately jumps to `fallbackRef` on every removal
67
+ - [ ] `fallbackRef` (hook) and `innerRef` (DSFilterBarMenuButton) are the same ref object — separate refs leave `fallbackRef.current` null at focus-return time
68
+
69
+ #### Screen reader checks
70
+
71
+ - [ ] `ScreenReaderOnly aria-live="polite"` is rendered inside `DSFilterBar`
72
+ - [ ] The announcement string is updated on every filter state change (remove single, clear all, restore) — not only on one type of change
73
+ - [ ] `ScreenReaderOnly` is imported from `@elliemae/ds-accessibility` — not from `@elliemae/ds-filter-bar`
74
+
75
+ #### Assembly checks
76
+
77
+ - [ ] `RemovableFilterPill` (or equivalent pill wrapper) is defined at module level — not inside the parent component body
78
+ - [ ] Filter state (active filters list) lives in the project's state manager — not `useState`
79
+ - [ ] The one acceptable `useState` is the `announcement` string — ephemeral UI feedback only
80
+ - [ ] No `useEffect` watches filter state to dispatch API calls — consequences belong inside callbacks
81
+ - [ ] If the filter bar is inside a DataTable view: was `withFilterBar=true` on DataTable evaluated first? Standalone `ds-filter-bar` is for non-DataTable contexts
82
+
83
+ #### Slot checks
84
+
85
+ - [ ] No event listeners (`onClick`, `onKeyDown`) injected through slot props — the filter bar manages its own interaction model
86
+
87
+ #### Memoization checks
88
+
89
+ - [ ] `onClearFilters` is wrapped in `useCallback`
90
+ - [ ] `options` for `DSFilterBarMenuButton` is stable (`useMemo` if constructed inline)
91
+ - [ ] Per-pill `onRemove` handlers: each callback is stable (either `useCallback` per item or using a handler factory)
92
+
93
+ ### Step 3 — Produce the state report
94
+
95
+ **Foundation — what is correct and sound**
96
+ What the implementation gets right. Be specific — this is what you can rely on.
97
+
98
+ **Critical issues — fix before proceeding**
99
+ Each item with file, location, and pointer to the `ds-filter-bar-setup` skill section covering the fix. A11y failures (runAfterRemoval, ScreenReaderOnly) are always critical.
100
+
101
+ **Warnings — should address, not blocking**
102
+ Each item with context on the risk.
103
+
104
+ **Awareness — no action needed, good to know**
105
+ Suboptimal patterns that are not breaking.
106
+
107
+ **Gaps relevant to this task**
108
+ What the current task will require that is not yet present.
109
+
110
+ ---
111
+
112
+ ## What this skill does NOT do
113
+
114
+ - It does not generate or modify code
115
+ - It does not run the setup skill as a task
116
+ - It does not replace human judgment on trade-offs
117
+
118
+ ## When this isn't enough
119
+
120
+ **ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
121
+ **Partners:** Your organization's Dimsum point of contact
@@ -0,0 +1,289 @@
1
+ ---
2
+ name: ds-filter-bar-setup
3
+ description: >
4
+ Filter bar assembly for @elliemae/ds-filter-bar. DSFilterBar (filterBarRef required),
5
+ DSFilterBarContent (pills area), DSFilterBarActions, DSFilterBarMenuButton (onClearFilters,
6
+ options, innerRef). useFilterBarPrevNextFocus hook with filterBarRef + fallbackRef →
7
+ runAfterRemoval wraps every pill onRemove handler. ScreenReaderOnly aria-live="polite"
8
+ required for screen reader announcements. fallbackRef must be passed as both hook
9
+ fallbackRef and DSFilterBarMenuButton innerRef — same ref object.
10
+ type: core
11
+ library: ds-filter-bar
12
+ library_version: '3.60.0'
13
+ sources:
14
+ - '@elliemae/ds-filter-bar:dist/types/react-desc-prop-types.d.ts'
15
+ - '@elliemae/ds-filter-bar:dist/types/hooks/react-desc-prop-types.d.ts'
16
+ - '@elliemae/ds-filter-bar:dist/types/parts/DSFilterBarMenuButton/react-desc-prop-types.d.ts'
17
+ ---
18
+
19
+ ## Setup
20
+
21
+ **Before using standalone `ds-filter-bar`:** if the consuming application already uses `@elliemae/ds-data-table`, check whether `withFilterBar=true` on the DataTable covers the requirement. The DataTable's built-in filter bar handles integration with its own filter state. Standalone `ds-filter-bar` is for filter UIs outside a DataTable context, or in applications that don't use DataTable.
22
+
23
+ Define `RemovableFilterPill` at module level — never inside another component. A component defined inside a parent gets a new type identity on every render, causing React to unmount and remount the subtree on every cycle, which destroys focus mid-removal.
24
+
25
+ Read filter state from the project's state manager (RTK is most common in ICE codebases; adapt to your project's convention — vanilla Redux, Redux-Saga, Zustand, or other). The one acceptable `useState` is the `announcement` string — it is ephemeral UI feedback with no cross-component visibility requirement.
26
+
27
+ `filterBarRef` and `menuButtonRef` must be the same ref objects passed to both the hook and the components. `filterBarRef` wires `DSFilterBar`'s DOM node to the hook's focusable-element scan. `menuButtonRef` is the fallback focus target when no pills remain — it must be passed as `innerRef` to `DSFilterBarMenuButton` so it is populated when the hook needs it.
28
+
29
+ ```jsx
30
+ import {
31
+ DSFilterBar,
32
+ DSFilterBarActions,
33
+ DSFilterBarContent,
34
+ DSFilterBarMenuButton,
35
+ useFilterBarPrevNextFocus,
36
+ } from '@elliemae/ds-filter-bar';
37
+ import { ScreenReaderOnly } from '@elliemae/ds-accessibility';
38
+ import { DSPillV2 } from '@elliemae/ds-pills-v2';
39
+ import { useCallback, useRef, useState } from 'react';
40
+
41
+ const RemovableFilterPill = ({ label, onRemove }) => <DSPillV2 type="removable" label={label} onRemove={onRemove} />;
42
+
43
+ const FilterBarComponent = () => {
44
+ const filters = useSelector(selectActiveFilters);
45
+ const dispatch = useDispatch();
46
+ const [announcement, setAnnouncement] = useState('');
47
+
48
+ const filterBarRef = useRef(null);
49
+ const menuButtonRef = useRef(null);
50
+
51
+ const { runAfterRemoval } = useFilterBarPrevNextFocus({
52
+ filterBarRef,
53
+ fallbackRef: menuButtonRef,
54
+ });
55
+
56
+ const handleSingleFilterRemove = useCallback(
57
+ (filter) => {
58
+ const newFilters = filters.filter((f) => f !== filter);
59
+ dispatch(setFilters(newFilters));
60
+ dispatch(fetchFilteredData(newFilters));
61
+ setAnnouncement(`Filter removed: ${filter}`);
62
+ },
63
+ [dispatch, filters],
64
+ );
65
+
66
+ const handleClearAll = useCallback(() => {
67
+ dispatch(setFilters([]));
68
+ dispatch(fetchFilteredData([]));
69
+ setAnnouncement('All filters cleared');
70
+ }, [dispatch]);
71
+
72
+ return (
73
+ <DSFilterBar filterBarRef={filterBarRef}>
74
+ <ScreenReaderOnly aria-live="polite">{announcement}</ScreenReaderOnly>
75
+ <DSFilterBarContent>
76
+ {filters.map((filter) => (
77
+ <RemovableFilterPill
78
+ key={filter}
79
+ label={filter}
80
+ onRemove={() => runAfterRemoval(() => handleSingleFilterRemove(filter))}
81
+ />
82
+ ))}
83
+ </DSFilterBarContent>
84
+ <DSFilterBarActions>
85
+ <DSFilterBarMenuButton innerRef={menuButtonRef} onClearFilters={handleClearAll} />
86
+ </DSFilterBarActions>
87
+ </DSFilterBar>
88
+ );
89
+ };
90
+ ```
91
+
92
+ ## Core Patterns
93
+
94
+ ### useFilterBarPrevNextFocus — what it does and why it's required
95
+
96
+ `useFilterBarPrevNextFocus` returns `runAfterRemoval(fn)`. Call it instead of the removal handler directly:
97
+
98
+ 1. Before the removal: scans all focusable elements inside the filter bar
99
+ 2. Executes the removal function
100
+ 3. After the DOM updates (via `requestAnimationFrame`): moves focus to the previous pill, the same index, or the last remaining pill
101
+ 4. If no pills remain: focuses `fallbackRef` (the menu button)
102
+
103
+ Without this, keyboard users activating a pill's remove button lose focus when the pill disappears. This is a silent WCAG 2.4.3 (Focus Order) failure — no console error, no axe-core violation on the visible state, but broken keyboard navigation in practice.
104
+
105
+ `runAfterRemoval` is NOT needed for `DSFilterBarMenuButton.onClearFilters`. That action is triggered from inside the menu button, which persists after clearing. Focus stays on the button naturally.
106
+
107
+ **Testing note:** because `runAfterRemoval` uses `requestAnimationFrame` internally, Jest tests that assert focus state after removal may be flaky. If focus assertion tests are needed, use Playwright CT which runs in a real browser.
108
+
109
+ ### DSFilterBarMenuButton — options configuration
110
+
111
+ `onClearFilters` is required — it wires the built-in "Clear filters" menu item. `options` adds custom items below it following the DSMenuButton item shape:
112
+
113
+ ```jsx
114
+ <DSFilterBarMenuButton
115
+ innerRef={menuButtonRef}
116
+ onClearFilters={handleClearAll}
117
+ options={[
118
+ {
119
+ dsId: 'restore-defaults',
120
+ label: 'Restore default filters',
121
+ type: 'activable-item',
122
+ onClick: () => {
123
+ dispatch(restoreDefaultFilters());
124
+ setAnnouncement('Default filters restored');
125
+ },
126
+ },
127
+ ]}
128
+ />
129
+ ```
130
+
131
+ ## Common Mistakes
132
+
133
+ ### CRITICAL Not wrapping pill removal handlers with runAfterRemoval
134
+
135
+ Wrong:
136
+
137
+ ```jsx
138
+ <RemovableFilterPill label={filter} onRemove={() => handleSingleFilterRemove(filter)} />
139
+ ```
140
+
141
+ Correct:
142
+
143
+ ```jsx
144
+ <RemovableFilterPill label={filter} onRemove={() => runAfterRemoval(() => handleSingleFilterRemove(filter))} />
145
+ ```
146
+
147
+ When a pill is removed via keyboard, focus is on the pill's remove button. The pill disappears after removal. Without `runAfterRemoval`, focus is stranded — a silent WCAG 2.4.3 (Focus Order) failure. The hook scans focusable elements before removal and moves focus correctly after the DOM updates.
148
+
149
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
150
+
151
+ ---
152
+
153
+ ### CRITICAL Passing fallbackRef to hook but not as innerRef to DSFilterBarMenuButton
154
+
155
+ Wrong:
156
+
157
+ ```jsx
158
+ const menuButtonRef = useRef(null);
159
+ const { runAfterRemoval } = useFilterBarPrevNextFocus({ filterBarRef, fallbackRef: menuButtonRef });
160
+ <DSFilterBarMenuButton onClearFilters={handleClearAll} />;
161
+ ```
162
+
163
+ Correct:
164
+
165
+ ```jsx
166
+ const menuButtonRef = useRef(null);
167
+ const { runAfterRemoval } = useFilterBarPrevNextFocus({ filterBarRef, fallbackRef: menuButtonRef });
168
+ <DSFilterBarMenuButton innerRef={menuButtonRef} onClearFilters={handleClearAll} />;
169
+ ```
170
+
171
+ `fallbackRef.current` is null until `innerRef` connects it to the DOM element. When the last pill is removed, the hook calls `fallbackRef.current?.focus?.()` — if the ref is null, focus is lost silently. The keyboard user must Tab from the beginning of the page to re-orient.
172
+
173
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
174
+
175
+ ---
176
+
177
+ ### HIGH Omitting ScreenReaderOnly aria-live announcements
178
+
179
+ Wrong:
180
+
181
+ ```jsx
182
+ const handleSingleFilterRemove = (filter) => {
183
+ dispatch(removeFilter(filter));
184
+ };
185
+ ```
186
+
187
+ Correct:
188
+
189
+ ```jsx
190
+ const [announcement, setAnnouncement] = useState('');
191
+
192
+ const handleSingleFilterRemove = (filter) => {
193
+ dispatch(removeFilter(filter));
194
+ setAnnouncement(`Filter removed: ${filter}`);
195
+ };
196
+
197
+ // Inside DSFilterBar:
198
+ <ScreenReaderOnly aria-live="polite">{announcement}</ScreenReaderOnly>;
199
+ ```
200
+
201
+ Screen readers do not announce dynamic DOM changes unless explicitly told to. Without `aria-live="polite"`, screen reader users receive no feedback on filter operations. Every state change (remove single, clear all, restore) must update the announcement string. This is a WCAG 4.1.3 (Status Messages) requirement.
202
+
203
+ `ScreenReaderOnly` is from `@elliemae/ds-accessibility` — add this import separately alongside the `@elliemae/ds-filter-bar` imports.
204
+
205
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
206
+
207
+ ---
208
+
209
+ ### HIGH Omitting filterBarRef from DSFilterBar
210
+
211
+ Wrong:
212
+
213
+ ```jsx
214
+ const filterBarRef = useRef(null);
215
+ const { runAfterRemoval } = useFilterBarPrevNextFocus({ filterBarRef, fallbackRef });
216
+ <DSFilterBar>...</DSFilterBar>;
217
+ ```
218
+
219
+ Correct:
220
+
221
+ ```jsx
222
+ <DSFilterBar filterBarRef={filterBarRef}>...</DSFilterBar>
223
+ ```
224
+
225
+ Without `filterBarRef`, the hook's container is null. It skips the focusable-elements scan and immediately focuses `fallbackRef` on every removal — correct only for the last pill, wrong for all others.
226
+
227
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
228
+
229
+ ---
230
+
231
+ ### HIGH Non-primitive filter bar props not memoized
232
+
233
+ | Prop | Component | Memoization |
234
+ | ---------------- | ----------------------- | ------------------------------- |
235
+ | `onClearFilters` | `DSFilterBarMenuButton` | `useCallback` |
236
+ | `options` | `DSFilterBarMenuButton` | `useMemo` if constructed inline |
237
+ | `filterBarRef` | `DSFilterBar` | `useRef` (stable by definition) |
238
+ | `innerRef` | `DSFilterBarMenuButton` | `useRef` (stable by definition) |
239
+
240
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
241
+
242
+ ---
243
+
244
+ ### HIGH Defining RemovableFilterPill inside parent component
245
+
246
+ Wrong:
247
+
248
+ ```jsx
249
+ const FilterBarComponent = () => {
250
+ const RemovableFilterPill = ({ label, onRemove }) => <DSPillV2 type="removable" label={label} onRemove={onRemove} />;
251
+ // ...
252
+ };
253
+ ```
254
+
255
+ Correct:
256
+
257
+ ```jsx
258
+ const RemovableFilterPill = ({ label, onRemove }) => <DSPillV2 type="removable" label={label} onRemove={onRemove} />;
259
+
260
+ const FilterBarComponent = () => {
261
+ /* ... */
262
+ };
263
+ ```
264
+
265
+ A component defined inside another component gets a new type identity on every render. React treats each new identity as a different component, unmounting and remounting the subtree — this destroys internal state, re-triggers effects, and causes focus to be lost during the render cycle that follows a pill removal.
266
+
267
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
268
+
269
+ ---
270
+
271
+ ### HIGH Updating filter state without propagating to the data source
272
+
273
+ The filter bar is UI-only — removing a pill has no effect on displayed data unless the consumer propagates the change to the data source. Before implementing, confirm whether the model is server-side (each filter change dispatches an API call — correct for any non-trivial dataset) or client-side (filters applied to an in-memory array — a temporary workaround while backend endpoints are built, not a design pattern). Client-side filtering is a legitimate interim state when explicitly owned by the team with a documented remediation plan; it becomes a problem when treated as the default.
274
+
275
+ Source: https://dimsum.mortgagetech.ice.com/iframe.html?id=components-filterbar-features--basic
276
+
277
+ ---
278
+
279
+ ## When composable parts are not enough
280
+
281
+ If the filter bar assembly cannot satisfy the layout or behavior requirement:
282
+
283
+ 1. Confirm the requirement with UI/UX — most variations are achievable via composition, ordering, and xstyled props on the primitives.
284
+ 2. Contact the Dimsum team to validate whether a first-class solution should be added.
285
+
286
+ **ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
287
+ **Partners:** Your organization's Dimsum point of contact
288
+
289
+ See also: `@elliemae/ds-data-table` `withFilterBar` integration — if this filter bar is used alongside a DataTable, load the DataTable filtering skill for the integration wiring.
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: ds-filter-bar-slots
3
+ description: >
4
+ Slot injection surface for @elliemae/ds-filter-bar. Verified against v3.60.0 via probe.
5
+ DSFilterBar (ROOT), DSFilterBarContent (CONTENT), DSFilterBarActions (ACTIONS) have
6
+ functional slot injection. DSFilterBarMenuButton slot props have no effect — use its
7
+ OOB props (innerRef, onClearFilters, options) instead. Valid uses: data-*, aria-*,
8
+ conditional styling. Event listeners through slots are a red flag.
9
+ type: core
10
+ library: ds-filter-bar
11
+ library_version: '3.60.0'
12
+ requires:
13
+ - ds-filter-bar-setup
14
+ sources:
15
+ - '@elliemae/ds-filter-bar:dist/types/react-desc-prop-types.d.ts'
16
+ - '@elliemae/ds-filter-bar:dist/types/parts/DSFilterBarMenuButton/react-desc-prop-types.d.ts'
17
+ ---
18
+
19
+ ## Slot surface — DOM hierarchy
20
+
21
+ Slots are injection points into specific rendered DOM nodes. Understanding the hierarchy is required to inject props at the correct level — injecting `aria-*` at the wrong node produces silent accessibility failures.
22
+
23
+ **About this map:** Verified against `@elliemae/ds-filter-bar@3.60.0` via probe tests. Slot names are governed by the Dimsum breaking change protocol — renames are intentional and documented. If something doesn't match your installed version, inspect the rendered HTML in your environment. If you cannot do it autonomously, ask the human in the loop to render the component with synthetic data only — not production data containing real user information.
24
+
25
+ Each entry shows: `slot-key (data-dimsum-slot="value") · tag[role] · notes`.
26
+
27
+ ```
28
+ DSFilterBar renders:
29
+
30
+ ROOT (dsFilterbarRoot) · div[role="region"] ← ★ semantic region landmark
31
+ │ inject aria-label here — it labels the landmark for screen readers
32
+ │ inject data-* tracking attributes here
33
+ ├── CONTENT (dsFilterbarContent) · div
34
+ │ └── (pill components — DSPillV2 or custom)
35
+ └── ACTIONS (dsFilterbarActions) · div
36
+ └── DSFilterBarMenuButton ← no dsFilterbarMenuButton in DOM
37
+ └── dsMenubuttonRoot · div (the DOM shows this instead of dsFilterbarMenuButton)
38
+ └── dsButtonRoot · button
39
+ └── dsIconRoot · span
40
+ └── dsIconSvg · svg
41
+ ```
42
+
43
+ **Key finding — ROOT has `role="region"`:** `dsFilterbarRoot` renders as a semantic region landmark. This means:
44
+
45
+ - Screen readers announce it as a navigable region
46
+ - `aria-label` passed to `DSFilterBar` (or via `dsFilterbarRoot` slot) labels that region
47
+ - Do not inject a second `role` attribute — it is already set correctly
48
+
49
+ ## When to reach for slots
50
+
51
+ Try OOB first. `DSFilterBar` accepts standard HTML global attributes (`aria-*`, `data-*`, `className`, `style`) directly as props. Slots are for injecting into a specific internal DOM node rather than the root.
52
+
53
+ **Event listeners through slots are a red flag.** The filter bar manages its own interaction model. If you need to respond to filter bar interactions, use the OOB callbacks (`onRemove`, `onClearFilters`). If no OOB callback covers your requirement, stop and surface this to the human in the loop.
54
+
55
+ ## Setup
56
+
57
+ ```jsx
58
+ import {
59
+ DSFilterBar,
60
+ DSFilterBarContent,
61
+ DSFilterBarActions,
62
+ } from '@elliemae/ds-filter-bar';
63
+
64
+ // Inject into the region root
65
+ <DSFilterBar
66
+ filterBarRef={filterBarRef}
67
+ dsFilterbarRoot={{ 'aria-label': 'Active loan filters', 'data-section': 'filters' }}
68
+ >
69
+ ...
70
+ </DSFilterBar>
71
+
72
+ // Inject into the content area
73
+ <DSFilterBarContent dsFilterbarContent={{ 'data-pills-zone': 'true' }}>
74
+ ...
75
+ </DSFilterBarContent>
76
+
77
+ // Inject into the actions area
78
+ <DSFilterBarActions dsFilterbarActions={{ 'data-actions-zone': 'true' }}>
79
+ ...
80
+ </DSFilterBarActions>
81
+ ```
82
+
83
+ ### DSFilterBarMenuButton — no dsFilterbarMenuButton in DOM
84
+
85
+ `dsFilterbarMenuButton` does not appear in the DOM — the DOM shows `dsMenubuttonRoot` in its place. Passing `dsFilterbarMenuButton` slot props has no effect. Use `innerRef`, `onClearFilters`, and `options` props to interact with this component instead.
86
+
87
+ ## Common Mistakes
88
+
89
+ ### HIGH Injecting event listeners through slot props
90
+
91
+ The filter bar manages its own interaction model. Injecting `onClick`, `onKeyDown`, or similar through slot props bypasses it and can produce unpredictable behavior. Use OOB callbacks (`onRemove`, `onClearFilters`) instead. If no OOB callback exists for your requirement, surface this to the human in the loop before proceeding.
92
+
93
+ ---
94
+
95
+ ### MEDIUM Slot props not memoized
96
+
97
+ ```jsx
98
+ // Wrong — new object reference on every render
99
+ <DSFilterBar dsFilterbarRoot={{ 'data-id': computedId }} />;
100
+
101
+ // Correct
102
+ const rootSlotProps = useMemo(() => ({ 'data-id': computedId }), [computedId]);
103
+ <DSFilterBar dsFilterbarRoot={rootSlotProps} />;
104
+ ```
105
+
106
+ ## When this isn't enough
107
+
108
+ **ICE internal:** Microsoft Teams — Dimsum channel (informal) / Jira Dimsum board (formal)
109
+ **Partners:** Your organization's Dimsum point of contact