@dataloop-ai/components 0.18.65 → 0.18.66

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.
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div
3
3
  :id="`DlSmartSearchInput${uuid}`"
4
- class="dl-smart-search-input"
5
4
  :style="cssVars"
5
+ class="dl-smart-search-input"
6
6
  >
7
7
  <div class="dl-smart-search-input__search-bar-wrapper">
8
8
  <div
@@ -11,7 +11,7 @@
11
11
  >
12
12
  <div class="dl-smart-search-input__status-icon-wrapper">
13
13
  <dl-icon
14
- v-if="!focused && (withSearchIcon || status)"
14
+ v-if="!focused && computedStatus"
15
15
  :icon="statusIcon"
16
16
  :color="statusIconColor"
17
17
  size="16px"
@@ -26,15 +26,15 @@
26
26
  :style="textareaStyles"
27
27
  :placeholder="placeholder"
28
28
  :contenteditable="!disabled"
29
- @keypress="keyPress"
30
- @input="handleValueChange"
29
+ @keypress="onKeyPress"
30
+ @input="onInput"
31
31
  @click.stop.prevent="focus"
32
32
  @blur="blur"
33
33
  />
34
34
  </div>
35
35
  <div class="dl-smart-search-input__toolbar">
36
36
  <div
37
- v-if="withClearBtn && modelValue"
37
+ v-if="showClearButton && modelValue"
38
38
  class="dl-smart-search-input__clear-btn-wrapper"
39
39
  >
40
40
  <dl-button
@@ -42,55 +42,10 @@
42
42
  size="12px"
43
43
  flat
44
44
  :disabled="disabled"
45
- @mousedown="clearValue"
45
+ @mousedown="onClear"
46
46
  />
47
47
  <dl-tooltip> Clear Query </dl-tooltip>
48
48
  </div>
49
- <div class="dl-smart-search-input__toolbar--right">
50
- <div
51
- v-if="withScreenButton"
52
- class="dl-smart-search-input__screen-btn-wrapper"
53
- >
54
- <dl-button
55
- :icon="screenIcon"
56
- size="16px"
57
- flat
58
- :disabled="disabled"
59
- @mousedown="handleScreenButtonClick"
60
- />
61
- <dl-tooltip>
62
- {{ expanded ? 'Collapse' : 'Expand' }} Smart
63
- Search
64
- </dl-tooltip>
65
- </div>
66
- <div
67
- v-if="withSaveButton"
68
- class="dl-smart-search-input__save-btn-wrapper"
69
- >
70
- <div>
71
- <dl-button
72
- icon="icon-dl-save"
73
- size="16px"
74
- flat
75
- :disabled="saveStatus"
76
- @click="save"
77
- />
78
- <dl-tooltip> Save Query </dl-tooltip>
79
- </div>
80
- <dl-button
81
- icon="icon-dl-edit"
82
- size="16px"
83
- flat
84
- transform="none"
85
- text-color="dl-color-darker"
86
- uppercase
87
- label="DQL"
88
- @click="edit"
89
- >
90
- <dl-tooltip> Switch to DQL </dl-tooltip>
91
- </dl-button>
92
- </div>
93
- </div>
94
49
  </div>
95
50
  </div>
96
51
  <label
@@ -100,59 +55,79 @@
100
55
  :style="labelStyles"
101
56
  >{{ status ? status.message : null }}</label>
102
57
  </div>
103
- <div :class="messageClasses">
104
- {{ message }}
105
- </div>
106
- <dl-suggestions-dropdown
107
- v-model="suggestionModal"
58
+ <suggestions-dropdown
59
+ v-model="showSuggestions"
108
60
  :parent-id="`${uuid}`"
109
61
  :disabled="disabled"
110
62
  :suggestions="suggestions"
111
63
  :offset="menuOffset"
112
64
  :expanded="expanded"
113
- @set-input-value="setInputValue"
65
+ @set-input-value="setInputFromSuggestion"
114
66
  />
115
67
  <dl-menu
116
- v-if="isDatePickerVisible && focused"
117
- v-model="isDatePickerVisible"
68
+ v-if="showDatePicker && focused"
69
+ v-model="showDatePicker"
118
70
  :disabled="disabled"
119
71
  :offset="[0, 3]"
120
72
  >
121
73
  <div class="dl-smart-search-input__date-picker-wrapper">
122
74
  <dl-date-picker
123
75
  :single-selection="true"
124
- @change="handleDateSelectionUpdate"
76
+ @change="onDateSelection"
125
77
  />
126
78
  </div>
127
79
  </dl-menu>
128
80
  </div>
129
81
  </template>
130
82
  <script lang="ts">
131
- import { defineComponent, ref, PropType, nextTick } from 'vue-demi'
83
+ import {
84
+ defineComponent,
85
+ ref,
86
+ PropType,
87
+ nextTick,
88
+ toRefs,
89
+ computed,
90
+ watch,
91
+ onMounted,
92
+ onBeforeUnmount
93
+ } from 'vue-demi'
132
94
  import { DlButton } from '../../../../basic'
133
95
  import { DlDatePicker } from '../../../DlDateTime'
134
96
  import { DlMenu, DlIcon } from '../../../../essential'
135
97
  import { isEllipsisActive } from '../../../../../utils/is-ellipsis-active'
136
98
  import { useSizeObserver } from '../../../../../hooks/use-size-observer'
137
- import { SearchStatus, SyntaxColorSchema } from '../types'
138
- import { debounce } from 'lodash'
99
+ import { ColorSchema, SearchStatus, SyntaxColorSchema } from '../types'
100
+ import { debounce, isEqual } from 'lodash'
139
101
  import { DlTooltip } from '../../../../shared'
140
- import DlSuggestionsDropdown from './DlSuggestionsDropdown.vue'
102
+ import SuggestionsDropdown from './SuggestionsDropdown.vue'
141
103
  import { DateInterval } from '../../../DlDateTime/types'
142
104
  import {
143
105
  isEndingWithDateIntervalPattern,
144
106
  replaceDateInterval,
145
107
  setCaret,
146
108
  updateEditor,
147
- isEligibleToChange
109
+ isEligibleToChange,
110
+ createColorSchema,
111
+ restoreCursorPosition,
112
+ replaceJSDatesWithStringifiedDates,
113
+ replaceStringifiedDatesWithJSDates,
114
+ setAliases,
115
+ revertAliases
148
116
  } from '../utils'
149
117
  import { v4 } from 'uuid'
118
+ import {
119
+ Schema,
120
+ Alias,
121
+ useSuggestions,
122
+ removeBrackets
123
+ } from '../../../../../hooks/use-suggestions'
124
+ import { parseSmartQuery, stringifySmartQuery } from '../../../../../utils'
150
125
 
151
126
  export default defineComponent({
152
127
  components: {
153
128
  DlIcon,
154
129
  DlButton,
155
- DlSuggestionsDropdown,
130
+ SuggestionsDropdown,
156
131
  DlTooltip,
157
132
  DlDatePicker,
158
133
  DlMenu
@@ -162,183 +137,321 @@ export default defineComponent({
162
137
  event: 'update:model-value'
163
138
  },
164
139
  props: {
140
+ modelValue: {
141
+ type: Object as PropType<Record<string, any>>,
142
+ default: () => ({})
143
+ },
144
+ schema: {
145
+ type: Object as PropType<Schema>,
146
+ default: () => ({})
147
+ },
148
+ aliases: {
149
+ type: Array as PropType<Alias[]>,
150
+ default: () => [] as Alias[]
151
+ },
152
+ colorSchema: {
153
+ type: Object as PropType<ColorSchema>,
154
+ default: () => ({
155
+ fields: 'var(--dl-color-secondary)',
156
+ operators: 'var(--dl-color-positive)',
157
+ keywords: 'var(--dl-color-medium)'
158
+ })
159
+ },
165
160
  status: {
166
161
  type: Object as PropType<SearchStatus>,
167
162
  default: () => ({ type: 'info', message: '' })
168
163
  },
169
- styleModel: {
170
- type: Object as PropType<SyntaxColorSchema>,
171
- default: null
172
- },
173
164
  placeholder: {
174
165
  type: String,
175
166
  default: ''
176
167
  },
177
- suggestions: {
178
- type: Array as PropType<string[]>,
179
- default: () => [] as string[]
180
- },
181
- inputHeight: {
182
- type: String,
183
- default: '26px'
184
- },
185
- expandedInputHeight: {
186
- type: String,
187
- default: '327px'
188
- },
189
- modelValue: {
190
- type: String,
191
- default: ''
192
- },
193
- withSearchIcon: {
194
- type: Boolean,
195
- default: false
196
- },
197
- withScreenButton: {
198
- type: Boolean,
199
- default: false
200
- },
201
- withSaveButton: {
202
- type: Boolean,
203
- default: false
204
- },
205
- withDQLButton: {
168
+ clearButton: {
206
169
  type: Boolean,
207
170
  default: true
208
171
  },
209
- message: {
210
- type: String,
211
- default: ''
212
- },
213
172
  disabled: {
214
173
  type: Boolean,
215
174
  default: false
216
175
  },
217
- searchBarWidth: {
176
+ width: {
218
177
  type: String,
219
- default: 'auto'
178
+ default: '250px'
220
179
  },
221
- defaultWidth: {
180
+ height: {
222
181
  type: String,
223
- default: '450px'
182
+ default: '28px'
183
+ },
184
+
185
+ strict: {
186
+ type: Boolean,
187
+ default: false
224
188
  }
225
189
  },
226
- emits: [
227
- 'update:model-value',
228
- 'update:expanded',
229
- 'save',
230
- 'search',
231
- 'filter',
232
- 'dql-edit',
233
- 'focus'
234
- ],
190
+ emits: ['update:model-value', 'focus', 'blur', 'input'],
235
191
  setup(props, { emit }) {
236
- const input = ref(null)
237
- const label = ref(null)
238
- const styledTexarea = ref(null)
239
- const styledInput = ref(null)
240
-
192
+ //#region refs
193
+ const input = ref<HTMLInputElement>(null)
194
+ const label = ref<HTMLLabelElement>(null)
195
+ const searchBar = ref<HTMLDivElement>(null)
196
+ //#endregion
197
+
198
+ //#region props
199
+ const {
200
+ modelValue,
201
+ colorSchema,
202
+ aliases,
203
+ strict,
204
+ status,
205
+ disabled,
206
+ schema,
207
+ height,
208
+ width
209
+ } = toRefs(props)
210
+ //#endregion
211
+
212
+ //#region data
213
+ const searchQuery = ref<string>(stringifySmartQuery(modelValue.value))
241
214
  const focused = ref(false)
242
- const isOverflow = ref(false)
215
+ const isOverflowing = ref(false)
243
216
  const isTyping = ref(false)
244
217
  const scroll = ref(false)
245
-
246
- const { hasEllipsis } = useSizeObserver(input)
247
-
248
- const suggestionModal = ref(false)
218
+ const showSuggestions = ref(false)
249
219
  const menuOffset = ref([0, 5])
250
220
  const cancelBlur = ref(0)
251
- const expanded = ref(false)
252
-
221
+ const expanded = ref(true)
253
222
  const datePickerSelection = ref(null)
254
- const isDatePickerVisible = ref(false)
223
+ const showDatePicker = ref(false)
224
+ //#endregion
225
+
226
+ //#region hooks
227
+ const { hasEllipsis } = useSizeObserver(input)
228
+ const { suggestions, error, findSuggestions } = useSuggestions(
229
+ props.schema,
230
+ props.aliases,
231
+ { strict }
232
+ )
233
+ //#endregion
255
234
 
235
+ //#region methods
256
236
  const setInputValue = (value: string) => {
257
- const query = props.modelValue
258
- .split(' ')
259
- .map((string) => string.trim())
237
+ showSuggestions.value = false
260
238
 
261
- suggestionModal.value = false
239
+ // to handle date suggestion modal to open automatically.
240
+ if (value.includes('(dd/mm/yyyy)')) {
241
+ value = value.trimEnd()
242
+ }
262
243
 
263
- let stringValue = ''
244
+ const specialSuggestions = suggestions.value.filter((suggestion) =>
245
+ suggestion.startsWith('.')
246
+ )
247
+
248
+ for (const suggestion of specialSuggestions) {
249
+ if (value.includes(suggestion)) {
250
+ value = value.replace(` ${suggestion}`, suggestion)
251
+ }
252
+ }
253
+
254
+ searchQuery.value = value
255
+ input.value.innerHTML = value
256
+
257
+ // const resetCursor = restoreCursorPosition(input.value)
258
+ updateEditor(input.value, editorStyle.value)
259
+ setMenuOffset(isEligibleToChange(input.value, expanded.value))
260
+ // resetCursor()
261
+
262
+ setCaret(input.value)
263
+
264
+ if (!expanded.value) {
265
+ isOverflowing.value =
266
+ isEllipsisActive(input.value) || hasEllipsis.value
267
+ }
264
268
 
265
- if (query.length > 1) {
266
- if (query[query.length - 1] === '') {
267
- stringValue = [...query, value, '']
268
- .join(' ')
269
- .replace(' ', ' ')
269
+ if (value.length && isEndingWithDateIntervalPattern(value)) {
270
+ showDatePicker.value = true
271
+ showSuggestions.value = false
272
+ } else {
273
+ showDatePicker.value = false
274
+ datePickerSelection.value = null
275
+ showSuggestions.value = true
276
+ }
277
+ scroll.value = input.value.offsetHeight > 40
278
+
279
+ nextTick(() => {
280
+ findSuggestions(value)
281
+ })
282
+
283
+ emit('input', value)
284
+ }
285
+
286
+ const setInputFromSuggestion = (value: string) => {
287
+ let stringValue = ''
288
+ if (searchQuery.value.length) {
289
+ const query = searchQuery.value
290
+ .replace(new RegExp(' ', 'g'), ' ')
291
+ .split(' ')
292
+ .map((string: string) => string.trim())
293
+ .filter((string: string) => !!string.length)
294
+
295
+ if (query.length > 1) {
296
+ if (query[query.length - 1] === '') {
297
+ stringValue = [...query, value, '']
298
+ .join(' ')
299
+ .replace(' ', ' ')
300
+ } else {
301
+ if (query[query.length - 1].endsWith('.')) {
302
+ query[query.length - 1] = query[
303
+ query.length - 1
304
+ ].replace('.', '')
305
+ }
306
+ stringValue = [...query, value, ''].join(' ')
307
+ }
270
308
  } else {
271
309
  if (query[query.length - 1].endsWith('.')) {
272
310
  query[query.length - 1] = query[
273
311
  query.length - 1
274
312
  ].replace('.', '')
275
- } else {
276
- query.splice(-1)
277
313
  }
278
314
  stringValue = [...query, value, ''].join(' ')
279
315
  }
280
316
  } else {
281
- if (query[query.length - 1].endsWith('.')) {
282
- query[query.length - 1] = query[query.length - 1].replace(
283
- '.',
284
- ''
285
- )
286
- stringValue = [...query, value, ''].join(' ')
287
- } else {
288
- stringValue = [value, ''].join(' ')
317
+ stringValue = value + ' '
318
+ }
319
+
320
+ setInputValue(stringValue)
321
+ }
322
+
323
+ const debouncedSetInputValue = debounce(setInputValue, 300)
324
+
325
+ const updateJSONQuery = () => {
326
+ const bracketless = removeBrackets(searchQuery.value)
327
+ const cleanedAliases = revertAliases(bracketless, aliases.value)
328
+ const json = toJSON(cleanedAliases)
329
+ if (!isEqual(json, modelValue.value)) {
330
+ emit('update:model-value', json)
331
+ }
332
+ }
333
+
334
+ const setInputFromModel = (value: string) => {
335
+ searchQuery.value = value
336
+ input.value.innerHTML = value
337
+ }
338
+
339
+ const debouncedSetInputFromModel = debounce(setInputFromModel, 300)
340
+
341
+ const setMenuOffset = (value: number[]) => {
342
+ menuOffset.value = value
343
+ }
344
+
345
+ const focus = () => {
346
+ if (disabled.value) {
347
+ return
348
+ }
349
+
350
+ input.value.scrollTo(0, input.value.scrollHeight)
351
+ input.value.scrollLeft = input.value.scrollWidth
352
+
353
+ input.value.focus()
354
+
355
+ focused.value = true
356
+ emit('focus')
357
+ }
358
+
359
+ const blur = () => {
360
+ if (showDatePicker.value) {
361
+ return
362
+ }
363
+
364
+ if (cancelBlur.value === 0) {
365
+ if (showSuggestions.value) {
366
+ focused.value = true
367
+ return
289
368
  }
369
+
370
+ input.value.scrollLeft = 0
371
+ input.value.scrollTop = 0
372
+ focused.value = false
373
+ expanded.value = true
374
+ updateJSONQuery()
375
+ emit('blur')
376
+ } else {
377
+ focus()
378
+ cancelBlur.value = cancelBlur.value - 1
290
379
  }
380
+ }
291
381
 
292
- // to handle date suggestion modal to open automatically.
293
- if (stringValue.includes('(dd/mm/yyyy)')) {
294
- stringValue = stringValue.trimEnd()
382
+ const onClear = () => {
383
+ cancelBlur.value = cancelBlur.value === 0 ? 1 : cancelBlur.value
384
+ searchQuery.value = ''
385
+ input.value.innerHTML = ''
386
+ if (!focused.value) {
387
+ focus()
295
388
  }
389
+ }
296
390
 
297
- const specialSuggestions = props.suggestions.filter((suggestion) =>
298
- suggestion.startsWith('.')
299
- )
391
+ const onKeyPress = (e: KeyboardEvent) => {
392
+ if (e.key === 'Enter') {
393
+ e.preventDefault()
394
+ }
395
+ }
300
396
 
301
- for (const suggestion of specialSuggestions) {
302
- if (stringValue.includes(suggestion)) {
303
- stringValue = stringValue.replace(
304
- ` ${suggestion}`,
305
- suggestion
306
- )
397
+ const onInput = (e: Event) => {
398
+ const text = (e.target as HTMLElement).textContent
399
+ debouncedSetInputValue(text)
400
+ }
401
+
402
+ const onDateSelection = (value: DateInterval) => {
403
+ datePickerSelection.value = value
404
+ searchQuery.value = replaceDateInterval(searchQuery.value, value)
405
+ input.value.innerHTML = searchQuery.value
406
+ }
407
+
408
+ const readModelValue = (val: { [key: string]: any }) => {
409
+ if (val) {
410
+ const aliased = fromJSON(val)
411
+
412
+ if (aliased !== searchQuery.value.trim()) {
413
+ debouncedSetInputFromModel(aliased)
307
414
  }
308
415
  }
416
+ }
417
+
418
+ const isValidJSON = (item: string | Object): boolean => {
419
+ let value = typeof item !== 'string' ? JSON.stringify(item) : item
420
+ try {
421
+ value = JSON.parse(value)
422
+ } catch (e) {
423
+ return false
424
+ }
309
425
 
310
- emit('update:model-value', stringValue)
426
+ return typeof value === 'object' && value !== null
311
427
  }
312
428
 
313
- const debouncedSetModal = debounce(
314
- () => (suggestionModal.value = true),
315
- 200
316
- )
429
+ const toJSON = (value: string) => {
430
+ const replacedDate = replaceStringifiedDatesWithJSDates(value)
431
+ const json = parseSmartQuery(replacedDate ?? searchQuery.value)
317
432
 
318
- return {
319
- uuid: v4(),
320
- input,
321
- label,
322
- hasEllipsis,
323
- suggestionModal,
324
- setInputValue,
325
- menuOffset,
326
- cancelBlur,
327
- expanded,
328
- styledTexarea,
329
- styledInput,
330
- datePickerSelection,
331
- isDatePickerVisible,
332
- focused,
333
- isOverflow,
334
- isTyping,
335
- scroll,
336
- debouncedSetModal
433
+ return isValidJSON(json) ? json : searchQuery.value
337
434
  }
338
- },
339
- computed: {
340
- statusIcon(): string {
341
- switch (this.status.type) {
435
+
436
+ const fromJSON = (value: { [key: string]: any }) => {
437
+ const replacedDate = replaceJSDatesWithStringifiedDates(
438
+ value,
439
+ dateKeys.value
440
+ )
441
+
442
+ const stringQuery = stringifySmartQuery(replacedDate)
443
+ const aliased = setAliases(stringQuery, props.aliases)
444
+ return aliased
445
+ }
446
+ //#endregion
447
+
448
+ //#region computed
449
+ const editorStyle = computed((): SyntaxColorSchema => {
450
+ return createColorSchema(colorSchema.value, aliases.value)
451
+ })
452
+
453
+ const statusIcon = computed(() => {
454
+ switch (computedStatus.value.type) {
342
455
  case 'success':
343
456
  return 'icon-dl-approve-filled'
344
457
  case 'error':
@@ -348,9 +461,10 @@ export default defineComponent({
348
461
  default:
349
462
  return ''
350
463
  }
351
- },
352
- statusIconColor(): string {
353
- switch (this.status.type) {
464
+ })
465
+
466
+ const statusIconColor = computed(() => {
467
+ switch (computedStatus.value.type) {
354
468
  case 'success':
355
469
  return 'dl-color-positive'
356
470
  case 'error':
@@ -360,253 +474,206 @@ export default defineComponent({
360
474
  default:
361
475
  return ''
362
476
  }
363
- },
364
- screenIcon(): string {
365
- return this.expanded
366
- ? 'icon-dl-fit-to-screen'
367
- : 'icon-dl-full-screen'
368
- },
369
- textareaStyles(): Record<string, any> {
477
+ })
478
+
479
+ const textareaStyles = computed<Record<string, string | number>>(() => {
370
480
  const overflow: string =
371
- this.scroll && this.focused ? 'scroll' : 'hidden'
481
+ scroll.value && focused.value ? 'scroll' : 'hidden'
372
482
  return {
373
483
  overflow,
374
484
  '-webkit-appearance': 'textfield'
375
485
  }
376
- },
377
- searchBarClasses(): string {
486
+ })
487
+
488
+ const searchBarClasses = computed<string>(() => {
378
489
  let classes = 'dl-smart-search-input__search-bar'
379
490
 
380
- if (this.focused) {
491
+ if (focused.value) {
381
492
  classes += ' dl-smart-search-input__search-bar--focused'
382
- } else if (!this.focused) {
383
- if (this.status.type === 'error') {
493
+ } else if (!focused.value) {
494
+ if (computedStatus.value.type === 'error') {
384
495
  classes += ' dl-smart-search-input__search-bar--error'
385
- } else if (this.status.type === 'warning') {
496
+ } else if (computedStatus.value.type === 'warning') {
386
497
  classes += ' dl-smart-search-input__search-bar--warning'
387
498
  }
388
499
  }
389
500
 
390
- if (this.expanded) {
501
+ if (expanded.value) {
391
502
  classes += ' dl-smart-search-input__search-bar--expanded'
392
503
  }
393
504
 
394
- if (this.disabled) {
505
+ if (props.disabled) {
395
506
  classes += ' dl-smart-search-input__search-bar--disabled'
396
507
  }
397
508
 
398
509
  return classes
399
- },
400
- inputClass(): string {
510
+ })
511
+
512
+ const inputClass = computed<string>(() => {
401
513
  return `dl-smart-search-input__textarea${
402
- this.focused ? ' focus' : ''
514
+ focused.value ? ' focus' : ''
403
515
  }`
404
- },
405
- messageClasses(): string {
406
- let classes = 'dl-smart-search-input__message'
516
+ })
407
517
 
408
- if (this.status) {
409
- classes += ` dl-smart-search-input__message--${this.status}`
410
- }
518
+ const showClearButton = computed(() => {
519
+ return searchQuery.value.length > 0
520
+ })
411
521
 
412
- return classes
413
- },
414
- withClearBtn(): boolean {
415
- return this.modelValue.length > 0
416
- },
417
- cssVars(): Record<string, string> {
522
+ const cssVars = computed<Record<string, string | number>>(() => {
418
523
  return {
419
- '--dl-smart-search-bar-wrapper-height':
420
- this.expandedInputHeight,
421
- '--dl-smart-search-input-height': this.inputHeight,
422
- '--search-bar-width': this.searchBarWidth
524
+ '--dl-smart-search-bar-wrapper-height': !focused.value
525
+ ? 'auto'
526
+ : 'fit-content',
527
+ '--dl-smart-search-input-height': height.value,
528
+ '--dl-smart-search-input-bar-width': focused.value
529
+ ? '100%'
530
+ : width.value,
531
+ '--dl-smart-search-input-max-width': focused.value
532
+ ? '100%'
533
+ : width.value
423
534
  }
424
- },
425
- saveStatus(): boolean {
426
- return (
427
- this.disabled ||
428
- !this.modelValue ||
429
- this.status.type === 'error'
430
- )
431
- },
432
- labelStyles(): Record<string, any> {
535
+ })
536
+
537
+ const labelStyles = computed<Record<string, string | number>>(() => {
433
538
  return {
434
- color: this.status?.type === 'error' ? 'red' : 'gray'
435
- }
436
- }
437
- },
438
- watch: {
439
- modelValue(value: string) {
440
- const target = this.$refs['input'] as HTMLInputElement
441
- value = value?.replaceAll(' ', ' ') ?? ''
442
- /*
443
- * I commented out this line because it was blocking arrow navigation
444
- * */
445
- // if (!this.isTyping) target.innerHTML = value
446
- target.innerHTML = value
447
- updateEditor(this.styleModel)
448
- this.setMenuOffset(isEligibleToChange(target, this.expanded))
449
- /*
450
- * I commented out this line because it was blocking arrow navigation
451
- * */
452
- // if (!this.isTyping) setCaret(target)
453
- setCaret(target)
454
- if (!this.expanded) {
455
- this.isOverflow =
456
- isEllipsisActive(this.$refs['input'] as Element) ||
457
- this.hasEllipsis
539
+ color: computedStatus.value.type === 'error' ? 'red' : 'gray'
458
540
  }
541
+ })
459
542
 
460
- if (value.length && isEndingWithDateIntervalPattern(value)) {
461
- this.isDatePickerVisible = true
462
- this.suggestionModal = false
463
- } else {
464
- this.isDatePickerVisible = false
465
- this.suggestionModal = true
543
+ const dateKeys = computed(() => {
544
+ return Object.keys(schema.value).filter(
545
+ (key) => schema.value[key] === 'date'
546
+ )
547
+ })
548
+
549
+ const computedStatus = computed<SearchStatus>((): SearchStatus => {
550
+ if (searchQuery.value === '') {
551
+ return status.value
466
552
  }
467
- this.scroll = (this.$refs.input as HTMLDivElement).offsetHeight > 40
468
- },
469
- suggestions(val) {
470
- if (this.isDatePickerVisible) return
471
- nextTick(() => {
472
- if (!val.length) {
473
- this.suggestionModal = false
474
- }
475
553
 
476
- if (!this.suggestionModal && val.length > 0 && this.focused) {
477
- this.suggestionModal = true
554
+ if (!error.value && searchQuery.value !== '') {
555
+ return {
556
+ type: 'success',
557
+ message: ''
478
558
  }
479
- })
480
- },
481
- expanded(value) {
482
- this.$nextTick(() => {
483
- const element = this.$refs['input'] as HTMLTextAreaElement
559
+ }
484
560
 
485
- if (!value) {
486
- element.scrollLeft = 0
561
+ if (error.value === 'warning') {
562
+ return {
563
+ type: 'warning',
564
+ message: 'The query is not supported technically.'
487
565
  }
566
+ }
488
567
 
489
- this.setMenuOffset(isEligibleToChange(element, value))
568
+ return {
569
+ type: 'error',
570
+ message: error.value
571
+ }
572
+ })
573
+ //#endregion
490
574
 
491
- if (value) {
492
- this.focus()
575
+ //#region watcher
576
+ watch(suggestions, (value) => {
577
+ if (showDatePicker.value) return
578
+ nextTick(() => {
579
+ if (!value.length) {
580
+ showSuggestions.value = false
581
+ }
582
+
583
+ if (
584
+ !showSuggestions.value &&
585
+ value.length > 0 &&
586
+ focused.value
587
+ ) {
588
+ showSuggestions.value = true
493
589
  }
494
590
  })
495
- },
496
- focused(value) {
497
- (this.$refs.searchBar as HTMLElement).style.maxHeight = `${
498
- value ? parseInt(this.searchBarWidth) : 450
499
- }px`
591
+ })
592
+
593
+ watch(focused, (value) => {
500
594
  if (!value) {
501
- (this.$refs.input as HTMLElement).parentElement.style.width =
502
- '1px'
595
+ input.value.parentElement.style.width = '1px'
596
+ } else {
597
+ setMenuOffset(isEligibleToChange(input.value, value))
598
+ input.value.parentElement.style.width = '100%'
503
599
  }
504
- },
505
- isDatePickerVisible(val: boolean) {
506
- if (!val) {
507
- this.datePickerSelection = null
600
+ })
601
+
602
+ watch(showDatePicker, (value) => {
603
+ if (!value) {
604
+ datePickerSelection.value = null
508
605
 
509
606
  nextTick(() => {
510
- this.focus()
607
+ focus()
511
608
  })
512
609
  }
513
- }
514
- },
515
- mounted() {
516
- if (!this.expanded) {
517
- this.isOverflow =
518
- isEllipsisActive(this.$refs['input'] as Element) ||
519
- this.hasEllipsis
520
- }
521
- window.addEventListener('mousemove', () => (this.isTyping = false))
522
- },
523
- methods: {
524
- setMenuOffset(value: number[]) {
525
- this.menuOffset = value
526
- },
527
- focus() {
528
- const target = this.$refs['input'] as HTMLInputElement
529
- if (this.disabled) {
530
- return
531
- }
532
-
533
- target.scrollTo(0, target.scrollHeight)
534
- target.scrollLeft = target.scrollWidth
535
-
536
- target.focus()
537
-
538
- this.focused = true
539
- this.$emit('focus', true)
540
- },
541
- blur() {
542
- const element = this.$refs['input'] as HTMLTextAreaElement
543
-
544
- if (this.isDatePickerVisible) {
545
- return
546
- }
610
+ })
547
611
 
548
- if (this.cancelBlur === 0) {
549
- if (this.suggestionModal) {
550
- this.focused = true
551
- return
612
+ watch(
613
+ modelValue,
614
+ (value, old) => {
615
+ if (value) {
616
+ if (isEqual(value, old)) {
617
+ return
618
+ }
619
+ readModelValue(value)
552
620
  }
621
+ },
622
+ { immediate: true }
623
+ )
624
+ //#endregion
553
625
 
554
- element.scrollLeft = 0
555
- element.scrollTop = 0
556
- this.focused = false
557
- this.expanded = false
558
- this.$emit('focus', false)
559
- } else {
560
- this.focus()
561
- this.focused = true
562
- this.cancelBlur = this.cancelBlur - 1
563
- }
564
- },
565
- save() {
566
- this.$emit('save')
567
- },
568
- edit() {
569
- this.$emit('dql-edit')
570
- },
571
- clearValue() {
572
- this.cancelBlur = this.cancelBlur === 0 ? 1 : this.cancelBlur
573
- this.$emit('update:model-value', '')
574
- if (!this.focused) {
575
- this.focus()
576
- }
577
- },
578
- keyPress(e: KeyboardEvent) {
579
- if (e.key === 'Enter') {
580
- this.$emit('search', this.modelValue)
581
- e.preventDefault()
582
- return
583
- } else if (e.key === 'backspace') {
584
- setCaret(this.$refs['input'] as HTMLElement)
585
- }
586
- },
587
- handleValueChange(e: Event) {
588
- this.isTyping = true
589
-
590
- const text = (e.target as HTMLElement).textContent
591
- .toString()
592
- .replaceAll(' ', ' ')
593
-
594
- this.$emit('update:model-value', text)
595
- },
596
- handleScreenButtonClick() {
597
- this.cancelBlur = this.cancelBlur === 0 ? 1 : this.cancelBlur
598
- this.expanded = !this.expanded
599
- if (!this.focused) {
600
- this.focus()
626
+ onMounted(() => {
627
+ if (!expanded.value) {
628
+ isOverflowing.value =
629
+ isEllipsisActive(input.value) || hasEllipsis.value
601
630
  }
602
- },
603
- handleDateSelectionUpdate(val: DateInterval) {
604
- this.datePickerSelection = val
605
-
606
- this.$emit(
607
- 'update:model-value',
608
- replaceDateInterval(this.modelValue, val)
631
+ window.addEventListener('mousemove', () => (isTyping.value = false))
632
+ blur()
633
+ })
634
+ onBeforeUnmount(() => {
635
+ window.removeEventListener(
636
+ 'mousemove',
637
+ () => (isTyping.value = false)
609
638
  )
639
+ })
640
+
641
+ return {
642
+ uuid: v4(),
643
+ input,
644
+ label,
645
+ searchBar,
646
+ searchQuery,
647
+ focused,
648
+ isOverflowing,
649
+ isTyping,
650
+ scroll,
651
+ showSuggestions,
652
+ menuOffset,
653
+ cancelBlur,
654
+ expanded,
655
+ datePickerSelection,
656
+ showDatePicker,
657
+ suggestions,
658
+ error,
659
+ editorStyle,
660
+ debouncedSetInputValue,
661
+ statusIcon,
662
+ statusIconColor,
663
+ textareaStyles,
664
+ searchBarClasses,
665
+ inputClass,
666
+ showClearButton,
667
+ cssVars,
668
+ labelStyles,
669
+ focus,
670
+ blur,
671
+ onClear,
672
+ onKeyPress,
673
+ onInput,
674
+ onDateSelection,
675
+ computedStatus,
676
+ setInputFromSuggestion
610
677
  }
611
678
  }
612
679
  })
@@ -614,9 +681,11 @@ export default defineComponent({
614
681
  <style lang="scss" scoped>
615
682
  .dl-smart-search-input {
616
683
  display: flex;
617
- text-align: left;
618
- position: absolute;
619
- width: var(--search-bar-width);
684
+ flex-grow: 1;
685
+ max-width: var(--dl-smart-search-input-max-width);
686
+ width: var(--dl-smart-search-input-bar-width);
687
+ height: 100%;
688
+ transition: max-width 0.3s ease-out;
620
689
 
621
690
  &__char {
622
691
  ::selection {
@@ -635,7 +704,7 @@ export default defineComponent({
635
704
  display: flex;
636
705
  flex-grow: 1;
637
706
  height: 100%;
638
- padding: 0 10px;
707
+ padding-left: 10px;
639
708
  overflow-y: auto;
640
709
  background-color: var(--dl-color-panel-background);
641
710
 
@@ -753,7 +822,7 @@ export default defineComponent({
753
822
  }
754
823
 
755
824
  &__clear-btn-wrapper {
756
- border-right: 1px solid var(--dl-color-separator);
825
+ padding-left: 5px;
757
826
  height: 100%;
758
827
  display: flex;
759
828
  align-items: center;