@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.
@@ -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/index.js");
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
- const FilterButton = (0, import_ds_system.styled)("span", { name: import_constants.DSDataTableName, slot: import_constants.DSDataTableSlots.FILTER_POPOVER_BUTTON })`
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: import_constants.DSDataTableName, slot: import_constants.DSDataTableSlots.FILTER_POPOVER_CONTENT })`
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: import_constants.DSDataTableName,
54
- slot: import_constants.DSDataTableSlots.FILTER_POPOVER
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": import_constants2.DATA_TESTID.DATA_TABLE_FILTER_BUTTON,
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": import_constants2.DATA_TESTID.DATA_TABLE_FILTER_BUTTON_ELEMENT,
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": import_constants2.DATA_TESTID.DATA_TABLE_FILTER_MENU_CONTENT,
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
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PopperContent, { getOwnerProps, children: [
151
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ButtonTrap, { cb: buttonTrapCallback }),
152
- menuContent,
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 { DSDataTableName, DSDataTableSlots } from '../../constants/index.js';\nimport { DATA_TESTID } from '../../configs/constants.js';\nimport { useInternalStore, usePropsStore } from '../../configs/useStore/createInternalAndPropsContext.js';\nimport type { FilterPopoverProps } from './types.js';\nimport { useGetFilterHandlers } from './useGetFilterHandlers.js';\nimport { useGetFilterVisibility } from './useGetFilterVisibility.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\nconst ButtonTrap = ({ cb }: { cb: () => void }) => (\n <span\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n onFocus={(e: React.FocusEvent) => {\n e.stopPropagation();\n cb();\n }}\n />\n);\n\nexport const FilterPopover: React.ComponentType<FilterPopoverProps> = (props: FilterPopoverProps) => {\n const {\n column,\n customStyles,\n reduxHeader,\n menuContent,\n columnId,\n ariaLabel,\n triggerIcon,\n innerRef,\n columnReference,\n } = 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 patchHeader = useInternalStore((state) => state.patchHeader);\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 buttonTrapCallback = useCallback(() => {\n patchHeader(columnId, { hideFilterMenu: true, hideFilterButton: false });\n buttonReference?.focus();\n }, [columnId, patchHeader, buttonReference]);\n\n const actionRef: Required<DSPopperJST.Props>['actionRef'] = useRef({\n update: null,\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 >\n <PopperContent getOwnerProps={getOwnerProps}>\n <ButtonTrap cb={buttonTrapCallback} />\n {menuContent}\n <ButtonTrap cb={buttonTrapCallback} />\n </PopperContent>\n </StyledPoppoverJS>\n )}\n </div>\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": "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",
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 { useInternalStore, usePropsStore } from "../../configs/useStore/createInternalAndPropsContext.js";
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
- children: /* @__PURE__ */ jsxs(PopperContent, { getOwnerProps, children: [
118
- /* @__PURE__ */ jsx(ButtonTrap, { cb: buttonTrapCallback }),
119
- menuContent,
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 { DSDataTableName, DSDataTableSlots } from '../../constants/index.js';\nimport { DATA_TESTID } from '../../configs/constants.js';\nimport { useInternalStore, usePropsStore } from '../../configs/useStore/createInternalAndPropsContext.js';\nimport type { FilterPopoverProps } from './types.js';\nimport { useGetFilterHandlers } from './useGetFilterHandlers.js';\nimport { useGetFilterVisibility } from './useGetFilterVisibility.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\nconst ButtonTrap = ({ cb }: { cb: () => void }) => (\n <span\n // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex\n tabIndex={0}\n onFocus={(e: React.FocusEvent) => {\n e.stopPropagation();\n cb();\n }}\n />\n);\n\nexport const FilterPopover: React.ComponentType<FilterPopoverProps> = (props: FilterPopoverProps) => {\n const {\n column,\n customStyles,\n reduxHeader,\n menuContent,\n columnId,\n ariaLabel,\n triggerIcon,\n innerRef,\n columnReference,\n } = 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 patchHeader = useInternalStore((state) => state.patchHeader);\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 buttonTrapCallback = useCallback(() => {\n patchHeader(columnId, { hideFilterMenu: true, hideFilterButton: false });\n buttonReference?.focus();\n }, [columnId, patchHeader, buttonReference]);\n\n const actionRef: Required<DSPopperJST.Props>['actionRef'] = useRef({\n update: null,\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 >\n <PopperContent getOwnerProps={getOwnerProps}>\n <ButtonTrap cb={buttonTrapCallback} />\n {menuContent}\n <ButtonTrap cb={buttonTrapCallback} />\n </PopperContent>\n </StyledPoppoverJS>\n )}\n </div>\n );\n};\n"],
5
- "mappings": "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",
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 {};
@@ -0,0 +1,2 @@
1
+ export const ControlledFilterTestRenderer: React.FunctionComponent<React.JSX.IntrinsicAttributes>;
2
+ import React from 'react';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-data-table",
3
- "version": "3.60.0-next.19",
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.19",
44
- "@elliemae/ds-circular-progress-indicator": "3.60.0-next.19",
45
- "@elliemae/ds-dropdownmenu-v2": "3.60.0-next.19",
46
- "@elliemae/ds-drag-and-drop": "3.60.0-next.19",
47
- "@elliemae/ds-form-checkbox": "3.60.0-next.19",
48
- "@elliemae/ds-form-combobox": "3.60.0-next.19",
49
- "@elliemae/ds-form-date-range-picker": "3.60.0-next.19",
50
- "@elliemae/ds-form-helpers-mask-hooks": "3.60.0-next.19",
51
- "@elliemae/ds-form-radio": "3.60.0-next.19",
52
- "@elliemae/ds-form-layout-blocks": "3.60.0-next.19",
53
- "@elliemae/ds-form-input-text": "3.60.0-next.19",
54
- "@elliemae/ds-form-date-time-picker": "3.60.0-next.19",
55
- "@elliemae/ds-grid": "3.60.0-next.19",
56
- "@elliemae/ds-menu-button": "3.60.0-next.19",
57
- "@elliemae/ds-icons": "3.60.0-next.19",
58
- "@elliemae/ds-pills-v2": "3.60.0-next.19",
59
- "@elliemae/ds-popperjs": "3.60.0-next.19",
60
- "@elliemae/ds-pagination": "3.60.0-next.19",
61
- "@elliemae/ds-props-helpers": "3.60.0-next.19",
62
- "@elliemae/ds-skeleton": "3.60.0-next.19",
63
- "@elliemae/ds-system": "3.60.0-next.19",
64
- "@elliemae/ds-truncated-tooltip-text": "3.60.0-next.19",
65
- "@elliemae/ds-typescript-helpers": "3.60.0-next.19",
66
- "@elliemae/ds-zustand-helpers": "3.60.0-next.19"
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.19",
73
- "@elliemae/ds-test-utils": "3.60.0-next.19",
74
- "@elliemae/ds-toolbar-v2": "3.60.0-next.19"
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",