@stcn52/pro 0.2.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,12 @@ the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] — 2026-10-09
11
+
12
+ - Table: preserve selections added during asynchronous bulk operations, align sorting entry points, avoid repeated full-row scans, correct pinned action widths and tree loading geometry, and support wrapping cells and accessible inline actions.
13
+ - Workbench: export the full selected set or reject unavailable records, support local column unfreezing, reset empty-board filters, and show accurate totals for unpaginated grouped lists and boards.
14
+ - Async boundaries: isolate attachment sessions by record, stop disabled forms after validation, accumulate batched uncontrolled state updates, and reject obsolete entity select values.
15
+
10
16
  - Clear all 13 lint warnings: publish controlled props before layout effects, reset closed overlays without effect cascades, isolate ref-backed event handlers and render composer tools as components; enforce hook checks and a zero-warning verification gate.
11
17
 
12
18
  - Ship editors: validate owned drag sessions, quarter/year boundaries and wizard drafts; wait for real workbench imports/deletes/edits and preserve new selection, parse quoted multiline CSV safely, guard queued callbacks after unmount, and contain mobile canvas cards.
package/dist/index.cjs CHANGED
@@ -95,7 +95,10 @@ function useControllableState(options) {
95
95
  });
96
96
  const setValue = React2.useCallback((next) => {
97
97
  const resolved = typeof next === "function" ? next(currentRef.current) : next;
98
- if (!controlledRef.current) setUncontrolled(resolved);
98
+ if (!controlledRef.current) {
99
+ currentRef.current = resolved;
100
+ setUncontrolled(resolved);
101
+ }
99
102
  onChangeRef.current?.(resolved);
100
103
  }, []);
101
104
  return [current, setValue];
@@ -2332,7 +2335,7 @@ function SelectionBar({
2332
2335
  "aria-expanded": moreOpen,
2333
2336
  onClick: () => setMoreOpen(true),
2334
2337
  children: [
2335
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "menu", size: 14 }),
2338
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more", size: 14 }),
2336
2339
  " ",
2337
2340
  locale === "zh-CN" ? "\u66F4\u591A" : "More"
2338
2341
  ]
@@ -2362,7 +2365,8 @@ function buildBulkCommands({
2362
2365
  more,
2363
2366
  onNotify,
2364
2367
  onEditProperties,
2365
- onClear
2368
+ onClear,
2369
+ onComplete
2366
2370
  }) {
2367
2371
  const count = keys.length;
2368
2372
  const many = count === 1 ? noun.one : noun.many;
@@ -2370,7 +2374,8 @@ function buildBulkCommands({
2370
2374
  if (!count) return;
2371
2375
  const finish = () => {
2372
2376
  onNotify?.(message2);
2373
- onClear();
2377
+ if (onComplete) onComplete(keys);
2378
+ else onClear();
2374
2379
  };
2375
2380
  const result = run();
2376
2381
  if (result) return result.then(finish);
@@ -4075,6 +4080,7 @@ function BulkBar({
4075
4080
  someSelected,
4076
4081
  onToggleAll,
4077
4082
  onClear,
4083
+ onComplete,
4078
4084
  onEditProperties,
4079
4085
  onExportSelection,
4080
4086
  onDelete,
@@ -4106,7 +4112,8 @@ function BulkBar({
4106
4112
  ],
4107
4113
  onNotify,
4108
4114
  onEditProperties,
4109
- onClear
4115
+ onClear,
4116
+ onComplete
4110
4117
  });
4111
4118
  return /* @__PURE__ */ jsxRuntime.jsx(
4112
4119
  SelectionBar,
@@ -4128,6 +4135,7 @@ function BulkBar({
4128
4135
 
4129
4136
  // src/components/table/geometry.ts
4130
4137
  var LEADING_WIDTH = 44;
4138
+ var COMPACT_ACTIONS_WIDTH = 96;
4131
4139
  function widthStyle(width) {
4132
4140
  if (width === void 0) return void 0;
4133
4141
  return { width: typeof width === "number" ? `${width}px` : width };
@@ -4162,6 +4170,7 @@ function pinStyle(pin, direction = "ltr") {
4162
4170
  }
4163
4171
  function SkeletonRows({
4164
4172
  columns,
4173
+ compact,
4165
4174
  rows,
4166
4175
  widths,
4167
4176
  selectable,
@@ -4218,7 +4227,16 @@ function SkeletonRows({
4218
4227
  },
4219
4228
  column.id
4220
4229
  )),
4221
- hasRowActions ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: cx("pro-table__actions-col", pinned && "is-fixed-right-first") }) : null,
4230
+ hasRowActions ? /* @__PURE__ */ jsxRuntime.jsx(
4231
+ "td",
4232
+ {
4233
+ className: cx(
4234
+ "pro-table__actions-col",
4235
+ pinned && (compact ? pinClass({ side: "right"}, true) : "is-fixed-right-first")
4236
+ ),
4237
+ style: pinned && compact ? pinStyle({ side: "right", offset: 0 }, direction) : void 0
4238
+ }
4239
+ ) : null,
4222
4240
  /* @__PURE__ */ jsxRuntime.jsx("td", { className: "pro-table__filler" }),
4223
4241
  hasHeaderTools ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: "pro-table__tool-col" }) : null
4224
4242
  ] }, `skeleton-${rowIndex}`)) });
@@ -4241,7 +4259,7 @@ function ColumnMenu({
4241
4259
  close();
4242
4260
  };
4243
4261
  const items = [
4244
- ...column.sortable && onSortChange ? [
4262
+ ...(column.sortable || column.sorter) && onSortChange ? [
4245
4263
  {
4246
4264
  id: "sort",
4247
4265
  label: "Sort by",
@@ -4495,10 +4513,10 @@ function TableHead({
4495
4513
  "aria-label": `Sort by ${typeof column.header === "string" ? column.header : column.id}`,
4496
4514
  children: [
4497
4515
  column.header,
4498
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pro-table__sort-icon", "aria-hidden": "true", children: [
4499
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: sorted && current?.direction === "desc" ? "angle-down" : "angle-up", size: 9 }),
4500
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: sorted && current?.direction === "asc" ? "angle-up" : "angle-down", size: 9 })
4501
- ] })
4516
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pro-table__sort-icon", "aria-hidden": "true", children: current ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: current.direction === "asc" ? "angle-up" : "angle-down", size: 9 }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4517
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "angle-up", size: 9 }),
4518
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "angle-down", size: 9 })
4519
+ ] }) })
4502
4520
  ]
4503
4521
  }
4504
4522
  ) : column.header,
@@ -4645,17 +4663,22 @@ function nextCell(key, cell, rowCount, colCount) {
4645
4663
  else return null;
4646
4664
  return next;
4647
4665
  }
4648
- function CellCommands({ actions, row }) {
4666
+ function CellCommands({
4667
+ actions,
4668
+ row,
4669
+ disabled
4670
+ }) {
4649
4671
  if (actions.length === 0) return null;
4650
4672
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pro-table__cell-actions", children: actions.map((action) => /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { label: action.label, children: /* @__PURE__ */ jsxRuntime.jsx(
4651
4673
  "button",
4652
4674
  {
4653
4675
  type: "button",
4676
+ disabled,
4654
4677
  className: "pro-table__cell-action",
4655
4678
  "aria-label": action.label,
4656
4679
  onClick: (event) => {
4657
4680
  event.stopPropagation();
4658
- action.onSelect(row);
4681
+ if (!disabled) action.onSelect(row);
4659
4682
  },
4660
4683
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: action.icon, size: 14 })
4661
4684
  }
@@ -4839,12 +4862,13 @@ function TableRow({
4839
4862
  {
4840
4863
  className: cx(
4841
4864
  "pro-table__cell",
4865
+ column.ellipsis === false && "pro-table__cell--wrap",
4842
4866
  column.align === "center" && "pro-table__cell--center",
4843
4867
  column.align === "right" && "pro-table__cell--right"
4844
4868
  ),
4845
4869
  children: [
4846
4870
  /* @__PURE__ */ jsxRuntime.jsx(CellContent, { value: column.render(row, index), ellipsis: column.ellipsis }),
4847
- column.actions?.length ? /* @__PURE__ */ jsxRuntime.jsx(CellCommands, { actions: column.actions, row }) : null
4871
+ column.actions?.length ? /* @__PURE__ */ jsxRuntime.jsx(CellCommands, { actions: column.actions, row, disabled }) : null
4848
4872
  ]
4849
4873
  }
4850
4874
  )
@@ -4856,7 +4880,11 @@ function TableRow({
4856
4880
  /* @__PURE__ */ jsxRuntime.jsx(
4857
4881
  "td",
4858
4882
  {
4859
- className: cx("pro-table__actions-col", pinned && "is-fixed-right-first"),
4883
+ className: cx(
4884
+ "pro-table__actions-col",
4885
+ pinned && (compact ? pinClass({ side: "right"}, true) : "is-fixed-right-first")
4886
+ ),
4887
+ style: pinned && compact ? pinStyle({ side: "right", offset: 0 }, direction) : void 0,
4860
4888
  onClick: (event) => event.stopPropagation(),
4861
4889
  children: compact ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pro-table__cell pro-table__cell--actions", children: rowActions(row) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pro-table__row-actions", children: rowActions(row) })
4862
4890
  }
@@ -5018,6 +5046,44 @@ function DataTable({
5018
5046
  const { direction, locale } = useProConfig();
5019
5047
  const { leaves: columns, headers } = React2.useMemo(() => tableColumns(suppliedColumns), [suppliedColumns]);
5020
5048
  const selectionName = React2.useId();
5049
+ const selectionMounted = React2.useRef(false);
5050
+ React2.useLayoutEffect(() => {
5051
+ selectionMounted.current = true;
5052
+ return () => {
5053
+ selectionMounted.current = false;
5054
+ };
5055
+ }, []);
5056
+ const latestSelection = React2.useRef({
5057
+ selectedKeys,
5058
+ onSelectedKeysChange,
5059
+ suppliedRows,
5060
+ rowKey,
5061
+ getChildren,
5062
+ isRowSelectable,
5063
+ onNotify
5064
+ });
5065
+ React2.useLayoutEffect(() => {
5066
+ latestSelection.current = {
5067
+ selectedKeys,
5068
+ onSelectedKeysChange,
5069
+ suppliedRows,
5070
+ rowKey,
5071
+ getChildren,
5072
+ isRowSelectable,
5073
+ onNotify
5074
+ };
5075
+ });
5076
+ const clearSelection = (completed) => {
5077
+ if (!selectionMounted.current) return;
5078
+ const current = latestSelection.current;
5079
+ const locked = new Set(
5080
+ flattenRecords(tableRecords(current.suppliedRows, current.rowKey, current.getChildren), null).filter(({ row }) => current.isRowSelectable?.(row) === false).map(({ key }) => key)
5081
+ );
5082
+ const processed2 = completed ? new Set(completed) : void 0;
5083
+ current.onSelectedKeysChange?.(
5084
+ current.selectedKeys.filter((key) => locked.has(key) || processed2 !== void 0 && !processed2.has(key))
5085
+ );
5086
+ };
5021
5087
  const [scrollTop, setScrollTop] = React2.useState(0);
5022
5088
  const [focusedKey, setFocusedKey] = React2.useState(null);
5023
5089
  const [headerHeight, setHeaderHeight] = React2.useState(44);
@@ -5140,7 +5206,7 @@ function DataTable({
5140
5206
  const columnsCount = columns.length + (selectable ? 1 : 0) + (showIndex ? 1 : 0) + (rowActions ? 1 : 0) + (headerTools ? 1 : 0) + (hasExpansion ? 1 : 0) + 1;
5141
5207
  const pinned = columns.some((column) => column.fixed !== void 0);
5142
5208
  const leadingWidth = (selectable ? LEADING_WIDTH : 0) + (showIndex ? LEADING_WIDTH : 0) + (hasExpansion ? LEADING_WIDTH : 0);
5143
- const pins = pinOffsets(columns, leadingWidth, 0);
5209
+ const pins = pinOffsets(columns, leadingWidth, compact && rowActions ? COMPACT_ACTIONS_WIDTH : 0);
5144
5210
  const lastLeft = [...columns].reverse().find((column) => column.fixed === "left")?.id;
5145
5211
  const firstRight = columns.find((column) => column.fixed === "right")?.id;
5146
5212
  const rootRef = React2.useRef(null);
@@ -5201,7 +5267,9 @@ function DataTable({
5201
5267
  const rowHeight = suppliedHeight !== void 0 && Number.isFinite(suppliedHeight) && suppliedHeight > 0 ? suppliedHeight : compact ? 36 : 45;
5202
5268
  const overscan = suppliedOverscan !== void 0 && Number.isFinite(suppliedOverscan) ? Math.max(0, Math.floor(suppliedOverscan)) : 3;
5203
5269
  const viewportHeight = typeof scroll?.y === "number" && Number.isFinite(scroll.y) && scroll.y > 0 ? scroll.y : 300;
5204
- const windowed = Boolean(virtual && !groupBy && !expandedRowRender && !columns.some((column) => column.onCell));
5270
+ const windowed = Boolean(
5271
+ virtual && !groupBy && !expandedRowRender && !columns.some((column) => column.onCell || column.ellipsis === false)
5272
+ );
5205
5273
  const start = windowed ? Math.min(
5206
5274
  Math.max(0, rows.length - Math.ceil(viewportHeight / rowHeight)),
5207
5275
  Math.max(0, Math.floor((scrollTop - (showHeader ? headerHeight : 0)) / rowHeight) - overscan)
@@ -5288,9 +5356,10 @@ function DataTable({
5288
5356
  widths: loadingColumns,
5289
5357
  selectable,
5290
5358
  showIndex,
5359
+ compact,
5291
5360
  hasRowActions: Boolean(rowActions),
5292
5361
  hasHeaderTools: Boolean(headerTools),
5293
- hasExpansion: Boolean(expandedRowRender),
5362
+ hasExpansion,
5294
5363
  pinned,
5295
5364
  pins,
5296
5365
  lastLeft,
@@ -5327,6 +5396,7 @@ function DataTable({
5327
5396
  const indexes = Array.from({ length: end - start }, (_, index) => start + index);
5328
5397
  const focusedIndex = focusedKey === null ? -1 : visibleRecords.findIndex((record) => record.key === focusedKey);
5329
5398
  if (windowed && focusedIndex >= 0 && !indexes.includes(focusedIndex)) indexes.push(focusedIndex);
5399
+ if (windowed && cursor && rows.length > 0 && !indexes.includes(cursorRow)) indexes.push(cursorRow);
5330
5400
  indexes.sort((a, b) => a - b);
5331
5401
  const spacer = (height, key) => /* @__PURE__ */ jsxRuntime.jsx("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: columnsCount, style: { height, padding: 0, border: 0 } }) }, key);
5332
5402
  body = /* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
@@ -5374,17 +5444,14 @@ function DataTable({
5374
5444
  allSelected,
5375
5445
  someSelected,
5376
5446
  onToggleAll: toggleAll,
5377
- onClear: () => onSelectedKeysChange?.(
5378
- selectedKeys.filter(
5379
- (key) => flattenRecords(records, null).some(
5380
- ({ row }) => rowKey(row) === key && isRowSelectable?.(row) === false
5381
- )
5382
- )
5383
- ),
5447
+ onClear: () => clearSelection(),
5448
+ onComplete: clearSelection,
5384
5449
  onEditProperties,
5385
5450
  onExportSelection,
5386
5451
  onDelete,
5387
- onNotify
5452
+ onNotify: (message2) => {
5453
+ if (selectionMounted.current) latestSelection.current.onNotify?.(message2);
5454
+ }
5388
5455
  }
5389
5456
  ) : null,
5390
5457
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -5407,7 +5474,7 @@ function DataTable({
5407
5474
  showIndex ? /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: LEADING_WIDTH } }) : null,
5408
5475
  hasExpansion ? /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: LEADING_WIDTH } }) : null,
5409
5476
  columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: column.width } }, column.id)),
5410
- rowActions ? /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: compact ? 96 : 0 } }) : null,
5477
+ rowActions ? /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: compact ? COMPACT_ACTIONS_WIDTH : 0 } }) : null,
5411
5478
  /* @__PURE__ */ jsxRuntime.jsx("col", {}),
5412
5479
  headerTools ? /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: LEADING_WIDTH } }) : null
5413
5480
  ] }),
@@ -10464,7 +10531,10 @@ function isPromise(value) {
10464
10531
  function clamp(pct) {
10465
10532
  return Number.isFinite(pct) ? Math.max(0, Math.min(100, Math.round(pct))) : 0;
10466
10533
  }
10467
- function AttachmentUpload({
10534
+ function AttachmentUpload(props) {
10535
+ return /* @__PURE__ */ jsxRuntime.jsx(AttachmentUploadSession, { ...props }, props.entityId);
10536
+ }
10537
+ function AttachmentUploadSession({
10468
10538
  files,
10469
10539
  label,
10470
10540
  maxSizeMb = DEFAULT_MAX_MB,
@@ -11386,7 +11456,14 @@ function EntityDialog({
11386
11456
  const fieldValue = (id) => Object.hasOwn(values, id) && typeof values[id] === "string" ? values[id] : "";
11387
11457
  const dirty = Object.keys(values).some((key) => values[key] !== baseline[key]);
11388
11458
  const errors = Object.fromEntries(
11389
- [...fields, ...asideFields].filter((field) => field.required && !fieldValue(field.id).trim()).map((field) => [field.id, zh ? `\u8BF7\u586B\u5199${field.label}` : `${field.label} is required`])
11459
+ [...fields, ...asideFields].flatMap((field) => {
11460
+ const value = fieldValue(field.id);
11461
+ if (field.required && !value.trim())
11462
+ return [[field.id, zh ? `\u8BF7\u586B\u5199${field.label}` : `${field.label} is required`]];
11463
+ if (field.kind === "select" && value && !field.options?.some((option) => option.value === value))
11464
+ return [[field.id, zh ? `\u8BF7\u9009\u62E9\u6709\u6548\u7684${field.label}` : `Choose a valid ${field.label}`]];
11465
+ return [];
11466
+ })
11390
11467
  );
11391
11468
  const invalid = Object.keys(errors).length > 0;
11392
11469
  const idPrefix = React2.useId();
@@ -13062,7 +13139,7 @@ function AssigneeCell({ field, row, rowKey }) {
13062
13139
  disabled,
13063
13140
  className: "pro-assignee-cell__assign",
13064
13141
  "aria-label": field.assignLabel?.(row) ?? `Assign ${rowKey}`,
13065
- tabIndex: -1,
13142
+ tabIndex: field.onAssign ? 0 : -1,
13066
13143
  onClick: (event) => {
13067
13144
  event.stopPropagation();
13068
13145
  if (field.onAssign) {
@@ -13255,6 +13332,7 @@ function ListWorkbench({
13255
13332
  const [deleteError, setDeleteError] = React2.useState(false);
13256
13333
  const [sessionIdentity, setSessionIdentity] = React2.useState(identity);
13257
13334
  const [hiddenColumns, setHiddenColumns] = React2.useState([]);
13335
+ const [unfrozenColumns, setUnfrozenColumns] = React2.useState([]);
13258
13336
  const [columnsOpen, setColumnsOpen] = React2.useState(false);
13259
13337
  const [columnAnchor, setColumnAnchor] = React2.useState(null);
13260
13338
  const [settingsOpen, setSettingsOpen] = React2.useState(false);
@@ -13269,6 +13347,12 @@ function ListWorkbench({
13269
13347
  const editable = Boolean(editFields && entityKind && onEdit);
13270
13348
  const createSheet = Boolean(editFields && entityKind && onCreate);
13271
13349
  const filteredEmpty = Boolean(view.q || view.rules.length);
13350
+ const [session, setSession] = React2.useState({ identity });
13351
+ if (session.identity !== identity) setSession({ identity });
13352
+ const boardSession = React2.useRef(session);
13353
+ React2.useLayoutEffect(() => {
13354
+ boardSession.current = session;
13355
+ }, [session]);
13272
13356
  const latest = React2.useRef({ onImport, onNotify, onSelectionChange, selection, disabled });
13273
13357
  React2.useLayoutEffect(() => {
13274
13358
  latest.current = { onImport, onNotify, onSelectionChange, selection, disabled };
@@ -13297,6 +13381,7 @@ function ListWorkbench({
13297
13381
  setBulkEditOpen(false);
13298
13382
  setBulkConfirm(false);
13299
13383
  setHiddenColumns([]);
13384
+ setUnfrozenColumns([]);
13300
13385
  }
13301
13386
  if (disabled && columnsOpen) setColumnsOpen(false);
13302
13387
  if (disabled && settingsOpen) setSettingsOpen(false);
@@ -13362,7 +13447,9 @@ function ListWorkbench({
13362
13447
  };
13363
13448
  }
13364
13449
  return column;
13365
- }).map((column) => frozen.includes(column.id) ? { ...column, fixed: "left" } : column);
13450
+ }).map(
13451
+ (column) => frozen.includes(column.id) ? { ...column, fixed: "left" } : unfrozenColumns.includes(column.id) ? { ...column, fixed: void 0 } : column
13452
+ );
13366
13453
  const groupValue = React2.useMemo(() => {
13367
13454
  if (!view.group) return void 0;
13368
13455
  return (row) => {
@@ -13373,7 +13460,15 @@ function ListWorkbench({
13373
13460
  }, [view.group]);
13374
13461
  const exportCsv = (keys = []) => {
13375
13462
  if (disabled) return;
13376
- const source = keys.length > 0 ? sorted.filter((row) => keys.includes(rowKey(row))) : sorted;
13463
+ const selected = new Set(keys);
13464
+ const source = selected.size > 0 ? rows.filter((row) => selected.has(rowKey(row))) : sorted;
13465
+ if (selected.size > 0 && source.length !== selected.size) {
13466
+ notify(
13467
+ "error",
13468
+ zh ? "\u90E8\u5206\u6240\u9009\u8BB0\u5F55\u672A\u52A0\u8F7D\uFF0C\u8BF7\u52A0\u8F7D\u5B8C\u6574\u6570\u636E\u540E\u91CD\u8BD5\u3002" : "Some selected records are not loaded. Load all selected records and retry."
13469
+ );
13470
+ return;
13471
+ }
13377
13472
  try {
13378
13473
  if (onExport) download(`${slug}.csv`, onExport(source));
13379
13474
  else exportRowsToCsv(slug, source);
@@ -13656,7 +13751,7 @@ function ListWorkbench({
13656
13751
  {
13657
13752
  title: `No ${noun.many} to show`,
13658
13753
  hint: emptyBoardText ?? "Nothing matches the current filters. Clear them to see the full board.",
13659
- action: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "default", onClick: () => update({ rules: [], page: 1 }), children: "Clear filters" })
13754
+ action: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "default", onClick: () => update({ q: "", rules: [], page: 1 }), children: "Clear filters" })
13660
13755
  }
13661
13756
  ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
13662
13757
  selection.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -13666,9 +13761,14 @@ function ListWorkbench({
13666
13761
  count: selection.length,
13667
13762
  clearLabel: "Cancel",
13668
13763
  onClear: () => onSelectionChange([]),
13669
- allSelected: selection.length === rows.length,
13670
- someSelected: selection.some((key) => rows.some((row) => rowKey(row) === key)),
13671
- onToggleAll: () => onSelectionChange(selection.length === rows.length ? [] : rows.map(rowKey)),
13764
+ allSelected: sorted.length > 0 && sorted.every((row) => selection.includes(rowKey(row))),
13765
+ someSelected: !sorted.every((row) => selection.includes(rowKey(row))) && selection.some((key) => sorted.some((row) => rowKey(row) === key)),
13766
+ onToggleAll: () => {
13767
+ const keys = sorted.map(rowKey);
13768
+ onSelectionChange(
13769
+ keys.every((key) => selection.includes(key)) ? selection.filter((key) => !keys.includes(key)) : [.../* @__PURE__ */ new Set([...selection, ...keys])]
13770
+ );
13771
+ },
13672
13772
  ...buildBulkCommands({
13673
13773
  bulk,
13674
13774
  keys: selection,
@@ -13682,9 +13782,16 @@ function ListWorkbench({
13682
13782
  onSelect: () => exportCsv(selection)
13683
13783
  }
13684
13784
  ],
13685
- onNotify: (message2) => notify("success", message2),
13785
+ onNotify: (message2) => {
13786
+ if (live.current && boardSession.current === session) notify("success", message2);
13787
+ },
13686
13788
  onEditProperties: bulk?.properties ? () => setBulkEditOpen(true) : void 0,
13687
- onClear: () => onSelectionChange([])
13789
+ onClear: () => onSelectionChange([]),
13790
+ onComplete: (keys) => {
13791
+ if (!live.current || boardSession.current !== session) return;
13792
+ const processed = new Set(keys);
13793
+ latest.current.onSelectionChange(latest.current.selection.filter((key) => !processed.has(key)));
13794
+ }
13688
13795
  })
13689
13796
  }
13690
13797
  ) : null,
@@ -13724,6 +13831,10 @@ function ListWorkbench({
13724
13831
  onSortChange: sortFields.length ? (sort) => update({ sort }) : void 0,
13725
13832
  onColumnCommand: (command) => {
13726
13833
  if (command.kind === "freeze") {
13834
+ if (command.fixed === null && columns.find((column) => column.id === command.columnId)?.fixed) {
13835
+ if (onColumnCommand) onColumnCommand(command);
13836
+ else setUnfrozenColumns((current) => [.../* @__PURE__ */ new Set([...current, command.columnId])]);
13837
+ }
13727
13838
  update({
13728
13839
  frozen: command.fixed === null ? frozen.filter((id) => id !== command.columnId) : [...frozen.filter((id) => id !== command.columnId), command.columnId]
13729
13840
  });
@@ -13785,7 +13896,10 @@ function ListWorkbench({
13785
13896
  )
13786
13897
  }
13787
13898
  ),
13788
- /* @__PURE__ */ jsxRuntime.jsx(
13899
+ view.group || view.view === "board" && boardView ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pro-row", style: { justifyContent: "space-between" }, children: [
13900
+ footerLeft ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pro-text-sm", children: title }),
13901
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pro-text-sm", children: zh ? `\u5171 ${total} \u6761` : `Total: ${total}` })
13902
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
13789
13903
  Pagination,
13790
13904
  {
13791
13905
  page,
@@ -13852,6 +13966,7 @@ function ListWorkbench({
13852
13966
  onSelect: () => {
13853
13967
  update({ q: "", rules: [], sort: null, group: null, preview: "auto", page: 1 });
13854
13968
  setHiddenColumns([]);
13969
+ setUnfrozenColumns([]);
13855
13970
  }
13856
13971
  }
13857
13972
  ]
@@ -16903,6 +17018,10 @@ function FormRoot({
16903
17018
  const store = stores.get(instance);
16904
17019
  if (!store) throw new Error("Form requires an instance created by useForm");
16905
17020
  const config = useProConfig();
17021
+ const latestSubmit = React2.useRef({ disabled: disabled ?? config.disabled, onFinish });
17022
+ React2.useLayoutEffect(() => {
17023
+ latestSubmit.current = { disabled: disabled ?? config.disabled, onFinish };
17024
+ }, [disabled, config.disabled, onFinish]);
16906
17025
  const initial = React2.useRef(initialValues);
16907
17026
  const submitting = React2.useRef(false);
16908
17027
  const generation = React2.useRef(0);
@@ -16944,9 +17063,9 @@ function FormRoot({
16944
17063
  }
16945
17064
  return;
16946
17065
  }
16947
- if (generation.current !== current) return;
17066
+ if (generation.current !== current || latestSubmit.current.disabled) return;
16948
17067
  try {
16949
- await onFinish?.(values);
17068
+ await latestSubmit.current.onFinish?.(values);
16950
17069
  } catch (error) {
16951
17070
  if (generation.current !== current) return;
16952
17071
  setSubmitError(true);