@canvas-components/react-list-table 0.3.18 → 0.3.20

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,11 +1,11 @@
1
1
  import { createContext, forwardRef, useMemo, useRef, useState, useImperativeHandle, useContext, useEffect, useLayoutEffect, Fragment as Fragment$1 } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
- import { ReactImagePreview, ReactNumberInput, ReactSelect, ReactPagination, ReactFilterPanel, ReactPopup, ReactModal, ReactInput, ReactPopconfirm, ReactContentScaleProvider, ReactInputNumber, ReactCheckbox, ReactAutoComplete, ReactColorPicker, ReactTimeRangePicker, ReactDateTimeRangePicker, ReactDateTimePicker, ReactTimePicker, ReactDateRangePicker, ReactDatePicker, ReactMonthPicker, ReactQuarterPicker, ReactYearPicker, ReactInputRange } from '@canvas-components/react-components';
3
+ import { ReactImagePreview, ReactCheckbox, ReactNumberInput, ReactSelect, ReactPagination, ReactFilterPanel, ReactPopup, ReactModal, ReactInput, ReactPopconfirm, ReactContentScaleProvider, ReactButton, ReactColorPicker, ReactInputNumber, ReactAutoComplete, ReactTimeRangePicker, ReactDateTimeRangePicker, ReactDateTimePicker, ReactTimePicker, ReactDateRangePicker, ReactDatePicker, ReactMonthPicker, ReactQuarterPicker, ReactYearPicker, ReactInputRange, ReactCascader } from '@canvas-components/react-components';
4
4
  import { EXPAND_COLUMN, SELECTION_COLUMN, ROW_SELECTION_COLUMN, resetListTableStoredColumnConfig, ListTable } from '@canvas-components/list-table';
5
5
  export { EXPAND_COLUMN, ROW_SELECTION_COLUMN, SELECTION_COLUMN, getListTableStoredPageSize } from '@canvas-components/list-table';
6
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
7
  import { formatDateTimeValue, isDateTimeFormatPattern } from '@canvas-components/utils/date';
7
8
  import { formatNumberValue, formatPercentValue, formatScientificValue, formatCustomNumberPattern } from '@canvas-components/utils/number';
8
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
9
9
 
10
10
  // #style-inject:#style-inject
11
11
  function styleInject(css, { insertAt } = {}) {
@@ -31,6 +31,328 @@ function styleInject(css, { insertAt } = {}) {
31
31
 
32
32
  // src/styles/table-overrides.css
33
33
  styleInject(".react-list-table-filter-option {\n transition: background-color 0.2s ease;\n}\n.react-list-table-filter-option:hover {\n background: #f3f4f6;\n}\n.react-list-table-menu-action,\n.react-list-table-menu-row {\n transition: background-color 0.2s ease, color 0.2s ease;\n}\n.react-list-table-menu-action:hover,\n.react-list-table-menu-row:hover {\n background: #f3f4f6 !important;\n}\n.react-list-table-custom-selection-copy {\n transition: background-color 0.2s ease;\n}\n.react-list-table-custom-selection-copy:hover {\n background: #4096ff !important;\n}\n.react-list-table-custom-selection-input > .canvas-react-input-number-handler-down {\n display: none;\n}\n.react-list-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-list-table-menu-action:hover .react-list-table-menu-chevron,\n.react-list-table-menu-row:hover .react-list-table-menu-chevron {\n border-color: #6b7280;\n transform: rotate(135deg);\n}\n.react-list-table-menu-chevron-active {\n border-color: #6b7280;\n transform: rotate(135deg);\n}\n");
34
+ var conditionalOperators = {
35
+ eq: "\u7B49\u4E8E",
36
+ neq: "\u4E0D\u7B49\u4E8E",
37
+ gt: "\u5927\u4E8E",
38
+ gte: "\u5927\u4E8E\u7B49\u4E8E",
39
+ lt: "\u5C0F\u4E8E",
40
+ lte: "\u5C0F\u4E8E\u7B49\u4E8E",
41
+ between: "\u4ECB\u4E8E\uFF08\u542B\u8FB9\u754C\uFF09",
42
+ contains: "\u5305\u542B\u6587\u672C",
43
+ empty: "\u4E3A\u7A7A",
44
+ notEmpty: "\u4E0D\u4E3A\u7A7A"
45
+ };
46
+ function describeConditionalRule(rule) {
47
+ return resolveConditions(rule).map(
48
+ (condition, index) => `${index === 0 ? "" : (condition.relation ?? rule.conditionRelation ?? "and") === "or" ? " \u6216 " : " \u4E14 "}${conditionalOperators[condition.operator]}${condition.operator === "empty" || condition.operator === "notEmpty" ? "" : ` ${condition.value ?? ""}`}${condition.operator === "between" ? ` \uFF5E ${condition.endValue ?? ""}` : ""}`
49
+ ).join("");
50
+ }
51
+ function resolveConditions(rule) {
52
+ return rule.conditions ?? (rule.operator ? [
53
+ {
54
+ operator: rule.operator,
55
+ value: rule.value,
56
+ endValue: rule.endValue
57
+ }
58
+ ] : []);
59
+ }
60
+ function validateConditionalRule(rule) {
61
+ const conditions = resolveConditions(rule);
62
+ if (!conditions.length) return "\u8BF7\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u6761\u4EF6\u3002";
63
+ for (const [index, condition] of conditions.entries()) {
64
+ const error = validateCondition(condition);
65
+ if (error) return `\u6761\u4EF6 ${index + 1}\uFF1A${error}`;
66
+ }
67
+ return null;
68
+ }
69
+ function validateCondition(rule) {
70
+ if (rule.operator === "empty" || rule.operator === "notEmpty") return null;
71
+ if (String(rule.value ?? "").trim() === "")
72
+ return "\u8BF7\u8F93\u5165\u6BD4\u8F83\u503C\uFF1B\u7A7A\u503C\u8BF7\u4F7F\u7528\u201C\u4E3A\u7A7A\u201D\u6761\u4EF6\u3002";
73
+ if (["gt", "gte", "lt", "lte", "between"].includes(rule.operator)) {
74
+ if (!Number.isFinite(Number(rule.value))) return "\u8BF7\u8F93\u5165\u6709\u6548\u6570\u503C\u3002";
75
+ if (rule.operator === "between" && (String(rule.endValue ?? "").trim() === "" || !Number.isFinite(Number(rule.endValue)) || Number(rule.endValue) < Number(rule.value)))
76
+ return "\u8BF7\u8F93\u5165\u4E0D\u5C0F\u4E8E\u8D77\u59CB\u503C\u7684\u7ED3\u675F\u6570\u503C\u3002";
77
+ }
78
+ return null;
79
+ }
80
+ function ConditionalFormatDialog(props) {
81
+ const rules = props.request.conditionalFormats ?? [];
82
+ const [rule, setRule] = useState(
83
+ () => props.index == null ? {
84
+ id: `rule-${Date.now()}-${Math.random().toString(36).slice(2)}`,
85
+ operator: "gt",
86
+ value: "",
87
+ backgroundColor: "#fff1b8",
88
+ textColor: "#1f2329"
89
+ } : { ...rules[props.index] }
90
+ );
91
+ const [error, setError] = useState(null);
92
+ const conditions = resolveConditions(rule);
93
+ const patch = (value) => {
94
+ setRule((current) => ({ ...current, ...value }));
95
+ setError(null);
96
+ };
97
+ const apply = (next) => {
98
+ props.request.onChangeConditionalFormats?.(next);
99
+ props.onClose();
100
+ };
101
+ const patchCondition = (index, value) => {
102
+ patch({
103
+ conditions: conditions.map(
104
+ (condition, i) => i === index ? { ...condition, ...value } : condition
105
+ )
106
+ });
107
+ };
108
+ return /* @__PURE__ */ jsx(
109
+ ReactModal,
110
+ {
111
+ open: true,
112
+ title: props.index == null ? "\u65B0\u589E\u6761\u4EF6\u683C\u5F0F\u89C4\u5219" : "\u7F16\u8F91\u6761\u4EF6\u683C\u5F0F\u89C4\u5219",
113
+ width: 460,
114
+ onCancel: props.onClose,
115
+ onOk: () => {
116
+ const message = validateConditionalRule(rule);
117
+ if (message) {
118
+ setError(message);
119
+ return;
120
+ }
121
+ const next = rules.map((item) => ({ ...item }));
122
+ if (props.index == null) next.push(rule);
123
+ else next[props.index] = rule;
124
+ apply(next);
125
+ },
126
+ okText: "\u4FDD\u5B58\u89C4\u5219",
127
+ cancelText: "\u53D6\u6D88",
128
+ children: /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: 14, fontSize: 14 }, children: [
129
+ /* @__PURE__ */ jsx("div", { style: { color: "#64748b" }, children: "\u4EC5\u5F53\u524D\u5217\u751F\u6548\uFF1B\u4ECE\u4E0A\u5230\u4E0B\u5339\u914D\uFF0C\u9996\u6761\u547D\u4E2D\u7684\u80CC\u666F\u751F\u6548\u3002 \u6761\u4EF6\u4E4B\u95F4\u53EF\u5206\u522B\u8BBE\u7F6E\u201C\u4E14 / \u6216\u201D\uFF0C\u4E14\u4F18\u5148\u4E8E\u6216\u3002" }),
130
+ /* @__PURE__ */ jsxs("label", { children: [
131
+ "\u89C4\u5219\u540D\u79F0\uFF08\u53EF\u9009\uFF09",
132
+ /* @__PURE__ */ jsx(
133
+ ReactInput,
134
+ {
135
+ value: rule.name ?? "",
136
+ onChange: (event) => patch({ name: event.target.value })
137
+ }
138
+ )
139
+ ] }),
140
+ /* @__PURE__ */ jsx(
141
+ "div",
142
+ {
143
+ style: {
144
+ display: "grid",
145
+ gap: 12,
146
+ maxHeight: "35vh",
147
+ overflowY: "auto"
148
+ },
149
+ children: conditions.map((condition, index) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
150
+ index > 0 && /* @__PURE__ */ jsx(
151
+ ReactSelect,
152
+ {
153
+ "aria-label": `\u6761\u4EF6 ${index} \u4E0E\u6761\u4EF6 ${index + 1} \u7684\u5173\u7CFB`,
154
+ value: condition.relation ?? rule.conditionRelation ?? "and",
155
+ options: [
156
+ { value: "and", label: "\u4E14" },
157
+ { value: "or", label: "\u6216" }
158
+ ],
159
+ onChange: (value) => patchCondition(index, { relation: value }),
160
+ style: { width: 88, justifySelf: "center" }
161
+ }
162
+ ),
163
+ /* @__PURE__ */ jsxs(
164
+ "div",
165
+ {
166
+ style: {
167
+ display: "grid",
168
+ gap: 8,
169
+ padding: 12,
170
+ border: "1px solid #d9e1ec"
171
+ },
172
+ children: [
173
+ /* @__PURE__ */ jsxs(
174
+ "div",
175
+ {
176
+ style: {
177
+ display: "flex",
178
+ justifyContent: "space-between",
179
+ alignItems: "center"
180
+ },
181
+ children: [
182
+ /* @__PURE__ */ jsxs("span", { children: [
183
+ "\u6761\u4EF6 ",
184
+ index + 1
185
+ ] }),
186
+ /* @__PURE__ */ jsx(
187
+ ReactButton,
188
+ {
189
+ type: "text",
190
+ size: "small",
191
+ disabled: conditions.length === 1,
192
+ "aria-label": `\u5220\u9664\u6761\u4EF6 ${index + 1}`,
193
+ onClick: () => patch({
194
+ conditions: conditions.filter((_, i) => i !== index)
195
+ }),
196
+ children: "\u5220\u9664\u6761\u4EF6"
197
+ }
198
+ )
199
+ ]
200
+ }
201
+ ),
202
+ /* @__PURE__ */ jsx(
203
+ ReactSelect,
204
+ {
205
+ "aria-label": `\u6761\u4EF6 ${index + 1} \u6BD4\u8F83\u65B9\u5F0F`,
206
+ value: condition.operator,
207
+ options: Object.entries(conditionalOperators).map(
208
+ ([value, label]) => ({ value, label })
209
+ ),
210
+ onChange: (value) => patchCondition(index, {
211
+ operator: value
212
+ })
213
+ }
214
+ ),
215
+ condition.operator !== "empty" && condition.operator !== "notEmpty" && /* @__PURE__ */ jsxs("label", { children: [
216
+ "\u6BD4\u8F83\u503C",
217
+ /* @__PURE__ */ jsx(
218
+ ReactInput,
219
+ {
220
+ value: String(condition.value ?? ""),
221
+ onChange: (event) => patchCondition(index, { value: event.target.value })
222
+ }
223
+ )
224
+ ] }),
225
+ condition.operator === "between" && /* @__PURE__ */ jsxs("label", { children: [
226
+ "\u7ED3\u675F\u503C",
227
+ /* @__PURE__ */ jsx(
228
+ ReactInput,
229
+ {
230
+ value: String(condition.endValue ?? ""),
231
+ onChange: (event) => patchCondition(index, { endValue: event.target.value })
232
+ }
233
+ )
234
+ ] })
235
+ ]
236
+ }
237
+ )
238
+ ] }, index))
239
+ }
240
+ ),
241
+ /* @__PURE__ */ jsx(
242
+ ReactButton,
243
+ {
244
+ onClick: () => patch({
245
+ conditions: [
246
+ ...conditions,
247
+ { operator: "gt", value: "", relation: "and" }
248
+ ]
249
+ }),
250
+ children: "\u65B0\u589E\u6761\u4EF6"
251
+ }
252
+ ),
253
+ /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
254
+ "\u5355\u5143\u683C\u80CC\u666F",
255
+ /* @__PURE__ */ jsx(
256
+ ReactColorPicker,
257
+ {
258
+ "aria-label": "\u5355\u5143\u683C\u80CC\u666F",
259
+ value: rule.backgroundColor,
260
+ onChange: (backgroundColor) => patch({ backgroundColor })
261
+ }
262
+ )
263
+ ] }),
264
+ /* @__PURE__ */ jsxs("label", { style: { display: "grid", gap: 4 }, children: [
265
+ "\u6587\u672C\u989C\u8272",
266
+ /* @__PURE__ */ jsx(
267
+ ReactColorPicker,
268
+ {
269
+ "aria-label": "\u6587\u672C\u989C\u8272",
270
+ value: rule.textColor,
271
+ onChange: (textColor) => patch({ textColor })
272
+ }
273
+ )
274
+ ] }),
275
+ /* @__PURE__ */ jsxs(
276
+ "div",
277
+ {
278
+ style: {
279
+ padding: "8px 12px",
280
+ backgroundColor: rule.backgroundColor,
281
+ color: rule.textColor,
282
+ border: "1px solid #d9e1ec"
283
+ },
284
+ children: [
285
+ "\u6837\u5F0F\u9884\u89C8\uFF1A",
286
+ describeConditionalRule(rule)
287
+ ]
288
+ }
289
+ ),
290
+ error && /* @__PURE__ */ jsx("div", { role: "alert", style: { color: "#cf1322" }, children: error }),
291
+ props.index != null && /* @__PURE__ */ jsx(
292
+ ReactButton,
293
+ {
294
+ type: "text",
295
+ danger: true,
296
+ onClick: () => apply(rules.filter((_, index) => index !== props.index)),
297
+ children: "\u5220\u9664\u6B64\u89C4\u5219"
298
+ }
299
+ )
300
+ ] })
301
+ }
302
+ );
303
+ }
304
+
305
+ // src/features/context-menu/cache-menu.ts
306
+ function buildCacheMenuItems(request) {
307
+ const available = request.columnSettingsCacheAvailable === true;
308
+ const items = [];
309
+ const state = {
310
+ disabled: !available,
311
+ title: available ? void 0 : "\u914D\u7F6E storageKey \u540E\u53EF\u7F13\u5B58\u914D\u7F6E"
312
+ };
313
+ if (request.onChangeColumnSettingsCache)
314
+ items.push({
315
+ ...state,
316
+ key: "cache-columns",
317
+ label: "\u5217\u8BBE\u7F6E",
318
+ selected: available && request.cacheColumnSettings === true,
319
+ onClick: () => request.onChangeColumnSettingsCache?.(!request.cacheColumnSettings)
320
+ });
321
+ if (request.onChangeCachePreference) {
322
+ const categories = [
323
+ ["sort", "\u6392\u5E8F"],
324
+ ["filter", "\u7B5B\u9009"],
325
+ ["summary", "\u6C47\u603B"],
326
+ ["alignment", "\u5BF9\u9F50"],
327
+ ["dataFormat", "\u683C\u5F0F"],
328
+ ["density", "\u8868\u683C\u5BC6\u5EA6"],
329
+ ["zoom", "\u7F29\u653E"]
330
+ ];
331
+ for (const [category, label] of categories)
332
+ items.push({
333
+ ...state,
334
+ key: `cache-${category}`,
335
+ label,
336
+ selected: available && request.cachePreferences?.[category] !== false,
337
+ onClick: () => request.onChangeCachePreference?.(
338
+ category,
339
+ request.cachePreferences?.[category] === false
340
+ )
341
+ });
342
+ }
343
+ if (request.onChangeConditionalFormats)
344
+ items.push({
345
+ ...state,
346
+ key: "cache-conditional",
347
+ label: "\u5F53\u524D\u5217\u6761\u4EF6\u683C\u5F0F",
348
+ selected: available && request.cacheConditionalFormats !== false,
349
+ onClick: () => request.onChangeConditionalFormats?.(
350
+ request.conditionalFormats ?? [],
351
+ request.cacheConditionalFormats === false
352
+ )
353
+ });
354
+ return items;
355
+ }
34
356
  var CONTEXT_MENU_SURFACE_STYLE = {
35
357
  padding: 8,
36
358
  background: "#ffffff",
@@ -39,10 +361,22 @@ var CONTEXT_MENU_SURFACE_STYLE = {
39
361
  function ReactTableContextMenuView(props) {
40
362
  const { request } = props;
41
363
  const [menuOpen, setMenuOpen] = useState(true);
364
+ const [conditionalEditor, setConditionalEditor] = useState(null);
365
+ const conditionalDialog = conditionalEditor ? /* @__PURE__ */ jsx(
366
+ ConditionalFormatDialog,
367
+ {
368
+ ...conditionalEditor,
369
+ onClose: () => {
370
+ setConditionalEditor(null);
371
+ conditionalEditor.request.onClose();
372
+ }
373
+ }
374
+ ) : null;
42
375
  const [moreFormatRequest, setMoreFormatRequest] = useState(null);
43
376
  const [customSelectionRequest, setCustomSelectionRequest] = useState(null);
44
377
  if (!request) {
45
378
  return /* @__PURE__ */ jsxs(Fragment, { children: [
379
+ conditionalDialog,
46
380
  moreFormatRequest ? /* @__PURE__ */ jsx(
47
381
  MoreFormatDialog,
48
382
  {
@@ -67,15 +401,17 @@ function ReactTableContextMenuView(props) {
67
401
  width: 260
68
402
  };
69
403
  return /* @__PURE__ */ jsxs(Fragment, { children: [
404
+ conditionalDialog,
70
405
  /* @__PURE__ */ jsx(
71
406
  ReactPopup,
72
407
  {
408
+ constrainHeight: true,
73
409
  open: menuOpen,
74
410
  anchorRect: { x: request.x, y: request.y, width: 0, height: 0 },
75
411
  minWidth: 260,
76
412
  onOpenChange: (open) => {
77
413
  setMenuOpen(open);
78
- if (!open && !moreFormatRequest && !customSelectionRequest) {
414
+ if (!open && !moreFormatRequest && !customSelectionRequest && !conditionalEditor) {
79
415
  request.onClose();
80
416
  }
81
417
  },
@@ -110,7 +446,28 @@ function ReactTableContextMenuView(props) {
110
446
  key: "copy-selection",
111
447
  label: "\u590D\u5236\u9009\u533A",
112
448
  onClick: request.onCopySelection
113
- }
449
+ },
450
+ ...request.onCopyImage ? [
451
+ {
452
+ key: "copy-image-all",
453
+ label: "\u590D\u5236\u4E3A\u56FE\u7247 \xB7 \u6240\u6709\u533A\u57DF",
454
+ separatorBefore: true,
455
+ title: "\u5F53\u524D\u5DF2\u52A0\u8F7D\u5E76\u7ECF\u8FC7\u6392\u5E8F\u8FC7\u6EE4\u7684\u5168\u90E8\u6570\u636E\uFF0C\u4E0D\u8BF7\u6C42\u672A\u52A0\u8F7D\u7684\u670D\u52A1\u7AEF\u6570\u636E",
456
+ onClick: () => request.onCopyImage?.("all")
457
+ },
458
+ {
459
+ key: "copy-image-selection",
460
+ label: "\u590D\u5236\u4E3A\u56FE\u7247 \xB7 \u9009\u4E2D\u533A\u57DF",
461
+ disabled: !request.canCopySelectionImage,
462
+ onClick: () => request.onCopyImage?.("selection")
463
+ },
464
+ {
465
+ key: "copy-image-viewport",
466
+ label: "\u590D\u5236\u4E3A\u56FE\u7247 \xB7 \u89C6\u53E3\u533A\u57DF",
467
+ title: "\u5F53\u524D\u53EF\u89C1\u8868\u683C\uFF0C\u5305\u542B\u8868\u5934\u548C\u6C47\u603B\u884C\uFF0C\u4E0D\u5305\u542B\u5206\u9875\u5668\u548C\u83DC\u5355",
468
+ onClick: () => request.onCopyImage?.("viewport")
469
+ }
470
+ ] : []
114
471
  ],
115
472
  children: /* @__PURE__ */ jsx(
116
473
  CustomSelectionMenu,
@@ -149,7 +506,26 @@ function ReactTableContextMenuView(props) {
149
506
  key: "export-json",
150
507
  label: "\u5BFC\u51FA JSON",
151
508
  onClick: request.onExportJson
152
- }
509
+ },
510
+ ...request.onExportImage ? [
511
+ {
512
+ key: "export-image-all",
513
+ label: "\u5BFC\u51FA\u56FE\u7247 \xB7 \u6240\u6709\u533A\u57DF",
514
+ separatorBefore: true,
515
+ onClick: () => request.onExportImage?.("all")
516
+ },
517
+ {
518
+ key: "export-image-selection",
519
+ label: "\u5BFC\u51FA\u56FE\u7247 \xB7 \u9009\u4E2D\u533A\u57DF",
520
+ disabled: !request.canCopySelectionImage,
521
+ onClick: () => request.onExportImage?.("selection")
522
+ },
523
+ {
524
+ key: "export-image-viewport",
525
+ label: "\u5BFC\u51FA\u56FE\u7247 \xB7 \u89C6\u53E3\u533A\u57DF",
526
+ onClick: () => request.onExportImage?.("viewport")
527
+ }
528
+ ] : []
153
529
  ]
154
530
  }
155
531
  ),
@@ -222,6 +598,41 @@ function ReactTableContextMenuView(props) {
222
598
  ),
223
599
  /* @__PURE__ */ jsx(SummaryMenu, { request }),
224
600
  /* @__PURE__ */ jsx(AlignmentMenu, { request }),
601
+ request.onChangeConditionalFormats && /* @__PURE__ */ jsx(
602
+ MenuSubmenu,
603
+ {
604
+ label: "\u6761\u4EF6\u683C\u5F0F",
605
+ items: [
606
+ {
607
+ key: "add-rule",
608
+ label: "\u65B0\u589E\u89C4\u5219",
609
+ onClick: () => {
610
+ setConditionalEditor({ request, index: null });
611
+ setMenuOpen(false);
612
+ }
613
+ },
614
+ {
615
+ key: "reset-rules",
616
+ label: "\u91CD\u7F6E\u6761\u4EF6\u683C\u5F0F",
617
+ disabled: !request.conditionalFormats?.length,
618
+ onClick: () => request.onChangeConditionalFormats?.([])
619
+ }
620
+ ],
621
+ header: /* @__PURE__ */ jsx("div", { style: { display: "grid", gap: 4, paddingBottom: 8 }, children: request.conditionalFormats?.length ? request.conditionalFormats.map((rule, index) => /* @__PURE__ */ jsx(
622
+ MenuAction,
623
+ {
624
+ label: rule.name?.trim() || `\u89C4\u5219 ${index + 1}`,
625
+ colorSwatch: rule.backgroundColor,
626
+ title: `${rule.name?.trim() || `\u89C4\u5219 ${index + 1}`}\uFF1A${describeConditionalRule(rule)}`,
627
+ onClick: () => {
628
+ setConditionalEditor({ request, index });
629
+ setMenuOpen(false);
630
+ }
631
+ },
632
+ rule.id
633
+ )) : /* @__PURE__ */ jsx("span", { style: { color: "#64748b", padding: 8 }, children: "\u6682\u65E0\u89C4\u5219" }) })
634
+ }
635
+ ),
225
636
  /* @__PURE__ */ jsx(
226
637
  DataFormatMenu,
227
638
  {
@@ -352,6 +763,13 @@ function ReactTableContextMenuView(props) {
352
763
  }
353
764
  ),
354
765
  /* @__PURE__ */ jsx(ZoomMenu, { value: request.zoom ?? 1, onChange: request.onChangeZoom }),
766
+ /* @__PURE__ */ jsx(
767
+ MenuSubmenu,
768
+ {
769
+ label: "\u7F13\u5B58\u5230\u672C\u5730",
770
+ items: buildCacheMenuItems(request)
771
+ }
772
+ ),
355
773
  request.columns?.length && request.onToggleColumn ? /* @__PURE__ */ jsxs(Fragment, { children: [
356
774
  /* @__PURE__ */ jsx(MenuDivider, {}),
357
775
  /* @__PURE__ */ jsx(
@@ -398,6 +816,7 @@ function MenuAction(props) {
398
816
  type: "button",
399
817
  onClick: disabled ? void 0 : onClick,
400
818
  disabled,
819
+ title: props.title,
401
820
  className: "react-list-table-menu-action",
402
821
  style: {
403
822
  display: "flex",
@@ -426,7 +845,36 @@ function MenuAction(props) {
426
845
  children: selected ? "\u2713" : ""
427
846
  }
428
847
  ) : null,
429
- /* @__PURE__ */ jsx("span", { style: { flex: 1, whiteSpace: "nowrap" }, children: label }),
848
+ props.colorSwatch ? /* @__PURE__ */ jsx(
849
+ "span",
850
+ {
851
+ "aria-hidden": "true",
852
+ style: {
853
+ width: 14,
854
+ height: 14,
855
+ flex: "0 0 14px",
856
+ marginRight: 8,
857
+ borderRadius: 2,
858
+ backgroundColor: props.colorSwatch,
859
+ boxShadow: "inset 0 0 0 1px rgba(0, 0, 0, 0.12)"
860
+ }
861
+ }
862
+ ) : null,
863
+ /* @__PURE__ */ jsx(
864
+ "span",
865
+ {
866
+ style: {
867
+ flex: 1,
868
+ whiteSpace: "nowrap",
869
+ ...props.colorSwatch ? {
870
+ minWidth: 0,
871
+ overflow: "hidden",
872
+ textOverflow: "ellipsis"
873
+ } : null
874
+ },
875
+ children: label
876
+ }
877
+ ),
430
878
  preview ? /* @__PURE__ */ jsx(
431
879
  "span",
432
880
  {
@@ -509,6 +957,7 @@ function MenuSubmenu(props) {
509
957
  /* @__PURE__ */ jsx(
510
958
  ReactPopup,
511
959
  {
960
+ constrainHeight: true,
512
961
  open,
513
962
  anchorRef: triggerRef,
514
963
  placement: "rightTop",
@@ -536,6 +985,7 @@ function MenuSubmenu(props) {
536
985
  MenuAction,
537
986
  {
538
987
  label: item.label,
988
+ title: item.title,
539
989
  preview: item.preview,
540
990
  onClick: item.onClick,
541
991
  selected: item.selected,
@@ -698,6 +1148,7 @@ function ZoomMenu(props) {
698
1148
  /* @__PURE__ */ jsx(
699
1149
  ReactPopup,
700
1150
  {
1151
+ constrainHeight: true,
701
1152
  open,
702
1153
  anchorRef: triggerRef,
703
1154
  placement: "rightTop",
@@ -862,6 +1313,7 @@ function ColumnVisibilityMenu(props) {
862
1313
  /* @__PURE__ */ jsx(
863
1314
  ReactPopup,
864
1315
  {
1316
+ constrainHeight: true,
865
1317
  open,
866
1318
  anchorRef: triggerRef,
867
1319
  placement: "rightTop",
@@ -968,6 +1420,7 @@ function ColumnVisibilityItem(props) {
968
1420
  hasChildren ? /* @__PURE__ */ jsx(
969
1421
  ReactPopup,
970
1422
  {
1423
+ constrainHeight: true,
971
1424
  open,
972
1425
  anchorRef: triggerRef,
973
1426
  placement: "rightTop",
@@ -1022,13 +1475,7 @@ function CustomSelectionMenu(props) {
1022
1475
  if (!request.onCopyCustomSelection || !request.customSelection) {
1023
1476
  return null;
1024
1477
  }
1025
- return /* @__PURE__ */ jsx(
1026
- MenuAction,
1027
- {
1028
- label: "\u590D\u5236\u81EA\u5B9A\u4E49\u9009\u533A",
1029
- onClick: props.onOpen
1030
- }
1031
- );
1478
+ return /* @__PURE__ */ jsx(MenuAction, { label: "\u590D\u5236\u81EA\u5B9A\u4E49\u9009\u533A", onClick: props.onOpen });
1032
1479
  }
1033
1480
  function CustomSelectionDialog(props) {
1034
1481
  const { request } = props;
@@ -1241,6 +1688,16 @@ function SummaryMenu(props) {
1241
1688
  selected: request.summaryType === item.value,
1242
1689
  onClick: () => request.onChangeSummaryType?.(item.value)
1243
1690
  })),
1691
+ {
1692
+ key: "show-summary-type-prefix",
1693
+ label: "\u5C55\u793A\u6C47\u603B\u65B9\u5F0F\u524D\u7F00",
1694
+ separatorBefore: true,
1695
+ selected: request.summaryShowTypePrefix !== false,
1696
+ disabled: !request.summaryType,
1697
+ onClick: request.onChangeSummaryShowTypePrefix ? () => request.onChangeSummaryShowTypePrefix?.(
1698
+ request.summaryShowTypePrefix === false
1699
+ ) : void 0
1700
+ },
1244
1701
  {
1245
1702
  key: "clear-summary",
1246
1703
  label: "\u53D6\u6D88\u6C47\u603B",
@@ -2329,6 +2786,8 @@ function ReactTableEditorOverlay(props) {
2329
2786
  }
2330
2787
  function renderEditorByType(request, style) {
2331
2788
  switch (request.config.valueType ?? "text") {
2789
+ case "cascader":
2790
+ return /* @__PURE__ */ jsx(CascaderEditor, { request, style });
2332
2791
  case "number":
2333
2792
  return /* @__PURE__ */ jsx(NumberEditor, { request, style });
2334
2793
  case "input-range":
@@ -2617,6 +3076,50 @@ function DateEditor(props) {
2617
3076
  }
2618
3077
  ) });
2619
3078
  }
3079
+ function CascaderEditor({
3080
+ request,
3081
+ style
3082
+ }) {
3083
+ const [value, setValue] = useState(
3084
+ Array.isArray(request.value) ? request.value : []
3085
+ );
3086
+ const skipBlurCommitRef = useRef(false);
3087
+ useEffect(() => {
3088
+ setValue(Array.isArray(request.value) ? request.value : []);
3089
+ }, [request.value]);
3090
+ return /* @__PURE__ */ jsx("div", { style, children: /* @__PURE__ */ jsx(
3091
+ ReactCascader,
3092
+ {
3093
+ value,
3094
+ options: request.config.options,
3095
+ autoFocus: true,
3096
+ placeholder: request.config.placeholder,
3097
+ showSearch: request.config.showSearch,
3098
+ allowClear: request.config.allowClear,
3099
+ changeOnSelect: request.config.changeOnSelect,
3100
+ expandTrigger: request.config.expandTrigger,
3101
+ displayRender: request.config.displayRender,
3102
+ style: getEditorInputStyle(request),
3103
+ onChange: (next, _options, info) => {
3104
+ setValue(next);
3105
+ request.onChange(next);
3106
+ if (info.complete && info.trigger !== "clear") {
3107
+ skipBlurCommitRef.current = true;
3108
+ request.onCommit();
3109
+ }
3110
+ },
3111
+ onBlur: (event) => handleEditorBlur(event, skipBlurCommitRef, request),
3112
+ onKeyDown: (event) => {
3113
+ if (event.key === "Tab")
3114
+ handleEditorNavigationKey(event, request, skipBlurCommitRef);
3115
+ if (event.key === "Escape") {
3116
+ skipBlurCommitRef.current = true;
3117
+ handleEditorEscapeKey(event, request);
3118
+ }
3119
+ }
3120
+ }
3121
+ ) });
3122
+ }
2620
3123
  function SelectEditor(props) {
2621
3124
  const { request, style } = props;
2622
3125
  const options = request.config.options ?? [];
@@ -3443,7 +3946,7 @@ function resolveSortableTransform(index, sourceIndex, targetIndex, height) {
3443
3946
  }
3444
3947
 
3445
3948
  // src/features/toolbar/style.css
3446
- styleInject('.react-list-table-shell {\n background: #fff;\n font-size: 14px;\n}\n.react-list-table-titlebar {\n height: 44px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 0;\n box-sizing: border-box;\n background: #fff;\n}\n.react-list-table-shell-fullscreen {\n width: 100% !important;\n height: 100% !important;\n}\n.react-list-table-shell-fullscreen .canvas-list-table-root {\n height: 100% !important;\n}\n.react-list-table-title {\n min-width: 0;\n color: #1f2937;\n font-size: 14px;\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.react-list-table-tools {\n display: flex;\n align-items: center;\n gap: 14px;\n color: #475569;\n}\n.react-list-table-tool-wrap {\n position: relative;\n display: inline-flex;\n}\n.react-list-table-tool {\n width: 20px;\n height: 24px;\n padding: 0;\n border: 0;\n color: inherit;\n background: transparent;\n font: 16px/24px sans-serif;\n cursor: pointer;\n}\n.react-list-table-tool:hover {\n color: #1677ff;\n}\n.react-list-table-tool-menu,\n.react-list-table-column-setting {\n position: absolute;\n z-index: 120;\n top: 28px;\n right: 0;\n min-width: 112px;\n padding: 6px;\n border: 1px solid #e5e7eb;\n border-radius: 6px;\n background: #fff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, .14);\n}\n.react-list-table-tool-menu button {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n padding: 6px 10px;\n border: 0;\n border-radius: 4px;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n.react-list-table-tool-menu button.active::after {\n content: "\\2713";\n margin-left: 12px;\n font-size: 14px;\n}\n.react-list-table-tool-menu button:hover,\n.react-list-table-tool-menu button.active {\n color: #1677ff;\n background: #e6f4ff;\n}\n.react-list-table-column-setting {\n width: 328px;\n max-height: 360px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n.react-list-table-column-setting-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 6px 8px;\n border-bottom: 1px solid #f0f0f0;\n flex: 0 0 auto;\n}\n.react-list-table-column-setting-head button {\n border: 0;\n color: #1677ff;\n background: transparent;\n cursor: pointer;\n}\n.react-list-table-column-setting-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n}\n.react-list-table-column-item {\n display: flex;\n align-items: center;\n gap: 8px;\n justify-content: space-between;\n padding: 7px 6px;\n box-sizing: border-box;\n cursor: pointer;\n}\n.react-list-table-column-main,\n.react-list-table-column-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.react-list-table-column-main {\n min-width: 0;\n}\n.react-list-table-column-actions {\n flex: 0 0 auto;\n}\n.react-list-table-column-actions > .canvas-react-select-wrap {\n flex: 0 0 96px;\n width: 96px;\n height: 24px;\n}\n.react-list-table-column-label {\n min-width: 64px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.react-list-table-column-width-input {\n height: 22px;\n padding-inline: 5px;\n}\n.react-list-table-column-fixed-input {\n height: 22px;\n padding-inline: 6px;\n font-size: 14px;\n}\n.react-list-table-column-setting label.dragging {\n opacity: 0.45;\n background: #e6f4ff;\n}\n.react-list-table-column-setting [data-sortable-key].dragging {\n opacity: 0.28;\n}\n.react-list-table-column-drag {\n color: #94a3b8;\n display: inline-flex;\n font-size: 14px;\n cursor: grab;\n user-select: none;\n}\nbody.react-list-table-sorting,\nbody.react-list-table-sorting * {\n cursor: grabbing !important;\n}\n.react-list-table-sort-overlay {\n position: fixed;\n z-index: 1400;\n display: block;\n padding: 0;\n box-sizing: border-box;\n color: #334155;\n background: #fff;\n border: 1px solid #91caff;\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);\n pointer-events: none;\n}\n.react-list-table-sort-overlay .react-list-table-column-item {\n width: 100%;\n height: 100%;\n}\n');
3949
+ styleInject('.react-list-table-shell {\n background: #fff;\n font-size: 14px;\n}\n.react-list-table-titlebar {\n height: 44px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 0;\n box-sizing: border-box;\n background: #fff;\n}\n.react-list-table-shell-fullscreen {\n width: 100% !important;\n height: 100% !important;\n}\n.react-list-table-shell-fullscreen .canvas-list-table-root {\n height: 100% !important;\n}\n.react-list-table-title {\n min-width: 0;\n color: #1f2937;\n font-size: 14px;\n font-weight: 600;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.react-list-table-tools {\n display: flex;\n align-items: center;\n gap: 14px;\n color: #475569;\n}\n.react-list-table-tool-wrap {\n position: relative;\n display: inline-flex;\n}\n.react-list-table-tool {\n width: 20px;\n height: 24px;\n padding: 0;\n border: 0;\n color: inherit;\n background: transparent;\n font: 16px/24px sans-serif;\n cursor: pointer;\n}\n.react-list-table-tool:hover {\n color: #1677ff;\n}\n.react-list-table-tool-menu,\n.react-list-table-column-setting {\n position: absolute;\n z-index: 120;\n top: 28px;\n right: 0;\n min-width: 112px;\n padding: 6px;\n border: 1px solid #e5e7eb;\n border-radius: 6px;\n background: #fff;\n box-shadow: 0 8px 24px rgba(15, 23, 42, .14);\n}\n.react-list-table-tool-menu button {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n padding: 6px 10px;\n border: 0;\n border-radius: 4px;\n background: transparent;\n text-align: left;\n cursor: pointer;\n}\n.react-list-table-tool-menu button.active::after {\n content: "\\2713";\n margin-left: 12px;\n font-size: 14px;\n}\n.react-list-table-tool-menu button:hover,\n.react-list-table-tool-menu button.active {\n color: #1677ff;\n background: #e6f4ff;\n}\n.react-list-table-column-setting {\n width: 328px;\n max-height: 360px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n.react-list-table-column-setting-head {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: 4px 6px 8px;\n border-bottom: 1px solid #f0f0f0;\n flex: 0 0 auto;\n}\n.react-list-table-column-setting-head button {\n border: 0;\n color: #1677ff;\n background: transparent;\n cursor: pointer;\n}\n.react-list-table-column-setting-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n}\n.react-list-table-column-item {\n display: flex;\n align-items: center;\n gap: 8px;\n justify-content: space-between;\n padding: 7px 6px;\n box-sizing: border-box;\n cursor: pointer;\n}\n.react-list-table-column-main,\n.react-list-table-column-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.react-list-table-column-main {\n min-width: 0;\n}\n.react-list-table-column-actions {\n flex: 0 0 auto;\n}\n.react-list-table-column-actions > .canvas-react-select-wrap {\n flex: 0 0 96px;\n width: 96px;\n height: 24px;\n}\n.react-list-table-column-label {\n min-width: 64px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.react-list-table-column-width-input {\n height: 22px;\n padding-inline: 5px;\n}\n.react-list-table-column-fixed-input {\n height: 22px;\n padding-inline: 6px;\n font-size: 14px;\n}\n.react-list-table-column-setting label.dragging {\n opacity: 0.45;\n background: #e6f4ff;\n}\n.react-list-table-column-setting [data-sortable-key].dragging {\n opacity: 0.28;\n}\n.react-list-table-column-drag {\n color: #94a3b8;\n display: inline-flex;\n font-size: 14px;\n cursor: grab;\n user-select: none;\n}\nbody.react-list-table-sorting,\nbody.react-list-table-sorting * {\n cursor: grabbing !important;\n}\n.react-list-table-sort-overlay {\n position: fixed;\n z-index: 1400;\n display: block;\n padding: 0;\n box-sizing: border-box;\n color: #334155;\n background: #fff;\n border: 1px solid #91caff;\n border-radius: 6px;\n box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);\n pointer-events: none;\n}\n.react-list-table-sort-overlay .react-list-table-column-item {\n width: 100%;\n height: 100%;\n}\n');
3447
3950
  function ReactTableToolbar(props) {
3448
3951
  const { toolbar, title } = props;
3449
3952
  const [densityOpen, setDensityOpen] = useState(false);
@@ -3560,6 +4063,18 @@ function ReactTableToolbar(props) {
3560
4063
  children: [
3561
4064
  /* @__PURE__ */ jsxs("div", { className: "react-list-table-column-setting-head", children: [
3562
4065
  /* @__PURE__ */ jsx("strong", { children: "\u5217\u8BBE\u7F6E" }),
4066
+ /* @__PURE__ */ jsx(
4067
+ ReactCheckbox,
4068
+ {
4069
+ checked: props.cacheColumnSettings === true,
4070
+ disabled: !props.columnSettingsCacheAvailable,
4071
+ title: props.columnSettingsCacheAvailable ? "\u7F13\u5B58\u5217\u5BBD\u3001\u663E\u9690\u3001\u56FA\u5B9A\u548C\u987A\u5E8F" : "\u914D\u7F6E storageKey \u540E\u53EF\u7F13\u5B58\u5217\u8BBE\u7F6E",
4072
+ onChange: (event) => props.onColumnSettingsCacheChange?.(
4073
+ event.target.checked
4074
+ ),
4075
+ children: "\u7F13\u5B58\u5230\u672C\u5730"
4076
+ }
4077
+ ),
3563
4078
  /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto" }, children: config.columnSettingHeaderExtra }),
3564
4079
  /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onResetColumns, children: "\u91CD\u7F6E" })
3565
4080
  ] }),
@@ -3895,6 +4410,8 @@ function ReactListTableInner(props, ref) {
3895
4410
  stretchColumns,
3896
4411
  resizable,
3897
4412
  storageKey,
4413
+ cacheColumnSettings,
4414
+ cachePreferences,
3898
4415
  draggable,
3899
4416
  tableId,
3900
4417
  rowDrag,
@@ -3916,6 +4433,10 @@ function ReactListTableInner(props, ref) {
3916
4433
  );
3917
4434
  const [columnConfig, setColumnConfig] = useState(() => /* @__PURE__ */ new Map());
3918
4435
  const [columnConfigRevision, setColumnConfigRevision] = useState(0);
4436
+ const [columnSettingsCacheOverride, setColumnSettingsCacheOverride] = useState(null);
4437
+ const resolvedColumnSettingsCache = (columnSettingsCacheOverride?.storageKey === storageKey ? columnSettingsCacheOverride?.value : void 0) ?? cacheColumnSettings ?? true;
4438
+ const [cachePreferencesOverride, setCachePreferencesOverride] = useState();
4439
+ const resolvedCachePreferences = (cachePreferencesOverride?.storageKey === storageKey ? cachePreferencesOverride?.value : void 0) ?? cachePreferences;
3919
4440
  const [columnConfigResetRevision, setColumnConfigResetRevision] = useState(0);
3920
4441
  const resolvedColumns = useMemo(
3921
4442
  () => applyColumnManagerState(
@@ -3969,6 +4490,8 @@ function ReactListTableInner(props, ref) {
3969
4490
  stretchColumns,
3970
4491
  resizable,
3971
4492
  storageKey,
4493
+ cacheColumnSettings: resolvedColumnSettingsCache,
4494
+ cachePreferences: resolvedCachePreferences,
3972
4495
  draggable,
3973
4496
  tableId,
3974
4497
  rowDrag,
@@ -3986,7 +4509,13 @@ function ReactListTableInner(props, ref) {
3986
4509
  columnConfigResetRevision,
3987
4510
  ui: {
3988
4511
  ...nextOptions.ui,
3989
- onDensityChange: setDensityOverride
4512
+ onDensityChange: setDensityOverride,
4513
+ onColumnSettingsCacheChange: (value) => setColumnSettingsCacheOverride(
4514
+ (current) => current?.storageKey === storageKey && current?.value === value ? current : { storageKey, value }
4515
+ ),
4516
+ onCachePreferencesChange: (next) => setCachePreferencesOverride(
4517
+ (current) => current?.storageKey === storageKey && JSON.stringify(current?.value ?? {}) === JSON.stringify(next) ? current : { storageKey, value: next }
4518
+ )
3990
4519
  }
3991
4520
  };
3992
4521
  }, [
@@ -4022,6 +4551,8 @@ function ReactListTableInner(props, ref) {
4022
4551
  stretchColumns,
4023
4552
  resizable,
4024
4553
  storageKey,
4554
+ resolvedColumnSettingsCache,
4555
+ resolvedCachePreferences,
4025
4556
  draggable,
4026
4557
  tableId,
4027
4558
  rowDrag,
@@ -4090,6 +4621,9 @@ function ReactListTableInner(props, ref) {
4090
4621
  {
4091
4622
  title,
4092
4623
  toolbar,
4624
+ cacheColumnSettings: Boolean(storageKey) && resolvedColumnSettingsCache,
4625
+ columnSettingsCacheAvailable: Boolean(storageKey),
4626
+ onColumnSettingsCacheChange: (enabled) => tableRef.current?.setColumnSettingsCache(enabled),
4093
4627
  columns: resolvedColumns,
4094
4628
  density: resolvedRowHeight ?? 32,
4095
4629
  hostRef: shellRef,