@spectric/ui 0.0.21 → 0.0.22
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/dist/components/dialog/dialog.d.ts +1 -0
- package/dist/components/pagination/pagination.d.ts +1 -1
- package/dist/components/query_bar/QueryBar.d.ts +30 -10
- package/dist/components/query_bar/dateTimePopup.d.ts +2 -0
- package/dist/components/query_bar/geojsonPopup.d.ts +2 -0
- package/dist/components/query_bar/querylanguage/kuery/functions/geospatial.d.ts +19 -0
- package/dist/components/query_bar/querylanguage/outputTypes/toCQL.d.ts +2 -1
- package/dist/components/query_bar/querylanguage/outputTypes/toMongo.d.ts +6 -1
- package/dist/components/symbols.d.ts +6 -0
- package/dist/components/table/cell.d.ts +1 -1
- package/dist/components/table/table.d.ts +5 -1
- package/dist/custom-elements.json +5 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.es.js +4382 -2795
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +349 -248
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +6 -1
- package/src/components/dialog/dialog.css.ts +29 -29
- package/src/components/dialog/dialog.ts +3 -1
- package/src/components/input.ts +49 -39
- package/src/components/pagination/pagination.ts +167 -113
- package/src/components/query_bar/QueryBar.ts +438 -187
- package/src/components/query_bar/dateTimePopup.ts +54 -0
- package/src/components/query_bar/geojsonPopup.ts +44 -0
- package/src/components/query_bar/querylanguage/kuery/ast/_generated_/kuery.js +1836 -2745
- package/src/components/query_bar/querylanguage/kuery/ast/ast.ts +15 -13
- package/src/components/query_bar/querylanguage/kuery/ast/kuery.peg +92 -126
- package/src/components/query_bar/querylanguage/kuery/functions/geospatial.ts +25 -0
- package/src/components/query_bar/querylanguage/kuery/functions/index.ts +9 -7
- package/src/components/query_bar/querylanguage/outputTypes/toCQL.ts +56 -34
- package/src/components/query_bar/querylanguage/outputTypes/toMongo.ts +46 -34
- package/src/components/symbols.ts +6 -0
- package/src/components/table/__tests__/table.spec.ts +2 -2
- package/src/components/table/cell.ts +7 -3
- package/src/components/table/header.ts +3 -2
- package/src/components/table/table.css +4 -2
- package/src/components/table/table.ts +61 -5
- package/src/components/table/virtualBody.ts +8 -3
- package/src/components/tooltip/popover.ts +263 -225
- package/src/stories/Dialog.stories.ts +59 -0
- package/src/stories/QueryBar.stories.ts +46 -37
- package/src/stories/fixtures/data.ts +195 -36
- package/src/stories/table.stories.ts +70 -22
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
import { html, LitElement, PropertyValues } from "lit";
|
|
2
|
-
import * as kuery from "./querylanguage/kuery"
|
|
3
|
-
import {
|
|
2
|
+
import * as kuery from "./querylanguage/kuery";
|
|
3
|
+
import {
|
|
4
|
+
customElement,
|
|
5
|
+
property,
|
|
6
|
+
query,
|
|
7
|
+
queryAsync,
|
|
8
|
+
state,
|
|
9
|
+
} from "lit/decorators.js";
|
|
4
10
|
import { SpectricInput } from "../input";
|
|
5
11
|
import { JsonObject } from "./types";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
HTMLElementTagWithEvents,
|
|
14
|
+
ReactElementWithPropsAndEvents,
|
|
15
|
+
} from "../types";
|
|
16
|
+
import "./QueryBar.css";
|
|
11
17
|
import { PopoverElement } from "../tooltip/popover";
|
|
18
|
+
|
|
19
|
+
import { DateTimePopup } from "./dateTimePopup";
|
|
20
|
+
import { GeoJSONPopup } from "./geojsonPopup";
|
|
21
|
+
import { DELETE, EDIT } from "../symbols";
|
|
12
22
|
export type FieldTypes = {
|
|
13
23
|
name: string;
|
|
14
|
-
type: "string" | "number" | "boolean" | "integer" | "object"
|
|
15
|
-
format?: "date-time"
|
|
16
|
-
}
|
|
17
|
-
type SuggestionType =
|
|
24
|
+
type: "string" | "number" | "boolean" | "integer" | "object";
|
|
25
|
+
format?: "date-time" | "geojson";
|
|
26
|
+
};
|
|
27
|
+
type SuggestionType =
|
|
28
|
+
| "conjunction"
|
|
29
|
+
| "field"
|
|
30
|
+
| "operator"
|
|
31
|
+
| "value"
|
|
32
|
+
| "geo_value";
|
|
18
33
|
type Suggestion = {
|
|
19
|
-
fieldName: string
|
|
20
|
-
end: number
|
|
21
|
-
prefix: string
|
|
22
|
-
start: number
|
|
23
|
-
suffix: string
|
|
24
|
-
suggestionTypes:
|
|
25
|
-
text: string
|
|
26
|
-
type: "cursor"
|
|
27
|
-
}
|
|
34
|
+
fieldName: string;
|
|
35
|
+
end: number;
|
|
36
|
+
prefix: string;
|
|
37
|
+
start: number;
|
|
38
|
+
suffix: string;
|
|
39
|
+
suggestionTypes: SuggestionType[];
|
|
40
|
+
text: string;
|
|
41
|
+
type: "cursor";
|
|
42
|
+
};
|
|
28
43
|
export enum SupportedLanguages {
|
|
29
44
|
MONGO = "toMongo",
|
|
30
45
|
CQL = "toCql",
|
|
31
46
|
DSL = "toDSL",
|
|
32
|
-
AST = "AST"
|
|
47
|
+
AST = "AST",
|
|
33
48
|
}
|
|
34
|
-
type SupportedLanguagesTypes = `${SupportedLanguages}
|
|
49
|
+
type SupportedLanguagesTypes = `${SupportedLanguages}`;
|
|
35
50
|
interface QueryEventMap {
|
|
36
|
-
|
|
51
|
+
change: (event: CustomEvent<string | kuery.KueryNode | JsonObject>) => void;
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
export interface IQueryProps {
|
|
@@ -52,7 +67,10 @@ export interface IQueryProps {
|
|
|
52
67
|
/**
|
|
53
68
|
* Callback that will provide values for specific fields
|
|
54
69
|
*/
|
|
55
|
-
getValuesForField?: (
|
|
70
|
+
getValuesForField?: (
|
|
71
|
+
field: string,
|
|
72
|
+
text: string
|
|
73
|
+
) => Promise<LabelValuesOrStrings>;
|
|
56
74
|
|
|
57
75
|
/**
|
|
58
76
|
* Input placeholder
|
|
@@ -62,58 +80,81 @@ export interface IQueryProps {
|
|
|
62
80
|
type LabelValue = {
|
|
63
81
|
label?: string;
|
|
64
82
|
value: string;
|
|
65
|
-
}
|
|
83
|
+
};
|
|
84
|
+
export type LabelValueOrString = LabelValue | string;
|
|
85
|
+
export type LabelValuesOrStrings = (LabelValue | string)[];
|
|
66
86
|
type Completion = LabelValue & {
|
|
67
87
|
start: number;
|
|
68
88
|
end: number;
|
|
69
|
-
type: SuggestionType
|
|
70
|
-
onSelect?: () => Promise<string | undefined | number
|
|
71
|
-
}
|
|
89
|
+
type: SuggestionType;
|
|
90
|
+
onSelect?: () => Promise<string | undefined | number | LabelValue>;
|
|
91
|
+
};
|
|
92
|
+
export const toLabelValue = (value: LabelValueOrString) => {
|
|
93
|
+
if (typeof value === "string") {
|
|
94
|
+
return { label: value, value };
|
|
95
|
+
}
|
|
96
|
+
return { label: value.label || value.value, value: value.value };
|
|
97
|
+
};
|
|
98
|
+
|
|
72
99
|
const NumberOperators: Record<string, LabelValue> = {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
100
|
+
eq: { value: " : ", label: " equals some value" },
|
|
101
|
+
gt: { value: " > ", label: " is greater than some value" },
|
|
102
|
+
lt: { value: " < ", label: " is less than some value" },
|
|
103
|
+
gte: { value: " >= ", label: " is greater than or equal to some value" },
|
|
104
|
+
lte: { value: " <= ", label: " is less than or equal to some value" },
|
|
105
|
+
};
|
|
106
|
+
const GeospatialOperators: Record<string, LabelValue> = {
|
|
107
|
+
within: { value: " <@ ", label: " is contained within geometry" },
|
|
108
|
+
};
|
|
79
109
|
const ObjectOperators: Record<string, LabelValue> = {
|
|
80
|
-
|
|
81
|
-
}
|
|
110
|
+
exists: { value: ": *", label: " exists in any form" },
|
|
111
|
+
};
|
|
82
112
|
const StringOperators: Record<string, LabelValue> = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const BoolOperators: LabelValue[] = [
|
|
87
|
-
{ value: ":
|
|
88
|
-
|
|
113
|
+
eq: { value: ": ", label: " equals some value" },
|
|
114
|
+
exists: { value: ": *", label: " exists in any form" },
|
|
115
|
+
};
|
|
116
|
+
const BoolOperators: LabelValue[] = [
|
|
117
|
+
{ value: ": true", label: " value is true" },
|
|
118
|
+
{ value: ": false", label: "value is false" },
|
|
119
|
+
];
|
|
89
120
|
//Date operators are the same as number but we want a string value
|
|
90
|
-
const DateOperators: Record<string, LabelValue> = Object.fromEntries(
|
|
121
|
+
const DateOperators: Record<string, LabelValue> = Object.fromEntries(
|
|
122
|
+
Object.entries(NumberOperators).map(([key, labelValue]) => [
|
|
123
|
+
key,
|
|
124
|
+
{
|
|
125
|
+
value: labelValue.value,
|
|
126
|
+
label: (labelValue.label || "").replace("value", "date-time"),
|
|
127
|
+
},
|
|
128
|
+
])
|
|
129
|
+
);
|
|
91
130
|
/**
|
|
92
131
|
* The Query component will take Opensearch Dashboard Query language and transform it into various outputs
|
|
93
132
|
*/
|
|
94
|
-
@customElement(
|
|
133
|
+
@customElement("spectric-query")
|
|
95
134
|
export class SpectricQuery extends LitElement implements IQueryProps {
|
|
96
135
|
private uuid: string;
|
|
97
136
|
@property({ type: String, reflect: true })
|
|
98
137
|
placeholder: string = "";
|
|
138
|
+
@state()
|
|
139
|
+
valueHelper: any;
|
|
99
140
|
constructor() {
|
|
100
|
-
super()
|
|
101
|
-
this.uuid = crypto.randomUUID()
|
|
141
|
+
super();
|
|
142
|
+
this.uuid = crypto.randomUUID();
|
|
102
143
|
}
|
|
103
144
|
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
104
|
-
return this
|
|
145
|
+
return this;
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
/**
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
protected _value: string =
|
|
149
|
+
* The internal value.
|
|
150
|
+
*/
|
|
151
|
+
protected _value: string = "";
|
|
111
152
|
private suggestion?: Suggestion;
|
|
112
153
|
/**
|
|
113
154
|
* The value of the input.
|
|
114
155
|
*/
|
|
115
156
|
@property({ type: String, reflect: true })
|
|
116
|
-
value = ""
|
|
157
|
+
value = "";
|
|
117
158
|
@property({ type: String, reflect: true })
|
|
118
159
|
outputLanguage: SupportedLanguagesTypes = "AST";
|
|
119
160
|
@state()
|
|
@@ -124,266 +165,472 @@ export class SpectricQuery extends LitElement implements IQueryProps {
|
|
|
124
165
|
fields: FieldTypes[] = [];
|
|
125
166
|
|
|
126
167
|
@query(".autocomplete")
|
|
127
|
-
_autocomplete?: PopoverElement
|
|
168
|
+
_autocomplete?: PopoverElement;
|
|
169
|
+
@query(".valueHelper")
|
|
170
|
+
_valueHelper?: PopoverElement;
|
|
128
171
|
|
|
129
172
|
@queryAsync(".autocomplete")
|
|
130
173
|
//@ts-expect-error
|
|
131
|
-
_asyncAutocomplete: Promise<HTMLDivElement
|
|
174
|
+
_asyncAutocomplete: Promise<HTMLDivElement>;
|
|
132
175
|
|
|
133
176
|
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@query(
|
|
177
|
+
* The underlying input element
|
|
178
|
+
*/
|
|
179
|
+
@query("spectric-input")
|
|
137
180
|
protected _input!: SpectricInput;
|
|
181
|
+
_checkClickLocation = async (e: MouseEvent) => {
|
|
182
|
+
console.log(e);
|
|
183
|
+
let suggestion: Suggestion | null = null;
|
|
184
|
+
try {
|
|
185
|
+
suggestion = this._getSuggestion();
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.log(error);
|
|
188
|
+
}
|
|
189
|
+
if (!suggestion) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this._showValueHelper(suggestion);
|
|
193
|
+
};
|
|
194
|
+
_setValue = (value: string | undefined, suggestion: Suggestion) => {
|
|
195
|
+
if (value !== undefined) {
|
|
196
|
+
this.value =
|
|
197
|
+
this.value.substring(0, suggestion.start) +
|
|
198
|
+
value +
|
|
199
|
+
this.value.substring(suggestion.end);
|
|
200
|
+
|
|
201
|
+
//Important if the underlying inputs value hasn't been set the setSelectionRange will fail to set a range greater than the inputs current value
|
|
202
|
+
this._input.value = this.value;
|
|
203
|
+
this._input.setSelectionRange(
|
|
204
|
+
suggestion.start + value.length,
|
|
205
|
+
suggestion.start + value.length
|
|
206
|
+
);
|
|
207
|
+
if (this._valueHelper) {
|
|
208
|
+
this._valueHelper.hidePopover();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Value helper is a popover above the query bar that will help users set/change/clear the value if it is complex (like date time or geoshape)
|
|
214
|
+
* @param suggestion
|
|
215
|
+
*/
|
|
216
|
+
_showValueHelper = async (suggestion: Suggestion) => {
|
|
217
|
+
this.valueHelper = undefined;
|
|
218
|
+
if (!suggestion || suggestion.start === suggestion.end) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let field = this.fields.find(
|
|
222
|
+
(field) => field.name === suggestion.fieldName
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (!field) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
let values = await this.getValuesForField(
|
|
229
|
+
suggestion.fieldName,
|
|
230
|
+
suggestion.prefix
|
|
231
|
+
);
|
|
232
|
+
if (field.format === "date-time" && !values.length) {
|
|
233
|
+
values = ["now-1m", "now-1d", "now-1M"];
|
|
234
|
+
}
|
|
235
|
+
let modalFunction:
|
|
236
|
+
| ((values: LabelValuesOrStrings) => Promise<string | undefined>)
|
|
237
|
+
| undefined = undefined;
|
|
238
|
+
if (field.format == "date-time") {
|
|
239
|
+
modalFunction = DateTimePopup;
|
|
240
|
+
} else if (field.format == "geojson") {
|
|
241
|
+
modalFunction = GeoJSONPopup;
|
|
242
|
+
}
|
|
243
|
+
if (!this.completions.length && !this._autocomplete?.isOpen()) {
|
|
244
|
+
this.valueHelper = html`<div>
|
|
245
|
+
<spectric-button
|
|
246
|
+
tooltip="Delete Value"
|
|
247
|
+
tooltipPosition="top"
|
|
248
|
+
icon
|
|
249
|
+
size="xsmall"
|
|
250
|
+
@click=${() => {
|
|
251
|
+
this._setValue("", suggestion);
|
|
252
|
+
}}
|
|
253
|
+
>${DELETE}</spectric-button
|
|
254
|
+
>
|
|
255
|
+
${modalFunction
|
|
256
|
+
? html`<spectric-button
|
|
257
|
+
icon
|
|
258
|
+
tooltip="Edit Value"
|
|
259
|
+
tooltipPosition="top"
|
|
260
|
+
size="xsmall"
|
|
261
|
+
@click=${async () => {
|
|
262
|
+
this._setValue(await modalFunction(values), suggestion);
|
|
263
|
+
}}
|
|
264
|
+
>${EDIT}</spectric-button
|
|
265
|
+
>`
|
|
266
|
+
: null}
|
|
267
|
+
</div>`;
|
|
268
|
+
this._valueHelper?.showPopover();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
_getSuggestion = () => {
|
|
272
|
+
let value = this.value;
|
|
273
|
+
if (this._input.selectionStart !== null) {
|
|
274
|
+
value =
|
|
275
|
+
value.substring(0, this._input.selectionStart) +
|
|
276
|
+
"©kuery-cursor©" +
|
|
277
|
+
value.substring(this._input.selectionStart);
|
|
278
|
+
}
|
|
279
|
+
//FIXME: make auto complete work well.
|
|
280
|
+
let suggestion = kuery.parse(value, {
|
|
281
|
+
parseCursor: true,
|
|
282
|
+
cursorSymbol: "©kuery-cursor©",
|
|
283
|
+
allowLeadingWildcards: false,
|
|
284
|
+
}) as unknown as Suggestion;
|
|
285
|
+
this._showValueHelper(suggestion);
|
|
286
|
+
return suggestion;
|
|
287
|
+
};
|
|
138
288
|
_parseQuery = (e: InputEvent | undefined = undefined) => {
|
|
139
289
|
let ast;
|
|
140
290
|
if (e && e?.currentTarget) {
|
|
141
|
-
this.value = (e.currentTarget as HTMLInputElement).value
|
|
291
|
+
this.value = (e.currentTarget as HTMLInputElement).value;
|
|
142
292
|
}
|
|
143
293
|
if (this.value) {
|
|
144
294
|
try {
|
|
145
295
|
if (e && e.data == "(") {
|
|
146
296
|
//Auto close parentheses or parsing and suggestions fail
|
|
147
|
-
this.value = this.value + " )"
|
|
148
|
-
this._input.setSelectionRange(
|
|
297
|
+
this.value = this.value + " )";
|
|
298
|
+
this._input.setSelectionRange(
|
|
299
|
+
this.value.length - 2,
|
|
300
|
+
this.value.length - 2
|
|
301
|
+
);
|
|
149
302
|
}
|
|
150
|
-
let
|
|
151
|
-
|
|
152
|
-
value = value.substring(0, this._input.selectionStart) + "@kuery-cursor@" + value.substring(this._input.selectionStart)
|
|
153
|
-
}
|
|
154
|
-
//FIXME: make auto complete work well.
|
|
155
|
-
let suggestions = kuery.parse(value, { parseCursor: true, cursorSymbol: "@kuery-cursor@", allowLeadingWildcards: false }) as unknown as Suggestion;
|
|
156
|
-
this.autoComplete(suggestions)
|
|
303
|
+
let suggestions = this._getSuggestion();
|
|
304
|
+
this.autoComplete(suggestions);
|
|
157
305
|
} catch (error: any) {
|
|
158
306
|
// this.completions = []
|
|
159
307
|
// this._input.invalid = true;
|
|
160
308
|
// let [expect, _, arrow] = e.message.split("\n")
|
|
161
309
|
// this._input.invalidText = html`  ${arrow} ${expect}`;
|
|
162
|
-
return
|
|
310
|
+
return;
|
|
163
311
|
}
|
|
164
312
|
}
|
|
165
313
|
try {
|
|
166
314
|
ast = kuery.parse(this.value, { allowLeadingWildcards: false });
|
|
167
315
|
} catch (error: any) {
|
|
168
|
-
return
|
|
316
|
+
return;
|
|
169
317
|
}
|
|
170
|
-
let output
|
|
318
|
+
let output;
|
|
171
319
|
if (this.outputLanguage == "AST") {
|
|
172
|
-
output = ast
|
|
320
|
+
output = ast;
|
|
173
321
|
} else {
|
|
174
|
-
output = kuery[this.outputLanguage](ast, this.fields)
|
|
322
|
+
output = kuery[this.outputLanguage](ast, this.fields);
|
|
175
323
|
}
|
|
176
|
-
let event = new CustomEvent("change", { detail: output })
|
|
177
|
-
this.dispatchEvent(event)
|
|
178
|
-
}
|
|
324
|
+
let event = new CustomEvent("change", { detail: output });
|
|
325
|
+
this.dispatchEvent(event);
|
|
326
|
+
};
|
|
179
327
|
|
|
180
|
-
getValuesForField = async (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
328
|
+
getValuesForField = async (
|
|
329
|
+
field: string,
|
|
330
|
+
text: string
|
|
331
|
+
): Promise<LabelValuesOrStrings> => {
|
|
332
|
+
console.log("getValuesForField isn't set no values returned", field, text);
|
|
333
|
+
return [];
|
|
334
|
+
};
|
|
184
335
|
async autoComplete(suggestion: Suggestion) {
|
|
185
|
-
this.completions = []
|
|
336
|
+
this.completions = [];
|
|
186
337
|
if (suggestion.type !== "cursor") {
|
|
187
|
-
return
|
|
338
|
+
return;
|
|
188
339
|
}
|
|
189
|
-
let completions: Completion[] = []
|
|
190
|
-
this.suggestion = suggestion
|
|
191
|
-
let { start, end } = suggestion
|
|
340
|
+
let completions: Completion[] = [];
|
|
341
|
+
this.suggestion = suggestion;
|
|
342
|
+
let { start, end } = suggestion;
|
|
192
343
|
for (let type of suggestion.suggestionTypes) {
|
|
193
|
-
if (type == "conjunction" && suggestion.text.endsWith(
|
|
194
|
-
completions.push(
|
|
344
|
+
if (type == "conjunction" && suggestion.text.endsWith(" ")) {
|
|
345
|
+
completions.push(
|
|
346
|
+
...["and ", "or "].map((value) => ({
|
|
347
|
+
type,
|
|
348
|
+
value,
|
|
349
|
+
start: end,
|
|
350
|
+
end: end,
|
|
351
|
+
}))
|
|
352
|
+
);
|
|
195
353
|
}
|
|
196
354
|
if (type === "field") {
|
|
197
|
-
let fieldCompletions = this.fields
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
355
|
+
let fieldCompletions = this.fields
|
|
356
|
+
.filter(
|
|
357
|
+
(field) =>
|
|
358
|
+
field.name.includes(suggestion.fieldName) ||
|
|
359
|
+
field.name.includes(suggestion.prefix)
|
|
360
|
+
)
|
|
361
|
+
.map((f) => {
|
|
362
|
+
return [{ type, value: f.name, start, end }];
|
|
363
|
+
})
|
|
364
|
+
.flat();
|
|
365
|
+
completions.push(...fieldCompletions);
|
|
201
366
|
}
|
|
202
367
|
if (type === "operator") {
|
|
203
|
-
let fieldType = this.fields.find(
|
|
368
|
+
let fieldType = this.fields.find(
|
|
369
|
+
(field) => field.name === suggestion.fieldName
|
|
370
|
+
);
|
|
204
371
|
if (fieldType) {
|
|
205
372
|
if (["number", "integer"].includes(fieldType.type)) {
|
|
206
|
-
completions.push(
|
|
373
|
+
completions.push(
|
|
374
|
+
...Object.values(NumberOperators).map((value) => ({
|
|
375
|
+
type,
|
|
376
|
+
...value,
|
|
377
|
+
start: end,
|
|
378
|
+
end: end,
|
|
379
|
+
}))
|
|
380
|
+
);
|
|
207
381
|
} else if (fieldType.type === "object") {
|
|
208
|
-
|
|
382
|
+
if (fieldType.format == "geojson") {
|
|
383
|
+
completions.push(
|
|
384
|
+
...Object.values(GeospatialOperators).map((value) => ({
|
|
385
|
+
type,
|
|
386
|
+
...value,
|
|
387
|
+
start: end,
|
|
388
|
+
end: end,
|
|
389
|
+
onSelect: async () => {
|
|
390
|
+
let values = await this.getValuesForField(
|
|
391
|
+
suggestion.fieldName,
|
|
392
|
+
suggestion.prefix
|
|
393
|
+
);
|
|
394
|
+
return await GeoJSONPopup(values);
|
|
395
|
+
},
|
|
396
|
+
}))
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
completions.push(
|
|
400
|
+
...Object.values(ObjectOperators).map((value) => ({
|
|
401
|
+
type,
|
|
402
|
+
...value,
|
|
403
|
+
start: end,
|
|
404
|
+
end: end,
|
|
405
|
+
}))
|
|
406
|
+
);
|
|
209
407
|
} else if (fieldType.type === "string") {
|
|
210
408
|
if (fieldType.format === "date-time") {
|
|
211
|
-
completions.push(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (!e.target) {
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
let date = (new Date(e.target.value + ":00.000Z")).toISOString()
|
|
234
|
-
value = `"${date}"`
|
|
235
|
-
buttonRef.value!.disabled = value === undefined
|
|
236
|
-
}}></spectric-input>
|
|
237
|
-
<spectric-button ${ref(buttonRef)} .disabled=${true} @click=${() => { resolve(value); dialog.open = false }}>Submit</spectric-button>
|
|
238
|
-
`)
|
|
239
|
-
})
|
|
240
|
-
return value
|
|
241
|
-
}
|
|
242
|
-
})))
|
|
409
|
+
completions.push(
|
|
410
|
+
...Object.values(DateOperators).map((value) => ({
|
|
411
|
+
type,
|
|
412
|
+
...value,
|
|
413
|
+
start: end,
|
|
414
|
+
end: end,
|
|
415
|
+
onSelect: async () => {
|
|
416
|
+
let values = await this.getValuesForField(
|
|
417
|
+
suggestion.fieldName,
|
|
418
|
+
suggestion.prefix
|
|
419
|
+
);
|
|
420
|
+
if (values.length === 0) {
|
|
421
|
+
values = ["now-1m", "now-1d", "now-1M"];
|
|
422
|
+
}
|
|
423
|
+
let value = await DateTimePopup(values);
|
|
424
|
+
return value;
|
|
425
|
+
},
|
|
426
|
+
}))
|
|
427
|
+
);
|
|
243
428
|
} else {
|
|
244
|
-
completions.push(
|
|
429
|
+
completions.push(
|
|
430
|
+
...Object.values(StringOperators).map((value) => ({
|
|
431
|
+
type,
|
|
432
|
+
...value,
|
|
433
|
+
start: end,
|
|
434
|
+
end: end,
|
|
435
|
+
}))
|
|
436
|
+
);
|
|
245
437
|
}
|
|
246
438
|
} else if (fieldType.type === "boolean") {
|
|
247
|
-
completions.push(
|
|
439
|
+
completions.push(
|
|
440
|
+
...BoolOperators.map((value) => ({
|
|
441
|
+
type,
|
|
442
|
+
...value,
|
|
443
|
+
start: end,
|
|
444
|
+
end: end,
|
|
445
|
+
}))
|
|
446
|
+
);
|
|
248
447
|
}
|
|
249
448
|
}
|
|
250
449
|
}
|
|
251
450
|
if (type === "value") {
|
|
252
|
-
let fieldType = this.fields.find(
|
|
253
|
-
|
|
254
|
-
|
|
451
|
+
let fieldType = this.fields.find(
|
|
452
|
+
(field) => field.name === suggestion.fieldName
|
|
453
|
+
);
|
|
454
|
+
if (
|
|
455
|
+
(fieldType && fieldType.type == "number") ||
|
|
456
|
+
fieldType?.type == "integer"
|
|
457
|
+
) {
|
|
458
|
+
//Do nothing for numbers... Maybe show a min/max?
|
|
459
|
+
} else if (fieldType && fieldType.type == "boolean") {
|
|
460
|
+
completions.push(
|
|
461
|
+
{ type, value: "true", start, end },
|
|
462
|
+
{ type, value: "false", start, end }
|
|
463
|
+
);
|
|
255
464
|
} else {
|
|
256
|
-
console.log(
|
|
257
|
-
|
|
465
|
+
console.log(
|
|
466
|
+
`invoke callback to get values for ${suggestion.fieldName}`
|
|
467
|
+
);
|
|
468
|
+
let values = await this.getValuesForField(
|
|
469
|
+
suggestion.fieldName,
|
|
470
|
+
suggestion.prefix
|
|
471
|
+
);
|
|
258
472
|
if (fieldType?.type === "string") {
|
|
259
473
|
//quote the values
|
|
260
|
-
values = values.map(v => `"${v}"`)
|
|
474
|
+
values = values.map((v) => `"${v}"`);
|
|
261
475
|
}
|
|
262
|
-
completions.push(
|
|
476
|
+
completions.push(
|
|
477
|
+
...values
|
|
478
|
+
.map(toLabelValue)
|
|
479
|
+
.map((value) => ({ type, value: value.value, start, end }))
|
|
480
|
+
);
|
|
263
481
|
}
|
|
264
|
-
|
|
265
482
|
}
|
|
266
483
|
}
|
|
267
|
-
this.completions = completions
|
|
484
|
+
this.completions = completions;
|
|
268
485
|
if (this.completions.length && this._autocomplete) {
|
|
486
|
+
this._valueHelper?.hidePopover();
|
|
269
487
|
let { width } = this._input.getBoundingClientRect();
|
|
270
|
-
this._autocomplete.maxWidth = width
|
|
488
|
+
this._autocomplete.maxWidth = width;
|
|
271
489
|
this._autocomplete.showPopover();
|
|
272
490
|
|
|
273
491
|
this._autocomplete.style.width = `${width - 15}px`;
|
|
274
|
-
let popover = this._autocomplete.querySelector<HTMLElement>("[popover]")
|
|
492
|
+
let popover = this._autocomplete.querySelector<HTMLElement>("[popover]");
|
|
275
493
|
if (popover) {
|
|
276
494
|
popover.style.width = `${width - 15}px`;
|
|
277
495
|
}
|
|
278
496
|
} else {
|
|
279
|
-
this._autocomplete?.hidePopover()
|
|
497
|
+
this._autocomplete?.hidePopover();
|
|
280
498
|
}
|
|
281
499
|
}
|
|
282
500
|
protected updated(changed: PropertyValues): void {
|
|
283
501
|
if (changed.has("outputLanguage")) {
|
|
284
|
-
this._parseQuery()
|
|
502
|
+
this._parseQuery();
|
|
285
503
|
}
|
|
286
504
|
}
|
|
287
505
|
_selectCompletion = async () => {
|
|
288
506
|
if (!this.suggestion) {
|
|
289
|
-
return
|
|
507
|
+
return;
|
|
290
508
|
}
|
|
291
|
-
let completion = this.completions[this.completionIndex]
|
|
509
|
+
let completion = this.completions[this.completionIndex];
|
|
292
510
|
|
|
293
|
-
let prefix = this.value.substring(0, completion.start) +
|
|
294
|
-
|
|
295
|
-
let
|
|
296
|
-
let afterStart = this.value.substring(completion.end)
|
|
511
|
+
let prefix = this.value.substring(0, completion.start) + completion.value;
|
|
512
|
+
let insertIndex = prefix.length;
|
|
513
|
+
let afterStart = this.value.substring(completion.end);
|
|
297
514
|
if (completion.value.includes(afterStart)) {
|
|
298
|
-
this.value = prefix
|
|
515
|
+
this.value = prefix;
|
|
299
516
|
} else {
|
|
300
|
-
this.value = prefix + this.value.substring(completion.end)
|
|
517
|
+
this.value = prefix + this.value.substring(completion.end);
|
|
301
518
|
}
|
|
302
519
|
//Important if the underlying inputs value hasn't been set the setSelectionRange will fail to set a range greater than the inputs current value
|
|
303
|
-
this._input.value = this.value
|
|
304
|
-
this._input.setSelectionRange(insertIndex, insertIndex)
|
|
520
|
+
this._input.value = this.value;
|
|
521
|
+
this._input.setSelectionRange(insertIndex, insertIndex);
|
|
305
522
|
if (completion.onSelect) {
|
|
306
|
-
let value = await completion.onSelect()
|
|
523
|
+
let value = await completion.onSelect();
|
|
307
524
|
if (value !== undefined) {
|
|
308
|
-
this.value += value
|
|
309
|
-
setTimeout(() => {
|
|
525
|
+
this.value += value;
|
|
526
|
+
setTimeout(() => {
|
|
527
|
+
this._input.focus();
|
|
528
|
+
this._showValueHelper(this._getSuggestion());
|
|
529
|
+
});
|
|
310
530
|
}
|
|
311
531
|
}
|
|
532
|
+
this._autocomplete?.hidePopover();
|
|
312
533
|
this.completionIndex = 0;
|
|
313
|
-
this.completions = []
|
|
314
|
-
this._parseQuery()
|
|
315
|
-
this._input.focus()
|
|
316
|
-
}
|
|
534
|
+
this.completions = [];
|
|
535
|
+
this._parseQuery();
|
|
536
|
+
this._input.focus();
|
|
537
|
+
};
|
|
317
538
|
_handleArrows = (e: KeyboardEvent) => {
|
|
318
539
|
if (e.key === "Escape") {
|
|
319
540
|
this.completions = []; //Escape closes the popover toplayer lets ensure the completions aren't selectable
|
|
320
541
|
}
|
|
321
542
|
if (e.key == "ArrowLeft" || e.key === "ArrowRight") {
|
|
322
|
-
setTimeout(this._parseQuery, 100)
|
|
543
|
+
setTimeout(this._parseQuery, 100);
|
|
323
544
|
}
|
|
324
545
|
if (!this.completions.length) {
|
|
325
|
-
return
|
|
546
|
+
return;
|
|
326
547
|
}
|
|
327
|
-
if (
|
|
548
|
+
if (
|
|
549
|
+
["ArrowUp", "ArrowDown", "Enter", "Tab"].includes(e.key) &&
|
|
550
|
+
this.suggestion
|
|
551
|
+
) {
|
|
328
552
|
e.preventDefault();
|
|
329
553
|
if (e.key === "ArrowDown" || e.key === "Tab") {
|
|
330
554
|
this.completionIndex += 1;
|
|
331
555
|
if (this.completionIndex > this.completions.length - 1) {
|
|
332
|
-
this.completionIndex = 0
|
|
556
|
+
this.completionIndex = 0;
|
|
333
557
|
}
|
|
334
558
|
}
|
|
335
559
|
if (e.key === "ArrowUp") {
|
|
336
560
|
this.completionIndex -= 1;
|
|
337
561
|
if (this.completionIndex < 0) {
|
|
338
|
-
this.completionIndex = this.completions.length - 1
|
|
562
|
+
this.completionIndex = this.completions.length - 1;
|
|
339
563
|
}
|
|
340
564
|
}
|
|
341
565
|
|
|
342
|
-
this._asyncAutocomplete?.then(element => {
|
|
566
|
+
this._asyncAutocomplete?.then((element) => {
|
|
343
567
|
let active = element.querySelector(".option.active");
|
|
344
568
|
if (active) {
|
|
345
|
-
active.scrollIntoView({ block: "nearest" })
|
|
569
|
+
active.scrollIntoView({ block: "nearest" });
|
|
346
570
|
}
|
|
347
|
-
})
|
|
571
|
+
});
|
|
348
572
|
|
|
349
573
|
if (e.key === "Enter") {
|
|
350
|
-
this._selectCompletion()
|
|
574
|
+
this._selectCompletion();
|
|
351
575
|
}
|
|
352
576
|
}
|
|
353
|
-
}
|
|
577
|
+
};
|
|
354
578
|
protected render() {
|
|
355
579
|
return html`
|
|
356
|
-
<spectric-input
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
580
|
+
<spectric-input
|
|
581
|
+
.value=${this.value}
|
|
582
|
+
.placeholder=${this.placeholder}
|
|
583
|
+
style=${`anchor-name:--${this.uuid};`}
|
|
584
|
+
autocomplete="off"
|
|
585
|
+
@input=${this._parseQuery}
|
|
586
|
+
@keydown=${this._handleArrows}
|
|
587
|
+
@change=${(e: Event) => e.stopPropagation()}
|
|
588
|
+
@click=${this._checkClickLocation}
|
|
589
|
+
></spectric-input>
|
|
590
|
+
<spectric-popover
|
|
591
|
+
class="valueHelper"
|
|
592
|
+
.text=${this.valueHelper}
|
|
593
|
+
position="top"
|
|
594
|
+
></spectric-popover>
|
|
595
|
+
<spectric-popover
|
|
596
|
+
position="bottom"
|
|
597
|
+
class="autocomplete"
|
|
598
|
+
.text=${this.completions.map(
|
|
599
|
+
(option: Completion, index) =>
|
|
600
|
+
html`<div
|
|
601
|
+
@click=${() => {
|
|
602
|
+
this.completionIndex = index;
|
|
603
|
+
this._selectCompletion();
|
|
604
|
+
}}
|
|
605
|
+
class=${this.completionIndex == index
|
|
606
|
+
? "option active"
|
|
607
|
+
: "option"}
|
|
608
|
+
>
|
|
609
|
+
<span class="optiontype ${option.type}">${option.type}</span>
|
|
610
|
+
<span class="value">${option.value}</span>
|
|
611
|
+
<span class="label">${option.label}</span>
|
|
612
|
+
</div> `
|
|
613
|
+
)}
|
|
614
|
+
>
|
|
615
|
+
</spectric-popover>
|
|
616
|
+
`;
|
|
373
617
|
}
|
|
374
618
|
}
|
|
375
619
|
|
|
376
|
-
|
|
377
620
|
declare global {
|
|
378
621
|
interface HTMLElementTagNameMap {
|
|
379
|
-
"spectric-query": HTMLElementTagWithEvents<SpectricQuery, QueryEventMap
|
|
622
|
+
"spectric-query": HTMLElementTagWithEvents<SpectricQuery, QueryEventMap>;
|
|
380
623
|
}
|
|
381
624
|
namespace JSX {
|
|
382
625
|
interface IntrinsicElements {
|
|
383
626
|
/**
|
|
384
627
|
* {@link SpectricQuery}
|
|
385
628
|
*/
|
|
386
|
-
"spectric-query": ReactElementWithPropsAndEvents<
|
|
629
|
+
"spectric-query": ReactElementWithPropsAndEvents<
|
|
630
|
+
SpectricQuery,
|
|
631
|
+
IQueryProps,
|
|
632
|
+
QueryEventMap
|
|
633
|
+
>;
|
|
387
634
|
}
|
|
388
635
|
}
|
|
389
636
|
namespace React {
|
|
@@ -392,7 +639,11 @@ declare global {
|
|
|
392
639
|
/**
|
|
393
640
|
* {@link SpectricQuery}
|
|
394
641
|
*/
|
|
395
|
-
"spectric-query": ReactElementWithPropsAndEvents<
|
|
642
|
+
"spectric-query": ReactElementWithPropsAndEvents<
|
|
643
|
+
SpectricQuery,
|
|
644
|
+
IQueryProps,
|
|
645
|
+
QueryEventMap
|
|
646
|
+
>;
|
|
396
647
|
}
|
|
397
648
|
}
|
|
398
649
|
}
|