@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.
Files changed (48) hide show
  1. package/dist/components/Banner.d.ts +1 -1
  2. package/dist/components/dialog/dialog.d.ts +2 -1
  3. package/dist/components/pagination/pagination.d.ts +1 -1
  4. package/dist/components/query_bar/QueryBar.d.ts +31 -11
  5. package/dist/components/query_bar/dateTimePopup.d.ts +2 -0
  6. package/dist/components/query_bar/geojsonPopup.d.ts +2 -0
  7. package/dist/components/query_bar/querylanguage/kuery/functions/geospatial.d.ts +19 -0
  8. package/dist/components/query_bar/querylanguage/outputTypes/toCQL.d.ts +2 -1
  9. package/dist/components/query_bar/querylanguage/outputTypes/toMongo.d.ts +6 -1
  10. package/dist/components/symbols.d.ts +6 -0
  11. package/dist/components/table/cell.d.ts +2 -2
  12. package/dist/components/table/header.d.ts +2 -1
  13. package/dist/components/table/table.d.ts +14 -7
  14. package/dist/custom-elements.json +8 -8
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.es.js +4556 -2834
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +424 -248
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/style.css +1 -1
  21. package/package.json +6 -1
  22. package/src/components/Banner.ts +46 -31
  23. package/src/components/dialog/dialog.css.ts +29 -29
  24. package/src/components/dialog/dialog.ts +165 -135
  25. package/src/components/input.ts +49 -39
  26. package/src/components/pagination/pagination.ts +167 -113
  27. package/src/components/query_bar/QueryBar.ts +441 -185
  28. package/src/components/query_bar/dateTimePopup.ts +54 -0
  29. package/src/components/query_bar/geojsonPopup.ts +44 -0
  30. package/src/components/query_bar/querylanguage/kuery/ast/_generated_/kuery.js +1836 -2745
  31. package/src/components/query_bar/querylanguage/kuery/ast/ast.ts +15 -13
  32. package/src/components/query_bar/querylanguage/kuery/ast/kuery.peg +92 -126
  33. package/src/components/query_bar/querylanguage/kuery/functions/geospatial.ts +25 -0
  34. package/src/components/query_bar/querylanguage/kuery/functions/index.ts +9 -7
  35. package/src/components/query_bar/querylanguage/outputTypes/toCQL.ts +56 -34
  36. package/src/components/query_bar/querylanguage/outputTypes/toMongo.ts +46 -34
  37. package/src/components/symbols.ts +6 -0
  38. package/src/components/table/__tests__/table.spec.ts +143 -55
  39. package/src/components/table/cell.ts +188 -145
  40. package/src/components/table/header.ts +163 -152
  41. package/src/components/table/table.css +4 -2
  42. package/src/components/table/table.ts +415 -262
  43. package/src/components/table/virtualBody.ts +170 -115
  44. package/src/components/tooltip/popover.ts +263 -225
  45. package/src/stories/Dialog.stories.ts +59 -0
  46. package/src/stories/QueryBar.stories.ts +46 -37
  47. package/src/stories/fixtures/data.ts +195 -36
  48. 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 { customElement, property, query, queryAsync, state } from "lit/decorators.js";
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 { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from "../types";
7
- import "./QueryBar.css"
8
- import { DialogElement } from "../dialog";
9
- import { createRef, ref } from "lit/directives/ref.js";
10
- import { SpectricButton } from "../Button";
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 = 'conjunction' | "field" | 'operator' | 'value'
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: (SuggestionType)[]
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
- "change": (event: CustomEvent<string | kuery.KueryNode | JsonObject>) => void;
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?: (field: string, text: string) => Promise<string[]>;
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
- "eq": { value: " : ", label: " equals some value" },
74
- "gt": { value: " > ", label: " is greater than some value" },
75
- "lt": { value: " < ", label: " is less than some value" },
76
- "gte": { value: " >= ", label: " is greater than or equal to some value" },
77
- "lte": { value: " <= ", label: " is less than or equal to some value" }
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
- "eq": { value: ": ", label: " equals some value" },
81
- "exists": { value: ": *", label: " exists in any form" }
82
- }
83
- const BoolOperators: LabelValue[] = [{ value: ": true", label: " value is true" },
84
- { value: ": false", label: "value is false" }
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(Object.entries(NumberOperators).map(([key, labelValue]) => [key, { value: labelValue.value, label: (labelValue.label || "").replace("value", "date-time") }]))
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('spectric-query')
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
- * The internal value.
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
- * The underlying input element
132
- */
133
- @query('spectric-input')
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(this.value.length - 2, this.value.length - 2)
146
- }
147
- let value = this.value;
148
- if (this._input.selectionStart !== null) {
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
- //FIXME: make auto complete work well.
152
- let suggestions = kuery.parse(value, { parseCursor: true, cursorSymbol: "@kuery-cursor@", allowLeadingWildcards: false }) as unknown as Suggestion;
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`&#160;&#160;${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 (field: string, text: string): Promise<string[]> => {
178
- console.log("getValuesForField isn't set no values returned", field, text)
179
- return []
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(...["and ", "or "].map(value => ({ type, value, start: end, end: end })))
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.filter(field => field.name.includes(suggestion.fieldName) || field.name.includes(suggestion.prefix)).map(f => {
195
- return [{ type, value: f.name, start, end }]
196
- }).flat()
197
- completions.push(...fieldCompletions)
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(field => field.name === suggestion.fieldName);
368
+ let fieldType = this.fields.find(
369
+ (field) => field.name === suggestion.fieldName
370
+ );
201
371
  if (fieldType) {
202
- if (fieldType.type === "number") {
203
- completions.push(...Object.values(NumberOperators).map(value => ({ type, ...value, start: end, end: end })))
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(...Object.values(DateOperators).map(value => ({
207
- type, ...value, start: end, end: end, onSelect: async () => {
208
- let values = await this.getValuesForField(suggestion.fieldName, suggestion.prefix);
209
- if (values.length === 0) {
210
- values = ["now-1m", "now-1d", "now-1M",]
211
- }
212
- let value: string | undefined;
213
- let buttonRef = createRef<SpectricButton>()
214
- await new Promise((resolve) => {
215
- let dialog = DialogElement.display({}, html`
216
- <div class="query-bar-date-quick-select">
217
- ${values.map(v => html`<a href="#" @click=${(e: MouseEvent) => {
218
- e.preventDefault()
219
- value = `"${v}"`;
220
- resolve(value); dialog.open = false
221
- }}>${v}</a>`)}
222
- </div>
223
- <spectric-input variant="datetime-local" @change=${(e: any) => {
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(...Object.values(StringOperators).map(value => ({ type, ...value, start: end, end: end })))
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(...BoolOperators.map(value => ({ type, ...value, start: end, end: end })))
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(field => field.name === suggestion.fieldName);
248
- if (fieldType && fieldType.type == "boolean") {
249
- completions.push({ type, value: "true", start, end }, { type, value: "false", start, end })
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(`invoke callback to get values for ${suggestion.fieldName}`)
252
- let values = await this.getValuesForField(suggestion.fieldName, suggestion.prefix);
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(...values.map(value => ({ type, value, start, end })))
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
- completion.value
290
- let insertIndex = prefix.length
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(() => { this._input.focus(); })
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 (["ArrowUp", "ArrowDown", 'Enter', "Tab"].includes(e.key) && this.suggestion) {
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 .value=${this.value} .placeholder=${this.placeholder} style=${`anchor-name:--${this.uuid};`} autocomplete="off" @input=${this._parseQuery} @keydown=${this._handleArrows} @change=${(e: Event) => e.stopPropagation()}></spectric-input>
352
- <spectric-popover position="bottom" class="autocomplete" .text=${this.completions.map((option: Completion, index) =>
353
- html`<div @click=${() => {
354
- this.completionIndex = index;
355
- this._selectCompletion()
356
- }}
357
- class=${this.completionIndex == index ? "option active" : "option"}>
358
- <span class="optiontype ${option.type}">${option.type}</span>
359
- <span class="value">${option.value}</span>
360
- <span class="label">${option.label}</span>
361
- </div>
362
- `
363
- )
364
- }>
365
-
366
- </spectric-popover>
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<SpectricQuery, IQueryProps, QueryEventMap>
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<SpectricQuery, IQueryProps, QueryEventMap>;
642
+ "spectric-query": ReactElementWithPropsAndEvents<
643
+ SpectricQuery,
644
+ IQueryProps,
645
+ QueryEventMap
646
+ >;
391
647
  }
392
648
  }
393
649
  }