From 0b29ebdf63ca7539bb13e0227291915e51e2ae9b Mon Sep 17 00:00:00 2001 From: Austin Turner Date: Sat, 7 Dec 2024 16:30:01 -0700 Subject: [PATCH] Data Table Fixes shift+click on a row will select multiple rows Navigating with keyboard on checkbox column, space can now be used to select the checkbox and focus does not go crazy Triggering edit with the keyboard now works properly in all cases regardless of the field type Focus is now trapped in the edit popover and focus is returned when the modal closes The right-click context menu is now properly keyboard navigable resolves #1103 --- .eslintrc.json | 3 +- apps/jetstream/src/main.scss | 18 ++ .../src/utils/deploy-metadata.utils.tsx | 16 +- .../src/PlatformEventMonitorEvents.tsx | 3 +- libs/types/src/lib/ui/types.ts | 15 ++ libs/ui/src/index.ts | 2 +- libs/ui/src/lib/data-table/DataTable.tsx | 79 ++++--- .../src/lib/data-table/DataTableEditors.tsx | 12 +- .../src/lib/data-table/DataTableRenderers.tsx | 30 +-- .../data-table/DataTableSubqueryRenderer.tsx | 3 +- libs/ui/src/lib/data-table/DataTree.tsx | 77 ++++--- .../data-table/SalesforceRecordDataTable.tsx | 3 +- .../src/lib/data-table/data-table-utils.tsx | 16 +- libs/ui/src/lib/data-table/useDataTable.tsx | 96 ++++---- .../src/lib/form/context-menu/ContextMenu.tsx | 205 ++++++++++++++++++ libs/ui/src/lib/form/date/DateGrid.tsx | 14 ++ libs/ui/src/lib/popover/ContextMenu.tsx | 184 ---------------- libs/ui/src/lib/widgets/Tooltip.tsx | 5 +- package.json | 3 +- yarn.lock | 174 ++++++++++++++- 20 files changed, 593 insertions(+), 365 deletions(-) create mode 100644 libs/ui/src/lib/form/context-menu/ContextMenu.tsx delete mode 100644 libs/ui/src/lib/popover/ContextMenu.tsx diff --git a/.eslintrc.json b/.eslintrc.json index d01435157..25f7d5734 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,7 @@ { "root": true, "ignorePatterns": ["**/*"], - "plugins": ["@nx/eslint-plugin"], + "plugins": ["@nx/eslint-plugin", "eslint-plugin-react-compiler"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "prettier"], "rules": { "@typescript-eslint/explicit-member-accessibility": "off", @@ -43,6 +43,7 @@ { "files": ["*.tsx"], "rules": { + "react-compiler/react-compiler": "warn", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": "warn" } diff --git a/apps/jetstream/src/main.scss b/apps/jetstream/src/main.scss index 3cd5144d8..f554955db 100644 --- a/apps/jetstream/src/main.scss +++ b/apps/jetstream/src/main.scss @@ -1,5 +1,23 @@ @import '@salesforce-ux/design-system/scss/_design-tokens'; +@layer rdg.rdg-checkbox-input { + .rdg-checkbox-input { + inline-size: 14px; /* Override the width */ + block-size: 14px; /* Override the height */ + // background-color: #0176d3; /* Example: add a custom background */ + // accent-color: white; + } +} + +// TODO: play around with this as we can potentially figure out how to style the checkbox + +// .rdg .rdg-checkbox-input { +// // inline-size: 25px !important; +// // block-size: 25px !important; +// background-color: white !important; /* Ensure it overrides */ +// accent-color: #0176d3 !important; /* Ensure it overrides */ +// } + html { background-color: rgb(17, 24, 39); } diff --git a/libs/features/deploy/src/utils/deploy-metadata.utils.tsx b/libs/features/deploy/src/utils/deploy-metadata.utils.tsx index d06d9b438..76f8d9d02 100644 --- a/libs/features/deploy/src/utils/deploy-metadata.utils.tsx +++ b/libs/features/deploy/src/utils/deploy-metadata.utils.tsx @@ -20,16 +20,7 @@ import { SalesforceDeployHistoryType, SalesforceOrgUi, } from '@jetstream/types'; -import { - ColumnWithFilter, - Grid, - Icon, - SelectFormatter, - SelectHeaderGroupRenderer, - SelectHeaderRenderer, - setColumnFromType, - Spinner, -} from '@jetstream/ui'; +import { ColumnWithFilter, Grid, Icon, SelectFormatter, SelectHeaderGroupRenderer, setColumnFromType, Spinner } from '@jetstream/ui'; import { composeQuery, getField, Query } from '@jetstreamapp/soql-parser-js'; import { formatISO } from 'date-fns/formatISO'; import { parseISO } from 'date-fns/parseISO'; @@ -232,16 +223,15 @@ export function getColumnDefinitions(): ColumnWithFilter ); } - return ; + return SelectColumn.renderCell?.(args) || ; }, - renderHeaderCell: SelectHeaderRenderer, renderGroupCell: (args) => { const { childRows } = args; // Don't allow selection if child rows are loading if (childRows.length === 0 || (childRows.length === 1 && (childRows[0].loading || !childRows[0].metadata))) { return null; } - return ; + return SelectColumn.renderGroupCell?.(args) || ; }, colSpan: (args) => { if (args.type === 'ROW') { diff --git a/libs/features/platform-event-monitor/src/PlatformEventMonitorEvents.tsx b/libs/features/platform-event-monitor/src/PlatformEventMonitorEvents.tsx index ed70b8fa9..d0d4d6843 100644 --- a/libs/features/platform-event-monitor/src/PlatformEventMonitorEvents.tsx +++ b/libs/features/platform-event-monitor/src/PlatformEventMonitorEvents.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/react'; import { orderValues } from '@jetstream/shared/utils'; -import { AutoFullHeightContainer, ColumnWithFilter, ContextMenuActionData, ContextMenuItem, DataTree } from '@jetstream/ui'; +import { ContextMenuItem } from '@jetstream/types'; +import { AutoFullHeightContainer, ColumnWithFilter, ContextMenuActionData, DataTree } from '@jetstream/ui'; import copyToClipboard from 'copy-to-clipboard'; import groupBy from 'lodash/groupBy'; import { FunctionComponent, useCallback, useEffect, useState } from 'react'; diff --git a/libs/types/src/lib/ui/types.ts b/libs/types/src/lib/ui/types.ts index 2fcb337ba..9262987c8 100644 --- a/libs/types/src/lib/ui/types.ts +++ b/libs/types/src/lib/ui/types.ts @@ -380,6 +380,21 @@ export interface DropDownItem { metadata?: T; } +export interface ContextMenuItem { + subheader?: string; + label: string | ReactNode; + value: T; + icon?: { + type: string; + icon: string; + description?: string; + }; // FIXME: unable to import cross module boundaries + trailingDivider?: boolean; + disabled?: boolean; + title?: string; + metadata?: T; +} + export interface QueryFieldHeader { label: string; accessor: string; diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts index eac3af454..612ed8e46 100644 --- a/libs/ui/src/index.ts +++ b/libs/ui/src/index.ts @@ -40,6 +40,7 @@ export * from './lib/form/combobox/ComboboxWithItems'; export * from './lib/form/combobox/ComboboxWithItemsTypeAhead'; export * from './lib/form/combobox/ComboboxWithItemsVirtual'; export * from './lib/form/combobox/useFieldListItemsWithDrillIn'; +export * from './lib/form/context-menu/ContextMenu'; export * from './lib/form/controlled-inputs/ControlledInput'; export * from './lib/form/controlled-inputs/ControlledTextarea'; export * from './lib/form/date-time/DateTime'; @@ -113,7 +114,6 @@ export * from './lib/nav/Navbar'; export * from './lib/nav/NavbarAppLauncher'; export * from './lib/nav/NavbarItem'; export * from './lib/nav/NavbarMenuItems'; -export * from './lib/popover/ContextMenu'; export * from './lib/popover/Popover'; export * from './lib/popover/PopoverErrorButton'; export * from './lib/progress-indicator/ProgressIndicator'; diff --git a/libs/ui/src/lib/data-table/DataTable.tsx b/libs/ui/src/lib/data-table/DataTable.tsx index b978b1b10..7a1797339 100644 --- a/libs/ui/src/lib/data-table/DataTable.tsx +++ b/libs/ui/src/lib/data-table/DataTable.tsx @@ -1,8 +1,8 @@ -import { SalesforceOrgUi } from '@jetstream/types'; +import { ContextMenuItem, SalesforceOrgUi } from '@jetstream/types'; import { forwardRef } from 'react'; import DataGrid, { DataGridProps, SortColumn } from 'react-data-grid'; import 'react-data-grid/lib/styles.css'; -import { ContextMenuContext, ContextMenuItem } from '../popover/ContextMenu'; +import { ContextMenu } from '../form/context-menu/ContextMenu'; import { DataTableFilterContext, DataTableGenericContext } from './data-table-context'; import './data-table-styles.scss'; import { ColumnWithFilter, ContextMenuActionData, RowWithKey } from './data-table-types'; @@ -76,10 +76,13 @@ export const DataTable = forwardRef>( reorderedColumns, filterSetValues, filteredRows, + contextMenuProps, setSortColumns, updateFilter, handleReorderColumns, handleCellKeydown, + handleCellContextMenu, + handleCloseContextMenu, } = useDataTable({ data, columns: _columns, @@ -100,33 +103,53 @@ export const DataTable = forwardRef>( }); return ( - - - - + + + {contextMenuProps && contextMenuItems && contextMenuAction && ( + { + contextMenuAction(item, { + row: filteredRows[contextMenuProps.rowIdx] as T, + rowIdx: contextMenuProps.rowIdx, + rows: filteredRows as T[], + column: columns[contextMenuProps.rowIdx], + columns, + }); + handleCloseContextMenu(); + }} + onClose={handleCloseContextMenu} /> - - - + )} + + ); } ); diff --git a/libs/ui/src/lib/data-table/DataTableEditors.tsx b/libs/ui/src/lib/data-table/DataTableEditors.tsx index d406a18ec..250417d53 100644 --- a/libs/ui/src/lib/data-table/DataTableEditors.tsx +++ b/libs/ui/src/lib/data-table/DataTableEditors.tsx @@ -1,3 +1,4 @@ +import { FocusTrap } from '@headlessui/react'; import { logger } from '@jetstream/shared/client-logger'; import { SFDC_BLANK_PICKLIST_VALUE } from '@jetstream/shared/constants'; import { describeSObject, query } from '@jetstream/shared/data'; @@ -59,7 +60,7 @@ function DataTableEditorPopover({ ref={popoverRef} isOpen referenceElement={referenceElement as any} - className={`slds-popover slds-popover slds-popover_edit`} + className="slds-popover slds-popover slds-popover_edit" role="dialog" offset={[0, -28.5]} usePortal @@ -69,7 +70,11 @@ function DataTableEditorPopover({ } }} > - {referenceElement &&
{children}
} + {referenceElement && ( + +
{children}
+
+ )} ); @@ -247,6 +252,7 @@ export function DataTableEditorDate( className="d-block" initialSelectedDate={currDate} openOnInit + inputProps={{ autoFocus: true }} onChange={(value) => { /** setTimeout is used to avoid a React error about flushSync being called during a render */ setTimeout(() => { @@ -328,7 +334,7 @@ export const dataTableEditorRecordLookup = ({ sobject }: { sobject: string }) => ); if (!org || !sobject) { - return ; + return ; } return ( diff --git a/libs/ui/src/lib/data-table/DataTableRenderers.tsx b/libs/ui/src/lib/data-table/DataTableRenderers.tsx index 50d3884ab..f5667d850 100644 --- a/libs/ui/src/lib/data-table/DataTableRenderers.tsx +++ b/libs/ui/src/lib/data-table/DataTableRenderers.tsx @@ -56,29 +56,9 @@ export function configIdLinkRenderer(serverUrl: string, org: SalesforceOrgUi, sk // HEADER RENDERERS -/** - * SELECT ALL CHECKBOX HEADER - */ -export function SelectHeaderRenderer(props: RenderHeaderCellProps) { - const { column } = props; - const [isRowSelected, onRowSelectionChange] = useRowSelection(); - - return ( - onRowSelectionChange({ type: 'HEADER', checked })} - // WAITING ON: https://github.com/adazzle/react-data-grid/issues/3058 - // indeterminate={props.row.getIsSomeSelected()} - /> - ); -} - export function SelectHeaderGroupRenderer(props: RenderGroupCellProps) { const { column, groupKey, row, childRows } = props; - const [isRowSelected, onRowSelectionChange] = useRowSelection(); + const { isRowSelectionDisabled, isRowSelected, onRowSelectionChange } = useRowSelection(); return ( @@ -88,8 +68,9 @@ export function SelectHeaderGroupRenderer(props: RenderGroupCellProps) { label="Select all" hideLabel checked={isRowSelected} + disabled={isRowSelectionDisabled} indeterminate={selectedRowIds.size > 0 && childRows.some((childRow) => selectedRowIds.has((getRowKey || getRowId)(childRow)))} - onChange={(checked) => onRowSelectionChange({ type: 'ROW', row: row, checked, isShiftClick: false })} + onChange={(checked) => onRowSelectionChange({ row: row, checked, isShiftClick: false })} /> )} @@ -511,7 +492,7 @@ export function GenericRenderer(RenderCellProps: RenderCellProps) { export function SelectFormatter(props: RenderCellProps) { const { column, row } = props; - const [isRowSelected, onRowSelectionChange] = useRowSelection(); + const { isRowSelectionDisabled, isRowSelected, onRowSelectionChange } = useRowSelection(); return ( (props: RenderCellProps) { label="Select row" hideLabel checked={isRowSelected} - onChange={(checked) => onRowSelectionChange({ type: 'ROW', row, checked, isShiftClick: false })} + disabled={isRowSelectionDisabled} + onChange={(checked) => onRowSelectionChange({ row, checked, isShiftClick: false })} /> ); } diff --git a/libs/ui/src/lib/data-table/DataTableSubqueryRenderer.tsx b/libs/ui/src/lib/data-table/DataTableSubqueryRenderer.tsx index b17d2bb42..c5759381c 100644 --- a/libs/ui/src/lib/data-table/DataTableSubqueryRenderer.tsx +++ b/libs/ui/src/lib/data-table/DataTableSubqueryRenderer.tsx @@ -1,14 +1,13 @@ import { queryMore } from '@jetstream/shared/data'; import { copyRecordsToClipboard, formatNumber } from '@jetstream/shared/ui-utils'; import { flattenRecord } from '@jetstream/shared/utils'; -import { Maybe, QueryResult, SalesforceOrgUi } from '@jetstream/types'; +import { ContextMenuItem, Maybe, QueryResult, SalesforceOrgUi } from '@jetstream/types'; import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { RenderCellProps } from 'react-data-grid'; import RecordDownloadModal from '../file-download-modal/RecordDownloadModal'; import Grid from '../grid/Grid'; import AutoFullHeightContainer from '../layout/AutoFullHeightContainer'; import Modal from '../modal/Modal'; -import { ContextMenuItem } from '../popover/ContextMenu'; import Icon from '../widgets/Icon'; import Spinner from '../widgets/Spinner'; import { DataTable } from './DataTable'; diff --git a/libs/ui/src/lib/data-table/DataTree.tsx b/libs/ui/src/lib/data-table/DataTree.tsx index 23cd05dda..feb13e078 100644 --- a/libs/ui/src/lib/data-table/DataTree.tsx +++ b/libs/ui/src/lib/data-table/DataTree.tsx @@ -1,8 +1,8 @@ -import { SalesforceOrgUi } from '@jetstream/types'; +import { ContextMenuItem, SalesforceOrgUi } from '@jetstream/types'; import { forwardRef } from 'react'; import { TreeDataGrid, TreeDataGridProps } from 'react-data-grid'; import 'react-data-grid/lib/styles.css'; -import { ContextMenuContext, ContextMenuItem } from '../popover/ContextMenu'; +import { ContextMenu } from '../form/context-menu/ContextMenu'; import { DataTableFilterContext, DataTableGenericContext } from './data-table-context'; import './data-table-styles.scss'; import { ColumnWithFilter, ContextMenuActionData, RowWithKey } from './data-table-types'; @@ -73,10 +73,12 @@ export const DataTree = forwardRef>( reorderedColumns, filterSetValues, filteredRows, + contextMenuProps, setSortColumns, updateFilter, handleReorderColumns, handleCellKeydown, + handleCloseContextMenu, } = useDataTable({ data, columns: _columns, @@ -96,33 +98,52 @@ export const DataTree = forwardRef>( }); return ( - - - - + + + {contextMenuProps && contextMenuItems && contextMenuAction && ( + { + contextMenuAction(item, { + row: filteredRows[contextMenuProps.rowIdx] as T, + rowIdx: contextMenuProps.rowIdx, + rows: filteredRows as T[], + column: columns[contextMenuProps.rowIdx], + columns, + }); + handleCloseContextMenu(); + }} + onClose={handleCloseContextMenu} /> - - - + )} + + ); } ); diff --git a/libs/ui/src/lib/data-table/SalesforceRecordDataTable.tsx b/libs/ui/src/lib/data-table/SalesforceRecordDataTable.tsx index 54695c219..29caf9270 100644 --- a/libs/ui/src/lib/data-table/SalesforceRecordDataTable.tsx +++ b/libs/ui/src/lib/data-table/SalesforceRecordDataTable.tsx @@ -3,7 +3,7 @@ import { logger } from '@jetstream/shared/client-logger'; import { queryRemaining } from '@jetstream/shared/data'; import { formatNumber, useRollbar } from '@jetstream/shared/ui-utils'; import { flattenRecord, getIdFromRecordUrl, nullifyEmptyStrings } from '@jetstream/shared/utils'; -import { CloneEditView, Field, Maybe, QueryResults, SalesforceOrgUi, SobjectCollectionResponse } from '@jetstream/types'; +import { CloneEditView, ContextMenuItem, Field, Maybe, QueryResults, SalesforceOrgUi, SobjectCollectionResponse } from '@jetstream/types'; import uniqueId from 'lodash/uniqueId'; import { Fragment, FunctionComponent, ReactNode, memo, useCallback, useEffect, useRef, useState } from 'react'; import { Column, RowsChangeData } from 'react-data-grid'; @@ -11,7 +11,6 @@ import SearchInput from '../form/search-input/SearchInput'; import Grid from '../grid/Grid'; import AutoFullHeightContainer from '../layout/AutoFullHeightContainer'; import { ConfirmationModalPromise } from '../modal/ConfirmationModalPromise'; -import { ContextMenuItem } from '../popover/ContextMenu'; import { PopoverErrorButton } from '../popover/PopoverErrorButton'; import { fireToast } from '../toast/AppToast'; import Spinner from '../widgets/Spinner'; diff --git a/libs/ui/src/lib/data-table/data-table-utils.tsx b/libs/ui/src/lib/data-table/data-table-utils.tsx index 6c13a8ec7..dfe66b532 100644 --- a/libs/ui/src/lib/data-table/data-table-utils.tsx +++ b/libs/ui/src/lib/data-table/data-table-utils.tsx @@ -2,7 +2,7 @@ import { logger } from '@jetstream/shared/client-logger'; import { DATE_FORMATS, RECORD_PREFIX_MAP } from '@jetstream/shared/constants'; import { copyRecordsToClipboard } from '@jetstream/shared/ui-utils'; import { ensureBoolean, getIdFromRecordUrl, pluralizeFromNumber } from '@jetstream/shared/utils'; -import { Field, Maybe, QueryResults, QueryResultsColumn } from '@jetstream/types'; +import { ContextMenuItem, Field, Maybe, QueryResults, QueryResultsColumn } from '@jetstream/types'; import { FieldSubquery, getField, getFlattenedFields, isFieldSubquery } from '@jetstreamapp/soql-parser-js'; import { isAfter } from 'date-fns/isAfter'; import { isBefore } from 'date-fns/isBefore'; @@ -18,7 +18,6 @@ import isObject from 'lodash/isObject'; import isString from 'lodash/isString'; import uniqueId from 'lodash/uniqueId'; import { SelectColumn, SELECT_COLUMN_KEY as _SELECT_COLUMN_KEY } from 'react-data-grid'; -import { ContextMenuItem } from '../popover/ContextMenu'; import { DataTableEditorBoolean, DataTableEditorDate, @@ -34,8 +33,6 @@ import { GenericRenderer, HeaderFilter, IdLinkRenderer, - SelectFormatter, - SelectHeaderRenderer, TextOrIdLinkRenderer, } from './DataTableRenderers'; import { SubqueryRenderer } from './DataTableSubqueryRenderer'; @@ -186,8 +183,6 @@ export function getColumnDefinitions( ...SelectColumn, key: SELECT_COLUMN_KEY, resizable: false, - renderCell: SelectFormatter, - renderHeaderCell: SelectHeaderRenderer, }); if (includeRecordActions) { parentColumns.unshift({ @@ -747,15 +742,12 @@ export function getSearchTextByRow(rows: T[], columns: ColumnWithFilter[], } export const TABLE_CONTEXT_MENU_ITEMS: ContextMenuItem[] = [ - { label: 'Copy cell to clipboard', value: 'COPY_CELL', divider: true }, - + { label: 'Copy cell to clipboard', value: 'COPY_CELL', trailingDivider: true }, { label: 'Copy row to clipboard (Excel)', value: 'COPY_ROW_EXCEL' }, - { label: 'Copy row to clipboard (JSON)', value: 'COPY_ROW_JSON', divider: true }, - + { label: 'Copy row to clipboard (JSON)', value: 'COPY_ROW_JSON', trailingDivider: true }, { label: 'Copy column to clipboard (Excel)', value: 'COPY_COL' }, { label: 'Copy column to clipboard (JSON)', value: 'COPY_COL_JSON' }, - { label: 'Copy column to clipboard without header', value: 'COPY_COL_NO_HEADER', divider: true }, - + { label: 'Copy column to clipboard without header', value: 'COPY_COL_NO_HEADER', trailingDivider: true }, { label: 'Copy table to clipboard (Excel)', value: 'COPY_TABLE' }, { label: 'Copy table to clipboard (JSON)', value: 'COPY_TABLE_JSON' }, ]; diff --git a/libs/ui/src/lib/data-table/useDataTable.tsx b/libs/ui/src/lib/data-table/useDataTable.tsx index d49b115a1..57cc5fcb1 100644 --- a/libs/ui/src/lib/data-table/useDataTable.tsx +++ b/libs/ui/src/lib/data-table/useDataTable.tsx @@ -2,28 +2,26 @@ import { IconName } from '@jetstream/icon-factory'; import { logger } from '@jetstream/shared/client-logger'; import { hasCtrlOrMeta, isArrowKey, isCKey, isEnterKey, isVKey, useNonInitialEffect } from '@jetstream/shared/ui-utils'; import { orderObjectsBy, orderValues } from '@jetstream/shared/utils'; -import { SalesforceOrgUi } from '@jetstream/types'; +import { ContextMenuItem, SalesforceOrgUi } from '@jetstream/types'; import copyToClipboard from 'copy-to-clipboard'; import escapeRegExp from 'lodash/escapeRegExp'; import isArray from 'lodash/isArray'; import isNil from 'lodash/isNil'; import isObject from 'lodash/isObject'; import uniqueId from 'lodash/uniqueId'; -import { useCallback, useContext, useEffect, useImperativeHandle, useMemo, useReducer, useState } from 'react'; +import { useCallback, useEffect, useImperativeHandle, useMemo, useReducer, useState } from 'react'; import { + CellClickArgs, CellKeyDownArgs, CellKeyboardEvent, - Row as GridRow, - RenderRowProps, + CellMouseEvent, RenderSortStatusProps, Renderers, SortColumn, } from 'react-data-grid'; import 'react-data-grid/lib/styles.css'; -import ContextMenu, { ContextMenuItem } from '../popover/ContextMenu'; import Icon from '../widgets/Icon'; import { configIdLinkRenderer } from './DataTableRenderers'; -import { DataTableGenericContext } from './data-table-context'; import './data-table-styles.scss'; import { ColumnWithFilter, ContextMenuActionData, DataTableFilter, DataTableRef, FILTER_SET_TYPES, RowWithKey } from './data-table-types'; import { EMPTY_FIELD, NON_DATA_COLUMN_KEYS, filterRecord, getSearchTextByRow, isFilterActive, resetFilter } from './data-table-utils'; @@ -72,34 +70,20 @@ export function useDataTable({ const [columns, setColumns] = useState(_columns || []); const [sortColumns, setSortColumns] = useState(() => initialSortColumns || []); const [rowFilterText, setRowFilterText] = useState>({}); - const [renderers, setRenderers] = useState>({}); + const [renderers] = useState>(() => ({ renderSortStatus })); const [columnsOrder, setColumnsOrder] = useState((): readonly number[] => columns.map((_, index) => index)); + const [contextMenuProps, setContextMenuProps] = useState<{ + rowIdx: number; + column: ColumnWithFilter; + top: number; + left: number; + element: HTMLElement; + } | null>(null); const reorderedColumns = useMemo(() => { return columnsOrder.map((index) => columns[index]); }, [columns, columnsOrder]); - useEffect(() => { - if (contextMenuItems && contextMenuAction) { - setRenderers({ - renderSortStatus, - renderRow: (key: React.Key, props: RenderRowProps) => { - return ( - - ); - }, - }); - } else { - setRenderers({ renderSortStatus }); - } - }, [contextMenuAction, contextMenuItems, gridId]); - const [{ columnMap, filters, filterSetValues }, dispatch] = useReducer(reducer, { hasFilters: false, columnMap: new Map(), @@ -242,6 +226,28 @@ export function useDataTable({ } } + const handleCellContextMenu = useCallback( + ({ row, column }: CellClickArgs, event: CellMouseEvent) => { + event.preventGridDefault(); + // Do not show the default context menu + event.preventDefault(); + setContextMenuProps(null); + // the second menu closes upon opening - ensure open happens in next render + setTimeout(() => { + setContextMenuProps({ + rowIdx: filteredRows.indexOf(row as any), + column: column as any, + top: event.clientY, + left: event.clientX, + element: event.currentTarget, + }); + }); + }, + [filteredRows] + ); + + const handleCloseContextMenu = useCallback(() => setContextMenuProps(null), []); + // NOTE: this is not used anywhere, so we may consider removing it. useImperativeHandle>( ref, @@ -275,10 +281,13 @@ export function useDataTable({ reorderedColumns, filterSetValues, filteredRows, + contextMenuProps, setSortColumns, updateFilter, handleReorderColumns, handleCellKeydown, + handleCellContextMenu: contextMenuItems && contextMenuAction ? handleCellContextMenu : undefined, + handleCloseContextMenu: handleCloseContextMenu, }; } @@ -296,37 +305,6 @@ function renderSortStatus({ sortDirection, priority }: RenderSortStatusProps) { ) : null; } -interface ContextMenuRendererProps { - containerId?: string; - props: RenderRowProps; - contextMenuItems: ContextMenuItem[]; - contextMenuAction: (item: ContextMenuItem, data: ContextMenuActionData) => void; -} - -function ContextMenuRenderer({ containerId, props, contextMenuItems, contextMenuAction }: ContextMenuRendererProps) { - const { columns, rows } = useContext(DataTableGenericContext); - return ( - { - if (!props.selectedCellIdx) { - return; - } - contextMenuAction(item, { - row: props.row, - rowIdx: props.rowIdx, - rows, - column: columns[props.selectedCellIdx], - columns, - }); - }} - > - - - ); -} - interface State { hasFilters: boolean; columnMap: Map>; diff --git a/libs/ui/src/lib/form/context-menu/ContextMenu.tsx b/libs/ui/src/lib/form/context-menu/ContextMenu.tsx new file mode 100644 index 000000000..70ea6b44c --- /dev/null +++ b/libs/ui/src/lib/form/context-menu/ContextMenu.tsx @@ -0,0 +1,205 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import { css } from '@emotion/react'; +import { FocusTrap } from '@headlessui/react'; +import { IconName, IconType } from '@jetstream/icon-factory'; +import { + KeyBuffer, + isArrowDownKey, + isArrowUpKey, + isEnterKey, + isEscapeKey, + menuItemSelectScroll, + selectMenuItemFromKeyboard, +} from '@jetstream/shared/ui-utils'; +import { ContextMenuItem } from '@jetstream/types'; +import isNumber from 'lodash/isNumber'; +import isString from 'lodash/isString'; +import React, { Fragment, FunctionComponent, KeyboardEvent, RefObject, createRef, useEffect, useRef, useState } from 'react'; +import { usePopper } from 'react-popper'; +import OutsideClickHandler from '../../utils/OutsideClickHandler'; +import Icon from '../../widgets/Icon'; + +export interface ContextMenuProps { + actionText?: string; + items: ContextMenuItem[]; + parentElement: HTMLElement; + onClose: () => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onSelected: (item: ContextMenuItem) => void; +} + +/** + * ContextMenu - this is a dropdown-like menu except it is used for context menus. + * It is a popper component that is positioned relative to the parentElement. + */ +export const ContextMenu: FunctionComponent = ({ parentElement, actionText = 'action', items, onClose, onSelected }) => { + const keyBuffer = useRef(new KeyBuffer()); + + const [focusedItem, setFocusedItem] = useState(null); + const [selectedItem, setSelectedItem] = useState(); + const ulContainerEl = useRef(null); + const elRefs = useRef[]>([]); + + const [popperElement, setPopperElement] = useState(null); + + const { styles, attributes } = usePopper(parentElement, popperElement, { + placement: 'bottom-start', + modifiers: [{ name: 'offset', options: { offset: [0, 0] } }], + }); + + // init array to hold element refs for each item in list + if (elRefs.current.length !== items.length) { + const refs: RefObject[] = []; + items.forEach((item, i) => { + refs[i] = elRefs[i] || createRef(); + }); + // add or remove refs + elRefs.current = refs; + } + + useEffect(() => { + if (elRefs.current && isNumber(focusedItem) && elRefs.current[focusedItem] && elRefs.current[focusedItem]) { + try { + elRefs.current?.[focusedItem]?.current?.focus(); + + if (ulContainerEl.current) { + menuItemSelectScroll({ + container: ulContainerEl.current, + focusedIndex: focusedItem, + }); + } + } catch (ex) { + // silent error on keyboard navigation + } + } + }, [focusedItem]); + + useEffect(() => { + if (!isNumber(focusedItem)) { + if (selectedItem) { + let idx = items.findIndex((item) => item.value === selectedItem.value); + idx = idx >= 0 ? idx : 0; + setFocusedItem(idx); + } else { + setFocusedItem(0); + } + } + }, [focusedItem, items, selectedItem]); + + function handleKeyDown(event: KeyboardEvent) { + event.preventDefault(); + event.stopPropagation(); + let newFocusedItem; + + if (isEscapeKey(event)) { + onClose(); + return; + } + + if (isArrowUpKey(event)) { + if (!isNumber(focusedItem) || focusedItem === 0) { + newFocusedItem = items.length - 1; + } else { + newFocusedItem = focusedItem - 1; + } + } else if (isArrowDownKey(event)) { + if (!isNumber(focusedItem) || focusedItem === items.length - 1) { + newFocusedItem = 0; + } else { + newFocusedItem = focusedItem + 1; + } + } else if (isEnterKey(event) && isNumber(focusedItem)) { + const item = items[focusedItem]; + if (!item.disabled) { + setSelectedItem(item); + onSelected(item); + onClose(); + } + } else { + // allow user to use keyboard to navigate to a specific item in the list by typing words + newFocusedItem = selectMenuItemFromKeyboard({ + key: event.key, + keyCode: event.keyCode, + keyBuffer: keyBuffer.current, + items: items, + labelProp: 'value', + }); + } + + if (isNumber(newFocusedItem)) { + setFocusedItem(newFocusedItem); + } + } + + function handleSelection(event: React.MouseEvent, item: ContextMenuItem) { + event.preventDefault(); + onClose(); + onSelected(item); + setSelectedItem(item); + } + + return ( + onClose()}> + +
+ +
+
+
+ ); +}; diff --git a/libs/ui/src/lib/form/date/DateGrid.tsx b/libs/ui/src/lib/form/date/DateGrid.tsx index 8376d21e5..0d2056b95 100644 --- a/libs/ui/src/lib/form/date/DateGrid.tsx +++ b/libs/ui/src/lib/form/date/DateGrid.tsx @@ -110,6 +110,7 @@ export const DateGrid: FunctionComponent = ({ onPrevYear, onNextYear, }) => { + const lastFocusedElement = useRef(); const [dateGrid, setDateGrid] = useState([]); const elRefs = useRef[][]>([]); @@ -201,6 +202,14 @@ export const DateGrid: FunctionComponent = ({ setDateGrid(grid); }, [selectedDate, currMonth, currYear, minAvailableDate, maxAvailableDate, minYear, maxYear]); + function handleKeyDown(event: KeyboardEvent) { + // When editing a table cell, something was hijacking the keydown event + if (isEnterOrSpace(event)) { + event.stopPropagation(); + } + lastFocusedElement.current = document.activeElement as HTMLElement; + } + /** * Handle keyboard navigation * Esc = close @@ -214,6 +223,10 @@ export const DateGrid: FunctionComponent = ({ function handleKeyUp(day: DateGridDate, weekIdx: number, dayIdx: number, event: KeyboardEvent) { event.preventDefault(); event.stopPropagation(); + // keydown happened elsewhere, so ignore keypress events since the user would not intend enter to submit prior to grid being visible + if (!lastFocusedElement.current) { + return; + } const currentRefs = elRefs.current; let targetWeekIdx; let targetDayIdx; @@ -342,6 +355,7 @@ export const DateGrid: FunctionComponent = ({ aria-disabled={day.readOnly} tabIndex={day.readOnly ? undefined : day.label === 1 && day.isCurrMonth ? 0 : -1} onClick={() => !day.readOnly && onSelected(day.value)} + onKeyDown={handleKeyDown} onKeyUp={(event) => handleKeyUp(day, i, k, event)} > {day.label} diff --git a/libs/ui/src/lib/popover/ContextMenu.tsx b/libs/ui/src/lib/popover/ContextMenu.tsx deleted file mode 100644 index 86ed549bb..000000000 --- a/libs/ui/src/lib/popover/ContextMenu.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { css } from '@emotion/react'; -import { logger } from '@jetstream/shared/client-logger'; -import uniqueId from 'lodash/uniqueId'; -import { createContext, ReactNode, useContext, useEffect, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; -import { usePopper } from 'react-popper'; - -// This is used to close menus when another is opened -export const ContextMenuContext = createContext void>>(new Map()); - -export interface ContextMenuItem { - label: string; - value: T; - /** Show heading before item */ - heading?: string; - /** Show divider after item */ - divider?: boolean; - /** omit item from list */ - disabled?: boolean; -} - -// Hook to manage state of context menu and close other unrelated context menus when opened -const useContextMenu = (id: string) => { - const openMenus = useContext(ContextMenuContext); - const [open, setOpen] = useState(false); - - // register outside click to close menu - useEffect(() => { - const handleClick = () => setOpen(false); - document.addEventListener('click', handleClick); - return () => { - document.removeEventListener('click', handleClick); - setOpen(false); - openMenus.delete(id); - }; - }, [id, openMenus]); - - function setOpenState(state: boolean) { - // close any other open menus - for (const [key, setOpenFn] of openMenus) { - setOpenFn(false); - } - openMenus.clear(); - openMenus.set(id, setOpen); - setOpen(state); - } - - return { - open, - setOpen: setOpenState, - }; -}; - -function Item({ item, onItemSelected }: { item: ContextMenuItem; onItemSelected: (item: ContextMenuItem) => void }) { - return ( - <> - {item.heading && ( -
  • - {item.heading} -
  • - )} -
  • onItemSelected(item)}> - - {item.label} - -
  • - {item.divider &&
  • } - - ); -} - -interface ContextMenuProps { - containerId?: string; - menu: ContextMenuItem[]; - onItemSelected: (item: ContextMenuItem) => void; - children: ReactNode; -} - -/** - * This is required to be wrapped in ContextMenuContext.Provider - * - * Shows a menu on right click - */ -export function ContextMenu({ containerId, menu, onItemSelected, children }: ContextMenuProps) { - const idRef = useRef(uniqueId()); - const { open, setOpen } = useContextMenu(idRef.current); - - const [referenceElement, setReferenceElement] = useState(null); - const [popperElement, setPopperElement] = useState(null); - - const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: 'bottom-start', - modifiers: [{ name: 'offset', options: { offset: [0, 0] } }], - }); - - return ( - <> -
    { - if (containerId) { - try { - const itemId = - event.currentTarget.getAttribute('data-id') || - event.currentTarget?.parentElement?.getAttribute('data-id') || - event.currentTarget?.firstElementChild?.getAttribute('data-id') || - null; - if (itemId !== containerId) { - return; - } - } catch (ex) { - logger.warn('Error determining click target for context menu', ex); - } - } - event.preventDefault(); - event.stopPropagation(); - setOpen(true); - setReferenceElement(document.elementFromPoint(event.pageX, event.pageY) as HTMLElement); - }} - > - {children} -
    - {open && - createPortal( -
    -
      - {menu - .filter((item) => !item.disabled) - .map((item) => ( - - ))} -
    -
    , - document.body - )} - - ); -} - -export default ContextMenu; diff --git a/libs/ui/src/lib/widgets/Tooltip.tsx b/libs/ui/src/lib/widgets/Tooltip.tsx index f1c676ac0..1de5d5004 100644 --- a/libs/ui/src/lib/widgets/Tooltip.tsx +++ b/libs/ui/src/lib/widgets/Tooltip.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/react'; import { Maybe } from '@jetstream/types'; import Tippy, { TippyProps } from '@tippyjs/react'; -import { FunctionComponent, MouseEvent, useState } from 'react'; +import { FunctionComponent, MouseEvent, useRef, useState } from 'react'; export interface TooltipProps { /** @deprecated This is not used in the component */ @@ -43,6 +43,7 @@ const LazyTippy = (props: LazyTippyProps) => { }; export const Tooltip: FunctionComponent = ({ className, content, delay, onClick, children }) => { + const containerRef = useRef(null); const [visible, setVisible] = useState(false); const [arrowElement, setArrowElement] = useState(null); @@ -146,7 +147,7 @@ export const Tooltip: FunctionComponent = ({ className, content, d ); }} > - + {children} diff --git a/package.json b/package.json index fab820bf2..9b5b7f1b6 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,7 @@ "eslint-plugin-jsx-a11y": "6.10.1", "eslint-plugin-playwright": "^0.15.3", "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124", "eslint-plugin-react-hooks": "5.0.0", "eslint-plugin-storybook": "^0.8.0", "git-revision-webpack-plugin": "^5.0.0", @@ -324,7 +325,7 @@ "prettier-plugin-prisma": "^3.1.1", "qrcode": "^1.5.4", "react": "18.3.1", - "react-data-grid": "7.0.0-beta.41", + "react-data-grid": "7.0.0-beta.47", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.3.1", diff --git a/yarn.lock b/yarn.lock index 2b0afaefb..3be35393b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -134,6 +134,15 @@ "@babel/highlight" "^7.25.7" picocolors "^1.0.0" +"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/compat-data@^7.17.10": version "7.18.5" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz" @@ -435,6 +444,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" + integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== + dependencies: + "@babel/parser" "^7.26.3" + "@babel/types" "^7.26.3" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz" @@ -463,6 +483,13 @@ dependencies: "@babel/types" "^7.24.7" +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== + dependencies: + "@babel/types" "^7.25.9" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" @@ -626,6 +653,19 @@ "@babel/traverse" "^7.25.4" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" + integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/traverse" "^7.25.9" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.16.7", "@babel/helper-create-regexp-features-plugin@^7.17.12": version "7.17.12" resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.12.tgz" @@ -816,6 +856,14 @@ "@babel/traverse" "^7.24.8" "@babel/types" "^7.24.8" +"@babel/helper-member-expression-to-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" + integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-imports@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" @@ -975,6 +1023,13 @@ dependencies: "@babel/types" "^7.24.7" +"@babel/helper-optimise-call-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" + integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== + dependencies: + "@babel/types" "^7.25.9" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.17.12" resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz" @@ -1005,6 +1060,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" @@ -1075,6 +1135,15 @@ "@babel/helper-optimise-call-expression" "^7.24.7" "@babel/traverse" "^7.25.0" +"@babel/helper-replace-supers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz#ba447224798c3da3f8713fc272b145e33da6a5c5" + integrity sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-simple-access@^7.17.7": version "7.18.2" resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz" @@ -1141,6 +1210,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" @@ -1192,6 +1269,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" @@ -1222,6 +1304,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@babel/helper-validator-option@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" @@ -1485,6 +1572,13 @@ dependencies: "@babel/types" "^7.25.7" +"@babel/parser@^7.25.9", "@babel/parser@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" + integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== + dependencies: + "@babel/types" "^7.26.3" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" @@ -2762,6 +2856,14 @@ "@babel/helper-create-class-features-plugin" "^7.24.1" "@babel/helper-plugin-utils" "^7.24.0" +"@babel/plugin-transform-private-methods@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" + integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-private-property-in-object@^7.22.3": version "7.22.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56" @@ -3689,6 +3791,15 @@ "@babel/parser" "^7.25.7" "@babel/types" "^7.25.7" +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/traverse@^7.16.0", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.7.2": version "7.18.5" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz" @@ -3843,6 +3954,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.9": + version "7.26.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" + integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.3" + "@babel/parser" "^7.26.3" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.3" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.18.4" resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz" @@ -3932,6 +4056,14 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@babel/types@^7.25.9", "@babel/types@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" + integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz" @@ -14188,6 +14320,18 @@ eslint-plugin-playwright@^0.15.3: resolved "https://registry.yarnpkg.com/eslint-plugin-playwright/-/eslint-plugin-playwright-0.15.3.tgz#9fd8753688351bcaf41797eb6a7df8807fd5eb1b" integrity sha512-LQMW5y0DLK5Fnpya7JR1oAYL2/7Y9wDiYw6VZqlKqcRGSgjbVKNqxraphk7ra1U3Bb5EK444xMgUlQPbMg2M1g== +eslint-plugin-react-compiler@^19.0.0-beta-df7b47d-20241124: + version "19.0.0-beta-df7b47d-20241124" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.0.0-beta-df7b47d-20241124.tgz#468751d3a8a6781189405ee56b39b80545306df8" + integrity sha512-82PfnllC8jP/68KdLAbpWuYTcfmtGLzkqy2IW85WopKMTr+4rdQpp+lfliQ/QE79wWrv/dRoADrk3Pdhq25nTw== + dependencies: + "@babel/core" "^7.24.4" + "@babel/parser" "^7.24.4" + "@babel/plugin-transform-private-methods" "^7.25.9" + hermes-parser "^0.25.1" + zod "^3.22.4" + zod-validation-error "^3.0.3" + eslint-plugin-react-hooks@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz#72e2eefbac4b694f5324154619fee44f5f60f101" @@ -15946,6 +16090,18 @@ help-me@^5.0.0: resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== +hermes-estree@0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" + integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== + +hermes-parser@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" + integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== + dependencies: + hermes-estree "0.25.1" + hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" @@ -21743,10 +21899,10 @@ react-colorful@^5.6.1: resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== -react-data-grid@7.0.0-beta.41: - version "7.0.0-beta.41" - resolved "https://registry.yarnpkg.com/react-data-grid/-/react-data-grid-7.0.0-beta.41.tgz#4de30876df528cf0ecf8d2922b674917c15b50c6" - integrity sha512-WmTP/PV+vtVjIaGVLgyG6WAhqvuPBM8I54bsR7oJZl6w43+mIasZM9rEBWjQ52XHJEy41/tjcMBIMNiWqoEbrQ== +react-data-grid@7.0.0-beta.47: + version "7.0.0-beta.47" + resolved "https://registry.yarnpkg.com/react-data-grid/-/react-data-grid-7.0.0-beta.47.tgz#5c4b324f57a1e6fe76ae1659e566dc6cb36fcb79" + integrity sha512-28kjsmwQGD/9RXYC50zn5Zv/SQMhBBoSvG5seq0fM8XXi9TZ0zr9Z5T3YJqLwcEtoNzTOq3y0njkmdujGkIwQQ== dependencies: clsx "^2.0.0" @@ -25810,6 +25966,16 @@ z-schema@~5.0.2: optionalDependencies: commander "^10.0.0" +zod-validation-error@^3.0.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-3.4.0.tgz#3a8a1f55c65579822d7faa190b51336c61bee2a6" + integrity sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ== + +zod@^3.22.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== + zod@^3.23.4: version "3.23.4" resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.4.tgz#c63805b2f39e10d4ab3d55eb3c8cdb472c79dfb1"