feat(VHighlight): add new component#22817
Conversation
|
Nice addition — opening a brainstorm thread on whether any of this should live in v0 first, since the shape is a near-perfect fit for a headless primitive. What could move to v0
// packages/0/src/composables/toHighlight/index.ts
export type HighlightChunk = { text: string, match: boolean }
export interface ToHighlightOptions {
text: MaybeRefOrGetter<string>
query?: MaybeRefOrGetter<string | string[] | undefined>
matches?: MaybeRefOrGetter<MatchRange[] | undefined>
matchAll?: MaybeRefOrGetter<boolean>
ignoreCase?: MaybeRefOrGetter<boolean>
}
export function toHighlight (options: ToHighlightOptions): ComputedRef<HighlightChunk[]>Usage: import { toHighlight } from '@vuetify/v0'
const chunks = toHighlight({
text: () => props.text,
query: () => props.query,
ignoreCase: true,
})
// packages/0/src/composables/createFilter/index.ts
const filter = createFilter({ mode: 'some', matches: true })
const { items, matches } = filter.apply(query, items)
// matches: ComputedRef<Map<FilterItem, MatchRange[]>>Then end-to-end, with no private types crossing boundaries: const filter = createFilter({ mode: 'some', matches: true })
const { items, matches } = filter.apply(search, allItems)
// In each row:
const chunks = toHighlight({
text: () => row.title,
matches: () => matches.value.get(row),
})What VHighlight could pull from v0
// packages/vuetify/src/labs/VHighlight/VHighlight.tsx
import { toHighlight } from '@vuetify/v0'
export const VHighlight = defineComponent({
name: 'VHighlight',
props: makeVHighlightProps(),
setup (props) {
const chunks = toHighlight({
text: () => props.text,
query: () => props.query,
matches: () => props.matches,
matchAll: () => props.matchAll,
ignoreCase: () => props.ignoreCase,
})
return () => (
<props.tag class="v-highlight">
{ chunks.value.map((chunk, i) => chunk.match
? <mark key={i} class={['v-highlight__mark', props.markClass]}>{chunk.text}</mark>
: <span key={i}>{chunk.text}</span>
)}
</props.tag>
)
},
})Benefits:
What probably shouldn't move to v0The <template>
<p>
<template v-for="(chunk, i) in chunks" :key="i">
<mark v-if="chunk.match" :class="myCustomClass">{{ chunk.text }}</mark>
<template v-else>{{ chunk.text }}</template>
</template>
</p>
</template>Independent of v0 — nits on this PR
Happy to open v0 issues for |
|
It was designed to be moved to v0 as a component, but since the presentation layer is so thin, we can go with |
v-highlightis meant to make it easier to implement custom dropdowns, and filterable list. It might be a good fit for VDataTable as well.API
text- primary piece of content to scan for matchesquery(string or string array, case-insensitive, overlapping spans merged)matches(array of [start, end]) - takes priority overquerymark-class- for customizationmatch-allignore-casetag- defaults tospancolorandopacityfor customization--v-highlight-opacity, CSS variable)not covered
collapse-whitespace- to ignore double spaces, and line breaks (normalize as single space)Playground