@canvas-components/react-pivot-table 0.2.3 → 0.2.5

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.
package/dist/index.js CHANGED
@@ -1,79 +1,242 @@
1
- import { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle } from 'react';
2
- import { createEmptyPivotFieldLayout, createPivotFieldCatalog, resolvePivotDimensionKey, resolvePivotFieldLayoutOptions, PivotTable, getValueByDataIndex, resolvePivotDimensionTitle, findPivotFieldArea, removePivotField, movePivotField, resolvePivotValueFieldTitle, createPivotCellKey } from '@canvas-components/pivot-table';
1
+ import { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle, Fragment as Fragment$1 } from 'react';
2
+ import { createEmptyPivotFieldLayout, createPivotFieldCatalog, resolvePivotDimensionKey, resolvePivotFieldLayoutOptions, PivotTable, loadPivotFieldLayout, getValueByDataIndex, resolvePivotDimensionTitle, findPivotFieldArea, removePivotField, movePivotField, resolvePivotValueFieldTitle, savePivotFieldLayout, createPivotCellKey } from '@canvas-components/pivot-table';
3
3
  import { ReactResizablePanels, ReactFilterPanel, ReactPopup, ReactCheckbox, ReactInput, ReactSelect, ReactInputNumber } from '@canvas-components/react-components';
4
4
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
5
  import { createPortal } from 'react-dom';
6
6
  import { CanvasChart } from '@canvas-components/chart';
7
7
 
8
8
  // src/components/react-pivot-table.tsx
9
+
10
+ // #style-inject:#style-inject
11
+ function styleInject(css, { insertAt } = {}) {
12
+ if (!css || typeof document === "undefined") return;
13
+ const head = document.head || document.getElementsByTagName("head")[0];
14
+ const style = document.createElement("style");
15
+ style.type = "text/css";
16
+ if (insertAt === "top") {
17
+ if (head.firstChild) {
18
+ head.insertBefore(style, head.firstChild);
19
+ } else {
20
+ head.appendChild(style);
21
+ }
22
+ } else {
23
+ head.appendChild(style);
24
+ }
25
+ if (style.styleSheet) {
26
+ style.styleSheet.cssText = css;
27
+ } else {
28
+ style.appendChild(document.createTextNode(css));
29
+ }
30
+ }
31
+
32
+ // src/features/context-menu/pivot-context-menu.css
33
+ styleInject(".react-pivot-table-menu-action {\n transition: background-color 0.2s ease, color 0.2s ease;\n}\n.react-pivot-table-menu-action:hover {\n background: #f3f4f6 !important;\n}\n.react-pivot-table-menu-chevron {\n width: 7px;\n height: 7px;\n flex: 0 0 auto;\n margin-left: 8px;\n border-top: 1.5px solid #9ca3af;\n border-right: 1.5px solid #9ca3af;\n transform: rotate(45deg);\n transform-origin: center;\n transition: transform 0.18s ease, border-color 0.18s ease;\n}\n.react-pivot-table-menu-action:hover .react-pivot-table-menu-chevron,\n.react-pivot-table-menu-chevron-active {\n border-color: #6b7280;\n transform: rotate(135deg);\n}\n");
34
+ var CONTEXT_MENU_SURFACE_STYLE = {
35
+ padding: 8,
36
+ background: "#ffffff",
37
+ boxShadow: "0 8px 24px rgba(15, 23, 42, 0.16)"
38
+ };
9
39
  function PivotContextMenu(props) {
10
40
  const { request } = props;
41
+ const [menuOpen, setMenuOpen] = useState(true);
42
+ useEffect(() => {
43
+ if (request) setMenuOpen(true);
44
+ }, [request]);
11
45
  if (!request) return null;
12
46
  return /* @__PURE__ */ jsx(
13
47
  ReactPopup,
14
48
  {
15
- open: true,
49
+ open: menuOpen,
16
50
  anchorRect: { x: request.x, y: request.y, width: 0, height: 0 },
17
51
  offset: 4,
18
52
  matchAnchorWidth: false,
19
- minWidth: 180,
53
+ minWidth: 260,
20
54
  onOpenChange: (open) => {
55
+ setMenuOpen(open);
21
56
  if (!open) request.onClose();
22
57
  },
23
- surfaceStyle: { padding: 6 },
24
- children: /* @__PURE__ */ jsxs("div", { style: { display: "grid", minWidth: 180 }, children: [
58
+ surfaceStyle: { ...CONTEXT_MENU_SURFACE_STYLE, width: 260 },
59
+ children: /* @__PURE__ */ jsxs("div", { children: [
60
+ request.title ? /* @__PURE__ */ jsx("div", { style: { padding: "6px 10px", color: "#8f959e", fontSize: 12 }, children: request.title }) : null,
25
61
  /* @__PURE__ */ jsx(
26
- MenuButton,
62
+ MenuSubmenu,
27
63
  {
28
- label: "\u590D\u5236\u5355\u5143\u683C",
29
- onClick: request.onCopy,
30
- close: request.onClose
64
+ label: "\u590D\u5236",
65
+ items: [
66
+ {
67
+ key: "copy-cell",
68
+ label: request.target === "header" ? "\u590D\u5236\u8868\u5934" : "\u590D\u5236\u5355\u5143\u683C",
69
+ onClick: request.onCopy
70
+ },
71
+ {
72
+ key: "copy-selection",
73
+ label: "\u590D\u5236\u9009\u533A",
74
+ onClick: request.target === "header" ? void 0 : request.onCopySelection
75
+ }
76
+ ],
77
+ children: request.target !== "header" ? /* @__PURE__ */ jsx(CustomSelectionMenu, { request }) : null
31
78
  }
32
79
  ),
33
- request.onCopySelection ? /* @__PURE__ */ jsx(
34
- MenuButton,
35
- {
36
- label: "\u590D\u5236\u9009\u533A",
37
- onClick: request.onCopySelection,
38
- close: request.onClose
39
- }
40
- ) : null,
41
- /* @__PURE__ */ jsx(
42
- CustomSelectionMenu,
80
+ request.target === "header" ? /* @__PURE__ */ jsx(HeaderMenu, { request }) : null,
81
+ request.target === "summary" ? /* @__PURE__ */ jsx(
82
+ SummaryMenu,
43
83
  {
44
84
  request
45
85
  },
46
- `${request.x}-${request.y}`
47
- ),
48
- /* @__PURE__ */ jsx(
49
- MenuButton,
50
- {
51
- label: "\u5BFC\u51FA CSV",
52
- onClick: request.onExportCsv,
53
- close: request.onClose
54
- }
55
- ),
86
+ `${request.indicatorKey}-${request.x}-${request.y}`
87
+ ) : null,
88
+ /* @__PURE__ */ jsx(DataFormatMenu, { request }),
89
+ /* @__PURE__ */ jsx(DensityMenu, { request }),
56
90
  /* @__PURE__ */ jsx(
57
- MenuButton,
91
+ MenuSubmenu,
58
92
  {
59
- label: "\u5BFC\u51FA XLSX",
60
- onClick: request.onExportXlsx,
61
- close: request.onClose
93
+ label: "\u5BFC\u51FA",
94
+ items: [
95
+ {
96
+ key: "export-csv",
97
+ label: "\u5BFC\u51FA CSV",
98
+ onClick: request.onExportCsv
99
+ },
100
+ {
101
+ key: "export-xlsx",
102
+ label: "\u5BFC\u51FA XLSX",
103
+ onClick: request.onExportXlsx
104
+ }
105
+ ]
62
106
  }
63
107
  ),
64
- /* @__PURE__ */ jsxs("div", { style: { padding: "8px 10px", color: "#8f959e", fontSize: 12 }, children: [
108
+ request.recordCount != null ? /* @__PURE__ */ jsxs("div", { style: { padding: "8px 10px", color: "#8f959e", fontSize: 12 }, children: [
65
109
  "\u4E0B\u94BB\u8BB0\u5F55 ",
66
110
  request.recordCount,
67
111
  " \u6761"
68
- ] })
112
+ ] }) : null
69
113
  ] })
70
114
  }
71
115
  );
72
116
  }
117
+ function HeaderMenu(props) {
118
+ const { request } = props;
119
+ const hasSort = request.onSortAsc || request.onSortDesc || request.onClearSort;
120
+ const hasExpand = request.onExpand || request.onCollapse || request.onExpandAll || request.onCollapseAll;
121
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
122
+ hasSort ? /* @__PURE__ */ jsx(
123
+ MenuSubmenu,
124
+ {
125
+ label: "\u6392\u5E8F",
126
+ items: [
127
+ {
128
+ key: "sort-asc",
129
+ label: "\u5347\u5E8F",
130
+ selected: request.sortDirection === "asc",
131
+ onClick: request.onSortAsc
132
+ },
133
+ {
134
+ key: "sort-desc",
135
+ label: "\u964D\u5E8F",
136
+ selected: request.sortDirection === "desc",
137
+ onClick: request.onSortDesc
138
+ },
139
+ {
140
+ key: "sort-clear",
141
+ label: "\u53D6\u6D88\u6392\u5E8F",
142
+ onClick: request.onClearSort
143
+ }
144
+ ]
145
+ }
146
+ ) : null,
147
+ request.onOpenFilter ? /* @__PURE__ */ jsx(MenuAction, { label: "\u6253\u5F00\u7B5B\u9009\u9762\u677F", onClick: request.onOpenFilter }) : null,
148
+ hasExpand ? /* @__PURE__ */ jsx(
149
+ MenuSubmenu,
150
+ {
151
+ label: "\u5C55\u5F00/\u6298\u53E0",
152
+ items: [
153
+ {
154
+ key: "toggle-current",
155
+ label: request.expanded ? "\u6298\u53E0\u5F53\u524D\u9879" : "\u5C55\u5F00\u5F53\u524D\u9879",
156
+ onClick: request.expanded ? request.onCollapse : request.onExpand
157
+ },
158
+ {
159
+ key: "expand-all",
160
+ label: "\u5168\u90E8\u5C55\u5F00",
161
+ onClick: request.onExpandAll
162
+ },
163
+ {
164
+ key: "collapse-all",
165
+ label: "\u5168\u90E8\u6298\u53E0",
166
+ onClick: request.onCollapseAll
167
+ }
168
+ ]
169
+ }
170
+ ) : null
171
+ ] });
172
+ }
173
+ function SummaryMenu(props) {
174
+ const { request } = props;
175
+ const [precision, setPrecision] = useState(
176
+ request.summaryPrecision ?? null
177
+ );
178
+ const aggregators = [
179
+ ["sum", "\u6C42\u548C"],
180
+ ["avg", "\u5E73\u5747\u503C"],
181
+ ["count", "\u8BA1\u6570"],
182
+ ["distinctCount", "\u53BB\u91CD\u8BA1\u6570"],
183
+ ["min", "\u6700\u5C0F\u503C"],
184
+ ["max", "\u6700\u5927\u503C"]
185
+ ];
186
+ if (!request.onChangeSummaryAggregator) return null;
187
+ return /* @__PURE__ */ jsx(
188
+ MenuSubmenu,
189
+ {
190
+ label: "\u6C47\u603B\u65B9\u5F0F",
191
+ items: aggregators.map(([value, label]) => ({
192
+ key: `summary-${value}`,
193
+ label,
194
+ selected: request.summaryAggregator === value,
195
+ onClick: () => {
196
+ request.onChangeSummaryAggregator?.(value);
197
+ request.onClose();
198
+ }
199
+ })),
200
+ footer: request.onChangeSummaryPrecision ? /* @__PURE__ */ jsxs(
201
+ "div",
202
+ {
203
+ style: {
204
+ display: "grid",
205
+ gridTemplateColumns: "72px 1fr",
206
+ gap: 8,
207
+ alignItems: "center",
208
+ marginTop: 4,
209
+ padding: "10px 10px 4px",
210
+ borderTop: "1px solid #e5e7eb"
211
+ },
212
+ children: [
213
+ /* @__PURE__ */ jsx("span", { style: { color: "#374151", fontSize: 13 }, children: "\u5C0F\u6570\u4F4D\u6570" }),
214
+ /* @__PURE__ */ jsx(
215
+ ReactInputNumber,
216
+ {
217
+ value: precision,
218
+ min: 0,
219
+ max: 20,
220
+ step: 1,
221
+ precision: 0,
222
+ placeholder: "\u81EA\u52A8",
223
+ onChange: (value) => {
224
+ setPrecision(value);
225
+ request.onChangeSummaryPrecision?.(value ?? void 0);
226
+ }
227
+ }
228
+ )
229
+ ]
230
+ }
231
+ ) : null
232
+ }
233
+ );
234
+ }
235
+ function MenuDivider() {
236
+ return /* @__PURE__ */ jsx("div", { style: { height: 1, margin: "6px 0", background: "#f0f0f0" } });
237
+ }
73
238
  function CustomSelectionMenu(props) {
74
239
  const { request } = props;
75
- const triggerRef = useRef(null);
76
- const [open, setOpen] = useState(false);
77
240
  const [range, setRange] = useState(
78
241
  request.customSelection ?? {
79
242
  startRow: 1,
@@ -91,104 +254,79 @@ function CustomSelectionMenu(props) {
91
254
  [key]: value == null ? 1 : Math.floor(value)
92
255
  }));
93
256
  };
94
- return /* @__PURE__ */ jsxs("div", { children: [
257
+ return /* @__PURE__ */ jsx(MenuSubmenu, { label: "\u590D\u5236\u81EA\u5B9A\u4E49\u9009\u533A", items: [], minWidth: 250, children: /* @__PURE__ */ jsxs("div", { style: { padding: 6 }, children: [
258
+ /* @__PURE__ */ jsx("div", { style: { color: "#6b7280", fontSize: 12, marginBottom: 6 }, children: "\u8F93\u5165\u8D77\u59CB\u4F4D\u7F6E\u548C\u7ED3\u675F\u4F4D\u7F6E" }),
259
+ /* @__PURE__ */ jsxs(
260
+ "div",
261
+ {
262
+ style: {
263
+ display: "grid",
264
+ gridTemplateColumns: "38px 1fr 1fr",
265
+ gap: 6,
266
+ marginBottom: 4,
267
+ color: "#6b7280",
268
+ fontSize: 12,
269
+ textAlign: "center"
270
+ },
271
+ children: [
272
+ /* @__PURE__ */ jsx("span", {}),
273
+ /* @__PURE__ */ jsx("span", { children: "\u884C" }),
274
+ /* @__PURE__ */ jsx("span", { children: "\u5217" })
275
+ ]
276
+ }
277
+ ),
95
278
  /* @__PURE__ */ jsx(
96
- "button",
279
+ CustomSelectionRow,
97
280
  {
98
- ref: triggerRef,
99
- type: "button",
100
- onClick: () => setOpen((value) => !value),
101
- style: menuButtonStyle,
102
- children: "\u590D\u5236\u81EA\u5B9A\u4E49\u9009\u533A"
281
+ label: "\u8D77\u59CB",
282
+ row: range.startRow,
283
+ column: range.startColumn,
284
+ maxRow: range.rowCount,
285
+ maxColumn: range.columnCount,
286
+ onRowChange: (value) => update("startRow", value),
287
+ onColumnChange: (value) => update("startColumn", value)
103
288
  }
104
289
  ),
105
290
  /* @__PURE__ */ jsx(
106
- ReactPopup,
291
+ CustomSelectionRow,
107
292
  {
108
- open,
109
- anchorRef: triggerRef,
110
- placement: "rightTop",
111
- offset: 2,
112
- minWidth: 250,
113
- onOpenChange: setOpen,
114
- surfaceStyle: { padding: 6, width: 250 },
115
- children: /* @__PURE__ */ jsxs("div", { style: { padding: 6 }, children: [
116
- /* @__PURE__ */ jsx("div", { style: { color: "#6b7280", fontSize: 12, marginBottom: 6 }, children: "\u8F93\u5165\u8D77\u59CB\u4F4D\u7F6E\u548C\u7ED3\u675F\u4F4D\u7F6E" }),
117
- /* @__PURE__ */ jsxs(
118
- "div",
119
- {
120
- style: {
121
- display: "grid",
122
- gridTemplateColumns: "38px 1fr 1fr",
123
- gap: 6,
124
- marginBottom: 4,
125
- color: "#6b7280",
126
- fontSize: 12,
127
- textAlign: "center"
128
- },
129
- children: [
130
- /* @__PURE__ */ jsx("span", {}),
131
- /* @__PURE__ */ jsx("span", { children: "\u884C" }),
132
- /* @__PURE__ */ jsx("span", { children: "\u5217" })
133
- ]
134
- }
135
- ),
136
- /* @__PURE__ */ jsx(
137
- CustomSelectionRow,
138
- {
139
- label: "\u8D77\u59CB",
140
- row: range.startRow,
141
- column: range.startColumn,
142
- maxRow: range.rowCount,
143
- maxColumn: range.columnCount,
144
- onRowChange: (value) => update("startRow", value),
145
- onColumnChange: (value) => update("startColumn", value)
146
- }
147
- ),
148
- /* @__PURE__ */ jsx(
149
- CustomSelectionRow,
150
- {
151
- label: "\u7ED3\u675F",
152
- row: range.endRow,
153
- column: range.endColumn,
154
- maxRow: range.rowCount,
155
- maxColumn: range.columnCount,
156
- onRowChange: (value) => update("endRow", value),
157
- onColumnChange: (value) => update("endColumn", value)
158
- }
159
- ),
160
- /* @__PURE__ */ jsx(
161
- "button",
162
- {
163
- type: "button",
164
- style: {
165
- display: "block",
166
- width: "100%",
167
- height: 32,
168
- marginTop: 6,
169
- border: "none",
170
- borderRadius: 4,
171
- background: "#1677ff",
172
- color: "#fff",
173
- cursor: "pointer"
174
- },
175
- onClick: () => {
176
- request.onCopyCustomSelection?.({
177
- startRow: range.startRow,
178
- startColumn: range.startColumn,
179
- endRow: range.endRow,
180
- endColumn: range.endColumn
181
- });
182
- setOpen(false);
183
- request.onClose();
184
- },
185
- children: "\u590D\u5236"
186
- }
187
- )
188
- ] })
293
+ label: "\u7ED3\u675F",
294
+ row: range.endRow,
295
+ column: range.endColumn,
296
+ maxRow: range.rowCount,
297
+ maxColumn: range.columnCount,
298
+ onRowChange: (value) => update("endRow", value),
299
+ onColumnChange: (value) => update("endColumn", value)
300
+ }
301
+ ),
302
+ /* @__PURE__ */ jsx(
303
+ "button",
304
+ {
305
+ type: "button",
306
+ style: {
307
+ display: "block",
308
+ width: "100%",
309
+ height: 32,
310
+ marginTop: 6,
311
+ border: "none",
312
+ borderRadius: 4,
313
+ background: "#1677ff",
314
+ color: "#fff",
315
+ cursor: "pointer"
316
+ },
317
+ onClick: () => {
318
+ request.onCopyCustomSelection?.({
319
+ startRow: range.startRow,
320
+ startColumn: range.startColumn,
321
+ endRow: range.endRow,
322
+ endColumn: range.endColumn
323
+ });
324
+ request.onClose();
325
+ },
326
+ children: "\u590D\u5236"
189
327
  }
190
328
  )
191
- ] });
329
+ ] }) });
192
330
  }
193
331
  function CustomSelectionRow(props) {
194
332
  return /* @__PURE__ */ jsxs(
@@ -231,27 +369,324 @@ function CustomSelectionRow(props) {
231
369
  }
232
370
  );
233
371
  }
234
- var menuButtonStyle = {
235
- height: 34,
236
- padding: "0 10px",
237
- border: 0,
238
- borderRadius: 4,
239
- background: "transparent",
240
- color: "#1f2329",
241
- textAlign: "left",
242
- cursor: "pointer"
243
- };
244
- function MenuButton(props) {
245
- return /* @__PURE__ */ jsx(
372
+ function MenuAction(props) {
373
+ const { label, preview, onClick, selected, disabled, reserveSelectionIcon } = props;
374
+ if (!onClick && !disabled) return null;
375
+ return /* @__PURE__ */ jsxs(
246
376
  "button",
247
377
  {
248
378
  type: "button",
249
- onClick: () => {
250
- props.onClick();
251
- props.close();
379
+ onClick: disabled ? void 0 : onClick,
380
+ disabled,
381
+ className: "react-pivot-table-menu-action",
382
+ style: {
383
+ display: "flex",
384
+ alignItems: "center",
385
+ width: "100%",
386
+ height: 32,
387
+ padding: "0 10px",
388
+ border: "none",
389
+ borderRadius: 8,
390
+ background: "transparent",
391
+ color: disabled ? "#9ca3af" : "#374151",
392
+ textAlign: "left",
393
+ cursor: disabled ? "default" : "pointer"
252
394
  },
253
- style: menuButtonStyle,
254
- children: props.label
395
+ children: [
396
+ reserveSelectionIcon ? /* @__PURE__ */ jsx(
397
+ "span",
398
+ {
399
+ "aria-hidden": "true",
400
+ style: {
401
+ width: 16,
402
+ flex: "0 0 16px",
403
+ marginRight: 6,
404
+ textAlign: "center"
405
+ },
406
+ children: selected ? "\u2713" : ""
407
+ }
408
+ ) : null,
409
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, whiteSpace: "nowrap" }, children: label }),
410
+ preview ? /* @__PURE__ */ jsx(
411
+ "span",
412
+ {
413
+ style: { marginLeft: 16, color: "#9ca3af", whiteSpace: "nowrap" },
414
+ children: preview
415
+ }
416
+ ) : null
417
+ ]
418
+ }
419
+ );
420
+ }
421
+ function MenuSubmenu(props) {
422
+ const { label, items, footer, children, minWidth = 220 } = props;
423
+ const menuItems = items.filter((item) => item.onClick || item.disabled);
424
+ const reserveSelectionIcon = menuItems.some(
425
+ (item) => typeof item.selected === "boolean"
426
+ );
427
+ const triggerRef = useRef(null);
428
+ const [open, setOpen] = useState(false);
429
+ const closeTimerRef = useRef(null);
430
+ if (!menuItems.length && !footer && !children) return null;
431
+ const clearCloseTimer = () => {
432
+ if (closeTimerRef.current != null) {
433
+ window.clearTimeout(closeTimerRef.current);
434
+ closeTimerRef.current = null;
435
+ }
436
+ };
437
+ const scheduleClose = () => {
438
+ clearCloseTimer();
439
+ closeTimerRef.current = window.setTimeout(() => {
440
+ setOpen(false);
441
+ closeTimerRef.current = null;
442
+ }, 120);
443
+ };
444
+ return /* @__PURE__ */ jsxs(
445
+ "div",
446
+ {
447
+ onMouseEnter: () => {
448
+ clearCloseTimer();
449
+ setOpen(true);
450
+ },
451
+ onMouseLeave: scheduleClose,
452
+ children: [
453
+ /* @__PURE__ */ jsxs(
454
+ "button",
455
+ {
456
+ ref: triggerRef,
457
+ type: "button",
458
+ onClick: () => setOpen((value) => !value),
459
+ className: "react-pivot-table-menu-action react-pivot-table-menu-action-submenu",
460
+ style: {
461
+ display: "flex",
462
+ alignItems: "center",
463
+ justifyContent: "space-between",
464
+ width: "100%",
465
+ height: 32,
466
+ padding: "0 10px",
467
+ border: "none",
468
+ borderRadius: 8,
469
+ background: "transparent",
470
+ color: "#374151",
471
+ textAlign: "left",
472
+ cursor: "pointer"
473
+ },
474
+ children: [
475
+ /* @__PURE__ */ jsx("span", { children: label }),
476
+ /* @__PURE__ */ jsx(
477
+ "span",
478
+ {
479
+ className: `react-pivot-table-menu-chevron${open ? " react-pivot-table-menu-chevron-active" : ""}`,
480
+ "aria-hidden": "true"
481
+ }
482
+ )
483
+ ]
484
+ }
485
+ ),
486
+ /* @__PURE__ */ jsx(
487
+ ReactPopup,
488
+ {
489
+ open,
490
+ anchorRef: triggerRef,
491
+ placement: "rightTop",
492
+ offset: 2,
493
+ minWidth,
494
+ onOpenChange: setOpen,
495
+ surfaceStyle: { ...CONTEXT_MENU_SURFACE_STYLE, width: minWidth },
496
+ children: /* @__PURE__ */ jsxs("div", { onMouseEnter: clearCloseTimer, onMouseLeave: scheduleClose, children: [
497
+ menuItems.map((item) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
498
+ item.separatorBefore ? /* @__PURE__ */ jsx(MenuDivider, {}) : null,
499
+ /* @__PURE__ */ jsx(
500
+ MenuAction,
501
+ {
502
+ label: item.label,
503
+ preview: item.preview,
504
+ selected: item.selected,
505
+ disabled: item.disabled,
506
+ onClick: item.onClick,
507
+ reserveSelectionIcon
508
+ }
509
+ )
510
+ ] }, item.key)),
511
+ children,
512
+ footer
513
+ ] })
514
+ }
515
+ )
516
+ ]
517
+ }
518
+ );
519
+ }
520
+ function DensityMenu(props) {
521
+ const { request } = props;
522
+ if (!request.onChangeDensity) return null;
523
+ return /* @__PURE__ */ jsx(
524
+ MenuSubmenu,
525
+ {
526
+ label: "\u8868\u683C\u5BC6\u5EA6",
527
+ items: [
528
+ {
529
+ key: "density-comfortable",
530
+ label: "\u5BBD\u677E",
531
+ selected: request.density === "comfortable",
532
+ onClick: () => request.onChangeDensity?.("comfortable")
533
+ },
534
+ {
535
+ key: "density-middle",
536
+ label: "\u4E2D\u7B49",
537
+ selected: request.density === "middle",
538
+ onClick: () => request.onChangeDensity?.("middle")
539
+ },
540
+ {
541
+ key: "density-compact",
542
+ label: "\u7D27\u51D1",
543
+ selected: request.density === "compact",
544
+ onClick: () => request.onChangeDensity?.("compact")
545
+ },
546
+ {
547
+ key: "density-ultra-compact",
548
+ label: "\u8D85\u7D27\u51D1",
549
+ selected: request.density === "ultra-compact",
550
+ onClick: () => request.onChangeDensity?.("ultra-compact")
551
+ },
552
+ {
553
+ key: "density-auto",
554
+ label: "\u81EA\u9002\u5E94",
555
+ selected: request.density === "auto",
556
+ onClick: () => request.onChangeDensity?.("auto")
557
+ }
558
+ ]
559
+ }
560
+ );
561
+ }
562
+ function DataFormatMenu(props) {
563
+ const { request } = props;
564
+ if (!request.onChangeDataFormat) return null;
565
+ const formats = [
566
+ { key: "default", label: "\u5E38\u89C4", value: "default" },
567
+ { key: "text", label: "\u7EAF\u6587\u672C", value: "text" },
568
+ {
569
+ key: "number",
570
+ label: "\u6570\u5B57",
571
+ preview: "1024",
572
+ value: "number",
573
+ separatorBefore: true
574
+ },
575
+ {
576
+ key: "thousands",
577
+ label: "\u6570\u5B57\uFF08\u5343\u5206\u4F4D\uFF09",
578
+ preview: "1,024",
579
+ value: "thousands"
580
+ },
581
+ {
582
+ key: "decimal",
583
+ label: "\u6570\u5B57\uFF08\u5343\u5206\u4F4D\uFF0C\u5C0F\u6570\u70B9\uFF09",
584
+ preview: "1,024.56",
585
+ value: "decimal"
586
+ },
587
+ {
588
+ key: "percent",
589
+ label: "\u767E\u5206\u6BD4",
590
+ preview: "10%",
591
+ value: "percent",
592
+ separatorBefore: true
593
+ },
594
+ {
595
+ key: "percentDecimal",
596
+ label: "\u767E\u5206\u6BD4\uFF08\u5C0F\u6570\u70B9\uFF09",
597
+ preview: "10.24%",
598
+ value: "percentDecimal"
599
+ },
600
+ {
601
+ key: "scientific",
602
+ label: "\u79D1\u5B66\u8BB0\u6570",
603
+ preview: "1.02E+003",
604
+ value: "scientific"
605
+ },
606
+ {
607
+ key: "numberToChineseUpper",
608
+ label: "\u6570\u5B57\u8F6C\u5927\u5199",
609
+ preview: "\u7396\u62FE\u58F9\u4E07\u634C\u4EDF",
610
+ value: "numberToChineseUpper"
611
+ },
612
+ {
613
+ key: "chineseUpperToNumber",
614
+ label: "\u5927\u5199\u8F6C\u6570\u5B57",
615
+ preview: "918000",
616
+ value: "chineseUpperToNumber"
617
+ },
618
+ {
619
+ key: "cny",
620
+ label: "\u4EBA\u6C11\u5E01",
621
+ preview: "\xA51,024",
622
+ value: "cny",
623
+ separatorBefore: true
624
+ },
625
+ {
626
+ key: "cnyDecimal",
627
+ label: "\u4EBA\u6C11\u5E01\uFF08\u5C0F\u6570\u70B9\uFF09",
628
+ preview: "\xA51,024.56",
629
+ value: "cnyDecimal"
630
+ },
631
+ { key: "usd", label: "\u7F8E\u5143", preview: "$1,024", value: "usd" },
632
+ {
633
+ key: "usdDecimal",
634
+ label: "\u7F8E\u5143\uFF08\u5C0F\u6570\u70B9\uFF09",
635
+ preview: "$1,024.56",
636
+ value: "usdDecimal"
637
+ },
638
+ {
639
+ key: "dateSlash",
640
+ label: "\u65E5\u671F\uFF08\u659C\u6760\uFF09",
641
+ preview: "2017/08/01",
642
+ value: "dateSlash",
643
+ separatorBefore: true
644
+ },
645
+ {
646
+ key: "dateDash",
647
+ label: "\u65E5\u671F\uFF08\u77ED\u6A2A\u7EBF\uFF09",
648
+ preview: "2017-08-01",
649
+ value: "dateDash"
650
+ },
651
+ {
652
+ key: "dateFullCN",
653
+ label: "\u65E5\u671F\uFF08\u4E2D\u6587\uFF09",
654
+ preview: "2017\u5E748\u67081\u65E5",
655
+ value: "dateFullCN"
656
+ },
657
+ { key: "time", label: "\u65F6\u95F4", preview: "23:24:25", value: "time" },
658
+ {
659
+ key: "timeShort",
660
+ label: "\u65F6\u95F4\uFF08\u5206\uFF09",
661
+ preview: "23:24",
662
+ value: "timeShort"
663
+ },
664
+ {
665
+ key: "datetime",
666
+ label: "\u65E5\u671F\u65F6\u95F4",
667
+ preview: "2017/08/01 23:24:25",
668
+ value: "datetime"
669
+ },
670
+ {
671
+ key: "datetime12",
672
+ label: "\u65E5\u671F\u65F6\u95F4\uFF08\u4E0A\u4E0B\uFF09",
673
+ preview: "2017/08/01 \u4E0B\u534811:24",
674
+ value: "datetime12"
675
+ }
676
+ ];
677
+ return /* @__PURE__ */ jsx(
678
+ MenuSubmenu,
679
+ {
680
+ label: "\u683C\u5F0F",
681
+ minWidth: 340,
682
+ items: formats.map((item) => ({
683
+ key: `data-format-${item.key}`,
684
+ label: item.label,
685
+ preview: "preview" in item ? item.preview : void 0,
686
+ separatorBefore: "separatorBefore" in item ? item.separatorBefore : void 0,
687
+ selected: request.dataFormat === item.value,
688
+ onClick: () => request.onChangeDataFormat?.(item.value)
689
+ }))
255
690
  }
256
691
  );
257
692
  }
@@ -286,28 +721,6 @@ function PivotTooltip(props) {
286
721
  );
287
722
  }
288
723
 
289
- // #style-inject:#style-inject
290
- function styleInject(css, { insertAt } = {}) {
291
- if (!css || typeof document === "undefined") return;
292
- const head = document.head || document.getElementsByTagName("head")[0];
293
- const style = document.createElement("style");
294
- style.type = "text/css";
295
- if (insertAt === "top") {
296
- if (head.firstChild) {
297
- head.insertBefore(style, head.firstChild);
298
- } else {
299
- head.appendChild(style);
300
- }
301
- } else {
302
- head.appendChild(style);
303
- }
304
- if (style.styleSheet) {
305
- style.styleSheet.cssText = css;
306
- } else {
307
- style.appendChild(document.createTextNode(css));
308
- }
309
- }
310
-
311
724
  // src/features/field-panel/pivot-field-panel.css
312
725
  styleInject('.react-pivot-table-customization {\n position: relative;\n overflow: hidden;\n}\n.react-pivot-table-customization__table {\n position: relative;\n min-width: 0;\n min-height: 0;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n.react-pivot-table-customization__panel-column {\n flex-grow: 0 !important;\n overflow: hidden;\n}\n.react-pivot-table-customization__chart-column {\n flex-grow: 0 !important;\n overflow: hidden;\n}\n.react-pivot-field-panel {\n display: flex;\n width: 100%;\n min-width: 280px;\n height: 100%;\n flex-direction: column;\n overflow: hidden;\n background: #fff;\n color: #1f2329;\n font-family:\n "Segoe UI",\n "Microsoft YaHei",\n sans-serif;\n font-size: 13px;\n box-sizing: border-box;\n}\n.react-pivot-field-panel button,\n.react-pivot-field-panel input,\n.react-pivot-field-panel select {\n font: inherit;\n}\n.react-pivot-field-panel__header {\n display: flex;\n height: 42px;\n flex: 0 0 42px;\n align-items: center;\n justify-content: space-between;\n padding: 0 10px 0 14px;\n border-bottom: 1px solid #edf0f2;\n background: #fff;\n}\n.react-pivot-field-panel__icon-button {\n display: inline-flex;\n width: 24px;\n height: 24px;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n background: transparent;\n color: #646a73;\n cursor: pointer;\n font: 18px/24px "Segoe UI", sans-serif;\n}\n.react-pivot-field-panel__icon-button:hover {\n background: #f2f3f5;\n color: #d14343;\n}\n.react-pivot-field-panel__layout {\n width: 100%;\n height: auto;\n min-height: 0;\n flex: 1 1 0;\n overflow: hidden;\n}\n.react-pivot-field-panel__layout > .canvas-react-resizable-panels-item > .canvas-react-resizable-panel {\n overflow: hidden;\n}\n.react-pivot-field-panel__section {\n height: 100%;\n padding: 10px 12px 0;\n box-sizing: border-box;\n}\n.react-pivot-field-panel__section--fields,\n.react-pivot-field-panel__section--areas {\n display: flex;\n min-height: 0;\n flex-direction: column;\n}\n.react-pivot-field-panel__section--fields {\n padding-bottom: 6px;\n}\n.react-pivot-field-panel__section--areas {\n padding-bottom: 12px;\n}\n.react-pivot-field-panel__section-title {\n margin-bottom: 5px;\n font-weight: 600;\n}\n.react-pivot-field-panel__hint {\n margin-bottom: 8px;\n color: #646a73;\n font-size: 12px;\n}\n.react-pivot-field-panel__search {\n display: flex;\n height: 32px;\n align-items: center;\n gap: 8px;\n padding: 0 9px;\n border: 1px solid #d5d9de;\n border-radius: 3px;\n background: #fff;\n box-sizing: border-box;\n}\n.react-pivot-field-panel__search > span {\n width: 12px;\n height: 12px;\n border: 1.5px solid #4f5660;\n border-radius: 50%;\n position: relative;\n}\n.react-pivot-field-panel__search > span::after {\n position: absolute;\n right: -4px;\n bottom: -3px;\n width: 5px;\n height: 1.5px;\n background: #4f5660;\n content: "";\n transform: rotate(45deg);\n}\n.react-pivot-field-panel__search input {\n min-width: 0;\n flex: 1;\n border: 0;\n outline: 0;\n color: #1f2329;\n}\n.react-pivot-field-panel__field-list {\n min-height: 0;\n flex: 1;\n margin-top: 6px;\n overflow: auto;\n border: 1px solid #dfe3e7;\n border-radius: 3px;\n background: #fff;\n}\n.react-pivot-field-panel__field,\n.react-pivot-field-panel__area-field {\n display: grid;\n height: 28px;\n align-items: center;\n column-gap: 6px;\n box-sizing: border-box;\n}\n.react-pivot-field-panel__field {\n grid-template-columns: 10px 16px minmax(0, 1fr);\n padding: 0 8px 0 6px;\n}\n.react-pivot-field-panel__field:hover {\n background: #f5f8fb;\n}\n.react-pivot-field-panel__field[data-dragging] {\n opacity: 0.45;\n}\n.react-pivot-field-panel__field > span:nth-of-type(2),\n.react-pivot-field-panel__area-field > span:nth-of-type(2) {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.react-pivot-field-panel__drag {\n width: 8px;\n height: 12px;\n cursor: grab;\n background-image:\n radial-gradient(\n circle at 2px 2px,\n #8f959e 1px,\n transparent 1.2px);\n background-position: 0 0;\n background-size: 4px 4px;\n background-repeat: repeat;\n}\n.react-pivot-field-panel__empty,\n.react-pivot-field-panel__area-empty {\n display: grid;\n height: 100%;\n place-items: center;\n color: #8f959e;\n font-size: 12px;\n}\n.react-pivot-field-panel__areas {\n display: grid;\n min-height: 0;\n flex: 1;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);\n gap: 8px;\n}\n.react-pivot-field-panel__area-wrap {\n display: flex;\n min-width: 0;\n min-height: 0;\n flex-direction: column;\n}\n.react-pivot-field-panel__area-title {\n display: grid;\n height: 25px;\n grid-template-columns: minmax(0, 1fr);\n align-items: center;\n gap: 5px;\n color: #3f454d;\n}\n.react-pivot-field-panel__area {\n min-height: 0;\n flex: 1;\n overflow: auto;\n border: 1px solid #dfe3e7;\n border-radius: 3px;\n background: #fff;\n}\n.react-pivot-field-panel__area[data-dragging] {\n border-color: #2f78d0;\n background: #f7faff;\n}\n.react-pivot-field-panel__area-field {\n grid-template-columns: 9px minmax(0, 1fr) 18px;\n padding: 0 7px 0 5px;\n}\n.react-pivot-field-panel__area-field:hover {\n background: #f5f8fb;\n}\n.react-pivot-field-panel__area-field-menu {\n display: grid;\n width: 18px;\n height: 22px;\n padding: 0;\n place-items: center;\n border: 0;\n background: transparent;\n color: #646a73;\n cursor: pointer;\n}\n.react-pivot-field-panel__area-field-menu:hover {\n color: #1f2329;\n background: #e9edf2;\n}\n.react-pivot-field-panel__area-field-menu > span {\n width: 0;\n height: 0;\n border-top: 5px solid currentColor;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.react-pivot-field-menu {\n min-width: 176px;\n padding: 5px 0;\n color: #1f2329;\n font:\n 13px "Segoe UI",\n "Microsoft YaHei",\n sans-serif;\n}\n.react-pivot-field-menu__item {\n display: block;\n width: 100%;\n height: 30px;\n padding: 0 12px;\n border: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n cursor: pointer;\n}\n.react-pivot-field-menu__item:hover:not(:disabled) {\n background: #e9f2fc;\n}\n.react-pivot-field-menu__item:disabled {\n color: #a7abb2;\n cursor: default;\n}\n.react-pivot-field-menu__item[data-danger] {\n color: #c9372c;\n}\n.react-pivot-field-menu__separator {\n height: 1px;\n margin: 4px 0;\n background: #e1e4e8;\n}\n.react-pivot-field-settings {\n display: grid;\n min-width: 260px;\n gap: 12px;\n padding: 14px;\n color: #1f2329;\n font:\n 13px "Segoe UI",\n "Microsoft YaHei",\n sans-serif;\n}\n.react-pivot-field-settings label {\n display: grid;\n gap: 5px;\n color: #515761;\n}\n.react-pivot-field-settings__name-input,\n.react-pivot-field-settings__aggregator {\n height: 30px;\n font: inherit;\n}\n.react-pivot-field-settings__actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.react-pivot-field-settings__actions button {\n height: 30px;\n padding: 0 14px;\n border: 1px solid #bfc4cb;\n border-radius: 3px;\n background: #fff;\n cursor: pointer;\n}\n.react-pivot-field-settings__actions button.is-primary {\n border-color: #2f78d0;\n background: #2f78d0;\n color: #fff;\n}\n');
313
726
  function PivotFieldPanel(props) {
@@ -573,7 +986,7 @@ function FieldContextMenu(props) {
573
986
  },
574
987
  children: /* @__PURE__ */ jsxs("div", { className: "react-pivot-field-menu", role: "menu", children: [
575
988
  /* @__PURE__ */ jsx(
576
- MenuAction,
989
+ MenuAction2,
577
990
  {
578
991
  label: "\u4E0A\u79FB",
579
992
  disabled: index <= 0,
@@ -582,7 +995,7 @@ function FieldContextMenu(props) {
582
995
  }
583
996
  ),
584
997
  /* @__PURE__ */ jsx(
585
- MenuAction,
998
+ MenuAction2,
586
999
  {
587
1000
  label: "\u4E0B\u79FB",
588
1001
  disabled: index < 0 || index >= keys.length - 1,
@@ -591,7 +1004,7 @@ function FieldContextMenu(props) {
591
1004
  }
592
1005
  ),
593
1006
  /* @__PURE__ */ jsx(
594
- MenuAction,
1007
+ MenuAction2,
595
1008
  {
596
1009
  label: "\u79FB\u81F3\u5F00\u5934",
597
1010
  disabled: index <= 0,
@@ -600,7 +1013,7 @@ function FieldContextMenu(props) {
600
1013
  }
601
1014
  ),
602
1015
  /* @__PURE__ */ jsx(
603
- MenuAction,
1016
+ MenuAction2,
604
1017
  {
605
1018
  label: "\u79FB\u81F3\u672B\u5C3E",
606
1019
  disabled: index === keys.length - 1,
@@ -610,7 +1023,7 @@ function FieldContextMenu(props) {
610
1023
  ),
611
1024
  /* @__PURE__ */ jsx(MenuSeparator, {}),
612
1025
  /* @__PURE__ */ jsx(
613
- MenuAction,
1026
+ MenuAction2,
614
1027
  {
615
1028
  label: "\u6DFB\u52A0\u5230\u62A5\u8868\u7B5B\u9009",
616
1029
  disabled: state.area === "filter",
@@ -619,7 +1032,7 @@ function FieldContextMenu(props) {
619
1032
  }
620
1033
  ),
621
1034
  /* @__PURE__ */ jsx(
622
- MenuAction,
1035
+ MenuAction2,
623
1036
  {
624
1037
  label: "\u6DFB\u52A0\u5230\u884C\u6807\u7B7E",
625
1038
  disabled: state.area === "row",
@@ -628,7 +1041,7 @@ function FieldContextMenu(props) {
628
1041
  }
629
1042
  ),
630
1043
  /* @__PURE__ */ jsx(
631
- MenuAction,
1044
+ MenuAction2,
632
1045
  {
633
1046
  label: "\u6DFB\u52A0\u5230\u5217\u6807\u7B7E",
634
1047
  disabled: state.area === "column",
@@ -637,7 +1050,7 @@ function FieldContextMenu(props) {
637
1050
  }
638
1051
  ),
639
1052
  /* @__PURE__ */ jsx(
640
- MenuAction,
1053
+ MenuAction2,
641
1054
  {
642
1055
  label: "\u79FB\u52A8\u5230\u503C",
643
1056
  disabled: state.area === "value",
@@ -647,7 +1060,7 @@ function FieldContextMenu(props) {
647
1060
  ),
648
1061
  /* @__PURE__ */ jsx(MenuSeparator, {}),
649
1062
  /* @__PURE__ */ jsx(
650
- MenuAction,
1063
+ MenuAction2,
651
1064
  {
652
1065
  label: "\u5220\u9664\u5B57\u6BB5",
653
1066
  danger: true,
@@ -656,7 +1069,7 @@ function FieldContextMenu(props) {
656
1069
  }
657
1070
  ),
658
1071
  /* @__PURE__ */ jsx(
659
- MenuAction,
1072
+ MenuAction2,
660
1073
  {
661
1074
  label: "\u5B57\u6BB5\u8BBE\u7F6E...",
662
1075
  onClick: () => props.onSettings(state)
@@ -739,7 +1152,7 @@ function FieldSettingsPopup(props) {
739
1152
  }
740
1153
  );
741
1154
  }
742
- function MenuAction(props) {
1155
+ function MenuAction2(props) {
743
1156
  return /* @__PURE__ */ jsx(
744
1157
  "button",
745
1158
  {
@@ -1266,10 +1679,12 @@ function ReactPivotTableInner(props, ref) {
1266
1679
  null
1267
1680
  );
1268
1681
  const chartIdRef = useRef(0);
1682
+ const layoutRevisionRef = useRef(0);
1269
1683
  const [internalLayout, setInternalLayout] = useState(
1270
1684
  () => customizationOptions?.defaultLayout ?? createEmptyPivotFieldLayout()
1271
1685
  );
1272
1686
  const [fieldOverrides, setFieldOverrides] = useState({});
1687
+ const [densityOverride, setDensityOverride] = useState(null);
1273
1688
  const [filterRequest, setFilterRequest] = useState(null);
1274
1689
  const [contextMenuRequest, setContextMenuRequest] = useState(null);
1275
1690
  const [tooltipRequest, setTooltipRequest] = useState(null);
@@ -1313,7 +1728,9 @@ function ReactPivotTableInner(props, ref) {
1313
1728
  indicator: {
1314
1729
  ...field.indicator,
1315
1730
  title: override.title ?? field.indicator?.title,
1316
- aggregator: override.aggregator ?? field.indicator?.aggregator
1731
+ aggregator: override.aggregator ?? field.indicator?.aggregator,
1732
+ precision: "precision" in override ? override.precision : field.indicator?.precision,
1733
+ dataFormat: "dataFormat" in override ? override.dataFormat : field.indicator?.dataFormat
1317
1734
  }
1318
1735
  };
1319
1736
  }),
@@ -1323,6 +1740,20 @@ function ReactPivotTableInner(props, ref) {
1323
1740
  () => customizationEnabled ? resolvePivotFieldLayoutOptions(fields, fieldLayout) : null,
1324
1741
  [customizationEnabled, fieldLayout, fields]
1325
1742
  );
1743
+ const overriddenIndicators = useMemo(
1744
+ () => tableOptions.indicators.map((indicator) => {
1745
+ const override = fieldOverrides[indicator.key];
1746
+ if (!override) return indicator;
1747
+ return {
1748
+ ...indicator,
1749
+ title: override.title ?? indicator.title,
1750
+ aggregator: override.aggregator ?? indicator.aggregator,
1751
+ precision: "precision" in override ? override.precision : indicator.precision,
1752
+ dataFormat: "dataFormat" in override ? override.dataFormat : indicator.dataFormat
1753
+ };
1754
+ }),
1755
+ [fieldOverrides, tableOptions.indicators]
1756
+ );
1326
1757
  const refreshChartModel = () => {
1327
1758
  const nextModel = tableRef.current?.getPivotModel() ?? null;
1328
1759
  setChartModel((current) => current === nextModel ? current : nextModel);
@@ -1330,9 +1761,31 @@ function ReactPivotTableInner(props, ref) {
1330
1761
  const options = {
1331
1762
  ...tableOptions,
1332
1763
  ...customizedOptions,
1764
+ rowHeight: densityOverride == null ? tableOptions.rowHeight : resolveDensityRowHeight(densityOverride),
1765
+ indicators: customizedOptions?.indicators ?? overriddenIndicators,
1333
1766
  ui: {
1334
1767
  onFilterPanelRequest: setFilterRequest,
1335
- onContextMenuRequest: setContextMenuRequest,
1768
+ onContextMenuRequest: (request) => {
1769
+ if (request) {
1770
+ const nativeChangeDensity = request.onChangeDensity;
1771
+ const fieldKey = request.indicatorKey;
1772
+ const enhancedRequest = {
1773
+ ...request,
1774
+ onChangeDensity: (density) => {
1775
+ nativeChangeDensity?.(density);
1776
+ setDensityOverride(density);
1777
+ },
1778
+ onChangeDataFormat: fieldKey == null ? void 0 : (dataFormat) => updateField(fieldKey, { dataFormat })
1779
+ };
1780
+ if (request.target === "summary" && fieldKey != null) {
1781
+ enhancedRequest.onChangeSummaryAggregator = (aggregator) => updateField(fieldKey, { aggregator });
1782
+ enhancedRequest.onChangeSummaryPrecision = (precision) => updateField(fieldKey, { precision });
1783
+ }
1784
+ setContextMenuRequest(enhancedRequest);
1785
+ return;
1786
+ }
1787
+ setContextMenuRequest(null);
1788
+ },
1336
1789
  onTooltipRequest: setTooltipRequest
1337
1790
  },
1338
1791
  onFilterChange: (event) => {
@@ -1371,7 +1824,13 @@ function ReactPivotTableInner(props, ref) {
1371
1824
  setCharts((current) => current.filter((chart) => chart.id !== chartId));
1372
1825
  };
1373
1826
  const applyLayout = (next) => {
1374
- if (customizationOptions?.layout === void 0) setInternalLayout(next);
1827
+ if (customizationOptions?.layout === void 0) {
1828
+ layoutRevisionRef.current += 1;
1829
+ setInternalLayout(next);
1830
+ if (customizationOptions?.storageKey) {
1831
+ void savePivotFieldLayout(customizationOptions.storageKey, next);
1832
+ }
1833
+ }
1375
1834
  customizationOptions?.onLayoutChange?.(next);
1376
1835
  };
1377
1836
  const moveField = (fieldKey, area, index, sourceArea) => {
@@ -1380,7 +1839,7 @@ function ReactPivotTableInner(props, ref) {
1380
1839
  const removeField = (fieldKey) => {
1381
1840
  applyLayout(removePivotField(fieldLayout, fieldKey));
1382
1841
  };
1383
- const updateField = (fieldKey, patch) => {
1842
+ function updateField(fieldKey, patch) {
1384
1843
  setFieldOverrides((current) => ({
1385
1844
  ...current,
1386
1845
  [fieldKey]: { ...current[fieldKey], ...patch }
@@ -1395,11 +1854,13 @@ function ReactPivotTableInner(props, ref) {
1395
1854
  indicator: {
1396
1855
  ...field.indicator,
1397
1856
  title: patch.title ?? field.indicator?.title,
1398
- aggregator: patch.aggregator ?? field.indicator?.aggregator
1857
+ aggregator: patch.aggregator ?? field.indicator?.aggregator,
1858
+ precision: "precision" in patch ? patch.precision : field.indicator?.precision,
1859
+ dataFormat: "dataFormat" in patch ? patch.dataFormat : field.indicator?.dataFormat
1399
1860
  }
1400
1861
  });
1401
1862
  }
1402
- };
1863
+ }
1403
1864
  useEffect(() => {
1404
1865
  const container = containerRef.current;
1405
1866
  if (!container) return;
@@ -1419,6 +1880,22 @@ function ReactPivotTableInner(props, ref) {
1419
1880
  table.updateOptions(options);
1420
1881
  refreshChartModel();
1421
1882
  }, [options]);
1883
+ useEffect(() => {
1884
+ const storageKey = customizationOptions?.storageKey;
1885
+ if (!storageKey || customizationOptions?.layout !== void 0) return;
1886
+ let active = true;
1887
+ const revision = layoutRevisionRef.current;
1888
+ void loadPivotFieldLayout(storageKey).then((storedLayout) => {
1889
+ if (!active || !storedLayout || layoutRevisionRef.current !== revision) {
1890
+ return;
1891
+ }
1892
+ setInternalLayout(storedLayout);
1893
+ customizationOptions?.onLayoutChange?.(storedLayout);
1894
+ });
1895
+ return () => {
1896
+ active = false;
1897
+ };
1898
+ }, [customizationOptions?.layout, customizationOptions?.storageKey]);
1422
1899
  useEffect(() => {
1423
1900
  if (customizationOptions?.panelWidth != null) {
1424
1901
  setFieldPanelWidth(customizationOptions.panelWidth);
@@ -1565,6 +2042,13 @@ function ReactPivotTableInner(props, ref) {
1565
2042
  /* @__PURE__ */ jsx(PivotTooltip, { request: tooltipRequest })
1566
2043
  ] });
1567
2044
  }
2045
+ function resolveDensityRowHeight(density) {
2046
+ if (density === "comfortable") return 56;
2047
+ if (density === "middle") return 48;
2048
+ if (density === "compact") return 32;
2049
+ if (density === "ultra-compact") return 26;
2050
+ return void 0;
2051
+ }
1568
2052
  var ReactPivotTable = forwardRef(ReactPivotTableInner);
1569
2053
  function toSharedFilterRequest(request, onValueChange) {
1570
2054
  if (!request) return null;