@elliemae/ds-data-table 3.60.0-next.19 → 3.60.0-next.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/dist/cjs/exported-related/FilterPopover/index.js +42 -40
- package/dist/cjs/exported-related/FilterPopover/index.js.map +2 -2
- package/dist/cjs/exported-related/FilterPopover/useTrackFocusableElements.js +59 -0
- package/dist/cjs/exported-related/FilterPopover/useTrackFocusableElements.js.map +7 -0
- package/dist/esm/exported-related/FilterPopover/index.js +35 -33
- package/dist/esm/exported-related/FilterPopover/index.js.map +2 -2
- package/dist/esm/exported-related/FilterPopover/useTrackFocusableElements.js +29 -0
- package/dist/esm/exported-related/FilterPopover/useTrackFocusableElements.js.map +7 -0
- package/dist/types/exported-related/FilterPopover/useTrackFocusableElements.d.ts +17 -0
- package/dist/types/tests/playwright/DSDataTable.ControlledFilterTestRenderer.d.ts +2 -0
- package/package.json +28 -28
|
@@ -34,47 +34,29 @@ module.exports = __toCommonJS(FilterPopover_exports);
|
|
|
34
34
|
var React = __toESM(require("react"));
|
|
35
35
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
36
|
var import_ds_button_v2 = require("@elliemae/ds-button-v2");
|
|
37
|
+
var import_ds_hooks_focus_trap = require("@elliemae/ds-hooks-focus-trap");
|
|
37
38
|
var import_ds_popperjs = require("@elliemae/ds-popperjs");
|
|
38
39
|
var import_ds_system = require("@elliemae/ds-system");
|
|
39
40
|
var import_react = require("react");
|
|
40
|
-
var import_constants = require("../../constants
|
|
41
|
-
var import_constants2 = require("../../configs/constants.js");
|
|
41
|
+
var import_constants = require("../../configs/constants.js");
|
|
42
42
|
var import_createInternalAndPropsContext = require("../../configs/useStore/createInternalAndPropsContext.js");
|
|
43
|
+
var import_constants2 = require("../../constants/index.js");
|
|
43
44
|
var import_useGetFilterHandlers = require("./useGetFilterHandlers.js");
|
|
44
45
|
var import_useGetFilterVisibility = require("./useGetFilterVisibility.js");
|
|
45
|
-
|
|
46
|
+
var import_useTrackFocusableElements = require("./useTrackFocusableElements.js");
|
|
47
|
+
const FilterButton = (0, import_ds_system.styled)("span", { name: import_constants2.DSDataTableName, slot: import_constants2.DSDataTableSlots.FILTER_POPOVER_BUTTON })`
|
|
46
48
|
display: inline-grid;
|
|
47
49
|
${(props) => props.hide ? "opacity: 0; display: none; width: 0;" : ""}
|
|
48
50
|
`;
|
|
49
|
-
const PopperContent = (0, import_ds_system.styled)("div", { name:
|
|
51
|
+
const PopperContent = (0, import_ds_system.styled)("div", { name: import_constants2.DSDataTableName, slot: import_constants2.DSDataTableSlots.FILTER_POPOVER_CONTENT })`
|
|
50
52
|
background-color: #fff;
|
|
51
53
|
`;
|
|
52
54
|
const StyledPoppoverJS = (0, import_ds_system.styled)(import_ds_popperjs.DSPopperJS, {
|
|
53
|
-
name:
|
|
54
|
-
slot:
|
|
55
|
+
name: import_constants2.DSDataTableName,
|
|
56
|
+
slot: import_constants2.DSDataTableSlots.FILTER_POPOVER
|
|
55
57
|
})``;
|
|
56
|
-
const ButtonTrap = ({ cb }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
57
|
-
"span",
|
|
58
|
-
{
|
|
59
|
-
tabIndex: 0,
|
|
60
|
-
onFocus: (e) => {
|
|
61
|
-
e.stopPropagation();
|
|
62
|
-
cb();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
);
|
|
66
58
|
const FilterPopover = (props) => {
|
|
67
|
-
const {
|
|
68
|
-
column,
|
|
69
|
-
customStyles,
|
|
70
|
-
reduxHeader,
|
|
71
|
-
menuContent,
|
|
72
|
-
columnId,
|
|
73
|
-
ariaLabel,
|
|
74
|
-
triggerIcon,
|
|
75
|
-
innerRef,
|
|
76
|
-
columnReference
|
|
77
|
-
} = props;
|
|
59
|
+
const { column, customStyles, reduxHeader, menuContent, ariaLabel, triggerIcon, innerRef, columnReference } = props;
|
|
78
60
|
const filters = (0, import_createInternalAndPropsContext.usePropsStore)((state) => state.filters);
|
|
79
61
|
const getOwnerProps = (0, import_createInternalAndPropsContext.usePropsStore)((store) => store.get);
|
|
80
62
|
const getOwnerPropsArguments = (0, import_react.useCallback)(
|
|
@@ -83,18 +65,40 @@ const FilterPopover = (props) => {
|
|
|
83
65
|
}),
|
|
84
66
|
[column.id]
|
|
85
67
|
);
|
|
86
|
-
const patchHeader = (0, import_createInternalAndPropsContext.useInternalStore)((state) => state.patchHeader);
|
|
87
68
|
const { isIconVisible, isMenuOpen } = (0, import_useGetFilterVisibility.useGetFilterVisibility)(reduxHeader);
|
|
88
69
|
const [buttonReference, setButtonReference] = (0, import_react.useState)(null);
|
|
89
70
|
const [isButtonFocused, setIsButtonFocused] = (0, import_react.useState)(false);
|
|
90
71
|
const { handleTriggerClick, handleClickOutsideMenu, handleMenuOnKeyDown, handleTriggerOnFocus, handleTriggerOnBlur } = (0, import_useGetFilterHandlers.useGetFilterHandlers)(props, isMenuOpen, buttonReference, setIsButtonFocused);
|
|
91
|
-
const buttonTrapCallback = (0, import_react.useCallback)(() => {
|
|
92
|
-
patchHeader(columnId, { hideFilterMenu: true, hideFilterButton: false });
|
|
93
|
-
buttonReference?.focus();
|
|
94
|
-
}, [columnId, patchHeader, buttonReference]);
|
|
95
72
|
const actionRef = (0, import_react.useRef)({
|
|
96
73
|
update: null
|
|
97
74
|
});
|
|
75
|
+
const popoverContentRef = (0, import_react.useRef)(null);
|
|
76
|
+
const { firstElementRef, lastElementRef } = (0, import_useTrackFocusableElements.useTrackFocusableElements)({
|
|
77
|
+
containerRef: popoverContentRef,
|
|
78
|
+
dependencies: [isMenuOpen, menuContent]
|
|
79
|
+
});
|
|
80
|
+
const focusedDayRef = (0, import_react.useRef)(null);
|
|
81
|
+
const handleOnKeyDown = (0, import_ds_hooks_focus_trap.useFocusTrap)({
|
|
82
|
+
firstElementRef,
|
|
83
|
+
lastElementRef,
|
|
84
|
+
onKeyDown: (e) => {
|
|
85
|
+
const target = e.target;
|
|
86
|
+
const dayWrapper = target.closest(".focusedDay");
|
|
87
|
+
if (dayWrapper) {
|
|
88
|
+
if (focusedDayRef.current && focusedDayRef.current !== dayWrapper) {
|
|
89
|
+
focusedDayRef.current.classList.remove("focusedDay");
|
|
90
|
+
}
|
|
91
|
+
focusedDayRef.current = dayWrapper;
|
|
92
|
+
lastElementRef.current = target;
|
|
93
|
+
}
|
|
94
|
+
if (e.key === "Tab" && focusedDayRef.current) {
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
focusedDayRef.current?.classList.remove("focusedDay");
|
|
97
|
+
focusedDayRef.current = null;
|
|
98
|
+
}, 0);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
98
102
|
(0, import_react.useEffect)(() => {
|
|
99
103
|
void actionRef.current.update?.();
|
|
100
104
|
}, [filters]);
|
|
@@ -108,7 +112,7 @@ const FilterPopover = (props) => {
|
|
|
108
112
|
FilterButton,
|
|
109
113
|
{
|
|
110
114
|
hide: !isIconVisible,
|
|
111
|
-
"data-testid":
|
|
115
|
+
"data-testid": import_constants.DATA_TESTID.DATA_TABLE_FILTER_BUTTON,
|
|
112
116
|
getOwnerProps,
|
|
113
117
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
114
118
|
import_ds_button_v2.DSButtonV2,
|
|
@@ -122,7 +126,7 @@ const FilterPopover = (props) => {
|
|
|
122
126
|
tabIndex: reduxHeader?.withTabStops ? 0 : -1,
|
|
123
127
|
"aria-label": ariaLabel,
|
|
124
128
|
"aria-hidden": !isButtonFocused,
|
|
125
|
-
"data-testid":
|
|
129
|
+
"data-testid": import_constants.DATA_TESTID.DATA_TABLE_FILTER_BUTTON_ELEMENT,
|
|
126
130
|
children: triggerIcon
|
|
127
131
|
}
|
|
128
132
|
)
|
|
@@ -135,7 +139,7 @@ const FilterPopover = (props) => {
|
|
|
135
139
|
referenceElement: columnReference || buttonReference,
|
|
136
140
|
showPopover: isMenuOpen,
|
|
137
141
|
closeContextMenu: handleClickOutsideMenu,
|
|
138
|
-
"data-testid":
|
|
142
|
+
"data-testid": import_constants.DATA_TESTID.DATA_TABLE_FILTER_MENU_CONTENT,
|
|
139
143
|
startPlacementPreference: "bottom-end",
|
|
140
144
|
customOffset: columnReference ? [0, 1] : [5, 4],
|
|
141
145
|
withoutArrow: true,
|
|
@@ -147,11 +151,9 @@ const FilterPopover = (props) => {
|
|
|
147
151
|
placementOrderPreference: ["bottom-end", "top-end"],
|
|
148
152
|
getOwnerProps,
|
|
149
153
|
getOwnerPropsArguments,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonTrap, { cb: buttonTrapCallback })
|
|
154
|
-
] })
|
|
154
|
+
role: "dialog",
|
|
155
|
+
"aria-label": `Filter by ${column.Header}`,
|
|
156
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PopperContent, { getOwnerProps, innerRef: popoverContentRef, onKeyDown: handleOnKeyDown, children: menuContent })
|
|
155
157
|
}
|
|
156
158
|
)
|
|
157
159
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/exported-related/FilterPopover/index.tsx", "../../../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable import/no-cycle */\n/* eslint-disable no-void */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport { DSButtonV2 } from '@elliemae/ds-button-v2';\nimport type { DSPopperJST } from '@elliemae/ds-popperjs';\nimport { DSPopperJS } from '@elliemae/ds-popperjs';\nimport { mergeRefs, styled } from '@elliemae/ds-system';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport {
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable import/no-cycle */\n/* eslint-disable no-void */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport { DSButtonV2 } from '@elliemae/ds-button-v2';\nimport { useFocusTrap } from '@elliemae/ds-hooks-focus-trap';\nimport type { DSPopperJST } from '@elliemae/ds-popperjs';\nimport { DSPopperJS } from '@elliemae/ds-popperjs';\nimport { mergeRefs, styled } from '@elliemae/ds-system';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { DATA_TESTID } from '../../configs/constants.js';\nimport { usePropsStore } from '../../configs/useStore/createInternalAndPropsContext.js';\nimport { DSDataTableName, DSDataTableSlots } from '../../constants/index.js';\nimport type { FilterPopoverProps } from './types.js';\nimport { useGetFilterHandlers } from './useGetFilterHandlers.js';\nimport { useGetFilterVisibility } from './useGetFilterVisibility.js';\nimport { useTrackFocusableElements } from './useTrackFocusableElements.js';\n\nconst FilterButton = styled('span', { name: DSDataTableName, slot: DSDataTableSlots.FILTER_POPOVER_BUTTON })<{\n hide: boolean;\n}>`\n display: inline-grid;\n ${(props) => (props.hide ? 'opacity: 0; display: none; width: 0;' : '')}\n`;\n\nconst PopperContent = styled('div', { name: DSDataTableName, slot: DSDataTableSlots.FILTER_POPOVER_CONTENT })`\n background-color: #fff;\n`;\n\nconst StyledPoppoverJS = styled(DSPopperJS, {\n name: DSDataTableName,\n slot: DSDataTableSlots.FILTER_POPOVER,\n})``;\n\nexport const FilterPopover: React.ComponentType<FilterPopoverProps> = (props: FilterPopoverProps) => {\n const { column, customStyles, reduxHeader, menuContent, ariaLabel, triggerIcon, innerRef, columnReference } = props;\n\n const filters = usePropsStore((state) => state.filters);\n const getOwnerProps = usePropsStore((store) => store.get);\n\n const getOwnerPropsArguments = useCallback(\n () => ({\n columnId: column.id,\n }),\n [column.id],\n );\n\n const { isIconVisible, isMenuOpen } = useGetFilterVisibility(reduxHeader);\n\n const [buttonReference, setButtonReference] = useState<HTMLButtonElement | null>(null);\n\n const [isButtonFocused, setIsButtonFocused] = useState(false);\n\n const { handleTriggerClick, handleClickOutsideMenu, handleMenuOnKeyDown, handleTriggerOnFocus, handleTriggerOnBlur } =\n useGetFilterHandlers(props, isMenuOpen, buttonReference, setIsButtonFocused);\n\n const actionRef: Required<DSPopperJST.Props>['actionRef'] = useRef({\n update: null,\n });\n const popoverContentRef = useRef<HTMLDivElement>(null);\n const { firstElementRef, lastElementRef } = useTrackFocusableElements({\n containerRef: popoverContentRef,\n dependencies: [isMenuOpen, menuContent],\n });\n\n const focusedDayRef = useRef<HTMLElement | null>(null);\n const handleOnKeyDown = useFocusTrap({\n firstElementRef,\n lastElementRef,\n onKeyDown: (e) => {\n const target = e.target as HTMLElement;\n // Special handling for DateRangePicker calendar: days use a roving tabindex pattern.\n // Track the focused day to update the lastElementRef and clear visual focus on Tab loop.\n const dayWrapper = target.closest('.focusedDay') as HTMLElement;\n if (dayWrapper) {\n // Remove visual focus from the previously focused day\n if (focusedDayRef.current && focusedDayRef.current !== dayWrapper) {\n focusedDayRef.current.classList.remove('focusedDay');\n }\n\n focusedDayRef.current = dayWrapper;\n // Update trap boundary to the focused day's button\n lastElementRef.current = target;\n }\n // Clear visual focus when Tab navigates away from the calendar\n if (e.key === 'Tab' && focusedDayRef.current) {\n setTimeout(() => {\n focusedDayRef.current?.classList.remove('focusedDay');\n focusedDayRef.current = null;\n }, 0);\n }\n },\n });\n\n useEffect(() => {\n // When the filters change, we need to update the popper position,\n // because the filter bar might push the datatable up or down, causing the popper to be misaligned\n void actionRef.current.update?.();\n }, [filters]);\n\n return (\n <div\n // This is here to prevent propagation, and not trigger the sort functionality\n onClick={(e) => e.stopPropagation()}\n onKeyDown={handleMenuOnKeyDown}\n >\n <FilterButton\n hide={!isIconVisible}\n data-testid={DATA_TESTID.DATA_TABLE_FILTER_BUTTON}\n getOwnerProps={getOwnerProps}\n >\n <DSButtonV2\n buttonType=\"icon\"\n size=\"s\"\n onClick={handleTriggerClick}\n onFocus={handleTriggerOnFocus}\n onBlur={handleTriggerOnBlur}\n innerRef={mergeRefs(isIconVisible && setButtonReference, innerRef)}\n tabIndex={reduxHeader?.withTabStops ? 0 : -1}\n aria-label={ariaLabel}\n aria-hidden={!isButtonFocused}\n data-testid={DATA_TESTID.DATA_TABLE_FILTER_BUTTON_ELEMENT}\n >\n {triggerIcon}\n </DSButtonV2>\n </FilterButton>\n {(columnReference || buttonReference) && (\n <StyledPoppoverJS\n actionRef={actionRef}\n referenceElement={columnReference || buttonReference}\n showPopover={isMenuOpen}\n closeContextMenu={handleClickOutsideMenu}\n data-testid={DATA_TESTID.DATA_TABLE_FILTER_MENU_CONTENT}\n startPlacementPreference=\"bottom-end\"\n customOffset={columnReference ? [0, 1] : [5, 4]}\n withoutArrow\n withoutAnimation\n extraPopperStyles={{\n ...customStyles,\n minWidth: column.ref?.current?.offsetWidth ?? '0px',\n }}\n placementOrderPreference={['bottom-end', 'top-end']}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n role=\"dialog\"\n aria-label={`Filter by ${column.Header as string}`}\n >\n <PopperContent getOwnerProps={getOwnerProps} innerRef={popoverContentRef} onKeyDown={handleOnKeyDown}>\n {menuContent}\n </PopperContent>\n </StyledPoppoverJS>\n )}\n </div>\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_constants"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var useTrackFocusableElements_exports = {};
|
|
30
|
+
__export(useTrackFocusableElements_exports, {
|
|
31
|
+
useTrackFocusableElements: () => useTrackFocusableElements
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(useTrackFocusableElements_exports);
|
|
34
|
+
var React = __toESM(require("react"));
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
const DEFAULT_FOCUSABLE_SELECTOR = 'button:not([disabled]):not([tabindex="-1"]), [href], input:not([disabled]):not([tabindex="-1"]), select:not([disabled]), button[data-isfocused="true"],textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])';
|
|
37
|
+
const useTrackFocusableElements = ({
|
|
38
|
+
containerRef,
|
|
39
|
+
selector = DEFAULT_FOCUSABLE_SELECTOR,
|
|
40
|
+
dependencies = []
|
|
41
|
+
}) => {
|
|
42
|
+
const firstElementRef = (0, import_react.useRef)(null);
|
|
43
|
+
const lastElementRef = (0, import_react.useRef)(null);
|
|
44
|
+
(0, import_react.useEffect)(() => {
|
|
45
|
+
const container = containerRef && "current" in containerRef ? containerRef.current : containerRef;
|
|
46
|
+
if (!container) return;
|
|
47
|
+
const elements = container.querySelectorAll(selector);
|
|
48
|
+
if (elements.length > 0) {
|
|
49
|
+
const [firstElement] = elements;
|
|
50
|
+
firstElementRef.current = firstElement;
|
|
51
|
+
lastElementRef.current = elements[elements.length - 1];
|
|
52
|
+
} else {
|
|
53
|
+
firstElementRef.current = null;
|
|
54
|
+
lastElementRef.current = null;
|
|
55
|
+
}
|
|
56
|
+
}, [containerRef, selector, ...dependencies]);
|
|
57
|
+
return { firstElementRef, lastElementRef };
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=useTrackFocusableElements.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/exported-related/FilterPopover/useTrackFocusableElements.ts", "../../../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
+
"sourcesContent": ["import { useEffect, useRef } from 'react';\n\n/**\n * Tracks the first and last focusable elements in a container.\n * Used by the FilterPopover focus trap.\n */\ninterface UseTrackFocusableElementsConfig {\n /** Container element (HTMLElement or RefObject) */\n containerRef: React.RefObject<HTMLElement> | HTMLElement | null;\n\n /** CSS selector for focusable elements */\n selector?: string;\n\n /** Dependencies to trigger re-scan */\n dependencies?: React.DependencyList;\n}\n\nconst DEFAULT_FOCUSABLE_SELECTOR =\n 'button:not([disabled]):not([tabindex=\"-1\"]), ' +\n '[href], ' +\n 'input:not([disabled]):not([tabindex=\"-1\"]), ' +\n 'select:not([disabled]), ' +\n 'button[data-isfocused=\"true\"],' +\n 'textarea:not([disabled]), ' +\n '[tabindex]:not([tabindex=\"-1\"]):not([disabled])';\n\nexport const useTrackFocusableElements = ({\n containerRef,\n selector = DEFAULT_FOCUSABLE_SELECTOR,\n dependencies = [],\n}: UseTrackFocusableElementsConfig) => {\n const firstElementRef = useRef<HTMLElement | null>(null);\n const lastElementRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n const container = containerRef && 'current' in containerRef ? containerRef.current : containerRef;\n\n if (!container) return;\n\n const elements = container.querySelectorAll<HTMLElement>(selector);\n\n if (elements.length > 0) {\n const [firstElement] = elements;\n firstElementRef.current = firstElement;\n lastElementRef.current = elements[elements.length - 1];\n } else {\n firstElementRef.current = null;\n lastElementRef.current = null;\n }\n // Spread operator is intentionally used to allow consumers to pass dynamic dependencies.\n // ESLint cannot statically verify spread elements, but this pattern enables flexible hook composition.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [containerRef, selector, ...dependencies]);\n\n return { firstElementRef, lastElementRef };\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAkC;AAiBlC,MAAM,6BACJ;AAQK,MAAM,4BAA4B,CAAC;AAAA,EACxC;AAAA,EACA,WAAW;AAAA,EACX,eAAe,CAAC;AAClB,MAAuC;AACrC,QAAM,sBAAkB,qBAA2B,IAAI;AACvD,QAAM,qBAAiB,qBAA2B,IAAI;AAEtD,8BAAU,MAAM;AACd,UAAM,YAAY,gBAAgB,aAAa,eAAe,aAAa,UAAU;AAErF,QAAI,CAAC,UAAW;AAEhB,UAAM,WAAW,UAAU,iBAA8B,QAAQ;AAEjE,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,CAAC,YAAY,IAAI;AACvB,sBAAgB,UAAU;AAC1B,qBAAe,UAAU,SAAS,SAAS,SAAS,CAAC;AAAA,IACvD,OAAO;AACL,sBAAgB,UAAU;AAC1B,qBAAe,UAAU;AAAA,IAC3B;AAAA,EAIF,GAAG,CAAC,cAAc,UAAU,GAAG,YAAY,CAAC;AAE5C,SAAO,EAAE,iBAAiB,eAAe;AAC3C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { DSButtonV2 } from "@elliemae/ds-button-v2";
|
|
4
|
+
import { useFocusTrap } from "@elliemae/ds-hooks-focus-trap";
|
|
4
5
|
import { DSPopperJS } from "@elliemae/ds-popperjs";
|
|
5
6
|
import { mergeRefs, styled } from "@elliemae/ds-system";
|
|
6
7
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
|
-
import { DSDataTableName, DSDataTableSlots } from "../../constants/index.js";
|
|
8
8
|
import { DATA_TESTID } from "../../configs/constants.js";
|
|
9
|
-
import {
|
|
9
|
+
import { usePropsStore } from "../../configs/useStore/createInternalAndPropsContext.js";
|
|
10
|
+
import { DSDataTableName, DSDataTableSlots } from "../../constants/index.js";
|
|
10
11
|
import { useGetFilterHandlers } from "./useGetFilterHandlers.js";
|
|
11
12
|
import { useGetFilterVisibility } from "./useGetFilterVisibility.js";
|
|
13
|
+
import { useTrackFocusableElements } from "./useTrackFocusableElements.js";
|
|
12
14
|
const FilterButton = styled("span", { name: DSDataTableName, slot: DSDataTableSlots.FILTER_POPOVER_BUTTON })`
|
|
13
15
|
display: inline-grid;
|
|
14
16
|
${(props) => props.hide ? "opacity: 0; display: none; width: 0;" : ""}
|
|
@@ -20,28 +22,8 @@ const StyledPoppoverJS = styled(DSPopperJS, {
|
|
|
20
22
|
name: DSDataTableName,
|
|
21
23
|
slot: DSDataTableSlots.FILTER_POPOVER
|
|
22
24
|
})``;
|
|
23
|
-
const ButtonTrap = ({ cb }) => /* @__PURE__ */ jsx(
|
|
24
|
-
"span",
|
|
25
|
-
{
|
|
26
|
-
tabIndex: 0,
|
|
27
|
-
onFocus: (e) => {
|
|
28
|
-
e.stopPropagation();
|
|
29
|
-
cb();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
);
|
|
33
25
|
const FilterPopover = (props) => {
|
|
34
|
-
const {
|
|
35
|
-
column,
|
|
36
|
-
customStyles,
|
|
37
|
-
reduxHeader,
|
|
38
|
-
menuContent,
|
|
39
|
-
columnId,
|
|
40
|
-
ariaLabel,
|
|
41
|
-
triggerIcon,
|
|
42
|
-
innerRef,
|
|
43
|
-
columnReference
|
|
44
|
-
} = props;
|
|
26
|
+
const { column, customStyles, reduxHeader, menuContent, ariaLabel, triggerIcon, innerRef, columnReference } = props;
|
|
45
27
|
const filters = usePropsStore((state) => state.filters);
|
|
46
28
|
const getOwnerProps = usePropsStore((store) => store.get);
|
|
47
29
|
const getOwnerPropsArguments = useCallback(
|
|
@@ -50,18 +32,40 @@ const FilterPopover = (props) => {
|
|
|
50
32
|
}),
|
|
51
33
|
[column.id]
|
|
52
34
|
);
|
|
53
|
-
const patchHeader = useInternalStore((state) => state.patchHeader);
|
|
54
35
|
const { isIconVisible, isMenuOpen } = useGetFilterVisibility(reduxHeader);
|
|
55
36
|
const [buttonReference, setButtonReference] = useState(null);
|
|
56
37
|
const [isButtonFocused, setIsButtonFocused] = useState(false);
|
|
57
38
|
const { handleTriggerClick, handleClickOutsideMenu, handleMenuOnKeyDown, handleTriggerOnFocus, handleTriggerOnBlur } = useGetFilterHandlers(props, isMenuOpen, buttonReference, setIsButtonFocused);
|
|
58
|
-
const buttonTrapCallback = useCallback(() => {
|
|
59
|
-
patchHeader(columnId, { hideFilterMenu: true, hideFilterButton: false });
|
|
60
|
-
buttonReference?.focus();
|
|
61
|
-
}, [columnId, patchHeader, buttonReference]);
|
|
62
39
|
const actionRef = useRef({
|
|
63
40
|
update: null
|
|
64
41
|
});
|
|
42
|
+
const popoverContentRef = useRef(null);
|
|
43
|
+
const { firstElementRef, lastElementRef } = useTrackFocusableElements({
|
|
44
|
+
containerRef: popoverContentRef,
|
|
45
|
+
dependencies: [isMenuOpen, menuContent]
|
|
46
|
+
});
|
|
47
|
+
const focusedDayRef = useRef(null);
|
|
48
|
+
const handleOnKeyDown = useFocusTrap({
|
|
49
|
+
firstElementRef,
|
|
50
|
+
lastElementRef,
|
|
51
|
+
onKeyDown: (e) => {
|
|
52
|
+
const target = e.target;
|
|
53
|
+
const dayWrapper = target.closest(".focusedDay");
|
|
54
|
+
if (dayWrapper) {
|
|
55
|
+
if (focusedDayRef.current && focusedDayRef.current !== dayWrapper) {
|
|
56
|
+
focusedDayRef.current.classList.remove("focusedDay");
|
|
57
|
+
}
|
|
58
|
+
focusedDayRef.current = dayWrapper;
|
|
59
|
+
lastElementRef.current = target;
|
|
60
|
+
}
|
|
61
|
+
if (e.key === "Tab" && focusedDayRef.current) {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
focusedDayRef.current?.classList.remove("focusedDay");
|
|
64
|
+
focusedDayRef.current = null;
|
|
65
|
+
}, 0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
65
69
|
useEffect(() => {
|
|
66
70
|
void actionRef.current.update?.();
|
|
67
71
|
}, [filters]);
|
|
@@ -114,11 +118,9 @@ const FilterPopover = (props) => {
|
|
|
114
118
|
placementOrderPreference: ["bottom-end", "top-end"],
|
|
115
119
|
getOwnerProps,
|
|
116
120
|
getOwnerPropsArguments,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
/* @__PURE__ */ jsx(ButtonTrap, { cb: buttonTrapCallback })
|
|
121
|
-
] })
|
|
121
|
+
role: "dialog",
|
|
122
|
+
"aria-label": `Filter by ${column.Header}`,
|
|
123
|
+
children: /* @__PURE__ */ jsx(PopperContent, { getOwnerProps, innerRef: popoverContentRef, onKeyDown: handleOnKeyDown, children: menuContent })
|
|
122
124
|
}
|
|
123
125
|
)
|
|
124
126
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../scripts/build/transpile/react-shim.js", "../../../../src/exported-related/FilterPopover/index.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable import/no-cycle */\n/* eslint-disable no-void */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport { DSButtonV2 } from '@elliemae/ds-button-v2';\nimport type { DSPopperJST } from '@elliemae/ds-popperjs';\nimport { DSPopperJS } from '@elliemae/ds-popperjs';\nimport { mergeRefs, styled } from '@elliemae/ds-system';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport {
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable import/no-cycle */\n/* eslint-disable no-void */\n/* eslint-disable jsx-a11y/no-static-element-interactions */\nimport { DSButtonV2 } from '@elliemae/ds-button-v2';\nimport { useFocusTrap } from '@elliemae/ds-hooks-focus-trap';\nimport type { DSPopperJST } from '@elliemae/ds-popperjs';\nimport { DSPopperJS } from '@elliemae/ds-popperjs';\nimport { mergeRefs, styled } from '@elliemae/ds-system';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { DATA_TESTID } from '../../configs/constants.js';\nimport { usePropsStore } from '../../configs/useStore/createInternalAndPropsContext.js';\nimport { DSDataTableName, DSDataTableSlots } from '../../constants/index.js';\nimport type { FilterPopoverProps } from './types.js';\nimport { useGetFilterHandlers } from './useGetFilterHandlers.js';\nimport { useGetFilterVisibility } from './useGetFilterVisibility.js';\nimport { useTrackFocusableElements } from './useTrackFocusableElements.js';\n\nconst FilterButton = styled('span', { name: DSDataTableName, slot: DSDataTableSlots.FILTER_POPOVER_BUTTON })<{\n hide: boolean;\n}>`\n display: inline-grid;\n ${(props) => (props.hide ? 'opacity: 0; display: none; width: 0;' : '')}\n`;\n\nconst PopperContent = styled('div', { name: DSDataTableName, slot: DSDataTableSlots.FILTER_POPOVER_CONTENT })`\n background-color: #fff;\n`;\n\nconst StyledPoppoverJS = styled(DSPopperJS, {\n name: DSDataTableName,\n slot: DSDataTableSlots.FILTER_POPOVER,\n})``;\n\nexport const FilterPopover: React.ComponentType<FilterPopoverProps> = (props: FilterPopoverProps) => {\n const { column, customStyles, reduxHeader, menuContent, ariaLabel, triggerIcon, innerRef, columnReference } = props;\n\n const filters = usePropsStore((state) => state.filters);\n const getOwnerProps = usePropsStore((store) => store.get);\n\n const getOwnerPropsArguments = useCallback(\n () => ({\n columnId: column.id,\n }),\n [column.id],\n );\n\n const { isIconVisible, isMenuOpen } = useGetFilterVisibility(reduxHeader);\n\n const [buttonReference, setButtonReference] = useState<HTMLButtonElement | null>(null);\n\n const [isButtonFocused, setIsButtonFocused] = useState(false);\n\n const { handleTriggerClick, handleClickOutsideMenu, handleMenuOnKeyDown, handleTriggerOnFocus, handleTriggerOnBlur } =\n useGetFilterHandlers(props, isMenuOpen, buttonReference, setIsButtonFocused);\n\n const actionRef: Required<DSPopperJST.Props>['actionRef'] = useRef({\n update: null,\n });\n const popoverContentRef = useRef<HTMLDivElement>(null);\n const { firstElementRef, lastElementRef } = useTrackFocusableElements({\n containerRef: popoverContentRef,\n dependencies: [isMenuOpen, menuContent],\n });\n\n const focusedDayRef = useRef<HTMLElement | null>(null);\n const handleOnKeyDown = useFocusTrap({\n firstElementRef,\n lastElementRef,\n onKeyDown: (e) => {\n const target = e.target as HTMLElement;\n // Special handling for DateRangePicker calendar: days use a roving tabindex pattern.\n // Track the focused day to update the lastElementRef and clear visual focus on Tab loop.\n const dayWrapper = target.closest('.focusedDay') as HTMLElement;\n if (dayWrapper) {\n // Remove visual focus from the previously focused day\n if (focusedDayRef.current && focusedDayRef.current !== dayWrapper) {\n focusedDayRef.current.classList.remove('focusedDay');\n }\n\n focusedDayRef.current = dayWrapper;\n // Update trap boundary to the focused day's button\n lastElementRef.current = target;\n }\n // Clear visual focus when Tab navigates away from the calendar\n if (e.key === 'Tab' && focusedDayRef.current) {\n setTimeout(() => {\n focusedDayRef.current?.classList.remove('focusedDay');\n focusedDayRef.current = null;\n }, 0);\n }\n },\n });\n\n useEffect(() => {\n // When the filters change, we need to update the popper position,\n // because the filter bar might push the datatable up or down, causing the popper to be misaligned\n void actionRef.current.update?.();\n }, [filters]);\n\n return (\n <div\n // This is here to prevent propagation, and not trigger the sort functionality\n onClick={(e) => e.stopPropagation()}\n onKeyDown={handleMenuOnKeyDown}\n >\n <FilterButton\n hide={!isIconVisible}\n data-testid={DATA_TESTID.DATA_TABLE_FILTER_BUTTON}\n getOwnerProps={getOwnerProps}\n >\n <DSButtonV2\n buttonType=\"icon\"\n size=\"s\"\n onClick={handleTriggerClick}\n onFocus={handleTriggerOnFocus}\n onBlur={handleTriggerOnBlur}\n innerRef={mergeRefs(isIconVisible && setButtonReference, innerRef)}\n tabIndex={reduxHeader?.withTabStops ? 0 : -1}\n aria-label={ariaLabel}\n aria-hidden={!isButtonFocused}\n data-testid={DATA_TESTID.DATA_TABLE_FILTER_BUTTON_ELEMENT}\n >\n {triggerIcon}\n </DSButtonV2>\n </FilterButton>\n {(columnReference || buttonReference) && (\n <StyledPoppoverJS\n actionRef={actionRef}\n referenceElement={columnReference || buttonReference}\n showPopover={isMenuOpen}\n closeContextMenu={handleClickOutsideMenu}\n data-testid={DATA_TESTID.DATA_TABLE_FILTER_MENU_CONTENT}\n startPlacementPreference=\"bottom-end\"\n customOffset={columnReference ? [0, 1] : [5, 4]}\n withoutArrow\n withoutAnimation\n extraPopperStyles={{\n ...customStyles,\n minWidth: column.ref?.current?.offsetWidth ?? '0px',\n }}\n placementOrderPreference={['bottom-end', 'top-end']}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n role=\"dialog\"\n aria-label={`Filter by ${column.Header as string}`}\n >\n <PopperContent getOwnerProps={getOwnerProps} innerRef={popoverContentRef} onKeyDown={handleOnKeyDown}>\n {menuContent}\n </PopperContent>\n </StyledPoppoverJS>\n )}\n </div>\n );\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
const DEFAULT_FOCUSABLE_SELECTOR = 'button:not([disabled]):not([tabindex="-1"]), [href], input:not([disabled]):not([tabindex="-1"]), select:not([disabled]), button[data-isfocused="true"],textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])';
|
|
4
|
+
const useTrackFocusableElements = ({
|
|
5
|
+
containerRef,
|
|
6
|
+
selector = DEFAULT_FOCUSABLE_SELECTOR,
|
|
7
|
+
dependencies = []
|
|
8
|
+
}) => {
|
|
9
|
+
const firstElementRef = useRef(null);
|
|
10
|
+
const lastElementRef = useRef(null);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const container = containerRef && "current" in containerRef ? containerRef.current : containerRef;
|
|
13
|
+
if (!container) return;
|
|
14
|
+
const elements = container.querySelectorAll(selector);
|
|
15
|
+
if (elements.length > 0) {
|
|
16
|
+
const [firstElement] = elements;
|
|
17
|
+
firstElementRef.current = firstElement;
|
|
18
|
+
lastElementRef.current = elements[elements.length - 1];
|
|
19
|
+
} else {
|
|
20
|
+
firstElementRef.current = null;
|
|
21
|
+
lastElementRef.current = null;
|
|
22
|
+
}
|
|
23
|
+
}, [containerRef, selector, ...dependencies]);
|
|
24
|
+
return { firstElementRef, lastElementRef };
|
|
25
|
+
};
|
|
26
|
+
export {
|
|
27
|
+
useTrackFocusableElements
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=useTrackFocusableElements.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../scripts/build/transpile/react-shim.js", "../../../../src/exported-related/FilterPopover/useTrackFocusableElements.ts"],
|
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { useEffect, useRef } from 'react';\n\n/**\n * Tracks the first and last focusable elements in a container.\n * Used by the FilterPopover focus trap.\n */\ninterface UseTrackFocusableElementsConfig {\n /** Container element (HTMLElement or RefObject) */\n containerRef: React.RefObject<HTMLElement> | HTMLElement | null;\n\n /** CSS selector for focusable elements */\n selector?: string;\n\n /** Dependencies to trigger re-scan */\n dependencies?: React.DependencyList;\n}\n\nconst DEFAULT_FOCUSABLE_SELECTOR =\n 'button:not([disabled]):not([tabindex=\"-1\"]), ' +\n '[href], ' +\n 'input:not([disabled]):not([tabindex=\"-1\"]), ' +\n 'select:not([disabled]), ' +\n 'button[data-isfocused=\"true\"],' +\n 'textarea:not([disabled]), ' +\n '[tabindex]:not([tabindex=\"-1\"]):not([disabled])';\n\nexport const useTrackFocusableElements = ({\n containerRef,\n selector = DEFAULT_FOCUSABLE_SELECTOR,\n dependencies = [],\n}: UseTrackFocusableElementsConfig) => {\n const firstElementRef = useRef<HTMLElement | null>(null);\n const lastElementRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n const container = containerRef && 'current' in containerRef ? containerRef.current : containerRef;\n\n if (!container) return;\n\n const elements = container.querySelectorAll<HTMLElement>(selector);\n\n if (elements.length > 0) {\n const [firstElement] = elements;\n firstElementRef.current = firstElement;\n lastElementRef.current = elements[elements.length - 1];\n } else {\n firstElementRef.current = null;\n lastElementRef.current = null;\n }\n // Spread operator is intentionally used to allow consumers to pass dynamic dependencies.\n // ESLint cannot statically verify spread elements, but this pattern enables flexible hook composition.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [containerRef, selector, ...dependencies]);\n\n return { firstElementRef, lastElementRef };\n};\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,WAAW,cAAc;AAiBlC,MAAM,6BACJ;AAQK,MAAM,4BAA4B,CAAC;AAAA,EACxC;AAAA,EACA,WAAW;AAAA,EACX,eAAe,CAAC;AAClB,MAAuC;AACrC,QAAM,kBAAkB,OAA2B,IAAI;AACvD,QAAM,iBAAiB,OAA2B,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,YAAY,gBAAgB,aAAa,eAAe,aAAa,UAAU;AAErF,QAAI,CAAC,UAAW;AAEhB,UAAM,WAAW,UAAU,iBAA8B,QAAQ;AAEjE,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,CAAC,YAAY,IAAI;AACvB,sBAAgB,UAAU;AAC1B,qBAAe,UAAU,SAAS,SAAS,SAAS,CAAC;AAAA,IACvD,OAAO;AACL,sBAAgB,UAAU;AAC1B,qBAAe,UAAU;AAAA,IAC3B;AAAA,EAIF,GAAG,CAAC,cAAc,UAAU,GAAG,YAAY,CAAC;AAE5C,SAAO,EAAE,iBAAiB,eAAe;AAC3C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the first and last focusable elements in a container.
|
|
3
|
+
* Used by the FilterPopover focus trap.
|
|
4
|
+
*/
|
|
5
|
+
interface UseTrackFocusableElementsConfig {
|
|
6
|
+
/** Container element (HTMLElement or RefObject) */
|
|
7
|
+
containerRef: React.RefObject<HTMLElement> | HTMLElement | null;
|
|
8
|
+
/** CSS selector for focusable elements */
|
|
9
|
+
selector?: string;
|
|
10
|
+
/** Dependencies to trigger re-scan */
|
|
11
|
+
dependencies?: React.DependencyList;
|
|
12
|
+
}
|
|
13
|
+
export declare const useTrackFocusableElements: ({ containerRef, selector, dependencies, }: UseTrackFocusableElementsConfig) => {
|
|
14
|
+
firstElementRef: import("react").MutableRefObject<HTMLElement | null>;
|
|
15
|
+
lastElementRef: import("react").MutableRefObject<HTMLElement | null>;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elliemae/ds-data-table",
|
|
3
|
-
"version": "3.60.0-next.
|
|
3
|
+
"version": "3.60.0-next.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "ICE MT - Dimsum - Data Table",
|
|
6
6
|
"files": [
|
|
@@ -40,38 +40,38 @@
|
|
|
40
40
|
"react-virtual": "~2.10.4",
|
|
41
41
|
"uid": "^2.0.2",
|
|
42
42
|
"use-onclickoutside": "0.4.1",
|
|
43
|
-
"@elliemae/ds-button-v2": "3.60.0-next.
|
|
44
|
-
"@elliemae/ds-
|
|
45
|
-
"@elliemae/ds-
|
|
46
|
-
"@elliemae/ds-drag-and-drop": "3.60.0-next.
|
|
47
|
-
"@elliemae/ds-form-
|
|
48
|
-
"@elliemae/ds-
|
|
49
|
-
"@elliemae/ds-form-
|
|
50
|
-
"@elliemae/ds-form-
|
|
51
|
-
"@elliemae/ds-form-
|
|
52
|
-
"@elliemae/ds-form-
|
|
53
|
-
"@elliemae/ds-form-
|
|
54
|
-
"@elliemae/ds-
|
|
55
|
-
"@elliemae/ds-
|
|
56
|
-
"@elliemae/ds-
|
|
57
|
-
"@elliemae/ds-
|
|
58
|
-
"@elliemae/ds-
|
|
59
|
-
"@elliemae/ds-
|
|
60
|
-
"@elliemae/ds-
|
|
61
|
-
"@elliemae/ds-
|
|
62
|
-
"@elliemae/ds-
|
|
63
|
-
"@elliemae/ds-system": "3.60.0-next.
|
|
64
|
-
"@elliemae/ds-
|
|
65
|
-
"@elliemae/ds-
|
|
66
|
-
"@elliemae/ds-
|
|
43
|
+
"@elliemae/ds-button-v2": "3.60.0-next.20",
|
|
44
|
+
"@elliemae/ds-dropdownmenu-v2": "3.60.0-next.20",
|
|
45
|
+
"@elliemae/ds-form-combobox": "3.60.0-next.20",
|
|
46
|
+
"@elliemae/ds-drag-and-drop": "3.60.0-next.20",
|
|
47
|
+
"@elliemae/ds-form-date-time-picker": "3.60.0-next.20",
|
|
48
|
+
"@elliemae/ds-circular-progress-indicator": "3.60.0-next.20",
|
|
49
|
+
"@elliemae/ds-form-checkbox": "3.60.0-next.20",
|
|
50
|
+
"@elliemae/ds-form-radio": "3.60.0-next.20",
|
|
51
|
+
"@elliemae/ds-form-input-text": "3.60.0-next.20",
|
|
52
|
+
"@elliemae/ds-form-helpers-mask-hooks": "3.60.0-next.20",
|
|
53
|
+
"@elliemae/ds-form-layout-blocks": "3.60.0-next.20",
|
|
54
|
+
"@elliemae/ds-menu-button": "3.60.0-next.20",
|
|
55
|
+
"@elliemae/ds-form-date-range-picker": "3.60.0-next.20",
|
|
56
|
+
"@elliemae/ds-grid": "3.60.0-next.20",
|
|
57
|
+
"@elliemae/ds-pills-v2": "3.60.0-next.20",
|
|
58
|
+
"@elliemae/ds-icons": "3.60.0-next.20",
|
|
59
|
+
"@elliemae/ds-pagination": "3.60.0-next.20",
|
|
60
|
+
"@elliemae/ds-props-helpers": "3.60.0-next.20",
|
|
61
|
+
"@elliemae/ds-typescript-helpers": "3.60.0-next.20",
|
|
62
|
+
"@elliemae/ds-popperjs": "3.60.0-next.20",
|
|
63
|
+
"@elliemae/ds-system": "3.60.0-next.20",
|
|
64
|
+
"@elliemae/ds-zustand-helpers": "3.60.0-next.20",
|
|
65
|
+
"@elliemae/ds-skeleton": "3.60.0-next.20",
|
|
66
|
+
"@elliemae/ds-truncated-tooltip-text": "3.60.0-next.20"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"jest": "^30.0.0",
|
|
70
70
|
"styled-components": "~5.3.9",
|
|
71
71
|
"styled-system": "^5.1.5",
|
|
72
|
-
"@elliemae/ds-monorepo-devops": "3.60.0-next.
|
|
73
|
-
"@elliemae/ds-test-utils": "3.60.0-next.
|
|
74
|
-
"@elliemae/ds-toolbar-v2": "3.60.0-next.
|
|
72
|
+
"@elliemae/ds-monorepo-devops": "3.60.0-next.20",
|
|
73
|
+
"@elliemae/ds-test-utils": "3.60.0-next.20",
|
|
74
|
+
"@elliemae/ds-toolbar-v2": "3.60.0-next.20"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"lodash-es": "^4.17.21",
|