@trebco/treb 29.5.2 → 29.6.2
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/api-generator/api-generator.ts +10 -1
- package/dist/treb-spreadsheet-light.mjs +9 -9
- package/dist/treb-spreadsheet.mjs +15 -15
- package/dist/treb.d.ts +134 -60
- package/package.json +1 -1
- package/treb-calculator/src/functions/matrix-functions.ts +1 -1
- package/treb-data-model/src/data_model.ts +2 -63
- package/treb-data-model/src/index.ts +2 -10
- package/treb-data-model/src/serialize_options.ts +5 -0
- package/treb-data-model/src/sheet.ts +29 -8
- package/treb-data-model/src/types.ts +71 -0
- package/treb-embed/src/embedded-spreadsheet.ts +274 -2
- package/treb-export/src/export.ts +4 -0
- package/treb-grid/src/types/grid.ts +26 -7
- package/treb-grid/src/types/grid_base.ts +14 -2
|
@@ -73,7 +73,7 @@ import type {
|
|
|
73
73
|
|
|
74
74
|
import {
|
|
75
75
|
IsArea, ThemeColorTable, ComplexToString, Rectangle, IsComplex, type CellStyle,
|
|
76
|
-
Localization, Style, type Color, ResolveThemeColor, IsCellAddress, Area, IsFlatData, IsFlatDataArray, Gradient, DOMContext
|
|
76
|
+
Localization, Style, type Color, ResolveThemeColor, IsCellAddress, Area, IsFlatData, IsFlatDataArray, Gradient, DOMContext
|
|
77
77
|
} from 'treb-base-types';
|
|
78
78
|
|
|
79
79
|
import { EventSource, ValidateURI } from 'treb-utils';
|
|
@@ -114,6 +114,60 @@ import * as export_worker_script from 'worker:../../treb-export/src/export-worke
|
|
|
114
114
|
|
|
115
115
|
// --- types -------------------------------------------------------------------
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* this is a structure for copy/paste data. clipboard data may include
|
|
119
|
+
* relative formauls and resolved styles, so it's suitable for pasting into
|
|
120
|
+
* other areas of the spreadsheet.
|
|
121
|
+
*
|
|
122
|
+
* @privateRemarks
|
|
123
|
+
* work in progress. atm we're not using the system clipboard, although it
|
|
124
|
+
* might be useful to merge this with grid copy/paste routines in the future.
|
|
125
|
+
*
|
|
126
|
+
* if it hits the clipboard this should use mime type `application/x-treb-data`
|
|
127
|
+
*
|
|
128
|
+
*/
|
|
129
|
+
export interface ClipboardDataElement {
|
|
130
|
+
|
|
131
|
+
/** calculated cell value */
|
|
132
|
+
calculated: CellValue,
|
|
133
|
+
|
|
134
|
+
/** the actual cell value or formula */
|
|
135
|
+
value: CellValue,
|
|
136
|
+
|
|
137
|
+
/** cell style. this may include row/column styles from the copy source */
|
|
138
|
+
style?: CellStyle,
|
|
139
|
+
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** clipboard data is a 2d array */
|
|
143
|
+
export type ClipboardData = ClipboardDataElement[][];
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* optional paste options. we can paste formulas or values, and we
|
|
147
|
+
* can use the source style, target style, or just use the source
|
|
148
|
+
* number formats.
|
|
149
|
+
*/
|
|
150
|
+
export interface PasteOptions {
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* when clipboard data includes formulas, optionally paste calculated
|
|
154
|
+
* values instead of the original formulas. defaults to false.
|
|
155
|
+
*/
|
|
156
|
+
values?: boolean;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* when pasting data from the clipboard, we can copy formatting/style
|
|
160
|
+
* from the original data, or we can retain the target range formatting
|
|
161
|
+
* and just paste data. a third option allows pasting source number
|
|
162
|
+
* formats but dropping other style information.
|
|
163
|
+
*
|
|
164
|
+
* defaults to "source", meaning paste source styles.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
formatting?: 'source'|'target'|'number-formats'
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
117
171
|
/**
|
|
118
172
|
* options for saving files. we add the option for JSON formatting.
|
|
119
173
|
*/
|
|
@@ -264,6 +318,9 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
264
318
|
/** @internal */
|
|
265
319
|
public static one_time_warnings: Record<string, boolean> = {};
|
|
266
320
|
|
|
321
|
+
/** @internal */
|
|
322
|
+
protected static clipboard?: ClipboardData;
|
|
323
|
+
|
|
267
324
|
protected DOM = DOMContext.GetInstance(); // default
|
|
268
325
|
|
|
269
326
|
/**
|
|
@@ -2009,7 +2066,25 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2009
2066
|
* grid stops broadcasting events for the duration of the function call,
|
|
2010
2067
|
* and collects them instead. After the function call we update as necessary.
|
|
2011
2068
|
*
|
|
2012
|
-
* @
|
|
2069
|
+
* @privateRemarks
|
|
2070
|
+
*
|
|
2071
|
+
* FIXME: we need to consider the case where this is nested, since we now
|
|
2072
|
+
* call it from the Paste method. that might be batched by a caller. we
|
|
2073
|
+
* need a batch stack, and we need to consolidate any options (paint is the
|
|
2074
|
+
* only option) and keep the top-of-stack last selection.
|
|
2075
|
+
*
|
|
2076
|
+
* Q: why does this work the way it does, anyway? why not just rely
|
|
2077
|
+
* on the actual events? if grid published them after a batch, wouldn't
|
|
2078
|
+
* everything just work? or is the problem that we normally handle events
|
|
2079
|
+
* serially? maybe we need to rethink how we handle events coming from
|
|
2080
|
+
* the grid.
|
|
2081
|
+
*
|
|
2082
|
+
* ---
|
|
2083
|
+
*
|
|
2084
|
+
* OK so now, grid will handle nested batching. it won't return anything
|
|
2085
|
+
* until the last batch is complete. so this should work in the case of
|
|
2086
|
+
* nested calls, because nothing will happen until the last one is complete.
|
|
2087
|
+
*
|
|
2013
2088
|
*/
|
|
2014
2089
|
public async Batch(func: () => void, paint = false): Promise<void> {
|
|
2015
2090
|
|
|
@@ -2021,6 +2096,8 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2021
2096
|
let recalc = false;
|
|
2022
2097
|
let reset = false;
|
|
2023
2098
|
|
|
2099
|
+
// FIXME (2024): are these FIXMEs below still a thing? (...)
|
|
2100
|
+
|
|
2024
2101
|
// FIXME: annotation events
|
|
2025
2102
|
// TODO: annotation events
|
|
2026
2103
|
|
|
@@ -2822,6 +2899,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2822
2899
|
tables: true,
|
|
2823
2900
|
share_resources: false,
|
|
2824
2901
|
export_functions: true,
|
|
2902
|
+
apply_row_pattern: true, // if there's a row pattern, set it on rows so they export properly
|
|
2825
2903
|
});
|
|
2826
2904
|
|
|
2827
2905
|
// why do _we_ put this in, instead of the grid method?
|
|
@@ -4288,6 +4366,139 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4288
4366
|
|
|
4289
4367
|
}
|
|
4290
4368
|
|
|
4369
|
+
/**
|
|
4370
|
+
*
|
|
4371
|
+
* @param target - the target to paste data into. this can be larger
|
|
4372
|
+
* than the clipboard data, in which case values will be recycled in
|
|
4373
|
+
* blocks. if the target is smaller than the source data, we will expand
|
|
4374
|
+
* it.
|
|
4375
|
+
*
|
|
4376
|
+
* @param data - clipboard data to paste.
|
|
4377
|
+
*
|
|
4378
|
+
* @param style - optional paste style. default is to paste formulas and
|
|
4379
|
+
* source formatting. paste options can be usef to paste values, values
|
|
4380
|
+
* and number formats, or retain the target formatting.
|
|
4381
|
+
*
|
|
4382
|
+
* @privateRemarks LLM API
|
|
4383
|
+
*/
|
|
4384
|
+
public async Paste(target?: RangeReference, data = EmbeddedSpreadsheet.clipboard, options: PasteOptions = {}) {
|
|
4385
|
+
|
|
4386
|
+
if (!data) {
|
|
4387
|
+
throw new Error('no clipboad data');
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4390
|
+
if (!target) {
|
|
4391
|
+
const selection = this.GetSelectionReference();
|
|
4392
|
+
if (!selection.empty) {
|
|
4393
|
+
target = selection.area;
|
|
4394
|
+
}
|
|
4395
|
+
else {
|
|
4396
|
+
throw new Error('no range and no selection');
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
const resolved = this.model.ResolveArea(target, this.grid.active_sheet);
|
|
4401
|
+
|
|
4402
|
+
// paste has some special semantics. if the target smaller than the
|
|
4403
|
+
// source data, we write the full data irrespective of size (similar
|
|
4404
|
+
// to "spill"). otherwise, we recycle in blocks.
|
|
4405
|
+
|
|
4406
|
+
// the setrange method will recycle, but we also need to recycle styles.
|
|
4407
|
+
|
|
4408
|
+
// start with data length
|
|
4409
|
+
|
|
4410
|
+
const rows = data.length;
|
|
4411
|
+
const columns = data[0]?.length || 0;
|
|
4412
|
+
|
|
4413
|
+
// target -> block size
|
|
4414
|
+
|
|
4415
|
+
resolved.Resize(
|
|
4416
|
+
Math.max(1, Math.floor(resolved.rows / rows)) * rows,
|
|
4417
|
+
Math.max(1, Math.floor(resolved.columns / columns)) * columns );
|
|
4418
|
+
|
|
4419
|
+
const sheet = (resolved.start.sheet_id ? this.model.sheets.Find(resolved.start.sheet_id) : this.grid.active_sheet) || this.grid.active_sheet;
|
|
4420
|
+
|
|
4421
|
+
const values: CellValue[][] = [];
|
|
4422
|
+
|
|
4423
|
+
// optionally collect calculated values, instead of raw values
|
|
4424
|
+
|
|
4425
|
+
if (options.values) {
|
|
4426
|
+
for (const [index, row] of data.entries()) {
|
|
4427
|
+
values[index] = [];
|
|
4428
|
+
for (const cell of row) {
|
|
4429
|
+
values[index].push(cell.calculated);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
else {
|
|
4434
|
+
for (const [index, row] of data.entries()) {
|
|
4435
|
+
values[index] = [];
|
|
4436
|
+
for (const cell of row) {
|
|
4437
|
+
values[index].push(cell.value);
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
// batch to limit events, sync up undo
|
|
4443
|
+
|
|
4444
|
+
return this.Batch(() => {
|
|
4445
|
+
|
|
4446
|
+
this.grid.SetRange(resolved, values, {
|
|
4447
|
+
r1c1: true, recycle: true,
|
|
4448
|
+
});
|
|
4449
|
+
|
|
4450
|
+
if (options.formatting === 'number-formats') {
|
|
4451
|
+
|
|
4452
|
+
// number format only, and apply delta
|
|
4453
|
+
|
|
4454
|
+
for (const address of resolved) {
|
|
4455
|
+
const r = (address.row - resolved.start.row) % rows;
|
|
4456
|
+
const c = (address.column - resolved.start.column) % columns;
|
|
4457
|
+
const number_format = (data[r][c].style || {}).number_format;
|
|
4458
|
+
sheet.UpdateCellStyle(address, { number_format }, true);
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
else if (options.formatting !== 'target') {
|
|
4462
|
+
|
|
4463
|
+
// use source formatting (default)
|
|
4464
|
+
for (const address of resolved) {
|
|
4465
|
+
const r = (address.row - resolved.start.row) % rows;
|
|
4466
|
+
const c = (address.column - resolved.start.column) % columns;
|
|
4467
|
+
sheet.UpdateCellStyle(address, data[r][c].style || {}, false);
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
}, true);
|
|
4473
|
+
|
|
4474
|
+
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
/**
|
|
4478
|
+
* copy data. this method returns the copied data. it does not put it on
|
|
4479
|
+
* the system clipboard. this is for API access when the system clipboard
|
|
4480
|
+
* might not be available.
|
|
4481
|
+
*
|
|
4482
|
+
* @privateRemarks LLM API
|
|
4483
|
+
*/
|
|
4484
|
+
public Copy(source?: RangeReference): ClipboardData {
|
|
4485
|
+
return this.CopyInternal(source, 'copy');
|
|
4486
|
+
}
|
|
4487
|
+
|
|
4488
|
+
/**
|
|
4489
|
+
* cut data. this method returns the cut data. it does not put it on the
|
|
4490
|
+
* system clipboard. this method is similar to the Copy method, with
|
|
4491
|
+
* two differences: (1) we remove the source data, effectively clearing
|
|
4492
|
+
* the source range; and (2) the clipboard data retains references, meaning
|
|
4493
|
+
* if you paste the data in a different location it will refer to the same
|
|
4494
|
+
* cells.
|
|
4495
|
+
*
|
|
4496
|
+
* @privateRemarks LLM API
|
|
4497
|
+
*/
|
|
4498
|
+
public Cut(source?: RangeReference): ClipboardData {
|
|
4499
|
+
return this.CopyInternal(source, 'cut');
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4291
4502
|
/**
|
|
4292
4503
|
*
|
|
4293
4504
|
* @param range target range. leave undefined to use current selection.
|
|
@@ -4440,6 +4651,67 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
4440
4651
|
|
|
4441
4652
|
// --- internal (protected) methods ------------------------------------------
|
|
4442
4653
|
|
|
4654
|
+
/**
|
|
4655
|
+
* internal composite for cut/copy. mostly identical except we
|
|
4656
|
+
* read data as A1 for cut, so it will retain references. also
|
|
4657
|
+
* cut clears the data.
|
|
4658
|
+
*
|
|
4659
|
+
* FIXME: merge with grid cut/copy/paste routines. we already
|
|
4660
|
+
* handle recycling and relative addressing, the only thing missing
|
|
4661
|
+
* is alternate formats.
|
|
4662
|
+
*/
|
|
4663
|
+
protected CopyInternal(source?: RangeReference, semantics: 'cut'|'copy' = 'copy'): ClipboardData {
|
|
4664
|
+
|
|
4665
|
+
if (!source) {
|
|
4666
|
+
const selection = this.GetSelectionReference();
|
|
4667
|
+
if (!selection.empty) {
|
|
4668
|
+
source = selection.area;
|
|
4669
|
+
}
|
|
4670
|
+
else {
|
|
4671
|
+
throw new Error('no range and no selection');
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4675
|
+
// resolve range so we can use it later -> Area
|
|
4676
|
+
const resolved = this.model.ResolveArea(source, this.grid.active_sheet);
|
|
4677
|
+
const sheet = (resolved.start.sheet_id ? this.model.sheets.Find(resolved.start.sheet_id) : this.grid.active_sheet) || this.grid.active_sheet;
|
|
4678
|
+
|
|
4679
|
+
// get cell data as R1C1 for copy but A1 for cut.
|
|
4680
|
+
const r1c1 = this.grid.GetRange(resolved, semantics === 'cut' ? 'A1' : 'R1C1') as CellValue[][];
|
|
4681
|
+
|
|
4682
|
+
// get style data, !apply theme but do apply r/c styles
|
|
4683
|
+
const styles = sheet.GetCellStyle(resolved, false);
|
|
4684
|
+
|
|
4685
|
+
// get calculated values
|
|
4686
|
+
let calculated = sheet.cells.GetRange(resolved.start, resolved.end);
|
|
4687
|
+
if (!Array.isArray(calculated)) {
|
|
4688
|
+
calculated = [[calculated]];
|
|
4689
|
+
}
|
|
4690
|
+
|
|
4691
|
+
const data: ClipboardData = [];
|
|
4692
|
+
|
|
4693
|
+
for (const [r, row] of r1c1.entries()) {
|
|
4694
|
+
data[r] = [];
|
|
4695
|
+
for (const [c, value] of row.entries()) {
|
|
4696
|
+
|
|
4697
|
+
data[r][c] = {
|
|
4698
|
+
value,
|
|
4699
|
+
calculated: calculated[r][c],
|
|
4700
|
+
style: styles[r]?.[c],
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
EmbeddedSpreadsheet.clipboard = structuredClone(data);
|
|
4706
|
+
|
|
4707
|
+
if (semantics === 'cut') {
|
|
4708
|
+
this.grid.SetRange(resolved, undefined, { recycle: true }); // clear
|
|
4709
|
+
}
|
|
4710
|
+
|
|
4711
|
+
return data;
|
|
4712
|
+
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4443
4715
|
// --- moved from grid/grid base ---------------------------------------------
|
|
4444
4716
|
|
|
4445
4717
|
|
|
@@ -611,9 +611,13 @@ export class Exporter {
|
|
|
611
611
|
|
|
612
612
|
const list: CellStyle[] = [sheet.sheet_style];
|
|
613
613
|
|
|
614
|
+
/*
|
|
615
|
+
// should apply to rows, not cells
|
|
616
|
+
|
|
614
617
|
if (sheet.row_pattern && sheet.row_pattern.length) {
|
|
615
618
|
list.push(sheet.row_pattern[row % sheet.row_pattern.length]);
|
|
616
619
|
}
|
|
620
|
+
*/
|
|
617
621
|
|
|
618
622
|
// is this backwards, vis a vis our rendering? I think it might be...
|
|
619
623
|
// YES: should be row pattern -> row -> column -> cell [corrected]
|
|
@@ -2176,21 +2176,40 @@ export class Grid extends GridBase {
|
|
|
2176
2176
|
/**
|
|
2177
2177
|
* batch updates. returns all the events that _would_ have been sent.
|
|
2178
2178
|
* also does a paint (can disable).
|
|
2179
|
+
*
|
|
2180
|
+
* update for nesting/stacking. we won't return events until the last
|
|
2181
|
+
* batch is complete. paint will similarly toll until the last batch
|
|
2182
|
+
* is complete, and we'll carry forward any paint requirement from
|
|
2183
|
+
* inner batch funcs.
|
|
2184
|
+
*
|
|
2179
2185
|
* @param func
|
|
2180
2186
|
*/
|
|
2181
2187
|
public Batch(func: () => void, paint = true): GridEvent[] {
|
|
2182
2188
|
|
|
2183
|
-
this.batch
|
|
2189
|
+
if (this.batch === 0) {
|
|
2190
|
+
this.batch_paint = paint; // clear any old setting
|
|
2191
|
+
}
|
|
2192
|
+
else {
|
|
2193
|
+
this.batch_paint = this.batch_paint || paint; // ensure we honor it at the end
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
this.batch++;
|
|
2184
2197
|
func();
|
|
2185
|
-
this.batch
|
|
2186
|
-
|
|
2187
|
-
this.
|
|
2198
|
+
this.batch--;
|
|
2199
|
+
|
|
2200
|
+
if (this.batch === 0) {
|
|
2201
|
+
const events = this.batch_events.slice(0);
|
|
2202
|
+
this.batch_events = [];
|
|
2188
2203
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2204
|
+
if (this.batch_paint) {
|
|
2205
|
+
this.DelayedRender(false);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
return events;
|
|
2191
2209
|
}
|
|
2192
2210
|
|
|
2193
|
-
return
|
|
2211
|
+
return []; // either nothing happened or we're not done
|
|
2212
|
+
|
|
2194
2213
|
}
|
|
2195
2214
|
|
|
2196
2215
|
|
|
@@ -109,7 +109,19 @@ export class GridBase {
|
|
|
109
109
|
|
|
110
110
|
// --- protected members -----------------------------------------------------
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
/**
|
|
113
|
+
* switching to a stack, in case batching is nested. we don't need
|
|
114
|
+
* actual data so (atm) just count the depth.
|
|
115
|
+
*/
|
|
116
|
+
protected batch = 0; // false;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* if any batch method along the way requests a paint update, we
|
|
120
|
+
* want to toll it until the last batch call is complete, but we don't
|
|
121
|
+
* want to lose it. just remember to reset. [FIXME: isn't tolling this
|
|
122
|
+
* paint implicit, since it's async? ...]
|
|
123
|
+
*/
|
|
124
|
+
protected batch_paint = false;
|
|
113
125
|
|
|
114
126
|
protected batch_events: GridEvent[] = [];
|
|
115
127
|
|
|
@@ -4474,7 +4486,7 @@ export class GridBase {
|
|
|
4474
4486
|
});
|
|
4475
4487
|
}
|
|
4476
4488
|
|
|
4477
|
-
if (this.batch) {
|
|
4489
|
+
if (this.batch > 0) {
|
|
4478
4490
|
this.batch_events.push(...events);
|
|
4479
4491
|
}
|
|
4480
4492
|
else {
|