2
0
mirror of https://github.com/tenrok/vue-tribute.git synced 2026-06-16 14:40:35 +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
+24
View File
@@ -0,0 +1,24 @@
name: Tests
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14, 16]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Run tests
run: |
yarn
yarn test
env:
CI: true
+6
View File
@@ -0,0 +1,6 @@
node_modules
.DS_Store
dist
dist-demo
dist-ssr
*.local
+11
View File
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 120,
"vueIndentScriptAndStyle": false
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["johnsoncodehk.volar"]
}
+19
View File
@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Debug Current Test File",
"autoAttachChildProcesses": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"smartStep": true,
"console": "integratedTerminal"
}
]
}
+11
View File
@@ -0,0 +1,11 @@
# Vue 3 + Typescript + Vite
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.
+39
View File
@@ -0,0 +1,39 @@
<template>
<div class="px-8 mt-16 mb-6 sm:mt-32 sm:mb-6 md:px-0">
<div class="w-full mx-auto mt-8 max-w-prose">
<BasicExample />
</div>
</div>
</template>
<script setup lang="ts">
import BasicExample from './BasicExample.vue'
</script>
<style>
.tribute-container {
@apply absolute top-0 left-0 h-auto overflow-y-auto block z-10 rounded shadow;
max-height: 300px;
max-width: 500px;
}
.tribute-container ul {
@apply mt-5 p-0 list-none bg-white rounded overflow-hidden;
}
.tribute-container li {
@apply text-blue-600 pl-2 pr-6 py-2 cursor-pointer text-sm;
}
.tribute-container li.highlight,
.tribute-container li:hover {
@apply bg-blue-600 text-white;
}
.tribute-container li span {
@apply font-bold;
}
.tribute-container li.no-match {
@apply cursor-default;
}
.tribute-container .menu-highlighted {
@apply font-bold;
}
</style>
+21
View File
@@ -0,0 +1,21 @@
<template>
<div id="container">
<vue-tribute :options="options">
<input type="text" class="" placeholder="@..." />
</vue-tribute>
</div>
</template>
<script setup lang="ts">
import VueTribute from '../lib'
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,
}
</script>
+15
View File
@@ -0,0 +1,15 @@
<template>
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke-width="2"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
></path>
</svg>
</template>
+7
View File
@@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply dark:bg-gray-900;
}
+8
View File
@@ -0,0 +1,8 @@
import { createApp } from 'vue'
import App from './App.vue'
import 'highlight.js/lib/common'
import hljsVuePlugin from '@highlightjs/vue-plugin'
import 'highlight.js/styles/atom-one-dark.css'
import './app.css'
createApp(App).use(hljsVuePlugin).mount('#app')
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue-input-autowidth</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/app.ts"></script>
</body>
</html>
Vendored
+8
View File
@@ -0,0 +1,8 @@
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+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)
)
},
})
+50
View File
@@ -0,0 +1,50 @@
{
"name": "vue-tribute",
"version": "2.0.0",
"license": "MIT",
"files": [
"dist"
],
"main": "./dist/vue-tribute.umd.js",
"module": "./dist/vue-tribute.es.js",
"exports": {
".": {
"import": "./dist/vue-tribute.es.js",
"require": "./dist/vue-tribute.umd.js"
}
},
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vue-tsc --noEmit && vite build",
"build:demo": "vite build --mode demo",
"preview": "vite preview",
"serve": "vite preview",
"test": "vitest"
},
"dependencies": {},
"devDependencies": {
"@highlightjs/vue-plugin": "^2.1.0",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.0",
"@testing-library/user-event": "^14.0.0-beta",
"@testing-library/vue": "^6.4.2",
"@types/node": "^17.0.10",
"@vitejs/plugin-vue": "^2.0.0",
"@vue/test-utils": "^2.0.0-rc.18",
"autoprefixer": "^10.4.2",
"highlight.js": "^11.4.0",
"jsdom": "^19.0.0",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.15",
"tributejs": "^5.1.3",
"typescript": "^4.4.4",
"vite": "^2.7.2",
"vitest": "^0.1.27",
"vue": "^3.2.25",
"vue-tsc": "^0.29.8"
},
"peerDependencies": {
"tributejs": ">= 2.0.0"
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+24
View File
@@ -0,0 +1,24 @@
module.exports = {
mode: 'jit',
content: ['./demo/**/*.{ts,vue,html}'],
theme: {
extend: {
typography: theme => ({
dark: {
css: {
h2: {
color: theme('colors.gray.200'),
},
h3: {
color: theme('colors.gray.300'),
},
p: {
color: theme('colors.gray.400'),
},
},
},
}),
},
},
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
}
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"baseUrl": "./",
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["node", "vitest/globals"]
},
"include": ["vite.config.ts", "env.d.ts", "lib/**/*.ts", "demo/**/*.ts", "demo/**/*.vue"]
}
+44
View File
@@ -0,0 +1,44 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
const resolvePath = (str: string) => resolve(__dirname, str)
const isProd = process.env.NODE_ENV === 'production'
const devConfig = defineConfig({
root: './demo',
plugins: [vue()],
build: {
outDir: '../dist-demo',
emptyOutDir: true,
rollupOptions: {
input: {
main: resolve(__dirname, 'demo/index.html'),
},
},
},
})
const prodConfig = defineConfig({
build: {
lib: {
entry: resolvePath('lib/index.ts'),
name: 'vue-tribute',
fileName: format => `vue-tribute.${format}.js`,
},
rollupOptions: {
external: ['vue', 'tributejs'],
output: {
exports: 'named',
globals: {
vue: 'Vue',
tributejs: 'Tribute',
},
},
},
},
})
export default isProd ? prodConfig : devConfig
+12
View File
@@ -0,0 +1,12 @@
/// <reference types="vitest" />
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [Vue()],
test: {
include: ['./lib/test/**/*.test.ts'],
globals: true,
environment: 'jsdom',
},
})
+2145
View File
File diff suppressed because it is too large Load Diff