@a3s-lab/office 0.10.0 → 0.12.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.
Files changed (47) hide show
  1. package/COLLABORATION_ROADMAP.md +22 -12
  2. package/README.md +40 -15
  3. package/dist/{0~4705.js → 0~5093.js} +959 -10
  4. package/dist/0~document-editor.js +1 -0
  5. package/dist/0~spreadsheet-editor.js +189 -104
  6. package/dist/0~work-docx-export.js +225 -6
  7. package/dist/0~work-docx-import.js +26 -755
  8. package/dist/0~work-office-diagnostics.js +12 -8
  9. package/dist/0~work-pptx-import.js +2 -2
  10. package/dist/4104.js +330 -122
  11. package/dist/4476.js +82 -65
  12. package/dist/8715.js +41 -3
  13. package/dist/8928.js +448 -137
  14. package/dist/9356.js +126 -8
  15. package/dist/core.d.ts +1 -0
  16. package/dist/internal/features/work/editors/spreadsheet-editor-support.d.ts +1 -0
  17. package/dist/internal/features/work/editors/use-spreadsheet-collaboration.d.ts +2 -1
  18. package/dist/internal/features/work/spreadsheet-sparse.d.ts +5 -0
  19. package/dist/internal/features/work/work-document-file-io.d.ts +2 -1
  20. package/dist/internal/features/work/work-document-format-change-tracking.d.ts +1 -1
  21. package/dist/internal/features/work/work-document-paragraph-format-change-tracking.d.ts +7 -0
  22. package/dist/internal/features/work/work-document-paragraph-format-changes.d.ts +8 -0
  23. package/dist/internal/features/work/work-docx-import.d.ts +5 -2
  24. package/dist/internal/features/work/work-docx-paragraph-alignment-import.d.ts +1 -0
  25. package/dist/internal/features/work/work-docx-paragraph-borders-import.d.ts +1 -0
  26. package/dist/internal/features/work/work-docx-paragraph-direction-import.d.ts +1 -0
  27. package/dist/internal/features/work/work-docx-paragraph-format-change-export.d.ts +13 -0
  28. package/dist/internal/features/work/work-docx-paragraph-format-change-import.d.ts +17 -0
  29. package/dist/internal/features/work/work-docx-paragraph-indent-import.d.ts +1 -0
  30. package/dist/internal/features/work/work-docx-paragraph-pagination-import.d.ts +1 -0
  31. package/dist/internal/features/work/work-docx-paragraph-shading-import.d.ts +1 -0
  32. package/dist/internal/features/work/work-docx-paragraph-spacing-import.d.ts +1 -0
  33. package/dist/internal/features/work/work-docx-tab-stop-import.d.ts +1 -0
  34. package/dist/internal/features/work/work-file-data.d.ts +7 -1
  35. package/dist/internal/features/work/work-file-import.d.ts +28 -0
  36. package/dist/internal/features/work/work-file-io.d.ts +2 -1
  37. package/dist/internal/features/work/work-markdown-file-io.d.ts +2 -1
  38. package/dist/internal/features/work/work-office-diagnostics.d.ts +3 -2
  39. package/dist/internal/features/work/work-pptx-import.d.ts +1 -1
  40. package/dist/internal/features/work/work-presentation-file-io.d.ts +2 -1
  41. package/dist/internal/features/work/work-spreadsheet-protection.d.ts +2 -0
  42. package/dist/internal/features/work/work-types.d.ts +23 -1
  43. package/dist/internal/features/work/work-xlsx-interop.d.ts +4 -14
  44. package/dist/office-kernel.wasm +0 -0
  45. package/dist/styles.css +8 -0
  46. package/docs/latest/en/browser-editor-architecture.md +18 -0
  47. package/package.json +10 -4
@@ -896,6 +896,7 @@ function documentChangeDecisionKey(changeKey, decision) {
896
896
  function documentChangeKindLabel(kind) {
897
897
  if ('insertion' === kind) return '插入';
898
898
  if ('formatting' === kind) return '格式';
899
+ if ('paragraph-formatting' === kind) return '段落格式';
899
900
  return '删除';
900
901
  }
901
902
  function documentChangeWindowKey(change) {
@@ -4,12 +4,12 @@ import { Workbook } from "@fortune-sheet/react";
4
4
  import { AlignCenter, AlignLeft, AlignRight, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, AlignVerticalJustifyStart, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, BarChart3, BetweenHorizontalEnd, BetweenHorizontalStart, BetweenVerticalEnd, BetweenVerticalStart, Bold, Bookmark, Calculator, Check, ChevronDown, ChevronLeft, ChevronRight, ClipboardPaste, Cloud, Columns3, Copy, CopyPlus, DecimalsArrowLeft, DecimalsArrowRight, Eraser, Eye, EyeOff, FileX2, Grid3X3, Italic, KeyRound, Layers2, Link2Off, ListFilter, Merge, MessageSquareX, MoveHorizontal, MoveVertical, Paintbrush, Palette, PanelLeft, PanelTop, PanelsTopLeft, Pencil, Percent, Plus, Printer, Redo2, RefreshCw, Rows3, Save, Scissors, Search, ShieldCheck, SortAsc, SortDesc, TableProperties, Trash2, Underline, Undo2, WrapText, X } from "lucide-react";
5
5
  import { Fragment as external_react_Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
6
6
  import { genarate, indexToColumnChar, update as core_update } from "@fortune-sheet/core";
7
- import { withoutEditableRange, SPREADSHEET_CONDITIONAL_COMPARISON_OPERATORS, editableRangeRequiresCredentials, normalizeSpreadsheetConditionalIconSetFormat, defaultSpreadsheetDataBarOptions, spreadsheetConditionalThresholdValue, normalizeSpreadsheetConditionalVisualOptions, withSheetProtection, spreadsheetProtectionKey, defaultSpreadsheetColorScaleThresholds, effectiveSpreadsheetPageSetup, protectedSheetCount, SPREADSHEET_CONDITIONAL_ICON_SETS, withSheetSelectionPermissions, withEditableRange, spreadsheetConditionalComparisonNeedsUpperValue, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, isSpreadsheetConditionalComparisonOperator, drawSpreadsheetConditionalIcon, spreadsheetConditionalIconForValue, defaultSpreadsheetConditionalIconThresholds, sheetProtectionAuthority, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, spreadsheetConditionalThresholdsEqual, editableRangeCellCount, unlockedCellCount } from "./4476.js";
7
+ import { withoutEditableRange, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, SPREADSHEET_CONDITIONAL_COMPARISON_OPERATORS, editableRangeRequiresCredentials, normalizeSpreadsheetConditionalIconSetFormat, defaultSpreadsheetDataBarOptions, spreadsheetConditionalThresholdValue, normalizeSpreadsheetConditionalVisualOptions, withSheetProtection, spreadsheetProtectionKey, defaultSpreadsheetColorScaleThresholds, effectiveSpreadsheetPageSetup, protectedSheetCount, SPREADSHEET_CONDITIONAL_ICON_SETS, withSheetSelectionPermissions, withEditableRange, spreadsheetConditionalComparisonNeedsUpperValue, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, isSpreadsheetConditionalComparisonOperator, drawSpreadsheetConditionalIcon, spreadsheetConditionalIconForValue, defaultSpreadsheetConditionalIconThresholds, sheetProtectionAuthority, replaceOfficeSpreadsheetCollaboration as replaceWorkOfficeSpreadsheetCollaboration, spreadsheetConditionalThresholdsEqual, editableRangeCellCount, unlockedCellCount } from "./4476.js";
8
+ import { workSpreadsheetChartSupportsGrouping, createSpreadsheetChartFromSelection, normalizeWorkSpreadsheetDataLabelPosition, normalizeSpreadsheetPrintTitleColumns, normalizeSpreadsheetPrintTitleRows, formulaHasStructuredReference, normalizeWorkSpreadsheetErrorBars, defaultPivotValueCaption, workSpreadsheetChartSupportsBarSpacing, normalizeWorkSpreadsheetChartGrouping, normalizeWorkSpreadsheetChartAxisGroup, normalizeWorkSpreadsheetScatterStyle, spreadsheetPivotAggregationLabel, workSpreadsheetChartAxisIsCategoryAxis, reconcileSpreadsheetChartPreviews, normalizeWorkSpreadsheetChartAxes, normalizeWorkSpreadsheetDoughnutHoleSize, normalizeWorkSpreadsheetChartLegendPosition, spreadsheetChartCount, workSpreadsheetChartTypeLabel, sparseArrayEntries, normalizeWorkSpreadsheetTrendline, spreadsheetPivotOutputContains, effectiveSpreadsheetCalculationSettings, normalizeWorkSpreadsheetDataLabels, spreadsheetPivotIntersects, normalizeSpreadsheetPrintArea, spreadsheetCellAddress, formatSpreadsheetCellRanges, normalizeWorkSpreadsheetChartLayout, spreadsheetFormulaRangeForCell, workSpreadsheetChartAxisIsValueAxis, normalizeWorkSpreadsheetChartSmoothLines, spreadsheetPivotFilterValueKey, normalizeWorkSpreadsheetBubbleScale, normalizeWorkSpreadsheetChartSeriesStyle, volatileSpreadsheetFormulaFunctions, sparseArrayIndexes, normalizeWorkSpreadsheetBubbleSizeRepresents, workSpreadsheetChartSupportsTrendlines, normalizeWorkSpreadsheetChartLegendOverlay, parseSpreadsheetChartReference, workSpreadsheetChartGroupingIsStacked, normalizeWorkSpreadsheetChartGapWidth, workSpreadsheetChartSupportsSmoothLines, refreshSpreadsheetPivotTables, createSpreadsheetPivotFromSelection, spreadsheetPivotCount, reconcileSpreadsheetPivots, sparseMatrixColumnCount, workSpreadsheetChartSupportsErrorBars, formulaHasExternalReference, isValidSpreadsheetDefinedName, spreadsheetPivotValidation, deleteSpreadsheetPivotTable, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartSupportsSeriesAnalysis, parseSpreadsheetCellRanges, spreadsheetPivotFields, spreadsheetPivotFilterItems, normalizeWorkSpreadsheetChartOverlap, spreadsheetFormulaRangeConflict, spreadsheetSheetsWithChartPreviews, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, workSpreadsheetChartSupportsAxes, normalizeWorkSpreadsheetCombinationSeriesType, normalizeWorkSpreadsheetRadarStyle } from "./8715.js";
8
9
  import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeDialog, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown } from "./0~7048.js";
9
10
  import { isOfficeShortcutBlocked, OfficeColorPicker } from "./0~3635.js";
10
11
  import { createOfficeEditorExtension, useOfficeEditorRuntime, useOfficeEditorKeyboardShortcuts } from "./0~3557.js";
11
12
  import { showToast } from "./6489.js";
12
- import { workSpreadsheetChartSupportsGrouping, createSpreadsheetChartFromSelection, normalizeWorkSpreadsheetDataLabelPosition, normalizeSpreadsheetPrintTitleRows, normalizeSpreadsheetPrintTitleColumns, formulaHasStructuredReference, normalizeWorkSpreadsheetErrorBars, defaultPivotValueCaption, workSpreadsheetChartSupportsBarSpacing, normalizeWorkSpreadsheetChartGrouping, normalizeWorkSpreadsheetChartAxisGroup, normalizeWorkSpreadsheetScatterStyle, spreadsheetPivotAggregationLabel, workSpreadsheetChartAxisIsCategoryAxis, reconcileSpreadsheetChartPreviews, normalizeWorkSpreadsheetChartAxes, normalizeWorkSpreadsheetDoughnutHoleSize, normalizeWorkSpreadsheetChartLegendPosition, spreadsheetChartCount, workSpreadsheetChartTypeLabel, normalizeWorkSpreadsheetTrendline, spreadsheetPivotOutputContains, effectiveSpreadsheetCalculationSettings, normalizeWorkSpreadsheetDataLabels, spreadsheetPivotIntersects, normalizeSpreadsheetPrintArea, spreadsheetCellAddress, formatSpreadsheetCellRanges, normalizeWorkSpreadsheetChartLayout, spreadsheetFormulaRangeForCell, workSpreadsheetChartAxisIsValueAxis, normalizeWorkSpreadsheetChartSmoothLines, spreadsheetPivotFilterValueKey, normalizeWorkSpreadsheetBubbleScale, normalizeWorkSpreadsheetChartSeriesStyle, volatileSpreadsheetFormulaFunctions, normalizeWorkSpreadsheetBubbleSizeRepresents, workSpreadsheetChartSupportsTrendlines, normalizeWorkSpreadsheetChartLegendOverlay, parseSpreadsheetChartReference, workSpreadsheetChartGroupingIsStacked, normalizeWorkSpreadsheetChartGapWidth, workSpreadsheetChartSupportsSmoothLines, refreshSpreadsheetPivotTables, createSpreadsheetPivotFromSelection, spreadsheetPivotCount, reconcileSpreadsheetPivots, workSpreadsheetChartSupportsErrorBars, formulaHasExternalReference, isValidSpreadsheetDefinedName, spreadsheetPivotValidation, deleteSpreadsheetPivotTable, workSpreadsheetChartAxisDefaultLabelPosition, workSpreadsheetChartSupportsSeriesAnalysis, parseSpreadsheetCellRanges, spreadsheetPivotFields, spreadsheetPivotFilterItems, normalizeWorkSpreadsheetChartOverlap, spreadsheetFormulaRangeConflict, spreadsheetSheetsWithChartPreviews, workSpreadsheetChartUsesNumericXAxis, workSpreadsheetChartAxisShowsMajorGridlinesByDefault, workSpreadsheetChartSupportsAxes, normalizeWorkSpreadsheetCombinationSeriesType, normalizeWorkSpreadsheetRadarStyle } from "./8715.js";
13
13
  import { unsupportedSpreadsheetFormulaFunctions } from "./0~work-spreadsheet-formula-support.js";
14
14
  import { WorkspaceContextMenu, isWorkspaceContextMenuKeyboardEvent, WorkOfficeZoomControls, WorkOfficeStatusBar, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkOfficeRibbonGroup, WorkOfficePreviewBar, WorkOfficeRibbonButton } from "./0~4595.js";
15
15
  import { moveOfficeMenuFocus, stepOfficeZoom, Popover, useOfficeRemoteParticipants, button_Button, officePresenceColor, useOfficePublishPresenceLocation, icon_button_IconButton, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom } from "./0~6090.js";
@@ -440,18 +440,10 @@ function cellKey(row, column) {
440
440
  return `${row}_${column}`;
441
441
  }
442
442
  function forEachCell(data, ranges, visit) {
443
- const maximumRow = data.length - 1;
444
- const maximumColumn = Math.max(0, ...data.map((cells)=>cells.length - 1));
445
- for (const range of ranges){
446
- const startRow = Math.max(0, Math.min(maximumRow, range.row[0]));
447
- const endRow = Math.max(0, Math.min(maximumRow, range.row[1]));
448
- const startColumn = Math.max(0, Math.min(maximumColumn, range.column[0]));
449
- const endColumn = Math.max(0, Math.min(maximumColumn, range.column[1]));
450
- for(let row = startRow; row <= endRow; row += 1)for(let column = startColumn; column <= endColumn; column += 1)visit({
451
- row,
452
- column
453
- });
454
- }
443
+ for (const [row, cells] of sparseArrayEntries(data))for (const [column] of sparseArrayEntries(cells))if (ranges.some((range)=>row >= range.row[0] && row <= range.row[1] && column >= range.column[0] && column <= range.column[1])) visit({
444
+ row,
445
+ column
446
+ });
455
447
  }
456
448
  function cellValue(data, row, column) {
457
449
  return data[row]?.[column]?.v ?? null;
@@ -547,11 +539,12 @@ function spreadsheetSelectionSummary(sheet, selection) {
547
539
  const rowEnd = Math.min(range.row[1] ?? rowStart, sheet.data.length - 1);
548
540
  const columnStart = range.column[0] ?? 0;
549
541
  const columnEnd = range.column[1] ?? columnStart;
550
- for(let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex += 1){
542
+ for (const rowIndex of sparseArrayIndexes(sheet.data)){
543
+ if (rowIndex < rowStart || rowIndex > rowEnd) continue;
551
544
  const row = sheet.data[rowIndex];
552
- if (!row) continue;
553
- const populatedColumnEnd = Math.min(columnEnd, row.length - 1);
554
- for(let columnIndex = columnStart; columnIndex <= populatedColumnEnd; columnIndex += 1)collect(row[columnIndex]);
545
+ if (row) {
546
+ for (const columnIndex of sparseArrayIndexes(row))if (!(columnIndex < columnStart) && !(columnIndex > columnEnd)) collect(row[columnIndex]);
547
+ }
555
548
  }
556
549
  } else {
557
550
  const rowStart = range.row[0] ?? 0;
@@ -607,28 +600,66 @@ function spreadsheetSheetsWithFiniteSelections(sheets) {
607
600
  }));
608
601
  }
609
602
  function spreadsheetSheetsForFortune(sheets) {
610
- return structuredClone(sheets).map((sheet)=>{
611
- for (const merge of Object.values(sheet.config?.merge ?? {}))for(let rowIndex = merge.r; rowIndex < merge.r + merge.rs; rowIndex += 1){
612
- const row = sheet.data?.[rowIndex];
613
- if (row) for(let columnIndex = merge.c; columnIndex < merge.c + merge.cs; columnIndex += 1)row[columnIndex] = {
614
- ...row[columnIndex] ?? {},
615
- mc: rowIndex === merge.r && columnIndex === merge.c ? {
616
- ...merge
617
- } : {
618
- r: merge.r,
619
- c: merge.c
603
+ return sheets.map((sheet)=>{
604
+ const { celldata: sourceCellData, data, ...sourceMetadata } = sheet;
605
+ const projected = structuredClone(sourceMetadata);
606
+ const cells = new Map();
607
+ if (data) for (const rowIndex of sparseArrayIndexes(data)){
608
+ const row = data[rowIndex];
609
+ if (row) for (const columnIndex of sparseArrayIndexes(row)){
610
+ const cell = row[columnIndex];
611
+ if (null != cell) cells.set(`${rowIndex}_${columnIndex}`, {
612
+ r: rowIndex,
613
+ c: columnIndex,
614
+ v: structuredClone(cell)
615
+ });
616
+ }
617
+ }
618
+ else for (const entry of sourceCellData ?? [])cells.set(`${entry.r}_${entry.c}`, structuredClone(entry));
619
+ for (const merge of Object.values(projected.config?.merge ?? {}))for(let rowIndex = merge.r; rowIndex < merge.r + merge.rs; rowIndex += 1)for(let columnIndex = merge.c; columnIndex < merge.c + merge.cs; columnIndex += 1){
620
+ const key = `${rowIndex}_${columnIndex}`;
621
+ const current = cells.get(key);
622
+ cells.set(key, {
623
+ r: rowIndex,
624
+ c: columnIndex,
625
+ v: {
626
+ ...current?.v ?? {},
627
+ mc: rowIndex === merge.r && columnIndex === merge.c ? {
628
+ ...merge
629
+ } : {
630
+ r: merge.r,
631
+ c: merge.c
632
+ }
620
633
  }
621
- };
634
+ });
622
635
  }
623
636
  return {
624
- ...sheet,
625
- celldata: sheet.data ? sheet.data.flatMap((row, rowIndex)=>row.flatMap((cell, columnIndex)=>null == cell ? [] : [
626
- {
627
- r: rowIndex,
628
- c: columnIndex,
629
- v: cell
630
- }
631
- ])) : sheet.celldata ?? []
637
+ ...projected,
638
+ celldata: Array.from(cells.values()).sort((left, right)=>left.r - right.r || left.c - right.c)
639
+ };
640
+ });
641
+ }
642
+ function spreadsheetSheetsFromFortune(sheets, sourceSheets) {
643
+ return sheets.map((sheet, index)=>{
644
+ const source = (sheet.id ? sourceSheets.find((candidate)=>candidate.id === sheet.id) : void 0) ?? sourceSheets[index];
645
+ const cells = spreadsheetPopulatedCellData(sheet);
646
+ const { celldata: _cellData, data: _data, ...metadata } = sheet;
647
+ const useMatrix = source ? void 0 !== source.data : void 0 !== sheet.data;
648
+ if (!useMatrix) return {
649
+ ...metadata,
650
+ celldata: cells
651
+ };
652
+ const data = [];
653
+ data.length = Math.max(sheet.data?.length ?? 0, source?.data?.length ?? 0, positiveSpreadsheetDimension(sheet.row));
654
+ for (const { r: rowIndex, c: columnIndex, v: cell } of cells){
655
+ const row = data[rowIndex] ?? [];
656
+ data[rowIndex] = row;
657
+ row.length = Math.max(row.length, sheet.data?.[rowIndex]?.length ?? 0, source?.data?.[rowIndex]?.length ?? 0, positiveSpreadsheetDimension(sheet.column));
658
+ row[columnIndex] = cell;
659
+ }
660
+ return {
661
+ ...metadata,
662
+ data
632
663
  };
633
664
  });
634
665
  }
@@ -644,7 +675,9 @@ function finiteSpreadsheetSelection(selection) {
644
675
  };
645
676
  }
646
677
  function sameSpreadsheetWorkbookState(changed, rendered) {
647
- return JSON.stringify(changed.map(spreadsheetSheetWithoutTransientSelection)) === JSON.stringify(rendered.map(spreadsheetSheetWithoutTransientSelection));
678
+ const left = changed.map(spreadsheetSheetWithoutTransientSelection);
679
+ const right = rendered.map(spreadsheetSheetWithoutTransientSelection);
680
+ return sameSpreadsheetHistoryValue(left, right);
648
681
  }
649
682
  function sameSpreadsheetHistoryContent(left, right) {
650
683
  return sameSpreadsheetHistoryValue({
@@ -737,8 +770,33 @@ function finiteSpreadsheetIndex(value, fallback) {
737
770
  return 'number' == typeof value && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
738
771
  }
739
772
  function spreadsheetSheetWithoutTransientSelection(sheet) {
740
- const { celldata: _cellData, luckysheet_select_save: _selection, luckysheet_selection_range: _range, ...content } = sheet;
741
- return content;
773
+ const { celldata: _cellData, calcChain: _calculationChain, data: _data, dynamicArray_compute: _dynamicArrayComputation, luckysheet_select_save: _selection, luckysheet_selection_range: _range, zoomRatio, ...content } = sheet;
774
+ return {
775
+ ...content,
776
+ ...void 0 === zoomRatio || 1 === zoomRatio ? {} : {
777
+ zoomRatio
778
+ },
779
+ celldata: spreadsheetPopulatedCellData(sheet)
780
+ };
781
+ }
782
+ function spreadsheetPopulatedCellData(sheet) {
783
+ const cells = [];
784
+ if (void 0 !== sheet.data) for (const rowIndex of sparseArrayIndexes(sheet.data)){
785
+ const row = sheet.data[rowIndex];
786
+ if (row) for (const columnIndex of sparseArrayIndexes(row)){
787
+ const cell = row[columnIndex];
788
+ if (null != cell) cells.push({
789
+ r: rowIndex,
790
+ c: columnIndex,
791
+ v: cell
792
+ });
793
+ }
794
+ }
795
+ else for (const cell of sheet.celldata ?? [])if (null != cell.v) cells.push(cell);
796
+ return cells.sort((left, right)=>left.r - right.r || left.c - right.c);
797
+ }
798
+ function positiveSpreadsheetDimension(value) {
799
+ return 'number' == typeof value && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
742
800
  }
743
801
  function sameSpreadsheetHistoryValue(left, right) {
744
802
  if (left === right) return true;
@@ -748,7 +806,9 @@ function sameSpreadsheetHistoryValue(left, right) {
748
806
  if (leftFormulaCell || rightFormulaCell) return Boolean(leftFormulaCell && rightFormulaCell && sameSpreadsheetFormulaHistoryCell(leftFormulaCell, rightFormulaCell));
749
807
  if (Array.isArray(left) || Array.isArray(right)) {
750
808
  if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
751
- return left.every((value, index)=>sameSpreadsheetHistoryValue(value, right[index]));
809
+ const leftKeys = Object.keys(left).filter((key)=>void 0 !== left[Number(key)]);
810
+ const rightKeys = Object.keys(right).filter((key)=>void 0 !== right[Number(key)]);
811
+ return leftKeys.length === rightKeys.length && leftKeys.every((key)=>Object.hasOwn(right, key) && sameSpreadsheetHistoryValue(left[Number(key)], right[Number(key)]));
752
812
  }
753
813
  if ('object' != typeof left || 'object' != typeof right) return false;
754
814
  const leftRecord = left;
@@ -1471,13 +1531,13 @@ function spreadsheetSelectionContainsFocus(selection, focus) {
1471
1531
  }
1472
1532
  function spreadsheetSheetBounds(sheet) {
1473
1533
  const dataRows = sheet.data?.length ?? 0;
1474
- const dataColumns = Math.max(0, ...sheet.data?.map((row)=>row?.length ?? 0) ?? []);
1534
+ const dataColumns = sparseMatrixColumnCount(sheet.data);
1475
1535
  return {
1476
- lastRow: positiveSpreadsheetDimension(sheet.row, dataRows, spreadsheetDefaultRows) - 1,
1477
- lastColumn: positiveSpreadsheetDimension(sheet.column, dataColumns, spreadsheetDefaultColumns) - 1
1536
+ lastRow: spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(sheet.row, dataRows, spreadsheetDefaultRows) - 1,
1537
+ lastColumn: spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(sheet.column, dataColumns, spreadsheetDefaultColumns) - 1
1478
1538
  };
1479
1539
  }
1480
- function positiveSpreadsheetDimension(configured, dataDimension, fallback) {
1540
+ function spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(configured, dataDimension, fallback) {
1481
1541
  const value = Number(configured);
1482
1542
  if (Number.isFinite(value) && value > 0) return Math.max(1, Math.trunc(value));
1483
1543
  if (dataDimension > 0) return dataDimension;
@@ -1817,11 +1877,10 @@ function createSpreadsheetEditorExtensions() {
1817
1877
  return Boolean(sheet && (context.editable || context.view && 1 !== sheet.hide));
1818
1878
  },
1819
1879
  execute: (context, sheetId)=>{
1820
- if (!context.editable) {
1821
- const sheet = context.content.sheets.find((candidate)=>candidate.id === sheetId);
1822
- if (!sheet || 1 === sheet.hide) return false;
1823
- return context.view?.activateSheet(sheetId) ?? false;
1824
- }
1880
+ const sheet = context.content.sheets.find((candidate)=>candidate.id === sheetId);
1881
+ if (!sheet) return false;
1882
+ if (context.view && 1 !== sheet.hide) return context.view?.activateSheet(sheetId) ?? false;
1883
+ if (!context.editable) return false;
1825
1884
  return applySpreadsheetSheetChange(context, activateSpreadsheetSheet(context.content, sheetId));
1826
1885
  }
1827
1886
  },
@@ -2134,7 +2193,7 @@ function spreadsheetStructureExtent(context, axis) {
2134
2193
  const range = spreadsheetStructureRange(liveRange(context), axis);
2135
2194
  if (!sheet) return range[1] + 1;
2136
2195
  if ('row' === axis) return Math.max(sheet.row ?? 0, sheet.data?.length ?? 0, range[1] + 1);
2137
- const dataWidth = Math.max(0, ...(sheet.data ?? []).map((row)=>row.length));
2196
+ const dataWidth = sparseMatrixColumnCount(sheet.data);
2138
2197
  return Math.max(sheet.column ?? 0, dataWidth, range[1] + 1);
2139
2198
  }
2140
2199
  function clearSelectedCells(context, mode = 'contents') {
@@ -2757,7 +2816,7 @@ function scanSpreadsheetFormulas(content) {
2757
2816
  for (const sheet of content.sheets){
2758
2817
  summary.normalizedSharedFormulaGroups += sheet.formulaMetadata?.normalizedSharedFormulaGroups ?? 0;
2759
2818
  summary.normalizedSharedFormulaCells += sheet.formulaMetadata?.normalizedSharedFormulaCells ?? 0;
2760
- for (const [row, cells] of (sheet.data ?? []).entries())for (const [column, cell] of cells.entries()){
2819
+ for (const [row, cells] of sparseArrayEntries(sheet.data))for (const [column, cell] of sparseArrayEntries(cells)){
2761
2820
  if (!cell?.f) continue;
2762
2821
  const address = spreadsheetCellAddress(row, column);
2763
2822
  const location = `${sheet.name}!${address}`;
@@ -5459,17 +5518,11 @@ function spreadsheetFindMatches(sheet, query) {
5459
5518
  }
5460
5519
  function indexedSpreadsheetCells(sheet) {
5461
5520
  const indexed = new Map();
5462
- for(let row = 0; row < (sheet.data?.length ?? 0); row += 1){
5463
- const cells = sheet.data?.[row];
5464
- if (cells) for(let column = 0; column < cells.length; column += 1){
5465
- const cell = cells[column];
5466
- if (cell) indexed.set(`${row}:${column}`, {
5467
- row,
5468
- column,
5469
- cell
5470
- });
5471
- }
5472
- }
5521
+ for (const [row, cells] of sparseArrayEntries(sheet.data))for (const [column, cell] of sparseArrayEntries(cells))if (cell) indexed.set(`${row}:${column}`, {
5522
+ row,
5523
+ column,
5524
+ cell
5525
+ });
5473
5526
  for (const entry of sheet.celldata ?? []){
5474
5527
  if (!Number.isSafeInteger(entry.r) || !Number.isSafeInteger(entry.c) || entry.r < 0 || entry.c < 0 || !entry.v) continue;
5475
5528
  const key = `${entry.r}:${entry.c}`;
@@ -9643,7 +9696,7 @@ function SpreadsheetProtectionPanel({ content, onChange }) {
9643
9696
  if (ranges.some((range, index)=>index !== selectedIndex && range.name.trim().toLowerCase() === name.toLowerCase())) return void setError('当前工作表中已经存在这个区域名称。');
9644
9697
  if (editableRangeCellCount(parsed) > MAX_EDITABLE_RANGE_CELLS) return void setError('一次最多可设置 100,000 个可编辑单元格。');
9645
9698
  const maximumRow = Math.max(1, sheet.row ?? sheet.data?.length ?? 1) - 1;
9646
- const maximumColumn = Math.max(1, sheet.column ?? Math.max(0, ...(sheet.data ?? []).map((row)=>row.length))) - 1;
9699
+ const maximumColumn = Math.max(1, sheet.column ?? sparseMatrixColumnCount(sheet.data)) - 1;
9647
9700
  if (parsed.some((range)=>range.row[1] > maximumRow || range.column[1] > maximumColumn)) return void setError(`范围必须位于当前工作表的 ${maximumRow + 1} 行 × ${maximumColumn + 1} 列以内。`);
9648
9701
  const index = selectedIndex ?? ranges.length;
9649
9702
  updateSheet(withEditableRange(sheet, selectedIndex, {
@@ -10682,12 +10735,9 @@ function sparseSpreadsheetCells(sheet, maximumCells) {
10682
10735
  };
10683
10736
  if (sheet.data) {
10684
10737
  if (sheet.data.length > OFFICE_KERNEL_SPREADSHEET_MAX_ROWS) return null;
10685
- for(let row = 0; row < sheet.data.length; row += 1){
10686
- const values = sheet.data[row];
10687
- if (values) {
10688
- if (values.length > 16384) return null;
10689
- for(let column = 0; column < values.length; column += 1)if (!appendCell(values[column], row, column)) return null;
10690
- }
10738
+ for (const [row, values] of sparseArrayEntries(sheet.data)){
10739
+ if (values.length > 16384) return null;
10740
+ for (const [column, cell] of sparseArrayEntries(values))if (!appendCell(cell, row, column)) return null;
10691
10741
  }
10692
10742
  } else for (const entry of sheet.celldata ?? [])if (!appendCell(entry.v, entry.r, entry.c)) return null;
10693
10743
  return cells.sort((left, right)=>left.row - right.row || left.column - right.column);
@@ -11316,9 +11366,7 @@ function spreadsheetCellReader(sheet) {
11316
11366
  }
11317
11367
  function spreadsheetUsedBounds(sheet) {
11318
11368
  let lastRow = Math.max((sheet.data?.length ?? 1) - 1, 0);
11319
- let lastColumn = Math.max(...sheet.data?.map((row)=>Math.max((row?.length ?? 1) - 1, 0)) ?? [
11320
- 0
11321
- ]);
11369
+ let lastColumn = Math.max(sparseMatrixColumnCount(sheet.data) - 1, 0);
11322
11370
  for (const cell of sheet.celldata ?? []){
11323
11371
  lastRow = Math.max(lastRow, cell.r);
11324
11372
  lastColumn = Math.max(lastColumn, cell.c);
@@ -11940,9 +11988,7 @@ function spreadsheetFormatPainterSheetBounds(sheet, target, pattern) {
11940
11988
  const requestedColumnCount = Math.max(target.column[0], target.column[1]) + pattern.columnCount;
11941
11989
  return {
11942
11990
  rowCount: positiveInteger(sheet.row) ?? Math.max(sheet.data?.length ?? 0, requestedRowCount),
11943
- columnCount: positiveInteger(sheet.column) ?? Math.max(...sheet.data?.map((row)=>row?.length ?? 0) ?? [
11944
- 0
11945
- ], requestedColumnCount)
11991
+ columnCount: positiveInteger(sheet.column) ?? Math.max(sparseMatrixColumnCount(sheet.data), requestedColumnCount)
11946
11992
  };
11947
11993
  }
11948
11994
  function positiveInteger(value) {
@@ -12035,6 +12081,7 @@ function useSpreadsheetCollaboration({ initialContent, onChange, session }) {
12035
12081
  const sessionRef = useRef(session);
12036
12082
  if (sessionRef.current !== session) throw new Error('SpreadsheetEditor collaboration sessions cannot be replaced while mounted. Remount the editor for another shared workbook.');
12037
12083
  const viewStateRef = useRef(spreadsheetCollaborationViewState(initialContent));
12084
+ const [derivedOrigin] = useState(()=>session.createOrigin('system', 'spreadsheet-calculation'));
12038
12085
  const [content, setContent] = useState(()=>spreadsheetContentWithCollaborationViewState(readWorkOfficeSpreadsheetCollaboration(session), viewStateRef.current));
12039
12086
  const contentRef = useRef(content);
12040
12087
  const bindingRef = useRef(void 0);
@@ -12042,15 +12089,15 @@ function useSpreadsheetCollaboration({ initialContent, onChange, session }) {
12042
12089
  const onChangeRef = useRef(onChange);
12043
12090
  onChangeRef.current = onChange;
12044
12091
  contentRef.current = content;
12092
+ const project = useCallback((sharedContent)=>{
12093
+ const projected = spreadsheetContentWithCollaborationViewState(sharedContent, viewStateRef.current);
12094
+ contentRef.current = projected;
12095
+ setContent(projected);
12096
+ return projected;
12097
+ }, []);
12045
12098
  useEffect(()=>{
12046
12099
  const binding = createWorkOfficeSpreadsheetCollaborationBinding(session);
12047
12100
  bindingRef.current = binding;
12048
- const project = (sharedContent)=>{
12049
- const projected = spreadsheetContentWithCollaborationViewState(sharedContent, viewStateRef.current);
12050
- contentRef.current = projected;
12051
- setContent(projected);
12052
- return projected;
12053
- };
12054
12101
  const unsubscribeContent = binding.subscribe(({ content: next })=>{
12055
12102
  onChangeRef.current(project(next));
12056
12103
  });
@@ -12069,11 +12116,12 @@ function useSpreadsheetCollaboration({ initialContent, onChange, session }) {
12069
12116
  binding.destroy();
12070
12117
  };
12071
12118
  }, [
12119
+ project,
12072
12120
  session
12073
12121
  ]);
12074
12122
  const commit = useCallback((next)=>{
12075
- updateSpreadsheetCollaborationViewState(viewStateRef.current, next);
12076
12123
  const previous = contentRef.current;
12124
+ updateSpreadsheetCollaborationViewStateForContentChange(viewStateRef.current, previous, next);
12077
12125
  const binding = bindingRef.current;
12078
12126
  if (binding?.replace(previous, next)) {
12079
12127
  contentRef.current = spreadsheetContentWithCollaborationViewState(binding.content(), viewStateRef.current);
@@ -12083,17 +12131,42 @@ function useSpreadsheetCollaboration({ initialContent, onChange, session }) {
12083
12131
  contentRef.current = projected;
12084
12132
  setContent(projected);
12085
12133
  }, []);
12134
+ const commitDerived = useCallback((next)=>{
12135
+ const previous = contentRef.current;
12136
+ const binding = bindingRef.current;
12137
+ if (binding && replaceWorkOfficeSpreadsheetCollaboration(session, previous, next, derivedOrigin)) {
12138
+ contentRef.current = spreadsheetContentWithCollaborationViewState(binding.content(), viewStateRef.current);
12139
+ return;
12140
+ }
12141
+ project(binding?.content() ?? previous);
12142
+ }, [
12143
+ derivedOrigin,
12144
+ project,
12145
+ session
12146
+ ]);
12086
12147
  const view = useMemo(()=>({
12087
12148
  activateSheet: (sheetId)=>{
12149
+ const sharedContent = bindingRef.current?.content() ?? contentRef.current;
12150
+ const sheet = sharedContent.sheets.find((candidate)=>candidate.id === sheetId && 1 !== candidate.hide);
12151
+ if (!sheet) return false;
12152
+ if (viewStateRef.current.activeSheetId === sheetId) return true;
12088
12153
  viewStateRef.current.activeSheetId = sheetId;
12154
+ project(sharedContent);
12155
+ return true;
12089
12156
  },
12090
12157
  select: (sheetId, selection)=>{
12091
12158
  viewStateRef.current.selections.set(sheetId, finiteSpreadsheetSelection(selection));
12092
12159
  },
12093
12160
  setZoom: (sheetId, zoomRatio)=>{
12161
+ const sharedContent = bindingRef.current?.content() ?? contentRef.current;
12162
+ if (!sharedContent.sheets.some((sheet)=>sheet.id === sheetId)) return;
12163
+ if (viewStateRef.current.zoomBySheet.get(sheetId) === zoomRatio) return;
12094
12164
  viewStateRef.current.zoomBySheet.set(sheetId, zoomRatio);
12165
+ project(sharedContent);
12095
12166
  }
12096
- }), []);
12167
+ }), [
12168
+ project
12169
+ ]);
12097
12170
  const history = useMemo(()=>({
12098
12171
  canRedo: bindingRef.current?.canRedo() ?? false,
12099
12172
  canUndo: bindingRef.current?.canUndo() ?? false,
@@ -12106,6 +12179,7 @@ function useSpreadsheetCollaboration({ initialContent, onChange, session }) {
12106
12179
  content,
12107
12180
  history,
12108
12181
  onChange: commit,
12182
+ onDerivedChange: commitDerived,
12109
12183
  readOnly: 'edit' !== session.mode,
12110
12184
  view
12111
12185
  };
@@ -12125,16 +12199,21 @@ function spreadsheetCollaborationViewState(content) {
12125
12199
  zoomBySheet
12126
12200
  };
12127
12201
  }
12128
- function updateSpreadsheetCollaborationViewState(view, content) {
12129
- pruneSpreadsheetCollaborationViewState(view, content);
12130
- const active = content.sheets.find((sheet)=>1 === sheet.status);
12202
+ function updateSpreadsheetCollaborationViewStateForContentChange(view, previous, next) {
12203
+ pruneSpreadsheetCollaborationViewState(view, next);
12204
+ if (!spreadsheetSheetVisibilityChanged(previous, next)) return;
12205
+ const active = next.sheets.find((sheet)=>1 === sheet.status && 1 !== sheet.hide);
12131
12206
  if (active?.id) view.activeSheetId = active.id;
12132
- for (const sheet of content.sheets){
12133
- if (!sheet.id) continue;
12134
- const selection = sheet.luckysheet_select_save?.at(-1);
12135
- if (selection) view.selections.set(sheet.id, finiteSpreadsheetSelection(selection));
12136
- if (void 0 !== sheet.zoomRatio) view.zoomBySheet.set(sheet.id, sheet.zoomRatio);
12137
- }
12207
+ }
12208
+ function spreadsheetSheetVisibilityChanged(previous, next) {
12209
+ if (previous.sheets.length !== next.sheets.length) return true;
12210
+ const previousById = new Map(previous.sheets.flatMap((sheet)=>sheet.id ? [
12211
+ [
12212
+ sheet.id,
12213
+ sheet.hide
12214
+ ]
12215
+ ] : []));
12216
+ return next.sheets.some((sheet)=>!sheet.id || !previousById.has(sheet.id) || previousById.get(sheet.id) !== sheet.hide);
12138
12217
  }
12139
12218
  function spreadsheetContentWithCollaborationViewState(content, view) {
12140
12219
  pruneSpreadsheetCollaborationViewState(view, content);
@@ -12194,10 +12273,11 @@ function CollaborativeSpreadsheetEditor(props) {
12194
12273
  collaborationHistory: shared.history,
12195
12274
  collaborationView: shared.view,
12196
12275
  onChange: shared.onChange,
12276
+ onDerivedChange: shared.onDerivedChange,
12197
12277
  preview: props.preview || shared.readOnly
12198
12278
  });
12199
12279
  }
12200
- function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHistory, collaborationView, kernelWasmUrl, preview, saveStatus = '已自动保存', fileActions, onChange, onAgentRequest }) {
12280
+ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHistory, collaborationView, kernelWasmUrl, preview, saveStatus = '已自动保存', fileActions, onChange, onDerivedChange, onAgentRequest }) {
12201
12281
  const materializedContent = useMemo(()=>refreshSpreadsheetPivotTables(content), [
12202
12282
  content
12203
12283
  ]);
@@ -12396,19 +12476,24 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
12396
12476
  const workbookSheetsRef = useRef(workbookSheets);
12397
12477
  workbookSheetsRef.current = displayedWorkbookSheets;
12398
12478
  const handleWorkbookChange = useCallback((sheets)=>{
12399
- if (ignoreChangeDuringExternalSync(sameSpreadsheetWorkbookState(sheets, workbookSheetsRef.current))) return void takeWorkbookOperations();
12479
+ const controlledSheets = spreadsheetSheetsFromFortune(sheets, contentRef.current.sheets);
12480
+ const matchesRenderedWorkbook = sameSpreadsheetWorkbookState(controlledSheets, workbookSheetsRef.current);
12481
+ if (ignoreChangeDuringExternalSync(matchesRenderedWorkbook)) return void takeWorkbookOperations();
12400
12482
  const operations = takeWorkbookOperations();
12401
- if (previewRef.current || !operations.length && !calculation.hasPendingResultPatches() && sameSpreadsheetWorkbookState(sheets, workbookSheetsRef.current)) return;
12402
- const withCharts = reconcileSpreadsheetChartPreviews(contentRef.current, sheets);
12483
+ const hasPendingResultPatches = calculation.hasPendingResultPatches();
12484
+ if (previewRef.current || matchesRenderedWorkbook && !hasPendingResultPatches) return;
12485
+ const withCharts = reconcileSpreadsheetChartPreviews(contentRef.current, controlledSheets);
12403
12486
  const next = reconcileSpreadsheetPivots(contentRef.current, withCharts.sheets);
12404
12487
  contentRef.current = next;
12405
12488
  acceptWorkbookContent(next);
12406
12489
  calculation.synchronizeWorkbook(next, operations);
12407
- spreadsheetCommandsRef.current?.setSpreadsheetContent(next);
12490
+ if (matchesRenderedWorkbook && onDerivedChange) onDerivedChange(next);
12491
+ else spreadsheetCommandsRef.current?.setSpreadsheetContent(next);
12408
12492
  }, [
12409
12493
  acceptWorkbookContent,
12410
12494
  calculation,
12411
12495
  ignoreChangeDuringExternalSync,
12496
+ onDerivedChange,
12412
12497
  takeWorkbookOperations
12413
12498
  ]);
12414
12499
  const handleWorkbookOperations = useCallback((operations)=>{
@@ -12514,14 +12599,14 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
12514
12599
  pasteSpreadsheetSelection(browserSpreadsheetClipboard, commands.pasteCells);
12515
12600
  return true;
12516
12601
  };
12517
- const activateReadOnlySpreadsheetSheet = useCallback((sheetId)=>{
12518
- if (!previewRef.current) return false;
12602
+ const activateLocalSpreadsheetSheet = useCallback((sheetId)=>{
12603
+ if (!previewRef.current && !collaborationView) return false;
12519
12604
  const sheet = contentRef.current.sheets.find((candidate)=>candidate.id === sheetId && 1 !== candidate.hide);
12520
12605
  if (!sheet) return false;
12521
12606
  activeSheetIdRef.current = sheetId;
12522
- collaborationView?.activateSheet(sheetId);
12607
+ if (collaborationView && !collaborationView.activateSheet(sheetId)) return false;
12523
12608
  setSelectionState(null);
12524
- setPreviewActiveSheetId(sheetId);
12609
+ if (previewRef.current) setPreviewActiveSheetId(sheetId);
12525
12610
  try {
12526
12611
  workbookRef.current?.activateSheet({
12527
12612
  id: sheetId
@@ -12564,8 +12649,8 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
12564
12649
  selection: selectionState,
12565
12650
  targetSheetId: toolbarSheetId,
12566
12651
  toolbarCell,
12567
- view: preview ? {
12568
- activateSheet: activateReadOnlySpreadsheetSheet
12652
+ view: preview || collaborationView ? {
12653
+ activateSheet: activateLocalSpreadsheetSheet
12569
12654
  } : null,
12570
12655
  workbook: workbookInstance
12571
12656
  }, spreadsheetExtensions);