@a3s-lab/office 0.40.0 → 0.42.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 +26 -0
- package/dist/0~7048.js +1 -1
- package/dist/0~spreadsheet-editor.js +421 -7
- package/dist/4121.js +27 -1
- package/dist/core.js +2 -0
- package/dist/internal/features/work/editors/spreadsheet-data-validation-custom.d.ts +18 -0
- package/dist/internal/features/work/editors/spreadsheet-data-validation-interaction.d.ts +23 -0
- package/dist/internal/features/work/editors/spreadsheet-data-validation.d.ts +2 -2
- package/dist/internal/features/work/work-spreadsheet-data-validation.d.ts +1 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -309,6 +309,32 @@ and [CLI reference](./docs/latest/en/cli-reference.md).
|
|
|
309
309
|
|
|
310
310
|
## Current release
|
|
311
311
|
|
|
312
|
+
Version `0.42.0` extends Spreadsheet data validation with a local custom-formula
|
|
313
|
+
workflow that remains bounded, inspectable, and native-file compatible:
|
|
314
|
+
|
|
315
|
+
- Author a formula with an optional `=` prefix in the same accessible dialog;
|
|
316
|
+
relative references are anchored per selected range and the proposed value
|
|
317
|
+
is evaluated before it enters the controlled workbook.
|
|
318
|
+
- Common local functions, cell/range references, and sheet-qualified references
|
|
319
|
+
are evaluated synchronously with a 255-character formula and 1,024-cell read
|
|
320
|
+
budget. External, whole-row/column, missing-sheet, and uncached-formula
|
|
321
|
+
references fail closed instead of silently accepting an unsafe edit.
|
|
322
|
+
- Custom rules round-trip through XLSX and are demonstrated in the public
|
|
323
|
+
**新建 → 数据验证** template, alongside the existing list, date, and alert
|
|
324
|
+
branches. Paste and object-level batch writes remain separate preflight
|
|
325
|
+
boundaries.
|
|
326
|
+
|
|
327
|
+
Version `0.41.0` aligns Spreadsheet data-validation alerts with the local,
|
|
328
|
+
testable interaction model used by Traditional Office:
|
|
329
|
+
|
|
330
|
+
- Stop blocks an invalid edit with an accessible notice. Warning and
|
|
331
|
+
Information ask whether the invalid value should be kept, using the authored
|
|
332
|
+
title and message plus the current input so the decision is explicit.
|
|
333
|
+
- Keeping a Warning or Information value commits it once through the controlled
|
|
334
|
+
workbook API; cancelling restores the original value. Selection, focus,
|
|
335
|
+
Undo, and collaboration remain on the same bounded edit path, and native
|
|
336
|
+
XLSX `errorStyle` metadata remains lossless on reopen.
|
|
337
|
+
|
|
312
338
|
Version `0.40.0` extends Writer's local, testable review workflow with atomic
|
|
313
339
|
ordered-list numbering revisions:
|
|
314
340
|
|
package/dist/0~7048.js
CHANGED
|
@@ -2733,4 +2733,4 @@ function abortError() {
|
|
|
2733
2733
|
function disposedError() {
|
|
2734
2734
|
return new Error('Office kernel client is disposed.');
|
|
2735
2735
|
}
|
|
2736
|
-
export { CollectionState, CommittedOfficeNumberField, OfficeCheckbox, OfficeNumberField, createOfficeKernelClient, handleOfficeTaskPaneKeyDown, normalizeOfficeFontFamily, officeFontFamilies, officeFontFamilyLabel, useOfficeDialog, useOfficeTaskPaneEscape, useOfficeTaskPaneModal };
|
|
2736
|
+
export { CollectionState, CommittedOfficeNumberField, OfficeCheckbox, OfficeNumberField, createOfficeKernelClient, handleOfficeTaskPaneKeyDown, normalizeFormulaForFortuneParser, normalizeOfficeFontFamily, officeFontFamilies, officeFontFamilyLabel, useOfficeDialog, useOfficeTaskPaneEscape, useOfficeTaskPaneModal };
|
|
@@ -4,12 +4,13 @@ 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 { Parser, SUPPORTED_FORMULAS } from "@fortune-sheet/formula-parser";
|
|
7
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";
|
|
8
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";
|
|
9
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";
|
|
10
11
|
import { moveOfficeMenuFocus, stepOfficeZoom, useOfficeRemoteParticipants, Popover, Dialog, officePresenceColor, useOfficePublishPresenceLocation, button_Button, icon_button_IconButton, moveOfficeGridMenuFocus, office_text_field_OfficeTextField, useDialogFocusScope, useOfficeEditorWheelZoom } from "./0~8136.js";
|
|
11
12
|
import { isOfficeKernelSpreadsheetError, normalizeCssColor, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, createOfficeId as createWorkId } from "./4121.js";
|
|
12
|
-
import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeDialog, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown } from "./0~7048.js";
|
|
13
|
+
import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeDialog, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown, normalizeFormulaForFortuneParser } from "./0~7048.js";
|
|
13
14
|
import { isOfficeShortcutBlocked, OfficeColorPicker } from "./0~3635.js";
|
|
14
15
|
import { createOfficeEditorExtension, useOfficeEditorRuntime, useOfficeEditorKeyboardShortcuts } from "./0~3557.js";
|
|
15
16
|
import { showToast } from "./6489.js";
|
|
@@ -8036,7 +8037,7 @@ const dateOperators = [
|
|
|
8036
8037
|
'noLaterThan'
|
|
8037
8038
|
];
|
|
8038
8039
|
function spreadsheetDataValidationOperators(type) {
|
|
8039
|
-
if ('dropdown' === type) return [];
|
|
8040
|
+
if ('custom' === type || 'dropdown' === type) return [];
|
|
8040
8041
|
return 'date' === type ? dateOperators : numericOperators;
|
|
8041
8042
|
}
|
|
8042
8043
|
function createSpreadsheetDataValidationDialogSource(content, target) {
|
|
@@ -8121,8 +8122,8 @@ function spreadsheetDataValidationTargetError(sheet, ranges) {
|
|
|
8121
8122
|
}
|
|
8122
8123
|
function normalizeSpreadsheetDataValidationItem(content, sheet, ranges, value) {
|
|
8123
8124
|
const operators = spreadsheetDataValidationOperators(value.type);
|
|
8124
|
-
const type2 = 'dropdown' === value.type ? '' : value.type2;
|
|
8125
|
-
if ('dropdown' !== value.type && !operators.includes(type2)) return spreadsheetDataValidationError('invalid-operator');
|
|
8125
|
+
const type2 = 'dropdown' === value.type || 'custom' === value.type ? '' : value.type2;
|
|
8126
|
+
if ('dropdown' !== value.type && 'custom' !== value.type && !operators.includes(type2)) return spreadsheetDataValidationError('invalid-operator');
|
|
8126
8127
|
const normalizedValues = normalizeSpreadsheetDataValidationValues(content, sheet, value.type, type2, value.value1, value.value2);
|
|
8127
8128
|
if (!normalizedValues.ok) return normalizedValues;
|
|
8128
8129
|
return {
|
|
@@ -8153,6 +8154,15 @@ function normalizeSpreadsheetDataValidationValues(content, sheet, type, type2, r
|
|
|
8153
8154
|
const value2 = rawValue2.trim();
|
|
8154
8155
|
if (!value1) return spreadsheetDataValidationError('missing-value');
|
|
8155
8156
|
if ('dropdown' === type) return normalizeSpreadsheetListValidation(content, sheet, value1);
|
|
8157
|
+
if ('custom' === type) {
|
|
8158
|
+
const formula = value1.replace(/^=/, '').trim();
|
|
8159
|
+
if (!formula || Array.from(formula).length > 255 || /[\u0000-\u001f\u007f]/u.test(formula)) return spreadsheetDataValidationError('invalid-custom-formula');
|
|
8160
|
+
return {
|
|
8161
|
+
ok: true,
|
|
8162
|
+
value1: formula,
|
|
8163
|
+
value2: ''
|
|
8164
|
+
};
|
|
8165
|
+
}
|
|
8156
8166
|
const needsSecond = 'between' === type2 || 'notBetween' === type2;
|
|
8157
8167
|
if (needsSecond && !value2) return spreadsheetDataValidationError('missing-value');
|
|
8158
8168
|
if ('date' === type) {
|
|
@@ -8256,6 +8266,7 @@ function spreadsheetDataValidationDialogValue(value) {
|
|
|
8256
8266
|
}
|
|
8257
8267
|
function spreadsheetDataValidationType(value) {
|
|
8258
8268
|
return [
|
|
8269
|
+
'custom',
|
|
8259
8270
|
'date',
|
|
8260
8271
|
'dropdown',
|
|
8261
8272
|
'number',
|
|
@@ -8443,6 +8454,7 @@ function cloneSpreadsheetCellRange(range) {
|
|
|
8443
8454
|
}
|
|
8444
8455
|
function spreadsheetDataValidationError(code) {
|
|
8445
8456
|
const messages = {
|
|
8457
|
+
'invalid-custom-formula': '自定义公式必须是 255 个字符以内的本地公式,且不能包含控制字符。',
|
|
8446
8458
|
'invalid-date': '请输入有效的日期、Excel 日期序号或 DATE(...) 表达式。',
|
|
8447
8459
|
'invalid-list-source': '请输入不超过 255 个字符的逗号分隔列表,或有效的单行/单列区域。',
|
|
8448
8460
|
'invalid-number': '请输入有效的数字;整数验证不能使用小数边界。',
|
|
@@ -26523,6 +26535,10 @@ function pruneSpreadsheetCollaborationViewState(view, content) {
|
|
|
26523
26535
|
for (const sheetId of view.zoomBySheet.keys())if (!sheetIds.has(sheetId)) view.zoomBySheet.delete(sheetId);
|
|
26524
26536
|
}
|
|
26525
26537
|
const validationTypes = [
|
|
26538
|
+
{
|
|
26539
|
+
value: 'custom',
|
|
26540
|
+
label: '自定义公式'
|
|
26541
|
+
},
|
|
26526
26542
|
{
|
|
26527
26543
|
value: 'dropdown',
|
|
26528
26544
|
label: '序列'
|
|
@@ -26679,7 +26695,7 @@ function SpreadsheetDataValidationDialog({ source, restoreFocusTarget, onApply,
|
|
|
26679
26695
|
}, option.value))
|
|
26680
26696
|
})
|
|
26681
26697
|
}),
|
|
26682
|
-
'dropdown' !== value.type && /*#__PURE__*/ jsx(field_Field, {
|
|
26698
|
+
'dropdown' !== value.type && 'custom' !== value.type && /*#__PURE__*/ jsx(field_Field, {
|
|
26683
26699
|
label: "数据",
|
|
26684
26700
|
children: /*#__PURE__*/ jsx("select", {
|
|
26685
26701
|
value: value.type2,
|
|
@@ -26741,6 +26757,13 @@ function SpreadsheetDataValidationDialog({ source, restoreFocusTarget, onApply,
|
|
|
26741
26757
|
setTouched(true);
|
|
26742
26758
|
}
|
|
26743
26759
|
})
|
|
26760
|
+
}) : 'custom' === value.type ? /*#__PURE__*/ jsx(SpreadsheetDataValidationCustomFormulaField, {
|
|
26761
|
+
value: value.value1,
|
|
26762
|
+
error: visibleError,
|
|
26763
|
+
onTouched: ()=>setTouched(true),
|
|
26764
|
+
onValueChange: (value1)=>update({
|
|
26765
|
+
value1
|
|
26766
|
+
})
|
|
26744
26767
|
}) : /*#__PURE__*/ jsx(SpreadsheetDataValidationBoundaryFields, {
|
|
26745
26768
|
type: value.type,
|
|
26746
26769
|
type2: value.type2,
|
|
@@ -26838,7 +26861,7 @@ function SpreadsheetDataValidationDialog({ source, restoreFocusTarget, onApply,
|
|
|
26838
26861
|
children: [
|
|
26839
26862
|
/*#__PURE__*/ jsx(field_Field, {
|
|
26840
26863
|
label: "错误警告样式",
|
|
26841
|
-
description: "
|
|
26864
|
+
description: "停止会阻止无效输入;警告和信息会询问是否保留。三种样式都会写入原生文件。",
|
|
26842
26865
|
children: /*#__PURE__*/ jsxs("select", {
|
|
26843
26866
|
"aria-label": "错误警告样式",
|
|
26844
26867
|
value: value.errorStyle,
|
|
@@ -26900,6 +26923,45 @@ function SpreadsheetDataValidationDialog({ source, restoreFocusTarget, onApply,
|
|
|
26900
26923
|
})
|
|
26901
26924
|
});
|
|
26902
26925
|
}
|
|
26926
|
+
function SpreadsheetDataValidationCustomFormulaField({ error, onTouched, onValueChange, value }) {
|
|
26927
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
26928
|
+
className: "work-spreadsheet-data-validation-custom-formula",
|
|
26929
|
+
children: [
|
|
26930
|
+
/*#__PURE__*/ jsx(field_Field, {
|
|
26931
|
+
label: "公式",
|
|
26932
|
+
required: true,
|
|
26933
|
+
description: "公式必须返回 TRUE;相对引用以每个选定区域的左上角为基准。仅计算本地单元格和区域,不访问网络。",
|
|
26934
|
+
error: error ?? void 0,
|
|
26935
|
+
children: /*#__PURE__*/ jsx("textarea", {
|
|
26936
|
+
"aria-label": "公式",
|
|
26937
|
+
rows: 2,
|
|
26938
|
+
maxLength: 255,
|
|
26939
|
+
placeholder: '例如:=AND(A1<>"",A1<=100)',
|
|
26940
|
+
autoCapitalize: "none",
|
|
26941
|
+
spellCheck: false,
|
|
26942
|
+
value: value,
|
|
26943
|
+
onBlur: onTouched,
|
|
26944
|
+
onChange: (event)=>{
|
|
26945
|
+
onValueChange(event.currentTarget.value);
|
|
26946
|
+
onTouched();
|
|
26947
|
+
}
|
|
26948
|
+
})
|
|
26949
|
+
}),
|
|
26950
|
+
/*#__PURE__*/ jsxs("p", {
|
|
26951
|
+
className: "work-spreadsheet-data-validation-formula-note",
|
|
26952
|
+
children: [
|
|
26953
|
+
/*#__PURE__*/ jsx(Sigma, {
|
|
26954
|
+
size: 14,
|
|
26955
|
+
"aria-hidden": "true"
|
|
26956
|
+
}),
|
|
26957
|
+
/*#__PURE__*/ jsx("span", {
|
|
26958
|
+
children: "支持常用 Excel 函数、单元格引用和区域引用;无法安全求值时会阻止输入。"
|
|
26959
|
+
})
|
|
26960
|
+
]
|
|
26961
|
+
})
|
|
26962
|
+
]
|
|
26963
|
+
});
|
|
26964
|
+
}
|
|
26903
26965
|
function SpreadsheetDataValidationBoundaryFields({ error, onTouched, onValueChange, type, type2, value1, value2 }) {
|
|
26904
26966
|
const needsSecond = spreadsheetDataValidationNeedsSecondValue(type2);
|
|
26905
26967
|
const inputMode = 'date' === type ? 'text' : 'decimal';
|
|
@@ -27073,6 +27135,219 @@ function use_spreadsheet_data_validation_spreadsheetDataValidationError(content,
|
|
|
27073
27135
|
value
|
|
27074
27136
|
}) ?? fallback;
|
|
27075
27137
|
}
|
|
27138
|
+
function spreadsheetDataValidationErrorStyle(item) {
|
|
27139
|
+
return 'warning' === item.errorStyle || 'information' === item.errorStyle ? item.errorStyle : 'stop';
|
|
27140
|
+
}
|
|
27141
|
+
function spreadsheetDataValidationInteraction(style) {
|
|
27142
|
+
return 'stop' === style ? 'notice' : 'confirm';
|
|
27143
|
+
}
|
|
27144
|
+
function spreadsheetDataValidationDialogTitle(item) {
|
|
27145
|
+
const customTitle = item.errorTitle?.trim();
|
|
27146
|
+
if (customTitle) return customTitle;
|
|
27147
|
+
switch(spreadsheetDataValidationErrorStyle(item)){
|
|
27148
|
+
case 'warning':
|
|
27149
|
+
return '数据验证警告';
|
|
27150
|
+
case 'information':
|
|
27151
|
+
return '数据验证提示';
|
|
27152
|
+
default:
|
|
27153
|
+
return '数据验证错误';
|
|
27154
|
+
}
|
|
27155
|
+
}
|
|
27156
|
+
function spreadsheetDataValidationDialogDescription(failureText, value, item) {
|
|
27157
|
+
const customTitle = item?.errorTitle?.trim();
|
|
27158
|
+
const customMessage = item?.errorMessage?.trim();
|
|
27159
|
+
let message = customMessage || failureText.trim();
|
|
27160
|
+
if (customTitle && message.startsWith(`${customTitle}\n`)) message = message.slice(customTitle.length + 1).trim();
|
|
27161
|
+
message ||= '当前输入不符合此单元格的数据验证规则。';
|
|
27162
|
+
const valueText = spreadsheetDataValidationValueText(value);
|
|
27163
|
+
return valueText ? `${message}\n\n当前输入:${valueText}` : message;
|
|
27164
|
+
}
|
|
27165
|
+
function spreadsheetDataValidationConfirmLabels(style) {
|
|
27166
|
+
return 'warning' === style ? {
|
|
27167
|
+
cancelLabel: '取消',
|
|
27168
|
+
confirmLabel: '继续输入'
|
|
27169
|
+
} : {
|
|
27170
|
+
cancelLabel: '返回修改',
|
|
27171
|
+
confirmLabel: '保留输入'
|
|
27172
|
+
};
|
|
27173
|
+
}
|
|
27174
|
+
function spreadsheetDataValidationValueText(value) {
|
|
27175
|
+
if ('string' == typeof value) return value.trim();
|
|
27176
|
+
if ('number' == typeof value || 'boolean' == typeof value) return String(value);
|
|
27177
|
+
return '';
|
|
27178
|
+
}
|
|
27179
|
+
function cloneSpreadsheetDataValidationItem(item) {
|
|
27180
|
+
return {
|
|
27181
|
+
...item,
|
|
27182
|
+
errorTitle: item.errorTitle,
|
|
27183
|
+
errorMessage: item.errorMessage,
|
|
27184
|
+
hintTitle: item.hintTitle
|
|
27185
|
+
};
|
|
27186
|
+
}
|
|
27187
|
+
const MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH = 255;
|
|
27188
|
+
const MAX_SPREADSHEET_CUSTOM_VALIDATION_REFERENCED_CELLS = 1024;
|
|
27189
|
+
function evaluateSpreadsheetCustomValidation(content, sheetId, row, column, item, value) {
|
|
27190
|
+
const sheet = content.sheets.find((candidate)=>candidate.id === sheetId);
|
|
27191
|
+
if (!sheet) return unsupportedCustomValidation('找不到自定义公式所在的工作表。');
|
|
27192
|
+
const proposed = spreadsheetCustomValidationValue(value);
|
|
27193
|
+
const normalizedProposed = spreadsheetCustomValidationBlank(proposed) ? '' : proposed;
|
|
27194
|
+
if (false !== item.allowBlank && '' === normalizedProposed) return {
|
|
27195
|
+
supported: true,
|
|
27196
|
+
valid: true
|
|
27197
|
+
};
|
|
27198
|
+
const formula = item.value1.trim().replace(/^=/, '').trim();
|
|
27199
|
+
if (!formula) return unsupportedCustomValidation('自定义公式为空。');
|
|
27200
|
+
if (Array.from(formula).length > MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH) return unsupportedCustomValidation('自定义公式超过 255 个字符。');
|
|
27201
|
+
const ranges = parseSpreadsheetCellRanges(item.rangeTxt);
|
|
27202
|
+
const anchor = spreadsheetCustomValidationAnchor(ranges, row, column);
|
|
27203
|
+
const evaluator = new SpreadsheetCustomFormulaEvaluator(content, sheet, row, column, anchor, normalizedProposed);
|
|
27204
|
+
return evaluator.evaluate(formula);
|
|
27205
|
+
}
|
|
27206
|
+
class SpreadsheetCustomFormulaEvaluator {
|
|
27207
|
+
content;
|
|
27208
|
+
currentSheet;
|
|
27209
|
+
targetRow;
|
|
27210
|
+
targetColumn;
|
|
27211
|
+
anchor;
|
|
27212
|
+
proposedValue;
|
|
27213
|
+
sheetsByName = new Map();
|
|
27214
|
+
referencedCells = 0;
|
|
27215
|
+
unsupportedMessage;
|
|
27216
|
+
constructor(content, currentSheet, targetRow, targetColumn, anchor, proposedValue){
|
|
27217
|
+
this.content = content;
|
|
27218
|
+
this.currentSheet = currentSheet;
|
|
27219
|
+
this.targetRow = targetRow;
|
|
27220
|
+
this.targetColumn = targetColumn;
|
|
27221
|
+
this.anchor = anchor;
|
|
27222
|
+
this.proposedValue = proposedValue;
|
|
27223
|
+
for (const sheet of content.sheets)this.sheetsByName.set(sheet.name.trim().toLocaleLowerCase(), sheet);
|
|
27224
|
+
}
|
|
27225
|
+
evaluate(formula) {
|
|
27226
|
+
const allowedFunctions = new Set(SUPPORTED_FORMULAS.map((name)=>name.toUpperCase()));
|
|
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
|
+
}
|
|
27319
|
+
}
|
|
27320
|
+
function spreadsheetCustomValidationAnchor(ranges, row, column) {
|
|
27321
|
+
const range = ranges?.find((candidate)=>spreadsheetCellRangeContains(candidate, row, column));
|
|
27322
|
+
return {
|
|
27323
|
+
row: range?.row[0] ?? row,
|
|
27324
|
+
column: range?.column[0] ?? column
|
|
27325
|
+
};
|
|
27326
|
+
}
|
|
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
|
+
function spreadsheetCustomValidationValue(value) {
|
|
27332
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) return value;
|
|
27333
|
+
if ('v' in value) return value.v;
|
|
27334
|
+
return value;
|
|
27335
|
+
}
|
|
27336
|
+
function spreadsheetCustomValidationBlank(value) {
|
|
27337
|
+
return null == value || 'string' == typeof value && '' === value.trim();
|
|
27338
|
+
}
|
|
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
|
+
function unsupportedCustomValidation(message) {
|
|
27345
|
+
return {
|
|
27346
|
+
supported: false,
|
|
27347
|
+
valid: false,
|
|
27348
|
+
message
|
|
27349
|
+
};
|
|
27350
|
+
}
|
|
27076
27351
|
const spreadsheetCellFormatAttributes = [
|
|
27077
27352
|
'bl',
|
|
27078
27353
|
'it',
|
|
@@ -29935,6 +30210,49 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
29935
30210
|
const activeSheetId = viewActiveSheetId && content.sheets.some((sheet)=>sheet.id === viewActiveSheetId && 1 !== sheet.hide) ? viewActiveSheetId : contentActiveSheetId;
|
|
29936
30211
|
const activeSheetIdRef = useRef(activeSheetId);
|
|
29937
30212
|
const focusedSheetIdRef = useRef(null);
|
|
30213
|
+
const pendingDataValidationRef = useRef(null);
|
|
30214
|
+
const [pendingDataValidation, setPendingDataValidation] = useState(null);
|
|
30215
|
+
const queueDataValidationEdit = useCallback((row, column, value, item, failureText, sheetId)=>{
|
|
30216
|
+
const requestSheetId = sheetId ?? activeSheetIdRef.current;
|
|
30217
|
+
if (!requestSheetId || !item || 'object' != typeof item || pendingDataValidationRef.current) return pendingDataValidationRef.current ? false : void 0;
|
|
30218
|
+
const validationItem = item;
|
|
30219
|
+
if ('custom' === validationItem.type) {
|
|
30220
|
+
const evaluation = evaluateSpreadsheetCustomValidation(contentRef.current, requestSheetId, row, column, validationItem, value);
|
|
30221
|
+
if (evaluation.supported && evaluation.valid) return true;
|
|
30222
|
+
const displayItem = cloneSpreadsheetDataValidationItem(validationItem);
|
|
30223
|
+
if (!displayItem.errorMessage?.trim() && evaluation.message) displayItem.errorMessage = evaluation.message;
|
|
30224
|
+
const style = spreadsheetDataValidationErrorStyle(displayItem);
|
|
30225
|
+
const request = {
|
|
30226
|
+
column,
|
|
30227
|
+
failureText: evaluation.message ?? failureText,
|
|
30228
|
+
interaction: spreadsheetDataValidationInteraction(style),
|
|
30229
|
+
item: displayItem,
|
|
30230
|
+
row,
|
|
30231
|
+
sheetId: requestSheetId,
|
|
30232
|
+
value
|
|
30233
|
+
};
|
|
30234
|
+
pendingDataValidationRef.current = request;
|
|
30235
|
+
queueMicrotask(()=>{
|
|
30236
|
+
if (pendingDataValidationRef.current === request) setPendingDataValidation(request);
|
|
30237
|
+
});
|
|
30238
|
+
return false;
|
|
30239
|
+
}
|
|
30240
|
+
const style = spreadsheetDataValidationErrorStyle(validationItem);
|
|
30241
|
+
const request = {
|
|
30242
|
+
column,
|
|
30243
|
+
failureText,
|
|
30244
|
+
interaction: spreadsheetDataValidationInteraction(style),
|
|
30245
|
+
item: cloneSpreadsheetDataValidationItem(validationItem),
|
|
30246
|
+
row,
|
|
30247
|
+
sheetId: requestSheetId,
|
|
30248
|
+
value
|
|
30249
|
+
};
|
|
30250
|
+
pendingDataValidationRef.current = request;
|
|
30251
|
+
queueMicrotask(()=>{
|
|
30252
|
+
if (pendingDataValidationRef.current === request) setPendingDataValidation(request);
|
|
30253
|
+
});
|
|
30254
|
+
return false;
|
|
30255
|
+
}, []);
|
|
29938
30256
|
contentRef.current = materializedContent;
|
|
29939
30257
|
previewRef.current = preview;
|
|
29940
30258
|
const controlledHistory = useOfficeHistory({
|
|
@@ -29992,6 +30310,100 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
29992
30310
|
}, [
|
|
29993
30311
|
activeSheetId
|
|
29994
30312
|
]);
|
|
30313
|
+
useEffect(()=>{
|
|
30314
|
+
const request = pendingDataValidation;
|
|
30315
|
+
if (!request) return;
|
|
30316
|
+
let cancelled = false;
|
|
30317
|
+
const style = spreadsheetDataValidationErrorStyle(request.item);
|
|
30318
|
+
const description = spreadsheetDataValidationDialogDescription(request.failureText, request.value, request.item);
|
|
30319
|
+
const restoreValidationSelection = ()=>{
|
|
30320
|
+
const selection = {
|
|
30321
|
+
row: [
|
|
30322
|
+
request.row,
|
|
30323
|
+
request.row
|
|
30324
|
+
],
|
|
30325
|
+
column: [
|
|
30326
|
+
request.column,
|
|
30327
|
+
request.column
|
|
30328
|
+
],
|
|
30329
|
+
row_focus: request.row,
|
|
30330
|
+
column_focus: request.column
|
|
30331
|
+
};
|
|
30332
|
+
const nextSelection = {
|
|
30333
|
+
sheetId: request.sheetId,
|
|
30334
|
+
selection
|
|
30335
|
+
};
|
|
30336
|
+
selectionStateRef.current.current = nextSelection;
|
|
30337
|
+
selectionStateRef.current.requested = {
|
|
30338
|
+
sheetId: request.sheetId,
|
|
30339
|
+
selection: {
|
|
30340
|
+
...selection,
|
|
30341
|
+
row: [
|
|
30342
|
+
...selection.row
|
|
30343
|
+
],
|
|
30344
|
+
column: [
|
|
30345
|
+
...selection.column
|
|
30346
|
+
]
|
|
30347
|
+
}
|
|
30348
|
+
};
|
|
30349
|
+
setSelectionState(nextSelection);
|
|
30350
|
+
try {
|
|
30351
|
+
workbookRef.current?.setSelection([
|
|
30352
|
+
{
|
|
30353
|
+
row: selection.row,
|
|
30354
|
+
column: selection.column
|
|
30355
|
+
}
|
|
30356
|
+
], {
|
|
30357
|
+
id: request.sheetId
|
|
30358
|
+
});
|
|
30359
|
+
} catch {}
|
|
30360
|
+
};
|
|
30361
|
+
const finish = (accepted)=>{
|
|
30362
|
+
if (cancelled || pendingDataValidationRef.current !== request) return;
|
|
30363
|
+
pendingDataValidationRef.current = null;
|
|
30364
|
+
setPendingDataValidation(null);
|
|
30365
|
+
if (accepted) try {
|
|
30366
|
+
workbookRef.current?.setCellValue(request.row, request.column, request.value, {
|
|
30367
|
+
id: request.sheetId,
|
|
30368
|
+
skipDataValidation: true
|
|
30369
|
+
});
|
|
30370
|
+
} catch {
|
|
30371
|
+
showToast('无法保留此输入,请重试。', 'error');
|
|
30372
|
+
}
|
|
30373
|
+
restoreValidationSelection();
|
|
30374
|
+
};
|
|
30375
|
+
const showValidationDialog = async ()=>{
|
|
30376
|
+
if ('notice' === request.interaction) {
|
|
30377
|
+
await officeDialog.notice({
|
|
30378
|
+
title: spreadsheetDataValidationDialogTitle(request.item),
|
|
30379
|
+
description,
|
|
30380
|
+
confirmLabel: '知道了',
|
|
30381
|
+
restoreFocusTarget: getSpreadsheetDialogGridFocusTarget
|
|
30382
|
+
});
|
|
30383
|
+
finish(false);
|
|
30384
|
+
return;
|
|
30385
|
+
}
|
|
30386
|
+
const labels = spreadsheetDataValidationConfirmLabels(style);
|
|
30387
|
+
const accepted = await officeDialog.confirm({
|
|
30388
|
+
title: spreadsheetDataValidationDialogTitle(request.item),
|
|
30389
|
+
description,
|
|
30390
|
+
confirmLabel: labels.confirmLabel,
|
|
30391
|
+
cancelLabel: labels.cancelLabel,
|
|
30392
|
+
confirmTone: 'warning' === style ? 'primary' : 'secondary',
|
|
30393
|
+
restoreFocusTarget: getSpreadsheetDialogGridFocusTarget
|
|
30394
|
+
});
|
|
30395
|
+
finish(accepted);
|
|
30396
|
+
};
|
|
30397
|
+
showValidationDialog();
|
|
30398
|
+
return ()=>{
|
|
30399
|
+
cancelled = true;
|
|
30400
|
+
};
|
|
30401
|
+
}, [
|
|
30402
|
+
getSpreadsheetDialogGridFocusTarget,
|
|
30403
|
+
officeDialog.confirm,
|
|
30404
|
+
officeDialog.notice,
|
|
30405
|
+
pendingDataValidation
|
|
30406
|
+
]);
|
|
29995
30407
|
useEffect(()=>{
|
|
29996
30408
|
if (preview || !activeSheetId || focusedSheetIdRef.current === activeSheetId) return;
|
|
29997
30409
|
const initialFocus = null === focusedSheetIdRef.current;
|
|
@@ -30077,6 +30489,7 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
30077
30489
|
const sheet = contentRef.current.sheets.find((candidate)=>candidate.id === activeSheetIdRef.current);
|
|
30078
30490
|
return !sheet || !spreadsheetPivotOutputContains(sheet, row, column);
|
|
30079
30491
|
},
|
|
30492
|
+
beforeDataValidation: queueDataValidationEdit,
|
|
30080
30493
|
beforePaste: (selections)=>{
|
|
30081
30494
|
const sheet = contentRef.current.sheets.find((candidate)=>candidate.id === activeSheetIdRef.current);
|
|
30082
30495
|
if (!sheet) return true;
|
|
@@ -30116,7 +30529,8 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
30116
30529
|
}
|
|
30117
30530
|
}
|
|
30118
30531
|
}), [
|
|
30119
|
-
collaborationView
|
|
30532
|
+
collaborationView,
|
|
30533
|
+
queueDataValidationEdit
|
|
30120
30534
|
]);
|
|
30121
30535
|
const conditionalFormatKey = useMemo(()=>content.sheets.map((sheet)=>`${sheet.id}:${JSON.stringify(sheet.luckysheet_conditionformat_save ?? [])}`).join('|'), [
|
|
30122
30536
|
content.sheets
|
package/dist/4121.js
CHANGED
|
@@ -2902,7 +2902,7 @@ const WORK_TEMPLATES = [
|
|
|
2902
2902
|
id: 'data-validation',
|
|
2903
2903
|
kind: 'spreadsheet',
|
|
2904
2904
|
name: '数据验证',
|
|
2905
|
-
description: '
|
|
2905
|
+
description: '自定义公式、下拉列表、输入提示与错误警告',
|
|
2906
2906
|
accent: '#13795b'
|
|
2907
2907
|
},
|
|
2908
2908
|
{
|
|
@@ -3566,6 +3566,32 @@ function dataValidationTemplateSheets() {
|
|
|
3566
3566
|
hintTitle: 'Priority',
|
|
3567
3567
|
hintValue: '1 is highest priority; 5 is lowest.'
|
|
3568
3568
|
})
|
|
3569
|
+
},
|
|
3570
|
+
{
|
|
3571
|
+
ranges: [
|
|
3572
|
+
{
|
|
3573
|
+
row: [
|
|
3574
|
+
1,
|
|
3575
|
+
5
|
|
3576
|
+
],
|
|
3577
|
+
column: [
|
|
3578
|
+
4,
|
|
3579
|
+
4
|
|
3580
|
+
]
|
|
3581
|
+
}
|
|
3582
|
+
],
|
|
3583
|
+
item: dataValidationTemplateItem({
|
|
3584
|
+
type: 'custom',
|
|
3585
|
+
rangeTxt: 'E2:E6',
|
|
3586
|
+
value1: 'LEN(E2)>0',
|
|
3587
|
+
allowBlank: false,
|
|
3588
|
+
errorStyle: 'stop',
|
|
3589
|
+
errorTitle: 'Owner required',
|
|
3590
|
+
errorMessage: 'Enter an owner for every task.',
|
|
3591
|
+
hintShow: true,
|
|
3592
|
+
hintTitle: 'Owner',
|
|
3593
|
+
hintValue: 'The custom formula checks that this cell is not blank.'
|
|
3594
|
+
})
|
|
3569
3595
|
}
|
|
3570
3596
|
],
|
|
3571
3597
|
luckysheet_select_save: [
|
package/dist/core.js
CHANGED
|
@@ -3325,6 +3325,7 @@ function parseListFormula(value) {
|
|
|
3325
3325
|
}
|
|
3326
3326
|
function fortuneValidationType(value) {
|
|
3327
3327
|
const types = {
|
|
3328
|
+
custom: 'custom',
|
|
3328
3329
|
list: 'dropdown',
|
|
3329
3330
|
whole: 'number_integer',
|
|
3330
3331
|
decimal: 'number',
|
|
@@ -3335,6 +3336,7 @@ function fortuneValidationType(value) {
|
|
|
3335
3336
|
}
|
|
3336
3337
|
function xlsxValidationType(value) {
|
|
3337
3338
|
const types = {
|
|
3339
|
+
custom: 'custom',
|
|
3338
3340
|
dropdown: 'list',
|
|
3339
3341
|
number: 'decimal',
|
|
3340
3342
|
number_integer: 'whole',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { WorkSpreadsheetContent, WorkSpreadsheetDataValidationItem } from '../work-types';
|
|
2
|
+
/** OOXML limits a data-validation formula to 255 characters. */
|
|
3
|
+
export declare const MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH = 255;
|
|
4
|
+
/** Keep synchronous browser validation bounded even for a hostile workbook. */
|
|
5
|
+
export declare const MAX_SPREADSHEET_CUSTOM_VALIDATION_REFERENCED_CELLS = 1024;
|
|
6
|
+
export interface SpreadsheetCustomValidationResult {
|
|
7
|
+
message?: string;
|
|
8
|
+
supported: boolean;
|
|
9
|
+
valid: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Evaluates a local Excel custom-validation formula synchronously.
|
|
13
|
+
*
|
|
14
|
+
* The editor deliberately evaluates against cached cell values. Formula
|
|
15
|
+
* recalculation remains a separate kernel concern; a formula cell without a
|
|
16
|
+
* cached value therefore fails closed instead of silently accepting input.
|
|
17
|
+
*/
|
|
18
|
+
export declare function evaluateSpreadsheetCustomValidation(content: WorkSpreadsheetContent, sheetId: string, row: number, column: number, item: WorkSpreadsheetDataValidationItem, value: unknown): SpreadsheetCustomValidationResult;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WorkSpreadsheetDataValidationErrorStyle, WorkSpreadsheetDataValidationItem } from '../work-types';
|
|
2
|
+
export type SpreadsheetDataValidationInteraction = 'notice' | 'confirm';
|
|
3
|
+
export interface SpreadsheetDataValidationEditRequest {
|
|
4
|
+
column: number;
|
|
5
|
+
failureText: string;
|
|
6
|
+
interaction: SpreadsheetDataValidationInteraction;
|
|
7
|
+
item: WorkSpreadsheetDataValidationItem;
|
|
8
|
+
row: number;
|
|
9
|
+
sheetId: string;
|
|
10
|
+
value: unknown;
|
|
11
|
+
}
|
|
12
|
+
type SpreadsheetDataValidationMessageSource = Pick<WorkSpreadsheetDataValidationItem, 'errorMessage' | 'errorTitle'>;
|
|
13
|
+
export declare function spreadsheetDataValidationErrorStyle(item: Pick<WorkSpreadsheetDataValidationItem, 'errorStyle'>): WorkSpreadsheetDataValidationErrorStyle;
|
|
14
|
+
export declare function spreadsheetDataValidationInteraction(style: WorkSpreadsheetDataValidationErrorStyle): SpreadsheetDataValidationInteraction;
|
|
15
|
+
export declare function spreadsheetDataValidationDialogTitle(item: Pick<WorkSpreadsheetDataValidationItem, 'errorStyle' | 'errorTitle'>): string;
|
|
16
|
+
export declare function spreadsheetDataValidationDialogDescription(failureText: string, value: unknown, item?: SpreadsheetDataValidationMessageSource): string;
|
|
17
|
+
export declare function spreadsheetDataValidationConfirmLabels(style: WorkSpreadsheetDataValidationErrorStyle): {
|
|
18
|
+
cancelLabel: string;
|
|
19
|
+
confirmLabel: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function spreadsheetDataValidationValueText(value: unknown): string;
|
|
22
|
+
export declare function cloneSpreadsheetDataValidationItem(item: WorkSpreadsheetDataValidationItem): WorkSpreadsheetDataValidationItem;
|
|
23
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WorkSpreadsheetContent, WorkSpreadsheetDataValidationErrorStyle, WorkSpreadsheetDataValidationItem, WorkSpreadsheetSheet } from '../work-types';
|
|
2
2
|
import { type SpreadsheetCellRange } from './spreadsheet-cell-range';
|
|
3
3
|
export declare const MAX_SPREADSHEET_DATA_VALIDATION_CELLS = 10000;
|
|
4
|
-
export type SpreadsheetDataValidationType = 'date' | 'dropdown' | 'number' | 'number_integer' | 'text_length';
|
|
4
|
+
export type SpreadsheetDataValidationType = 'custom' | 'date' | 'dropdown' | 'number' | 'number_integer' | 'text_length';
|
|
5
5
|
export type SpreadsheetDataValidationOperator = 'between' | 'equal' | 'greaterOrEqualTo' | 'lessThan' | 'lessThanOrEqualTo' | 'moreThanThe' | 'noEarlierThan' | 'noLaterThan' | 'notBetween' | 'notEqualTo' | 'earlierThan' | 'laterThan';
|
|
6
6
|
export interface SpreadsheetDataValidationDialogValue {
|
|
7
7
|
allowBlank: boolean;
|
|
@@ -46,7 +46,7 @@ export type SpreadsheetDataValidationResult = {
|
|
|
46
46
|
message: string;
|
|
47
47
|
ok: false;
|
|
48
48
|
};
|
|
49
|
-
export type SpreadsheetDataValidationErrorCode = 'invalid-date' | 'invalid-list-source' | 'invalid-number' | 'invalid-operator' | 'invalid-range' | 'invalid-text-length' | 'missing-value' | 'multiple-list-columns' | 'out-of-bounds' | 'protected-range' | 'range-too-large' | 'sheet-not-found' | 'value-order';
|
|
49
|
+
export type SpreadsheetDataValidationErrorCode = 'invalid-custom-formula' | 'invalid-date' | 'invalid-list-source' | 'invalid-number' | 'invalid-operator' | 'invalid-range' | 'invalid-text-length' | 'missing-value' | 'multiple-list-columns' | 'out-of-bounds' | 'protected-range' | 'range-too-large' | 'sheet-not-found' | 'value-order';
|
|
50
50
|
export declare function spreadsheetDataValidationOperators(type: SpreadsheetDataValidationType): readonly SpreadsheetDataValidationOperator[];
|
|
51
51
|
export declare function createSpreadsheetDataValidationDialogSource(content: WorkSpreadsheetContent, target: SpreadsheetDataValidationTarget): SpreadsheetDataValidationDialogSource | null;
|
|
52
52
|
export declare function validateSpreadsheetDataValidationRequest(content: WorkSpreadsheetContent, request: SpreadsheetDataValidationRequest): SpreadsheetDataValidationResult;
|
|
@@ -2,6 +2,7 @@ import type { WorkSpreadsheetDataValidationErrorStyle } from './work-types';
|
|
|
2
2
|
export declare const SPREADSHEET_DATA_VALIDATION_TITLE_LIMIT = 32;
|
|
3
3
|
export declare const SPREADSHEET_DATA_VALIDATION_HINT_LIMIT = 255;
|
|
4
4
|
export declare const SPREADSHEET_DATA_VALIDATION_ERROR_LIMIT = 225;
|
|
5
|
+
export declare const SPREADSHEET_DATA_VALIDATION_FORMULA_LIMIT = 255;
|
|
5
6
|
export declare const SPREADSHEET_DATA_VALIDATION_ERROR_STYLES: readonly ["stop", "warning", "information"];
|
|
6
7
|
export declare function normalizeSpreadsheetDataValidationErrorStyle(value: unknown): WorkSpreadsheetDataValidationErrorStyle;
|
|
7
8
|
export declare function boundedSpreadsheetDataValidationText(value: unknown, limit: number): string;
|
package/dist/office-kernel.wasm
CHANGED
|
Binary file
|
package/dist/styles.css
CHANGED
|
@@ -17939,6 +17939,27 @@ sub [data-office-character-position-half-points], sup [data-office-character-pos
|
|
|
17939
17939
|
display: grid;
|
|
17940
17940
|
}
|
|
17941
17941
|
|
|
17942
|
+
.work-spreadsheet-data-validation-custom-formula {
|
|
17943
|
+
gap: 8px;
|
|
17944
|
+
display: grid;
|
|
17945
|
+
}
|
|
17946
|
+
|
|
17947
|
+
.work-spreadsheet-data-validation-formula-note {
|
|
17948
|
+
color: var(--a3s-muted);
|
|
17949
|
+
align-items: flex-start;
|
|
17950
|
+
gap: 6px;
|
|
17951
|
+
margin: -2px 0 0;
|
|
17952
|
+
font-size: 10px;
|
|
17953
|
+
line-height: 1.45;
|
|
17954
|
+
display: flex;
|
|
17955
|
+
}
|
|
17956
|
+
|
|
17957
|
+
.work-spreadsheet-data-validation-formula-note svg {
|
|
17958
|
+
color: var(--work-spreadsheet-accent, #159469);
|
|
17959
|
+
flex: none;
|
|
17960
|
+
margin-top: 1px;
|
|
17961
|
+
}
|
|
17962
|
+
|
|
17942
17963
|
.work-spreadsheet-data-validation-inline-error {
|
|
17943
17964
|
color: var(--a3s-danger, #c23535);
|
|
17944
17965
|
grid-column: 1 / -1;
|