mirror of
https://github.com/tenrok/vue-tribute.git
synced 2026-06-22 15:10:36 +03:00
Initial v2 commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import type { App, Plugin } from 'vue'
|
||||
import { VueTribute } from './vue-tribute'
|
||||
|
||||
const install = (app: App) => {
|
||||
app.component(VueTribute.name, VueTribute)
|
||||
}
|
||||
|
||||
VueTribute.install = install
|
||||
|
||||
export { VueTribute }
|
||||
export default VueTribute as unknown as Plugin
|
||||
@@ -0,0 +1,58 @@
|
||||
import { defineComponent, h, onRenderTracked } from 'vue'
|
||||
import { render, screen } from '@testing-library/vue'
|
||||
import { VueTribute } from '../'
|
||||
import type Tribute from 'tributejs'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
interface TributeElement extends HTMLElement {
|
||||
tributeInstance?: Tribute<any>
|
||||
}
|
||||
|
||||
describe('VueTribute', () => {
|
||||
const options = {
|
||||
trigger: '@',
|
||||
values: [
|
||||
{ key: 'Collin Henderson', value: 'syropian' },
|
||||
{ key: 'Sarah Drasner', value: 'sarah_edo' },
|
||||
{ key: 'Evan You', value: 'youyuxi' },
|
||||
{ key: 'Adam Wathan', value: 'adamwathan' },
|
||||
],
|
||||
positionMenu: true,
|
||||
}
|
||||
|
||||
test('attaches Tribute instance to the slot DOM node', async () => {
|
||||
const containerStub = defineComponent({
|
||||
setup() {
|
||||
return () => h('div', { id: 'container' }, [h(VueTribute, { options }, () => h('input', { type: 'text' }))])
|
||||
},
|
||||
})
|
||||
render(containerStub)
|
||||
const input = screen.getByRole('textbox')
|
||||
|
||||
expect((input as TributeElement).tributeInstance).toBeTruthy()
|
||||
})
|
||||
|
||||
test('The slot DOM node passes through custom Tribute-related events', async () => {
|
||||
const activeSpy = vi.fn()
|
||||
const notActiveSpy = vi.fn()
|
||||
const user = userEvent.setup()
|
||||
|
||||
const containerStub = defineComponent({
|
||||
setup() {
|
||||
return () =>
|
||||
h('div', { id: 'container' }, [
|
||||
h(VueTribute, { options }, () =>
|
||||
h('input', { type: 'text', onTributeActiveTrue: activeSpy, onTributeActiveFalse: notActiveSpy })
|
||||
),
|
||||
])
|
||||
},
|
||||
})
|
||||
render(containerStub)
|
||||
const input = screen.getByRole('textbox')
|
||||
await user.type(input, '@')
|
||||
await user.type(input, '{Backspace}')
|
||||
|
||||
expect(activeSpy).toHaveBeenCalledOnce()
|
||||
expect(notActiveSpy).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import { defineComponent, watch, h, onMounted, PropType, onBeforeUnmount, nextTick, Ref, ref, unref } from 'vue'
|
||||
import Tribute, { TributeOptions } from 'tributejs'
|
||||
|
||||
type Maybe<T> = T | undefined
|
||||
type MaybeRef<T> = T | Ref<T>
|
||||
|
||||
interface TributeElement extends HTMLElement {
|
||||
tributeInstance?: Tribute<any>
|
||||
}
|
||||
|
||||
export const VueTribute = defineComponent({
|
||||
name: 'vue-tribute',
|
||||
props: {
|
||||
options: {
|
||||
type: Object as PropType<MaybeRef<TributeOptions<any>>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, context) {
|
||||
if (typeof Tribute === 'undefined') {
|
||||
throw new Error('[vue-tribute] cannot locate tributejs.')
|
||||
}
|
||||
|
||||
const root = ref<HTMLElement>()
|
||||
const el = ref<TributeElement>()
|
||||
|
||||
const attachTribute = (el: Ref<Maybe<TributeElement>>, options: MaybeRef<TributeOptions<any>> = props.options) => {
|
||||
if (!el.value) return
|
||||
|
||||
let tribute = new Tribute(unref(options))
|
||||
tribute.attach(el.value)
|
||||
el.value.tributeInstance = tribute
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
el.value = root.value?.childNodes[0] as TributeElement
|
||||
|
||||
if (!el) {
|
||||
throw new Error('[vue-tribute] cannot find a suitable element to attach to.')
|
||||
}
|
||||
|
||||
attachTribute(el)
|
||||
|
||||
el.value.addEventListener('tribute-replaced', e => {
|
||||
e.target?.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
})
|
||||
})
|
||||
|
||||
const detachTribute = (el: Ref<Maybe<TributeElement>>) => {
|
||||
if (!el.value?.tributeInstance) return
|
||||
|
||||
el.value.tributeInstance.detach(el.value)
|
||||
el.value.tributeInstance = undefined
|
||||
delete el.value.dataset.tribute
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
detachTribute(el)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
async newOptions => {
|
||||
if (el.value?.tributeInstance) {
|
||||
await nextTick()
|
||||
detachTribute(el)
|
||||
await nextTick()
|
||||
attachTribute(el, { ...newOptions })
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
return () =>
|
||||
h(
|
||||
'div',
|
||||
{ class: 'v-tribute', ref: root },
|
||||
[context.slots.default ? context.slots.default()[0] : null].filter(Boolean)
|
||||
)
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user