diff --git a/config/jest.config.json b/config/jest.config.json index d4e1e7136..c11f11824 100644 --- a/config/jest.config.json +++ b/config/jest.config.json @@ -18,7 +18,7 @@ "src/common/telemetry/(.*)$": "identity-obj-proxy", "@pnp/sp": "identity-obj-proxy", "'@pnp/sp/fields": "identity-obj-proxy", - "ControlStrings": "identity-obj-proxy", + "ControlStrings": "/tests/__mocks__/ControlStrings.js", "\\.(css|scss)$": "identity-obj-proxy" } } diff --git a/docs/documentation/docs/assets/DetailsList.png b/docs/documentation/docs/assets/DetailsList.png new file mode 100644 index 000000000..6398f7978 Binary files /dev/null and b/docs/documentation/docs/assets/DetailsList.png differ diff --git a/docs/documentation/docs/controls/DetailsList.md b/docs/documentation/docs/controls/DetailsList.md new file mode 100644 index 000000000..82ac6c3c3 --- /dev/null +++ b/docs/documentation/docs/controls/DetailsList.md @@ -0,0 +1,433 @@ +# DetailsList control + +This control renders tabular data with selectable rows, custom columns, grouping, +virtualization, infinite scrolling, column resizing, column reordering, and row +drag and drop. + +The control is generic, so the item type is shared by the columns, renderers, +groups, selection callbacks, and drag-and-drop callbacks. + +## How to use this control in your solutions + +- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency. +- Import the control and the types you need from the `DetailsList` module: + +```TypeScript +import { + DetailsList, + DetailsListSelectionMode, + IColumn, +} from "@pnp/spfx-controls-react/lib/DetailsList"; +``` + +- Define the item type, columns, and items in your component: + +```TypeScript +interface IProject { + id: number; + name: string; + owner: string; + status: "Active" | "Archived"; +} + +const projects: IProject[] = [ + { id: 1, name: "Intranet refresh", owner: "Maya Patel", status: "Active" }, + { id: 2, name: "Launch checklist", owner: "Alex Kim", status: "Archived" }, +]; + +const columns: IColumn[] = [ + { + key: "name", + name: "Project", + fieldName: "name", + minWidth: 180, + isRowHeader: true, + }, + { + key: "owner", + name: "Owner", + fieldName: "owner", + minWidth: 140, + }, + { + key: "status", + name: "Status", + fieldName: "status", + minWidth: 100, + }, +]; +``` + +- Use the `DetailsList` control in your component as follows: + +```TypeScript +const [selectedProjects, setSelectedProjects] = React.useState([]); + + project.id} + selectionMode={DetailsListSelectionMode.multiple} + onSelectionChanged={({ selectedItems }) => setSelectedProjects(selectedItems)} + ariaLabelForGrid="Projects" +/> +``` + +The control uses Fluent UI 9 components internally. Render it inside a Fluent UI +9 `FluentProvider` when the surrounding application provides a custom Fluent UI +9 theme. + +## Selection + +The `selectionMode` property controls whether rows can be selected. Use +`selectedKeys` for a controlled selection or `defaultSelectedKeys` for an +initial selection. + +```TypeScript +const [selectedKeys, setSelectedKeys] = React.useState>(new Set()); + + project.id} + selectionMode={DetailsListSelectionMode.multiple} + selectedKeys={selectedKeys} + onSelectionChanged={({ selectedKeys: nextKeys }) => { + setSelectedKeys(new Set(Array.from(nextKeys).map((key) => Number(key)))); + }} +/> +``` + +`CheckboxVisibility` can be used to show checkboxes always, on hover, or never. +The `disabledItemKeys` property prevents specific rows from being selected. + +## Grouping + +Groups use indexes into the flattened `items` array. `startIndex` identifies the +first item in a group and `count` identifies how many items it contains. + +```TypeScript +import { + DetailsList, + DetailsListSelectionMode, + IDetailsListGroup, +} from "@pnp/spfx-controls-react/lib/DetailsList"; + +const groupedProjects: IProject[] = [ + { id: 1, name: "Intranet refresh", owner: "Maya Patel", status: "Active" }, + { id: 2, name: "Launch checklist", owner: "Alex Kim", status: "Active" }, + { id: 3, name: "Legacy migration", owner: "João Mendes", status: "Archived" }, +]; + +const groups: IDetailsListGroup[] = [ + { key: "active", name: "Active", startIndex: 0, count: 2 }, + { key: "archived", name: "Archived", startIndex: 2, count: 1, isCollapsed: true }, +]; + + { + console.log(`${group.name} collapsed: ${isCollapsed}`); + }} + selectionMode={DetailsListSelectionMode.none} +/> +``` + +Use `onRenderGroupHeader` when a solution needs a custom group header. The +default group header provides the expand and collapse interaction and aligns the +group label with the leading edge of the list. + +## Sorting, resizing, and custom cells + +Sorting is controlled by the consuming component. Update the items and the +column sort state in `onColumnClick`: + +```TypeScript +import { ColumnActionsMode } from "@pnp/spfx-controls-react/lib/DetailsList"; + +const [projectItems, setProjectItems] = React.useState(projects); + +const sortableColumns: IColumn[] = columns.map((column) => ({ + ...column, + isResizable: true, + columnActionsMode: ColumnActionsMode.clickable, + onColumnClick: (_event, clickedColumn) => { + const fieldName = clickedColumn.fieldName; + if (fieldName !== "name" && fieldName !== "owner" && fieldName !== "status") { + return; + } + setProjectItems((currentProjects) => [...currentProjects].sort((left, right) => + left[fieldName].localeCompare(right[fieldName]), + )); + }, +})); + + { + console.log(`Column ${columnIndex} (${column.name}) resized to ${width}`); + }} +/> +``` + +Set `isResizable` on a column to enable its resize handle. Users can resize with +the pointer, use the keyboard arrow keys, or double-click the handle to +auto-fit the column. Use `minWidth` and `maxWidth` to constrain the result. + +Use `onRender` on an individual `IColumn` or `onRenderItemColumn` on the control +to render icons, badges, links, and row actions: + +```TypeScript +import { CheckmarkCircle20Filled } from "@fluentui/react-icons"; + +const columnsWithIcons: IColumn[] = [ + { + key: "status", + name: "Status", + fieldName: "status", + minWidth: 120, + onRender: (project) => ( +
+ {project.status === "Active" && } + {project.status} +
+ ), + }, +]; +``` + +## Column drag and drop + +Pass `columnReorderOptions` to allow users to drag column headers into a new +order. Frozen columns remain at the beginning or end of the list. + +```TypeScript +import { + ColumnDragEndLocation, + IColumnReorderOptions, +} from "@pnp/spfx-controls-react/lib/DetailsList"; + +const columnReorderOptions: IColumnReorderOptions = { + frozenColumnCountFromStart: 1, + frozenColumnCountFromEnd: 1, + onColumnDrop: ({ draggedColumn, targetColumn }) => { + console.log(`Moved ${draggedColumn.name} before ${targetColumn.name}`); + }, + onDragEnd: (location) => { + if (location === ColumnDragEndLocation.header) { + console.log("Column order updated"); + } + }, +}; + + +``` + +## Row drag and drop + +Use `dragDropEvents` to implement scenarios such as prioritizing a work queue, +reordering a playlist, or moving files between an ordered list. + +```TypeScript +import { IDetailsListRowDragDropEvents } from "@pnp/spfx-controls-react/lib/DetailsList"; + +const dragDropEvents: IDetailsListRowDragDropEvents = { + canDrag: (project) => project.status === "Active", + canDrop: (draggedProject, targetProject) => draggedProject.id !== targetProject.id, + onDragStart: (project) => console.log(`Dragging ${project.name}`), + onDrop: (draggedProject, targetProject, targetIndex) => { + console.log(`Move ${draggedProject.name} before row ${targetIndex}`); + console.log(`Target row: ${targetProject.name}`); + }, +}; + + +``` + +## Infinite scrolling and loading states + +Set `enableInfiniteScroll` and provide `onLoadMore` for a list backed by a +paged API. `hasNextPage` prevents another request after the final page. + +```TypeScript + { + const nextPage = await loadNextProjectPage(); + setProjects((currentProjects) => [...currentProjects, ...nextPage.items]); + setHasNextPage(nextPage.hasNextPage); + }} + hasNextPage={hasNextPage} + isLoadingMore={isLoadingMore} + loadingMoreMessage="Loading more projects..." +/> +``` + +For an initial load, use `isLoading` and `loadingMessage`. The control also +supports shimmer placeholders through `enableShimmer`, `shimmerLines`, +`ariaLabelForShimmer`, and `onRenderCustomPlaceholder`. Use `emptyState` when +there are no items to display. + +## Virtualization + +Virtualization is useful for large lists. Set a fixed `height`, enable +`enableVirtualization`, and adjust `overscanCount` when the application needs +more rows rendered just outside the viewport. + +```TypeScript + itemCount > 50} +/> +``` + +## Implementation + +The `DetailsList` control can be configured with the following properties: + +| Property | Type | Required | Description | +| ---- | ---- | ---- | ---- | +| items | readonly T[] | yes | Items to render in the list. | +| columns | readonly IColumn<T>[] | yes | Columns to render and their rendering, sorting, grouping, and resizing configuration. | +| getKey | (item: T, index: number) => React.Key | no | Returns the stable key for a row. The index is used when this is not provided. | +| setKey | string | no | Key used to identify the list instance. | +| height | number | no | Height of the scrollable viewport. Required for practical virtualization and infinite scrolling. | +| compact | boolean | no | Renders rows with a compact layout. | +| layoutMode | DetailsListLayoutMode | no | Uses `fixedColumns` or `justified` column layout. | +| constrainMode | ConstrainMode | no | Uses `unconstrained` or `horizontalConstrained` horizontal layout. | +| isHeaderVisible | boolean | no | Controls whether the column header is rendered. | +| selectionMode | DetailsListSelectionMode | no | Uses `none`, `single`, or `multiple` row selection. | +| checkboxVisibility | CheckboxVisibility | no | Uses `onHover`, `always`, or `hidden` selection checkboxes. | +| selectedKeys | ReadonlySet<React.Key> or readonly React.Key[] | no | Controlled selected row keys. | +| defaultSelectedKeys | readonly React.Key[] | no | Initial selected row keys for an uncontrolled list. | +| disabledItemKeys | ReadonlySet<React.Key> or readonly React.Key[] | no | Row keys that cannot be selected or invoked. | +| onSelectionChanged | (change: IDetailsListSelectionChange<T>) => void | no | Called after the selected rows change. | +| onActiveItemChanged | (item: T, index: number, event: React.FocusEvent<HTMLElement>) => void | no | Called when the active row changes. | +| onItemInvoked | (item: T, index: number, event: React.SyntheticEvent) => void | no | Called when a row is invoked. | +| onItemContextMenu | (item: T, index: number, event: React.MouseEvent<HTMLElement>) => void or boolean | no | Handles a row context menu. Return `true` when the event is handled. | +| onColumnHeaderClick | (event: React.MouseEvent<HTMLElement>, column: IColumn<T>) => void | no | Handles a column header click, commonly used for sorting. | +| onColumnHeaderContextMenu | (column: IColumn<T>, event: React.MouseEvent<HTMLElement>) => void | no | Handles a column header context menu. | +| onColumnResize | (column: IColumn<T>, width: number, columnIndex: number) => void | no | Called after a column is resized. | +| columnReorderOptions | IColumnReorderOptions<T> | no | Enables column drag and drop and optionally freezes columns at either edge. | +| dragDropEvents | IDetailsListRowDragDropEvents<T> | no | Enables row drag and drop callbacks. | +| groups | readonly IDetailsListGroup<T>[] | no | Group definitions for the flattened items array. | +| onToggleGroup | (group: IDetailsListGroup<T>, isCollapsed: boolean) => void | no | Called when a group is expanded or collapsed. | +| enableVirtualization | boolean | no | Enables viewport-based row rendering. | +| rowHeight | number | no | Row height used by virtualization. | +| groupHeaderHeight | number | no | Group header height used by virtualization. | +| overscanCount | number | no | Number of extra rows rendered outside the viewport. | +| onShouldVirtualize | (itemCount: number) => boolean | no | Determines whether virtualization is used for the current item count. | +| enableInfiniteScroll | boolean | no | Enables loading more rows near the end of the viewport. | +| infiniteScrollThreshold | number | no | Distance from the end of the viewport that triggers `onLoadMore`. | +| onLoadMore | () => void or Promise<void> | no | Loads the next page of items. | +| hasNextPage | boolean | no | Indicates whether another page is available. | +| isLoadingMore | boolean | no | Displays the loading-more state while the next page is being loaded. | +| isLoading | boolean | no | Displays the initial loading state. | +| enableShimmer | boolean | no | Renders shimmer rows while loading. | +| shimmerLines | number | no | Number of shimmer rows. | +| loadingMessage | React.ReactNode | no | Content shown during the initial loading state. | +| loadingMoreMessage | React.ReactNode | no | Content shown while loading another page. | +| emptyState | React.ReactNode | no | Content shown when there are no items. | +| ariaLabelForGrid | string | no | Accessible label for the list grid. | +| getRowAriaLabel | (item: T) => string | no | Returns the accessible label for a row. | +| styles | IDetailsListStyles | no | Inline style object for the root, viewport, grid, header, rows, cells, and group headers. | + +## `IColumn` properties + +| Property | Type | Required | Description | +| ---- | ---- | ---- | ---- | +| key | string | yes | Unique column key. | +| name | string | yes | Display name for the column header. | +| fieldName | keyof T & string | no | Item property rendered by the default cell renderer. | +| minWidth | number | yes | Minimum column width. | +| maxWidth | number | no | Maximum column width. | +| isResizable | boolean | no | Enables pointer, keyboard, and double-click resizing. | +| isRowHeader | boolean | no | Marks the column as the row header for accessibility. | +| isSorted | boolean | no | Displays the sorted state. Sorting is handled by the consuming component. | +| isSortedDescending | boolean | no | Indicates descending sort direction. | +| isGrouped | boolean | no | Marks the column as the grouping column. | +| columnActionsMode | ColumnActionsMode | no | Uses `disabled`, `clickable`, or `hasDropdown`. | +| onRender | (item: T, index: number, column: IColumn<T>) => React.ReactNode | no | Renders an individual cell. | +| onRenderHeader | (column: IColumn<T>) => React.ReactNode | no | Renders a custom column header. | +| onColumnClick | (event, column) => void | no | Handles a column header click. | +| onColumnResize | (width: number) => void | no | Handles a resize for this column. | + +## `IColumnReorderOptions` properties + +| Property | Type | Required | Description | +| ---- | ---- | ---- | ---- | +| frozenColumnCountFromStart | number | no | Number of columns that remain at the beginning of the list. | +| frozenColumnCountFromEnd | number | no | Number of columns that remain at the end of the list. | +| onColumnDragStart | (dragStarted: boolean) => void | no | Called when column dragging starts or ends. | +| onColumnDrop | (details: IDetailsListColumnDragDropDetails<T>) => void | no | Called after a column is dropped. | +| onDragEnd | (location: ColumnDragEndLocation) => void | no | Called when column dragging ends. | + +## Enums + +### `DetailsListSelectionMode` + +| Name | Value | +| ---- | ---- | +| none | 0 | +| single | 1 | +| multiple | 2 | + +### `CheckboxVisibility` + +| Name | Value | +| ---- | ---- | +| onHover | 0 | +| always | 1 | +| hidden | 2 | + +### `DetailsListLayoutMode` + +| Name | Value | +| ---- | ---- | +| fixedColumns | 0 | +| justified | 1 | + +### `ConstrainMode` + +| Name | Value | +| ---- | ---- | +| unconstrained | 0 | +| horizontalConstrained | 1 | + +### `ColumnActionsMode` + +| Name | Value | +| ---- | ---- | +| disabled | 0 | +| clickable | 1 | +| hasDropdown | 2 | + +### `ColumnDragEndLocation` + +| Name | Value | +| ---- | ---- | +| outside | 0 | +| surface | 1 | +| header | 2 | + +![](https://telemetry.sharepointpnp.com/sp-dev-fx-controls-react/wiki/controls/DetailsList) diff --git a/docs/documentation/mkdocs.yml b/docs/documentation/mkdocs.yml index f5eb3b0a3..9457e3301 100644 --- a/docs/documentation/mkdocs.yml +++ b/docs/documentation/mkdocs.yml @@ -30,6 +30,7 @@ nav: - ContentTypePicker: 'controls/ContentTypePicker.md' - Dashboard: 'controls/Dashboard.md' - DateTimePicker: 'controls/DateTimePicker.md' + - DetailsList: 'controls/DetailsList.md' - DragDropFiles: 'controls/DragDropFiles.md' - DynamicForm: 'controls/DynamicForm.md' - EnhancedThemeProvider: 'controls/EnhancedThemeProvider.md' diff --git a/package-lock.json b/package-lock.json index 67b6172c1..4825574a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1610,12 +1610,12 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/devtools": { @@ -1628,19 +1628,19 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@fluentui/accessibility": { @@ -1777,9 +1777,9 @@ } }, "node_modules/@fluentui/keyboard-keys": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@fluentui/keyboard-keys/-/keyboard-keys-9.0.8.tgz", - "integrity": "sha512-iUSJUUHAyTosnXK8O2Ilbfxma+ZyZPMua5vB028Ys96z80v+LFwntoehlFsdH3rMuPsA8GaC1RE7LMezwPBPdw==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@fluentui/keyboard-keys/-/keyboard-keys-9.0.9.tgz", + "integrity": "sha512-DjX4z+dDCB49rsmyDuOwtD28Zbe9CUf86JKi8aUEvO5/TetsiuGPokgJiFIR0S9SGeFbquj5CUmAslk1RpgMzw==", "license": "MIT", "dependencies": { "@swc/helpers": "^0.5.1" @@ -1796,9 +1796,9 @@ } }, "node_modules/@fluentui/priority-overflow": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@fluentui/priority-overflow/-/priority-overflow-9.3.0.tgz", - "integrity": "sha512-yaBC0R4e+4ZlCWDulB5S+xBrlnLwfzdg68GaarCqQO8OHjLg7Ah05xTj7PsAYcoHeEg/9vYeBwGXBpRO8+Tjqw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/@fluentui/priority-overflow/-/priority-overflow-9.4.2.tgz", + "integrity": "sha512-xjjOolrrFWXny0RQFazheFnQ6O5qEm2cmHrsl8sUveRyARGmlzZQFxSQ4NAvtpnhx/GHXcqCKfINH+9ciyJX0w==", "license": "MIT", "dependencies": { "@swc/helpers": "^0.5.1" @@ -1833,21 +1833,21 @@ } }, "node_modules/@fluentui/react-accordion": { - "version": "9.9.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-accordion/-/react-accordion-9.9.2.tgz", - "integrity": "sha512-Mmi5nVKfQrBiBiD1JPVtCmIMrR1CpCy8hsWZLwv/pHt+uHHyW9HyrPXwiOitj3ookA5ec1kXyl34BN8RUi7DGQ==", + "version": "9.12.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-accordion/-/react-accordion-9.12.2.tgz", + "integrity": "sha512-j4uWILIw3jnVduQVxttQUZV26eHfkqlzsOavrdf7cjTtfJItmiicqvP520jZgfFLR3FsQhLQmcJvzxXbB9GQuw==", "license": "MIT", "dependencies": { - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -1859,18 +1859,18 @@ } }, "node_modules/@fluentui/react-alert": { - "version": "9.0.0-beta.136", - "resolved": "https://registry.npmjs.org/@fluentui/react-alert/-/react-alert-9.0.0-beta.136.tgz", - "integrity": "sha512-tXxU+fTjWtpv4V6qn21Vuhy1FS6VveRYy747241R1Ta4lqvxHfZ+Z8fowRZaXNdcso0ZM+QUzpcaz8kAELUm2g==", + "version": "9.0.1-0", + "resolved": "https://registry.npmjs.org/@fluentui/react-alert/-/react-alert-9.0.1-0.tgz", + "integrity": "sha512-pzV5sFD6JX38PegV+rmV2i0psTO1yCTODYewg0PPEz/g/hpLsbUCpLoPn0Ga3Nqc/szMcWbY4b/2c7cAkO79fg==", "license": "MIT", "dependencies": { - "@fluentui/react-avatar": "^9.10.3", - "@fluentui/react-button": "^9.9.0", + "@fluentui/react-avatar": "^9.11.5", + "@fluentui/react-button": "^9.11.0", "@fluentui/react-icons": "^2.0.239", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -1882,16 +1882,16 @@ } }, "node_modules/@fluentui/react-aria": { - "version": "9.17.10", - "resolved": "https://registry.npmjs.org/@fluentui/react-aria/-/react-aria-9.17.10.tgz", - "integrity": "sha512-KqS2XcdN84XsgVG4fAESyOBfixN7zbObWfQVLNZ2gZrp2b1hPGVYfQ6J4WOO0vXMKYp0rre/QMOgDm6/srL0XQ==", + "version": "9.17.14", + "resolved": "https://registry.npmjs.org/@fluentui/react-aria/-/react-aria-9.17.14.tgz", + "integrity": "sha512-/oBdrvWcIpziaVj5b5e8lDR2DLzVztvQQXuJqscM7ztYxhLYK2wZG0rAVxlAv+Bhyvx1Zqf/YtPCv/6mItx3zw==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-utilities": "^9.26.6", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -1902,21 +1902,21 @@ } }, "node_modules/@fluentui/react-avatar": { - "version": "9.10.3", - "resolved": "https://registry.npmjs.org/@fluentui/react-avatar/-/react-avatar-9.10.3.tgz", - "integrity": "sha512-f8oW58Z+9nehDk/7PInN1paw1jptN2+TiBKcRagU6evHlmnfsV9+GKnS/vInEG3QPQAVPSdYVub03EkF/w90SA==", + "version": "9.11.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-avatar/-/react-avatar-9.11.5.tgz", + "integrity": "sha512-frrqEbOKTGyff3aW5kaal7TNa+Slguwc6nVj+CvxV4AidEmf0r1Vk0QnIj/ioNT1UoG1/PNzaLfz3uu8ce6aSg==", "license": "MIT", "dependencies": { - "@fluentui/react-badge": "^9.5.0", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/react-badge": "^9.5.5", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-popover": "^9.14.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-tooltip": "^9.9.3", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-popover": "^9.14.6", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-tooltip": "^9.10.5", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -1928,16 +1928,16 @@ } }, "node_modules/@fluentui/react-badge": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-badge/-/react-badge-9.5.0.tgz", - "integrity": "sha512-38vaGn7DjS2BqIzjon2U6Xmh+39e2khBwOfsmYx36Z3jqGHbuPwAjQ/kiquTIvtlRRSufwmNtj6C8/J1gN8jmw==", + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-badge/-/react-badge-9.5.5.tgz", + "integrity": "sha512-1BtuzPTsIgV6edHwV9oVKnZRhOIP/3RZTMDd3gSHt3nmXV/R8CcpOxxFhDuG+4AtKC26cvk6DV3hy5BKhw9bPg==", "license": "MIT", "dependencies": { "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -1975,20 +1975,20 @@ } }, "node_modules/@fluentui/react-breadcrumb": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-breadcrumb/-/react-breadcrumb-9.4.0.tgz", - "integrity": "sha512-QpCjYlM3JTMnNwh/sDehDbuAVjTcgSfjkPdSmFaPk2lPHpER32CBcJVhheP9en2U5NbW1e+Gtvq8y06RN8FCWw==", + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-breadcrumb/-/react-breadcrumb-9.4.5.tgz", + "integrity": "sha512-WUfOJSsiWLuNT7QCTGGWGi7bx5gRjspEE0B14eQIj5bfYhu5svfjfbf4Cgxi0Q3VYootiCciPHgom8/pFi0ItQ==", "license": "MIT", "dependencies": { - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-button": "^9.9.0", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-button": "^9.11.0", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-link": "^9.8.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-link": "^9.8.4", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2000,19 +2000,19 @@ } }, "node_modules/@fluentui/react-button": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-button/-/react-button-9.9.0.tgz", - "integrity": "sha512-aH3aSjKyxIiNb9jJOUaaIq47w7jP5ESFSRzvMjcWOETvlWo4QgNqEOOsYqpcltM1OrQZ0sTy/isxppRcyMDlcQ==", + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-button/-/react-button-9.11.0.tgz", + "integrity": "sha512-81zsqSvJSZx1SEG0kztcmEZb2kS8YUX0uPYsPp2bZCPEJB9YIBQn9kPrdt0M0YeAkoPKXQXG5gudSun9M80maA==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2049,18 +2049,18 @@ } }, "node_modules/@fluentui/react-card": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-card/-/react-card-9.6.0.tgz", - "integrity": "sha512-vgBvhtSzQDa01aOP9zdhJXFLsZAiDVslRfX3HmlIo1pAMt8w+PBq+ypDp1wxM7HPFpj9+RYcERRKtf4MSNP9Nw==", - "license": "MIT", - "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-text": "^9.6.15", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.7.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-card/-/react-card-9.7.2.tgz", + "integrity": "sha512-FsSmSYguIorjDPKcIfaXKIYRo+ZgQfu7toyKLRZsxtUiPa2i+C0bBTDwYVd0lLTo9oJN5dIiPshy9JBvw94BZw==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-text": "^9.6.19", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2072,21 +2072,21 @@ } }, "node_modules/@fluentui/react-carousel": { - "version": "9.9.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-carousel/-/react-carousel-9.9.5.tgz", - "integrity": "sha512-YitJHBj+9bbJMB6E6mdqV0tLSFMkxXUdqa0xMY6QKjGXoFkG8GYLI8FZwIfpbqmQfZ2oP7cdUvibGQ4Qyh3LHQ==", + "version": "9.9.12", + "resolved": "https://registry.npmjs.org/@fluentui/react-carousel/-/react-carousel-9.9.12.tgz", + "integrity": "sha512-N8QUMzrKP/n+e+xJl9UO6tJhpMl5+S0/KZzUWYL7UxYwZ+cPJ1IdC45JOxI+iOwnFDXPpt+aAjU3WkPFo2PyLQ==", "license": "MIT", "dependencies": { - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-button": "^9.9.0", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-button": "^9.11.0", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-tooltip": "^9.9.3", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-tooltip": "^9.10.5", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1", "embla-carousel": "^8.5.1", @@ -2101,19 +2101,19 @@ } }, "node_modules/@fluentui/react-checkbox": { - "version": "9.5.17", - "resolved": "https://registry.npmjs.org/@fluentui/react-checkbox/-/react-checkbox-9.5.17.tgz", - "integrity": "sha512-40uRrCnWBMiWyVF2ZN9Ep2nnl/onYrSaa8fNnLBn6Tunhuk9flCxWZygkO5h9Da2QP6DasyGG8WZld1nrR9GUg==", + "version": "9.6.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-checkbox/-/react-checkbox-9.6.4.tgz", + "integrity": "sha512-3OQaUJPXsMbi4ODVLl4zmRn4XqQBlfJS93cSnSYAAYHsA162D1UlsqwfZoTbnuUEJS+st9cWgbEcuZTYYQxm0Q==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2125,18 +2125,17 @@ } }, "node_modules/@fluentui/react-color-picker": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-color-picker/-/react-color-picker-9.2.15.tgz", - "integrity": "sha512-RMmawl7g4gUYLuTQG2QwCcR9fGC+vDD+snsBlXtObpj/cKpeDmYif46g88pYv86jeIXY1zsjINmLpELmz+uFmw==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-color-picker/-/react-color-picker-9.3.0.tgz", + "integrity": "sha512-2pNHOcDe0aci5hyqsnA+0YvJ1CMfHKt1+a4UUrgssb6i5S1l1rcwnljonAQ4sCve4bMx6FVqNybx9tJ+g7CNXg==", "license": "MIT", "dependencies": { - "@ctrl/tinycolor": "^3.3.4", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2148,23 +2147,23 @@ } }, "node_modules/@fluentui/react-combobox": { - "version": "9.16.18", - "resolved": "https://registry.npmjs.org/@fluentui/react-combobox/-/react-combobox-9.16.18.tgz", - "integrity": "sha512-nmyleswOSS9O/3gn8AWQ9Uuyis0WTHO1zZnDVapFUdgd2+hAcUSjJXPQv6NGftuUB5bgS2qAx9prRJg17ZrZvA==", + "version": "9.17.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-combobox/-/react-combobox-9.17.5.tgz", + "integrity": "sha512-roH1DwhPslUxvtOnI9lnnIn+YtEqnmd5SIBgqDB1pQVqcznDZsbGSNXk/8IoVntM5LsMOa0YKKOd8N+8+/7aQg==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-field": "^9.4.16", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-positioning": "^9.22.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-positioning": "^9.23.1", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2217,71 +2216,71 @@ } }, "node_modules/@fluentui/react-components": { - "version": "9.73.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-components/-/react-components-9.73.5.tgz", - "integrity": "sha512-vq7xsdImOXaSAT6jBqPWRPiDDZFs44Lc8IshxO/IjzbstqLNz6T6I65BBueyhX7lHFQnCq6JpAah+HXVRqpkpQ==", - "license": "MIT", - "dependencies": { - "@fluentui/react-accordion": "^9.9.2", - "@fluentui/react-alert": "9.0.0-beta.136", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-avatar": "^9.10.3", - "@fluentui/react-badge": "^9.5.0", - "@fluentui/react-breadcrumb": "^9.4.0", - "@fluentui/react-button": "^9.9.0", - "@fluentui/react-card": "^9.6.0", - "@fluentui/react-carousel": "^9.9.5", - "@fluentui/react-checkbox": "^9.5.17", - "@fluentui/react-color-picker": "^9.2.15", - "@fluentui/react-combobox": "^9.16.18", - "@fluentui/react-dialog": "^9.17.2", - "@fluentui/react-divider": "^9.7.0", - "@fluentui/react-drawer": "^9.11.5", - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-image": "^9.4.0", - "@fluentui/react-infobutton": "9.0.0-beta.112", - "@fluentui/react-infolabel": "^9.4.17", - "@fluentui/react-input": "^9.8.0", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-link": "^9.8.0", - "@fluentui/react-list": "^9.6.12", - "@fluentui/react-menu": "^9.23.0", - "@fluentui/react-message-bar": "^9.6.22", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-nav": "^9.3.21", - "@fluentui/react-overflow": "^9.7.1", - "@fluentui/react-persona": "^9.7.0", - "@fluentui/react-popover": "^9.14.0", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-positioning": "^9.22.0", - "@fluentui/react-progress": "^9.4.16", - "@fluentui/react-provider": "^9.22.15", - "@fluentui/react-radio": "^9.5.16", - "@fluentui/react-rating": "^9.4.0", - "@fluentui/react-search": "^9.4.0", - "@fluentui/react-select": "^9.4.16", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-skeleton": "^9.7.0", - "@fluentui/react-slider": "^9.6.0", - "@fluentui/react-spinbutton": "^9.5.16", - "@fluentui/react-spinner": "^9.8.0", - "@fluentui/react-swatch-picker": "^9.5.0", - "@fluentui/react-switch": "^9.7.0", - "@fluentui/react-table": "^9.19.12", - "@fluentui/react-tabs": "^9.11.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-tag-picker": "^9.8.3", - "@fluentui/react-tags": "^9.7.18", - "@fluentui/react-teaching-popover": "^9.6.19", - "@fluentui/react-text": "^9.6.15", - "@fluentui/react-textarea": "^9.7.0", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-toast": "^9.7.15", - "@fluentui/react-toolbar": "^9.7.5", - "@fluentui/react-tooltip": "^9.9.3", - "@fluentui/react-tree": "^9.15.14", - "@fluentui/react-utilities": "^9.26.2", - "@fluentui/react-virtualizer": "9.0.0-alpha.111", + "version": "9.74.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-components/-/react-components-9.74.6.tgz", + "integrity": "sha512-JfAbId8J0W+dMh2KFxZbpX2ag7ZbpchIQam8Twc15k0qis+OkrCCxts+DF1xVb403PTFz/5n2YFxGWmEajUlDA==", + "license": "MIT", + "dependencies": { + "@fluentui/react-accordion": "^9.12.2", + "@fluentui/react-alert": "9.0.1-0", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-avatar": "^9.11.5", + "@fluentui/react-badge": "^9.5.5", + "@fluentui/react-breadcrumb": "^9.4.5", + "@fluentui/react-button": "^9.11.0", + "@fluentui/react-card": "^9.7.2", + "@fluentui/react-carousel": "^9.9.12", + "@fluentui/react-checkbox": "^9.6.4", + "@fluentui/react-color-picker": "^9.3.0", + "@fluentui/react-combobox": "^9.17.5", + "@fluentui/react-dialog": "^9.18.3", + "@fluentui/react-divider": "^9.7.4", + "@fluentui/react-drawer": "^9.13.2", + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-image": "^9.4.4", + "@fluentui/react-infobutton": "9.0.0-beta.119", + "@fluentui/react-infolabel": "^9.4.24", + "@fluentui/react-input": "^9.8.6", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-link": "^9.8.4", + "@fluentui/react-list": "^9.6.17", + "@fluentui/react-menu": "^9.25.3", + "@fluentui/react-message-bar": "^9.7.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-nav": "^9.4.4", + "@fluentui/react-overflow": "^9.9.2", + "@fluentui/react-persona": "^9.7.7", + "@fluentui/react-popover": "^9.14.6", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-positioning": "^9.23.1", + "@fluentui/react-progress": "^9.5.4", + "@fluentui/react-provider": "^9.22.20", + "@fluentui/react-radio": "^9.6.5", + "@fluentui/react-rating": "^9.4.4", + "@fluentui/react-search": "^9.4.6", + "@fluentui/react-select": "^9.5.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-skeleton": "^9.7.5", + "@fluentui/react-slider": "^9.6.5", + "@fluentui/react-spinbutton": "^9.6.5", + "@fluentui/react-spinner": "^9.8.5", + "@fluentui/react-swatch-picker": "^9.6.0", + "@fluentui/react-switch": "^9.7.5", + "@fluentui/react-table": "^9.19.19", + "@fluentui/react-tabs": "^9.12.4", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-tag-picker": "^9.10.2", + "@fluentui/react-tags": "^9.9.4", + "@fluentui/react-teaching-popover": "^9.7.4", + "@fluentui/react-text": "^9.6.19", + "@fluentui/react-textarea": "^9.7.6", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-toast": "^9.8.2", + "@fluentui/react-toolbar": "^9.8.4", + "@fluentui/react-tooltip": "^9.10.5", + "@fluentui/react-tree": "^9.16.5", + "@fluentui/react-utilities": "^9.26.6", + "@fluentui/react-virtualizer": "9.0.0-alpha.115", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2316,12 +2315,12 @@ "license": "0BSD" }, "node_modules/@fluentui/react-context-selector": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-context-selector/-/react-context-selector-9.2.15.tgz", - "integrity": "sha512-QymBntFLJNZ9VfTOaBn2ApUSSSC5UuDW8ZcgPJPA+06XEFH+U9Zny2d9QAg1xYNYwIGWahWGQ+7ATOuLxtB8Jw==", + "version": "9.2.19", + "resolved": "https://registry.npmjs.org/@fluentui/react-context-selector/-/react-context-selector-9.2.19.tgz", + "integrity": "sha512-NfBKjtaWxXGFI4ZC75aPeQRTKPJOSQDwpdsiX6L4miewLnEwBKtK7+8Tbj2eTLh6fKY0aRwBfHwGqydF2kXVzQ==", "license": "MIT", "dependencies": { - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-utilities": "^9.26.6", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -2333,23 +2332,23 @@ } }, "node_modules/@fluentui/react-dialog": { - "version": "9.17.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-dialog/-/react-dialog-9.17.2.tgz", - "integrity": "sha512-mZdKylSvh2fRf0e3wMX3ZNccb9DahsOE7A5Y9LG97ghYvndMBVG2YwScIzUFVvLS206ari6HMOl0lC5JRB1bKA==", + "version": "9.18.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-dialog/-/react-dialog-9.18.3.tgz", + "integrity": "sha512-fI87iSHyncgeGKxiXquGDORm2vRAjygZOQkjVehEPsyX1RFlEDZxjqvW82NX41f4wv4ie6/qLclOOSkHzCTG3w==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2361,15 +2360,15 @@ } }, "node_modules/@fluentui/react-divider": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-divider/-/react-divider-9.7.0.tgz", - "integrity": "sha512-U8Nhrghjeh+XCGM4B7aHYosd6fXaxHC3MpZi7DB0xQ20ljn5cSTpBt4Yvl+tB9ld2+/eM8wekx1GVKyI4yWa3g==", + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-divider/-/react-divider-9.7.4.tgz", + "integrity": "sha512-Ue7mwFIVSpyiATZPl5g0H/08Gaj6M0OzA9SVGVVpYK90zIYvP1Da23tlUpr80OkFTDCjSNUP1NSYtBGG27AH3g==", "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2381,20 +2380,20 @@ } }, "node_modules/@fluentui/react-drawer": { - "version": "9.11.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-drawer/-/react-drawer-9.11.5.tgz", - "integrity": "sha512-eoZY+jKZwbJo1PUsb7Ico7u/8aObHL4BhPP6hd+HHNzB7seTpN7rLd0DpASLZsxJUy5yvch4QF2TrjOu6V8kRA==", - "license": "MIT", - "dependencies": { - "@fluentui/react-dialog": "^9.17.2", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.13.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-drawer/-/react-drawer-9.13.2.tgz", + "integrity": "sha512-VM50S0Lr7PmA1DP770q+tZxCY9GmnAbQoKrlQWNJbhy74759dXbV2PVT0JnIWRkCqvNP0S2RDN4Z6SRJArIzQw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-dialog": "^9.18.3", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2406,18 +2405,18 @@ } }, "node_modules/@fluentui/react-field": { - "version": "9.4.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-field/-/react-field-9.4.16.tgz", - "integrity": "sha512-2mfuYGldeqr9Llt8QSfwdj1hQofScvNQ/1Rns9TE4QUP6cdqs3cPX2+FZNJzpgO9vq5bk0hJpKqo7lvXZdyEzw==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-field/-/react-field-9.5.4.tgz", + "integrity": "sha512-RoVtH7/GK/45V45NPvCuVVG3K3/FeBXF+Jkk2Gw12XKqfKru2eI8MIhDofKsSH5l6ZkLclZ0ElmQvrYrJSvqJQ==", "license": "MIT", "dependencies": { - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2518,15 +2517,15 @@ } }, "node_modules/@fluentui/react-image": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-image/-/react-image-9.4.0.tgz", - "integrity": "sha512-BpcBlmkukm7YYf6PTCbAIMkeCXc8+7aq2eMADsxF5gFD8j3d5lBY3cKByOWRM1NvXcMXmqXr/hQP+ovqNAHzEA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-image/-/react-image-9.4.4.tgz", + "integrity": "sha512-vjBHrUTuuDVp/IvIiz4bB0iqebU00EJHGJZpF5NlMd+TL7bcMhLU29MrbxQdvlNch78C+XCqoexbjHYoAUET9w==", "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2538,18 +2537,18 @@ } }, "node_modules/@fluentui/react-infobutton": { - "version": "9.0.0-beta.112", - "resolved": "https://registry.npmjs.org/@fluentui/react-infobutton/-/react-infobutton-9.0.0-beta.112.tgz", - "integrity": "sha512-Fhqoc6b1MQtHW+Mm5sBhfa5ZrRdOV4azuUa5WyBvwD4Ozq/z2pBOC/wi/A/WCjKMnGoMlQ2CggoLaMhQmenzAQ==", + "version": "9.0.0-beta.119", + "resolved": "https://registry.npmjs.org/@fluentui/react-infobutton/-/react-infobutton-9.0.0-beta.119.tgz", + "integrity": "sha512-6BsHqXEOxqWmSo3yqLynEht1uQTJO5snD+GbuyGJgnNWW2BuJ9Fzq/ZxaJNgY6t6c7T7W9JqRMPerXi0YsnNqQ==", "license": "MIT", "dependencies": { "@fluentui/react-icons": "^2.0.237", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-popover": "^9.14.0", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-popover": "^9.14.6", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2561,19 +2560,19 @@ } }, "node_modules/@fluentui/react-infolabel": { - "version": "9.4.17", - "resolved": "https://registry.npmjs.org/@fluentui/react-infolabel/-/react-infolabel-9.4.17.tgz", - "integrity": "sha512-zLw52jn2wAuEKWFzaNj3aKhuB4BAEI8LqblryCg0LKPKHcv/z9d9RllCqcVz+ngdK1tQGtCIPH/wxNlZXx/I3Q==", + "version": "9.4.24", + "resolved": "https://registry.npmjs.org/@fluentui/react-infolabel/-/react-infolabel-9.4.24.tgz", + "integrity": "sha512-AfApeHDRMxWMT1JsxJpDW+5Q2JfDX3HUxMhaj4q1Zz9204rqPN8r4VB3+fTqEC8SNghqiiDhIfI1bNeQ4baPwg==", "license": "MIT", "dependencies": { "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-popover": "^9.14.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-popover": "^9.14.6", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2585,16 +2584,16 @@ } }, "node_modules/@fluentui/react-input": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-input/-/react-input-9.8.0.tgz", - "integrity": "sha512-y/CUMEo2pgFLHUDnKTfXV1hwZ5j0GUD5exTyBKoeNgfAwY1UelWIvKc7fgelhV5GYEQJL7ycm8eNq71CqLA74A==", + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-input/-/react-input-9.8.6.tgz", + "integrity": "sha512-4EAMOTN5DCXB5DFKLwsFkCbMMelqOfl+jnB4kbH9Nu6nLwhJj89EYYGzzs2V3bctmJN9mtd7xkGITAZLYhUpTQ==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2606,12 +2605,12 @@ } }, "node_modules/@fluentui/react-jsx-runtime": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/@fluentui/react-jsx-runtime/-/react-jsx-runtime-9.4.4.tgz", - "integrity": "sha512-npqPWSJ2qciCRB4B/cyWyrTbf8V8Z2Kfr9HnZqrUBDgEvq72rRGb+gml6naxGNzhaT4NBLQLZmdPZqt+1wZ4ig==", + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-jsx-runtime/-/react-jsx-runtime-9.4.5.tgz", + "integrity": "sha512-T791vRKj9l3WhJd6oWZ30J9xA8Pyv32VfB55PlywSqH8PchO+EzteG6q2yZ+6j919WRRMy45ElYebe1DulnGOA==", "license": "MIT", "dependencies": { - "@fluentui/react-utilities": "^9.26.5", + "@fluentui/react-utilities": "^9.26.6", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -2620,15 +2619,15 @@ } }, "node_modules/@fluentui/react-label": { - "version": "9.3.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-label/-/react-label-9.3.15.tgz", - "integrity": "sha512-ycmaQwC4tavA8WeDfgcay1Ywu/4goHq1NOeVxkyzWTPGA7rs+tdCgdZBQZLAsBK2XFaZiHs7l+KG9r1oIRKolA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-label/-/react-label-9.4.4.tgz", + "integrity": "sha512-Xsi+K+c0SawFQFr72zpmzTHFhSqBpI8Lo6uqrfnVHtoF6lQyL11LyAIk0JtoLq82Pikqy4vvRta14fvwy0UM2g==", "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2640,17 +2639,17 @@ } }, "node_modules/@fluentui/react-link": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-link/-/react-link-9.8.0.tgz", - "integrity": "sha512-TH5LS4iuQ4jYzlR84A4n7lQTKaJuiuuGFHMIxoEqtKeMoL9F5AiabuBs6m7Q7clSdTrrcRMNzXLuEFarQrzGTQ==", + "version": "9.8.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-link/-/react-link-9.8.4.tgz", + "integrity": "sha512-wwyA/wRJOozyT2C0RK6maGKZM6xFgfoNt196WN5g+WApbNlgDEgqOLUScj14o+MNOe8H/bnfuNN1U6KEGtD09A==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2662,19 +2661,19 @@ } }, "node_modules/@fluentui/react-list": { - "version": "9.6.12", - "resolved": "https://registry.npmjs.org/@fluentui/react-list/-/react-list-9.6.12.tgz", - "integrity": "sha512-vFeqP4r3rjqtd/p9p7woma/j2U3UlcirfqGje26ppBMzDs/0MWQiUmjTkQTMLnPeh72knnqwsF43dRSKSdTSng==", - "license": "MIT", - "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-checkbox": "^9.5.17", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.6.17", + "resolved": "https://registry.npmjs.org/@fluentui/react-list/-/react-list-9.6.17.tgz", + "integrity": "sha512-KytdJlezG06DByb9uLfdnXtlaM1VxEpMXeaINfgjxwlHa/uohJVpwN3il+NSchYfTh/kle6telVRWG515Klhhg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-checkbox": "^9.6.4", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2686,24 +2685,24 @@ } }, "node_modules/@fluentui/react-menu": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-menu/-/react-menu-9.23.0.tgz", - "integrity": "sha512-KM08C5GQ+wcsZ6dcmjrMonsW1RRcuhygR7GBT6rnWxxx6qzKv6RCumckzL5NfSgvyJw4wWKs857k5sUczs+7VQ==", + "version": "9.25.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-menu/-/react-menu-9.25.3.tgz", + "integrity": "sha512-TLOIxNwrZG0vqLoQRQPPOcVJm+q8X6CiFBLHmEeomFgV1BWcxKkjo4Qohcqd2UEiBzGbSNc9vP1UZAC2wNc+5w==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-positioning": "^9.22.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-positioning": "^9.23.1", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2715,20 +2714,20 @@ } }, "node_modules/@fluentui/react-message-bar": { - "version": "9.6.22", - "resolved": "https://registry.npmjs.org/@fluentui/react-message-bar/-/react-message-bar-9.6.22.tgz", - "integrity": "sha512-9FvKlJwBvfcvwjOf/hsiXUjcIFOvPmJ0woFH4y6rwXGCY/TL3dztcLHbPZ6oVPN4GK+5VwRt1eRRVifrpQRGZw==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-message-bar/-/react-message-bar-9.7.5.tgz", + "integrity": "sha512-Q5VM6EkW3D0P7+/XCUO/4IBZW8tRLcsd9/yqLRk4ec9egxVrZZ6l5Z+SUK/MCEyoHXKP7GcErjcDGR1jm6JzxQ==", "license": "MIT", "dependencies": { - "@fluentui/react-button": "^9.9.0", + "@fluentui/react-button": "^9.11.0", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-link": "^9.8.0", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-link": "^9.8.4", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2800,13 +2799,13 @@ } }, "node_modules/@fluentui/react-motion": { - "version": "9.16.1", - "resolved": "https://registry.npmjs.org/@fluentui/react-motion/-/react-motion-9.16.1.tgz", - "integrity": "sha512-sbrNuauwI5uw20XOAqPjXBfgBqPreHc5AxU7bJ6yoLUHL2gDKo7KGAIvLEd216GX37mgPkb3dx9rarIVGx3uvA==", + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-motion/-/react-motion-9.16.2.tgz", + "integrity": "sha512-fraG2s/UlduW5JYbvP619S2755DlG7kiPKd/rlXEebgPRTSVgkuSWW5suvYVqO64/PJpZrnJD23sweAeqol6PA==", "license": "MIT", "dependencies": { - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-utilities": "^9.26.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-utilities": "^9.26.6", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -2817,9 +2816,9 @@ } }, "node_modules/@fluentui/react-motion-components-preview": { - "version": "0.15.6", - "resolved": "https://registry.npmjs.org/@fluentui/react-motion-components-preview/-/react-motion-components-preview-0.15.6.tgz", - "integrity": "sha512-9aNzHAHNdfbH/8/mYGy5YVrUOGnpiru3YrqQ7KhCRWXvlce7yV3WF5CzN1g/uNh3MFmKGwQeW4ui6xJOfEaI4Q==", + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@fluentui/react-motion-components-preview/-/react-motion-components-preview-0.15.7.tgz", + "integrity": "sha512-YeG5cPkmODOGfmVktpPCFCe/0mKj3CCrquHOREcf+gug2aXLp/0jFy8LY4LUhYeJw7wVzVr9XzBxuzo5dE4XIg==", "license": "MIT", "dependencies": { "@fluentui/react-motion": "*", @@ -2834,25 +2833,25 @@ } }, "node_modules/@fluentui/react-nav": { - "version": "9.3.21", - "resolved": "https://registry.npmjs.org/@fluentui/react-nav/-/react-nav-9.3.21.tgz", - "integrity": "sha512-HsynotAKugl+mESwrON1rrahODNvbU8lsKNzwge/OQuf9yE0TdkpgpZPgYNzLgIabj1B4mtP3rUvHTRNy78GOQ==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-nav/-/react-nav-9.4.4.tgz", + "integrity": "sha512-JIIC/QuWE+ySEEW6jF414Vh/wMNbO210wU+qC2U5+r6KiPFBvgkog5Pqr5qRY62XHlmJz2Zl+pDXBwoX9RSe4Q==", "license": "MIT", "dependencies": { - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-button": "^9.9.0", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-divider": "^9.7.0", - "@fluentui/react-drawer": "^9.11.5", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-button": "^9.11.0", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-divider": "^9.7.4", + "@fluentui/react-drawer": "^9.13.2", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-tooltip": "^9.9.3", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-tooltip": "^9.10.5", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2944,15 +2943,15 @@ } }, "node_modules/@fluentui/react-overflow": { - "version": "9.7.1", - "resolved": "https://registry.npmjs.org/@fluentui/react-overflow/-/react-overflow-9.7.1.tgz", - "integrity": "sha512-Ml1GlcLrAUv31d9WN15WGOZv32gzDtZD5Mp1MOQ3ichDfTtxrswIch7MDzZ8hLMGf/7Y2IzBpV8iFR1XdSrGBA==", + "version": "9.9.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-overflow/-/react-overflow-9.9.2.tgz", + "integrity": "sha512-11NL9YtysCKQrgcPkty4271MQ6cVsBANEU09qHtDkNpN2bODddwzrvK5ggCBaK7n7cTVlpJWYZzQZV+YxZ5EQQ==", "license": "MIT", "dependencies": { - "@fluentui/priority-overflow": "^9.3.0", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/priority-overflow": "^9.4.2", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2964,17 +2963,17 @@ } }, "node_modules/@fluentui/react-persona": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-persona/-/react-persona-9.7.0.tgz", - "integrity": "sha512-RBxY9nKL1TwuDzK8mAe29A7RigUbqiAUrJ0R14fom5cDWo+e4qLwTxgUy/8MVe6+joWLjfLeuVWbgvIEYNoEbg==", + "version": "9.7.7", + "resolved": "https://registry.npmjs.org/@fluentui/react-persona/-/react-persona-9.7.7.tgz", + "integrity": "sha512-+xrJIClBzH6ua0yWRum0+9McEuZ/lCcOpdC9cmltcBag0Gb3/rjmf5Dr2w4tLPTIGzbLuDE00Yoexy6jJLWm2A==", "license": "MIT", "dependencies": { - "@fluentui/react-avatar": "^9.10.3", - "@fluentui/react-badge": "^9.5.0", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-avatar": "^9.11.5", + "@fluentui/react-badge": "^9.5.5", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -2986,23 +2985,23 @@ } }, "node_modules/@fluentui/react-popover": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-popover/-/react-popover-9.14.0.tgz", - "integrity": "sha512-XrZlSfSYhA12j5bna4Sq8N/If2vul7gl8woVrN8U3iQUjdaHB6OAMZ/WMNUdMm35Z+4e4rHClAZxU2dUsbHrmw==", - "license": "MIT", - "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-positioning": "^9.22.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.14.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-popover/-/react-popover-9.14.6.tgz", + "integrity": "sha512-BMDS4acfZbg/44za41s38PzRplqid5ardTA2Xcjq77YPwhoaLRVAPztU1/9/ivss3tuPDn1+20knyv+bBhInzw==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-positioning": "^9.23.1", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3014,14 +3013,14 @@ } }, "node_modules/@fluentui/react-portal": { - "version": "9.8.11", - "resolved": "https://registry.npmjs.org/@fluentui/react-portal/-/react-portal-9.8.11.tgz", - "integrity": "sha512-2eg4MdW7e2UGRYWPg05GCytAjWYNd55YOP9+iUDINoQwwto9oeFTtZRyn08HYw37cSNqoH24qGz/VBctzTkqDA==", + "version": "9.8.15", + "resolved": "https://registry.npmjs.org/@fluentui/react-portal/-/react-portal-9.8.15.tgz", + "integrity": "sha512-1lXeUn+K31DwpJGQNKIRs6wz1iZOQWsH0gfLF6qy69m1EdR16SqfjprLZ3jI97xJDVatHIuYAvO/5EmmVv60/g==", "license": "MIT", "dependencies": { - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3046,16 +3045,16 @@ } }, "node_modules/@fluentui/react-positioning": { - "version": "9.22.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-positioning/-/react-positioning-9.22.0.tgz", - "integrity": "sha512-i3DLC4jd4MoYSZMYLKQNUTpkjKAJ0snIcihvkrjt2jpvv34CifKJhqVtjFQ470pRW4XNx/pBBX07vdXpA3poxA==", + "version": "9.23.1", + "resolved": "https://registry.npmjs.org/@fluentui/react-positioning/-/react-positioning-9.23.1.tgz", + "integrity": "sha512-QtnF1gOc0pQx5ks0DADpl5Jxh8mh4R82ddtaYu9XdyqBIfRz/SdYOPD6eB99qc7ynZnywuHsNH144W77jO+vdQ==", "license": "MIT", "dependencies": { "@floating-ui/devtools": "^0.2.3", "@floating-ui/dom": "^1.6.12", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1", "use-sync-external-store": "^1.2.0" @@ -3068,16 +3067,17 @@ } }, "node_modules/@fluentui/react-progress": { - "version": "9.4.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-progress/-/react-progress-9.4.16.tgz", - "integrity": "sha512-IWVuD1hQoyIBK+RIGOCTc3HUPkdtOQghJPZ5uGwRrUlxGgpUV1h7rdAApiuQTWitrFfN6bP4PrsJmHT2DM2OFw==", + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-progress/-/react-progress-9.5.4.tgz", + "integrity": "sha512-SLVw/NCrW3pglcF2J7Cu9hc5CAnDq6yyeDuRd4FTxUtMnOpqH+k3AkkLAZDmELKlHe2ZJs5E7jHwFb5kPYcV7A==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3100,17 +3100,17 @@ } }, "node_modules/@fluentui/react-provider": { - "version": "9.22.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-provider/-/react-provider-9.22.15.tgz", - "integrity": "sha512-a+ImgL9DOlylDM4UYPnxQTA3yXxbVj+O0iNEyTZ6fMzdMsHzpALU4GAq6tOyW4L7RaQtRBmNpVfwTCEKpqaTJQ==", + "version": "9.22.20", + "resolved": "https://registry.npmjs.org/@fluentui/react-provider/-/react-provider-9.22.20.tgz", + "integrity": "sha512-Cu97d65ry3t+nbGWxBmkfbmQo3IPyFjxxskQWUkd4yQcC2OB5VKbuaO28gHP8EgZglxkQmoEMf/0sWCo5HI0rQ==", "license": "MIT", "dependencies": { "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/core": "^1.16.0", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" @@ -3123,18 +3123,18 @@ } }, "node_modules/@fluentui/react-radio": { - "version": "9.5.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-radio/-/react-radio-9.5.16.tgz", - "integrity": "sha512-xHRqm+MTkIf6JLEz/dMLlHSL9X+ysXAkig+VOV5QTPZwDIr3SqfJVvBmLNUVmtzf+cmWsRKrrIbVGpFGo/CvxA==", - "license": "MIT", - "dependencies": { - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-radio/-/react-radio-9.6.5.tgz", + "integrity": "sha512-xcyyGvklsmvcSYa95jPVKreJRwfOWVXFmPzWQ4OTSqDKe3zVRvPmkqiBVMwHAORsjkchMUQ6Gdvh0BG0hh6DLw==", + "license": "MIT", + "dependencies": { + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3146,17 +3146,17 @@ } }, "node_modules/@fluentui/react-rating": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-rating/-/react-rating-9.4.0.tgz", - "integrity": "sha512-qVesFNgQ7uuX8z9d8xqxIXn5ax06xffgBr/eAuZfqVYZG5aRrPHHRoiWf0HDrYD4Lb/HRBLPtbNihNxhXj/LEA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-rating/-/react-rating-9.4.4.tgz", + "integrity": "sha512-GhM49vZ5018vE/FBDs8LXZVmK1SVdG/KNbJ0jDvQSUp4acgonnSxe9dPej2l8l6l3TZ2aEGmeUKwYhxQC3DcJg==", "license": "MIT", "dependencies": { "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3168,17 +3168,17 @@ } }, "node_modules/@fluentui/react-search": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-search/-/react-search-9.4.0.tgz", - "integrity": "sha512-/uBJv2IK7gN7Mt+diByV+0COvKnkluvJ2gCnYQfeOpGjPS97IIeGUIa2xpfSq+eB7Ri++1OWlK61jRjlItDmsw==", + "version": "9.4.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-search/-/react-search-9.4.6.tgz", + "integrity": "sha512-cKe1nPhl08ity4wEktUdX2UGWlcVkwSjVckmCHoyVKKP1w/9jlL9KqzkEHaOUpo0NUr8vHWyOLjZSG5mcJox+g==", "license": "MIT", "dependencies": { "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-input": "^9.8.0", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-input": "^9.8.6", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3190,17 +3190,17 @@ } }, "node_modules/@fluentui/react-select": { - "version": "9.4.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-select/-/react-select-9.4.16.tgz", - "integrity": "sha512-YsHMZsiKxH8suBtNTBXhtsvjM0u9UUXH641cEumgtjUz7SzeKNc/cWToLVyNz7GIoANL49rvubkByTeAQVCo2g==", + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-select/-/react-select-9.5.5.tgz", + "integrity": "sha512-cUu9gTlrlXdnVJ0GhzjTXfpMckyiFgNc1eY4HmcWbAIN6si250CsPkrdyvuqCwklWR9A/JHH3RD8RU+GUFmn7g==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3212,12 +3212,12 @@ } }, "node_modules/@fluentui/react-shared-contexts": { - "version": "9.26.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-shared-contexts/-/react-shared-contexts-9.26.2.tgz", - "integrity": "sha512-upKXkwlIp5oIhELr4clAZXQkuCd4GDXM6GZEz8BOmRO+PnxyqmycCXvxDxsmi6XN+0vkGM4joiIgkB14o/FctQ==", + "version": "9.26.3", + "resolved": "https://registry.npmjs.org/@fluentui/react-shared-contexts/-/react-shared-contexts-9.26.3.tgz", + "integrity": "sha512-+FvkVJqX9yuZaZ//FWtNA4O0V33UA8Q82/5shp2fCox6HItENn87E5QecUobsodeYJ6xCAbTHccMh+LRiZhHJA==", "license": "MIT", "dependencies": { - "@fluentui/react-theme": "^9.2.1", + "@fluentui/react-theme": "^9.2.2", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -3226,16 +3226,16 @@ } }, "node_modules/@fluentui/react-skeleton": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-skeleton/-/react-skeleton-9.7.0.tgz", - "integrity": "sha512-dSmB0jiz/swu/zquCbHx4nS0HKLJ09N6m9+3HNXY/t24JtK4gFNcl0jQssjIsgupeA8xWsjP7+b+VxUeWq1h9Q==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-skeleton/-/react-skeleton-9.7.5.tgz", + "integrity": "sha512-CpjtweojZjeDzOYdOgMneKfKrLosxR5xe1Fw3sySws8fZP0bqF7u6MKaJYUhRL6sFt4XK1e2gBFzJLe5IbAgRA==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3247,17 +3247,17 @@ } }, "node_modules/@fluentui/react-slider": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-slider/-/react-slider-9.6.0.tgz", - "integrity": "sha512-AlSU3GVVgcuiHL0b5xcSy8KDPZbN7yuFZMjKRe1yInK9mGfc6LuUB73EQoSIdJxRw74lMAC+am/+xCtjONlc9w==", + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-slider/-/react-slider-9.6.5.tgz", + "integrity": "sha512-GTDrlMaacPsXzrHPmmb+HdwOlJ6DSk9svFYHq/uDlneHPqGaBHH5RkkpRi3iqAQySd9YjqXVhUk9yOAgbci4OQ==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3269,18 +3269,18 @@ } }, "node_modules/@fluentui/react-spinbutton": { - "version": "9.5.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-spinbutton/-/react-spinbutton-9.5.16.tgz", - "integrity": "sha512-V4U9PSJM26BXrFqJ9K/VYYQeusBf8ldx5KOlZZ7hRamPsKTS5hyytWrF39lTLqCRlGckXPCLNzJpb1DLB+ID1g==", + "version": "9.6.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-spinbutton/-/react-spinbutton-9.6.5.tgz", + "integrity": "sha512-ooGgs1QJM2inLGlO0LDLAtOTw/erG78ZfPNhbeKTkOAcNYnoOykw9WQ3rTqYRuNmFfhIRyusjcDky5aiYYCqJg==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-field": "^9.4.16", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3292,16 +3292,16 @@ } }, "node_modules/@fluentui/react-spinner": { - "version": "9.8.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-spinner/-/react-spinner-9.8.0.tgz", - "integrity": "sha512-E1jMQueIvEEHdON6itZb3KxP67ACv+IKU/APNvQPftZVEpAZWn265T1EIe3OXAnAFHbXI3MjFcVxV9tu8+6yeg==", + "version": "9.8.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-spinner/-/react-spinner-9.8.5.tgz", + "integrity": "sha512-75LFy1x95R6UB50uaxNCgrlQJ72PodO15vrCawcWkVF4DkR+RynpZLoQNIPAABpbNM6ZD+Cdq9fnvYF+d1U1ww==", "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3333,19 +3333,19 @@ "license": "0BSD" }, "node_modules/@fluentui/react-swatch-picker": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-swatch-picker/-/react-swatch-picker-9.5.0.tgz", - "integrity": "sha512-sl7MifqQGR4QGDhhgBIYc25YgPuFQW7+BOfNRMO5DYPq33lX5xHNcczhXywcBESAVHrjM0MC1lsE7glv6gU8RA==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@fluentui/react-swatch-picker/-/react-swatch-picker-9.6.0.tgz", + "integrity": "sha512-5s8P3MQyTOsCNoGiKMUvVufJXJ+0Hb0KDWL79CvGsJGpwnN9uhKHuVft3MkfUl+SOExacv+OqqzzMFk+wB4HTg==", "license": "MIT", "dependencies": { - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-field": "^9.4.16", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3357,19 +3357,19 @@ } }, "node_modules/@fluentui/react-switch": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-switch/-/react-switch-9.7.0.tgz", - "integrity": "sha512-fSgbLWmB+O7BREZsT9QvXsqRB39+DXMNkJwsVyRnzZ9XboUHTeN7fVGEuvWQdj8HTjtYE2YYfGUXFo3fST88xA==", + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-switch/-/react-switch-9.7.5.tgz", + "integrity": "sha512-YTHlC8FA47A/0zspQq2K31FQ9lPhe0bO3Y30FRpaubmia1I+XqbmenyGNRxMhz7CYYVU7kbMb6o458eDrfutxQ==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-label": "^9.3.15", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-label": "^9.4.4", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3381,23 +3381,23 @@ } }, "node_modules/@fluentui/react-table": { - "version": "9.19.12", - "resolved": "https://registry.npmjs.org/@fluentui/react-table/-/react-table-9.19.12.tgz", - "integrity": "sha512-4SgGOAAL9P1gi9A6286SHGde6JJnT1uiT/st7wHJOMhbo8QE6+Cfn5MgH9LnvMbW+RxZ8m0NV8sMPi9QorAMZg==", + "version": "9.19.19", + "resolved": "https://registry.npmjs.org/@fluentui/react-table/-/react-table-9.19.19.tgz", + "integrity": "sha512-w2T+EsBATODdu9zbzXL3dho2ZTVvW5ep57yXFT9r+6X2gz+cNUejVGCChWaSxL+/1y3KTt1dyE0JCquRfEST3g==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-avatar": "^9.10.3", - "@fluentui/react-checkbox": "^9.5.17", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-avatar": "^9.11.5", + "@fluentui/react-checkbox": "^9.6.4", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-radio": "^9.5.16", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-radio": "^9.6.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3409,17 +3409,17 @@ } }, "node_modules/@fluentui/react-tabs": { - "version": "9.11.2", - "resolved": "https://registry.npmjs.org/@fluentui/react-tabs/-/react-tabs-9.11.2.tgz", - "integrity": "sha512-zmWzySlPM9EwHJNW0/JhyxBCqBvmfZIj1OZLdRDpbPDsKjhO0aGZV6WjLHFYJmq58kbN0wHKUbxc7LfafHHUwA==", + "version": "9.12.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-tabs/-/react-tabs-9.12.4.tgz", + "integrity": "sha512-YsaqjDFXcC8nvEAW3QpwdD1xIpzzyLFdftqXDA8ULmb2gmeRNWqPClCHftLa0QEIyLAIhX5pXDejHLDPFnQ2mg==", "license": "MIT", "dependencies": { - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3431,14 +3431,14 @@ } }, "node_modules/@fluentui/react-tabster": { - "version": "9.26.16", - "resolved": "https://registry.npmjs.org/@fluentui/react-tabster/-/react-tabster-9.26.16.tgz", - "integrity": "sha512-napGx7dGdLoKoUpKlzc2Til43UMUTtr9J1GWrOFvCT6aZLTox5GjtoxQM/IPhcZ09jJOOUMbVF8ScA5u3/uyTA==", + "version": "9.26.17", + "resolved": "https://registry.npmjs.org/@fluentui/react-tabster/-/react-tabster-9.26.17.tgz", + "integrity": "sha512-kr6el429OvaMD1sSIOt9qyoQTbbgz2zLar81qM9OTi2lj9vhclHSgwaHT7x5yTV0a2LfYQ02emEJeG5NHVAn2w==", "license": "MIT", "dependencies": { - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1", "keyborg": "^2.14.1", @@ -3451,26 +3451,42 @@ "react-dom": ">=16.14.0 <20.0.0" } }, + "node_modules/@fluentui/react-tabster/node_modules/tabster": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/tabster/-/tabster-8.8.0.tgz", + "integrity": "sha512-eGFXgtvKOQP5BywDI9Ngs+Atm6TRj45epAAqWKyVoi+HmOmdamEB//1H/FttLdNly/+Cz+GJ4RN8TnXTw0KwfA==", + "license": "MIT", + "dependencies": { + "keyborg": "^2.14.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@fluentui/react-tabster/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@fluentui/react-tag-picker": { - "version": "9.8.3", - "resolved": "https://registry.npmjs.org/@fluentui/react-tag-picker/-/react-tag-picker-9.8.3.tgz", - "integrity": "sha512-dM+Cl+Y1FDYBbpa5MNWzxGLCrcAlz0B2n90Fjt2LKeA4S5+zWH3/vzH3U9M0YxJCKe7S/5XlAtgO6VwDYu3CRw==", + "version": "9.10.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-tag-picker/-/react-tag-picker-9.10.2.tgz", + "integrity": "sha512-9T+XWOgBBcPwV4vTOyrawVNxnYvIZbngQ1lpcEwec0Ap51pRIr7DxG0Z459S2HB4qZbF2JJ+xX2z4Snx9Y9QUQ==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-combobox": "^9.16.18", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-field": "^9.4.16", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-combobox": "^9.17.5", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-field": "^9.5.4", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-positioning": "^9.22.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-tags": "^9.7.18", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-positioning": "^9.23.1", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-tags": "^9.9.4", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3482,20 +3498,20 @@ } }, "node_modules/@fluentui/react-tags": { - "version": "9.7.18", - "resolved": "https://registry.npmjs.org/@fluentui/react-tags/-/react-tags-9.7.18.tgz", - "integrity": "sha512-8SIdeU3jjkvbIlwno/VuAk5zoMuk1qBfLgSIrFXG/PJWI7hjWL5S6gT/5AqAZYQMzftHZkm6hRnLJhWpgHqllA==", + "version": "9.9.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-tags/-/react-tags-9.9.4.tgz", + "integrity": "sha512-vwE56xx1sWdem1PejayTquRg7a+Uef/o10tf8fJ2z2w+y4jS98DcXVlH4BG57ZPWef1DGVuCFh1xUPldYLt8Zg==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-avatar": "^9.10.3", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-avatar": "^9.11.5", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3507,21 +3523,21 @@ } }, "node_modules/@fluentui/react-teaching-popover": { - "version": "9.6.19", - "resolved": "https://registry.npmjs.org/@fluentui/react-teaching-popover/-/react-teaching-popover-9.6.19.tgz", - "integrity": "sha512-rSidcDbgnJaZPA7RW6uJqQqEga/2ON5PuuLauZFLGxZyuAAZv+8dwUBsxHYwneVpbFIITF3GX4f1sdH5Ei+lqQ==", + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-teaching-popover/-/react-teaching-popover-9.7.4.tgz", + "integrity": "sha512-XPnHrNlb6/wj1Gl0hLL306ANtlgDLxfXdjApdGS2aSNp136C1I3pJvRmHxW9byEaY26Wbn4Gb+cC1NI2Fb9lEQ==", "license": "MIT", "dependencies": { - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-button": "^9.9.0", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-button": "^9.11.0", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-popover": "^9.14.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-popover": "^9.14.6", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1", "use-sync-external-store": "^1.2.0" @@ -3534,15 +3550,15 @@ } }, "node_modules/@fluentui/react-text": { - "version": "9.6.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-text/-/react-text-9.6.15.tgz", - "integrity": "sha512-YB1azhq8MGfnYTGlEAX1mzcFZ6CvqkkaxaCogU4TM9BtPgQ1YUAxE01RMenl8VVi8W9hNbJKkuc8R8GzYwzT4Q==", + "version": "9.6.19", + "resolved": "https://registry.npmjs.org/@fluentui/react-text/-/react-text-9.6.19.tgz", + "integrity": "sha512-mgGdeRfDHOWJHUeywwebJAVOqZeQLUK09YMbJsETskELsDH1JOVHOGPBrBs+OKnoVNh43hYwqpDqScFT5z+nrg==", "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3554,16 +3570,16 @@ } }, "node_modules/@fluentui/react-textarea": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/@fluentui/react-textarea/-/react-textarea-9.7.0.tgz", - "integrity": "sha512-AaBcoTHQv1dZ36w0Uoy8bnnkO0Ag7T0+6ZbjkiSGu50245WvK+MJawuCW91UuZvEUR7MPaAK/TDXWlHYWlMqRA==", + "version": "9.7.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-textarea/-/react-textarea-9.7.6.tgz", + "integrity": "sha512-7evgM3j9uUaXUHDhsCygrL0xsWwG+AUs3w6gQgBb8YNzXZa4q2sQJaxv2IRyDxNIoIp6pBZDI4F9nZlNlLtrdg==", "license": "MIT", "dependencies": { - "@fluentui/react-field": "^9.4.16", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-field": "^9.5.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3575,12 +3591,12 @@ } }, "node_modules/@fluentui/react-theme": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@fluentui/react-theme/-/react-theme-9.2.1.tgz", - "integrity": "sha512-lJxfz7LmmglFz+c9C41qmMqaRRZZUPtPPl9DWQ79vH+JwZd4dkN7eA78OTRwcGCOTPEKoLTX72R+EFaWEDlX+w==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-theme/-/react-theme-9.2.2.tgz", + "integrity": "sha512-yUQMQkMLGvo6+Q/RITxpLh00STvI299gnk1IV/QHc2DW5EKiCcmIp9xma7+lJzfLACfDkwJDy5hZSl+7MIYf9w==", "license": "MIT", "dependencies": { - "@fluentui/tokens": "1.0.0-alpha.23", + "@fluentui/tokens": "1.0.0-alpha.24", "@swc/helpers": "^0.5.1" } }, @@ -3647,22 +3663,22 @@ "license": "0BSD" }, "node_modules/@fluentui/react-toast": { - "version": "9.7.15", - "resolved": "https://registry.npmjs.org/@fluentui/react-toast/-/react-toast-9.7.15.tgz", - "integrity": "sha512-iuk4rf/WumpGrNIpRVLNamlPBY0rT9BhI4qTnVmzXqz5pY+8GmAq/TKUPER9/withtQW8V9srj91FWblxzpHRg==", + "version": "9.8.2", + "resolved": "https://registry.npmjs.org/@fluentui/react-toast/-/react-toast-9.8.2.tgz", + "integrity": "sha512-Zm6J35ZBQ6si/tnWdEZnrhtoqkCUBjr+2z5uNJAZUP8RQSyZA78JwgkRXn7GxJ7VHeNQVUQN4SjuRCFmD6oJ4w==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3674,20 +3690,20 @@ } }, "node_modules/@fluentui/react-toolbar": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-toolbar/-/react-toolbar-9.7.5.tgz", - "integrity": "sha512-mafkxOQAdmUyDqaWC0FLMxYOCx2dEYafDK+3V3IQALuCuZ90nrW3lH9Uh6/GdkcXQ21ykiOmkGoxtQ3xNt6YeQ==", - "license": "MIT", - "dependencies": { - "@fluentui/react-button": "^9.9.0", - "@fluentui/react-context-selector": "^9.2.15", - "@fluentui/react-divider": "^9.7.0", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-radio": "^9.5.16", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.8.4", + "resolved": "https://registry.npmjs.org/@fluentui/react-toolbar/-/react-toolbar-9.8.4.tgz", + "integrity": "sha512-fFLTzT6lV4u58xkLy6mdWFTxdQAPYk8wDAKplHHKtCfV1hilFH2IPIcRakyN2aLS3c3hS87QDWtEBO6nfXum+g==", + "license": "MIT", + "dependencies": { + "@fluentui/react-button": "^9.11.0", + "@fluentui/react-context-selector": "^9.2.19", + "@fluentui/react-divider": "^9.7.4", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-radio": "^9.6.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3699,19 +3715,19 @@ } }, "node_modules/@fluentui/react-tooltip": { - "version": "9.9.3", - "resolved": "https://registry.npmjs.org/@fluentui/react-tooltip/-/react-tooltip-9.9.3.tgz", - "integrity": "sha512-a351JFoaBAOn0SnQ76tzuNv2ieHzAS+VO8Ncy4m9/emrIs5lvBBfKX8fvA4/efVxY+683XEQdoL1LuApuJuTWw==", - "license": "MIT", - "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-portal": "^9.8.11", - "@fluentui/react-positioning": "^9.22.0", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "version": "9.10.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-tooltip/-/react-tooltip-9.10.5.tgz", + "integrity": "sha512-GiQiu2NeD9GTJHOCqjARMR4eQIN2815a73ZBvXOxyB0H1Z4I/IpiHdsoHMiBIGv6OhKWASO4KYh0RPuJ1TiRSg==", + "license": "MIT", + "dependencies": { + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-portal": "^9.8.15", + "@fluentui/react-positioning": "^9.23.1", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3723,26 +3739,26 @@ } }, "node_modules/@fluentui/react-tree": { - "version": "9.15.14", - "resolved": "https://registry.npmjs.org/@fluentui/react-tree/-/react-tree-9.15.14.tgz", - "integrity": "sha512-gjLlvjFD64SREUeKaSocueUnntSofplU4EMZToaYqGixlzAouHzTFEZeiohuuYjVWhOn/WBBZsOEdr7bPva+kQ==", + "version": "9.16.5", + "resolved": "https://registry.npmjs.org/@fluentui/react-tree/-/react-tree-9.16.5.tgz", + "integrity": "sha512-KTlUEjfcYHq02Hd63KXvqIBxBbXT1uheuu5Bzr202FlY7lsIEveF6EJU9h09GLJQkFYro/SYy/V3qxapM3nDHg==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-aria": "^9.17.10", - "@fluentui/react-avatar": "^9.10.3", - "@fluentui/react-button": "^9.9.0", - "@fluentui/react-checkbox": "^9.5.17", - "@fluentui/react-context-selector": "^9.2.15", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-aria": "^9.17.14", + "@fluentui/react-avatar": "^9.11.5", + "@fluentui/react-button": "^9.11.0", + "@fluentui/react-checkbox": "^9.6.4", + "@fluentui/react-context-selector": "^9.2.19", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-motion": "^9.13.0", - "@fluentui/react-motion-components-preview": "^0.15.2", - "@fluentui/react-radio": "^9.5.16", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-tabster": "^9.26.13", - "@fluentui/react-theme": "^9.2.1", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-motion": "^9.16.2", + "@fluentui/react-motion-components-preview": "^0.15.7", + "@fluentui/react-radio": "^9.6.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-tabster": "^9.26.17", + "@fluentui/react-theme": "^9.2.2", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3754,13 +3770,13 @@ } }, "node_modules/@fluentui/react-utilities": { - "version": "9.26.5", - "resolved": "https://registry.npmjs.org/@fluentui/react-utilities/-/react-utilities-9.26.5.tgz", - "integrity": "sha512-lLWhnfMVEu+cWx3o9uTA5sDwo4U9PnpDJGVtheZ1bOCdh1YiQsJxeFM7n6nLE72UK2w+ATkGZQPYZA4QW1JLPQ==", + "version": "9.26.6", + "resolved": "https://registry.npmjs.org/@fluentui/react-utilities/-/react-utilities-9.26.6.tgz", + "integrity": "sha512-a14Iihg2MmRSq1fxqr/4ffMl8VO0gYWRHAYKENz2PoL+oWnCtkMz4PDglIY8rVa7RADwXYI65XOjui0BjIFMvQ==", "license": "MIT", "dependencies": { - "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-shared-contexts": "^9.26.2", + "@fluentui/keyboard-keys": "^9.0.9", + "@fluentui/react-shared-contexts": "^9.26.3", "@swc/helpers": "^0.5.1" }, "peerDependencies": { @@ -3769,14 +3785,14 @@ } }, "node_modules/@fluentui/react-virtualizer": { - "version": "9.0.0-alpha.111", - "resolved": "https://registry.npmjs.org/@fluentui/react-virtualizer/-/react-virtualizer-9.0.0-alpha.111.tgz", - "integrity": "sha512-yku++0779Ve1RNz6y/HWjlXKd2x1wCSbWMydT2IdCICBVwolXjPYMpkqqZUSjbJ0N9gl6BfsCBpU9Dfe2bR8Zg==", + "version": "9.0.0-alpha.115", + "resolved": "https://registry.npmjs.org/@fluentui/react-virtualizer/-/react-virtualizer-9.0.0-alpha.115.tgz", + "integrity": "sha512-8ZOFmt5eSkiH9jWYvfmMkef+2jGvWrDeXjUptZeesgn4VRXwQu4SUKPLWqZ+ETcYXX/0leGEDl6KItdp/XxbmA==", "license": "MIT", "dependencies": { - "@fluentui/react-jsx-runtime": "^9.4.1", - "@fluentui/react-shared-contexts": "^9.26.2", - "@fluentui/react-utilities": "^9.26.2", + "@fluentui/react-jsx-runtime": "^9.4.5", + "@fluentui/react-shared-contexts": "^9.26.3", + "@fluentui/react-utilities": "^9.26.6", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, @@ -3892,9 +3908,9 @@ } }, "node_modules/@fluentui/tokens": { - "version": "1.0.0-alpha.23", - "resolved": "https://registry.npmjs.org/@fluentui/tokens/-/tokens-1.0.0-alpha.23.tgz", - "integrity": "sha512-uxrzF9Z+J10naP0pGS7zPmzSkspSS+3OJDmYIK3o1nkntQrgBXq3dBob4xSlTDm5aOQ0kw6EvB9wQgtlyy4eKQ==", + "version": "1.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@fluentui/tokens/-/tokens-1.0.0-alpha.24.tgz", + "integrity": "sha512-YrjGQfnxBR+9o8wXyTflON3Ohsv7OrZWYLVRU+7RaiFO1gcu36iDm3WjudlVmYuWRLDO0Gmxotm6RQNhbZuEQQ==", "license": "MIT", "dependencies": { "@swc/helpers": "^0.5.1" @@ -8331,80 +8347,6 @@ "webpack": "5.105.4" } }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/types": "30.4.1", - "@sinonjs/fake-timers": "^15.4.0", - "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@microsoft/spfx-web-build-rig/node_modules/@rushstack/heft-config-file": { "version": "0.20.10", "resolved": "https://registry.npmjs.org/@rushstack/heft-config-file/-/heft-config-file-0.20.10.tgz", @@ -8512,27 +8454,6 @@ } } }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/@sinonjs/fake-timers": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", - "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, "node_modules/@microsoft/spfx-web-build-rig/node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -8565,156 +8486,6 @@ } } }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", - "picomatch": "^4.0.3", - "pretty-format": "30.4.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "jest-util": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/types": "30.4.1", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.3" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/jest-validate": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.4.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@microsoft/spfx-web-build-rig/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -8722,39 +8493,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@microsoft/spfx-web-build-rig/node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@microsoft/spfx-web-build-rig/node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -40105,22 +39843,6 @@ "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", "license": "MIT" }, - "node_modules/tabster": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/tabster/-/tabster-8.8.0.tgz", - "integrity": "sha512-eGFXgtvKOQP5BywDI9Ngs+Atm6TRj45epAAqWKyVoi+HmOmdamEB//1H/FttLdNly/+Cz+GJ4RN8TnXTw0KwfA==", - "license": "MIT", - "dependencies": { - "keyborg": "^2.14.0", - "tslib": "^2.8.1" - } - }, - "node_modules/tabster/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", diff --git a/package.json b/package.json index 6b4765222..5849cafe3 100644 --- a/package.json +++ b/package.json @@ -192,7 +192,7 @@ "src/common/telemetry/(.*)$": "identity-obj-proxy", "@pnp/sp": "identity-obj-proxy", "'@pnp/sp/fields": "identity-obj-proxy", - "ControlStrings": "identity-obj-proxy", + "ControlStrings": "/tests/__mocks__/ControlStrings.js", "\\.(css|scss)$": "identity-obj-proxy" } } diff --git a/src/DetailsList.ts b/src/DetailsList.ts new file mode 100644 index 000000000..e1bb548ec --- /dev/null +++ b/src/DetailsList.ts @@ -0,0 +1,26 @@ +export { + DetailsList, + CheckboxVisibility, + ColumnActionsMode, + ColumnDragEndLocation, + ConstrainMode, + DetailsListLayoutMode, + DetailsListSelectionMode, +} from './controls/DetailsList'; +export type { + DetailsListKey, + DetailsListScrollToMode, + IColumn, + IColumnReorderOptions, + IDetailsGroupHeaderProps, + IDetailsHeaderProps, + IDetailsList, + IDetailsListFieldProps, + IDetailsListGroup, + IDetailsListProps, + IDetailsListRowDragDropEvents, + IDetailsListSelectionChange, + IDetailsListShimmerRowProps, + IDetailsRowProps, + IDetailsListStyles, +} from './controls/DetailsList'; diff --git a/src/controls/DetailsList/DetailsList.test.tsx b/src/controls/DetailsList/DetailsList.test.tsx new file mode 100644 index 000000000..f70848783 --- /dev/null +++ b/src/controls/DetailsList/DetailsList.test.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { + CheckboxVisibility, + DetailsList, + DetailsListSelectionMode, + IColumn, + IDetailsListGroup, +} from '../../DetailsList'; + +interface ITestItem { + id: string; + name: string; +} + +const columns: IColumn[] = [ + { key: 'name', name: 'Name', fieldName: 'name', minWidth: 120 }, +]; + +const items: ITestItem[] = [ + { id: 'one', name: 'One' }, + { id: 'two', name: 'Two' }, +]; + +const groups: IDetailsListGroup[] = [ + { key: 'planning', name: 'Planning', startIndex: 0, count: items.length }, +]; + +describe('DetailsList', () => { + let detailsList: ReactWrapper | undefined; + + afterEach(() => { + detailsList?.unmount(); + detailsList = undefined; + }); + + it('renders with Fluent UI 9 controls on the React 17 runtime', () => { + detailsList = mount( + item.id} + selectionMode={DetailsListSelectionMode.multiple} + checkboxVisibility={CheckboxVisibility.always} + />, + ); + + expect(detailsList.find('[role="grid"]').exists()).toBe(true); + expect(detailsList.find('[role="row"]').length).toBe(3); + expect(detailsList.find('input[type="checkbox"]').length).toBe(3); + expect(detailsList.text()).toContain('One'); + expect(detailsList.text()).toContain('Two'); + }); + + it('aligns group headers with the leading edge', () => { + detailsList = mount( + item.id} + />, + ); + + expect(detailsList.find('button[role="gridcell"]').first().prop('style')).toMatchObject({ + justifyContent: 'flex-start', + textAlign: 'left', + }); + }); +}); diff --git a/src/controls/DetailsList/DetailsList.tsx b/src/controls/DetailsList/DetailsList.tsx new file mode 100644 index 000000000..40c8c0276 --- /dev/null +++ b/src/controls/DetailsList/DetailsList.tsx @@ -0,0 +1,617 @@ +import * as React from 'react'; +import { IdPrefixProvider, Spinner, Text, mergeClasses } from '@fluentui/react-components'; +import type { + DetailsListKey, + DetailsListScrollToMode, + IDetailsList, + IDetailsListProps, +} from './DetailsList.types'; +import { + CheckboxVisibility, + DetailsListLayoutMode, + SelectionMode, +} from './DetailsList.types'; +import { + buildDetailsListEntries, + calculateEntryOffsets, + findEntryIndexAtOffset, +} from './DetailsList.utils'; +import { + DETAILS_LIST_COMPACT_ROW_HEIGHT, + DETAILS_LIST_DEFAULT_HEIGHT, + DETAILS_LIST_DEFAULT_ROW_HEIGHT, + DETAILS_LIST_SELECTION_COLUMN_WIDTH, + useDetailsListStyles, +} from './useDetailsListStyles'; +import { renderDetailsListContent } from './DetailsListContent'; +import { DetailsListGroupHeader } from './DetailsListGroupHeader'; +import { DetailsListHeader } from './DetailsListHeader'; +import { DetailsListRow } from './DetailsListRow'; +import { DetailsListShimmerRows } from './DetailsListShimmerRows'; +import { useDetailsListColumns } from './useDetailsListColumns'; +import { useDetailsListGroups } from './useDetailsListGroups'; +import { useDetailsListSelection } from './useDetailsListSelection'; +import { useDetailsListViewport } from './useDetailsListViewport'; +import * as strings from 'ControlStrings'; + +const DEFAULT_GROUP_HEADER_HEIGHT = 36; +const DEFAULT_OVERSCAN_COUNT = 5; +const DEFAULT_INFINITE_SCROLL_THRESHOLD = 160; +const DEFAULT_SHIMMER_LINES = 10; + +// SPFx 1.23 hosts React 17, which does not expose React.useId. Keep the +// generated id stable for the lifetime of the component without requiring a +// runtime shim or a second React dependency. +let nextDetailsListId = 0; + +const useDetailsListId = (): string => { + const idRef = React.useRef(); + if (idRef.current === undefined) { + nextDetailsListId += 1; + idRef.current = `details-list-${nextDetailsListId}`; + } + return idRef.current; +}; + +function DetailsListInner( + props: IDetailsListProps, + forwardedRef: React.ForwardedRef>, +): React.ReactElement { + const { + items, + columns, + getKey, + setKey, + componentRef, + className, + styles: styleOverrides, + height, + compact = false, + layoutMode = DetailsListLayoutMode.justified, + isHeaderVisible = true, + selectionMode = SelectionMode.multiple, + checkboxVisibility = CheckboxVisibility.onHover, + selectedKeys, + defaultSelectedKeys = [], + selectionPreservedOnEmptyClick = false, + isSelectedOnFocus = false, + disabledItemKeys, + onSelectionChanged, + onActiveItemChanged, + onItemInvoked, + onItemContextMenu, + onColumnHeaderClick, + onColumnHeaderContextMenu, + onColumnResize, + columnReorderOptions, + dragDropEvents, + onRenderItemColumn, + onRenderField, + onRenderRow, + onRenderDetailsHeader, + onRenderDetailsFooter, + groups, + onToggleGroup, + onRenderGroupHeader, + enableVirtualization, + rowHeight, + groupHeaderHeight = DEFAULT_GROUP_HEADER_HEIGHT, + overscanCount = DEFAULT_OVERSCAN_COUNT, + initialFocusedIndex = 0, + onShouldVirtualize, + enableInfiniteScroll = false, + infiniteScrollThreshold = DEFAULT_INFINITE_SCROLL_THRESHOLD, + onLoadMore, + hasNextPage = false, + isLoadingMore = false, + isLoading = false, + enableShimmer = false, + shimmerLines = DEFAULT_SHIMMER_LINES, + ariaLabelForShimmer, + onRenderCustomPlaceholder, + loadingMessage, + loadingMoreMessage, + emptyState, + ariaLabelForGrid, + ariaLabelForListHeader, + ariaLabelForSelectAllCheckbox, + ariaLabelForSelectionColumn, + getRowAriaLabel, + getRowAriaDescribedBy, + } = props; + + const resolvedAriaLabelForGrid = ariaLabelForGrid ?? strings.DetailsListAriaLabel; + const resolvedAriaLabelForListHeader = ariaLabelForListHeader ?? strings.DetailsListHeaderAriaLabel; + const resolvedAriaLabelForSelectAllCheckbox = ariaLabelForSelectAllCheckbox ?? strings.DetailsListSelectAllRowsAriaLabel; + const resolvedAriaLabelForSelectionColumn = ariaLabelForSelectionColumn ?? strings.DetailsListSelectionAriaLabel; + + const classes = useDetailsListStyles(); + const selectionControlName = useDetailsListId(); + const headerViewportRef = React.useRef(null); + const draggedItemRef = React.useRef<{ item: T; index: number }>(); + const initialFocusAppliedRef = React.useRef(false); + + const [, forceRender] = React.useReducer((version: number) => version + 1, 0); + const [activeItemIndex, setActiveItemIndex] = React.useState(initialFocusedIndex ?? 0); + const [dropTargetKey, setDropTargetKey] = React.useState(); + + const { + viewportRef, + viewportWidth, + viewportHeight, + scrollTop, + setScrollTop, + isLoadingMoreInternally, + handleScroll, + handleScrollIntent, + } = useDetailsListViewport({ + itemCount: items.length, + enableInfiniteScroll, + infiniteScrollThreshold, + onLoadMore, + hasNextPage, + isLoadingMore, + }); + + const effectiveRowHeight = rowHeight ?? (compact ? DETAILS_LIST_COMPACT_ROW_HEIGHT : DETAILS_LIST_DEFAULT_ROW_HEIGHT); + const shouldVirtualize = + onShouldVirtualize?.(items.length) ?? enableVirtualization ?? items.length > 100; + const effectiveHeight = + height ?? (shouldVirtualize || enableInfiniteScroll ? DETAILS_LIST_DEFAULT_HEIGHT : undefined); + const showSelectionColumn = + selectionMode !== SelectionMode.none && checkboxVisibility !== CheckboxVisibility.hidden; + const selectionColumnWidth = showSelectionColumn ? DETAILS_LIST_SELECTION_COLUMN_WIDTH : 0; + const { + getItemKey, + selectedKeySet, + disabledKeySet, + selectedItems, + isAllSelected, + isPartiallySelected, + commitSelection, + selectItem, + toggleSelectAll, + } = useDetailsListSelection({ + items, + getKey, + setKey, + selectionMode, + selectedKeys, + defaultSelectedKeys, + disabledItemKeys, + onSelectionChanged, + }); + + const { + orderedColumns, + columnWidths, + contentWidth, + gridWidth, + gridTemplateColumns, + draggedColumnIndexRef, + columnDropLocationRef, + resizeColumn, + beginColumnResize, + autoFitColumn, + isColumnDraggable, + handleColumnDrop, + moveColumn, + } = useDetailsListColumns({ + columns, + viewportWidth, + selectionColumnWidth, + layoutMode, + viewportRef, + onColumnResize, + columnReorderOptions, + }); + + const { isGroupCollapsed, toggleGroup } = useDetailsListGroups({ onToggleGroup }); + + const entries = React.useMemo( + () => + buildDetailsListEntries( + items, + groups, + effectiveRowHeight, + groupHeaderHeight, + getItemKey, + isGroupCollapsed, + ), + [effectiveRowHeight, getItemKey, groupHeaderHeight, groups, isGroupCollapsed, items], + ); + const entryOffsets = React.useMemo(() => calculateEntryOffsets(entries), [entries]); + const totalBodyHeight = entryOffsets[entryOffsets.length - 1] ?? 0; + const firstVisibleEntry = findEntryIndexAtOffset(entryOffsets, scrollTop); + const lastVisibleEntry = findEntryIndexAtOffset( + entryOffsets, + scrollTop + Math.max(viewportHeight, effectiveRowHeight), + ); + const renderStartIndex = shouldVirtualize + ? Math.max(0, firstVisibleEntry - overscanCount) + : 0; + const renderEndIndex = shouldVirtualize + ? Math.min(entries.length, lastVisibleEntry + overscanCount + 1) + : entries.length; + const renderedEntries = entries.slice(renderStartIndex, renderEndIndex); + const visibleItemIndexes = React.useMemo( + () => entries.filter((entry) => entry.type === 'item').map((entry) => entry.itemIndex), + [entries], + ); + + const scrollToIndex = React.useCallback( + (itemIndex: number, mode: DetailsListScrollToMode = 'auto'): void => { + const viewport = viewportRef.current; + const entryIndex = entries.findIndex( + (entry) => entry.type === 'item' && entry.itemIndex === itemIndex, + ); + if (!viewport || entryIndex < 0) { + return; + } + + const entryTop = entryOffsets[entryIndex]; + const entryBottom = entryOffsets[entryIndex + 1]; + const visibleTop = viewport.scrollTop; + const visibleBottom = viewport.scrollTop + viewport.clientHeight; + + if (mode === 'top') { + viewport.scrollTop = entryTop; + } else if (mode === 'bottom') { + viewport.scrollTop = entryBottom - viewport.clientHeight; + } else if (mode === 'center') { + viewport.scrollTop = entryTop - (viewport.clientHeight - effectiveRowHeight) / 2; + } else if (entryTop < visibleTop) { + viewport.scrollTop = entryTop; + } else if (entryBottom > visibleBottom) { + viewport.scrollTop = entryBottom - viewport.clientHeight; + } + setScrollTop(viewport.scrollTop); + }, + [effectiveRowHeight, entries, entryOffsets], + ); + + const focusIndex = React.useCallback( + (itemIndex: number, mode: DetailsListScrollToMode = 'auto'): void => { + setActiveItemIndex(itemIndex); + scrollToIndex(itemIndex, mode); + window.requestAnimationFrame(() => { + viewportRef.current + ?.querySelector(`[data-details-row-index="${itemIndex}"]`) + ?.focus(); + }); + }, + [scrollToIndex], + ); + + React.useEffect(() => { + if ( + initialFocusedIndex !== undefined && + !initialFocusAppliedRef.current && + items[initialFocusedIndex] !== undefined + ) { + initialFocusAppliedRef.current = true; + focusIndex(initialFocusedIndex); + } + }, [focusIndex, initialFocusedIndex, items]); + + const imperativeHandle = React.useMemo>( + () => ({ + forceUpdate: forceRender, + focusIndex, + scrollToIndex, + getStartItemIndexInView: () => { + for (let entryIndex = firstVisibleEntry; entryIndex < entries.length; entryIndex += 1) { + const entry = entries[entryIndex]; + if (entry.type === 'item') { + return entry.itemIndex; + } + } + return 0; + }, + updateColumn: (column, options) => { + const currentIndex = orderedColumns.findIndex((candidate) => candidate.key === column.key); + if (options.width !== undefined && currentIndex >= 0) { + resizeColumn(column, currentIndex, options.width); + } + if (options.newColumnIndex !== undefined && currentIndex >= 0) { + moveColumn(column, options.newColumnIndex); + } + }, + getSelectedItems: () => selectedItems, + setSelectedKeys: (keys) => commitSelection(new Set(keys)), + }), + [ + commitSelection, + entries, + firstVisibleEntry, + focusIndex, + orderedColumns, + moveColumn, + resizeColumn, + scrollToIndex, + selectedItems, + ], + ); + + React.useImperativeHandle(forwardedRef, () => imperativeHandle, [imperativeHandle]); + React.useImperativeHandle(componentRef, () => imperativeHandle, [imperativeHandle]); + + const focusAdjacentItem = React.useCallback( + (itemIndex: number, offset: number): void => { + const visibleIndex = visibleItemIndexes.indexOf(itemIndex); + const targetVisibleIndex = Math.max( + 0, + Math.min(visibleItemIndexes.length - 1, visibleIndex + offset), + ); + const targetItemIndex = visibleItemIndexes[targetVisibleIndex]; + if (targetItemIndex !== undefined) { + focusIndex(targetItemIndex); + } + }, + [focusIndex, visibleItemIndexes], + ); + + const handleRowKeyDown = React.useCallback( + (event: React.KeyboardEvent, item: T, itemIndex: number): void => { + const pageSize = Math.max(1, Math.floor(viewportHeight / effectiveRowHeight)); + if (event.key === 'ArrowDown') { + event.preventDefault(); + focusAdjacentItem(itemIndex, 1); + } else if (event.key === 'ArrowUp') { + event.preventDefault(); + focusAdjacentItem(itemIndex, -1); + } else if (event.key === 'PageDown') { + event.preventDefault(); + focusAdjacentItem(itemIndex, pageSize); + } else if (event.key === 'PageUp') { + event.preventDefault(); + focusAdjacentItem(itemIndex, -pageSize); + } else if (event.key === 'Home') { + event.preventDefault(); + const firstIndex = visibleItemIndexes[0]; + if (firstIndex !== undefined) { + focusIndex(firstIndex, 'top'); + } + } else if (event.key === 'End') { + event.preventDefault(); + const lastIndex = visibleItemIndexes[visibleItemIndexes.length - 1]; + if (lastIndex !== undefined) { + focusIndex(lastIndex, 'bottom'); + } + } else if (event.key === ' ') { + event.preventDefault(); + selectItem(item, itemIndex, event, true); + } else if (event.key === 'Enter') { + event.preventDefault(); + onItemInvoked?.(item, itemIndex, event); + } + }, + [ + effectiveRowHeight, + focusAdjacentItem, + focusIndex, + onItemInvoked, + selectItem, + viewportHeight, + visibleItemIndexes, + ], + ); + + const showLoadingMore = isLoadingMore || isLoadingMoreInternally; + + return ( + +
+
+ {isHeaderVisible && ( +
+
+ +
+
+ )} +
{ + handleScroll(event); + if (headerViewportRef.current) { + headerViewportRef.current.scrollLeft = event.currentTarget.scrollLeft; + } + }} + onWheel={(event) => { + if (event.deltaY > 0) { + handleScrollIntent(event.currentTarget); + } + }} + onTouchMove={(event) => handleScrollIntent(event.currentTarget)} + > +
{ + if ( + !selectionPreservedOnEmptyClick && + event.target === event.currentTarget && + selectionMode !== SelectionMode.none + ) { + commitSelection(new Set()); + } + }} + > +
0 && !isLoading && !enableShimmer + ? totalBodyHeight + : undefined, + }} + > + {enableShimmer ? ( + + ) : isLoading && items.length === 0 ? ( +
+ + {loadingMessage !== undefined ? ( + renderDetailsListContent(loadingMessage) + ) : ( + {strings.DetailsListLoading} + )} +
+ ) : entries.length === 0 ? ( +
+ {emptyState !== undefined ? ( + renderDetailsListContent(emptyState) + ) : ( + {strings.DetailsListNoItems} + )} +
+ ) : ( + renderedEntries.map((entry, renderedIndex) => { + const entryIndex = renderStartIndex + renderedIndex; + return ( + + {entry.type === 'group' + ? ( + toggleGroup(entry.group)} + isHeaderVisible={isHeaderVisible} + shouldVirtualize={shouldVirtualize} + entryTop={entryOffsets[entryIndex]} + groupHeaderHeight={groupHeaderHeight} + gridTemplateColumns={gridTemplateColumns} + classes={classes} + style={styleOverrides?.groupHeader} + onRenderGroupHeader={onRenderGroupHeader} + /> + ) + : ( + + )} + + ); + }) + )} +
+ {showLoadingMore && !enableShimmer && ( +
+ + {loadingMoreMessage !== undefined ? ( + renderDetailsListContent(loadingMoreMessage) + ) : ( + {strings.DetailsListLoadingMore} + )} +
+ )} + {onRenderDetailsFooter ? renderDetailsListContent(onRenderDetailsFooter(items)) : null} +
+
+
+
+ + ); +} + +export const DetailsList = React.forwardRef(DetailsListInner) as ( + props: IDetailsListProps & React.RefAttributes>, +) => React.ReactElement; + +(DetailsList as React.FC).displayName = 'DetailsList'; diff --git a/src/controls/DetailsList/DetailsList.types.ts b/src/controls/DetailsList/DetailsList.types.ts new file mode 100644 index 000000000..dde0a4034 --- /dev/null +++ b/src/controls/DetailsList/DetailsList.types.ts @@ -0,0 +1,270 @@ +import type * as React from 'react'; + +export type DetailsListKey = React.Key; + +export enum SelectionMode { + none = 0, + single = 1, + multiple = 2, +} + +/** Public alias that avoids ambiguity with the legacy Fluent UI 8 ListView export. */ +export { SelectionMode as DetailsListSelectionMode }; + +export enum CheckboxVisibility { + onHover = 0, + always = 1, + hidden = 2, +} + +export enum DetailsListLayoutMode { + fixedColumns = 0, + justified = 1, +} + +export enum ConstrainMode { + unconstrained = 0, + horizontalConstrained = 1, +} + +export enum ColumnActionsMode { + disabled = 0, + clickable = 1, + hasDropdown = 2, +} + +export enum ColumnDragEndLocation { + outside = 0, + surface = 1, + header = 2, +} + +export type DetailsListScrollToMode = 'auto' | 'top' | 'bottom' | 'center'; + +export interface IDetailsListColumnDragDropDetails { + draggedColumn: IColumn; + draggedIndex: number; + targetColumn: IColumn; + targetIndex: number; +} + +export interface IColumnReorderOptions { + frozenColumnCountFromStart?: number; + frozenColumnCountFromEnd?: number; + onColumnDragStart?: (dragStarted: boolean) => void; + onColumnDrop?: (details: IDetailsListColumnDragDropDetails) => void; + onDragEnd?: (location: ColumnDragEndLocation) => void; +} + +export interface IColumn { + key: string; + name: string; + fieldName?: keyof T & string; + minWidth: number; + maxWidth?: number; + currentWidth?: number; + flexGrow?: number; + targetWidthProportion?: number; + ariaLabel?: string; + className?: string; + headerClassName?: string; + isRowHeader?: boolean; + isResizable?: boolean; + isMultiline?: boolean; + isPadded?: boolean; + isSorted?: boolean; + isSortedDescending?: boolean; + isFiltered?: boolean; + isGrouped?: boolean; + isIconOnly?: boolean; + isCollapsible?: boolean; + showSortIconWhenUnsorted?: boolean; + columnActionsMode?: ColumnActionsMode; + icon?: React.ReactNode; + data?: unknown; + sortAscendingAriaLabel?: string; + sortDescendingAriaLabel?: string; + sortableAriaLabel?: string; + filterAriaLabel?: string; + groupAriaLabel?: string; + onRender?: (item: T, index: number, column: IColumn) => React.ReactNode; + onRenderHeader?: (column: IColumn) => React.ReactNode; + onColumnClick?: (event: React.MouseEvent, column: IColumn) => void; + onColumnContextMenu?: (column: IColumn, event: React.MouseEvent) => void; + onColumnKeyDown?: (event: React.KeyboardEvent, column: IColumn) => void; + onColumnResize?: (width: number) => void; +} + +export interface IDetailsListSelectionChange { + selectedItems: readonly T[]; + selectedKeys: ReadonlySet; +} + +export interface IDetailsListFieldProps { + item: T; + itemIndex: number; + isSelected: boolean; + column: IColumn; + children: React.ReactNode; +} + +export interface IDetailsRowProps { + item: T; + itemIndex: number; + itemKey: DetailsListKey; + columns: readonly IColumn[]; + isSelected: boolean; + compact: boolean; + disabled: boolean; + onToggleSelection: (event: React.SyntheticEvent) => void; + onInvoke: (event: React.SyntheticEvent) => void; +} + +export interface IDetailsHeaderProps { + columns: readonly IColumn[]; + selectionMode: SelectionMode; + checkboxVisibility: CheckboxVisibility; + isAllSelected: boolean; + isPartiallySelected: boolean; + onToggleSelectAll: (event: React.SyntheticEvent) => void; +} + +export interface IDetailsListGroup { + key: string; + name: string; + startIndex: number; + count: number; + level?: number; + isCollapsed?: boolean; + children?: readonly IDetailsListGroup[]; + data?: unknown; +} + +export interface IDetailsGroupHeaderProps { + group: IDetailsListGroup; + isCollapsed: boolean; + onToggle: () => void; +} + +export interface IDetailsListShimmerRowProps { + columns: readonly IColumn[]; + rowIndex: number; + compact: boolean; + selectionMode: SelectionMode; + checkboxVisibility: CheckboxVisibility; +} + +export interface IDetailsListRowDragDropEvents { + canDrag?: (item: T, index: number) => boolean; + canDrop?: (draggedItem: T, targetItem: T, targetIndex: number) => boolean; + onDragStart?: (item: T, index: number, event: React.DragEvent) => void; + onDragEnter?: (item: T, index: number, event: React.DragEvent) => void; + onDragLeave?: (item: T, index: number, event: React.DragEvent) => void; + onDrop?: ( + draggedItem: T, + targetItem: T, + targetIndex: number, + event: React.DragEvent, + ) => void; + onDragEnd?: (item: T, index: number, event: React.DragEvent) => void; +} + +export interface IDetailsListStyles { + root?: React.CSSProperties; + viewport?: React.CSSProperties; + grid?: React.CSSProperties; + header?: React.CSSProperties; + row?: React.CSSProperties; + cell?: React.CSSProperties; + groupHeader?: React.CSSProperties; +} + +export interface IDetailsListProps { + items: readonly T[]; + columns: readonly IColumn[]; + getKey?: (item: T, index: number) => DetailsListKey; + setKey?: string; + componentRef?: React.Ref>; + className?: string; + styles?: IDetailsListStyles; + height?: number; + compact?: boolean; + layoutMode?: DetailsListLayoutMode; + constrainMode?: ConstrainMode; + isHeaderVisible?: boolean; + selectionMode?: SelectionMode; + checkboxVisibility?: CheckboxVisibility; + selectedKeys?: ReadonlySet | readonly DetailsListKey[]; + defaultSelectedKeys?: readonly DetailsListKey[]; + selectionPreservedOnEmptyClick?: boolean; + isSelectedOnFocus?: boolean; + disabledItemKeys?: ReadonlySet | readonly DetailsListKey[]; + onSelectionChanged?: (change: IDetailsListSelectionChange) => void; + onActiveItemChanged?: (item: T, index: number, event: React.FocusEvent) => void; + onItemInvoked?: (item: T, index: number, event: React.SyntheticEvent) => void; + onItemContextMenu?: (item: T, index: number, event: React.MouseEvent) => void | boolean; + onColumnHeaderClick?: (event: React.MouseEvent, column: IColumn) => void; + onColumnHeaderContextMenu?: (column: IColumn, event: React.MouseEvent) => void; + onColumnResize?: (column: IColumn, width: number, columnIndex: number) => void; + columnReorderOptions?: IColumnReorderOptions; + dragDropEvents?: IDetailsListRowDragDropEvents; + onRenderItemColumn?: (item: T, index: number, column: IColumn) => React.ReactNode; + onRenderField?: ( + props: IDetailsListFieldProps, + defaultRender: (props: IDetailsListFieldProps) => React.ReactElement, + ) => React.ReactNode; + onRenderRow?: ( + props: IDetailsRowProps, + defaultRender: (props: IDetailsRowProps) => React.ReactElement, + ) => React.ReactNode; + onRenderDetailsHeader?: ( + props: IDetailsHeaderProps, + defaultRender: (props: IDetailsHeaderProps) => React.ReactElement, + ) => React.ReactNode; + onRenderDetailsFooter?: (items: readonly T[]) => React.ReactNode; + groups?: readonly IDetailsListGroup[]; + onToggleGroup?: (group: IDetailsListGroup, isCollapsed: boolean) => void; + onRenderGroupHeader?: ( + props: IDetailsGroupHeaderProps, + defaultRender: (props: IDetailsGroupHeaderProps) => React.ReactElement, + ) => React.ReactNode; + enableVirtualization?: boolean; + rowHeight?: number; + groupHeaderHeight?: number; + overscanCount?: number; + initialFocusedIndex?: number; + onShouldVirtualize?: (itemCount: number) => boolean; + enableInfiniteScroll?: boolean; + infiniteScrollThreshold?: number; + onLoadMore?: () => void | Promise; + hasNextPage?: boolean; + isLoadingMore?: boolean; + isLoading?: boolean; + enableShimmer?: boolean; + shimmerLines?: number; + ariaLabelForShimmer?: string; + onRenderCustomPlaceholder?: ( + rowProps: IDetailsListShimmerRowProps, + index?: number, + defaultRender?: (props: IDetailsListShimmerRowProps) => React.ReactNode, + ) => React.ReactNode; + loadingMessage?: React.ReactNode; + loadingMoreMessage?: React.ReactNode; + emptyState?: React.ReactNode; + ariaLabelForGrid?: string; + ariaLabelForListHeader?: string; + ariaLabelForSelectAllCheckbox?: string; + ariaLabelForSelectionColumn?: string; + getRowAriaLabel?: (item: T) => string; + getRowAriaDescribedBy?: (item: T) => string | undefined; +} + +export interface IDetailsList { + forceUpdate: () => void; + focusIndex: (index: number, scrollToMode?: DetailsListScrollToMode) => void; + scrollToIndex: (index: number, scrollToMode?: DetailsListScrollToMode) => void; + getStartItemIndexInView: () => number; + updateColumn: (column: IColumn, options: { width?: number; newColumnIndex?: number }) => void; + getSelectedItems: () => readonly T[]; + setSelectedKeys: (keys: readonly DetailsListKey[]) => void; +} diff --git a/src/controls/DetailsList/DetailsList.utils.ts b/src/controls/DetailsList/DetailsList.utils.ts new file mode 100644 index 000000000..567bea583 --- /dev/null +++ b/src/controls/DetailsList/DetailsList.utils.ts @@ -0,0 +1,189 @@ +import type { + DetailsListKey, + IColumn, + IDetailsListGroup, +} from './DetailsList.types'; + +export interface IDetailsListItemEntry { + type: 'item'; + key: DetailsListKey; + item: T; + itemIndex: number; + height: number; +} + +export interface IDetailsListGroupEntry { + type: 'group'; + key: string; + group: IDetailsListGroup; + height: number; +} + +export type DetailsListEntry = + | IDetailsListItemEntry + | IDetailsListGroupEntry; + +export const clampColumnWidth = ( + column: IColumn, + width: number, +): number => + Math.max( + column.minWidth, + Math.min(width, column.maxWidth ?? Number.POSITIVE_INFINITY), + ); + +export const calculateColumnWidths = ( + columns: readonly IColumn[], + widthOverrides: Readonly>, + availableWidth: number, + justifyColumns: boolean, +): Readonly> => { + const widths = columns.map((column) => + clampColumnWidth( + column, + widthOverrides[column.key] ?? column.currentWidth ?? column.minWidth, + ), + ); + + let remainingWidth = justifyColumns + ? Math.max( + 0, + availableWidth - widths.reduce((total, width) => total + width, 0), + ) + : 0; + let activeIndexes = columns + .map((column, index) => ({ column, index })) + .filter( + ({ column, index }) => + !Object.prototype.hasOwnProperty.call(widthOverrides, column.key) && + widths[index] < (column.maxWidth ?? Number.POSITIVE_INFINITY), + ) + .map(({ index }) => index); + + while (remainingWidth > 0.5 && activeIndexes.length > 0) { + const totalWeight = activeIndexes.reduce( + (total, index) => + total + + (columns[index].flexGrow ?? columns[index].targetWidthProportion ?? 1), + 0, + ); + let distributedWidth = 0; + + activeIndexes.forEach((index) => { + const column = columns[index]; + const weight = column.flexGrow ?? column.targetWidthProportion ?? 1; + const share = remainingWidth * (weight / totalWeight); + const nextWidth = clampColumnWidth(column, widths[index] + share); + distributedWidth += nextWidth - widths[index]; + widths[index] = nextWidth; + }); + + if (distributedWidth <= 0.5) { + break; + } + + remainingWidth -= distributedWidth; + activeIndexes = activeIndexes.filter( + (index) => + widths[index] < (columns[index].maxWidth ?? Number.POSITIVE_INFINITY), + ); + } + + return Object.fromEntries( + columns.map((column, index) => [column.key, widths[index]]), + ); +}; + +export const buildDetailsListEntries = ( + items: readonly T[], + groups: readonly IDetailsListGroup[] | undefined, + rowHeight: number, + groupHeaderHeight: number, + getKey: (item: T, index: number) => DetailsListKey, + isGroupCollapsed: (group: IDetailsListGroup) => boolean, +): readonly DetailsListEntry[] => { + if (!groups?.length) { + return items.map((item, itemIndex) => ({ + type: 'item', + key: getKey(item, itemIndex), + item, + itemIndex, + height: rowHeight, + })); + } + + const entries: DetailsListEntry[] = []; + + const appendGroup = (group: IDetailsListGroup): void => { + entries.push({ + type: 'group', + key: group.key, + group, + height: groupHeaderHeight, + }); + + if (isGroupCollapsed(group)) { + return; + } + + if (group.children?.length) { + group.children.forEach(appendGroup); + return; + } + + const lastIndex = Math.min(items.length, group.startIndex + group.count); + for ( + let itemIndex = group.startIndex; + itemIndex < lastIndex; + itemIndex += 1 + ) { + const item = items[itemIndex]; + entries.push({ + type: 'item', + key: getKey(item, itemIndex), + item, + itemIndex, + height: rowHeight, + }); + } + }; + + groups.forEach(appendGroup); + return entries; +}; + +export const calculateEntryOffsets = ( + entries: readonly DetailsListEntry[], +): readonly number[] => { + const offsets: number[] = [0]; + entries.forEach((entry) => + offsets.push(offsets[offsets.length - 1] + entry.height), + ); + return offsets; +}; + +export const findEntryIndexAtOffset = ( + offsets: readonly number[], + offset: number, +): number => { + if (offsets.length <= 1) { + return 0; + } + + let low = 0; + let high = offsets.length - 2; + + while (low <= high) { + const middle = Math.floor((low + high) / 2); + if (offsets[middle] <= offset && offset < offsets[middle + 1]) { + return middle; + } + if (offset < offsets[middle]) { + high = middle - 1; + } else { + low = middle + 1; + } + } + + return Math.max(0, Math.min(low, offsets.length - 2)); +}; diff --git a/src/controls/DetailsList/DetailsListContent.tsx b/src/controls/DetailsList/DetailsListContent.tsx new file mode 100644 index 000000000..f1fd099e5 --- /dev/null +++ b/src/controls/DetailsList/DetailsListContent.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { Text } from '@fluentui/react-components'; + +export const renderDetailsListContent = ( + content: React.ReactNode, + className?: string, +): React.ReactNode => { + if (typeof content === 'string' || typeof content === 'number') { + return {content}; + } + return content; +}; diff --git a/src/controls/DetailsList/DetailsListGroupHeader.tsx b/src/controls/DetailsList/DetailsListGroupHeader.tsx new file mode 100644 index 000000000..802b897ec --- /dev/null +++ b/src/controls/DetailsList/DetailsListGroupHeader.tsx @@ -0,0 +1,97 @@ +import * as React from 'react'; +import { Button, Text } from '@fluentui/react-components'; +import { ChevronDown16Regular, ChevronRight16Regular } from '@fluentui/react-icons'; + +import type { IDetailsGroupHeaderProps, IDetailsListProps } from './DetailsList.types'; +import type { IDetailsListGroupEntry } from './DetailsList.utils'; +import type { useDetailsListStyles } from './useDetailsListStyles'; + +interface DetailsListGroupHeaderProps { + entry: IDetailsListGroupEntry; + entryIndex: number; + isCollapsed: boolean; + onToggle: () => void; + isHeaderVisible: boolean; + shouldVirtualize: boolean; + entryTop: number; + groupHeaderHeight: number; + gridTemplateColumns: string; + classes: ReturnType; + style: React.CSSProperties | undefined; + onRenderGroupHeader: IDetailsListProps['onRenderGroupHeader']; +} + +export const DetailsListGroupHeader = ({ + entry, + entryIndex, + isCollapsed, + onToggle, + isHeaderVisible, + shouldVirtualize, + entryTop, + groupHeaderHeight, + gridTemplateColumns, + classes, + style, + onRenderGroupHeader, +}: DetailsListGroupHeaderProps): React.ReactElement => { + const groupProps: IDetailsGroupHeaderProps = { + group: entry.group, + isCollapsed, + onToggle, + }; + const defaultRender = (renderProps: IDetailsGroupHeaderProps): React.ReactElement => ( +
+ +
+ ); + + return ( + <> + {onRenderGroupHeader + ? onRenderGroupHeader(groupProps, defaultRender) + : defaultRender(groupProps)} + + ); +}; diff --git a/src/controls/DetailsList/DetailsListHeader.tsx b/src/controls/DetailsList/DetailsListHeader.tsx new file mode 100644 index 000000000..974e40d4d --- /dev/null +++ b/src/controls/DetailsList/DetailsListHeader.tsx @@ -0,0 +1,252 @@ +import * as React from 'react'; +import * as strings from 'ControlStrings'; +import { Button, Checkbox, mergeClasses } from '@fluentui/react-components'; +import { + ArrowSortDown16Regular, + ArrowSortUp16Regular, + ChevronDown16Regular, + Filter16Regular, + ReOrderDotsVertical16Regular, +} from '@fluentui/react-icons'; + +import type { + IColumn, + IColumnReorderOptions, + IDetailsHeaderProps, + IDetailsListProps, +} from './DetailsList.types'; +import { + CheckboxVisibility, + ColumnActionsMode, + ColumnDragEndLocation, + SelectionMode, +} from './DetailsList.types'; +import { renderDetailsListContent } from './DetailsListContent'; +import type { useDetailsListStyles } from './useDetailsListStyles'; + +const COLUMN_RESIZE_KEYBOARD_STEP = 10; + +interface DetailsListHeaderProps { + columns: readonly IColumn[]; + selectionMode: SelectionMode; + checkboxVisibility: CheckboxVisibility; + isAllSelected: boolean; + isPartiallySelected: boolean; + onToggleSelectAll: (event: React.SyntheticEvent) => void; + showSelectionColumn: boolean; + ariaLabelForSelectionColumn: string; + ariaLabelForSelectAllCheckbox: string; + gridTemplateColumns: string; + style: React.CSSProperties | undefined; + classes: ReturnType; + columnWidths: Readonly>; + columnReorderOptions: IColumnReorderOptions | undefined; + draggedColumnIndexRef: React.MutableRefObject; + columnDropLocationRef: React.MutableRefObject; + isColumnDraggable: (columnIndex: number) => boolean; + handleColumnDrop: (targetIndex: number) => void; + resizeColumn: (column: IColumn, columnIndex: number, width: number) => void; + autoFitColumn: (column: IColumn, columnIndex: number) => void; + beginColumnResize: ( + event: React.PointerEvent, + column: IColumn, + columnIndex: number, + ) => void; + onColumnHeaderClick: IDetailsListProps['onColumnHeaderClick']; + onColumnHeaderContextMenu: IDetailsListProps['onColumnHeaderContextMenu']; + onRenderDetailsHeader: IDetailsListProps['onRenderDetailsHeader']; +} + +export const DetailsListHeader = ({ + columns, + selectionMode, + checkboxVisibility, + isAllSelected, + isPartiallySelected, + onToggleSelectAll, + showSelectionColumn, + ariaLabelForSelectionColumn, + ariaLabelForSelectAllCheckbox, + gridTemplateColumns, + style, + classes, + columnWidths, + columnReorderOptions, + draggedColumnIndexRef, + columnDropLocationRef, + isColumnDraggable, + handleColumnDrop, + resizeColumn, + autoFitColumn, + beginColumnResize, + onColumnHeaderClick, + onColumnHeaderContextMenu, + onRenderDetailsHeader, +}: DetailsListHeaderProps): React.ReactElement => { + const headerProps: IDetailsHeaderProps = { + columns, + selectionMode, + checkboxVisibility, + isAllSelected, + isPartiallySelected, + onToggleSelectAll, + }; + + const defaultRender = (renderProps: IDetailsHeaderProps): React.ReactElement => ( +
+ {showSelectionColumn && ( +
+ {renderProps.selectionMode === SelectionMode.multiple && ( + renderProps.onToggleSelectAll(event)} + /> + )} +
+ )} + {renderProps.columns.map((column, columnIndex) => { + const isActionable = column.columnActionsMode !== ColumnActionsMode.disabled; + const isDraggable = isColumnDraggable(columnIndex); + const ariaSort = column.isSorted + ? column.isSortedDescending + ? 'descending' + : 'ascending' + : 'none'; + const headerContent = column.onRenderHeader + ? column.onRenderHeader(column) + : column.name; + + return ( +
{ + column.onColumnContextMenu?.(column, event); + onColumnHeaderContextMenu?.(column, event); + }} + onDragStart={(event) => { + draggedColumnIndexRef.current = columnIndex; + columnDropLocationRef.current = ColumnDragEndLocation.outside; + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', column.key); + columnReorderOptions?.onColumnDragStart?.(true); + }} + onDragOver={(event) => { + if (draggedColumnIndexRef.current !== undefined && isColumnDraggable(columnIndex)) { + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + } + }} + onDrop={(event) => { + event.preventDefault(); + handleColumnDrop(columnIndex); + }} + onDragEnd={() => { + columnReorderOptions?.onColumnDragStart?.(false); + columnReorderOptions?.onDragEnd?.(columnDropLocationRef.current); + draggedColumnIndexRef.current = undefined; + columnDropLocationRef.current = ColumnDragEndLocation.outside; + }} + > + + {column.isResizable && ( +
+ ); + })} +
+ ); + + return ( + <> + {onRenderDetailsHeader + ? onRenderDetailsHeader(headerProps, defaultRender) + : defaultRender(headerProps)} + + ); +}; diff --git a/src/controls/DetailsList/DetailsListRow.tsx b/src/controls/DetailsList/DetailsListRow.tsx new file mode 100644 index 000000000..df0605acb --- /dev/null +++ b/src/controls/DetailsList/DetailsListRow.tsx @@ -0,0 +1,343 @@ +import * as React from 'react'; +import * as strings from 'ControlStrings'; +import { Checkbox, mergeClasses, Radio } from '@fluentui/react-components'; + +import type { + DetailsListKey, + IColumn, + IDetailsListFieldProps, + IDetailsListProps, + IDetailsRowProps, +} from './DetailsList.types'; +import { CheckboxVisibility, SelectionMode } from './DetailsList.types'; +import type { IDetailsListItemEntry } from './DetailsList.utils'; +import { renderDetailsListContent } from './DetailsListContent'; +import type { useDetailsListStyles } from './useDetailsListStyles'; + +interface DetailsListCellProps { + item: T; + itemIndex: number; + column: IColumn; + isSelected: boolean; + ariaColumnIndex: number; + classes: ReturnType; + style: React.CSSProperties | undefined; + onRenderItemColumn: IDetailsListProps['onRenderItemColumn']; + onRenderField: IDetailsListProps['onRenderField']; +} + +interface DetailsListRowProps { + entry: IDetailsListItemEntry; + entryIndex: number; + columns: readonly IColumn[]; + selectedKeySet: ReadonlySet; + disabledKeySet: ReadonlySet; + compact: boolean; + activeItemIndex: number; + setActiveItemIndex: React.Dispatch>; + dropTargetKey: DetailsListKey | undefined; + setDropTargetKey: React.Dispatch>; + draggedItemRef: React.MutableRefObject<{ item: T; index: number } | undefined>; + effectiveRowHeight: number; + entryTop: number; + gridTemplateColumns: string; + isHeaderVisible: boolean; + shouldVirtualize: boolean; + showSelectionColumn: boolean; + selectionMode: SelectionMode; + selectionControlName: string; + checkboxVisibility: CheckboxVisibility; + ariaLabelForSelectionColumn: string; + isSelectedOnFocus: boolean; + classes: ReturnType; + rowStyle: React.CSSProperties | undefined; + cellStyle: React.CSSProperties | undefined; + selectItem: ( + item: T, + itemIndex: number, + event: React.SyntheticEvent, + preserveExistingSelection: boolean, + ) => void; + handleRowKeyDown: (event: React.KeyboardEvent, item: T, itemIndex: number) => void; + onActiveItemChanged: IDetailsListProps['onActiveItemChanged']; + onItemInvoked: IDetailsListProps['onItemInvoked']; + onItemContextMenu: IDetailsListProps['onItemContextMenu']; + dragDropEvents: IDetailsListProps['dragDropEvents']; + getRowAriaLabel: IDetailsListProps['getRowAriaLabel']; + getRowAriaDescribedBy: IDetailsListProps['getRowAriaDescribedBy']; + onRenderItemColumn: IDetailsListProps['onRenderItemColumn']; + onRenderField: IDetailsListProps['onRenderField']; + onRenderRow: IDetailsListProps['onRenderRow']; +} + +const isInteractiveTarget = (target: EventTarget | null): boolean => + target instanceof Element && + Boolean( + target.closest( + 'a, button, input, select, textarea, [role="button"], [data-selection-disabled]', + ), + ); + +const DetailsListCell = ({ + item, + itemIndex, + column, + isSelected, + ariaColumnIndex, + classes, + style, + onRenderItemColumn, + onRenderField, +}: DetailsListCellProps): React.ReactElement => { + const rawContent = column.onRender + ? column.onRender(item, itemIndex, column) + : onRenderItemColumn + ? onRenderItemColumn(item, itemIndex, column) + : column.fieldName + ? String(item[column.fieldName] ?? '') + : ''; + const fieldProps: IDetailsListFieldProps = { + item, + itemIndex, + isSelected, + column, + children: rawContent, + }; + const defaultRender = (renderProps: IDetailsListFieldProps): React.ReactElement => ( +
+ {renderDetailsListContent(renderProps.children, classes.cellText)} +
+ ); + + return <>{onRenderField ? onRenderField(fieldProps, defaultRender) : defaultRender(fieldProps)}; +}; + +export const DetailsListRow = ({ + entry, + entryIndex, + columns, + selectedKeySet, + disabledKeySet, + compact, + activeItemIndex, + setActiveItemIndex, + dropTargetKey, + setDropTargetKey, + draggedItemRef, + effectiveRowHeight, + entryTop, + gridTemplateColumns, + isHeaderVisible, + shouldVirtualize, + showSelectionColumn, + selectionMode, + selectionControlName, + checkboxVisibility, + ariaLabelForSelectionColumn, + isSelectedOnFocus, + classes, + rowStyle, + cellStyle, + selectItem, + handleRowKeyDown, + onActiveItemChanged, + onItemInvoked, + onItemContextMenu, + dragDropEvents, + getRowAriaLabel, + getRowAriaDescribedBy, + onRenderItemColumn, + onRenderField, + onRenderRow, +}: DetailsListRowProps): React.ReactElement => { + const { item, itemIndex, key: itemKey } = entry; + const isSelected = selectedKeySet.has(itemKey); + const isDisabled = disabledKeySet.has(itemKey); + const rowProps: IDetailsRowProps = { + item, + itemIndex, + itemKey, + columns, + isSelected, + compact, + disabled: isDisabled, + onToggleSelection: (event) => selectItem(item, itemIndex, event, true), + onInvoke: (event) => onItemInvoked?.(item, itemIndex, event), + }; + + const defaultRender = (renderProps: IDetailsRowProps): React.ReactElement => ( +
{ + if (!isInteractiveTarget(event.target)) { + selectItem(renderProps.item, renderProps.itemIndex, event, false); + } + }} + onDoubleClick={(event) => onItemInvoked?.(renderProps.item, renderProps.itemIndex, event)} + onContextMenu={(event) => { + if (onItemContextMenu?.(renderProps.item, renderProps.itemIndex, event) !== true) { + event.preventDefault(); + } + }} + onFocus={(event) => { + setActiveItemIndex(renderProps.itemIndex); + onActiveItemChanged?.(renderProps.item, renderProps.itemIndex, event); + if (isSelectedOnFocus && !renderProps.isSelected) { + selectItem(renderProps.item, renderProps.itemIndex, event, false); + } + }} + onKeyDown={(event) => handleRowKeyDown(event, renderProps.item, renderProps.itemIndex)} + onDragStart={(event) => { + draggedItemRef.current = { item: renderProps.item, index: renderProps.itemIndex }; + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', String(renderProps.itemKey)); + dragDropEvents?.onDragStart?.(renderProps.item, renderProps.itemIndex, event); + }} + onDragEnter={(event) => { + const draggedItem = draggedItemRef.current; + if ( + draggedItem && + (dragDropEvents?.canDrop?.( + draggedItem.item, + renderProps.item, + renderProps.itemIndex, + ) ?? true) + ) { + setDropTargetKey(renderProps.itemKey); + dragDropEvents?.onDragEnter?.(renderProps.item, renderProps.itemIndex, event); + } + }} + onDragOver={(event) => { + if (dropTargetKey === renderProps.itemKey) { + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + } + }} + onDragLeave={(event) => { + if (!event.currentTarget.contains(event.relatedTarget as Node | null)) { + setDropTargetKey(undefined); + dragDropEvents?.onDragLeave?.(renderProps.item, renderProps.itemIndex, event); + } + }} + onDrop={(event) => { + event.preventDefault(); + const draggedItem = draggedItemRef.current; + if (draggedItem) { + dragDropEvents?.onDrop?.( + draggedItem.item, + renderProps.item, + renderProps.itemIndex, + event, + ); + } + setDropTargetKey(undefined); + }} + onDragEnd={(event) => { + dragDropEvents?.onDragEnd?.(renderProps.item, renderProps.itemIndex, event); + draggedItemRef.current = undefined; + setDropTargetKey(undefined); + }} + > + {showSelectionColumn && ( +
+ {selectionMode === SelectionMode.single ? ( + { + event.stopPropagation(); + renderProps.onToggleSelection(event); + }} + /> + ) : ( + { + event.stopPropagation(); + renderProps.onToggleSelection(event); + }} + /> + )} +
+ )} + {renderProps.columns.map((column, columnIndex) => ( + + ))} +
+ ); + + return <>{onRenderRow ? onRenderRow(rowProps, defaultRender) : defaultRender(rowProps)}; +}; diff --git a/src/controls/DetailsList/DetailsListShimmerRows.tsx b/src/controls/DetailsList/DetailsListShimmerRows.tsx new file mode 100644 index 000000000..6d695a485 --- /dev/null +++ b/src/controls/DetailsList/DetailsListShimmerRows.tsx @@ -0,0 +1,92 @@ +import * as React from 'react'; +import { Skeleton, SkeletonItem } from '@fluentui/react-components'; + +import type { + IDetailsListShimmerRowProps, + IDetailsListProps, +} from './DetailsList.types'; +import type { useDetailsListStyles } from './useDetailsListStyles'; + +interface DetailsListShimmerRowsProps { + columns: IDetailsListShimmerRowProps['columns']; + rowCount: number; + rowHeight: number; + compact: boolean; + selectionMode: IDetailsListShimmerRowProps['selectionMode']; + checkboxVisibility: IDetailsListShimmerRowProps['checkboxVisibility']; + showSelectionColumn: boolean; + gridTemplateColumns: string; + classes: ReturnType; + onRenderCustomPlaceholder: IDetailsListProps['onRenderCustomPlaceholder']; +} + +const SHIMMER_LINE_WIDTHS = ['72%', '88%', '64%', '80%'] as const; + +export const DetailsListShimmerRows = ({ + columns, + rowCount, + rowHeight, + compact, + selectionMode, + checkboxVisibility, + showSelectionColumn, + gridTemplateColumns, + classes, + onRenderCustomPlaceholder, +}: DetailsListShimmerRowsProps): React.ReactElement => ( + <> + {Array.from({ length: rowCount }, (_, rowIndex) => { + const shimmerRowProps: IDetailsListShimmerRowProps = { + columns, + rowIndex, + compact, + selectionMode, + checkboxVisibility, + }; + const defaultRender = ( + renderProps: IDetailsListShimmerRowProps, + ): React.ReactElement => ( +
+ {showSelectionColumn && ( +
+ + + +
+ )} + {renderProps.columns.map((column, columnIndex) => ( +
+ + + +
+ ))} +
+ ); + + return ( + + {onRenderCustomPlaceholder + ? onRenderCustomPlaceholder(shimmerRowProps, rowIndex, defaultRender) + : defaultRender(shimmerRowProps)} + + ); + })} + +); \ No newline at end of file diff --git a/src/controls/DetailsList/index.ts b/src/controls/DetailsList/index.ts new file mode 100644 index 000000000..f9c52d5fb --- /dev/null +++ b/src/controls/DetailsList/index.ts @@ -0,0 +1,2 @@ +export { DetailsList } from './DetailsList'; +export * from './DetailsList.types'; diff --git a/src/controls/DetailsList/useDetailsListColumns.ts b/src/controls/DetailsList/useDetailsListColumns.ts new file mode 100644 index 000000000..9b3178b64 --- /dev/null +++ b/src/controls/DetailsList/useDetailsListColumns.ts @@ -0,0 +1,222 @@ +import * as React from 'react'; + +import type { IColumn, IColumnReorderOptions, IDetailsListProps } from './DetailsList.types'; +import { ColumnDragEndLocation, DetailsListLayoutMode } from './DetailsList.types'; +import { calculateColumnWidths, clampColumnWidth } from './DetailsList.utils'; +import { DETAILS_LIST_SELECTION_COLUMN_WIDTH } from './useDetailsListStyles'; + +interface UseDetailsListColumnsOptions { + columns: readonly IColumn[]; + viewportWidth: number; + selectionColumnWidth: number; + layoutMode: DetailsListLayoutMode; + viewportRef: React.RefObject; + onColumnResize: IDetailsListProps['onColumnResize']; + columnReorderOptions: IColumnReorderOptions | undefined; +} + +interface UseDetailsListColumnsResult { + orderedColumns: readonly IColumn[]; + columnWidths: Readonly>; + contentWidth: number; + gridWidth: number; + gridTemplateColumns: string; + draggedColumnIndexRef: React.MutableRefObject; + columnDropLocationRef: React.MutableRefObject; + resizeColumn: (column: IColumn, columnIndex: number, width: number) => void; + beginColumnResize: ( + event: React.PointerEvent, + column: IColumn, + columnIndex: number, + ) => void; + autoFitColumn: (column: IColumn, columnIndex: number) => void; + isColumnDraggable: (columnIndex: number) => boolean; + handleColumnDrop: (targetIndex: number) => void; + moveColumn: (column: IColumn, targetIndex: number) => void; +} + +const areArraysEqual = (left: readonly string[], right: readonly string[]): boolean => + left.length === right.length && left.every((value, index) => value === right[index]); + +export const useDetailsListColumns = ({ + columns, + viewportWidth, + selectionColumnWidth, + layoutMode, + viewportRef, + onColumnResize, + columnReorderOptions, +}: UseDetailsListColumnsOptions): UseDetailsListColumnsResult => { + const resizeCleanupRef = React.useRef<(() => void) | undefined>(); + const draggedColumnIndexRef = React.useRef(); + const columnDropLocationRef = React.useRef(ColumnDragEndLocation.outside); + const [columnOrder, setColumnOrder] = React.useState(() => + columns.map((column) => column.key), + ); + const [columnWidthOverrides, setColumnWidthOverrides] = React.useState>({}); + + React.useEffect(() => { + const nextOrder = columns.map((column) => column.key); + setColumnOrder((currentOrder) => { + const retainedKeys = currentOrder.filter((key) => nextOrder.includes(key)); + const addedKeys = nextOrder.filter((key) => !retainedKeys.includes(key)); + const mergedOrder = [...retainedKeys, ...addedKeys]; + return areArraysEqual(currentOrder, mergedOrder) ? currentOrder : mergedOrder; + }); + }, [columns]); + + React.useEffect(() => () => resizeCleanupRef.current?.(), []); + + const orderedColumns = React.useMemo(() => { + const columnByKey = new Map(columns.map((column) => [column.key, column])); + return columnOrder + .map((key) => columnByKey.get(key)) + .filter((column): column is IColumn => Boolean(column)); + }, [columnOrder, columns]); + + const columnWidths = React.useMemo( + () => + calculateColumnWidths( + orderedColumns, + columnWidthOverrides, + Math.max(0, viewportWidth - selectionColumnWidth), + layoutMode === DetailsListLayoutMode.justified, + ), + [columnWidthOverrides, layoutMode, orderedColumns, selectionColumnWidth, viewportWidth], + ); + + const contentWidth = React.useMemo( + () => + selectionColumnWidth + + orderedColumns.reduce((total, column) => total + columnWidths[column.key], 0), + [columnWidths, orderedColumns, selectionColumnWidth], + ); + const gridWidth = Math.max(contentWidth, viewportWidth); + const gridTemplateColumns = [ + ...(selectionColumnWidth > 0 ? [`${DETAILS_LIST_SELECTION_COLUMN_WIDTH}px`] : []), + ...orderedColumns.map((column) => `${columnWidths[column.key]}px`), + ].join(' '); + + const resizeColumn = React.useCallback( + (column: IColumn, columnIndex: number, width: number): void => { + const nextWidth = clampColumnWidth(column, width); + setColumnWidthOverrides((currentWidths) => ({ + ...currentWidths, + [column.key]: nextWidth, + })); + column.onColumnResize?.(nextWidth); + onColumnResize?.(column, nextWidth, columnIndex); + }, + [onColumnResize], + ); + + const beginColumnResize = React.useCallback( + (event: React.PointerEvent, column: IColumn, columnIndex: number): void => { + event.preventDefault(); + event.stopPropagation(); + resizeCleanupRef.current?.(); + const originX = event.clientX; + const originWidth = columnWidths[column.key]; + + const handlePointerMove = (pointerEvent: PointerEvent): void => { + resizeColumn(column, columnIndex, originWidth + pointerEvent.clientX - originX); + }; + const handlePointerUp = (): void => { + window.removeEventListener('pointermove', handlePointerMove); + window.removeEventListener('pointerup', handlePointerUp); + window.removeEventListener('pointercancel', handlePointerUp); + resizeCleanupRef.current = undefined; + }; + + window.addEventListener('pointermove', handlePointerMove); + window.addEventListener('pointerup', handlePointerUp); + window.addEventListener('pointercancel', handlePointerUp); + resizeCleanupRef.current = handlePointerUp; + }, + [columnWidths, resizeColumn], + ); + + const autoFitColumn = React.useCallback( + (column: IColumn, columnIndex: number): void => { + const cells = viewportRef.current?.querySelectorAll('[data-details-column-key]'); + let measuredWidth = column.minWidth; + cells?.forEach((cell) => { + if (cell.dataset.detailsColumnKey === column.key) { + measuredWidth = Math.max(measuredWidth, cell.scrollWidth); + } + }); + resizeColumn(column, columnIndex, measuredWidth); + }, + [resizeColumn, viewportRef], + ); + + const isColumnDraggable = React.useCallback( + (columnIndex: number): boolean => { + if (!columnReorderOptions) { + return false; + } + const frozenFromStart = columnReorderOptions.frozenColumnCountFromStart ?? 0; + const frozenFromEnd = columnReorderOptions.frozenColumnCountFromEnd ?? 0; + return columnIndex >= frozenFromStart && columnIndex < orderedColumns.length - frozenFromEnd; + }, + [columnReorderOptions, orderedColumns.length], + ); + + const handleColumnDrop = React.useCallback( + (targetIndex: number): void => { + const draggedIndex = draggedColumnIndexRef.current; + if (draggedIndex === undefined || draggedIndex === targetIndex || !isColumnDraggable(targetIndex)) { + return; + } + const draggedColumn = orderedColumns[draggedIndex]; + const targetColumn = orderedColumns[targetIndex]; + setColumnOrder((currentOrder) => { + const nextOrder = [...currentOrder]; + const [draggedKey] = nextOrder.splice(draggedIndex, 1); + nextOrder.splice(targetIndex, 0, draggedKey); + return nextOrder; + }); + columnDropLocationRef.current = ColumnDragEndLocation.header; + columnReorderOptions?.onColumnDrop?.({ + draggedColumn, + draggedIndex, + targetColumn, + targetIndex, + }); + }, + [columnReorderOptions, isColumnDraggable, orderedColumns], + ); + + const moveColumn = React.useCallback( + (column: IColumn, targetIndex: number): void => { + const currentIndex = orderedColumns.findIndex((candidate) => candidate.key === column.key); + if (currentIndex < 0) { + return; + } + const boundedTargetIndex = Math.max(0, Math.min(targetIndex, orderedColumns.length - 1)); + setColumnOrder((currentOrder) => { + const nextOrder = [...currentOrder]; + const [columnKey] = nextOrder.splice(currentIndex, 1); + nextOrder.splice(boundedTargetIndex, 0, columnKey); + return nextOrder; + }); + }, + [orderedColumns], + ); + + return { + orderedColumns, + columnWidths, + contentWidth, + gridWidth, + gridTemplateColumns, + draggedColumnIndexRef, + columnDropLocationRef, + resizeColumn, + beginColumnResize, + autoFitColumn, + isColumnDraggable, + handleColumnDrop, + moveColumn, + }; +}; diff --git a/src/controls/DetailsList/useDetailsListGroups.ts b/src/controls/DetailsList/useDetailsListGroups.ts new file mode 100644 index 000000000..c8482f047 --- /dev/null +++ b/src/controls/DetailsList/useDetailsListGroups.ts @@ -0,0 +1,45 @@ +import * as React from 'react'; + +import type { IDetailsListGroup, IDetailsListProps } from './DetailsList.types'; + +interface UseDetailsListGroupsOptions { + onToggleGroup: IDetailsListProps['onToggleGroup']; +} + +interface UseDetailsListGroupsResult { + isGroupCollapsed: (group: IDetailsListGroup) => boolean; + toggleGroup: (group: IDetailsListGroup) => void; +} + +export const useDetailsListGroups = ({ + onToggleGroup, +}: UseDetailsListGroupsOptions): UseDetailsListGroupsResult => { + const [collapsedGroupKeys, setCollapsedGroupKeys] = React.useState>(new Set()); + + const isGroupCollapsed = React.useCallback( + (group: IDetailsListGroup): boolean => + group.isCollapsed ?? collapsedGroupKeys.has(group.key), + [collapsedGroupKeys], + ); + + const toggleGroup = React.useCallback( + (group: IDetailsListGroup): void => { + const nextCollapsed = !isGroupCollapsed(group); + if (group.isCollapsed === undefined) { + setCollapsedGroupKeys((currentKeys) => { + const nextKeys = new Set(currentKeys); + if (nextCollapsed) { + nextKeys.add(group.key); + } else { + nextKeys.delete(group.key); + } + return nextKeys; + }); + } + onToggleGroup?.(group, nextCollapsed); + }, + [isGroupCollapsed, onToggleGroup], + ); + + return { isGroupCollapsed, toggleGroup }; +}; \ No newline at end of file diff --git a/src/controls/DetailsList/useDetailsListSelection.ts b/src/controls/DetailsList/useDetailsListSelection.ts new file mode 100644 index 000000000..221c3f281 --- /dev/null +++ b/src/controls/DetailsList/useDetailsListSelection.ts @@ -0,0 +1,193 @@ +import * as React from 'react'; + +import type { + DetailsListKey, + IDetailsListProps, + IDetailsListSelectionChange, +} from './DetailsList.types'; +import { SelectionMode } from './DetailsList.types'; + +interface UseDetailsListSelectionOptions { + items: readonly T[]; + getKey: IDetailsListProps['getKey']; + setKey: IDetailsListProps['setKey']; + selectionMode: SelectionMode; + selectedKeys: IDetailsListProps['selectedKeys']; + defaultSelectedKeys: readonly DetailsListKey[]; + disabledItemKeys: IDetailsListProps['disabledItemKeys']; + onSelectionChanged: ((change: IDetailsListSelectionChange) => void) | undefined; +} + +interface UseDetailsListSelectionResult { + getItemKey: (item: T, index: number) => DetailsListKey; + selectedKeySet: ReadonlySet; + disabledKeySet: ReadonlySet; + selectedItems: readonly T[]; + isAllSelected: boolean; + isPartiallySelected: boolean; + commitSelection: (nextKeys: Set) => void; + selectItem: ( + item: T, + itemIndex: number, + event: React.SyntheticEvent, + preserveExistingSelection: boolean, + ) => void; + toggleSelectAll: (event: React.SyntheticEvent) => void; +} + +const toKeySet = ( + keys: ReadonlySet | readonly DetailsListKey[] | undefined, +): Set => new Set(keys ?? []); + +export const useDetailsListSelection = ({ + items, + getKey, + setKey, + selectionMode, + selectedKeys, + defaultSelectedKeys, + disabledItemKeys, + onSelectionChanged, +}: UseDetailsListSelectionOptions): UseDetailsListSelectionResult => { + const lastSelectedIndexRef = React.useRef(); + const previousSetKeyRef = React.useRef(setKey); + const [internalSelectedKeys, setInternalSelectedKeys] = React.useState>(() => + toKeySet(defaultSelectedKeys), + ); + const isSelectionControlled = selectedKeys !== undefined; + const selectedKeySet = React.useMemo( + () => (isSelectionControlled ? toKeySet(selectedKeys) : internalSelectedKeys), + [internalSelectedKeys, isSelectionControlled, selectedKeys], + ); + const disabledKeySet = React.useMemo(() => toKeySet(disabledItemKeys), [disabledItemKeys]); + + const getItemKey = React.useCallback( + (item: T, index: number): DetailsListKey => getKey?.(item, index) ?? index, + [getKey], + ); + + React.useEffect(() => { + if (previousSetKeyRef.current !== setKey) { + previousSetKeyRef.current = setKey; + if (!isSelectionControlled) { + setInternalSelectedKeys(toKeySet(defaultSelectedKeys)); + } + } + }, [defaultSelectedKeys, isSelectionControlled, setKey]); + + const selectedItems = React.useMemo( + () => items.filter((item, index) => selectedKeySet.has(getItemKey(item, index))), + [getItemKey, items, selectedKeySet], + ); + const enabledItemKeys = React.useMemo( + () => + items + .map((item, index) => getItemKey(item, index)) + .filter((itemKey) => !disabledKeySet.has(itemKey)), + [disabledKeySet, getItemKey, items], + ); + const isAllSelected = + enabledItemKeys.length > 0 && enabledItemKeys.every((itemKey) => selectedKeySet.has(itemKey)); + const isPartiallySelected = + !isAllSelected && enabledItemKeys.some((itemKey) => selectedKeySet.has(itemKey)); + + const commitSelection = React.useCallback( + (nextKeys: Set): void => { + if (!isSelectionControlled) { + setInternalSelectedKeys(new Set(nextKeys)); + } + const nextItems = items.filter((item, index) => nextKeys.has(getItemKey(item, index))); + onSelectionChanged?.({ selectedItems: nextItems, selectedKeys: new Set(nextKeys) }); + }, + [getItemKey, isSelectionControlled, items, onSelectionChanged], + ); + + const selectItem = React.useCallback( + ( + item: T, + itemIndex: number, + event: React.SyntheticEvent, + preserveExistingSelection: boolean, + ): void => { + if (selectionMode === SelectionMode.none) { + return; + } + + const itemKey = getItemKey(item, itemIndex); + if (disabledKeySet.has(itemKey)) { + return; + } + + const nextKeys = new Set(selectedKeySet); + const nativeEvent = event.nativeEvent; + const shiftKey = 'shiftKey' in nativeEvent && Boolean(nativeEvent.shiftKey); + const modifierKey = + ('metaKey' in nativeEvent && Boolean(nativeEvent.metaKey)) || + ('ctrlKey' in nativeEvent && Boolean(nativeEvent.ctrlKey)); + + if (selectionMode === SelectionMode.single) { + nextKeys.clear(); + if (!selectedKeySet.has(itemKey) || !preserveExistingSelection) { + nextKeys.add(itemKey); + } + } else if (shiftKey && lastSelectedIndexRef.current !== undefined) { + if (!modifierKey) { + nextKeys.clear(); + } + const rangeStart = Math.min(lastSelectedIndexRef.current, itemIndex); + const rangeEnd = Math.max(lastSelectedIndexRef.current, itemIndex); + for (let index = rangeStart; index <= rangeEnd; index += 1) { + const rangeItem = items[index]; + if (rangeItem !== undefined) { + const rangeKey = getItemKey(rangeItem, index); + if (!disabledKeySet.has(rangeKey)) { + nextKeys.add(rangeKey); + } + } + } + } else if (preserveExistingSelection || modifierKey) { + if (nextKeys.has(itemKey)) { + nextKeys.delete(itemKey); + } else { + nextKeys.add(itemKey); + } + } else { + nextKeys.clear(); + nextKeys.add(itemKey); + } + + lastSelectedIndexRef.current = itemIndex; + commitSelection(nextKeys); + }, + [commitSelection, disabledKeySet, getItemKey, items, selectedKeySet, selectionMode], + ); + + const toggleSelectAll = React.useCallback( + (event: React.SyntheticEvent): void => { + event.stopPropagation(); + if (selectionMode !== SelectionMode.multiple) { + return; + } + const nextKeys = new Set(selectedKeySet); + if (isAllSelected) { + enabledItemKeys.forEach((itemKey) => nextKeys.delete(itemKey)); + } else { + enabledItemKeys.forEach((itemKey) => nextKeys.add(itemKey)); + } + commitSelection(nextKeys); + }, + [commitSelection, enabledItemKeys, isAllSelected, selectedKeySet, selectionMode], + ); + + return { + getItemKey, + selectedKeySet, + disabledKeySet, + selectedItems, + isAllSelected, + isPartiallySelected, + commitSelection, + selectItem, + toggleSelectAll, + }; +}; diff --git a/src/controls/DetailsList/useDetailsListStyles.ts b/src/controls/DetailsList/useDetailsListStyles.ts new file mode 100644 index 000000000..3ff00cfb8 --- /dev/null +++ b/src/controls/DetailsList/useDetailsListStyles.ts @@ -0,0 +1,374 @@ +import { css } from '@emotion/css'; +import { tokens } from '@fluentui/react-components'; + +export const DETAILS_LIST_HEADER_HEIGHT = 42; +export const DETAILS_LIST_HEADER_TOP_SPACING = 16; +export const DETAILS_LIST_SELECTION_COLUMN_WIDTH = 48; +export const DETAILS_LIST_DEFAULT_ROW_HEIGHT = 42; +export const DETAILS_LIST_COMPACT_ROW_HEIGHT = 32; +export const DETAILS_LIST_DEFAULT_HEIGHT = 600; + +const DETAILS_LIST_SCROLLBAR_SIZE = '3px'; + +interface IDetailsListClassNames { + root: string; + viewport: string; + grid: string; + headerViewport: string; + headerWrapper: string; + headerRow: string; + headerCell: string; + headerButton: string; + headerLabel: string; + headerIcon: string; + resizeHandle: string; + row: string; + selectedRow: string; + disabledRow: string; + dropTargetRow: string; + cell: string; + paddedCell: string; + multilineCell: string; + rowHeaderCell: string; + cellText: string; + selectionCell: string; + selectionCellOnHover: string; + groupHeader: string; + groupButton: string; + groupIndent: string; + body: string; + shimmerRow: string; + shimmerCell: string; + virtualEntry: string; + stateContainer: string; + loadingMore: string; +} + +export const useDetailsListStyles = (): IDetailsListClassNames => { + const selectionCell = css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + boxSizing: 'border-box', + minWidth: 0, + overflow: 'hidden', + }); + + const row = css({ + position: 'relative', + display: 'grid', + alignItems: 'stretch', + minWidth: '100%', + boxSizing: 'border-box', + color: tokens.colorNeutralForeground2, + backgroundColor: tokens.colorNeutralBackground1, + borderBottom: `1px solid ${tokens.colorNeutralStroke2}`, + cursor: 'default', + outline: 'none', + '&:hover': { + color: tokens.colorNeutralForeground1, + backgroundColor: tokens.colorNeutralBackground1Hover, + }, + '&:focus-visible': { + outline: `2px solid ${tokens.colorBrandStroke1}`, + outlineOffset: '-2px', + zIndex: 1, + }, + [`&:hover .${selectionCell}, &:focus-within .${selectionCell}`]: { + opacity: 1, + }, + }); + + return { + root: css({ + position: 'relative', + width: '100%', + minWidth: 0, + color: tokens.colorNeutralForeground2, + backgroundColor: tokens.colorNeutralBackground1, + fontFamily: tokens.fontFamilyBase, + fontSize: tokens.fontSizeBase200, + lineHeight: tokens.lineHeightBase200, + }), + viewport: css({ + position: 'relative', + flex: '1 1 auto', + width: '100%', + minWidth: 0, + minHeight: 0, + overflow: 'auto', + overscrollBehavior: 'contain', + WebkitOverflowScrolling: 'touch', + scrollbarWidth: 'thin', + scrollbarColor: `${tokens.colorBrandBackground} ${tokens.colorNeutralBackground3}`, + '&::-webkit-scrollbar': { + width: DETAILS_LIST_SCROLLBAR_SIZE, + height: DETAILS_LIST_SCROLLBAR_SIZE, + }, + '&::-webkit-scrollbar-track': { + background: tokens.colorNeutralBackground3, + borderRadius: tokens.borderRadiusMedium, + }, + '&::-webkit-scrollbar-thumb': { + background: tokens.colorBrandBackground, + borderRadius: tokens.borderRadiusMedium, + }, + '&::-webkit-scrollbar-thumb:hover': { + background: tokens.colorBrandBackgroundHover, + }, + }), + grid: css({ + position: 'relative', + display: 'flex', + flexDirection: 'column', + width: '100%', + minWidth: 0, + outline: 'none', + }), + headerViewport: css({ + flex: '0 0 auto', + width: '100%', + minWidth: 0, + overflow: 'hidden', + }), + headerWrapper: css({ + position: 'relative', + minWidth: '100%', + boxSizing: 'border-box', + paddingTop: tokens.spacingVerticalL, + backgroundColor: tokens.colorNeutralBackground1, + borderBottom: `1px solid ${tokens.colorNeutralStroke1}`, + }), + headerRow: css({ + display: 'grid', + alignItems: 'stretch', + minWidth: '100%', + height: DETAILS_LIST_HEADER_HEIGHT, + boxSizing: 'border-box', + }), + headerCell: css({ + position: 'relative', + display: 'flex', + minWidth: 0, + height: DETAILS_LIST_HEADER_HEIGHT, + boxSizing: 'border-box', + overflow: 'visible', + backgroundColor: tokens.colorNeutralBackground1, + }), + headerButton: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + gap: tokens.spacingHorizontalXS, + width: '100%', + minWidth: 0, + height: '100%', + boxSizing: 'border-box', + padding: `0 ${tokens.spacingHorizontalS} 0 ${tokens.spacingHorizontalM}`, + color: 'inherit', + backgroundColor: 'transparent', + border: 0, + borderRadius: 0, + font: 'inherit', + textAlign: 'left', + cursor: 'pointer', + outline: 'none', + '&:hover': { + backgroundColor: tokens.colorNeutralBackground1Hover, + }, + '&:active': { + backgroundColor: tokens.colorNeutralBackground1Pressed, + }, + '&:focus-visible': { + outline: `2px solid ${tokens.colorBrandStroke1}`, + outlineOffset: '-2px', + }, + '&:disabled': { + cursor: 'default', + }, + }), + headerLabel: css({ + minWidth: 0, + flexGrow: 1, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }), + headerIcon: css({ + flexShrink: 0, + color: tokens.colorNeutralForeground3, + }), + resizeHandle: css({ + position: 'absolute', + top: 0, + right: -8, + bottom: 0, + zIndex: 2, + width: 16, + padding: 0, + backgroundColor: 'transparent', + border: 0, + // Fluent UI 9 Button applies a pointer cursor to its root. The resize + // handle must keep the native horizontal-resize affordance instead. + cursor: 'col-resize !important', + touchAction: 'none', + outline: 'none', + '&:hover, &:active, &:focus-visible': { + cursor: 'col-resize !important', + }, + '&::after': { + position: 'absolute', + top: 0, + bottom: 0, + left: '50%', + width: 1, + content: '""', + backgroundColor: tokens.colorNeutralStroke1, + opacity: 0, + }, + '&:hover::after, &:focus-visible::after': { + opacity: 1, + }, + '&:focus-visible': { + outline: `2px solid ${tokens.colorBrandStroke1}`, + outlineOffset: '-2px', + }, + }), + row, + selectedRow: css({ + color: tokens.colorNeutralForeground1, + backgroundColor: tokens.colorSubtleBackgroundSelected, + '&:hover': { + backgroundColor: tokens.colorNeutralBackground3, + }, + }), + disabledRow: css({ + color: tokens.colorNeutralForegroundDisabled, + pointerEvents: 'none', + }), + dropTargetRow: css({ + outline: `2px solid ${tokens.colorBrandStroke1}`, + outlineOffset: '-2px', + }), + cell: css({ + display: 'flex', + alignItems: 'center', + minWidth: 0, + boxSizing: 'border-box', + overflow: 'hidden', + padding: `0 ${tokens.spacingHorizontalS} 0 ${tokens.spacingHorizontalM}`, + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }), + paddedCell: css({ + paddingRight: tokens.spacingHorizontalXXL, + }), + multilineCell: css({ + alignItems: 'flex-start', + paddingTop: tokens.spacingVerticalS, + paddingBottom: tokens.spacingVerticalS, + whiteSpace: 'normal', + wordBreak: 'break-word', + textOverflow: 'clip', + }), + rowHeaderCell: css({ + color: tokens.colorNeutralForeground1, + fontSize: tokens.fontSizeBase300, + }), + cellText: css({ + display: 'block', + minWidth: 0, + maxWidth: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'inherit', + }), + selectionCell, + selectionCellOnHover: css({ + opacity: 0, + }), + groupHeader: css({ + display: 'grid', + alignItems: 'stretch', + minWidth: '100%', + boxSizing: 'border-box', + backgroundColor: tokens.colorNeutralBackground2, + borderBottom: `1px solid ${tokens.colorNeutralStroke2}`, + }), + groupButton: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + gap: tokens.spacingHorizontalS, + minWidth: 0, + width: '100%', + padding: `0 ${tokens.spacingHorizontalM}`, + color: tokens.colorNeutralForeground1, + backgroundColor: 'transparent', + border: 0, + borderRadius: 0, + font: 'inherit', + textAlign: 'left', + cursor: 'pointer', + outline: 'none', + '&:hover': { + backgroundColor: tokens.colorNeutralBackground2Hover, + }, + '&:focus-visible': { + outline: `2px solid ${tokens.colorBrandStroke1}`, + outlineOffset: '-2px', + }, + }), + groupIndent: css({ + flex: `0 0 ${tokens.spacingHorizontalXXL}`, + width: tokens.spacingHorizontalXXL, + }), + body: css({ + position: 'relative', + minWidth: '100%', + overflowAnchor: 'none', + }), + shimmerRow: css({ + display: 'grid', + alignItems: 'stretch', + minWidth: '100%', + boxSizing: 'border-box', + backgroundColor: tokens.colorNeutralBackground1, + borderBottom: `1px solid ${tokens.colorNeutralStroke2}`, + pointerEvents: 'none', + }), + shimmerCell: css({ + display: 'flex', + alignItems: 'center', + minWidth: 0, + boxSizing: 'border-box', + overflow: 'hidden', + padding: `0 ${tokens.spacingHorizontalS} 0 ${tokens.spacingHorizontalM}`, + }), + virtualEntry: css({ + position: 'absolute', + right: 0, + left: 0, + }), + stateContainer: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: tokens.spacingHorizontalS, + minHeight: 160, + padding: tokens.spacingVerticalXXL, + boxSizing: 'border-box', + }), + loadingMore: css({ + position: 'sticky', + left: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + gap: tokens.spacingHorizontalS, + minHeight: DETAILS_LIST_COMPACT_ROW_HEIGHT, + color: tokens.colorNeutralForeground2, + backgroundColor: tokens.colorNeutralBackground1, + }), + }; +}; diff --git a/src/controls/DetailsList/useDetailsListViewport.ts b/src/controls/DetailsList/useDetailsListViewport.ts new file mode 100644 index 000000000..96ddd2461 --- /dev/null +++ b/src/controls/DetailsList/useDetailsListViewport.ts @@ -0,0 +1,131 @@ +/* eslint-disable no-void */ +import * as React from 'react'; +import { ResizeObserver } from '@juggle/resize-observer'; + +import type { IDetailsListProps } from './DetailsList.types'; + +interface UseDetailsListViewportOptions { + itemCount: number; + enableInfiniteScroll: boolean; + infiniteScrollThreshold: number; + onLoadMore: IDetailsListProps['onLoadMore']; + hasNextPage: boolean; + isLoadingMore: boolean; +} + +interface UseDetailsListViewportResult { + viewportRef: React.RefObject; + viewportWidth: number; + viewportHeight: number; + scrollTop: number; + setScrollTop: React.Dispatch>; + isLoadingMoreInternally: boolean; + handleScroll: (event: React.UIEvent) => void; + handleScrollIntent: (viewport: HTMLDivElement) => void; +} + +export const useDetailsListViewport = ({ + itemCount, + enableInfiniteScroll, + infiniteScrollThreshold, + onLoadMore, + hasNextPage, + isLoadingMore, +}: UseDetailsListViewportOptions): UseDetailsListViewportResult => { + const viewportRef = React.useRef(null); + const loadTriggeredForLengthRef = React.useRef(-1); + const infiniteScrollArmedRef = React.useRef(true); + const [viewportWidth, setViewportWidth] = React.useState(0); + const [viewportHeight, setViewportHeight] = React.useState(0); + const [scrollTop, setScrollTop] = React.useState(0); + const [isLoadingMoreInternally, setIsLoadingMoreInternally] = React.useState(false); + + React.useEffect(() => { + const viewport = viewportRef.current; + if (!viewport) { + return undefined; + } + + const updateViewportSize = (): void => { + setViewportWidth(viewport.clientWidth); + setViewportHeight(viewport.clientHeight); + }; + const resizeObserver = new ResizeObserver(updateViewportSize); + resizeObserver.observe(viewport); + updateViewportSize(); + + return () => resizeObserver.disconnect(); + }, []); + + React.useEffect(() => { + if (itemCount !== loadTriggeredForLengthRef.current) { + setIsLoadingMoreInternally(false); + } + }, [itemCount]); + + const triggerLoadMore = React.useCallback((): void => { + if ( + !enableInfiniteScroll || + !hasNextPage || + !onLoadMore || + isLoadingMore || + isLoadingMoreInternally || + loadTriggeredForLengthRef.current === itemCount + ) { + return; + } + + loadTriggeredForLengthRef.current = itemCount; + infiniteScrollArmedRef.current = false; + setIsLoadingMoreInternally(true); + void Promise.resolve(onLoadMore()) + .catch(() => { + loadTriggeredForLengthRef.current = -1; + }) + .finally(() => setIsLoadingMoreInternally(false)); + }, [ + enableInfiniteScroll, + hasNextPage, + isLoadingMore, + isLoadingMoreInternally, + itemCount, + onLoadMore, + ]); + + const handleScroll = React.useCallback( + (event: React.UIEvent): void => { + const viewport = event.currentTarget; + setScrollTop(viewport.scrollTop); + const remainingScroll = viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop; + if (remainingScroll <= infiniteScrollThreshold && infiniteScrollArmedRef.current) { + triggerLoadMore(); + } + }, + [infiniteScrollThreshold, triggerLoadMore], + ); + + const handleScrollIntent = React.useCallback( + (viewport: HTMLDivElement): void => { + if (isLoadingMore || isLoadingMoreInternally) { + return; + } + infiniteScrollArmedRef.current = true; + const remainingScroll = viewport.scrollHeight - viewport.clientHeight - viewport.scrollTop; + if (remainingScroll <= infiniteScrollThreshold) { + triggerLoadMore(); + } + }, + [infiniteScrollThreshold, isLoadingMore, isLoadingMoreInternally, triggerLoadMore], + ); + + return { + viewportRef, + viewportWidth, + viewportHeight, + scrollTop, + setScrollTop, + isLoadingMoreInternally, + handleScroll, + handleScrollIntent, + }; +}; diff --git a/src/index.ts b/src/index.ts index 3f6827b15..148032d21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * from './FileTypeIcon'; export * from './ListView'; +export * from './DetailsList'; export * from './Placeholder'; export * from './SiteBreadcrumb'; export * from './TaxonomyPicker'; diff --git a/src/loc/bg-bg.ts b/src/loc/bg-bg.ts index b7290dac9..58a470256 100644 --- a/src/loc/bg-bg.ts +++ b/src/loc/bg-bg.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Търсене", "mapsTitlePrefix": "Карта на", "ListViewFilterLabel": "Филтриране на списъка", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Нормален текст", "HeaderH2": "Функция 2", "HeaderH3": "Функция 3", diff --git a/src/loc/ca-es.ts b/src/loc/ca-es.ts index fb01d1b7c..d699314aa 100644 --- a/src/loc/ca-es.ts +++ b/src/loc/ca-es.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Cerca", "mapsTitlePrefix": "Mapa de", "ListViewFilterLabel": "Filtrar la llista", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Text normal", "HeaderH2": "Encapçalament 2", "HeaderH3": "Encapçalament 3", diff --git a/src/loc/cs-cz.ts b/src/loc/cs-cz.ts index 330059b49..1856eb864 100644 --- a/src/loc/cs-cz.ts +++ b/src/loc/cs-cz.ts @@ -94,6 +94,15 @@ define([], () => { mapsTitlePrefix: "Mapa:", ListViewFilterLabel: "Filtrovat seznam", + DetailsListAriaLabel: "Details list", + DetailsListHeaderAriaLabel: "List header", + DetailsListSelectAllRowsAriaLabel: "Select all rows", + DetailsListSelectionAriaLabel: "Selection", + DetailsListSelectRowAriaLabel: "Select row {0}", + DetailsListResizeColumnAriaLabel: "Resize {0} column", + DetailsListLoading: "Loading", + DetailsListLoadingMore: "Loading more items", + DetailsListNoItems: "No items to display", HeaderNormalText: "Normální text", HeaderH2: "Nadpis 2", diff --git a/src/loc/da-dk.ts b/src/loc/da-dk.ts index 03631d955..36531b104 100644 --- a/src/loc/da-dk.ts +++ b/src/loc/da-dk.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Søg", "mapsTitlePrefix": "Kort over", "ListViewFilterLabel": "Filtrere listen", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normal tekst", "HeaderH2": "Overskrift 2", "HeaderH3": "Overskrift 3", diff --git a/src/loc/de-de.ts b/src/loc/de-de.ts index ecf48939e..1a730ee6a 100644 --- a/src/loc/de-de.ts +++ b/src/loc/de-de.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Suche", "mapsTitlePrefix": "Karte von", "ListViewFilterLabel": "Filter", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Standardtext", "HeaderH2": "Überschrift 2", "HeaderH3": "Überschrift 3", diff --git a/src/loc/el-gr.ts b/src/loc/el-gr.ts index d1d61eb36..e9003dc69 100644 --- a/src/loc/el-gr.ts +++ b/src/loc/el-gr.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Αναζήτηση", "mapsTitlePrefix": "Χάρτης", "ListViewFilterLabel": "Φιλτράρισμα της λίστας", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Κανονικό κείμενο", "HeaderH2": "Τομέας 2", "HeaderH3": "Τομέας 3", diff --git a/src/loc/en-us.ts b/src/loc/en-us.ts index 52ba0cd0f..2e7dda64d 100644 --- a/src/loc/en-us.ts +++ b/src/loc/en-us.ts @@ -89,6 +89,15 @@ define([], () => { mapsTitlePrefix: "Map of", ListViewFilterLabel: "Filter the list", + DetailsListAriaLabel: "Details list", + DetailsListHeaderAriaLabel: "List header", + DetailsListSelectAllRowsAriaLabel: "Select all rows", + DetailsListSelectionAriaLabel: "Selection", + DetailsListSelectRowAriaLabel: "Select row {0}", + DetailsListResizeColumnAriaLabel: "Resize {0} column", + DetailsListLoading: "Loading", + DetailsListLoadingMore: "Loading more items", + DetailsListNoItems: "No items to display", HeaderNormalText: "Normal text", HeaderH2: "Heading 2", diff --git a/src/loc/es-es.ts b/src/loc/es-es.ts index 1a918dca7..207c6435a 100644 --- a/src/loc/es-es.ts +++ b/src/loc/es-es.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Búsqueda", "mapsTitlePrefix": "Mapa de", "ListViewFilterLabel": "Filtrar la lista", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Texto normal", "HeaderH2": "Rumbo 2", "HeaderH3": "Rumbo 3", diff --git a/src/loc/et-ee.ts b/src/loc/et-ee.ts index 83553049a..2a4b72f5c 100644 --- a/src/loc/et-ee.ts +++ b/src/loc/et-ee.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Otsing", "mapsTitlePrefix": "Kaardi kaart", "ListViewFilterLabel": "Filtreeri loendit", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Tavaline tekst", "HeaderH2": "1. rubriik", "HeaderH3": "2. rubriik", diff --git a/src/loc/eu-es.ts b/src/loc/eu-es.ts index 86f760733..4475530e2 100644 --- a/src/loc/eu-es.ts +++ b/src/loc/eu-es.ts @@ -82,6 +82,15 @@ define([], () => { mapsTitlePrefix: 'Map of', ListViewFilterLabel: "Filter the list", + DetailsListAriaLabel: "Details list", + DetailsListHeaderAriaLabel: "List header", + DetailsListSelectAllRowsAriaLabel: "Select all rows", + DetailsListSelectionAriaLabel: "Selection", + DetailsListSelectRowAriaLabel: "Select row {0}", + DetailsListResizeColumnAriaLabel: "Resize {0} column", + DetailsListLoading: "Loading", + DetailsListLoadingMore: "Loading more items", + DetailsListNoItems: "No items to display", HeaderNormalText: "Normal text", HeaderH2: "Heading 2", diff --git a/src/loc/fi-fi.ts b/src/loc/fi-fi.ts index b6b79fcaf..3ce99afdb 100644 --- a/src/loc/fi-fi.ts +++ b/src/loc/fi-fi.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Etsi", "mapsTitlePrefix": "Kartta", "ListViewFilterLabel": "Suodata luettelo", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normaali teksti", "HeaderH2": "Otsake 2", "HeaderH3": "Otsake 3", diff --git a/src/loc/fr-ca.ts b/src/loc/fr-ca.ts index eb089c267..b1e87057b 100644 --- a/src/loc/fr-ca.ts +++ b/src/loc/fr-ca.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Rechercher", "mapsTitlePrefix": "Carte de", "ListViewFilterLabel": "Filtrer", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Texte normal", "HeaderH2": "Titre 2", "HeaderH3": "Titre 3", diff --git a/src/loc/fr-fr.ts b/src/loc/fr-fr.ts index 7235df017..06abddcea 100644 --- a/src/loc/fr-fr.ts +++ b/src/loc/fr-fr.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Rechercher", "mapsTitlePrefix": "Carte de", "ListViewFilterLabel": "Filtrer", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Texte normal", "HeaderH2": "Titre 2", "HeaderH3": "Titre 3", diff --git a/src/loc/it-it.ts b/src/loc/it-it.ts index 8604aec9e..0d916ae76 100644 --- a/src/loc/it-it.ts +++ b/src/loc/it-it.ts @@ -108,6 +108,15 @@ define([], () => { "mapsSearchButtonText": "Ricerca", "mapsTitlePrefix": "Mappa di", "ListViewFilterLabel": "Filtrare l'elenco", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Testo normale", "HeaderH2": "Titolo 2", "HeaderH3": "Titolo 3", diff --git a/src/loc/ja-jp.ts b/src/loc/ja-jp.ts index ae0514d8a..25aa37301 100644 --- a/src/loc/ja-jp.ts +++ b/src/loc/ja-jp.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "検索", "mapsTitlePrefix": "地図", "ListViewFilterLabel": "リストをフィルター処理する", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "標準テキスト", "HeaderH2": "見出し 2", "HeaderH3": "見出し 3", diff --git a/src/loc/lt-lt.ts b/src/loc/lt-lt.ts index 49b602ba5..04521858f 100644 --- a/src/loc/lt-lt.ts +++ b/src/loc/lt-lt.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Paieška", "mapsTitlePrefix": "Žemėlapis", "ListViewFilterLabel": "Filtruoti", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Paprastas tekstas", "HeaderH2": "1 antraštė", "HeaderH3": "2 antraštė", diff --git a/src/loc/lv-lv.ts b/src/loc/lv-lv.ts index 89d3cc4e8..fe835e7db 100644 --- a/src/loc/lv-lv.ts +++ b/src/loc/lv-lv.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Meklēšanas", "mapsTitlePrefix": "Karti par", "ListViewFilterLabel": "Filtrētu sarakstu", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Parastu tekstu", "HeaderH2": "1. izdevumu kategorija", "HeaderH3": "2. izdevumu kategorija", diff --git a/src/loc/mystrings.d.ts b/src/loc/mystrings.d.ts index dc3dba7f9..321310290 100644 --- a/src/loc/mystrings.d.ts +++ b/src/loc/mystrings.d.ts @@ -98,6 +98,15 @@ KPIPercentOfTotal: string; PeoplePickerGroupNotFound: string; ListViewFilterLabel: string; + DetailsListAriaLabel: string; + DetailsListHeaderAriaLabel: string; + DetailsListSelectAllRowsAriaLabel: string; + DetailsListSelectionAriaLabel: string; + DetailsListSelectRowAriaLabel: string; + DetailsListResizeColumnAriaLabel: string; + DetailsListLoading: string; + DetailsListLoadingMore: string; + DetailsListNoItems: string; PeoplePickerSearchText: string; peoplePickerComponentTooltipMessage: string; diff --git a/src/loc/nb-no.ts b/src/loc/nb-no.ts index 3e9f95ec8..fc89c5498 100644 --- a/src/loc/nb-no.ts +++ b/src/loc/nb-no.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Søk", "mapsTitlePrefix": "Kart over", "ListViewFilterLabel": "Filtrer listen", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Vanlig tekst", "HeaderH2": "Overskrift 2", "HeaderH3": "Overskrift 3", diff --git a/src/loc/nl-nl.ts b/src/loc/nl-nl.ts index 2433e8ee9..8d96927f0 100644 --- a/src/loc/nl-nl.ts +++ b/src/loc/nl-nl.ts @@ -108,6 +108,15 @@ define([], () => { "mapsSearchButtonText": "Zoeken", "mapsTitlePrefix": "Kaart van", "ListViewFilterLabel": "Filter de lijst", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normale tekst", "HeaderH2": "Rubriek 2", "HeaderH3": "Rubriek 3", diff --git a/src/loc/pl-pl.ts b/src/loc/pl-pl.ts index ea0367e14..3e8f94e5c 100644 --- a/src/loc/pl-pl.ts +++ b/src/loc/pl-pl.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Szukaj", "mapsTitlePrefix": "Mapa", "ListViewFilterLabel": "Filtrowanie listy", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Zwykły tekst", "HeaderH2": "Dział 2", "HeaderH3": "Dział 3", diff --git a/src/loc/pt-br.ts b/src/loc/pt-br.ts index 951a9f65d..21fa91202 100644 --- a/src/loc/pt-br.ts +++ b/src/loc/pt-br.ts @@ -77,6 +77,15 @@ define([], () => { mapsSearchButtonText: 'Procurar', mapsTitlePrefix: 'Mapa de', ListViewFilterLabel: "Filtre a lista", + DetailsListAriaLabel: "Details list", + DetailsListHeaderAriaLabel: "List header", + DetailsListSelectAllRowsAriaLabel: "Select all rows", + DetailsListSelectionAriaLabel: "Selection", + DetailsListSelectRowAriaLabel: "Select row {0}", + DetailsListResizeColumnAriaLabel: "Resize {0} column", + DetailsListLoading: "Loading", + DetailsListLoadingMore: "Loading more items", + DetailsListNoItems: "No items to display", HeaderNormalText: "Texto normal", HeaderH2: "Cabeçalho 2", HeaderH3: "Título 3", @@ -443,4 +452,3 @@ worldMapZoomIn: "Zoom in", "UploadFilesUploadButtonLabel": "Upload", }; }); - diff --git a/src/loc/pt-pt.ts b/src/loc/pt-pt.ts index 96a37baa1..6700e0ec9 100644 --- a/src/loc/pt-pt.ts +++ b/src/loc/pt-pt.ts @@ -112,6 +112,15 @@ define([], () => { "mapsSearchButtonText": "Procurar", "mapsTitlePrefix": "Mapa de", "ListViewFilterLabel": "Filtrar a lista", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Texto normal", "HeaderH2": "Cabeçalho 2", "HeaderH3": "Título 3", diff --git a/src/loc/ro-ro.ts b/src/loc/ro-ro.ts index 424ed4374..c17791024 100644 --- a/src/loc/ro-ro.ts +++ b/src/loc/ro-ro.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Căutaţi", "mapsTitlePrefix": "Harta", "ListViewFilterLabel": "Filtrați lista", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Text normal", "HeaderH2": "Titlul 2", "HeaderH3": "Titlu 3", diff --git a/src/loc/ru-ru.ts b/src/loc/ru-ru.ts index 3a2be3896..2580f4979 100644 --- a/src/loc/ru-ru.ts +++ b/src/loc/ru-ru.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Поиск", "mapsTitlePrefix": "Карта", "ListViewFilterLabel": "Фильтр", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Обычный", "HeaderH2": "Заголовок 2", "HeaderH3": "Заголовок 3", diff --git a/src/loc/sk-sk.ts b/src/loc/sk-sk.ts index 6abec4431..a1410cdc7 100644 --- a/src/loc/sk-sk.ts +++ b/src/loc/sk-sk.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Hľadať", "mapsTitlePrefix": "Mapa mesta", "ListViewFilterLabel": "Filtrovanie zoznamu", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normálny text", "HeaderH2": "Nadpis 2", "HeaderH3": "Nadpis 3", diff --git a/src/loc/sr-latn-rs.ts b/src/loc/sr-latn-rs.ts index 61a6e5cee..ba077ffe1 100644 --- a/src/loc/sr-latn-rs.ts +++ b/src/loc/sr-latn-rs.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Pretrage", "mapsTitlePrefix": "Karta", "ListViewFilterLabel": "Filtrirajte listu", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normalan tekst", "HeaderH2": "Naslov 2", "HeaderH3": "Naslov 3", diff --git a/src/loc/sv-se.ts b/src/loc/sv-se.ts index 90f41a517..4d0ecb755 100644 --- a/src/loc/sv-se.ts +++ b/src/loc/sv-se.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Sök", "mapsTitlePrefix": "Karta över", "ListViewFilterLabel": "Filtrera listan", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normal", "HeaderH2": "Rubrik 2", "HeaderH3": "Rubrik 3", diff --git a/src/loc/tr-tr.ts b/src/loc/tr-tr.ts index 5a488b9fb..1ed2f5e75 100644 --- a/src/loc/tr-tr.ts +++ b/src/loc/tr-tr.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Arama", "mapsTitlePrefix": "Harita", "ListViewFilterLabel": "Listeye filtre uygulayın", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Normal metin", "HeaderH2": "Başlık 2", "HeaderH3": "Başlık 3", diff --git a/src/loc/vi-vn.ts b/src/loc/vi-vn.ts index 7cd00393e..a6d05f541 100644 --- a/src/loc/vi-vn.ts +++ b/src/loc/vi-vn.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "Tìm", "mapsTitlePrefix": "Bản đồ", "ListViewFilterLabel": "Lọc danh sách", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "Văn bản bình thường", "HeaderH2": "Nhóm 2", "HeaderH3": "Nhóm 3", diff --git a/src/loc/zh-cn.ts b/src/loc/zh-cn.ts index 60cb860ef..a55f2e9d4 100644 --- a/src/loc/zh-cn.ts +++ b/src/loc/zh-cn.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "搜索", "mapsTitlePrefix": "地图", "ListViewFilterLabel": "筛选列表", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "普通文本", "HeaderH2": "标题 2", "HeaderH3": "标题 3", diff --git a/src/loc/zh-tw.ts b/src/loc/zh-tw.ts index 5f59bf406..67567a3cc 100644 --- a/src/loc/zh-tw.ts +++ b/src/loc/zh-tw.ts @@ -110,6 +110,15 @@ define([], () => { "mapsSearchButtonText": "搜尋", "mapsTitlePrefix": "地圖", "ListViewFilterLabel": "篩選列表", + "DetailsListAriaLabel": "Details list", + "DetailsListHeaderAriaLabel": "List header", + "DetailsListSelectAllRowsAriaLabel": "Select all rows", + "DetailsListSelectionAriaLabel": "Selection", + "DetailsListSelectRowAriaLabel": "Select row {0}", + "DetailsListResizeColumnAriaLabel": "Resize {0} column", + "DetailsListLoading": "Loading", + "DetailsListLoadingMore": "Loading more items", + "DetailsListNoItems": "No items to display", "HeaderNormalText": "普通文字", "HeaderH2": "標題 2", "HeaderH3": "標題 3", diff --git a/src/webparts/controlsTest/ControlsTestWebPart.manifest.json b/src/webparts/controlsTest/ControlsTestWebPart.manifest.json index 2491779a5..4a342bb28 100644 --- a/src/webparts/controlsTest/ControlsTestWebPart.manifest.json +++ b/src/webparts/controlsTest/ControlsTestWebPart.manifest.json @@ -64,6 +64,7 @@ "ListItemPicker": false, "ListPicker": false, "ListView": false, + "DetailsList": false, "LivePersona": false, "LocationPicker": false, "Map": false, diff --git a/src/webparts/controlsTest/IControlsTestWebPartProps.ts b/src/webparts/controlsTest/IControlsTestWebPartProps.ts index 22d9e51ed..d365d8313 100644 --- a/src/webparts/controlsTest/IControlsTestWebPartProps.ts +++ b/src/webparts/controlsTest/IControlsTestWebPartProps.ts @@ -8,7 +8,7 @@ export type ValidControls = "all" | "GroupPicker" | "GridLayout" | "HoverReactionsBar" | "IconPicker" | "IFrameDialog" | "IFramePanel" | "ImagePicker" | "ListItemAttachments" | "ListItemComments" | - "ListItemPicker" | "ListPicker" | "ListToolbar" | "ListView" | "LivePersona" | + "ListItemPicker" | "ListPicker" | "ListToolbar" | "ListView" | "DetailsList" | "LivePersona" | "LocationPicker" | "Map" | "ModernAudio" | "ModernTaxonomyPicker" | "MonacoEditor" | "Pagination" | "PeoplePicker" | "Placeholder" | "Progress" | "ProgressStepsIndicator" | "RichText" | diff --git a/src/webparts/controlsTest/components/ControlsTest.tsx b/src/webparts/controlsTest/components/ControlsTest.tsx index 2b37294c9..343206d83 100644 --- a/src/webparts/controlsTest/components/ControlsTest.tsx +++ b/src/webparts/controlsTest/components/ControlsTest.tsx @@ -249,6 +249,44 @@ const ListToolbar = React.lazy(() => import('../../../controls/ListToolbar').the const ListView = React.lazy(() => import('../../../ListView').then(module => ({ default: module.ListView }))); +interface IDetailsListShowcaseErrorBoundaryProps { + children?: React.ReactNode; +} + +interface IDetailsListShowcaseErrorBoundaryState { + error?: Error; +} + +class DetailsListShowcaseErrorBoundary extends React.Component< + IDetailsListShowcaseErrorBoundaryProps, + IDetailsListShowcaseErrorBoundaryState +> { + public state: IDetailsListShowcaseErrorBoundaryState = {}; + + public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + // Keep the host page alive while preserving the original diagnostic. + console.error('DetailsList showcase failed to render.', error, errorInfo); + this.setState({ error }); + } + + public render(): React.ReactNode { + if (this.state.error) { + return ( +
+ DetailsList showcase could not be rendered. Check the browser console for details. +
+ ); + } + + return this.props.children; + } +} + +const DetailsList = React.lazy(() => + import(/* webpackChunkName: 'details-list-showcase' */ './TestDetailsListControl') + .then(module => ({ default: module.TestDetailsListControl })) +); + const Map = React.lazy(() => import('../../../Map').then(module => ({ default: module.Map }))); const ModernAudio = React.lazy(() => import('../../../ModernAudio').then(module => ({ default: module.ModernAudio }))); @@ -1699,6 +1737,13 @@ export default class ControlsTest extends React.Component } + {controlVisibility.DetailsList && +
+ + + +
+ } {controlVisibility.ChartControl &&
; + +const BASE_SHOWCASE_ITEMS: readonly IBaseDetailsListShowcaseItem[] = [ + { id: 'project-01', name: 'Project charter', category: 'Planning', status: 'Complete', updatedAt: '2026-07-28T09:00:00Z' }, + { id: 'project-02', name: 'Risk register', category: 'Planning', status: 'In progress', updatedAt: '2026-07-30T14:30:00Z' }, + { id: 'project-03', name: 'Design system', category: 'Design', status: 'In review', updatedAt: '2026-08-01T10:15:00Z' }, + { id: 'project-04', name: 'Accessibility audit', category: 'Design', status: 'Complete', updatedAt: '2026-08-02T16:00:00Z' }, + { id: 'project-05', name: 'API contract', category: 'Engineering', status: 'In progress', updatedAt: '2026-08-03T11:45:00Z' }, + { id: 'project-06', name: 'Integration tests', category: 'Engineering', status: 'Not started', updatedAt: '2026-08-04T08:20:00Z' }, + { id: 'project-07', name: 'Release notes', category: 'Launch', status: 'Not started', updatedAt: '2026-08-05T13:10:00Z' }, + { id: 'project-08', name: 'Support handover', category: 'Launch', status: 'In progress', updatedAt: '2026-08-06T15:25:00Z' }, + { id: 'project-09', name: 'User interviews', category: 'Research', status: 'Complete', updatedAt: '2026-08-07T09:40:00Z' }, + { id: 'project-10', name: 'Usage analytics', category: 'Research', status: 'In review', updatedAt: '2026-08-08T12:05:00Z' }, + { id: 'project-11', name: 'Roadmap update', category: 'Planning', status: 'In progress', updatedAt: '2026-08-09T10:00:00Z' }, + { id: 'project-12', name: 'Wireframe refresh', category: 'Design', status: 'Complete', updatedAt: '2026-08-09T16:35:00Z' }, + { id: 'project-13', name: 'Performance budget', category: 'Engineering', status: 'In review', updatedAt: '2026-08-10T09:50:00Z' }, + { id: 'project-14', name: 'Deployment checklist', category: 'Launch', status: 'Not started', updatedAt: '2026-08-10T14:15:00Z' }, + { id: 'project-15', name: 'Feedback synthesis', category: 'Research', status: 'Complete', updatedAt: '2026-08-11T11:30:00Z' }, + { id: 'project-16', name: 'Sprint summary', category: 'Planning', status: 'Complete', updatedAt: '2026-08-11T17:20:00Z' }, + { id: 'project-17', name: 'Component inventory', category: 'Design', status: 'In progress', updatedAt: '2026-08-12T08:45:00Z' }, + { id: 'project-18', name: 'Error handling', category: 'Engineering', status: 'In review', updatedAt: '2026-08-12T12:40:00Z' }, + { id: 'project-19', name: 'Demo script', category: 'Launch', status: 'Complete', updatedAt: '2026-08-12T15:05:00Z' }, + { id: 'project-20', name: 'Satisfaction survey', category: 'Research', status: 'Not started', updatedAt: '2026-08-12T16:55:00Z' }, + { id: 'project-21', name: 'Dependency review', category: 'Engineering', status: 'Complete', updatedAt: '2026-08-13T08:10:00Z' }, + { id: 'project-22', name: 'Launch announcement', category: 'Launch', status: 'In progress', updatedAt: '2026-08-13T09:25:00Z' }, + { id: 'project-23', name: 'Research archive', category: 'Research', status: 'Complete', updatedAt: '2026-08-13T10:45:00Z' }, + { id: 'project-24', name: 'Quarterly goals', category: 'Planning', status: 'In review', updatedAt: '2026-08-13T11:15:00Z' }, +]; + +const SHOWCASE_OWNERS: readonly string[] = ['Maya Patel', 'João Mendes', 'Alex Kim', 'Sofia Rossi', 'Daniel Chen']; +const SHOWCASE_PRIORITIES: readonly WorkItemPriority[] = ['High', 'Medium', 'Low', 'Medium', 'High']; + +const SHOWCASE_ITEMS: readonly IDetailsListShowcaseItem[] = BASE_SHOWCASE_ITEMS.map((item, index) => ({ + ...item, + owner: SHOWCASE_OWNERS[index % SHOWCASE_OWNERS.length], + priority: SHOWCASE_PRIORITIES[index % SHOWCASE_PRIORITIES.length], + dueDate: new Date(Date.UTC(2026, 7, 14 + index)).toISOString(), +})); + +const getSortValue = (item: IDetailsListShowcaseItem, key: SortKey): string => { + return item[key]; +}; + +const getStatusIcon = (status: WorkItemStatus): React.ReactElement => { + switch (status) { + case 'Complete': + return ; + case 'In progress': + return ; + case 'In review': + return ; + default: + return ; + } +}; + +const wait = (duration: number): Promise => { + return new Promise((resolve) => { + window.setTimeout(resolve, duration); + }); +}; + +export const TestDetailsListControl: React.FunctionComponent = () => { + const styles = useShowcaseStyles(); + const [items, setItems] = React.useState(() => ( + SHOWCASE_ITEMS.slice(0, INITIAL_ITEM_COUNT) + )); + const [sortState, setSortState] = React.useState(); + const [collapsedGroups, setCollapsedGroups] = React.useState>(() => new Set()); + const [selectedItems, setSelectedItems] = React.useState([]); + const [isLoadingMore, setIsLoadingMore] = React.useState(false); + const [contextMenuTarget, setContextMenuTarget] = React.useState(); + const [contextMenuItem, setContextMenuItem] = React.useState(); + const [statusMessage, setStatusMessage] = React.useState('Select a work item, use its action buttons, or open the row context menu.'); + + const hasNextPage = React.useMemo(() => { + const loadedIds = new Set(items.map((item) => item.id)); + return SHOWCASE_ITEMS.some((item) => !loadedIds.has(item.id)); + }, [items]); + + const selectedKeys = React.useMemo(() => { + return new Set(selectedItems.map((item) => item.id)); + }, [selectedItems]); + + const sortedItems = React.useMemo(() => { + const nextItems = [...items]; + + if (!sortState) { + return nextItems; + } + + const direction = sortState.descending ? -1 : 1; + return nextItems.sort((left, right) => { + const result = getSortValue(left, sortState.key).localeCompare(getSortValue(right, sortState.key)); + return result === 0 ? left.id.localeCompare(right.id) : direction * result; + }); + }, [items, sortState]); + + const groupedData = React.useMemo(() => { + const itemsByCategory = new Map(); + + sortedItems.forEach((item) => { + const categoryItems = itemsByCategory.get(item.category) || []; + categoryItems.push(item); + itemsByCategory.set(item.category, categoryItems); + }); + + const groupedItems: IDetailsListShowcaseItem[] = []; + const groups: IDetailsListGroup[] = []; + + itemsByCategory.forEach((categoryItems, category) => { + const startIndex = groupedItems.length; + groupedItems.push(...categoryItems); + groups.push({ + key: category, + name: category, + startIndex, + count: categoryItems.length, + isCollapsed: collapsedGroups.has(category), + }); + }); + + return { items: groupedItems, groups }; + }, [collapsedGroups, sortedItems]); + + const handleColumnHeaderClick = React.useCallback( + (_event: React.MouseEvent, column: IColumn): void => { + if (SORT_KEYS.indexOf(column.key as SortKey) < 0) { + return; + } + + const key = column.key as SortKey; + const descending = sortState && sortState.key === key ? !sortState.descending : false; + setSortState({ key, descending }); + setStatusMessage(`Sorted by ${column.name} (${descending ? 'descending' : 'ascending'}).`); + }, + [sortState] + ); + + const handleSelectionChanged = React.useCallback( + (change: IDetailsListSelectionChange): void => { + setSelectedItems(change.selectedItems); + setStatusMessage(`${change.selectedItems.length} row(s) selected.`); + }, + [] + ); + + const handleToggleGroup = React.useCallback( + (group: IDetailsListGroup, isCollapsed: boolean): void => { + setCollapsedGroups((currentGroups) => { + const nextGroups = new Set(currentGroups); + if (isCollapsed) { + nextGroups.add(group.key); + } else { + nextGroups.delete(group.key); + } + return nextGroups; + }); + }, + [] + ); + + const handleLoadMore = React.useCallback(async (): Promise => { + if (isLoadingMore || !hasNextPage) { + return; + } + + setIsLoadingMore(true); + setStatusMessage('Loading more rows...'); + await wait(LOAD_DELAY_MS); + + setItems((currentItems) => { + const loadedIds = new Set(currentItems.map((item) => item.id)); + const nextItems = SHOWCASE_ITEMS + .filter((item) => !loadedIds.has(item.id)) + .slice(0, LOAD_PAGE_SIZE); + return [...currentItems, ...nextItems]; + }); + setIsLoadingMore(false); + setStatusMessage('More rows loaded. Scroll again to continue.'); + }, [hasNextPage, isLoadingMore]); + + const handleDrop = React.useCallback( + ( + draggedItem: IDetailsListShowcaseItem, + targetItem: IDetailsListShowcaseItem, + _targetIndex: number, + _event: React.DragEvent + ): void => { + if (draggedItem.id === targetItem.id) { + return; + } + + setItems((currentItems) => { + const sourceIndex = currentItems.findIndex((item) => item.id === draggedItem.id); + const targetIndex = currentItems.findIndex((item) => item.id === targetItem.id); + if (sourceIndex < 0 || targetIndex < 0) { + return currentItems; + } + + const nextItems = [...currentItems]; + const [movedItem] = nextItems.splice(sourceIndex, 1); + const insertionIndex = targetIndex > sourceIndex ? targetIndex - 1 : targetIndex; + nextItems.splice(insertionIndex, 0, movedItem); + return nextItems; + }); + setSortState(undefined); + setStatusMessage(`Moved ${draggedItem.name} before ${targetItem.name}.`); + }, + [] + ); + + const dragDropEvents = React.useMemo>(() => ({ + canDrag: () => true, + canDrop: (draggedItem, targetItem) => draggedItem.id !== targetItem.id, + onDragStart: (item) => setStatusMessage(`Dragging ${item.name}...`), + onDrop: handleDrop, + }), [handleDrop]); + + const handleComplete = React.useCallback((item: IDetailsListShowcaseItem): void => { + setItems((currentItems) => currentItems.map((currentItem) => ( + currentItem.id === item.id + ? { ...currentItem, status: 'Complete', updatedAt: new Date().toISOString() } + : currentItem + ))); + setStatusMessage(`Marked ${item.name} as complete.`); + }, []); + + const dismissContextMenu = React.useCallback((): void => { + setContextMenuTarget(undefined); + setContextMenuItem(undefined); + }, []); + + const openContextMenu = React.useCallback((item: IDetailsListShowcaseItem, target: HTMLElement): void => { + setContextMenuItem(item); + setContextMenuTarget(target); + }, []); + + const handleContextMenu = React.useCallback( + (item: IDetailsListShowcaseItem, _index: number, event: React.MouseEvent): boolean => { + event.preventDefault(); + openContextMenu(item, event.currentTarget); + return true; + }, + [openContextMenu] + ); + + const handleContextMenuAction = React.useCallback((action: ContextMenuAction): void => { + if (!contextMenuItem) { + return; + } + + if (action === 'complete') { + handleComplete(contextMenuItem); + } else if (action === 'delete') { + setItems((currentItems) => currentItems.filter((item) => item.id !== contextMenuItem.id)); + setSelectedItems((currentItems) => currentItems.filter((item) => item.id !== contextMenuItem.id)); + setStatusMessage(`Deleted ${contextMenuItem.name}.`); + } else { + const duplicatedItem: IDetailsListShowcaseItem = { + ...contextMenuItem, + id: `${contextMenuItem.id}-copy-${Date.now()}`, + name: `${contextMenuItem.name} (copy)`, + }; + setItems((currentItems) => [...currentItems, duplicatedItem]); + setStatusMessage(`Duplicated ${contextMenuItem.name}.`); + } + + dismissContextMenu(); + }, [contextMenuItem, dismissContextMenu, handleComplete]); + + const columnReorderOptions = React.useMemo>(() => ({ + frozenColumnCountFromStart: 1, + frozenColumnCountFromEnd: 1, + onColumnDragStart: (dragStarted) => { + setStatusMessage(dragStarted ? 'Dragging a column. Drop it where it belongs.' : 'Column drag ended.'); + }, + onColumnDrop: ({ draggedColumn, targetColumn }) => { + setStatusMessage(`Moved ${draggedColumn.name} before ${targetColumn.name}.`); + }, + onDragEnd: (location) => { + if (location === ColumnDragEndLocation.header) { + setStatusMessage('Column order updated.'); + } + }, + }), []); + + const handleColumnResize = React.useCallback((column: IColumn, width: number): void => { + setStatusMessage(`Resized ${column.name} to ${Math.round(width)} pixels.`); + }, []); + + const renderRowActions = React.useCallback((item: IDetailsListShowcaseItem): React.ReactNode => ( +
+
+ ), [handleComplete, openContextMenu, styles.actions]); + + const columns = React.useMemo[]>(() => { + const getSortProps = (key: SortKey): Pick, 'isSorted' | 'isSortedDescending'> => ({ + isSorted: sortState && sortState.key === key, + isSortedDescending: sortState && sortState.key === key && sortState.descending, + }); + + return [ + { + key: 'name', + name: 'Work item', + fieldName: 'name', + minWidth: 210, + isResizable: true, + isRowHeader: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('name'), + onColumnClick: handleColumnHeaderClick, + onRender: (item) => ( +
+ + + {item.name} +
+ ), + }, + { + key: 'category', + name: 'Category', + fieldName: 'category', + minWidth: 120, + isResizable: true, + isGrouped: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('category'), + onColumnClick: handleColumnHeaderClick, + }, + { + key: 'owner', + name: 'Owner', + fieldName: 'owner', + minWidth: 135, + isResizable: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('owner'), + onColumnClick: handleColumnHeaderClick, + onRender: (item) => ( +
+ + {item.owner} +
+ ), + }, + { + key: 'priority', + name: 'Priority', + fieldName: 'priority', + minWidth: 90, + isResizable: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('priority'), + onColumnClick: handleColumnHeaderClick, + }, + { + key: 'status', + name: 'Status', + fieldName: 'status', + minWidth: 125, + isResizable: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('status'), + onColumnClick: handleColumnHeaderClick, + onRender: (item) => ( +
+ {getStatusIcon(item.status)} + {item.status} +
+ ), + }, + { + key: 'dueDate', + name: 'Due', + fieldName: 'dueDate', + minWidth: 105, + isResizable: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('dueDate'), + onColumnClick: handleColumnHeaderClick, + onRender: (item) => ( +
+ + {new Date(item.dueDate).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} +
+ ), + }, + { + key: 'updatedAt', + name: 'Updated', + fieldName: 'updatedAt', + minWidth: 110, + isResizable: true, + columnActionsMode: ColumnActionsMode.clickable, + ...getSortProps('updatedAt'), + onColumnClick: handleColumnHeaderClick, + onRender: (item) => new Date(item.updatedAt).toLocaleDateString(), + }, + { + key: 'actions', + name: 'Actions', + minWidth: 88, + maxWidth: 88, + isIconOnly: true, + ariaLabel: 'Work item actions', + columnActionsMode: ColumnActionsMode.disabled, + onRender: renderRowActions, + }, + ]; + }, [handleColumnHeaderClick, renderRowActions, sortState, styles]); + + return ( + + +
+ DetailsList samples + + Project delivery queue: sort or resize a column, collapse a category, scroll to load more work, drag rows to reprioritise them, drag column headers to personalise the view, or use row actions and the context menu. + + item.id} + setKey="DetailsListShowcase" + height={DETAILS_LIST_HEIGHT} + compact={true} + layoutMode={DetailsListLayoutMode.justified} + constrainMode={ConstrainMode.horizontalConstrained} + selectionMode={DetailsListSelectionMode.multiple} + checkboxVisibility={CheckboxVisibility.always} + selectedKeys={selectedKeys} + onSelectionChanged={handleSelectionChanged} + onToggleGroup={handleToggleGroup} + onItemContextMenu={handleContextMenu} + dragDropEvents={dragDropEvents} + enableVirtualization={true} + overscanCount={4} + enableInfiniteScroll={true} + infiniteScrollThreshold={160} + onLoadMore={handleLoadMore} + hasNextPage={hasNextPage} + isLoadingMore={isLoadingMore} + rowHeight={40} + groupHeaderHeight={32} + ariaLabelForGrid={strings.DetailsListAriaLabel} + loadingMoreMessage={{strings.DetailsListLoadingMore}} + emptyState={{strings.DetailsListNoItems}} + /> + {statusMessage} + {contextMenuTarget && contextMenuItem && ( + { + if (!data.open) { + dismissContextMenu(); + } + }} + > + + + } onClick={() => handleContextMenuAction('complete')}> + Mark complete + + } onClick={() => handleContextMenuAction('duplicate')}> + Duplicate row + + } onClick={() => handleContextMenuAction('delete')}> + Delete row + + + + + )} +
+
+
+ ); +}; + +TestDetailsListControl.displayName = 'TestDetailsListControl'; diff --git a/src/webparts/controlsTest/propertyPane/controls/ControlToggles.tsx b/src/webparts/controlsTest/propertyPane/controls/ControlToggles.tsx index e600c65bc..ea79f7bc7 100644 --- a/src/webparts/controlsTest/propertyPane/controls/ControlToggles.tsx +++ b/src/webparts/controlsTest/propertyPane/controls/ControlToggles.tsx @@ -60,7 +60,7 @@ export class ControlToggles extends React.Component