@a3s-lab/office 0.35.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 { spreadsheetTextOrientationFromChoice, isSpreadsheetTextOrientationId, xlsxRichTextCellText, spreadsheetTextOrientationFromAngle, spreadsheetCellValueWithDiagonalBorder, spreadsheetUnderlineCellValue, normalizeSpreadsheetDataValidationErrorStyle, normalizeXlsxRichTextEditSource, normalizeXlsxRichTextColor, isHighSurrogate, xlsxNativeFillCellKeys, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, boundedSpreadsheetDataValidationText, normalizeSpreadsheetDateValidationBoundary, normalizeXlsxRichTextCell, activeXlsxNativeFill, spreadsheetUnderlineStyle, deleteXlsxNativeFills, spreadsheetTextOrientationCellStyle, spreadsheetTextOrientationChoiceFromCell, normalizeXlsxRichTextRun, spreadsheetDiagonalBorderFromCellValue, sameXlsxRichTextRunStyle, isLowSurrogate, spreadsheetTextOrientationFromCell, coalesceXlsxRichTextRuns, patchSpreadsheetRichTextFontRuns, replaceOfficeSpreadsheetCollaboration as replaceWorkOfficeSpreadsheetCollaboration, validXlsxRichText, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, 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";
@@ -3769,12 +3769,23 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3769
3769
  const sheetOperations = operations.filter((operation)=>operation.id === source.id);
3770
3770
  let tables = source.tables;
3771
3771
  const reconcileColumns = new Set();
3772
+ const calculatedColumnRows = new Map();
3773
+ const editedTotalsColumns = new Map();
3772
3774
  if (!operations.length) for (const table of tables)reconcileColumns.add(table.id);
3773
3775
  for (const operation of sheetOperations){
3774
3776
  const structure = spreadsheetTableStructureChangeFromOperation(operation);
3775
3777
  if (structure) {
3778
+ transformCalculatedColumnRows(calculatedColumnRows, structure);
3776
3779
  const transformed = [];
3777
3780
  for (const table of tables){
3781
+ if ('row' === structure.axis && 'insert' === structure.kind) {
3782
+ const insertedRows = rowsInsertedIntoSpreadsheetTableBody(table, structure);
3783
+ if (insertedRows.length) {
3784
+ const rows = calculatedColumnRows.get(table.id) ?? new Set();
3785
+ for (const row of insertedRows)rows.add(row);
3786
+ calculatedColumnRows.set(table.id, rows);
3787
+ }
3788
+ }
3778
3789
  const next = transformSpreadsheetTableStructure(table, structure);
3779
3790
  if (next) {
3780
3791
  transformed.push(next);
@@ -3786,15 +3797,33 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3786
3797
  }
3787
3798
  const coordinate = spreadsheetCellCoordinateFromOperation(operation);
3788
3799
  if (!coordinate) continue;
3789
- const table = tables.find((candidate)=>candidate.headerRow && candidate.range.row[0] === coordinate.row && coordinate.column >= candidate.range.column[0] && coordinate.column <= candidate.range.column[1]);
3790
- if (table) reconcileColumns.add(table.id);
3800
+ const table = tables.find((candidate)=>spreadsheetTableContainsCell(candidate, coordinate.row, coordinate.column));
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
+ }
3791
3812
  }
3792
3813
  const updates = [];
3793
3814
  const readCell = spreadsheetSheetCellReader(sheet);
3794
3815
  tables = tables.map((table)=>{
3795
3816
  if (!reconcileColumns.has(table.id)) return table;
3796
3817
  const columns = canonicalSpreadsheetTableColumns(table, readCell);
3797
- if (table.headerRow) for (const [offset, column] of columns.entries()){
3818
+ const calculatedColumns = reconcileSpreadsheetTableCalculatedColumns(sheet, {
3819
+ ...table,
3820
+ columns
3821
+ });
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()){
3798
3827
  const cellColumn = table.range.column[0] + offset;
3799
3828
  if (spreadsheetCellText(readCell(table.range.row[0], cellColumn)) !== column.name) updates.push({
3800
3829
  column: cellColumn,
@@ -3802,14 +3831,26 @@ function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations
3802
3831
  row: table.range.row[0]
3803
3832
  });
3804
3833
  }
3805
- return sameSpreadsheetTableColumns(table.columns, columns) ? table : {
3834
+ return sameSpreadsheetTableColumns(table.columns, reconciledColumns) ? table : {
3806
3835
  ...table,
3807
- columns
3836
+ columns: reconciledColumns
3808
3837
  };
3809
3838
  });
3810
- const withHeaders = stampSpreadsheetTableHeaderCells(sheet, updates);
3839
+ let withCalculatedColumns = sheet;
3840
+ for (const table of tables){
3841
+ const rows = calculatedColumnRows.get(table.id);
3842
+ if (rows?.size) withCalculatedColumns = fillSpreadsheetTableCalculatedColumns(withCalculatedColumns, table, [
3843
+ ...rows
3844
+ ]);
3845
+ }
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
+ }
3811
3852
  return {
3812
- ...withHeaders,
3853
+ ...withTotals,
3813
3854
  tables: tables.length ? tables : void 0
3814
3855
  };
3815
3856
  });
@@ -3870,6 +3911,39 @@ function transformSpreadsheetTableStructure(table, change) {
3870
3911
  filters: filtersAfterSpreadsheetTableColumnDeletion(table.filters, transformed.offset, removed)
3871
3912
  };
3872
3913
  }
3914
+ function spreadsheetTableContainsCell(table, row, column) {
3915
+ return row >= table.range.row[0] && row <= table.range.row[1] && column >= table.range.column[0] && column <= table.range.column[1];
3916
+ }
3917
+ function rowsInsertedIntoSpreadsheetTableBody(table, change) {
3918
+ if ('row' !== change.axis) return [];
3919
+ const transformed = transformSpreadsheetTableAxisForInsertion(table.range.row, change);
3920
+ if (!transformed.inside) return [];
3921
+ const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
3922
+ const bodyStart = table.range.row[0] + Number(table.headerRow);
3923
+ if (insertion < bodyStart) return [];
3924
+ return Array.from({
3925
+ length: change.count
3926
+ }, (_, offset)=>insertion + offset);
3927
+ }
3928
+ function transformCalculatedColumnRows(rowsByTable, change) {
3929
+ if ('row' !== change.axis || !rowsByTable.size) return;
3930
+ if ('insert' === change.kind) {
3931
+ const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
3932
+ for (const [tableId, rows] of rowsByTable){
3933
+ const next = new Set();
3934
+ for (const row of rows)next.add(row >= insertion ? row + change.count : row);
3935
+ rowsByTable.set(tableId, next);
3936
+ }
3937
+ return;
3938
+ }
3939
+ const count = change.end - change.start + 1;
3940
+ for (const [tableId, rows] of rowsByTable){
3941
+ const next = new Set();
3942
+ for (const row of rows)if (row < change.start) next.add(row);
3943
+ else if (row > change.end) next.add(row - count);
3944
+ rowsByTable.set(tableId, next);
3945
+ }
3946
+ }
3873
3947
  function transformSpreadsheetTableAxisForInsertion(axis, change) {
3874
3948
  const start = axis[0] ?? 0;
3875
3949
  const end = axis[1] ?? start;
@@ -3967,6 +4041,7 @@ function canonicalSpreadsheetTableColumns(table, readCell) {
3967
4041
  }
3968
4042
  observed.add(candidate.toLocaleLowerCase());
3969
4043
  return {
4044
+ ...table.columns[offset] ?? {},
3970
4045
  name: candidate
3971
4046
  };
3972
4047
  });
@@ -4092,7 +4167,7 @@ function truncateSpreadsheetTableColumnName(value, maximum) {
4092
4167
  return Array.from(value).slice(0, maximum).join('');
4093
4168
  }
4094
4169
  function sameSpreadsheetTableColumns(left, right) {
4095
- return left.length === right.length && left.every((column, index)=>column.name === right[index]?.name);
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);
4096
4171
  }
4097
4172
  function spreadsheet_table_reconciliation_isRecord(value) {
4098
4173
  return Boolean(value && 'object' == typeof value && !Array.isArray(value));
@@ -9324,7 +9399,8 @@ function createSpreadsheetTableDialogSource(content, target) {
9324
9399
  sheetName: sheet.name,
9325
9400
  value: {
9326
9401
  headerRow: request.headerRow,
9327
- rangeReference
9402
+ rangeReference,
9403
+ totalsRow: false
9328
9404
  }
9329
9405
  };
9330
9406
  }
@@ -9336,13 +9412,25 @@ function validateSpreadsheetTableRequest(content, request) {
9336
9412
  const sheet = content.sheets.find((candidate)=>candidate.id === request.sheetId);
9337
9413
  if (!sheet) return tableError('sheet-not-found');
9338
9414
  if (sheet.isPivotTable || sheet.pivotTables?.length) return tableError('pivot-table');
9339
- const range = spreadsheet_cell_range_normalizeSpreadsheetCellRange(request.range);
9340
- 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;
9341
9427
  const name = request.name.trim();
9342
9428
  if (!validSpreadsheetTableName(name)) return tableError('invalid-name');
9343
9429
  if (spreadsheetTableNameExists(content, name)) return tableError('name-conflict');
9344
9430
  const bounds = spreadsheetSheetBounds(sheet);
9345
- 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');
9346
9434
  const height = range.row[1] - range.row[0] + 1;
9347
9435
  if (height <= Number(request.headerRow)) return tableError('invalid-range');
9348
9436
  if (spreadsheet_cell_range_spreadsheetCellRangeArea(range) > 100000) return tableError('range-too-large');
@@ -9361,8 +9449,27 @@ function validateSpreadsheetTableRequest(content, request) {
9361
9449
  number: 2
9362
9450
  };
9363
9451
  if (!validSpreadsheetTableStyle(style)) return tableError('invalid-style');
9364
- const columns = spreadsheetTableColumnNames(sheet, range, request.headerRow);
9452
+ const columns = spreadsheetTableColumnNames(sheet, bodyRange, request.headerRow);
9365
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');
9366
9473
  return {
9367
9474
  columns,
9368
9475
  name,
@@ -9378,27 +9485,38 @@ function applySpreadsheetTable(content, request) {
9378
9485
  family: 'medium',
9379
9486
  number: 2
9380
9487
  };
9381
- const table = {
9488
+ const totalsRow = true === request.totalsRow;
9489
+ const totalsColumns = spreadsheetTableTotalsColumnPatch(validation.columns.map((name)=>({
9490
+ name
9491
+ })), request.totalsColumns);
9492
+ if (!totalsColumns) return null;
9493
+ const tableWithoutCalculatedColumns = {
9382
9494
  id: createWorkId('spreadsheet-table'),
9383
9495
  name: validation.name,
9384
9496
  range: cloneRange(validation.range),
9385
- columns: validation.columns.map((name)=>({
9386
- name
9387
- })),
9497
+ columns: withDefaultSpreadsheetTableTotalsLabel(totalsColumns, totalsRow),
9388
9498
  filters: [],
9389
9499
  headerRow: request.headerRow,
9390
- totalsRow: false,
9500
+ totalsRow,
9391
9501
  style,
9392
9502
  showFirstColumn: false,
9393
9503
  showLastColumn: false,
9394
9504
  showRowStripes: 'none' !== style.family,
9395
9505
  showColumnStripes: false
9396
9506
  };
9507
+ const table = {
9508
+ ...tableWithoutCalculatedColumns,
9509
+ columns: reconcileSpreadsheetTableCalculatedColumns(validation.sheet, tableWithoutCalculatedColumns)
9510
+ };
9397
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
+ });
9398
9516
  return replaceSpreadsheetTableSheet(content, {
9399
- ...nextSheet,
9517
+ ...synchronizedSheet,
9400
9518
  tables: [
9401
- ...nextSheet.tables ?? [],
9519
+ ...synchronizedSheet.tables ?? [],
9402
9520
  table
9403
9521
  ]
9404
9522
  });
@@ -9416,20 +9534,46 @@ function updateSpreadsheetTable(content, sheetId, tableId, patch) {
9416
9534
  const showRowStripes = patch.showRowStripes ?? table.showRowStripes;
9417
9535
  const showColumnStripes = patch.showColumnStripes ?? table.showColumnStripes;
9418
9536
  if ('none' === style.family && (showFirstColumn || showLastColumn || showRowStripes || showColumnStripes)) return null;
9419
- const tables = sheet.tables?.map((candidate)=>candidate.id === tableId ? {
9420
- ...candidate,
9421
- name,
9422
- ...void 0 === patch.name ? {} : {
9423
- displayName: void 0
9424
- },
9425
- style,
9426
- showFirstColumn,
9427
- showLastColumn,
9428
- showRowStripes,
9429
- showColumnStripes
9430
- } : 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);
9431
9575
  return replaceSpreadsheetTableSheet(content, {
9432
- ...sheet,
9576
+ ...synchronizedSheet,
9433
9577
  tables
9434
9578
  });
9435
9579
  }
@@ -9577,6 +9721,16 @@ function spreadsheetTableIntersectsMerge(sheet, range) {
9577
9721
  ]
9578
9722
  }));
9579
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
+ }
9580
9734
  function replaceSpreadsheetTableSheet(content, nextSheet) {
9581
9735
  return {
9582
9736
  ...content,
@@ -9617,6 +9771,7 @@ function tableError(code) {
9617
9771
  'invalid-name': '表格名称必须是有效且不类似单元格引用的标识符。',
9618
9772
  'invalid-range': '表格至少需要一行数据。',
9619
9773
  'invalid-style': '表格样式身份无效。',
9774
+ 'invalid-totals': '汇总行函数、标签或公式无效。',
9620
9775
  'merged-range': '请先取消表格区域内的合并单元格。',
9621
9776
  'name-conflict': '表格名称已被其他表格或名称使用。',
9622
9777
  'out-of-bounds': '表格区域超出当前工作表边界。',
@@ -9624,7 +9779,8 @@ function tableError(code) {
9624
9779
  'protected-range': '受保护或只读区域不能创建表格。',
9625
9780
  'range-too-large': `一次最多可创建 ${100000..toLocaleString()} 个单元格的表格。`,
9626
9781
  'sheet-not-found': '找不到目标工作表。',
9627
- 'table-overlap': '表格区域不能与其他表格重叠。'
9782
+ 'table-overlap': '表格区域不能与其他表格重叠。',
9783
+ 'totals-row-content': '汇总行目标单元格已有内容,请先清空后再启用。'
9628
9784
  };
9629
9785
  return {
9630
9786
  code,
@@ -9632,6 +9788,20 @@ function tableError(code) {
9632
9788
  ok: false
9633
9789
  };
9634
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
+ }
9635
9805
  function createSpreadsheetTableExtension() {
9636
9806
  return createOfficeEditorExtension({
9637
9807
  name: 'spreadsheetTables',
@@ -9684,7 +9854,8 @@ function updateSpreadsheetTableCommand(context, sheetId, tableId, patch) {
9684
9854
  if (!context.editable) return false;
9685
9855
  const next = updateSpreadsheetTable(context.content, sheetId, tableId, patch);
9686
9856
  if (!next) return false;
9687
- 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));
9688
9859
  return true;
9689
9860
  }
9690
9861
  function canConvertSpreadsheetTableToRange(context, sheetId, tableId) {
@@ -10444,9 +10615,9 @@ function createSpreadsheetFormulaDiagnostics(content, { summary, locations }) {
10444
10615
  });
10445
10616
  if (summary.structuredReferenceCells) diagnostics.push({
10446
10617
  code: 'formula.structured-references',
10447
- severity: 'warning',
10618
+ severity: 'info',
10448
10619
  title: '结构化引用',
10449
- message: `${summary.structuredReferenceCells} 个公式使用表格结构化引用;公式会原样保存,但当前计算引擎不解析 Excel 表对象。`,
10620
+ message: `${summary.structuredReferenceCells} 个公式使用表格结构化引用;浏览器内核支持有界表名、显示名称、连续列范围以及常见标题、数据、汇总和当前行选择。不连续、外部或高级形式会保留公式与缓存值,交由兼容应用重算。`,
10450
10621
  locations: locations.structured
10451
10622
  });
10452
10623
  if (summary.unsupportedFunctions.length) diagnostics.push({
@@ -13548,6 +13719,16 @@ function SpreadsheetTableDesignRibbon({ can, commands, sheetId, table }) {
13548
13719
  })
13549
13720
  ]
13550
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
+ }),
13551
13732
  /*#__PURE__*/ jsx(WorkOfficeRibbonGroup, {
13552
13733
  label: "表格样式",
13553
13734
  priority: "high",
@@ -13617,6 +13798,225 @@ function SpreadsheetTableDesignRibbon({ can, commands, sheetId, table }) {
13617
13798
  ]
13618
13799
  });
13619
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
+ }
13620
14020
  function SpreadsheetTableNameControl({ can, commands, sheetId, table }) {
13621
14021
  const [name, setName] = useState(table.name);
13622
14022
  useEffect(()=>setName(table.name), [
@@ -21908,10 +22308,15 @@ function compileSpreadsheetKernelWorkbook(sheets, fallbackCells = []) {
21908
22308
  cellCount += cells.length;
21909
22309
  sheetIds.add(id);
21910
22310
  sheetNames.add(normalizedName);
22311
+ const tables = spreadsheetKernelTables(sheet.tables);
22312
+ if (null === tables) return null;
21911
22313
  sourceSheets.push({
21912
22314
  id,
21913
22315
  name,
21914
- cells
22316
+ cells,
22317
+ ...tables.length ? {
22318
+ tables
22319
+ } : {}
21915
22320
  });
21916
22321
  }
21917
22322
  const compiled = sourceSheets.map((sheet)=>({
@@ -21954,7 +22359,8 @@ function spreadsheetKernelSourceState(sheets) {
21954
22359
  sourceCells,
21955
22360
  structureKey: JSON.stringify(sheets.map((sheet)=>({
21956
22361
  id: sheet.id,
21957
- name: sheet.name
22362
+ name: sheet.name,
22363
+ tables: sheet.tables ?? []
21958
22364
  })))
21959
22365
  };
21960
22366
  }
@@ -21972,7 +22378,18 @@ function spreadsheetStructureKey(sheets) {
21972
22378
  names.add(normalizedName);
21973
22379
  structure.push({
21974
22380
  id,
21975
- name
22381
+ name,
22382
+ tables: (sheet.tables ?? []).map((table)=>({
22383
+ name: table.name,
22384
+ displayName: table.displayName,
22385
+ startRow: table.range.row[0],
22386
+ endRow: table.range.row[1],
22387
+ startColumn: table.range.column[0],
22388
+ endColumn: table.range.column[1],
22389
+ columns: table.columns.map((column)=>column.name),
22390
+ headerRow: table.headerRow,
22391
+ totalsRow: table.totalsRow
22392
+ }))
21976
22393
  });
21977
22394
  }
21978
22395
  return JSON.stringify(structure);
@@ -22055,6 +22472,49 @@ function spreadsheetKernelInputCell(cell, fallback) {
22055
22472
  const { formula: _formula, ...cachedCell } = cell;
22056
22473
  return cachedCell;
22057
22474
  }
22475
+ function spreadsheetKernelTables(tables) {
22476
+ if (!tables?.length) return [];
22477
+ const names = new Map();
22478
+ const ranges = [];
22479
+ for (const [tableIndex, table] of tables.entries()){
22480
+ const [startRow, endRow] = table.range.row;
22481
+ const [startColumn, endColumn] = table.range.column;
22482
+ if (!table.name?.trim() || utf8ByteLength(table.name) > SPREADSHEET_KERNEL_MAX_IDENTIFIER_BYTES || void 0 !== table.displayName && (!table.displayName.trim() || utf8ByteLength(table.displayName) > SPREADSHEET_KERNEL_MAX_IDENTIFIER_BYTES) || !isSpreadsheetCoordinateIndex(startRow, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS) || !isSpreadsheetCoordinateIndex(endRow, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS) || !isSpreadsheetCoordinateIndex(startColumn, 16384) || !isSpreadsheetCoordinateIndex(endColumn, 16384) || startRow > endRow || startColumn > endColumn || table.columns.length !== endColumn - startColumn + 1) return null;
22483
+ for (const alias of [
22484
+ table.name,
22485
+ table.displayName
22486
+ ]){
22487
+ if (!alias) continue;
22488
+ const normalized = alias.toLocaleLowerCase();
22489
+ const previous = names.get(normalized);
22490
+ if (void 0 !== previous && previous !== tableIndex) return null;
22491
+ names.set(normalized, tableIndex);
22492
+ }
22493
+ const columnNames = new Set();
22494
+ for (const column of table.columns){
22495
+ if (!column.name?.trim() || utf8ByteLength(column.name) > SPREADSHEET_KERNEL_MAX_IDENTIFIER_BYTES) return null;
22496
+ const normalized = column.name.toLocaleLowerCase();
22497
+ if (columnNames.has(normalized)) return null;
22498
+ columnNames.add(normalized);
22499
+ }
22500
+ const next = {
22501
+ name: table.name,
22502
+ ...table.displayName ? {
22503
+ displayName: table.displayName
22504
+ } : {},
22505
+ startRow,
22506
+ endRow,
22507
+ startColumn,
22508
+ endColumn,
22509
+ columns: table.columns.map((column)=>column.name),
22510
+ headerRow: Boolean(table.headerRow),
22511
+ totalsRow: Boolean(table.totalsRow)
22512
+ };
22513
+ if (ranges.some((previous)=>next.startRow <= previous.endRow && next.endRow >= previous.startRow && next.startColumn <= previous.endColumn && next.endColumn >= previous.startColumn)) return null;
22514
+ ranges.push(next);
22515
+ }
22516
+ return ranges;
22517
+ }
22058
22518
  function spreadsheetSheetsWithKernelChanges(sheets, changes) {
22059
22519
  const changesBySheet = new Map();
22060
22520
  for (const change of changes.values()){
@@ -22313,7 +22773,7 @@ function spreadsheetCalculationOps(sheets, calculatedCells) {
22313
22773
  });
22314
22774
  }
22315
22775
  function sameSpreadsheetStructure(previous, current) {
22316
- return previous.length === current.length && previous.every((sheet, index)=>sheet.id === current[index]?.id && sheet.name === current[index]?.name);
22776
+ return previous.length === current.length && previous.every((sheet, index)=>sheet.id === current[index]?.id && sheet.name === current[index]?.name && JSON.stringify(sheet.tables ?? []) === JSON.stringify(current[index]?.tables ?? []));
22317
22777
  }
22318
22778
  function cellWithCalculatedValue(cell, value) {
22319
22779
  const next = {
@@ -25266,6 +25726,15 @@ function SpreadsheetTableDialog({ source, restoreFocusTarget, onApply, onClose,
25266
25726
  headerRow
25267
25727
  })),
25268
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: "启用汇总行"
25269
25738
  })
25270
25739
  ]
25271
25740
  })
@@ -25353,7 +25822,8 @@ function useSpreadsheetTable({ commandsRef, contentRef, focusGrid, getGridFocusT
25353
25822
  headerRow: value.headerRow,
25354
25823
  name: source.name,
25355
25824
  range,
25356
- sheetId: source.sheetId
25825
+ sheetId: source.sheetId,
25826
+ totalsRow: true === value.totalsRow
25357
25827
  }) ?? false;
25358
25828
  appliedRef.current = handled;
25359
25829
  } finally{
@@ -25374,7 +25844,8 @@ function spreadsheetTableValidationMessage(content, source, value) {
25374
25844
  headerRow: value.headerRow,
25375
25845
  name: source.name,
25376
25846
  range,
25377
- sheetId: source.sheetId
25847
+ sheetId: source.sheetId,
25848
+ totalsRow: true === value.totalsRow
25378
25849
  });
25379
25850
  }
25380
25851
  const MAXIMUM_BUFFERED_SPREADSHEET_OPERATIONS = 10001;