@vuu-ui/vuu-table-extras 2.1.19-beta.1 → 2.1.19-beta.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.
Files changed (96) hide show
  1. package/package.json +13 -14
  2. package/src/calculated-column/CalculatedColumnPanel.js +95 -0
  3. package/src/calculated-column/useCalculatedColumnPanel.js +60 -0
  4. package/src/calculated-column/useEditCalculatedColumn.js +104 -0
  5. package/src/cell-edit-validators/CaseValidator.js +21 -0
  6. package/src/cell-edit-validators/PatternValidator.js +23 -0
  7. package/src/cell-edit-validators/index.js +2 -0
  8. package/src/cell-renderers/background-cell/BackgroundCell.css.js +103 -0
  9. package/src/cell-renderers/background-cell/BackgroundCell.js +76 -0
  10. package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.css.js +9 -0
  11. package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.js +71 -0
  12. package/src/cell-renderers/background-cell/BackgroundKeyframes.css.js +187 -0
  13. package/src/cell-renderers/background-cell/index.js +2 -0
  14. package/src/cell-renderers/background-cell/useDirection.js +25 -0
  15. package/src/cell-renderers/button-cell/IconButtonCell.css.js +23 -0
  16. package/src/cell-renderers/button-cell/IconButtonCell.js +33 -0
  17. package/src/cell-renderers/dropdown-cell/DropdownCell.css.js +36 -0
  18. package/src/cell-renderers/dropdown-cell/DropdownCell.js +84 -0
  19. package/src/cell-renderers/dropdown-cell/index.js +1 -0
  20. package/src/cell-renderers/index.js +6 -0
  21. package/src/cell-renderers/lookup-cell/LookupCell.js +15 -0
  22. package/src/cell-renderers/lookup-cell/index.js +1 -0
  23. package/src/cell-renderers/pct-progress-cell/PctProgressCell.css.js +31 -0
  24. package/src/cell-renderers/pct-progress-cell/PctProgressCell.js +49 -0
  25. package/src/cell-renderers/pct-progress-cell/index.js +1 -0
  26. package/src/cell-renderers/progress-cell/ProgressCell.css.js +43 -0
  27. package/src/cell-renderers/progress-cell/ProgressCell.js +74 -0
  28. package/src/cell-renderers/progress-cell/index.js +1 -0
  29. package/src/column-expression-input/ColumnExpressionInput.css.js +94 -0
  30. package/src/column-expression-input/ColumnExpressionInput.js +28 -0
  31. package/src/column-expression-input/column-function-descriptors.js +281 -0
  32. package/src/column-expression-input/column-language-parser/ColumnExpressionLanguage.js +18 -0
  33. package/src/column-expression-input/column-language-parser/ColumnExpressionTreeWalker.js +381 -0
  34. package/src/column-expression-input/column-language-parser/column-expression-parse-utils.js +56 -0
  35. package/src/column-expression-input/column-language-parser/index.js +3 -0
  36. package/src/column-expression-input/functionDocInfo.js +24 -0
  37. package/src/column-expression-input/highlighting.js +18 -0
  38. package/src/column-expression-input/index.js +4 -0
  39. package/src/column-expression-input/theme.js +57 -0
  40. package/src/column-expression-input/useColumnAutoComplete.js +307 -0
  41. package/src/column-expression-input/useColumnExpressionEditor.js +133 -0
  42. package/src/column-expression-input/useColumnExpressionSuggestionProvider.js +211 -0
  43. package/src/column-formatting-settings/BaseNumericFormattingSettings.js +96 -0
  44. package/src/column-formatting-settings/ColumnFormattingPanel.js +112 -0
  45. package/src/column-formatting-settings/DateTimeFormattingSettings.js +116 -0
  46. package/src/column-formatting-settings/LongTypeFormattingSettings.css.js +11 -0
  47. package/src/column-formatting-settings/LongTypeFormattingSettings.js +54 -0
  48. package/src/column-formatting-settings/index.js +3 -0
  49. package/src/column-menu/ColumnMenu.css.js +25 -0
  50. package/src/column-menu/ColumnMenu.js +69 -0
  51. package/src/column-menu/column-action-types.js +0 -0
  52. package/src/column-menu/column-menu-utils.js +318 -0
  53. package/src/column-menu/useColumnActions.js +77 -0
  54. package/src/column-picker/ColumnModel.js +113 -0
  55. package/src/column-picker/ColumnPicker.css.js +96 -0
  56. package/src/column-picker/ColumnPicker.js +204 -0
  57. package/src/column-picker/ColumnPickerAction.js +24 -0
  58. package/src/column-picker/useColumnPicker.js +42 -0
  59. package/src/column-picker/useTableColumnPicker.js +27 -0
  60. package/src/column-picker/useTableColumnPickerDeprecated.js +87 -0
  61. package/src/column-settings-panel/ColumnNameLabel.css.js +18 -0
  62. package/src/column-settings-panel/ColumnNameLabel.js +54 -0
  63. package/src/column-settings-panel/ColumnSettingsPanel.css.js +70 -0
  64. package/src/column-settings-panel/ColumnSettingsPanel.js +173 -0
  65. package/src/column-settings-panel/useColumnSettings.js +205 -0
  66. package/src/csv-upload/CsvUpload.css.js +28 -0
  67. package/src/csv-upload/CsvUpload.js +95 -0
  68. package/src/csv-upload/index.js +7 -0
  69. package/src/csv-upload/parse/csv-constants.js +3 -0
  70. package/src/csv-upload/parse/csv-errors.js +54 -0
  71. package/src/csv-upload/parse/csv-parse.js +79 -0
  72. package/src/csv-upload/parse/csv-schema-validation.js +45 -0
  73. package/src/csv-upload/parse/csv-upload-utils.js +59 -0
  74. package/src/csv-upload/parse/index.js +5 -0
  75. package/src/csv-upload/useCsvUpload.js +260 -0
  76. package/src/csv-upload/useCsvUploadSessionPreview.js +70 -0
  77. package/src/datasource-stats/DatasourceStats.css.js +56 -0
  78. package/src/datasource-stats/DatasourceStats.js +89 -0
  79. package/src/datasource-stats/useDatasourceStats.js +48 -0
  80. package/src/freeze-control/FreezeControl.css.js +108 -0
  81. package/src/freeze-control/FreezeControl.js +96 -0
  82. package/src/freeze-control/FrozenBanner.css.js +16 -0
  83. package/src/freeze-control/FrozenBanner.js +48 -0
  84. package/src/freeze-control/freezeControlBadge.js +9 -0
  85. package/src/freeze-control/useFreezeControl.js +68 -0
  86. package/src/index.js +22 -0
  87. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.css.js +23 -0
  88. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +99 -0
  89. package/src/tabbed-table-config-panel/TabbedTableSettingsAction.js +28 -0
  90. package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +29 -0
  91. package/src/table-footer/TableFooter.css.js +22 -0
  92. package/src/table-footer/TableFooter.js +30 -0
  93. package/src/table-provider/TableProvider.js +33 -0
  94. package/src/table-settings-panel/TableSettingsPanel.css.js +70 -0
  95. package/src/table-settings-panel/TableSettingsPanel.js +128 -0
  96. package/src/table-settings-panel/useTableSettings.js +50 -0
@@ -0,0 +1,307 @@
1
+ import { booleanJoinSuggestions, getNamedParentNode, getPreviousNode, getValue, syntaxTree } from "@vuu-ui/vuu-codemirror";
2
+ import { useCallback } from "react";
3
+ import { ColumnNamedTerms, isCompleteExpression, isCompleteRelationalExpression, lastNamedChild } from "./column-language-parser/index.js";
4
+ const applyPrefix = (completions, prefix)=>prefix ? completions.map((completion)=>({
5
+ ...completion,
6
+ apply: "function" == typeof completion.apply ? completion.apply : `${prefix}${completion.apply ?? completion.label}`
7
+ })) : completions;
8
+ const isOperator = (node)=>void 0 === node ? false : [
9
+ "Times",
10
+ "Divide",
11
+ "Plus",
12
+ "Minus"
13
+ ].includes(node.name);
14
+ const completionDone = (onSubmit)=>({
15
+ apply: ()=>{
16
+ onSubmit?.();
17
+ },
18
+ label: "Done",
19
+ boost: 10
20
+ });
21
+ const getLastChild = (node, context)=>{
22
+ let { lastChild: childNode } = node;
23
+ const { pos } = context;
24
+ while(childNode){
25
+ const isBeforeCursor = childNode.from < pos;
26
+ if (isBeforeCursor && ColumnNamedTerms.includes(childNode.name)) {
27
+ if ("ParenthesizedExpression" === childNode.name) {
28
+ const expression = childNode.firstChild?.nextSibling;
29
+ if (expression) childNode = expression;
30
+ }
31
+ return childNode;
32
+ }
33
+ childNode = childNode.prevSibling;
34
+ }
35
+ };
36
+ const getFunctionName = (node, state)=>{
37
+ if ("ArgList" === node.name) {
38
+ const functionNode = node.prevSibling;
39
+ if (functionNode) return getValue(functionNode, state);
40
+ } else if ("OpenBrace" === node.name) {
41
+ const maybeFunction = node.parent?.prevSibling;
42
+ if (maybeFunction?.name === "Function") return getValue(maybeFunction, state);
43
+ }
44
+ };
45
+ const getRelationalOperator = (node, state)=>{
46
+ if ("RelationalExpression" === node.name) {
47
+ const lastNode = lastNamedChild(node);
48
+ if (lastNode?.name === "RelationalOperator") return getValue(lastNode, state);
49
+ } else {
50
+ const prevNode = node.prevSibling;
51
+ if (prevNode?.name === "RelationalOperator") return getValue(prevNode, state);
52
+ }
53
+ };
54
+ const getColumnName = (node, state)=>{
55
+ if ("RelationalExpression" === node.name) {
56
+ if (node.firstChild?.name === "Column") return getValue(node.firstChild, state);
57
+ } else {
58
+ const prevNode = node.prevSibling;
59
+ if (prevNode?.name === "Column") return getValue(prevNode, state);
60
+ if (prevNode?.name === "RelationalOperator") return getColumnName(prevNode, state);
61
+ }
62
+ };
63
+ const useColumnAutoComplete_makeSuggestions = async (context, suggestionProvider, suggestionType, optionalArgs = {})=>{
64
+ const options = await suggestionProvider.getSuggestions(suggestionType, optionalArgs);
65
+ const { startsWith = "" } = optionalArgs;
66
+ return {
67
+ from: context.pos - startsWith.length,
68
+ options
69
+ };
70
+ };
71
+ const handleConditionalExpression = (node, context, suggestionProvider, maybeComplete, onSubmit)=>{
72
+ const lastChild = getLastChild(node, context);
73
+ switch(lastChild?.name){
74
+ case "If":
75
+ return useColumnAutoComplete_makeSuggestions(context, suggestionProvider, "expression", {
76
+ prefix: "( "
77
+ });
78
+ case "OpenBrace":
79
+ return useColumnAutoComplete_makeSuggestions(context, suggestionProvider, "expression");
80
+ case "Condition":
81
+ return useColumnAutoComplete_makeSuggestions(context, suggestionProvider, "expression", {
82
+ prefix: ", "
83
+ });
84
+ case "CloseBrace":
85
+ if (maybeComplete) {
86
+ const options = [
87
+ completionDone(onSubmit)
88
+ ];
89
+ return {
90
+ from: context.pos,
91
+ options
92
+ };
93
+ }
94
+ }
95
+ };
96
+ const promptToSave = (context, onSubmit)=>{
97
+ const options = [
98
+ completionDone(onSubmit)
99
+ ];
100
+ return {
101
+ from: context.pos,
102
+ options
103
+ };
104
+ };
105
+ const useColumnAutoComplete = (suggestionProvider, onSubmit)=>{
106
+ const makeSuggestions = useCallback(async (context, suggestionType, optionalArgs = {})=>{
107
+ const options = await suggestionProvider.getSuggestions(suggestionType, optionalArgs);
108
+ const { startsWith = "" } = optionalArgs;
109
+ return {
110
+ from: context.pos - startsWith.length,
111
+ options
112
+ };
113
+ }, [
114
+ suggestionProvider
115
+ ]);
116
+ return useCallback(async (context)=>{
117
+ const { state, pos } = context;
118
+ const word = context.matchBefore(/\w*/) ?? {
119
+ from: 0,
120
+ to: 0,
121
+ text: void 0
122
+ };
123
+ const tree = syntaxTree(state);
124
+ const nodeBefore = tree.resolveInner(pos, -1);
125
+ const text = state.doc.toString();
126
+ const maybeComplete = isCompleteExpression(text);
127
+ switch(nodeBefore.name){
128
+ case "If":
129
+ return makeSuggestions(context, "expression", {
130
+ prefix: "( "
131
+ });
132
+ case "Condition":
133
+ {
134
+ const lastChild = getLastChild(nodeBefore, context);
135
+ if (lastChild?.name === "Column") {
136
+ const prevChild = getPreviousNode(lastChild);
137
+ if (prevChild?.name !== "RelationalOperator") return makeSuggestions(context, "condition-operator", {
138
+ columnName: getValue(lastChild, state)
139
+ });
140
+ } else if (lastChild?.name === "RelationalOperator") return makeSuggestions(context, "expression");
141
+ }
142
+ break;
143
+ case "ConditionalExpression":
144
+ return handleConditionalExpression(nodeBefore, context, suggestionProvider);
145
+ case "RelationalExpression":
146
+ if (isCompleteRelationalExpression(nodeBefore)) return {
147
+ from: context.pos,
148
+ options: booleanJoinSuggestions.concat({
149
+ label: ", <truthy expression>, <falsy expression>",
150
+ apply: ", "
151
+ })
152
+ };
153
+ {
154
+ const operator = getRelationalOperator(nodeBefore, state);
155
+ const columnName = getColumnName(nodeBefore, state);
156
+ if (operator) return makeSuggestions(context, "expression");
157
+ {
158
+ const options = await suggestionProvider.getSuggestions("condition-operator", {
159
+ columnName
160
+ });
161
+ return {
162
+ from: context.pos,
163
+ options
164
+ };
165
+ }
166
+ }
167
+ case "RelationalOperator":
168
+ return makeSuggestions(context, "expression");
169
+ case "String":
170
+ {
171
+ const operator = getRelationalOperator(nodeBefore, state);
172
+ const columnName = getColumnName(nodeBefore, state);
173
+ const { from, to } = nodeBefore;
174
+ if (to - from === 2 && context.pos === from + 1) {
175
+ if (columnName && operator) return makeSuggestions(context, "columnValue", {
176
+ columnName,
177
+ operator,
178
+ startsWith: word.text
179
+ });
180
+ } else if (to - from > 2 && context.pos === to) return makeSuggestions(context, "expression", {
181
+ prefix: ", "
182
+ });
183
+ }
184
+ break;
185
+ case "ArithmeticExpression":
186
+ {
187
+ const lastChild = getLastChild(nodeBefore, context);
188
+ if (lastChild?.name === "Column") return makeSuggestions(context, "expression");
189
+ if (isOperator(lastChild)) {
190
+ const operator = lastChild.name;
191
+ return makeSuggestions(context, "column", {
192
+ operator
193
+ });
194
+ }
195
+ }
196
+ break;
197
+ case "OpenBrace":
198
+ {
199
+ const functionName = getFunctionName(nodeBefore, state);
200
+ return makeSuggestions(context, "expression", {
201
+ functionName
202
+ });
203
+ }
204
+ case "ArgList":
205
+ {
206
+ const functionName = getFunctionName(nodeBefore, state);
207
+ const lastArgument = getLastChild(nodeBefore, context);
208
+ const prefix = lastArgument?.name === "OpenBrace" || lastArgument?.name === "Comma" ? void 0 : ",";
209
+ let options = await suggestionProvider.getSuggestions("expression", {
210
+ functionName
211
+ });
212
+ options = prefix ? applyPrefix(options, ", ") : options;
213
+ if (lastArgument?.name !== "OpenBrace" && lastArgument?.name !== "Comma") options = [
214
+ {
215
+ apply: ") ",
216
+ boost: 10,
217
+ label: "Done - no more arguments"
218
+ }
219
+ ].concat(options);
220
+ return {
221
+ from: context.pos,
222
+ options
223
+ };
224
+ }
225
+ case "Equal":
226
+ if ("=" === text.trim()) return makeSuggestions(context, "expression");
227
+ break;
228
+ case "ParenthesizedExpression":
229
+ case "ColumnDefinitionExpression":
230
+ if (0 === context.pos) return makeSuggestions(context, "expression");
231
+ {
232
+ const lastChild = getLastChild(nodeBefore, context);
233
+ if (lastChild?.name === "Column") {
234
+ if (maybeComplete) {
235
+ const options = [
236
+ completionDone(onSubmit.current)
237
+ ];
238
+ const columnName = getValue(lastChild, state);
239
+ const columnOptions = await suggestionProvider.getSuggestions("operator", {
240
+ columnName
241
+ });
242
+ return {
243
+ from: context.pos,
244
+ options: options.concat(columnOptions)
245
+ };
246
+ }
247
+ } else if (lastChild?.name === "CallExpression") {
248
+ if (maybeComplete) return {
249
+ from: context.pos,
250
+ options: [
251
+ completionDone(onSubmit.current)
252
+ ]
253
+ };
254
+ } else if (lastChild?.name === "ArithmeticExpression") {
255
+ if (maybeComplete) {
256
+ let options = [
257
+ completionDone(onSubmit.current)
258
+ ];
259
+ const lastExpressionChild = getLastChild(lastChild, context);
260
+ if (lastExpressionChild?.name === "Column") {
261
+ const columnName = getValue(lastExpressionChild, state);
262
+ const suggestions = await suggestionProvider.getSuggestions("operator", {
263
+ columnName
264
+ });
265
+ options = options.concat(suggestions);
266
+ }
267
+ return {
268
+ from: context.pos,
269
+ options
270
+ };
271
+ }
272
+ } else if (lastChild?.name === "ConditionalExpression") return handleConditionalExpression(lastChild, context, suggestionProvider, maybeComplete, onSubmit.current);
273
+ break;
274
+ }
275
+ case "Column":
276
+ {
277
+ const isPartialMatch = await suggestionProvider.isPartialMatch("expression", void 0, word.text);
278
+ if (isPartialMatch) return makeSuggestions(context, "expression", {
279
+ startsWith: word.text
280
+ });
281
+ }
282
+ break;
283
+ case "Comma":
284
+ {
285
+ const parentNode = getNamedParentNode(nodeBefore);
286
+ if (parentNode?.name === "ConditionalExpression") return makeSuggestions(context, "expression");
287
+ }
288
+ break;
289
+ case "CloseBrace":
290
+ {
291
+ const parentNode = getNamedParentNode(nodeBefore);
292
+ if (parentNode?.name === "ConditionalExpression") return handleConditionalExpression(parentNode, context, suggestionProvider, maybeComplete, onSubmit.current);
293
+ if (parentNode?.name === "ArgList") {
294
+ if (maybeComplete) return promptToSave(context, onSubmit.current);
295
+ }
296
+ }
297
+ break;
298
+ default:
299
+ if (nodeBefore?.prevSibling?.name === "FilterClause") console.log("looks like we ight be a or|and operator");
300
+ }
301
+ }, [
302
+ makeSuggestions,
303
+ onSubmit,
304
+ suggestionProvider
305
+ ]);
306
+ };
307
+ export { useColumnAutoComplete };
@@ -0,0 +1,133 @@
1
+ import { EditorState, EditorView, autocompletion, defaultKeymap, ensureSyntaxTree, keymap, minimalSetup, startCompletion } from "@vuu-ui/vuu-codemirror";
2
+ import { createEl } from "@vuu-ui/vuu-utils";
3
+ import { useCallback, useEffect, useMemo, useRef } from "react";
4
+ import { columnExpressionLanguageSupport } from "./column-language-parser/index.js";
5
+ import { walkTree } from "./column-language-parser/ColumnExpressionTreeWalker.js";
6
+ import { vuuHighlighting } from "./highlighting.js";
7
+ import { vuuTheme } from "./theme.js";
8
+ import { useColumnAutoComplete } from "./useColumnAutoComplete.js";
9
+ const getView = (ref)=>{
10
+ if (void 0 == ref.current) throw Error("EditorView not defined");
11
+ return ref.current;
12
+ };
13
+ const getOptionClass = ()=>"vuuSuggestion";
14
+ const noop = ()=>console.log("noooop");
15
+ const hasExpressionType = (completion)=>"expressionType" in completion;
16
+ const injectOptionContent = (completion)=>{
17
+ if (!hasExpressionType(completion)) return null;
18
+ {
19
+ const div = createEl("div", "expression-type-container");
20
+ const span = createEl("span", "expression-type", completion.expressionType);
21
+ div.appendChild(span);
22
+ return div;
23
+ }
24
+ };
25
+ const useColumnExpressionEditor = ({ onChange, onSubmitExpression, source, suggestionProvider })=>{
26
+ const editorRef = useRef(null);
27
+ const onSubmitRef = useRef(noop);
28
+ const viewRef = useRef(void 0);
29
+ const completionFn = useColumnAutoComplete(suggestionProvider, onSubmitRef);
30
+ const [createState, clearInput, submit] = useMemo(()=>{
31
+ const parseExpression = ()=>{
32
+ const view = getView(viewRef);
33
+ const source = view.state.doc.toString();
34
+ const tree = ensureSyntaxTree(view.state, view.state.doc.length, 5000);
35
+ if (!tree) return [
36
+ "",
37
+ void 0
38
+ ];
39
+ {
40
+ const expression = walkTree(tree, source);
41
+ return [
42
+ source,
43
+ expression
44
+ ];
45
+ }
46
+ };
47
+ const clearInput = ()=>{
48
+ getView(viewRef).setState(createState());
49
+ };
50
+ const submitExpression = ()=>{
51
+ const [source, expression] = parseExpression();
52
+ onSubmitExpression?.(source, expression);
53
+ };
54
+ const showSuggestions = (key)=>keymap.of([
55
+ {
56
+ key,
57
+ run () {
58
+ startCompletion(getView(viewRef));
59
+ return true;
60
+ }
61
+ }
62
+ ]);
63
+ const createState = ()=>EditorState.create({
64
+ doc: source,
65
+ extensions: [
66
+ minimalSetup,
67
+ autocompletion({
68
+ addToOptions: [
69
+ {
70
+ render: injectOptionContent,
71
+ position: 70
72
+ }
73
+ ],
74
+ override: [
75
+ completionFn
76
+ ],
77
+ optionClass: getOptionClass
78
+ }),
79
+ columnExpressionLanguageSupport(),
80
+ keymap.of(defaultKeymap),
81
+ showSuggestions("ArrowDown"),
82
+ EditorView.updateListener.of((v)=>{
83
+ const view = getView(viewRef);
84
+ if (v.docChanged) {
85
+ startCompletion(view);
86
+ const source = view.state.doc.toString();
87
+ onChange?.(source);
88
+ }
89
+ }),
90
+ EditorState.transactionFilter.of((tr)=>tr.newDoc.lines > 1 ? [] : tr),
91
+ vuuTheme,
92
+ vuuHighlighting
93
+ ]
94
+ });
95
+ onSubmitRef.current = ()=>{
96
+ submitExpression();
97
+ };
98
+ return [
99
+ createState,
100
+ clearInput,
101
+ submitExpression
102
+ ];
103
+ }, [
104
+ completionFn,
105
+ onChange,
106
+ onSubmitExpression,
107
+ source
108
+ ]);
109
+ useEffect(()=>{
110
+ if (!editorRef.current) throw Error("editor not in dom");
111
+ viewRef.current = new EditorView({
112
+ state: createState(),
113
+ parent: editorRef.current
114
+ });
115
+ return ()=>{
116
+ viewRef.current?.destroy();
117
+ };
118
+ }, [
119
+ completionFn,
120
+ createState
121
+ ]);
122
+ const handleBlur = useCallback(()=>{
123
+ submit();
124
+ }, [
125
+ submit
126
+ ]);
127
+ return {
128
+ editorRef,
129
+ clearInput,
130
+ onBlur: handleBlur
131
+ };
132
+ };
133
+ export { useColumnExpressionEditor };
@@ -0,0 +1,211 @@
1
+ import { AnnotationType, getRelationalOperators, numericOperators, stringOperators, toSuggestions } from "@vuu-ui/vuu-codemirror";
2
+ import { getTypeaheadParams, useTypeaheadSuggestions } from "@vuu-ui/vuu-data-react";
3
+ import { isNumericColumn, isTextColumn } from "@vuu-ui/vuu-utils";
4
+ import { useCallback, useRef } from "react";
5
+ import { columnFunctionDescriptors } from "./column-function-descriptors.js";
6
+ import { functionDocInfo } from "./functionDocInfo.js";
7
+ const NO_OPERATORS = [];
8
+ const withApplySpace = (suggestions)=>suggestions.map((suggestion)=>({
9
+ ...suggestion,
10
+ apply: (suggestion.apply ?? suggestion.label) + " "
11
+ }));
12
+ const getValidColumns = (columns, { functionName, operator })=>{
13
+ if (operator) return columns.filter(isNumericColumn);
14
+ if (functionName) {
15
+ const fn = columnFunctionDescriptors.find((f)=>f.name === functionName);
16
+ if (fn) switch(fn.accepts){
17
+ case "string":
18
+ return columns.filter(isTextColumn);
19
+ case "number":
20
+ return columns.filter(isNumericColumn);
21
+ default:
22
+ break;
23
+ }
24
+ }
25
+ return columns;
26
+ };
27
+ const getColumns = (columns, options)=>{
28
+ const validColumns = getValidColumns(columns, options);
29
+ return validColumns.map((column)=>{
30
+ const label = column.label ?? column.name;
31
+ return {
32
+ apply: options.prefix ? `${options.prefix}${column.name}` : column.name,
33
+ label,
34
+ boost: 5,
35
+ type: "column",
36
+ expressionType: column.serverDataType
37
+ };
38
+ });
39
+ };
40
+ const arithmeticOperators = [
41
+ {
42
+ apply: "* ",
43
+ boost: 2,
44
+ label: "*",
45
+ type: "operator"
46
+ },
47
+ {
48
+ apply: "/ ",
49
+ boost: 2,
50
+ label: "/",
51
+ type: "operator"
52
+ },
53
+ {
54
+ apply: "+ ",
55
+ boost: 2,
56
+ label: "+",
57
+ type: "operator"
58
+ },
59
+ {
60
+ apply: "- ",
61
+ boost: 2,
62
+ label: "-",
63
+ type: "operator"
64
+ }
65
+ ];
66
+ const getOperators = (column)=>{
67
+ if (void 0 === column || isNumericColumn(column)) return arithmeticOperators;
68
+ return NO_OPERATORS;
69
+ };
70
+ const getConditionOperators = (column)=>{
71
+ switch(column.serverDataType){
72
+ case "string":
73
+ case "char":
74
+ return withApplySpace(stringOperators);
75
+ case "int":
76
+ case "long":
77
+ case "double":
78
+ return withApplySpace(numericOperators);
79
+ }
80
+ };
81
+ const toFunctionCompletion = (functionDescriptor)=>({
82
+ apply: `${functionDescriptor.name}( `,
83
+ boost: 2,
84
+ expressionType: functionDescriptor.type,
85
+ info: ()=>functionDocInfo(functionDescriptor),
86
+ label: functionDescriptor.name,
87
+ type: "function"
88
+ });
89
+ const getAcceptedTypes = (fn)=>{
90
+ if (fn) {
91
+ if ("string" == typeof fn.accepts) return fn.accepts;
92
+ else if (Array.isArray(fn.accepts)) {
93
+ if (fn.accepts.every((s)=>"string" === s)) return "string";
94
+ }
95
+ }
96
+ return "any";
97
+ };
98
+ const functions = columnFunctionDescriptors.map(toFunctionCompletion);
99
+ const getFunctions = ({ functionName })=>{
100
+ if (functionName) {
101
+ const fn = columnFunctionDescriptors.find((f)=>f.name === functionName);
102
+ const acceptedTypes = getAcceptedTypes(fn);
103
+ if (fn) switch(acceptedTypes){
104
+ case "string":
105
+ return columnFunctionDescriptors.filter((f)=>"string" === f.type || "variable" === f.type).map(toFunctionCompletion);
106
+ case "number":
107
+ return columnFunctionDescriptors.filter((f)=>"number" === f.type || "variable" === f.type).map(toFunctionCompletion);
108
+ default:
109
+ }
110
+ }
111
+ return functions;
112
+ };
113
+ const NONE = {};
114
+ const useColumnExpressionSuggestionProvider = ({ columns, table })=>{
115
+ const findColumn = useCallback((name)=>name ? columns.find((col)=>col.name === name) : void 0, [
116
+ columns
117
+ ]);
118
+ const latestSuggestionsRef = useRef(void 0);
119
+ const getTypeaheadSuggestions = useTypeaheadSuggestions();
120
+ const getSuggestions = useCallback(async (suggestionType, options = NONE)=>{
121
+ const { columnName, functionName, operator, prefix } = options;
122
+ switch(suggestionType){
123
+ case "expression":
124
+ {
125
+ const suggestions = await withApplySpace(getColumns(columns, {
126
+ functionName,
127
+ prefix
128
+ })).concat(getFunctions(options));
129
+ return latestSuggestionsRef.current = suggestions;
130
+ }
131
+ case "column":
132
+ {
133
+ const suggestions = await getColumns(columns, options);
134
+ return latestSuggestionsRef.current = withApplySpace(suggestions);
135
+ }
136
+ case "operator":
137
+ {
138
+ const suggestions = await getOperators(findColumn(columnName));
139
+ return latestSuggestionsRef.current = withApplySpace(suggestions);
140
+ }
141
+ case "relational-operator":
142
+ {
143
+ const suggestions = await getRelationalOperators(findColumn(columnName));
144
+ return latestSuggestionsRef.current = withApplySpace(suggestions);
145
+ }
146
+ case "condition-operator":
147
+ {
148
+ const column = findColumn(columnName);
149
+ if (column) {
150
+ const suggestions = await getConditionOperators(column);
151
+ if (suggestions) return latestSuggestionsRef.current = withApplySpace(suggestions);
152
+ }
153
+ }
154
+ break;
155
+ case "columnValue":
156
+ if (columnName && operator) {
157
+ const params = getTypeaheadParams(table, columnName);
158
+ const suggestions = await getTypeaheadSuggestions(params);
159
+ if (suggestions) {
160
+ latestSuggestionsRef.current = toSuggestions(suggestions, {
161
+ suffix: ""
162
+ });
163
+ latestSuggestionsRef.current.forEach((suggestion)=>{
164
+ suggestion.apply = (view, completion, from)=>{
165
+ const annotation = new AnnotationType();
166
+ const cursorPos = from + completion.label.length + 1;
167
+ view.dispatch({
168
+ changes: {
169
+ from,
170
+ insert: completion.label
171
+ },
172
+ selection: {
173
+ anchor: cursorPos,
174
+ head: cursorPos
175
+ },
176
+ annotations: annotation.of(completion)
177
+ });
178
+ };
179
+ });
180
+ }
181
+ return latestSuggestionsRef.current || [];
182
+ }
183
+ break;
184
+ }
185
+ return [];
186
+ }, [
187
+ columns,
188
+ findColumn,
189
+ getTypeaheadSuggestions,
190
+ table
191
+ ]);
192
+ const isPartialMatch = useCallback(async (valueType, columnName, pattern)=>{
193
+ const { current: latestSuggestions } = latestSuggestionsRef;
194
+ let maybe = false;
195
+ const suggestions = latestSuggestions || await getSuggestions(valueType, {
196
+ columnName
197
+ });
198
+ if (pattern && suggestions) {
199
+ for (const option of suggestions)if (option.label === pattern) return false;
200
+ else if (option.label.startsWith(pattern)) maybe = true;
201
+ }
202
+ return maybe;
203
+ }, [
204
+ getSuggestions
205
+ ]);
206
+ return {
207
+ getSuggestions,
208
+ isPartialMatch
209
+ };
210
+ };
211
+ export { useColumnExpressionSuggestionProvider };