@a3s-lab/office 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/dist/0~7048.js +93 -1
- package/dist/0~spreadsheet-editor.js +388 -35
- package/dist/3266.js +366 -6
- package/dist/4121.js +1942 -1939
- package/dist/core.js +30 -3
- package/dist/internal/features/work/editors/spreadsheet-table-totals.d.ts +39 -0
- package/dist/internal/features/work/editors/spreadsheet-table.d.ts +11 -1
- package/dist/internal/features/work/work-types.d.ts +14 -0
- package/dist/internal/kernel/office-kernel-spreadsheet-fallback-formula.d.ts +11 -0
- package/dist/office-kernel.wasm +0 -0
- package/dist/office-kernel.worker.js +93 -1
- package/dist/styles.css +120 -0
- package/docs/latest/en/browser-editor-architecture.md +8 -2
- package/package.json +4 -1
package/dist/core.js
CHANGED
|
@@ -5,7 +5,7 @@ import { OFFICE_COLLABORATION_PROTOCOL as WORK_OFFICE_COLLABORATION_PROTOCOL, Of
|
|
|
5
5
|
import { directChildren, OoxmlPackage as work_ooxml_package_OoxmlPackage, parseXml, firstDescendant, directChild, descendants, createArtifact as createWorkArtifact, xmlContainsAnyElement, createOfficeId as createWorkId, attribute as work_ooxml_package_attribute } from "./4121.js";
|
|
6
6
|
import { fileNameWithoutExtension, work_file_download_downloadBlob, mountWorkLiveDocumentCapture, importWorkDocumentFile, moveWorkSourceBlob, materializeWorkDocumentContent as work_document_model_codec_materializeWorkDocumentContent, createWorkDocumentModel, work_file_download_safeFileName, documentPageSurfaceGeometryForElement, registerSourceBlob as rememberWorkSourceBlob, documentModelForContent, readSourceBlob as readWorkSourceBlob, createWorkDocumentExtensions, positionWorkLiveDocumentCapture, createWorkDocumentBlob } from "./4174.js";
|
|
7
7
|
import { normalizeSpreadsheetPrintTitleRows, sparseMatrixColumnCount, normalizeSpreadsheetPrintArea, parseSpreadsheetPrintTitles, isValidSpreadsheetDefinedName, formatSpreadsheetCellRanges, stripSpreadsheetSheetQualifier, editableSpreadsheetFormula, sparseArrayEntries, qualifySpreadsheetRange, refreshSpreadsheetPivotTables, parseSpreadsheetCellRanges, normalizeSpreadsheetPrintTitleColumns } from "./8715.js";
|
|
8
|
-
import { xlsxColorMatches, xlsxRichTextCellText, spreadsheetCellValueWithDiagonalBorder, xlsxRichTextStyleOrigins, normalizeSpreadsheetDataValidationErrorStyle, directXlsxAlignment,
|
|
8
|
+
import { xlsxColorMatches, xlsxRichTextCellText, spreadsheetCellValueWithDiagonalBorder, xlsxRichTextStyleOrigins, normalizeSpreadsheetDataValidationErrorStyle, directXlsxAlignment, spreadsheetTableTotalsFunctionToOoxml, spreadsheetTextOrientationFromXlsx, directXlsxFontStyle, hasXlsxDirectFontStyle, applyImportedXlsxRichText, xlsxStyleCollectionIndex, sheetHasXlsxRichTextCells, spreadsheetExplicitTextOrientationFromCell, activeXlsxNativeFill, readXlsxRichTextCells, normalizeSpreadsheetDateValidationBoundary, createXlsxRichTextReadContext, boundedSpreadsheetDataValidationText, xlsxNativeFillKey, xlsxBooleanAttribute, xlsxAlignmentMatches, xlsxBorderLineMatches, spreadsheetTextOrientationCellStyle, spreadsheetTableTotalsFunctionFromOoxml, spreadsheetDateValidationFormula, spreadsheetDiagonalBorderFromCellValue, xlsxUnderlineStyle, spreadsheetUnderlineCellValueFromXlsx, xlsxToggleEnabled, normalizeSpreadsheetTableTotalsLabel, activeXlsxSemanticColorOrigin, writeXlsxRichTextCells, xlsxNativeFillSemanticColors, normalizeSpreadsheetTableCalculatedFormula, normalizeSpreadsheetTableTotalsFormula, spreadsheetUnderlineCellValueFromSheetJs } from "./3266.js";
|
|
9
9
|
import { freezeImportedSpreadsheetCell, readXlsxGradientFill, withXlsxPatternFill, xlsxCellStyleOriginSemanticColors, setXlsxToggleChild, withXlsxCellStyleOrigin, normalizeSpreadsheetConditionalVisualOptions, xlsxColorElementMatchesOrigin, withXlsxGradientFill, ensureXlsxStyleCollection, xlsxCellStyleOrigin, xlsxSemanticColorOriginKey, readXlsxPatternFill, isSpreadsheetConditionalComparisonOperator, registerImportedSpreadsheetMatrix, defaultSpreadsheetConditionalIconThresholds, readXlsxSemanticColorOrigin, setXlsxUnderlineChild, xlsxGradientFillFallbackColor, setXlsxColorChild, createXlsxColorResolver, resolveXlsxColor, setXlsxBorderLine, normalizeSpreadsheetConditionalIconSetFormat, defaultSpreadsheetDataBarOptions, defaultXlsxBorder, prepareXlsxSemanticPalette, importedSheetProtectionAuthority, defaultSpreadsheetColorScaleThresholds, xlsxRgbColor, writeXlsxAlignment, xlsxGradientFillKey, applyXlsxSemanticColorOrigin, spreadsheetConditionalComparisonNeedsUpperValue, sheetHasProtectionState, xlsxWorksheetCellEntries, isSpreadsheetConditionalIconSetName, setXlsxValueChild, defaultXlsxFill, spreadsheetConditionalThresholdsEqual, spreadsheetConditionalIconSetCount } from "./9333.js";
|
|
10
10
|
import { updateSpreadsheetWorksheetCompatibilitySummary, inspectXlsxPivotTables, readXlsxWorksheetCharts, createXlsxFormulaCell, patchXlsxWorksheetDrawings, readXlsxWorksheetImages, writeXlsxProtection, xlsxWorksheetChartsToSheet, readXlsxFormulaFeaturesFromPackage, emptySpreadsheetWorksheetCompatibilitySummary, createSpreadsheetFormulaMetadata, createXlsxErrorCell, xlsxWorksheetImagesToSheet, readXlsxProtection, patchXlsxFormulaFeatures, patchXlsxPivotTables, writeXlsxPageSetup, XlsxCellProtectionWriter, applyImportedXlsxPivotTables, readXlsxPageSetup } from "./4166.js";
|
|
11
11
|
import { WorkFileImportController, materializeWorkFileSource } from "./9356.js";
|
|
@@ -2702,11 +2702,24 @@ function parseXlsxTable(document1, partPath) {
|
|
|
2702
2702
|
const columnName = work_ooxml_package_attribute(column, 'name');
|
|
2703
2703
|
if (null === columnName) return [];
|
|
2704
2704
|
const calculatedFormula = normalizeSpreadsheetTableCalculatedFormula(directChild(column, 'calculatedColumnFormula')?.textContent);
|
|
2705
|
+
const totalsFunction = spreadsheetTableTotalsFunctionFromOoxml(work_ooxml_package_attribute(column, 'totalsRowFunction'));
|
|
2706
|
+
const totalsLabel = normalizeSpreadsheetTableTotalsLabel(work_ooxml_package_attribute(column, 'totalsRowLabel'));
|
|
2707
|
+
const totalsFormula = normalizeSpreadsheetTableTotalsFormula(directChild(column, 'totalsRowFormula')?.textContent);
|
|
2708
|
+
const normalizedTotalsFunction = totalsFormula ? 'custom' : totalsFunction;
|
|
2705
2709
|
return [
|
|
2706
2710
|
{
|
|
2707
2711
|
name: columnName,
|
|
2708
2712
|
...calculatedFormula ? {
|
|
2709
2713
|
calculatedFormula
|
|
2714
|
+
} : {},
|
|
2715
|
+
...normalizedTotalsFunction ? {
|
|
2716
|
+
totalsFunction: normalizedTotalsFunction
|
|
2717
|
+
} : {},
|
|
2718
|
+
...totalsLabel && !totalsFormula ? {
|
|
2719
|
+
totalsLabel
|
|
2720
|
+
} : {},
|
|
2721
|
+
...totalsFormula ? {
|
|
2722
|
+
totalsFormula
|
|
2710
2723
|
} : {}
|
|
2711
2724
|
}
|
|
2712
2725
|
];
|
|
@@ -2750,8 +2763,22 @@ function xlsxTableXml(table, tableId, namespace) {
|
|
|
2750
2763
|
const columns = table.columns.map((column, index)=>{
|
|
2751
2764
|
const formula = normalizeSpreadsheetTableCalculatedFormula(column.calculatedFormula);
|
|
2752
2765
|
const formulaElement = formula ? `<calculatedColumnFormula>${work_xlsx_tables_escapeXml(formula.replace(/^=/, ''))}</calculatedColumnFormula>` : '';
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2766
|
+
const totalsFunction = spreadsheetTableTotalsFunctionToOoxml(column.totalsFunction);
|
|
2767
|
+
const totalsLabel = normalizeSpreadsheetTableTotalsLabel(column.totalsLabel);
|
|
2768
|
+
const totalsFormula = normalizeSpreadsheetTableTotalsFormula(column.totalsFormula);
|
|
2769
|
+
const attributes = [
|
|
2770
|
+
`id="${index + 1}"`,
|
|
2771
|
+
`name="${work_xlsx_tables_escapeXml(column.name)}"`,
|
|
2772
|
+
...totalsFunction ? [
|
|
2773
|
+
`totalsRowFunction="${work_xlsx_tables_escapeXml(totalsFunction)}"`
|
|
2774
|
+
] : [],
|
|
2775
|
+
...totalsLabel && !totalsFormula ? [
|
|
2776
|
+
`totalsRowLabel="${work_xlsx_tables_escapeXml(totalsLabel)}"`
|
|
2777
|
+
] : []
|
|
2778
|
+
].join(' ');
|
|
2779
|
+
const totalsFormulaElement = totalsFormula ? `<totalsRowFormula>${work_xlsx_tables_escapeXml(totalsFormula.replace(/^=/, ''))}</totalsRowFormula>` : '';
|
|
2780
|
+
const children = `${formulaElement}${totalsFormulaElement}`;
|
|
2781
|
+
return children ? `<tableColumn ${attributes}>${children}</tableColumn>` : `<tableColumn ${attributes}/>`;
|
|
2755
2782
|
}).join('');
|
|
2756
2783
|
const styleName = tableStyleName(table.style);
|
|
2757
2784
|
const style = styleName ? `<tableStyleInfo name="${styleName}" showColumnStripes="${booleanToken(table.showColumnStripes)}" showFirstColumn="${booleanToken(table.showFirstColumn)}" showLastColumn="${booleanToken(table.showLastColumn)}" showRowStripes="${booleanToken(table.showRowStripes)}"/>` : '';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WorkSpreadsheetSheet, WorkSpreadsheetTable, WorkSpreadsheetTableColumn, WorkSpreadsheetTableTotalsFunction } from '../work-types';
|
|
2
|
+
export declare const MAX_SPREADSHEET_TABLE_TOTALS_FORMULA_LENGTH = 8192;
|
|
3
|
+
export declare const MAX_SPREADSHEET_TABLE_TOTALS_LABEL_LENGTH = 255;
|
|
4
|
+
export declare const DEFAULT_SPREADSHEET_TABLE_TOTALS_LABEL = "Total";
|
|
5
|
+
/** The closed function set exposed by the Table Design totals menu. */
|
|
6
|
+
export declare const SPREADSHEET_TABLE_TOTALS_FUNCTIONS: readonly ["sum", "average", "count", "countNums", "max", "min", "stdDev", "stdDevP", "var", "varP", "custom"];
|
|
7
|
+
export interface SpreadsheetTableTotalsColumnPatch {
|
|
8
|
+
totalsFormula?: string | null;
|
|
9
|
+
totalsFunction?: WorkSpreadsheetTableTotalsFunction | null;
|
|
10
|
+
totalsLabel?: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function spreadsheetTableTotalsFunctionLabel(value: WorkSpreadsheetTableTotalsFunction | undefined): string;
|
|
13
|
+
export declare function normalizeSpreadsheetTableTotalsFunction(value: unknown): WorkSpreadsheetTableTotalsFunction | undefined;
|
|
14
|
+
/** Normalize a table totals formula to the editable leading-equals form. */
|
|
15
|
+
export declare function normalizeSpreadsheetTableTotalsFormula(value: unknown): string | undefined;
|
|
16
|
+
export declare function normalizeSpreadsheetTableTotalsLabel(value: unknown): string | undefined;
|
|
17
|
+
export declare function spreadsheetTableTotalsFunctionFromOoxml(value: unknown): WorkSpreadsheetTableTotalsFunction | undefined;
|
|
18
|
+
export declare function spreadsheetTableTotalsFunctionToOoxml(value: WorkSpreadsheetTableTotalsFunction | undefined): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the formula that should be placed in one totals-row cell. Native
|
|
21
|
+
* aggregate functions use SUBTOTAL so filtered-out rows stay excluded; a
|
|
22
|
+
* custom rule uses its explicit formula.
|
|
23
|
+
*/
|
|
24
|
+
export declare function spreadsheetTableTotalsFormula(table: WorkSpreadsheetTable, columnOffset: number): string | undefined;
|
|
25
|
+
export declare function spreadsheetTableTotalsColumnPatch(columns: readonly WorkSpreadsheetTableColumn[], patches: Readonly<Record<string, SpreadsheetTableTotalsColumnPatch>> | readonly (SpreadsheetTableTotalsColumnPatch | null | undefined)[] | undefined): WorkSpreadsheetTableColumn[] | null;
|
|
26
|
+
/** Apply the native default label used when a totals row is first enabled. */
|
|
27
|
+
export declare function withDefaultSpreadsheetTableTotalsLabel(columns: readonly WorkSpreadsheetTableColumn[], totalsRow: boolean): WorkSpreadsheetTableColumn[];
|
|
28
|
+
/** True when a proposed totals row would overwrite a populated cell. */
|
|
29
|
+
export declare function spreadsheetTableTotalsRowHasContent(sheet: WorkSpreadsheetSheet, table: WorkSpreadsheetTable, row?: number): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Synchronize totals-row cells after a table metadata change. Explicitly
|
|
32
|
+
* changed column metadata owns the target cell; untouched manual cells remain
|
|
33
|
+
* authoritative. The helper works with Fortune's dense and sparse forms.
|
|
34
|
+
*/
|
|
35
|
+
export declare function synchronizeSpreadsheetTableTotalsRow(sheet: WorkSpreadsheetSheet, previousTable: WorkSpreadsheetTable | undefined, nextTable: WorkSpreadsheetTable, options?: {
|
|
36
|
+
force?: boolean;
|
|
37
|
+
}): WorkSpreadsheetSheet;
|
|
38
|
+
/** Reconcile metadata after a user edits a totals-row cell directly. */
|
|
39
|
+
export declare function reconcileSpreadsheetTableTotalsColumns(sheet: WorkSpreadsheetSheet, table: WorkSpreadsheetTable, editedOffsets?: ReadonlySet<number>): WorkSpreadsheetTableColumn[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Selection } from '@fortune-sheet/core';
|
|
2
2
|
import type { WorkSpreadsheetContent, WorkSpreadsheetSheet, WorkSpreadsheetTable, WorkSpreadsheetTableStyle } from '../work-types';
|
|
3
3
|
import { type SpreadsheetCellRange } from './spreadsheet-cell-range';
|
|
4
|
+
import { type SpreadsheetTableTotalsColumnPatch } from './spreadsheet-table-totals';
|
|
4
5
|
export { MAX_SPREADSHEET_TABLE_CELLS } from './spreadsheet-table-limits';
|
|
5
6
|
export interface SpreadsheetTableTarget {
|
|
6
7
|
sheetId: string;
|
|
@@ -9,6 +10,7 @@ export interface SpreadsheetTableTarget {
|
|
|
9
10
|
export interface SpreadsheetTableDialogValue {
|
|
10
11
|
headerRow: boolean;
|
|
11
12
|
rangeReference: string;
|
|
13
|
+
totalsRow?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export interface SpreadsheetTableDialogSource {
|
|
14
16
|
name: string;
|
|
@@ -24,6 +26,10 @@ export interface SpreadsheetTableRequest {
|
|
|
24
26
|
range: SpreadsheetCellRange;
|
|
25
27
|
sheetId: string;
|
|
26
28
|
style?: WorkSpreadsheetTableStyle;
|
|
29
|
+
/** Create the table with a native totals row appended to the range. */
|
|
30
|
+
totalsRow?: boolean;
|
|
31
|
+
/** Initial per-column totals metadata, keyed by zero-based table offset. */
|
|
32
|
+
totalsColumns?: Readonly<Record<string, SpreadsheetTableTotalsColumnPatch>> | readonly (SpreadsheetTableTotalsColumnPatch | null | undefined)[];
|
|
27
33
|
}
|
|
28
34
|
export type SpreadsheetTableValidation = {
|
|
29
35
|
columns: string[];
|
|
@@ -36,7 +42,7 @@ export type SpreadsheetTableValidation = {
|
|
|
36
42
|
message: string;
|
|
37
43
|
ok: false;
|
|
38
44
|
};
|
|
39
|
-
export type SpreadsheetTableErrorCode = 'auto-filter-overlap' | 'invalid-column-name' | 'invalid-name' | 'invalid-range' | 'invalid-style' | 'merged-range' | 'name-conflict' | 'out-of-bounds' | 'protected-range' | 'pivot-table' | 'range-too-large' | 'sheet-not-found' | 'table-overlap';
|
|
45
|
+
export type SpreadsheetTableErrorCode = 'auto-filter-overlap' | 'invalid-column-name' | 'invalid-name' | 'invalid-range' | 'invalid-style' | 'invalid-totals' | 'merged-range' | 'name-conflict' | 'out-of-bounds' | 'protected-range' | 'pivot-table' | 'range-too-large' | 'sheet-not-found' | 'table-overlap' | 'totals-row-content';
|
|
40
46
|
export interface SpreadsheetTableDesignPatch {
|
|
41
47
|
name?: string;
|
|
42
48
|
showColumnStripes?: boolean;
|
|
@@ -44,6 +50,10 @@ export interface SpreadsheetTableDesignPatch {
|
|
|
44
50
|
showLastColumn?: boolean;
|
|
45
51
|
showRowStripes?: boolean;
|
|
46
52
|
style?: WorkSpreadsheetTableStyle;
|
|
53
|
+
/** Toggle the native totals row. Enabling appends one row to the table. */
|
|
54
|
+
totalsRow?: boolean;
|
|
55
|
+
/** Update per-column totals function, label, or custom formula. */
|
|
56
|
+
totalsColumns?: Readonly<Record<string, SpreadsheetTableTotalsColumnPatch>> | readonly (SpreadsheetTableTotalsColumnPatch | null | undefined)[];
|
|
47
57
|
}
|
|
48
58
|
export declare function createSpreadsheetTableDialogSource(content: WorkSpreadsheetContent, target: SpreadsheetTableTarget): SpreadsheetTableDialogSource | null;
|
|
49
59
|
export declare function spreadsheetTableRangeFromText(value: string): SpreadsheetCellRange | null;
|
|
@@ -241,7 +241,21 @@ export interface WorkSpreadsheetTableColumn {
|
|
|
241
241
|
* stores the editable form with a leading `=`; XLSX table parts omit it.
|
|
242
242
|
*/
|
|
243
243
|
calculatedFormula?: string;
|
|
244
|
+
/**
|
|
245
|
+
* Native totals-row aggregation. An omitted value means that the totals
|
|
246
|
+
* cell is not owned by the table. The controlled model uses camelCase names
|
|
247
|
+
* while XLSX table parts use their OOXML spellings.
|
|
248
|
+
*/
|
|
249
|
+
totalsFunction?: WorkSpreadsheetTableTotalsFunction;
|
|
250
|
+
/** Optional text label rendered in this column's totals-row cell. */
|
|
251
|
+
totalsLabel?: string;
|
|
252
|
+
/**
|
|
253
|
+
* A bounded custom totals-row formula. The controlled model stores the
|
|
254
|
+
* editable form with a leading `=`; XLSX table parts omit it.
|
|
255
|
+
*/
|
|
256
|
+
totalsFormula?: string;
|
|
244
257
|
}
|
|
258
|
+
export type WorkSpreadsheetTableTotalsFunction = 'sum' | 'average' | 'count' | 'countNums' | 'max' | 'min' | 'stdDev' | 'stdDevP' | 'var' | 'varP' | 'custom';
|
|
245
259
|
export type WorkSpreadsheetDynamicFilter = 'above-average' | 'below-average' | 'tomorrow' | 'today' | 'yesterday' | 'next-week' | 'this-week' | 'last-week' | 'next-month' | 'this-month' | 'last-month' | 'next-quarter' | 'this-quarter' | 'last-quarter' | 'next-year' | 'this-year' | 'last-year' | 'year-to-date' | 'quarter-1' | 'quarter-2' | 'quarter-3' | 'quarter-4' | 'month-1' | 'month-2' | 'month-3' | 'month-4' | 'month-5' | 'month-6' | 'month-7' | 'month-8' | 'month-9' | 'month-10' | 'month-11' | 'month-12';
|
|
246
260
|
export type WorkSpreadsheetTableFilterCriteria = {
|
|
247
261
|
type: 'values';
|
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
export declare const browserScalarFunctionArities: Map<string, readonly [minimum: number, maximum: number]>;
|
|
2
2
|
export declare function normalizeSpreadsheetFunctionName(name: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Evaluate the bounded browser implementation of `SUBTOTAL`.
|
|
5
|
+
*
|
|
6
|
+
* Fortune delegates unknown functions to Formula.js. Formula.js returns an
|
|
7
|
+
* undefined value for an unknown function number, which the parser can then
|
|
8
|
+
* coerce into a boolean. Keeping this small implementation here makes the
|
|
9
|
+
* JavaScript fallback deterministic and aligned with the Rust kernel. The
|
|
10
|
+
* calculation request does not carry hidden-row metadata yet, so the 1–11
|
|
11
|
+
* and 101–111 function-number families intentionally have the same result.
|
|
12
|
+
*/
|
|
13
|
+
export declare function evaluateParserSubtotal(parameters: readonly unknown[]): unknown;
|
|
3
14
|
export declare function normalizeFormulaForFortuneParser(formula: string): string;
|
package/dist/office-kernel.wasm
CHANGED
|
Binary file
|
|
@@ -13727,6 +13727,13 @@
|
|
|
13727
13727
|
1
|
|
13728
13728
|
]
|
|
13729
13729
|
],
|
|
13730
|
+
[
|
|
13731
|
+
'SUBTOTAL',
|
|
13732
|
+
[
|
|
13733
|
+
2,
|
|
13734
|
+
255
|
|
13735
|
+
]
|
|
13736
|
+
],
|
|
13730
13737
|
[
|
|
13731
13738
|
'SUM',
|
|
13732
13739
|
[
|
|
@@ -13747,6 +13754,90 @@
|
|
|
13747
13754
|
while(normalized.startsWith('_XLFN.') || normalized.startsWith('_XLWS.'))normalized = normalized.slice(6);
|
|
13748
13755
|
return normalized;
|
|
13749
13756
|
}
|
|
13757
|
+
function evaluateParserSubtotal(parameters) {
|
|
13758
|
+
const codeValue = parserNumericValue(parameters[0]);
|
|
13759
|
+
if (void 0 === codeValue) return 'VALUE!';
|
|
13760
|
+
const code = Math.trunc(codeValue);
|
|
13761
|
+
const values = parameters.slice(1).flatMap((parameter)=>flattenParserValues(parameter));
|
|
13762
|
+
for (const value of values){
|
|
13763
|
+
const error = parserErrorValue(value);
|
|
13764
|
+
if (error) return error;
|
|
13765
|
+
}
|
|
13766
|
+
switch(code){
|
|
13767
|
+
case 1:
|
|
13768
|
+
case 101:
|
|
13769
|
+
return parserSubtotalNumeric(values, 'average');
|
|
13770
|
+
case 2:
|
|
13771
|
+
case 102:
|
|
13772
|
+
return values.filter((value)=>'number' == typeof value).length;
|
|
13773
|
+
case 3:
|
|
13774
|
+
case 103:
|
|
13775
|
+
return values.filter((value)=>null != value).length;
|
|
13776
|
+
case 4:
|
|
13777
|
+
case 104:
|
|
13778
|
+
return parserSubtotalNumeric(values, 'max');
|
|
13779
|
+
case 5:
|
|
13780
|
+
case 105:
|
|
13781
|
+
return parserSubtotalNumeric(values, 'min');
|
|
13782
|
+
case 6:
|
|
13783
|
+
case 106:
|
|
13784
|
+
return parserSubtotalNumeric(values, 'product');
|
|
13785
|
+
case 7:
|
|
13786
|
+
case 107:
|
|
13787
|
+
return parserSubtotalNumeric(values, 'stddev');
|
|
13788
|
+
case 8:
|
|
13789
|
+
case 108:
|
|
13790
|
+
return parserSubtotalNumeric(values, 'stddevp');
|
|
13791
|
+
case 9:
|
|
13792
|
+
case 109:
|
|
13793
|
+
return parserSubtotalNumeric(values, 'sum');
|
|
13794
|
+
case 10:
|
|
13795
|
+
case 110:
|
|
13796
|
+
return parserSubtotalNumeric(values, 'var');
|
|
13797
|
+
case 11:
|
|
13798
|
+
case 111:
|
|
13799
|
+
return parserSubtotalNumeric(values, 'varp');
|
|
13800
|
+
default:
|
|
13801
|
+
return 'VALUE!';
|
|
13802
|
+
}
|
|
13803
|
+
}
|
|
13804
|
+
function parserSubtotalNumeric(values, operation) {
|
|
13805
|
+
const numbers = values.filter((value)=>'number' == typeof value && Number.isFinite(value));
|
|
13806
|
+
const count = numbers.length;
|
|
13807
|
+
const sum = numbers.reduce((total, value)=>total + value, 0);
|
|
13808
|
+
if ('sum' === operation) return finiteParserNumber(sum);
|
|
13809
|
+
if ('average' === operation) return count ? finiteParserNumber(sum / count) : 'DIV/0!';
|
|
13810
|
+
if ('max' === operation) return finiteParserNumber(Math.max(...numbers, 0));
|
|
13811
|
+
if ('min' === operation) return finiteParserNumber(Math.min(...numbers, 0));
|
|
13812
|
+
if ('product' === operation) return finiteParserNumber(count ? numbers.reduce((total, value)=>total * value, 1) : 0);
|
|
13813
|
+
if ('stddev' === operation || 'var' === operation) {
|
|
13814
|
+
if (count < 2) return 'DIV/0!';
|
|
13815
|
+
} else if (0 === count) return 'DIV/0!';
|
|
13816
|
+
const mean = sum / count;
|
|
13817
|
+
const divisor = 'stddev' === operation || 'var' === operation ? count - 1 : count;
|
|
13818
|
+
const variance = numbers.reduce((total, value)=>total + (value - mean) ** 2, 0) / divisor;
|
|
13819
|
+
return finiteParserNumber('stddev' === operation || 'stddevp' === operation ? Math.sqrt(variance) : variance);
|
|
13820
|
+
}
|
|
13821
|
+
function flattenParserValues(value) {
|
|
13822
|
+
if (!Array.isArray(value)) return [
|
|
13823
|
+
value
|
|
13824
|
+
];
|
|
13825
|
+
return value.flatMap((entry)=>flattenParserValues(entry));
|
|
13826
|
+
}
|
|
13827
|
+
function parserNumericValue(value) {
|
|
13828
|
+
if ('number' == typeof value) return Number.isFinite(value) ? value : void 0;
|
|
13829
|
+
if ('boolean' == typeof value) return value ? 1 : 0;
|
|
13830
|
+
if ('string' != typeof value || !value.trim()) return;
|
|
13831
|
+
const parsed = Number(value);
|
|
13832
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
13833
|
+
}
|
|
13834
|
+
function parserErrorValue(value) {
|
|
13835
|
+
if (value instanceof Error) return value.message.startsWith('#') ? value.message.slice(1) : 'VALUE!';
|
|
13836
|
+
return 'string' == typeof value && value.startsWith('#') ? value.slice(1) : void 0;
|
|
13837
|
+
}
|
|
13838
|
+
function finiteParserNumber(value) {
|
|
13839
|
+
return Number.isFinite(value) ? value : 'NUM!';
|
|
13840
|
+
}
|
|
13750
13841
|
function normalizeFormulaForFortuneParser(formula) {
|
|
13751
13842
|
const source = formula.replace(/^=/, '');
|
|
13752
13843
|
let output = '';
|
|
@@ -14608,10 +14699,11 @@
|
|
|
14608
14699
|
let unsupportedFunction;
|
|
14609
14700
|
let materializedRangeCells = 0;
|
|
14610
14701
|
parser.setFunction('IFERROR', evaluateParserIfError).setFunction('ROW', (parameters)=>parameters.length ? null : coordinate.row + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : coordinate.column + 1);
|
|
14611
|
-
parser.on('callFunction', (name, parameters)=>{
|
|
14702
|
+
parser.on('callFunction', (name, parameters, done)=>{
|
|
14612
14703
|
const normalized = normalizeSpreadsheetFunctionName(name);
|
|
14613
14704
|
const arity = browserScalarFunctionArities.get(normalized);
|
|
14614
14705
|
if (!arity || parameters.length < arity[0] || parameters.length > arity[1]) unsupportedFunction ??= name.toUpperCase();
|
|
14706
|
+
if ('SUBTOTAL' === normalized) done(evaluateParserSubtotal(parameters));
|
|
14615
14707
|
});
|
|
14616
14708
|
parser.on('callCellValue', (cell, _options, done)=>{
|
|
14617
14709
|
const dependency = this.resolveCoordinate(indexed.sheet, cell);
|
package/dist/styles.css
CHANGED
|
@@ -18364,6 +18364,113 @@ sub [data-office-character-position-half-points], sup [data-office-character-pos
|
|
|
18364
18364
|
min-width: 76px;
|
|
18365
18365
|
}
|
|
18366
18366
|
|
|
18367
|
+
.work-spreadsheet-table-totals-root {
|
|
18368
|
+
display: flex;
|
|
18369
|
+
}
|
|
18370
|
+
|
|
18371
|
+
.work-office-ribbon .work-spreadsheet-table-totals-trigger {
|
|
18372
|
+
min-width: 70px;
|
|
18373
|
+
}
|
|
18374
|
+
|
|
18375
|
+
.work-spreadsheet-table-totals-menu {
|
|
18376
|
+
border: 1px solid var(--a3s-overlay-border);
|
|
18377
|
+
border-radius: var(--a3s-overlay-radius);
|
|
18378
|
+
background: var(--a3s-overlay-surface);
|
|
18379
|
+
width: min(560px, 100vw - 24px);
|
|
18380
|
+
max-height: min(650px, 100vh - 32px);
|
|
18381
|
+
box-shadow: var(--a3s-shadow-soft);
|
|
18382
|
+
padding: 14px;
|
|
18383
|
+
overflow: auto;
|
|
18384
|
+
}
|
|
18385
|
+
|
|
18386
|
+
.work-spreadsheet-table-totals-content {
|
|
18387
|
+
gap: 10px;
|
|
18388
|
+
display: grid;
|
|
18389
|
+
}
|
|
18390
|
+
|
|
18391
|
+
.work-spreadsheet-table-totals-hint {
|
|
18392
|
+
color: var(--a3s-muted);
|
|
18393
|
+
margin: -4px 0 2px;
|
|
18394
|
+
font-size: 11px;
|
|
18395
|
+
line-height: 1.45;
|
|
18396
|
+
}
|
|
18397
|
+
|
|
18398
|
+
.work-spreadsheet-table-totals-columns {
|
|
18399
|
+
gap: 8px;
|
|
18400
|
+
max-height: 440px;
|
|
18401
|
+
padding-right: 2px;
|
|
18402
|
+
display: grid;
|
|
18403
|
+
overflow: auto;
|
|
18404
|
+
}
|
|
18405
|
+
|
|
18406
|
+
.work-spreadsheet-table-totals-column {
|
|
18407
|
+
border: 1px solid var(--a3s-line);
|
|
18408
|
+
background: var(--a3s-panel-soft);
|
|
18409
|
+
border-radius: 7px;
|
|
18410
|
+
grid-template-columns: minmax(92px, 1fr) minmax(100px, 1fr) minmax(100px, 1fr);
|
|
18411
|
+
align-items: end;
|
|
18412
|
+
gap: 7px;
|
|
18413
|
+
padding: 9px;
|
|
18414
|
+
display: grid;
|
|
18415
|
+
}
|
|
18416
|
+
|
|
18417
|
+
.work-spreadsheet-table-totals-column > strong {
|
|
18418
|
+
color: var(--a3s-ink);
|
|
18419
|
+
text-overflow: ellipsis;
|
|
18420
|
+
white-space: nowrap;
|
|
18421
|
+
grid-column: 1 / -1;
|
|
18422
|
+
font-size: 11px;
|
|
18423
|
+
font-weight: 650;
|
|
18424
|
+
overflow: hidden;
|
|
18425
|
+
}
|
|
18426
|
+
|
|
18427
|
+
.work-spreadsheet-table-totals-column label {
|
|
18428
|
+
gap: 3px;
|
|
18429
|
+
min-width: 0;
|
|
18430
|
+
display: grid;
|
|
18431
|
+
}
|
|
18432
|
+
|
|
18433
|
+
.work-spreadsheet-table-totals-column label > span {
|
|
18434
|
+
color: var(--a3s-muted);
|
|
18435
|
+
font-size: 9px;
|
|
18436
|
+
line-height: 1;
|
|
18437
|
+
}
|
|
18438
|
+
|
|
18439
|
+
.work-spreadsheet-table-totals-column select, .work-spreadsheet-table-totals-column input {
|
|
18440
|
+
box-sizing: border-box;
|
|
18441
|
+
border: 1px solid var(--a3s-line-strong);
|
|
18442
|
+
background: var(--a3s-panel);
|
|
18443
|
+
width: 100%;
|
|
18444
|
+
min-width: 0;
|
|
18445
|
+
height: 28px;
|
|
18446
|
+
color: var(--a3s-ink);
|
|
18447
|
+
border-radius: 5px;
|
|
18448
|
+
padding: 0 7px;
|
|
18449
|
+
font-size: 11px;
|
|
18450
|
+
}
|
|
18451
|
+
|
|
18452
|
+
.work-spreadsheet-table-totals-column select:disabled, .work-spreadsheet-table-totals-column input:disabled {
|
|
18453
|
+
cursor: not-allowed;
|
|
18454
|
+
opacity: .55;
|
|
18455
|
+
}
|
|
18456
|
+
|
|
18457
|
+
.work-spreadsheet-table-totals-close {
|
|
18458
|
+
border: 1px solid var(--work-spreadsheet-accent, #159469);
|
|
18459
|
+
background: var(--work-spreadsheet-accent, #159469);
|
|
18460
|
+
color: #fff;
|
|
18461
|
+
cursor: pointer;
|
|
18462
|
+
border-radius: 5px;
|
|
18463
|
+
justify-self: end;
|
|
18464
|
+
min-height: 29px;
|
|
18465
|
+
padding: 0 13px;
|
|
18466
|
+
font-size: 11px;
|
|
18467
|
+
font-weight: 650;
|
|
18468
|
+
}
|
|
18469
|
+
|
|
18470
|
+
.work-spreadsheet-table-totals-close:hover, .work-spreadsheet-table-totals-close:focus-visible {
|
|
18471
|
+
filter: brightness(.94);
|
|
18472
|
+
}
|
|
18473
|
+
|
|
18367
18474
|
.work-spreadsheet-table-style-preview {
|
|
18368
18475
|
background: #fff;
|
|
18369
18476
|
border: 1px solid #0f172a29;
|
|
@@ -18455,6 +18562,19 @@ sub [data-office-character-position-half-points], sup [data-office-character-pos
|
|
|
18455
18562
|
padding: 8px;
|
|
18456
18563
|
}
|
|
18457
18564
|
|
|
18565
|
+
.work-spreadsheet-table-totals-menu {
|
|
18566
|
+
width: min(440px, 100vw - 16px);
|
|
18567
|
+
padding: 10px;
|
|
18568
|
+
}
|
|
18569
|
+
|
|
18570
|
+
.work-spreadsheet-table-totals-column {
|
|
18571
|
+
grid-template-columns: 1fr 1fr;
|
|
18572
|
+
}
|
|
18573
|
+
|
|
18574
|
+
.work-spreadsheet-table-totals-column label:last-child {
|
|
18575
|
+
grid-column: 1 / -1;
|
|
18576
|
+
}
|
|
18577
|
+
|
|
18458
18578
|
.work-spreadsheet-table-style-family > div {
|
|
18459
18579
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
18460
18580
|
}
|
|
@@ -1430,8 +1430,14 @@ formulas authored on data rows and totals/header/data selectors. A consistent
|
|
|
1430
1430
|
current-row rule is persisted in the table metadata and fills only empty cells
|
|
1431
1431
|
in newly inserted body rows; manual values and conflicting formulas fail closed.
|
|
1432
1432
|
Dense and sparse sheet representations use the same path, and native XLSX
|
|
1433
|
-
`<calculatedColumnFormula>` metadata round-trips.
|
|
1434
|
-
|
|
1433
|
+
`<calculatedColumnFormula>` metadata round-trips. Native totals-row authoring
|
|
1434
|
+
now extends the same model: Table Design controls the row and each column's
|
|
1435
|
+
label, aggregate, or custom formula; native aggregates map to filter-aware
|
|
1436
|
+
`SUBTOTAL` codes; generated cells are reconciled after direct edits, row/column
|
|
1437
|
+
changes, and table renames; and `totalsRowFunction`, `totalsRowLabel`, and
|
|
1438
|
+
`totalsRowFormula` survive XLSX and Yjs round trips. Occupied, merged,
|
|
1439
|
+
protected, overlapping, AutoFilter, unsafe, external, and over-budget targets
|
|
1440
|
+
fail closed. Slicers and external/query tables remain open gates.
|
|
1435
1441
|
|
|
1436
1442
|
Exit criteria: scrolling and selection do not scale with total row count;
|
|
1437
1443
|
incremental recalculation touches only affected dependency subgraphs; XLSX
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a3s-lab/office",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Open-source collaborative browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"office",
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
"playground:visual:spreadsheet-rich-text": "playwright test visual-tests/spreadsheet-rich-text.functional.spec.ts --config playwright.config.ts",
|
|
115
115
|
"playground:visual:spreadsheet-underline": "playwright test visual-tests/spreadsheet-underline.functional.spec.ts --config playwright.config.ts",
|
|
116
116
|
"playground:visual:spreadsheet-table": "playwright test visual-tests/spreadsheet-table.functional.spec.ts --config playwright.config.ts",
|
|
117
|
+
"playground:visual:spreadsheet-table-totals": "playwright test visual-tests/spreadsheet-table-totals.functional.spec.ts --config playwright.config.ts",
|
|
117
118
|
"playground:visual:presentation-animation": "playwright test visual-tests/presentation-animation.functional.spec.ts --config playwright.config.ts",
|
|
118
119
|
"playground:visual:pdf-page-organization": "playwright test visual-tests/pdf-page-organization.functional.spec.ts --config playwright.config.ts",
|
|
119
120
|
"playground:visual:update": "playwright test --config playwright.config.ts --update-snapshots",
|
|
@@ -178,6 +179,8 @@
|
|
|
178
179
|
"test:e2e:spreadsheet-underline-styles:check": "a3s-test check tests/e2e/spreadsheet-underline-styles.acl --json",
|
|
179
180
|
"test:e2e:spreadsheet-table": "a3s-test run tests/e2e/spreadsheet-table.acl --json",
|
|
180
181
|
"test:e2e:spreadsheet-table:check": "a3s-test check tests/e2e/spreadsheet-table.acl --json",
|
|
182
|
+
"test:e2e:spreadsheet-table-totals": "a3s-test run tests/e2e/spreadsheet-table-totals.acl --json",
|
|
183
|
+
"test:e2e:spreadsheet-table-totals:check": "a3s-test check tests/e2e/spreadsheet-table-totals.acl --json",
|
|
181
184
|
"test:e2e:spreadsheet-cell-fill": "a3s-test run tests/e2e/spreadsheet-cell-fill.acl --json",
|
|
182
185
|
"test:e2e:spreadsheet-cell-fill:check": "a3s-test check tests/e2e/spreadsheet-cell-fill.acl --json",
|
|
183
186
|
"test:e2e:spreadsheet-auto-sum": "a3s-test run tests/e2e/spreadsheet-auto-sum.acl --json",
|