@a3s-lab/office 0.42.0 → 0.44.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.
- package/README.md +33 -1
- package/dist/0~1403.js +1 -1
- package/dist/0~2330.js +1 -1
- package/dist/0~3539.js +2 -2
- package/dist/0~3557.js +1 -1
- package/dist/0~4980.js +1 -1
- package/dist/0~5024.js +1 -1
- package/dist/0~7828.js +1 -1
- package/dist/{0~7048.js → 0~7831.js} +2 -547
- package/dist/0~document-editor.js +4 -3
- package/dist/{0~3635.js → 0~office-color-picker.js} +1 -12
- package/dist/0~pdf-viewer.js +22 -8
- package/dist/0~presentation-editor.js +4 -3
- package/dist/0~spreadsheet-editor.js +314 -3617
- package/dist/0~work-docx-export.js +1 -1
- package/dist/0~work-docx-import.js +1 -1
- package/dist/0~work-office-diagnostics.js +1 -1
- package/dist/0~work-pptx-export.js +1 -1
- package/dist/0~work-pptx-import.js +1 -1
- package/dist/0~work-presentation-charts.js +1 -1
- package/dist/3818.js +1 -1
- package/dist/4166.js +1 -1
- package/dist/4174.js +3 -3
- package/dist/4560.js +2 -2
- package/dist/{5416.js → 610.js} +4335 -410
- package/dist/{4121.js → 6968.js} +875 -13
- package/dist/7060.js +1 -1
- package/dist/{4650.js → 816.js} +12 -1
- package/dist/8715.js +1 -1
- package/dist/9333.js +17 -17
- package/dist/core.js +7 -5
- package/dist/index.js +2 -2
- package/dist/internal/features/work/editors/spreadsheet-conditional-format-model.d.ts +6 -1
- package/dist/internal/features/work/editors/spreadsheet-data-validation-list.d.ts +44 -0
- package/dist/internal/features/work/editors/spreadsheet-data-validation.d.ts +2 -1
- package/dist/internal/features/work/editors/spreadsheet-editor-support.d.ts +1 -1
- package/dist/internal/features/work/work-spreadsheet-conditional-format.d.ts +3 -1
- package/dist/internal/features/work/work-spreadsheet-local-formula.d.ts +43 -0
- package/dist/internal/features/work/work-types.d.ts +4 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +13 -1
- package/package.json +4 -1
|
@@ -4,18 +4,18 @@ import { Workbook } from "@fortune-sheet/react";
|
|
|
4
4
|
import { AArrowDown, AArrowUp, AlignCenter, AlignLeft, AlignRight, AlignVerticalJustifyCenter, AlignVerticalJustifyEnd, AlignVerticalJustifyStart, ArrowDown, ArrowDownToLine, ArrowLeft, ArrowLeftToLine, ArrowRight, ArrowRightToLine, ArrowUp, ArrowUpToLine, BadgeJapaneseYen, BarChart3, BetweenHorizontalEnd, BetweenHorizontalStart, BetweenVerticalEnd, BetweenVerticalStart, Bold, Bookmark, Calculator, CalendarClock, CalendarDays, Check, ChevronDown, ChevronLeft, ChevronRight, ClipboardPaste, Clock, Cloud, Columns3, Copy, CopyPlus, DecimalsArrowLeft, DecimalsArrowRight, Eraser, Eye, EyeOff, FileX2, Globe2, Grid2X2, Grid3X3, Hash, Info, Italic, KeyRound, Layers2, Link2, Link2Off, ListChecks, ListFilter, ListOrdered, ListPlus, LocateFixed, Merge, MessageSquareX, MoveHorizontal, MoveVertical, Paintbrush, Palette, PanelLeft, PanelTop, PanelsTopLeft, Pencil, Percent, Plus, Printer, Redo2, RefreshCw, Rows3, Save, Scissors, Search, Settings2, ShieldCheck, Sigma, SlidersHorizontal, SortAsc, SortDesc, Strikethrough, SwatchBook, Table2, TableProperties, Trash2, TriangleAlert, Underline, Undo2, WrapText, X } from "lucide-react";
|
|
5
5
|
import { Fragment as external_react_Fragment, cloneElement, createElement, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { convertSpanToShareString, genarate, indexToColumnChar, update as core_update } from "@fortune-sheet/core";
|
|
7
|
-
import {
|
|
8
|
-
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, isSpreadsheetConditionalIconSetName, spreadsheetConditionalThresholdsEqual, spreadsheetConditionalIconSetCount, spreadsheetConditionalIconAppearance } from "./9333.js";
|
|
9
|
-
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";
|
|
10
|
-
import { workSpreadsheetFilterHiddenRows, xlsxRichTextCellText, spreadsheetTextOrientationFromAngle, spreadsheetCellValueWithDiagonalBorder, spreadsheetUnderlineCellValue, workSpreadsheetAutoFilterCriteriaEntries, spreadsheetTableTotalsFunctionLabel, withDefaultSpreadsheetTableTotalsLabel, normalizeXlsxRichTextEditSource, spreadsheetTableTotalsColumnPatch, SPREADSHEET_TABLE_TOTALS_FUNCTIONS, normalizeXlsxRichTextCell, activeXlsxNativeFill, normalizeSpreadsheetDateValidationBoundary, spreadsheetTextOrientationChoiceFromCell, parseSpreadsheetSortCustomList, workSpreadsheetFilterTextCharacters, sameXlsxRichTextRunStyle, spreadsheetSortCustomListMatchKey, createSpreadsheetSortCustomList, reconcileSpreadsheetTableTotalsColumns, spreadsheetTextOrientationFromCell, patchSpreadsheetRichTextFontRuns, synchronizeSpreadsheetTableTotalsRow, workSpreadsheetAutoFilterManuallyHiddenRows, normalizedWorkSpreadsheetAutoFilterRange, workSpreadsheetSheetWithoutAutoFilterCriteria, isSpreadsheetTextOrientationId, reconcileSpreadsheetTableCalculatedColumns, spreadsheetVisibleTextRotationFromCell, spreadsheetTextOrientationFromChoice, normalizeWorkSpreadsheetFilterCriteria, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, normalizeSpreadsheetDataValidationErrorStyle, workSpreadsheetAutoFilterCriteria, normalizeXlsxRichTextColor, isHighSurrogate, xlsxNativeFillCellKeys, workSpreadsheetSheetWithReappliedAutoFilterCriteria, workSpreadsheetSheetWithAutoFilterCriteria, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, boundedSpreadsheetDataValidationText, spreadsheetUnderlineStyle, workSpreadsheetCellIsDate, workSpreadsheetAutoFilterOwnedRows, deleteXlsxNativeFills, spreadsheetTextOrientationCellStyle, normalizeXlsxRichTextRun, spreadsheetDiagonalBorderFromCellValue, isLowSurrogate, mergeSpreadsheetSortCustomLists, spreadsheetTableTotalsRowHasContent, validateSpreadsheetSortCustomList, normalizeStoredSpreadsheetSortCustomLists, coalesceXlsxRichTextRuns, fillSpreadsheetTableCalculatedColumns, replaceOfficeSpreadsheetCollaboration as replaceWorkOfficeSpreadsheetCollaboration, validXlsxRichText, spreadsheetSortCustomListsEqual, isSpreadsheetUnderlineStyle } from "./5416.js";
|
|
7
|
+
import { spreadsheetCellRangeContains, isSpreadsheetCellEditorTarget, spreadsheetSortAppearanceFields, finiteSpreadsheetSelection, withDefaultSpreadsheetTableTotalsLabel, SPREADSHEET_TABLE_TOTALS_FUNCTIONS, normalizeSpreadsheetDateValidationBoundary, createSpreadsheetSortDirectAppearanceRows, createSpreadsheetSortRangePlan, spreadsheetTextOrientationChoiceFromCell, spreadsheetSheetsForFortune, selectSpreadsheetFormulaBarContents, evaluateSpreadsheetLocalFormula, normalizeSpreadsheetCellRange as spreadsheet_cell_range_normalizeSpreadsheetCellRange, spreadsheetCellRangesIntersect, parseSpreadsheetSortCustomList, subtractSpreadsheetCellRange, workSpreadsheetFilterTextCharacters, spreadsheetSheetsWithFiniteSelections, spreadsheetTextOrientationFromCell, captureSpreadsheetRichTextPaste, patchSpreadsheetRichTextFontRuns, withSpreadsheetCellFillFormat, spreadsheetCellRangeArea as spreadsheet_cell_range_spreadsheetCellRangeArea, normalizeSpreadsheetDependentListFormula, spreadsheetSortAppearanceTargetsEqual, normalizeWorkSpreadsheetFilterCriteria, spreadsheet_editor_support_spreadsheetCellAt, isSpreadsheetTextOrientationId, spreadsheetSortAppearanceTargetValue, reconcileSpreadsheetTableCalculatedColumns, workSpreadsheetAutoFilterCriteria, spreadsheetSortCellMatchesAppearance, spreadsheetSelectionSummary, normalizeXlsxRichTextColor, resolveSpreadsheetGoToTarget, createSpreadsheetSortDialogSource, createOfficeSpreadsheetCollaborationBinding as createWorkOfficeSpreadsheetCollaborationBinding, boundedSpreadsheetDataValidationText, workSpreadsheetCellIsDate, scopeSpreadsheetKeyboardSelection, captureSpreadsheetRichTextDomSelection, spreadsheetSortAppearanceRowsMatch, spreadsheetTableTotalsRowHasContent, spreadsheetDiagonalBorderFromCellValue, reconcileSpreadsheetFiltersAfterSort, parseSpreadsheetCellRange, spreadsheetFontSizeOptions, spreadsheetCellFillFormat, normalizeStoredSpreadsheetSortCustomLists, spreadsheetContentWithSelection, spreadsheetConditionalFormatStyles, replaceOfficeSpreadsheetCollaboration as replaceWorkOfficeSpreadsheetCollaboration, spreadsheetSortCustomListsEqual, isSpreadsheetUnderlineStyle, spreadsheetLocalFormulaBoolean, xlsxRichTextCellText, spreadsheetTextOrientationFromAngle, spreadsheetCellValueWithDiagonalBorder, spreadsheetUnderlineCellValue, resolveSpreadsheetDependentListReference, spreadsheetFilterReconciliationIsBounded, validateSpreadsheetSortRequest, spreadsheetTableTotalsFunctionLabel, spreadsheetCurrentRegion, createSpreadsheetSortTextComparator, normalizeXlsxRichTextEditSource, spreadsheetTableTotalsColumnPatch, spreadsheetSheetsFromFortune, spreadsheetSortAppearanceTargetLabel, activeXlsxNativeFill, isSpreadsheetDependentListFormula, spreadsheetSortRowsMatchRange, spreadsheetCellRangesEqual, sameSpreadsheetHistoryContent, spreadsheetSheetBounds, isSpreadsheetNativeTextUndoTarget, isSpreadsheetCellEditingTarget, runSpreadsheetSelectionMoveShortcut, spreadsheetGoToValidationMessage, spreadsheetSortCustomListMatchKey, createSpreadsheetSortCustomList, spreadsheetRowHasContent, workSpreadsheetAutoFilterManuallyHiddenRows, normalizedWorkSpreadsheetAutoFilterRange, nextSpreadsheetFontSize, spreadsheetSelectionReference, workSpreadsheetSheetWithoutAutoFilterCriteria, spreadsheetVisibleTextRotationFromCell, spreadsheetTextOrientationFromChoice, isSpreadsheetGridKeyboardTarget, synchronizeSpreadsheetTableTotalsRow, readOfficeSpreadsheetCollaboration as readWorkOfficeSpreadsheetCollaboration, sameSpreadsheetWorkbookState, normalizeSpreadsheetDataValidationErrorStyle, moveSpreadsheetKeyboardSelection, spreadsheetSortAppearanceTargets, createSpreadsheetSortAppearanceRows, xlsxNativeFillCellKeys, workSpreadsheetSheetWithAutoFilterCriteria, MAX_SPREADSHEET_SORT_CELLS, runSpreadsheetSelectionScopeShortcut, spreadsheetUnderlineStyle, spreadsheetSortError, deleteXlsxNativeFills, spreadsheetTextOrientationCellStyle, parseSpreadsheetSortAppearanceTargetValue, normalizeSpreadsheetCellFillFormat, createSpreadsheetSortRangeDialogSource, spreadsheetSortRowsFromSheet, spreadsheetSelectionContainsFocus, mergeSpreadsheetSortCustomLists, spreadsheetFontFamilyOptions, canApplySpreadsheetTableStructureChange, spreadsheetSingleRange, sameSpreadsheetWorkbookStateAfterOperations, spreadsheetSheetCellReader, coalesceXlsxRichTextRuns, spreadsheetContentWithSelections, spreadsheetSortOwnedRangeForExactRange } from "./610.js";
|
|
11
8
|
import { moveOfficeMenuFocus, stepOfficeZoom, useOfficeRemoteParticipants, Popover, Dialog, officePresenceColor, useOfficePublishPresenceLocation, button_Button, icon_button_IconButton, moveOfficeGridMenuFocus, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom } from "./0~8136.js";
|
|
12
|
-
import {
|
|
13
|
-
import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeDialog, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown, normalizeFormulaForFortuneParser } from "./0~7048.js";
|
|
14
|
-
import { isOfficeShortcutBlocked, OfficeColorPicker } from "./0~3635.js";
|
|
9
|
+
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";
|
|
15
10
|
import { createOfficeEditorExtension, useOfficeEditorRuntime, useOfficeEditorKeyboardShortcuts } from "./0~3557.js";
|
|
11
|
+
import { isOfficeShortcutBlocked } from "./816.js";
|
|
12
|
+
import { withoutEditableRange, editableRangeRequiresCredentials, spreadsheetMatrixProfile, normalizeSheetProtectionAuthority, SPREADSHEET_CONDITIONAL_COMPARISON_OPERATORS, defaultSpreadsheetDataBarOptions, spreadsheetProtectionKey, withSheetProtection, normalizeSpreadsheetConditionalVisualOptions, defaultSpreadsheetColorScaleThresholds, protectedSheetCount, normalizeSpreadsheetConditionalIconSetFormat, SPREADSHEET_CONDITIONAL_ICON_SETS, effectiveSpreadsheetPageSetup, withSheetSelectionPermissions, withEditableRange, spreadsheetConditionalComparisonNeedsUpperValue, isSpreadsheetConditionalComparisonOperator, drawSpreadsheetConditionalIcon, defaultSpreadsheetConditionalIconThresholds, xlsxPatternFillTypes, sheetProtectionAuthority, editableRangeCellCount, unlockedCellCount, spreadsheetConditionalThresholdsEqual } from "./9333.js";
|
|
13
|
+
import { isOfficeKernelSpreadsheetError, normalizeCssColor, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, createOfficeId as createWorkId } from "./6968.js";
|
|
16
14
|
import { showToast } from "./6489.js";
|
|
17
15
|
import { unsupportedSpreadsheetFormulaFunctions } from "./0~work-spreadsheet-formula-support.js";
|
|
18
16
|
import { WorkspaceContextMenu, isWorkspaceContextMenuKeyboardEvent, WorkOfficeZoomControls, Tabs, WorkOfficeStatusBar, WorkOfficeRibbon, workspaceContextMenuPosition, OfficeSelect, WorkOfficeRibbonGroup, WorkOfficeRibbonButton, WorkOfficePreviewBar } from "./0~9073.js";
|
|
17
|
+
import { OfficeColorPicker } from "./0~office-color-picker.js";
|
|
18
|
+
import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeTaskPaneModal, useOfficeDialog, createOfficeKernelClient, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown } from "./0~7831.js";
|
|
19
19
|
import { useOfficeDraft, InlineNotice } from "./0~345.js";
|
|
20
20
|
import { spreadsheetAgentMenuItems, SpreadsheetChartSeriesStyleEditor, normalizeOptionalOfficeNumber, normalizeRequiredOfficeNumber, SpreadsheetErrorBarEditor, useOfficeHistory, spreadsheetAgentSelection, SpreadsheetTrendlineEditor, applySpreadsheetAgentProposalChanges } from "./0~3539.js";
|
|
21
21
|
import { useOfficeEditorFocusOrigin, useOfficeCollaborationLocationNavigator, StateView } from "./432.js";
|
|
@@ -89,395 +89,6 @@ function drawCellValue(context, bounds, cell, conditionalTextColor) {
|
|
|
89
89
|
context.fillText(String(value), bounds.startX + 4, bounds.startY + height / 2, bounds.endX - bounds.startX - 8);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
function parseSpreadsheetConditionalColor(value) {
|
|
93
|
-
const hexadecimal = /^#?([0-9a-f]{6})$/i.exec(value.trim());
|
|
94
|
-
if (hexadecimal) return {
|
|
95
|
-
red: Number.parseInt(hexadecimal[1].slice(0, 2), 16),
|
|
96
|
-
green: Number.parseInt(hexadecimal[1].slice(2, 4), 16),
|
|
97
|
-
blue: Number.parseInt(hexadecimal[1].slice(4, 6), 16)
|
|
98
|
-
};
|
|
99
|
-
const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(value);
|
|
100
|
-
if (!rgb) return null;
|
|
101
|
-
return {
|
|
102
|
-
red: boundedColorChannel(rgb[1]),
|
|
103
|
-
green: boundedColorChannel(rgb[2]),
|
|
104
|
-
blue: boundedColorChannel(rgb[3])
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function interpolateSpreadsheetConditionalColor(start, end, amount) {
|
|
108
|
-
return {
|
|
109
|
-
red: Math.round(start.red + (end.red - start.red) * amount),
|
|
110
|
-
green: Math.round(start.green + (end.green - start.green) * amount),
|
|
111
|
-
blue: Math.round(start.blue + (end.blue - start.blue) * amount)
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function spreadsheetConditionalCssColor(color) {
|
|
115
|
-
return `rgb(${color.red}, ${color.green}, ${color.blue})`;
|
|
116
|
-
}
|
|
117
|
-
function spreadsheetConditionalRatio(value, minimum, maximum) {
|
|
118
|
-
if (maximum === minimum) return 0;
|
|
119
|
-
return Math.max(0, Math.min(1, (value - minimum) / (maximum - minimum)));
|
|
120
|
-
}
|
|
121
|
-
function boundedColorChannel(value) {
|
|
122
|
-
return Math.max(0, Math.min(255, Number(value)));
|
|
123
|
-
}
|
|
124
|
-
function spreadsheetConditionalDataBar(color, value, minimum, maximum, minLength, maxLength, showValue) {
|
|
125
|
-
if (minimum < 0 && maximum > 0) {
|
|
126
|
-
const axis = -minimum / (maximum - minimum) * 100;
|
|
127
|
-
const ratio = value < 0 ? Math.abs(value / minimum) : value > 0 ? value / maximum : 0;
|
|
128
|
-
const width = 0 === value ? 0 : displayLength(ratio, minLength, maxLength) * (value < 0 ? axis / 100 : (100 - axis) / 100);
|
|
129
|
-
return {
|
|
130
|
-
color,
|
|
131
|
-
startPercent: value < 0 ? axis - width : axis,
|
|
132
|
-
widthPercent: width,
|
|
133
|
-
axisPercent: axis,
|
|
134
|
-
showValue
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
if (maximum <= 0) {
|
|
138
|
-
const ratio = minimum === maximum ? 0 === value ? 0 : 1 : Math.max(0, Math.min(1, (maximum - value) / (maximum - minimum)));
|
|
139
|
-
const width = 0 === value ? 0 : displayLength(ratio, minLength, maxLength);
|
|
140
|
-
return {
|
|
141
|
-
color,
|
|
142
|
-
startPercent: 100 - width,
|
|
143
|
-
widthPercent: width,
|
|
144
|
-
axisPercent: 100,
|
|
145
|
-
showValue
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
const ratio = minimum === maximum ? 0 === value ? 0 : 1 : Math.max(0, Math.min(1, (value - minimum) / (maximum - minimum)));
|
|
149
|
-
const width = 0 === value ? 0 : displayLength(ratio, minLength, maxLength);
|
|
150
|
-
return {
|
|
151
|
-
color,
|
|
152
|
-
startPercent: 0,
|
|
153
|
-
widthPercent: width,
|
|
154
|
-
axisPercent: 0,
|
|
155
|
-
showValue
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function displayLength(valueRatio, minLength, maxLength) {
|
|
159
|
-
const normalized = Math.max(0, Math.min(1, valueRatio));
|
|
160
|
-
return minLength + (maxLength - minLength) * normalized;
|
|
161
|
-
}
|
|
162
|
-
function spreadsheetConditionalFormatStyles(sheet) {
|
|
163
|
-
const styles = new Map();
|
|
164
|
-
const data = sheet.data ?? [];
|
|
165
|
-
if (!data.length) return styles;
|
|
166
|
-
const blocked = new Set();
|
|
167
|
-
for (const rule of conditionalRules(sheet)){
|
|
168
|
-
const matched = new Set();
|
|
169
|
-
if ('colorGradation' === rule.type) applyColorScale(styles, data, rule, blocked, matched);
|
|
170
|
-
else if ('dataBar' === rule.type) applyDataBars(styles, data, rule, blocked, matched);
|
|
171
|
-
else if ('icons' === rule.type) applyIconSet(styles, data, rule, blocked, matched);
|
|
172
|
-
else applyDefaultRule(styles, data, rule, blocked, matched);
|
|
173
|
-
if (rule.stopIfTrue) for (const key of matched)blocked.add(key);
|
|
174
|
-
}
|
|
175
|
-
return styles;
|
|
176
|
-
}
|
|
177
|
-
function applyDefaultRule(styles, data, rule, blocked, matched) {
|
|
178
|
-
if ('default' !== rule.type || !isConditionalFormatStyle(rule.format) || !rule.conditionName) return;
|
|
179
|
-
const values = (rule.conditionValue ?? []).map(String);
|
|
180
|
-
const style = rule.format;
|
|
181
|
-
if ('formula' === rule.conditionName && values[0]) {
|
|
182
|
-
for (const range of rule.cellrange)forEachCell(data, [
|
|
183
|
-
range
|
|
184
|
-
], ({ row, column })=>{
|
|
185
|
-
if (evaluateFormula(values[0], row, column, range, data)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if ('duplicateValue' === rule.conditionName) {
|
|
190
|
-
for (const range of rule.cellrange){
|
|
191
|
-
const positions = cellPositions(data, [
|
|
192
|
-
range
|
|
193
|
-
]).filter(({ row, column })=>null != work_spreadsheet_conditional_format_cellValue(data, row, column));
|
|
194
|
-
const occurrences = new Map();
|
|
195
|
-
for (const { row, column } of positions){
|
|
196
|
-
const key = comparableKey(work_spreadsheet_conditional_format_cellValue(data, row, column));
|
|
197
|
-
occurrences.set(key, (occurrences.get(key) ?? 0) + 1);
|
|
198
|
-
}
|
|
199
|
-
const unique = '1' === values[0];
|
|
200
|
-
for (const { row, column } of positions){
|
|
201
|
-
const count = occurrences.get(comparableKey(work_spreadsheet_conditional_format_cellValue(data, row, column))) ?? 0;
|
|
202
|
-
if (unique && 1 === count || !unique && count > 1) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if ([
|
|
208
|
-
'top10',
|
|
209
|
-
'top10_percent',
|
|
210
|
-
'last10',
|
|
211
|
-
'last10_percent'
|
|
212
|
-
].includes(rule.conditionName)) {
|
|
213
|
-
for (const range of rule.cellrange)applyRankRule(styles, data, range, rule.conditionName, values[0], style, blocked, matched);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if ('aboveAverage' === rule.conditionName || 'belowAverage' === rule.conditionName) {
|
|
217
|
-
for (const range of rule.cellrange)applyAverageRule(styles, data, range, rule.conditionName, style, blocked, matched);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
forEachCell(data, rule.cellrange, ({ row, column })=>{
|
|
221
|
-
const current = work_spreadsheet_conditional_format_cellValue(data, row, column);
|
|
222
|
-
if (matchesCondition(current, rule.conditionName, values)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
function applyRankRule(styles, data, range, conditionName, sourceRank, style, blocked, matched) {
|
|
226
|
-
const positions = cellPositions(data, [
|
|
227
|
-
range
|
|
228
|
-
]);
|
|
229
|
-
const numbers = positions.flatMap(({ row, column })=>{
|
|
230
|
-
const value = numericCellValue(data, row, column);
|
|
231
|
-
return null === value ? [] : [
|
|
232
|
-
value
|
|
233
|
-
];
|
|
234
|
-
});
|
|
235
|
-
if (!numbers.length) return;
|
|
236
|
-
numbers.sort((left, right)=>right - left);
|
|
237
|
-
const requested = Math.max(1, Math.trunc(Number(sourceRank) || 10));
|
|
238
|
-
const count = conditionName.endsWith('_percent') ? Math.max(1, Math.ceil(Math.min(100, requested) / 100 * numbers.length)) : Math.min(numbers.length, requested);
|
|
239
|
-
const bottom = conditionName.startsWith('last');
|
|
240
|
-
const threshold = bottom ? numbers[numbers.length - count] : numbers[count - 1];
|
|
241
|
-
for (const { row, column } of positions){
|
|
242
|
-
const value = numericCellValue(data, row, column);
|
|
243
|
-
if (null !== value && (bottom ? value <= threshold : value >= threshold)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function applyAverageRule(styles, data, range, conditionName, style, blocked, matched) {
|
|
247
|
-
const positions = cellPositions(data, [
|
|
248
|
-
range
|
|
249
|
-
]);
|
|
250
|
-
const values = positions.flatMap(({ row, column })=>{
|
|
251
|
-
const value = numericCellValue(data, row, column);
|
|
252
|
-
return null === value ? [] : [
|
|
253
|
-
value
|
|
254
|
-
];
|
|
255
|
-
});
|
|
256
|
-
if (!values.length) return;
|
|
257
|
-
const average = values.reduce((sum, value)=>sum + value, 0) / values.length;
|
|
258
|
-
for (const { row, column } of positions){
|
|
259
|
-
const value = numericCellValue(data, row, column);
|
|
260
|
-
if (null !== value && ('aboveAverage' === conditionName ? value > average : value < average)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
function applyColorScale(styles, data, rule, blocked, matched) {
|
|
264
|
-
if (!Array.isArray(rule.format) || rule.format.length < 2) return;
|
|
265
|
-
const positions = cellPositions(data, rule.cellrange);
|
|
266
|
-
const numericPositions = positions.flatMap((position)=>{
|
|
267
|
-
const value = numericCellValue(data, position.row, position.column);
|
|
268
|
-
return null === value ? [] : [
|
|
269
|
-
{
|
|
270
|
-
...position,
|
|
271
|
-
value
|
|
272
|
-
}
|
|
273
|
-
];
|
|
274
|
-
});
|
|
275
|
-
if (!numericPositions.length) return;
|
|
276
|
-
const colors = rule.format.slice(0, 3).map(parseSpreadsheetConditionalColor);
|
|
277
|
-
if (colors.some((color)=>!color)) return;
|
|
278
|
-
const values = numericPositions.map(({ value })=>value);
|
|
279
|
-
const options = rule.visualOptions ? normalizeSpreadsheetConditionalVisualOptions(rule.visualOptions, colors.length, 'colorScale') : {
|
|
280
|
-
thresholds: defaultSpreadsheetColorScaleThresholds(colors.length)
|
|
281
|
-
};
|
|
282
|
-
if (!options) return;
|
|
283
|
-
const points = options.thresholds.map((threshold)=>spreadsheetConditionalThresholdValue(threshold, values));
|
|
284
|
-
if (points.some((point)=>null === point)) return;
|
|
285
|
-
const minimum = points[0];
|
|
286
|
-
const midpoint = 3 === points.length ? points[1] : null;
|
|
287
|
-
const maximum = points.at(-1);
|
|
288
|
-
const maximumColor = colors[0];
|
|
289
|
-
const midpointColor = 3 === colors.length ? colors[1] : null;
|
|
290
|
-
const minimumColor = colors.at(-1);
|
|
291
|
-
for (const { row, column, value } of numericPositions){
|
|
292
|
-
let color;
|
|
293
|
-
color = maximum === minimum ? midpointColor ?? minimumColor : midpointColor && null !== midpoint && value <= midpoint ? interpolateSpreadsheetConditionalColor(minimumColor, midpointColor, spreadsheetConditionalRatio(value, minimum, midpoint)) : midpointColor && null !== midpoint ? interpolateSpreadsheetConditionalColor(midpointColor, maximumColor, spreadsheetConditionalRatio(value, midpoint, maximum)) : interpolateSpreadsheetConditionalColor(minimumColor, maximumColor, spreadsheetConditionalRatio(value, minimum, maximum));
|
|
294
|
-
applyConditionalStyle(styles, blocked, matched, row, column, {
|
|
295
|
-
cellColor: spreadsheetConditionalCssColor(color)
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
function applyDataBars(styles, data, rule, blocked, matched) {
|
|
300
|
-
if (!isConditionalFormatStyle(rule.format)) return;
|
|
301
|
-
const color = rule.format.cellColor;
|
|
302
|
-
if (!color) return;
|
|
303
|
-
const positions = cellPositions(data, rule.cellrange);
|
|
304
|
-
const numericPositions = positions.flatMap((position)=>{
|
|
305
|
-
const value = numericCellValue(data, position.row, position.column);
|
|
306
|
-
return null === value ? [] : [
|
|
307
|
-
{
|
|
308
|
-
...position,
|
|
309
|
-
value
|
|
310
|
-
}
|
|
311
|
-
];
|
|
312
|
-
});
|
|
313
|
-
if (!numericPositions.length) return;
|
|
314
|
-
const values = numericPositions.map(({ value })=>value);
|
|
315
|
-
const options = rule.visualOptions ? normalizeSpreadsheetConditionalVisualOptions(rule.visualOptions, 2, 'dataBar') : defaultSpreadsheetDataBarOptions();
|
|
316
|
-
if (!options) return;
|
|
317
|
-
const points = options.thresholds.map((threshold)=>spreadsheetConditionalThresholdValue(threshold, values));
|
|
318
|
-
if (points.some((point)=>null === point)) return;
|
|
319
|
-
const minimum = points[0];
|
|
320
|
-
const maximum = points[1];
|
|
321
|
-
for (const { row, column, value } of numericPositions)applyConditionalStyle(styles, blocked, matched, row, column, {
|
|
322
|
-
dataBar: spreadsheetConditionalDataBar(color, value, minimum, maximum, options.minLength ?? 10, options.maxLength ?? 90, false !== options.showValue)
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
function applyIconSet(styles, data, rule, blocked, matched) {
|
|
326
|
-
if ('icons' !== rule.type) return;
|
|
327
|
-
const format = normalizeSpreadsheetConditionalIconSetFormat(rule.format);
|
|
328
|
-
if (!format) return;
|
|
329
|
-
const numericPositions = cellPositions(data, rule.cellrange).flatMap((position)=>{
|
|
330
|
-
const value = numericCellValue(data, position.row, position.column);
|
|
331
|
-
return null === value ? [] : [
|
|
332
|
-
{
|
|
333
|
-
...position,
|
|
334
|
-
value
|
|
335
|
-
}
|
|
336
|
-
];
|
|
337
|
-
});
|
|
338
|
-
const values = numericPositions.map(({ value })=>value);
|
|
339
|
-
for (const { row, column, value } of numericPositions){
|
|
340
|
-
const icon = spreadsheetConditionalIconForValue(format, value, values);
|
|
341
|
-
if (icon) applyConditionalStyle(styles, blocked, matched, row, column, {
|
|
342
|
-
icon
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
function matchesCondition(value, name, conditions) {
|
|
347
|
-
if (null == value) return false;
|
|
348
|
-
if ('textContains' === name) return String(value).toLowerCase().includes((conditions[0] ?? '').toLowerCase());
|
|
349
|
-
if ('equal' === name) return 0 === compareValues(value, conditions[0]);
|
|
350
|
-
if ('notEqual' === name) return 0 !== compareValues(value, conditions[0]);
|
|
351
|
-
if ('greaterThan' === name) return compareValues(value, conditions[0]) > 0;
|
|
352
|
-
if ('greaterThanOrEqual' === name) return compareValues(value, conditions[0]) >= 0;
|
|
353
|
-
if ('lessThan' === name) return compareValues(value, conditions[0]) < 0;
|
|
354
|
-
if ('lessThanOrEqual' === name) return compareValues(value, conditions[0]) <= 0;
|
|
355
|
-
if ('between' === name || 'notBetween' === name) {
|
|
356
|
-
const lower = Math.min(Number(conditions[0]), Number(conditions[1]));
|
|
357
|
-
const upper = Math.max(Number(conditions[0]), Number(conditions[1]));
|
|
358
|
-
if (!Number.isFinite(lower) || !Number.isFinite(upper)) return false;
|
|
359
|
-
const between = Number(value) >= lower && Number(value) <= upper;
|
|
360
|
-
return 'between' === name ? between : !between;
|
|
361
|
-
}
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
function evaluateFormula(source, row, column, baseRange, data) {
|
|
365
|
-
const formula = source.trim().replace(/^=/, '').replace(/\s+/g, '');
|
|
366
|
-
if (/^TRUE\(\)?$/i.test(formula)) return true;
|
|
367
|
-
if (/^FALSE\(\)?$/i.test(formula)) return false;
|
|
368
|
-
const parity = /^MOD\((ROW|COLUMN)\(\),(\d+)\)(=|<>|>=|<=|>|<)(-?\d+)$/i.exec(formula);
|
|
369
|
-
if (parity) {
|
|
370
|
-
const value = 'ROW' === parity[1].toUpperCase() ? row + 1 : column + 1;
|
|
371
|
-
return compareWithOperator(value % Number(parity[2]), parity[3], Number(parity[4]));
|
|
372
|
-
}
|
|
373
|
-
const blank = /^(NOT\()?ISBLANK\((\$?)([A-Z]{1,3})(\$?)([1-9]\d*)\)\)?$/i.exec(formula);
|
|
374
|
-
if (blank) {
|
|
375
|
-
const target = formulaReference(blank[2], blank[3], blank[4], blank[5], row, column, baseRange);
|
|
376
|
-
const empty = null == work_spreadsheet_conditional_format_cellValue(data, target.row, target.column) || '' === work_spreadsheet_conditional_format_cellValue(data, target.row, target.column);
|
|
377
|
-
return blank[1] ? !empty : empty;
|
|
378
|
-
}
|
|
379
|
-
const comparison = /^(\$?)([A-Z]{1,3})(\$?)([1-9]\d*)(=|<>|>=|<=|>|<)(?:"((?:[^"]|"")*)"|(-?\d+(?:\.\d+)?))$/i.exec(formula);
|
|
380
|
-
if (!comparison) return false;
|
|
381
|
-
const target = formulaReference(comparison[1], comparison[2], comparison[3], comparison[4], row, column, baseRange);
|
|
382
|
-
const left = work_spreadsheet_conditional_format_cellValue(data, target.row, target.column);
|
|
383
|
-
const right = void 0 !== comparison[6] ? comparison[6].replaceAll('""', '"') : Number(comparison[7]);
|
|
384
|
-
return compareWithOperator(left, comparison[5], right);
|
|
385
|
-
}
|
|
386
|
-
function formulaReference(absoluteColumn, columnName, absoluteRow, rowNumber, row, column, baseRange) {
|
|
387
|
-
const sourceColumn = decodeColumn(columnName);
|
|
388
|
-
const sourceRow = Number(rowNumber) - 1;
|
|
389
|
-
return {
|
|
390
|
-
row: absoluteRow ? sourceRow : sourceRow + row - baseRange.row[0],
|
|
391
|
-
column: absoluteColumn ? sourceColumn : sourceColumn + column - baseRange.column[0]
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
function applyDifferentialStyle(styles, blocked, matched, row, column, style) {
|
|
395
|
-
const patch = {};
|
|
396
|
-
if (style.textColor) patch.textColor = style.textColor;
|
|
397
|
-
if (style.cellColor) patch.cellColor = style.cellColor;
|
|
398
|
-
applyConditionalStyle(styles, blocked, matched, row, column, patch);
|
|
399
|
-
}
|
|
400
|
-
function applyConditionalStyle(styles, blocked, matched, row, column, patch) {
|
|
401
|
-
const key = cellKey(row, column);
|
|
402
|
-
if (blocked.has(key)) return;
|
|
403
|
-
matched.add(key);
|
|
404
|
-
if (Object.keys(patch).length) patchCellStyle(styles, row, column, patch);
|
|
405
|
-
}
|
|
406
|
-
function patchCellStyle(styles, row, column, patch) {
|
|
407
|
-
const key = cellKey(row, column);
|
|
408
|
-
styles.set(key, {
|
|
409
|
-
...patch,
|
|
410
|
-
...styles.get(key)
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
function conditionalRules(sheet) {
|
|
414
|
-
if (!Array.isArray(sheet.luckysheet_conditionformat_save)) return [];
|
|
415
|
-
return sheet.luckysheet_conditionformat_save.filter(isConditionalRule);
|
|
416
|
-
}
|
|
417
|
-
function isConditionalRule(value) {
|
|
418
|
-
if (!value || 'object' != typeof value) return false;
|
|
419
|
-
const rule = value;
|
|
420
|
-
return [
|
|
421
|
-
'default',
|
|
422
|
-
'colorGradation',
|
|
423
|
-
'dataBar',
|
|
424
|
-
'icons'
|
|
425
|
-
].includes(String(rule.type)) && Array.isArray(rule.cellrange);
|
|
426
|
-
}
|
|
427
|
-
function isConditionalFormatStyle(value) {
|
|
428
|
-
return Boolean(value) && !Array.isArray(value) && 'object' == typeof value && !('iconSet' in value) && 'textColor' in value && 'cellColor' in value;
|
|
429
|
-
}
|
|
430
|
-
function cellPositions(data, ranges) {
|
|
431
|
-
const positions = [];
|
|
432
|
-
const seen = new Set();
|
|
433
|
-
forEachCell(data, ranges, (position)=>{
|
|
434
|
-
const key = cellKey(position.row, position.column);
|
|
435
|
-
if (!seen.has(key)) {
|
|
436
|
-
seen.add(key);
|
|
437
|
-
positions.push(position);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
return positions;
|
|
441
|
-
}
|
|
442
|
-
function cellKey(row, column) {
|
|
443
|
-
return `${row}_${column}`;
|
|
444
|
-
}
|
|
445
|
-
function forEachCell(data, ranges, visit) {
|
|
446
|
-
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({
|
|
447
|
-
row,
|
|
448
|
-
column
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
function work_spreadsheet_conditional_format_cellValue(data, row, column) {
|
|
452
|
-
return data[row]?.[column]?.v ?? null;
|
|
453
|
-
}
|
|
454
|
-
function numericCellValue(data, row, column) {
|
|
455
|
-
const value = work_spreadsheet_conditional_format_cellValue(data, row, column);
|
|
456
|
-
return 'number' == typeof value && Number.isFinite(value) ? value : null;
|
|
457
|
-
}
|
|
458
|
-
function comparableKey(value) {
|
|
459
|
-
return `${typeof value}:${String(value)}`;
|
|
460
|
-
}
|
|
461
|
-
function compareValues(left, right) {
|
|
462
|
-
const leftNumber = Number(left);
|
|
463
|
-
const rightNumber = Number(right);
|
|
464
|
-
if (String(left).trim() && String(right).trim() && Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) return leftNumber === rightNumber ? 0 : leftNumber > rightNumber ? 1 : -1;
|
|
465
|
-
return String(left).localeCompare(String(right));
|
|
466
|
-
}
|
|
467
|
-
function compareWithOperator(left, operator, right) {
|
|
468
|
-
const comparison = compareValues(left, right);
|
|
469
|
-
if ('=' === operator) return 0 === comparison;
|
|
470
|
-
if ('<>' === operator) return 0 !== comparison;
|
|
471
|
-
if ('>' === operator) return comparison > 0;
|
|
472
|
-
if ('<' === operator) return comparison < 0;
|
|
473
|
-
if ('>=' === operator) return comparison >= 0;
|
|
474
|
-
return '<=' === operator && comparison <= 0;
|
|
475
|
-
}
|
|
476
|
-
function decodeColumn(value) {
|
|
477
|
-
let result = 0;
|
|
478
|
-
for (const character of value.toUpperCase())result = 26 * result + character.charCodeAt(0) - 64;
|
|
479
|
-
return result - 1;
|
|
480
|
-
}
|
|
481
92
|
function drawSpreadsheetDiagonalDownBorder(context, bounds, line) {
|
|
482
93
|
drawSpreadsheetDiagonalBorder(context, bounds, line, 'down');
|
|
483
94
|
}
|
|
@@ -788,102 +399,6 @@ function restoreSpreadsheetNativeFillContext(context) {
|
|
|
788
399
|
else delete context.fillRect;
|
|
789
400
|
pendingContextRestores.delete(context);
|
|
790
401
|
}
|
|
791
|
-
function spreadsheet_cell_range_normalizeSpreadsheetCellRange(value) {
|
|
792
|
-
const row = normalizeSpreadsheetCellRangeAxis(value.row);
|
|
793
|
-
const column = normalizeSpreadsheetCellRangeAxis(value.column);
|
|
794
|
-
return row && column ? {
|
|
795
|
-
row,
|
|
796
|
-
column
|
|
797
|
-
} : null;
|
|
798
|
-
}
|
|
799
|
-
function parseSpreadsheetCellRange(value) {
|
|
800
|
-
if (!isRecord(value)) return null;
|
|
801
|
-
const row = normalizeSpreadsheetCellRangeAxis(value.row);
|
|
802
|
-
const column = normalizeSpreadsheetCellRangeAxis(value.column);
|
|
803
|
-
return row && column ? {
|
|
804
|
-
row,
|
|
805
|
-
column
|
|
806
|
-
} : null;
|
|
807
|
-
}
|
|
808
|
-
function subtractSpreadsheetCellRange(source, removed) {
|
|
809
|
-
const top = Math.max(source.row[0], removed.row[0]);
|
|
810
|
-
const bottom = Math.min(source.row[1], removed.row[1]);
|
|
811
|
-
const left = Math.max(source.column[0], removed.column[0]);
|
|
812
|
-
const right = Math.min(source.column[1], removed.column[1]);
|
|
813
|
-
if (top > bottom || left > right) return [
|
|
814
|
-
source
|
|
815
|
-
];
|
|
816
|
-
const remaining = [];
|
|
817
|
-
if (source.row[0] < top) remaining.push({
|
|
818
|
-
row: [
|
|
819
|
-
source.row[0],
|
|
820
|
-
top - 1
|
|
821
|
-
],
|
|
822
|
-
column: [
|
|
823
|
-
...source.column
|
|
824
|
-
]
|
|
825
|
-
});
|
|
826
|
-
if (bottom < source.row[1]) remaining.push({
|
|
827
|
-
row: [
|
|
828
|
-
bottom + 1,
|
|
829
|
-
source.row[1]
|
|
830
|
-
],
|
|
831
|
-
column: [
|
|
832
|
-
...source.column
|
|
833
|
-
]
|
|
834
|
-
});
|
|
835
|
-
if (source.column[0] < left) remaining.push({
|
|
836
|
-
row: [
|
|
837
|
-
top,
|
|
838
|
-
bottom
|
|
839
|
-
],
|
|
840
|
-
column: [
|
|
841
|
-
source.column[0],
|
|
842
|
-
left - 1
|
|
843
|
-
]
|
|
844
|
-
});
|
|
845
|
-
if (right < source.column[1]) remaining.push({
|
|
846
|
-
row: [
|
|
847
|
-
top,
|
|
848
|
-
bottom
|
|
849
|
-
],
|
|
850
|
-
column: [
|
|
851
|
-
right + 1,
|
|
852
|
-
source.column[1]
|
|
853
|
-
]
|
|
854
|
-
});
|
|
855
|
-
return remaining;
|
|
856
|
-
}
|
|
857
|
-
function spreadsheetCellRangesIntersect(left, right) {
|
|
858
|
-
return !(left.row[1] < right.row[0] || right.row[1] < left.row[0] || left.column[1] < right.column[0] || right.column[1] < left.column[0]);
|
|
859
|
-
}
|
|
860
|
-
function spreadsheetCellRangesEqual(left, right) {
|
|
861
|
-
const normalizedLeft = spreadsheet_cell_range_normalizeSpreadsheetCellRange(left);
|
|
862
|
-
const normalizedRight = spreadsheet_cell_range_normalizeSpreadsheetCellRange(right);
|
|
863
|
-
return Boolean(normalizedLeft && normalizedRight && normalizedLeft.row[0] === normalizedRight.row[0] && normalizedLeft.row[1] === normalizedRight.row[1] && normalizedLeft.column[0] === normalizedRight.column[0] && normalizedLeft.column[1] === normalizedRight.column[1]);
|
|
864
|
-
}
|
|
865
|
-
function spreadsheetCellRangeContains(range, row, column) {
|
|
866
|
-
return row >= range.row[0] && row <= range.row[1] && column >= range.column[0] && column <= range.column[1];
|
|
867
|
-
}
|
|
868
|
-
function spreadsheet_cell_range_spreadsheetCellRangeArea(range) {
|
|
869
|
-
return (range.row[1] - range.row[0] + 1) * (range.column[1] - range.column[0] + 1);
|
|
870
|
-
}
|
|
871
|
-
function normalizeSpreadsheetCellRangeAxis(value) {
|
|
872
|
-
if (!Array.isArray(value)) return null;
|
|
873
|
-
const first = finiteSpreadsheetCellIndex(value[0]);
|
|
874
|
-
const second = finiteSpreadsheetCellIndex(value[1]);
|
|
875
|
-
if (null === first || null === second) return null;
|
|
876
|
-
return [
|
|
877
|
-
Math.min(first, second),
|
|
878
|
-
Math.max(first, second)
|
|
879
|
-
];
|
|
880
|
-
}
|
|
881
|
-
function finiteSpreadsheetCellIndex(value) {
|
|
882
|
-
return 'number' == typeof value && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
883
|
-
}
|
|
884
|
-
function isRecord(value) {
|
|
885
|
-
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
886
|
-
}
|
|
887
402
|
const spreadsheetCellBorderTargets = [
|
|
888
403
|
'top',
|
|
889
404
|
'bottom',
|
|
@@ -981,9 +496,9 @@ function spreadsheetCellBordersAt(sheet, row, column) {
|
|
|
981
496
|
const borders = {};
|
|
982
497
|
const source = Array.isArray(sheet?.config?.borderInfo) ? sheet.config.borderInfo : [];
|
|
983
498
|
for (const candidate of source){
|
|
984
|
-
if (!
|
|
499
|
+
if (!isRecord(candidate)) continue;
|
|
985
500
|
if ('cell' === candidate.rangeType) {
|
|
986
|
-
if (!
|
|
501
|
+
if (!isRecord(candidate.value)) continue;
|
|
987
502
|
if (finiteSpreadsheetBorderIndex(candidate.value.row_index) !== row || finiteSpreadsheetBorderIndex(candidate.value.col_index) !== column) continue;
|
|
988
503
|
applyResolvedCellBorderLine(borders, 'left', candidate.value.l);
|
|
989
504
|
applyResolvedCellBorderLine(borders, 'right', candidate.value.r);
|
|
@@ -1005,9 +520,9 @@ function spreadsheetRenderableDiagonalBorders(sheet) {
|
|
|
1005
520
|
const borders = new Map();
|
|
1006
521
|
const source = Array.isArray(sheet.config?.borderInfo) ? sheet.config.borderInfo : [];
|
|
1007
522
|
for (const candidate of source){
|
|
1008
|
-
if (!
|
|
523
|
+
if (!isRecord(candidate)) continue;
|
|
1009
524
|
if ('cell' === candidate.rangeType) {
|
|
1010
|
-
if (!
|
|
525
|
+
if (!isRecord(candidate.value)) continue;
|
|
1011
526
|
const row = finiteSpreadsheetBorderIndex(candidate.value.row_index);
|
|
1012
527
|
const column = finiteSpreadsheetBorderIndex(candidate.value.col_index);
|
|
1013
528
|
if (null === row || null === column) continue;
|
|
@@ -1086,11 +601,11 @@ function spreadsheetBorderInfoIsWritable(sheet) {
|
|
|
1086
601
|
function compactSpreadsheetBorderInfo(formats, range, appliedType) {
|
|
1087
602
|
const supersedesAll = 'border-all' === appliedType || 'border-none' === appliedType;
|
|
1088
603
|
return formats.flatMap((format)=>{
|
|
1089
|
-
if (!
|
|
604
|
+
if (!isRecord(format)) return [
|
|
1090
605
|
format
|
|
1091
606
|
];
|
|
1092
607
|
if ('cell' === format.rangeType) {
|
|
1093
|
-
if (!
|
|
608
|
+
if (!isRecord(format.value)) return [
|
|
1094
609
|
format
|
|
1095
610
|
];
|
|
1096
611
|
const row = finiteSpreadsheetBorderIndex(format.value.row_index);
|
|
@@ -1177,9 +692,9 @@ function spreadsheetDiagonalBorderDirectionsInRange(sheet, selection) {
|
|
|
1177
692
|
const directions = new Map();
|
|
1178
693
|
const source = Array.isArray(sheet.config?.borderInfo) ? sheet.config.borderInfo : [];
|
|
1179
694
|
for (const candidate of source){
|
|
1180
|
-
if (!
|
|
695
|
+
if (!isRecord(candidate)) continue;
|
|
1181
696
|
if ('cell' === candidate.rangeType) {
|
|
1182
|
-
if (!
|
|
697
|
+
if (!isRecord(candidate.value)) continue;
|
|
1183
698
|
const row = finiteSpreadsheetBorderIndex(candidate.value.row_index);
|
|
1184
699
|
const column = finiteSpreadsheetBorderIndex(candidate.value.col_index);
|
|
1185
700
|
if (null === row || null === column || !spreadsheetCellRangeContains(selection, row, column)) continue;
|
|
@@ -1237,7 +752,7 @@ function contentWithSpreadsheetBorderInfo(content, sheetIndex, sheet, borderInfo
|
|
|
1237
752
|
}
|
|
1238
753
|
function applyResolvedCellBorderLine(borders, side, value) {
|
|
1239
754
|
if (null === value) return void delete borders[side];
|
|
1240
|
-
if (!
|
|
755
|
+
if (!isRecord(value)) return;
|
|
1241
756
|
const color = 'string' == typeof value.color ? normalizeSpreadsheetBorderColor(value.color) : null;
|
|
1242
757
|
const style = 'string' == typeof value.style ? value.style : null;
|
|
1243
758
|
if (color && style) borders[side] = {
|
|
@@ -1257,7 +772,7 @@ function applyResolvedCellDiagonalBorders(borders, value) {
|
|
|
1257
772
|
if (diagonal.up) borders.diagonalUp = line;
|
|
1258
773
|
}
|
|
1259
774
|
function resolvedCellBorderLine(value) {
|
|
1260
|
-
if (!
|
|
775
|
+
if (!isRecord(value)) return null;
|
|
1261
776
|
const color = 'string' == typeof value.color ? normalizeSpreadsheetBorderColor(value.color) : null;
|
|
1262
777
|
const style = 'string' == typeof value.style ? value.style : null;
|
|
1263
778
|
return color && style ? {
|
|
@@ -1346,7 +861,7 @@ function isSpreadsheetNativeBorderType(value) {
|
|
|
1346
861
|
function finiteSpreadsheetBorderIndex(value) {
|
|
1347
862
|
return 'number' == typeof value && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
1348
863
|
}
|
|
1349
|
-
function
|
|
864
|
+
function isRecord(value) {
|
|
1350
865
|
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
1351
866
|
}
|
|
1352
867
|
function useSpreadsheetCollaborationPresenceProjection({ content, workbook }) {
|
|
@@ -3058,2781 +2573,55 @@ function spreadsheetNativeMergeRange(value) {
|
|
|
3058
2573
|
if (null === row || null === column || null === rowSpan || null === columnSpan || !Number.isSafeInteger(row + rowSpan - 1) || !Number.isSafeInteger(column + columnSpan - 1)) return null;
|
|
3059
2574
|
return {
|
|
3060
2575
|
row: [
|
|
3061
|
-
row,
|
|
3062
|
-
row + rowSpan - 1
|
|
3063
|
-
],
|
|
3064
|
-
column: [
|
|
3065
|
-
column,
|
|
3066
|
-
column + columnSpan - 1
|
|
3067
|
-
]
|
|
3068
|
-
};
|
|
3069
|
-
}
|
|
3070
|
-
function spreadsheetCellIsLocked(sheet, cellAt, row, column) {
|
|
3071
|
-
const cell = cellAt(row, column);
|
|
3072
|
-
if (cell?.lo !== void 0 && null !== cell.lo) return Boolean(cell.lo);
|
|
3073
|
-
const authority = sheet.config?.authority;
|
|
3074
|
-
if (!spreadsheet_cell_mutation_guard_isRecord(authority)) return false;
|
|
3075
|
-
const ranges = authority.cellProtectionRanges;
|
|
3076
|
-
if (Array.isArray(ranges)) for(let index = ranges.length - 1; index >= 0; index -= 1){
|
|
3077
|
-
const candidate = ranges[index];
|
|
3078
|
-
if (!spreadsheet_cell_mutation_guard_isRecord(candidate)) continue;
|
|
3079
|
-
const protectedRange = parseSpreadsheetCellRange(candidate.range);
|
|
3080
|
-
if (protectedRange && row >= protectedRange.row[0] && row <= protectedRange.row[1] && column >= protectedRange.column[0] && column <= protectedRange.column[1]) return false !== candidate.locked;
|
|
3081
|
-
}
|
|
3082
|
-
return void 0 !== authority.sheet && 0 !== authority.sheet;
|
|
3083
|
-
}
|
|
3084
|
-
function createSpreadsheetMutationCellReader(sheet) {
|
|
3085
|
-
let sparseRows = null;
|
|
3086
|
-
const sparseCell = (row, column)=>{
|
|
3087
|
-
if (!sparseRows) {
|
|
3088
|
-
sparseRows = new Map();
|
|
3089
|
-
for (const entry of sheet.celldata ?? []){
|
|
3090
|
-
let sparseRow = sparseRows.get(entry.r);
|
|
3091
|
-
if (!sparseRow) {
|
|
3092
|
-
sparseRow = new Map();
|
|
3093
|
-
sparseRows.set(entry.r, sparseRow);
|
|
3094
|
-
}
|
|
3095
|
-
sparseRow.set(entry.c, entry.v);
|
|
3096
|
-
}
|
|
3097
|
-
}
|
|
3098
|
-
return sparseRows.get(row)?.get(column);
|
|
3099
|
-
};
|
|
3100
|
-
return (row, column)=>sheet.data?.[row]?.[column] ?? sparseCell(row, column);
|
|
3101
|
-
}
|
|
3102
|
-
function finiteIndex(value) {
|
|
3103
|
-
return Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
3104
|
-
}
|
|
3105
|
-
function positiveSpan(value) {
|
|
3106
|
-
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : null;
|
|
3107
|
-
}
|
|
3108
|
-
function spreadsheet_cell_mutation_guard_isRecord(value) {
|
|
3109
|
-
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
3110
|
-
}
|
|
3111
|
-
function reconcileSpreadsheetRichTextCellEdit(previous, current, formattedPaste) {
|
|
3112
|
-
if (current.f) return current;
|
|
3113
|
-
const source = formattedPaste ? normalizeXlsxRichTextEditSource(previous) : previous ? normalizeXlsxRichTextCell(previous) : null;
|
|
3114
|
-
if (!source) return current;
|
|
3115
|
-
const live = liveRichTextValue(current);
|
|
3116
|
-
if (!live || '' === live.text) return current;
|
|
3117
|
-
const replacement = richTextReplacement(source.text, live.text);
|
|
3118
|
-
const sourceRuns = indexRichTextRuns(source);
|
|
3119
|
-
const formattedPasteRuns = formattedPaste ? reconstructFormattedPasteRuns(source, sourceRuns, live.text, formattedPaste) : null;
|
|
3120
|
-
const runs = formattedPasteRuns ?? (live.rich && live.text === source.text ? restoreSemanticColorOrigins(live.rich.runs, sourceRuns, replacement) : reconstructPlainTextRuns(sourceRuns, replacement));
|
|
3121
|
-
const normalizedRuns = coalesceXlsxRichTextRuns(runs);
|
|
3122
|
-
if (!normalizedRuns.length || normalizedRuns.length > 512 || normalizedRuns.map((run)=>run.v).join('') !== live.text) return current;
|
|
3123
|
-
return spreadsheetRichTextCellWithRuns(previous, current, live.text, normalizedRuns);
|
|
3124
|
-
}
|
|
3125
|
-
function restoreSpreadsheetRichTextCellRuns(previous, current) {
|
|
3126
|
-
if (!previous || current.f) return current;
|
|
3127
|
-
const source = normalizeXlsxRichTextCell(previous);
|
|
3128
|
-
const live = liveRichTextValue(current);
|
|
3129
|
-
if (!source || !live || source.text !== live.text) return current;
|
|
3130
|
-
return spreadsheetRichTextCellWithRuns(previous, current, source.text, source.runs);
|
|
3131
|
-
}
|
|
3132
|
-
function spreadsheetRichTextCellWithRuns(previous, current, text, runs) {
|
|
3133
|
-
const next = {
|
|
3134
|
-
...current,
|
|
3135
|
-
ct: {
|
|
3136
|
-
...previous?.ct,
|
|
3137
|
-
...current.ct,
|
|
3138
|
-
s: runs.map((run)=>({
|
|
3139
|
-
...run
|
|
3140
|
-
})),
|
|
3141
|
-
t: 'inlineStr'
|
|
3142
|
-
},
|
|
3143
|
-
v: text
|
|
3144
|
-
};
|
|
3145
|
-
delete next.f;
|
|
3146
|
-
delete next.m;
|
|
3147
|
-
return next;
|
|
3148
|
-
}
|
|
3149
|
-
function sameSpreadsheetRichTextCellText(previous, current) {
|
|
3150
|
-
if (!previous || current.f) return false;
|
|
3151
|
-
const source = normalizeXlsxRichTextCell(previous);
|
|
3152
|
-
const live = liveRichTextValue(current);
|
|
3153
|
-
return Boolean(source && live && source.text === live.text);
|
|
3154
|
-
}
|
|
3155
|
-
function liveRichTextValue(cell) {
|
|
3156
|
-
const rich = normalizeXlsxRichTextCell(cell);
|
|
3157
|
-
if ('string' == typeof cell.v && cell.v !== rich?.text) return validBoundedText(cell.v) ? {
|
|
3158
|
-
rich: null,
|
|
3159
|
-
text: cell.v
|
|
3160
|
-
} : null;
|
|
3161
|
-
if (rich) return {
|
|
3162
|
-
rich,
|
|
3163
|
-
text: rich.text
|
|
3164
|
-
};
|
|
3165
|
-
return 'string' == typeof cell.v && validBoundedText(cell.v) ? {
|
|
3166
|
-
rich: null,
|
|
3167
|
-
text: cell.v
|
|
3168
|
-
} : null;
|
|
3169
|
-
}
|
|
3170
|
-
function validBoundedText(value) {
|
|
3171
|
-
return value.length <= 32767 && validXlsxRichText(value);
|
|
3172
|
-
}
|
|
3173
|
-
function richTextReplacement(previous, current) {
|
|
3174
|
-
let left = 0;
|
|
3175
|
-
let right = 0;
|
|
3176
|
-
while(left < previous.length && left < current.length && previous[left] === current[left])left += 1;
|
|
3177
|
-
if (left > 0 && isHighSurrogate(previous.charCodeAt(left - 1))) left -= 1;
|
|
3178
|
-
while(right + left < previous.length && right + left < current.length && previous[previous.length - right - 1] === current[current.length - right - 1])right += 1;
|
|
3179
|
-
if (right > 0 && isLowSurrogate(previous.charCodeAt(previous.length - right))) right -= 1;
|
|
3180
|
-
return {
|
|
3181
|
-
index: left,
|
|
3182
|
-
insert: current.slice(left, current.length - right),
|
|
3183
|
-
remove: previous.length - left - right
|
|
3184
|
-
};
|
|
3185
|
-
}
|
|
3186
|
-
function indexRichTextRuns(source) {
|
|
3187
|
-
let offset = 0;
|
|
3188
|
-
return source.runs.map((run)=>{
|
|
3189
|
-
const indexed = {
|
|
3190
|
-
end: offset + run.v.length,
|
|
3191
|
-
run,
|
|
3192
|
-
start: offset
|
|
3193
|
-
};
|
|
3194
|
-
offset = indexed.end;
|
|
3195
|
-
return indexed;
|
|
3196
|
-
});
|
|
3197
|
-
}
|
|
3198
|
-
function reconstructPlainTextRuns(source, replacement) {
|
|
3199
|
-
const runs = [];
|
|
3200
|
-
appendSourceRange(runs, source, 0, replacement.index);
|
|
3201
|
-
if (replacement.insert) {
|
|
3202
|
-
const inherited = insertionSourceRun(source, replacement);
|
|
3203
|
-
if (inherited) runs.push({
|
|
3204
|
-
...inherited.run,
|
|
3205
|
-
v: replacement.insert
|
|
3206
|
-
});
|
|
3207
|
-
}
|
|
3208
|
-
const sourceLength = source.at(-1)?.end ?? 0;
|
|
3209
|
-
appendSourceRange(runs, source, replacement.index + replacement.remove, sourceLength);
|
|
3210
|
-
return runs;
|
|
3211
|
-
}
|
|
3212
|
-
function reconstructFormattedPasteRuns(source, sourceRuns, currentText, intent) {
|
|
3213
|
-
if (!validRichTextPasteSelection(source.text, intent.start, intent.end) || 'string' != typeof intent.text || !intent.text || intent.text.length > 32767 || !validXlsxRichText(intent.text) || !Array.isArray(intent.runs) || !intent.runs.length || intent.runs.length > 512) return null;
|
|
3214
|
-
const pastedRuns = [];
|
|
3215
|
-
for (const candidate of intent.runs){
|
|
3216
|
-
const run = normalizeXlsxRichTextRun(candidate);
|
|
3217
|
-
if (!run) return null;
|
|
3218
|
-
if (run.v) pastedRuns.push(run);
|
|
3219
|
-
}
|
|
3220
|
-
if (!pastedRuns.length || pastedRuns.map((run)=>run.v).join('') !== intent.text) return null;
|
|
3221
|
-
const expectedText = source.text.slice(0, intent.start) + intent.text + source.text.slice(intent.end);
|
|
3222
|
-
if (expectedText !== currentText || expectedText.length > 32767 || !validXlsxRichText(expectedText)) return null;
|
|
3223
|
-
const runs = [];
|
|
3224
|
-
appendSourceRange(runs, sourceRuns, 0, intent.start);
|
|
3225
|
-
runs.push(...pastedRuns);
|
|
3226
|
-
appendSourceRange(runs, sourceRuns, intent.end, source.text.length);
|
|
3227
|
-
const normalized = coalesceXlsxRichTextRuns(runs);
|
|
3228
|
-
return normalized.length > 0 && normalized.length <= 512 ? normalized : null;
|
|
3229
|
-
}
|
|
3230
|
-
function validRichTextPasteSelection(text, start, end) {
|
|
3231
|
-
return Boolean(Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end && end <= text.length && !splitsSurrogatePair(text, start) && !splitsSurrogatePair(text, end));
|
|
3232
|
-
}
|
|
3233
|
-
function splitsSurrogatePair(text, offset) {
|
|
3234
|
-
if (offset <= 0 || offset >= text.length) return false;
|
|
3235
|
-
return isHighSurrogate(text.charCodeAt(offset - 1)) && isLowSurrogate(text.charCodeAt(offset));
|
|
3236
|
-
}
|
|
3237
|
-
function appendSourceRange(target, source, start, end) {
|
|
3238
|
-
if (start >= end) return;
|
|
3239
|
-
for (const indexed of source){
|
|
3240
|
-
const overlapStart = Math.max(start, indexed.start);
|
|
3241
|
-
const overlapEnd = Math.min(end, indexed.end);
|
|
3242
|
-
if (!(overlapStart >= overlapEnd)) target.push({
|
|
3243
|
-
...indexed.run,
|
|
3244
|
-
v: indexed.run.v.slice(overlapStart - indexed.start, overlapEnd - indexed.start)
|
|
3245
|
-
});
|
|
3246
|
-
}
|
|
3247
|
-
}
|
|
3248
|
-
function restoreSemanticColorOrigins(current, source, replacement) {
|
|
3249
|
-
const runs = [];
|
|
3250
|
-
let absoluteOffset = 0;
|
|
3251
|
-
for (const currentRun of current){
|
|
3252
|
-
let localOffset = 0;
|
|
3253
|
-
while(localOffset < currentRun.v.length){
|
|
3254
|
-
const width = codePointWidth(currentRun.v, localOffset);
|
|
3255
|
-
const origin = semanticOriginAt(source, replacement, absoluteOffset + localOffset);
|
|
3256
|
-
const next = {
|
|
3257
|
-
...currentRun,
|
|
3258
|
-
v: currentRun.v.slice(localOffset, localOffset + width)
|
|
3259
|
-
};
|
|
3260
|
-
delete next.a3sXlsxColorOrigin;
|
|
3261
|
-
if (origin && currentRun.fc && xlsxSemanticColorMatchesValue(origin, currentRun.fc)) next.a3sXlsxColorOrigin = origin;
|
|
3262
|
-
runs.push(next);
|
|
3263
|
-
localOffset += width;
|
|
3264
|
-
}
|
|
3265
|
-
absoluteOffset += currentRun.v.length;
|
|
3266
|
-
}
|
|
3267
|
-
return runs;
|
|
3268
|
-
}
|
|
3269
|
-
function semanticOriginAt(source, replacement, currentOffset) {
|
|
3270
|
-
let indexed;
|
|
3271
|
-
indexed = currentOffset < replacement.index ? sourceRunAt(source, currentOffset) : currentOffset >= replacement.index + replacement.insert.length ? sourceRunAt(source, currentOffset - replacement.insert.length + replacement.remove) : insertionSourceRun(source, replacement);
|
|
3272
|
-
return normalizeXlsxSemanticColorOrigin(indexed?.run.a3sXlsxColorOrigin);
|
|
3273
|
-
}
|
|
3274
|
-
function insertionSourceRun(source, replacement) {
|
|
3275
|
-
const sourceLength = source.at(-1)?.end ?? 0;
|
|
3276
|
-
if (!sourceLength) return;
|
|
3277
|
-
if (replacement.remove > 0 && replacement.index < sourceLength) return sourceRunAt(source, replacement.index);
|
|
3278
|
-
if (replacement.index > 0) return sourceRunAt(source, replacement.index - 1);
|
|
3279
|
-
return sourceRunAt(source, 0);
|
|
3280
|
-
}
|
|
3281
|
-
function sourceRunAt(source, offset) {
|
|
3282
|
-
let left = 0;
|
|
3283
|
-
let right = source.length - 1;
|
|
3284
|
-
while(left <= right){
|
|
3285
|
-
const middle = left + Math.floor((right - left) / 2);
|
|
3286
|
-
const indexed = source[middle];
|
|
3287
|
-
if (!indexed) break;
|
|
3288
|
-
if (offset < indexed.start) right = middle - 1;
|
|
3289
|
-
else {
|
|
3290
|
-
if (!(offset >= indexed.end)) return indexed;
|
|
3291
|
-
left = middle + 1;
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
function codePointWidth(value, offset) {
|
|
3296
|
-
return isHighSurrogate(value.charCodeAt(offset)) && isLowSurrogate(value.charCodeAt(offset + 1)) ? 2 : 1;
|
|
3297
|
-
}
|
|
3298
|
-
const spreadsheetFontSizes = [
|
|
3299
|
-
9,
|
|
3300
|
-
10,
|
|
3301
|
-
11,
|
|
3302
|
-
12,
|
|
3303
|
-
14,
|
|
3304
|
-
16,
|
|
3305
|
-
18,
|
|
3306
|
-
20,
|
|
3307
|
-
22,
|
|
3308
|
-
24,
|
|
3309
|
-
28,
|
|
3310
|
-
36,
|
|
3311
|
-
48,
|
|
3312
|
-
72
|
|
3313
|
-
];
|
|
3314
|
-
function nextSpreadsheetFontSize(current, direction) {
|
|
3315
|
-
if (!Number.isFinite(current) || current < 1 || current > 409) return null;
|
|
3316
|
-
return 'grow' === direction ? spreadsheetFontSizes.find((size)=>size > current) ?? null : [
|
|
3317
|
-
...spreadsheetFontSizes
|
|
3318
|
-
].reverse().find((size)=>size < current) ?? null;
|
|
3319
|
-
}
|
|
3320
|
-
function captureSpreadsheetRichTextDomSelection({ expandedOnly = false, root, target }) {
|
|
3321
|
-
const selection = window.getSelection();
|
|
3322
|
-
if (!root || !selection || 1 !== selection.rangeCount) return null;
|
|
3323
|
-
const range = selection.getRangeAt(0);
|
|
3324
|
-
if (expandedOnly && range.collapsed) return null;
|
|
3325
|
-
const targetNode = target instanceof Node ? target : null;
|
|
3326
|
-
const editor = spreadsheetRichTextEditor(targetNode ?? range.commonAncestorContainer);
|
|
3327
|
-
if (!editor || !root.contains(editor) || !editorContainsRange(editor, range)) return null;
|
|
3328
|
-
const start = spreadsheetTextOffsetAtPoint(editor, range.startContainer, range.startOffset);
|
|
3329
|
-
const end = spreadsheetTextOffsetAtPoint(editor, range.endContainer, range.endOffset);
|
|
3330
|
-
return null === start || null === end || start > end ? null : {
|
|
3331
|
-
editor,
|
|
3332
|
-
end,
|
|
3333
|
-
start
|
|
3334
|
-
};
|
|
3335
|
-
}
|
|
3336
|
-
function spreadsheetRichTextEditor(node) {
|
|
3337
|
-
const element = node instanceof Element ? node : node.parentElement;
|
|
3338
|
-
const editor = element?.closest('.luckysheet-cell-input, .fortune-fx-input');
|
|
3339
|
-
return editor?.isContentEditable || editor?.getAttribute('contenteditable') === 'true' ? editor : null;
|
|
3340
|
-
}
|
|
3341
|
-
function editorContainsRange(editor, range) {
|
|
3342
|
-
return (range.startContainer === editor || editor.contains(range.startContainer)) && (range.endContainer === editor || editor.contains(range.endContainer));
|
|
3343
|
-
}
|
|
3344
|
-
function spreadsheetTextOffsetAtPoint(editor, node, offset) {
|
|
3345
|
-
try {
|
|
3346
|
-
const prefix = document.createRange();
|
|
3347
|
-
prefix.selectNodeContents(editor);
|
|
3348
|
-
prefix.setEnd(node, offset);
|
|
3349
|
-
return prefix.toString().length;
|
|
3350
|
-
} catch {
|
|
3351
|
-
return null;
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
const MAX_SPREADSHEET_RICH_TEXT_CLIPBOARD_HTML_CHARACTERS = 256000;
|
|
3355
|
-
const pendingPastes = new WeakMap();
|
|
3356
|
-
const ignoredClipboardElements = new Set([
|
|
3357
|
-
'HEAD',
|
|
3358
|
-
'META',
|
|
3359
|
-
'NOSCRIPT',
|
|
3360
|
-
'SCRIPT',
|
|
3361
|
-
'STYLE',
|
|
3362
|
-
'TITLE'
|
|
3363
|
-
]);
|
|
3364
|
-
const blockClipboardElements = new Set([
|
|
3365
|
-
'ADDRESS',
|
|
3366
|
-
'BLOCKQUOTE',
|
|
3367
|
-
'DIV',
|
|
3368
|
-
'H1',
|
|
3369
|
-
'H2',
|
|
3370
|
-
'H3',
|
|
3371
|
-
'H4',
|
|
3372
|
-
'H5',
|
|
3373
|
-
'H6',
|
|
3374
|
-
'LI',
|
|
3375
|
-
'P',
|
|
3376
|
-
'PRE'
|
|
3377
|
-
]);
|
|
3378
|
-
function parseSpreadsheetRichTextClipboard(html, plainText) {
|
|
3379
|
-
const text = normalizeClipboardText(plainText);
|
|
3380
|
-
if (!html || html.length > MAX_SPREADSHEET_RICH_TEXT_CLIPBOARD_HTML_CHARACTERS || !text || text.length > 32767 || !validXlsxRichText(text)) return null;
|
|
3381
|
-
const template = document.createElement('template');
|
|
3382
|
-
template.innerHTML = html;
|
|
3383
|
-
const runs = [];
|
|
3384
|
-
const state = {
|
|
3385
|
-
characterCount: 0,
|
|
3386
|
-
invalid: false
|
|
3387
|
-
};
|
|
3388
|
-
for (const child of template.content.childNodes){
|
|
3389
|
-
collectClipboardRuns(child, {}, runs, state);
|
|
3390
|
-
if (state.invalid) return null;
|
|
3391
|
-
}
|
|
3392
|
-
trimTrailingClipboardNewline(runs, text);
|
|
3393
|
-
const normalized = coalesceXlsxRichTextRuns(runs);
|
|
3394
|
-
if (!normalized.length || normalized.length > 512 || normalized.map((run)=>run.v).join('') !== text || !normalized.some(spreadsheetRichTextRunHasFormatting)) return null;
|
|
3395
|
-
return {
|
|
3396
|
-
runs: normalized,
|
|
3397
|
-
text
|
|
3398
|
-
};
|
|
3399
|
-
}
|
|
3400
|
-
function captureSpreadsheetRichTextPaste({ clipboardData, content, root, selection, sheetId, target }) {
|
|
3401
|
-
const domSelection = captureSpreadsheetRichTextDomSelection({
|
|
3402
|
-
root,
|
|
3403
|
-
target
|
|
3404
|
-
});
|
|
3405
|
-
const sheet = content.sheets.find((candidate)=>candidate.id === sheetId);
|
|
3406
|
-
if (!domSelection || !sheet || !selection) return false;
|
|
3407
|
-
const row = focusedAxisIndex(selection.row_focus, selection.row);
|
|
3408
|
-
const column = focusedAxisIndex(selection.column_focus, selection.column);
|
|
3409
|
-
const cell = spreadsheetCellAt(sheet, row, column);
|
|
3410
|
-
const source = normalizeXlsxRichTextEditSource(cell);
|
|
3411
|
-
const key = spreadsheetRichTextPasteKey(row, column);
|
|
3412
|
-
if (!source || domSelection.editor.textContent !== source.text || domSelection.end > source.text.length) {
|
|
3413
|
-
clearPendingPaste(sheet, key);
|
|
3414
|
-
return false;
|
|
3415
|
-
}
|
|
3416
|
-
const parsed = parseSpreadsheetRichTextClipboard(clipboardData.getData('text/html'), clipboardData.getData('text/plain'));
|
|
3417
|
-
if (!parsed) {
|
|
3418
|
-
clearPendingPaste(sheet, key);
|
|
3419
|
-
return false;
|
|
3420
|
-
}
|
|
3421
|
-
stageSpreadsheetRichTextPaste(sheet, row, column, source.text, {
|
|
3422
|
-
end: domSelection.end,
|
|
3423
|
-
runs: parsed.runs,
|
|
3424
|
-
start: domSelection.start,
|
|
3425
|
-
text: parsed.text
|
|
3426
|
-
});
|
|
3427
|
-
return true;
|
|
3428
|
-
}
|
|
3429
|
-
function stageSpreadsheetRichTextPaste(sheet, row, column, sourceText, intent) {
|
|
3430
|
-
if (!validCoordinate(row) || !validCoordinate(column)) return;
|
|
3431
|
-
const key = spreadsheetRichTextPasteKey(row, column);
|
|
3432
|
-
const entries = pendingPastes.get(sheet) ?? new Map();
|
|
3433
|
-
pendingPastes.set(sheet, entries);
|
|
3434
|
-
entries.set(key, {
|
|
3435
|
-
intent: {
|
|
3436
|
-
...intent,
|
|
3437
|
-
runs: intent.runs.map((run)=>({
|
|
3438
|
-
...run
|
|
3439
|
-
}))
|
|
3440
|
-
},
|
|
3441
|
-
sourceText
|
|
3442
|
-
});
|
|
3443
|
-
}
|
|
3444
|
-
function takeSpreadsheetRichTextPaste(sheet, row, column, current) {
|
|
3445
|
-
const intent = peekSpreadsheetRichTextPaste(sheet, row, column, current);
|
|
3446
|
-
if (intent) consumeSpreadsheetRichTextPaste(sheet, row, column);
|
|
3447
|
-
return intent;
|
|
3448
|
-
}
|
|
3449
|
-
function peekSpreadsheetRichTextPaste(sheet, row, column, current) {
|
|
3450
|
-
const entries = pendingPastes.get(sheet);
|
|
3451
|
-
const key = spreadsheetRichTextPasteKey(row, column);
|
|
3452
|
-
const pending = entries?.get(key);
|
|
3453
|
-
if (!entries || !pending) return;
|
|
3454
|
-
const currentText = spreadsheetRichTextCellText(current);
|
|
3455
|
-
if (currentText === pending.sourceText) return;
|
|
3456
|
-
return null === currentText ? void 0 : pending.intent;
|
|
3457
|
-
}
|
|
3458
|
-
function consumeSpreadsheetRichTextPaste(sheet, row, column) {
|
|
3459
|
-
clearPendingPaste(sheet, spreadsheetRichTextPasteKey(row, column));
|
|
3460
|
-
}
|
|
3461
|
-
function collectClipboardRuns(node, inherited, runs, state) {
|
|
3462
|
-
if (state.invalid) return;
|
|
3463
|
-
if (node.nodeType === Node.TEXT_NODE) return void appendClipboardText(runs, inherited, node.textContent ?? '', state);
|
|
3464
|
-
if (!(node instanceof HTMLElement) || ignoredClipboardElements.has(node.tagName)) return;
|
|
3465
|
-
const style = spreadsheetClipboardElementStyle(node, inherited);
|
|
3466
|
-
if ('BR' === node.tagName) return void appendClipboardText(runs, style, '\n', state);
|
|
3467
|
-
for (const child of node.childNodes){
|
|
3468
|
-
collectClipboardRuns(child, style, runs, state);
|
|
3469
|
-
if (state.invalid) return;
|
|
3470
|
-
}
|
|
3471
|
-
if (blockClipboardElements.has(node.tagName) && hasFollowingClipboardContent(node)) appendClipboardText(runs, style, '\n', state);
|
|
3472
|
-
}
|
|
3473
|
-
function appendClipboardText(runs, style, source, state) {
|
|
3474
|
-
const value = normalizeClipboardText(source);
|
|
3475
|
-
if (!value) return;
|
|
3476
|
-
state.characterCount += value.length;
|
|
3477
|
-
if (state.characterCount > 32767 || !validXlsxRichText(value)) {
|
|
3478
|
-
state.invalid = true;
|
|
3479
|
-
return;
|
|
3480
|
-
}
|
|
3481
|
-
const run = {
|
|
3482
|
-
...style,
|
|
3483
|
-
v: value
|
|
3484
|
-
};
|
|
3485
|
-
const previous = runs.at(-1);
|
|
3486
|
-
if (previous && sameXlsxRichTextRunStyle(previous, run)) previous.v += value;
|
|
3487
|
-
else runs.push(run);
|
|
3488
|
-
if (runs.length > 512) state.invalid = true;
|
|
3489
|
-
}
|
|
3490
|
-
function spreadsheetClipboardElementStyle(element, inherited) {
|
|
3491
|
-
const style = {
|
|
3492
|
-
...inherited
|
|
3493
|
-
};
|
|
3494
|
-
if (element.matches('b, strong')) style.bl = 1;
|
|
3495
|
-
if (element.matches('i, em')) style.it = 1;
|
|
3496
|
-
if (element.matches('s, strike, del')) style.cl = 1;
|
|
3497
|
-
if (element.matches('u')) style.un = 1;
|
|
3498
|
-
const declarations = cssDeclarations(element.getAttribute('style'));
|
|
3499
|
-
applyFontWeight(style, declarations.get('font-weight'));
|
|
3500
|
-
applyToggle(style, 'it', declarations.get('font-style'), 'italic');
|
|
3501
|
-
applyTextDecoration(style, declarations);
|
|
3502
|
-
applyFontFamily(style, declarations.get('font-family'));
|
|
3503
|
-
applyFontSize(style, declarations.get('font-size'));
|
|
3504
|
-
applyFontColor(style, declarations.get('color'));
|
|
3505
|
-
applyFortuneToggle(style, 'cl', declarations.get('lucky-strike'));
|
|
3506
|
-
applyFortuneUnderline(style, declarations.get('lucky-underline'));
|
|
3507
|
-
if (declarations.get('border-bottom')?.trim().toLowerCase() !== 'none') {
|
|
3508
|
-
if (declarations.has('border-bottom')) style.un = 1;
|
|
3509
|
-
}
|
|
3510
|
-
if ('FONT' === element.tagName) {
|
|
3511
|
-
applyFontFamily(style, element.getAttribute('face') ?? void 0);
|
|
3512
|
-
applyFontColor(style, element.getAttribute('color') ?? void 0);
|
|
3513
|
-
applyHtmlFontSize(style, element.getAttribute('size'));
|
|
3514
|
-
}
|
|
3515
|
-
return style;
|
|
3516
|
-
}
|
|
3517
|
-
function cssDeclarations(value) {
|
|
3518
|
-
const declarations = new Map();
|
|
3519
|
-
for (const candidate of value?.split(';') ?? []){
|
|
3520
|
-
const separator = candidate.indexOf(':');
|
|
3521
|
-
if (separator <= 0) continue;
|
|
3522
|
-
const key = candidate.slice(0, separator).trim().toLowerCase();
|
|
3523
|
-
const declaration = candidate.slice(separator + 1).trim();
|
|
3524
|
-
if (key && declaration) declarations.set(key, declaration);
|
|
3525
|
-
}
|
|
3526
|
-
return declarations;
|
|
3527
|
-
}
|
|
3528
|
-
function applyFontWeight(style, value) {
|
|
3529
|
-
const normalized = value?.trim().toLowerCase();
|
|
3530
|
-
if (!normalized) return;
|
|
3531
|
-
if ('bold' === normalized || 'bolder' === normalized) style.bl = 1;
|
|
3532
|
-
else if ('normal' === normalized || 'lighter' === normalized) style.bl = 0;
|
|
3533
|
-
else if (/^\d+$/.test(normalized)) style.bl = Number(normalized) >= 600 ? 1 : 0;
|
|
3534
|
-
}
|
|
3535
|
-
function applyToggle(style, key, value, enabled) {
|
|
3536
|
-
const normalized = value?.trim().toLowerCase();
|
|
3537
|
-
if (!normalized) return;
|
|
3538
|
-
style[key] = normalized === enabled || 'oblique' === normalized ? 1 : 0;
|
|
3539
|
-
}
|
|
3540
|
-
function applyTextDecoration(style, declarations) {
|
|
3541
|
-
const value = declarations.get('text-decoration-line') ?? declarations.get('text-decoration');
|
|
3542
|
-
const normalized = value?.toLowerCase();
|
|
3543
|
-
if (!normalized) return;
|
|
3544
|
-
if (normalized.includes('none')) {
|
|
3545
|
-
style.cl = 0;
|
|
3546
|
-
style.un = 0;
|
|
3547
|
-
return;
|
|
3548
|
-
}
|
|
3549
|
-
if (normalized.includes('line-through')) style.cl = 1;
|
|
3550
|
-
if (normalized.includes('underline')) style.un = 'double' === declarations.get('text-decoration-style') ? 2 : 1;
|
|
3551
|
-
}
|
|
3552
|
-
function applyFontFamily(style, value) {
|
|
3553
|
-
const family = value?.split(',')[0]?.trim().replace(/^(['"])(.*)\1$/, '$2');
|
|
3554
|
-
if (family && family.length <= 128) style.ff = family;
|
|
3555
|
-
}
|
|
3556
|
-
function applyFontSize(style, value) {
|
|
3557
|
-
const match = /^([\d.]+)\s*(pt|px)$/i.exec(value?.trim() ?? '');
|
|
3558
|
-
if (!match) return;
|
|
3559
|
-
const number = Number(match[1]);
|
|
3560
|
-
const size = match[2]?.toLowerCase() === 'px' ? 0.75 * number : number;
|
|
3561
|
-
if (Number.isFinite(size) && size >= 1 && size <= 409) style.fs = Math.round(100 * size) / 100;
|
|
3562
|
-
}
|
|
3563
|
-
function applyFontColor(style, value) {
|
|
3564
|
-
const color = normalizeCssColor(value);
|
|
3565
|
-
if (color && 'transparent' !== color) style.fc = color;
|
|
3566
|
-
}
|
|
3567
|
-
function applyFortuneToggle(style, key, value) {
|
|
3568
|
-
const toggle = Number(value);
|
|
3569
|
-
if (Number.isSafeInteger(toggle) && (0 === toggle || 1 === toggle)) style[key] = toggle;
|
|
3570
|
-
}
|
|
3571
|
-
function applyFortuneUnderline(style, value) {
|
|
3572
|
-
const underline = Number(value);
|
|
3573
|
-
if (Number.isSafeInteger(underline) && underline >= 0 && underline <= 4) style.un = underline;
|
|
3574
|
-
}
|
|
3575
|
-
function applyHtmlFontSize(style, value) {
|
|
3576
|
-
const index = Number(value);
|
|
3577
|
-
const size = [
|
|
3578
|
-
0,
|
|
3579
|
-
8,
|
|
3580
|
-
10,
|
|
3581
|
-
12,
|
|
3582
|
-
14,
|
|
3583
|
-
18,
|
|
3584
|
-
24,
|
|
3585
|
-
36
|
|
3586
|
-
][index];
|
|
3587
|
-
if (size) style.fs = size;
|
|
3588
|
-
}
|
|
3589
|
-
function spreadsheetRichTextRunHasFormatting(run) {
|
|
3590
|
-
return [
|
|
3591
|
-
'bl',
|
|
3592
|
-
'cl',
|
|
3593
|
-
'fc',
|
|
3594
|
-
'ff',
|
|
3595
|
-
'fs',
|
|
3596
|
-
'it',
|
|
3597
|
-
'un'
|
|
3598
|
-
].some((key)=>void 0 !== run[key]);
|
|
3599
|
-
}
|
|
3600
|
-
function trimTrailingClipboardNewline(runs, expectedText) {
|
|
3601
|
-
const actual = runs.map((run)=>run.v).join('');
|
|
3602
|
-
if (!actual.endsWith('\n') || expectedText.endsWith('\n')) return;
|
|
3603
|
-
const last = runs.at(-1);
|
|
3604
|
-
if (!last) return;
|
|
3605
|
-
last.v = last.v.slice(0, -1);
|
|
3606
|
-
if (!last.v) runs.pop();
|
|
3607
|
-
}
|
|
3608
|
-
function hasFollowingClipboardContent(element) {
|
|
3609
|
-
let sibling = element.nextSibling;
|
|
3610
|
-
while(sibling){
|
|
3611
|
-
if (sibling.nodeType === Node.TEXT_NODE) {
|
|
3612
|
-
if (sibling.textContent) return true;
|
|
3613
|
-
} else if (sibling instanceof HTMLElement && !ignoredClipboardElements.has(sibling.tagName)) return true;
|
|
3614
|
-
sibling = sibling.nextSibling;
|
|
3615
|
-
}
|
|
3616
|
-
return false;
|
|
3617
|
-
}
|
|
3618
|
-
function spreadsheetRichTextCellText(cell) {
|
|
3619
|
-
const rich = normalizeXlsxRichTextCell(cell);
|
|
3620
|
-
if ('string' == typeof cell.v && cell.v !== rich?.text) return cell.v;
|
|
3621
|
-
return rich?.text ?? ('string' == typeof cell.v ? cell.v : null);
|
|
3622
|
-
}
|
|
3623
|
-
function spreadsheetCellAt(sheet, row, column) {
|
|
3624
|
-
return sheet.data?.[row]?.[column] ?? sheet.celldata?.find((candidate)=>candidate.r === row && candidate.c === column)?.v ?? null;
|
|
3625
|
-
}
|
|
3626
|
-
function focusedAxisIndex(value, axis) {
|
|
3627
|
-
const minimum = spreadsheet_rich_text_paste_finiteIndex(axis[0], 0);
|
|
3628
|
-
const maximum = spreadsheet_rich_text_paste_finiteIndex(axis[1], minimum);
|
|
3629
|
-
const focus = spreadsheet_rich_text_paste_finiteIndex(value, minimum);
|
|
3630
|
-
return Math.min(Math.max(minimum, maximum), Math.max(Math.min(minimum, maximum), focus));
|
|
3631
|
-
}
|
|
3632
|
-
function spreadsheet_rich_text_paste_finiteIndex(value, fallback) {
|
|
3633
|
-
return 'number' == typeof value && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
|
|
3634
|
-
}
|
|
3635
|
-
function normalizeClipboardText(value) {
|
|
3636
|
-
return value.replace(/\r\n?/g, '\n').replace(/\u00a0/g, ' ');
|
|
3637
|
-
}
|
|
3638
|
-
function clearPendingPaste(sheet, key) {
|
|
3639
|
-
const entries = pendingPastes.get(sheet);
|
|
3640
|
-
entries?.delete(key);
|
|
3641
|
-
if (entries && !entries.size) pendingPastes.delete(sheet);
|
|
3642
|
-
}
|
|
3643
|
-
function spreadsheetRichTextPasteKey(row, column) {
|
|
3644
|
-
return `${row}:${column}`;
|
|
3645
|
-
}
|
|
3646
|
-
function validCoordinate(value) {
|
|
3647
|
-
return Number.isSafeInteger(value) && value >= 0;
|
|
3648
|
-
}
|
|
3649
|
-
const MAXIMUM_INCREMENTAL_SPREADSHEET_OPERATIONS = 10000;
|
|
3650
|
-
function spreadsheetCellOperationKey(row, column) {
|
|
3651
|
-
return `${row}:${column}`;
|
|
3652
|
-
}
|
|
3653
|
-
function spreadsheetCellOperationCoordinates(operations) {
|
|
3654
|
-
if (operations.length > MAXIMUM_INCREMENTAL_SPREADSHEET_OPERATIONS) return null;
|
|
3655
|
-
const coordinatesBySheet = new Map();
|
|
3656
|
-
for (const operation of operations){
|
|
3657
|
-
if ('insertRowCol' === operation.op || 'deleteRowCol' === operation.op || 'addSheet' === operation.op || 'deleteSheet' === operation.op || 0 === operation.path.length || !operation.id) return null;
|
|
3658
|
-
const root = operation.path[0];
|
|
3659
|
-
if ('id' === root || 'celldata' === root) return null;
|
|
3660
|
-
if ('data' !== root) continue;
|
|
3661
|
-
const row = operation.path[1];
|
|
3662
|
-
const column = operation.path[2];
|
|
3663
|
-
if (operation.path.length < 3 || !isSpreadsheetCoordinate(row) || !isSpreadsheetCoordinate(column)) return null;
|
|
3664
|
-
const coordinates = coordinatesBySheet.get(operation.id) ?? new Map();
|
|
3665
|
-
coordinatesBySheet.set(operation.id, coordinates);
|
|
3666
|
-
coordinates.set(spreadsheetCellOperationKey(row, column), {
|
|
3667
|
-
column,
|
|
3668
|
-
row
|
|
3669
|
-
});
|
|
3670
|
-
}
|
|
3671
|
-
return coordinatesBySheet;
|
|
3672
|
-
}
|
|
3673
|
-
function projectSpreadsheetSheetsFromFortuneOperations(sheets, sourceSheets, operations) {
|
|
3674
|
-
if (0 === operations.length || sheets.length !== sourceSheets.length) return null;
|
|
3675
|
-
const changedSheets = spreadsheetSheetsById(sheets);
|
|
3676
|
-
const controlledSheets = spreadsheetSheetsById(sourceSheets);
|
|
3677
|
-
if (!changedSheets || !controlledSheets) return null;
|
|
3678
|
-
if (changedSheets.size !== sheets.length || controlledSheets.size !== sourceSheets.length) return null;
|
|
3679
|
-
const coordinatesBySheet = spreadsheetCellOperationCoordinates(operations);
|
|
3680
|
-
if (!coordinatesBySheet) return null;
|
|
3681
|
-
for (const operation of operations)if (!operation.id || !changedSheets.has(operation.id) || !controlledSheets.has(operation.id)) return null;
|
|
3682
|
-
let affectedCellCount = 0;
|
|
3683
|
-
const projectedSheets = [];
|
|
3684
|
-
const pendingRichTextPastes = [];
|
|
3685
|
-
for (const sheet of sheets){
|
|
3686
|
-
const id = sheet.id;
|
|
3687
|
-
if (!id) return null;
|
|
3688
|
-
const source = controlledSheets.get(id);
|
|
3689
|
-
if (!source) return null;
|
|
3690
|
-
const coordinates = coordinatesBySheet.get(id);
|
|
3691
|
-
const { celldata: _cellData, data: _data, ...metadata } = sheet;
|
|
3692
|
-
if (!coordinates?.size) {
|
|
3693
|
-
projectedSheets.push(void 0 !== source.data ? {
|
|
3694
|
-
...metadata,
|
|
3695
|
-
data: source.data
|
|
3696
|
-
} : {
|
|
3697
|
-
...metadata,
|
|
3698
|
-
celldata: source.celldata ?? []
|
|
3699
|
-
});
|
|
3700
|
-
continue;
|
|
3701
|
-
}
|
|
3702
|
-
if (void 0 === source.data || void 0 === sheet.data || !spreadsheetMatrixProfile(source.data)?.fortuneReady) return null;
|
|
3703
|
-
const projection = projectSpreadsheetMatrixCells(sheet, source, coordinates.values(), pendingRichTextPastes);
|
|
3704
|
-
if (!projection) return null;
|
|
3705
|
-
affectedCellCount += projection.affectedCellCount;
|
|
3706
|
-
projectedSheets.push({
|
|
3707
|
-
...metadata,
|
|
3708
|
-
data: projection.data
|
|
3709
|
-
});
|
|
3710
|
-
}
|
|
3711
|
-
for (const pending of pendingRichTextPastes)consumeSpreadsheetRichTextPaste(pending.sheet, pending.row, pending.column);
|
|
3712
|
-
return {
|
|
3713
|
-
affectedCellCount,
|
|
3714
|
-
sheets: projectedSheets
|
|
3715
|
-
};
|
|
3716
|
-
}
|
|
3717
|
-
function projectSpreadsheetMatrixCells(changed, source, coordinates, pendingRichTextPastes) {
|
|
3718
|
-
if (!changed.data || !source.data) return null;
|
|
3719
|
-
const data = source.data.slice();
|
|
3720
|
-
data.length = Math.max(data.length, changed.data.length, positiveSpreadsheetDimension(changed.row));
|
|
3721
|
-
const mutableRows = new Map();
|
|
3722
|
-
const profileChanges = [];
|
|
3723
|
-
for (const { column, row } of coordinates){
|
|
3724
|
-
const changedRow = changed.data[row];
|
|
3725
|
-
const sourceRow = source.data[row];
|
|
3726
|
-
const currentCell = changedRow?.[column];
|
|
3727
|
-
const formattedPaste = currentCell ? peekSpreadsheetRichTextPaste(source, row, column, currentCell) : void 0;
|
|
3728
|
-
const nextCell = null == currentCell ? currentCell : cloneControlledSpreadsheetCell(currentCell, sourceRow?.[column], formattedPaste);
|
|
3729
|
-
if (formattedPaste) pendingRichTextPastes.push({
|
|
3730
|
-
column,
|
|
3731
|
-
row,
|
|
3732
|
-
sheet: source
|
|
3733
|
-
});
|
|
3734
|
-
let nextRow = mutableRows.get(row);
|
|
3735
|
-
if (!nextRow) {
|
|
3736
|
-
nextRow = sourceRow?.slice() ?? [];
|
|
3737
|
-
nextRow.length = Math.max(nextRow.length, changedRow?.length ?? 0, positiveSpreadsheetDimension(changed.column));
|
|
3738
|
-
mutableRows.set(row, nextRow);
|
|
3739
|
-
data[row] = nextRow;
|
|
3740
|
-
}
|
|
3741
|
-
if (null == nextCell) delete nextRow[column];
|
|
3742
|
-
else nextRow[column] = nextCell;
|
|
3743
|
-
profileChanges.push({
|
|
3744
|
-
column,
|
|
3745
|
-
current: nextCell,
|
|
3746
|
-
previous: sourceRow?.[column],
|
|
3747
|
-
row
|
|
3748
|
-
});
|
|
3749
|
-
}
|
|
3750
|
-
if (!registerDerivedSpreadsheetMatrix(data, source.data, profileChanges)) return null;
|
|
3751
|
-
return {
|
|
3752
|
-
affectedCellCount: profileChanges.length,
|
|
3753
|
-
data
|
|
3754
|
-
};
|
|
3755
|
-
}
|
|
3756
|
-
function cloneControlledSpreadsheetCell(cell, previous, formattedPaste) {
|
|
3757
|
-
return freezeImportedSpreadsheetCell(structuredClone(reconcileSpreadsheetRichTextCellEdit(previous, cell, formattedPaste)));
|
|
3758
|
-
}
|
|
3759
|
-
function spreadsheetSheetsById(sheets) {
|
|
3760
|
-
const byId = new Map();
|
|
3761
|
-
for (const sheet of sheets){
|
|
3762
|
-
if (!sheet.id || byId.has(sheet.id)) return null;
|
|
3763
|
-
byId.set(sheet.id, sheet);
|
|
3764
|
-
}
|
|
3765
|
-
return byId;
|
|
3766
|
-
}
|
|
3767
|
-
function isSpreadsheetCoordinate(value) {
|
|
3768
|
-
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
3769
|
-
}
|
|
3770
|
-
function positiveSpreadsheetDimension(value) {
|
|
3771
|
-
return 'number' == typeof value && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
3772
|
-
}
|
|
3773
|
-
function spreadsheetCurrentRegion(sheet, seed) {
|
|
3774
|
-
const range = spreadsheet_cell_range_normalizeSpreadsheetCellRange(seed);
|
|
3775
|
-
if (!range) return null;
|
|
3776
|
-
const cells = spreadsheetSheetCellReader(sheet);
|
|
3777
|
-
const bounds = spreadsheetCurrentRegionUsedBounds(sheet);
|
|
3778
|
-
let startRow = range.row[0];
|
|
3779
|
-
let endRow = range.row[1];
|
|
3780
|
-
let startColumn = range.column[0];
|
|
3781
|
-
let endColumn = range.column[1];
|
|
3782
|
-
if (startRow > bounds.lastRow || startColumn > bounds.lastColumn || !spreadsheet_current_region_spreadsheetCellHasContent(cells(startRow, startColumn))) return null;
|
|
3783
|
-
let changed = true;
|
|
3784
|
-
while(changed){
|
|
3785
|
-
changed = false;
|
|
3786
|
-
while(startColumn > 0 && spreadsheetCurrentRegionColumnHasContent(cells, startColumn - 1, startRow, endRow)){
|
|
3787
|
-
startColumn -= 1;
|
|
3788
|
-
changed = true;
|
|
3789
|
-
}
|
|
3790
|
-
while(endColumn < bounds.lastColumn && spreadsheetCurrentRegionColumnHasContent(cells, endColumn + 1, startRow, endRow)){
|
|
3791
|
-
endColumn += 1;
|
|
3792
|
-
changed = true;
|
|
3793
|
-
}
|
|
3794
|
-
while(startRow > 0 && spreadsheetRowHasContent(cells, startRow - 1, startColumn, endColumn)){
|
|
3795
|
-
startRow -= 1;
|
|
3796
|
-
changed = true;
|
|
3797
|
-
}
|
|
3798
|
-
while(endRow < bounds.lastRow && spreadsheetRowHasContent(cells, endRow + 1, startColumn, endColumn)){
|
|
3799
|
-
endRow += 1;
|
|
3800
|
-
changed = true;
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
return {
|
|
3804
|
-
row: [
|
|
3805
|
-
startRow,
|
|
3806
|
-
endRow
|
|
3807
|
-
],
|
|
3808
|
-
column: [
|
|
3809
|
-
startColumn,
|
|
3810
|
-
endColumn
|
|
3811
|
-
]
|
|
3812
|
-
};
|
|
3813
|
-
}
|
|
3814
|
-
function spreadsheetRowHasContent(cellAt, row, startColumn, endColumn) {
|
|
3815
|
-
for(let column = startColumn; column <= endColumn; column += 1)if (spreadsheet_current_region_spreadsheetCellHasContent(cellAt(row, column))) return true;
|
|
3816
|
-
return false;
|
|
3817
|
-
}
|
|
3818
|
-
function spreadsheetCurrentRegionColumnHasContent(cellAt, column, startRow, endRow) {
|
|
3819
|
-
for(let row = startRow; row <= endRow; row += 1)if (spreadsheet_current_region_spreadsheetCellHasContent(cellAt(row, column))) return true;
|
|
3820
|
-
return false;
|
|
3821
|
-
}
|
|
3822
|
-
function spreadsheet_current_region_spreadsheetCellHasContent(cell) {
|
|
3823
|
-
if (!cell) return false;
|
|
3824
|
-
return [
|
|
3825
|
-
cell.v,
|
|
3826
|
-
cell.m,
|
|
3827
|
-
cell.f
|
|
3828
|
-
].some((value)=>null != value && '' !== value);
|
|
3829
|
-
}
|
|
3830
|
-
function spreadsheetSheetCellReader(sheet) {
|
|
3831
|
-
if (sheet.data) return (row, column)=>sheet.data?.[row]?.[column] ?? null;
|
|
3832
|
-
const cells = new Map((sheet.celldata ?? []).map((cell)=>[
|
|
3833
|
-
`${cell.r}:${cell.c}`,
|
|
3834
|
-
cell.v
|
|
3835
|
-
]));
|
|
3836
|
-
return (row, column)=>cells.get(`${row}:${column}`) ?? null;
|
|
3837
|
-
}
|
|
3838
|
-
function spreadsheetCurrentRegionUsedBounds(sheet) {
|
|
3839
|
-
let lastRow = Math.max((sheet.data?.length ?? 1) - 1, 0);
|
|
3840
|
-
let lastColumn = Math.max(sparseMatrixColumnCount(sheet.data) - 1, 0);
|
|
3841
|
-
for (const cell of sheet.celldata ?? []){
|
|
3842
|
-
lastRow = Math.max(lastRow, cell.r);
|
|
3843
|
-
lastColumn = Math.max(lastColumn, cell.c);
|
|
3844
|
-
}
|
|
3845
|
-
return {
|
|
3846
|
-
lastColumn,
|
|
3847
|
-
lastRow
|
|
3848
|
-
};
|
|
3849
|
-
}
|
|
3850
|
-
function spreadsheetCellFillFormat(cell) {
|
|
3851
|
-
const nativeFill = activeXlsxNativeFill(cell);
|
|
3852
|
-
if (nativeFill) return nativeFill;
|
|
3853
|
-
const color = normalizeSpreadsheetFillColor(cell?.bg);
|
|
3854
|
-
return color ? {
|
|
3855
|
-
color,
|
|
3856
|
-
kind: 'solid'
|
|
3857
|
-
} : {
|
|
3858
|
-
kind: 'none'
|
|
3859
|
-
};
|
|
3860
|
-
}
|
|
3861
|
-
function normalizeSpreadsheetCellFillFormat(value) {
|
|
3862
|
-
if (!spreadsheet_cell_fill_format_isRecord(value)) return null;
|
|
3863
|
-
if ('none' === value.kind) return {
|
|
3864
|
-
kind: 'none'
|
|
3865
|
-
};
|
|
3866
|
-
if ('solid' === value.kind) {
|
|
3867
|
-
const color = normalizeSpreadsheetFillColor(value.color);
|
|
3868
|
-
return color ? {
|
|
3869
|
-
color,
|
|
3870
|
-
kind: 'solid'
|
|
3871
|
-
} : null;
|
|
3872
|
-
}
|
|
3873
|
-
if ('pattern' === value.kind) {
|
|
3874
|
-
const fill = normalizeXlsxPatternFill(value.value);
|
|
3875
|
-
return fill ? {
|
|
3876
|
-
kind: 'pattern',
|
|
3877
|
-
value: fill
|
|
3878
|
-
} : null;
|
|
3879
|
-
}
|
|
3880
|
-
if ('gradient' === value.kind) {
|
|
3881
|
-
const fill = normalizeXlsxGradientFill(value.value);
|
|
3882
|
-
return fill ? {
|
|
3883
|
-
kind: 'gradient',
|
|
3884
|
-
value: fill
|
|
3885
|
-
} : null;
|
|
3886
|
-
}
|
|
3887
|
-
return null;
|
|
3888
|
-
}
|
|
3889
|
-
function withSpreadsheetCellFillFormat(cell, fill) {
|
|
3890
|
-
const normalized = normalizeSpreadsheetCellFillFormat(fill);
|
|
3891
|
-
if (!normalized) return null;
|
|
3892
|
-
const next = {
|
|
3893
|
-
...cell
|
|
3894
|
-
};
|
|
3895
|
-
deleteXlsxNativeFills(next);
|
|
3896
|
-
if ('none' === normalized.kind) {
|
|
3897
|
-
delete next.bg;
|
|
3898
|
-
return next;
|
|
3899
|
-
}
|
|
3900
|
-
if ('solid' === normalized.kind) {
|
|
3901
|
-
next.bg = normalized.color;
|
|
3902
|
-
return next;
|
|
3903
|
-
}
|
|
3904
|
-
if ('pattern' === normalized.kind) {
|
|
3905
|
-
next.bg = normalized.value.backgroundColor;
|
|
3906
|
-
return withXlsxPatternFill(next, normalized.value);
|
|
3907
|
-
}
|
|
3908
|
-
next.bg = xlsxGradientFillFallbackColor(normalized.value);
|
|
3909
|
-
return withXlsxGradientFill(next, normalized.value);
|
|
3910
|
-
}
|
|
3911
|
-
function normalizeSpreadsheetFillColor(value) {
|
|
3912
|
-
if ('string' != typeof value) return null;
|
|
3913
|
-
const color = value.trim().toLowerCase();
|
|
3914
|
-
if (/^#[0-9a-f]{6}$/.test(color)) return color;
|
|
3915
|
-
if (!/^#[0-9a-f]{3}$/.test(color)) return null;
|
|
3916
|
-
return `#${[
|
|
3917
|
-
...color.slice(1)
|
|
3918
|
-
].map((character)=>character.repeat(2)).join('')}`;
|
|
3919
|
-
}
|
|
3920
|
-
function spreadsheet_cell_fill_format_isRecord(value) {
|
|
3921
|
-
return 'object' == typeof value && null !== value && !Array.isArray(value);
|
|
3922
|
-
}
|
|
3923
|
-
function createSpreadsheetSortAppearanceRows(sheet, range, rows) {
|
|
3924
|
-
const data = liveSpreadsheetSortMatrix(sheet, range, rows);
|
|
3925
|
-
const conditional = spreadsheetConditionalFormatStyles({
|
|
3926
|
-
...sheet,
|
|
3927
|
-
data
|
|
3928
|
-
});
|
|
3929
|
-
return rows.map((row, rowOffset)=>row.map((cell, columnOffset)=>{
|
|
3930
|
-
const direct = spreadsheetSortDirectCellAppearance(cell);
|
|
3931
|
-
const style = conditional.get(`${range.row[0] + rowOffset}_${range.column[0] + columnOffset}`);
|
|
3932
|
-
const conditionalCellColor = normalizeSpreadsheetSortColor(style?.cellColor);
|
|
3933
|
-
const conditionalFontColor = normalizeSpreadsheetSortColor(style?.textColor);
|
|
3934
|
-
return {
|
|
3935
|
-
cellColor: void 0 !== conditionalCellColor ? conditionalCellColor : direct.cellColor,
|
|
3936
|
-
fontColor: void 0 !== conditionalFontColor ? conditionalFontColor : direct.fontColor,
|
|
3937
|
-
icon: normalizeSpreadsheetSortIcon(style?.icon) ?? null
|
|
3938
|
-
};
|
|
3939
|
-
}));
|
|
3940
|
-
}
|
|
3941
|
-
function createSpreadsheetSortDirectAppearanceRows(rows) {
|
|
3942
|
-
return rows.map((row)=>row.map(spreadsheetSortDirectCellAppearance));
|
|
3943
|
-
}
|
|
3944
|
-
function spreadsheetSortAppearanceFields(rows, range, orientation, hasHeader) {
|
|
3945
|
-
const vertical = 'top-to-bottom' === orientation;
|
|
3946
|
-
const fieldCount = vertical ? range.column[1] - range.column[0] + 1 : range.row[1] - range.row[0] + 1;
|
|
3947
|
-
return Array.from({
|
|
3948
|
-
length: fieldCount
|
|
3949
|
-
}, (_, offset)=>{
|
|
3950
|
-
const cellColors = [];
|
|
3951
|
-
const fontColors = [];
|
|
3952
|
-
const icons = [];
|
|
3953
|
-
const seenCellColors = new Set();
|
|
3954
|
-
const seenFontColors = new Set();
|
|
3955
|
-
const seenIcons = new Set();
|
|
3956
|
-
const appearances = vertical ? rows.slice(hasHeader ? 1 : 0).map((row)=>row[offset]) : rows[offset] ?? [];
|
|
3957
|
-
for (const appearance of appearances)if (appearance) {
|
|
3958
|
-
if (void 0 !== appearance.cellColor) appendUniqueColor(cellColors, seenCellColors, appearance.cellColor);
|
|
3959
|
-
appendUniqueColor(fontColors, seenFontColors, appearance.fontColor);
|
|
3960
|
-
if (appearance.icon) {
|
|
3961
|
-
const key = spreadsheetSortIconTargetKey(appearance.icon);
|
|
3962
|
-
if (!seenIcons.has(key)) {
|
|
3963
|
-
seenIcons.add(key);
|
|
3964
|
-
icons.push({
|
|
3965
|
-
...appearance.icon
|
|
3966
|
-
});
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
return {
|
|
3971
|
-
index: (vertical ? range.column[0] : range.row[0]) + offset,
|
|
3972
|
-
cellColors,
|
|
3973
|
-
fontColors,
|
|
3974
|
-
icons
|
|
3975
|
-
};
|
|
3976
|
-
});
|
|
3977
|
-
}
|
|
3978
|
-
function normalizeSpreadsheetSortColor(value) {
|
|
3979
|
-
if (null === value) return null;
|
|
3980
|
-
if ('string' != typeof value) return;
|
|
3981
|
-
const source = value.trim();
|
|
3982
|
-
const shorthand = /^#([0-9a-f]{3})$/iu.exec(source);
|
|
3983
|
-
const parsed = parseSpreadsheetConditionalColor(shorthand ? `#${[
|
|
3984
|
-
...shorthand[1]
|
|
3985
|
-
].map((character)=>character.repeat(2)).join('')}` : source);
|
|
3986
|
-
return parsed ? spreadsheetSortHexColor(parsed) : void 0;
|
|
3987
|
-
}
|
|
3988
|
-
function normalizeSpreadsheetSortIcon(value) {
|
|
3989
|
-
if (!value || 'object' != typeof value || Array.isArray(value)) return null;
|
|
3990
|
-
const source = value;
|
|
3991
|
-
if (!isSpreadsheetConditionalIconSetName(source.iconSet) || !Number.isSafeInteger(source.index) || source.index < 0 || source.index >= spreadsheetConditionalIconSetCount(source.iconSet)) return null;
|
|
3992
|
-
return {
|
|
3993
|
-
iconSet: source.iconSet,
|
|
3994
|
-
index: source.index
|
|
3995
|
-
};
|
|
3996
|
-
}
|
|
3997
|
-
function spreadsheetSortAppearanceTargetValue(target) {
|
|
3998
|
-
if ('icon' === target.kind) return `icon:${target.icon.iconSet}:${target.icon.index}`;
|
|
3999
|
-
const value = target.color ?? ('cell-color' === target.kind ? 'none' : 'automatic');
|
|
4000
|
-
return `${target.kind}:${value}`;
|
|
4001
|
-
}
|
|
4002
|
-
function parseSpreadsheetSortAppearanceTargetValue(value) {
|
|
4003
|
-
if (value.startsWith('cell-color:')) {
|
|
4004
|
-
const source = value.slice(11);
|
|
4005
|
-
if ('none' === source) return {
|
|
4006
|
-
kind: 'cell-color',
|
|
4007
|
-
color: null
|
|
4008
|
-
};
|
|
4009
|
-
const color = normalizeSpreadsheetSortColor(source);
|
|
4010
|
-
return 'string' == typeof color ? {
|
|
4011
|
-
kind: 'cell-color',
|
|
4012
|
-
color
|
|
4013
|
-
} : null;
|
|
4014
|
-
}
|
|
4015
|
-
if (value.startsWith('font-color:')) {
|
|
4016
|
-
const source = value.slice(11);
|
|
4017
|
-
if ('automatic' === source) return {
|
|
4018
|
-
kind: 'font-color',
|
|
4019
|
-
color: null
|
|
4020
|
-
};
|
|
4021
|
-
const color = normalizeSpreadsheetSortColor(source);
|
|
4022
|
-
return 'string' == typeof color ? {
|
|
4023
|
-
kind: 'font-color',
|
|
4024
|
-
color
|
|
4025
|
-
} : null;
|
|
4026
|
-
}
|
|
4027
|
-
const icon = /^icon:([^:]+):(\d+)$/u.exec(value);
|
|
4028
|
-
if (!icon) return null;
|
|
4029
|
-
const normalized = normalizeSpreadsheetSortIcon({
|
|
4030
|
-
iconSet: icon[1],
|
|
4031
|
-
index: Number(icon[2])
|
|
4032
|
-
});
|
|
4033
|
-
return normalized ? {
|
|
4034
|
-
kind: 'icon',
|
|
4035
|
-
icon: normalized
|
|
4036
|
-
} : null;
|
|
4037
|
-
}
|
|
4038
|
-
function spreadsheetSortAppearanceTargetLabel(target) {
|
|
4039
|
-
if ('cell-color' === target.kind) return target.color ? `单元格颜色 ${target.color.toUpperCase()}` : '无单元格颜色';
|
|
4040
|
-
if ('font-color' === target.kind) return target.color ? `字体颜色 ${target.color.toUpperCase()}` : '自动字体颜色';
|
|
4041
|
-
const count = spreadsheetConditionalIconSetCount(target.icon.iconSet);
|
|
4042
|
-
const appearance = spreadsheetConditionalIconAppearance({
|
|
4043
|
-
...target.icon,
|
|
4044
|
-
count,
|
|
4045
|
-
showValue: true
|
|
4046
|
-
});
|
|
4047
|
-
return `${appearance.glyph} ${appearance.label}`;
|
|
4048
|
-
}
|
|
4049
|
-
function spreadsheetSortAppearanceTargets(field, kind) {
|
|
4050
|
-
if (!field) return [];
|
|
4051
|
-
if ('cell-color' === kind) return field.cellColors.map((color)=>({
|
|
4052
|
-
kind,
|
|
4053
|
-
color
|
|
4054
|
-
}));
|
|
4055
|
-
if ('font-color' === kind) return field.fontColors.map((color)=>({
|
|
4056
|
-
kind,
|
|
4057
|
-
color
|
|
4058
|
-
}));
|
|
4059
|
-
return field.icons.map((icon)=>({
|
|
4060
|
-
kind,
|
|
4061
|
-
icon: {
|
|
4062
|
-
...icon
|
|
4063
|
-
}
|
|
4064
|
-
}));
|
|
4065
|
-
}
|
|
4066
|
-
function spreadsheetSortAppearanceTargetsEqual(left, right) {
|
|
4067
|
-
return spreadsheetSortAppearanceTargetValue(left) === spreadsheetSortAppearanceTargetValue(right);
|
|
4068
|
-
}
|
|
4069
|
-
function spreadsheetSortCellMatchesAppearance(appearance, target) {
|
|
4070
|
-
if (!appearance) return false;
|
|
4071
|
-
if ('cell-color' === target.kind) return appearance.cellColor === target.color;
|
|
4072
|
-
if ('font-color' === target.kind) return appearance.fontColor === target.color;
|
|
4073
|
-
return Boolean(appearance.icon && spreadsheetSortIconTargetKey(appearance.icon) === spreadsheetSortIconTargetKey(target.icon));
|
|
4074
|
-
}
|
|
4075
|
-
function spreadsheetSortDirectCellAppearance(cell) {
|
|
4076
|
-
const fill = spreadsheetCellFillFormat(cell);
|
|
4077
|
-
const fontColor = normalizeSpreadsheetSortColor(cell?.fc);
|
|
4078
|
-
return {
|
|
4079
|
-
cellColor: 'solid' === fill.kind ? fill.color : 'none' === fill.kind ? null : void 0,
|
|
4080
|
-
fontColor: 'string' == typeof fontColor ? fontColor : null,
|
|
4081
|
-
icon: null
|
|
4082
|
-
};
|
|
4083
|
-
}
|
|
4084
|
-
function liveSpreadsheetSortMatrix(sheet, range, rows) {
|
|
4085
|
-
const data = cloneSparseMatrix(sheet.data);
|
|
4086
|
-
for (const entry of sheet.celldata ?? []){
|
|
4087
|
-
const row = data[entry.r] ? [
|
|
4088
|
-
...data[entry.r]
|
|
4089
|
-
] : [];
|
|
4090
|
-
if (void 0 === row[entry.c]) row[entry.c] = entry.v;
|
|
4091
|
-
data[entry.r] = row;
|
|
4092
|
-
}
|
|
4093
|
-
for(let rowOffset = 0; rowOffset < rows.length; rowOffset += 1){
|
|
4094
|
-
const absoluteRow = range.row[0] + rowOffset;
|
|
4095
|
-
const target = data[absoluteRow] ? [
|
|
4096
|
-
...data[absoluteRow]
|
|
4097
|
-
] : [];
|
|
4098
|
-
const source = rows[rowOffset] ?? [];
|
|
4099
|
-
for(let columnOffset = 0; columnOffset < source.length; columnOffset += 1)target[range.column[0] + columnOffset] = source[columnOffset] ?? null;
|
|
4100
|
-
data[absoluteRow] = target;
|
|
4101
|
-
}
|
|
4102
|
-
return data;
|
|
4103
|
-
}
|
|
4104
|
-
function appendUniqueColor(target, seen, color) {
|
|
4105
|
-
const key = color ?? 'none';
|
|
4106
|
-
if (seen.has(key)) return;
|
|
4107
|
-
seen.add(key);
|
|
4108
|
-
target.push(color);
|
|
4109
|
-
}
|
|
4110
|
-
function spreadsheetSortIconTargetKey(target) {
|
|
4111
|
-
return `${target.iconSet}:${target.index}`;
|
|
4112
|
-
}
|
|
4113
|
-
function spreadsheetSortHexColor(color) {
|
|
4114
|
-
return `#${[
|
|
4115
|
-
color.red,
|
|
4116
|
-
color.green,
|
|
4117
|
-
color.blue
|
|
4118
|
-
].map((channel)=>channel.toString(16).padStart(2, '0')).join('')}`;
|
|
4119
|
-
}
|
|
4120
|
-
const DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS = Object.freeze({
|
|
4121
|
-
caseSensitive: false,
|
|
4122
|
-
textMethod: 'pinyin'
|
|
4123
|
-
});
|
|
4124
|
-
const SPREADSHEET_SORT_COLLATION_PROBES = Object.freeze({
|
|
4125
|
-
pinyin: Object.freeze([
|
|
4126
|
-
'阿',
|
|
4127
|
-
'丁',
|
|
4128
|
-
'王',
|
|
4129
|
-
'赵'
|
|
4130
|
-
]),
|
|
4131
|
-
stroke: Object.freeze([
|
|
4132
|
-
'丁',
|
|
4133
|
-
'王',
|
|
4134
|
-
'安',
|
|
4135
|
-
'阿',
|
|
4136
|
-
'赵'
|
|
4137
|
-
])
|
|
4138
|
-
});
|
|
4139
|
-
function isSpreadsheetSortTextMethod(value) {
|
|
4140
|
-
return 'pinyin' === value || 'stroke' === value;
|
|
4141
|
-
}
|
|
4142
|
-
function createSpreadsheetSortTextComparator(options) {
|
|
4143
|
-
try {
|
|
4144
|
-
const collator = new Intl.Collator(`zh-CN-u-co-${options.textMethod}`, {
|
|
4145
|
-
caseFirst: 'lower',
|
|
4146
|
-
numeric: false,
|
|
4147
|
-
sensitivity: options.caseSensitive ? 'case' : 'base',
|
|
4148
|
-
usage: 'sort'
|
|
4149
|
-
});
|
|
4150
|
-
if (!spreadsheetSortCollatorSupportsMethod(collator, options.textMethod)) return null;
|
|
4151
|
-
return collator.compare;
|
|
4152
|
-
} catch {
|
|
4153
|
-
return null;
|
|
4154
|
-
}
|
|
4155
|
-
}
|
|
4156
|
-
function spreadsheetSortCollatorSupportsMethod(collator, textMethod) {
|
|
4157
|
-
const probe = SPREADSHEET_SORT_COLLATION_PROBES[textMethod];
|
|
4158
|
-
for(let index = 1; index < probe.length; index += 1){
|
|
4159
|
-
const previous = probe[index - 1];
|
|
4160
|
-
const current = probe[index];
|
|
4161
|
-
if (void 0 === previous || void 0 === current) return false;
|
|
4162
|
-
if (collator.compare(previous, current) >= 0) return false;
|
|
4163
|
-
}
|
|
4164
|
-
return true;
|
|
4165
|
-
}
|
|
4166
|
-
const MAX_SPREADSHEET_SORT_KEYS = 64;
|
|
4167
|
-
const MAX_SPREADSHEET_SORT_CELLS = 1000000;
|
|
4168
|
-
const MAX_SPREADSHEET_SORT_HEADER_LENGTH = 48;
|
|
4169
|
-
function createSpreadsheetSortRangePlan(sheet, selectedRange) {
|
|
4170
|
-
const selected = spreadsheet_cell_range_normalizeSpreadsheetCellRange(selectedRange);
|
|
4171
|
-
if (!selected) return null;
|
|
4172
|
-
const owned = spreadsheetSortOwnedRangeContaining(sheet, selected);
|
|
4173
|
-
if (owned) return {
|
|
4174
|
-
selectedRange: selected,
|
|
4175
|
-
expandedRange: spreadsheetCellRangesEqual(owned.range, selected) ? null : owned.range
|
|
4176
|
-
};
|
|
4177
|
-
const currentRegion = spreadsheetCurrentRegion(sheet, selected);
|
|
4178
|
-
return {
|
|
4179
|
-
selectedRange: selected,
|
|
4180
|
-
expandedRange: currentRegion && !spreadsheetCellRangesEqual(currentRegion, selected) ? currentRegion : null
|
|
4181
|
-
};
|
|
4182
|
-
}
|
|
4183
|
-
function createSpreadsheetSortRangeDialogSource(sheetName, request) {
|
|
4184
|
-
if (!request.expanded) return null;
|
|
4185
|
-
return {
|
|
4186
|
-
sheetName,
|
|
4187
|
-
selectedRangeReference: formatSpreadsheetCellRanges([
|
|
4188
|
-
request.selected.range
|
|
4189
|
-
]),
|
|
4190
|
-
expandedRangeReference: formatSpreadsheetCellRanges([
|
|
4191
|
-
request.expanded.range
|
|
4192
|
-
]),
|
|
4193
|
-
canSortSelection: request.selected.available,
|
|
4194
|
-
canSortExpandedRange: request.expanded.available,
|
|
4195
|
-
...request.expanded.scope ? {
|
|
4196
|
-
ownedScope: request.expanded.scope
|
|
4197
|
-
} : {}
|
|
4198
|
-
};
|
|
4199
|
-
}
|
|
4200
|
-
function createSpreadsheetSortDialogSource(sheetId, sheetName, target, rows, customLists = [], appearanceRows) {
|
|
4201
|
-
const range = spreadsheet_cell_range_normalizeSpreadsheetCellRange(target.range);
|
|
4202
|
-
if (!range || spreadsheetSortRangeError(range)) return null;
|
|
4203
|
-
const width = range.column[1] - range.column[0] + 1;
|
|
4204
|
-
const height = range.row[1] - range.row[0] + 1;
|
|
4205
|
-
if (!spreadsheetSortRowsMatchRange(rows, range)) return null;
|
|
4206
|
-
const hasHeader = height > 2 && spreadsheetSortRowsHaveHeader(rows);
|
|
4207
|
-
const resolvedHasHeader = target.scope?.hasHeader ?? hasHeader;
|
|
4208
|
-
const activeColumn = spreadsheetSortActiveIndex(target.activeColumn, range.column);
|
|
4209
|
-
const activeRow = spreadsheetSortActiveIndex(target.activeRow, range.row);
|
|
4210
|
-
const header = rows[0] ?? [];
|
|
4211
|
-
const resolvedAppearanceRows = appearanceRows && spreadsheetSortAppearanceRowsMatch(rows, appearanceRows) ? appearanceRows : createSpreadsheetSortDirectAppearanceRows(rows);
|
|
4212
|
-
return {
|
|
4213
|
-
sheetId,
|
|
4214
|
-
sheetName,
|
|
4215
|
-
range,
|
|
4216
|
-
rangeReference: formatSpreadsheetCellRanges([
|
|
4217
|
-
range
|
|
4218
|
-
]),
|
|
4219
|
-
activeRow,
|
|
4220
|
-
appearanceRows: resolvedAppearanceRows,
|
|
4221
|
-
customLists: mergeSpreadsheetSortCustomLists(customLists),
|
|
4222
|
-
...target.scope ? {
|
|
4223
|
-
scope: target.scope
|
|
4224
|
-
} : {},
|
|
4225
|
-
columns: Array.from({
|
|
4226
|
-
length: width
|
|
4227
|
-
}, (_, offset)=>{
|
|
4228
|
-
const index = range.column[0] + offset;
|
|
4229
|
-
const name = spreadsheetSortHeaderText(header[offset]);
|
|
4230
|
-
const coordinate = spreadsheetSortColumnLabel(index);
|
|
4231
|
-
return {
|
|
4232
|
-
index,
|
|
4233
|
-
label: name ? `${coordinate}(${name})` : coordinate
|
|
4234
|
-
};
|
|
4235
|
-
}),
|
|
4236
|
-
rows: Array.from({
|
|
4237
|
-
length: height
|
|
4238
|
-
}, (_, offset)=>{
|
|
4239
|
-
const index = range.row[0] + offset;
|
|
4240
|
-
return {
|
|
4241
|
-
index,
|
|
4242
|
-
label: `行 ${index + 1}`
|
|
4243
|
-
};
|
|
4244
|
-
}),
|
|
4245
|
-
value: {
|
|
4246
|
-
orientation: 'top-to-bottom',
|
|
4247
|
-
...DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS,
|
|
4248
|
-
hasHeader: resolvedHasHeader,
|
|
4249
|
-
keys: [
|
|
4250
|
-
{
|
|
4251
|
-
index: activeColumn,
|
|
4252
|
-
direction: 'ascending'
|
|
4253
|
-
}
|
|
4254
|
-
]
|
|
4255
|
-
}
|
|
4256
|
-
};
|
|
4257
|
-
}
|
|
4258
|
-
function spreadsheetSortRowsMatchRange(rows, range) {
|
|
4259
|
-
const normalized = spreadsheet_cell_range_normalizeSpreadsheetCellRange(range);
|
|
4260
|
-
if (!normalized) return false;
|
|
4261
|
-
const width = normalized.column[1] - normalized.column[0] + 1;
|
|
4262
|
-
const height = normalized.row[1] - normalized.row[0] + 1;
|
|
4263
|
-
return rows.length === height && rows.every((row)=>row.length === width);
|
|
4264
|
-
}
|
|
4265
|
-
function spreadsheetSortRowsFromSheet(sheet, range) {
|
|
4266
|
-
const normalized = spreadsheet_cell_range_normalizeSpreadsheetCellRange(range);
|
|
4267
|
-
if (!normalized || spreadsheetSortRangeError(normalized)) return null;
|
|
4268
|
-
const width = normalized.column[1] - normalized.column[0] + 1;
|
|
4269
|
-
const height = normalized.row[1] - normalized.row[0] + 1;
|
|
4270
|
-
const rows = Array.from({
|
|
4271
|
-
length: height
|
|
4272
|
-
}, ()=>Array(width).fill(null));
|
|
4273
|
-
for(let row = normalized.row[0]; row <= normalized.row[1]; row += 1){
|
|
4274
|
-
const source = sheet.data?.[row];
|
|
4275
|
-
if (!source) continue;
|
|
4276
|
-
const target = rows[row - normalized.row[0]];
|
|
4277
|
-
for(let column = normalized.column[0]; column <= normalized.column[1]; column += 1)target[column - normalized.column[0]] = source[column] ?? null;
|
|
4278
|
-
}
|
|
4279
|
-
for (const entry of sheet.celldata ?? []){
|
|
4280
|
-
if (entry.r < normalized.row[0] || entry.r > normalized.row[1] || entry.c < normalized.column[0] || entry.c > normalized.column[1]) continue;
|
|
4281
|
-
const row = rows[entry.r - normalized.row[0]];
|
|
4282
|
-
const column = entry.c - normalized.column[0];
|
|
4283
|
-
if (null === row[column]) row[column] = entry.v ?? null;
|
|
4284
|
-
}
|
|
4285
|
-
return rows;
|
|
4286
|
-
}
|
|
4287
|
-
function spreadsheetSortOwnedRangeForExactRange(sheet, range) {
|
|
4288
|
-
const normalized = spreadsheet_cell_range_normalizeSpreadsheetCellRange(range);
|
|
4289
|
-
if (!normalized) return null;
|
|
4290
|
-
const matches = spreadsheetSortOwnedRanges(sheet).filter((candidate)=>spreadsheetCellRangesEqual(candidate.range, normalized));
|
|
4291
|
-
return 1 === matches.length ? matches[0] : null;
|
|
4292
|
-
}
|
|
4293
|
-
function spreadsheetSortOwnedRangeContaining(sheet, range) {
|
|
4294
|
-
const matches = spreadsheetSortOwnedRanges(sheet).filter((candidate)=>spreadsheetSortRangeContains(candidate.range, range));
|
|
4295
|
-
return 1 === matches.length ? matches[0] : null;
|
|
4296
|
-
}
|
|
4297
|
-
function spreadsheetSortOwnedRanges(sheet) {
|
|
4298
|
-
const ranges = [];
|
|
4299
|
-
const autoFilter = spreadsheet_cell_range_normalizeSpreadsheetCellRange(sheet.filter_select ?? {
|
|
4300
|
-
row: [],
|
|
4301
|
-
column: []
|
|
4302
|
-
});
|
|
4303
|
-
if (autoFilter && autoFilter.row[1] > autoFilter.row[0]) ranges.push({
|
|
4304
|
-
range: autoFilter,
|
|
4305
|
-
scope: {
|
|
4306
|
-
kind: 'auto-filter',
|
|
4307
|
-
hasHeader: true
|
|
4308
|
-
}
|
|
4309
|
-
});
|
|
4310
|
-
for (const table of sheet.tables ?? []){
|
|
4311
|
-
const tableRange = spreadsheet_cell_range_normalizeSpreadsheetCellRange(table.range);
|
|
4312
|
-
if (!tableRange || !table.id || 0 === table.columns.length) continue;
|
|
4313
|
-
const width = tableRange.column[1] - tableRange.column[0] + 1;
|
|
4314
|
-
if (width !== table.columns.length) continue;
|
|
4315
|
-
const endRow = tableRange.row[1] - Number(table.totalsRow);
|
|
4316
|
-
if (!(endRow <= tableRange.row[0])) ranges.push({
|
|
4317
|
-
range: {
|
|
4318
|
-
row: [
|
|
4319
|
-
tableRange.row[0],
|
|
4320
|
-
endRow
|
|
4321
|
-
],
|
|
4322
|
-
column: [
|
|
4323
|
-
...tableRange.column
|
|
4324
|
-
]
|
|
4325
|
-
},
|
|
4326
|
-
scope: {
|
|
4327
|
-
kind: 'table',
|
|
4328
|
-
tableId: table.id,
|
|
4329
|
-
hasHeader: table.headerRow
|
|
4330
|
-
}
|
|
4331
|
-
});
|
|
4332
|
-
}
|
|
4333
|
-
return ranges;
|
|
4334
|
-
}
|
|
4335
|
-
function spreadsheetSortRangeContains(outer, inner) {
|
|
4336
|
-
return outer.row[0] <= inner.row[0] && outer.row[1] >= inner.row[1] && outer.column[0] <= inner.column[0] && outer.column[1] >= inner.column[1];
|
|
4337
|
-
}
|
|
4338
|
-
function validateSpreadsheetSortRequest(request) {
|
|
4339
|
-
const range = spreadsheet_cell_range_normalizeSpreadsheetCellRange(request.range);
|
|
4340
|
-
if (!range || 'string' != typeof request.sheetId || !request.sheetId.trim()) return spreadsheetSortError('invalid-range');
|
|
4341
|
-
const scope = normalizeSpreadsheetSortOwnedScope(request.scope);
|
|
4342
|
-
if (void 0 !== request.scope && !scope) return spreadsheetSortError('invalid-scope');
|
|
4343
|
-
if ('top-to-bottom' !== request.orientation && 'left-to-right' !== request.orientation) return spreadsheetSortError('invalid-orientation');
|
|
4344
|
-
const caseSensitive = void 0 === request.caseSensitive ? DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS.caseSensitive : request.caseSensitive;
|
|
4345
|
-
if ('boolean' != typeof caseSensitive) return spreadsheetSortError('invalid-case-sensitivity');
|
|
4346
|
-
const textMethod = void 0 === request.textMethod ? DEFAULT_SPREADSHEET_SORT_TEXT_OPTIONS.textMethod : request.textMethod;
|
|
4347
|
-
if (!isSpreadsheetSortTextMethod(textMethod)) return spreadsheetSortError('invalid-text-method');
|
|
4348
|
-
if ('left-to-right' === request.orientation && request.hasHeader) return spreadsheetSortError('invalid-header');
|
|
4349
|
-
const rangeError = spreadsheetSortRangeError(range);
|
|
4350
|
-
if (rangeError) return rangeError;
|
|
4351
|
-
if (!Array.isArray(request.keys) || !request.keys.length) return spreadsheetSortError('missing-key');
|
|
4352
|
-
if (request.keys.length > MAX_SPREADSHEET_SORT_KEYS) return spreadsheetSortError('too-many-keys');
|
|
4353
|
-
const keyRange = 'top-to-bottom' === request.orientation ? range.column : range.row;
|
|
4354
|
-
const outOfRangeCode = 'top-to-bottom' === request.orientation ? 'column-out-of-range' : 'row-out-of-range';
|
|
4355
|
-
const keys = [];
|
|
4356
|
-
for (const key of request.keys){
|
|
4357
|
-
if (!Number.isSafeInteger(key.index) || key.index < keyRange[0] || key.index > keyRange[1]) return spreadsheetSortError(outOfRangeCode);
|
|
4358
|
-
if (void 0 !== key.sortOn) {
|
|
4359
|
-
if ('first' !== key.position && 'last' !== key.position || void 0 !== key.customList || void 0 !== key.direction) return spreadsheetSortError('invalid-appearance');
|
|
4360
|
-
if ('cell-color' === key.sortOn || 'font-color' === key.sortOn) {
|
|
4361
|
-
const color = normalizeSpreadsheetSortColor(key.color);
|
|
4362
|
-
if (void 0 === color || void 0 !== key.icon) return spreadsheetSortError('invalid-appearance');
|
|
4363
|
-
const normalized = {
|
|
4364
|
-
index: key.index,
|
|
4365
|
-
sortOn: key.sortOn,
|
|
4366
|
-
color,
|
|
4367
|
-
position: key.position
|
|
4368
|
-
};
|
|
4369
|
-
if (!appendSpreadsheetSortKey(keys, normalized)) return spreadsheetSortError('duplicate-key');
|
|
4370
|
-
continue;
|
|
4371
|
-
}
|
|
4372
|
-
if ('icon' === key.sortOn && void 0 === key.color) {
|
|
4373
|
-
const icon = normalizeSpreadsheetSortIcon(key.icon);
|
|
4374
|
-
if (icon) {
|
|
4375
|
-
const normalized = {
|
|
4376
|
-
index: key.index,
|
|
4377
|
-
sortOn: 'icon',
|
|
4378
|
-
icon,
|
|
4379
|
-
position: key.position
|
|
4380
|
-
};
|
|
4381
|
-
if (!appendSpreadsheetSortKey(keys, normalized)) return spreadsheetSortError('duplicate-key');
|
|
4382
|
-
continue;
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
return spreadsheetSortError('invalid-appearance');
|
|
4386
|
-
}
|
|
4387
|
-
if (void 0 !== key.customList) {
|
|
4388
|
-
if (void 0 !== key.direction) return spreadsheetSortError('invalid-custom-list');
|
|
4389
|
-
const customList = validateSpreadsheetSortCustomList(key.customList);
|
|
4390
|
-
if (!customList.ok) return spreadsheetSortError('invalid-custom-list');
|
|
4391
|
-
if (!appendSpreadsheetSortKey(keys, {
|
|
4392
|
-
index: key.index,
|
|
4393
|
-
customList: customList.entries
|
|
4394
|
-
})) return spreadsheetSortError('duplicate-key');
|
|
4395
|
-
continue;
|
|
4396
|
-
}
|
|
4397
|
-
if ('ascending' !== key.direction && 'descending' !== key.direction) return spreadsheetSortError('invalid-direction');
|
|
4398
|
-
if (!appendSpreadsheetSortKey(keys, {
|
|
4399
|
-
index: key.index,
|
|
4400
|
-
direction: key.direction
|
|
4401
|
-
})) return spreadsheetSortError('duplicate-key');
|
|
4402
|
-
}
|
|
4403
|
-
if ('top-to-bottom' === request.orientation) {
|
|
4404
|
-
const rowCount = range.row[1] - range.row[0] + 1;
|
|
4405
|
-
if (rowCount - (request.hasHeader ? 1 : 0) < 2) return spreadsheetSortError('not-enough-rows');
|
|
4406
|
-
} else if (range.column[1] - range.column[0] + 1 < 2) return spreadsheetSortError('not-enough-columns');
|
|
4407
|
-
return {
|
|
4408
|
-
ok: true,
|
|
4409
|
-
request: {
|
|
4410
|
-
sheetId: request.sheetId,
|
|
4411
|
-
range,
|
|
4412
|
-
orientation: request.orientation,
|
|
4413
|
-
caseSensitive,
|
|
4414
|
-
textMethod,
|
|
4415
|
-
hasHeader: Boolean(request.hasHeader),
|
|
4416
|
-
keys,
|
|
4417
|
-
...scope ? {
|
|
4418
|
-
scope
|
|
4419
|
-
} : {}
|
|
4420
|
-
}
|
|
4421
|
-
};
|
|
4422
|
-
}
|
|
4423
|
-
function spreadsheetSortAppearanceRowsMatch(rows, appearances) {
|
|
4424
|
-
return rows.length === appearances.length && rows.every((row, index)=>row.length === (appearances[index]?.length ?? -1));
|
|
4425
|
-
}
|
|
4426
|
-
function spreadsheetSortError(code) {
|
|
4427
|
-
const messages = {
|
|
4428
|
-
'column-out-of-range': '排序列必须位于当前选定区域内。',
|
|
4429
|
-
'duplicate-key': '同一行或列不能重复使用相同的值或外观排序条件。',
|
|
4430
|
-
'formula-reference-out-of-range': '排序会使相对公式引用超出工作表范围,因此未应用任何更改。',
|
|
4431
|
-
'invalid-appearance': '外观排序条件或当前颜色/图标快照无效,因此未应用任何更改。',
|
|
4432
|
-
'invalid-case-sensitivity': '请选择有效的大小写比较规则。',
|
|
4433
|
-
'invalid-custom-list': '自定义排序序列无效,请检查项目数量、长度和重复项。',
|
|
4434
|
-
'invalid-direction': '请选择有效的升序或降序次序。',
|
|
4435
|
-
'invalid-header': '按行排序会移动所有选定列,不能保留标题列。',
|
|
4436
|
-
'invalid-matrix': '排序只能应用到已完整读取的矩形区域。',
|
|
4437
|
-
'invalid-orientation': '请选择有效的按列或按行排序方向。',
|
|
4438
|
-
'invalid-range': '请选择一个有效的连续单元格区域。',
|
|
4439
|
-
'invalid-scope': '排序区域的表格或筛选所有权已变化,请重新打开排序。',
|
|
4440
|
-
'invalid-text-method': '请选择拼音排序或笔画排序。',
|
|
4441
|
-
'missing-key': '请至少添加一个排序条件。',
|
|
4442
|
-
'not-enough-columns': '当前区域没有足够的数据列可供按行排序。',
|
|
4443
|
-
'not-enough-rows': '当前区域没有足够的数据行可供排序。',
|
|
4444
|
-
'range-too-large': `一次最多可排序 ${MAX_SPREADSHEET_SORT_CELLS.toLocaleString('en-US')} 个单元格。`,
|
|
4445
|
-
'row-out-of-range': '排序行必须位于当前选定区域内。',
|
|
4446
|
-
'too-many-keys': `一次最多可设置 ${MAX_SPREADSHEET_SORT_KEYS} 个排序条件。`,
|
|
4447
|
-
'unsupported-text-method': '当前运行环境不支持所选中文排序方法,因此未应用任何更改。',
|
|
4448
|
-
'unsupported-linked-cell': '当前区域包含坐标关联的超链接,尚不能安全地随排序移动。'
|
|
4449
|
-
};
|
|
4450
|
-
return {
|
|
4451
|
-
ok: false,
|
|
4452
|
-
code,
|
|
4453
|
-
message: messages[code]
|
|
4454
|
-
};
|
|
4455
|
-
}
|
|
4456
|
-
function normalizeSpreadsheetSortOwnedScope(value) {
|
|
4457
|
-
if (void 0 === value) return null;
|
|
4458
|
-
if (!value || 'object' != typeof value || Array.isArray(value)) return null;
|
|
4459
|
-
if ('auto-filter' === value.kind && true === value.hasHeader && Object.keys(value).every((key)=>'kind' === key || 'hasHeader' === key)) return {
|
|
4460
|
-
kind: 'auto-filter',
|
|
4461
|
-
hasHeader: true
|
|
4462
|
-
};
|
|
4463
|
-
if ('table' === value.kind && 'string' == typeof value.tableId && Boolean(value.tableId.trim()) && 'boolean' == typeof value.hasHeader && Object.keys(value).every((key)=>'kind' === key || 'tableId' === key || 'hasHeader' === key)) return {
|
|
4464
|
-
kind: 'table',
|
|
4465
|
-
tableId: value.tableId,
|
|
4466
|
-
hasHeader: value.hasHeader
|
|
4467
|
-
};
|
|
4468
|
-
return null;
|
|
4469
|
-
}
|
|
4470
|
-
function spreadsheetSortRangeError(range) {
|
|
4471
|
-
const area = spreadsheet_cell_range_spreadsheetCellRangeArea(range);
|
|
4472
|
-
if (!Number.isSafeInteger(area) || area > MAX_SPREADSHEET_SORT_CELLS) return spreadsheetSortError('range-too-large');
|
|
4473
|
-
return null;
|
|
4474
|
-
}
|
|
4475
|
-
function spreadsheetSortRowsHaveHeader(rows) {
|
|
4476
|
-
const first = rows[0];
|
|
4477
|
-
if (!first?.length) return false;
|
|
4478
|
-
const labels = first.map(spreadsheetSortHeaderText);
|
|
4479
|
-
return labels.every(Boolean) && new Set(labels.map((label)=>label.toLocaleLowerCase())).size === labels.length;
|
|
4480
|
-
}
|
|
4481
|
-
function appendSpreadsheetSortKey(keys, key) {
|
|
4482
|
-
const identity = spreadsheetSortKeyIdentity(key);
|
|
4483
|
-
if (keys.some((candidate)=>spreadsheetSortKeyIdentity(candidate) === identity)) return false;
|
|
4484
|
-
keys.push(key);
|
|
4485
|
-
return true;
|
|
4486
|
-
}
|
|
4487
|
-
function spreadsheetSortKeyIdentity(key) {
|
|
4488
|
-
if ('cell-color' === key.sortOn || 'font-color' === key.sortOn) return `${key.index}:${spreadsheetSortAppearanceTargetValue({
|
|
4489
|
-
kind: key.sortOn,
|
|
4490
|
-
color: key.color
|
|
4491
|
-
})}`;
|
|
4492
|
-
if ('icon' === key.sortOn) return `${key.index}:${spreadsheetSortAppearanceTargetValue({
|
|
4493
|
-
kind: 'icon',
|
|
4494
|
-
icon: key.icon
|
|
4495
|
-
})}`;
|
|
4496
|
-
return `${key.index}:values`;
|
|
4497
|
-
}
|
|
4498
|
-
function spreadsheetSortActiveIndex(active, range) {
|
|
4499
|
-
return active >= range[0] && active <= range[1] ? active : range[0];
|
|
4500
|
-
}
|
|
4501
|
-
function spreadsheetSortHeaderText(cell) {
|
|
4502
|
-
if (cell?.f || 'string' != typeof cell?.v) return '';
|
|
4503
|
-
const value = cell.v;
|
|
4504
|
-
return Array.from(value.trim()).slice(0, MAX_SPREADSHEET_SORT_HEADER_LENGTH).join('');
|
|
4505
|
-
}
|
|
4506
|
-
function spreadsheetSortColumnLabel(column) {
|
|
4507
|
-
return formatSpreadsheetCellRanges([
|
|
4508
|
-
{
|
|
4509
|
-
row: [
|
|
4510
|
-
0,
|
|
4511
|
-
0
|
|
4512
|
-
],
|
|
4513
|
-
column: [
|
|
4514
|
-
column,
|
|
4515
|
-
column
|
|
4516
|
-
]
|
|
4517
|
-
}
|
|
4518
|
-
]).replace(/\d+$/, '');
|
|
4519
|
-
}
|
|
4520
|
-
function reconcileSpreadsheetFiltersAfterFortune(sheets, sourceSheets, operations = [], context = {
|
|
4521
|
-
now: new Date()
|
|
4522
|
-
}) {
|
|
4523
|
-
const coordinates = spreadsheetCellOperationCoordinates(operations);
|
|
4524
|
-
return sheets.map((sheet, index)=>{
|
|
4525
|
-
const source = spreadsheet_filter_reconciliation_sourceSheet(sheet, sourceSheets, index);
|
|
4526
|
-
if (!source || !spreadsheetSheetHasCriteria(source) || !spreadsheetFilterReconciliationIsBounded(source) || !spreadsheetFilterRangesWereEdited(source, operations, coordinates)) return sheet;
|
|
4527
|
-
const previousTableOwnedRows = spreadsheetTableFilterOwnedRows(source, context);
|
|
4528
|
-
const manuallyHiddenRows = workSpreadsheetAutoFilterManuallyHiddenRows(source);
|
|
4529
|
-
for (const row of previousTableOwnedRows)manuallyHiddenRows.delete(row);
|
|
4530
|
-
let next = sheet;
|
|
4531
|
-
if (normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select) && workSpreadsheetAutoFilterCriteriaEntries(source).length) next = workSpreadsheetSheetWithReappliedAutoFilterCriteria(sheet, source, manuallyHiddenRows, context);
|
|
4532
|
-
const hiddenRows = new Set(manuallyHiddenRows);
|
|
4533
|
-
for (const row of workSpreadsheetAutoFilterOwnedRows(next))hiddenRows.add(row);
|
|
4534
|
-
for (const row of spreadsheetTableFilterOwnedRows(next, context))hiddenRows.add(row);
|
|
4535
|
-
const rowhidden = Object.fromEntries([
|
|
4536
|
-
...hiddenRows
|
|
4537
|
-
].sort(compareSpreadsheetRowKeys).map((row)=>[
|
|
4538
|
-
row,
|
|
4539
|
-
0
|
|
4540
|
-
]));
|
|
4541
|
-
return {
|
|
4542
|
-
...next,
|
|
4543
|
-
config: {
|
|
4544
|
-
...next.config ?? {},
|
|
4545
|
-
rowhidden
|
|
4546
|
-
}
|
|
4547
|
-
};
|
|
4548
|
-
});
|
|
4549
|
-
}
|
|
4550
|
-
function reconcileSpreadsheetFiltersAfterSort(sheet, source, range, sourceIndexes, context = {
|
|
4551
|
-
now: new Date()
|
|
4552
|
-
}) {
|
|
4553
|
-
if (!spreadsheetFilterReconciliationIsBounded(source) || !spreadsheetSortSourceIndexesAreValid(range, sourceIndexes)) return null;
|
|
4554
|
-
const reconciled = reconcileSpreadsheetFiltersAfterFortune([
|
|
4555
|
-
sheet
|
|
4556
|
-
], [
|
|
4557
|
-
source
|
|
4558
|
-
], [], context)[0];
|
|
4559
|
-
if (!reconciled) return null;
|
|
4560
|
-
let filter = reconciled.filter;
|
|
4561
|
-
const autoFilter = normalizedWorkSpreadsheetAutoFilterRange(source.filter_select);
|
|
4562
|
-
if (autoFilter && autoFilter.row[0] === range.row[0] && autoFilter.row[1] === range.row[1] && autoFilter.column[0] === range.column[0] && autoFilter.column[1] === range.column[1]) filter = spreadsheetFilterWithRemappedOpaqueEntries(reconciled, source, range, sourceIndexes);
|
|
4563
|
-
const manuallyHiddenRows = workSpreadsheetAutoFilterManuallyHiddenRows(source);
|
|
4564
|
-
for (const row of spreadsheetTableFilterOwnedRows(source, context))manuallyHiddenRows.delete(row);
|
|
4565
|
-
const withFilter = {
|
|
4566
|
-
...reconciled,
|
|
4567
|
-
filter
|
|
4568
|
-
};
|
|
4569
|
-
const hiddenRows = new Set(manuallyHiddenRows);
|
|
4570
|
-
for (const row of workSpreadsheetAutoFilterOwnedRows(withFilter))hiddenRows.add(row);
|
|
4571
|
-
for (const row of spreadsheetTableFilterOwnedRows(withFilter, context))hiddenRows.add(row);
|
|
4572
|
-
const rowhidden = Object.fromEntries([
|
|
4573
|
-
...hiddenRows
|
|
4574
|
-
].sort(compareSpreadsheetRowKeys).map((row)=>[
|
|
4575
|
-
row,
|
|
4576
|
-
0
|
|
4577
|
-
]));
|
|
4578
|
-
return {
|
|
4579
|
-
...sheet,
|
|
4580
|
-
filter,
|
|
4581
|
-
config: {
|
|
4582
|
-
...sheet.config ?? {},
|
|
4583
|
-
rowhidden
|
|
4584
|
-
}
|
|
4585
|
-
};
|
|
4586
|
-
}
|
|
4587
|
-
function spreadsheetFilterReconciliationIsBounded(sheet) {
|
|
4588
|
-
let cellVisits = 0;
|
|
4589
|
-
const autoFilter = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
4590
|
-
if (autoFilter) {
|
|
4591
|
-
const filterCount = Object.keys(sheet.filter ?? {}).length;
|
|
4592
|
-
cellVisits += (autoFilter.row[1] - autoFilter.row[0]) * Math.max(filterCount, workSpreadsheetAutoFilterCriteriaEntries(sheet).length);
|
|
4593
|
-
}
|
|
4594
|
-
for (const table of sheet.tables ?? []){
|
|
4595
|
-
const range = spreadsheetTableFilterRange(table);
|
|
4596
|
-
if (range) {
|
|
4597
|
-
cellVisits += (range.row[1] - range.row[0]) * table.filters.length;
|
|
4598
|
-
if (!Number.isSafeInteger(cellVisits) || cellVisits > MAX_SPREADSHEET_SORT_CELLS) return false;
|
|
4599
|
-
}
|
|
4600
|
-
}
|
|
4601
|
-
return Number.isSafeInteger(cellVisits) && cellVisits <= MAX_SPREADSHEET_SORT_CELLS;
|
|
4602
|
-
}
|
|
4603
|
-
function spreadsheetFilterWithRemappedOpaqueEntries(reconciled, source, range, sourceIndexes) {
|
|
4604
|
-
const next = {
|
|
4605
|
-
...reconciled.filter ?? {}
|
|
4606
|
-
};
|
|
4607
|
-
const typedColumns = new Set(workSpreadsheetAutoFilterCriteriaEntries(source).map((entry)=>String(entry.column)));
|
|
4608
|
-
for (const [column, entry] of Object.entries(source.filter ?? {}))if (!typedColumns.has(column) && spreadsheet_filter_reconciliation_isRecord(entry)) next[column] = {
|
|
4609
|
-
...entry,
|
|
4610
|
-
rowhidden: spreadsheetRemappedFilterRows(entry.rowhidden, range, sourceIndexes)
|
|
4611
|
-
};
|
|
4612
|
-
return next;
|
|
4613
|
-
}
|
|
4614
|
-
function spreadsheetRemappedFilterRows(value, range, sourceIndexes) {
|
|
4615
|
-
const sourceRows = spreadsheet_filter_reconciliation_isRecord(value) ? value : {};
|
|
4616
|
-
const rowhidden = {};
|
|
4617
|
-
const dataStart = range.row[0] + 1;
|
|
4618
|
-
sourceIndexes.forEach((sourceIndex, targetIndex)=>{
|
|
4619
|
-
if (Object.hasOwn(sourceRows, String(dataStart + sourceIndex))) rowhidden[String(dataStart + targetIndex)] = 0;
|
|
4620
|
-
});
|
|
4621
|
-
return rowhidden;
|
|
4622
|
-
}
|
|
4623
|
-
function spreadsheetSortSourceIndexesAreValid(range, sourceIndexes) {
|
|
4624
|
-
const count = range.row[1] - range.row[0];
|
|
4625
|
-
return sourceIndexes.length === count && sourceIndexes.every((index)=>Number.isSafeInteger(index) && index >= 0 && index < count) && new Set(sourceIndexes).size === count;
|
|
4626
|
-
}
|
|
4627
|
-
function spreadsheetSheetHasCriteria(sheet) {
|
|
4628
|
-
return Boolean(workSpreadsheetAutoFilterCriteriaEntries(sheet).length || sheet.tables?.some((table)=>table.filters.length));
|
|
4629
|
-
}
|
|
4630
|
-
function spreadsheetFilterRangesWereEdited(sheet, operations, coordinates) {
|
|
4631
|
-
if (!operations.length) return true;
|
|
4632
|
-
if (!sheet.id) return false;
|
|
4633
|
-
const sheetCoordinates = coordinates?.get(sheet.id);
|
|
4634
|
-
if (sheetCoordinates) {
|
|
4635
|
-
const ranges = spreadsheetFilterRanges(sheet);
|
|
4636
|
-
return [
|
|
4637
|
-
...sheetCoordinates.values()
|
|
4638
|
-
].some(({ column, row })=>ranges.some((range)=>row >= range.row[0] && row <= range.row[1] && column >= range.column[0] && column <= range.column[1]));
|
|
4639
|
-
}
|
|
4640
|
-
return operations.some((operation)=>operation.id === sheet.id);
|
|
4641
|
-
}
|
|
4642
|
-
function spreadsheetFilterRanges(sheet) {
|
|
4643
|
-
const ranges = [];
|
|
4644
|
-
const autoFilter = normalizedWorkSpreadsheetAutoFilterRange(sheet.filter_select);
|
|
4645
|
-
if (autoFilter && workSpreadsheetAutoFilterCriteriaEntries(sheet).length) ranges.push(autoFilter);
|
|
4646
|
-
for (const table of sheet.tables ?? []){
|
|
4647
|
-
if (!table.filters.length) continue;
|
|
4648
|
-
const range = spreadsheetTableFilterRange(table);
|
|
4649
|
-
if (range) ranges.push(range);
|
|
4650
|
-
}
|
|
4651
|
-
return ranges;
|
|
4652
|
-
}
|
|
4653
|
-
function spreadsheetTableFilterOwnedRows(sheet, context) {
|
|
4654
|
-
const hidden = new Set();
|
|
4655
|
-
for (const table of sheet.tables ?? []){
|
|
4656
|
-
const range = spreadsheetTableFilterRange(table);
|
|
4657
|
-
if (range) for (const filter of table.filters){
|
|
4658
|
-
const column = range.column[0] + filter.column;
|
|
4659
|
-
const rowhidden = workSpreadsheetFilterHiddenRows(sheet, range, column, filter.criteria, context);
|
|
4660
|
-
for (const row of Object.keys(rowhidden ?? {}))hidden.add(row);
|
|
4661
|
-
}
|
|
4662
|
-
}
|
|
4663
|
-
return hidden;
|
|
4664
|
-
}
|
|
4665
|
-
function spreadsheetTableFilterRange(table) {
|
|
4666
|
-
if (!table.headerRow || !table.filters.length) return null;
|
|
4667
|
-
const rowStart = spreadsheet_filter_reconciliation_finiteIndex(table.range.row[0]);
|
|
4668
|
-
const rowEnd = spreadsheet_filter_reconciliation_finiteIndex(table.range.row[1]);
|
|
4669
|
-
const columnStart = spreadsheet_filter_reconciliation_finiteIndex(table.range.column[0]);
|
|
4670
|
-
const columnEnd = spreadsheet_filter_reconciliation_finiteIndex(table.range.column[1]);
|
|
4671
|
-
if (null === rowStart || null === rowEnd || null === columnStart || null === columnEnd) return null;
|
|
4672
|
-
const end = Math.max(rowStart, rowEnd) - Number(table.totalsRow);
|
|
4673
|
-
const start = Math.min(rowStart, rowEnd);
|
|
4674
|
-
if (end <= start) return null;
|
|
4675
|
-
return {
|
|
4676
|
-
row: [
|
|
4677
|
-
start,
|
|
4678
|
-
end
|
|
4679
|
-
],
|
|
4680
|
-
column: [
|
|
4681
|
-
Math.min(columnStart, columnEnd),
|
|
4682
|
-
Math.max(columnStart, columnEnd)
|
|
4683
|
-
]
|
|
4684
|
-
};
|
|
4685
|
-
}
|
|
4686
|
-
function spreadsheet_filter_reconciliation_sourceSheet(sheet, sources, index) {
|
|
4687
|
-
return sources.find((candidate)=>candidate.id === sheet.id) ?? sources[index];
|
|
4688
|
-
}
|
|
4689
|
-
function spreadsheet_filter_reconciliation_finiteIndex(value) {
|
|
4690
|
-
return 'number' == typeof value && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
4691
|
-
}
|
|
4692
|
-
function compareSpreadsheetRowKeys(left, right) {
|
|
4693
|
-
return Number(left) - Number(right);
|
|
4694
|
-
}
|
|
4695
|
-
function spreadsheet_filter_reconciliation_isRecord(value) {
|
|
4696
|
-
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
4697
|
-
}
|
|
4698
|
-
function canApplySpreadsheetTableStructureChange(sheet, change) {
|
|
4699
|
-
if (!sheet || !validSpreadsheetTableStructureChange(change)) return false;
|
|
4700
|
-
for (const table of sheet.tables ?? []){
|
|
4701
|
-
if ('delete' === change.kind && 'row' === change.axis && spreadsheetTableDeleteRemovesSemanticRow(table, change.start, change.end)) return false;
|
|
4702
|
-
if (!transformSpreadsheetTableStructure(table, change)) return false;
|
|
4703
|
-
}
|
|
4704
|
-
return true;
|
|
4705
|
-
}
|
|
4706
|
-
function reconcileSpreadsheetTablesAfterFortune(sheets, sourceSheets, operations = []) {
|
|
4707
|
-
return sheets.map((sheet, index)=>{
|
|
4708
|
-
const source = sourceSheetForReconciliation(sheet, sourceSheets, index);
|
|
4709
|
-
if (!source?.tables?.length) return sheet;
|
|
4710
|
-
const sheetOperations = operations.filter((operation)=>operation.id === source.id);
|
|
4711
|
-
let tables = source.tables;
|
|
4712
|
-
const reconcileColumns = new Set();
|
|
4713
|
-
const calculatedColumnRows = new Map();
|
|
4714
|
-
const editedTotalsColumns = new Map();
|
|
4715
|
-
if (!operations.length) for (const table of tables)reconcileColumns.add(table.id);
|
|
4716
|
-
for (const operation of sheetOperations){
|
|
4717
|
-
const structure = spreadsheetTableStructureChangeFromOperation(operation);
|
|
4718
|
-
if (structure) {
|
|
4719
|
-
transformCalculatedColumnRows(calculatedColumnRows, structure);
|
|
4720
|
-
const transformed = [];
|
|
4721
|
-
for (const table of tables){
|
|
4722
|
-
if ('row' === structure.axis && 'insert' === structure.kind) {
|
|
4723
|
-
const insertedRows = rowsInsertedIntoSpreadsheetTableBody(table, structure);
|
|
4724
|
-
if (insertedRows.length) {
|
|
4725
|
-
const rows = calculatedColumnRows.get(table.id) ?? new Set();
|
|
4726
|
-
for (const row of insertedRows)rows.add(row);
|
|
4727
|
-
calculatedColumnRows.set(table.id, rows);
|
|
4728
|
-
}
|
|
4729
|
-
}
|
|
4730
|
-
const next = transformSpreadsheetTableStructure(table, structure);
|
|
4731
|
-
if (next) {
|
|
4732
|
-
transformed.push(next);
|
|
4733
|
-
if (next !== table) reconcileColumns.add(table.id);
|
|
4734
|
-
}
|
|
4735
|
-
}
|
|
4736
|
-
tables = transformed;
|
|
4737
|
-
continue;
|
|
4738
|
-
}
|
|
4739
|
-
const coordinate = spreadsheetCellCoordinateFromOperation(operation);
|
|
4740
|
-
if (!coordinate) continue;
|
|
4741
|
-
const table = tables.find((candidate)=>spreadsheetTableContainsCell(candidate, coordinate.row, coordinate.column));
|
|
4742
|
-
if (table) {
|
|
4743
|
-
reconcileColumns.add(table.id);
|
|
4744
|
-
if (table.totalsRow && coordinate.row === table.range.row[1]) {
|
|
4745
|
-
const offset = coordinate.column - table.range.column[0];
|
|
4746
|
-
if (offset >= 0 && offset < table.columns.length) {
|
|
4747
|
-
const offsets = editedTotalsColumns.get(table.id) ?? new Set();
|
|
4748
|
-
offsets.add(offset);
|
|
4749
|
-
editedTotalsColumns.set(table.id, offsets);
|
|
4750
|
-
}
|
|
4751
|
-
}
|
|
4752
|
-
}
|
|
4753
|
-
}
|
|
4754
|
-
const updates = [];
|
|
4755
|
-
const readCell = spreadsheet_table_reconciliation_spreadsheetSheetCellReader(sheet);
|
|
4756
|
-
tables = tables.map((table)=>{
|
|
4757
|
-
if (!reconcileColumns.has(table.id)) return table;
|
|
4758
|
-
const columns = canonicalSpreadsheetTableColumns(table, readCell);
|
|
4759
|
-
const calculatedColumns = reconcileSpreadsheetTableCalculatedColumns(sheet, {
|
|
4760
|
-
...table,
|
|
4761
|
-
columns
|
|
4762
|
-
});
|
|
4763
|
-
const reconciledColumns = reconcileSpreadsheetTableTotalsColumns(sheet, {
|
|
4764
|
-
...table,
|
|
4765
|
-
columns: calculatedColumns
|
|
4766
|
-
}, editedTotalsColumns.get(table.id));
|
|
4767
|
-
if (table.headerRow) for (const [offset, column] of reconciledColumns.entries()){
|
|
4768
|
-
const cellColumn = table.range.column[0] + offset;
|
|
4769
|
-
if (spreadsheetCellText(readCell(table.range.row[0], cellColumn)) !== column.name) updates.push({
|
|
4770
|
-
column: cellColumn,
|
|
4771
|
-
name: column.name,
|
|
4772
|
-
row: table.range.row[0]
|
|
4773
|
-
});
|
|
4774
|
-
}
|
|
4775
|
-
return sameSpreadsheetTableColumns(table.columns, reconciledColumns) ? table : {
|
|
4776
|
-
...table,
|
|
4777
|
-
columns: reconciledColumns
|
|
4778
|
-
};
|
|
4779
|
-
});
|
|
4780
|
-
let withCalculatedColumns = sheet;
|
|
4781
|
-
for (const table of tables){
|
|
4782
|
-
const rows = calculatedColumnRows.get(table.id);
|
|
4783
|
-
if (rows?.size) withCalculatedColumns = fillSpreadsheetTableCalculatedColumns(withCalculatedColumns, table, [
|
|
4784
|
-
...rows
|
|
4785
|
-
]);
|
|
4786
|
-
}
|
|
4787
|
-
const withHeaders = stampSpreadsheetTableHeaderCells(withCalculatedColumns, updates);
|
|
4788
|
-
let withTotals = withHeaders;
|
|
4789
|
-
for (const table of tables){
|
|
4790
|
-
const previous = source.tables.find((candidate)=>candidate.id === table.id);
|
|
4791
|
-
withTotals = synchronizeSpreadsheetTableTotalsRow(withTotals, previous, table);
|
|
4792
|
-
}
|
|
4793
|
-
return {
|
|
4794
|
-
...withTotals,
|
|
4795
|
-
tables: tables.length ? tables : void 0
|
|
4796
|
-
};
|
|
4797
|
-
});
|
|
4798
|
-
}
|
|
4799
|
-
function transformSpreadsheetTableStructure(table, change) {
|
|
4800
|
-
if (!validSpreadsheetTableStructureChange(change)) return null;
|
|
4801
|
-
const axis = 'row' === change.axis ? table.range.row : table.range.column;
|
|
4802
|
-
const transformed = 'insert' === change.kind ? transformSpreadsheetTableAxisForInsertion(axis, change) : transformSpreadsheetTableAxisForDeletion(axis, change);
|
|
4803
|
-
if (!transformed) return null;
|
|
4804
|
-
const range = {
|
|
4805
|
-
row: 'row' === change.axis ? transformed.axis : [
|
|
4806
|
-
...table.range.row
|
|
4807
|
-
],
|
|
4808
|
-
column: 'column' === change.axis ? transformed.axis : [
|
|
4809
|
-
...table.range.column
|
|
4810
|
-
]
|
|
4811
|
-
};
|
|
4812
|
-
const height = range.row[1] - range.row[0] + 1;
|
|
4813
|
-
if (height <= Number(table.headerRow) + Number(table.totalsRow)) return null;
|
|
4814
|
-
if ('column' !== change.axis || !transformed.inside) {
|
|
4815
|
-
if (range.row[0] === table.range.row[0] && range.row[1] === table.range.row[1] && range.column[0] === table.range.column[0] && range.column[1] === table.range.column[1]) return table;
|
|
4816
|
-
return {
|
|
4817
|
-
...table,
|
|
4818
|
-
range
|
|
4819
|
-
};
|
|
4820
|
-
}
|
|
4821
|
-
if ('insert' === change.kind) {
|
|
4822
|
-
const columns = [
|
|
4823
|
-
...table.columns
|
|
4824
|
-
];
|
|
4825
|
-
columns.splice(transformed.offset, 0, ...Array.from({
|
|
4826
|
-
length: change.count
|
|
4827
|
-
}, ()=>({
|
|
4828
|
-
name: ''
|
|
4829
|
-
})));
|
|
4830
|
-
return {
|
|
4831
|
-
...table,
|
|
4832
|
-
range,
|
|
4833
|
-
columns,
|
|
4834
|
-
filters: table.filters.map((filter)=>filter.column >= transformed.offset ? {
|
|
4835
|
-
...filter,
|
|
4836
|
-
column: filter.column + change.count
|
|
4837
|
-
} : filter)
|
|
4838
|
-
};
|
|
4839
|
-
}
|
|
4840
|
-
if (!('removed' in transformed)) return null;
|
|
4841
|
-
const removed = transformed.removed;
|
|
4842
|
-
if ('number' != typeof removed) return null;
|
|
4843
|
-
const columns = [
|
|
4844
|
-
...table.columns
|
|
4845
|
-
];
|
|
4846
|
-
columns.splice(transformed.offset, removed);
|
|
4847
|
-
if (!columns.length) return null;
|
|
4848
|
-
return {
|
|
4849
|
-
...table,
|
|
4850
|
-
range,
|
|
4851
|
-
columns,
|
|
4852
|
-
filters: filtersAfterSpreadsheetTableColumnDeletion(table.filters, transformed.offset, removed)
|
|
4853
|
-
};
|
|
4854
|
-
}
|
|
4855
|
-
function spreadsheetTableContainsCell(table, row, column) {
|
|
4856
|
-
return row >= table.range.row[0] && row <= table.range.row[1] && column >= table.range.column[0] && column <= table.range.column[1];
|
|
4857
|
-
}
|
|
4858
|
-
function rowsInsertedIntoSpreadsheetTableBody(table, change) {
|
|
4859
|
-
if ('row' !== change.axis) return [];
|
|
4860
|
-
const transformed = transformSpreadsheetTableAxisForInsertion(table.range.row, change);
|
|
4861
|
-
if (!transformed.inside) return [];
|
|
4862
|
-
const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
|
|
4863
|
-
const bodyStart = table.range.row[0] + Number(table.headerRow);
|
|
4864
|
-
if (insertion < bodyStart) return [];
|
|
4865
|
-
return Array.from({
|
|
4866
|
-
length: change.count
|
|
4867
|
-
}, (_, offset)=>insertion + offset);
|
|
4868
|
-
}
|
|
4869
|
-
function transformCalculatedColumnRows(rowsByTable, change) {
|
|
4870
|
-
if ('row' !== change.axis || !rowsByTable.size) return;
|
|
4871
|
-
if ('insert' === change.kind) {
|
|
4872
|
-
const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
|
|
4873
|
-
for (const [tableId, rows] of rowsByTable){
|
|
4874
|
-
const next = new Set();
|
|
4875
|
-
for (const row of rows)next.add(row >= insertion ? row + change.count : row);
|
|
4876
|
-
rowsByTable.set(tableId, next);
|
|
4877
|
-
}
|
|
4878
|
-
return;
|
|
4879
|
-
}
|
|
4880
|
-
const count = change.end - change.start + 1;
|
|
4881
|
-
for (const [tableId, rows] of rowsByTable){
|
|
4882
|
-
const next = new Set();
|
|
4883
|
-
for (const row of rows)if (row < change.start) next.add(row);
|
|
4884
|
-
else if (row > change.end) next.add(row - count);
|
|
4885
|
-
rowsByTable.set(tableId, next);
|
|
4886
|
-
}
|
|
4887
|
-
}
|
|
4888
|
-
function transformSpreadsheetTableAxisForInsertion(axis, change) {
|
|
4889
|
-
const start = axis[0] ?? 0;
|
|
4890
|
-
const end = axis[1] ?? start;
|
|
4891
|
-
const insertion = change.index + ('rightbottom' === change.direction ? 1 : 0);
|
|
4892
|
-
if (insertion <= start) return {
|
|
4893
|
-
axis: [
|
|
4894
|
-
start + change.count,
|
|
4895
|
-
end + change.count
|
|
4896
|
-
],
|
|
4897
|
-
inside: false,
|
|
4898
|
-
offset: 0
|
|
4899
|
-
};
|
|
4900
|
-
if (insertion <= end) return {
|
|
4901
|
-
axis: [
|
|
4902
|
-
start,
|
|
4903
|
-
end + change.count
|
|
4904
|
-
],
|
|
4905
|
-
inside: true,
|
|
4906
|
-
offset: insertion - start
|
|
4907
|
-
};
|
|
4908
|
-
return {
|
|
4909
|
-
axis: [
|
|
4910
|
-
start,
|
|
4911
|
-
end
|
|
4912
|
-
],
|
|
4913
|
-
inside: false,
|
|
4914
|
-
offset: 0
|
|
4915
|
-
};
|
|
4916
|
-
}
|
|
4917
|
-
function transformSpreadsheetTableAxisForDeletion(axis, change) {
|
|
4918
|
-
const start = axis[0] ?? 0;
|
|
4919
|
-
const end = axis[1] ?? start;
|
|
4920
|
-
const count = change.end - change.start + 1;
|
|
4921
|
-
if (change.end < start) return {
|
|
4922
|
-
axis: [
|
|
4923
|
-
start - count,
|
|
4924
|
-
end - count
|
|
4925
|
-
],
|
|
4926
|
-
inside: false,
|
|
4927
|
-
offset: 0,
|
|
4928
|
-
removed: 0
|
|
4929
|
-
};
|
|
4930
|
-
if (change.start > end) return {
|
|
4931
|
-
axis: [
|
|
4932
|
-
start,
|
|
4933
|
-
end
|
|
4934
|
-
],
|
|
4935
|
-
inside: false,
|
|
4936
|
-
offset: 0,
|
|
4937
|
-
removed: 0
|
|
4938
|
-
};
|
|
4939
|
-
const overlapStart = Math.max(start, change.start);
|
|
4940
|
-
const overlapEnd = Math.min(end, change.end);
|
|
4941
|
-
const removed = overlapEnd - overlapStart + 1;
|
|
4942
|
-
const remaining = end - start + 1 - removed;
|
|
4943
|
-
if (remaining <= 0) return null;
|
|
4944
|
-
const deletedBeforeStart = Math.max(0, Math.min(change.end, start - 1) - change.start + 1);
|
|
4945
|
-
const nextStart = start - deletedBeforeStart;
|
|
4946
|
-
return {
|
|
4947
|
-
axis: [
|
|
4948
|
-
nextStart,
|
|
4949
|
-
nextStart + remaining - 1
|
|
4950
|
-
],
|
|
4951
|
-
inside: true,
|
|
4952
|
-
offset: overlapStart - start,
|
|
4953
|
-
removed
|
|
4954
|
-
};
|
|
4955
|
-
}
|
|
4956
|
-
function filtersAfterSpreadsheetTableColumnDeletion(filters, offset, removed) {
|
|
4957
|
-
const end = offset + removed - 1;
|
|
4958
|
-
return filters.flatMap((filter)=>{
|
|
4959
|
-
if (filter.column >= offset && filter.column <= end) return [];
|
|
4960
|
-
return [
|
|
4961
|
-
filter.column > end ? {
|
|
4962
|
-
...filter,
|
|
4963
|
-
column: filter.column - removed
|
|
4964
|
-
} : filter
|
|
4965
|
-
];
|
|
4966
|
-
});
|
|
4967
|
-
}
|
|
4968
|
-
function canonicalSpreadsheetTableColumns(table, readCell) {
|
|
4969
|
-
const width = table.range.column[1] - table.range.column[0] + 1;
|
|
4970
|
-
const observed = new Set();
|
|
4971
|
-
return Array.from({
|
|
4972
|
-
length: width
|
|
4973
|
-
}, (_, offset)=>{
|
|
4974
|
-
const raw = table.headerRow ? spreadsheetCellText(readCell(table.range.row[0], table.range.column[0] + offset)).trim() : table.columns[offset]?.name.trim() ?? '';
|
|
4975
|
-
const base = validSpreadsheetTableColumnName(raw) ? raw : `Column${offset + 1}`;
|
|
4976
|
-
let candidate = base;
|
|
4977
|
-
let suffix = 2;
|
|
4978
|
-
while(observed.has(candidate.toLocaleLowerCase())){
|
|
4979
|
-
const suffixText = String(suffix);
|
|
4980
|
-
candidate = `${truncateSpreadsheetTableColumnName(base, 255 - suffixText.length)}${suffixText}`;
|
|
4981
|
-
suffix += 1;
|
|
4982
|
-
}
|
|
4983
|
-
observed.add(candidate.toLocaleLowerCase());
|
|
4984
|
-
return {
|
|
4985
|
-
...table.columns[offset] ?? {},
|
|
4986
|
-
name: candidate
|
|
4987
|
-
};
|
|
4988
|
-
});
|
|
4989
|
-
}
|
|
4990
|
-
function spreadsheetTableDeleteRemovesSemanticRow(table, start, end) {
|
|
4991
|
-
return Boolean(table.headerRow && start <= table.range.row[0] && end >= table.range.row[0] || table.totalsRow && start <= table.range.row[1] && end >= table.range.row[1]);
|
|
4992
|
-
}
|
|
4993
|
-
function spreadsheetTableStructureChangeFromOperation(operation) {
|
|
4994
|
-
if (!spreadsheet_table_reconciliation_isRecord(operation.value)) return null;
|
|
4995
|
-
if ('insertRowCol' === operation.op) {
|
|
4996
|
-
const { count, direction, index, type } = operation.value;
|
|
4997
|
-
const change = {
|
|
4998
|
-
axis: 'column' === type ? 'column' : 'row',
|
|
4999
|
-
count: Number(count),
|
|
5000
|
-
direction: 'rightbottom' === direction ? 'rightbottom' : 'lefttop',
|
|
5001
|
-
index: Number(index),
|
|
5002
|
-
kind: 'insert'
|
|
5003
|
-
};
|
|
5004
|
-
return ('column' === type || 'row' === type) && ('lefttop' === direction || 'rightbottom' === direction) && validSpreadsheetTableStructureChange(change) ? change : null;
|
|
5005
|
-
}
|
|
5006
|
-
if ('deleteRowCol' === operation.op) {
|
|
5007
|
-
const { end, start, type } = operation.value;
|
|
5008
|
-
const change = {
|
|
5009
|
-
axis: 'column' === type ? 'column' : 'row',
|
|
5010
|
-
end: Number(end),
|
|
5011
|
-
kind: 'delete',
|
|
5012
|
-
start: Number(start)
|
|
5013
|
-
};
|
|
5014
|
-
return ('column' === type || 'row' === type) && validSpreadsheetTableStructureChange(change) ? change : null;
|
|
5015
|
-
}
|
|
5016
|
-
return null;
|
|
5017
|
-
}
|
|
5018
|
-
function spreadsheetCellCoordinateFromOperation(operation) {
|
|
5019
|
-
if ('data' !== operation.path[0] || operation.path.length < 3) return null;
|
|
5020
|
-
const row = operation.path[1];
|
|
5021
|
-
const column = operation.path[2];
|
|
5022
|
-
return Number.isSafeInteger(row) && Number(row) >= 0 && Number.isSafeInteger(column) && Number(column) >= 0 ? {
|
|
5023
|
-
column: Number(column),
|
|
5024
|
-
row: Number(row)
|
|
5025
|
-
} : null;
|
|
5026
|
-
}
|
|
5027
|
-
function validSpreadsheetTableStructureChange(change) {
|
|
5028
|
-
if ('insert' === change.kind) return Number.isSafeInteger(change.index) && change.index >= 0 && Number.isSafeInteger(change.count) && change.count > 0;
|
|
5029
|
-
return Number.isSafeInteger(change.start) && change.start >= 0 && Number.isSafeInteger(change.end) && change.end >= change.start;
|
|
5030
|
-
}
|
|
5031
|
-
function sourceSheetForReconciliation(sheet, sourceSheets, index) {
|
|
5032
|
-
return (sheet.id ? sourceSheets.find((candidate)=>candidate.id === sheet.id) : void 0) ?? sourceSheets[index];
|
|
5033
|
-
}
|
|
5034
|
-
function spreadsheet_table_reconciliation_spreadsheetSheetCellReader(sheet) {
|
|
5035
|
-
if (void 0 !== sheet.data) return (row, column)=>sheet.data?.[row]?.[column] ?? null;
|
|
5036
|
-
const cells = new Map((sheet.celldata ?? []).map((entry)=>[
|
|
5037
|
-
`${entry.r}:${entry.c}`,
|
|
5038
|
-
entry.v
|
|
5039
|
-
]));
|
|
5040
|
-
return (row, column)=>cells.get(`${row}:${column}`) ?? null;
|
|
5041
|
-
}
|
|
5042
|
-
function stampSpreadsheetTableHeaderCells(sheet, updates) {
|
|
5043
|
-
if (!updates.length) return sheet;
|
|
5044
|
-
if (void 0 !== sheet.data) {
|
|
5045
|
-
const data = [];
|
|
5046
|
-
data.length = sheet.data.length;
|
|
5047
|
-
for (const [row, value] of sparseArrayEntries(sheet.data))data[row] = value;
|
|
5048
|
-
const mutableRows = new Map();
|
|
5049
|
-
for (const update of updates){
|
|
5050
|
-
let row = mutableRows.get(update.row);
|
|
5051
|
-
if (!row) {
|
|
5052
|
-
const source = data[update.row];
|
|
5053
|
-
row = [];
|
|
5054
|
-
row.length = source?.length ?? 0;
|
|
5055
|
-
for (const column of sparseArrayIndexes(source))row[column] = source?.[column];
|
|
5056
|
-
mutableRows.set(update.row, row);
|
|
5057
|
-
data[update.row] = row;
|
|
5058
|
-
}
|
|
5059
|
-
row[update.column] = stampSpreadsheetTableHeaderCell(row[update.column], update.name);
|
|
5060
|
-
}
|
|
5061
|
-
return {
|
|
5062
|
-
...sheet,
|
|
5063
|
-
data
|
|
5064
|
-
};
|
|
5065
|
-
}
|
|
5066
|
-
const celldata = [
|
|
5067
|
-
...sheet.celldata ?? []
|
|
5068
|
-
];
|
|
5069
|
-
const indexes = new Map(celldata.map((entry, index)=>[
|
|
5070
|
-
`${entry.r}:${entry.c}`,
|
|
5071
|
-
index
|
|
5072
|
-
]));
|
|
5073
|
-
for (const update of updates){
|
|
5074
|
-
const key = `${update.row}:${update.column}`;
|
|
5075
|
-
const index = indexes.get(key);
|
|
5076
|
-
const source = void 0 === index ? void 0 : celldata[index];
|
|
5077
|
-
const entry = {
|
|
5078
|
-
r: update.row,
|
|
5079
|
-
c: update.column,
|
|
5080
|
-
v: stampSpreadsheetTableHeaderCell(source?.v, update.name)
|
|
5081
|
-
};
|
|
5082
|
-
if (void 0 === index) {
|
|
5083
|
-
indexes.set(key, celldata.length);
|
|
5084
|
-
celldata.push(entry);
|
|
5085
|
-
} else celldata[index] = entry;
|
|
5086
|
-
}
|
|
5087
|
-
return {
|
|
5088
|
-
...sheet,
|
|
5089
|
-
celldata
|
|
5090
|
-
};
|
|
5091
|
-
}
|
|
5092
|
-
function stampSpreadsheetTableHeaderCell(cell, name) {
|
|
5093
|
-
return {
|
|
5094
|
-
...cell ?? {},
|
|
5095
|
-
m: name,
|
|
5096
|
-
v: name
|
|
5097
|
-
};
|
|
5098
|
-
}
|
|
5099
|
-
function spreadsheetCellText(cell) {
|
|
5100
|
-
const value = cell?.m ?? cell?.v;
|
|
5101
|
-
return null == value ? '' : String(value);
|
|
5102
|
-
}
|
|
5103
|
-
function validSpreadsheetTableColumnName(name) {
|
|
5104
|
-
const characters = Array.from(name);
|
|
5105
|
-
return name.trim() === name && characters.length >= 1 && characters.length <= 255 && !characters.some((character)=>/\p{Cc}/u.test(character) || '\uFFFE' === character || '\uFFFF' === character);
|
|
5106
|
-
}
|
|
5107
|
-
function truncateSpreadsheetTableColumnName(value, maximum) {
|
|
5108
|
-
return Array.from(value).slice(0, maximum).join('');
|
|
5109
|
-
}
|
|
5110
|
-
function sameSpreadsheetTableColumns(left, right) {
|
|
5111
|
-
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);
|
|
5112
|
-
}
|
|
5113
|
-
function spreadsheet_table_reconciliation_isRecord(value) {
|
|
5114
|
-
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
5115
|
-
}
|
|
5116
|
-
function spreadsheetSelectionReference(selection) {
|
|
5117
|
-
const rowStart = Math.min(selection.row[0] ?? 0, selection.row[1] ?? selection.row[0] ?? 0);
|
|
5118
|
-
const rowEnd = Math.max(selection.row[0] ?? 0, selection.row[1] ?? selection.row[0] ?? 0);
|
|
5119
|
-
const columnStart = Math.min(selection.column[0] ?? 0, selection.column[1] ?? selection.column[0] ?? 0);
|
|
5120
|
-
const columnEnd = Math.max(selection.column[0] ?? 0, selection.column[1] ?? selection.column[0] ?? 0);
|
|
5121
|
-
const start = `${spreadsheetColumnLabel(columnStart)}${rowStart + 1}`;
|
|
5122
|
-
const end = `${spreadsheetColumnLabel(columnEnd)}${rowEnd + 1}`;
|
|
5123
|
-
return start === end ? start : `${start}:${end}`;
|
|
5124
|
-
}
|
|
5125
|
-
function spreadsheetSingleRange(selection) {
|
|
5126
|
-
return {
|
|
5127
|
-
row: finiteSpreadsheetSelectionAxis(selection.row),
|
|
5128
|
-
column: finiteSpreadsheetSelectionAxis(selection.column)
|
|
5129
|
-
};
|
|
5130
|
-
}
|
|
5131
|
-
function spreadsheet_editor_support_spreadsheetCellAt(sheet, row, column) {
|
|
5132
|
-
if (!sheet) return null;
|
|
5133
|
-
const safeRow = finiteSpreadsheetIndex(row, 0);
|
|
5134
|
-
const safeColumn = finiteSpreadsheetIndex(column, 0);
|
|
5135
|
-
return sheet.data?.[safeRow]?.[safeColumn] ?? sheet.celldata?.find((entry)=>entry.r === safeRow && entry.c === safeColumn)?.v ?? null;
|
|
5136
|
-
}
|
|
5137
|
-
function spreadsheetSelectionSummary(sheet, selection) {
|
|
5138
|
-
const summary = {
|
|
5139
|
-
average: null,
|
|
5140
|
-
nonEmptyCount: 0,
|
|
5141
|
-
numericCount: 0,
|
|
5142
|
-
sum: null
|
|
5143
|
-
};
|
|
5144
|
-
if (!sheet) return summary;
|
|
5145
|
-
const range = spreadsheetSingleRange(selection);
|
|
5146
|
-
let sum = 0;
|
|
5147
|
-
const collect = (cell)=>{
|
|
5148
|
-
if (!cell || !spreadsheet_editor_support_spreadsheetCellHasContent(cell)) return;
|
|
5149
|
-
summary.nonEmptyCount += 1;
|
|
5150
|
-
if ('number' != typeof cell.v || !Number.isFinite(cell.v)) return;
|
|
5151
|
-
summary.numericCount += 1;
|
|
5152
|
-
sum += cell.v;
|
|
5153
|
-
};
|
|
5154
|
-
if (sheet.data?.length) {
|
|
5155
|
-
const rowStart = range.row[0] ?? 0;
|
|
5156
|
-
const rowEnd = Math.min(range.row[1] ?? rowStart, sheet.data.length - 1);
|
|
5157
|
-
const columnStart = range.column[0] ?? 0;
|
|
5158
|
-
const columnEnd = range.column[1] ?? columnStart;
|
|
5159
|
-
for (const rowIndex of sparseArrayIndexes(sheet.data)){
|
|
5160
|
-
if (rowIndex < rowStart || rowIndex > rowEnd) continue;
|
|
5161
|
-
const row = sheet.data[rowIndex];
|
|
5162
|
-
if (row) {
|
|
5163
|
-
for (const columnIndex of sparseArrayIndexes(row))if (!(columnIndex < columnStart) && !(columnIndex > columnEnd)) collect(row[columnIndex]);
|
|
5164
|
-
}
|
|
5165
|
-
}
|
|
5166
|
-
} else {
|
|
5167
|
-
const rowStart = range.row[0] ?? 0;
|
|
5168
|
-
const rowEnd = range.row[1] ?? rowStart;
|
|
5169
|
-
const columnStart = range.column[0] ?? 0;
|
|
5170
|
-
const columnEnd = range.column[1] ?? columnStart;
|
|
5171
|
-
for (const entry of sheet.celldata ?? [])if (!(entry.r < rowStart) && !(entry.r > rowEnd) && !(entry.c < columnStart) && !(entry.c > columnEnd)) collect(entry.v);
|
|
5172
|
-
}
|
|
5173
|
-
if (summary.numericCount > 0) {
|
|
5174
|
-
summary.sum = sum;
|
|
5175
|
-
summary.average = sum / summary.numericCount;
|
|
5176
|
-
}
|
|
5177
|
-
return summary;
|
|
5178
|
-
}
|
|
5179
|
-
function spreadsheetFontSizeOptions(current) {
|
|
5180
|
-
const values = [
|
|
5181
|
-
...spreadsheetFontSizes
|
|
5182
|
-
];
|
|
5183
|
-
if (current && !values.includes(current)) values.push(current);
|
|
5184
|
-
return values.sort((left, right)=>left - right).map((value)=>({
|
|
5185
|
-
value: String(value),
|
|
5186
|
-
label: String(value)
|
|
5187
|
-
}));
|
|
5188
|
-
}
|
|
5189
|
-
function spreadsheetFontFamilyOptions(current) {
|
|
5190
|
-
const options = officeFontFamilies.map(({ cssFamily, group, label, name })=>({
|
|
5191
|
-
value: name,
|
|
5192
|
-
group,
|
|
5193
|
-
label,
|
|
5194
|
-
previewStyle: {
|
|
5195
|
-
fontFamily: cssFamily
|
|
5196
|
-
},
|
|
5197
|
-
searchText: `${name} ${label}`
|
|
5198
|
-
}));
|
|
5199
|
-
const normalizedCurrent = current?.trim();
|
|
5200
|
-
if (normalizedCurrent && !options.some(({ value })=>value === normalizedCurrent)) options.push({
|
|
5201
|
-
value: normalizedCurrent,
|
|
5202
|
-
group: '文档字体',
|
|
5203
|
-
label: normalizedCurrent,
|
|
5204
|
-
previewStyle: {
|
|
5205
|
-
fontFamily: normalizedCurrent
|
|
5206
|
-
},
|
|
5207
|
-
searchText: normalizedCurrent
|
|
5208
|
-
});
|
|
5209
|
-
return options;
|
|
5210
|
-
}
|
|
5211
|
-
function spreadsheetSheetsWithFiniteSelections(sheets) {
|
|
5212
|
-
return sheets.map((sheet)=>({
|
|
5213
|
-
...sheet,
|
|
5214
|
-
luckysheet_select_save: (sheet.luckysheet_select_save?.length ? sheet.luckysheet_select_save : [
|
|
5215
|
-
void 0
|
|
5216
|
-
]).map(finiteSpreadsheetSelection)
|
|
5217
|
-
}));
|
|
5218
|
-
}
|
|
5219
|
-
function spreadsheetSheetsForFortune(sheets) {
|
|
5220
|
-
const startedAt = spreadsheetProjectionNow();
|
|
5221
|
-
let populatedCellCount = 0;
|
|
5222
|
-
const projectedSheets = sheets.map((sheet)=>{
|
|
5223
|
-
const { celldata: sourceCellData, data, ...sourceMetadata } = sheet;
|
|
5224
|
-
const projected = structuredClone(sourceMetadata);
|
|
5225
|
-
const profile = spreadsheetMatrixProfile(data);
|
|
5226
|
-
if (data && profile?.fortuneReady) {
|
|
5227
|
-
populatedCellCount += profile.populatedCellCount;
|
|
5228
|
-
return {
|
|
5229
|
-
...projected,
|
|
5230
|
-
data
|
|
5231
|
-
};
|
|
5232
|
-
}
|
|
5233
|
-
const fortuneProjection = spreadsheetMatrixForFortune({
|
|
5234
|
-
...projected,
|
|
5235
|
-
...void 0 === data ? {} : {
|
|
5236
|
-
data
|
|
5237
|
-
},
|
|
5238
|
-
...void 0 === sourceCellData ? {} : {
|
|
5239
|
-
celldata: sourceCellData
|
|
5240
|
-
}
|
|
5241
|
-
}, projected.config?.merge);
|
|
5242
|
-
populatedCellCount += fortuneProjection.populatedCellCount;
|
|
5243
|
-
return {
|
|
5244
|
-
...projected,
|
|
5245
|
-
data: fortuneProjection.data
|
|
5246
|
-
};
|
|
5247
|
-
});
|
|
5248
|
-
recordSpreadsheetProjectionMeasure(startedAt, spreadsheetProjectionNow(), {
|
|
5249
|
-
populatedCellCount,
|
|
5250
|
-
sheetCount: sheets.length
|
|
5251
|
-
});
|
|
5252
|
-
return projectedSheets;
|
|
5253
|
-
}
|
|
5254
|
-
function spreadsheetMatrixForFortune(sheet, merges) {
|
|
5255
|
-
const declaredRows = spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.row);
|
|
5256
|
-
const declaredColumns = spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.column);
|
|
5257
|
-
const hasDeclaredGrid = declaredRows > 0 && declaredColumns > 0;
|
|
5258
|
-
let rowCount = hasDeclaredGrid ? declaredRows : 60;
|
|
5259
|
-
let columnCount = hasDeclaredGrid ? declaredColumns : 26;
|
|
5260
|
-
let populatedCellCount = 0;
|
|
5261
|
-
const shownCommentCells = [];
|
|
5262
|
-
const projected = [];
|
|
5263
|
-
const setSourceCell = (rowIndex, columnIndex, cell)=>{
|
|
5264
|
-
rowCount = Math.max(rowCount, rowIndex + 1);
|
|
5265
|
-
columnCount = Math.max(columnCount, columnIndex + 1);
|
|
5266
|
-
const row = projected[rowIndex] ?? [];
|
|
5267
|
-
projected[rowIndex] = row;
|
|
5268
|
-
row[columnIndex] = cloneSpreadsheetCellForFortune(cell);
|
|
5269
|
-
if (cell.ps?.isShow) shownCommentCells.push({
|
|
5270
|
-
c: columnIndex,
|
|
5271
|
-
r: rowIndex
|
|
5272
|
-
});
|
|
5273
|
-
populatedCellCount += 1;
|
|
5274
|
-
};
|
|
5275
|
-
if (void 0 !== sheet.data) {
|
|
5276
|
-
rowCount = Math.max(rowCount, sheet.data.length);
|
|
5277
|
-
for (const rowIndex of sparseArrayIndexes(sheet.data)){
|
|
5278
|
-
const sourceRow = sheet.data[rowIndex];
|
|
5279
|
-
if (sourceRow) {
|
|
5280
|
-
columnCount = Math.max(columnCount, sourceRow.length);
|
|
5281
|
-
for (const columnIndex of sparseArrayIndexes(sourceRow)){
|
|
5282
|
-
const cell = sourceRow[columnIndex];
|
|
5283
|
-
if (null != cell) setSourceCell(rowIndex, columnIndex, cell);
|
|
5284
|
-
}
|
|
5285
|
-
}
|
|
5286
|
-
}
|
|
5287
|
-
} else for (const entry of sheet.celldata ?? [])if (null != entry.v) setSourceCell(entry.r, entry.c, entry.v);
|
|
5288
|
-
for (const merge of Object.values(merges ?? {})){
|
|
5289
|
-
rowCount = Math.max(rowCount, merge.r + merge.rs);
|
|
5290
|
-
columnCount = Math.max(columnCount, merge.c + merge.cs);
|
|
5291
|
-
for(let rowIndex = merge.r; rowIndex < merge.r + merge.rs; rowIndex += 1){
|
|
5292
|
-
const row = projected[rowIndex] ?? [];
|
|
5293
|
-
projected[rowIndex] = row;
|
|
5294
|
-
for(let columnIndex = merge.c; columnIndex < merge.c + merge.cs; columnIndex += 1)row[columnIndex] = Object.freeze({
|
|
5295
|
-
...row[columnIndex] ?? {},
|
|
5296
|
-
mc: rowIndex === merge.r && columnIndex === merge.c ? {
|
|
5297
|
-
...merge
|
|
5298
|
-
} : {
|
|
5299
|
-
r: merge.r,
|
|
5300
|
-
c: merge.c
|
|
5301
|
-
}
|
|
5302
|
-
});
|
|
5303
|
-
}
|
|
5304
|
-
}
|
|
5305
|
-
projected.length = rowCount;
|
|
5306
|
-
projected[0] ??= [];
|
|
5307
|
-
for (const rowIndex of sparseArrayIndexes(projected)){
|
|
5308
|
-
const row = projected[rowIndex];
|
|
5309
|
-
if (row) {
|
|
5310
|
-
row.length = Math.max(row.length, columnCount);
|
|
5311
|
-
Object.freeze(row);
|
|
5312
|
-
}
|
|
5313
|
-
}
|
|
5314
|
-
attachSpreadsheetShownCommentCells(projected, Object.freeze(shownCommentCells.map((cell)=>Object.freeze(cell))));
|
|
5315
|
-
Object.freeze(projected);
|
|
5316
|
-
return {
|
|
5317
|
-
data: projected,
|
|
5318
|
-
populatedCellCount
|
|
5319
|
-
};
|
|
5320
|
-
}
|
|
5321
|
-
function cloneSpreadsheetCellForFortune(cell) {
|
|
5322
|
-
const clone = {
|
|
5323
|
-
...cell
|
|
5324
|
-
};
|
|
5325
|
-
if (cell.mc) clone.mc = {
|
|
5326
|
-
...cell.mc
|
|
5327
|
-
};
|
|
5328
|
-
if (cell.ct) clone.ct = {
|
|
5329
|
-
...cell.ct,
|
|
5330
|
-
...void 0 === cell.ct.s ? {} : {
|
|
5331
|
-
s: structuredClone(cell.ct.s)
|
|
5332
|
-
}
|
|
5333
|
-
};
|
|
5334
|
-
if (cell.ps) clone.ps = {
|
|
5335
|
-
...cell.ps
|
|
5336
|
-
};
|
|
5337
|
-
if (cell.hl) clone.hl = {
|
|
5338
|
-
...cell.hl
|
|
5339
|
-
};
|
|
5340
|
-
if (cell.spl && 'object' == typeof cell.spl) clone.spl = structuredClone(cell.spl);
|
|
5341
|
-
Object.freeze(clone);
|
|
5342
|
-
return clone;
|
|
5343
|
-
}
|
|
5344
|
-
function spreadsheetProjectionNow() {
|
|
5345
|
-
return globalThis.performance?.now?.() ?? Date.now();
|
|
5346
|
-
}
|
|
5347
|
-
function recordSpreadsheetProjectionMeasure(start, end, detail) {
|
|
5348
|
-
try {
|
|
5349
|
-
globalThis.performance?.measure('a3s-office.spreadsheet.fortune-projection', {
|
|
5350
|
-
detail,
|
|
5351
|
-
end,
|
|
5352
|
-
start
|
|
5353
|
-
});
|
|
5354
|
-
} catch {}
|
|
5355
|
-
}
|
|
5356
|
-
function spreadsheetSheetsFromFortune(sheets, sourceSheets, operations = [], dateSystem = '1900') {
|
|
5357
|
-
const startedAt = spreadsheetProjectionNow();
|
|
5358
|
-
const filterContext = {
|
|
5359
|
-
dateSystem,
|
|
5360
|
-
now: new Date()
|
|
5361
|
-
};
|
|
5362
|
-
const incremental = projectSpreadsheetSheetsFromFortuneOperations(sheets, sourceSheets, operations);
|
|
5363
|
-
if (incremental) {
|
|
5364
|
-
recordSpreadsheetControlledProjectionMeasure(startedAt, spreadsheetProjectionNow(), {
|
|
5365
|
-
affectedCellCount: incremental.affectedCellCount,
|
|
5366
|
-
incremental: true,
|
|
5367
|
-
operationCount: operations.length
|
|
5368
|
-
});
|
|
5369
|
-
return reconcileSpreadsheetFiltersAfterFortune(reconcileSpreadsheetTablesAfterFortune(incremental.sheets, sourceSheets, operations), sourceSheets, operations, filterContext);
|
|
5370
|
-
}
|
|
5371
|
-
const operationCoordinatesBySheet = spreadsheetCellOperationCoordinates(operations);
|
|
5372
|
-
const projected = sheets.map((sheet, index)=>{
|
|
5373
|
-
const source = (sheet.id ? sourceSheets.find((candidate)=>candidate.id === sheet.id) : void 0) ?? sourceSheets[index];
|
|
5374
|
-
const operationCoordinates = sheet.id ? operationCoordinatesBySheet?.get(sheet.id) : void 0;
|
|
5375
|
-
const cells = spreadsheetPopulatedCellData(sheet).map((entry)=>{
|
|
5376
|
-
if (!source || !entry.v) return entry;
|
|
5377
|
-
const previous = spreadsheet_editor_support_spreadsheetCellAt(source, entry.r, entry.c);
|
|
5378
|
-
const exactCellOperation = operationCoordinates?.has(spreadsheetCellOperationKey(entry.r, entry.c));
|
|
5379
|
-
const textStableNonStructuralCallback = null !== operationCoordinatesBySheet && sameSpreadsheetRichTextCellText(previous, entry.v);
|
|
5380
|
-
if (!exactCellOperation && !textStableNonStructuralCallback) return entry;
|
|
5381
|
-
const reconciled = exactCellOperation ? reconcileSpreadsheetRichTextCellEdit(previous, entry.v, takeSpreadsheetRichTextPaste(source, entry.r, entry.c, entry.v)) : restoreSpreadsheetRichTextCellRuns(previous, entry.v);
|
|
5382
|
-
return reconciled === entry.v ? entry : {
|
|
5383
|
-
...entry,
|
|
5384
|
-
v: reconciled
|
|
5385
|
-
};
|
|
5386
|
-
});
|
|
5387
|
-
const { celldata: _cellData, data: _data, ...metadata } = sheet;
|
|
5388
|
-
const useMatrix = source ? void 0 !== source.data : void 0 !== sheet.data;
|
|
5389
|
-
if (!useMatrix) return {
|
|
5390
|
-
...metadata,
|
|
5391
|
-
celldata: cells
|
|
5392
|
-
};
|
|
5393
|
-
const data = [];
|
|
5394
|
-
data.length = Math.max(sheet.data?.length ?? 0, source?.data?.length ?? 0, spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.row));
|
|
5395
|
-
for (const { r: rowIndex, c: columnIndex, v: cell } of cells){
|
|
5396
|
-
const row = data[rowIndex] ?? [];
|
|
5397
|
-
data[rowIndex] = row;
|
|
5398
|
-
row.length = Math.max(row.length, sheet.data?.[rowIndex]?.length ?? 0, source?.data?.[rowIndex]?.length ?? 0, spreadsheet_editor_support_positiveSpreadsheetDimension(sheet.column));
|
|
5399
|
-
row[columnIndex] = cell;
|
|
5400
|
-
}
|
|
5401
|
-
return {
|
|
5402
|
-
...metadata,
|
|
5403
|
-
data
|
|
5404
|
-
};
|
|
5405
|
-
});
|
|
5406
|
-
recordSpreadsheetControlledProjectionMeasure(startedAt, spreadsheetProjectionNow(), {
|
|
5407
|
-
affectedCellCount: 0,
|
|
5408
|
-
incremental: false,
|
|
5409
|
-
operationCount: operations.length
|
|
5410
|
-
});
|
|
5411
|
-
return reconcileSpreadsheetFiltersAfterFortune(reconcileSpreadsheetTablesAfterFortune(projected, sourceSheets, operations), sourceSheets, operations, filterContext);
|
|
5412
|
-
}
|
|
5413
|
-
function recordSpreadsheetControlledProjectionMeasure(start, end, detail) {
|
|
5414
|
-
try {
|
|
5415
|
-
globalThis.performance?.measure('a3s-office.spreadsheet.controlled-projection', {
|
|
5416
|
-
detail,
|
|
5417
|
-
end,
|
|
5418
|
-
start
|
|
5419
|
-
});
|
|
5420
|
-
} catch {}
|
|
5421
|
-
}
|
|
5422
|
-
function finiteSpreadsheetSelection(selection) {
|
|
5423
|
-
const row = finiteSpreadsheetSelectionAxis(selection?.row);
|
|
5424
|
-
const column = finiteSpreadsheetSelectionAxis(selection?.column);
|
|
5425
|
-
return {
|
|
5426
|
-
...selection,
|
|
5427
|
-
row,
|
|
5428
|
-
column,
|
|
5429
|
-
row_focus: finiteSpreadsheetFocus(selection?.row_focus, row),
|
|
5430
|
-
column_focus: finiteSpreadsheetFocus(selection?.column_focus, column)
|
|
5431
|
-
};
|
|
5432
|
-
}
|
|
5433
|
-
function sameSpreadsheetWorkbookState(changed, rendered) {
|
|
5434
|
-
return changed.length === rendered.length && changed.every((sheet, index)=>sameSpreadsheetSheetState(sheet, rendered[index]));
|
|
5435
|
-
}
|
|
5436
|
-
function sameSpreadsheetWorkbookStateAfterOperations(changed, rendered, operations) {
|
|
5437
|
-
if (0 === operations.length || operations.length > 10000) return null;
|
|
5438
|
-
if (changed.length !== rendered.length || !changed.every((sheet, index)=>sameSpreadsheetHistoryValue(spreadsheetSheetMetadata(sheet), spreadsheetSheetMetadata(rendered[index])))) return false;
|
|
5439
|
-
const changedById = spreadsheet_editor_support_spreadsheetSheetsById(changed);
|
|
5440
|
-
const renderedById = spreadsheet_editor_support_spreadsheetSheetsById(rendered);
|
|
5441
|
-
if (!changedById || !renderedById) return null;
|
|
5442
|
-
const coordinates = new Map();
|
|
5443
|
-
for (const operation of operations){
|
|
5444
|
-
if ('insertRowCol' === operation.op || 'deleteRowCol' === operation.op || 'addSheet' === operation.op || 'deleteSheet' === operation.op || 0 === operation.path.length) return false;
|
|
5445
|
-
if (!operation.id || !changedById.has(operation.id) || !renderedById.has(operation.id)) return null;
|
|
5446
|
-
if ('data' !== operation.path[0]) continue;
|
|
5447
|
-
const row = operation.path[1];
|
|
5448
|
-
const column = operation.path[2];
|
|
5449
|
-
if (operation.path.length < 3 || !spreadsheet_editor_support_isSpreadsheetCoordinate(row) || !spreadsheet_editor_support_isSpreadsheetCoordinate(column)) return null;
|
|
5450
|
-
coordinates.set(`${operation.id}:${row}:${column}`, {
|
|
5451
|
-
column,
|
|
5452
|
-
row,
|
|
5453
|
-
sheetId: operation.id
|
|
5454
|
-
});
|
|
5455
|
-
}
|
|
5456
|
-
for (const { column, row, sheetId } of coordinates.values())if (!sameSpreadsheetHistoryValue(spreadsheet_editor_support_spreadsheetCellAt(changedById.get(sheetId), row, column), spreadsheet_editor_support_spreadsheetCellAt(renderedById.get(sheetId), row, column))) return false;
|
|
5457
|
-
return true;
|
|
5458
|
-
}
|
|
5459
|
-
function sameSpreadsheetHistoryContent(left, right) {
|
|
5460
|
-
if (left === right) return true;
|
|
5461
|
-
const { sheets: leftSheets, ...leftMetadata } = left;
|
|
5462
|
-
const { sheets: rightSheets, ...rightMetadata } = right;
|
|
5463
|
-
return sameSpreadsheetHistoryValue(leftMetadata, rightMetadata) && leftSheets.length === rightSheets.length && leftSheets.every((sheet, index)=>sameSpreadsheetSheetState(sheet, rightSheets[index]));
|
|
5464
|
-
}
|
|
5465
|
-
function spreadsheetContentWithSelection(content, sheetId, selection) {
|
|
5466
|
-
return spreadsheetContentWithSelections(content, sheetId, selection ? [
|
|
5467
|
-
selection
|
|
5468
|
-
] : []);
|
|
5469
|
-
}
|
|
5470
|
-
function spreadsheetContentWithSelections(content, sheetId, selections) {
|
|
5471
|
-
if (!sheetId || !selections.length) return content;
|
|
5472
|
-
const nextSelections = selections.map(finiteSpreadsheetSelection);
|
|
5473
|
-
let changed = false;
|
|
5474
|
-
const sheets = content.sheets.map((sheet)=>{
|
|
5475
|
-
if (sheet.id !== sheetId) return sheet;
|
|
5476
|
-
changed = true;
|
|
5477
|
-
return {
|
|
5478
|
-
...sheet,
|
|
5479
|
-
luckysheet_select_save: nextSelections
|
|
5480
|
-
};
|
|
5481
|
-
});
|
|
5482
|
-
return changed ? {
|
|
5483
|
-
...content,
|
|
5484
|
-
sheets
|
|
5485
|
-
} : content;
|
|
5486
|
-
}
|
|
5487
|
-
function isSpreadsheetNativeTextUndoTarget(target) {
|
|
5488
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
5489
|
-
if (isSpreadsheetCellEditingTarget(target)) return true;
|
|
5490
|
-
if (target.closest('.fortune-container')) return false;
|
|
5491
|
-
return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.isContentEditable || Boolean(target.closest('[contenteditable="true"]'));
|
|
5492
|
-
}
|
|
5493
|
-
function isSpreadsheetCellEditingTarget(target) {
|
|
5494
|
-
if (!isSpreadsheetCellEditorTarget(target)) return false;
|
|
5495
|
-
if (!(target instanceof Element)) return false;
|
|
5496
|
-
const formulaInput = target.closest('.fortune-fx-input');
|
|
5497
|
-
if (formulaInput) return true;
|
|
5498
|
-
const cellInput = target.closest('.luckysheet-cell-input');
|
|
5499
|
-
if (!cellInput) return false;
|
|
5500
|
-
const inputBox = cellInput.closest('.luckysheet-input-box');
|
|
5501
|
-
if (!inputBox) return true;
|
|
5502
|
-
const zIndex = Number.parseInt(inputBox.style.zIndex || getComputedStyle(inputBox).zIndex, 10);
|
|
5503
|
-
return !Number.isFinite(zIndex) || zIndex >= 0;
|
|
5504
|
-
}
|
|
5505
|
-
function isSpreadsheetCellEditorTarget(target) {
|
|
5506
|
-
return target instanceof Element && Boolean(target.closest('.fortune-fx-input, .luckysheet-cell-input'));
|
|
5507
|
-
}
|
|
5508
|
-
function spreadsheetFormulaBarSelectAllTarget(event) {
|
|
5509
|
-
if (event.defaultPrevented || event.repeat || event.altKey || event.shiftKey || !(event.metaKey || event.ctrlKey) || 'a' !== event.key.toLocaleLowerCase() || !(event.target instanceof Element)) return null;
|
|
5510
|
-
const formulaBar = event.target.closest('.fortune-fx-input');
|
|
5511
|
-
return formulaBar instanceof HTMLElement ? formulaBar : null;
|
|
5512
|
-
}
|
|
5513
|
-
function selectSpreadsheetFormulaBarContents(event) {
|
|
5514
|
-
const formulaBar = spreadsheetFormulaBarSelectAllTarget(event);
|
|
5515
|
-
const selection = window.getSelection();
|
|
5516
|
-
if (!formulaBar || !selection) return false;
|
|
5517
|
-
const range = document.createRange();
|
|
5518
|
-
range.selectNodeContents(formulaBar);
|
|
5519
|
-
selection.removeAllRanges();
|
|
5520
|
-
selection.addRange(range);
|
|
5521
|
-
return true;
|
|
5522
|
-
}
|
|
5523
|
-
function spreadsheetColumnLabel(column) {
|
|
5524
|
-
let value = Math.max(0, column) + 1;
|
|
5525
|
-
let label = '';
|
|
5526
|
-
while(value > 0){
|
|
5527
|
-
value -= 1;
|
|
5528
|
-
label = String.fromCharCode(65 + value % 26) + label;
|
|
5529
|
-
value = Math.floor(value / 26);
|
|
5530
|
-
}
|
|
5531
|
-
return label;
|
|
5532
|
-
}
|
|
5533
|
-
function spreadsheet_editor_support_spreadsheetCellHasContent(cell) {
|
|
5534
|
-
if ('string' == typeof cell.f && cell.f.trim()) return true;
|
|
5535
|
-
if (null === cell.v || void 0 === cell.v) return false;
|
|
5536
|
-
return 'string' != typeof cell.v || cell.v.trim().length > 0;
|
|
5537
|
-
}
|
|
5538
|
-
function finiteSpreadsheetSelectionAxis(axis) {
|
|
5539
|
-
const first = finiteSpreadsheetIndex(axis?.[0], 0);
|
|
5540
|
-
const second = finiteSpreadsheetIndex(axis?.[1], first);
|
|
5541
|
-
return [
|
|
5542
|
-
Math.min(first, second),
|
|
5543
|
-
Math.max(first, second)
|
|
5544
|
-
];
|
|
5545
|
-
}
|
|
5546
|
-
function finiteSpreadsheetFocus(value, axis) {
|
|
5547
|
-
const focus = finiteSpreadsheetIndex(value, axis[0] ?? 0);
|
|
5548
|
-
return Math.min(axis[1] ?? focus, Math.max(axis[0] ?? focus, focus));
|
|
5549
|
-
}
|
|
5550
|
-
function finiteSpreadsheetIndex(value, fallback) {
|
|
5551
|
-
return 'number' == typeof value && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
|
|
5552
|
-
}
|
|
5553
|
-
function spreadsheet_editor_support_isSpreadsheetCoordinate(value) {
|
|
5554
|
-
return Number.isSafeInteger(value) && Number(value) >= 0;
|
|
5555
|
-
}
|
|
5556
|
-
function spreadsheet_editor_support_spreadsheetSheetsById(sheets) {
|
|
5557
|
-
const byId = new Map();
|
|
5558
|
-
for (const sheet of sheets){
|
|
5559
|
-
if (!sheet.id || byId.has(sheet.id)) return null;
|
|
5560
|
-
byId.set(sheet.id, sheet);
|
|
5561
|
-
}
|
|
5562
|
-
return byId;
|
|
5563
|
-
}
|
|
5564
|
-
function spreadsheetSheetMetadata(sheet) {
|
|
5565
|
-
if (!sheet) return null;
|
|
5566
|
-
const { celldata: _cellData, calcChain: _calculationChain, data: _data, dynamicArray_compute: _dynamicArrayComputation, luckysheet_select_save: _selection, luckysheet_selection_range: _range, zoomRatio, ...content } = sheet;
|
|
5567
|
-
return {
|
|
5568
|
-
...content,
|
|
5569
|
-
...void 0 === zoomRatio || 1 === zoomRatio ? {} : {
|
|
5570
|
-
zoomRatio
|
|
5571
|
-
}
|
|
5572
|
-
};
|
|
5573
|
-
}
|
|
5574
|
-
function sameSpreadsheetSheetState(left, right) {
|
|
5575
|
-
return Boolean(right && sameSpreadsheetHistoryValue(spreadsheetSheetMetadata(left), spreadsheetSheetMetadata(right)) && sameSpreadsheetCellStorage(left, right));
|
|
5576
|
-
}
|
|
5577
|
-
function sameSpreadsheetCellStorage(left, right) {
|
|
5578
|
-
if (void 0 !== left.data && void 0 !== right.data) {
|
|
5579
|
-
const leftProfile = spreadsheetMatrixProfile(left.data);
|
|
5580
|
-
const rightProfile = spreadsheetMatrixProfile(right.data);
|
|
5581
|
-
if (leftProfile && rightProfile && leftProfile.historyRoot === rightProfile.historyRoot) return leftProfile.historyState === rightProfile.historyState;
|
|
5582
|
-
return sameSpreadsheetDataMatrices(left.data, right.data);
|
|
5583
|
-
}
|
|
5584
|
-
if (void 0 === left.data && void 0 === right.data) {
|
|
5585
|
-
if (left.celldata === right.celldata) return true;
|
|
5586
|
-
}
|
|
5587
|
-
return sameSpreadsheetHistoryValue(spreadsheetPopulatedCellData(left), spreadsheetPopulatedCellData(right));
|
|
5588
|
-
}
|
|
5589
|
-
function sameSpreadsheetDataMatrices(left, right) {
|
|
5590
|
-
if (left === right) return true;
|
|
5591
|
-
for (const rowIndex of sparseArrayIndexes(left))if (!sameSpreadsheetDataRows(left[rowIndex], right[rowIndex])) return false;
|
|
5592
|
-
for (const rowIndex of sparseArrayIndexes(right))if (!Object.hasOwn(left, rowIndex) && !sameSpreadsheetDataRows(void 0, right[rowIndex])) return false;
|
|
5593
|
-
return true;
|
|
5594
|
-
}
|
|
5595
|
-
function sameSpreadsheetDataRows(left, right) {
|
|
5596
|
-
if (left === right) return true;
|
|
5597
|
-
for (const columnIndex of sparseArrayIndexes(left ?? [])){
|
|
5598
|
-
const leftCell = left?.[columnIndex];
|
|
5599
|
-
if (null == leftCell) continue;
|
|
5600
|
-
const rightCell = right?.[columnIndex];
|
|
5601
|
-
if (null == rightCell || !sameSpreadsheetHistoryValue(leftCell, rightCell)) return false;
|
|
5602
|
-
}
|
|
5603
|
-
for (const columnIndex of sparseArrayIndexes(right ?? []))if (right?.[columnIndex] != null && left?.[columnIndex] == null) return false;
|
|
5604
|
-
return true;
|
|
5605
|
-
}
|
|
5606
|
-
function spreadsheetPopulatedCellData(sheet) {
|
|
5607
|
-
const cells = [];
|
|
5608
|
-
if (void 0 !== sheet.data) for (const rowIndex of sparseArrayIndexes(sheet.data)){
|
|
5609
|
-
const row = sheet.data[rowIndex];
|
|
5610
|
-
if (row) for (const columnIndex of sparseArrayIndexes(row)){
|
|
5611
|
-
const cell = row[columnIndex];
|
|
5612
|
-
if (null != cell) cells.push({
|
|
5613
|
-
r: rowIndex,
|
|
5614
|
-
c: columnIndex,
|
|
5615
|
-
v: cell
|
|
5616
|
-
});
|
|
5617
|
-
}
|
|
5618
|
-
}
|
|
5619
|
-
else for (const cell of sheet.celldata ?? [])if (null != cell.v) cells.push(cell);
|
|
5620
|
-
return cells.sort((left, right)=>left.r - right.r || left.c - right.c);
|
|
5621
|
-
}
|
|
5622
|
-
function spreadsheet_editor_support_positiveSpreadsheetDimension(value) {
|
|
5623
|
-
return 'number' == typeof value && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
5624
|
-
}
|
|
5625
|
-
const spreadsheetKeyboardPageRows = 20;
|
|
5626
|
-
const spreadsheetDefaultRows = 60;
|
|
5627
|
-
const spreadsheetDefaultColumns = 26;
|
|
5628
|
-
function moveSpreadsheetKeyboardSelection(sheet, selection, move, extend) {
|
|
5629
|
-
const bounds = spreadsheetSheetBounds(sheet);
|
|
5630
|
-
const current = normalizeSpreadsheetKeyboardSelection(selection, bounds);
|
|
5631
|
-
let targetRow = current.rowFocus;
|
|
5632
|
-
let targetColumn = current.columnFocus;
|
|
5633
|
-
switch(move){
|
|
5634
|
-
case 'up':
|
|
5635
|
-
targetRow -= 1;
|
|
5636
|
-
break;
|
|
5637
|
-
case 'down':
|
|
5638
|
-
targetRow += 1;
|
|
5639
|
-
break;
|
|
5640
|
-
case 'left':
|
|
5641
|
-
targetColumn -= 1;
|
|
5642
|
-
break;
|
|
5643
|
-
case 'right':
|
|
5644
|
-
targetColumn += 1;
|
|
5645
|
-
break;
|
|
5646
|
-
case 'next-cell':
|
|
5647
|
-
if (targetColumn < bounds.lastColumn) targetColumn += 1;
|
|
5648
|
-
else if (targetRow < bounds.lastRow) {
|
|
5649
|
-
targetRow += 1;
|
|
5650
|
-
targetColumn = 0;
|
|
5651
|
-
}
|
|
5652
|
-
break;
|
|
5653
|
-
case 'previous-cell':
|
|
5654
|
-
if (targetColumn > 0) targetColumn -= 1;
|
|
5655
|
-
else if (targetRow > 0) {
|
|
5656
|
-
targetRow -= 1;
|
|
5657
|
-
targetColumn = bounds.lastColumn;
|
|
5658
|
-
}
|
|
5659
|
-
break;
|
|
5660
|
-
case 'page-up':
|
|
5661
|
-
targetRow -= spreadsheetKeyboardPageRows;
|
|
5662
|
-
break;
|
|
5663
|
-
case 'page-down':
|
|
5664
|
-
targetRow += spreadsheetKeyboardPageRows;
|
|
5665
|
-
break;
|
|
5666
|
-
case 'row-start':
|
|
5667
|
-
targetColumn = 0;
|
|
5668
|
-
break;
|
|
5669
|
-
case 'sheet-start':
|
|
5670
|
-
targetRow = 0;
|
|
5671
|
-
targetColumn = 0;
|
|
5672
|
-
break;
|
|
5673
|
-
case 'used-end':
|
|
5674
|
-
{
|
|
5675
|
-
const usedEnd = spreadsheetUsedRangeEnd(sheet);
|
|
5676
|
-
targetRow = usedEnd.row;
|
|
5677
|
-
targetColumn = usedEnd.column;
|
|
5678
|
-
break;
|
|
5679
|
-
}
|
|
5680
|
-
}
|
|
5681
|
-
targetRow = clampSpreadsheetIndex(targetRow, bounds.lastRow);
|
|
5682
|
-
targetColumn = clampSpreadsheetIndex(targetColumn, bounds.lastColumn);
|
|
5683
|
-
if (!extend) return {
|
|
5684
|
-
row: [
|
|
5685
|
-
targetRow,
|
|
5686
|
-
targetRow
|
|
5687
|
-
],
|
|
5688
|
-
column: [
|
|
5689
|
-
targetColumn,
|
|
5690
|
-
targetColumn
|
|
5691
|
-
],
|
|
5692
|
-
row_focus: targetRow,
|
|
5693
|
-
column_focus: targetColumn
|
|
5694
|
-
};
|
|
5695
|
-
const rowAnchor = selectionAnchor(current.startRow, current.endRow, current.rowFocus);
|
|
5696
|
-
const columnAnchor = selectionAnchor(current.startColumn, current.endColumn, current.columnFocus);
|
|
5697
|
-
return {
|
|
5698
|
-
row: [
|
|
5699
|
-
Math.min(rowAnchor, targetRow),
|
|
5700
|
-
Math.max(rowAnchor, targetRow)
|
|
5701
|
-
],
|
|
5702
|
-
column: [
|
|
5703
|
-
Math.min(columnAnchor, targetColumn),
|
|
5704
|
-
Math.max(columnAnchor, targetColumn)
|
|
5705
|
-
],
|
|
5706
|
-
row_focus: targetRow,
|
|
5707
|
-
column_focus: targetColumn
|
|
5708
|
-
};
|
|
5709
|
-
}
|
|
5710
|
-
function scopeSpreadsheetKeyboardSelection(sheet, selection, scope) {
|
|
5711
|
-
const bounds = spreadsheetSheetBounds(sheet);
|
|
5712
|
-
const current = normalizeSpreadsheetKeyboardSelection(selection, bounds);
|
|
5713
|
-
if ('column' === scope) return {
|
|
5714
|
-
row: [
|
|
5715
|
-
0,
|
|
5716
|
-
bounds.lastRow
|
|
5717
|
-
],
|
|
5718
|
-
column: [
|
|
5719
|
-
current.columnFocus,
|
|
5720
|
-
current.columnFocus
|
|
5721
|
-
],
|
|
5722
|
-
row_focus: current.rowFocus,
|
|
5723
|
-
column_focus: current.columnFocus
|
|
5724
|
-
};
|
|
5725
|
-
if ('row' === scope) return {
|
|
5726
|
-
row: [
|
|
5727
|
-
current.rowFocus,
|
|
5728
|
-
current.rowFocus
|
|
5729
|
-
],
|
|
5730
|
-
column: [
|
|
5731
|
-
0,
|
|
5732
|
-
bounds.lastColumn
|
|
5733
|
-
],
|
|
5734
|
-
row_focus: current.rowFocus,
|
|
5735
|
-
column_focus: current.columnFocus
|
|
5736
|
-
};
|
|
5737
|
-
return {
|
|
5738
|
-
row: [
|
|
5739
|
-
0,
|
|
5740
|
-
bounds.lastRow
|
|
5741
|
-
],
|
|
5742
|
-
column: [
|
|
5743
|
-
0,
|
|
5744
|
-
bounds.lastColumn
|
|
5745
|
-
],
|
|
5746
|
-
row_focus: current.rowFocus,
|
|
5747
|
-
column_focus: current.columnFocus
|
|
5748
|
-
};
|
|
5749
|
-
}
|
|
5750
|
-
function isSpreadsheetGridKeyboardTarget(target) {
|
|
5751
|
-
if (null === target) return true;
|
|
5752
|
-
return target instanceof Element && Boolean(target.closest('.fortune-container'));
|
|
5753
|
-
}
|
|
5754
|
-
function runSpreadsheetSelectionMoveShortcut(event, canExecute, execute, move, extend) {
|
|
5755
|
-
if (event.isComposing || isOfficeShortcutBlocked(event.target) || isSpreadsheetNativeTextUndoTarget(event.target) || !isSpreadsheetGridKeyboardTarget(event.target) || !canExecute(move, extend)) return false;
|
|
5756
|
-
return execute(move, extend);
|
|
5757
|
-
}
|
|
5758
|
-
function runSpreadsheetSelectionScopeShortcut(event, canExecute, execute, scope) {
|
|
5759
|
-
if (event.repeat || event.isComposing || isOfficeShortcutBlocked(event.target) || isSpreadsheetNativeTextUndoTarget(event.target) || !isSpreadsheetGridKeyboardTarget(event.target) || !canExecute(scope)) return false;
|
|
5760
|
-
return execute(scope);
|
|
5761
|
-
}
|
|
5762
|
-
function spreadsheetSelectionContainsFocus(selection, focus) {
|
|
5763
|
-
const firstRow = selection.row[0] ?? 0;
|
|
5764
|
-
const secondRow = selection.row[1] ?? firstRow;
|
|
5765
|
-
const firstColumn = selection.column[0] ?? 0;
|
|
5766
|
-
const secondColumn = selection.column[1] ?? firstColumn;
|
|
5767
|
-
return focus.row >= Math.min(firstRow, secondRow) && focus.row <= Math.max(firstRow, secondRow) && focus.column >= Math.min(firstColumn, secondColumn) && focus.column <= Math.max(firstColumn, secondColumn);
|
|
5768
|
-
}
|
|
5769
|
-
function spreadsheetSheetBounds(sheet) {
|
|
5770
|
-
const dataRows = sheet.data?.length ?? 0;
|
|
5771
|
-
const dataColumns = sparseMatrixColumnCount(sheet.data);
|
|
5772
|
-
return {
|
|
5773
|
-
lastRow: spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(sheet.row, dataRows, spreadsheetDefaultRows) - 1,
|
|
5774
|
-
lastColumn: spreadsheet_keyboard_navigation_positiveSpreadsheetDimension(sheet.column, dataColumns, spreadsheetDefaultColumns) - 1
|
|
2576
|
+
row,
|
|
2577
|
+
row + rowSpan - 1
|
|
2578
|
+
],
|
|
2579
|
+
column: [
|
|
2580
|
+
column,
|
|
2581
|
+
column + columnSpan - 1
|
|
2582
|
+
]
|
|
5775
2583
|
};
|
|
5776
2584
|
}
|
|
5777
|
-
function
|
|
5778
|
-
const
|
|
5779
|
-
if (
|
|
5780
|
-
|
|
5781
|
-
return
|
|
2585
|
+
function spreadsheetCellIsLocked(sheet, cellAt, row, column) {
|
|
2586
|
+
const cell = cellAt(row, column);
|
|
2587
|
+
if (cell?.lo !== void 0 && null !== cell.lo) return Boolean(cell.lo);
|
|
2588
|
+
const authority = sheet.config?.authority;
|
|
2589
|
+
if (!spreadsheet_cell_mutation_guard_isRecord(authority)) return false;
|
|
2590
|
+
const ranges = authority.cellProtectionRanges;
|
|
2591
|
+
if (Array.isArray(ranges)) for(let index = ranges.length - 1; index >= 0; index -= 1){
|
|
2592
|
+
const candidate = ranges[index];
|
|
2593
|
+
if (!spreadsheet_cell_mutation_guard_isRecord(candidate)) continue;
|
|
2594
|
+
const protectedRange = parseSpreadsheetCellRange(candidate.range);
|
|
2595
|
+
if (protectedRange && row >= protectedRange.row[0] && row <= protectedRange.row[1] && column >= protectedRange.column[0] && column <= protectedRange.column[1]) return false !== candidate.locked;
|
|
2596
|
+
}
|
|
2597
|
+
return void 0 !== authority.sheet && 0 !== authority.sheet;
|
|
5782
2598
|
}
|
|
5783
|
-
function
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
});
|
|
5799
|
-
return {
|
|
5800
|
-
row: clampSpreadsheetIndex(row, bounds.lastRow),
|
|
5801
|
-
column: clampSpreadsheetIndex(column, bounds.lastColumn)
|
|
2599
|
+
function createSpreadsheetMutationCellReader(sheet) {
|
|
2600
|
+
let sparseRows = null;
|
|
2601
|
+
const sparseCell = (row, column)=>{
|
|
2602
|
+
if (!sparseRows) {
|
|
2603
|
+
sparseRows = new Map();
|
|
2604
|
+
for (const entry of sheet.celldata ?? []){
|
|
2605
|
+
let sparseRow = sparseRows.get(entry.r);
|
|
2606
|
+
if (!sparseRow) {
|
|
2607
|
+
sparseRow = new Map();
|
|
2608
|
+
sparseRows.set(entry.r, sparseRow);
|
|
2609
|
+
}
|
|
2610
|
+
sparseRow.set(entry.c, entry.v);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
return sparseRows.get(row)?.get(column);
|
|
5802
2614
|
};
|
|
2615
|
+
return (row, column)=>sheet.data?.[row]?.[column] ?? sparseCell(row, column);
|
|
5803
2616
|
}
|
|
5804
|
-
function
|
|
5805
|
-
|
|
5806
|
-
if ('string' == typeof cell.f && cell.f.trim()) return true;
|
|
5807
|
-
const value = cell.v;
|
|
5808
|
-
if (null != value) return 'string' != typeof value || value.trim().length > 0;
|
|
5809
|
-
return 'string' == typeof cell.m && cell.m.trim().length > 0;
|
|
5810
|
-
}
|
|
5811
|
-
function normalizeSpreadsheetKeyboardSelection(selection, bounds) {
|
|
5812
|
-
const firstRow = clampSpreadsheetIndex(selection.row[0], bounds.lastRow);
|
|
5813
|
-
const secondRow = clampSpreadsheetIndex(selection.row[1] ?? firstRow, bounds.lastRow);
|
|
5814
|
-
const firstColumn = clampSpreadsheetIndex(selection.column[0], bounds.lastColumn);
|
|
5815
|
-
const secondColumn = clampSpreadsheetIndex(selection.column[1] ?? firstColumn, bounds.lastColumn);
|
|
5816
|
-
const startRow = Math.min(firstRow, secondRow);
|
|
5817
|
-
const endRow = Math.max(firstRow, secondRow);
|
|
5818
|
-
const startColumn = Math.min(firstColumn, secondColumn);
|
|
5819
|
-
const endColumn = Math.max(firstColumn, secondColumn);
|
|
5820
|
-
return {
|
|
5821
|
-
startRow,
|
|
5822
|
-
endRow,
|
|
5823
|
-
startColumn,
|
|
5824
|
-
endColumn,
|
|
5825
|
-
rowFocus: clampSpreadsheetIndex(selection.row_focus ?? secondRow, bounds.lastRow),
|
|
5826
|
-
columnFocus: clampSpreadsheetIndex(selection.column_focus ?? secondColumn, bounds.lastColumn)
|
|
5827
|
-
};
|
|
2617
|
+
function finiteIndex(value) {
|
|
2618
|
+
return Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
5828
2619
|
}
|
|
5829
|
-
function
|
|
5830
|
-
|
|
5831
|
-
const finite = Number.isFinite(numeric) ? Math.trunc(numeric) : 0;
|
|
5832
|
-
return Math.min(maximum, Math.max(0, finite));
|
|
2620
|
+
function positiveSpan(value) {
|
|
2621
|
+
return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : null;
|
|
5833
2622
|
}
|
|
5834
|
-
function
|
|
5835
|
-
return
|
|
2623
|
+
function spreadsheet_cell_mutation_guard_isRecord(value) {
|
|
2624
|
+
return Boolean(value && 'object' == typeof value && !Array.isArray(value));
|
|
5836
2625
|
}
|
|
5837
2626
|
function createSpreadsheetAutoSumExtension() {
|
|
5838
2627
|
return createOfficeEditorExtension({
|
|
@@ -6953,10 +3742,10 @@ function formatMatrixCells(sheet, range, patch) {
|
|
|
6953
3742
|
}
|
|
6954
3743
|
function formatSparseCells(sheet, range, patch) {
|
|
6955
3744
|
const byCoordinate = new Map();
|
|
6956
|
-
for (const entry of sheet.celldata ?? [])byCoordinate.set(
|
|
3745
|
+
for (const entry of sheet.celldata ?? [])byCoordinate.set(cellKey(entry.r, entry.c), entry);
|
|
6957
3746
|
let materialized = false;
|
|
6958
3747
|
for(let row = range.row[0]; row <= range.row[1]; row += 1)for(let column = range.column[0]; column <= range.column[1]; column += 1){
|
|
6959
|
-
const key =
|
|
3748
|
+
const key = cellKey(row, column);
|
|
6960
3749
|
const entry = byCoordinate.get(key);
|
|
6961
3750
|
const next = formatCell(entry?.v ?? null, patch);
|
|
6962
3751
|
if (next) {
|
|
@@ -7114,7 +3903,7 @@ function normalizeColor(value) {
|
|
|
7114
3903
|
...color.slice(1)
|
|
7115
3904
|
].map((character)=>character.repeat(2)).join('')}`;
|
|
7116
3905
|
}
|
|
7117
|
-
function
|
|
3906
|
+
function cellKey(row, column) {
|
|
7118
3907
|
return `${row}:${column}`;
|
|
7119
3908
|
}
|
|
7120
3909
|
const spreadsheetFormatCellsTabs = [
|
|
@@ -7821,185 +4610,6 @@ function runSpreadsheetCopyFromAboveShortcut(event, kind, canExecute, execute) {
|
|
|
7821
4610
|
if (event.repeat || event.isComposing || isOfficeShortcutBlocked(event.target) || isSpreadsheetNativeTextUndoTarget(event.target) || !isSpreadsheetGridKeyboardTarget(event.target) || !canExecute(kind)) return false;
|
|
7822
4611
|
return execute(kind);
|
|
7823
4612
|
}
|
|
7824
|
-
const spreadsheet_go_to_spreadsheetMaximumRows = 1048576;
|
|
7825
|
-
const spreadsheet_go_to_spreadsheetMaximumColumns = 16384;
|
|
7826
|
-
const spreadsheetGoToMaximumInputLength = 512;
|
|
7827
|
-
function resolveSpreadsheetGoToTarget(content, activeSheetId, input) {
|
|
7828
|
-
const value = input.trim().replace(/^=/, '').trim();
|
|
7829
|
-
if (!value) return spreadsheetGoToError('empty');
|
|
7830
|
-
if (value.length > spreadsheetGoToMaximumInputLength) return spreadsheetGoToError('invalid-reference');
|
|
7831
|
-
const activeSheet = content.sheets.find((sheet)=>sheet.id === activeSheetId);
|
|
7832
|
-
if (!activeSheet) return spreadsheetGoToError('sheet-not-found');
|
|
7833
|
-
const qualified = splitSpreadsheetQualifiedReference(value);
|
|
7834
|
-
if (!qualified) return spreadsheetGoToError('invalid-reference');
|
|
7835
|
-
const requestedSheet = qualified.qualifier ? spreadsheetSheetByName(content, qualified.qualifier) : activeSheet;
|
|
7836
|
-
if (qualified.qualifier && !requestedSheet) return spreadsheetGoToError('sheet-not-found');
|
|
7837
|
-
if (requestedSheet?.hide === 1) return spreadsheetGoToError('hidden-sheet');
|
|
7838
|
-
const direct = resolveSpreadsheetDirectRange(content, requestedSheet ?? activeSheet, qualified);
|
|
7839
|
-
if ('error' === direct.kind) return spreadsheetGoToError(direct.code);
|
|
7840
|
-
if ('match' === direct.kind) return spreadsheetGoToSuccess(direct.value, 'reference');
|
|
7841
|
-
const named = findSpreadsheetGoToName(content, qualified.reference, requestedSheet ?? activeSheet, Boolean(qualified.qualifier));
|
|
7842
|
-
if ('error' === named.kind) return spreadsheetGoToError(named.code);
|
|
7843
|
-
if (!named.value) return spreadsheetGoToError('invalid-reference');
|
|
7844
|
-
const fallbackSheet = named.value.scopeSheetId ? content.sheets.find((sheet)=>sheet.id === named.value?.scopeSheetId) : requestedSheet ?? activeSheet;
|
|
7845
|
-
if (!fallbackSheet) return spreadsheetGoToError('sheet-not-found');
|
|
7846
|
-
if (1 === fallbackSheet.hide) return spreadsheetGoToError('hidden-sheet');
|
|
7847
|
-
const namedReference = splitSpreadsheetQualifiedReference(named.value.reference.trim().replace(/^=/, '').trim());
|
|
7848
|
-
if (!namedReference) return spreadsheetGoToError('unsupported-name');
|
|
7849
|
-
const resolved = resolveSpreadsheetDirectRange(content, fallbackSheet, namedReference);
|
|
7850
|
-
if ('error' === resolved.kind) return spreadsheetGoToError(resolved.code);
|
|
7851
|
-
if ('match' !== resolved.kind) return spreadsheetGoToError('unsupported-name');
|
|
7852
|
-
return spreadsheetGoToSuccess(resolved.value, 'named-range', named.value.name);
|
|
7853
|
-
}
|
|
7854
|
-
function spreadsheetGoToValidationMessage(content, activeSheetId, input) {
|
|
7855
|
-
const resolution = resolveSpreadsheetGoToTarget(content, activeSheetId, input);
|
|
7856
|
-
return resolution.ok ? null : resolution.message;
|
|
7857
|
-
}
|
|
7858
|
-
function resolveSpreadsheetDirectRange(content, fallbackSheet, qualified) {
|
|
7859
|
-
const sheet = qualified.qualifier ? spreadsheetSheetByName(content, qualified.qualifier) : fallbackSheet;
|
|
7860
|
-
if (!sheet) return {
|
|
7861
|
-
kind: 'error',
|
|
7862
|
-
code: 'sheet-not-found'
|
|
7863
|
-
};
|
|
7864
|
-
if (1 === sheet.hide) return {
|
|
7865
|
-
kind: 'error',
|
|
7866
|
-
code: 'hidden-sheet'
|
|
7867
|
-
};
|
|
7868
|
-
const ranges = parseSpreadsheetCellRanges(qualified.reference);
|
|
7869
|
-
if (!ranges) return {
|
|
7870
|
-
kind: 'miss'
|
|
7871
|
-
};
|
|
7872
|
-
if (1 !== ranges.length) return {
|
|
7873
|
-
kind: 'error',
|
|
7874
|
-
code: 'multiple-ranges'
|
|
7875
|
-
};
|
|
7876
|
-
const range = ranges[0];
|
|
7877
|
-
if (!range || !spreadsheetGoToRangeIsBounded(sheet, range)) return {
|
|
7878
|
-
kind: 'error',
|
|
7879
|
-
code: 'out-of-bounds'
|
|
7880
|
-
};
|
|
7881
|
-
return {
|
|
7882
|
-
kind: 'match',
|
|
7883
|
-
value: {
|
|
7884
|
-
range,
|
|
7885
|
-
sheet
|
|
7886
|
-
}
|
|
7887
|
-
};
|
|
7888
|
-
}
|
|
7889
|
-
function findSpreadsheetGoToName(content, input, scopeSheet, qualified) {
|
|
7890
|
-
const normalized = input.trim().toLocaleLowerCase();
|
|
7891
|
-
const matching = (content.namedRanges ?? []).filter((range)=>range.name.trim().toLocaleLowerCase() === normalized);
|
|
7892
|
-
const scoped = matching.filter((range)=>range.scopeSheetId === scopeSheet.id);
|
|
7893
|
-
const workbook = matching.filter((range)=>!range.scopeSheetId);
|
|
7894
|
-
const preferred = scoped.length ? scoped : workbook;
|
|
7895
|
-
if (preferred.length > 1) return {
|
|
7896
|
-
kind: 'error',
|
|
7897
|
-
code: 'ambiguous-name'
|
|
7898
|
-
};
|
|
7899
|
-
if (!preferred.length && !qualified) return {
|
|
7900
|
-
kind: 'match',
|
|
7901
|
-
value: null
|
|
7902
|
-
};
|
|
7903
|
-
return {
|
|
7904
|
-
kind: 'match',
|
|
7905
|
-
value: preferred[0] ?? null
|
|
7906
|
-
};
|
|
7907
|
-
}
|
|
7908
|
-
function spreadsheetGoToRangeIsBounded(sheet, range) {
|
|
7909
|
-
const bounds = spreadsheetSheetBounds(sheet);
|
|
7910
|
-
return range.row[0] >= 0 && range.row[1] < spreadsheet_go_to_spreadsheetMaximumRows && range.row[1] <= bounds.lastRow && range.column[0] >= 0 && range.column[1] < spreadsheet_go_to_spreadsheetMaximumColumns && range.column[1] <= bounds.lastColumn;
|
|
7911
|
-
}
|
|
7912
|
-
function spreadsheetGoToSuccess(resolved, source, name) {
|
|
7913
|
-
const reference = formatSpreadsheetCellRanges([
|
|
7914
|
-
resolved.range
|
|
7915
|
-
]);
|
|
7916
|
-
return {
|
|
7917
|
-
ok: true,
|
|
7918
|
-
target: {
|
|
7919
|
-
displayReference: `${quoteSpreadsheetSheetName(resolved.sheet.name)}!${reference}`,
|
|
7920
|
-
name,
|
|
7921
|
-
range: resolved.range,
|
|
7922
|
-
selection: {
|
|
7923
|
-
row: [
|
|
7924
|
-
...resolved.range.row
|
|
7925
|
-
],
|
|
7926
|
-
column: [
|
|
7927
|
-
...resolved.range.column
|
|
7928
|
-
],
|
|
7929
|
-
row_focus: resolved.range.row[0],
|
|
7930
|
-
column_focus: resolved.range.column[0]
|
|
7931
|
-
},
|
|
7932
|
-
sheetId: resolved.sheet.id ?? '',
|
|
7933
|
-
source
|
|
7934
|
-
}
|
|
7935
|
-
};
|
|
7936
|
-
}
|
|
7937
|
-
function spreadsheetGoToError(code) {
|
|
7938
|
-
const messages = {
|
|
7939
|
-
'ambiguous-name': '同一作用域中存在多个同名区域,无法确定定位目标。',
|
|
7940
|
-
empty: '请输入单元格、连续区域或已定义名称。',
|
|
7941
|
-
'hidden-sheet': '不能定位到隐藏工作表。',
|
|
7942
|
-
'invalid-reference': '请输入有效的 A1 单元格、连续区域或已定义名称。',
|
|
7943
|
-
'multiple-ranges': '一次只能定位到一个连续区域。',
|
|
7944
|
-
'out-of-bounds': '引用超出了目标工作表的有效边界。',
|
|
7945
|
-
'sheet-not-found': '找不到引用的工作表。',
|
|
7946
|
-
'unsupported-name': '该名称不是可定位的连续单元格区域。'
|
|
7947
|
-
};
|
|
7948
|
-
return {
|
|
7949
|
-
ok: false,
|
|
7950
|
-
code,
|
|
7951
|
-
message: messages[code]
|
|
7952
|
-
};
|
|
7953
|
-
}
|
|
7954
|
-
function splitSpreadsheetQualifiedReference(value) {
|
|
7955
|
-
let quoted = false;
|
|
7956
|
-
let separator = -1;
|
|
7957
|
-
for(let index = 0; index < value.length; index += 1){
|
|
7958
|
-
const character = value[index];
|
|
7959
|
-
if ("'" === character) {
|
|
7960
|
-
if (quoted && "'" === value[index + 1]) {
|
|
7961
|
-
index += 1;
|
|
7962
|
-
continue;
|
|
7963
|
-
}
|
|
7964
|
-
quoted = !quoted;
|
|
7965
|
-
continue;
|
|
7966
|
-
}
|
|
7967
|
-
if ('!' === character && !quoted) {
|
|
7968
|
-
if (separator >= 0) return null;
|
|
7969
|
-
separator = index;
|
|
7970
|
-
}
|
|
7971
|
-
}
|
|
7972
|
-
if (quoted) return null;
|
|
7973
|
-
if (separator < 0) return {
|
|
7974
|
-
qualifier: null,
|
|
7975
|
-
reference: value.trim()
|
|
7976
|
-
};
|
|
7977
|
-
const rawQualifier = value.slice(0, separator).trim();
|
|
7978
|
-
const reference = value.slice(separator + 1).trim();
|
|
7979
|
-
if (!rawQualifier || !reference) return null;
|
|
7980
|
-
const qualifier = unquoteSpreadsheetSheetName(rawQualifier);
|
|
7981
|
-
return qualifier ? {
|
|
7982
|
-
qualifier,
|
|
7983
|
-
reference
|
|
7984
|
-
} : null;
|
|
7985
|
-
}
|
|
7986
|
-
function unquoteSpreadsheetSheetName(value) {
|
|
7987
|
-
if (!value.startsWith("'")) return value.includes("'") ? null : value;
|
|
7988
|
-
if (!value.endsWith("'") || value.length < 2) return null;
|
|
7989
|
-
const inner = value.slice(1, -1);
|
|
7990
|
-
for(let index = 0; index < inner.length; index += 1)if ("'" === inner[index]) {
|
|
7991
|
-
if ("'" !== inner[index + 1]) return null;
|
|
7992
|
-
index += 1;
|
|
7993
|
-
}
|
|
7994
|
-
return inner.replaceAll("''", "'");
|
|
7995
|
-
}
|
|
7996
|
-
function spreadsheetSheetByName(content, name) {
|
|
7997
|
-
const normalized = name.trim().toLocaleLowerCase();
|
|
7998
|
-
return content.sheets.find((sheet)=>sheet.name.trim().toLocaleLowerCase() === normalized);
|
|
7999
|
-
}
|
|
8000
|
-
function quoteSpreadsheetSheetName(name) {
|
|
8001
|
-
return `'${name.replaceAll("'", "''")}'`;
|
|
8002
|
-
}
|
|
8003
4613
|
const MAX_SPREADSHEET_DATA_VALIDATION_CELLS = 10000;
|
|
8004
4614
|
const defaultSpreadsheetDataValidationValue = {
|
|
8005
4615
|
allowBlank: true,
|
|
@@ -8124,7 +4734,7 @@ function normalizeSpreadsheetDataValidationItem(content, sheet, ranges, value) {
|
|
|
8124
4734
|
const operators = spreadsheetDataValidationOperators(value.type);
|
|
8125
4735
|
const type2 = 'dropdown' === value.type || 'custom' === value.type ? '' : value.type2;
|
|
8126
4736
|
if ('dropdown' !== value.type && 'custom' !== value.type && !operators.includes(type2)) return spreadsheetDataValidationError('invalid-operator');
|
|
8127
|
-
const normalizedValues = normalizeSpreadsheetDataValidationValues(content, sheet, value.type, type2, value.value1, value.value2);
|
|
4737
|
+
const normalizedValues = normalizeSpreadsheetDataValidationValues(content, sheet, ranges, value.type, type2, value.value1, value.value2);
|
|
8128
4738
|
if (!normalizedValues.ok) return normalizedValues;
|
|
8129
4739
|
return {
|
|
8130
4740
|
ok: true,
|
|
@@ -8149,11 +4759,11 @@ function normalizeSpreadsheetDataValidationItem(content, sheet, ranges, value) {
|
|
|
8149
4759
|
}
|
|
8150
4760
|
};
|
|
8151
4761
|
}
|
|
8152
|
-
function normalizeSpreadsheetDataValidationValues(content, sheet, type, type2, rawValue1, rawValue2) {
|
|
4762
|
+
function normalizeSpreadsheetDataValidationValues(content, sheet, ranges, type, type2, rawValue1, rawValue2) {
|
|
8153
4763
|
const value1 = rawValue1.trim();
|
|
8154
4764
|
const value2 = rawValue2.trim();
|
|
8155
4765
|
if (!value1) return spreadsheetDataValidationError('missing-value');
|
|
8156
|
-
if ('dropdown' === type) return normalizeSpreadsheetListValidation(content, sheet, value1);
|
|
4766
|
+
if ('dropdown' === type) return normalizeSpreadsheetListValidation(content, sheet, ranges, value1);
|
|
8157
4767
|
if ('custom' === type) {
|
|
8158
4768
|
const formula = value1.replace(/^=/, '').trim();
|
|
8159
4769
|
if (!formula || Array.from(formula).length > 255 || /[\u0000-\u001f\u007f]/u.test(formula)) return spreadsheetDataValidationError('invalid-custom-formula');
|
|
@@ -8187,7 +4797,20 @@ function normalizeSpreadsheetDataValidationValues(content, sheet, type, type2, r
|
|
|
8187
4797
|
value2: null === second ? '' : String(second)
|
|
8188
4798
|
};
|
|
8189
4799
|
}
|
|
8190
|
-
function normalizeSpreadsheetListValidation(content, sheet, value) {
|
|
4800
|
+
function normalizeSpreadsheetListValidation(content, sheet, ranges, value) {
|
|
4801
|
+
if (isSpreadsheetDependentListFormula(value)) {
|
|
4802
|
+
const normalized = normalizeSpreadsheetDependentListFormula(value);
|
|
4803
|
+
if (!normalized.ok || !normalized.formula) return spreadsheetDataValidationError('invalid-list-formula');
|
|
4804
|
+
for (const range of ranges){
|
|
4805
|
+
const resolved = resolveSpreadsheetDependentListReference(content, sheet.id ?? '', range.row[0], range.column[0], range.row[0], range.column[0], normalized.formula);
|
|
4806
|
+
if (!resolved.ok) return spreadsheetDataValidationError('invalid-list-formula');
|
|
4807
|
+
}
|
|
4808
|
+
return {
|
|
4809
|
+
ok: true,
|
|
4810
|
+
value1: normalized.formula,
|
|
4811
|
+
value2: ''
|
|
4812
|
+
};
|
|
4813
|
+
}
|
|
8191
4814
|
const looksLikeReference = value.startsWith('=') || value.includes('!') || /^\$?[A-Z]{1,3}\$?\d+(?::\$?[A-Z]{1,3}\$?\d+)?$/i.test(value) || (content.namedRanges ?? []).some((range)=>range.name.trim().toLocaleLowerCase() === value.toLocaleLowerCase());
|
|
8192
4815
|
if (looksLikeReference) {
|
|
8193
4816
|
const resolved = resolveSpreadsheetGoToTarget(content, sheet.id ?? '', value);
|
|
@@ -8456,6 +5079,7 @@ function spreadsheetDataValidationError(code) {
|
|
|
8456
5079
|
const messages = {
|
|
8457
5080
|
'invalid-custom-formula': '自定义公式必须是 255 个字符以内的本地公式,且不能包含控制字符。',
|
|
8458
5081
|
'invalid-date': '请输入有效的日期、Excel 日期序号或 DATE(...) 表达式。',
|
|
5082
|
+
'invalid-list-formula': '动态下拉公式必须是本地 =INDIRECT(单元格或文本拼接),并解析为当前工作簿内的一行或一列区域。',
|
|
8459
5083
|
'invalid-list-source': '请输入不超过 255 个字符的逗号分隔列表,或有效的单行/单列区域。',
|
|
8460
5084
|
'invalid-number': '请输入有效的数字;整数验证不能使用小数边界。',
|
|
8461
5085
|
'invalid-operator': '请选择与当前验证类型匹配的数据条件。',
|
|
@@ -10058,7 +6682,7 @@ function translateFormulaSegment(segment, rowOffset, columnOffset) {
|
|
|
10058
6682
|
valid = false;
|
|
10059
6683
|
return match;
|
|
10060
6684
|
}
|
|
10061
|
-
return `${prefix}${sheet ?? ''}${absoluteColumn}${
|
|
6685
|
+
return `${prefix}${sheet ?? ''}${absoluteColumn}${spreadsheetColumnLabel(translatedColumn)}${absoluteRow}${translatedRow}`;
|
|
10062
6686
|
});
|
|
10063
6687
|
next = next.replace(new RegExp(String.raw`(^|[^A-Za-z0-9_.])(${sheetPrefix})?(\$?)([A-Za-z]{1,3}):(\$?)([A-Za-z]{1,3})(?![A-Za-z0-9_])`, 'g'), (match, prefix, sheet, leftAbsolute, left, rightAbsolute, right)=>{
|
|
10064
6688
|
const leftColumn = leftAbsolute ? spreadsheetColumnIndex(left) : spreadsheetColumnIndex(left) + columnOffset;
|
|
@@ -10067,7 +6691,7 @@ function translateFormulaSegment(segment, rowOffset, columnOffset) {
|
|
|
10067
6691
|
valid = false;
|
|
10068
6692
|
return match;
|
|
10069
6693
|
}
|
|
10070
|
-
return `${prefix}${sheet ?? ''}${leftAbsolute}${
|
|
6694
|
+
return `${prefix}${sheet ?? ''}${leftAbsolute}${spreadsheetColumnLabel(leftColumn)}:${rightAbsolute}${spreadsheetColumnLabel(rightColumn)}`;
|
|
10071
6695
|
});
|
|
10072
6696
|
next = next.replace(new RegExp(String.raw`(^|[^A-Za-z0-9_.])(${sheetPrefix})?(\$?)([1-9]\d{0,6}):(\$?)([1-9]\d{0,6})(?![A-Za-z0-9_])`, 'g'), (match, prefix, sheet, topAbsolute, top, bottomAbsolute, bottom)=>{
|
|
10073
6697
|
const topRow = topAbsolute ? Number(top) : Number(top) + rowOffset;
|
|
@@ -10085,7 +6709,7 @@ function spreadsheetColumnIndex(value) {
|
|
|
10085
6709
|
for (const character of value.toUpperCase())index = 26 * index + character.charCodeAt(0) - 64;
|
|
10086
6710
|
return index - 1;
|
|
10087
6711
|
}
|
|
10088
|
-
function
|
|
6712
|
+
function spreadsheetColumnLabel(index) {
|
|
10089
6713
|
let value = index + 1;
|
|
10090
6714
|
let label = '';
|
|
10091
6715
|
while(value > 0){
|
|
@@ -11297,7 +7921,7 @@ function spreadsheetTableColumnNames(sheet, range, headerRow) {
|
|
|
11297
7921
|
const observed = new Set();
|
|
11298
7922
|
const names = [];
|
|
11299
7923
|
for(let column = range.column[0]; column <= range.column[1]; column += 1){
|
|
11300
|
-
const raw = headerRow ?
|
|
7924
|
+
const raw = headerRow ? spreadsheetCellText(sheet, range.row[0], column).trim() : '';
|
|
11301
7925
|
const base = validTableColumnName(raw) ? raw : `Column${column - range.column[0] + 1}`;
|
|
11302
7926
|
let candidate = base;
|
|
11303
7927
|
let suffix = 2;
|
|
@@ -11318,7 +7942,7 @@ function validTableColumnName(name) {
|
|
|
11318
7942
|
function truncateTableColumnName(value, maximum) {
|
|
11319
7943
|
return Array.from(value).slice(0, maximum).join('');
|
|
11320
7944
|
}
|
|
11321
|
-
function
|
|
7945
|
+
function spreadsheetCellText(sheet, row, column) {
|
|
11322
7946
|
const cell = sheet.data?.[row]?.[column] ?? sheet.celldata?.find((candidate)=>candidate.r === row && candidate.c === column)?.v;
|
|
11323
7947
|
const value = cell?.m ?? cell?.v;
|
|
11324
7948
|
return null == value ? '' : String(value);
|
|
@@ -13073,6 +9697,11 @@ function newConditionalRuleDraft(sheetId) {
|
|
|
13073
9697
|
comparisonCellColor: DEFAULT_COLORS.comparisonCell,
|
|
13074
9698
|
comparisonUseTextColor: true,
|
|
13075
9699
|
comparisonUseCellColor: true,
|
|
9700
|
+
formula: '=A1>0',
|
|
9701
|
+
formulaTextColor: DEFAULT_COLORS.comparisonText,
|
|
9702
|
+
formulaCellColor: DEFAULT_COLORS.comparisonCell,
|
|
9703
|
+
formulaUseTextColor: true,
|
|
9704
|
+
formulaUseCellColor: true,
|
|
13076
9705
|
scaleSize: '3',
|
|
13077
9706
|
minimumColor: DEFAULT_COLORS.minimum,
|
|
13078
9707
|
midpointColor: DEFAULT_COLORS.midpoint,
|
|
@@ -13104,7 +9733,7 @@ function conditionalRuleDraftForRule(sheetId, rule) {
|
|
|
13104
9733
|
draft.comparisonUseCellColor = Boolean(style.cellColor);
|
|
13105
9734
|
draft.comparisonTextColor = colorInputValue(style.textColor, DEFAULT_COLORS.comparisonText);
|
|
13106
9735
|
draft.comparisonCellColor = colorInputValue(style.cellColor, DEFAULT_COLORS.comparisonCell);
|
|
13107
|
-
} else if (isToolbarConditionalRule(rule)) {
|
|
9736
|
+
} else if ('default' !== rule.type || 'formula' !== rule.conditionName || Array.isArray(rule.format)) if (isToolbarConditionalRule(rule)) {
|
|
13108
9737
|
draft.type = 'toolbarRule';
|
|
13109
9738
|
draft.preservedRule = rule;
|
|
13110
9739
|
} else if ('colorGradation' === rule.type && Array.isArray(rule.format)) {
|
|
@@ -13139,6 +9768,16 @@ function conditionalRuleDraftForRule(sheetId, rule) {
|
|
|
13139
9768
|
draft.barMaxLength = String(options.maxLength ?? 90);
|
|
13140
9769
|
}
|
|
13141
9770
|
}
|
|
9771
|
+
else {
|
|
9772
|
+
const style = rule.format;
|
|
9773
|
+
const values = (rule.conditionValue ?? []).map(String);
|
|
9774
|
+
draft.type = 'formula';
|
|
9775
|
+
draft.formula = values[0] ?? '';
|
|
9776
|
+
draft.formulaUseTextColor = Boolean(style.textColor);
|
|
9777
|
+
draft.formulaUseCellColor = Boolean(style.cellColor);
|
|
9778
|
+
draft.formulaTextColor = colorInputValue(style.textColor, DEFAULT_COLORS.comparisonText);
|
|
9779
|
+
draft.formulaCellColor = colorInputValue(style.cellColor, DEFAULT_COLORS.comparisonCell);
|
|
9780
|
+
}
|
|
13142
9781
|
return draft;
|
|
13143
9782
|
}
|
|
13144
9783
|
function buildConditionalRule(draft) {
|
|
@@ -13179,6 +9818,34 @@ function buildConditionalRule(draft) {
|
|
|
13179
9818
|
value
|
|
13180
9819
|
]
|
|
13181
9820
|
};
|
|
9821
|
+
} else if ('formula' === draft.type) {
|
|
9822
|
+
const formula = draft.formula.trim();
|
|
9823
|
+
if (!formula) return {
|
|
9824
|
+
error: '请输入条件格式公式。'
|
|
9825
|
+
};
|
|
9826
|
+
const formulaSource = formula.replace(/^=/, '').trim();
|
|
9827
|
+
if (!formulaSource) return {
|
|
9828
|
+
error: '请输入条件格式公式,例如 =A2>0。'
|
|
9829
|
+
};
|
|
9830
|
+
if (Array.from(formulaSource).length > 255) return {
|
|
9831
|
+
error: '条件格式公式不能超过 255 个字符。'
|
|
9832
|
+
};
|
|
9833
|
+
if (!draft.formulaUseTextColor && !draft.formulaUseCellColor) return {
|
|
9834
|
+
error: '至少启用一种文字或填充颜色。'
|
|
9835
|
+
};
|
|
9836
|
+
rule = {
|
|
9837
|
+
type: 'default',
|
|
9838
|
+
cellrange,
|
|
9839
|
+
format: {
|
|
9840
|
+
textColor: draft.formulaUseTextColor ? draft.formulaTextColor : null,
|
|
9841
|
+
cellColor: draft.formulaUseCellColor ? draft.formulaCellColor : null
|
|
9842
|
+
},
|
|
9843
|
+
conditionName: 'formula',
|
|
9844
|
+
conditionRange: [],
|
|
9845
|
+
conditionValue: [
|
|
9846
|
+
`=${formulaSource}`
|
|
9847
|
+
]
|
|
9848
|
+
};
|
|
13182
9849
|
} else if ('dataBar' === draft.type) {
|
|
13183
9850
|
const thresholds = savedVisualThresholds(draft.barThresholds);
|
|
13184
9851
|
const minLength = Number(draft.barMinLength);
|
|
@@ -14429,7 +11096,7 @@ function formatSpreadsheetRowPageBreaks(breaks) {
|
|
|
14429
11096
|
return normalizedBreaks(breaks).map((row)=>String(row + 1)).join(', ');
|
|
14430
11097
|
}
|
|
14431
11098
|
function parseSpreadsheetColumnPageBreaks(value, maximumColumn) {
|
|
14432
|
-
return parsePageBreakList(value, maximumColumn,
|
|
11099
|
+
return parsePageBreakList(value, maximumColumn, decodeColumn);
|
|
14433
11100
|
}
|
|
14434
11101
|
function formatSpreadsheetColumnPageBreaks(breaks) {
|
|
14435
11102
|
return normalizedBreaks(breaks).map(encodeColumn).join(', ');
|
|
@@ -14449,7 +11116,7 @@ function parsePageBreakList(value, maximum, decode) {
|
|
|
14449
11116
|
function normalizedBreaks(breaks) {
|
|
14450
11117
|
return Array.from(new Set((breaks ?? []).filter((value)=>Number.isSafeInteger(value) && value > 0))).sort((left, right)=>left - right);
|
|
14451
11118
|
}
|
|
14452
|
-
function
|
|
11119
|
+
function decodeColumn(token) {
|
|
14453
11120
|
const match = /^\$?([A-Z]+)$/i.exec(token);
|
|
14454
11121
|
if (!match) return null;
|
|
14455
11122
|
let value = 0;
|
|
@@ -18538,9 +15205,9 @@ function appendRunSegment(target, source, absoluteStart, absoluteEnd, sourceStar
|
|
|
18538
15205
|
});
|
|
18539
15206
|
}
|
|
18540
15207
|
function validTextSelection(text, selection) {
|
|
18541
|
-
return Boolean(Number.isInteger(selection.start) && Number.isInteger(selection.end) && selection.start >= 0 && selection.start < selection.end && selection.end <= text.length && !
|
|
15208
|
+
return Boolean(Number.isInteger(selection.start) && Number.isInteger(selection.end) && selection.start >= 0 && selection.start < selection.end && selection.end <= text.length && !splitsSurrogatePair(text, selection.start) && !splitsSurrogatePair(text, selection.end));
|
|
18542
15209
|
}
|
|
18543
|
-
function
|
|
15210
|
+
function splitsSurrogatePair(text, offset) {
|
|
18544
15211
|
if (offset <= 0 || offset >= text.length) return false;
|
|
18545
15212
|
const previous = text.charCodeAt(offset - 1);
|
|
18546
15213
|
const next = text.charCodeAt(offset);
|
|
@@ -18693,8 +15360,8 @@ function captureSpreadsheetRichTextSelection({ content, root, selection, sheetId
|
|
|
18693
15360
|
});
|
|
18694
15361
|
if (!sheetId || !selection || !domSelection) return null;
|
|
18695
15362
|
const { editor, end, start } = domSelection;
|
|
18696
|
-
const row =
|
|
18697
|
-
const column =
|
|
15363
|
+
const row = focusedAxisIndex(selection.row_focus, selection.row);
|
|
15364
|
+
const column = focusedAxisIndex(selection.column_focus, selection.column);
|
|
18698
15365
|
const source = spreadsheet_rich_text_selection_controller_sheetCellAt(content, sheetId, row, column);
|
|
18699
15366
|
if (!source) return null;
|
|
18700
15367
|
const sourceCell = liveSpreadsheetTextCell(editor, source);
|
|
@@ -18797,7 +15464,7 @@ function currentSpreadsheetTextEditor(snapshot) {
|
|
|
18797
15464
|
function editorText(editor) {
|
|
18798
15465
|
return editor.textContent ?? '';
|
|
18799
15466
|
}
|
|
18800
|
-
function
|
|
15467
|
+
function focusedAxisIndex(value, axis) {
|
|
18801
15468
|
const minimum = spreadsheet_rich_text_selection_controller_finiteIndex(axis[0], 0);
|
|
18802
15469
|
const maximum = spreadsheet_rich_text_selection_controller_finiteIndex(axis[1], minimum);
|
|
18803
15470
|
const focus = spreadsheet_rich_text_selection_controller_finiteIndex(value, minimum);
|
|
@@ -21598,6 +18265,10 @@ function SpreadsheetConditionalFormatPanel({ content, onChange }) {
|
|
|
21598
18265
|
value: 'icons',
|
|
21599
18266
|
label: '图标集'
|
|
21600
18267
|
},
|
|
18268
|
+
{
|
|
18269
|
+
value: 'formula',
|
|
18270
|
+
label: '自定义公式'
|
|
18271
|
+
},
|
|
21601
18272
|
...'toolbarRule' === draft.type ? [
|
|
21602
18273
|
{
|
|
21603
18274
|
value: 'toolbarRule',
|
|
@@ -21657,6 +18328,76 @@ function SpreadsheetConditionalFormatPanel({ content, onChange }) {
|
|
|
21657
18328
|
...draft,
|
|
21658
18329
|
...patch
|
|
21659
18330
|
})
|
|
18331
|
+
}) : 'formula' === draft.type ? /*#__PURE__*/ jsxs(Fragment, {
|
|
18332
|
+
children: [
|
|
18333
|
+
/*#__PURE__*/ jsxs("div", {
|
|
18334
|
+
className: "work-office-field reference",
|
|
18335
|
+
children: [
|
|
18336
|
+
/*#__PURE__*/ jsx("span", {
|
|
18337
|
+
children: "公式"
|
|
18338
|
+
}),
|
|
18339
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeTextField, {
|
|
18340
|
+
"aria-label": "条件格式公式",
|
|
18341
|
+
value: draft.formula,
|
|
18342
|
+
maxLength: 256,
|
|
18343
|
+
placeholder: "=A2>0",
|
|
18344
|
+
onChange: (event)=>setDraft({
|
|
18345
|
+
...draft,
|
|
18346
|
+
formula: event.target.value
|
|
18347
|
+
})
|
|
18348
|
+
}),
|
|
18349
|
+
/*#__PURE__*/ jsxs("small", {
|
|
18350
|
+
children: [
|
|
18351
|
+
"以应用范围左上角为相对锚点;只读取本地缓存值。",
|
|
18352
|
+
' ',
|
|
18353
|
+
Array.from(draft.formula.replace(/^=/, '')).length,
|
|
18354
|
+
"/",
|
|
18355
|
+
255
|
|
18356
|
+
]
|
|
18357
|
+
})
|
|
18358
|
+
]
|
|
18359
|
+
}),
|
|
18360
|
+
/*#__PURE__*/ jsx("p", {
|
|
18361
|
+
className: "work-office-field-hint",
|
|
18362
|
+
children: "支持常用 Excel 函数、相对/绝对引用和有限区域;无法本地求值时保持原样。"
|
|
18363
|
+
}),
|
|
18364
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
18365
|
+
className: "toggle",
|
|
18366
|
+
ariaLabel: "公式规则使用文字颜色",
|
|
18367
|
+
checked: draft.formulaUseTextColor,
|
|
18368
|
+
onCheckedChange: (formulaUseTextColor)=>setDraft({
|
|
18369
|
+
...draft,
|
|
18370
|
+
formulaUseTextColor
|
|
18371
|
+
}),
|
|
18372
|
+
children: "使用文字颜色"
|
|
18373
|
+
}),
|
|
18374
|
+
draft.formulaUseTextColor && /*#__PURE__*/ jsx(ColorField, {
|
|
18375
|
+
label: "文字颜色",
|
|
18376
|
+
value: draft.formulaTextColor,
|
|
18377
|
+
onChange: (formulaTextColor)=>setDraft({
|
|
18378
|
+
...draft,
|
|
18379
|
+
formulaTextColor
|
|
18380
|
+
})
|
|
18381
|
+
}),
|
|
18382
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
18383
|
+
className: "toggle",
|
|
18384
|
+
ariaLabel: "公式规则使用填充颜色",
|
|
18385
|
+
checked: draft.formulaUseCellColor,
|
|
18386
|
+
onCheckedChange: (formulaUseCellColor)=>setDraft({
|
|
18387
|
+
...draft,
|
|
18388
|
+
formulaUseCellColor
|
|
18389
|
+
}),
|
|
18390
|
+
children: "使用填充颜色"
|
|
18391
|
+
}),
|
|
18392
|
+
draft.formulaUseCellColor && /*#__PURE__*/ jsx(ColorField, {
|
|
18393
|
+
label: "填充颜色",
|
|
18394
|
+
value: draft.formulaCellColor,
|
|
18395
|
+
onChange: (formulaCellColor)=>setDraft({
|
|
18396
|
+
...draft,
|
|
18397
|
+
formulaCellColor
|
|
18398
|
+
})
|
|
18399
|
+
})
|
|
18400
|
+
]
|
|
21660
18401
|
}) : 'colorGradation' === draft.type ? /*#__PURE__*/ jsxs(Fragment, {
|
|
21661
18402
|
children: [
|
|
21662
18403
|
/*#__PURE__*/ jsxs("div", {
|
|
@@ -26738,25 +23479,42 @@ function SpreadsheetDataValidationDialog({ source, restoreFocusTarget, onApply,
|
|
|
26738
23479
|
})
|
|
26739
23480
|
]
|
|
26740
23481
|
}),
|
|
26741
|
-
'dropdown' === value.type ? /*#__PURE__*/
|
|
26742
|
-
|
|
26743
|
-
|
|
26744
|
-
|
|
26745
|
-
|
|
26746
|
-
|
|
26747
|
-
|
|
26748
|
-
|
|
26749
|
-
|
|
26750
|
-
|
|
26751
|
-
|
|
26752
|
-
|
|
26753
|
-
|
|
26754
|
-
|
|
26755
|
-
|
|
26756
|
-
|
|
26757
|
-
|
|
26758
|
-
|
|
26759
|
-
|
|
23482
|
+
'dropdown' === value.type ? /*#__PURE__*/ jsxs("div", {
|
|
23483
|
+
className: "work-spreadsheet-data-validation-list-source",
|
|
23484
|
+
children: [
|
|
23485
|
+
/*#__PURE__*/ jsx(field_Field, {
|
|
23486
|
+
label: "来源",
|
|
23487
|
+
required: true,
|
|
23488
|
+
description: isSpreadsheetDependentListFormula(value.value1) ? '动态来源支持 =INDIRECT(单元格或文本拼接),驱动单元格需返回区域或已定义名称。' : '输入逗号分隔项目,或单行/单列区域,例如 Ready,Blocked 或 Lists!A1:A8。',
|
|
23489
|
+
error: visibleError ?? void 0,
|
|
23490
|
+
children: /*#__PURE__*/ jsx("input", {
|
|
23491
|
+
type: "text",
|
|
23492
|
+
"aria-label": "来源",
|
|
23493
|
+
autoCapitalize: "none",
|
|
23494
|
+
spellCheck: false,
|
|
23495
|
+
value: value.value1,
|
|
23496
|
+
onBlur: ()=>setTouched(true),
|
|
23497
|
+
onChange: (event)=>{
|
|
23498
|
+
update({
|
|
23499
|
+
value1: event.currentTarget.value
|
|
23500
|
+
});
|
|
23501
|
+
setTouched(true);
|
|
23502
|
+
}
|
|
23503
|
+
})
|
|
23504
|
+
}),
|
|
23505
|
+
isSpreadsheetDependentListFormula(value.value1) && /*#__PURE__*/ jsxs("p", {
|
|
23506
|
+
className: "work-spreadsheet-data-validation-formula-note",
|
|
23507
|
+
children: [
|
|
23508
|
+
/*#__PURE__*/ jsx(Sigma, {
|
|
23509
|
+
size: 14,
|
|
23510
|
+
"aria-hidden": "true"
|
|
23511
|
+
}),
|
|
23512
|
+
/*#__PURE__*/ jsx("span", {
|
|
23513
|
+
children: "每个受验证单元格会按相对引用重新解析来源;空驱动值会显示为空列表,引用范围限制为本地有限区域。"
|
|
23514
|
+
})
|
|
23515
|
+
]
|
|
23516
|
+
})
|
|
23517
|
+
]
|
|
26760
23518
|
}) : 'custom' === value.type ? /*#__PURE__*/ jsx(SpreadsheetDataValidationCustomFormulaField, {
|
|
26761
23519
|
value: value.value1,
|
|
26762
23520
|
error: visibleError,
|
|
@@ -27185,7 +23943,6 @@ function cloneSpreadsheetDataValidationItem(item) {
|
|
|
27185
23943
|
};
|
|
27186
23944
|
}
|
|
27187
23945
|
const MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH = 255;
|
|
27188
|
-
const MAX_SPREADSHEET_CUSTOM_VALIDATION_REFERENCED_CELLS = 1024;
|
|
27189
23946
|
function evaluateSpreadsheetCustomValidation(content, sheetId, row, column, item, value) {
|
|
27190
23947
|
const sheet = content.sheets.find((candidate)=>candidate.id === sheetId);
|
|
27191
23948
|
if (!sheet) return unsupportedCustomValidation('找不到自定义公式所在的工作表。');
|
|
@@ -27200,122 +23957,30 @@ function evaluateSpreadsheetCustomValidation(content, sheetId, row, column, item
|
|
|
27200
23957
|
if (Array.from(formula).length > MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH) return unsupportedCustomValidation('自定义公式超过 255 个字符。');
|
|
27201
23958
|
const ranges = parseSpreadsheetCellRanges(item.rangeTxt);
|
|
27202
23959
|
const anchor = spreadsheetCustomValidationAnchor(ranges, row, column);
|
|
27203
|
-
const
|
|
27204
|
-
|
|
27205
|
-
|
|
27206
|
-
|
|
27207
|
-
|
|
27208
|
-
|
|
27209
|
-
|
|
27210
|
-
|
|
27211
|
-
|
|
27212
|
-
|
|
27213
|
-
|
|
27214
|
-
|
|
27215
|
-
|
|
27216
|
-
|
|
27217
|
-
|
|
27218
|
-
|
|
27219
|
-
|
|
27220
|
-
|
|
27221
|
-
|
|
27222
|
-
|
|
27223
|
-
|
|
27224
|
-
|
|
27225
|
-
|
|
27226
|
-
|
|
27227
|
-
const parser = new Parser();
|
|
27228
|
-
parser.setFunction('ROW', (parameters)=>parameters.length ? null : this.targetRow + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : this.targetColumn + 1);
|
|
27229
|
-
let unsupportedFunction;
|
|
27230
|
-
parser.on('callFunction', (name)=>{
|
|
27231
|
-
const normalized = name.toUpperCase();
|
|
27232
|
-
if (!allowedFunctions.has(normalized)) unsupportedFunction ??= normalized;
|
|
27233
|
-
});
|
|
27234
|
-
parser.on('callCellValue', (coordinate, _options, done)=>{
|
|
27235
|
-
const resolved = this.resolveCoordinate(this.currentSheet, coordinate);
|
|
27236
|
-
if (!resolved) return void done(null);
|
|
27237
|
-
done(this.readCell(resolved));
|
|
27238
|
-
});
|
|
27239
|
-
parser.on('callRangeValue', (start, end, _options, done)=>{
|
|
27240
|
-
done(this.readRange(start, end));
|
|
27241
|
-
});
|
|
27242
|
-
const parsed = parser.parse(normalizeFormulaForFortuneParser(formula), {
|
|
27243
|
-
sheetId: this.currentSheet.id
|
|
27244
|
-
});
|
|
27245
|
-
if (unsupportedFunction) return unsupportedCustomValidation(`自定义公式函数“${unsupportedFunction}”无法在浏览器本地求值。`);
|
|
27246
|
-
if (this.unsupportedMessage) return unsupportedCustomValidation(this.unsupportedMessage);
|
|
27247
|
-
if (parsed.error) return unsupportedCustomValidation('自定义公式无法求值,请检查函数、引用和括号。');
|
|
27248
|
-
return {
|
|
27249
|
-
supported: true,
|
|
27250
|
-
valid: spreadsheetCustomValidationBoolean(parsed.result)
|
|
27251
|
-
};
|
|
27252
|
-
}
|
|
27253
|
-
resolveCoordinate(fallbackSheet, coordinate) {
|
|
27254
|
-
const sheet = coordinate.sheetName ? this.sheetsByName.get(coordinate.sheetName.trim().toLocaleLowerCase()) : fallbackSheet;
|
|
27255
|
-
if (!sheet) {
|
|
27256
|
-
this.unsupportedMessage = '自定义公式引用了不存在的工作表。';
|
|
27257
|
-
return null;
|
|
27258
|
-
}
|
|
27259
|
-
if (coordinate.row.index < 0 || coordinate.column.index < 0) {
|
|
27260
|
-
this.unsupportedMessage = '自定义公式暂不支持整行或整列引用,请改用有限区域。';
|
|
27261
|
-
return null;
|
|
27262
|
-
}
|
|
27263
|
-
const resolvedRow = coordinate.row.isAbsolute ? coordinate.row.index : this.targetRow + (coordinate.row.index - this.anchor.row);
|
|
27264
|
-
const resolvedColumn = coordinate.column.isAbsolute ? coordinate.column.index : this.targetColumn + (coordinate.column.index - this.anchor.column);
|
|
27265
|
-
if (resolvedRow < 0 || resolvedColumn < 0 || resolvedRow >= 1048576 || resolvedColumn >= 16384) {
|
|
27266
|
-
this.unsupportedMessage = '自定义公式引用超出了工作表边界。';
|
|
27267
|
-
return null;
|
|
27268
|
-
}
|
|
27269
|
-
return {
|
|
27270
|
-
sheet,
|
|
27271
|
-
row: resolvedRow,
|
|
27272
|
-
column: resolvedColumn
|
|
27273
|
-
};
|
|
27274
|
-
}
|
|
27275
|
-
readRange(start, end) {
|
|
27276
|
-
const startCoordinate = this.resolveCoordinate(this.currentSheet, start);
|
|
27277
|
-
const rangeSheet = start.sheetName ? this.sheetsByName.get(start.sheetName.trim().toLocaleLowerCase()) : this.currentSheet;
|
|
27278
|
-
const endCoordinate = rangeSheet ? this.resolveCoordinate(rangeSheet, end) : null;
|
|
27279
|
-
if (!startCoordinate || !endCoordinate || startCoordinate.sheet.id !== endCoordinate.sheet.id) {
|
|
27280
|
-
this.unsupportedMessage ??= '自定义公式区域引用必须位于同一工作表。';
|
|
27281
|
-
return [];
|
|
27282
|
-
}
|
|
27283
|
-
const rowStart = Math.min(startCoordinate.row, endCoordinate.row);
|
|
27284
|
-
const rowEnd = Math.max(startCoordinate.row, endCoordinate.row);
|
|
27285
|
-
const columnStart = Math.min(startCoordinate.column, endCoordinate.column);
|
|
27286
|
-
const columnEnd = Math.max(startCoordinate.column, endCoordinate.column);
|
|
27287
|
-
const area = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1);
|
|
27288
|
-
if (area <= 0 || area > MAX_SPREADSHEET_CUSTOM_VALIDATION_REFERENCED_CELLS - this.referencedCells) {
|
|
27289
|
-
this.unsupportedMessage = '自定义公式一次最多读取 1,024 个单元格。';
|
|
27290
|
-
return [];
|
|
27291
|
-
}
|
|
27292
|
-
const values = [];
|
|
27293
|
-
for(let currentRow = rowStart; currentRow <= rowEnd; currentRow += 1){
|
|
27294
|
-
const row = [];
|
|
27295
|
-
for(let currentColumn = columnStart; currentColumn <= columnEnd; currentColumn += 1)row.push(this.readCell({
|
|
27296
|
-
sheet: startCoordinate.sheet,
|
|
27297
|
-
row: currentRow,
|
|
27298
|
-
column: currentColumn
|
|
27299
|
-
}));
|
|
27300
|
-
values.push(row);
|
|
27301
|
-
}
|
|
27302
|
-
return values;
|
|
27303
|
-
}
|
|
27304
|
-
readCell(coordinate) {
|
|
27305
|
-
this.referencedCells += 1;
|
|
27306
|
-
if (this.referencedCells > MAX_SPREADSHEET_CUSTOM_VALIDATION_REFERENCED_CELLS) {
|
|
27307
|
-
this.unsupportedMessage = '自定义公式一次最多读取 1,024 个单元格。';
|
|
27308
|
-
return null;
|
|
27309
|
-
}
|
|
27310
|
-
if (coordinate.sheet.id === this.currentSheet.id && coordinate.row === this.targetRow && coordinate.column === this.targetColumn) return this.proposedValue;
|
|
27311
|
-
const cell = spreadsheetCustomValidationCellAt(coordinate.sheet, coordinate.row, coordinate.column);
|
|
27312
|
-
if (!cell) return null;
|
|
27313
|
-
if (cell.f && void 0 === cell.v) {
|
|
27314
|
-
this.unsupportedMessage ??= '自定义公式引用了尚未计算的公式单元格。';
|
|
27315
|
-
return null;
|
|
27316
|
-
}
|
|
27317
|
-
return cell.v ?? null;
|
|
27318
|
-
}
|
|
23960
|
+
const result = evaluateSpreadsheetLocalFormula(formula, {
|
|
23961
|
+
sheets: content.sheets.flatMap((candidate)=>candidate.id ? [
|
|
23962
|
+
{
|
|
23963
|
+
...candidate,
|
|
23964
|
+
id: candidate.id
|
|
23965
|
+
}
|
|
23966
|
+
] : []),
|
|
23967
|
+
sheetId,
|
|
23968
|
+
row,
|
|
23969
|
+
column,
|
|
23970
|
+
anchorRow: anchor?.row,
|
|
23971
|
+
anchorColumn: anchor?.column,
|
|
23972
|
+
proposedValue: normalizedProposed,
|
|
23973
|
+
useProposedValue: true
|
|
23974
|
+
});
|
|
23975
|
+
if (!result.supported) return {
|
|
23976
|
+
supported: false,
|
|
23977
|
+
valid: false,
|
|
23978
|
+
message: result.message ? `自定义${result.message}` : result.message
|
|
23979
|
+
};
|
|
23980
|
+
return {
|
|
23981
|
+
supported: true,
|
|
23982
|
+
valid: spreadsheetLocalFormulaBoolean(result.value)
|
|
23983
|
+
};
|
|
27319
23984
|
}
|
|
27320
23985
|
function spreadsheetCustomValidationAnchor(ranges, row, column) {
|
|
27321
23986
|
const range = ranges?.find((candidate)=>spreadsheetCellRangeContains(candidate, row, column));
|
|
@@ -27324,10 +23989,6 @@ function spreadsheetCustomValidationAnchor(ranges, row, column) {
|
|
|
27324
23989
|
column: range?.column[0] ?? column
|
|
27325
23990
|
};
|
|
27326
23991
|
}
|
|
27327
|
-
function spreadsheetCustomValidationCellAt(sheet, row, column) {
|
|
27328
|
-
if (void 0 !== sheet.data) return sheet.data[row]?.[column] ?? null;
|
|
27329
|
-
return sheet.celldata?.find((entry)=>entry.r === row && entry.c === column)?.v ?? null;
|
|
27330
|
-
}
|
|
27331
23992
|
function spreadsheetCustomValidationValue(value) {
|
|
27332
23993
|
if (!value || 'object' != typeof value || Array.isArray(value)) return value;
|
|
27333
23994
|
if ('v' in value) return value.v;
|
|
@@ -27336,11 +23997,6 @@ function spreadsheetCustomValidationValue(value) {
|
|
|
27336
23997
|
function spreadsheetCustomValidationBlank(value) {
|
|
27337
23998
|
return null == value || 'string' == typeof value && '' === value.trim();
|
|
27338
23999
|
}
|
|
27339
|
-
function spreadsheetCustomValidationBoolean(value) {
|
|
27340
|
-
if ('boolean' == typeof value) return value;
|
|
27341
|
-
if ('number' == typeof value) return Number.isFinite(value) && 0 !== value;
|
|
27342
|
-
return 'string' == typeof value && 'TRUE' === value.trim().toUpperCase();
|
|
27343
|
-
}
|
|
27344
24000
|
function unsupportedCustomValidation(message) {
|
|
27345
24001
|
return {
|
|
27346
24002
|
supported: false,
|
|
@@ -30150,6 +26806,46 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
30150
26806
|
const formatCellsInvokerRef = useRef(null);
|
|
30151
26807
|
const formatCellsSelectionRef = useRef(null);
|
|
30152
26808
|
const formatCellsApplyingRef = useRef(false);
|
|
26809
|
+
useEffect(()=>{
|
|
26810
|
+
const container = spreadsheetCanvasRef.current;
|
|
26811
|
+
if (!container || preview || !selectionState) return;
|
|
26812
|
+
const frame = requestAnimationFrame(()=>{
|
|
26813
|
+
const button = container.querySelector('#luckysheet-dataVerification-dropdown-btn');
|
|
26814
|
+
const selectedCell = container.querySelector('#luckysheet-cell-selected');
|
|
26815
|
+
const cellArea = selectedCell?.closest('.fortune-cell-area');
|
|
26816
|
+
const sheet = materializedContent.sheets.find((candidate)=>candidate.id === selectionState.sheetId);
|
|
26817
|
+
const row = selectionState.selection.row[0];
|
|
26818
|
+
const column = selectionState.selection.column[0];
|
|
26819
|
+
const item = sheet && null != row && null != column ? spreadsheetDataValidationItemAt(sheet, row, column) : null;
|
|
26820
|
+
if (!button || !selectedCell || !cellArea || !item || 'dropdown' !== item.type || false === item.showDropdownArrow) {
|
|
26821
|
+
if (button) button.style.display = 'none';
|
|
26822
|
+
return;
|
|
26823
|
+
}
|
|
26824
|
+
button.setAttribute('role', 'button');
|
|
26825
|
+
button.setAttribute('aria-label', '打开下拉列表');
|
|
26826
|
+
button.setAttribute('aria-haspopup', 'listbox');
|
|
26827
|
+
button.setAttribute('title', '打开下拉列表');
|
|
26828
|
+
const cellBounds = selectedCell.getBoundingClientRect();
|
|
26829
|
+
const areaBounds = cellArea.getBoundingClientRect();
|
|
26830
|
+
const buttonWidth = 20;
|
|
26831
|
+
const buttonHeight = 20;
|
|
26832
|
+
const maxLeft = Math.max(0, areaBounds.width - buttonWidth);
|
|
26833
|
+
const maxTop = Math.max(0, areaBounds.height - buttonHeight);
|
|
26834
|
+
const left = Math.max(0, Math.min(cellBounds.right - areaBounds.left - buttonWidth, maxLeft));
|
|
26835
|
+
const top = Math.max(0, Math.min(cellBounds.top - areaBounds.top + (cellBounds.height - buttonHeight) / 2 - 2, maxTop));
|
|
26836
|
+
button.style.display = 'block';
|
|
26837
|
+
button.style.maxWidth = `${Math.max(0, cellBounds.width)}px`;
|
|
26838
|
+
button.style.maxHeight = `${Math.max(0, cellBounds.height)}px`;
|
|
26839
|
+
button.style.left = `${left}px`;
|
|
26840
|
+
button.style.top = `${top}px`;
|
|
26841
|
+
});
|
|
26842
|
+
return ()=>cancelAnimationFrame(frame);
|
|
26843
|
+
}, [
|
|
26844
|
+
materializedContent,
|
|
26845
|
+
preview,
|
|
26846
|
+
selectionState,
|
|
26847
|
+
workbookMountRevision
|
|
26848
|
+
]);
|
|
30153
26849
|
const focusSpreadsheetDialogGrid = useCallback((focusOrigin)=>focusSpreadsheetGrid(spreadsheetCanvasRef.current, {
|
|
30154
26850
|
focusOrigin
|
|
30155
26851
|
}), []);
|
|
@@ -30267,7 +26963,7 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
30267
26963
|
const conditionalStylesBySheet = useMemo(()=>new Map(materializedContent.sheets.flatMap((sheet)=>sheet.id ? [
|
|
30268
26964
|
[
|
|
30269
26965
|
sheet.id,
|
|
30270
|
-
spreadsheetConditionalFormatStyles(sheet)
|
|
26966
|
+
spreadsheetConditionalFormatStyles(sheet, materializedContent.sheets)
|
|
30271
26967
|
]
|
|
30272
26968
|
] : [])), [
|
|
30273
26969
|
materializedContent.sheets
|
|
@@ -30546,7 +27242,8 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
30546
27242
|
materializedContent,
|
|
30547
27243
|
requestedWorkbookSelection
|
|
30548
27244
|
]);
|
|
30549
|
-
const workbookSheets = useMemo(()=>spreadsheetSheetsForFortune(renderedWorkbookSheets), [
|
|
27245
|
+
const workbookSheets = useMemo(()=>spreadsheetSheetsForFortune(renderedWorkbookSheets, materializedContent.namedRanges), [
|
|
27246
|
+
materializedContent.namedRanges,
|
|
30550
27247
|
renderedWorkbookSheets
|
|
30551
27248
|
]);
|
|
30552
27249
|
const displayedWorkbookSheets = useMemo(()=>preview ? workbookSheets.map((sheet)=>({
|