@spectric/ui 0.0.20 → 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/Banner.d.ts +1 -1
- package/dist/components/dialog/dialog.d.ts +2 -1
- package/dist/components/pagination/pagination.d.ts +1 -1
- package/dist/components/query_bar/QueryBar.d.ts +31 -11
- 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 +2 -2
- package/dist/components/table/header.d.ts +2 -1
- package/dist/components/table/table.d.ts +14 -7
- package/dist/custom-elements.json +8 -8
- package/dist/index.d.ts +4 -0
- package/dist/index.es.js +4556 -2834
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +424 -248
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +6 -1
- package/src/components/Banner.ts +46 -31
- package/src/components/dialog/dialog.css.ts +29 -29
- package/src/components/dialog/dialog.ts +165 -135
- package/src/components/input.ts +49 -39
- package/src/components/pagination/pagination.ts +167 -113
- package/src/components/query_bar/QueryBar.ts +441 -185
- 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 +143 -55
- package/src/components/table/cell.ts +188 -145
- package/src/components/table/header.ts +163 -152
- package/src/components/table/table.css +4 -2
- package/src/components/table/table.ts +415 -262
- package/src/components/table/virtualBody.ts +170 -115
- 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"
|
|
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,55 +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
|
+
};
|
|
109
|
+
const ObjectOperators: Record<string, LabelValue> = {
|
|
110
|
+
exists: { value: ": *", label: " exists in any form" },
|
|
111
|
+
};
|
|
79
112
|
const StringOperators: Record<string, LabelValue> = {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
const BoolOperators: LabelValue[] = [
|
|
84
|
-
{ value: ":
|
|
85
|
-
|
|
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
|
+
];
|
|
86
120
|
//Date operators are the same as number but we want a string value
|
|
87
|
-
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
|
+
);
|
|
88
130
|
/**
|
|
89
131
|
* The Query component will take Opensearch Dashboard Query language and transform it into various outputs
|
|
90
132
|
*/
|
|
91
|
-
@customElement(
|
|
133
|
+
@customElement("spectric-query")
|
|
92
134
|
export class SpectricQuery extends LitElement implements IQueryProps {
|
|
93
135
|
private uuid: string;
|
|
94
136
|
@property({ type: String, reflect: true })
|
|
95
137
|
placeholder: string = "";
|
|
138
|
+
@state()
|
|
139
|
+
valueHelper: any;
|
|
96
140
|
constructor() {
|
|
97
|
-
super()
|
|
98
|
-
this.uuid = crypto.randomUUID()
|
|
141
|
+
super();
|
|
142
|
+
this.uuid = crypto.randomUUID();
|
|
99
143
|
}
|
|
100
144
|
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
|
101
|
-
return this
|
|
145
|
+
return this;
|
|
102
146
|
}
|
|
103
147
|
|
|
104
148
|
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
protected _value: string =
|
|
149
|
+
* The internal value.
|
|
150
|
+
*/
|
|
151
|
+
protected _value: string = "";
|
|
108
152
|
private suggestion?: Suggestion;
|
|
109
153
|
/**
|
|
110
154
|
* The value of the input.
|
|
111
155
|
*/
|
|
112
156
|
@property({ type: String, reflect: true })
|
|
113
|
-
value = ""
|
|
157
|
+
value = "";
|
|
114
158
|
@property({ type: String, reflect: true })
|
|
115
159
|
outputLanguage: SupportedLanguagesTypes = "AST";
|
|
116
160
|
@state()
|
|
@@ -121,264 +165,472 @@ export class SpectricQuery extends LitElement implements IQueryProps {
|
|
|
121
165
|
fields: FieldTypes[] = [];
|
|
122
166
|
|
|
123
167
|
@query(".autocomplete")
|
|
124
|
-
_autocomplete?: PopoverElement
|
|
168
|
+
_autocomplete?: PopoverElement;
|
|
169
|
+
@query(".valueHelper")
|
|
170
|
+
_valueHelper?: PopoverElement;
|
|
125
171
|
|
|
126
172
|
@queryAsync(".autocomplete")
|
|
127
173
|
//@ts-expect-error
|
|
128
|
-
_asyncAutocomplete: Promise<HTMLDivElement
|
|
174
|
+
_asyncAutocomplete: Promise<HTMLDivElement>;
|
|
129
175
|
|
|
130
176
|
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@query(
|
|
177
|
+
* The underlying input element
|
|
178
|
+
*/
|
|
179
|
+
@query("spectric-input")
|
|
134
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
|
+
};
|
|
135
288
|
_parseQuery = (e: InputEvent | undefined = undefined) => {
|
|
136
289
|
let ast;
|
|
137
290
|
if (e && e?.currentTarget) {
|
|
138
|
-
this.value = (e.currentTarget as HTMLInputElement).value
|
|
291
|
+
this.value = (e.currentTarget as HTMLInputElement).value;
|
|
139
292
|
}
|
|
140
293
|
if (this.value) {
|
|
141
294
|
try {
|
|
142
295
|
if (e && e.data == "(") {
|
|
143
296
|
//Auto close parentheses or parsing and suggestions fail
|
|
144
|
-
this.value = this.value + " )"
|
|
145
|
-
this._input.setSelectionRange(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
value = value.substring(0, this._input.selectionStart) + "@kuery-cursor@" + value.substring(this._input.selectionStart)
|
|
297
|
+
this.value = this.value + " )";
|
|
298
|
+
this._input.setSelectionRange(
|
|
299
|
+
this.value.length - 2,
|
|
300
|
+
this.value.length - 2
|
|
301
|
+
);
|
|
150
302
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.autoComplete(suggestions)
|
|
303
|
+
let suggestions = this._getSuggestion();
|
|
304
|
+
this.autoComplete(suggestions);
|
|
154
305
|
} catch (error: any) {
|
|
155
306
|
// this.completions = []
|
|
156
307
|
// this._input.invalid = true;
|
|
157
308
|
// let [expect, _, arrow] = e.message.split("\n")
|
|
158
309
|
// this._input.invalidText = html`  ${arrow} ${expect}`;
|
|
159
|
-
return
|
|
310
|
+
return;
|
|
160
311
|
}
|
|
161
312
|
}
|
|
162
313
|
try {
|
|
163
314
|
ast = kuery.parse(this.value, { allowLeadingWildcards: false });
|
|
164
315
|
} catch (error: any) {
|
|
165
|
-
return
|
|
316
|
+
return;
|
|
166
317
|
}
|
|
167
|
-
let output
|
|
318
|
+
let output;
|
|
168
319
|
if (this.outputLanguage == "AST") {
|
|
169
|
-
output = ast
|
|
320
|
+
output = ast;
|
|
170
321
|
} else {
|
|
171
|
-
output = kuery[this.outputLanguage](ast, this.fields)
|
|
322
|
+
output = kuery[this.outputLanguage](ast, this.fields);
|
|
172
323
|
}
|
|
173
|
-
let event = new CustomEvent("change", { detail: output })
|
|
174
|
-
this.dispatchEvent(event)
|
|
175
|
-
}
|
|
324
|
+
let event = new CustomEvent("change", { detail: output });
|
|
325
|
+
this.dispatchEvent(event);
|
|
326
|
+
};
|
|
176
327
|
|
|
177
|
-
getValuesForField = async (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
};
|
|
181
335
|
async autoComplete(suggestion: Suggestion) {
|
|
182
|
-
this.completions = []
|
|
336
|
+
this.completions = [];
|
|
183
337
|
if (suggestion.type !== "cursor") {
|
|
184
|
-
return
|
|
338
|
+
return;
|
|
185
339
|
}
|
|
186
|
-
let completions: Completion[] = []
|
|
187
|
-
this.suggestion = suggestion
|
|
188
|
-
let { start, end } = suggestion
|
|
340
|
+
let completions: Completion[] = [];
|
|
341
|
+
this.suggestion = suggestion;
|
|
342
|
+
let { start, end } = suggestion;
|
|
189
343
|
for (let type of suggestion.suggestionTypes) {
|
|
190
|
-
if (type == "conjunction" && suggestion.text.endsWith(
|
|
191
|
-
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
|
+
);
|
|
192
353
|
}
|
|
193
354
|
if (type === "field") {
|
|
194
|
-
let fieldCompletions = this.fields
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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);
|
|
198
366
|
}
|
|
199
367
|
if (type === "operator") {
|
|
200
|
-
let fieldType = this.fields.find(
|
|
368
|
+
let fieldType = this.fields.find(
|
|
369
|
+
(field) => field.name === suggestion.fieldName
|
|
370
|
+
);
|
|
201
371
|
if (fieldType) {
|
|
202
|
-
if (fieldType.type
|
|
203
|
-
completions.push(
|
|
372
|
+
if (["number", "integer"].includes(fieldType.type)) {
|
|
373
|
+
completions.push(
|
|
374
|
+
...Object.values(NumberOperators).map((value) => ({
|
|
375
|
+
type,
|
|
376
|
+
...value,
|
|
377
|
+
start: end,
|
|
378
|
+
end: end,
|
|
379
|
+
}))
|
|
380
|
+
);
|
|
381
|
+
} else if (fieldType.type === "object") {
|
|
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
|
+
);
|
|
204
407
|
} else if (fieldType.type === "string") {
|
|
205
408
|
if (fieldType.format === "date-time") {
|
|
206
|
-
completions.push(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (!e.target) {
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
let date = (new Date(e.target.value + ":00.000Z")).toISOString()
|
|
229
|
-
value = `"${date}"`
|
|
230
|
-
buttonRef.value!.disabled = value === undefined
|
|
231
|
-
}}></spectric-input>
|
|
232
|
-
<spectric-button ${ref(buttonRef)} .disabled=${true} @click=${() => { resolve(value); dialog.open = false }}>Submit</spectric-button>
|
|
233
|
-
`)
|
|
234
|
-
})
|
|
235
|
-
return value
|
|
236
|
-
}
|
|
237
|
-
})))
|
|
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
|
+
);
|
|
238
428
|
} else {
|
|
239
|
-
completions.push(
|
|
429
|
+
completions.push(
|
|
430
|
+
...Object.values(StringOperators).map((value) => ({
|
|
431
|
+
type,
|
|
432
|
+
...value,
|
|
433
|
+
start: end,
|
|
434
|
+
end: end,
|
|
435
|
+
}))
|
|
436
|
+
);
|
|
240
437
|
}
|
|
241
438
|
} else if (fieldType.type === "boolean") {
|
|
242
|
-
completions.push(
|
|
439
|
+
completions.push(
|
|
440
|
+
...BoolOperators.map((value) => ({
|
|
441
|
+
type,
|
|
442
|
+
...value,
|
|
443
|
+
start: end,
|
|
444
|
+
end: end,
|
|
445
|
+
}))
|
|
446
|
+
);
|
|
243
447
|
}
|
|
244
448
|
}
|
|
245
449
|
}
|
|
246
450
|
if (type === "value") {
|
|
247
|
-
let fieldType = this.fields.find(
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
);
|
|
250
464
|
} else {
|
|
251
|
-
console.log(
|
|
252
|
-
|
|
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
|
+
);
|
|
253
472
|
if (fieldType?.type === "string") {
|
|
254
473
|
//quote the values
|
|
255
|
-
values = values.map(v => `"${v}"`)
|
|
474
|
+
values = values.map((v) => `"${v}"`);
|
|
256
475
|
}
|
|
257
|
-
completions.push(
|
|
476
|
+
completions.push(
|
|
477
|
+
...values
|
|
478
|
+
.map(toLabelValue)
|
|
479
|
+
.map((value) => ({ type, value: value.value, start, end }))
|
|
480
|
+
);
|
|
258
481
|
}
|
|
259
|
-
|
|
260
482
|
}
|
|
261
483
|
}
|
|
262
|
-
this.completions = completions
|
|
484
|
+
this.completions = completions;
|
|
263
485
|
if (this.completions.length && this._autocomplete) {
|
|
486
|
+
this._valueHelper?.hidePopover();
|
|
264
487
|
let { width } = this._input.getBoundingClientRect();
|
|
265
|
-
this._autocomplete.maxWidth = width
|
|
488
|
+
this._autocomplete.maxWidth = width;
|
|
266
489
|
this._autocomplete.showPopover();
|
|
267
490
|
|
|
268
491
|
this._autocomplete.style.width = `${width - 15}px`;
|
|
269
|
-
let popover = this._autocomplete.querySelector<HTMLElement>("[popover]")
|
|
492
|
+
let popover = this._autocomplete.querySelector<HTMLElement>("[popover]");
|
|
270
493
|
if (popover) {
|
|
271
494
|
popover.style.width = `${width - 15}px`;
|
|
272
495
|
}
|
|
273
496
|
} else {
|
|
274
|
-
this._autocomplete?.hidePopover()
|
|
497
|
+
this._autocomplete?.hidePopover();
|
|
275
498
|
}
|
|
276
499
|
}
|
|
277
500
|
protected updated(changed: PropertyValues): void {
|
|
278
501
|
if (changed.has("outputLanguage")) {
|
|
279
|
-
this._parseQuery()
|
|
502
|
+
this._parseQuery();
|
|
280
503
|
}
|
|
281
504
|
}
|
|
282
505
|
_selectCompletion = async () => {
|
|
283
506
|
if (!this.suggestion) {
|
|
284
|
-
return
|
|
507
|
+
return;
|
|
285
508
|
}
|
|
286
|
-
let completion = this.completions[this.completionIndex]
|
|
509
|
+
let completion = this.completions[this.completionIndex];
|
|
287
510
|
|
|
288
|
-
let prefix = this.value.substring(0, completion.start) +
|
|
289
|
-
|
|
290
|
-
let
|
|
291
|
-
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);
|
|
292
514
|
if (completion.value.includes(afterStart)) {
|
|
293
|
-
this.value = prefix
|
|
515
|
+
this.value = prefix;
|
|
294
516
|
} else {
|
|
295
|
-
this.value = prefix + this.value.substring(completion.end)
|
|
517
|
+
this.value = prefix + this.value.substring(completion.end);
|
|
296
518
|
}
|
|
297
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
|
|
298
|
-
this._input.value = this.value
|
|
299
|
-
this._input.setSelectionRange(insertIndex, insertIndex)
|
|
520
|
+
this._input.value = this.value;
|
|
521
|
+
this._input.setSelectionRange(insertIndex, insertIndex);
|
|
300
522
|
if (completion.onSelect) {
|
|
301
|
-
let value = await completion.onSelect()
|
|
523
|
+
let value = await completion.onSelect();
|
|
302
524
|
if (value !== undefined) {
|
|
303
|
-
this.value += value
|
|
304
|
-
setTimeout(() => {
|
|
525
|
+
this.value += value;
|
|
526
|
+
setTimeout(() => {
|
|
527
|
+
this._input.focus();
|
|
528
|
+
this._showValueHelper(this._getSuggestion());
|
|
529
|
+
});
|
|
305
530
|
}
|
|
306
531
|
}
|
|
532
|
+
this._autocomplete?.hidePopover();
|
|
307
533
|
this.completionIndex = 0;
|
|
308
|
-
this.completions = []
|
|
309
|
-
this._parseQuery()
|
|
310
|
-
this._input.focus()
|
|
311
|
-
}
|
|
534
|
+
this.completions = [];
|
|
535
|
+
this._parseQuery();
|
|
536
|
+
this._input.focus();
|
|
537
|
+
};
|
|
312
538
|
_handleArrows = (e: KeyboardEvent) => {
|
|
313
539
|
if (e.key === "Escape") {
|
|
314
540
|
this.completions = []; //Escape closes the popover toplayer lets ensure the completions aren't selectable
|
|
315
541
|
}
|
|
316
542
|
if (e.key == "ArrowLeft" || e.key === "ArrowRight") {
|
|
317
|
-
setTimeout(this._parseQuery, 100)
|
|
543
|
+
setTimeout(this._parseQuery, 100);
|
|
318
544
|
}
|
|
319
545
|
if (!this.completions.length) {
|
|
320
|
-
return
|
|
546
|
+
return;
|
|
321
547
|
}
|
|
322
|
-
if (
|
|
548
|
+
if (
|
|
549
|
+
["ArrowUp", "ArrowDown", "Enter", "Tab"].includes(e.key) &&
|
|
550
|
+
this.suggestion
|
|
551
|
+
) {
|
|
323
552
|
e.preventDefault();
|
|
324
553
|
if (e.key === "ArrowDown" || e.key === "Tab") {
|
|
325
554
|
this.completionIndex += 1;
|
|
326
555
|
if (this.completionIndex > this.completions.length - 1) {
|
|
327
|
-
this.completionIndex = 0
|
|
556
|
+
this.completionIndex = 0;
|
|
328
557
|
}
|
|
329
558
|
}
|
|
330
559
|
if (e.key === "ArrowUp") {
|
|
331
560
|
this.completionIndex -= 1;
|
|
332
561
|
if (this.completionIndex < 0) {
|
|
333
|
-
this.completionIndex = this.completions.length - 1
|
|
562
|
+
this.completionIndex = this.completions.length - 1;
|
|
334
563
|
}
|
|
335
564
|
}
|
|
336
565
|
|
|
337
|
-
this._asyncAutocomplete?.then(element => {
|
|
566
|
+
this._asyncAutocomplete?.then((element) => {
|
|
338
567
|
let active = element.querySelector(".option.active");
|
|
339
568
|
if (active) {
|
|
340
|
-
active.scrollIntoView({ block: "nearest" })
|
|
569
|
+
active.scrollIntoView({ block: "nearest" });
|
|
341
570
|
}
|
|
342
|
-
})
|
|
571
|
+
});
|
|
343
572
|
|
|
344
573
|
if (e.key === "Enter") {
|
|
345
|
-
this._selectCompletion()
|
|
574
|
+
this._selectCompletion();
|
|
346
575
|
}
|
|
347
576
|
}
|
|
348
|
-
}
|
|
577
|
+
};
|
|
349
578
|
protected render() {
|
|
350
579
|
return html`
|
|
351
|
-
<spectric-input
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
this.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
+
`;
|
|
368
617
|
}
|
|
369
618
|
}
|
|
370
619
|
|
|
371
|
-
|
|
372
620
|
declare global {
|
|
373
621
|
interface HTMLElementTagNameMap {
|
|
374
|
-
"spectric-query": HTMLElementTagWithEvents<SpectricQuery, QueryEventMap
|
|
622
|
+
"spectric-query": HTMLElementTagWithEvents<SpectricQuery, QueryEventMap>;
|
|
375
623
|
}
|
|
376
624
|
namespace JSX {
|
|
377
625
|
interface IntrinsicElements {
|
|
378
626
|
/**
|
|
379
627
|
* {@link SpectricQuery}
|
|
380
628
|
*/
|
|
381
|
-
"spectric-query": ReactElementWithPropsAndEvents<
|
|
629
|
+
"spectric-query": ReactElementWithPropsAndEvents<
|
|
630
|
+
SpectricQuery,
|
|
631
|
+
IQueryProps,
|
|
632
|
+
QueryEventMap
|
|
633
|
+
>;
|
|
382
634
|
}
|
|
383
635
|
}
|
|
384
636
|
namespace React {
|
|
@@ -387,7 +639,11 @@ declare global {
|
|
|
387
639
|
/**
|
|
388
640
|
* {@link SpectricQuery}
|
|
389
641
|
*/
|
|
390
|
-
"spectric-query": ReactElementWithPropsAndEvents<
|
|
642
|
+
"spectric-query": ReactElementWithPropsAndEvents<
|
|
643
|
+
SpectricQuery,
|
|
644
|
+
IQueryProps,
|
|
645
|
+
QueryEventMap
|
|
646
|
+
>;
|
|
391
647
|
}
|
|
392
648
|
}
|
|
393
649
|
}
|