@adaptabletools/adaptable 22.0.0-canary.7 → 22.0.0-canary.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/agGrid.d.ts +9 -9
  2. package/agGrid.js +1 -0
  3. package/index.css +63 -71
  4. package/index.css.map +1 -1
  5. package/index.d.ts +66 -0
  6. package/index.js +73 -0
  7. package/package.json +3 -3
  8. package/src/AdaptableInterfaces/IAdaptable.d.ts +6 -6
  9. package/src/AdaptableOptions/ColumnOptions.d.ts +2 -2
  10. package/src/AdaptableOptions/ContainerOptions.d.ts +55 -15
  11. package/src/AdaptableState/AdaptableState.d.ts +2 -0
  12. package/src/AdaptableState/Common/AdaptableColumn.d.ts +10 -10
  13. package/src/AdaptableState/Common/AdaptableColumnContext.d.ts +9 -0
  14. package/src/AdaptableState/Common/AdaptableRowContext.d.ts +11 -0
  15. package/src/AdaptableState/Common/AdaptableRowContext.js +1 -0
  16. package/src/AdaptableState/Common/DataUpdateConfig.d.ts +7 -0
  17. package/src/AdaptableState/Common/TransposeConfig.d.ts +11 -9
  18. package/src/AdaptableState/InitialState.d.ts +9 -0
  19. package/src/AdaptableState/LayoutState.d.ts +1 -2
  20. package/src/AdaptableState/UserInterfaceState.d.ts +14 -0
  21. package/src/AdaptableState/UserInterfaceState.js +1 -0
  22. package/src/Api/GridApi.d.ts +5 -9
  23. package/src/Api/Implementation/AlertApiImpl.js +2 -6
  24. package/src/Api/Implementation/ColumnApiImpl.d.ts +1 -1
  25. package/src/Api/Implementation/GridApiImpl.d.ts +2 -6
  26. package/src/Api/Implementation/GridApiImpl.js +9 -9
  27. package/src/Api/Implementation/LayoutApiImpl.d.ts +1 -0
  28. package/src/Api/Implementation/LayoutApiImpl.js +3 -0
  29. package/src/Api/Implementation/SystemStatusApiImpl.js +2 -5
  30. package/src/Api/Implementation/UserInterfaceApiImpl.d.ts +5 -0
  31. package/src/Api/Implementation/UserInterfaceApiImpl.js +13 -0
  32. package/src/Api/LayoutApi.d.ts +6 -0
  33. package/src/Api/UserInterfaceApi.d.ts +17 -0
  34. package/src/Redux/ActionsReducers/UserInterfaceRedux.d.ts +11 -0
  35. package/src/Redux/ActionsReducers/UserInterfaceRedux.js +21 -0
  36. package/src/Redux/Store/AdaptableStore.js +27 -0
  37. package/src/Utilities/resolveContainerElement.d.ts +23 -0
  38. package/src/Utilities/resolveContainerElement.js +44 -0
  39. package/src/View/Components/ColumnFilter/components/ColumnFilterInput.js +1 -1
  40. package/src/View/Components/Popups/AdaptablePopup/AdaptablePopup.js +2 -1
  41. package/src/View/Components/Popups/AdaptablePopup/AdaptablePopupBody.js +1 -1
  42. package/src/View/Components/Popups/AdaptablePopup/AdaptablePopupDialog.d.ts +1 -1
  43. package/src/View/Components/Popups/AdaptablePopup/AdaptablePopupDialog.js +1 -8
  44. package/src/View/Components/Popups/WindowPopups/WindowPopups.js +36 -1
  45. package/src/View/Components/WizardSummaryPage.js +1 -1
  46. package/src/View/Dashboard/CustomToolbar.js +1 -1
  47. package/src/View/DataChangeHistory/DataChangeHistoryViewPanel.js +1 -1
  48. package/src/View/Layout/TransposedPopup.js +144 -138
  49. package/src/View/Schedule/Wizard/ScheduleSettingsWizard/ScheduleSettingsReminder.js +1 -1
  50. package/src/View/UIHelper.d.ts +2 -1
  51. package/src/View/UIHelper.js +8 -14
  52. package/src/agGrid/Adaptable.js +11 -11
  53. package/src/agGrid/AdaptableAgGrid.d.ts +12 -8
  54. package/src/agGrid/AdaptableAgGrid.js +112 -31
  55. package/src/agGrid/AgGridFloatingFilterAdapter.js +1 -1
  56. package/src/agGrid/AgGridMenuAdapter.js +9 -1
  57. package/src/components/CheckBox/index.js +1 -1
  58. package/src/components/Dropdown/Arrows.js +1 -1
  59. package/src/components/ExpressionEditor/DataTableEditor.js +3 -3
  60. package/src/components/FormLayout/index.js +1 -1
  61. package/src/components/Select/Select.js +1 -1
  62. package/src/components/Tree/TreeDropdown/index.js +1 -1
  63. package/src/env.js +2 -2
  64. package/src/metamodel/adaptable.metamodel.d.ts +62 -0
  65. package/src/metamodel/adaptable.metamodel.js +1 -1
  66. package/src/types.d.ts +6 -3
  67. package/themes/dark.css +30 -29
  68. package/themes/light.css +4 -2
  69. package/tsconfig.esm.tsbuildinfo +1 -1
@@ -0,0 +1,23 @@
1
+ import type { AdaptableContainerValue, ContainerContext, InitContainerContext } from '../AdaptableOptions/ContainerOptions';
2
+ /**
3
+ * The union type representing any container option value (static or function).
4
+ */
5
+ type AnyContainerOption = AdaptableContainerValue | ((context: any) => AdaptableContainerValue);
6
+ /**
7
+ * Resolves a container option value to an `HTMLElement`.
8
+ *
9
+ * Resolution order:
10
+ * 1. If `container` is `null`/`undefined`, returns `null`.
11
+ * 2. If `container` is a function, it is called with the provided `context` and the result is resolved.
12
+ * 3. If the value is an `HTMLElement`, it is returned directly.
13
+ * 4. If the value is an `AdaptableCSSSelector` (`{ selector: string }`), `document.querySelectorAll` is used.
14
+ * If multiple elements match, the first is returned and a console warning is logged.
15
+ * 5. If the value is a `string`, it is treated as an element ID and `document.getElementById` is used.
16
+ *
17
+ * @param container - The container reference to resolve.
18
+ * @param context - Optional context passed to the function form of the container.
19
+ * @param doc - The document to query against (defaults to `globalThis.document`).
20
+ * @returns The resolved `HTMLElement`, or `null` if not found.
21
+ */
22
+ export declare function resolveContainerElement(container: AnyContainerOption | undefined | null, context?: ContainerContext | InitContainerContext, doc?: Document): HTMLElement | null;
23
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Resolves a container option value to an `HTMLElement`.
3
+ *
4
+ * Resolution order:
5
+ * 1. If `container` is `null`/`undefined`, returns `null`.
6
+ * 2. If `container` is a function, it is called with the provided `context` and the result is resolved.
7
+ * 3. If the value is an `HTMLElement`, it is returned directly.
8
+ * 4. If the value is an `AdaptableCSSSelector` (`{ selector: string }`), `document.querySelectorAll` is used.
9
+ * If multiple elements match, the first is returned and a console warning is logged.
10
+ * 5. If the value is a `string`, it is treated as an element ID and `document.getElementById` is used.
11
+ *
12
+ * @param container - The container reference to resolve.
13
+ * @param context - Optional context passed to the function form of the container.
14
+ * @param doc - The document to query against (defaults to `globalThis.document`).
15
+ * @returns The resolved `HTMLElement`, or `null` if not found.
16
+ */
17
+ export function resolveContainerElement(container, context, doc = globalThis.document) {
18
+ if (container == null) {
19
+ return null;
20
+ }
21
+ // Unwrap function form — pass context if the container is a function
22
+ const value = typeof container === 'function' ? container(context) : container;
23
+ // Guard against null/undefined returned by a callback (e.g. document.getElementById(...))
24
+ if (value == null) {
25
+ return null;
26
+ }
27
+ // Direct HTMLElement reference
28
+ if (value instanceof HTMLElement) {
29
+ return value;
30
+ }
31
+ // CSS Selector object
32
+ if (typeof value === 'object' && 'selector' in value) {
33
+ const matches = doc.querySelectorAll(value.selector);
34
+ if (matches.length > 1) {
35
+ console.warn(`[AdapTable] CSS selector "${value.selector}" matched ${matches.length} elements. Using the first match. Consider using a more specific selector.`);
36
+ }
37
+ return matches[0] ?? null;
38
+ }
39
+ // String element ID
40
+ if (typeof value === 'string') {
41
+ return doc.getElementById(value);
42
+ }
43
+ return null;
44
+ }
@@ -93,7 +93,7 @@ export const ColumnFilterInput = (props) => {
93
93
  return (React.createElement(AdaptableInput, { style: filterType === 'floating'
94
94
  ? {
95
95
  width: '100%',
96
- padding: 'var(--ab-space-1)',
96
+ padding: 'var(--ab-base-space)',
97
97
  borderRadius: 0,
98
98
  border: 'none',
99
99
  }
@@ -9,6 +9,7 @@ import { AdaptablePopupBody } from './AdaptablePopupBody';
9
9
  import { useAdaptable } from '../../../AdaptableContext';
10
10
  import { CustomSettingsPanelView } from './CustomSettingsPanelView';
11
11
  import { useMenuItems } from './useMenuItems';
12
+ import { resolveContainerElement } from '../../../../Utilities/resolveContainerElement';
12
13
  import { AdaptablePopupDialog } from './AdaptablePopupDialog';
13
14
  import { Box } from '../../../../components/Flex';
14
15
  export const AdaptablePopup = (props) => {
@@ -17,7 +18,7 @@ export const AdaptablePopup = (props) => {
17
18
  const settingsPanelTitle = adaptable.ModuleService.getModuleFriendlyName('SettingsPanel');
18
19
  const menuItems = useMenuItems();
19
20
  const isWindowModal = settingsPanelOptions.popupType === 'window';
20
- const modalContainer = adaptable.adaptableOptions?.containerOptions?.modalContainer;
21
+ const modalContainer = resolveContainerElement(adaptable.adaptableOptions?.containerOptions?.modalContainer, props.api.internalApi.buildBaseContext());
21
22
  let friendlyName = null;
22
23
  /**
23
24
  * This means that it is not rendered in the context of Settings Panel
@@ -3,7 +3,7 @@ import { UIHelper } from '../../../UIHelper';
3
3
  import { AdaptableViewFactory } from '../../../AdaptableViewFactory';
4
4
  import { AdaptablePopupModuleView } from './AdaptablePopupModuleView';
5
5
  export const AdaptablePopupBody = (props) => {
6
- const modalContainer = UIHelper.getModalContainer(props.api.optionsApi.getAdaptableOptions(), document);
6
+ const modalContainer = UIHelper.getModalContainer(props.api.optionsApi.getAdaptableOptions(), document, props.api.internalApi.buildBaseContext());
7
7
  const moduleName = props.module.moduleInfo.ModuleName;
8
8
  const accessLevel = props.api.entitlementApi.getEntitlementAccessLevelForModule(moduleName);
9
9
  const moduleInfo = props.api.internalApi.getModuleService().getModuleInfoByModule(moduleName);
@@ -7,6 +7,6 @@ export declare const AdaptablePopupDialog: React.FunctionComponent<React.PropsWi
7
7
  isWindowModal: boolean;
8
8
  onHide: () => void;
9
9
  style?: React.CSSProperties;
10
- modalContainer?: string | HTMLElement;
10
+ modalContainer?: HTMLElement;
11
11
  dataName?: string;
12
12
  }>>;
@@ -43,14 +43,7 @@ const PopupDialog = (props) => {
43
43
  export const AdaptablePopupDialog = (props) => {
44
44
  const { isActionModule, style, friendlyName, baseClassName, className, children, onHide, isWindowModal, modalContainer, } = props;
45
45
  if (modalContainer) {
46
- let ref = null;
47
- if (typeof modalContainer === 'string') {
48
- ref = globalThis.document.querySelector(modalContainer);
49
- }
50
- else {
51
- ref = modalContainer;
52
- }
53
- return createPortal(React.createElement(Dialog, { "data-name": props.dataName, modal: false, fixed: false, onDismiss: onHide, className: className }, children), ref);
46
+ return createPortal(React.createElement(Dialog, { "data-name": props.dataName, modal: false, fixed: false, onDismiss: onHide, className: className }, children), modalContainer);
54
47
  }
55
48
  if (isWindowModal) {
56
49
  const settingsPanelOptionsKey = isActionModule ? `action-${friendlyName}` : 'settings';
@@ -1,16 +1,44 @@
1
1
  import * as React from 'react';
2
+ import { createPortal } from 'react-dom';
2
3
  import { useDispatch, useSelector } from 'react-redux';
3
4
  import Dialog from '../../../../components/Dialog';
4
5
  import { PopupHideWindow } from '../../../../Redux/ActionsReducers/PopupRedux';
6
+ import { resolveContainerElement } from '../../../../Utilities/resolveContainerElement';
5
7
  import { useAdaptable } from '../../../AdaptableContext';
6
8
  import { ExternalRenderer } from '../../ExternalRenderer';
7
9
  import { PanelWithImage } from '../../Panels/PanelWithImage';
8
10
  import { getMiddlePosition, getWindowPopupSize } from '../Utilities';
9
- import { windowFactory } from './windowFactory';
11
+ import { WINDOW_SHOW_TRANSPOSED_VIEW, windowFactory } from './windowFactory';
10
12
  const NoopComponent = () => {
11
13
  return React.createElement(React.Fragment, null);
12
14
  };
13
15
  export const CUSTOM_WINDOW_FACTORY_ID = 'CUSTOM_WINDOW_FACTORY_ID';
16
+ /**
17
+ * Portals children into a target container element.
18
+ * Monitors the container with a MutationObserver — if the container is removed
19
+ * from the DOM, `onContainerRemoved` is called so callers can clean up React/Redux state.
20
+ */
21
+ const ContainerPortal = ({ container, onContainerRemoved, children }) => {
22
+ React.useEffect(() => {
23
+ // If the container is already detached, clean up immediately
24
+ if (!document.contains(container)) {
25
+ onContainerRemoved();
26
+ return;
27
+ }
28
+ const observer = new MutationObserver(() => {
29
+ if (!document.contains(container)) {
30
+ onContainerRemoved();
31
+ observer.disconnect();
32
+ }
33
+ });
34
+ // Observe the entire document body for subtree removals
35
+ observer.observe(document.body, { childList: true, subtree: true });
36
+ return () => {
37
+ observer.disconnect();
38
+ };
39
+ }, [container, onContainerRemoved]);
40
+ return createPortal(children, container);
41
+ };
14
42
  export const WindowPopups = () => {
15
43
  const [windowModalSettings, setWindowModalSettings] = React.useState({});
16
44
  const adaptable = useAdaptable();
@@ -37,6 +65,13 @@ export const WindowPopups = () => {
37
65
  Component = Component ?? NoopComponent;
38
66
  componentNode = (React.createElement(Component, { api: adaptable.api, onDismiss: handleDismiss, popupProps: restPopupProps }));
39
67
  }
68
+ // Transposed View: portal into custom container if configured
69
+ if (windowItem.FactoryId === WINDOW_SHOW_TRANSPOSED_VIEW) {
70
+ const transposedContainer = resolveContainerElement(adaptable.adaptableOptions?.containerOptions?.transposedViewContainer, adaptable.api.internalApi.buildBaseContext());
71
+ if (transposedContainer) {
72
+ return (React.createElement(ContainerPortal, { key: windowItem.Id, container: transposedContainer, onContainerRemoved: handleDismiss }, componentNode));
73
+ }
74
+ }
40
75
  return (React.createElement(Dialog, { "data-name": windowItem.Id, className: "ab-Window-Modal twa:h-full twa:p-0", key: windowItem.Id, windowModal: true, windowModalProps: {
41
76
  ...windowModalProps,
42
77
  onChange: (settings) => {
@@ -9,7 +9,7 @@ const tableDOMProps = {
9
9
  className: 'ab-WizardSummary__list',
10
10
  style: {
11
11
  height: '100%',
12
- margin: 'var(--ab-space-2)',
12
+ margin: 'calc(var(--ab-base-space) * 2)',
13
13
  },
14
14
  };
15
15
  export const WizardSummaryPage = (props) => {
@@ -75,7 +75,7 @@ export const CustomToolbarCmp = (props) => {
75
75
  const disabled = button.disabled && button.disabled(button, dashboardContext);
76
76
  let buttonVariant = buttonStyle && buttonStyle.variant ? buttonStyle.variant : 'outlined';
77
77
  let buttonTone = buttonStyle && buttonStyle.tone ? buttonStyle.tone : 'neutral';
78
- return (React.createElement(AdaptableButtonComponent, { style: { marginLeft: index ? 'var(--ab-space-1)' : 0 }, key: index, disabled: disabled, tooltip: buttonTooltip, icon: buttonIcon, variant: buttonVariant, tone: buttonTone, className: buttonStyle?.className || '', onClick: () => {
78
+ return (React.createElement(AdaptableButtonComponent, { style: { marginLeft: index ? 'var(--ab-base-space)' : 0 }, key: index, disabled: disabled, tooltip: buttonTooltip, icon: buttonIcon, variant: buttonVariant, tone: buttonTone, className: buttonStyle?.className || '', onClick: () => {
79
79
  button.onClick ? button.onClick(button, dashboardContext) : null;
80
80
  setTimeout(() => {
81
81
  // mutate state to force a re-rendering
@@ -27,7 +27,7 @@ export const DataChangeHistoryViewPanelControl = (props) => {
27
27
  const enabled = changeHistoryMode === 'ACTIVE';
28
28
  const disabled = changeHistoryMode === 'INACTIVE';
29
29
  const suspended = changeHistoryMode === 'SUSPENDED';
30
- const gap = props.gap ?? 'var(--ab-space-1)';
30
+ const gap = props.gap ?? 'var(--ab-base-space)';
31
31
  const buttonsPaddingY = props.buttonsPaddingY ?? 2;
32
32
  const buttonPanel = (React.createElement(Flex, { className: "ab-DataChangeHistoryPanel--button-panel", style: { gap: gap, paddingBlock: buttonsPaddingY } },
33
33
  disabled && (React.createElement(ButtonPlay, { "aria-label": "Enable Data Change History", className: "ab-DataChangeHistoryPanel--button-activate", "data-name": 'data-change-history--button-activate', tooltip: '', onClick: () => onChangeHistoryEnable() })),
@@ -5,72 +5,153 @@ import { useAdaptable } from '../AdaptableContext';
5
5
  import { ColumnSelector } from '../Components/Selectors/ColumnSelector';
6
6
  import { AdaptableAgGrid } from '../../agGrid/AdaptableAgGrid';
7
7
  import { Box, Flex } from '../../components/Flex';
8
- const adaptableContainerId = `transposed-adaptable-container`;
9
- const agGridContainerId = `transposed-adaptable-ag-grid-container`;
8
+ import { ExportModuleId } from '../../Utilities/Constants/ModuleConstants';
9
+ import StringExtensions from '../../Utilities/Extensions/StringExtensions';
10
+ const ADAPTABLE_CONTAINER_ID = 'transposed-adaptable-container';
11
+ const AG_GRID_CONTAINER_ID = 'transposed-adaptable-ag-grid-container';
12
+ /** Field used for the first column in transposed grid (hidden, holds column id). */
13
+ const TRANSPOSED_FIRST_COLUMN_FIELD = '_transposed_column_value';
14
+ /** Header used for the first visible column in transposed grid (shows friendly name). */
15
+ const TRANSPOSED_FIRST_COLUMN_HEADER = '_transposed_column_header';
16
+ function buildTransposedAdaptableOptions({ hostOptions, transposedRowsAndColumns, currentTheme, }) {
17
+ return {
18
+ primaryKey: TRANSPOSED_FIRST_COLUMN_FIELD,
19
+ licenseKey: hostOptions.licenseKey,
20
+ userName: `${hostOptions.userName}`,
21
+ adaptableId: `${hostOptions.adaptableId}::TransposedView`,
22
+ containerOptions: {
23
+ adaptableContainer: ADAPTABLE_CONTAINER_ID,
24
+ agGridContainer: AG_GRID_CONTAINER_ID,
25
+ },
26
+ entitlementOptions: { defaultAccessLevel: 'Hidden' },
27
+ initialState: {
28
+ Layout: {
29
+ Revision: Date.now(),
30
+ CurrentLayout: 'TransposedView',
31
+ Layouts: [
32
+ {
33
+ Name: 'TransposedView',
34
+ TableColumns: [
35
+ TRANSPOSED_FIRST_COLUMN_HEADER,
36
+ ...transposedRowsAndColumns.transposedColumns.map((c) => c.colId),
37
+ ],
38
+ ColumnPinning: { [TRANSPOSED_FIRST_COLUMN_HEADER]: 'left' },
39
+ AutoSizeColumns: true,
40
+ },
41
+ ],
42
+ },
43
+ Theme: { CurrentTheme: currentTheme },
44
+ },
45
+ };
46
+ }
47
+ function buildTransposedGridOptions({ transposedRowsAndColumns, elevatedColumnId, adaptableApi, }) {
48
+ const firstColumn = {
49
+ field: TRANSPOSED_FIRST_COLUMN_HEADER,
50
+ headerName: adaptableApi.columnApi.getFriendlyNameForColumnId(elevatedColumnId),
51
+ };
52
+ return {
53
+ loading: false,
54
+ defaultColDef: {
55
+ floatingFilter: false,
56
+ filter: false,
57
+ sortable: true,
58
+ resizable: true,
59
+ enableRowGroup: false,
60
+ editable: false,
61
+ enablePivot: false,
62
+ enableValue: false,
63
+ lockPinned: true,
64
+ menuTabs: [],
65
+ width: 120,
66
+ },
67
+ columnDefs: [
68
+ { field: TRANSPOSED_FIRST_COLUMN_FIELD, hide: true },
69
+ firstColumn,
70
+ ...transposedRowsAndColumns.transposedColumns.map((col) => ({ field: col.colId, headerName: col.header })),
71
+ ],
72
+ rowData: transposedRowsAndColumns.transposedRows,
73
+ sideBar: false,
74
+ };
75
+ }
10
76
  export const TransposedPopup = (props) => {
11
77
  const adaptable = useAdaptable();
12
- const { transposedColumnId, hideTransposedColumn, visibleColumns, visibleRows, autosize } = props.popupProps;
13
- const rowNodes = React.useMemo(() => {
14
- return props.popupProps.visibleRows
15
- ? adaptable.api.gridApi.getVisibleRowNodes()
16
- : adaptable.api.gridApi.getAllRowNodes();
17
- }, [
18
- // can be later triggered by tickng data
19
- ]);
20
78
  const primaryKey = adaptable.api.optionsApi.getPrimaryKey();
21
- const [syntheticTransposedByColumnId, doSetSyntheticTransposedByColumnId] = React.useState(transposedColumnId);
22
- const setSyntheticTransposedByColumnId = (syntheticTransposedByColumnId) => {
79
+ const rawConfig = (props.popupProps ?? {});
80
+ const transposeConfig = {
81
+ transposedColumnId: rawConfig.transposedColumnId ?? primaryKey,
82
+ hideTransposedColumn: rawConfig.hideTransposedColumn ?? true,
83
+ autosize: rawConfig.autosize ?? true,
84
+ columnsToTranspose: rawConfig.columnsToTranspose,
85
+ rowsToTranspose: rawConfig.rowsToTranspose,
86
+ };
87
+ const rowNodes = React.useMemo(() => {
88
+ let transposableRowNodes = [];
89
+ const rowsToTranspose = transposeConfig.rowsToTranspose;
90
+ if (rowsToTranspose === 'VisibleOnly') {
91
+ return adaptable.api.gridApi.getVisibleRowNodes();
92
+ }
93
+ if (rowsToTranspose === 'All') {
94
+ return adaptable.api.gridApi.getAllRowNodes();
95
+ }
96
+ if (StringExtensions.IsNotNullOrEmpty(rowsToTranspose)) {
97
+ adaptable.api.gridApi.getAllRowNodes().forEach((rn) => {
98
+ const shouldTransposeRow = adaptable.api.internalApi
99
+ .getQueryLanguageService()
100
+ .evaluateBooleanExpression(rowsToTranspose, ExportModuleId, rn);
101
+ if (shouldTransposeRow) {
102
+ transposableRowNodes.push(rn);
103
+ }
104
+ });
105
+ return transposableRowNodes;
106
+ }
107
+ return adaptable.api.gridApi.getAllRowNodes();
108
+ }, [transposeConfig.rowsToTranspose, adaptable.api]);
109
+ const [elevatedColumnId, setElevatedColumnIdState] = React.useState(() => transposeConfig.transposedColumnId ?? primaryKey);
110
+ const setElevatedColumnId = (newElevatedColumnId) => {
23
111
  transposedAdaptableApiRef.current?.destroy({ unmount: true, destroyAgGrid: true });
24
- doSetSyntheticTransposedByColumnId(syntheticTransposedByColumnId);
112
+ setElevatedColumnIdState(newElevatedColumnId);
25
113
  };
26
114
  const transposedAdaptableApiRef = React.useRef(null);
27
115
  const columns = React.useMemo(() => {
28
- // customisable
29
- return visibleColumns
30
- ? adaptable.api.columnApi.getVisibleColumns()
31
- : adaptable.api.columnApi.getUIAvailableColumns();
32
- }, []);
116
+ const allUIColumns = adaptable.api.columnApi.getUIAvailableColumns();
117
+ const columnsToTranspose = transposeConfig.columnsToTranspose;
118
+ if (!columnsToTranspose) {
119
+ return allUIColumns;
120
+ }
121
+ const columnIds = typeof columnsToTranspose === 'function'
122
+ ? columnsToTranspose({
123
+ ...adaptable.api.internalApi.buildBaseContext(),
124
+ columns: allUIColumns,
125
+ })
126
+ : columnsToTranspose;
127
+ return columnIds
128
+ .map((c) => adaptable.api.columnApi.getColumnWithColumnId(c))
129
+ .filter((col) => col != null);
130
+ }, [transposeConfig.columnsToTranspose, adaptable.api]);
33
131
  /**
34
- * This is used as first field
132
+ * Build transposed structure: original rows become columns (colId = primaryKey value),
133
+ * original columns become rows. Each transposed row has the column id/header plus
134
+ * one cell per original row keyed by primaryKey value.
35
135
  */
36
- const transposedFirstColumnField = '_transposed_column_value';
37
- const transposedFirstColumnHeader = '_transposed_column_header';
38
136
  const transposedRowsAndColumns = React.useMemo(() => {
39
- /**
40
- * transposed column values become primaryKey of the new tarnsposed rows
41
- * we build row by row, might be easer
42
- */
43
137
  const transposedColumns = [];
44
138
  const transposedRows = [];
45
139
  for (const row of rowNodes) {
46
- // we force the col-ids to be strings, easer to work with
47
- //row[transposeByColumnId] + '';
48
140
  const colId = adaptable.api.gridApi.getNormalisedValueFromRowNode(row, primaryKey) + '';
49
- //row[synteticTransposedByColumnId] + '';
50
- const header = adaptable.api.gridApi.getNormalisedValueFromRowNode(row, syntheticTransposedByColumnId) +
51
- '';
52
- transposedColumns.push({
53
- colId,
54
- header,
55
- });
141
+ const header = adaptable.api.gridApi.getNormalisedValueFromRowNode(row, elevatedColumnId) + '';
142
+ transposedColumns.push({ colId, header });
56
143
  }
57
144
  for (const column of columns) {
58
- /**
59
- * We can hide the transposed column, if we want
60
- */
61
- if (hideTransposedColumn && column.columnId === syntheticTransposedByColumnId) {
145
+ if (transposeConfig.hideTransposedColumn &&
146
+ column.columnId === elevatedColumnId) {
62
147
  continue;
63
148
  }
64
149
  const transposedRow = {
65
- // [transposed-by-column-id]: [other column id],
66
- // the value can be set to friendlyname
67
- [transposedFirstColumnField]: column.columnId,
68
- [transposedFirstColumnHeader]: column.friendlyName,
150
+ [TRANSPOSED_FIRST_COLUMN_FIELD]: column.columnId,
151
+ [TRANSPOSED_FIRST_COLUMN_HEADER]: column.friendlyName,
69
152
  };
70
- for (let row of rowNodes) {
71
- // [transposed-by-column-value[n]]: [other column value[n]]
153
+ for (const row of rowNodes) {
72
154
  const key = adaptable.api.gridApi.getNormalisedValueFromRowNode(row, primaryKey);
73
- // row[column.field]
74
155
  let value = adaptable.api.gridApi.getDisplayValueFromRowNode(row, column.columnId);
75
156
  if (value instanceof Date) {
76
157
  value = value.toLocaleString();
@@ -79,90 +160,25 @@ export const TransposedPopup = (props) => {
79
160
  }
80
161
  transposedRows.push(transposedRow);
81
162
  }
82
- return {
83
- transposedColumns,
84
- transposedRows,
85
- };
86
- }, [rowNodes, primaryKey, syntheticTransposedByColumnId]);
163
+ return { transposedColumns, transposedRows };
164
+ }, [rowNodes, primaryKey, elevatedColumnId, columns, transposeConfig.hideTransposedColumn, adaptable.api]);
87
165
  React.useEffect(() => {
88
- // Mounting in an effect, so the nodes are rendered/available
89
- const hostAdaptableOptions = adaptable.adaptableOptions;
90
- const adaptableOptions = {
91
- primaryKey: transposedFirstColumnField,
92
- licenseKey: hostAdaptableOptions.licenseKey,
93
- userName: `${hostAdaptableOptions.userName}`,
94
- adaptableId: `${hostAdaptableOptions.adaptableId}::TransposedView`,
95
- containerOptions: {
96
- adaptableContainer: adaptableContainerId,
97
- agGridContainer: agGridContainerId,
98
- },
99
- entitlementOptions: {
100
- defaultAccessLevel: 'Hidden',
101
- },
102
- initialState: {
103
- Layout: {
104
- Revision: Date.now(),
105
- CurrentLayout: 'TransposedView',
106
- Layouts: [
107
- {
108
- Name: 'TransposedView',
109
- TableColumns: [
110
- transposedFirstColumnHeader,
111
- ...transposedRowsAndColumns.transposedColumns.map((c) => c.colId),
112
- ],
113
- ColumnPinning: {
114
- [transposedFirstColumnHeader]: 'left',
115
- },
116
- AutoSizeColumns: true,
117
- },
118
- ],
119
- },
120
- Theme: {
121
- CurrentTheme: adaptable.api.themeApi.getCurrentTheme(),
122
- },
123
- },
124
- };
125
- const firstColumn = {
126
- field: transposedFirstColumnHeader, // use the column friendly name
127
- headerName: adaptable.api.columnApi.getFriendlyNameForColumnId(syntheticTransposedByColumnId),
128
- };
129
- const agGridOptions = {
130
- loading: false,
131
- defaultColDef: {
132
- floatingFilter: false,
133
- filter: false,
134
- sortable: true,
135
- resizable: true,
136
- enableRowGroup: false,
137
- editable: false,
138
- enablePivot: false,
139
- enableValue: false,
140
- lockPinned: true,
141
- menuTabs: [],
142
- width: 120,
143
- },
144
- columnDefs: [
145
- {
146
- field: transposedFirstColumnField,
147
- hide: true,
148
- },
149
- firstColumn,
150
- ...transposedRowsAndColumns.transposedColumns.map((col) => {
151
- return {
152
- field: col.colId,
153
- type: null,
154
- headerName: col.header,
155
- };
156
- }),
157
- ],
158
- rowData: transposedRowsAndColumns.transposedRows,
159
- sideBar: false,
160
- };
166
+ const hostOptions = adaptable.adaptableOptions;
167
+ const adaptableOptions = buildTransposedAdaptableOptions({
168
+ hostOptions,
169
+ transposedRowsAndColumns,
170
+ currentTheme: adaptable.api.themeApi.getCurrentTheme(),
171
+ });
172
+ const gridOptions = buildTransposedGridOptions({
173
+ transposedRowsAndColumns,
174
+ elevatedColumnId,
175
+ adaptableApi: adaptable.api,
176
+ });
161
177
  const modules = adaptable.agGridModulesAdapter.getAgGridRegisteredModules();
162
178
  AdaptableAgGrid._initInternal({
163
179
  variant: 'vanilla',
164
180
  adaptableOptions,
165
- gridOptions: agGridOptions,
181
+ gridOptions,
166
182
  modules,
167
183
  }).then((adaptableApi) => {
168
184
  transposedAdaptableApiRef.current = adaptableApi;
@@ -170,30 +186,20 @@ export const TransposedPopup = (props) => {
170
186
  transposedAdaptableApiRef.current?.themeApi.loadTheme(typeof event.theme === 'object' ? event.theme.Name : event.theme);
171
187
  });
172
188
  });
173
- }, [syntheticTransposedByColumnId]);
189
+ }, [elevatedColumnId, adaptable]);
174
190
  React.useEffect(() => {
175
- // destroy when closing the popup
176
191
  return () => {
177
192
  requestAnimationFrame(() => {
178
193
  transposedAdaptableApiRef.current?.destroy({ unmount: true, destroyAgGrid: true });
179
194
  });
180
195
  };
181
196
  }, []);
182
- /**
183
- * Need to get all data, manualy pivot the grid using the primary key.
184
- *
185
- * 1. get the data, and pivot using the primary key
186
- * 2. create the col definitios, a col definition for each row
187
- * 3. create the grid
188
- */
189
197
  return (React.createElement(Flex, { flexDirection: "column", className: "twa:w-full twa:h-full" },
190
198
  React.createElement(Panel, null,
191
199
  React.createElement(FormLayout, null,
192
200
  React.createElement(FormRow, { label: "Elevated Column" },
193
- React.createElement(ColumnSelector, { value: syntheticTransposedByColumnId, onChange: (colId) => {
194
- setSyntheticTransposedByColumnId(colId);
195
- } })))),
201
+ React.createElement(ColumnSelector, { value: elevatedColumnId, onChange: setElevatedColumnId })))),
196
202
  React.createElement(Flex, { className: "twa:h-full" },
197
- React.createElement(Box, { id: adaptableContainerId }),
198
- React.createElement(Box, { className: "twa:h-full twa:w-full", id: agGridContainerId }))));
203
+ React.createElement(Box, { id: ADAPTABLE_CONTAINER_ID }),
204
+ React.createElement(Box, { className: "twa:h-full twa:w-full", id: AG_GRID_CONTAINER_ID }))));
199
205
  };
@@ -53,7 +53,7 @@ export const ScheduleSettingsReminder = (props) => {
53
53
  React.createElement(Tabs.Content, null,
54
54
  React.createElement(FormLayout, null,
55
55
  React.createElement(FormRow, { label: "Name" },
56
- React.createElement(Input, { "data-name": "schedule-name", className: "twa:w-[300px]", onChange: handleNameChange, placeholder: "Enter Schedule Name", type: "string", value: props.reminderSchedule?.Name ?? '' })),
56
+ React.createElement(Input, { "data-name": "schedule-name", className: "twa:w-[300px]", onChange: handleNameChange, placeholder: "Enter Reminder Name", type: "string", value: props.reminderSchedule?.Name ?? '' })),
57
57
  React.createElement(FormRow, { label: "" },
58
58
  React.createElement(Box, { className: "twa:h-2" })),
59
59
  React.createElement(FormRow, { label: "Header" },
@@ -6,6 +6,7 @@ import { Schedule, Weekday } from '../AdaptableState/Common/Schedule';
6
6
  import { AdaptableColumnDataType, AdaptableSystemIconName, AdaptableOptions } from '../types';
7
7
  import { AdaptableAlert } from '../AdaptableState/Common/AdaptableAlert';
8
8
  import { AdaptableMessageType } from '../AdaptableState/Common/AdaptableMessageType';
9
+ import type { ContainerContext } from '../AdaptableOptions/ContainerOptions';
9
10
  export declare const BLACK: string;
10
11
  export declare const WHITE: string;
11
12
  export declare const LIGHT_GRAY: string;
@@ -31,7 +32,7 @@ export declare function getDefaultColors(): string[];
31
32
  export declare function getEmptyConfigState(): EditableConfigEntityState;
32
33
  export declare function getDescriptionForDataType(dataType: AdaptableColumnDataType): "text" | "number" | "date";
33
34
  export declare function getPlaceholderForDataType(dataType: AdaptableColumnDataType): "Enter Value" | "Enter Number" | "Enter Date";
34
- export declare function getModalContainer(adaptableOptions: AdaptableOptions, document: Document): HTMLElement;
35
+ export declare function getModalContainer(adaptableOptions: AdaptableOptions, document: Document, context?: ContainerContext): HTMLElement;
35
36
  export declare function IsEmptyStyle(style: AdaptableStyle): boolean;
36
37
  export declare function IsNotEmptyStyle(style: AdaptableStyle): boolean;
37
38
  export declare function getMessageTypeByStatusColour(statusColour: StatusColour): AdaptableMessageType;
@@ -2,6 +2,7 @@ import { WizardStatus, } from './Components/SharedProps/EditableConfigEntityStat
2
2
  import { FontWeight, FontStyle, StatusColour } from '../AdaptableState/Common/Enums';
3
3
  import { StringExtensions } from '../Utilities/Extensions/StringExtensions';
4
4
  import ArrayExtensions from '../Utilities/Extensions/ArrayExtensions';
5
+ import { resolveContainerElement } from '../Utilities/resolveContainerElement';
5
6
  export const BLACK = 'Black';
6
7
  export const WHITE = 'White';
7
8
  export const LIGHT_GRAY = 'LightGray';
@@ -120,19 +121,12 @@ export function getPlaceholderForDataType(dataType) {
120
121
  return 'Enter Date';
121
122
  }
122
123
  }
123
- export function getModalContainer(adaptableOptions, document) {
124
- let modalContainer;
125
- if (adaptableOptions.containerOptions.modalContainer) {
126
- // this has been set, so we use the property
127
- modalContainer =
128
- typeof adaptableOptions.containerOptions.modalContainer === 'string'
129
- ? document.getElementById(adaptableOptions.containerOptions.modalContainer)
130
- : adaptableOptions.containerOptions.modalContainer;
131
- if (modalContainer) {
132
- const modalContainerClassName = ' modal-container';
133
- if (!modalContainer.className.includes(modalContainerClassName)) {
134
- modalContainer.className += modalContainerClassName;
135
- }
124
+ export function getModalContainer(adaptableOptions, document, context) {
125
+ let modalContainer = resolveContainerElement(adaptableOptions.containerOptions.modalContainer, context, document);
126
+ if (modalContainer) {
127
+ const modalContainerClassName = ' modal-container';
128
+ if (!modalContainer.className.includes(modalContainerClassName)) {
129
+ modalContainer.className += modalContainerClassName;
136
130
  }
137
131
  }
138
132
  if (!modalContainer) {
@@ -423,7 +417,7 @@ export function getAdaptableToolPanelWidth() {
423
417
  return getNumericCSSVariableValue(getCSSVariableValue('--ab-cmp-toolpanel__width'), 200);
424
418
  }
425
419
  export function getSimpleButtonPaddingWidth() {
426
- return getNumericCSSVariableValue(getCSSVariableValue('--ab-space-1'), 4);
420
+ return getNumericCSSVariableValue(getCSSVariableValue('--ab-base-space'), 4);
427
421
  }
428
422
  export function getCSSVariableValue(cssVariable) {
429
423
  if (!isBrowserDocumentAvailable()) {