2
0
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:
Collin Henderson
2022-01-24 09:40:45 -05:00
commit 03dc35436b
25 changed files with 2646 additions and 0 deletions
+11
View File
@@ -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
+58
View File
@@ -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()
})
})
+81
View File
@@ -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)
)
},
})