@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 CHANGED
@@ -56,7 +56,7 @@ workflows, and a separate Rust automation plane.
56
56
 
57
57
  ## Latest on `main`
58
58
 
59
- The `0.36.0` release exposes these capabilities as normal user-facing
59
+ The `0.37.0` release exposes these capabilities as normal user-facing
60
60
  Playground templates, with matching implementation detail in the documentation:
61
61
 
62
62
  | Editor | Latest capability | Public entry |
@@ -68,7 +68,7 @@ Playground templates, with matching implementation detail in the documentation:
68
68
  | Writer | Native character shading with exact `w:shd` patterns, foreground/background color identity, explicit resets, authoring, and DOCX reopen | Playground **Latest capabilities → 字符底纹** · [Document reference](docs/latest/en/components/document.mdx#native-character-shading) |
69
69
  | Writer | Independent Latin, East Asian, and bidi proofing languages plus explicit `w:noProof` inclusion/exclusion | Playground **Latest capabilities → 校对语言** · [Document reference](docs/latest/en/components/document.mdx#native-proofing-languages) |
70
70
  | Spreadsheet | Complete common Data Validation input, blank/dropdown, and Stop/Warning/Information error settings with native XLSX round trips | Playground **Latest capabilities → 数据验证** · [Spreadsheet reference](docs/latest/en/components/spreadsheet.mdx#data-validation) |
71
- | Spreadsheet | Bounded structured-reference calculation plus automatic calculated-column fill for table names/display names, contiguous columns, header/data/totals selectors, qualified references, and table-local current-row formulas across Rust/WASM and JavaScript fallback | Playground **Latest capabilities → 结构化引用** · [Spreadsheet reference](docs/latest/en/components/spreadsheet.mdx#structured-reference-calculation) |
71
+ | Spreadsheet | Native totals-row authoring with per-column aggregates, labels, custom formulas, filtered-row-aware `SUBTOTAL`, automatic calculated-column fill for newly inserted table body rows, and bounded structured-reference calculation across Rust/WASM and JavaScript fallback | Playground **Latest capabilities → 结构化引用** · [Totals-row reference](docs/latest/en/components/spreadsheet.mdx#native-totals-row-authoring) · [Structured-reference calculation](docs/latest/en/components/spreadsheet.mdx#structured-reference-calculation) |
72
72
  | PDF | Insert, delete, rotate, reorder, extract, merge, and split pages through a dedicated Web Worker, with Blob-level Undo/Redo and independent binary reopen verification | Playground **Latest capabilities → 组织 PDF 页面** · [PDF reference](docs/latest/en/components/pdf.mdx#page-organization) |
73
73
 
74
74
  All nine entries are reachable from the first Playground viewport. The
@@ -152,7 +152,7 @@ baseline rather than one specific release.
152
152
  | Formatting and style rendering | **Partial** — native fonts, colors, borders, alignment, number formats, cell styles, all 17 native non-solid OOXML pattern fills, native linear/path XLSX gradients, and one Format Cells surface that authors none, solid, pattern, or gradient fills with exact geometry and 2–256 ordered stops; static date/time entry, contrast-safe font preview, native XLSX rich-text runs, selected-text font formatting, direct formula-bar/F2 insertion or deletion, and bounded authenticated formatted-HTML paste | Disjoint multi-edit rich-text authoring, broader themes, locale formats, and advanced style effects |
153
153
  | Ribbon and shortcuts | **Partial** — common Office-style Home/Data/View commands, grid-scoped shortcuts, and focused Font-dialog aliases | Larger command catalog and platform-specific accelerators |
154
154
  | Formulas and recalculation | **Partial** — dependency-aware calculation plus bounded structured references (`Table[Column]`, contiguous ranges, `#All`/`#Headers`/`#Data`/`#Totals`/`#This Row`, and `[@Column]`) in the shared Rust/WASM and JavaScript paths | Wider functions, arrays, volatile semantics, and calculation parity |
155
- | Tables, pivots, charts, and rules | **Partial** — native tables, bounded structured-reference formulas, calculated-column formulas with automatic fill for newly inserted body rows, pivots, charts, conditional formatting, and common validation rules with complete input/error-setting authoring | Complete totals-row authoring, slicers, pivot charts, custom/dependent validation rules, advanced analysis, and broader browser alert flows |
155
+ | Tables, pivots, charts, and rules | **Partial** — native tables, bounded structured-reference formulas, calculated-column formulas with automatic fill for newly inserted body rows, complete common totals-row authoring, pivots, charts, conditional formatting, and common validation rules with complete input/error-setting authoring | Slicers, pivot charts, custom/dependent validation rules, advanced analysis, and broader browser alert flows |
156
156
  | Large worksheets | **Supported with boundaries** — maximum-dimension sparse import/editing and viewport-bounded Canvas painting | Highly optimized native grid with hardware-dependent limits |
157
157
  | Files and printing | **Partial** — XLS/XLSX/ODS/CSV import, XLSX export, and PDF output | Broader round trips, external data, print fidelity, and legacy conversion |
158
158
  | External data, macros, and specialist analysis | **Gap** — active macros are never executed; bounded models are still needed for data connections and solver-like tools | Established data, macro/add-in, scenario, and optimization ecosystems |
@@ -756,9 +756,15 @@ conflicting column fails closed and drops its automatic rule. XLSX
756
756
  `<calculatedColumnFormula>` metadata round-trips with the leading `=` restored
757
757
  in the controlled model, while unsafe or external formulas are omitted.
758
758
  The Playground's **结构化引用** template shows a calculated `Revenue` column,
759
- an insertion hint, a totals row, and a worksheet-qualified summary. Complete
760
- totals-row authoring, slicers, and external/query tables remain explicit
761
- boundaries.
759
+ an insertion hint, an editable totals row, and a worksheet-qualified summary.
760
+ The Table Design totals menu enables or disables the row and assigns each
761
+ column a label, one of ten native aggregate functions, or a bounded custom
762
+ formula. Native aggregates emit filtered-row-aware `SUBTOTAL` formulas; direct
763
+ cell edits reconcile back into table metadata, table structure changes move
764
+ the row without overwriting manual cells, and table renames rewrite only
765
+ generated formulas. Dense and sparse worksheets, Yjs collaboration, and XLSX
766
+ `totalsRowFunction`, `totalsRowLabel`, and `totalsRowFormula` round trips share
767
+ the same model. Slicers and external/query tables remain explicit boundaries.
762
768
 
763
769
  Use `downloadArtifact` to start a browser download or
764
770
  `createArtifactBlob` when your application owns upload and persistence.
@@ -1641,6 +1647,7 @@ keep working without hard-coded return URLs.
1641
1647
 
1642
1648
  - [Live Playground](https://a3s-lab.github.io/Office/playground/)
1643
1649
  - [Documentation center](https://a3s-lab.github.io/Office/docs/)
1650
+ - [A3S Office 0.37.0 documentation](https://a3s-lab.github.io/Office/docs/0.37.0/)
1644
1651
  - [A3S Office 0.36.0 documentation](https://a3s-lab.github.io/Office/docs/0.36.0/)
1645
1652
  - [A3S Office 0.34.0 documentation](https://a3s-lab.github.io/Office/docs/0.34.0/)
1646
1653
  - [A3S Office 0.33.0 documentation](https://a3s-lab.github.io/Office/docs/0.33.0/)
package/dist/0~7048.js CHANGED
@@ -1352,6 +1352,13 @@ const browserScalarFunctionArities = new Map([
1352
1352
  1
1353
1353
  ]
1354
1354
  ],
1355
+ [
1356
+ 'SUBTOTAL',
1357
+ [
1358
+ 2,
1359
+ 255
1360
+ ]
1361
+ ],
1355
1362
  [
1356
1363
  'SUM',
1357
1364
  [
@@ -1372,6 +1379,90 @@ function normalizeSpreadsheetFunctionName(name) {
1372
1379
  while(normalized.startsWith('_XLFN.') || normalized.startsWith('_XLWS.'))normalized = normalized.slice(6);
1373
1380
  return normalized;
1374
1381
  }
1382
+ function evaluateParserSubtotal(parameters) {
1383
+ const codeValue = parserNumericValue(parameters[0]);
1384
+ if (void 0 === codeValue) return 'VALUE!';
1385
+ const code = Math.trunc(codeValue);
1386
+ const values = parameters.slice(1).flatMap((parameter)=>flattenParserValues(parameter));
1387
+ for (const value of values){
1388
+ const error = parserErrorValue(value);
1389
+ if (error) return error;
1390
+ }
1391
+ switch(code){
1392
+ case 1:
1393
+ case 101:
1394
+ return parserSubtotalNumeric(values, 'average');
1395
+ case 2:
1396
+ case 102:
1397
+ return values.filter((value)=>'number' == typeof value).length;
1398
+ case 3:
1399
+ case 103:
1400
+ return values.filter((value)=>null != value).length;
1401
+ case 4:
1402
+ case 104:
1403
+ return parserSubtotalNumeric(values, 'max');
1404
+ case 5:
1405
+ case 105:
1406
+ return parserSubtotalNumeric(values, 'min');
1407
+ case 6:
1408
+ case 106:
1409
+ return parserSubtotalNumeric(values, 'product');
1410
+ case 7:
1411
+ case 107:
1412
+ return parserSubtotalNumeric(values, 'stddev');
1413
+ case 8:
1414
+ case 108:
1415
+ return parserSubtotalNumeric(values, 'stddevp');
1416
+ case 9:
1417
+ case 109:
1418
+ return parserSubtotalNumeric(values, 'sum');
1419
+ case 10:
1420
+ case 110:
1421
+ return parserSubtotalNumeric(values, 'var');
1422
+ case 11:
1423
+ case 111:
1424
+ return parserSubtotalNumeric(values, 'varp');
1425
+ default:
1426
+ return 'VALUE!';
1427
+ }
1428
+ }
1429
+ function parserSubtotalNumeric(values, operation) {
1430
+ const numbers = values.filter((value)=>'number' == typeof value && Number.isFinite(value));
1431
+ const count = numbers.length;
1432
+ const sum = numbers.reduce((total, value)=>total + value, 0);
1433
+ if ('sum' === operation) return finiteParserNumber(sum);
1434
+ if ('average' === operation) return count ? finiteParserNumber(sum / count) : 'DIV/0!';
1435
+ if ('max' === operation) return finiteParserNumber(Math.max(...numbers, 0));
1436
+ if ('min' === operation) return finiteParserNumber(Math.min(...numbers, 0));
1437
+ if ('product' === operation) return finiteParserNumber(count ? numbers.reduce((total, value)=>total * value, 1) : 0);
1438
+ if ('stddev' === operation || 'var' === operation) {
1439
+ if (count < 2) return 'DIV/0!';
1440
+ } else if (0 === count) return 'DIV/0!';
1441
+ const mean = sum / count;
1442
+ const divisor = 'stddev' === operation || 'var' === operation ? count - 1 : count;
1443
+ const variance = numbers.reduce((total, value)=>total + (value - mean) ** 2, 0) / divisor;
1444
+ return finiteParserNumber('stddev' === operation || 'stddevp' === operation ? Math.sqrt(variance) : variance);
1445
+ }
1446
+ function flattenParserValues(value) {
1447
+ if (!Array.isArray(value)) return [
1448
+ value
1449
+ ];
1450
+ return value.flatMap((entry)=>flattenParserValues(entry));
1451
+ }
1452
+ function parserNumericValue(value) {
1453
+ if ('number' == typeof value) return Number.isFinite(value) ? value : void 0;
1454
+ if ('boolean' == typeof value) return value ? 1 : 0;
1455
+ if ('string' != typeof value || !value.trim()) return;
1456
+ const parsed = Number(value);
1457
+ return Number.isFinite(parsed) ? parsed : void 0;
1458
+ }
1459
+ function parserErrorValue(value) {
1460
+ if (value instanceof Error) return value.message.startsWith('#') ? value.message.slice(1) : 'VALUE!';
1461
+ return 'string' == typeof value && value.startsWith('#') ? value.slice(1) : void 0;
1462
+ }
1463
+ function finiteParserNumber(value) {
1464
+ return Number.isFinite(value) ? value : 'NUM!';
1465
+ }
1375
1466
  function normalizeFormulaForFortuneParser(formula) {
1376
1467
  const source = formula.replace(/^=/, '');
1377
1468
  let output = '';
@@ -2154,10 +2245,11 @@ class JavaScriptSpreadsheetEvaluator {
2154
2245
  let unsupportedFunction;
2155
2246
  let materializedRangeCells = 0;
2156
2247
  parser.setFunction('IFERROR', evaluateParserIfError).setFunction('ROW', (parameters)=>parameters.length ? null : coordinate.row + 1).setFunction('COLUMN', (parameters)=>parameters.length ? null : coordinate.column + 1);
2157
- parser.on('callFunction', (name, parameters)=>{
2248
+ parser.on('callFunction', (name, parameters, done)=>{
2158
2249
  const normalized = normalizeSpreadsheetFunctionName(name);
2159
2250
  const arity = browserScalarFunctionArities.get(normalized);
2160
2251
  if (!arity || parameters.length < arity[0] || parameters.length > arity[1]) unsupportedFunction ??= name.toUpperCase();
2252
+ if ('SUBTOTAL' === normalized) done(evaluateParserSubtotal(parameters));
2161
2253
  });
2162
2254
  parser.on('callCellValue', (cell, _options, done)=>{
2163
2255
  const dependency = this.resolveCoordinate(indexed.sheet, cell);