@trebco/treb 32.11.0 → 32.14.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/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v32.11. Copyright 2018-2025 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v32.14. Copyright 2018-2025 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
  /*
3
3
  * This file is part of TREB.
4
4
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "32.11.0",
3
+ "version": "32.14.0",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -1757,8 +1757,10 @@ export const BaseFunctionLibrary: FunctionMap = {
1757
1757
  //}
1758
1758
 
1759
1759
  let rng: Area|undefined;
1760
+ let return_from_array = false;
1760
1761
 
1761
1762
  if (return_array.type === ValueType.array) {
1763
+
1762
1764
  // console.info({return_array});
1763
1765
 
1764
1766
  const arr = return_array.value;
@@ -1772,9 +1774,16 @@ export const BaseFunctionLibrary: FunctionMap = {
1772
1774
  rng = new Area(start.value.address, end.value.address);
1773
1775
  }
1774
1776
 
1777
+ // we can allow a regular array here... perhaps we should
1778
+ // check the dimensions to ensure they match? TODO/FIXME
1779
+
1780
+ else {
1781
+ return_from_array = true;
1782
+ }
1783
+
1775
1784
  }
1776
1785
 
1777
- if (!rng) {
1786
+ if (!rng && !return_from_array) {
1778
1787
  console.info('invalid range');
1779
1788
  return ReferenceError();
1780
1789
  }
@@ -1848,7 +1857,55 @@ export const BaseFunctionLibrary: FunctionMap = {
1848
1857
  // an array. we might prefer to return a scalar if there's only
1849
1858
  // one value, not sure what's the intended behavior
1850
1859
  //
1851
- const ReturnIndex = (rng: Area, index: number): UnionValue => {
1860
+ const ReturnIndex = (index: number): UnionValue => {
1861
+
1862
+ // FIXME: we could almost certainly merge these two paths
1863
+
1864
+ if (return_from_array && return_array.type === ValueType.array) {
1865
+
1866
+ // instead of a range, we're returning values from a static array
1867
+
1868
+ const src_columns = return_array.value.length;
1869
+ const src_rows = return_array.value[0]?.length || 0;
1870
+ const result: UnionValue[][] = [];
1871
+
1872
+ let start_row = 0;
1873
+ let end_row = src_rows - 1;
1874
+
1875
+ let start_column = 0;
1876
+ let end_column = src_columns - 1;
1877
+
1878
+ if (transpose) {
1879
+ if (search_mode < 0) {
1880
+ index = src_rows - 1 - index; // invert FIXME: test
1881
+ }
1882
+ start_row = end_row = index;
1883
+ }
1884
+ else {
1885
+ if (search_mode < 0) {
1886
+ index = src_columns - 1 - index; // invert FIXME: test
1887
+ }
1888
+ start_column = end_column = index;
1889
+ }
1890
+
1891
+ for (let c = start_column; c <= end_column; c++) {
1892
+ const column: UnionValue[] = [];
1893
+ for (let r = start_row; r <= end_row; r++) {
1894
+ column.push(return_array.value[c][r]);
1895
+ }
1896
+ result.push(column);
1897
+ }
1898
+
1899
+ return {
1900
+ type: ValueType.array,
1901
+ value: result,
1902
+ };
1903
+
1904
+ }
1905
+
1906
+ if (!rng) {
1907
+ throw new Error('invalid range');
1908
+ }
1852
1909
 
1853
1910
  // console.info("transpose?", transpose, {rng}, 'shape', rng.rows, rng.columns);
1854
1911
 
@@ -1953,7 +2010,7 @@ export const BaseFunctionLibrary: FunctionMap = {
1953
2010
 
1954
2011
  // check for exact match first, just in case
1955
2012
  if (value === lookup_value) {
1956
- return ReturnIndex(rng, i);
2013
+ return ReturnIndex(i);
1957
2014
  }
1958
2015
 
1959
2016
  const delta = Math.abs(value - lookup_value);
@@ -1969,7 +2026,7 @@ export const BaseFunctionLibrary: FunctionMap = {
1969
2026
  }
1970
2027
 
1971
2028
  if (index >= 0) {
1972
- return ReturnIndex(rng, index);
2029
+ return ReturnIndex(index);
1973
2030
  }
1974
2031
 
1975
2032
  }
@@ -1987,7 +2044,7 @@ export const BaseFunctionLibrary: FunctionMap = {
1987
2044
  for (let i = 0; i < lookup_array.length; i++) {
1988
2045
  const value = lookup_array[i][0];
1989
2046
  if (typeof value === 'string' && regex.exec(value)) {
1990
- return ReturnIndex(rng, i);
2047
+ return ReturnIndex(i);
1991
2048
  }
1992
2049
  }
1993
2050
 
@@ -2005,7 +2062,7 @@ export const BaseFunctionLibrary: FunctionMap = {
2005
2062
  value = value.toLowerCase();
2006
2063
  }
2007
2064
  if (value === lookup_value) {
2008
- return ReturnIndex(rng, i);
2065
+ return ReturnIndex(i);
2009
2066
  }
2010
2067
  }
2011
2068
 
@@ -2314,6 +2371,8 @@ export const BaseFunctionLibrary: FunctionMap = {
2314
2371
  unroll: true,
2315
2372
  } ],
2316
2373
 
2374
+ xlfn: true,
2375
+
2317
2376
  fn: (a: number, significance = 1, mode?: number) => {
2318
2377
 
2319
2378
  let value = 0;
@@ -20,9 +20,10 @@
20
20
  */
21
21
 
22
22
  import type { FunctionMap } from '../descriptors';
23
- import type { FunctionUnion, UnionValue} from 'treb-base-types';
24
- import { ValueType } from 'treb-base-types';
23
+ import type { CellValue, FunctionUnion, UnionValue} from 'treb-base-types';
24
+ import { Box, ValueType } from 'treb-base-types';
25
25
  import { ArgumentError, ValueError } from '../function-error';
26
+ import { FlattenCellValues } from '../utilities';
26
27
 
27
28
  export const FPFunctionLibrary: FunctionMap = {
28
29
 
@@ -67,6 +68,195 @@ export const FPFunctionLibrary: FunctionMap = {
67
68
  },
68
69
  },
69
70
 
71
+ ChooseCols: {
72
+ description: 'Returns one or more columns from an array, by column index',
73
+ arguments: [{
74
+ name: 'array',
75
+ }, {
76
+ name: 'column',
77
+ repeat: true,
78
+ }],
79
+ fn: function(data: CellValue|CellValue[][], ...args: number[]) {
80
+
81
+ if (!Array.isArray(data)) {
82
+ data = [[data]];
83
+ }
84
+
85
+ const flat = FlattenCellValues(args);
86
+ // console.info({flat});
87
+
88
+ const columns = data.length;
89
+ const result: UnionValue[][] = [];
90
+
91
+ for (let arg of flat) {
92
+ if (typeof arg === 'number') {
93
+
94
+ if (arg < 0) {
95
+ arg = columns + arg;
96
+ }
97
+ else {
98
+ arg--;
99
+ }
100
+
101
+ const col = data[arg];
102
+ if (col) {
103
+ const column: UnionValue[] = [];
104
+ for (const value of col) {
105
+ column.push(Box(value));
106
+ }
107
+ result.push(column);
108
+ }
109
+ else {
110
+ return ArgumentError(); // invalid column
111
+ }
112
+ }
113
+ }
114
+
115
+ if (!result.length) {
116
+ result.push([]);
117
+ }
118
+
119
+ return {
120
+ type: ValueType.array,
121
+ value: result,
122
+ };
123
+
124
+ },
125
+ },
126
+
127
+ ChooseRows: {
128
+ description: 'Returns one or more rows from an array, by row index',
129
+ arguments: [{
130
+ name: 'array',
131
+ }, {
132
+ name: 'row',
133
+ repeat: true,
134
+ }],
135
+ fn: function(data: CellValue|CellValue[][], ...args: number[]) {
136
+
137
+ if (!Array.isArray(data)) {
138
+ data = [[data]];
139
+ }
140
+
141
+ const flat = FlattenCellValues(args);
142
+ // console.info({flat});
143
+
144
+ const rows = data[0]?.length || 0;
145
+ const result: UnionValue[][] = [];
146
+
147
+ for (let arg of flat) {
148
+ if (typeof arg === 'number') {
149
+
150
+ if (arg < 0) {
151
+ arg = rows + arg;
152
+ }
153
+ else {
154
+ arg--;
155
+ }
156
+
157
+ for (const [column_index, col] of data.entries()) {
158
+ const column = result[column_index] || [];
159
+ if (arg < 0 || arg >= col.length) {
160
+ return ArgumentError();
161
+ }
162
+ column.push(Box(col[arg]));
163
+ result[column_index] = column;
164
+ }
165
+
166
+ }
167
+ }
168
+
169
+ if (!result.length) {
170
+ result.push([]);
171
+ }
172
+
173
+ return {
174
+ type: ValueType.array,
175
+ value: result,
176
+ };
177
+
178
+ },
179
+ },
180
+
181
+ Take: {
182
+ description: 'Returns some number of rows/columns from the start or end of an array',
183
+ arguments: [{
184
+ name: 'array',
185
+ }, {
186
+ name: 'rows',
187
+ }, {
188
+ name: 'columns',
189
+ }],
190
+ fn: function(data: CellValue|CellValue[][], rows?: number, columns?: number) {
191
+
192
+ // we need one of rows, columns to be defined. neither can === 0.
193
+
194
+ if ((!rows && !columns) || rows === 0 || columns === 0 || (typeof rows !== 'number' && typeof rows !== 'undefined') || (typeof columns !== 'number' && typeof columns !== 'undefined')) {
195
+ return ArgumentError();
196
+ }
197
+
198
+ if (!Array.isArray(data)) {
199
+ data = [[data]];
200
+ }
201
+
202
+ const data_columns = data.length;
203
+ const data_rows = data[0].length;
204
+ const result: UnionValue[][] = [];
205
+
206
+ // I guess we can compose?
207
+
208
+ // data is column-first
209
+
210
+ let start_column = 0;
211
+ let end_column = data_columns - 1;
212
+
213
+ let start_row = 0;
214
+ let end_row = data_rows - 1;
215
+
216
+ if (typeof columns === 'number') {
217
+
218
+ // clip data so it has the first (or last) X columns
219
+
220
+ if (columns > 0) {
221
+ end_column = Math.min(columns, data_columns) - 1;
222
+ }
223
+ else if (columns < 0) {
224
+ end_column = data_columns - 1;
225
+ start_column = Math.max(0, end_column + columns + 1);
226
+ }
227
+
228
+ }
229
+
230
+ if (typeof rows === 'number') {
231
+
232
+ // clip data so it has the first (or last) X columns
233
+
234
+ if (rows > 0) {
235
+ end_row = Math.min(rows, data_rows) - 1;
236
+ }
237
+ else if (rows < 0) {
238
+ end_row = data_rows - 1;
239
+ start_row = Math.max(0, end_row + rows + 1);
240
+ }
241
+
242
+ }
243
+
244
+ for (let c = start_column; c <= end_column; c++) {
245
+ const column: UnionValue[] = [];
246
+ for (let r = start_row; r <= end_row; r++) {
247
+ column.push(Box(data[c][r]));
248
+ }
249
+ result.push(column);
250
+ }
251
+
252
+ return {
253
+ type: ValueType.array,
254
+ value: result,
255
+ }
256
+
257
+ },
258
+ },
259
+
70
260
  Reduce: {
71
261
  description: 'Accumulates a value by applying a function to a set of values',
72
262
  arguments: [
@@ -1220,6 +1220,10 @@ export class Exporter {
1220
1220
  data = this.zip?.Get('xl/styles.xml');
1221
1221
  style_cache.FromXML(this.xmlparser2.parse(data || ''), theme);
1222
1222
 
1223
+ // new flag: we need metadata for dynamic arrays
1224
+
1225
+ let dynamic_array_metadata = false;
1226
+
1223
1227
  // reset counters
1224
1228
 
1225
1229
  Drawing.next_drawing_index = 1;
@@ -1573,6 +1577,7 @@ export class Exporter {
1573
1577
  }
1574
1578
 
1575
1579
  if (cell.area && cell.area.start.row === r && cell.area.start.column === c) {
1580
+
1576
1581
  if (typeof f === 'string') {
1577
1582
  f = {
1578
1583
  t$: f,
@@ -1584,6 +1589,25 @@ export class Exporter {
1584
1589
  }
1585
1590
  }
1586
1591
 
1592
+ let cm: number|undefined = undefined;
1593
+
1594
+ if (cell.spill && cell.spill.start.row === r && cell.spill.start.column === c) {
1595
+
1596
+ cm = 1;
1597
+ dynamic_array_metadata = true;
1598
+
1599
+ if (typeof f === 'string') {
1600
+ f = {
1601
+ t$: f,
1602
+ a$: {
1603
+ t: 'array',
1604
+ ref: cell.spill.spreadsheet_label,
1605
+ },
1606
+ }
1607
+ }
1608
+
1609
+ }
1610
+
1587
1611
  row.push({
1588
1612
  a$: {
1589
1613
 
@@ -1597,6 +1621,9 @@ export class Exporter {
1597
1621
  // or there is no column style and it's equal to sheet style
1598
1622
 
1599
1623
  s,
1624
+
1625
+ cm,
1626
+
1600
1627
  },
1601
1628
 
1602
1629
  f,
@@ -2357,6 +2384,83 @@ export class Exporter {
2357
2384
  `worksheets/sheet${index + 1}.xml`,
2358
2385
  ));
2359
2386
 
2387
+ if (dynamic_array_metadata) {
2388
+
2389
+ const metadata_dom: DOMContent = {
2390
+ metadata: {
2391
+ a$: {
2392
+ xmlns: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
2393
+ 'xmlns:xda': 'http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray',
2394
+ },
2395
+
2396
+ metadataTypes: {
2397
+ a$: {
2398
+ count: 1,
2399
+ },
2400
+ metadataType: {
2401
+ a$: {
2402
+ name: 'XLDAPR',
2403
+ minSupportedVersion: '120000',
2404
+ copy: 1,
2405
+ pasteAll: 1,
2406
+ pasteValues: 1,
2407
+ merge: 1,
2408
+ splitFirst: 1,
2409
+ rowColShift: 1,
2410
+ clearFormats: 1,
2411
+ clearComments: 1,
2412
+ assign: 1,
2413
+ coerce: 1,
2414
+ cellMeta: 1,
2415
+ },
2416
+ },
2417
+ },
2418
+
2419
+ futureMetadata: {
2420
+ a$: {
2421
+ name: 'XLDAPR',
2422
+ count: 1,
2423
+ },
2424
+ bk: {
2425
+ extLst: {
2426
+ ext: {
2427
+ a$: {
2428
+ uri: '{bdbb8cdc-fa1e-496e-a857-3c3f30c029c3}',
2429
+ },
2430
+ 'xda:dynamicArrayProperties': {
2431
+ a$: {
2432
+ fDynamic: 1,
2433
+ fCollapsed: `0`,
2434
+ },
2435
+ },
2436
+ },
2437
+ },
2438
+ },
2439
+ },
2440
+
2441
+ cellMetadata: {
2442
+ a$: { count: 1 },
2443
+ bk: {
2444
+ rc: {
2445
+ a$: {
2446
+ t: 1,
2447
+ v: `0`,
2448
+ }
2449
+ },
2450
+ },
2451
+ },
2452
+
2453
+
2454
+ },
2455
+ };
2456
+ const metadata_xml = XMLDeclaration + this.xmlbuilder1.build(metadata_dom);
2457
+ // console.info(metadata_xml);
2458
+ this.zip?.Set(`xl/metadata.xml`, metadata_xml);
2459
+
2460
+ // add rel
2461
+ AddRel(workbook_rels, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata', 'metadata.xml');
2462
+ }
2463
+
2360
2464
  this.WriteRels(workbook_rels, `xl/_rels/workbook.xml.rels`);
2361
2465
 
2362
2466
  const definedNames: DOMContent|undefined = source.named?.length ? {
@@ -2381,8 +2485,8 @@ export class Exporter {
2381
2485
 
2382
2486
  }),
2383
2487
  } : undefined;
2384
-
2385
-
2488
+
2489
+
2386
2490
  const workbook_dom: DOMContent = {
2387
2491
  workbook: {
2388
2492
  a$: {
@@ -2487,6 +2591,11 @@ export class Exporter {
2487
2591
  { a$: { PartName: '/xl/styles.xml', ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml' }},
2488
2592
  { a$: { PartName: '/xl/sharedStrings.xml', ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml' }},
2489
2593
 
2594
+ // metadata
2595
+ ...(dynamic_array_metadata ? [
2596
+ { a$: { PartName: '/xl/metadata.xml', ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml' }},
2597
+ ] : []),
2598
+
2490
2599
  // tables
2491
2600
  ...global_tables.map(table => {
2492
2601
  return { a$: {
@@ -140,11 +140,15 @@ export class ScaleControl extends EventSource<ScaleEvent> {
140
140
  }
141
141
  });
142
142
 
143
+ // we're overriding scroll behavior here, but it's a tiny panel.
144
+ // if we explicitly mark passive:false we can avoid the console
145
+ // warning (I think)
146
+
143
147
  container.addEventListener('wheel', (event: WheelEvent) => {
144
148
  event.stopPropagation();
145
149
  event.preventDefault();
146
150
  this.Tick(event.deltaY)
147
- });
151
+ }, { passive: false });
148
152
 
149
153
  }
150
154