@a3s-lab/office 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { Fragment as external_react_Fragment, cloneElement, memo, useCallback, u
6
6
  import { convertSpanToShareString, genarate, indexToColumnChar, update as core_update } from "@fortune-sheet/core";
7
7
  import { freezeImportedSpreadsheetCell, normalizeXlsxSemanticColorOrigin, spreadsheetMatrixProfile, withXlsxPatternFill, withoutEditableRange, normalizeSpreadsheetConditionalVisualOptions, xlsxSemanticColorMatchesValue, withXlsxGradientFill, effectiveSpreadsheetPageSetup, attachSpreadsheetShownCommentCells, normalizeXlsxPatternFill, withEditableRange, sameSpreadsheetHistoryValue, isSpreadsheetConditionalComparisonOperator, spreadsheetConditionalIconForValue, defaultSpreadsheetConditionalIconThresholds, xlsxPatternFillTypes, sheetProtectionAuthority, normalizeXlsxGradientFill, xlsxGradientFillFallbackColor, editableRangeCellCount, unlockedCellCount, registerDerivedSpreadsheetMatrix, editableRangeRequiresCredentials, SPREADSHEET_CONDITIONAL_COMPARISON_OPERATORS, normalizeSpreadsheetConditionalIconSetFormat, defaultSpreadsheetDataBarOptions, spreadsheetConditionalThresholdValue, normalizeSheetProtectionAuthority, withSheetProtection, spreadsheetProtectionKey, defaultSpreadsheetColorScaleThresholds, protectedSheetCount, SPREADSHEET_CONDITIONAL_ICON_SETS, withSheetSelectionPermissions, spreadsheetConditionalComparisonNeedsUpperValue, drawSpreadsheetConditionalIcon, spreadsheetConditionalThresholdsEqual } from "./9333.js";
8
8
  import { workSpreadsheetChartSupportsGrouping, spreadsheetFormulaRangesForSelection, createSpreadsheetChartFromSelection, normalizeWorkSpreadsheetDataLabelPosition, normalizeSpreadsheetPrintTitleColumns, normalizeSpreadsheetPrintTitleRows, formulaHasStructuredReference, normalizeWorkSpreadsheetErrorBars, defaultPivotValueCaption, workSpreadsheetChartSupportsBarSpacing, normalizeWorkSpreadsheetChartGrouping, spreadsheetGridSize, normalizeWorkSpreadsheetChartAxisGroup, normalizeWorkSpreadsheetScatterStyle, cloneSparseMatrix, spreadsheetPivotAggregationLabel, workSpreadsheetChartAxisIsCategoryAxis, reconcileSpreadsheetChartPreviews, normalizeWorkSpreadsheetChartAxes, normalizeWorkSpreadsheetDoughnutHoleSize, normalizeWorkSpreadsheetChartLegendPosition, spreadsheetChartCount, workSpreadsheetChartTypeLabel, sparseArrayEntries, normalizeWorkSpreadsheetTrendline, effectiveSpreadsheetCalculationSettings, spreadsheetPivotIntersects, normalizeWorkSpreadsheetDataLabels, spreadsheetPivotOutputContains, normalizeSpreadsheetPrintArea, spreadsheetCellAddress, formatSpreadsheetCellRanges, normalizeWorkSpreadsheetChartLayout, spreadsheetFormulaRangeForCell, workSpreadsheetChartAxisIsValueAxis, normalizeWorkSpreadsheetChartSmoothLines, spreadsheetPivotFilterValueKey, normalizeWorkSpreadsheetBubbleScale, normalizeWorkSpreadsheetChartSeriesStyle, volatileSpreadsheetFormulaFunctions, sparseArrayIndexes, normalizeWorkSpreadsheetBubbleSizeRepresents, workSpreadsheetChartSupportsTrendlines, normalizeWorkSpreadsheetChartLegendOverlay, parseSpreadsheetChartReference, workSpreadsheetChartGroupingIsStacked, normalizeWorkSpreadsheetChartGapWidth, workSpreadsheetChartSupportsSmoothLines, refreshSpreadsheetPivotTables, createSpreadsheetPivotFromSelection, reconcileSpreadsheetPivots, spreadsheetPivotCount, sparseMatrixColumnCount, workSpreadsheetChartSupportsErrorBars, formulaHasExternalReference, isValidSpreadsheetDefinedName, spreadsheetPivotValidation, deleteSpreadsheetPivotTable, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartSupportsSeriesAnalysis, parseSpreadsheetCellRanges, spreadsheetPivotFilterItems, spreadsheetPivotFields, normalizeWorkSpreadsheetChartOverlap, spreadsheetFormulaRangeConflict, spreadsheetSheetsWithChartPreviews, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, workSpreadsheetChartSupportsAxes, normalizeWorkSpreadsheetCombinationSeriesType, normalizeWorkSpreadsheetRadarStyle } from "./8715.js";
9
- import { reconcileSpreadsheetTableCalculatedColumns, spreadsheetTextOrientationFromChoice, xlsxRichTextCellText, isSpreadsheetTextOrientationId, spreadsheetTextOrientationFromAngle, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, spreadsheetCellValueWithDiagonalBorder, spreadsheetUnderlineCellValue, normalizeSpreadsheetDataValidationErrorStyle, normalizeXlsxRichTextEditSource, normalizeXlsxRichTextColor, isHighSurrogate, xlsxNativeFillCellKeys, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, boundedSpreadsheetDataValidationText, normalizeXlsxRichTextCell, activeXlsxNativeFill, spreadsheetUnderlineStyle, normalizeSpreadsheetDateValidationBoundary, deleteXlsxNativeFills, spreadsheetTextOrientationCellStyle, spreadsheetTextOrientationChoiceFromCell, normalizeXlsxRichTextRun, spreadsheetDiagonalBorderFromCellValue, sameXlsxRichTextRunStyle, isLowSurrogate, spreadsheetTextOrientationFromCell, coalesceXlsxRichTextRuns, patchSpreadsheetRichTextFontRuns, fillSpreadsheetTableCalculatedColumns, validXlsxRichText, replaceOfficeSpreadsheetCollaboration as replaceWorkOfficeSpreadsheetCollaboration, isSpreadsheetUnderlineStyle, spreadsheetVisibleTextRotationFromCell } from "./3266.js";
9
+ import { reconcileSpreadsheetTableCalculatedColumns, isSpreadsheetTextOrientationId, spreadsheetTextOrientationFromChoice, xlsxRichTextCellText, spreadsheetTextOrientationFromAngle, replaceOfficeSpreadsheetCollaboration as replaceWorkOfficeSpreadsheetCollaboration, spreadsheetCellValueWithDiagonalBorder, spreadsheetUnderlineCellValue, normalizeSpreadsheetDataValidationErrorStyle, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, spreadsheetTableTotalsFunctionLabel, withDefaultSpreadsheetTableTotalsLabel, normalizeXlsxRichTextEditSource, spreadsheetTableTotalsColumnPatch, normalizeXlsxRichTextColor, isHighSurrogate, xlsxNativeFillCellKeys, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, SPREADSHEET_TABLE_TOTALS_FUNCTIONS, boundedSpreadsheetDataValidationText, normalizeXlsxRichTextCell, activeXlsxNativeFill, spreadsheetUnderlineStyle, normalizeSpreadsheetDateValidationBoundary, deleteXlsxNativeFills, spreadsheetTextOrientationCellStyle, spreadsheetTextOrientationChoiceFromCell, normalizeXlsxRichTextRun, spreadsheetDiagonalBorderFromCellValue, sameXlsxRichTextRunStyle, isLowSurrogate, spreadsheetTableTotalsRowHasContent, reconcileSpreadsheetTableTotalsColumns, spreadsheetTextOrientationFromCell, coalesceXlsxRichTextRuns, fillSpreadsheetTableCalculatedColumns, synchronizeSpreadsheetTableTotalsRow, validXlsxRichText, patchSpreadsheetRichTextFontRuns, isSpreadsheetUnderlineStyle, spreadsheetVisibleTextRotationFromCell } from "./3266.js";
10
10
  import { moveOfficeMenuFocus, stepOfficeZoom, useOfficeRemoteParticipants, Popover, Dialog, officePresenceColor, useOfficePublishPresenceLocation, button_Button, icon_button_IconButton, moveOfficeGridMenuFocus, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom } from "./0~8136.js";
11
11
  import { isOfficeKernelSpreadsheetError, normalizeCssColor, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, createOfficeId as createWorkId } from "./4121.js";
12
12
  import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeDialog, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown } from "./0~7048.js";
@@ -3770,6 +3770,7 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3770
3770
  let tables = source.tables;
3771
3771
  const reconcileColumns = new Set();
3772
3772
  const calculatedColumnRows = new Map();
3773
+ const editedTotalsColumns = new Map();
3773
3774
  if (!operations.length) for (const table of tables)reconcileColumns.add(table.id);
3774
3775
  for (const operation of sheetOperations){
3775
3776
  const structure = spreadsheetTableStructureChangeFromOperation(operation);
@@ -3797,7 +3798,17 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3797
3798
  const coordinate = spreadsheetCellCoordinateFromOperation(operation);
3798
3799
  if (!coordinate) continue;
3799
3800
  const table = tables.find((candidate)=>spreadsheetTableContainsCell(candidate, coordinate.row, coordinate.column));
3800
- if (table) reconcileColumns.add(table.id);
3801
+ if (table) {
3802
+ reconcileColumns.add(table.id);
3803
+ if (table.totalsRow && coordinate.row === table.range.row[1]) {
3804
+ const offset = coordinate.column - table.range.column[0];
3805
+ if (offset >= 0 && offset < table.columns.length) {
3806
+ const offsets = editedTotalsColumns.get(table.id) ?? new Set();
3807
+ offsets.add(offset);
3808
+ editedTotalsColumns.set(table.id, offsets);
3809
+ }
3810
+ }
3811
+ }
3801
3812
  }
3802
3813
  const updates = [];
3803
3814
  const readCell = spreadsheetSheetCellReader(sheet);
@@ -3808,7 +3819,11 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3808
3819
  ...table,
3809
3820
  columns
3810
3821
  });
3811
- if (table.headerRow) for (const [offset, column] of calculatedColumns.entries()){
3822
+ const reconciledColumns = reconcileSpreadsheetTableTotalsColumns(sheet, {
3823
+ ...table,
3824
+ columns: calculatedColumns
3825
+ }, editedTotalsColumns.get(table.id));
3826
+ if (table.headerRow) for (const [offset, column] of reconciledColumns.entries()){
3812
3827
  const cellColumn = table.range.column[0] + offset;
3813
3828
  if (spreadsheetCellText(readCell(table.range.row[0], cellColumn)) !== column.name) updates.push({
3814
3829
  column: cellColumn,
@@ -3816,9 +3831,9 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3816
3831
  row: table.range.row[0]
3817
3832
  });
3818
3833
  }
3819
- return sameSpreadsheetTableColumns(table.columns, calculatedColumns) ? table : {
3834
+ return sameSpreadsheetTableColumns(table.columns, reconciledColumns) ? table : {
3820
3835
  ...table,
3821
- columns: calculatedColumns
3836
+ columns: reconciledColumns
3822
3837
  };
3823
3838
  });
3824
3839
  let withCalculatedColumns = sheet;
@@ -3829,8 +3844,13 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3829
3844
  ]);
3830
3845
  }
3831
3846
  const withHeaders = stampSpreadsheetTableHeaderCells(withCalculatedColumns, updates);
3847
+ let withTotals = withHeaders;
3848
+ for (const table of tables){
3849
+ const previous = source.tables.find((candidate)=>candidate.id === table.id);
3850
+ withTotals = synchronizeSpreadsheetTableTotalsRow(withTotals, previous, table);
3851
+ }
3832
3852
  return {
3833
- ...withHeaders,
3853
+ ...withTotals,
3834
3854
  tables: tables.length ? tables : void 0
3835
3855
  };
3836
3856
  });
@@ -4021,6 +4041,7 @@ function canonicalSpreadsheetTableColumns(table, readCell) {
4021
4041
  }
4022
4042
  observed.add(candidate.toLocaleLowerCase());
4023
4043
  return {
4044
+ ...table.columns[offset] ?? {},
4024
4045
  name: candidate
4025
4046
  };
4026
4047
  });
@@ -4146,7 +4167,7 @@ function truncateSpreadsheetTableColumnName(value, maximum) {
4146
4167
  return Array.from(value).slice(0, maximum).join('');
4147
4168
  }
4148
4169
  function sameSpreadsheetTableColumns(left, right) {
4149
- return left.length === right.length && left.every((column, index)=>column.name === right[index]?.name && column.calculatedFormula === right[index]?.calculatedFormula);
4170
+ return left.length === right.length && left.every((column, index)=>column.name === right[index]?.name && column.calculatedFormula === right[index]?.calculatedFormula && column.totalsFunction === right[index]?.totalsFunction && column.totalsLabel === right[index]?.totalsLabel && column.totalsFormula === right[index]?.totalsFormula);
4150
4171
  }
4151
4172
  function spreadsheet_table_reconciliation_isRecord(value) {
4152
4173
  return Boolean(value && 'object' == typeof value && !Array.isArray(value));
@@ -9378,7 +9399,8 @@ function createSpreadsheetTableDialogSource(content, target) {
9378
9399
  sheetName: sheet.name,
9379
9400
  value: {
9380
9401
  headerRow: request.headerRow,
9381
- rangeReference
9402
+ rangeReference,
9403
+ totalsRow: false
9382
9404
  }
9383
9405
  };
9384
9406
  }
@@ -9390,13 +9412,25 @@ function validateSpreadsheetTableRequest(content, request) {
9390
9412
  const sheet = content.sheets.find((candidate)=>candidate.id === request.sheetId);
9391
9413
  if (!sheet) return tableError('sheet-not-found');
9392
9414
  if (sheet.isPivotTable || sheet.pivotTables?.length) return tableError('pivot-table');
9393
- const range = spreadsheet_cell_range_normalizeSpreadsheetCellRange(request.range);
9394
- if (!range) return tableError('invalid-range');
9415
+ const bodyRange = spreadsheet_cell_range_normalizeSpreadsheetCellRange(request.range);
9416
+ if (!bodyRange) return tableError('invalid-range');
9417
+ const totalsRow = true === request.totalsRow;
9418
+ const range = totalsRow ? {
9419
+ row: [
9420
+ bodyRange.row[0],
9421
+ bodyRange.row[1] + 1
9422
+ ],
9423
+ column: [
9424
+ ...bodyRange.column
9425
+ ]
9426
+ } : bodyRange;
9395
9427
  const name = request.name.trim();
9396
9428
  if (!validSpreadsheetTableName(name)) return tableError('invalid-name');
9397
9429
  if (spreadsheetTableNameExists(content, name)) return tableError('name-conflict');
9398
9430
  const bounds = spreadsheetSheetBounds(sheet);
9399
- if (range.row[1] > bounds.lastRow || range.column[1] > bounds.lastColumn) return tableError('out-of-bounds');
9431
+ const configuredRows = Number(sheet.row);
9432
+ const lastAllowedRow = Number.isFinite(configuredRows) && configuredRows > 0 ? bounds.lastRow : Math.max(bounds.lastRow, range.row[1]);
9433
+ if (bodyRange.row[0] > lastAllowedRow || bodyRange.row[1] > lastAllowedRow || bodyRange.column[0] > bounds.lastColumn || bodyRange.column[1] > bounds.lastColumn || range.row[1] >= OFFICE_KERNEL_SPREADSHEET_MAX_ROWS) return tableError('out-of-bounds');
9400
9434
  const height = range.row[1] - range.row[0] + 1;
9401
9435
  if (height <= Number(request.headerRow)) return tableError('invalid-range');
9402
9436
  if (spreadsheet_cell_range_spreadsheetCellRangeArea(range) > 100000) return tableError('range-too-large');
@@ -9415,8 +9449,27 @@ function validateSpreadsheetTableRequest(content, request) {
9415
9449
  number: 2
9416
9450
  };
9417
9451
  if (!validSpreadsheetTableStyle(style)) return tableError('invalid-style');
9418
- const columns = spreadsheetTableColumnNames(sheet, range, request.headerRow);
9452
+ const columns = spreadsheetTableColumnNames(sheet, bodyRange, request.headerRow);
9419
9453
  if (!columns.length || columns.some((column)=>!validTableColumnName(column))) return tableError('invalid-column-name');
9454
+ const totalsColumns = spreadsheetTableTotalsColumnPatch(columns.map((name)=>({
9455
+ name
9456
+ })), request.totalsColumns);
9457
+ if (!totalsColumns) return tableError('invalid-totals');
9458
+ const candidate = {
9459
+ id: '__validation__',
9460
+ name,
9461
+ range,
9462
+ columns: withDefaultSpreadsheetTableTotalsLabel(totalsColumns, totalsRow),
9463
+ filters: [],
9464
+ headerRow: request.headerRow,
9465
+ totalsRow,
9466
+ style,
9467
+ showFirstColumn: false,
9468
+ showLastColumn: false,
9469
+ showRowStripes: 'none' !== style.family,
9470
+ showColumnStripes: false
9471
+ };
9472
+ if (totalsRow && spreadsheetTableTotalsRowHasContent(sheet, candidate)) return tableError('totals-row-content');
9420
9473
  return {
9421
9474
  columns,
9422
9475
  name,
@@ -9432,16 +9485,19 @@ function applySpreadsheetTable(content, request) {
9432
9485
  family: 'medium',
9433
9486
  number: 2
9434
9487
  };
9488
+ const totalsRow = true === request.totalsRow;
9489
+ const totalsColumns = spreadsheetTableTotalsColumnPatch(validation.columns.map((name)=>({
9490
+ name
9491
+ })), request.totalsColumns);
9492
+ if (!totalsColumns) return null;
9435
9493
  const tableWithoutCalculatedColumns = {
9436
9494
  id: createWorkId('spreadsheet-table'),
9437
9495
  name: validation.name,
9438
9496
  range: cloneRange(validation.range),
9439
- columns: validation.columns.map((name)=>({
9440
- name
9441
- })),
9497
+ columns: withDefaultSpreadsheetTableTotalsLabel(totalsColumns, totalsRow),
9442
9498
  filters: [],
9443
9499
  headerRow: request.headerRow,
9444
- totalsRow: false,
9500
+ totalsRow,
9445
9501
  style,
9446
9502
  showFirstColumn: false,
9447
9503
  showLastColumn: false,
@@ -9453,10 +9509,14 @@ function applySpreadsheetTable(content, request) {
9453
9509
  columns: reconcileSpreadsheetTableCalculatedColumns(validation.sheet, tableWithoutCalculatedColumns)
9454
9510
  };
9455
9511
  const nextSheet = request.headerRow ? stampSpreadsheetTableHeaders(validation.sheet, validation.range, validation.columns) : validation.sheet;
9512
+ const sizedSheet = ensureSpreadsheetTableRowCapacity(nextSheet, rangeEndRow(table));
9513
+ const synchronizedSheet = synchronizeSpreadsheetTableTotalsRow(sizedSheet, void 0, table, {
9514
+ force: totalsRow
9515
+ });
9456
9516
  return replaceSpreadsheetTableSheet(content, {
9457
- ...nextSheet,
9517
+ ...synchronizedSheet,
9458
9518
  tables: [
9459
- ...nextSheet.tables ?? [],
9519
+ ...synchronizedSheet.tables ?? [],
9460
9520
  table
9461
9521
  ]
9462
9522
  });
@@ -9474,20 +9534,46 @@ function updateSpreadsheetTable(content, sheetId, tableId, patch) {
9474
9534
  const showRowStripes = patch.showRowStripes ?? table.showRowStripes;
9475
9535
  const showColumnStripes = patch.showColumnStripes ?? table.showColumnStripes;
9476
9536
  if ('none' === style.family && (showFirstColumn || showLastColumn || showRowStripes || showColumnStripes)) return null;
9477
- const tables = sheet.tables?.map((candidate)=>candidate.id === tableId ? {
9478
- ...candidate,
9479
- name,
9480
- ...void 0 === patch.name ? {} : {
9481
- displayName: void 0
9482
- },
9483
- style,
9484
- showFirstColumn,
9485
- showLastColumn,
9486
- showRowStripes,
9487
- showColumnStripes
9488
- } : candidate);
9537
+ const totalsRow = patch.totalsRow ?? table.totalsRow;
9538
+ const range = cloneRange(table.range);
9539
+ if (totalsRow !== table.totalsRow) range.row[1] += totalsRow ? 1 : -1;
9540
+ if (range.row[0] < 0 || range.row[1] < range.row[0] || range.row[1] >= OFFICE_KERNEL_SPREADSHEET_MAX_ROWS || range.row[1] - range.row[0] + 1 <= Number(table.headerRow) + Number(totalsRow)) return null;
9541
+ const totalsColumns = spreadsheetTableTotalsColumnPatch(table.columns, patch.totalsColumns);
9542
+ if (!totalsColumns) return null;
9543
+ const nextColumns = withDefaultSpreadsheetTableTotalsLabel(totalsColumns, totalsRow);
9544
+ const nextTable = {
9545
+ ...table,
9546
+ name,
9547
+ ...void 0 === patch.name ? {} : {
9548
+ displayName: void 0
9549
+ },
9550
+ range,
9551
+ columns: nextColumns,
9552
+ totalsRow,
9553
+ style,
9554
+ showFirstColumn,
9555
+ showLastColumn,
9556
+ showRowStripes,
9557
+ showColumnStripes
9558
+ };
9559
+ if (totalsRow && !table.totalsRow && spreadsheetTableTotalsRowHasContent(sheet, nextTable)) return null;
9560
+ if (totalsRow && !table.totalsRow) {
9561
+ const totalsGrowthRange = {
9562
+ row: [
9563
+ table.range.row[1] + 1,
9564
+ range.row[1]
9565
+ ],
9566
+ column: [
9567
+ ...range.column
9568
+ ]
9569
+ };
9570
+ if (spreadsheet_cell_range_spreadsheetCellRangeArea(range) > 100000 || spreadsheetTableGrowthConflicts(sheet, tableId, totalsGrowthRange)) return null;
9571
+ }
9572
+ const tables = sheet.tables?.map((candidate)=>candidate.id === tableId ? nextTable : candidate);
9573
+ const sizedSheet = ensureSpreadsheetTableRowCapacity(sheet, rangeEndRow(nextTable));
9574
+ const synchronizedSheet = synchronizeSpreadsheetTableTotalsRow(sizedSheet, table, nextTable);
9489
9575
  return replaceSpreadsheetTableSheet(content, {
9490
- ...sheet,
9576
+ ...synchronizedSheet,
9491
9577
  tables
9492
9578
  });
9493
9579
  }
@@ -9635,6 +9721,16 @@ function spreadsheetTableIntersectsMerge(sheet, range) {
9635
9721
  ]
9636
9722
  }));
9637
9723
  }
9724
+ function spreadsheetTableGrowthConflicts(sheet, tableId, growthRange) {
9725
+ if (spreadsheetTableIntersectsMerge(sheet, growthRange) || (sheet.tables ?? []).some((candidate)=>candidate.id !== tableId && spreadsheetCellRangesIntersect(candidate.range, growthRange)) || !canMutateSpreadsheetCellRanges(sheet, [
9726
+ growthRange
9727
+ ])) return true;
9728
+ const autoFilter = spreadsheet_cell_range_normalizeSpreadsheetCellRange(sheet.filter_select ?? {
9729
+ row: [],
9730
+ column: []
9731
+ });
9732
+ return Boolean(autoFilter && spreadsheetCellRangesIntersect(autoFilter, growthRange));
9733
+ }
9638
9734
  function replaceSpreadsheetTableSheet(content, nextSheet) {
9639
9735
  return {
9640
9736
  ...content,
@@ -9675,6 +9771,7 @@ function tableError(code) {
9675
9771
  'invalid-name': '表格名称必须是有效且不类似单元格引用的标识符。',
9676
9772
  'invalid-range': '表格至少需要一行数据。',
9677
9773
  'invalid-style': '表格样式身份无效。',
9774
+ 'invalid-totals': '汇总行函数、标签或公式无效。',
9678
9775
  'merged-range': '请先取消表格区域内的合并单元格。',
9679
9776
  'name-conflict': '表格名称已被其他表格或名称使用。',
9680
9777
  'out-of-bounds': '表格区域超出当前工作表边界。',
@@ -9682,7 +9779,8 @@ function tableError(code) {
9682
9779
  'protected-range': '受保护或只读区域不能创建表格。',
9683
9780
  'range-too-large': `一次最多可创建 ${100000..toLocaleString()} 个单元格的表格。`,
9684
9781
  'sheet-not-found': '找不到目标工作表。',
9685
- 'table-overlap': '表格区域不能与其他表格重叠。'
9782
+ 'table-overlap': '表格区域不能与其他表格重叠。',
9783
+ 'totals-row-content': '汇总行目标单元格已有内容,请先清空后再启用。'
9686
9784
  };
9687
9785
  return {
9688
9786
  code,
@@ -9690,6 +9788,20 @@ function tableError(code) {
9690
9788
  ok: false
9691
9789
  };
9692
9790
  }
9791
+ function rangeEndRow(table) {
9792
+ return table.range.row[1];
9793
+ }
9794
+ function ensureSpreadsheetTableRowCapacity(sheet, lastRow) {
9795
+ if (!Number.isSafeInteger(lastRow) || lastRow < 0) return sheet;
9796
+ const configured = Number(sheet.row);
9797
+ if (!Number.isFinite(configured) || configured <= 0) return sheet;
9798
+ const required = lastRow + 1;
9799
+ if (required <= configured) return sheet;
9800
+ return {
9801
+ ...sheet,
9802
+ row: Math.min(OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, required)
9803
+ };
9804
+ }
9693
9805
  function createSpreadsheetTableExtension() {
9694
9806
  return createOfficeEditorExtension({
9695
9807
  name: 'spreadsheetTables',
@@ -9742,7 +9854,8 @@ function updateSpreadsheetTableCommand(context, sheetId, tableId, patch) {
9742
9854
  if (!context.editable) return false;
9743
9855
  const next = updateSpreadsheetTable(context.content, sheetId, tableId, patch);
9744
9856
  if (!next) return false;
9745
- context.onChange(next);
9857
+ const selection = context.workbook?.getSelection()?.at(-1) ?? (context.selection?.sheetId === sheetId ? context.selection.selection : void 0);
9858
+ context.onChange(spreadsheetContentWithSelection(next, sheetId, selection));
9746
9859
  return true;
9747
9860
  }
9748
9861
  function canConvertSpreadsheetTableToRange(context, sheetId, tableId) {
@@ -13606,6 +13719,16 @@ function SpreadsheetTableDesignRibbon({ can, commands, sheetId, table }) {
13606
13719
  })
13607
13720
  ]
13608
13721
  }),
13722
+ /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
13723
+ label: "汇总行",
13724
+ priority: "high",
13725
+ children: /*#__PURE__*/ jsx(SpreadsheetTableTotalsMenu, {
13726
+ can: can,
13727
+ commands: commands,
13728
+ sheetId: sheetId,
13729
+ table: table
13730
+ })
13731
+ }),
13609
13732
  /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
13610
13733
  label: "表格样式",
13611
13734
  priority: "high",
@@ -13675,6 +13798,225 @@ function SpreadsheetTableDesignRibbon({ can, commands, sheetId, table }) {
13675
13798
  ]
13676
13799
  });
13677
13800
  }
13801
+ const totalsFunctionOptions = [
13802
+ {
13803
+ value: 'none',
13804
+ label: '不汇总'
13805
+ },
13806
+ ...SPREADSHEET_TABLE_TOTALS_FUNCTIONS.map((value)=>({
13807
+ value,
13808
+ label: spreadsheetTableTotalsFunctionLabel(value)
13809
+ }))
13810
+ ];
13811
+ function SpreadsheetTableTotalsMenu({ can, commands, sheetId, table }) {
13812
+ const [drafts, setDrafts] = useState({});
13813
+ useEffect(()=>{
13814
+ const next = {};
13815
+ table.columns.forEach((column, offset)=>{
13816
+ next[offset] = {
13817
+ formula: column.totalsFormula ?? '',
13818
+ label: column.totalsLabel ?? ''
13819
+ };
13820
+ });
13821
+ setDrafts(next);
13822
+ }, [
13823
+ table.id,
13824
+ table.columns
13825
+ ]);
13826
+ const patchColumn = (offset, patch)=>{
13827
+ const designPatch = {
13828
+ totalsColumns: {
13829
+ [offset]: patch
13830
+ }
13831
+ };
13832
+ if (!can.updateTable(sheetId, table.id, designPatch) || !commands.updateTable(sheetId, table.id, designPatch)) showToast('汇总行设置无效,请检查函数、标签或公式。', 'error');
13833
+ };
13834
+ const toggleTotalsRow = ()=>{
13835
+ const patch = {
13836
+ totalsRow: !table.totalsRow
13837
+ };
13838
+ if (!can.updateTable(sheetId, table.id, patch) || !commands.updateTable(sheetId, table.id, patch)) showToast(table.totalsRow ? '无法关闭汇总行。' : '汇总行目标区域已有内容,请先清空该行。', 'error');
13839
+ };
13840
+ return /*#__PURE__*/ jsx(Popover, {
13841
+ label: "汇总行",
13842
+ panelLabel: "表格汇总行设置",
13843
+ panelRole: "dialog",
13844
+ portal: true,
13845
+ className: "work-spreadsheet-table-totals-root",
13846
+ panelClassName: "work-spreadsheet-table-totals-menu",
13847
+ placement: "bottom-end",
13848
+ trigger: (triggerProps, { open })=>/*#__PURE__*/ jsxs("button", {
13849
+ ...triggerProps,
13850
+ className: `with-label work-spreadsheet-table-totals-trigger${open || table.totalsRow ? ' active' : ''}`,
13851
+ "aria-pressed": table.totalsRow,
13852
+ title: "设置表格汇总行",
13853
+ children: [
13854
+ /*#__PURE__*/ jsx(Calculator, {
13855
+ size: 19
13856
+ }),
13857
+ /*#__PURE__*/ jsx("span", {
13858
+ children: "汇总行"
13859
+ })
13860
+ ]
13861
+ }),
13862
+ children: (close)=>/*#__PURE__*/ jsxs("div", {
13863
+ className: "work-spreadsheet-table-totals-content",
13864
+ children: [
13865
+ /*#__PURE__*/ jsx(OfficeCheckbox, {
13866
+ ariaLabel: "启用汇总行",
13867
+ checked: table.totalsRow,
13868
+ disabled: !table.totalsRow && !can.updateTable(sheetId, table.id, {
13869
+ totalsRow: true
13870
+ }),
13871
+ onCheckedChange: ()=>{
13872
+ toggleTotalsRow();
13873
+ },
13874
+ children: "启用汇总行"
13875
+ }),
13876
+ /*#__PURE__*/ jsx("p", {
13877
+ className: "work-spreadsheet-table-totals-hint",
13878
+ children: "汇总行位于表格末尾,函数会随筛选结果更新。"
13879
+ }),
13880
+ /*#__PURE__*/ jsx("div", {
13881
+ className: "work-spreadsheet-table-totals-columns",
13882
+ children: table.columns.map((column, offset)=>{
13883
+ const selected = column.totalsFormula ? 'custom' : column.totalsFunction ?? 'none';
13884
+ const draft = drafts[offset] ?? {
13885
+ formula: column.totalsFormula ?? '',
13886
+ label: column.totalsLabel ?? ''
13887
+ };
13888
+ const formulaEnabled = 'custom' === selected;
13889
+ const labelEnabled = 'none' === selected;
13890
+ return /*#__PURE__*/ jsxs("div", {
13891
+ className: "work-spreadsheet-table-totals-column",
13892
+ children: [
13893
+ /*#__PURE__*/ jsx("strong", {
13894
+ children: column.name
13895
+ }),
13896
+ /*#__PURE__*/ jsxs("label", {
13897
+ children: [
13898
+ /*#__PURE__*/ jsx("span", {
13899
+ children: "函数"
13900
+ }),
13901
+ /*#__PURE__*/ jsx("select", {
13902
+ "aria-label": `${column.name} 汇总函数`,
13903
+ "data-selected-value": selected,
13904
+ disabled: !table.totalsRow,
13905
+ value: selected,
13906
+ onChange: (event)=>{
13907
+ const value = event.currentTarget.value;
13908
+ if ('none' === value) patchColumn(offset, {
13909
+ totalsFunction: null,
13910
+ totalsFormula: null
13911
+ });
13912
+ else if ('custom' === value) {
13913
+ const formula = draft.formula || `=SUM(${table.name}[${escapeTotalsColumnName(column.name)}])`;
13914
+ setDrafts((current)=>({
13915
+ ...current,
13916
+ [offset]: {
13917
+ ...draft,
13918
+ formula
13919
+ }
13920
+ }));
13921
+ patchColumn(offset, {
13922
+ totalsFunction: 'custom',
13923
+ totalsFormula: formula,
13924
+ totalsLabel: null
13925
+ });
13926
+ } else patchColumn(offset, {
13927
+ totalsFunction: value,
13928
+ totalsFormula: null,
13929
+ totalsLabel: null
13930
+ });
13931
+ },
13932
+ children: totalsFunctionOptions.map((option)=>/*#__PURE__*/ jsx("option", {
13933
+ value: option.value,
13934
+ children: option.label
13935
+ }, option.value))
13936
+ })
13937
+ ]
13938
+ }),
13939
+ /*#__PURE__*/ jsxs("label", {
13940
+ children: [
13941
+ /*#__PURE__*/ jsx("span", {
13942
+ children: "标签"
13943
+ }),
13944
+ /*#__PURE__*/ jsx("input", {
13945
+ "aria-label": `${column.name} 汇总标签`,
13946
+ disabled: !table.totalsRow || !labelEnabled,
13947
+ value: draft.label,
13948
+ onChange: (event)=>{
13949
+ const value = event.currentTarget.value;
13950
+ setDrafts((current)=>({
13951
+ ...current,
13952
+ [offset]: {
13953
+ ...draft,
13954
+ label: value
13955
+ }
13956
+ }));
13957
+ },
13958
+ onBlur: ()=>{
13959
+ if (!labelEnabled) return;
13960
+ patchColumn(offset, {
13961
+ totalsLabel: draft.label.trim() || null
13962
+ });
13963
+ },
13964
+ onKeyDown: (event)=>{
13965
+ if ('Enter' === event.key) event.currentTarget.blur();
13966
+ }
13967
+ })
13968
+ ]
13969
+ }),
13970
+ /*#__PURE__*/ jsxs("label", {
13971
+ children: [
13972
+ /*#__PURE__*/ jsx("span", {
13973
+ children: "自定义公式"
13974
+ }),
13975
+ /*#__PURE__*/ jsx("input", {
13976
+ "aria-label": `${column.name} 汇总公式`,
13977
+ disabled: !table.totalsRow || !formulaEnabled,
13978
+ placeholder: "=SUM(Table[Column])",
13979
+ value: draft.formula,
13980
+ onChange: (event)=>{
13981
+ const value = event.currentTarget.value;
13982
+ setDrafts((current)=>({
13983
+ ...current,
13984
+ [offset]: {
13985
+ ...draft,
13986
+ formula: value
13987
+ }
13988
+ }));
13989
+ },
13990
+ onBlur: ()=>{
13991
+ if (!formulaEnabled) return;
13992
+ patchColumn(offset, {
13993
+ totalsFunction: 'custom',
13994
+ totalsFormula: draft.formula.trim() || null
13995
+ });
13996
+ },
13997
+ onKeyDown: (event)=>{
13998
+ if ('Enter' === event.key) event.currentTarget.blur();
13999
+ }
14000
+ })
14001
+ ]
14002
+ })
14003
+ ]
14004
+ }, `${table.id}-${offset}`);
14005
+ })
14006
+ }),
14007
+ /*#__PURE__*/ jsx("button", {
14008
+ type: "button",
14009
+ className: "work-spreadsheet-table-totals-close",
14010
+ onClick: close,
14011
+ children: "完成"
14012
+ })
14013
+ ]
14014
+ })
14015
+ });
14016
+ }
14017
+ function escapeTotalsColumnName(value) {
14018
+ return value.replaceAll(']', ']]');
14019
+ }
13678
14020
  function SpreadsheetTableNameControl({ can, commands, sheetId, table }) {
13679
14021
  const [name, setName] = useState(table.name);
13680
14022
  useEffect(()=>setName(table.name), [
@@ -25384,6 +25726,15 @@ function SpreadsheetTableDialog({ source, restoreFocusTarget, onApply, onClose,
25384
25726
  headerRow
25385
25727
  })),
25386
25728
  children: "表包含标题"
25729
+ }),
25730
+ /*#__PURE__*/ jsx(OfficeCheckbox, {
25731
+ ariaLabel: "启用汇总行",
25732
+ checked: true === value.totalsRow,
25733
+ onCheckedChange: (totalsRow)=>setValue((current)=>({
25734
+ ...current,
25735
+ totalsRow
25736
+ })),
25737
+ children: "启用汇总行"
25387
25738
  })
25388
25739
  ]
25389
25740
  })
@@ -25471,7 +25822,8 @@ function useSpreadsheetTable({ commandsRef, contentRef, focusGrid, getGridFocusT
25471
25822
  headerRow: value.headerRow,
25472
25823
  name: source.name,
25473
25824
  range,
25474
- sheetId: source.sheetId
25825
+ sheetId: source.sheetId,
25826
+ totalsRow: true === value.totalsRow
25475
25827
  }) ?? false;
25476
25828
  appliedRef.current = handled;
25477
25829
  } finally{
@@ -25492,7 +25844,8 @@ function spreadsheetTableValidationMessage(content, source, value) {
25492
25844
  headerRow: value.headerRow,
25493
25845
  name: source.name,
25494
25846
  range,
25495
- sheetId: source.sheetId
25847
+ sheetId: source.sheetId,
25848
+ totalsRow: true === value.totalsRow
25496
25849
  });
25497
25850
  }
25498
25851
  const MAXIMUM_BUFFERED_SPREADSHEET_OPERATIONS = 10001;