@trebco/treb 23.6.5 → 25.0.0-rc2
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/.eslintignore +8 -0
- package/.eslintrc.js +164 -0
- package/README-shadow-DOM.md +88 -0
- package/README.md +37 -130
- package/api-config.json +29 -0
- package/api-generator/api-generator-types.ts +82 -0
- package/api-generator/api-generator.ts +1172 -0
- package/api-generator/package.json +3 -0
- package/build/treb-spreadsheet.mjs +14 -0
- package/{treb.d.ts → build/treb.d.ts} +323 -271
- package/esbuild-custom-element.mjs +336 -0
- package/esbuild.js +305 -0
- package/package.json +49 -14
- package/treb-base-types/package.json +5 -0
- package/treb-base-types/src/api_types.ts +36 -0
- package/treb-base-types/src/area.ts +583 -0
- package/treb-base-types/src/basic_types.ts +45 -0
- package/treb-base-types/src/cell.ts +612 -0
- package/treb-base-types/src/cells.ts +1066 -0
- package/treb-base-types/src/color.ts +124 -0
- package/treb-base-types/src/import.ts +71 -0
- package/treb-base-types/src/index-standalone.ts +29 -0
- package/treb-base-types/src/index.ts +42 -0
- package/treb-base-types/src/layout.ts +47 -0
- package/treb-base-types/src/localization.ts +187 -0
- package/treb-base-types/src/rectangle.ts +145 -0
- package/treb-base-types/src/render_text.ts +72 -0
- package/treb-base-types/src/style.ts +545 -0
- package/treb-base-types/src/table.ts +109 -0
- package/treb-base-types/src/text_part.ts +54 -0
- package/treb-base-types/src/theme.ts +608 -0
- package/treb-base-types/src/union.ts +152 -0
- package/treb-base-types/src/value-type.ts +164 -0
- package/treb-base-types/style/resizable.css +59 -0
- package/treb-calculator/modern.tsconfig.json +11 -0
- package/treb-calculator/package.json +5 -0
- package/treb-calculator/src/calculator.ts +2546 -0
- package/treb-calculator/src/complex-math.ts +558 -0
- package/treb-calculator/src/dag/array-vertex.ts +198 -0
- package/treb-calculator/src/dag/graph.ts +951 -0
- package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
- package/treb-calculator/src/dag/vertex.ts +352 -0
- package/treb-calculator/src/descriptors.ts +162 -0
- package/treb-calculator/src/expression-calculator.ts +1069 -0
- package/treb-calculator/src/function-error.ts +103 -0
- package/treb-calculator/src/function-library.ts +103 -0
- package/treb-calculator/src/functions/base-functions.ts +1214 -0
- package/treb-calculator/src/functions/checkbox.ts +164 -0
- package/treb-calculator/src/functions/complex-functions.ts +253 -0
- package/treb-calculator/src/functions/finance-functions.ts +399 -0
- package/treb-calculator/src/functions/information-functions.ts +102 -0
- package/treb-calculator/src/functions/matrix-functions.ts +182 -0
- package/treb-calculator/src/functions/sparkline.ts +335 -0
- package/treb-calculator/src/functions/statistics-functions.ts +350 -0
- package/treb-calculator/src/functions/text-functions.ts +298 -0
- package/treb-calculator/src/index.ts +27 -0
- package/treb-calculator/src/notifier-types.ts +59 -0
- package/treb-calculator/src/primitives.ts +428 -0
- package/treb-calculator/src/utilities.ts +305 -0
- package/treb-charts/package.json +5 -0
- package/treb-charts/src/chart-functions.ts +156 -0
- package/treb-charts/src/chart-types.ts +230 -0
- package/treb-charts/src/chart.ts +1288 -0
- package/treb-charts/src/index.ts +24 -0
- package/treb-charts/src/main.ts +37 -0
- package/treb-charts/src/rectangle.ts +52 -0
- package/treb-charts/src/renderer.ts +1841 -0
- package/treb-charts/src/util.ts +122 -0
- package/treb-charts/style/charts.scss +221 -0
- package/treb-charts/style/old-charts.scss +250 -0
- package/treb-embed/markup/layout.html +137 -0
- package/treb-embed/markup/toolbar.html +175 -0
- package/treb-embed/modern.tsconfig.json +25 -0
- package/treb-embed/src/custom-element/content-types.d.ts +18 -0
- package/treb-embed/src/custom-element/global.d.ts +11 -0
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1228 -0
- package/treb-embed/src/custom-element/treb-global.ts +44 -0
- package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
- package/treb-embed/src/embedded-spreadsheet.ts +5358 -0
- package/treb-embed/src/index.ts +16 -0
- package/treb-embed/src/language-model.ts +41 -0
- package/treb-embed/src/options.ts +298 -0
- package/treb-embed/src/progress-dialog.ts +228 -0
- package/treb-embed/src/selection-state.ts +16 -0
- package/treb-embed/src/spinner.ts +42 -0
- package/treb-embed/src/toolbar-message.ts +96 -0
- package/treb-embed/src/types.ts +167 -0
- package/treb-embed/style/autocomplete.scss +103 -0
- package/treb-embed/style/dark-theme.scss +114 -0
- package/treb-embed/style/defaults.scss +36 -0
- package/treb-embed/style/dialog.scss +181 -0
- package/treb-embed/style/dropdown-select.scss +101 -0
- package/treb-embed/style/formula-bar.scss +193 -0
- package/treb-embed/style/grid.scss +374 -0
- package/treb-embed/style/layout.scss +424 -0
- package/treb-embed/style/mouse-mask.scss +67 -0
- package/treb-embed/style/note.scss +92 -0
- package/treb-embed/style/overlay-editor.scss +102 -0
- package/treb-embed/style/spinner.scss +92 -0
- package/treb-embed/style/tab-bar.scss +228 -0
- package/treb-embed/style/table.scss +80 -0
- package/treb-embed/style/theme-defaults.scss +444 -0
- package/treb-embed/style/toolbar.scss +416 -0
- package/treb-embed/style/tooltip.scss +68 -0
- package/treb-embed/style/treb-icons.scss +130 -0
- package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
- package/treb-embed/style/z-index.scss +43 -0
- package/treb-export/docs/charts.md +68 -0
- package/treb-export/modern.tsconfig.json +19 -0
- package/treb-export/package.json +4 -0
- package/treb-export/src/address-type.ts +77 -0
- package/treb-export/src/base-template.ts +22 -0
- package/treb-export/src/column-width.ts +85 -0
- package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
- package/treb-export/src/drawing2/chart2.ts +282 -0
- package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
- package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
- package/treb-export/src/drawing2/drawing2.ts +355 -0
- package/treb-export/src/drawing2/embedded-image.ts +71 -0
- package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
- package/treb-export/src/export-worker/export-worker.ts +99 -0
- package/treb-export/src/export-worker/index-modern.ts +22 -0
- package/treb-export/src/export2.ts +2204 -0
- package/treb-export/src/import2.ts +882 -0
- package/treb-export/src/relationship.ts +36 -0
- package/treb-export/src/shared-strings2.ts +128 -0
- package/treb-export/src/template-2.ts +22 -0
- package/treb-export/src/unescape_xml.ts +47 -0
- package/treb-export/src/workbook-sheet2.ts +182 -0
- package/treb-export/src/workbook-style2.ts +1285 -0
- package/treb-export/src/workbook-theme2.ts +88 -0
- package/treb-export/src/workbook2.ts +491 -0
- package/treb-export/src/xml-utils.ts +201 -0
- package/treb-export/template/base/[Content_Types].xml +2 -0
- package/treb-export/template/base/_rels/.rels +2 -0
- package/treb-export/template/base/docProps/app.xml +2 -0
- package/treb-export/template/base/docProps/core.xml +12 -0
- package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
- package/treb-export/template/base/xl/sharedStrings.xml +2 -0
- package/treb-export/template/base/xl/styles.xml +2 -0
- package/treb-export/template/base/xl/theme/theme1.xml +2 -0
- package/treb-export/template/base/xl/workbook.xml +2 -0
- package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
- package/treb-export/template/base.xlsx +0 -0
- package/treb-format/package.json +8 -0
- package/treb-format/src/format.test.ts +213 -0
- package/treb-format/src/format.ts +942 -0
- package/treb-format/src/format_cache.ts +199 -0
- package/treb-format/src/format_parser.ts +723 -0
- package/treb-format/src/index.ts +25 -0
- package/treb-format/src/number_format_section.ts +100 -0
- package/treb-format/src/value_parser.ts +337 -0
- package/treb-grid/package.json +5 -0
- package/treb-grid/src/editors/autocomplete.ts +394 -0
- package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
- package/treb-grid/src/editors/formula_bar.ts +473 -0
- package/treb-grid/src/editors/formula_editor_base.ts +910 -0
- package/treb-grid/src/editors/overlay_editor.ts +511 -0
- package/treb-grid/src/index.ts +37 -0
- package/treb-grid/src/layout/base_layout.ts +2618 -0
- package/treb-grid/src/layout/grid_layout.ts +299 -0
- package/treb-grid/src/layout/rectangle_cache.ts +86 -0
- package/treb-grid/src/render/selection-renderer.ts +414 -0
- package/treb-grid/src/render/svg_header_overlay.ts +93 -0
- package/treb-grid/src/render/svg_selection_block.ts +187 -0
- package/treb-grid/src/render/tile_renderer.ts +2122 -0
- package/treb-grid/src/types/annotation.ts +216 -0
- package/treb-grid/src/types/border_constants.ts +34 -0
- package/treb-grid/src/types/clipboard_data.ts +31 -0
- package/treb-grid/src/types/data_model.ts +334 -0
- package/treb-grid/src/types/drag_mask.ts +81 -0
- package/treb-grid/src/types/grid.ts +7743 -0
- package/treb-grid/src/types/grid_base.ts +3644 -0
- package/treb-grid/src/types/grid_command.ts +470 -0
- package/treb-grid/src/types/grid_events.ts +124 -0
- package/treb-grid/src/types/grid_options.ts +97 -0
- package/treb-grid/src/types/grid_selection.ts +60 -0
- package/treb-grid/src/types/named_range.ts +369 -0
- package/treb-grid/src/types/scale-control.ts +202 -0
- package/treb-grid/src/types/serialize_options.ts +72 -0
- package/treb-grid/src/types/set_range_options.ts +52 -0
- package/treb-grid/src/types/sheet.ts +3099 -0
- package/treb-grid/src/types/sheet_types.ts +95 -0
- package/treb-grid/src/types/tab_bar.ts +464 -0
- package/treb-grid/src/types/tile.ts +59 -0
- package/treb-grid/src/types/update_flags.ts +75 -0
- package/treb-grid/src/util/dom_utilities.ts +44 -0
- package/treb-grid/src/util/fontmetrics2.ts +179 -0
- package/treb-grid/src/util/ua.ts +104 -0
- package/treb-logo.svg +18 -0
- package/treb-parser/package.json +5 -0
- package/treb-parser/src/csv-parser.ts +122 -0
- package/treb-parser/src/index.ts +25 -0
- package/treb-parser/src/md-parser.ts +526 -0
- package/treb-parser/src/parser-types.ts +397 -0
- package/treb-parser/src/parser.test.ts +298 -0
- package/treb-parser/src/parser.ts +2673 -0
- package/treb-utils/package.json +5 -0
- package/treb-utils/src/dispatch.ts +57 -0
- package/treb-utils/src/event_source.ts +147 -0
- package/treb-utils/src/ievent_source.ts +33 -0
- package/treb-utils/src/index.ts +31 -0
- package/treb-utils/src/measurement.ts +174 -0
- package/treb-utils/src/resizable.ts +160 -0
- package/treb-utils/src/scale.ts +137 -0
- package/treb-utils/src/serialize_html.ts +124 -0
- package/treb-utils/src/template.ts +70 -0
- package/treb-utils/src/validate_uri.ts +61 -0
- package/tsconfig.json +10 -0
- package/tsproject.json +30 -0
- package/util/license-plugin-esbuild.js +86 -0
- package/util/list-css-vars.sh +46 -0
- package/README-esm.md +0 -37
- package/treb-bundle.css +0 -2
- 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
|
+
}
|