diff --git a/.storybook/components/Redirect.tsx b/.storybook/components/Redirect.tsx
new file mode 100644
index 00000000000..da1b13bfe13
--- /dev/null
+++ b/.storybook/components/Redirect.tsx
@@ -0,0 +1,58 @@
+import '@ui5/webcomponents-fiori/dist/illustrations/PageNotFound.js';
+import { IllustratedMessage, Link, Text } from '@ui5/webcomponents-react';
+import { useEffect, useState } from 'react';
+
+interface RedirectProps {
+ from: string;
+ to: string;
+ sectionName: string;
+ /**
+ * Delay in ms before redirecting
+ * @default 5000
+ */
+ delay?: number;
+}
+
+export const Redirect = ({ from, to, sectionName, delay = 1000000 }: RedirectProps) => {
+ const [countdown, setCountdown] = useState(Math.ceil(delay / 1000));
+
+ const target = typeof window !== 'undefined' ? window.top || window.parent || window : null;
+ const currentHref = target?.location.href ?? '';
+ const newHref = currentHref.replace(from, to);
+
+ useEffect(() => {
+ if (typeof window === 'undefined') return;
+ if (!currentHref.includes(from)) return;
+
+ const timer = setTimeout(() => {
+ target.location.replace(newHref);
+ }, delay);
+
+ const countdownInterval = setInterval(() => {
+ setCountdown((prev) => Math.max(0, prev - 1));
+ }, 1000);
+
+ return () => {
+ clearTimeout(timer);
+ clearInterval(countdownInterval);
+ };
+ }, [from, to, delay, currentHref, newHref, target]);
+
+ return (
+
+ This page has been moved to {sectionName}.
+
+ Redirecting in {countdown} seconds...
+
+ }
+ >
+
+ Go to {sectionName}
+
+
+ );
+};
diff --git a/.storybook/components/index.ts b/.storybook/components/index.ts
index da4d3b46f2f..62a637e0c84 100644
--- a/.storybook/components/index.ts
+++ b/.storybook/components/index.ts
@@ -8,3 +8,4 @@ export * from './TableOfContent.js';
export * from './LabelWithWrapping.js';
export * from './CommandsAndQueries.js';
export * from './FilterBarExample.js';
+export * from './Redirect.js';
diff --git a/.storybook/manager.tsx b/.storybook/manager.tsx
index b3e79874503..16ceeee0ce6 100644
--- a/.storybook/manager.tsx
+++ b/.storybook/manager.tsx
@@ -56,6 +56,10 @@ addons.setConfig({
showRoots: true,
filters: {
patterns: (item) => {
+ // todo: remove once "AnalyticalTable / Recipes" section is removed.
+ if (item.id?.includes('analyticaltable') && item.title.includes('Recipes')) {
+ return false;
+ }
return !item.tags.includes('excludeFromSidebar');
},
},
diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx b/packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/AnalyticalTable.mdx
rename to packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.mdx
diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx b/packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.stories.tsx
similarity index 94%
rename from packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx
rename to packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.stories.tsx
index 40cefa4437f..94ad5488146 100644
--- a/packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx
+++ b/packages/main/src/components/AnalyticalTable/docs/AnalyticalTable.stories.tsx
@@ -17,25 +17,25 @@ import {
FlexBoxDirection,
FlexBoxJustifyContent,
TextAlign,
-} from '../../enums/index.js';
-import { Button } from '../../webComponents/Button/index.js';
-import { IllustratedMessage } from '../../webComponents/IllustratedMessage/index.js';
-import { Label } from '../../webComponents/Label/index.js';
-import { MultiComboBox } from '../../webComponents/MultiComboBox/index.js';
-import { MultiComboBoxItem } from '../../webComponents/MultiComboBoxItem/index.js';
-import { Option } from '../../webComponents/Option/index.js';
-import type { SegmentedButtonPropTypes } from '../../webComponents/SegmentedButton/index.js';
-import { SegmentedButton } from '../../webComponents/SegmentedButton/index.js';
-import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/index.js';
-import { Select } from '../../webComponents/Select/index.js';
-import { Tag } from '../../webComponents/Tag/index.js';
-import { Text } from '../../webComponents/Text/index.js';
-import type { ToggleButtonPropTypes } from '../../webComponents/ToggleButton/index.js';
-import { ToggleButton } from '../../webComponents/ToggleButton/index.js';
-import { FlexBox } from '../FlexBox/index.js';
-import { ObjectStatus } from '../ObjectStatus/index.js';
-import type { AnalyticalTableColumnDefinition, AnalyticalTablePropTypes } from './index.js';
-import { AnalyticalTable } from './index.js';
+} from '../../../enums/index.js';
+import { Button } from '../../../webComponents/Button/index.js';
+import { IllustratedMessage } from '../../../webComponents/IllustratedMessage/index.js';
+import { Label } from '../../../webComponents/Label/index.js';
+import { MultiComboBox } from '../../../webComponents/MultiComboBox/index.js';
+import { MultiComboBoxItem } from '../../../webComponents/MultiComboBoxItem/index.js';
+import { Option } from '../../../webComponents/Option/index.js';
+import type { SegmentedButtonPropTypes } from '../../../webComponents/SegmentedButton/index.js';
+import { SegmentedButton } from '../../../webComponents/SegmentedButton/index.js';
+import { SegmentedButtonItem } from '../../../webComponents/SegmentedButtonItem/index.js';
+import { Select } from '../../../webComponents/Select/index.js';
+import { Tag } from '../../../webComponents/Tag/index.js';
+import { Text } from '../../../webComponents/Text/index.js';
+import type { ToggleButtonPropTypes } from '../../../webComponents/ToggleButton/index.js';
+import { ToggleButton } from '../../../webComponents/ToggleButton/index.js';
+import { FlexBox } from '../../FlexBox/index.js';
+import { ObjectStatus } from '../../ObjectStatus/index.js';
+import type { AnalyticalTableColumnDefinition, AnalyticalTablePropTypes } from '../index.js';
+import { AnalyticalTable } from '../index.js';
const kitchenSinkArgs: AnalyticalTablePropTypes = {
data: dataLarge,
diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.mdx b/packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.mdx
rename to packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.mdx
diff --git a/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx b/packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.stories.tsx
similarity index 92%
rename from packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx
rename to packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.stories.tsx
index 7d33772f065..57f832c45e8 100644
--- a/packages/main/src/components/AnalyticalTable/AnalyticalTableHooks.stories.tsx
+++ b/packages/main/src/components/AnalyticalTable/docs/AnalyticalTableHooks.stories.tsx
@@ -6,22 +6,22 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
import InputType from '@ui5/webcomponents/dist/types/InputType.js';
import paperPlaneIcon from '@ui5/webcomponents-icons/dist/paper-plane';
import { useCallback, useMemo, useReducer, useState } from 'react';
-import { AnalyticalTableSelectionMode, FlexBoxAlignItems, FlexBoxDirection } from '../../enums';
-import { Button } from '../../webComponents/Button/index.js';
-import { CheckBox } from '../../webComponents/CheckBox/index.js';
-import type { InputDomRef } from '../../webComponents/Input/index.js';
-import { Input } from '../../webComponents/Input/index.js';
-import { Label } from '../../webComponents/Label/index.js';
-import { Switch } from '../../webComponents/Switch/index.js';
-import { Tag } from '../../webComponents/Tag/index.js';
-import { Text } from '../../webComponents/Text/index.js';
-import { ToggleButton } from '../../webComponents/ToggleButton/index.js';
-import { FlexBox } from '../FlexBox';
-import meta from './AnalyticalTable.stories';
-import * as AnalyticalTableHooks from './pluginHooks/AnalyticalTableHooks';
-import { useF2CellEdit } from './pluginHooks/AnalyticalTableHooks';
-import { AnalyticalTable } from './index';
-import type { AnalyticalTableCellInstance, AnalyticalTableColumnDefinition } from './index';
+import { AnalyticalTableSelectionMode, FlexBoxAlignItems, FlexBoxDirection } from '../../../enums/index.js';
+import { Button } from '../../../webComponents/Button/index.js';
+import { CheckBox } from '../../../webComponents/CheckBox/index.js';
+import type { InputDomRef } from '../../../webComponents/Input/index.js';
+import { Input } from '../../../webComponents/Input/index.js';
+import { Label } from '../../../webComponents/Label/index.js';
+import { Switch } from '../../../webComponents/Switch/index.js';
+import { Tag } from '../../../webComponents/Tag/index.js';
+import { Text } from '../../../webComponents/Text/index.js';
+import { ToggleButton } from '../../../webComponents/ToggleButton/index.js';
+import { FlexBox } from '../../FlexBox/index.js';
+import { AnalyticalTable } from '../index.js';
+import type { AnalyticalTableCellInstance, AnalyticalTableColumnDefinition } from '../index.js';
+import * as AnalyticalTableHooks from '../pluginHooks/AnalyticalTableHooks.js';
+import { useF2CellEdit } from '../pluginHooks/AnalyticalTableHooks.js';
+import meta from './AnalyticalTable.stories.js';
const pluginsMeta = {
...meta,
diff --git a/packages/main/src/components/AnalyticalTable/Recipes.mdx b/packages/main/src/components/AnalyticalTable/docs/FAQ.mdx
similarity index 76%
rename from packages/main/src/components/AnalyticalTable/Recipes.mdx
rename to packages/main/src/components/AnalyticalTable/docs/FAQ.mdx
index efc31069db1..0775e3c8e21 100644
--- a/packages/main/src/components/AnalyticalTable/Recipes.mdx
+++ b/packages/main/src/components/AnalyticalTable/docs/FAQ.mdx
@@ -1,15 +1,82 @@
import { TableOfContent } from '@sb/components';
import { Meta, Canvas } from '@storybook/addon-docs/blocks';
import { Footer } from '@sb/components';
-import { MessageStrip } from '../../webComponents/MessageStrip/index';
+import { MessageStrip } from '../../../webComponents/MessageStrip/index.js';
import * as ComponentStories from './AnalyticalTable.stories';
-
+
-# AnalyticalTable Recipes
+# AnalyticalTable FAQ
+## Why is the AnalyticalTable slow in development mode?
+
+When using the `AnalyticalTable` (or other virtualized components) in development mode, you may experience noticeable performance degradation compared to production builds. This is expected behavior caused by the combination of **React's dev mode overhead** and **browser debugger instrumentation**.
+
+### Why Dev Mode is Slower
+
+#### React Dev Mode Overhead
+
+React's development build includes significant overhead:
+
+- **Strict Mode double-rendering**: Components, `useEffect`, and callback refs render an additional time to help detect side effects
+- **Validation checks**: Hooks order, prop types, deprecated API usage
+- **Extended error messages**: Component stack traces for better debugging
+- **Extra code paths**: Development-only warnings and diagnostics
+
+For a virtualized table that frequently mounts and unmounts cells during scrolling, this overhead compounds quickly.
+
+#### Browser Debugger Instrumentation
+
+Chromium-based browsers (Chrome, Edge, Brave, etc.) instrument async operations heavily in dev mode via the V8 engine - even when DevTools is closed. Firefox and Safari have significantly lighter debugger overhead in dev mode for example.
+
+### Recommendations
+
+#### High Impact
+
+1. **Use Firefox or Safari for development**: These browsers have significantly lighter debugger overhead in dev mode.
+
+2. **Test with production builds**: When working on performance-sensitive features, test with a production build to get accurate performance metrics.
+
+3. **Disable React Strict Mode** (temporarily): If dev mode performance is severely impacting your workflow, you can temporarily disable Strict Mode in development. Note that Strict Mode helps catch bugs, so re-enable it periodically.
+
+4. **Memoize props that require it**: Props marked with "Must be memoized" (e.g., `columns`) need a stable reference. Define them outside the component or use `useMemo`/`useCallback` to prevent unnecessary recalculations.
+
+#### Medium Impact
+
+5. **Disable React DevTools extension**: The React DevTools browser extension adds performance overhead and has been observed to cause small memory leaks.
+
+6. **Consider memoizing expensive Cell components**: If you have custom `Cell` renderers with expensive computations or deep component trees, wrapping them with `React.memo()` can help by creating render boundaries. However, avoid over-memoization - for simple cells, the overhead of memoization may outweigh the benefits.
+
+7. **Use [direct imports](?path=/docs/knowledge-base-faq--docs#why-use-direct-imports-via-package-export-maps)**: Since tree shaking is not available for most bundlers in dev mode, use direct imports to reduce initial load.
+
+#### Lower Impact
+
+8. **Keep DevTools closed**: When not actively debugging, keep DevTools closed to reduce overhead from source map parsing, DOM mutation observers, and memory tracking.
+
+9. **Deactivate UI5 Web Components animations in dev mode**: Animations cause components to recalculate multiple times. While we debounce frequent state changes, disabling animations can help.
+
+### Summary
+
+Dev mode slowness for virtualized components is expected behavior, not a bug. In production builds, the `AnalyticalTable` performs well. For the best development experience with heavy tables, consider using Firefox or Safari, or testing with production builds when performance is critical.
+
+
+Example: Performance Trace Comparison
+
+The following table shows a performance trace comparison from an investigation into dev mode performance ([GitHub issue #8234](https://github.com/SAP/ui5-webcomponents-react/issues/8234)). Your results may vary depending on your implementation, but the general pattern holds:
+
+| Metric | Chrome DEV | Chrome PROD | Firefox DEV |
+| --------------- | ---------- | ----------- | ----------- |
+| Trace file size | 561 MB | 3.3 MB | 6.3 MB |
+| Event count | ~millions | ~14,600 | ~3,700 |
+
+Firefox in dev mode has similar overhead to Chrome in prod mode - both are lightweight. Chromium-based browsers in dev mode are the outlier with massive V8 debugger instrumentation.
+
+For more context, see the [detailed findings comment](https://github.com/SAP/ui5-webcomponents-react/issues/8234#issuecomment-3971742068) and [production environment performance videos](https://github.com/SAP/ui5-webcomponents-react/issues/8234#issuecomment-3960465202) showing table performance (inside a complex container) on high-end, medium-range, and low-end devices.
+
+
+
## How to add your own plugin hooks?
The `AnalyticalTable` internally uses all plugin hooks of the [react-table v7](https://github.com/TanStack/table/blob/v7/docs/src/pages/docs/api/overview.md) except for `usePagination` and hooks that change the layout.
@@ -17,9 +84,9 @@ If you pass a `react-table` hook to the `tableHooks` prop of the `AnalyticalTabl
If you encounter a functionality that should be available with `react-table` hooks, but isn't in the `AnalyticalTable` please open an issue in GitHub.
For adding custom plugin hooks you can use the following code snippet.
@@ -438,8 +505,8 @@ For multi-line content or custom components that don't handle ellipsis, truncati
### Example
- Show Example
-
+ Show Example
+
diff --git a/packages/main/src/components/AnalyticalTable/PluginAnnounceEmptyCells.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginAnnounceEmptyCells.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/PluginAnnounceEmptyCells.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginAnnounceEmptyCells.mdx
diff --git a/packages/main/src/components/AnalyticalTable/PluginDisableRowSelection.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginDisableRowSelection.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/PluginDisableRowSelection.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginDisableRowSelection.mdx
diff --git a/packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginF2CellEdit.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/PluginF2CellEdit.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginF2CellEdit.mdx
diff --git a/packages/main/src/components/AnalyticalTable/PluginIndeterminateRowSelection.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginIndeterminateRowSelection.mdx
similarity index 96%
rename from packages/main/src/components/AnalyticalTable/PluginIndeterminateRowSelection.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginIndeterminateRowSelection.mdx
index 3a592a23711..5c12ed6136b 100644
--- a/packages/main/src/components/AnalyticalTable/PluginIndeterminateRowSelection.mdx
+++ b/packages/main/src/components/AnalyticalTable/docs/PluginIndeterminateRowSelection.mdx
@@ -1,7 +1,7 @@
import { ImportStatement } from '@sb/components/Import';
import { Canvas, Meta } from '@storybook/addon-docs/blocks';
import { Footer } from '@sb/components';
-import { MessageStrip } from '../../webComponents/MessageStrip/index';
+import { MessageStrip } from '../../../webComponents/MessageStrip/index.js';
import * as ComponentStories from './AnalyticalTableHooks.stories';
diff --git a/packages/main/src/components/AnalyticalTable/PluginManualRowSelect.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginManualRowSelect.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/PluginManualRowSelect.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginManualRowSelect.mdx
diff --git a/packages/main/src/components/AnalyticalTable/PluginOnColumnResize.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginOnColumnResize.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/PluginOnColumnResize.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginOnColumnResize.mdx
diff --git a/packages/main/src/components/AnalyticalTable/PluginOrderedMultiSort.mdx b/packages/main/src/components/AnalyticalTable/docs/PluginOrderedMultiSort.mdx
similarity index 100%
rename from packages/main/src/components/AnalyticalTable/PluginOrderedMultiSort.mdx
rename to packages/main/src/components/AnalyticalTable/docs/PluginOrderedMultiSort.mdx
diff --git a/packages/main/src/components/AnalyticalTable/docs/Recipes.mdx b/packages/main/src/components/AnalyticalTable/docs/Recipes.mdx
new file mode 100644
index 00000000000..0a9d9242fa2
--- /dev/null
+++ b/packages/main/src/components/AnalyticalTable/docs/Recipes.mdx
@@ -0,0 +1,6 @@
+import { Meta } from '@storybook/addon-docs/blocks';
+import { Redirect } from '@sb/components';
+
+
+
+
diff --git a/packages/main/src/components/AnalyticalTable/types/index.ts b/packages/main/src/components/AnalyticalTable/types/index.ts
index 80416338ee9..dbcfbcbc98d 100644
--- a/packages/main/src/components/AnalyticalTable/types/index.ts
+++ b/packages/main/src/components/AnalyticalTable/types/index.ts
@@ -832,7 +832,9 @@ export interface AnalyticalTablePropTypes extends Omit {
* The value of this prop can either be a `string` pointing to a `ValueState` or an `IndicationColor` in your dataset
* or an accessor function which should return a `ValueState` or an `IndicationColor`.
*
- * __Note:__ `IndicationColor`s are available since `v1.26.0`.
+ * __Note:__ If a function is used, it __must be memoized!__
+ *
+ * @since 1.26.0
*
* @default "status"
*/
@@ -860,6 +862,8 @@ export interface AnalyticalTablePropTypes extends Omit {
/**
* Group table rows by adding the column's `accessor` or `id` to the array.
*
+ * __Must be memoized!__
+ *
* __Note:__ This prop has no effect when `isTreeTable` is true or `renderRowSubComponent` is set.
*/
groupBy?: string[];
@@ -906,6 +910,8 @@ export interface AnalyticalTablePropTypes extends Omit {
scaleXFactor?: number;
/**
* Defines the columns order by their `accessor` or `id`.
+ *
+ * __Must be memoized!__
*/
columnOrder?: string[];
/**
@@ -932,12 +938,14 @@ export interface AnalyticalTablePropTypes extends Omit {
globalFilterValue?: string;
/**
* Additional options which will be passed to [v7 react-table“s useTable hook](https://github.com/TanStack/table/blob/v7/docs/src/pages/docs/api/useTable.md#table-options)
+ *
+ * __Must be memoized!__
*/
reactTableOptions?: Record;
/**
* You can use this prop to add custom hooks to the table.
*
- * __Note:__ Should be memoized!
+ * __Must be memoized!__
*
* @default []
*/
@@ -973,6 +981,8 @@ export interface AnalyticalTablePropTypes extends Omit {
/**
* Defines the subcomponent that should be displayed below each row.
*
+ * __Must be memoized!__
+ *
* __Note:__
* - __There is no design concept regarding this functionality!__
* - When rendering active elements inside the subcomponent, make sure to add the `data-subcomponent-active-element' attribute, otherwise focus behavior won't be consistent.