@adaptabletools/adaptable 22.1.0 → 22.1.1-canary.0

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 (142) hide show
  1. package/index.css +8 -9
  2. package/package.json +1 -1
  3. package/src/AdaptableInterfaces/IAdaptable.d.ts +2 -2
  4. package/src/AdaptableOptions/AdaptableOptions.d.ts +2 -21
  5. package/src/AdaptableOptions/AlertOptions.d.ts +0 -5
  6. package/src/AdaptableOptions/CellSummaryOptions.d.ts +2 -0
  7. package/src/AdaptableOptions/ChartingOptions.d.ts +2 -0
  8. package/src/AdaptableOptions/ColumnOptions.d.ts +0 -2
  9. package/src/AdaptableOptions/CommentOptions.d.ts +6 -0
  10. package/src/AdaptableOptions/ContainerOptions.d.ts +0 -6
  11. package/src/AdaptableOptions/DashboardOptions.d.ts +0 -2
  12. package/src/AdaptableOptions/DataChangeHistoryOptions.d.ts +0 -2
  13. package/src/AdaptableOptions/DataSetOptions.d.ts +2 -0
  14. package/src/AdaptableOptions/DefaultAdaptableOptions.js +35 -3
  15. package/src/AdaptableOptions/EditOptions.d.ts +0 -1
  16. package/src/AdaptableOptions/EntitlementOptions.d.ts +0 -2
  17. package/src/AdaptableOptions/ExportOptions.d.ts +1 -7
  18. package/src/AdaptableOptions/ExpressionOptions.d.ts +0 -18
  19. package/src/AdaptableOptions/Fdc3Options.d.ts +5 -1
  20. package/src/AdaptableOptions/FilterOptions.d.ts +3 -18
  21. package/src/AdaptableOptions/NoteOptions.d.ts +6 -0
  22. package/src/AdaptableOptions/NotificationsOptions.d.ts +0 -10
  23. package/src/AdaptableOptions/PredicateOptions.d.ts +12 -1
  24. package/src/AdaptableOptions/QuickSearchOptions.d.ts +0 -4
  25. package/src/AdaptableOptions/SettingsPanelOptions.d.ts +15 -5
  26. package/src/AdaptableOptions/TeamSharingOptions.d.ts +0 -4
  27. package/src/AdaptableOptions/ToolPanelOptions.d.ts +0 -1
  28. package/src/AdaptableOptions/UserInterfaceOptions.d.ts +6 -7
  29. package/src/AdaptableState/Common/AdaptableFormat.d.ts +9 -0
  30. package/src/AdaptableState/Common/AdaptableFormatPresets.d.ts +31 -0
  31. package/src/AdaptableState/Common/AdaptableFormatPresets.js +181 -0
  32. package/src/AdaptableState/Common/Menu.d.ts +1 -1
  33. package/src/AdaptableState/Common/Menu.js +2 -0
  34. package/src/AdaptableState/FormatColumnState.d.ts +6 -3
  35. package/src/Api/EventApi.d.ts +6 -6
  36. package/src/Api/Implementation/EntitlementApiImpl.js +5 -4
  37. package/src/Api/Implementation/FormatColumnApiImpl.js +8 -3
  38. package/src/Api/Implementation/GridApiImpl.js +1 -12
  39. package/src/Api/Internal/FormatColumnInternalApi.js +4 -2
  40. package/src/Api/Internal/LayoutInternalApi.js +5 -2
  41. package/src/Redux/Store/AdaptableStore.js +4 -4
  42. package/src/Strategy/AdaptableModuleBase.js +8 -7
  43. package/src/Strategy/AlertModule.d.ts +1 -1
  44. package/src/Strategy/BulkUpdateModule.d.ts +1 -1
  45. package/src/Strategy/BulkUpdateModule.js +2 -1
  46. package/src/Strategy/CalculatedColumnModule.d.ts +1 -1
  47. package/src/Strategy/CellSummaryModule.d.ts +1 -1
  48. package/src/Strategy/CellSummaryModule.js +2 -1
  49. package/src/Strategy/ColumnFilterModule.js +2 -1
  50. package/src/Strategy/ColumnInfoModule.d.ts +1 -1
  51. package/src/Strategy/ColumnInfoModule.js +2 -1
  52. package/src/Strategy/CommentModule.d.ts +1 -1
  53. package/src/Strategy/CommentModule.js +12 -2
  54. package/src/Strategy/ExportModule.d.ts +1 -1
  55. package/src/Strategy/Fdc3Module.d.ts +1 -1
  56. package/src/Strategy/FlashingCellModule.d.ts +1 -1
  57. package/src/Strategy/GridFilterModule.js +2 -1
  58. package/src/Strategy/GridInfoModule.d.ts +1 -1
  59. package/src/Strategy/GridInfoModule.js +2 -1
  60. package/src/Strategy/LayoutModule.d.ts +1 -1
  61. package/src/Strategy/NamedQueryModule.js +0 -16
  62. package/src/Strategy/NoteModule.d.ts +1 -1
  63. package/src/Strategy/NoteModule.js +16 -3
  64. package/src/Strategy/PlusMinusModule.js +8 -2
  65. package/src/Strategy/ScheduleModule.js +5 -4
  66. package/src/Strategy/SettingsPanelModule.d.ts +1 -1
  67. package/src/Strategy/ShortcutModule.js +5 -4
  68. package/src/Strategy/SmartEditModule.d.ts +1 -1
  69. package/src/Strategy/SmartEditModule.js +4 -4
  70. package/src/Strategy/SystemStatusModule.d.ts +1 -1
  71. package/src/Utilities/Constants/GeneralConstants.d.ts +4 -0
  72. package/src/Utilities/Constants/GeneralConstants.js +4 -0
  73. package/src/Utilities/Extensions/NumberExtensions.d.ts +2 -0
  74. package/src/Utilities/Extensions/NumberExtensions.js +8 -0
  75. package/src/Utilities/Helpers/AdaptableHelper.js +3 -2
  76. package/src/Utilities/Helpers/FormatHelper.js +26 -15
  77. package/src/Utilities/Services/AnnotationsService.js +10 -1
  78. package/src/Utilities/Services/Interface/IMetamodelService.d.ts +0 -1
  79. package/src/Utilities/Services/MetamodelService.d.ts +0 -2
  80. package/src/Utilities/Services/MetamodelService.js +6 -12
  81. package/src/View/AdaptableWizardView/AdaptableConfigurationDialog/EntitlementsForm.js +7 -6
  82. package/src/View/Alert/AlertEmptyView.js +2 -1
  83. package/src/View/BulkUpdate/BulkUpdateViewPanel.js +1 -1
  84. package/src/View/CellSummary/CellSummaryViewPanel.js +2 -1
  85. package/src/View/Comments/CommentsEditor.js +2 -1
  86. package/src/View/Comments/CommentsPopup.js +3 -1
  87. package/src/View/Components/Buttons/ButtonBase/index.js +3 -2
  88. package/src/View/Components/Buttons/EntityListActionButtons.js +7 -6
  89. package/src/View/Components/Buttons/SuspendToggleButton/SuspendToggleButton.js +2 -1
  90. package/src/View/Components/ColumnFilter/components/ColumnFilterInput.js +3 -2
  91. package/src/View/Components/ColumnFilter/components/ColumnFilterInputList.js +4 -1
  92. package/src/View/Components/Panels/PanelDashboard/index.js +2 -1
  93. package/src/View/Components/Popups/AdaptablePopup/AdaptablePopup.js +4 -2
  94. package/src/View/Components/Popups/AdaptablePopup/AdaptablePopupModuleView.js +2 -1
  95. package/src/View/Components/ToolPanel/AdaptableToolPanel.js +3 -2
  96. package/src/View/Components/ToolPanel/CustomToolPanelContent.js +2 -1
  97. package/src/View/Dashboard/CustomDashboardButton.js +3 -2
  98. package/src/View/Dashboard/Dashboard.js +3 -2
  99. package/src/View/Dashboard/DashboardPopup.js +3 -2
  100. package/src/View/Dashboard/PinnedToolbarsSelector.js +2 -1
  101. package/src/View/DataChangeHistory/DataChangeHistoryGrid.js +2 -1
  102. package/src/View/Export/ExportViewPanel.js +3 -1
  103. package/src/View/Filter/FilterViewPanel.js +2 -1
  104. package/src/View/FormatColumn/Wizard/FormatColumnFormatWizardSection.js +137 -181
  105. package/src/View/FormatColumn/Wizard/FormatColumnWizard.js +20 -8
  106. package/src/View/GridFilter/GridFilterPopupUI/index.d.ts +3 -2
  107. package/src/View/GridFilter/GridFilterPopupUI/index.js +2 -1
  108. package/src/View/GridFilter/GridFilterViewPanel.js +3 -2
  109. package/src/View/GridInfo/GridInfoPopup/GridInfoPopup.js +179 -6
  110. package/src/View/Layout/LayoutCloneButton.js +2 -1
  111. package/src/View/Layout/LayoutViewPanel.js +3 -1
  112. package/src/View/Layout/TransposedPopup.js +2 -1
  113. package/src/View/Note/NotePopup.js +3 -1
  114. package/src/View/SmartEdit/SmartEditViewPanel.js +2 -1
  115. package/src/View/StateManagement/StateManagementViewPanel.js +3 -1
  116. package/src/View/StatusBar/StatusBarPopup.js +2 -1
  117. package/src/View/Theme/ThemeEditor.js +2 -1
  118. package/src/View/Theme/ThemePopup.js +2 -1
  119. package/src/View/Theme/ThemeSelector.js +3 -1
  120. package/src/View/Theme/ThemeViewPanel.js +3 -1
  121. package/src/View/Wizard/OnePageWizards.js +3 -2
  122. package/src/agGrid/AdaptableAgGrid.d.ts +2 -2
  123. package/src/agGrid/AdaptableAgGrid.js +7 -29
  124. package/src/agGrid/AgGridAdapter.js +2 -2
  125. package/src/agGrid/AgGridColumnAdapter.js +11 -4
  126. package/src/agGrid/AgGridExportAdapter.js +4 -2
  127. package/src/agGrid/cellRenderers/ActionColumnRenderer.js +2 -1
  128. package/src/components/Dashboard/Dashboard.js +2 -1
  129. package/src/components/Dashboard/DashboardToolbar.js +2 -1
  130. package/src/components/Datepicker/index.js +2 -1
  131. package/src/components/ExpressionEditor/QueryBuilder/QueryBuilderInputs.js +1 -1
  132. package/src/components/Select/Select.js +4 -3
  133. package/src/components/SimpleButton/index.js +3 -2
  134. package/src/env.js +2 -2
  135. package/src/metamodel/adaptable-metamodel-model.d.ts +0 -2
  136. package/src/metamodel/adaptable.metamodel.d.ts +10 -197
  137. package/src/metamodel/adaptable.metamodel.js +1 -1
  138. package/src/types.d.ts +2 -0
  139. package/src/types.js +1 -0
  140. package/themes/dark.css +3 -1
  141. package/tsconfig.esm.tsbuildinfo +1 -1
  142. package/index.css.map +0 -1
@@ -4,6 +4,7 @@ import clamp from '../../../Utilities/utils/clamp';
4
4
  import HelpBlock from '../../../components/HelpBlock';
5
5
  import Input from '../../../components/Input';
6
6
  import SimpleButton from '../../../components/SimpleButton';
7
+ import { isAdaptableNumericFormatPreset, resolveDisplayFormat, } from '../../../AdaptableState/Common/AdaptableFormatPresets';
7
8
  import { CheckBox } from '../../../components/CheckBox';
8
9
  import FormLayout, { FormRow } from '../../../components/FormLayout';
9
10
  import { AdaptableObjectRow } from '../../Components/AdaptableObjectRow';
@@ -22,56 +23,6 @@ import { FormatColumnPlaceholderDocsLink } from '../../../Utilities/Constants/Do
22
23
  import { PIVOT_AGGREGATION_TOTAL_COLUMN_TYPE, PIVOT_ANY_TOTAL_COLUMN_TYPE, PIVOT_COLUMN_TOTAL_COLUMN_TYPE, PIVOT_GRAND_TOTAL_COLUMN_TYPE, } from '../../../AdaptableState/Common/AdaptableColumn';
23
24
  import { Box, Flex } from '../../../components/Flex';
24
25
  import { Card } from '../../../components/Card';
25
- const DOLLAR_OPTIONS = {
26
- FractionDigits: 2,
27
- FractionSeparator: '.',
28
- IntegerDigits: undefined,
29
- IntegerSeparator: ',',
30
- Prefix: '$',
31
- Suffix: '',
32
- Multiplier: 1,
33
- Parentheses: false,
34
- };
35
- const STERLING_OPTIONS = {
36
- FractionDigits: 2,
37
- FractionSeparator: '.',
38
- IntegerDigits: undefined,
39
- IntegerSeparator: ',',
40
- Prefix: '£',
41
- Suffix: '',
42
- Multiplier: 1,
43
- Parentheses: false,
44
- };
45
- const MILLION_OPTIONS = {
46
- FractionDigits: undefined,
47
- FractionSeparator: '.',
48
- IntegerDigits: undefined,
49
- IntegerSeparator: ',',
50
- Prefix: '',
51
- Suffix: 'M',
52
- Multiplier: 0.000001,
53
- Parentheses: false,
54
- };
55
- const THOUSAND_OPTIONS = {
56
- FractionDigits: undefined,
57
- FractionSeparator: '.',
58
- IntegerDigits: undefined,
59
- IntegerSeparator: ',',
60
- Prefix: '',
61
- Suffix: 'K',
62
- Multiplier: 0.001,
63
- Parentheses: false,
64
- };
65
- const PERCENT_OPTIONS = {
66
- FractionDigits: 2,
67
- FractionSeparator: '.',
68
- IntegerDigits: undefined,
69
- IntegerSeparator: ',',
70
- Prefix: '',
71
- Suffix: '%',
72
- Multiplier: 100,
73
- Parentheses: false,
74
- };
75
26
  const DateFormatPresets = [
76
27
  'MM/dd/yyyy',
77
28
  'dd-MM-yyyy',
@@ -83,33 +34,37 @@ const DateFormatPresets = [
83
34
  ];
84
35
  export const getFormatColumnFormatSummaryValue = (data) => {
85
36
  let content = 'N/A';
86
- if (!data.DisplayFormat) {
37
+ const resolved = resolveDisplayFormat(data.DisplayFormat);
38
+ if (!resolved) {
87
39
  content = 'N/A';
88
40
  }
89
41
  else {
90
- if (data.DisplayFormat.Formatter === 'NumberFormatter') {
91
- content = FormatHelper.NumberFormatter(DEFAULT_DOUBLE_DISPLAY_VALUE, data.DisplayFormat.Options);
42
+ if (resolved.Formatter === 'NumberFormatter') {
43
+ content = FormatHelper.NumberFormatter(DEFAULT_DOUBLE_DISPLAY_VALUE, resolved.Options);
92
44
  }
93
- if (data.DisplayFormat.Formatter === 'DateFormatter') {
94
- content = FormatHelper.DateFormatter(new Date(), data.DisplayFormat.Options);
45
+ if (resolved.Formatter === 'DateFormatter') {
46
+ content = FormatHelper.DateFormatter(new Date(), resolved.Options);
95
47
  }
96
- if (data.DisplayFormat.Formatter === 'StringFormatter') {
97
- content = FormatHelper.StringFormatter(DEFAULT_STRING_DISPLAY_VALUE, data.DisplayFormat.Options);
48
+ if (resolved.Formatter === 'StringFormatter') {
49
+ content = FormatHelper.StringFormatter(DEFAULT_STRING_DISPLAY_VALUE, resolved.Options);
98
50
  }
99
51
  }
100
52
  return content;
101
53
  };
102
- const renderCustomFormatter = (data, customFormatter, setFormatOption) => (React.createElement(FormRow, { key: customFormatter.id, label: customFormatter.label ?? customFormatter.id },
103
- React.createElement(CheckBox, { "data-name": customFormatter.id, checked: data.DisplayFormat.Options.CustomDisplayFormats?.some?.((item) => item === customFormatter.id), onChange: (checked) => {
104
- let newCustomFormats = data?.DisplayFormat?.Options?.CustomDisplayFormats ?? [];
105
- if (checked) {
106
- newCustomFormats = [...newCustomFormats, customFormatter.id];
107
- }
108
- else {
109
- newCustomFormats = newCustomFormats.filter((item) => item !== customFormatter.id);
110
- }
111
- setFormatOption('CustomDisplayFormats', newCustomFormats);
112
- } })));
54
+ const renderCustomFormatter = (data, customFormatter, setFormatOption) => {
55
+ const resolved = resolveDisplayFormat(data.DisplayFormat);
56
+ return (React.createElement(FormRow, { key: customFormatter.id, label: customFormatter.label ?? customFormatter.id },
57
+ React.createElement(CheckBox, { "data-name": customFormatter.id, checked: resolved?.Options.CustomDisplayFormats?.some?.((item) => item === customFormatter.id), onChange: (checked) => {
58
+ let newCustomFormats = resolved?.Options?.CustomDisplayFormats ?? [];
59
+ if (checked) {
60
+ newCustomFormats = [...newCustomFormats, customFormatter.id];
61
+ }
62
+ else {
63
+ newCustomFormats = newCustomFormats.filter((item) => item !== customFormatter.id);
64
+ }
65
+ setFormatOption('CustomDisplayFormats', newCustomFormats);
66
+ } })));
67
+ };
113
68
  export const renderFormatColumnFormatSummary = (data) => {
114
69
  return React.createElement(Tag, null, getFormatColumnFormatSummaryValue(data));
115
70
  };
@@ -209,7 +164,8 @@ export const getFormatDisplayTypeForScope = (scope, api) => {
209
164
  return undefined;
210
165
  };
211
166
  const renderDateFormat = (data, _onChange, setFormatOption, scopedCustomFormatters) => {
212
- if (data.DisplayFormat.Formatter !== 'DateFormatter') {
167
+ const resolved = resolveDisplayFormat(data.DisplayFormat);
168
+ if (resolved?.Formatter !== 'DateFormatter') {
213
169
  return null;
214
170
  }
215
171
  return (React.createElement(Box, { className: "twa:p-2" },
@@ -223,9 +179,9 @@ const renderDateFormat = (data, _onChange, setFormatOption, scopedCustomFormatte
223
179
  ")."),
224
180
  React.createElement(FormLayout, null,
225
181
  React.createElement(FormRow, { label: "Pattern" },
226
- React.createElement(Input, { "data-name": "pattern", value: data.DisplayFormat.Options.Pattern ?? '', onChange: (e) => setFormatOption('Pattern', e.currentTarget.value), className: "twa:mr-2" }),
227
- React.createElement("span", null, data.DisplayFormat.Options.Pattern &&
228
- FormatHelper.DateFormatter(new Date(), data.DisplayFormat.Options, true)))))),
182
+ React.createElement(Input, { "data-name": "pattern", value: resolved.Options.Pattern ?? '', onChange: (e) => setFormatOption('Pattern', e.currentTarget.value), className: "twa:mr-2" }),
183
+ React.createElement("span", null, resolved.Options.Pattern &&
184
+ FormatHelper.DateFormatter(new Date(), resolved.Options, true)))))),
229
185
  scopedCustomFormatters.length > 0 && (React.createElement(Tabs, { className: "twa:mt-2", keyboardNavigation: false },
230
186
  React.createElement(Tabs.Tab, null, "Custom Formats"),
231
187
  React.createElement(Tabs.Content, null,
@@ -251,128 +207,113 @@ const renderDateFormat = (data, _onChange, setFormatOption, scopedCustomFormatte
251
207
  ] })))))));
252
208
  };
253
209
  const renderNumberFormat = (data, onChange, setFormatOption, scopedCustomFormatters, api) => {
254
- if (data.DisplayFormat.Formatter !== 'NumberFormatter') {
210
+ const resolved = resolveDisplayFormat(data.DisplayFormat);
211
+ if (resolved?.Formatter !== 'NumberFormatter') {
255
212
  return null;
256
213
  }
257
- const setPercentPreset = () => {
258
- onChange({
259
- DisplayFormat: {
260
- Formatter: 'NumberFormatter',
261
- Options: PERCENT_OPTIONS,
262
- },
263
- });
264
- };
265
- const setDivideThousandPreset = () => {
266
- onChange({
267
- DisplayFormat: {
268
- Formatter: 'NumberFormatter',
269
- Options: THOUSAND_OPTIONS,
270
- },
271
- });
272
- };
273
- const setDivideMillionPreset = () => {
274
- onChange({
275
- DisplayFormat: {
276
- Formatter: 'NumberFormatter',
277
- Options: MILLION_OPTIONS,
278
- },
279
- });
280
- };
281
- const setDollarPreset = () => {
282
- onChange({
283
- DisplayFormat: {
284
- Formatter: 'NumberFormatter',
285
- Options: DOLLAR_OPTIONS,
286
- },
287
- });
288
- };
289
- const setSterlingPreset = () => {
290
- onChange({
291
- DisplayFormat: {
292
- Formatter: 'NumberFormatter',
293
- Options: STERLING_OPTIONS,
294
- },
295
- });
214
+ // A preset is now stored as a bare string ('Percentage', 'Dollar', ...)
215
+ // — selecting a preset just writes the name. The active preset is
216
+ // therefore a simple string comparison instead of the old fragile
217
+ // suffix/multiplier heuristic.
218
+ const setPreset = (preset) => {
219
+ onChange({ DisplayFormat: preset });
296
220
  };
297
- const IS_PERCENT = data.DisplayFormat.Options.Suffix === '%' &&
298
- data.DisplayFormat.Options.Multiplier === PERCENT_OPTIONS.Multiplier; //isEqual(data.DisplayFormat.Options, PERCENT_OPTIONS);
299
- const IS_THOUSAND = data.DisplayFormat.Options.Suffix === 'K' &&
300
- data.DisplayFormat.Options.Multiplier === THOUSAND_OPTIONS.Multiplier; // isEqual(data.DisplayFormat.Options, THOUSAND_OPTIONS);
301
- const IS_MILLION = data.DisplayFormat.Options.Suffix === 'M' &&
302
- data.DisplayFormat.Options.Multiplier === MILLION_OPTIONS.Multiplier; //isEqual(data.DisplayFormat.Options, MILLION_OPTIONS);
303
- const IS_DOLLAR = data.DisplayFormat.Options.Prefix === '$'; //isEqual(data.DisplayFormat.Options, DOLLAR_OPTIONS);
304
- const IS_STERLING = data.DisplayFormat.Options.Prefix === '£'; //isEqual(data.DisplayFormat, STERLING_OPTIONS);
221
+ const activePreset = isAdaptableNumericFormatPreset(data.DisplayFormat) ? data.DisplayFormat : undefined;
222
+ const IS_PERCENT = activePreset === 'Percentage';
223
+ const IS_THOUSAND = activePreset === 'Thousand';
224
+ const IS_MILLION = activePreset === 'Million';
225
+ const IS_BILLION = activePreset === 'Billion';
226
+ const IS_BASIS_POINTS = activePreset === 'BasisPoints';
227
+ const IS_DOLLAR = activePreset === 'Dollar';
228
+ const IS_STERLING = activePreset === 'Sterling';
229
+ const IS_EURO = activePreset === 'Euro';
230
+ const IS_YEN = activePreset === 'Yen';
231
+ const IS_BITCOIN = activePreset === 'Bitcoin';
232
+ const IS_INTEGER = activePreset === 'Integer';
233
+ const IS_DECIMAL = activePreset === 'Decimal';
234
+ const IS_ACCOUNTING = activePreset === 'Accounting';
235
+ const IS_FX_RATE = activePreset === 'FXRate';
236
+ const IS_SCIENTIFIC = activePreset === 'Scientific';
305
237
  const showDocumentationLinks = api.internalApi.isDocumentationLinksDisplayed();
306
238
  return (React.createElement(Box, { "data-name": 'format-column-display-format', className: "twa:p-2" },
307
- React.createElement(Tabs, null,
239
+ React.createElement(Tabs, { autoFocus: false, keyboardNavigation: false },
240
+ React.createElement(Tabs.Tab, null, "Presets"),
241
+ React.createElement(Tabs.Content, null,
242
+ React.createElement(Flex, { flexDirection: "row", className: "twa:m-2" },
243
+ React.createElement(Flex, { flexDirection: "column", className: "twa:mr-6" },
244
+ React.createElement(Radio, { "data-name": "preset-dollar", className: "twa:my-1", checked: IS_DOLLAR, onChange: () => setPreset('Dollar') }, "Dollar"),
245
+ React.createElement(Radio, { "data-name": "preset-sterling", className: "twa:my-1", checked: IS_STERLING, onChange: () => setPreset('Sterling') }, "Sterling"),
246
+ React.createElement(Radio, { "data-name": "preset-euro", className: "twa:my-1", checked: IS_EURO, onChange: () => setPreset('Euro') }, "Euro"),
247
+ React.createElement(Radio, { "data-name": "preset-yen", className: "twa:my-1", checked: IS_YEN, onChange: () => setPreset('Yen') }, "Yen")),
248
+ React.createElement(Flex, { flexDirection: "column", className: "twa:mr-6" },
249
+ React.createElement(Radio, { "data-name": "preset-thousand", className: "twa:my-1", checked: IS_THOUSAND, onChange: () => setPreset('Thousand') }, "K (Thousand)"),
250
+ React.createElement(Radio, { "data-name": "preset-million", className: "twa:my-1", checked: IS_MILLION, onChange: () => setPreset('Million') }, "M (Million)"),
251
+ React.createElement(Radio, { "data-name": "preset-billion", className: "twa:my-1", checked: IS_BILLION, onChange: () => setPreset('Billion') }, "B (Billion)"),
252
+ React.createElement(Radio, { "data-name": "preset-basis-points", className: "twa:my-1", checked: IS_BASIS_POINTS, onChange: () => setPreset('BasisPoints') }, "bps (Basis Pts)")),
253
+ React.createElement(Flex, { flexDirection: "column", className: "twa:mr-6" },
254
+ React.createElement(Radio, { "data-name": "preset-integer", className: "twa:my-1", checked: IS_INTEGER, onChange: () => setPreset('Integer') }, "Integer"),
255
+ React.createElement(Radio, { "data-name": "preset-decimal", className: "twa:my-1", checked: IS_DECIMAL, onChange: () => setPreset('Decimal') }, "Decimal"),
256
+ React.createElement(Radio, { "data-name": "preset-percentage", className: "twa:my-1", checked: IS_PERCENT, onChange: () => setPreset('Percentage') }, "Percentage"),
257
+ React.createElement(Radio, { "data-name": "preset-scientific", className: "twa:my-1", checked: IS_SCIENTIFIC, onChange: () => setPreset('Scientific') }, "Scientific")),
258
+ React.createElement(Flex, { flexDirection: "column" },
259
+ React.createElement(Radio, { "data-name": "preset-accounting", className: "twa:my-1", checked: IS_ACCOUNTING, onChange: () => setPreset('Accounting') }, "Accounting"),
260
+ React.createElement(Radio, { "data-name": "preset-fx-rate", className: "twa:my-1", checked: IS_FX_RATE, onChange: () => setPreset('FXRate') }, "FX Rate"),
261
+ React.createElement(Radio, { "data-name": "preset-bitcoin", className: "twa:my-1", checked: IS_BITCOIN, onChange: () => setPreset('Bitcoin') }, "Bitcoin"))))),
262
+ React.createElement(Tabs, { className: "twa:mt-2" },
308
263
  React.createElement(Tabs.Tab, null, "Format"),
309
264
  React.createElement(Tabs.Content, null,
310
265
  React.createElement(Flex, { flexDirection: "row" },
311
266
  React.createElement(FormLayout, { className: "twa:mr-3" },
312
- React.createElement(FormRow, { label: "Fraction Separator" },
313
- React.createElement(Input, { "data-name": "fraction-separator", value: data.DisplayFormat.Options.FractionSeparator ?? '', onChange: (e) => setFormatOption('FractionSeparator', e.currentTarget.value) }),
314
- ' '),
315
- React.createElement(FormRow, { label: "Integer Separator" },
316
- React.createElement(Input, { "data-name": "integer-separator", value: data.DisplayFormat.Options.IntegerSeparator ?? '', onChange: (e) => setFormatOption('IntegerSeparator', e.currentTarget.value) })),
317
- React.createElement(FormRow, { label: "Prefix" },
318
- React.createElement(Input, { "data-name": "prefix", value: data.DisplayFormat.Options.Prefix ?? '', onChange: (e) => setFormatOption('Prefix', e.currentTarget.value) })),
319
- React.createElement(FormRow, { label: "Suffix" },
320
- React.createElement(Input, { "data-name": "suffix", value: data.DisplayFormat.Options.Suffix ?? '', onChange: (e) => setFormatOption('Suffix', e.currentTarget.value) })),
321
- React.createElement(FormRow, { label: "Truncate" },
322
- React.createElement(CheckBox, { "data-name": "truncate-checkbox", checked: data.DisplayFormat.Options.Truncate, onChange: (checked) => setFormatOption('Truncate', checked) })),
323
- React.createElement(FormRow, { label: "Ceiling" },
324
- React.createElement(CheckBox, { "data-name": "ceiling-checkbox", checked: data.DisplayFormat.Options.Ceiling, onChange: (checked) => setFormatOption('Ceiling', checked) })),
325
- ' ',
326
- React.createElement(FormRow, { label: "Absolute" },
327
- React.createElement(CheckBox, { "data-name": "abs-checkbox", checked: data.DisplayFormat.Options.Abs, onChange: (checked) => setFormatOption('Abs', checked) }))),
328
- React.createElement(FormLayout, null,
329
267
  React.createElement(FormRow, { label: "Fraction Digits" },
330
- React.createElement(Input, { "data-name": "fraction-digits", type: "number", min: "0",
331
- // max="20"
332
- value: typeof data.DisplayFormat.Options.FractionDigits === 'number'
333
- ? data.DisplayFormat.Options.FractionDigits
268
+ React.createElement(Input, { "data-name": "fraction-digits", type: "number", min: "0", value: typeof resolved.Options.FractionDigits === 'number'
269
+ ? resolved.Options.FractionDigits
334
270
  : '', onChange: (e) => setFormatOption('FractionDigits', StringExtensions.IsNumeric(e.currentTarget.value)
335
271
  ? clamp(Number(e.currentTarget.value), 0, 20)
336
272
  : undefined) })),
337
273
  React.createElement(FormRow, { label: "Integer Digits" },
338
- React.createElement(Input, { "data-name": "integer-digits", type: "number", min: "0", value: data.DisplayFormat.Options.IntegerDigits, onChange: (e) => setFormatOption('IntegerDigits', StringExtensions.IsNumeric(e.currentTarget.value)
274
+ React.createElement(Input, { "data-name": "integer-digits", type: "number", min: "0", value: resolved.Options.IntegerDigits, onChange: (e) => setFormatOption('IntegerDigits', StringExtensions.IsNumeric(e.currentTarget.value)
339
275
  ? clamp(Number(e.currentTarget.value), 0, 20)
340
276
  : undefined) })),
277
+ React.createElement(FormRow, { label: "Fraction Separator" },
278
+ React.createElement(Input, { "data-name": "fraction-separator", value: resolved.Options.FractionSeparator ?? '', onChange: (e) => setFormatOption('FractionSeparator', e.currentTarget.value) })),
279
+ React.createElement(FormRow, { label: "Integer Separator" },
280
+ React.createElement(Input, { "data-name": "integer-separator", value: resolved.Options.IntegerSeparator ?? '', onChange: (e) => setFormatOption('IntegerSeparator', e.currentTarget.value) })),
341
281
  React.createElement(FormRow, { label: "Multiplier" },
342
- React.createElement(Input, { "data-name": "multiplier", type: "number", value: data.DisplayFormat.Options.Multiplier, onChange: (e) => setFormatOption('Multiplier', Number(e.currentTarget.value)) })),
343
- ' ',
282
+ React.createElement(Input, { "data-name": "multiplier", type: "number", value: resolved.Options.Multiplier, onChange: (e) => setFormatOption('Multiplier', Number(e.currentTarget.value)) }))),
283
+ React.createElement(FormLayout, { className: "twa:mr-3" },
284
+ React.createElement(FormRow, { label: "Prefix" },
285
+ React.createElement(Input, { "data-name": "prefix", value: resolved.Options.Prefix ?? '', onChange: (e) => setFormatOption('Prefix', e.currentTarget.value) })),
286
+ React.createElement(FormRow, { label: "Suffix" },
287
+ React.createElement(Input, { "data-name": "suffix", value: resolved.Options.Suffix ?? '', onChange: (e) => setFormatOption('Suffix', e.currentTarget.value) })),
344
288
  React.createElement(FormRow, { label: "Parentheses" },
345
- React.createElement(CheckBox, { "data-name": "parentheses-checkbox", checked: data.DisplayFormat.Options.Parentheses, onChange: (checked) => setFormatOption('Parentheses', checked) })),
346
- React.createElement(FormRow, { label: "Floor" },
347
- React.createElement(CheckBox, { "data-name": "floor-checkbox", checked: data.DisplayFormat.Options.Floor, onChange: (checked) => setFormatOption('Floor', checked) })),
348
- React.createElement(FormRow, { label: "Round" },
349
- React.createElement(CheckBox, { "data-name": "round-checkbox", checked: data.DisplayFormat.Options.Round, onChange: (checked) => setFormatOption('Round', checked) })),
289
+ React.createElement(CheckBox, { "data-name": "parentheses-checkbox", checked: resolved.Options.Parentheses, onChange: (checked) => setFormatOption('Parentheses', checked) })),
350
290
  React.createElement(FormRow, { label: "Empty" },
351
- React.createElement(CheckBox, { "data-name": "empty-checkbox", checked: data.DisplayFormat.Options.Empty, onChange: (checked) => setFormatOption('Empty', checked) })))))),
291
+ React.createElement(CheckBox, { "data-name": "empty-checkbox", checked: resolved.Options.Empty, onChange: (checked) => setFormatOption('Empty', checked) }))),
292
+ React.createElement(FormLayout, null,
293
+ React.createElement(FormRow, { label: "Truncate" },
294
+ React.createElement(CheckBox, { "data-name": "truncate-checkbox", checked: resolved.Options.Truncate, onChange: (checked) => setFormatOption('Truncate', checked) })),
295
+ React.createElement(FormRow, { label: "Round" },
296
+ React.createElement(CheckBox, { "data-name": "round-checkbox", checked: resolved.Options.Round, onChange: (checked) => setFormatOption('Round', checked) })),
297
+ React.createElement(FormRow, { label: "Ceiling" },
298
+ React.createElement(CheckBox, { "data-name": "ceiling-checkbox", checked: resolved.Options.Ceiling, onChange: (checked) => setFormatOption('Ceiling', checked) })),
299
+ React.createElement(FormRow, { label: "Floor" },
300
+ React.createElement(CheckBox, { "data-name": "floor-checkbox", checked: resolved.Options.Floor, onChange: (checked) => setFormatOption('Floor', checked) })),
301
+ React.createElement(FormRow, { label: "Absolute" },
302
+ React.createElement(CheckBox, { "data-name": "abs-checkbox", checked: resolved.Options.Abs, onChange: (checked) => setFormatOption('Abs', checked) })),
303
+ React.createElement(FormRow, { label: "Scientific" },
304
+ React.createElement(CheckBox, { "data-name": "scientific-checkbox", checked: resolved.Options.Notation === 'scientific', onChange: (checked) => setFormatOption('Notation', checked ? 'scientific' : undefined) })))))),
352
305
  scopedCustomFormatters.length > 0 && (React.createElement(Tabs, { className: "twa:mt-2", keyboardNavigation: false },
353
306
  React.createElement(Tabs.Tab, null, "Custom Formats"),
354
307
  React.createElement(Tabs.Content, null,
355
308
  React.createElement(Flex, { flexDirection: "row" },
356
309
  React.createElement(FormLayout, null, scopedCustomFormatters.map((formatter) => renderCustomFormatter(data, formatter, setFormatOption))))))),
357
- React.createElement(Tabs, { className: "twa:mt-2", autoFocus: false, keyboardNavigation: false },
358
- React.createElement(Tabs.Tab, null, "Presets"),
359
- React.createElement(Tabs.Content, null,
360
- React.createElement(Box, { className: "twa:p-2 twa:text-2" }, "Select a preset for common use cases"),
361
- React.createElement(Flex, { flexDirection: "row" },
362
- React.createElement(FormLayout, { className: "twa:m-2" },
363
- React.createElement(FormRow, { label: "Show As:" },
364
- React.createElement(Radio, { "data-name": "preset-percentage", className: "twa:ml-2", checked: IS_PERCENT, onChange: () => setPercentPreset() }, "Percentage"),
365
- React.createElement(Radio, { "data-name": "preset-thousand", className: "twa:ml-3", checked: IS_THOUSAND, onChange: () => setDivideThousandPreset() }, "K (Thousand)"),
366
- React.createElement(Radio, { "data-name": "preset-million", className: "twa:ml-3", checked: IS_MILLION, onChange: () => setDivideMillionPreset() }, "M (Million)"),
367
- React.createElement(Radio, { "data-name": "preset-dollar", className: "twa:ml-3", checked: IS_DOLLAR, onChange: () => setDollarPreset() }, "Dollar"),
368
- React.createElement(Radio, { "data-name": "preset-sterling", className: "twa:ml-3", checked: IS_STERLING, onChange: () => setSterlingPreset() }, "Sterling")))))),
369
310
  React.createElement(Tabs, { className: "twa:mt-2", autoFocus: false, keyboardNavigation: false },
370
311
  React.createElement(Tabs.Tab, null, "Dynamic Content"),
371
312
  React.createElement(Tabs.Content, null,
372
313
  React.createElement(Box, { className: "twa:p-2 twa:text-2" }, "Provide dynamic content through the use of Placeholders"),
373
314
  React.createElement(FormLayout, { className: "twa:m-2" },
374
315
  React.createElement(FormRow, { label: "" },
375
- React.createElement(Textarea, { className: "twa:min-w-[300px] twa:mt-2", rows: 3, placeholder: "", type: 'text', autoFocus: false, value: data.DisplayFormat.Options.Content?.toString() ?? '', onChange: (e) => setFormatOption('Content', e.currentTarget.value) }),
316
+ React.createElement(Textarea, { className: "twa:min-w-[300px] twa:mt-2", rows: 3, placeholder: "", type: 'text', autoFocus: false, value: resolved.Options.Content?.toString() ?? '', onChange: (e) => setFormatOption('Content', e.currentTarget.value) }),
376
317
  showDocumentationLinks && (React.createElement(HelpBlock, { "data-name": "query-documentation", className: "twa:mt-3 twa:mb-2 twa:p-0 twa:text-3" },
377
318
  React.createElement(ButtonInfo, { className: "twa:mr-2", onClick: () => window.open(FormatColumnPlaceholderDocsLink, '_blank') }),
378
319
  "Learn more about using placeholders"))),
@@ -387,27 +328,28 @@ const renderNumberFormat = (data, onChange, setFormatOption, scopedCustomFormatt
387
328
  React.createElement(AdaptableObjectRow, { colItems: [
388
329
  { Content: DEFAULT_DOUBLE_DISPLAY_VALUE, Size: 1 },
389
330
  {
390
- Content: FormatHelper.NumberFormatter(DEFAULT_DOUBLE_DISPLAY_VALUE, data.DisplayFormat.Options),
331
+ Content: FormatHelper.NumberFormatter(DEFAULT_DOUBLE_DISPLAY_VALUE, resolved.Options),
391
332
  Size: 1,
392
333
  },
393
334
  ] }),
394
335
  React.createElement(AdaptableObjectRow, { colItems: [
395
336
  { Content: '-' + DEFAULT_DOUBLE_DISPLAY_VALUE, Size: 1 },
396
337
  {
397
- Content: FormatHelper.NumberFormatter(-DEFAULT_DOUBLE_DISPLAY_VALUE, data.DisplayFormat.Options),
338
+ Content: FormatHelper.NumberFormatter(-DEFAULT_DOUBLE_DISPLAY_VALUE, resolved.Options),
398
339
  Size: 1,
399
340
  },
400
341
  ] }),
401
342
  React.createElement(AdaptableObjectRow, { colItems: [
402
343
  { Content: '0.123', Size: 1 },
403
344
  {
404
- Content: FormatHelper.NumberFormatter(0.123, data.DisplayFormat.Options),
345
+ Content: FormatHelper.NumberFormatter(0.123, resolved.Options),
405
346
  Size: 1,
406
347
  },
407
348
  ] })))));
408
349
  };
409
350
  const renderStringFormat = (data, _onChange, setFormatOption, scopedCustomFormatters, api) => {
410
- if (data.DisplayFormat.Formatter !== 'StringFormatter') {
351
+ const resolved = resolveDisplayFormat(data.DisplayFormat);
352
+ if (resolved?.Formatter !== 'StringFormatter') {
411
353
  return null;
412
354
  }
413
355
  const showDocumentationLinks = api.internalApi.isDocumentationLinksDisplayed();
@@ -418,23 +360,23 @@ const renderStringFormat = (data, _onChange, setFormatOption, scopedCustomFormat
418
360
  React.createElement(Flex, { flexDirection: "row", className: "twa:gap-2 twa:col-span-2 twa:items-center" },
419
361
  React.createElement("label", null, "Case:"),
420
362
  React.createElement(ToggleGroup, null,
421
- React.createElement(Toggle, { pressed: data.DisplayFormat.Options.Case === 'Upper', onPressedChange: (pressed) => setFormatOption('Case', pressed ? 'Upper' : undefined), icon: "case-upper" }),
422
- React.createElement(Toggle, { pressed: data.DisplayFormat.Options.Case === 'Lower', onPressedChange: (pressed) => setFormatOption('Case', pressed ? 'Lower' : undefined), icon: "case-lower" }),
423
- React.createElement(Toggle, { pressed: data.DisplayFormat.Options.Case === 'Sentence', onPressedChange: (pressed) => setFormatOption('Case', pressed ? 'Sentence' : undefined), icon: "case-sentence" })),
424
- React.createElement(CheckBox, { "data-name": "trim-checkbox", className: "twa:ml-5", checked: data.DisplayFormat.Options.Trim, onChange: (checked) => setFormatOption('Trim', checked) }, "Trim")),
363
+ React.createElement(Toggle, { pressed: resolved.Options.Case === 'Upper', onPressedChange: (pressed) => setFormatOption('Case', pressed ? 'Upper' : undefined), icon: "case-upper" }),
364
+ React.createElement(Toggle, { pressed: resolved.Options.Case === 'Lower', onPressedChange: (pressed) => setFormatOption('Case', pressed ? 'Lower' : undefined), icon: "case-lower" }),
365
+ React.createElement(Toggle, { pressed: resolved.Options.Case === 'Sentence', onPressedChange: (pressed) => setFormatOption('Case', pressed ? 'Sentence' : undefined), icon: "case-sentence" })),
366
+ React.createElement(CheckBox, { "data-name": "trim-checkbox", className: "twa:ml-5", checked: resolved.Options.Trim, onChange: (checked) => setFormatOption('Trim', checked) }, "Trim")),
425
367
  React.createElement(Flex, { flexDirection: "column", className: "twa:gap-2" },
426
368
  React.createElement("label", null, "Prefix"),
427
- React.createElement(Input, { "data-name": "prefix", value: data.DisplayFormat.Options.Prefix ?? '', onChange: (e) => setFormatOption('Prefix', e.currentTarget.value) })),
369
+ React.createElement(Input, { "data-name": "prefix", value: resolved.Options.Prefix ?? '', onChange: (e) => setFormatOption('Prefix', e.currentTarget.value) })),
428
370
  React.createElement(Flex, { flexDirection: "column", className: "twa:gap-2" },
429
371
  React.createElement("label", null, "Suffix"),
430
- React.createElement(Input, { "data-name": "suffix", value: data.DisplayFormat.Options.Suffix ?? '', onChange: (e) => setFormatOption('Suffix', e.currentTarget.value) })),
372
+ React.createElement(Input, { "data-name": "suffix", value: resolved.Options.Suffix ?? '', onChange: (e) => setFormatOption('Suffix', e.currentTarget.value) })),
431
373
  React.createElement(Flex, { flexDirection: "column", className: "twa:col-span-2 twa:gap-2" },
432
374
  React.createElement("label", null, "Content"),
433
- React.createElement(Textarea, { className: "twa:min-w-[300px]", rows: 3, placeholder: "", type: 'text', autoFocus: false, value: data.DisplayFormat.Options.Content ?? '', onChange: (e) => setFormatOption('Content', e.currentTarget.value) }),
375
+ React.createElement(Textarea, { className: "twa:min-w-[300px]", rows: 3, placeholder: "", type: 'text', autoFocus: false, value: resolved.Options.Content ?? '', onChange: (e) => setFormatOption('Content', e.currentTarget.value) }),
434
376
  showDocumentationLinks && (React.createElement(HelpBlock, { "data-name": "query-documentation", className: "twa:mt-3 twa:mb-2 twa:p-0 twa:text-3" },
435
377
  React.createElement(ButtonInfo, { className: "twa:mr-2", onClick: () => window.open(FormatColumnPlaceholderDocsLink, '_blank') }),
436
378
  "See how to create dynamic Display Format using placeholders"))),
437
- React.createElement(CheckBox, { className: "twa:col-span-2", "data-name": "empty-checkbox", checked: data.DisplayFormat.Options.Empty, onChange: (checked) => setFormatOption('Empty', checked) }, "Empty"))),
379
+ React.createElement(CheckBox, { className: "twa:col-span-2", "data-name": "empty-checkbox", checked: resolved.Options.Empty, onChange: (checked) => setFormatOption('Empty', checked) }, "Empty"))),
438
380
  scopedCustomFormatters.length > 0 && (React.createElement(Card, { shadow: false },
439
381
  React.createElement(Card.Title, null, "Custom Formats"),
440
382
  React.createElement(Card.Body, null,
@@ -451,7 +393,7 @@ const renderStringFormat = (data, _onChange, setFormatOption, scopedCustomFormat
451
393
  { Content: '"' + DEFAULT_STRING_DISPLAY_VALUE + '"', Size: 1 },
452
394
  {
453
395
  Content: '"' +
454
- FormatHelper.StringFormatter(DEFAULT_STRING_DISPLAY_VALUE, data.DisplayFormat.Options) +
396
+ FormatHelper.StringFormatter(DEFAULT_STRING_DISPLAY_VALUE, resolved.Options) +
455
397
  '"',
456
398
  Size: 1,
457
399
  },
@@ -466,12 +408,26 @@ export const FormatColumnFormatWizardSection = (props) => {
466
408
  props.onChange({ ...data, ...updated });
467
409
  };
468
410
  const setFormatOption = (key, value) => {
469
- const DisplayFormat = { ...data.DisplayFormat };
470
- // @ts-ignore
471
- DisplayFormat.Options = { ...DisplayFormat.Options, [key]: value };
411
+ // If the user is currently on a preset (e.g. 'Dollar'), materialise
412
+ // it into a concrete AdaptableFormat *before* applying the edit so
413
+ // that tweaking any field "forks" the preset into a bespoke format.
414
+ const current = resolveDisplayFormat(data.DisplayFormat);
415
+ if (!current)
416
+ return;
417
+ // The cast is safe: we copy `current.Formatter` and merge into
418
+ // `current.Options`, so the discriminant and the Options shape
419
+ // are always kept in sync — TypeScript just can't correlate the
420
+ // two ends of the discriminated union across a generic merge.
421
+ const DisplayFormat = {
422
+ Formatter: current.Formatter,
423
+ Options: { ...current.Options, [key]: value },
424
+ };
472
425
  update({ DisplayFormat });
473
426
  };
474
- const Type = data.DisplayFormat && data.DisplayFormat.Formatter;
427
+ // A preset string is logically a NumberFormatter for this routing.
428
+ const Type = isAdaptableNumericFormatPreset(data.DisplayFormat)
429
+ ? 'NumberFormatter'
430
+ : data.DisplayFormat && data.DisplayFormat.Formatter;
475
431
  const customScopedFormatters = customDisplayFormatters.filter((displayFormatter) => adaptable.api.columnScopeApi.isScopeInScope(data.Scope, displayFormatter.scope));
476
432
  if (Type === 'NumberFormatter') {
477
433
  return renderNumberFormat(data, update, setFormatOption, customScopedFormatters, adaptable.api);
@@ -18,6 +18,7 @@ import { isAdaptableRuleValid } from '../../Components/EntityRulesEditor/Utiliti
18
18
  import { FormatColumnRuleWizardSection } from './FormatColumnRuleWizardSection';
19
19
  import { DEFAULT_PREDICATE_ID_FOR_FORMAT_COLUMN } from './constants';
20
20
  import { isObjectEmpty } from '../../../Utilities/Extensions/ObjectExtensions';
21
+ import { isAdaptableNumericFormatPreset, } from '../../../AdaptableState/Common/AdaptableFormatPresets';
21
22
  import { Box, Flex } from '../../../components/Flex';
22
23
  const adjustDisplayFormat = (fc, api) => {
23
24
  const formatColumn = { ...fc };
@@ -41,22 +42,26 @@ const adjustDisplayFormat = (fc, api) => {
41
42
  if (!formatDataType && formatColumn.DisplayFormat) {
42
43
  formatColumn.DisplayFormat = undefined;
43
44
  }
44
- if ((!formatColumn.DisplayFormat || formatColumn.DisplayFormat.Formatter !== 'NumberFormatter') &&
45
- formatDataType === 'number') {
45
+ // For shape-detection purposes a numeric preset name is equivalent to a
46
+ // NumberFormatter. We only need to materialise an object literal when
47
+ // there is no DisplayFormat at all (or the existing one targets a
48
+ // different Formatter type to the new scope).
49
+ const resolvedFormatter = isAdaptableNumericFormatPreset(formatColumn.DisplayFormat)
50
+ ? 'NumberFormatter'
51
+ : formatColumn.DisplayFormat?.Formatter;
52
+ if (resolvedFormatter !== 'NumberFormatter' && formatDataType === 'number') {
46
53
  formatColumn.DisplayFormat = {
47
54
  Formatter: 'NumberFormatter',
48
55
  Options: {},
49
56
  };
50
57
  }
51
- if ((!formatColumn.DisplayFormat || formatColumn.DisplayFormat.Formatter !== 'DateFormatter') &&
52
- formatDataType === 'date') {
58
+ if (resolvedFormatter !== 'DateFormatter' && formatDataType === 'date') {
53
59
  formatColumn.DisplayFormat = {
54
60
  Formatter: 'DateFormatter',
55
61
  Options: {},
56
62
  };
57
63
  }
58
- if ((!formatColumn.DisplayFormat || formatColumn.DisplayFormat.Formatter !== 'StringFormatter') &&
59
- formatDataType === 'text') {
64
+ if (resolvedFormatter !== 'StringFormatter' && formatDataType === 'text') {
60
65
  formatColumn.DisplayFormat = {
61
66
  Formatter: 'StringFormatter',
62
67
  Options: {},
@@ -90,8 +95,15 @@ export function FormatColumnWizard(props) {
90
95
  }, []);
91
96
  const dispatch = useDispatch();
92
97
  const handleFinish = () => {
93
- if (formatColumn?.DisplayFormat?.Options &&
94
- isObjectEmpty(formatColumn?.DisplayFormat?.Options)) {
98
+ // If the user opened the wizard, picked a Formatter type, but never
99
+ // touched any options (and didn't pick a preset), drop the empty
100
+ // DisplayFormat object so we don't persist an empty shell. Preset
101
+ // strings are always meaningful and should never be removed here.
102
+ const df = formatColumn?.DisplayFormat;
103
+ if (df &&
104
+ !isAdaptableNumericFormatPreset(df) &&
105
+ df.Options &&
106
+ isObjectEmpty(df.Options)) {
95
107
  delete formatColumn.DisplayFormat;
96
108
  }
97
109
  if (formatColumn.Style && isObjectEmpty(formatColumn.Style)) {
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { NamedQuery, CachedQuery } from '../../../AdaptableState/NamedQueryState';
3
+ import { AccessLevel } from '../../../AdaptableState/Common/Entitlement';
3
4
  /**
4
5
  * Column item for the dropdown menu
5
6
  */
@@ -61,8 +62,8 @@ export interface GridFilterPopupUIProps {
61
62
  /** Called when setting the grid filter expression directly */
62
63
  onSetGridFilterExpression: (query: string) => void;
63
64
  /** Access level for grid filter operations */
64
- gridFilterAccessLevel: 'ReadOnly' | 'Hidden' | 'Full';
65
+ gridFilterAccessLevel: AccessLevel;
65
66
  /** Access level for named query operations */
66
- namedQueryAccessLevel: 'ReadOnly' | 'Hidden' | 'Full';
67
+ namedQueryAccessLevel: AccessLevel;
67
68
  }
68
69
  export declare const GridFilterPopupUI: (props: GridFilterPopupUIProps) => React.JSX.Element;
@@ -14,9 +14,10 @@ import { ButtonUnsuspend } from '../../Components/Buttons/ButtonUnsuspend';
14
14
  import { PopupPanel } from '../../Components/Popups/AdaptablePopup/PopupPanel';
15
15
  import { NamedQuerySelector } from './../NamedQuerySelector';
16
16
  import { Box, Flex } from '../../../components/Flex';
17
+ import { ACCESS_LEVEL_READ_ONLY } from '../../../Utilities/Constants/GeneralConstants';
17
18
  export const GridFilterPopupUI = (props) => {
18
19
  const { expression, isExpressionValid, isExpressionNamedQuery, isSuspended, isReadOnly, availableColumns, namedQueries, cachedQueries, currentGridFilterExpression, headerText, glyphicon, infoLink, infoLinkDisabled, onExpressionChange, onRunQuery, onClearQuery, onSaveQuery, onSuspend, onUnsuspend, onExpand, onSelectNamedQuery, onSetGridFilterExpression, gridFilterAccessLevel, namedQueryAccessLevel, } = props;
19
- const disabled = isReadOnly || isSuspended || gridFilterAccessLevel === 'ReadOnly';
20
+ const disabled = isReadOnly || isSuspended || gridFilterAccessLevel === ACCESS_LEVEL_READ_ONLY;
20
21
  const handleKeyDown = (e) => {
21
22
  if (e.key === 'Enter') {
22
23
  onRunQuery();
@@ -12,13 +12,14 @@ import { NamedQuerySelector } from './NamedQuerySelector';
12
12
  import { useGridFilterExpressionEditor } from './useGridFilterExpressionEditor';
13
13
  import { Box, Flex } from '../../components/Flex';
14
14
  import Tooltip from '../../components/Tooltip';
15
+ import { ACCESS_LEVEL_READ_ONLY } from '../../Utilities/Constants/GeneralConstants';
15
16
  const QueryViewPanelComponent = (props) => {
16
17
  const { cachedQueries, expression, isExpressionNamedQuery, isExpressionValid, isSuspended, gridFilter, isAdaptableReady, namedQueries, runQuery, onExpand, clearQuery, namedQueryModuleAccessLevel, saveQuery, suspendGridFilter, unSuspendGridFilter, setGridFilterExpression, gridFilterAccessLevel, isReadOnly, } = useGridFilterExpressionEditor();
17
18
  if (!isAdaptableReady) {
18
19
  return null;
19
20
  }
20
21
  const elementType = props.viewType === 'Toolbar' ? 'DashboardToolbar' : 'ToolPanel';
21
- const disabled = isReadOnly || isSuspended || gridFilterAccessLevel === 'ReadOnly';
22
+ const disabled = isReadOnly || isSuspended || gridFilterAccessLevel === ACCESS_LEVEL_READ_ONLY;
22
23
  const buttonExpand = (React.createElement(ButtonExpand, { disabled: disabled, accessLevel: gridFilterAccessLevel, variant: "text", tone: "neutral", onClick: onExpand, tooltip: "Edit the Expression in UI", className: "twa:ml-1" }));
23
24
  const renderExpressionLabel = () => {
24
25
  const baseClasses = 'twa:font-mono twa:text-s twa:py-2 twa:px-2 twa:overflow-hidden twa:text-ellipsis twa:whitespace-nowrap twa:bg-defaultbackground twa:text-text-on-defaultbackground twa:rounded twa:cursor-pointer twa:leading-4 twa:flex twa:items-center';
@@ -37,7 +38,7 @@ const QueryViewPanelComponent = (props) => {
37
38
  const hasActiveGridFilter = gridFilter != null && hasExpression;
38
39
  const hasNamedQueries = namedQueries && namedQueries.length > 0;
39
40
  const clearButton = hasExpression ? (React.createElement(ButtonClear, { onClick: () => clearQuery(), tooltip: "Clear Grid Filter", accessLevel: gridFilterAccessLevel, variant: "text", tone: "neutral" })) : null;
40
- const saveButton = hasExpression ? (React.createElement(ButtonSave, { onClick: () => saveQuery(), tooltip: "Save as Named Query", accessLevel: namedQueryModuleAccessLevel, disabled: isReadOnly || gridFilterAccessLevel === 'ReadOnly' || !isExpressionValid || isExpressionNamedQuery, variant: "text", tone: "neutral" })) : null;
41
+ const saveButton = hasExpression ? (React.createElement(ButtonSave, { onClick: () => saveQuery(), tooltip: "Save as Named Query", accessLevel: namedQueryModuleAccessLevel, disabled: isReadOnly || gridFilterAccessLevel === ACCESS_LEVEL_READ_ONLY || !isExpressionValid || isExpressionNamedQuery, variant: "text", tone: "neutral" })) : null;
41
42
  const suspendButton = hasActiveGridFilter ? (React.createElement(ButtonPause, { onClick: () => suspendGridFilter(), tooltip: "Suspend Grid Filter", accessLevel: gridFilterAccessLevel, disabled: disabled || !isExpressionValid, variant: "text", tone: "neutral" })) : null;
42
43
  const unSuspendButton = hasActiveGridFilter ? (React.createElement(ButtonUnsuspend, { onClick: () => unSuspendGridFilter(), tooltip: "Unsuspend Grid Filter", accessLevel: gridFilterAccessLevel, disabled: !isExpressionValid, variant: "text", tone: "neutral" })) : null;
43
44
  const namedQuerySelector = hasNamedQueries ? (React.createElement(NamedQuerySelector, { namedQueries: namedQueries, cachedQueries: cachedQueries, currentQuery: gridFilter?.Expression, onSelect: (query) => runQuery(query), setGridFilterExpression: (query) => setGridFilterExpression(query) })) : null;