@a3s-lab/office 0.42.0 → 0.43.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 +15 -0
- package/dist/0~spreadsheet-editor.js +347 -181
- package/dist/4121.js +253 -0
- package/dist/internal/features/work/editors/spreadsheet-conditional-format-model.d.ts +6 -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/office-kernel.wasm +0 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -309,6 +309,21 @@ and [CLI reference](./docs/latest/en/cli-reference.md).
|
|
|
309
309
|
|
|
310
310
|
## Current release
|
|
311
311
|
|
|
312
|
+
Version `0.43.0` makes Spreadsheet formula conditional formatting a local,
|
|
313
|
+
editable workflow instead of a file-only preservation path:
|
|
314
|
+
|
|
315
|
+
- **Home → Conditional Formatting → Custom formula** authors rules with
|
|
316
|
+
relative/absolute references, finite cross-sheet references, independent
|
|
317
|
+
text/fill colors, ordered precedence, and Stop-if-true behavior. Imported
|
|
318
|
+
XLSX expression rules reopen in the same editor.
|
|
319
|
+
- The shared synchronous evaluator reads only cached workbook values, caps
|
|
320
|
+
formulas at 255 Unicode characters and each decision at 1,024 cells, scans
|
|
321
|
+
bounded blank ranges without densifying the workbook, and fails closed for
|
|
322
|
+
external, whole-row/column, missing-sheet, or uncached-formula references.
|
|
323
|
+
- Native XLSX differential styles, `sqref`, priorities, and formulas round-trip
|
|
324
|
+
through export/reopen. The public **新建 → 公式条件格式** template shows a
|
|
325
|
+
blocking rule and a cross-sheet threshold without any remote service.
|
|
326
|
+
|
|
312
327
|
Version `0.42.0` extends Spreadsheet data validation with a local custom-formula
|
|
313
328
|
workflow that remains bounded, inspectable, and native-file compatible:
|
|
314
329
|
|
|
@@ -3,14 +3,14 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
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
|
-
import { convertSpanToShareString, genarate, indexToColumnChar, update as core_update } from "@fortune-sheet/core";
|
|
7
6
|
import { Parser, SUPPORTED_FORMULAS } from "@fortune-sheet/formula-parser";
|
|
7
|
+
import { convertSpanToShareString, genarate, indexToColumnChar, update as core_update } from "@fortune-sheet/core";
|
|
8
|
+
import { useOfficeTaskPaneEscape, OfficeCheckbox, useOfficeDialog, CommittedOfficeNumberField, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown, normalizeFormulaForFortuneParser } from "./0~7048.js";
|
|
8
9
|
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
10
|
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
11
|
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";
|
|
11
12
|
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
13
|
import { isOfficeKernelSpreadsheetError, normalizeCssColor, OFFICE_KERNEL_SPREADSHEET_MAX_ROWS, createOfficeId as createWorkId } from "./4121.js";
|
|
13
|
-
import { useOfficeTaskPaneEscape, OfficeCheckbox, CommittedOfficeNumberField, useOfficeDialog, useOfficeTaskPaneModal, createOfficeKernelClient, officeFontFamilies, CollectionState, OfficeNumberField, handleOfficeTaskPaneKeyDown, normalizeFormulaForFortuneParser } from "./0~7048.js";
|
|
14
14
|
import { isOfficeShortcutBlocked, OfficeColorPicker } from "./0~3635.js";
|
|
15
15
|
import { createOfficeEditorExtension, useOfficeEditorRuntime, useOfficeEditorKeyboardShortcuts } from "./0~3557.js";
|
|
16
16
|
import { showToast } from "./6489.js";
|
|
@@ -159,30 +159,187 @@ function displayLength(valueRatio, minLength, maxLength) {
|
|
|
159
159
|
const normalized = Math.max(0, Math.min(1, valueRatio));
|
|
160
160
|
return minLength + (maxLength - minLength) * normalized;
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
const MAX_SPREADSHEET_LOCAL_FORMULA_LENGTH = 255;
|
|
163
|
+
const work_spreadsheet_local_formula_MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS = 1024;
|
|
164
|
+
const MAX_SHEET_ROW = 1048576;
|
|
165
|
+
const MAX_SHEET_COLUMN = 16384;
|
|
166
|
+
const LOCAL_FORMULA_FUNCTIONS = new Set(SUPPORTED_FORMULAS.map((name)=>name.toUpperCase()));
|
|
167
|
+
function evaluateSpreadsheetLocalFormula(formula, options) {
|
|
168
|
+
const source = formula.trim().replace(/^=/, '').trim();
|
|
169
|
+
if (!source) return unsupportedLocalFormula('公式为空。');
|
|
170
|
+
if (Array.from(source).length > MAX_SPREADSHEET_LOCAL_FORMULA_LENGTH) return unsupportedLocalFormula('公式超过 255 个字符。');
|
|
171
|
+
const currentSheet = options.sheets.find((candidate)=>candidate.id === options.sheetId);
|
|
172
|
+
if (!currentSheet) return unsupportedLocalFormula('找不到公式所在的工作表。');
|
|
173
|
+
const evaluator = new SpreadsheetLocalFormulaEvaluator(options, currentSheet);
|
|
174
|
+
return evaluator.evaluate(source);
|
|
175
|
+
}
|
|
176
|
+
function spreadsheetLocalFormulaBoolean(value) {
|
|
177
|
+
if ('boolean' == typeof value) return value;
|
|
178
|
+
if ('number' == typeof value) return Number.isFinite(value) && 0 !== value;
|
|
179
|
+
return 'string' == typeof value && 'TRUE' === value.trim().toUpperCase();
|
|
180
|
+
}
|
|
181
|
+
class SpreadsheetLocalFormulaEvaluator {
|
|
182
|
+
options;
|
|
183
|
+
currentSheet;
|
|
184
|
+
sheetsByName = new Map();
|
|
185
|
+
anchor;
|
|
186
|
+
referencedCells = 0;
|
|
187
|
+
unsupportedMessage;
|
|
188
|
+
celldataBySheet = new Map();
|
|
189
|
+
constructor(options, currentSheet){
|
|
190
|
+
this.options = options;
|
|
191
|
+
this.currentSheet = currentSheet;
|
|
192
|
+
this.anchor = {
|
|
193
|
+
row: options.anchorRow ?? options.row,
|
|
194
|
+
column: options.anchorColumn ?? options.column
|
|
195
|
+
};
|
|
196
|
+
for (const sheet of options.sheets)this.sheetsByName.set(sheet.name.trim().toLocaleLowerCase(), sheet);
|
|
197
|
+
}
|
|
198
|
+
evaluate(formula) {
|
|
199
|
+
const parser = new Parser();
|
|
200
|
+
parser.setFunction('ROW', (parameters)=>parameters.length ? null : this.options.row + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : this.options.column + 1);
|
|
201
|
+
let unsupportedFunction;
|
|
202
|
+
parser.on('callFunction', (name)=>{
|
|
203
|
+
const normalized = name.toUpperCase();
|
|
204
|
+
if (!LOCAL_FORMULA_FUNCTIONS.has(normalized)) unsupportedFunction ??= normalized;
|
|
205
|
+
});
|
|
206
|
+
parser.on('callCellValue', (coordinate, _parserOptions, done)=>{
|
|
207
|
+
const resolved = this.resolveCoordinate(this.currentSheet, coordinate);
|
|
208
|
+
if (!resolved) return void done(null);
|
|
209
|
+
done(this.readCell(resolved));
|
|
210
|
+
});
|
|
211
|
+
parser.on('callRangeValue', (start, end, _parserOptions, done)=>{
|
|
212
|
+
done(this.readRange(start, end));
|
|
213
|
+
});
|
|
214
|
+
const parsed = parser.parse(normalizeFormulaForFortuneParser(formula), {
|
|
215
|
+
sheetId: this.currentSheet.id
|
|
216
|
+
});
|
|
217
|
+
if (unsupportedFunction) return unsupportedLocalFormula(`公式函数“${unsupportedFunction}”无法在浏览器本地求值。`);
|
|
218
|
+
if (this.unsupportedMessage) return unsupportedLocalFormula(this.unsupportedMessage);
|
|
219
|
+
if (parsed.error || void 0 === parsed.result) return unsupportedLocalFormula('公式无法求值,请检查函数、引用和括号。');
|
|
220
|
+
return {
|
|
221
|
+
supported: true,
|
|
222
|
+
value: parsed.result
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
resolveCoordinate(fallbackSheet, coordinate) {
|
|
226
|
+
const sheet = coordinate.sheetName ? this.sheetsByName.get(coordinate.sheetName.trim().toLocaleLowerCase()) : fallbackSheet;
|
|
227
|
+
if (!sheet) {
|
|
228
|
+
this.unsupportedMessage = '公式引用了不存在的工作表。';
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
if (coordinate.row.index < 0 || coordinate.column.index < 0) {
|
|
232
|
+
this.unsupportedMessage = '公式暂不支持整行或整列引用,请改用有限区域。';
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
const row = coordinate.row.isAbsolute ? coordinate.row.index : this.options.row + (coordinate.row.index - this.anchor.row);
|
|
236
|
+
const column = coordinate.column.isAbsolute ? coordinate.column.index : this.options.column + (coordinate.column.index - this.anchor.column);
|
|
237
|
+
if (row < 0 || column < 0 || row >= MAX_SHEET_ROW || column >= MAX_SHEET_COLUMN) {
|
|
238
|
+
this.unsupportedMessage = '公式引用超出了工作表边界。';
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
sheet,
|
|
243
|
+
row,
|
|
244
|
+
column
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
readRange(start, end) {
|
|
248
|
+
const startCoordinate = this.resolveCoordinate(this.currentSheet, start);
|
|
249
|
+
const rangeSheet = start.sheetName ? this.sheetsByName.get(start.sheetName.trim().toLocaleLowerCase()) : this.currentSheet;
|
|
250
|
+
const endCoordinate = rangeSheet ? this.resolveCoordinate(rangeSheet, end) : null;
|
|
251
|
+
if (!startCoordinate || !endCoordinate || startCoordinate.sheet.id !== endCoordinate.sheet.id) {
|
|
252
|
+
this.unsupportedMessage ??= '公式区域引用必须位于同一工作表。';
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
const rowStart = Math.min(startCoordinate.row, endCoordinate.row);
|
|
256
|
+
const rowEnd = Math.max(startCoordinate.row, endCoordinate.row);
|
|
257
|
+
const columnStart = Math.min(startCoordinate.column, endCoordinate.column);
|
|
258
|
+
const columnEnd = Math.max(startCoordinate.column, endCoordinate.column);
|
|
259
|
+
const area = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1);
|
|
260
|
+
if (area <= 0 || area > work_spreadsheet_local_formula_MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS - this.referencedCells) {
|
|
261
|
+
this.unsupportedMessage = '公式一次最多读取 1,024 个单元格。';
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
const values = [];
|
|
265
|
+
for(let row = rowStart; row <= rowEnd; row += 1){
|
|
266
|
+
const valuesRow = [];
|
|
267
|
+
for(let column = columnStart; column <= columnEnd; column += 1)valuesRow.push(this.readCell({
|
|
268
|
+
sheet: startCoordinate.sheet,
|
|
269
|
+
row,
|
|
270
|
+
column
|
|
271
|
+
}));
|
|
272
|
+
values.push(valuesRow);
|
|
273
|
+
}
|
|
274
|
+
return values;
|
|
275
|
+
}
|
|
276
|
+
readCell(coordinate) {
|
|
277
|
+
this.referencedCells += 1;
|
|
278
|
+
if (this.referencedCells > work_spreadsheet_local_formula_MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS) {
|
|
279
|
+
this.unsupportedMessage = '公式一次最多读取 1,024 个单元格。';
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
if (this.options.useProposedValue && coordinate.sheet.id === this.currentSheet.id && coordinate.row === this.options.row && coordinate.column === this.options.column) return this.options.proposedValue;
|
|
283
|
+
const cell = this.cellAt(coordinate.sheet, coordinate.row, coordinate.column);
|
|
284
|
+
if (!cell) return null;
|
|
285
|
+
if ('string' == typeof cell.f && cell.f.length > 0 && void 0 === cell.v) {
|
|
286
|
+
this.unsupportedMessage ??= '公式引用了尚未计算的公式单元格。';
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return cell.v ?? null;
|
|
290
|
+
}
|
|
291
|
+
cellAt(sheet, row, column) {
|
|
292
|
+
if (void 0 !== sheet.data) return sheet.data[row]?.[column] ?? null;
|
|
293
|
+
let cells = this.celldataBySheet.get(sheet.id);
|
|
294
|
+
if (!cells) {
|
|
295
|
+
cells = new Map();
|
|
296
|
+
for (const entry of sheet.celldata ?? []){
|
|
297
|
+
const value = entry.v && 'object' == typeof entry.v && 'v' in entry.v ? entry.v : {
|
|
298
|
+
v: entry.v
|
|
299
|
+
};
|
|
300
|
+
cells.set(`${entry.r}_${entry.c}`, value);
|
|
301
|
+
}
|
|
302
|
+
this.celldataBySheet.set(sheet.id, cells);
|
|
303
|
+
}
|
|
304
|
+
return cells.get(`${row}_${column}`) ?? null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function unsupportedLocalFormula(message) {
|
|
308
|
+
return {
|
|
309
|
+
supported: false,
|
|
310
|
+
value: false,
|
|
311
|
+
message
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const MAX_SPREADSHEET_CONDITIONAL_FORMULA_SCAN_CELLS = 10000;
|
|
315
|
+
function spreadsheetConditionalFormatStyles(sheet, formulaSheets) {
|
|
163
316
|
const styles = new Map();
|
|
164
317
|
const data = sheet.data ?? [];
|
|
165
|
-
|
|
318
|
+
const rules = conditionalRules(sheet);
|
|
319
|
+
if (!data.length && !rules.some(isFormulaConditionalRule)) return styles;
|
|
166
320
|
const blocked = new Set();
|
|
167
|
-
for (const rule of
|
|
321
|
+
for (const rule of rules){
|
|
168
322
|
const matched = new Set();
|
|
169
323
|
if ('colorGradation' === rule.type) applyColorScale(styles, data, rule, blocked, matched);
|
|
170
324
|
else if ('dataBar' === rule.type) applyDataBars(styles, data, rule, blocked, matched);
|
|
171
325
|
else if ('icons' === rule.type) applyIconSet(styles, data, rule, blocked, matched);
|
|
172
|
-
else applyDefaultRule(styles, data, rule, blocked, matched);
|
|
326
|
+
else applyDefaultRule(styles, data, rule, blocked, matched, formulaSheets, sheet);
|
|
173
327
|
if (rule.stopIfTrue) for (const key of matched)blocked.add(key);
|
|
174
328
|
}
|
|
175
329
|
return styles;
|
|
176
330
|
}
|
|
177
|
-
function
|
|
331
|
+
function isFormulaConditionalRule(rule) {
|
|
332
|
+
return 'default' === rule.type && 'formula' === rule.conditionName;
|
|
333
|
+
}
|
|
334
|
+
function applyDefaultRule(styles, data, rule, blocked, matched, formulaSheets, currentSheet) {
|
|
178
335
|
if ('default' !== rule.type || !isConditionalFormatStyle(rule.format) || !rule.conditionName) return;
|
|
179
336
|
const values = (rule.conditionValue ?? []).map(String);
|
|
180
337
|
const style = rule.format;
|
|
181
338
|
if ('formula' === rule.conditionName && values[0]) {
|
|
182
|
-
for (const range of rule.cellrange)
|
|
339
|
+
for (const range of rule.cellrange)forEachFormulaCell(data, [
|
|
183
340
|
range
|
|
184
341
|
], ({ row, column })=>{
|
|
185
|
-
if (evaluateFormula(values[0], row, column, range,
|
|
342
|
+
if (evaluateFormula(values[0], row, column, range, currentSheet, formulaSheets)) applyDifferentialStyle(styles, blocked, matched, row, column, style);
|
|
186
343
|
});
|
|
187
344
|
return;
|
|
188
345
|
}
|
|
@@ -361,35 +518,20 @@ function matchesCondition(value, name, conditions) {
|
|
|
361
518
|
}
|
|
362
519
|
return false;
|
|
363
520
|
}
|
|
364
|
-
function evaluateFormula(source, row, column, baseRange,
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
};
|
|
521
|
+
function evaluateFormula(source, row, column, baseRange, currentSheet, formulaSheets) {
|
|
522
|
+
const sheets = [
|
|
523
|
+
toFormulaSheet(currentSheet),
|
|
524
|
+
...(formulaSheets ?? []).filter((sheet)=>sheet.id && sheet.id !== currentSheet.id).map(toFormulaSheet)
|
|
525
|
+
];
|
|
526
|
+
const result = evaluateSpreadsheetLocalFormula(source, {
|
|
527
|
+
sheets,
|
|
528
|
+
sheetId: currentSheet.id ?? '',
|
|
529
|
+
row,
|
|
530
|
+
column,
|
|
531
|
+
anchorRow: baseRange.row[0],
|
|
532
|
+
anchorColumn: baseRange.column[0]
|
|
533
|
+
});
|
|
534
|
+
return result.supported && spreadsheetLocalFormulaBoolean(result.value);
|
|
393
535
|
}
|
|
394
536
|
function applyDifferentialStyle(styles, blocked, matched, row, column, style) {
|
|
395
537
|
const patch = {};
|
|
@@ -448,6 +590,21 @@ function forEachCell(data, ranges, visit) {
|
|
|
448
590
|
column
|
|
449
591
|
});
|
|
450
592
|
}
|
|
593
|
+
function forEachFormulaCell(data, ranges, visit) {
|
|
594
|
+
for (const range of ranges){
|
|
595
|
+
const area = (range.row[1] - range.row[0] + 1) * (range.column[1] - range.column[0] + 1);
|
|
596
|
+
if (area > 0 && area <= MAX_SPREADSHEET_CONDITIONAL_FORMULA_SCAN_CELLS) {
|
|
597
|
+
for(let row = range.row[0]; row <= range.row[1]; row += 1)for(let column = range.column[0]; column <= range.column[1]; column += 1)visit({
|
|
598
|
+
row,
|
|
599
|
+
column
|
|
600
|
+
});
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
forEachCell(data, [
|
|
604
|
+
range
|
|
605
|
+
], visit);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
451
608
|
function work_spreadsheet_conditional_format_cellValue(data, row, column) {
|
|
452
609
|
return data[row]?.[column]?.v ?? null;
|
|
453
610
|
}
|
|
@@ -464,19 +621,13 @@ function compareValues(left, right) {
|
|
|
464
621
|
if (String(left).trim() && String(right).trim() && Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) return leftNumber === rightNumber ? 0 : leftNumber > rightNumber ? 1 : -1;
|
|
465
622
|
return String(left).localeCompare(String(right));
|
|
466
623
|
}
|
|
467
|
-
function
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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;
|
|
624
|
+
function toFormulaSheet(sheet) {
|
|
625
|
+
return {
|
|
626
|
+
id: sheet.id ?? '',
|
|
627
|
+
name: sheet.name,
|
|
628
|
+
data: sheet.data,
|
|
629
|
+
celldata: sheet.celldata
|
|
630
|
+
};
|
|
480
631
|
}
|
|
481
632
|
function drawSpreadsheetDiagonalDownBorder(context, bounds, line) {
|
|
482
633
|
drawSpreadsheetDiagonalBorder(context, bounds, line, 'down');
|
|
@@ -13073,6 +13224,11 @@ function newConditionalRuleDraft(sheetId) {
|
|
|
13073
13224
|
comparisonCellColor: DEFAULT_COLORS.comparisonCell,
|
|
13074
13225
|
comparisonUseTextColor: true,
|
|
13075
13226
|
comparisonUseCellColor: true,
|
|
13227
|
+
formula: '=A1>0',
|
|
13228
|
+
formulaTextColor: DEFAULT_COLORS.comparisonText,
|
|
13229
|
+
formulaCellColor: DEFAULT_COLORS.comparisonCell,
|
|
13230
|
+
formulaUseTextColor: true,
|
|
13231
|
+
formulaUseCellColor: true,
|
|
13076
13232
|
scaleSize: '3',
|
|
13077
13233
|
minimumColor: DEFAULT_COLORS.minimum,
|
|
13078
13234
|
midpointColor: DEFAULT_COLORS.midpoint,
|
|
@@ -13104,7 +13260,7 @@ function conditionalRuleDraftForRule(sheetId, rule) {
|
|
|
13104
13260
|
draft.comparisonUseCellColor = Boolean(style.cellColor);
|
|
13105
13261
|
draft.comparisonTextColor = colorInputValue(style.textColor, DEFAULT_COLORS.comparisonText);
|
|
13106
13262
|
draft.comparisonCellColor = colorInputValue(style.cellColor, DEFAULT_COLORS.comparisonCell);
|
|
13107
|
-
} else if (isToolbarConditionalRule(rule)) {
|
|
13263
|
+
} else if ('default' !== rule.type || 'formula' !== rule.conditionName || Array.isArray(rule.format)) if (isToolbarConditionalRule(rule)) {
|
|
13108
13264
|
draft.type = 'toolbarRule';
|
|
13109
13265
|
draft.preservedRule = rule;
|
|
13110
13266
|
} else if ('colorGradation' === rule.type && Array.isArray(rule.format)) {
|
|
@@ -13139,6 +13295,16 @@ function conditionalRuleDraftForRule(sheetId, rule) {
|
|
|
13139
13295
|
draft.barMaxLength = String(options.maxLength ?? 90);
|
|
13140
13296
|
}
|
|
13141
13297
|
}
|
|
13298
|
+
else {
|
|
13299
|
+
const style = rule.format;
|
|
13300
|
+
const values = (rule.conditionValue ?? []).map(String);
|
|
13301
|
+
draft.type = 'formula';
|
|
13302
|
+
draft.formula = values[0] ?? '';
|
|
13303
|
+
draft.formulaUseTextColor = Boolean(style.textColor);
|
|
13304
|
+
draft.formulaUseCellColor = Boolean(style.cellColor);
|
|
13305
|
+
draft.formulaTextColor = colorInputValue(style.textColor, DEFAULT_COLORS.comparisonText);
|
|
13306
|
+
draft.formulaCellColor = colorInputValue(style.cellColor, DEFAULT_COLORS.comparisonCell);
|
|
13307
|
+
}
|
|
13142
13308
|
return draft;
|
|
13143
13309
|
}
|
|
13144
13310
|
function buildConditionalRule(draft) {
|
|
@@ -13179,6 +13345,34 @@ function buildConditionalRule(draft) {
|
|
|
13179
13345
|
value
|
|
13180
13346
|
]
|
|
13181
13347
|
};
|
|
13348
|
+
} else if ('formula' === draft.type) {
|
|
13349
|
+
const formula = draft.formula.trim();
|
|
13350
|
+
if (!formula) return {
|
|
13351
|
+
error: '请输入条件格式公式。'
|
|
13352
|
+
};
|
|
13353
|
+
const formulaSource = formula.replace(/^=/, '').trim();
|
|
13354
|
+
if (!formulaSource) return {
|
|
13355
|
+
error: '请输入条件格式公式,例如 =A2>0。'
|
|
13356
|
+
};
|
|
13357
|
+
if (Array.from(formulaSource).length > 255) return {
|
|
13358
|
+
error: '条件格式公式不能超过 255 个字符。'
|
|
13359
|
+
};
|
|
13360
|
+
if (!draft.formulaUseTextColor && !draft.formulaUseCellColor) return {
|
|
13361
|
+
error: '至少启用一种文字或填充颜色。'
|
|
13362
|
+
};
|
|
13363
|
+
rule = {
|
|
13364
|
+
type: 'default',
|
|
13365
|
+
cellrange,
|
|
13366
|
+
format: {
|
|
13367
|
+
textColor: draft.formulaUseTextColor ? draft.formulaTextColor : null,
|
|
13368
|
+
cellColor: draft.formulaUseCellColor ? draft.formulaCellColor : null
|
|
13369
|
+
},
|
|
13370
|
+
conditionName: 'formula',
|
|
13371
|
+
conditionRange: [],
|
|
13372
|
+
conditionValue: [
|
|
13373
|
+
`=${formulaSource}`
|
|
13374
|
+
]
|
|
13375
|
+
};
|
|
13182
13376
|
} else if ('dataBar' === draft.type) {
|
|
13183
13377
|
const thresholds = savedVisualThresholds(draft.barThresholds);
|
|
13184
13378
|
const minLength = Number(draft.barMinLength);
|
|
@@ -14429,7 +14623,7 @@ function formatSpreadsheetRowPageBreaks(breaks) {
|
|
|
14429
14623
|
return normalizedBreaks(breaks).map((row)=>String(row + 1)).join(', ');
|
|
14430
14624
|
}
|
|
14431
14625
|
function parseSpreadsheetColumnPageBreaks(value, maximumColumn) {
|
|
14432
|
-
return parsePageBreakList(value, maximumColumn,
|
|
14626
|
+
return parsePageBreakList(value, maximumColumn, decodeColumn);
|
|
14433
14627
|
}
|
|
14434
14628
|
function formatSpreadsheetColumnPageBreaks(breaks) {
|
|
14435
14629
|
return normalizedBreaks(breaks).map(encodeColumn).join(', ');
|
|
@@ -14449,7 +14643,7 @@ function parsePageBreakList(value, maximum, decode) {
|
|
|
14449
14643
|
function normalizedBreaks(breaks) {
|
|
14450
14644
|
return Array.from(new Set((breaks ?? []).filter((value)=>Number.isSafeInteger(value) && value > 0))).sort((left, right)=>left - right);
|
|
14451
14645
|
}
|
|
14452
|
-
function
|
|
14646
|
+
function decodeColumn(token) {
|
|
14453
14647
|
const match = /^\$?([A-Z]+)$/i.exec(token);
|
|
14454
14648
|
if (!match) return null;
|
|
14455
14649
|
let value = 0;
|
|
@@ -21598,6 +21792,10 @@ function SpreadsheetConditionalFormatPanel({ content, onChange }) {
|
|
|
21598
21792
|
value: 'icons',
|
|
21599
21793
|
label: '图标集'
|
|
21600
21794
|
},
|
|
21795
|
+
{
|
|
21796
|
+
value: 'formula',
|
|
21797
|
+
label: '自定义公式'
|
|
21798
|
+
},
|
|
21601
21799
|
...'toolbarRule' === draft.type ? [
|
|
21602
21800
|
{
|
|
21603
21801
|
value: 'toolbarRule',
|
|
@@ -21657,6 +21855,76 @@ function SpreadsheetConditionalFormatPanel({ content, onChange }) {
|
|
|
21657
21855
|
...draft,
|
|
21658
21856
|
...patch
|
|
21659
21857
|
})
|
|
21858
|
+
}) : 'formula' === draft.type ? /*#__PURE__*/ jsxs(Fragment, {
|
|
21859
|
+
children: [
|
|
21860
|
+
/*#__PURE__*/ jsxs("div", {
|
|
21861
|
+
className: "work-office-field reference",
|
|
21862
|
+
children: [
|
|
21863
|
+
/*#__PURE__*/ jsx("span", {
|
|
21864
|
+
children: "公式"
|
|
21865
|
+
}),
|
|
21866
|
+
/*#__PURE__*/ jsx(office_text_field_OfficeTextField, {
|
|
21867
|
+
"aria-label": "条件格式公式",
|
|
21868
|
+
value: draft.formula,
|
|
21869
|
+
maxLength: 256,
|
|
21870
|
+
placeholder: "=A2>0",
|
|
21871
|
+
onChange: (event)=>setDraft({
|
|
21872
|
+
...draft,
|
|
21873
|
+
formula: event.target.value
|
|
21874
|
+
})
|
|
21875
|
+
}),
|
|
21876
|
+
/*#__PURE__*/ jsxs("small", {
|
|
21877
|
+
children: [
|
|
21878
|
+
"以应用范围左上角为相对锚点;只读取本地缓存值。",
|
|
21879
|
+
' ',
|
|
21880
|
+
Array.from(draft.formula.replace(/^=/, '')).length,
|
|
21881
|
+
"/",
|
|
21882
|
+
255
|
|
21883
|
+
]
|
|
21884
|
+
})
|
|
21885
|
+
]
|
|
21886
|
+
}),
|
|
21887
|
+
/*#__PURE__*/ jsx("p", {
|
|
21888
|
+
className: "work-office-field-hint",
|
|
21889
|
+
children: "支持常用 Excel 函数、相对/绝对引用和有限区域;无法本地求值时保持原样。"
|
|
21890
|
+
}),
|
|
21891
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
21892
|
+
className: "toggle",
|
|
21893
|
+
ariaLabel: "公式规则使用文字颜色",
|
|
21894
|
+
checked: draft.formulaUseTextColor,
|
|
21895
|
+
onCheckedChange: (formulaUseTextColor)=>setDraft({
|
|
21896
|
+
...draft,
|
|
21897
|
+
formulaUseTextColor
|
|
21898
|
+
}),
|
|
21899
|
+
children: "使用文字颜色"
|
|
21900
|
+
}),
|
|
21901
|
+
draft.formulaUseTextColor && /*#__PURE__*/ jsx(ColorField, {
|
|
21902
|
+
label: "文字颜色",
|
|
21903
|
+
value: draft.formulaTextColor,
|
|
21904
|
+
onChange: (formulaTextColor)=>setDraft({
|
|
21905
|
+
...draft,
|
|
21906
|
+
formulaTextColor
|
|
21907
|
+
})
|
|
21908
|
+
}),
|
|
21909
|
+
/*#__PURE__*/ jsx(OfficeCheckbox, {
|
|
21910
|
+
className: "toggle",
|
|
21911
|
+
ariaLabel: "公式规则使用填充颜色",
|
|
21912
|
+
checked: draft.formulaUseCellColor,
|
|
21913
|
+
onCheckedChange: (formulaUseCellColor)=>setDraft({
|
|
21914
|
+
...draft,
|
|
21915
|
+
formulaUseCellColor
|
|
21916
|
+
}),
|
|
21917
|
+
children: "使用填充颜色"
|
|
21918
|
+
}),
|
|
21919
|
+
draft.formulaUseCellColor && /*#__PURE__*/ jsx(ColorField, {
|
|
21920
|
+
label: "填充颜色",
|
|
21921
|
+
value: draft.formulaCellColor,
|
|
21922
|
+
onChange: (formulaCellColor)=>setDraft({
|
|
21923
|
+
...draft,
|
|
21924
|
+
formulaCellColor
|
|
21925
|
+
})
|
|
21926
|
+
})
|
|
21927
|
+
]
|
|
21660
21928
|
}) : 'colorGradation' === draft.type ? /*#__PURE__*/ jsxs(Fragment, {
|
|
21661
21929
|
children: [
|
|
21662
21930
|
/*#__PURE__*/ jsxs("div", {
|
|
@@ -27185,7 +27453,6 @@ function cloneSpreadsheetDataValidationItem(item) {
|
|
|
27185
27453
|
};
|
|
27186
27454
|
}
|
|
27187
27455
|
const MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH = 255;
|
|
27188
|
-
const MAX_SPREADSHEET_CUSTOM_VALIDATION_REFERENCED_CELLS = 1024;
|
|
27189
27456
|
function evaluateSpreadsheetCustomValidation(content, sheetId, row, column, item, value) {
|
|
27190
27457
|
const sheet = content.sheets.find((candidate)=>candidate.id === sheetId);
|
|
27191
27458
|
if (!sheet) return unsupportedCustomValidation('找不到自定义公式所在的工作表。');
|
|
@@ -27200,122 +27467,30 @@ function evaluateSpreadsheetCustomValidation(content, sheetId, row, column, item
|
|
|
27200
27467
|
if (Array.from(formula).length > MAX_SPREADSHEET_CUSTOM_VALIDATION_FORMULA_LENGTH) return unsupportedCustomValidation('自定义公式超过 255 个字符。');
|
|
27201
27468
|
const ranges = parseSpreadsheetCellRanges(item.rangeTxt);
|
|
27202
27469
|
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
|
-
}
|
|
27470
|
+
const result = evaluateSpreadsheetLocalFormula(formula, {
|
|
27471
|
+
sheets: content.sheets.flatMap((candidate)=>candidate.id ? [
|
|
27472
|
+
{
|
|
27473
|
+
...candidate,
|
|
27474
|
+
id: candidate.id
|
|
27475
|
+
}
|
|
27476
|
+
] : []),
|
|
27477
|
+
sheetId,
|
|
27478
|
+
row,
|
|
27479
|
+
column,
|
|
27480
|
+
anchorRow: anchor?.row,
|
|
27481
|
+
anchorColumn: anchor?.column,
|
|
27482
|
+
proposedValue: normalizedProposed,
|
|
27483
|
+
useProposedValue: true
|
|
27484
|
+
});
|
|
27485
|
+
if (!result.supported) return {
|
|
27486
|
+
supported: false,
|
|
27487
|
+
valid: false,
|
|
27488
|
+
message: result.message ? `自定义${result.message}` : result.message
|
|
27489
|
+
};
|
|
27490
|
+
return {
|
|
27491
|
+
supported: true,
|
|
27492
|
+
valid: spreadsheetLocalFormulaBoolean(result.value)
|
|
27493
|
+
};
|
|
27319
27494
|
}
|
|
27320
27495
|
function spreadsheetCustomValidationAnchor(ranges, row, column) {
|
|
27321
27496
|
const range = ranges?.find((candidate)=>spreadsheetCellRangeContains(candidate, row, column));
|
|
@@ -27324,10 +27499,6 @@ function spreadsheetCustomValidationAnchor(ranges, row, column) {
|
|
|
27324
27499
|
column: range?.column[0] ?? column
|
|
27325
27500
|
};
|
|
27326
27501
|
}
|
|
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
27502
|
function spreadsheetCustomValidationValue(value) {
|
|
27332
27503
|
if (!value || 'object' != typeof value || Array.isArray(value)) return value;
|
|
27333
27504
|
if ('v' in value) return value.v;
|
|
@@ -27336,11 +27507,6 @@ function spreadsheetCustomValidationValue(value) {
|
|
|
27336
27507
|
function spreadsheetCustomValidationBlank(value) {
|
|
27337
27508
|
return null == value || 'string' == typeof value && '' === value.trim();
|
|
27338
27509
|
}
|
|
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
27510
|
function unsupportedCustomValidation(message) {
|
|
27345
27511
|
return {
|
|
27346
27512
|
supported: false,
|
|
@@ -30267,7 +30433,7 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
|
|
|
30267
30433
|
const conditionalStylesBySheet = useMemo(()=>new Map(materializedContent.sheets.flatMap((sheet)=>sheet.id ? [
|
|
30268
30434
|
[
|
|
30269
30435
|
sheet.id,
|
|
30270
|
-
spreadsheetConditionalFormatStyles(sheet)
|
|
30436
|
+
spreadsheetConditionalFormatStyles(sheet, materializedContent.sheets)
|
|
30271
30437
|
]
|
|
30272
30438
|
] : [])), [
|
|
30273
30439
|
materializedContent.sheets
|
package/dist/4121.js
CHANGED
|
@@ -2905,6 +2905,13 @@ const WORK_TEMPLATES = [
|
|
|
2905
2905
|
description: '自定义公式、下拉列表、输入提示与错误警告',
|
|
2906
2906
|
accent: '#13795b'
|
|
2907
2907
|
},
|
|
2908
|
+
{
|
|
2909
|
+
id: 'conditional-format',
|
|
2910
|
+
kind: 'spreadsheet',
|
|
2911
|
+
name: '公式条件格式',
|
|
2912
|
+
description: '相对引用、跨表阈值、停止后续规则与本地实时强调',
|
|
2913
|
+
accent: '#2f6fed'
|
|
2914
|
+
},
|
|
2908
2915
|
{
|
|
2909
2916
|
id: 'structured-references',
|
|
2910
2917
|
kind: 'spreadsheet',
|
|
@@ -2965,6 +2972,7 @@ function initialTitle(templateId, kind) {
|
|
|
2965
2972
|
'proofing-languages': '校对语言示例',
|
|
2966
2973
|
'quarterly-plan': '季度执行计划',
|
|
2967
2974
|
'data-validation': '数据验证示例',
|
|
2975
|
+
'conditional-format': '公式条件格式示例',
|
|
2968
2976
|
'structured-references': '结构化引用示例',
|
|
2969
2977
|
'strategy-deck': '业务策略汇报',
|
|
2970
2978
|
'animated-deck': '进入与退出动画示例'
|
|
@@ -3265,6 +3273,10 @@ function contentForTemplate(templateId) {
|
|
|
3265
3273
|
type: 'spreadsheet',
|
|
3266
3274
|
sheets: dataValidationTemplateSheets()
|
|
3267
3275
|
};
|
|
3276
|
+
if ('conditional-format' === templateId) return {
|
|
3277
|
+
type: 'spreadsheet',
|
|
3278
|
+
sheets: conditionalFormatTemplateSheets()
|
|
3279
|
+
};
|
|
3268
3280
|
if ('structured-references' === templateId) return {
|
|
3269
3281
|
type: 'spreadsheet',
|
|
3270
3282
|
sheets: structuredReferenceTemplateSheets()
|
|
@@ -3647,6 +3659,247 @@ function dataValidationTemplateSheets() {
|
|
|
3647
3659
|
}
|
|
3648
3660
|
];
|
|
3649
3661
|
}
|
|
3662
|
+
function conditionalFormatTemplateSheets() {
|
|
3663
|
+
const status = emptyMatrix(18, 8);
|
|
3664
|
+
status[0][0] = styledCell('公式条件格式 · 发布看板', {
|
|
3665
|
+
bl: 1,
|
|
3666
|
+
fs: 16,
|
|
3667
|
+
fc: '#ffffff',
|
|
3668
|
+
bg: '#2f6fed'
|
|
3669
|
+
});
|
|
3670
|
+
status[1][0] = styledCell('公式以 A2:H7 左上角为锚点;先标记阻塞项,再强调达到阈值的任务。', {
|
|
3671
|
+
fc: '#49617f',
|
|
3672
|
+
fs: 10
|
|
3673
|
+
});
|
|
3674
|
+
[
|
|
3675
|
+
'任务',
|
|
3676
|
+
'负责人',
|
|
3677
|
+
'完成率',
|
|
3678
|
+
'状态',
|
|
3679
|
+
'预算',
|
|
3680
|
+
'实际',
|
|
3681
|
+
'偏差',
|
|
3682
|
+
'备注'
|
|
3683
|
+
].forEach((value, column)=>{
|
|
3684
|
+
status[2][column] = headerCell(value);
|
|
3685
|
+
});
|
|
3686
|
+
const rows = [
|
|
3687
|
+
[
|
|
3688
|
+
'需求确认',
|
|
3689
|
+
'Avery',
|
|
3690
|
+
1,
|
|
3691
|
+
'完成',
|
|
3692
|
+
12000,
|
|
3693
|
+
11800,
|
|
3694
|
+
'按期',
|
|
3695
|
+
'可发布'
|
|
3696
|
+
],
|
|
3697
|
+
[
|
|
3698
|
+
'接口联调',
|
|
3699
|
+
'Morgan',
|
|
3700
|
+
0.82,
|
|
3701
|
+
'进行中',
|
|
3702
|
+
18000,
|
|
3703
|
+
20100,
|
|
3704
|
+
'超支',
|
|
3705
|
+
'关注预算'
|
|
3706
|
+
],
|
|
3707
|
+
[
|
|
3708
|
+
'回归测试',
|
|
3709
|
+
'Riley',
|
|
3710
|
+
0.54,
|
|
3711
|
+
'阻塞',
|
|
3712
|
+
15000,
|
|
3713
|
+
7600,
|
|
3714
|
+
'暂停',
|
|
3715
|
+
'等待依赖'
|
|
3716
|
+
],
|
|
3717
|
+
[
|
|
3718
|
+
'文档发布',
|
|
3719
|
+
'Jordan',
|
|
3720
|
+
0.76,
|
|
3721
|
+
'进行中',
|
|
3722
|
+
9000,
|
|
3723
|
+
8800,
|
|
3724
|
+
'按期',
|
|
3725
|
+
'待审阅'
|
|
3726
|
+
],
|
|
3727
|
+
[
|
|
3728
|
+
'版本发布',
|
|
3729
|
+
'Taylor',
|
|
3730
|
+
0.38,
|
|
3731
|
+
'未开始',
|
|
3732
|
+
22000,
|
|
3733
|
+
0,
|
|
3734
|
+
'未开始',
|
|
3735
|
+
'尚未排期'
|
|
3736
|
+
],
|
|
3737
|
+
[
|
|
3738
|
+
'复盘会议',
|
|
3739
|
+
'Avery',
|
|
3740
|
+
0.9,
|
|
3741
|
+
'进行中',
|
|
3742
|
+
4000,
|
|
3743
|
+
3500,
|
|
3744
|
+
'按期',
|
|
3745
|
+
'本周完成'
|
|
3746
|
+
]
|
|
3747
|
+
];
|
|
3748
|
+
rows.forEach((row, rowIndex)=>{
|
|
3749
|
+
row.forEach((value, columnIndex)=>{
|
|
3750
|
+
status[rowIndex + 3][columnIndex] = styledCell(value, {
|
|
3751
|
+
bg: rowIndex % 2 ? '#f6f9ff' : '#ffffff',
|
|
3752
|
+
...2 === columnIndex ? {
|
|
3753
|
+
ct: {
|
|
3754
|
+
fa: '0%',
|
|
3755
|
+
t: 'n'
|
|
3756
|
+
},
|
|
3757
|
+
m: `${Math.round(100 * Number(value))}%`
|
|
3758
|
+
} : {},
|
|
3759
|
+
...4 === columnIndex || 5 === columnIndex ? {
|
|
3760
|
+
ct: {
|
|
3761
|
+
fa: '#,##0',
|
|
3762
|
+
t: 'n'
|
|
3763
|
+
},
|
|
3764
|
+
m: Number(value).toLocaleString()
|
|
3765
|
+
} : {}
|
|
3766
|
+
});
|
|
3767
|
+
});
|
|
3768
|
+
});
|
|
3769
|
+
status[11][0] = styledCell('本地规则示例', {
|
|
3770
|
+
bl: 1,
|
|
3771
|
+
fc: '#244b9b'
|
|
3772
|
+
});
|
|
3773
|
+
status[12][0] = styledCell('阻塞项(停止后续规则)');
|
|
3774
|
+
status[12][1] = styledCell('=$D4="阻塞"');
|
|
3775
|
+
status[13][0] = styledCell('达标项(跨表阈值)');
|
|
3776
|
+
status[13][1] = styledCell('=AND($C4>=Limits!$A$2,$D4<>"阻塞")');
|
|
3777
|
+
const blockRule = {
|
|
3778
|
+
type: 'default',
|
|
3779
|
+
cellrange: [
|
|
3780
|
+
{
|
|
3781
|
+
row: [
|
|
3782
|
+
3,
|
|
3783
|
+
8
|
|
3784
|
+
],
|
|
3785
|
+
column: [
|
|
3786
|
+
0,
|
|
3787
|
+
7
|
|
3788
|
+
]
|
|
3789
|
+
}
|
|
3790
|
+
],
|
|
3791
|
+
format: {
|
|
3792
|
+
textColor: '#9c0006',
|
|
3793
|
+
cellColor: '#ffc7ce'
|
|
3794
|
+
},
|
|
3795
|
+
conditionName: 'formula',
|
|
3796
|
+
conditionRange: [],
|
|
3797
|
+
conditionValue: [
|
|
3798
|
+
'=$D4="阻塞"'
|
|
3799
|
+
],
|
|
3800
|
+
stopIfTrue: true
|
|
3801
|
+
};
|
|
3802
|
+
const completedRule = {
|
|
3803
|
+
type: 'default',
|
|
3804
|
+
cellrange: [
|
|
3805
|
+
{
|
|
3806
|
+
row: [
|
|
3807
|
+
3,
|
|
3808
|
+
8
|
|
3809
|
+
],
|
|
3810
|
+
column: [
|
|
3811
|
+
0,
|
|
3812
|
+
7
|
|
3813
|
+
]
|
|
3814
|
+
}
|
|
3815
|
+
],
|
|
3816
|
+
format: {
|
|
3817
|
+
textColor: '#006100',
|
|
3818
|
+
cellColor: '#c6efce'
|
|
3819
|
+
},
|
|
3820
|
+
conditionName: 'formula',
|
|
3821
|
+
conditionRange: [],
|
|
3822
|
+
conditionValue: [
|
|
3823
|
+
'=AND($C4>=Limits!$A$2,$D4<>"阻塞")'
|
|
3824
|
+
]
|
|
3825
|
+
};
|
|
3826
|
+
return [
|
|
3827
|
+
{
|
|
3828
|
+
id: createWorkId('sheet'),
|
|
3829
|
+
name: 'Status',
|
|
3830
|
+
status: 1,
|
|
3831
|
+
order: 0,
|
|
3832
|
+
row: 18,
|
|
3833
|
+
column: 8,
|
|
3834
|
+
data: status,
|
|
3835
|
+
luckysheet_conditionformat_save: [
|
|
3836
|
+
blockRule,
|
|
3837
|
+
completedRule
|
|
3838
|
+
],
|
|
3839
|
+
config: {
|
|
3840
|
+
columnlen: {
|
|
3841
|
+
0: 132,
|
|
3842
|
+
1: 92,
|
|
3843
|
+
2: 84,
|
|
3844
|
+
3: 86,
|
|
3845
|
+
4: 92,
|
|
3846
|
+
5: 92,
|
|
3847
|
+
6: 84,
|
|
3848
|
+
7: 116
|
|
3849
|
+
},
|
|
3850
|
+
rowlen: {
|
|
3851
|
+
0: 34,
|
|
3852
|
+
1: 26,
|
|
3853
|
+
2: 28,
|
|
3854
|
+
12: 24,
|
|
3855
|
+
13: 24
|
|
3856
|
+
},
|
|
3857
|
+
merge: {
|
|
3858
|
+
'0_0': {
|
|
3859
|
+
r: 0,
|
|
3860
|
+
c: 0,
|
|
3861
|
+
rs: 1,
|
|
3862
|
+
cs: 8
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
},
|
|
3867
|
+
{
|
|
3868
|
+
id: createWorkId('sheet'),
|
|
3869
|
+
name: 'Limits',
|
|
3870
|
+
status: 0,
|
|
3871
|
+
order: 1,
|
|
3872
|
+
row: 8,
|
|
3873
|
+
column: 4,
|
|
3874
|
+
data: [
|
|
3875
|
+
[
|
|
3876
|
+
headerCell('完成率阈值'),
|
|
3877
|
+
styledCell('说明')
|
|
3878
|
+
],
|
|
3879
|
+
[
|
|
3880
|
+
styledCell(0.8, {
|
|
3881
|
+
ct: {
|
|
3882
|
+
fa: '0%',
|
|
3883
|
+
t: 'n'
|
|
3884
|
+
},
|
|
3885
|
+
m: '80%'
|
|
3886
|
+
}),
|
|
3887
|
+
styledCell('达到阈值并且未阻塞的任务使用绿色强调。')
|
|
3888
|
+
]
|
|
3889
|
+
],
|
|
3890
|
+
config: {
|
|
3891
|
+
columnlen: {
|
|
3892
|
+
0: 120,
|
|
3893
|
+
1: 300
|
|
3894
|
+
},
|
|
3895
|
+
rowlen: {
|
|
3896
|
+
0: 28,
|
|
3897
|
+
1: 36
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
];
|
|
3902
|
+
}
|
|
3650
3903
|
function structuredReferenceTemplateSheets() {
|
|
3651
3904
|
const sales = emptyMatrix(16, 10);
|
|
3652
3905
|
sales[0][0] = styledCell('Sales · 结构化引用', {
|
|
@@ -15,7 +15,7 @@ export declare const SPREADSHEET_CONDITIONAL_COMPARISONS: {
|
|
|
15
15
|
}[];
|
|
16
16
|
export interface ConditionalRuleDraft {
|
|
17
17
|
sheetId: string;
|
|
18
|
-
type: 'cellComparison' | 'colorGradation' | 'dataBar' | 'icons' | 'toolbarRule';
|
|
18
|
+
type: 'cellComparison' | 'colorGradation' | 'dataBar' | 'icons' | 'formula' | 'toolbarRule';
|
|
19
19
|
reference: string;
|
|
20
20
|
stopIfTrue: boolean;
|
|
21
21
|
preservedRule: FortuneConditionalFormatRule | null;
|
|
@@ -26,6 +26,11 @@ export interface ConditionalRuleDraft {
|
|
|
26
26
|
comparisonCellColor: string;
|
|
27
27
|
comparisonUseTextColor: boolean;
|
|
28
28
|
comparisonUseCellColor: boolean;
|
|
29
|
+
formula: string;
|
|
30
|
+
formulaTextColor: string;
|
|
31
|
+
formulaCellColor: string;
|
|
32
|
+
formulaUseTextColor: boolean;
|
|
33
|
+
formulaUseCellColor: boolean;
|
|
29
34
|
scaleSize: '2' | '3';
|
|
30
35
|
minimumColor: string;
|
|
31
36
|
midpointColor: string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Sheet } from '@fortune-sheet/core';
|
|
2
2
|
import { type SpreadsheetConditionalDataBar } from './work-spreadsheet-conditional-data-bar';
|
|
3
3
|
import { type SpreadsheetConditionalIcon } from './work-spreadsheet-conditional-icons';
|
|
4
|
+
/** Full formula-rule scans stay bounded; larger ranges use materialized cells. */
|
|
5
|
+
export declare const MAX_SPREADSHEET_CONDITIONAL_FORMULA_SCAN_CELLS = 10000;
|
|
4
6
|
export interface SpreadsheetConditionalCellStyle {
|
|
5
7
|
textColor?: string;
|
|
6
8
|
cellColor?: string;
|
|
7
9
|
dataBar?: SpreadsheetConditionalDataBar;
|
|
8
10
|
icon?: SpreadsheetConditionalIcon;
|
|
9
11
|
}
|
|
10
|
-
export declare function spreadsheetConditionalFormatStyles(sheet: Sheet): Map<string, SpreadsheetConditionalCellStyle>;
|
|
12
|
+
export declare function spreadsheetConditionalFormatStyles(sheet: Sheet, formulaSheets?: readonly Sheet[]): Map<string, SpreadsheetConditionalCellStyle>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CellMatrix } from '@fortune-sheet/core';
|
|
2
|
+
/** OOXML and the browser editor keep local rule formulas deliberately small. */
|
|
3
|
+
export declare const MAX_SPREADSHEET_LOCAL_FORMULA_LENGTH = 255;
|
|
4
|
+
/** Keep synchronous browser formula evaluation bounded for hostile workbooks. */
|
|
5
|
+
export declare const MAX_SPREADSHEET_LOCAL_FORMULA_REFERENCED_CELLS = 1024;
|
|
6
|
+
export interface SpreadsheetFormulaSheet {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
data?: CellMatrix;
|
|
10
|
+
celldata?: readonly SpreadsheetFormulaCell[];
|
|
11
|
+
}
|
|
12
|
+
export interface SpreadsheetFormulaCell {
|
|
13
|
+
r: number;
|
|
14
|
+
c: number;
|
|
15
|
+
v: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface SpreadsheetLocalFormulaOptions {
|
|
18
|
+
sheets: readonly SpreadsheetFormulaSheet[];
|
|
19
|
+
sheetId: string;
|
|
20
|
+
row: number;
|
|
21
|
+
column: number;
|
|
22
|
+
/** The first row of the rule/validation range. Defaults to the target row. */
|
|
23
|
+
anchorRow?: number;
|
|
24
|
+
/** The first column of the rule/validation range. Defaults to the target column. */
|
|
25
|
+
anchorColumn?: number;
|
|
26
|
+
/** Substitute the target cell with this value, as data validation does. */
|
|
27
|
+
proposedValue?: unknown;
|
|
28
|
+
useProposedValue?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface SpreadsheetLocalFormulaResult {
|
|
31
|
+
supported: boolean;
|
|
32
|
+
value?: unknown;
|
|
33
|
+
message?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Evaluate a bounded Excel formula against the workbook's cached local data.
|
|
37
|
+
*
|
|
38
|
+
* This is intentionally shared by custom data validation and conditional
|
|
39
|
+
* formatting. It never recalculates formula cells or reaches a remote source;
|
|
40
|
+
* an unavailable dependency therefore fails closed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function evaluateSpreadsheetLocalFormula(formula: string, options: SpreadsheetLocalFormulaOptions): SpreadsheetLocalFormulaResult;
|
|
43
|
+
export declare function spreadsheetLocalFormulaBoolean(value: unknown): boolean;
|
package/dist/office-kernel.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a3s-lab/office",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Open-source collaborative browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"office",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"playground:visual:document-opentype": "playwright test visual-tests/document-opentype.functional.spec.ts --config playwright.config.ts",
|
|
107
107
|
"playground:visual:document-strike": "playwright test visual-tests/document-strike.functional.spec.ts --config playwright.config.ts",
|
|
108
108
|
"playground:visual:spreadsheet-data-validation": "playwright test visual-tests/spreadsheet-data-validation.functional.spec.ts --config playwright.config.ts",
|
|
109
|
+
"playground:visual:spreadsheet-conditional-format": "playwright test visual-tests/spreadsheet-conditional-format.functional.spec.ts --config playwright.config.ts",
|
|
109
110
|
"playground:visual:spreadsheet-date-time": "playwright test visual-tests/spreadsheet-date-time.functional.spec.ts --config playwright.config.ts",
|
|
110
111
|
"playground:visual:spreadsheet-copy-from-above": "playwright test visual-tests/spreadsheet-copy-from-above.functional.spec.ts --config playwright.config.ts",
|
|
111
112
|
"playground:visual:spreadsheet-custom-sort": "playwright test visual-tests/spreadsheet-custom-sort.functional.spec.ts --config playwright.config.ts",
|
|
@@ -224,6 +225,8 @@
|
|
|
224
225
|
"test:e2e:spreadsheet-go-to:check": "a3s-test check tests/e2e/spreadsheet-go-to.acl --json",
|
|
225
226
|
"test:e2e:spreadsheet-data-validation": "A3S_TEST_SUITE=tests/e2e/spreadsheet-data-validation.acl bash scripts/run-a3s-test-web-gate.sh",
|
|
226
227
|
"test:e2e:spreadsheet-data-validation:check": "a3s-test check tests/e2e/spreadsheet-data-validation.acl --json",
|
|
228
|
+
"test:e2e:spreadsheet-conditional-format": "A3S_TEST_SUITE=tests/e2e/spreadsheet-conditional-format.acl bash scripts/run-a3s-test-web-gate.sh",
|
|
229
|
+
"test:e2e:spreadsheet-conditional-format:check": "a3s-test check tests/e2e/spreadsheet-conditional-format.acl --json",
|
|
227
230
|
"test:e2e:spreadsheet-diagonal-borders": "a3s-test run tests/e2e/spreadsheet-diagonal-borders.acl --json",
|
|
228
231
|
"test:e2e:spreadsheet-diagonal-borders:check": "a3s-test check tests/e2e/spreadsheet-diagonal-borders.acl --json",
|
|
229
232
|
"test:e2e:spreadsheet-date-time": "a3s-test run tests/e2e/spreadsheet-date-time.acl --json",
|