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..5d3e92e7d 100644 --- a/apps/jetstream/src/main.scss +++ b/apps/jetstream/src/main.scss @@ -1,5 +1,14 @@ @import '@salesforce-ux/design-system/scss/_design-tokens'; +// Make react data grid checkbox look sort of like SLDS checkbox +@layer rdg.rdg-checkbox-input { + .rdg-checkbox-input { + inline-size: 14px; + block-size: 14px; + accent-color: #0176d3; + } +} + 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..3730c4f70 --- /dev/null +++ b/libs/ui/src/lib/form/context-menu/ContextMenu.tsx @@ -0,0 +1,209 @@ +/* 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, + isSpaceKey, + 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) { + if (isEscapeKey(event) || isArrowUpKey(event) || isArrowDownKey(event) || isEnterKey(event) || isSpaceKey(event)) { + 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) || isSpaceKey(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"