@trebco/treb 30.8.2 → 30.10.1

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.10. 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.10.1",
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
 
@@ -21,7 +21,10 @@
21
21
 
22
22
  import { type Color, type CellStyle, IsHTMLColor, IsThemeColor, ThemeColorIndex, type ThemeColor } from './style';
23
23
  import { ColorFunctions } from './color';
24
+ // import * as LCHColorFunctions from './color2';
25
+
24
26
  import { DOMContext } from './dom-utilities';
27
+ import { Measurement } from 'treb-utils';
25
28
 
26
29
  /*
27
30
  * so this is a little strange. we use CSS to populate a theme object,
@@ -106,7 +109,7 @@ export interface Theme {
106
109
  theme_colors?: string[];
107
110
 
108
111
  /** as RGB, so we can adjust them */
109
- theme_colors_rgb?: number[][];
112
+ theme_colors_rgb?: [number, number, number][];
110
113
 
111
114
  /**
112
115
  * cache tinted colors. the way this works is we index by the
@@ -206,7 +209,8 @@ const TintedColor = (theme: Theme, source: ThemeColor) => {
206
209
  let color = theme.tint_cache[index][tint];
207
210
  if (!color) {
208
211
 
209
- const rgb = (theme.theme_colors_rgb ? theme.theme_colors_rgb[index] : [0, 0, 0]) || [0, 0, 0];
212
+ const rgb: [number, number, number] = (theme.theme_colors_rgb ? theme.theme_colors_rgb[index] : [0, 0, 0]) || [0, 0, 0];
213
+
210
214
  let tinted: {r: number, g: number, b: number};
211
215
  if (tint > 0) {
212
216
  tinted = ColorFunctions.Lighten(rgb[0], rgb[1], rgb[2], tint * 100, true);
@@ -215,6 +219,21 @@ const TintedColor = (theme: Theme, source: ThemeColor) => {
215
219
  tinted = ColorFunctions.Darken(rgb[0], rgb[1], rgb[2], -tint * 100, true);
216
220
  }
217
221
  color = `rgb(${tinted.r},${tinted.g},${tinted.b})`;
222
+
223
+ /*
224
+ // L is in [0, 100] and the passed tint value is between (-1 and 1). but
225
+ // is the tint value the desired value, or the adjustment? (...)
226
+ // looks like it's the adjustment. makes sense I guess if you pick the
227
+ // original colors.
228
+
229
+ // if the tint value was not originally in perceptual space we might need
230
+ // to curve it a bit? (...)
231
+
232
+ const lch = LCHColorFunctions.RGBToLCH(...rgb);
233
+ const tinted = LCHColorFunctions.LCHToRGB(lch.l + 50 * tint, lch.c, lch.h);
234
+ color = `rgb(${tinted.r},${tinted.g},${tinted.b})`;
235
+ */
236
+
218
237
  theme.tint_cache[index][tint] = color;
219
238
 
220
239
  }
@@ -242,7 +261,15 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
242
261
  return '';
243
262
  }
244
263
 
245
- const resolved = ResolveThemeColor(theme, color.offset);
264
+ let resolved = '';
265
+
266
+ if (IsHTMLColor(color.offset)) {
267
+ const clamped = Measurement.MeasureColor(color.offset.text);
268
+ resolved = `rgb(${clamped[0]}, ${clamped[1]}, ${clamped[2]})`;
269
+ }
270
+ else {
271
+ resolved = ResolveThemeColor(theme, color.offset, undefined);
272
+ }
246
273
 
247
274
  // check cache
248
275
  if (theme.offset_cache && theme.offset_cache[resolved]) {
@@ -253,17 +280,35 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
253
280
 
254
281
  if (resolved) {
255
282
  // ok figure it out?
256
- const match = resolved.match(/rgb\((\d+), (\d+), (\d+)\)/);
283
+ const match = resolved.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
257
284
  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) {
285
+
286
+ type ColorTuple = [number, number, number];
287
+
288
+ const background: ColorTuple = [Number(match[1]), Number(match[2]), Number(match[3])];
289
+
290
+
291
+ // const hsl = ColorFunctions.RGBToHSL(r, g, b);
292
+ // const check = ColorFunctions.GetLuminance(r, g, b);
293
+
294
+ const a = Array.from(Measurement.MeasureColor(theme.offset_dark)) as ColorTuple;
295
+ const b = Array.from(Measurement.MeasureColor(theme.offset_light)) as ColorTuple;
296
+
297
+ const tc = ColorFunctions.GetTextColor(background, a, b);
298
+
299
+ offset = `rgb(${tc[0]}, ${tc[1]}, ${tc[2]})`;
300
+
301
+ /*
302
+ if (hsl.l >.65)) {
261
303
  offset = theme.offset_dark;
262
304
  }
305
+ */
306
+
307
+
263
308
  }
264
309
  else {
265
310
  // ...
266
- console.warn(`can't offset against color`, resolved);
311
+ console.warn(`can't offset against color`, resolved, '(1)');
267
312
  }
268
313
 
269
314
  if (!theme.offset_cache) {
@@ -272,7 +317,7 @@ export const ResolveThemeColor = (theme: Theme, color?: Color, default_index?: n
272
317
  theme.offset_cache[resolved] = offset;
273
318
  }
274
319
  else {
275
- console.warn(`can't resolve offset color`, color.offset);
320
+ console.warn(`can't resolve offset color`, color.offset, '(2)');
276
321
  }
277
322
 
278
323
  return offset;
@@ -597,7 +642,7 @@ export const LoadThemeProperties = (container: HTMLElement): Theme => {
597
642
  context.fillStyle = color;
598
643
  context.fillRect(0, 0, 3, 3);
599
644
  const imagedata = context.getImageData(1, 1, 1, 1);
600
- return Array.from(imagedata.data);
645
+ return Array.from(imagedata.data) as [number, number, number];
601
646
  });
602
647
  }
603
648
 
@@ -55,20 +55,64 @@
55
55
  * of times we have to define the same colors.
56
56
  */
57
57
 
58
- // moved // background: var(--treb-chart-background, #fff);
59
-
60
- .series-1 { color: var(--treb-chart-color-series-1, #80B1D3); }
61
- .series-2 { color: var(--treb-chart-color-series-2, #8DD3C7); }
62
- .series-3 { color: var(--treb-chart-color-series-3, #BEBADA); }
63
- .series-4 { color: var(--treb-chart-color-series-4, #FB8072); }
64
- .series-5 { color: var(--treb-chart-color-series-5, #FDB462); }
65
- .series-6 { color: var(--treb-chart-color-series-6, #B3DE69); }
66
- .series-7 { color: var(--treb-chart-color-series-7, #FCCDE5); }
67
- .series-8 { color: var(--treb-chart-color-series-8, #D9D9D9); }
68
- .series-9 { color: var(--treb-chart-color-series-9, #BC80BD); }
69
- .series-10 { color: var(--treb-chart-color-series-10, #CCEBC5); }
70
- .series-11 { color: var(--treb-chart-color-series-11, #FFED6F); }
71
- .series-12 { color: var(--treb-chart-color-series-12, #FFFFB3); }
58
+ // lightness offsets for subsequent colors. is there a way we could
59
+ // have the charts just use a modulo and an offset? (...) would be
60
+ // cleaner maybe
61
+
62
+ /* something like this:
63
+ *
64
+ * .series-1 { color: var(--theme-color); --base-color: var(--theme-color); }
65
+ * .segment-2 { color: lch(from var(--base-color) calc(l + 20) c h); }
66
+ *
67
+ */
68
+
69
+ --segment-2-offset: -20;
70
+ --segment-3-offset: 10;
71
+ --segment-4-offset: -10;
72
+ --segment-5-offset: 20;
73
+ --segment-6-offset: -25;
74
+
75
+ .series-1 { color: var(--treb-applied-theme-color-1); }
76
+ .series-2 { color: var(--treb-applied-theme-color-2); }
77
+ .series-3 { color: var(--treb-applied-theme-color-3); }
78
+ .series-4 { color: var(--treb-applied-theme-color-4); }
79
+ .series-5 { color: var(--treb-applied-theme-color-5); }
80
+ .series-6 { color: var(--treb-applied-theme-color-6); }
81
+
82
+ .series-7 { color: hsl(from var(--treb-applied-theme-color-1) h s calc(l + var(--segment-2-offset))); }
83
+ .series-8 { color: hsl(from var(--treb-applied-theme-color-2) h s calc(l + var(--segment-2-offset))); }
84
+ .series-9 { color: hsl(from var(--treb-applied-theme-color-3) h s calc(l + var(--segment-2-offset))); }
85
+ .series-10 { color: hsl(from var(--treb-applied-theme-color-4) h s calc(l + var(--segment-2-offset))); }
86
+ .series-11 { color: hsl(from var(--treb-applied-theme-color-5) h s calc(l + var(--segment-2-offset))); }
87
+ .series-12 { color: hsl(from var(--treb-applied-theme-color-6) h s calc(l + var(--segment-2-offset))); }
88
+
89
+ .series-13 { color: hsl(from var(--treb-applied-theme-color-1) h s calc(l + var(--segment-3-offset))); }
90
+ .series-14 { color: hsl(from var(--treb-applied-theme-color-2) h s calc(l + var(--segment-3-offset))); }
91
+ .series-15 { color: hsl(from var(--treb-applied-theme-color-3) h s calc(l + var(--segment-3-offset))); }
92
+ .series-16 { color: hsl(from var(--treb-applied-theme-color-4) h s calc(l + var(--segment-3-offset))); }
93
+ .series-17 { color: hsl(from var(--treb-applied-theme-color-5) h s calc(l + var(--segment-3-offset))); }
94
+ .series-18 { color: hsl(from var(--treb-applied-theme-color-6) h s calc(l + var(--segment-3-offset))); }
95
+
96
+ .series-19 { color: hsl(from var(--treb-applied-theme-color-1) h s calc(l + var(--segment-4-offset))); }
97
+ .series-20 { color: hsl(from var(--treb-applied-theme-color-2) h s calc(l + var(--segment-4-offset))); }
98
+ .series-21 { color: hsl(from var(--treb-applied-theme-color-3) h s calc(l + var(--segment-4-offset))); }
99
+ .series-22 { color: hsl(from var(--treb-applied-theme-color-4) h s calc(l + var(--segment-4-offset))); }
100
+ .series-23 { color: hsl(from var(--treb-applied-theme-color-5) h s calc(l + var(--segment-4-offset))); }
101
+ .series-24 { color: hsl(from var(--treb-applied-theme-color-6) h s calc(l + var(--segment-4-offset))); }
102
+
103
+ .series-25 { color: hsl(from var(--treb-applied-theme-color-1) h s calc(l + var(--segment-5-offset))); }
104
+ .series-26 { color: hsl(from var(--treb-applied-theme-color-2) h s calc(l + var(--segment-5-offset))); }
105
+ .series-27 { color: hsl(from var(--treb-applied-theme-color-3) h s calc(l + var(--segment-5-offset))); }
106
+ .series-28 { color: hsl(from var(--treb-applied-theme-color-4) h s calc(l + var(--segment-5-offset))); }
107
+ .series-29 { color: hsl(from var(--treb-applied-theme-color-5) h s calc(l + var(--segment-5-offset))); }
108
+ .series-30 { color: hsl(from var(--treb-applied-theme-color-6) h s calc(l + var(--segment-5-offset))); }
109
+
110
+ .series-31 { color: hsl(from var(--treb-applied-theme-color-1) h s calc(l + var(--segment-6-offset))); }
111
+ .series-32 { color: hsl(from var(--treb-applied-theme-color-2) h s calc(l + var(--segment-6-offset))); }
112
+ .series-33 { color: hsl(from var(--treb-applied-theme-color-3) h s calc(l + var(--segment-6-offset))); }
113
+ .series-34 { color: hsl(from var(--treb-applied-theme-color-4) h s calc(l + var(--segment-6-offset))); }
114
+ .series-35 { color: hsl(from var(--treb-applied-theme-color-5) h s calc(l + var(--segment-6-offset))); }
115
+ .series-36 { color: hsl(from var(--treb-applied-theme-color-6) h s calc(l + var(--segment-6-offset))); }
72
116
 
73
117
  /* chart title, at top or bottom */
74
118
  .chart-title {
@@ -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,