@trebco/treb 30.2.10 → 30.3.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.2. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.3. 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
@@ -1310,7 +1310,7 @@ export type Color = ThemeColor | HTMLColor | NullColor;
1310
1310
  export declare const ThemeColorIndex: (color: ThemeColor) => number;
1311
1311
  export declare const IsHTMLColor: (color?: Color) => color is HTMLColor;
1312
1312
  export declare const IsThemeColor: (color?: Color) => color is ThemeColor;
1313
- export declare const IsDefinedColor: (color?: Color) => color is ThemeColor | HTMLColor;
1313
+ export declare const IsDefinedColor: (color?: Color) => color is (ThemeColor | HTMLColor);
1314
1314
  export type CellValue = undefined | string | number | boolean | Complex | DimensionedQuantity;
1315
1315
 
1316
1316
  /**
@@ -1,8 +1,10 @@
1
1
  // @ts-check
2
2
 
3
+ /* global process */
4
+
3
5
  import * as esbuild from 'esbuild';
4
6
 
5
- import { SassPlugin, WorkerPlugin, NotifyPlugin, HTMLPlugin } from './esbuild-utils.mjs';
7
+ import { SassPlugin, WorkerPlugin, NotifyPlugin, HTMLPlugin, RewriteIgnoredImports } from './esbuild-utils.mjs';
6
8
  import { promises as fs } from 'fs';
7
9
 
8
10
  import pkg from './package.json' with { type: 'json' };
@@ -30,7 +32,7 @@ const options = {
30
32
  minify: true,
31
33
  verbose: false,
32
34
  xlsx_support: true,
33
- output_filename: 'treb-spreadsheet.mjs',
35
+ output_filename: 'treb-spreadsheet',
34
36
  };
35
37
 
36
38
  //------------------------------------------------------------------------------
@@ -58,36 +60,19 @@ for (let i = 0; i < process.argv.length; i++) {
58
60
  }
59
61
  }
60
62
 
61
- /**
62
- * thanks to
63
- * https://github.com/evanw/esbuild/issues/3337#issuecomment-2085394950
64
- */
65
- function RewriteIgnoredImports() {
66
- return {
67
- name: 'RewriteIgnoredImports',
68
- setup(build) {
69
- build.onEnd(async (result) => {
70
- if (result.outputFiles) {
71
- for (const file of result.outputFiles) {
72
- const { path, text } = file;
73
- await fs.writeFile(path, text.replace(/esbuild-ignore-import:/, ''));
74
- }
75
- }
76
- });
77
- },
78
- };
79
- }
80
63
 
81
64
  /** @type esbuild.BuildOptions */
82
65
  const build_options = {
83
66
  entryPoints: [
84
- 'treb-embed/src/index.ts',
67
+ { in: 'treb-embed/src/index.ts', out: options.output_filename },
68
+ { in: 'treb-embed/src/export-worker.ts', out: 'treb-export-worker' },
85
69
  ],
86
70
  banner: {
87
71
  js: `/*! TREB v${pkg.version}. Copyright 2018-${new Date().getFullYear()} trebco, llc. All rights reserved. LGPL: https://treb.app/license */`
88
72
  },
89
73
  bundle: true,
90
- outfile: 'dist/' + options.output_filename,
74
+ outdir: 'dist',
75
+ // outfile: 'dist/' + options.output_filename,
91
76
  outExtension: { '.js': '.mjs' },
92
77
  minify: options.minify,
93
78
  metafile: true,
package/esbuild-utils.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
 
2
2
  // @ts-check
3
3
 
4
+ /* global console */
5
+
4
6
  import * as esbuild from 'esbuild';
5
7
  import { promises as fs } from 'fs';
6
- import { minify } from 'html-minifier';
8
+ import { minify } from 'html-minifier-terser';
7
9
  import path from 'path';
8
10
  import * as sass from 'sass';
9
11
  import cssnano from 'cssnano';
@@ -114,7 +116,7 @@ export const WorkerPlugin = (options) => ({
114
116
  resolveDir: args.resolveDir,
115
117
  });
116
118
  return { path: result.path, namespace: 'worker', };
117
- }),
119
+ });
118
120
 
119
121
  // for some reason I can't get the filter to work here, but using
120
122
  // namespace works. as long as we don't collide with anybody else.
@@ -217,10 +219,14 @@ export const HTMLPlugin = (options) => ({
217
219
  }
218
220
 
219
221
  const text = await fs.readFile(args.path, 'utf8');
222
+
220
223
  return {
221
- contents: options?.minify ? minify(text, html_minifier_options) : text,
224
+ contents: options?.minify ? await minify(text, html_minifier_options) : text,
222
225
  loader: 'text',
223
226
  };
227
+
228
+
229
+
224
230
  });
225
231
  },
226
232
  });
@@ -280,3 +286,24 @@ export const SassPlugin = (options) => ({
280
286
  });
281
287
  },
282
288
  });
289
+
290
+ /**
291
+ * thanks to
292
+ * https://github.com/evanw/esbuild/issues/3337#issuecomment-2085394950
293
+ */
294
+ export const RewriteIgnoredImports = () => {
295
+ return {
296
+ name: 'RewriteIgnoredImports',
297
+ setup(build) {
298
+ build.onEnd(async (result) => {
299
+ if (result.outputFiles) {
300
+ for (const file of result.outputFiles) {
301
+ const { path, text } = file;
302
+ await fs.writeFile(path, text.replace(/esbuild-ignore-import:/g, ''));
303
+ }
304
+ }
305
+ });
306
+ },
307
+ };
308
+ };
309
+
package/eslint.config.js CHANGED
@@ -23,7 +23,8 @@ export default tseslint.config(
23
23
 
24
24
  "@typescript-eslint/no-unused-vars": [
25
25
  "error", {
26
- "destructuredArrayIgnorePattern": "^_",
26
+ destructuredArrayIgnorePattern: "^_",
27
+ varsIgnorePattern: "^_",
27
28
  }],
28
29
  },
29
30
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "30.2.10",
3
+ "version": "30.3.2",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -12,15 +12,14 @@
12
12
  "type": "module",
13
13
  "devDependencies": {
14
14
  "@types/html-minifier": "^4.0.2",
15
- "@types/node": "^20.8.5",
15
+ "@types/node": "^22.1.0",
16
16
  "@types/uzip": "^0.20201231.0",
17
- "archiver": "^6.0.1",
18
17
  "base64-js": "^1.5.1",
19
- "cssnano": "^6.0.0",
20
- "esbuild": "^0.20.1",
21
- "eslint": "^8.56.0",
18
+ "cssnano": "^7.0.4",
19
+ "esbuild": "^0.23.0",
20
+ "eslint": "^9.8.0",
22
21
  "fast-xml-parser": "^4.0.7",
23
- "html-minifier": "^4.0.0",
22
+ "html-minifier-terser": "^7.2.0",
24
23
  "sass": "^1.69.3",
25
24
  "treb-base-types": "file:treb-base-types",
26
25
  "treb-calculator": "file:treb-calculator",
@@ -34,21 +33,20 @@
34
33
  "ts-node-dev": "^2.0.0",
35
34
  "tslib": "^2.2.0",
36
35
  "typescript": "^5.3.3",
37
- "typescript-eslint": "^7.0.2",
36
+ "typescript-eslint": "^8.0.0",
38
37
  "uzip": "^0.20201231.0"
39
38
  },
40
39
  "scripts": {
41
- "build": "node esbuild-custom-element.mjs",
42
- "build-light": "node esbuild-custom-element.mjs --no-xlsx --output-filename treb-spreadsheet-light.mjs",
43
- "dev": "node esbuild-custom-element.mjs --dev",
40
+ "build": "node esbuild-composite.mjs",
41
+ "dev": "node esbuild-composite.mjs --dev",
44
42
  "clean": "rm -fr build dist declaration",
45
- "watch": "node --watch esbuild-custom-element.mjs --watch --dev",
46
- "watch-production": "node --watch esbuild-custom-element.mjs --watch",
43
+ "watch": "node --watch esbuild-composite.mjs --watch --dev",
44
+ "watch-production": "node --watch esbuild-composite.mjs --watch",
47
45
  "tsc": "tsc -b treb-embed/modern.tsconfig.json",
48
46
  "rebuild-tsc": "tsc -b --force treb-embed/modern.tsconfig.json",
49
47
  "watch-tsc": "tsc -b treb-embed/modern.tsconfig.json -w",
50
48
  "watch-api": "ts-node-dev --respawn api-generator/api-generator.ts --config api-config.json",
51
49
  "generate-api": "ts-node-dev api-generator/api-generator.ts --config api-config.json",
52
- "release": "npm run rebuild-tsc && npm run build && npm run generate-api && npm run build-light"
50
+ "release": "npm run rebuild-tsc && npm run build && npm run generate-api"
53
51
  }
54
52
  }
@@ -782,7 +782,36 @@ export class ExpressionCalculator {
782
782
  // unless the expression changes, which will discard the generated
783
783
  // function (along with the expression itself).
784
784
 
785
- const fn = Primitives.MapOperator(x.operator);
785
+ // pulling out concat so we can bind the model for language values
786
+
787
+ const fn = x.operator === '&' ? (a: UnionValue, b: UnionValue): UnionValue => {
788
+
789
+ // this works, but it's not volatile so it doesn't update on
790
+ // a language change; maybe language change should force a recalc? (...)
791
+
792
+ if (a.type === ValueType.error) { return a; }
793
+ if (b.type === ValueType.error) { return b; }
794
+
795
+ const strings = [a, b].map(x => {
796
+ if (x.type === ValueType.undefined) { return ''; }
797
+ if (x.type === ValueType.boolean) {
798
+ if (x.value) {
799
+ return this.data_model.language_model?.boolean_true || 'TRUE';
800
+ }
801
+ else {
802
+ return this.data_model.language_model?.boolean_false || 'FALSE';
803
+ }
804
+ }
805
+ return x.value;
806
+ });
807
+
808
+ return {
809
+ type: ValueType.string,
810
+ value: `${strings[0]}${strings[1]}`,
811
+ };
812
+
813
+
814
+ } : Primitives.MapOperator(x.operator);
786
815
 
787
816
  if (!fn) {
788
817
 
@@ -301,7 +301,7 @@ export const TextFunctionLibrary: FunctionMap = {
301
301
  },
302
302
  },
303
303
 
304
- /** canonical should be CONCAT; concatenate can be an alias */
304
+ /** canonical should be CONCAT; concatenate can be an alias */
305
305
  Concat: {
306
306
  description: 'Pastes strings together',
307
307
  fn: (...args: CellValue[]): UnionValue => {
@@ -266,6 +266,7 @@ export const Modulo = (a: UnionValue, b: UnionValue): UnionValue => {
266
266
  return { value: x % y, type: ValueType.number };
267
267
  };
268
268
 
269
+ /*
269
270
  export const Concatenate = (a: UnionValue, b: UnionValue): UnionValue => {
270
271
  if (a.type === ValueType.error) { return a; }
271
272
  if (b.type === ValueType.error) { return b; }
@@ -276,6 +277,7 @@ export const Concatenate = (a: UnionValue, b: UnionValue): UnionValue => {
276
277
  };
277
278
 
278
279
  };
280
+ */
279
281
 
280
282
  export const Equals = (a: UnionValue, b: UnionValue): UnionValue => {
281
283
  if (a.type === ValueType.error) { return a; }
@@ -430,7 +432,7 @@ export const UseComplex = () => {
430
432
 
431
433
  export const MapOperator = (operator: string) => {
432
434
  switch(operator) {
433
- case '&': return Concatenate;
435
+ // case '&': return Concatenate;
434
436
  case '+': return Add;
435
437
  case '-': return Subtract;
436
438
  case '*': return Multiply;
@@ -12,7 +12,6 @@
12
12
  "../treb-parser/**/*.ts",
13
13
  "../treb-calculator/**/*.ts",
14
14
  "../treb-base-types/**/*.ts",
15
- "../treb-export/**/*.ts",
16
15
  "src/content-types.d.ts"
17
16
  ],
18
17
  "exclude": [
@@ -107,13 +107,6 @@ import type { StateLeafVertex } from 'treb-calculator';
107
107
  */
108
108
  import './content-types.d.ts';
109
109
 
110
- /**
111
- * import the worker as a script file. tsc will read this on typecheck but
112
- * that's actually to the good; when we build with esbuild we will inline
113
- * the script so we can run it as a worker.
114
- */
115
- import * as export_worker_script from 'worker:../../treb-export/src/export-worker/index.worker';
116
-
117
110
  // --- types -------------------------------------------------------------------
118
111
 
119
112
  /**
@@ -803,7 +796,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
803
796
  const obj = JSON.parse(json);
804
797
  grid_options.initial_scale = obj.scale || 1;
805
798
  }
806
- catch (e) {
799
+ catch {
807
800
  console.warn('parsing persisted scale failed');
808
801
  }
809
802
  }
@@ -2006,10 +1999,17 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2006
1999
  }
2007
2000
 
2008
2001
  language = language.toLowerCase();
2009
-
2010
2002
  let mod: { LanguageMap: LanguageModel } | undefined;
2011
2003
 
2012
- if (language && language !== 'en') {
2004
+ // FIXME: this should be generated and included, to allow for changes.
2005
+ // for the time being hardcoding as a quick fix
2006
+
2007
+ const supported_languages = [
2008
+ 'es', 'fr', 'pt', 'nl', 'de',
2009
+ 'pl', 'sv', 'no', 'da', 'it',
2010
+ ];
2011
+
2012
+ if (language && supported_languages.includes(language)) {
2013
2013
 
2014
2014
  // FIXME: even though we now have a dynamic import
2015
2015
  // working, we still probably want to use a filter
@@ -6155,39 +6155,21 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
6155
6155
  }
6156
6156
 
6157
6157
  /**
6158
- * load worker. optionally uses an ambient path as prefix; intended for
6159
- * loading in different directories (or different hosts?)
6158
+ * worker now uses a dynamic import with a module built separately.
6159
+ * see `export-worker.ts` (in this directory) for more detail.
6160
6160
  */
6161
6161
  protected async LoadWorker(): Promise<Worker> {
6162
-
6163
- // this is inlined to ensure the code will be tree-shaken properly
6164
- // (we're trying to force it to remove the imported worker script)
6165
-
6166
- if (process.env.XLSX_SUPPORT) {
6167
-
6168
- // for esm we now support embedding the worker as a blob
6169
- // (as text, actually); we can construct it from the text
6170
- // as necessary.
6171
-
6172
- if (export_worker_script) {
6173
- try {
6174
- const worker = new Worker(
6175
- URL.createObjectURL(new Blob([(export_worker_script as {default: string}).default], { type: 'application/javascript' })));
6176
- return worker;
6177
- }
6178
- catch (err) {
6179
- console.info('embedded worker failed');
6180
- console.error(err);
6181
- }
6182
- }
6183
-
6184
- }
6185
- else {
6186
- console.warn('this build does not include xlsx support.');
6162
+ try {
6163
+ const worker = `export`;
6164
+ const mod = await import(`esbuild-ignore-import:./treb-${worker}-worker.mjs`) as {
6165
+ CreateWorker: () => Promise<Worker>;
6166
+ };
6167
+ return await mod.CreateWorker();
6168
+ }
6169
+ catch (err) {
6170
+ console.error(err);
6171
+ throw(err);
6187
6172
  }
6188
-
6189
- throw new Error('creating worker failed');
6190
-
6191
6173
  }
6192
6174
 
6193
6175
  /**
@@ -0,0 +1,44 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2024 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ // this is a new entrypoint for the worker, as a module. while we can't
23
+ // just create a worker script, if we create this as a module and embed
24
+ // the worker script we can still get the benefits of dynamic loading.
25
+
26
+ // why can't we create a worker script? it's OK for direct embedding but
27
+ // breaks when embedding in a bundler (vite is OK, but webpack is not).
28
+ // but all bundlers seem ok with local modules (knock on wood).
29
+
30
+ /**
31
+ * import the worker as text so it will be embedded in this module
32
+ */
33
+ import * as export_worker_script from 'worker:../../treb-export/src/index.worker';
34
+
35
+ /**
36
+ * create the worker by loading the embedded text
37
+ * @returns
38
+ */
39
+ export const CreateWorker = async (): Promise<Worker> => {
40
+ const worker = new Worker(
41
+ URL.createObjectURL(new Blob([(export_worker_script as {default: string}).default], { type: 'application/javascript' })));
42
+ return worker;
43
+ };
44
+
@@ -33,7 +33,6 @@
33
33
 
34
34
 
35
35
  box-sizing: border-box;
36
- * { box-sizing: border-box; }
37
36
 
38
37
  position: fixed;
39
38
  top: -1000px;
@@ -46,6 +45,8 @@
46
45
  overflow-y: auto;
47
46
  z-index: $z-index-autocomplete;
48
47
 
48
+ * { box-sizing: border-box; }
49
+
49
50
  ul {
50
51
 
51
52
  font-size: inherit;
@@ -73,23 +74,6 @@
73
74
  }
74
75
  }
75
76
 
76
- /*
77
- here we unwind the selected style if there's a hover,
78
- unless you're hovering over the selection. and so on.
79
- * /
80
-
81
- &:hover li a.selected {
82
- background: inherit;
83
- color: inherit;
84
-
85
- &:hover {
86
- background: #339966;
87
- color: #fff;
88
- }
89
-
90
- }
91
- */
92
-
93
77
  }
94
78
  }
95
79
 
@@ -98,6 +98,7 @@
98
98
  padding: 0;
99
99
  background: transparent;
100
100
  border: 0;
101
+ padding: 4px;
101
102
 
102
103
  &>svg {
103
104
 
@@ -112,7 +113,6 @@
112
113
  }
113
114
  }
114
115
 
115
- padding: 4px;
116
116
 
117
117
  }
118
118
 
@@ -56,6 +56,11 @@
56
56
  color: var(--treb-dropdown-color, inherit);
57
57
  border: 1px solid var(--treb-dropdown-border-color, unset);
58
58
 
59
+ text-align: left;
60
+ max-height: 10em;
61
+ overflow-y: auto;
62
+ outline: none;
63
+
59
64
  & div {
60
65
  padding: 2px;
61
66
  cursor: default;
@@ -66,12 +71,6 @@
66
71
  color: var(--treb-dropdown-selected-color, #fff);
67
72
  }
68
73
 
69
- text-align: left;
70
-
71
- max-height: 10em;
72
- overflow-y: auto;
73
- outline: none;
74
-
75
74
  }
76
75
 
77
76
  .treb-dropdown-caret.active {
@@ -30,15 +30,17 @@
30
30
  text-align: left;
31
31
  gap: .5em;
32
32
 
33
- &[hidden] {
34
- display: none;
35
- }
33
+
36
34
 
37
35
  padding: 0px 2px 12px 2px; // FIXME: use ems?
38
36
 
39
37
  max-width: 100%;
40
38
  overflow-x: hidden;
41
39
 
40
+ &[hidden] {
41
+ display: none;
42
+ }
43
+
42
44
  /** label for selection address */
43
45
  .treb-address-label {
44
46
 
@@ -207,23 +207,25 @@
207
207
  /** specific layout for column header cover */
208
208
  &.column-header-cover {
209
209
 
210
+ z-index: $z-index-header-tile-cover;
211
+
210
212
  /** style is attached when mousing over a column boundary */
211
213
  &.resize {
212
214
  cursor: col-resize;
213
215
  }
214
216
 
215
- z-index: $z-index-header-tile-cover;
216
217
  }
217
218
 
218
219
  /** specific layout for row header cover */
219
220
  &.row-header-cover {
220
221
 
222
+ z-index: $z-index-header-tile-cover;
223
+
221
224
  /** style is attached when mousing over a row boundary */
222
225
  &.resize {
223
226
  cursor: row-resize;
224
227
  }
225
228
 
226
- z-index: $z-index-header-tile-cover;
227
229
  }
228
230
 
229
231
  }
@@ -234,6 +236,9 @@
234
236
  top: 0px;
235
237
  left: 0px;
236
238
 
239
+ z-index: $z-index-annotations;
240
+ pointer-events: none;
241
+
237
242
  .annotation {
238
243
  position: absolute;
239
244
  overflow: hidden;
@@ -297,8 +302,6 @@
297
302
  cursor: move;
298
303
  }
299
304
 
300
- z-index: $z-index-annotations;
301
- pointer-events: none;
302
305
  }
303
306
 
304
307
  /** selection stacks over the grid but under the cover */
@@ -68,6 +68,28 @@
68
68
 
69
69
  line-height: normal;
70
70
 
71
+ // ---------------------------------------------------------------------------
72
+
73
+ color-scheme: var(--treb-color-scheme, unset);
74
+
75
+ font-family: $font-stack;
76
+
77
+ font-style: normal;
78
+ font-weight: normal;
79
+ color: inherit;
80
+ font-size: 14px; // ?
81
+
82
+ height: 100%;
83
+ width: 100%;
84
+
85
+ position: relative;
86
+ display: grid;
87
+ grid-template-rows: auto minmax(0, 1fr);
88
+ grid-template-columns: minmax(0, 1fr) auto;
89
+ // gap: 1em;
90
+
91
+ // ---------------------------------------------------------------------------
92
+
71
93
  div, button, input, ul, ol, li, a, textarea, svg {
72
94
 
73
95
  // maybe this is being too aggressive. we could be a little
@@ -110,23 +132,6 @@
110
132
 
111
133
  // ---------------------------------------------------------------------------
112
134
 
113
- color-scheme: var(--treb-color-scheme, unset);
114
-
115
- font-family: $font-stack;
116
-
117
- font-style: normal;
118
- font-weight: normal;
119
- color: inherit;
120
- font-size: 14px; // ?
121
-
122
- height: 100%;
123
- width: 100%;
124
-
125
- position: relative;
126
- display: grid;
127
- grid-template-rows: auto minmax(0, 1fr);
128
- grid-template-columns: minmax(0, 1fr) auto;
129
- // gap: 1em;
130
135
 
131
136
  &[animate] {
132
137
  .treb-layout-header {
@@ -354,11 +359,11 @@
354
359
  right: 0px;
355
360
  border-top-right-radius: 0px;
356
361
  border-bottom-right-radius: 0px;
362
+ background: var(--treb-toolbar-button-background, #fff);
357
363
  &::after {
358
364
  mask-image: var(--icon-chevron-left);
359
365
  -webkit-mask-image: var(--icon-chevron-left);
360
366
  }
361
- background: var(--treb-toolbar-button-background, #fff);
362
367
 
363
368
  }
364
369
  }
@@ -29,6 +29,8 @@
29
29
 
30
30
  grid-area: 3/1/4/2;
31
31
 
32
+ align-items: center;
33
+
32
34
  .treb-spreadsheet-tab-container {
33
35
  align-self: flex-start;
34
36
  overflow: hidden;
@@ -59,8 +61,6 @@
59
61
  display: none;
60
62
  }
61
63
 
62
- align-items: center;
63
-
64
64
  & .treb-spreadsheet-tabs>li {
65
65
  display: inline-block;
66
66
  position: relative;