@trebco/treb 30.1.2 → 30.2.4

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.
@@ -170,7 +170,6 @@ export interface LoadDocumentOptions {
170
170
  source?: LoadSource,
171
171
  }
172
172
 
173
-
174
173
  /**
175
174
  * options for the GetRange method
176
175
  */
@@ -255,6 +254,15 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
255
254
  /** @internal */
256
255
  public static treb_embedded_script_path = '';
257
256
 
257
+ /**
258
+ * @internal
259
+ *
260
+ * keep track of modules we've tried to load dynamically, so we don't
261
+ * do it again. not sure if this is strictly necessary but especially if
262
+ * we're logging I don't want to see it again
263
+ */
264
+ protected static failed_dynamic_modules: string[] = [];
265
+
258
266
  /* * @internal */
259
267
  // public static enable_engine = false;
260
268
 
@@ -646,8 +654,6 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
646
654
  */
647
655
  constructor(options: EmbeddedSpreadsheetOptions & { model?: EmbeddedSpreadsheet }) {
648
656
 
649
- // super();
650
-
651
657
  // we renamed this option, default to the new name
652
658
 
653
659
  if (options.storage_key && !options.local_storage) {
@@ -842,6 +848,24 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
842
848
  this.grid.headless = true; // FIXME: move into grid options
843
849
  }
844
850
 
851
+ // --- testing dynamic loading ---------------------------------------------
852
+
853
+ this.LoadLanguage(options.language);
854
+
855
+ // --- testing plugins -----------------------------------------------------
856
+
857
+ /*
858
+ // FIXME: when to do this? could it wait? should it be async? (...)
859
+
860
+ if (options.plugins) {
861
+ for (const plugin of options.plugins) {
862
+ plugin.Attach(this);
863
+ }
864
+ }
865
+ */
866
+
867
+ // -------------------------------------------------------------------------
868
+
845
869
  // we're now gating this on container to support fully headless operation
846
870
 
847
871
  if (container) {
@@ -1180,7 +1204,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1180
1204
 
1181
1205
  let list: FunctionDescriptor[] = this.calculator.SupportedFunctions();
1182
1206
 
1183
- if (this.language_model) {
1207
+ if (this.language_model?.functions) {
1184
1208
 
1185
1209
  const map: Record<string, TranslatedFunctionDescriptor> = {};
1186
1210
  for (const entry of this.language_model.functions || []) {
@@ -1188,7 +1212,20 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1188
1212
  }
1189
1213
 
1190
1214
  list = list.map(descriptor => {
1191
- return map[descriptor.name.toUpperCase()] || descriptor;
1215
+ const partial = map[descriptor.name.toUpperCase()];
1216
+
1217
+ // FIXME: this is not deep enough if we are going to modify
1218
+ // argument names. we will need to keep other elements of the
1219
+ // argument entries. this is sufficient for now if we're just
1220
+ // setting function names / descriptions.
1221
+
1222
+ if (partial) {
1223
+ return {
1224
+ ...descriptor,
1225
+ ...partial,
1226
+ };
1227
+ }
1228
+ return descriptor;
1192
1229
  });
1193
1230
 
1194
1231
  }
@@ -1956,6 +1993,47 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1956
1993
 
1957
1994
  // --- public API methods ----------------------------------------------------
1958
1995
 
1996
+ /** dynamically load language module */
1997
+ public async LoadLanguage(language = '') {
1998
+
1999
+ if (!language || language === 'locale') {
2000
+ const locale = Localization.locale || '';
2001
+ const parts = locale.split(/-/).map(part => part.toLowerCase());
2002
+ language = parts[0];
2003
+ }
2004
+
2005
+ this.SetLanguage(); // clear
2006
+
2007
+ language = language.toLowerCase();
2008
+
2009
+ let mod: { LanguageMap: LanguageModel } | undefined;
2010
+
2011
+ if (language && language !== 'en') {
2012
+
2013
+ // FIXME: even though we now have a dynamic import
2014
+ // working, we still probably want to use a filter
2015
+ // list to avoid unnecessary 404s.
2016
+
2017
+ // regarding the import, this is specially crafted to
2018
+ // work in both esbuild and vite. probably will break
2019
+ // some other bundlers though -- might need some magic
2020
+ // comments
2021
+
2022
+ try {
2023
+ mod = await import(`esbuild-ignore-import:./languages/treb-i18n-${language}.mjs`);
2024
+ }
2025
+ catch (err) {
2026
+ console.error(err);
2027
+ }
2028
+
2029
+ }
2030
+
2031
+ if (mod) {
2032
+ this.SetLanguage(mod.LanguageMap);
2033
+ }
2034
+
2035
+ }
2036
+
1959
2037
  /**
1960
2038
  * this is not public _yet_
1961
2039
  *
@@ -1973,10 +2051,15 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
1973
2051
  // create a name map for grid
1974
2052
 
1975
2053
  const map: Record< string, string > = {};
1976
- for (const entry of model.functions || []) {
1977
- map[entry.base] = entry.name;
2054
+
2055
+ if (model.functions) {
2056
+ for (const entry of model.functions || []) {
2057
+ map[entry.base] = entry.name;
2058
+ }
1978
2059
  }
2060
+
1979
2061
  this.grid.SetLanguageMap(map);
2062
+
1980
2063
  }
1981
2064
 
1982
2065
  this.UpdateAC();
@@ -22,6 +22,7 @@
22
22
  import type { ICellAddress } from 'treb-base-types';
23
23
  import type { TREBDocument } from './types';
24
24
  import type { ChartRenderer } from 'treb-charts';
25
+ // import type { TREBPlugin } from './plugin';
25
26
 
26
27
  /**
27
28
  * factory type for chart renderer, if you want instances (pass a constructor)
@@ -327,6 +328,23 @@ export interface EmbeddedSpreadsheetOptions {
327
328
  */
328
329
  spill?: boolean;
329
330
 
331
+ /**
332
+ * language. at the moment this controls spreadsheet function names
333
+ * only; the plan is to expand to the rest of the interface over time.
334
+ * should be an ISO 639-1 language code, like "en", "fr" or "sv" (case
335
+ * insensitive). we only support a limited subset of languages at the
336
+ * moment.
337
+ *
338
+ * leave blank or set to "locale" to use the current locale.
339
+ */
340
+ language?: string;
341
+
342
+ /* *
343
+ * @internal
344
+ * testing plugins
345
+ */
346
+ // plugins?: TREBPlugin[];
347
+
330
348
  }
331
349
 
332
350
  /**
@@ -0,0 +1,31 @@
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
+ import type { EmbeddedSpreadsheet } from './embedded-spreadsheet';
23
+
24
+ /**
25
+ * @internal
26
+ *
27
+ * testing plugins
28
+ */
29
+ export interface TREBPlugin {
30
+ Attach: (instance: EmbeddedSpreadsheet) => void;
31
+ }
@@ -151,6 +151,8 @@ export class AutocompleteMatcher {
151
151
  return {};
152
152
  }
153
153
 
154
+ // console.info(data);
155
+
154
156
  let match;
155
157
  let result: AutocompleteExecResult = {};
156
158
 
@@ -162,7 +164,8 @@ export class AutocompleteMatcher {
162
164
  // if it's a token, and ends with a legal character
163
165
  // UPDATE: adding the negative leading \d to fix entering complex numbers
164
166
 
165
- match = data.text.match(/(?:^|[^A-Za-z_\d])([A-Za-z_][\w\d_.]*)\s*$/);
167
+ // match = data.text.match(/(?:^|[^A-Za-z_\d])([A-Za-z_][\w\d_.]*)\s*$/);
168
+ match = data.text.match(/(?:^|[^a-zA-Z\u00C0-\u024F_\d])([a-zA-Z\u00C0-\u024F_][\w\d\u00C0-\u024F_.]*)\s*$/);
166
169
 
167
170
  if (match) {
168
171
  const token = match[1];
@@ -178,6 +181,9 @@ export class AutocompleteMatcher {
178
181
  };
179
182
 
180
183
  }
184
+ else {
185
+ // console.info("NOP");
186
+ }
181
187
 
182
188
  }
183
189
 
@@ -261,6 +267,7 @@ export class AutocompleteMatcher {
261
267
  if ( (char >= 0x61 && char <= 0x7a) // a-z
262
268
  || (char >= 0x41 && char <= 0x5a) // A-Z
263
269
  || (char >= 0x30 && char <= 0x39) // 0-9
270
+ || (char >= 0x00C0 && char <= 0x024F) // accented characters
264
271
  || (char === 0x5f) // _
265
272
  || (char === 0x2e)) { // .
266
273
 
@@ -6385,6 +6385,14 @@ export class Grid extends GridBase {
6385
6385
  if ((area.start.row === Infinity || area.start.row < this.active_sheet.rows) &&
6386
6386
  (area.start.column === Infinity || area.start.column < this.active_sheet.columns)) {
6387
6387
 
6388
+ if (area.start.spill && area.end.row === area.start.row && area.end.column === area.start.column) {
6389
+ const sheet = this.model.sheets.Find(area.start.sheet_id || -1);
6390
+ const cell = sheet?.CellData(area.start);
6391
+ if (cell?.spill && cell.spill.start.row === area.start.row && cell.spill.start.column === area.start.column) {
6392
+ area.ConsumeArea(cell.spill);
6393
+ }
6394
+ }
6395
+
6388
6396
  area = this.active_sheet.RealArea(area);
6389
6397
  const label = area.spreadsheet_label;
6390
6398