@dataloop-ai/components 0.19.69 → 0.19.70

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataloop-ai/components",
3
- "version": "0.19.69",
3
+ "version": "0.19.70",
4
4
  "exports": {
5
5
  ".": "./index.ts",
6
6
  "./models": "./models.ts",
@@ -105,7 +105,11 @@ import { DlDatePicker } from '../../../DlDateTime'
105
105
  import { DlMenu, DlIcon, DlLabel } from '../../../../essential'
106
106
  import { isEllipsisActive } from '../../../../../utils/is-ellipsis-active'
107
107
  import { useSizeObserver } from '../../../../../hooks/use-size-observer'
108
- import { setCaretAtTheEnd } from '../../../../../utils'
108
+ import {
109
+ getSelectionOffset,
110
+ setCaretAtTheEnd,
111
+ setSelectionOffset
112
+ } from '../../../../../utils'
109
113
  import { ColorSchema, SearchStatus, SyntaxColorSchema } from '../types'
110
114
  import { debounce, isEqual } from 'lodash'
111
115
  import { DlTooltip } from '../../../../shared'
@@ -120,15 +124,15 @@ import {
120
124
  replaceJSDatesWithStringifiedDates,
121
125
  replaceStringifiedDatesWithJSDates,
122
126
  setAliases,
123
- revertAliases,
124
- clearPartlyTypedSuggestion
127
+ revertAliases
125
128
  } from '../utils'
126
129
  import { v4 } from 'uuid'
127
130
  import {
128
131
  Schema,
129
132
  Alias,
130
133
  useSuggestions,
131
- removeBrackets
134
+ removeBrackets,
135
+ removeLeadingExpression
132
136
  } from '../../../../../hooks/use-suggestions'
133
137
  import { parseSmartQuery, stringifySmartQuery } from '../../../../../utils'
134
138
 
@@ -228,6 +232,7 @@ export default defineComponent({
228
232
  //#endregion
229
233
 
230
234
  //#region data
235
+ const caretAt = ref(0)
231
236
  const searchQuery = ref<string>('')
232
237
  const focused = ref(false)
233
238
  const isOverflowing = ref(false)
@@ -254,9 +259,9 @@ export default defineComponent({
254
259
  //#region methods
255
260
  const setInputValue = (
256
261
  value: string,
257
- options: { noEmit?: boolean } = {}
262
+ options: { noEmit?: boolean; testCaret?: number } = {}
258
263
  ) => {
259
- const { noEmit } = options
264
+ const { noEmit, testCaret } = options
260
265
 
261
266
  showSuggestions.value = false
262
267
 
@@ -265,8 +270,9 @@ export default defineComponent({
265
270
  value = value.trimEnd()
266
271
  }
267
272
 
268
- const specialSuggestions = suggestions.value.filter((suggestion) =>
269
- suggestion.startsWith('.')
273
+ const specialSuggestions = suggestions.value.filter(
274
+ (suggestion) =>
275
+ typeof suggestion === 'string' && suggestion.startsWith('.')
270
276
  )
271
277
 
272
278
  for (const suggestion of specialSuggestions) {
@@ -277,6 +283,18 @@ export default defineComponent({
277
283
 
278
284
  searchQuery.value = value
279
285
 
286
+ // TODO
287
+ // here the value is finalized - this should be inner function entry point
288
+
289
+ // find value left side before current input value is altered by any code below
290
+ // to match previous behavior, move the caret to the end if setInputValue has been passed new value
291
+ caretAt.value =
292
+ testCaret !== undefined
293
+ ? testCaret
294
+ : value === input.value.innerText
295
+ ? getSelectionOffset(input.value)[0]
296
+ : value.length
297
+
280
298
  if (value !== input.value.innerText) {
281
299
  input.value.innerHTML = value
282
300
  }
@@ -303,7 +321,7 @@ export default defineComponent({
303
321
  scroll.value = input.value.offsetHeight > 40
304
322
 
305
323
  nextTick(() => {
306
- findSuggestions(value)
324
+ findSuggestions(value.substring(0, caretAt.value))
307
325
  })
308
326
 
309
327
  if (!noEmit) {
@@ -313,57 +331,54 @@ export default defineComponent({
313
331
 
314
332
  const setInputFromSuggestion = (value: string) => {
315
333
  let stringValue = ''
334
+ let caretPosition = 0
316
335
  if (searchQuery.value.length) {
317
- let query = searchQuery.value
318
- .replace(new RegExp(' ', 'g'), ' ')
319
- .split(' ')
320
- .map((string: string) => string.trim())
321
- .filter((string: string) => !!string.length)
322
-
323
- if (query.length > 1) {
324
- if (query[query.length - 1] === '') {
325
- stringValue = [...query, value, '']
326
- .join(' ')
327
- .replace(' ', ' ')
328
- } else {
329
- if (query[query.length - 1].endsWith('.')) {
330
- query[query.length - 1] = query[
331
- query.length - 1
332
- ].replace('.', '')
333
- } else if (
334
- value
335
- .toLowerCase()
336
- .startsWith(
337
- query[query.length - 1].toLowerCase()
338
- )
339
- ) {
340
- query = query.slice(0, query.length - 1)
341
- }
342
- stringValue = [...query, value, ''].join(' ')
343
- }
336
+ let queryLeftSide = searchQuery.value.substring(
337
+ 0,
338
+ caretAt.value
339
+ )
340
+ let queryRightSide = searchQuery.value.substring(caretAt.value)
341
+
342
+ if (['AND', 'OR'].includes(value)) {
343
+ // do not replace text if the value is AND or OR
344
+ const leftover = queryLeftSide.match(/\S+$/)?.[0] || ''
345
+ queryLeftSide =
346
+ queryLeftSide.replace(/\S+$/, '').trimEnd() + ' '
347
+ queryRightSide = leftover + queryRightSide
348
+ } else if (value.startsWith('.')) {
349
+ // dot notation case
350
+ queryLeftSide = queryLeftSide.trimEnd().replace(/\.$/, '')
351
+ } else if (queryLeftSide.endsWith(' ')) {
352
+ // caret after space: only replace multiple spaces on the left
353
+ queryLeftSide = queryLeftSide.trimEnd() + ' '
354
+ } else if (/\.\S+$/.test(queryLeftSide)) {
355
+ // if there are dots in left side expression, suggestions have an operator
356
+ // looks like a bug in findSuggestions TODO find it - for now work around it here
357
+ const leftover = queryRightSide.match(/^\S+/)?.[0] || ''
358
+ queryLeftSide += leftover + ' '
359
+ queryRightSide = queryRightSide
360
+ .substring(leftover.length)
361
+ .trimStart()
362
+ } else if (queryRightSide.startsWith(' ')) {
363
+ // this| situation: replace whatever is there on the left side with the value
364
+ queryLeftSide = queryLeftSide.replace(/\S+$/, '')
365
+ queryRightSide = queryRightSide.trimStart()
344
366
  } else {
345
- if (query[query.length - 1].endsWith('.')) {
346
- query[query.length - 1] = query[
347
- query.length - 1
348
- ].replace('.', '')
349
- } else if (
350
- value
351
- .toLowerCase()
352
- .startsWith(query[query.length - 1].toLowerCase())
353
- ) {
354
- query = query.slice(0, query.length - 1)
355
- }
356
-
357
- stringValue = [...query, value, ''].join(' ')
367
+ // this|situation: replace whatever is there on both sides with the value
368
+ queryLeftSide = queryLeftSide.replace(/\S+$/, '')
369
+ queryRightSide =
370
+ removeLeadingExpression(queryRightSide).trimStart()
358
371
  }
372
+
373
+ stringValue = queryLeftSide + value + ' ' + queryRightSide
374
+ caretPosition = stringValue.length - queryRightSide.length
359
375
  } else {
360
376
  stringValue = value + ' '
377
+ caretPosition = stringValue.length
361
378
  }
362
379
 
363
- setInputValue(
364
- clearPartlyTypedSuggestion(input.value.innerText, stringValue)
365
- )
366
- setCaretAtTheEnd(input.value)
380
+ setInputValue(stringValue)
381
+ setSelectionOffset(input.value, caretPosition, caretPosition)
367
382
  }
368
383
 
369
384
  const debouncedSetInputValue = debounce(setInputValue, 300)
@@ -815,19 +830,31 @@ export default defineComponent({
815
830
  )
816
831
  //#endregion
817
832
 
833
+ const watchMouseMove = () => {
834
+ isTyping.value = false
835
+ }
836
+
837
+ const watchKeyUp = (e: KeyboardEvent) => {
838
+ if (
839
+ focused.value &&
840
+ (e.key === 'ArrowLeft' || e.key === 'ArrowRight')
841
+ ) {
842
+ setInputValue(searchQuery.value, { noEmit: true })
843
+ }
844
+ }
845
+
818
846
  onMounted(() => {
819
847
  if (!expanded.value) {
820
848
  isOverflowing.value =
821
849
  isEllipsisActive(input.value) || hasEllipsis.value
822
850
  }
823
- window.addEventListener('mousemove', () => (isTyping.value = false))
851
+ window.addEventListener('mousemove', watchMouseMove)
852
+ window.addEventListener('keyup', watchKeyUp)
824
853
  blur(null, { force: true })
825
854
  })
826
855
  onBeforeUnmount(() => {
827
- window.removeEventListener(
828
- 'mousemove',
829
- () => (isTyping.value = false)
830
- )
856
+ window.removeEventListener('mousemove', watchMouseMove)
857
+ window.removeEventListener('keyup', watchKeyUp)
831
858
  })
832
859
 
833
860
  return {
@@ -119,28 +119,3 @@ export function setCaretAtTheEnd(target: HTMLElement) {
119
119
  sel.addRange(range)
120
120
  target.focus()
121
121
  }
122
-
123
- export function clearPartlyTypedSuggestion(oldValue: string, newValue: string) {
124
- const oldSuggestions = oldValue.split(' ')
125
- const newSuggestions = newValue.split(' ')
126
-
127
- const oldSuggestion = oldSuggestions[oldSuggestions.length - 1]
128
- const newSuggestion = newSuggestions[newSuggestions.length - 2]
129
-
130
- if (oldSuggestion && newSuggestion?.includes(oldSuggestion)) {
131
- newValue = removeOldSuggestion(newValue)
132
- }
133
- return newValue
134
- }
135
-
136
- function removeOldSuggestion(inputString: string) {
137
- const words = inputString.trim().split(' ')
138
-
139
- if (words.length >= 2) {
140
- words.splice(-2, 1)
141
- const resultString = words.join(' ')
142
- return resultString + ' '
143
- } else {
144
- return inputString
145
- }
146
- }
@@ -610,8 +610,8 @@ export const removeBrackets = (str: string) => {
610
610
 
611
611
  let result = removeAllBrackets(str.substring(0, quotesAt[0]))
612
612
 
613
- let skipFrom = 0;
614
- let skipTo = 1
613
+ let skipFrom = 0
614
+ let skipTo = 1
615
615
  while (quotesAt[skipFrom] !== undefined) {
616
616
  // skip as far as isValidString fails
617
617
  while (
@@ -652,6 +652,17 @@ export const removeBrackets = (str: string) => {
652
652
  return result
653
653
  }
654
654
 
655
+ export const removeLeadingExpression = (str: string) => {
656
+ // scan for a substring that isValidString
657
+ for (let i = 2; i < str.length; i++) {
658
+ if (isValidString(str.substring(0, i))) {
659
+ return str.substring(i).trimStart()
660
+ }
661
+ }
662
+ // return everything after 1st space, or an empty string
663
+ return str.match(/\s+(.*)$/)?.[1] || ''
664
+ }
665
+
655
666
  const getValueSuggestions = (dataType: string | string[], operator: string) => {
656
667
  const types: string[] = Array.isArray(dataType) ? dataType : [dataType]
657
668
  const suggestion: string[] = []