@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.
- package/package.json +13 -14
- package/src/calculated-column/CalculatedColumnPanel.js +95 -0
- package/src/calculated-column/useCalculatedColumnPanel.js +60 -0
- package/src/calculated-column/useEditCalculatedColumn.js +104 -0
- package/src/cell-edit-validators/CaseValidator.js +21 -0
- package/src/cell-edit-validators/PatternValidator.js +23 -0
- package/src/cell-edit-validators/index.js +2 -0
- package/src/cell-renderers/background-cell/BackgroundCell.css.js +103 -0
- package/src/cell-renderers/background-cell/BackgroundCell.js +76 -0
- package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.css.js +9 -0
- package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.js +71 -0
- package/src/cell-renderers/background-cell/BackgroundKeyframes.css.js +187 -0
- package/src/cell-renderers/background-cell/index.js +2 -0
- package/src/cell-renderers/background-cell/useDirection.js +25 -0
- package/src/cell-renderers/button-cell/IconButtonCell.css.js +23 -0
- package/src/cell-renderers/button-cell/IconButtonCell.js +33 -0
- package/src/cell-renderers/dropdown-cell/DropdownCell.css.js +36 -0
- package/src/cell-renderers/dropdown-cell/DropdownCell.js +84 -0
- package/src/cell-renderers/dropdown-cell/index.js +1 -0
- package/src/cell-renderers/index.js +6 -0
- package/src/cell-renderers/lookup-cell/LookupCell.js +15 -0
- package/src/cell-renderers/lookup-cell/index.js +1 -0
- package/src/cell-renderers/pct-progress-cell/PctProgressCell.css.js +31 -0
- package/src/cell-renderers/pct-progress-cell/PctProgressCell.js +49 -0
- package/src/cell-renderers/pct-progress-cell/index.js +1 -0
- package/src/cell-renderers/progress-cell/ProgressCell.css.js +43 -0
- package/src/cell-renderers/progress-cell/ProgressCell.js +74 -0
- package/src/cell-renderers/progress-cell/index.js +1 -0
- package/src/column-expression-input/ColumnExpressionInput.css.js +94 -0
- package/src/column-expression-input/ColumnExpressionInput.js +28 -0
- package/src/column-expression-input/column-function-descriptors.js +281 -0
- package/src/column-expression-input/column-language-parser/ColumnExpressionLanguage.js +18 -0
- package/src/column-expression-input/column-language-parser/ColumnExpressionTreeWalker.js +381 -0
- package/src/column-expression-input/column-language-parser/column-expression-parse-utils.js +56 -0
- package/src/column-expression-input/column-language-parser/index.js +3 -0
- package/src/column-expression-input/functionDocInfo.js +24 -0
- package/src/column-expression-input/highlighting.js +18 -0
- package/src/column-expression-input/index.js +4 -0
- package/src/column-expression-input/theme.js +57 -0
- package/src/column-expression-input/useColumnAutoComplete.js +307 -0
- package/src/column-expression-input/useColumnExpressionEditor.js +133 -0
- package/src/column-expression-input/useColumnExpressionSuggestionProvider.js +211 -0
- package/src/column-formatting-settings/BaseNumericFormattingSettings.js +96 -0
- package/src/column-formatting-settings/ColumnFormattingPanel.js +112 -0
- package/src/column-formatting-settings/DateTimeFormattingSettings.js +116 -0
- package/src/column-formatting-settings/LongTypeFormattingSettings.css.js +11 -0
- package/src/column-formatting-settings/LongTypeFormattingSettings.js +54 -0
- package/src/column-formatting-settings/index.js +3 -0
- package/src/column-menu/ColumnMenu.css.js +25 -0
- package/src/column-menu/ColumnMenu.js +69 -0
- package/src/column-menu/column-action-types.js +0 -0
- package/src/column-menu/column-menu-utils.js +318 -0
- package/src/column-menu/useColumnActions.js +77 -0
- package/src/column-picker/ColumnModel.js +113 -0
- package/src/column-picker/ColumnPicker.css.js +96 -0
- package/src/column-picker/ColumnPicker.js +204 -0
- package/src/column-picker/ColumnPickerAction.js +24 -0
- package/src/column-picker/useColumnPicker.js +42 -0
- package/src/column-picker/useTableColumnPicker.js +27 -0
- package/src/column-picker/useTableColumnPickerDeprecated.js +87 -0
- package/src/column-settings-panel/ColumnNameLabel.css.js +18 -0
- package/src/column-settings-panel/ColumnNameLabel.js +54 -0
- package/src/column-settings-panel/ColumnSettingsPanel.css.js +70 -0
- package/src/column-settings-panel/ColumnSettingsPanel.js +173 -0
- package/src/column-settings-panel/useColumnSettings.js +205 -0
- package/src/csv-upload/CsvUpload.css.js +28 -0
- package/src/csv-upload/CsvUpload.js +95 -0
- package/src/csv-upload/index.js +7 -0
- package/src/csv-upload/parse/csv-constants.js +3 -0
- package/src/csv-upload/parse/csv-errors.js +54 -0
- package/src/csv-upload/parse/csv-parse.js +79 -0
- package/src/csv-upload/parse/csv-schema-validation.js +45 -0
- package/src/csv-upload/parse/csv-upload-utils.js +59 -0
- package/src/csv-upload/parse/index.js +5 -0
- package/src/csv-upload/useCsvUpload.js +260 -0
- package/src/csv-upload/useCsvUploadSessionPreview.js +70 -0
- package/src/datasource-stats/DatasourceStats.css.js +56 -0
- package/src/datasource-stats/DatasourceStats.js +89 -0
- package/src/datasource-stats/useDatasourceStats.js +48 -0
- package/src/freeze-control/FreezeControl.css.js +108 -0
- package/src/freeze-control/FreezeControl.js +96 -0
- package/src/freeze-control/FrozenBanner.css.js +16 -0
- package/src/freeze-control/FrozenBanner.js +48 -0
- package/src/freeze-control/freezeControlBadge.js +9 -0
- package/src/freeze-control/useFreezeControl.js +68 -0
- package/src/index.js +22 -0
- package/src/tabbed-table-config-panel/TabbedTableConfigPanel.css.js +23 -0
- package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +99 -0
- package/src/tabbed-table-config-panel/TabbedTableSettingsAction.js +28 -0
- package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +29 -0
- package/src/table-footer/TableFooter.css.js +22 -0
- package/src/table-footer/TableFooter.js +30 -0
- package/src/table-provider/TableProvider.js +33 -0
- package/src/table-settings-panel/TableSettingsPanel.css.js +70 -0
- package/src/table-settings-panel/TableSettingsPanel.js +128 -0
- 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 };
|