Enhance FilterInput component to support wildcard highlighting and improve caret positioning
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Added support for wildcard symbols (*, ?) in the FilterInput component, allowing for in-place highlighting. - Updated the caret positioning logic to handle wildcard spans correctly, ensuring accurate text selection. - Refactored tokenization and rendering functions to accommodate new wildcard types, enhancing overall functionality.
This commit is contained in:
parent
0723e8d73b
commit
b85089604f
@ -29,12 +29,14 @@ import EqualIcon from '../../Icons/EqualIcon'
|
|||||||
const operandIconStyle = { fontSize: 8 }
|
const operandIconStyle = { fontSize: 8 }
|
||||||
|
|
||||||
// Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">".
|
// Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">".
|
||||||
// Wildcards (* ?) and @ stay as plain text — they are not operands.
|
// Wildcards (* ?) are highlighted in place; @ stays as plain text — none are operands.
|
||||||
|
const WILDCARDS = new Set(['*', '?'])
|
||||||
|
|
||||||
const OPERANDS = [
|
const OPERANDS = [
|
||||||
{
|
{
|
||||||
symbol: '..',
|
symbol: '..',
|
||||||
label: <ArrowLeftIcon style={operandIconStyle} />,
|
label: <ArrowLeftIcon style={operandIconStyle} />,
|
||||||
color: 'blue'
|
color: 'purple'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: '<>',
|
symbol: '<>',
|
||||||
@ -93,6 +95,13 @@ const tokenize = (raw = '') => {
|
|||||||
}
|
}
|
||||||
tokens.push({ type: 'operand', value: match.symbol })
|
tokens.push({ type: 'operand', value: match.symbol })
|
||||||
i += match.symbol.length
|
i += match.symbol.length
|
||||||
|
} else if (WILDCARDS.has(value[i])) {
|
||||||
|
if (text) {
|
||||||
|
tokens.push({ type: 'text', value: text })
|
||||||
|
text = ''
|
||||||
|
}
|
||||||
|
tokens.push({ type: 'wildcard', value: value[i] })
|
||||||
|
i += 1
|
||||||
} else {
|
} else {
|
||||||
text += value[i]
|
text += value[i]
|
||||||
i += 1
|
i += 1
|
||||||
@ -103,6 +112,14 @@ const tokenize = (raw = '') => {
|
|||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderWildcardNode = (char) => {
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.setAttribute('data-wildcard', char)
|
||||||
|
span.style.color = 'var(--color-primary)'
|
||||||
|
span.textContent = char
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
const normalize = (raw = '') =>
|
const normalize = (raw = '') =>
|
||||||
tokenize(raw)
|
tokenize(raw)
|
||||||
.map((token) => token.value)
|
.map((token) => token.value)
|
||||||
@ -302,32 +319,41 @@ const setCaretOffset = (root, targetOffset) => {
|
|||||||
scrollSelectionIntoView(root)
|
scrollSelectionIntoView(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < root.childNodes.length; i += 1) {
|
const walk = (container) => {
|
||||||
const child = root.childNodes[i]
|
for (let i = 0; i < container.childNodes.length; i += 1) {
|
||||||
const length = nodeLength(child)
|
const child = container.childNodes[i]
|
||||||
|
const length = nodeLength(child)
|
||||||
|
|
||||||
if (child.nodeType === Node.TEXT_NODE) {
|
if (child.nodeType === Node.TEXT_NODE) {
|
||||||
if (remaining <= length) {
|
if (remaining <= length) {
|
||||||
place(child, remaining)
|
place(child, remaining)
|
||||||
return
|
return true
|
||||||
|
}
|
||||||
|
remaining -= length
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
remaining -= length
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child.getAttribute?.('data-operand') != null) {
|
if (child.getAttribute?.('data-operand') != null) {
|
||||||
if (remaining === 0) {
|
if (remaining === 0) {
|
||||||
place(root, i)
|
place(container, i)
|
||||||
return
|
return true
|
||||||
|
}
|
||||||
|
if (remaining <= length) {
|
||||||
|
place(container, i + 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
remaining -= length
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if (remaining <= length) {
|
|
||||||
place(root, i + 1)
|
// Element wrappers (e.g. wildcard spans): recurse into children.
|
||||||
return
|
if (walk(child)) return true
|
||||||
}
|
|
||||||
remaining -= length
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (walk(root)) return
|
||||||
|
|
||||||
const range = document.createRange()
|
const range = document.createRange()
|
||||||
range.selectNodeContents(root)
|
range.selectNodeContents(root)
|
||||||
range.collapse(false)
|
range.collapse(false)
|
||||||
@ -341,7 +367,10 @@ const domMatchesTokens = (root, raw) => {
|
|||||||
const tokens = tokenize(raw)
|
const tokens = tokenize(raw)
|
||||||
const children = Array.from(root.childNodes).filter((node) => {
|
const children = Array.from(root.childNodes).filter((node) => {
|
||||||
if (node.nodeType === Node.TEXT_NODE) return (node.textContent ?? '') !== ''
|
if (node.nodeType === Node.TEXT_NODE) return (node.textContent ?? '') !== ''
|
||||||
return node.getAttribute?.('data-operand') != null
|
return (
|
||||||
|
node.getAttribute?.('data-operand') != null ||
|
||||||
|
node.getAttribute?.('data-wildcard') != null
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Empty editor may keep a single empty text node.
|
// Empty editor may keep a single empty text node.
|
||||||
@ -363,6 +392,12 @@ const domMatchesTokens = (root, raw) => {
|
|||||||
child.nodeType === Node.TEXT_NODE && child.textContent === token.value
|
child.nodeType === Node.TEXT_NODE && child.textContent === token.value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (token.type === 'wildcard') {
|
||||||
|
return (
|
||||||
|
child.getAttribute?.('data-wildcard') === token.value &&
|
||||||
|
(child.textContent ?? '') === token.value
|
||||||
|
)
|
||||||
|
}
|
||||||
return child.getAttribute?.('data-operand') === token.value
|
return child.getAttribute?.('data-operand') === token.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -569,6 +604,8 @@ const FilterInput = ({
|
|||||||
for (const item of tokens) {
|
for (const item of tokens) {
|
||||||
if (item.type === 'text') {
|
if (item.type === 'text') {
|
||||||
nextChildren.push(document.createTextNode(item.value))
|
nextChildren.push(document.createTextNode(item.value))
|
||||||
|
} else if (item.type === 'wildcard') {
|
||||||
|
nextChildren.push(renderWildcardNode(item.value))
|
||||||
} else {
|
} else {
|
||||||
nextChildren.push(takeChip(item.value))
|
nextChildren.push(takeChip(item.value))
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user