@trebco/treb 30.8.2 → 30.9.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/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v30.8. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.9. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
@@ -1779,6 +1779,9 @@ export interface SerializedSheet {
1779
1779
  /** sheet name */
1780
1780
  name?: string;
1781
1781
 
1782
+ /** tab color */
1783
+ tab_color?: Color;
1784
+
1782
1785
  /** current active selection */
1783
1786
  selection: SerializedGridSelection;
1784
1787
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.8.2",
3
+ "version": "30.9.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -19,6 +19,8 @@
19
19
  *
20
20
  */
21
21
 
22
+
23
+
22
24
  /**
23
25
  * utility functions, primarily for adjusting lightness. since we generally
24
26
  * traffic in RGB (or symbolic colors) that requires translating to/from HSL.
@@ -120,5 +122,35 @@ export const ColorFunctions = {
120
122
  return p;
121
123
  },
122
124
 
125
+ ////////////////
126
+
127
+
128
+ GetLuminance: (r: number, g: number, b: number): number => {
129
+ const a = [r, g, b].map(v => {
130
+ v /= 255;
131
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
132
+ });
133
+ return 0.2126 * a[0] + 0.7152 * a[1] + 0.0722 * a[2];
134
+ },
135
+
136
+ GetContrastRatio: (data: [number, number]): number => {
137
+ data.sort((a, b) => b - a);
138
+ return (data[0] + 0.05) / (data[1] + 0.05);
139
+ },
140
+
141
+ GetTextColor: (background: [number, number, number], a: [number, number, number], b: [number, number, number]) => {
142
+
143
+ const luminance = ColorFunctions.GetLuminance(...background);
144
+ const luminance_a = ColorFunctions.GetLuminance(...a);
145
+ const luminance_b = ColorFunctions.GetLuminance(...b);
146
+
147
+ const contrast_a = ColorFunctions.GetContrastRatio([luminance_a, luminance]);
148
+ const contrast_b = ColorFunctions.GetContrastRatio([luminance_b, luminance]);
149
+
150
+ return contrast_a > contrast_b ? a : b;
151
+ },
152
+
123
153
  };
124
154
 
155
+
156
+
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { CellStyle } from './style';
22
+ import type { CellStyle, Color } from './style';
23
23
  import type { SerializedValueType } from './value-type';
24
24
  import type { IArea } from './area';
25
25
  import type { AnnotationLayout } from './layout';
@@ -64,14 +64,13 @@ export interface ImportedSheetData {
64
64
  // optional, for backcompat
65
65
  sheet_style?: number;
66
66
  column_styles?: number[];
67
-
68
67
  row_styles?: number[];
69
68
 
70
- // new
71
69
  annotations?: AnchoredAnnotation[];
70
+ outline?: number[];
72
71
 
73
72
  // new
74
- outline?: number[];
73
+ tab_color?: Color;
75
74
 
76
75
  hidden?: boolean;
77
76
 
@@ -22,6 +22,7 @@
22
22
  import { type Color, type CellStyle, IsHTMLColor, IsThemeColor, ThemeColorIndex, type ThemeColor } from './style';
23
23
  import { ColorFunctions } from './color';
24
24
  import { DOMContext } from './dom-utilities';
25
+ import { Measurement } from 'treb-utils';
25
26
 
26
27
  /*
27
28
  * so this is a little strange. we use CSS to populate a theme object,
@@ -242,7 +243,15 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
242
243
  return '';
243
244
  }
244
245
 
245
- const resolved = ResolveThemeColor(theme, color.offset);
246
+ let resolved = '';
247
+
248
+ if (IsHTMLColor(color.offset)) {
249
+ const clamped = Measurement.MeasureColor(color.offset.text);
250
+ resolved = `rgb(${clamped[0]}, ${clamped[1]}, ${clamped[2]})`;
251
+ }
252
+ else {
253
+ resolved = ResolveThemeColor(theme, color.offset, undefined);
254
+ }
246
255
 
247
256
  // check cache
248
257
  if (theme.offset_cache && theme.offset_cache[resolved]) {
@@ -253,17 +262,35 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
253
262
 
254
263
  if (resolved) {
255
264
  // ok figure it out?
256
- const match = resolved.match(/rgb\((\d+), (\d+), (\d+)\)/);
265
+ const match = resolved.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
257
266
  if (match) {
258
- const hsl = ColorFunctions.RGBToHSL(Number(match[1]), Number(match[2]), Number(match[3]));
259
- // console.info('resolved', resolved, {hsl});
260
- if (hsl.l > .65) {
267
+
268
+ type ColorTuple = [number, number, number];
269
+
270
+ const background: ColorTuple = [Number(match[1]), Number(match[2]), Number(match[3])];
271
+
272
+
273
+ // const hsl = ColorFunctions.RGBToHSL(r, g, b);
274
+ // const check = ColorFunctions.GetLuminance(r, g, b);
275
+
276
+ const a = Array.from(Measurement.MeasureColor(theme.offset_dark)) as ColorTuple;
277
+ const b = Array.from(Measurement.MeasureColor(theme.offset_light)) as ColorTuple;
278
+
279
+ const tc = ColorFunctions.GetTextColor(background, a, b);
280
+
281
+ offset = `rgb(${tc[0]}, ${tc[1]}, ${tc[2]})`;
282
+
283
+ /*
284
+ if (hsl.l >.65)) {
261
285
  offset = theme.offset_dark;
262
286
  }
287
+ */
288
+
289
+
263
290
  }
264
291
  else {
265
292
  // ...
266
- console.warn(`can't offset against color`, resolved);
293
+ console.warn(`can't offset against color`, resolved, '(1)');
267
294
  }
268
295
 
269
296
  if (!theme.offset_cache) {
@@ -272,7 +299,7 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
272
299
  theme.offset_cache[resolved] = offset;
273
300
  }
274
301
  else {
275
- console.warn(`can't resolve offset color`, color.offset);
302
+ console.warn(`can't resolve offset color`, color.offset, '(2)');
276
303
  }
277
304
 
278
305
  return offset;
@@ -145,6 +145,8 @@ export class Sheet {
145
145
 
146
146
  public name = Sheet.default_sheet_name;
147
147
 
148
+ public tab_color?: Color;
149
+
148
150
  public background_image?: string;
149
151
 
150
152
  protected _image: HTMLImageElement|undefined = undefined;
@@ -403,6 +405,9 @@ export class Sheet {
403
405
  if (source.name) {
404
406
  sheet.name = source.name;
405
407
  }
408
+ if (source.tab_color) {
409
+ sheet.tab_color = source.tab_color;
410
+ }
406
411
 
407
412
  if (source.background_image) {
408
413
  sheet.background_image = source.background_image;
@@ -2744,6 +2749,7 @@ export class Sheet {
2744
2749
 
2745
2750
  id: this.id,
2746
2751
  name: this.name,
2752
+ tab_color: this.tab_color,
2747
2753
 
2748
2754
  data,
2749
2755
  sheet_style,
@@ -2915,7 +2921,11 @@ export class Sheet {
2915
2921
  // this.cells.FromJSON(cell_data);
2916
2922
  this.cells.FromJSON(data.cells);
2917
2923
  if (data.name) {
2918
- this.name = data.name || '';
2924
+ this.name = data.name || ''; // wtf is this?
2925
+ }
2926
+
2927
+ if (data.tab_color) {
2928
+ this.tab_color = data.tab_color;
2919
2929
  }
2920
2930
 
2921
2931
  // 0 is implicitly just a general style
@@ -19,7 +19,7 @@
19
19
  *
20
20
  */
21
21
 
22
- import type { IArea, SerializedCellData, CellStyle } from 'treb-base-types';
22
+ import type { IArea, SerializedCellData, CellStyle, Color } from 'treb-base-types';
23
23
  import type { AnnotationData } from './annotation';
24
24
  import type { GridSelection, SerializedGridSelection } from './sheet_selection';
25
25
  import type { ConditionalFormatList } from './conditional_format';
@@ -126,6 +126,9 @@ export interface SerializedSheet {
126
126
  /** sheet name */
127
127
  name?: string;
128
128
 
129
+ /** tab color */
130
+ tab_color?: Color;
131
+
129
132
  /** current active selection */
130
133
  selection: SerializedGridSelection;
131
134
 
@@ -437,6 +437,22 @@ $text-reference-color-5: rgb(254, 47, 1);
437
437
  &[selected] {
438
438
  background: var(--treb-tab-bar-active-tab-background, #fff);
439
439
  color: var(--treb-tab-bar-active-tab-color, var(--treb-ui-color, inherit));
440
+
441
+ border-bottom-color: var(--treb-tab-bar-active-tab-border-color, currentColor);
442
+
443
+ /*
444
+ position: relative;
445
+ &::after {
446
+ content: '';
447
+ position: absolute;
448
+ bottom: 0px;
449
+ left: 0px;
450
+ width: 100%;
451
+ height: 1px;
452
+ background: currentColor;
453
+ }
454
+ */
455
+
440
456
  }
441
457
  }
442
458
 
@@ -439,13 +439,7 @@ export class Exporter {
439
439
 
440
440
  const dxf: DOMContent[] = style_cache.dxf_styles.map(style => {
441
441
 
442
- const entry: DOMContent = {
443
- fill: style.fill ? {
444
- patternFill: {
445
- bgColor: ColorAttrs(style.fill),
446
- },
447
- } : undefined,
448
- };
442
+ const entry: DOMContent = {};
449
443
 
450
444
  if (style.text || style.bold || style.italic || style.underline) {
451
445
  entry.font = {
@@ -457,6 +451,14 @@ export class Exporter {
457
451
  };
458
452
  }
459
453
 
454
+ if (style.fill) {
455
+ entry.fill = {
456
+ patternFill: {
457
+ bgColor: ColorAttrs(style.fill),
458
+ }
459
+ }
460
+ };
461
+
460
462
  return entry;
461
463
 
462
464
  });
@@ -2214,6 +2216,34 @@ export class Exporter {
2214
2216
  // delete dom.worksheet.drawing;
2215
2217
  }
2216
2218
 
2219
+ // --- tab color ---------------------------------------------------------
2220
+
2221
+ const tab_color_block: DOMContent = {};
2222
+ if (sheet.tab_color) {
2223
+ if (IsThemeColor(sheet.tab_color)) {
2224
+ tab_color_block.sheetPr = {
2225
+ tabColor: {
2226
+ a$: {
2227
+ theme: sheet.tab_color.theme,
2228
+ tint: sheet.tab_color.tint,
2229
+ }
2230
+ }
2231
+ };
2232
+ }
2233
+ else if (IsHTMLColor(sheet.tab_color)) {
2234
+ const color = sheet.tab_color.text || '';
2235
+ if (/^#[0-9a-fA-F]*$/.test(color)) {
2236
+ tab_color_block.sheetPr = {
2237
+ tabColor: {
2238
+ a$: {
2239
+ rgb: `FF` + color.substring(1)
2240
+ }
2241
+ }
2242
+ };
2243
+ }
2244
+ }
2245
+ }
2246
+
2217
2247
  // --- move page margins -------------------------------------------------
2218
2248
 
2219
2249
  // const margins = dom.worksheet.pageMargins;
@@ -2242,6 +2272,9 @@ export class Exporter {
2242
2272
 
2243
2273
  worksheet: {
2244
2274
  a$: { ...sheet_attributes },
2275
+
2276
+ ...tab_color_block,
2277
+
2245
2278
  dimension: {
2246
2279
  a$: {
2247
2280
  ref: new Area(extent.start, extent.end).spreadsheet_label,
@@ -166,20 +166,26 @@ export class Importer {
166
166
  // doing it like this is sloppy (also does not work properly).
167
167
  value = '=' + formula.replace(/^_xll\./g, '');
168
168
 
169
- const parse_result = this.parser.Parse(formula); // l10n?
170
- if (parse_result.expression) {
171
- this.parser.Walk(parse_result.expression, (unit) => {
172
- if (unit.type === 'call') {
173
- if (/^_xll\./.test(unit.name)) {
174
- unit.name = unit.name.substring(5);
175
- }
176
- else if (/^_xlfn\./.test(unit.name)) {
177
- unit.name = unit.name.substring(6);
169
+ // drop the formula if it's a ref error, we can't handle this
170
+ if (/#REF/.test(formula)) {
171
+ value = formula;
172
+ }
173
+ else {
174
+ const parse_result = this.parser.Parse(formula); // l10n?
175
+ if (parse_result.expression) {
176
+ this.parser.Walk(parse_result.expression, (unit) => {
177
+ if (unit.type === 'call') {
178
+ if (/^_xll\./.test(unit.name)) {
179
+ unit.name = unit.name.substring(5);
180
+ }
181
+ else if (/^_xlfn\./.test(unit.name)) {
182
+ unit.name = unit.name.substring(6);
183
+ }
178
184
  }
179
- }
180
- return true;
181
- });
182
- value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
185
+ return true;
186
+ });
187
+ value = '=' + this.parser.Render(parse_result.expression, { missing: '' });
188
+ }
183
189
  }
184
190
 
185
191
  if (typeof element.f !== 'string') {
@@ -591,6 +597,33 @@ export class Importer {
591
597
 
592
598
  const FindAll: (path: string) => any[] = XMLUtils.FindAll.bind(XMLUtils, sheet.sheet_data);
593
599
 
600
+ // tab color
601
+
602
+ const tab_color_element = FindAll('worksheet/sheetPr/tabColor');
603
+
604
+ let tab_color: Color|undefined;
605
+
606
+ if (tab_color_element?.[0]) {
607
+
608
+ const element = tab_color_element[0];
609
+ if (element.a$?.theme) {
610
+ tab_color = { theme: Number(element.a$.theme) };
611
+ if (element.a$?.tint) {
612
+ tab_color.tint = Number(element.a$.tint);
613
+ }
614
+ }
615
+ if (element.a$?.rgb) {
616
+ const argb = element.a$.rgb;
617
+ tab_color = {
618
+ text: '#' + (
619
+ argb.length > 6 ?
620
+ argb.substr(argb.length - 6) :
621
+ argb),
622
+ };
623
+ }
624
+
625
+ }
626
+
594
627
  // conditionals
595
628
 
596
629
  const conditional_formatting = FindAll('worksheet/conditionalFormatting');
@@ -1120,7 +1153,8 @@ export class Importer {
1120
1153
  type = 'treb-chart';
1121
1154
  func = 'Box.Plot';
1122
1155
  if (series?.length) {
1123
- args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})` || '').join(', ')})`;
1156
+ args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
1157
+ console.info("S?", {series}, args[0])
1124
1158
  }
1125
1159
  args[1] = descriptor.chart.title;
1126
1160
  break;
@@ -1129,7 +1163,7 @@ export class Importer {
1129
1163
  type = 'treb-chart';
1130
1164
  func = 'Scatter.Line';
1131
1165
  if (series && series.length) {
1132
- args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})` || '').join(', ')})`;
1166
+ args[0] = `Group(${series.map(s => `Series(${s.title || ''},${s.categories||''},${s.values||''})`).join(', ')})`;
1133
1167
  }
1134
1168
  args[1] = descriptor.chart.title;
1135
1169
  break;
@@ -1165,7 +1199,7 @@ export class Importer {
1165
1199
 
1166
1200
  if (series) {
1167
1201
  if (series.length > 1) {
1168
- args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})` || '').join(', ')})`;
1202
+ args[0] = `Group(${series.map(s => `Series(${s.title || ''},,${s.values||''})`).join(', ')})`;
1169
1203
  }
1170
1204
  else if (series.length === 1) {
1171
1205
  if (series[0].title) {
@@ -1289,6 +1323,7 @@ export class Importer {
1289
1323
  default_column_width,
1290
1324
  column_widths,
1291
1325
  row_heights,
1326
+ tab_color,
1292
1327
  row_styles,
1293
1328
  annotations,
1294
1329
  conditional_formats,
@@ -23,7 +23,7 @@ import type { ImportedSheetData } from 'treb-base-types';
23
23
  import type { SerializedModel } from 'treb-data-model';
24
24
 
25
25
  import { Exporter } from './export';
26
- import { Importer } from './import2';
26
+ import { Importer } from './import';
27
27
 
28
28
  const ctx: Worker = self as unknown as Worker;
29
29
  const exporter = new Exporter();
@@ -45,10 +45,10 @@ import type { SerializedNamed } from 'treb-data-model';
45
45
  export const ConditionalFormatOperators: Record<string, string> = {
46
46
  greaterThan: '>',
47
47
  greaterThanOrEqual: '>=',
48
- greaterThanOrEquals: '>=',
48
+ // greaterThanOrEquals: '>=',
49
49
  lessThan: '<',
50
50
  lessThanOrEqual: '<=',
51
- lessThanOrEquals: '<=',
51
+ // lessThanOrEquals: '<=',
52
52
  equal: '=',
53
53
  notEqual: '<>',
54
54
  };
@@ -967,7 +967,7 @@ export class Grid extends GridBase {
967
967
  if (add_to_layout) {
968
968
  this.layout.AddAnnotation(annotation);
969
969
  if (annotation.data.layout) {
970
- this.EnsureAddress(annotation.data.layout.br.address, 1);
970
+ this.EnsureAddress(annotation.data.layout.br.address, 1, toll_events);
971
971
  }
972
972
  }
973
973
  else {
@@ -1190,7 +1190,7 @@ export class Grid extends GridBase {
1190
1190
  this.QueueLayoutUpdate();
1191
1191
 
1192
1192
  this.StyleDefaultFromTheme();
1193
-
1193
+
1194
1194
  if (render) {
1195
1195
  this.Repaint(false, false); // true, true);
1196
1196
  }
@@ -1403,6 +1403,10 @@ export class Grid extends GridBase {
1403
1403
 
1404
1404
  // this.tile_renderer.UpdateTheme(); // has reference
1405
1405
 
1406
+ if (this.tab_bar) {
1407
+ this.tab_bar.UpdateTheme();
1408
+ }
1409
+
1406
1410
  if (!initial) {
1407
1411
 
1408
1412
  this.UpdateLayout(); // in case we have changed font size
@@ -1454,7 +1458,7 @@ export class Grid extends GridBase {
1454
1458
 
1455
1459
  if (this.options.tab_bar) {
1456
1460
 
1457
- this.tab_bar = new TabBar(this.layout, this.model, this.view, this.options, view_node);
1461
+ this.tab_bar = new TabBar(this.layout, this.model, this.view, this.options, this.theme, view_node);
1458
1462
  this.tab_bar.Subscribe((event) => {
1459
1463
  switch (event.type) {
1460
1464
  case 'cancel':
@@ -5936,7 +5940,7 @@ export class Grid extends GridBase {
5936
5940
  /**
5937
5941
  * if the address is outside of current extent, expand
5938
5942
  */
5939
- private EnsureAddress(address: ICellAddress, step = 8): boolean {
5943
+ private EnsureAddress(address: ICellAddress, step = 8, toll_layout = false): boolean {
5940
5944
 
5941
5945
  let expanded = false;
5942
5946
 
@@ -5959,7 +5963,7 @@ export class Grid extends GridBase {
5959
5963
  expanded = true;
5960
5964
  }
5961
5965
 
5962
- if (expanded) {
5966
+ if (expanded && !toll_layout) {
5963
5967
  this.layout.UpdateTiles();
5964
5968
  this.layout.UpdateContentsSize();
5965
5969
  this.Repaint(true, true);
@@ -25,7 +25,7 @@ import type { BaseLayout } from '../layout/base_layout';
25
25
  import { MouseDrag } from './drag_mask';
26
26
  import type { GridOptions } from './grid_options';
27
27
  import { type ScaleEvent, ScaleControl } from './scale-control';
28
- import { DOMContext } from 'treb-base-types';
28
+ import { DOMContext, ResolveThemeColor, type Theme } from 'treb-base-types';
29
29
 
30
30
  export interface ActivateSheetEvent {
31
31
  type: 'activate-sheet';
@@ -100,6 +100,8 @@ export class TabBar extends EventSource<TabEvent> {
100
100
  timeout?: number;
101
101
  } = {};
102
102
 
103
+ private tab_color_cache: Map<number, { background: string, foreground: string }> = new Map();
104
+
103
105
  // tslint:disable-next-line: variable-name
104
106
  private _visible = false;
105
107
 
@@ -133,6 +135,7 @@ export class TabBar extends EventSource<TabEvent> {
133
135
  private model: DataModel,
134
136
  private view: ViewModel,
135
137
  private options: GridOptions,
138
+ private theme: Theme,
136
139
  // private container: HTMLElement,
137
140
  view_node: HTMLElement,
138
141
  ) {
@@ -236,10 +239,21 @@ export class TabBar extends EventSource<TabEvent> {
236
239
  if (active) {
237
240
  // tab.classList.add('treb-selected');
238
241
  tab.setAttribute('selected', '');
242
+
243
+ if (tab.dataset.background_color) {
244
+ tab.style.backgroundColor = `color-mix(in srgb, ${tab.dataset.background_color} 20%, var(--treb-tab-bar-active-tab-background, #fff))`;
245
+ tab.style.color = '';
246
+ }
239
247
  }
240
248
  else {
241
249
  // tab.classList.remove('treb-selected');
242
250
  tab.removeAttribute('selected');
251
+ if (tab.dataset.background_color) {
252
+ tab.style.backgroundColor = tab.dataset.background_color;
253
+ }
254
+ if (tab.dataset.foreground_color) {
255
+ tab.style.color = tab.dataset.foreground_color;
256
+ }
243
257
  }
244
258
  }
245
259
 
@@ -419,6 +433,11 @@ export class TabBar extends EventSource<TabEvent> {
419
433
 
420
434
  }
421
435
 
436
+ public UpdateTheme() {
437
+ this.tab_color_cache.clear();
438
+ this.Update();
439
+ }
440
+
422
441
  /**
423
442
  * update tabs from model.
424
443
  */
@@ -464,6 +483,24 @@ export class TabBar extends EventSource<TabEvent> {
464
483
  const tab = this.DOM.Create('li');
465
484
  tab.setAttribute('tabindex', '0');
466
485
 
486
+ if (sheet.tab_color) {
487
+ const id = sheet.id;
488
+ if (!this.tab_color_cache.has(id)) {
489
+ const background = ResolveThemeColor(this.theme, sheet.tab_color);
490
+ const foreground = ResolveThemeColor(this.theme, { offset: sheet.tab_color });
491
+ if (background && foreground) {
492
+ this.tab_color_cache.set(id, { background, foreground });
493
+ }
494
+ }
495
+ const color = this.tab_color_cache.get(id);
496
+ if (color) {
497
+ tab.style.backgroundColor = color.background;
498
+ tab.style.color = color.foreground;
499
+ tab.dataset.background_color = color.background;
500
+ tab.dataset.foreground_color = color.foreground;
501
+ }
502
+ }
503
+
467
504
  // tab.classList.add('tab');
468
505
  tab.style.order = (index * 2).toString();
469
506
  tab.role = 'tab';