@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.
- package/package.json +1 -1
- package/src/components/basic/DlButton/DlButton.vue +1 -1
- package/src/components/basic/DlButton/utils.ts +1 -1
- package/src/components/compound/DlSearches/DlSmartSearch/DlSmartSearch.vue +90 -542
- package/src/components/compound/DlSearches/DlSmartSearch/components/DlSmartSearchInput.vue +464 -395
- package/src/components/compound/DlSearches/DlSmartSearch/index.ts +2 -1
- package/src/components/compound/DlSearches/DlSmartSearch/utils/highlightSyntax.ts +16 -16
- package/src/components/compound/DlSearches/DlSmartSearch/utils/index.ts +12 -7
- package/src/components/compound/DlSelect/types.ts +4 -0
- package/src/components/compound/types.ts +1 -0
- package/src/demos/SmartSearchDemo/DlSmartSearchDemo.vue +16 -6
- package/src/utils/parse-smart-query.ts +7 -3
- /package/src/components/compound/DlSearches/DlSmartSearch/components/{DlSuggestionsDropdown.vue → SuggestionsDropdown.vue} +0 -0
|
@@ -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 &&
|
|
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="
|
|
30
|
-
@input="
|
|
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="
|
|
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="
|
|
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
|
-
<
|
|
104
|
-
|
|
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="
|
|
65
|
+
@set-input-value="setInputFromSuggestion"
|
|
114
66
|
/>
|
|
115
67
|
<dl-menu
|
|
116
|
-
v-if="
|
|
117
|
-
v-model="
|
|
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="
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
+
width: {
|
|
218
177
|
type: String,
|
|
219
|
-
default: '
|
|
178
|
+
default: '250px'
|
|
220
179
|
},
|
|
221
|
-
|
|
180
|
+
height: {
|
|
222
181
|
type: String,
|
|
223
|
-
default: '
|
|
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
|
-
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
const
|
|
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
|
|
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(
|
|
252
|
-
|
|
221
|
+
const expanded = ref(true)
|
|
253
222
|
const datePickerSelection = ref(null)
|
|
254
|
-
const
|
|
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
|
-
|
|
258
|
-
.split(' ')
|
|
259
|
-
.map((string) => string.trim())
|
|
237
|
+
showSuggestions.value = false
|
|
260
238
|
|
|
261
|
-
|
|
239
|
+
// to handle date suggestion modal to open automatically.
|
|
240
|
+
if (value.includes('(dd/mm/yyyy)')) {
|
|
241
|
+
value = value.trimEnd()
|
|
242
|
+
}
|
|
262
243
|
|
|
263
|
-
|
|
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 (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
391
|
+
const onKeyPress = (e: KeyboardEvent) => {
|
|
392
|
+
if (e.key === 'Enter') {
|
|
393
|
+
e.preventDefault()
|
|
394
|
+
}
|
|
395
|
+
}
|
|
300
396
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
426
|
+
return typeof value === 'object' && value !== null
|
|
311
427
|
}
|
|
312
428
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
)
|
|
429
|
+
const toJSON = (value: string) => {
|
|
430
|
+
const replacedDate = replaceStringifiedDatesWithJSDates(value)
|
|
431
|
+
const json = parseSmartQuery(replacedDate ?? searchQuery.value)
|
|
317
432
|
|
|
318
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
|
|
365
|
-
|
|
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
|
-
|
|
481
|
+
scroll.value && focused.value ? 'scroll' : 'hidden'
|
|
372
482
|
return {
|
|
373
483
|
overflow,
|
|
374
484
|
'-webkit-appearance': 'textfield'
|
|
375
485
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
const searchBarClasses = computed<string>(() => {
|
|
378
489
|
let classes = 'dl-smart-search-input__search-bar'
|
|
379
490
|
|
|
380
|
-
if (
|
|
491
|
+
if (focused.value) {
|
|
381
492
|
classes += ' dl-smart-search-input__search-bar--focused'
|
|
382
|
-
} else if (!
|
|
383
|
-
if (
|
|
493
|
+
} else if (!focused.value) {
|
|
494
|
+
if (computedStatus.value.type === 'error') {
|
|
384
495
|
classes += ' dl-smart-search-input__search-bar--error'
|
|
385
|
-
} else if (
|
|
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 (
|
|
501
|
+
if (expanded.value) {
|
|
391
502
|
classes += ' dl-smart-search-input__search-bar--expanded'
|
|
392
503
|
}
|
|
393
504
|
|
|
394
|
-
if (
|
|
505
|
+
if (props.disabled) {
|
|
395
506
|
classes += ' dl-smart-search-input__search-bar--disabled'
|
|
396
507
|
}
|
|
397
508
|
|
|
398
509
|
return classes
|
|
399
|
-
}
|
|
400
|
-
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
const inputClass = computed<string>(() => {
|
|
401
513
|
return `dl-smart-search-input__textarea${
|
|
402
|
-
|
|
514
|
+
focused.value ? ' focus' : ''
|
|
403
515
|
}`
|
|
404
|
-
}
|
|
405
|
-
messageClasses(): string {
|
|
406
|
-
let classes = 'dl-smart-search-input__message'
|
|
516
|
+
})
|
|
407
517
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
518
|
+
const showClearButton = computed(() => {
|
|
519
|
+
return searchQuery.value.length > 0
|
|
520
|
+
})
|
|
411
521
|
|
|
412
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
'--search-
|
|
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
|
-
|
|
426
|
-
|
|
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:
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
477
|
-
|
|
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
|
-
|
|
486
|
-
|
|
561
|
+
if (error.value === 'warning') {
|
|
562
|
+
return {
|
|
563
|
+
type: 'warning',
|
|
564
|
+
message: 'The query is not supported technically.'
|
|
487
565
|
}
|
|
566
|
+
}
|
|
488
567
|
|
|
489
|
-
|
|
568
|
+
return {
|
|
569
|
+
type: 'error',
|
|
570
|
+
message: error.value
|
|
571
|
+
}
|
|
572
|
+
})
|
|
573
|
+
//#endregion
|
|
490
574
|
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
value ? parseInt(this.searchBarWidth) : 450
|
|
499
|
-
}px`
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
watch(focused, (value) => {
|
|
500
594
|
if (!value) {
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
watch(showDatePicker, (value) => {
|
|
603
|
+
if (!value) {
|
|
604
|
+
datePickerSelection.value = null
|
|
508
605
|
|
|
509
606
|
nextTick(() => {
|
|
510
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
'
|
|
608
|
-
|
|
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
|
-
|
|
618
|
-
|
|
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:
|
|
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
|
-
|
|
825
|
+
padding-left: 5px;
|
|
757
826
|
height: 100%;
|
|
758
827
|
display: flex;
|
|
759
828
|
align-items: center;
|