@dataloop-ai/components 0.18.69 → 0.18.71

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