Skip to content

Commit

Permalink
Initial v2 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
syropian committed Jan 24, 2022
0 parents commit 03dc354
Show file tree
Hide file tree
Showing 25 changed files with 2,646 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.DS_Store
dist
dist-demo
dist-ssr
*.local
11 changes: 11 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 120,
"vueIndentScriptAndStyle": false
}
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["johnsoncodehk.volar"]
}
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions demo/App.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions demo/BasicExample.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions demo/GitHubIcon.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions demo/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
@apply dark:bg-gray-900;
}
8 changes: 8 additions & 0 deletions demo/app.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -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>
8 changes: 8 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 58 additions & 0 deletions lib/test/vue-tribute.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 81 additions & 0 deletions lib/vue-tribute.ts
Original file line number Diff line number Diff line change
@@ -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)
)
},
})
Loading

0 comments on commit 03dc354

Please sign in to comment.