@spectric/ui 0.0.21 → 0.0.23

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 (46) hide show
  1. package/dist/components/dialog/dialog.d.ts +1 -0
  2. package/dist/components/pagination/pagination.d.ts +1 -1
  3. package/dist/components/query_bar/QueryBar.d.ts +30 -10
  4. package/dist/components/query_bar/dateTimePopup.d.ts +2 -0
  5. package/dist/components/query_bar/geojsonPopup.d.ts +2 -0
  6. package/dist/components/query_bar/querylanguage/kuery/functions/geospatial.d.ts +19 -0
  7. package/dist/components/query_bar/querylanguage/outputTypes/toCQL.d.ts +2 -1
  8. package/dist/components/query_bar/querylanguage/outputTypes/toMongo.d.ts +6 -1
  9. package/dist/components/symbols.d.ts +6 -0
  10. package/dist/components/table/cell.d.ts +1 -1
  11. package/dist/components/table/table.d.ts +14 -1
  12. package/dist/custom-elements.json +6 -6
  13. package/dist/index.d.ts +4 -0
  14. package/dist/index.es.js +4405 -2803
  15. package/dist/index.es.js.map +1 -1
  16. package/dist/index.umd.js +361 -252
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/style.css +1 -1
  19. package/package.json +6 -1
  20. package/src/components/dialog/dialog.css.ts +29 -29
  21. package/src/components/dialog/dialog.ts +3 -1
  22. package/src/components/input.css +5 -0
  23. package/src/components/input.ts +50 -41
  24. package/src/components/pagination/pagination.ts +167 -113
  25. package/src/components/query_bar/QueryBar.ts +438 -187
  26. package/src/components/query_bar/dateTimePopup.ts +54 -0
  27. package/src/components/query_bar/geojsonPopup.ts +44 -0
  28. package/src/components/query_bar/querylanguage/kuery/ast/_generated_/kuery.js +1836 -2745
  29. package/src/components/query_bar/querylanguage/kuery/ast/ast.ts +15 -13
  30. package/src/components/query_bar/querylanguage/kuery/ast/kuery.peg +92 -126
  31. package/src/components/query_bar/querylanguage/kuery/functions/geospatial.ts +25 -0
  32. package/src/components/query_bar/querylanguage/kuery/functions/index.ts +9 -7
  33. package/src/components/query_bar/querylanguage/outputTypes/toCQL.ts +56 -34
  34. package/src/components/query_bar/querylanguage/outputTypes/toMongo.ts +46 -34
  35. package/src/components/symbols.ts +6 -0
  36. package/src/components/table/__tests__/table.spec.ts +2 -2
  37. package/src/components/table/cell.ts +28 -11
  38. package/src/components/table/header.ts +3 -2
  39. package/src/components/table/table.css +11 -4
  40. package/src/components/table/table.ts +75 -5
  41. package/src/components/table/virtualBody.ts +8 -3
  42. package/src/components/tooltip/popover.ts +263 -225
  43. package/src/stories/Dialog.stories.ts +59 -0
  44. package/src/stories/QueryBar.stories.ts +46 -37
  45. package/src/stories/fixtures/data.ts +229 -37
  46. package/src/stories/table.stories.ts +77 -29
@@ -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" | "integer" | "object"
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,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
- "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
+ };
79
109
  const ObjectOperators: Record<string, LabelValue> = {
80
- "exists": { value: ": *", label: " exists in any form" }
81
- }
110
+ exists: { value: ": *", label: " exists in any form" },
111
+ };
82
112
  const StringOperators: Record<string, LabelValue> = {
83
- "eq": { value: ": ", label: " equals some value" },
84
- "exists": { value: ": *", label: " exists in any form" }
85
- }
86
- const BoolOperators: LabelValue[] = [{ value: ": true", label: " value is true" },
87
- { value: ": false", label: "value is false" }
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(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
+ );
91
130
  /**
92
131
  * The Query component will take Opensearch Dashboard Query language and transform it into various outputs
93
132
  */
94
- @customElement('spectric-query')
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
- * The internal value.
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
- * The underlying input element
135
- */
136
- @query('spectric-input')
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(this.value.length - 2, this.value.length - 2)
297
+ this.value = this.value + " )";
298
+ this._input.setSelectionRange(
299
+ this.value.length - 2,
300
+ this.value.length - 2
301
+ );
149
302
  }
150
- let value = this.value;
151
- if (this._input.selectionStart !== null) {
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`&#160;&#160;${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 (field: string, text: string): Promise<string[]> => {
181
- console.log("getValuesForField isn't set no values returned", field, text)
182
- return []
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(...["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
+ );
195
353
  }
196
354
  if (type === "field") {
197
- let fieldCompletions = this.fields.filter(field => field.name.includes(suggestion.fieldName) || field.name.includes(suggestion.prefix)).map(f => {
198
- return [{ type, value: f.name, start, end }]
199
- }).flat()
200
- 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);
201
366
  }
202
367
  if (type === "operator") {
203
- let fieldType = this.fields.find(field => field.name === suggestion.fieldName);
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(...Object.values(NumberOperators).map(value => ({ type, ...value, start: end, end: end })))
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
- completions.push(...Object.values(ObjectOperators).map(value => ({ type, ...value, start: end, end: end })))
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(...Object.values(DateOperators).map(value => ({
212
- type, ...value, start: end, end: end, onSelect: async () => {
213
- let values = await this.getValuesForField(suggestion.fieldName, suggestion.prefix);
214
- if (values.length === 0) {
215
- values = ["now-1m", "now-1d", "now-1M",]
216
- }
217
- let value: string | undefined;
218
- let buttonRef = createRef<SpectricButton>()
219
- await new Promise((resolve) => {
220
- let dialog = DialogElement.display({}, html`
221
- <div class="query-bar-date-quick-select">
222
- ${values.map(v => html`<a href="#" @click=${(e: MouseEvent) => {
223
- e.preventDefault()
224
- value = `"${v}"`;
225
- resolve(value); dialog.open = false
226
- }}>${v}</a>`)}
227
- </div>
228
- <spectric-input variant="datetime-local" @change=${(e: any) => {
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(...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
+ );
245
437
  }
246
438
  } else if (fieldType.type === "boolean") {
247
- 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
+ );
248
447
  }
249
448
  }
250
449
  }
251
450
  if (type === "value") {
252
- let fieldType = this.fields.find(field => field.name === suggestion.fieldName);
253
- if (fieldType && fieldType.type == "boolean") {
254
- 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
+ );
255
464
  } else {
256
- console.log(`invoke callback to get values for ${suggestion.fieldName}`)
257
- 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
+ );
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(...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
+ );
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
- completion.value
295
- let insertIndex = prefix.length
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(() => { this._input.focus(); })
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 (["ArrowUp", "ArrowDown", 'Enter', "Tab"].includes(e.key) && this.suggestion) {
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 .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>
357
- <spectric-popover position="bottom" class="autocomplete" .text=${this.completions.map((option: Completion, index) =>
358
- html`<div @click=${() => {
359
- this.completionIndex = index;
360
- this._selectCompletion()
361
- }}
362
- class=${this.completionIndex == index ? "option active" : "option"}>
363
- <span class="optiontype ${option.type}">${option.type}</span>
364
- <span class="value">${option.value}</span>
365
- <span class="label">${option.label}</span>
366
- </div>
367
- `
368
- )
369
- }>
370
-
371
- </spectric-popover>
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<SpectricQuery, IQueryProps, QueryEventMap>
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<SpectricQuery, IQueryProps, QueryEventMap>;
642
+ "spectric-query": ReactElementWithPropsAndEvents<
643
+ SpectricQuery,
644
+ IQueryProps,
645
+ QueryEventMap
646
+ >;
396
647
  }
397
648
  }
398
649
  }