@trebco/treb 23.6.2 → 25.0.0-rc1

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.
Files changed (217) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +164 -0
  3. package/README-shadow-DOM.md +88 -0
  4. package/README.md +37 -130
  5. package/api-config.json +29 -0
  6. package/api-generator/api-generator-types.ts +82 -0
  7. package/api-generator/api-generator.ts +1172 -0
  8. package/api-generator/package.json +3 -0
  9. package/build/treb-spreadsheet.mjs +14 -0
  10. package/{treb.d.ts → build/treb.d.ts} +293 -299
  11. package/esbuild-custom-element.mjs +336 -0
  12. package/esbuild.js +305 -0
  13. package/package.json +43 -14
  14. package/treb-base-types/package.json +5 -0
  15. package/treb-base-types/src/api_types.ts +36 -0
  16. package/treb-base-types/src/area.ts +583 -0
  17. package/treb-base-types/src/basic_types.ts +45 -0
  18. package/treb-base-types/src/cell.ts +612 -0
  19. package/treb-base-types/src/cells.ts +1066 -0
  20. package/treb-base-types/src/color.ts +124 -0
  21. package/treb-base-types/src/import.ts +71 -0
  22. package/treb-base-types/src/index-standalone.ts +29 -0
  23. package/treb-base-types/src/index.ts +42 -0
  24. package/treb-base-types/src/layout.ts +47 -0
  25. package/treb-base-types/src/localization.ts +187 -0
  26. package/treb-base-types/src/rectangle.ts +145 -0
  27. package/treb-base-types/src/render_text.ts +72 -0
  28. package/treb-base-types/src/style.ts +545 -0
  29. package/treb-base-types/src/table.ts +109 -0
  30. package/treb-base-types/src/text_part.ts +54 -0
  31. package/treb-base-types/src/theme.ts +608 -0
  32. package/treb-base-types/src/union.ts +152 -0
  33. package/treb-base-types/src/value-type.ts +164 -0
  34. package/treb-base-types/style/resizable.css +59 -0
  35. package/treb-calculator/modern.tsconfig.json +11 -0
  36. package/treb-calculator/package.json +5 -0
  37. package/treb-calculator/src/calculator.ts +2546 -0
  38. package/treb-calculator/src/complex-math.ts +558 -0
  39. package/treb-calculator/src/dag/array-vertex.ts +198 -0
  40. package/treb-calculator/src/dag/graph.ts +951 -0
  41. package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
  42. package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
  43. package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
  44. package/treb-calculator/src/dag/vertex.ts +352 -0
  45. package/treb-calculator/src/descriptors.ts +162 -0
  46. package/treb-calculator/src/expression-calculator.ts +1069 -0
  47. package/treb-calculator/src/function-error.ts +103 -0
  48. package/treb-calculator/src/function-library.ts +103 -0
  49. package/treb-calculator/src/functions/base-functions.ts +1214 -0
  50. package/treb-calculator/src/functions/checkbox.ts +164 -0
  51. package/treb-calculator/src/functions/complex-functions.ts +253 -0
  52. package/treb-calculator/src/functions/finance-functions.ts +399 -0
  53. package/treb-calculator/src/functions/information-functions.ts +102 -0
  54. package/treb-calculator/src/functions/matrix-functions.ts +182 -0
  55. package/treb-calculator/src/functions/sparkline.ts +335 -0
  56. package/treb-calculator/src/functions/statistics-functions.ts +350 -0
  57. package/treb-calculator/src/functions/text-functions.ts +298 -0
  58. package/treb-calculator/src/index.ts +27 -0
  59. package/treb-calculator/src/notifier-types.ts +59 -0
  60. package/treb-calculator/src/primitives.ts +428 -0
  61. package/treb-calculator/src/utilities.ts +305 -0
  62. package/treb-charts/package.json +5 -0
  63. package/treb-charts/src/chart-functions.ts +156 -0
  64. package/treb-charts/src/chart-types.ts +230 -0
  65. package/treb-charts/src/chart.ts +1288 -0
  66. package/treb-charts/src/index.ts +24 -0
  67. package/treb-charts/src/main.ts +37 -0
  68. package/treb-charts/src/rectangle.ts +52 -0
  69. package/treb-charts/src/renderer.ts +1841 -0
  70. package/treb-charts/src/util.ts +122 -0
  71. package/treb-charts/style/charts.scss +221 -0
  72. package/treb-charts/style/old-charts.scss +250 -0
  73. package/treb-embed/markup/layout.html +137 -0
  74. package/treb-embed/markup/toolbar.html +175 -0
  75. package/treb-embed/modern.tsconfig.json +25 -0
  76. package/treb-embed/src/custom-element/content-types.d.ts +18 -0
  77. package/treb-embed/src/custom-element/global.d.ts +11 -0
  78. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1227 -0
  79. package/treb-embed/src/custom-element/treb-global.ts +44 -0
  80. package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
  81. package/treb-embed/src/embedded-spreadsheet.ts +5362 -0
  82. package/treb-embed/src/index.ts +16 -0
  83. package/treb-embed/src/language-model.ts +41 -0
  84. package/treb-embed/src/options.ts +320 -0
  85. package/treb-embed/src/progress-dialog.ts +228 -0
  86. package/treb-embed/src/selection-state.ts +16 -0
  87. package/treb-embed/src/spinner.ts +42 -0
  88. package/treb-embed/src/toolbar-message.ts +96 -0
  89. package/treb-embed/src/types.ts +167 -0
  90. package/treb-embed/style/autocomplete.scss +103 -0
  91. package/treb-embed/style/dark-theme.scss +114 -0
  92. package/treb-embed/style/defaults.scss +36 -0
  93. package/treb-embed/style/dialog.scss +181 -0
  94. package/treb-embed/style/dropdown-select.scss +101 -0
  95. package/treb-embed/style/formula-bar.scss +193 -0
  96. package/treb-embed/style/grid.scss +374 -0
  97. package/treb-embed/style/layout.scss +424 -0
  98. package/treb-embed/style/mouse-mask.scss +67 -0
  99. package/treb-embed/style/note.scss +92 -0
  100. package/treb-embed/style/overlay-editor.scss +102 -0
  101. package/treb-embed/style/spinner.scss +92 -0
  102. package/treb-embed/style/tab-bar.scss +228 -0
  103. package/treb-embed/style/table.scss +80 -0
  104. package/treb-embed/style/theme-defaults.scss +444 -0
  105. package/treb-embed/style/toolbar.scss +416 -0
  106. package/treb-embed/style/tooltip.scss +68 -0
  107. package/treb-embed/style/treb-icons.scss +130 -0
  108. package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
  109. package/treb-embed/style/z-index.scss +43 -0
  110. package/treb-export/docs/charts.md +68 -0
  111. package/treb-export/modern.tsconfig.json +19 -0
  112. package/treb-export/package.json +4 -0
  113. package/treb-export/src/address-type.ts +77 -0
  114. package/treb-export/src/base-template.ts +22 -0
  115. package/treb-export/src/column-width.ts +85 -0
  116. package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
  117. package/treb-export/src/drawing2/chart2.ts +282 -0
  118. package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
  119. package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
  120. package/treb-export/src/drawing2/drawing2.ts +355 -0
  121. package/treb-export/src/drawing2/embedded-image.ts +71 -0
  122. package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
  123. package/treb-export/src/export-worker/export-worker.ts +99 -0
  124. package/treb-export/src/export-worker/index-modern.ts +22 -0
  125. package/treb-export/src/export2.ts +2204 -0
  126. package/treb-export/src/import2.ts +882 -0
  127. package/treb-export/src/relationship.ts +36 -0
  128. package/treb-export/src/shared-strings2.ts +128 -0
  129. package/treb-export/src/template-2.ts +22 -0
  130. package/treb-export/src/unescape_xml.ts +47 -0
  131. package/treb-export/src/workbook-sheet2.ts +182 -0
  132. package/treb-export/src/workbook-style2.ts +1285 -0
  133. package/treb-export/src/workbook-theme2.ts +88 -0
  134. package/treb-export/src/workbook2.ts +491 -0
  135. package/treb-export/src/xml-utils.ts +201 -0
  136. package/treb-export/template/base/[Content_Types].xml +2 -0
  137. package/treb-export/template/base/_rels/.rels +2 -0
  138. package/treb-export/template/base/docProps/app.xml +2 -0
  139. package/treb-export/template/base/docProps/core.xml +12 -0
  140. package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
  141. package/treb-export/template/base/xl/sharedStrings.xml +2 -0
  142. package/treb-export/template/base/xl/styles.xml +2 -0
  143. package/treb-export/template/base/xl/theme/theme1.xml +2 -0
  144. package/treb-export/template/base/xl/workbook.xml +2 -0
  145. package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
  146. package/treb-export/template/base.xlsx +0 -0
  147. package/treb-format/package.json +8 -0
  148. package/treb-format/src/format.test.ts +213 -0
  149. package/treb-format/src/format.ts +942 -0
  150. package/treb-format/src/format_cache.ts +199 -0
  151. package/treb-format/src/format_parser.ts +723 -0
  152. package/treb-format/src/index.ts +25 -0
  153. package/treb-format/src/number_format_section.ts +100 -0
  154. package/treb-format/src/value_parser.ts +337 -0
  155. package/treb-grid/package.json +5 -0
  156. package/treb-grid/src/editors/autocomplete.ts +394 -0
  157. package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
  158. package/treb-grid/src/editors/formula_bar.ts +473 -0
  159. package/treb-grid/src/editors/formula_editor_base.ts +910 -0
  160. package/treb-grid/src/editors/overlay_editor.ts +511 -0
  161. package/treb-grid/src/index.ts +37 -0
  162. package/treb-grid/src/layout/base_layout.ts +2618 -0
  163. package/treb-grid/src/layout/grid_layout.ts +299 -0
  164. package/treb-grid/src/layout/rectangle_cache.ts +86 -0
  165. package/treb-grid/src/render/selection-renderer.ts +414 -0
  166. package/treb-grid/src/render/svg_header_overlay.ts +93 -0
  167. package/treb-grid/src/render/svg_selection_block.ts +187 -0
  168. package/treb-grid/src/render/tile_renderer.ts +2122 -0
  169. package/treb-grid/src/types/annotation.ts +216 -0
  170. package/treb-grid/src/types/border_constants.ts +34 -0
  171. package/treb-grid/src/types/clipboard_data.ts +31 -0
  172. package/treb-grid/src/types/data_model.ts +334 -0
  173. package/treb-grid/src/types/drag_mask.ts +81 -0
  174. package/treb-grid/src/types/grid.ts +7743 -0
  175. package/treb-grid/src/types/grid_base.ts +3644 -0
  176. package/treb-grid/src/types/grid_command.ts +470 -0
  177. package/treb-grid/src/types/grid_events.ts +124 -0
  178. package/treb-grid/src/types/grid_options.ts +97 -0
  179. package/treb-grid/src/types/grid_selection.ts +60 -0
  180. package/treb-grid/src/types/named_range.ts +369 -0
  181. package/treb-grid/src/types/scale-control.ts +202 -0
  182. package/treb-grid/src/types/serialize_options.ts +72 -0
  183. package/treb-grid/src/types/set_range_options.ts +52 -0
  184. package/treb-grid/src/types/sheet.ts +3099 -0
  185. package/treb-grid/src/types/sheet_types.ts +95 -0
  186. package/treb-grid/src/types/tab_bar.ts +464 -0
  187. package/treb-grid/src/types/tile.ts +59 -0
  188. package/treb-grid/src/types/update_flags.ts +75 -0
  189. package/treb-grid/src/util/dom_utilities.ts +44 -0
  190. package/treb-grid/src/util/fontmetrics2.ts +179 -0
  191. package/treb-grid/src/util/ua.ts +104 -0
  192. package/treb-logo.svg +18 -0
  193. package/treb-parser/package.json +5 -0
  194. package/treb-parser/src/csv-parser.ts +122 -0
  195. package/treb-parser/src/index.ts +25 -0
  196. package/treb-parser/src/md-parser.ts +526 -0
  197. package/treb-parser/src/parser-types.ts +397 -0
  198. package/treb-parser/src/parser.test.ts +298 -0
  199. package/treb-parser/src/parser.ts +2673 -0
  200. package/treb-utils/package.json +5 -0
  201. package/treb-utils/src/dispatch.ts +57 -0
  202. package/treb-utils/src/event_source.ts +147 -0
  203. package/treb-utils/src/ievent_source.ts +33 -0
  204. package/treb-utils/src/index.ts +31 -0
  205. package/treb-utils/src/measurement.ts +174 -0
  206. package/treb-utils/src/resizable.ts +160 -0
  207. package/treb-utils/src/scale.ts +137 -0
  208. package/treb-utils/src/serialize_html.ts +124 -0
  209. package/treb-utils/src/template.ts +70 -0
  210. package/treb-utils/src/validate_uri.ts +61 -0
  211. package/tsconfig.json +10 -0
  212. package/tsproject.json +30 -0
  213. package/util/license-plugin-esbuild.js +86 -0
  214. package/util/list-css-vars.sh +46 -0
  215. package/README-esm.md +0 -37
  216. package/treb-bundle.css +0 -2
  217. package/treb-bundle.mjs +0 -15
@@ -0,0 +1,394 @@
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-2023 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import { DOMUtilities } from '../util/dom_utilities';
23
+ import type { Theme, Rectangle } from 'treb-base-types';
24
+ import type { AutocompleteExecResult, DescriptorType } from './autocomplete_matcher';
25
+
26
+ export interface AutocompleteResult {
27
+ handled: boolean;
28
+ accept?: boolean;
29
+ data?: AutocompleteExecResult;
30
+ value?: string;
31
+ click?: boolean;
32
+ type?: DescriptorType;
33
+ }
34
+
35
+ /*
36
+ export interface AutocompleteData {
37
+ completions?: string[];
38
+ tooltip?: string;
39
+ arguments?: string;
40
+ }
41
+ */
42
+
43
+ export type AcceptCallback = (result: AutocompleteResult) => void;
44
+
45
+ export interface AutocompleteOptions {
46
+ autocomplete_prefer_top?: boolean;
47
+ tooltip_prefer_top?: boolean;
48
+ theme?: Theme;
49
+ container?: HTMLElement;
50
+ }
51
+
52
+ export class Autocomplete {
53
+
54
+ public completion_list_visible = false;
55
+ public tooltip_visible = false;
56
+ public last_completion?: string;
57
+
58
+ private completion_list: HTMLDivElement;
59
+ private tooltip: HTMLDivElement;
60
+
61
+ private selected_index = 0;
62
+ private block = false;
63
+ private autocomplete_data: AutocompleteExecResult = {};
64
+
65
+ private callback?: AcceptCallback;
66
+
67
+ // ALTHOUGH we are dropping scope from _this_ node, it might be
68
+ // useful to add it to the containing node... something to think
69
+ // about
70
+
71
+ // private scope: string;
72
+
73
+ private active_element?: HTMLElement;
74
+
75
+ constructor(private options: AutocompleteOptions = {}){
76
+
77
+ // this.scope = 'AC' + Math.round(Math.random() * Math.pow(10, 10)).toString(16);
78
+
79
+ this.completion_list = DOMUtilities.CreateDiv(
80
+ 'treb-cell-editor-ac-list treb-autocomplete',
81
+ options.container || document.body,
82
+ ); // this.scope);
83
+
84
+ this.completion_list.addEventListener('mousedown', (event) => this.ListMouseDown(event));
85
+
86
+ // FIXME: should we add/remove listener based on visibility? (...)
87
+
88
+ this.completion_list.addEventListener('mousemove', (event) => this.ListMouseMove(event));
89
+
90
+ this.tooltip = DOMUtilities.CreateDiv('treb-cell-editor-ac-tooltip treb-autocomplete-tooltip',
91
+ options.container || document.body,
92
+ ); // this.scope);
93
+
94
+ // this.UpdateTheme();
95
+
96
+ }
97
+
98
+ /*
99
+ public UpdateTheme(): void {
100
+ if (this.options.theme) {
101
+
102
+ // FIXME: no longer sharing, don't need scoped styling anymore
103
+
104
+ // FIXME: split?
105
+
106
+ this.completion_list.style.fontFamily =
107
+ this.tooltip.style.fontFamily = this.options.theme.cell_font ?
108
+ this.options.theme.cell_font : '';
109
+
110
+ const font_size = (this.options.theme.cell_font_size_value || 0) +
111
+ (this.options.theme.cell_font_size_unit || 'pt');
112
+
113
+ / *
114
+ let font_size: string|null = null;
115
+ if (typeof this.options.theme.cell_font_size === 'string') {
116
+ font_size = this.options.theme.cell_font_size;
117
+ }
118
+ else if (typeof this.options.theme.cell_font_size === 'number') {
119
+ font_size = this.options.theme.cell_font_size + 'pt';
120
+ }
121
+ * /
122
+
123
+ this.completion_list.style.fontSize =
124
+ this.tooltip.style.fontSize =
125
+ font_size || '';
126
+
127
+ / *
128
+ this.stylesheet.textContent = `
129
+
130
+ .treb-ac-list[${this.scope}] {
131
+ background: ${this.options.theme.autocomplete_background};
132
+ }
133
+
134
+ .treb-ac-list[${this.scope}] ul li {
135
+ color: ${this.options.theme.autocomplete_color};
136
+ }
137
+
138
+ .treb-ac-list[${this.scope}] ul li a:hover, .treb-ac-list[${this.scope}] ul li a.selected,
139
+ .treb-ac-list[${this.scope}] ul:hover li a.selected:hover {
140
+ color: ${this.options.theme.autocomplete_highlight_color};
141
+ background: ${this.options.theme.autocomplete_highlight_background};
142
+ }
143
+
144
+ `.replace(/\s+/g, ' ').trim();
145
+ * /
146
+
147
+ this.stylesheet.textContent = css`
148
+
149
+ .treb-ac-list[${this.scope}] {
150
+ background: ${this.options.theme.autocomplete_background};
151
+ }
152
+
153
+ .treb-ac-list[${this.scope}] > ul > li {
154
+ color: ${this.options.theme.autocomplete_color};
155
+ }
156
+
157
+ .treb-ac-list[${this.scope}] > ul > li > a.selected {
158
+ color: ${this.options.theme.autocomplete_highlight_color};
159
+ background: ${this.options.theme.autocomplete_highlight_background};
160
+ }
161
+
162
+ `; // this is not needed because the template compressor will remove whitespace // .replace(/\s+/g, ' ').trim();
163
+
164
+ }
165
+ }
166
+ */
167
+
168
+ public Hide(): void {
169
+ this.tooltip.style.top = '-1000px';
170
+ this.completion_list.style.top = '-1000px';
171
+ this.completion_list_visible = false;
172
+ this.tooltip_visible = false;
173
+ this.active_element = undefined;
174
+ }
175
+
176
+ public ResetBlock(): void {
177
+ this.block = false;
178
+ }
179
+
180
+ public ListMouseMove(event: MouseEvent): void {
181
+
182
+ const target = event.target as HTMLElement;
183
+ if (target.tagName === 'A') {
184
+ if (target !== this.active_element) {
185
+ if (this.active_element) {
186
+ this.active_element.classList.remove('selected');
187
+ this.active_element = target;
188
+ this.active_element.classList.add('selected');
189
+ this.selected_index = Number(target.dataset.index || '0');
190
+ this.last_completion = target.textContent || '';
191
+ }
192
+ }
193
+ }
194
+
195
+ }
196
+
197
+ public ListMouseDown(event: MouseEvent): void {
198
+
199
+ event.stopPropagation();
200
+ event.preventDefault();
201
+
202
+ let target: HTMLElement|null = event.target as HTMLElement;
203
+ while (target){
204
+ if (target === this.completion_list) return;
205
+ if (target.tagName === 'A') break;
206
+ target = target.parentElement;
207
+ }
208
+ if (!target) return;
209
+ console.info(target);
210
+
211
+ if (this.callback) {
212
+ this.callback({
213
+ handled: true,
214
+ accept: true,
215
+ value: target.textContent ? target.textContent : undefined,
216
+ data: this.autocomplete_data,
217
+ click: true,
218
+ });
219
+ }
220
+ }
221
+
222
+ public HandleKey(event_type: 'keydown'|'keyup', event: KeyboardEvent): AutocompleteResult {
223
+
224
+ if (!this.completion_list_visible) return { handled: false };
225
+
226
+ let delta = 0;
227
+ let block = false;
228
+ let accept = false;
229
+
230
+ switch (event.key){
231
+ case 'Up':
232
+ case 'ArrowUp':
233
+ delta = -1;
234
+ break;
235
+ case 'Down':
236
+ case 'ArrowDown':
237
+ delta = 1;
238
+ break;
239
+ case 'Tab':
240
+ accept = true;
241
+ break;
242
+ case 'Escape':
243
+ case 'Esc':
244
+ block = true;
245
+ break;
246
+ default:
247
+ return { handled: false };
248
+ }
249
+
250
+ event.stopPropagation();
251
+ event.preventDefault();
252
+
253
+ // keyup just consume
254
+
255
+ if (event_type === 'keyup') return { handled: true };
256
+
257
+ // keydown handle
258
+
259
+ if (delta){
260
+ const list_rect = this.completion_list.getBoundingClientRect();
261
+ this.selected_index += delta;
262
+ this.selected_index = Math.max(0, this.selected_index);
263
+ const children = this.completion_list.querySelectorAll('a');
264
+ this.selected_index = Math.min(this.selected_index, children.length - 1);
265
+ for (let index = 0; index < children.length; index++){
266
+ const child = children[index];
267
+ if (index === this.selected_index){
268
+ child.classList.add('selected');
269
+ this.active_element = child;
270
+ const child_rect = child.getBoundingClientRect();
271
+ if (child_rect.top < list_rect.top){
272
+ this.completion_list.scrollBy(0, -child_rect.height);
273
+ }
274
+ else if (child_rect.bottom > list_rect.bottom) {
275
+ this.completion_list.scrollBy(0, child_rect.height);
276
+ }
277
+ this.last_completion = child.textContent || undefined;
278
+ }
279
+ else child.classList.remove('selected');
280
+ }
281
+ return { handled: true };
282
+ }
283
+ else if (block){
284
+ this.block = true;
285
+ this.Hide();
286
+ return { handled: true };
287
+ }
288
+ else if (accept){
289
+ return {
290
+ handled: true,
291
+ accept: true,
292
+ value: this.last_completion,
293
+ data: this.autocomplete_data,
294
+ };
295
+ }
296
+
297
+ return { handled: false };
298
+ }
299
+
300
+ public Show(
301
+ callback: AcceptCallback,
302
+ data: AutocompleteExecResult = {},
303
+ position: Rectangle): void {
304
+
305
+ this.completion_list_visible = false;
306
+ this.tooltip_visible = false;
307
+ this.autocomplete_data = data;
308
+ this.callback = callback;
309
+
310
+ if (this.block) return;
311
+
312
+ // handle tooltip first. we may offset AC list based for tooltip.
313
+
314
+ if (data.tooltip){
315
+
316
+ //this.tooltip.textContent
317
+ this.tooltip.innerHTML = data.tooltip + data.arguments +
318
+ (data.description ? '\n' + data.description : '');
319
+
320
+ this.tooltip.style.left = position.left + 'px';
321
+ if (this.options.tooltip_prefer_top){
322
+ this.tooltip.style.top = (position.top - this.tooltip.offsetHeight - 5 ) + 'px';
323
+ }
324
+ else {
325
+ this.tooltip.style.top = (position.bottom + 5 ) + 'px';
326
+ }
327
+ this.tooltip_visible = true;
328
+ }
329
+ else this.tooltip.style.top = '-1000px';
330
+
331
+ // now layout AC list
332
+
333
+ if (data.completions && data.completions.length){
334
+
335
+ // temp we are just hiding tooltip if there's AC, but we could layout
336
+
337
+ this.tooltip.style.top = '-1000px';
338
+
339
+ this.selected_index = 0;
340
+
341
+ this.completion_list.innerHTML = `<ul class="notranslate">`
342
+ + data.completions.map((descriptor, index) => {
343
+ if (descriptor.name === this.last_completion) this.selected_index = index;
344
+ return `<li><a data-index="${index}">${descriptor.name}</a></li>`;
345
+ }).join('\n') + `<ul>`;
346
+
347
+ const height = this.completion_list.offsetHeight;
348
+
349
+ let layout_top = false;
350
+
351
+ if (this.options.autocomplete_prefer_top){
352
+ layout_top = (position.top >= 200);
353
+ }
354
+ else {
355
+
356
+ // compiler thinks this is possibly undefined, but vs code does
357
+ // not -- I thought vs code used the same tsc we use to compile?
358
+
359
+ if (document.documentElement) {
360
+ const viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
361
+ if (viewport_height - position.bottom < 200 ){
362
+ layout_top = true;
363
+ }
364
+ }
365
+
366
+ }
367
+
368
+ if (layout_top) {
369
+ this.completion_list.style.top = (position.top - height - 5) + 'px';
370
+ }
371
+ else {
372
+ this.completion_list.style.top = (position.bottom + 5) + 'px';
373
+ }
374
+
375
+ this.completion_list.style.left = position.left + 'px';
376
+
377
+ const children = this.completion_list.querySelectorAll('a');
378
+ this.active_element = children[this.selected_index];
379
+ children[this.selected_index].classList.add('selected');
380
+ this.last_completion = children[this.selected_index].textContent || undefined;
381
+
382
+ // scroll into view
383
+ this.completion_list.scrollTop = this.active_element.offsetTop;
384
+
385
+ this.completion_list_visible = true;
386
+ }
387
+ else this.completion_list.style.top = '-1000px';
388
+
389
+
390
+ }
391
+
392
+
393
+ }
394
+
@@ -0,0 +1,260 @@
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-2023 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ /**
23
+ * this is the data side of autocomplete (maintaining the list, matching).
24
+ * we add this to grid because grid controls the editors; clients can pass
25
+ * in lists.
26
+ *
27
+ * TODO: structure
28
+ * TODO: other symbols... [FIXME: defined names need to go in here]
29
+ * TODO: context -- cell vs annotation (...)
30
+ *
31
+ * FIXME: why does this use different definitions than the functions?
32
+ * can't we merge the two?
33
+ *
34
+ * [I think they may have been developed independently and them converged...]
35
+ *
36
+ */
37
+
38
+ import { Localization } from 'treb-base-types';
39
+
40
+ export interface ArgumentDescriptor {
41
+ name?: string;
42
+ }
43
+
44
+ export enum DescriptorType {
45
+ Function, Token
46
+ }
47
+
48
+ export interface FunctionDescriptor {
49
+ name: string;
50
+ description?: string;
51
+ arguments?: ArgumentDescriptor[];
52
+ type?: DescriptorType;
53
+ }
54
+
55
+ export interface AutocompleteMatchData {
56
+ text: string;
57
+ cursor: number;
58
+ }
59
+
60
+ export interface AutocompleteExecResult {
61
+ // completions?: string[];
62
+ completions?: FunctionDescriptor[];
63
+ token?: string;
64
+ position?: number;
65
+ tooltip?: string;
66
+ arguments?: string;
67
+ description?: string;
68
+ function_position?: number;
69
+ }
70
+
71
+ export interface TooltipParserResult {
72
+ function: string|undefined;
73
+ argument: number;
74
+ position: number;
75
+ }
76
+
77
+ export class AutocompleteMatcher {
78
+
79
+ private function_names: string[] = [];
80
+
81
+ //private function_map: {[index: string]: FunctionDescriptor} = {};
82
+
83
+ private argument_separator = Localization.argument_separator.charCodeAt(0);
84
+
85
+ /**
86
+ * making this public (and scrubbing the type). we need it public so we
87
+ * can check collisions. I'm not sure why it was originally private...
88
+ */
89
+ public function_map: Record<string, FunctionDescriptor> = {};
90
+
91
+ public RemoveFunctions(functions: FunctionDescriptor|FunctionDescriptor[]): void {
92
+ if (!Array.isArray(functions)) { functions = [functions]; }
93
+ let list = Object.keys(this.function_map).map((key) => this.function_map[key]);
94
+ for (const func of functions) {
95
+ list = list.filter(test => test.name !== func.name);
96
+ }
97
+ this.SetFunctions(list);
98
+ }
99
+
100
+ public AddFunctions(functions: FunctionDescriptor|FunctionDescriptor[]): void {
101
+ if (!Array.isArray(functions)) { functions = [functions]; }
102
+ const list = Object.keys(this.function_map).map((key) => this.function_map[key]).concat(...functions);
103
+ this.SetFunctions(list);
104
+ }
105
+
106
+ public SetFunctions(functions: FunctionDescriptor[]): void {
107
+ this.function_map = {};
108
+ this.function_names = functions.map((fn) => {
109
+ this.function_map[fn.name.toLowerCase()] = fn;
110
+ return fn.name;
111
+ }).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
112
+ }
113
+
114
+ public NormalizeIdentifier(name: string): string|undefined {
115
+ const identifier = this.function_map[name.toLowerCase()];
116
+ return identifier ? identifier.name : undefined;
117
+ }
118
+
119
+ public Exec(data: AutocompleteMatchData): AutocompleteExecResult {
120
+
121
+ // ac/tt only for formula
122
+ if (data.text[0] !== '=') {
123
+ return {};
124
+ }
125
+
126
+ let match;
127
+ let result: AutocompleteExecResult = {};
128
+
129
+ // ac only at the end of the string
130
+ if (data.cursor === data.text.length) {
131
+
132
+ // FIXME: quoted strings...
133
+
134
+ // if it's a token, and ends with a legal character
135
+ // UPDATE: adding the negative leading \d to fix entering complex numbers
136
+
137
+ match = data.text.match(/(?:^|[^A-Za-z_\d])([A-Za-z_][\w\d_.]*)\s*$/);
138
+
139
+ if (match) {
140
+ const token = match[1];
141
+ const rex = new RegExp('^' + token.replace('.', '\\.'), 'i');
142
+ const list = this.function_names.filter((name) => rex.test(name)).map((name) => this.function_map[name.toLowerCase()]);
143
+
144
+ result = {
145
+ completions: list,
146
+ token,
147
+ position: data.cursor - token.length,
148
+ };
149
+
150
+ }
151
+
152
+ }
153
+
154
+ const parsed = this.ParseTooltip(data.text.substr(0, data.cursor));
155
+
156
+ if (parsed.function) {
157
+ const func = this.function_map[parsed.function.toLowerCase()];
158
+ if (func) {
159
+ // if (func.canonical_name) result.tooltip = func.canonical_name;
160
+ // else result.tooltip = tt.toUpperCase();
161
+
162
+ result.tooltip = '<span class="notranslate">' + func.name + '</span>';
163
+ result.arguments = '(' + (func.arguments || []).map((desc, index) => {
164
+ const argument = desc.name || 'argument';
165
+ return (index === parsed.argument) ? `<span class="active-argument">${argument}</span>` : argument;
166
+ }).join(Localization.argument_separator + ' ') + ')';
167
+ result.description = func.description ? `<span class="function-description">${func.description}</span>` : '';
168
+ result.function_position = parsed.position || 0;
169
+
170
+ }
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ /**
177
+ * baby parser for generating tooltips. we want the name of the
178
+ * current function, and the index of the current argument.
179
+ *
180
+ * not handled: escaped quotes (not even sure what the syntax for that is)
181
+ */
182
+ public ParseTooltip(expression: string): TooltipParserResult {
183
+
184
+ // these two things are actually unrelated, we just need to push/pop them at the same time
185
+ const stack: Array<{
186
+ buffer: string;
187
+ position: number;
188
+ argument: number; }> = [];
189
+
190
+ let argument = 0;
191
+ let buffer = '';
192
+
193
+ // state flag
194
+ let quote = false;
195
+
196
+ let position = 0;
197
+ for (const letter of expression) {
198
+
199
+ const char = letter.charCodeAt(0);
200
+ if (quote) {
201
+ if (char === 0x22) { quote = false; }
202
+ }
203
+ else {
204
+ switch (char) {
205
+ case 0x28: // OPEN_PAREN:
206
+ stack.push({
207
+ buffer: buffer.trim(), // there is no case where spaces get in this buffer
208
+ argument,
209
+ position: position - buffer.length,
210
+ });
211
+ buffer = '';
212
+ argument = 0;
213
+ break;
214
+
215
+ case this.argument_separator:
216
+ argument++;
217
+ break;
218
+
219
+ case 0x29: // CLOSE_PAREN:
220
+ argument = stack.pop()?.argument || 0;
221
+ break;
222
+
223
+ case 0x22: // QUOTE:
224
+ buffer = '';
225
+ quote = true;
226
+ break;
227
+
228
+ default:
229
+
230
+ // these are legal symbol characters
231
+ if ( (char >= 0x61 && char <= 0x7a) // a-z
232
+ || (char >= 0x41 && char <= 0x5a) // A-Z
233
+ || (char >= 0x30 && char <= 0x39) // 0-9
234
+ || (char === 0x5f) // _
235
+ || (char === 0x2e)) { // .
236
+
237
+ buffer += letter;
238
+ }
239
+ else {
240
+ buffer = '';
241
+ }
242
+
243
+ }
244
+ }
245
+
246
+ position++;
247
+
248
+ }
249
+
250
+ const last_func = stack.pop();
251
+
252
+ return {
253
+ function: last_func?.buffer || undefined,
254
+ position: last_func?.position || 0,
255
+ argument,
256
+ };
257
+
258
+ }
259
+
260
+ }