├── .deepsource.toml ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── ranger.yml └── workflows │ └── lint-build.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .kodiak.toml ├── .prettierrc ├── .restyled.yaml ├── .snyk ├── .stylelintrc ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app-home.png ├── demo.gif ├── docs ├── .nojekyll ├── README.md └── index.html ├── index.html ├── logo.png ├── package.json ├── pnpm-lock.yaml ├── public └── favicon.ico ├── rollup.config.mjs ├── social-slide-small.png ├── social-slide.png ├── src ├── App.vue ├── assets │ ├── github.svg │ └── logo.png ├── components │ ├── CloseIcon.vue │ ├── Demo.vue │ ├── HandlePaste.ts │ ├── Main.vue │ ├── MainSetup.ts │ ├── SuggestPane.vue │ ├── Tag.vue │ └── Tags.vue ├── examples │ ├── autocomplete.vue │ ├── basic.vue │ ├── defaults.vue │ ├── duplicates.vue │ ├── editable.vue │ ├── maxtags.vue │ ├── paste.vue │ ├── quickdelete.vue │ ├── readonly.vue │ └── theme.vue ├── index.css ├── index.js ├── main.js ├── models │ └── index.ts ├── prism.css ├── prism.js └── test │ └── countries.ts ├── tsconfig.json ├── types └── smart-tagz.d.ts └── vite.config.js /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | exclude_patterns = [ 4 | "src/examples/**", 5 | "examples/**", 6 | "test/**" 7 | ] 8 | 9 | [[analyzers]] 10 | name = "javascript" 11 | enabled = true 12 | 13 | [analyzers.meta] 14 | environment = ["browser"] 15 | plugins = ["vue"] 16 | style_guide = "standard" 17 | dialect = "typescript" 18 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/examples 2 | 3 | src/components/demo.vue -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // module.exports = { 2 | // parser: "vue-eslint-parser", 3 | // parserOptions: { 4 | // parser: "@typescript-eslint/parser", 5 | // }, 6 | // extends: [ 7 | // // add more generic rulesets here, such as: 8 | // // 'eslint:recommended', 9 | // "plugin:vue/vue3-recommended", 10 | // ], 11 | // rules: { 12 | // // override/add rules settings here, such as: 13 | // // 'vue/no-unused-vars': 'error' 14 | // }, 15 | // }; 16 | 17 | module.exports = { 18 | extends: [ 19 | "plugin:@typescript-eslint/recommended", 20 | "plugin:vue/vue3-recommended", 21 | "prettier", 22 | ], 23 | rules: { 24 | "@typescript-eslint/no-unused-vars": "off", 25 | "vue/no-unused-components": "off", 26 | "vue/no-unused-vars": "off", 27 | }, 28 | plugins: ["vue"], 29 | parserOptions: { 30 | parser: "@typescript-eslint/parser", 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.github/ranger.yml: -------------------------------------------------------------------------------- 1 | merges: 2 | # Delete branch after merging the PR 3 | - action: delete_branch -------------------------------------------------------------------------------- /.github/workflows/lint-build.yml: -------------------------------------------------------------------------------- 1 | name: Lint & Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18.x] 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: pnpm/action-setup@v2.2.2 18 | with: 19 | version: 7 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v2 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | cache: "pnpm" 25 | 26 | - name: Install dependencies 27 | run: pnpm install --frozen-lockfile 28 | 29 | - name: Lint 30 | run: pnpm lint:all 31 | 32 | - name: Build 33 | run: pnpm rollup 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local 5 | yarn-error.log -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint:all 5 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | # .kodiak.toml 2 | # Minimal config. version is the only required field. 3 | version = 1 4 | 5 | [merge] 6 | automerge_label = "automerge" 7 | 8 | [update] 9 | always = true 10 | require_automerge_label = true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /.restyled.yaml: -------------------------------------------------------------------------------- 1 | exclude: 2 | - "**/*.patch" 3 | - "**/node_modules/**/*" 4 | - "**/vendor/**/*" 5 | - ".github/workflows/**/*" 6 | - ".gitignore" 7 | - "pnpm-lock.yaml" 8 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.22.1 3 | ignore: {} 4 | patch: {} 5 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deepscan.enable": true 3 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Prabhu Murthy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 | [![Build Status](https://dev.azure.com/prabhummurthy/smart-tagz/_apis/build/status/prabhuignoto.smart-tagz?branchName=master)](https://dev.azure.com/prabhummurthy/smart-tagz/_build/latest?definitionId=4&branchName=master) 11 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/ece87afeb05c431fa375a8b98223290d)](https://www.codacy.com/manual/prabhuignoto/smart-tagz?utm_source=github.com&utm_medium=referral&utm_content=prabhuignoto/smart-tagz&utm_campaign=Badge_Grade) 12 | [![DeepScan grade](https://deepscan.io/api/teams/10074/projects/13324/branches/220204/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=10074&pid=13324&bid=220204) 13 | [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/prabhuignoto/smart-tagz.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/prabhuignoto/smart-tagz/context:javascript) 14 | ![Snyk Vulnerabilities for GitHub Repo](https://img.shields.io/snyk/vulnerabilities/github/prabhuignoto/smart-tagz) 15 | [![Depfu](https://badges.depfu.com/badges/b07de06e0726ec1cdfec6c7a12967582/overview.svg)](https://depfu.com/github/prabhuignoto/smart-tagz?project_id=18158) 16 | 17 |
18 | 19 |
20 | 21 | [![Edit smart-tagz](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/smart-tagz-pd32g?fontsize=14&hidenavigation=1&theme=dark) 22 | 23 |

✨ Features

24 | 25 | - ⚡ [Autosuggest](#auto-suggest) with support for keyboard selection. 26 | - ✏️ [Edit](#editable-tags) the tags inline by double clicking them. 27 | - 🏷️ [Paste](#paste) strings with delimiters of your choice and the component will create the tags for you. 28 | - 🗑️ Quickly delete the tags with a visual confirmation before removing a tag. 29 | - 🧹 Quickly clear all tags with `quick-delete` mode. 30 | - 🔒  [Lock the component](#readonly-tags) using the `readonly` mode. 31 | - ✋  [Restrict](#max-tags) the number of tags and Handle duplicates gracefully. 32 | - 🌈 [Customize](#theme) the colors. 33 | 34 | ### Table of Contents 35 | 36 | - [⚡ Installation](#-installation) 37 | - [🚀 Getting Started](#-getting-started) 38 | - [🍬 Demos](#-demos) 39 | - [Props](#props) 40 | - [Default Tags](#default-tags) 41 | - [Duplicates](#duplicates) 42 | - [Auto Suggest](#auto-suggest) 43 | - [Max Tags](#max-tags) 44 | - [Paste](#paste) 45 | - [Editable Tags](#editable-tags) 46 | - [Readonly Tags](#readonly-tags) 47 | - [Theme](#theme) 48 | - [Custom Class names](#custom-class-names) 49 | - [📦 Build Setup](#-build-setup) 50 | - [🔨 Contributing](#-contributing) 51 | - [Notes](#notes) 52 | - [Meta](#meta) 53 | 54 | ## ⚡ Installation 55 | 56 | ```jsx 57 | yarn install smart-tagz 58 | ``` 59 | 60 | ## 🚀 Getting Started 61 | 62 | smart-tagz has some great defaults to get you started quickly. Please check the props list for all options. 63 | 64 | ```jsx 65 | 77 | 78 | 91 | ``` 92 | 93 | ## 🍬 Demos 94 | 95 | Head to our demo page for examples showcasing all the features. 96 | 97 | [https://smart-tagz.vercel.app/](https://smart-tagz.vercel.app/) 98 | 99 | ## Props 100 | 101 | | Prop | Type | Description | Default | 102 | | ---------------- | --------------------- | ------------------------------------------------------------------------------------------------ | ---------------- | 103 | | defaultTags | Array | initialize with a `default` set of tags | [] | 104 | | width | String | `width` of the container | 100% | 105 | | autosuggest | Boolean | Enables the `autosuggest` feature. you also need to set the sources for the autosuggest to work. | false | 106 | | sources | Array | Works as the `datasource` for the autosuggest feature | [] | 107 | | allowPaste | { delimiter: String } | Parses the pasted string based on the passed delimiter and creates tags automatically | {delimiter: ","} | 108 | | editable | Boolean | makes the tags `editable` | false | 109 | | allowDuplicates | Boolean | allows/disallows `duplicate` tag entries while pasted or entered manually. | true | 110 | | maxTags | Number | sets the `Maximum` number of tags | 10 | 111 | | inputPlaceholder | String | `Placeholder` for the input box. | "Enter tag..." | 112 | | readOnly | Boolean | Makes the whole component `readOnly`. ideal for display only purposes. | false | 113 | | quick-delete | Boolean | When enabled all the tags can be cleared by CTRL + A, DEL | false | 114 | | on-changed | Function | `callback` that gets called when a new tag is added or an existing tag is deleted | false | 115 | 116 | ### Default Tags 117 | 118 | We can initialize smart-tagz with some `default` tags. This setting will mostly be used along with the `readonly` prop to create tags for display only purposes. 119 | 120 | ```jsx 121 | 122 | ``` 123 | 124 | ### Duplicates 125 | 126 | You can decide how to manage `duplicate` tags by either allowing or disallowing them completely. When set to `false` no duplicate values are allowed. 127 | 128 | ```jsx 129 | 130 | ``` 131 | 132 | ### Auto Suggest 133 | 134 | Whe set to `true`, the `autosuggest` prop suggests values in a dropdown. You also need to set the `sources` prop for this to work. The `sources` prop can be an Array of strings. 135 | 136 | ```jsx 137 | 138 | ``` 139 | 140 | ### Max Tags 141 | 142 | The component can also be configured to accept the `Maximum` number of tags that can be created. Once the threshold is reached, the input will be `hidden` from the user. 143 | 144 | Here we restrict the tags to `3` 145 | 146 | ```jsx 147 | 148 | ``` 149 | 150 | ### Paste 151 | 152 | The component can parse strings and automatically create tags for you. The default delimiter is `","` but you can override this setting by manually setting the `delimiter` option. 153 | 154 | ```jsx 155 | 156 | ``` 157 | 158 | ### Editable Tags 159 | 160 | The Tags are not `editable` by default, but you can change this setting with the `editable` prop. Simply double click a tag, make the changes and hit enter to save. 161 | 162 | ```jsx 163 | 164 | ``` 165 | 166 | ### Readonly Tags 167 | 168 | You can lock the component with `readonly` mode. All interactions are disabled in `read-only` mode. 169 | 170 | ```jsx 171 | 172 | ``` 173 | 174 | ### Theme 175 | 176 | The components color scheme can be customized by passing a custom theme prop. 177 | 178 | ```jsx 179 | 186 | ``` 187 | 188 | ### Custom Class names 189 | 190 | If you are looking for more control in terms of customizing the style of the tags, you can make use of the `classNames` prop to apply custom classes to the different elements within the component. 191 | 192 | ```jsx 193 | 208 | ``` 209 | 210 | ## 📦 Build Setup 211 | 212 | ```bash 213 | # install dependencies 214 | yarn install 215 | 216 | # start dev 217 | yarn run dev 218 | 219 | # package lib 220 | npm run rollup 221 | 222 | # run css linting 223 | yarn run lint:css 224 | ``` 225 | 226 | ## 🔨 Contributing 227 | 228 | 1. Fork it ( [https://github.com/prabhuignoto/smart-tagz/fork](https://github.com/prabhuignoto/smart-tagz/fork) ) 229 | 2. Create your feature branch (`git checkout -b new-feature`) 230 | 3. Commit your changes (`git commit -am 'Add feature'`) 231 | 4. Push to the branch (`git push origin new-feature`) 232 | 5. Create a new Pull Request 233 | 234 | ## Notes 235 | 236 | The project uses [vite](vite) instead of @vue/cli. I choose vite for speed and i also believe [vite](vite) will be the future. 237 | 238 | ## Meta 239 | 240 | Prabhu Murthy – [@prabhumurthy2](https://twitter.com/prabhumurthy2) – prabhu.m.murthy@gmail.com 241 | 242 | Distributed under the MIT license. See `LICENSE` for more information. 243 | 244 | [https://github.com/prabhuingoto/](https://github.com/prabhuingoto/) 245 | 246 | 247 | 248 | [vue]: https://vuejs.org 249 | [typescript]: https://typescriptlang.org 250 | [vite]: https://github.com/vitejs/vite 251 | -------------------------------------------------------------------------------- /app-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/app-home.png -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/demo.gif -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Headline 2 | 3 | > An awesome project. 4 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | smart-tagz 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-tagz", 3 | "version": "0.4.1", 4 | "license": "MIT", 5 | "type": "module", 6 | "description": "A Smart input tags component for Vue 3", 7 | "author": { 8 | "name": "Prabhu Murthy", 9 | "url": "https://www.prabhumurthy.com" 10 | }, 11 | "scripts": { 12 | "dev": "vite", 13 | "build": "vite build", 14 | "rollup": "rimraf ./dist && rollup -c ./rollup.config.mjs", 15 | "lint:css": "stylelint src/**/*.vue --custom-syntax postcss-html", 16 | "lint": "eslint src/**/*.vue", 17 | "lint:all": "pnpm lint:css && pnpm lint", 18 | "prepare": "husky install", 19 | "snyk": "pnpx snyk test", 20 | "format": "prettier --write src/**/*.{vue,js}", 21 | "clean": "pnpm format && pnpm lint:all" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/prabhuignoto/smart-tagz" 26 | }, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "lint-staged" 30 | } 31 | }, 32 | "lint-staged": { 33 | "src/**/*.scss": [ 34 | "stylelint src/**/*.scss", 35 | "git add" 36 | ], 37 | "src/**/*.vue": [ 38 | "eslint src/**/*.vue", 39 | "git add" 40 | ] 41 | }, 42 | "dependencies": { 43 | "escape-string-regexp": "^5.0.0", 44 | "vue-feather-icons": "^5.1.0" 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-beep": "^1.0.2", 48 | "@rollup/plugin-buble": "^1.0.2", 49 | "@rollup/plugin-commonjs": "^24.1.0", 50 | "@rollup/plugin-node-resolve": "^15.0.2", 51 | "@rollup/plugin-sucrase": "^5.0.1", 52 | "@types/nanoid": "^3.0.0", 53 | "@types/vue-feather-icons": "^5.0.4", 54 | "@typescript-eslint/eslint-plugin": "^5.58.0", 55 | "@typescript-eslint/parser": "^5.58.0", 56 | "@vitejs/plugin-vue": "^4.1.0", 57 | "@vue/compiler-sfc": "^3.2.47", 58 | "autoprefixer": "^10.4.14", 59 | "cssnano": "^6.0.0", 60 | "eslint": "^8.38.0", 61 | "eslint-config-prettier": "^8.8.0", 62 | "eslint-plugin-vue": "^9.10.0", 63 | "husky": "^8.0.3", 64 | "lint-staged": "^13.2.1", 65 | "postcss": "^8.4.21", 66 | "postcss-html": "^1.5.0", 67 | "postcss-preset-env": "^8.3.1", 68 | "prettier": "^2.8.7", 69 | "rimraf": "^5.0.0", 70 | "rollup": "^3.20.2", 71 | "rollup-plugin-postcss": "^4.0.2", 72 | "rollup-plugin-scss": "^4.0.0", 73 | "rollup-plugin-terser": "^7.0.2", 74 | "rollup-plugin-vue": "^6.0.0", 75 | "sass": "^1.62.0", 76 | "stylelint": "^15.4.0", 77 | "stylelint-config-standard": "^32.0.0", 78 | "typescript": "^5.0.4", 79 | "vite": "^4.2.1", 80 | "vue": "^3.2.47", 81 | "vue-template-compiler": "2.7.14" 82 | }, 83 | "files": [ 84 | "dist", 85 | "types" 86 | ], 87 | "peerDependencies": { 88 | "vue-feather-icons": "^5.1.0" 89 | }, 90 | "main": "dist/smart-tagz.js", 91 | "module": "dist/smart-tagz.esm.js", 92 | "umd": "dist/smart-tagz.umd.js" 93 | } 94 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/public/favicon.ico -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import beep from "@rollup/plugin-beep"; 2 | import buble from "@rollup/plugin-buble"; 3 | import common from "@rollup/plugin-commonjs"; 4 | import resolve from "@rollup/plugin-node-resolve"; 5 | import sucrase from "@rollup/plugin-sucrase"; 6 | import autoprefixer from "autoprefixer"; 7 | import preset from "postcss-preset-env"; 8 | import postcss from "rollup-plugin-postcss"; 9 | import { terser } from "rollup-plugin-terser"; 10 | import vue from "rollup-plugin-vue"; 11 | import pkg from "./package.json" assert { type: "json" }; 12 | import cssnano from "cssnano"; 13 | 14 | const banner = `/* 15 | * ${pkg.name} 16 | * ${pkg.description} 17 | * v${pkg.version} 18 | * ${pkg.license} License 19 | */ 20 | `; 21 | 22 | export default { 23 | input: "src/index.js", 24 | output: [ 25 | { 26 | file: pkg.main, 27 | format: "cjs", 28 | exports: "named", 29 | strict: true, 30 | banner, 31 | }, 32 | { 33 | file: pkg.module, 34 | format: "es", 35 | exports: "named", 36 | strict: true, 37 | banner, 38 | }, 39 | { 40 | file: pkg.umd, 41 | format: "umd", 42 | exports: "named", 43 | strict: true, 44 | banner, 45 | name: "FloatMenu", 46 | globals: { 47 | vue: "vue", 48 | }, 49 | }, 50 | ], 51 | plugins: [ 52 | vue({ 53 | target: "browser", 54 | css: true, 55 | }), 56 | postcss({ 57 | extract: "smart-tagz.css", 58 | plugins: [ 59 | preset({ 60 | stage: 0, 61 | }), 62 | autoprefixer(), 63 | cssnano() 64 | ], 65 | }), 66 | sucrase({ 67 | exclude: ["node_modules/**"], 68 | transforms: ["typescript"], 69 | }), 70 | common(), 71 | buble(), 72 | beep(), 73 | resolve(), 74 | terser(), 75 | ], 76 | external: ["vue", "vue-feather-icons"], 77 | }; 78 | -------------------------------------------------------------------------------- /social-slide-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/social-slide-small.png -------------------------------------------------------------------------------- /social-slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/social-slide.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /src/assets/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prabhuignoto/smart-tagz/8672695802b0ebd106fa0abdfa7a87337e8a619c/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/CloseIcon.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 32 | 33 | 48 | -------------------------------------------------------------------------------- /src/components/Demo.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 97 | 98 | 208 | -------------------------------------------------------------------------------- /src/components/HandlePaste.ts: -------------------------------------------------------------------------------- 1 | import { TagModel } from "../models"; 2 | 3 | const HandlePaste = ( 4 | tagsData: TagModel[], 5 | pasteData: string, 6 | maxTags: number, 7 | tagsCreated: number, 8 | delimiter: string, 9 | allowDuplicates: boolean 10 | ) => { 11 | if (pasteData) { 12 | // calculate available slots 13 | const availableSlots = maxTags - tagsCreated; 14 | 15 | // split string to create new tags 16 | let items = delimiter ? pasteData.split(delimiter) : []; 17 | 18 | 19 | if (items.length > 1) { 20 | // pick the items that can fit in the slot 21 | items = items.slice(0, Math.min(items.length, availableSlots)); 22 | 23 | // check if duplicates are disallowed 24 | if (!allowDuplicates) { 25 | const existingItems = tagsData.map((t) => t.name); 26 | const newSet = items.filter( 27 | (item) => existingItems.includes(item) === false 28 | ); 29 | 30 | // remove the duplicate entries 31 | items = [...new Set(newSet)] as string[]; 32 | } 33 | 34 | // update tagsData with new items 35 | if (items.length) { 36 | const newData = tagsData.concat( 37 | items.map((name) => ({ 38 | name, 39 | value: name, 40 | id: Math.random().toString(16).slice(2), 41 | })) 42 | ); 43 | return { 44 | newData, 45 | tagsCreated: tagsCreated + items.length 46 | } 47 | } 48 | } else { 49 | const newData = tagsData.concat({ 50 | name: pasteData, 51 | value: pasteData, 52 | id: Math.random().toString(16).slice(2), 53 | }); 54 | 55 | return { 56 | newData, 57 | tagsCreated: tagsCreated + 1 58 | } 59 | } 60 | } 61 | }; 62 | 63 | export default HandlePaste; -------------------------------------------------------------------------------- /src/components/Main.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 152 | 153 | 199 | -------------------------------------------------------------------------------- /src/components/MainSetup.ts: -------------------------------------------------------------------------------- 1 | import { computed, nextTick, ref, unref, watch } from "vue"; 2 | import escapeStringRegexp from "escape-string-regexp"; 3 | import { TagModel } from "../models"; 4 | import HandlePaste from "./HandlePaste"; 5 | 6 | interface PropModel { 7 | autosuggest: boolean; 8 | allowPaste: { 9 | delimiter: string; 10 | }; 11 | allowDuplicates: boolean; 12 | maxTags: number; 13 | width: string; 14 | defaultTags?: string[]; 15 | sources: string[]; 16 | quickDelete?: boolean; 17 | onChanged?: (result: string[]) => void; 18 | } 19 | 20 | export default function ({ autosuggest, allowPaste = { delimiter: "," }, allowDuplicates, maxTags, defaultTags = [], sources, quickDelete, width, onChanged }: PropModel) { 21 | const delTagRef = ref<{ id: string } | null>(null); 22 | // ref to store the tags data. init with default tags 23 | const tagsData = ref(defaultTags.slice(0, maxTags).map(name => ({ 24 | id: Math.random().toString(16).slice(2), 25 | name, 26 | value: name 27 | }))); 28 | const textInputRef = ref(null); 29 | // ref for the input box 30 | const input = ref(""); 31 | // ref to track the tags created by te user 32 | const tagsCreated = ref(defaultTags.length ? Math.min(defaultTags.length, maxTags) : 0); 33 | // ref to display the suggestion pane 34 | const showSuggestions = ref(false); 35 | // ref to track ctrl+a selection 36 | const selectAllRef = ref(false); 37 | const selectedIndex = ref(-1); 38 | 39 | const style = computed(() => ({ 40 | width, 41 | })); 42 | 43 | const filteredItems = computed(() => { 44 | const reg = new RegExp("^" + escapeStringRegexp(input.value), "i"); 45 | return sources.filter((f) => reg.test(f)); 46 | }); 47 | 48 | const focus = () => { 49 | (textInputRef.value as unknown as HTMLElement).focus() 50 | }; 51 | 52 | const reset: () => void = () => { 53 | // remove highlight from all tags 54 | tagsData.value = tagsData.value.map(t => { 55 | delete t.highlight; 56 | return t; 57 | }); 58 | // disable autosuggest 59 | showSuggestions.value = false; 60 | selectAllRef.value = false; 61 | selectedIndex.value = -1; 62 | }; 63 | 64 | watch(() => tagsData.value.length, () => { 65 | onChanged?.(tagsData.value.map(item => item.value)); 66 | }); 67 | 68 | watch(input, (newValue) => { 69 | 70 | if (delTagRef.value) { 71 | delTagRef.value = null; 72 | tagsData.value = tagsData.value.map((t) => { 73 | delete t.highlight; 74 | return t; 75 | }); 76 | } 77 | 78 | if (newValue) { 79 | selectAllRef.value = false; 80 | 81 | if (autosuggest && newValue.length > 0) { 82 | showSuggestions.value = true; 83 | } else if (autosuggest && newValue.length < 1) { 84 | showSuggestions.value = false; 85 | } 86 | } else { 87 | showSuggestions.value = false; 88 | } 89 | }); 90 | 91 | watch(selectAllRef, newValue => { 92 | tagsData.value = tagsData.value.map(tag => Object.assign({}, tag, { 93 | highlight: newValue 94 | })); 95 | }); 96 | 97 | // checks if a new tag can be added 98 | const canAddTag = (name: string) => { 99 | const tester = new RegExp(`^${escapeStringRegexp(name)}$`, "ig"); 100 | const duplicatesCheck = !allowDuplicates 101 | ? !tagsData.value.some((tag) => tag.name === name || tester.test(tag.name)) 102 | : allowDuplicates; 103 | const maxAllowed = tagsCreated.value < maxTags; 104 | return duplicatesCheck && maxAllowed; 105 | }; 106 | 107 | // handler to add a new tag 108 | const handleAddTag: (name: string) => void = (name) => { 109 | let nameToUse = ''; 110 | const selIndex = unref(selectedIndex); 111 | 112 | if (selIndex > -1) { 113 | nameToUse = filteredItems.value[selIndex]; 114 | } else { 115 | nameToUse = name; 116 | } 117 | 118 | if (!canAddTag(nameToUse)) { 119 | return; 120 | } 121 | 122 | let newTag = null; 123 | 124 | if (showSuggestions.value && selectedIndex.value > -1) { 125 | newTag = filteredItems.value[selectedIndex.value] 126 | } else { 127 | newTag = nameToUse; 128 | } 129 | 130 | if (newTag) { 131 | tagsData.value = tagsData.value.concat({ 132 | name: newTag, 133 | id: Math.random().toString(16).slice(2), 134 | value: newTag, 135 | }); 136 | } 137 | 138 | 139 | input.value = ""; 140 | showSuggestions.value = false; 141 | tagsCreated.value = (+tagsCreated.value) + 1; 142 | selectedIndex.value = -1; 143 | 144 | nextTick(() => focus()); 145 | }; 146 | 147 | // handler to remove a tag 148 | const handleRemoveTag: (id: string) => void = (id) => { 149 | tagsData.value = tagsData.value.filter((t) => t.id !== id); 150 | tagsCreated.value = (+tagsCreated.value) - 1; 151 | }; 152 | 153 | const handleDelete: () => void = () => { 154 | if (input.value) { 155 | return; 156 | } 157 | 158 | if (selectAllRef.value) { 159 | tagsData.value = []; 160 | selectAllRef.value = false; 161 | tagsCreated.value = 0; 162 | return; 163 | } 164 | 165 | if (delTagRef.value) { 166 | const tag = delTagRef.value; 167 | tagsData.value = tagsData.value.filter((t) => t.id !== tag.id); 168 | delTagRef.value = null; 169 | tagsCreated.value = (+tagsCreated.value) - 1; 170 | } else if (tagsData.value.length) { 171 | const tag = tagsData.value[tagsData.value.length - 1]; 172 | delTagRef.value = { 173 | id: tag.id, 174 | }; 175 | tagsData.value = tagsData.value.map((t) => { 176 | if (t.id === tag.id) { 177 | return Object.assign({}, t, { 178 | highlight: true, 179 | }); 180 | } else { 181 | return t; 182 | } 183 | }); 184 | } 185 | }; 186 | 187 | // handle to manage paste 188 | const handlePaste: (event: ClipboardEvent) => void = (event) => { 189 | // cancel the default operation 190 | event.stopPropagation(); 191 | event.preventDefault(); 192 | 193 | // get the clipboard data 194 | const data = event.clipboardData?.getData("text"); 195 | 196 | if (data) { 197 | const pasteResult = HandlePaste(unref(tagsData), data, maxTags, +unref(tagsCreated), allowPaste?.delimiter, allowDuplicates); 198 | 199 | if (pasteResult?.newData) { 200 | tagsData.value = pasteResult.newData; 201 | tagsCreated.value = pasteResult.tagsCreated; 202 | } 203 | } 204 | 205 | }; 206 | 207 | const handleEscape = () => reset(); 208 | 209 | const handleEditTag: (id: string, newValue: string) => void = (id, newValue) => { 210 | tagsData.value = tagsData.value.map((tag) => { 211 | if (tag.id === id) { 212 | return Object.assign({}, tag, { 213 | name: newValue, 214 | value: newValue, 215 | }); 216 | } else { 217 | return tag; 218 | } 219 | }); 220 | onChanged?.(tagsData.value.map(item => item.value)); 221 | }; 222 | 223 | const handleSuggestSelection: (name: string) => void = (name) => { 224 | showSuggestions.value = false; 225 | nextTick(() => { 226 | handleAddTag(name); 227 | }); 228 | }; 229 | 230 | const handleKeydown: (event: KeyboardEvent) => void = (event) => { 231 | event.preventDefault(); 232 | 233 | const curSelIndex = unref(selectedIndex); 234 | 235 | if (curSelIndex < unref(filteredItems).length - 1) { 236 | selectedIndex.value = +selectedIndex.value + 1; 237 | } else { 238 | selectedIndex.value = 0; 239 | } 240 | }; 241 | 242 | const handleKeyUp: (event: KeyboardEvent) => void = (event) => { 243 | event.preventDefault(); 244 | 245 | const curSelIndex = unref(selectedIndex); 246 | 247 | if (curSelIndex > 0) { 248 | selectedIndex.value = selectedIndex.value - 1; 249 | } else { 250 | selectedIndex.value = unref(filteredItems).length - 1; 251 | } 252 | }; 253 | 254 | const handleSuggestEsc: () => void = () => { 255 | focus(); 256 | showSuggestions.value = false; 257 | } 258 | 259 | const handleSelectAll: (event: KeyboardEvent) => void = (event) => { 260 | if (!quickDelete) { 261 | return; 262 | } 263 | if (event.keyCode === 65 && !input.value) { 264 | selectAllRef.value = true; 265 | delTagRef.value = null; 266 | } 267 | }; 268 | 269 | return { 270 | tagsData, 271 | input, 272 | style, 273 | textInputRef, 274 | showSuggestions, 275 | selectedIndex, 276 | filteredItems, 277 | handleKeyUp, 278 | handleKeydown, 279 | handleAddTag, 280 | handleRemoveTag, 281 | handleDelete, 282 | handleEscape, 283 | handlePaste, 284 | handleEditTag, 285 | handleSuggestSelection, 286 | handleSuggestEsc, 287 | handleSelectAll, 288 | }; 289 | } -------------------------------------------------------------------------------- /src/components/SuggestPane.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 95 | 96 | 132 | -------------------------------------------------------------------------------- /src/components/Tag.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 143 | 144 | 193 | -------------------------------------------------------------------------------- /src/components/Tags.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 97 | 98 | 116 | -------------------------------------------------------------------------------- /src/examples/autocomplete.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 70 | -------------------------------------------------------------------------------- /src/examples/basic.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 46 | -------------------------------------------------------------------------------- /src/examples/defaults.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 60 | -------------------------------------------------------------------------------- /src/examples/duplicates.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 66 | -------------------------------------------------------------------------------- /src/examples/editable.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 62 | -------------------------------------------------------------------------------- /src/examples/maxtags.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 68 | -------------------------------------------------------------------------------- /src/examples/paste.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 63 | -------------------------------------------------------------------------------- /src/examples/quickdelete.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 68 | -------------------------------------------------------------------------------- /src/examples/readonly.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 60 | -------------------------------------------------------------------------------- /src/examples/theme.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 83 | 84 | 91 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Nunito, Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 50px; 8 | height: 100%; 9 | } 10 | 11 | html, 12 | body { 13 | height: 100%; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import SmartTagz from "./components/Main.vue"; 2 | 3 | export { SmartTagz }; 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import "./index.css"; 4 | 5 | createApp(App).mount("#app"); 6 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export interface TagModel { 2 | name: string; 3 | id: string; 4 | value: string; 5 | highlight?: boolean; 6 | readOnly?: boolean; 7 | } 8 | 9 | export interface TagsPropModel { 10 | tags: TagModel[]; 11 | } 12 | 13 | export interface TagPropModel extends TagModel { 14 | onRemove: (id: string) => void; 15 | } 16 | 17 | export type TagClass = { 18 | container: string; 19 | name: string; 20 | closeButton: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/prism.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.21.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup&plugins=line-highlight+toolbar+copy-to-clipboard */ 3 | /** 4 | * prism.js default theme for JavaScript, CSS and HTML 5 | * Based on dabblet (http://dabblet.com) 6 | * @author Lea Verou 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: black; 12 | background: none; 13 | text-shadow: 0 1px white; 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | font-size: 1em; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | word-wrap: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 34 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 35 | text-shadow: none; 36 | background: #b3d4fc; 37 | } 38 | 39 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 40 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 41 | text-shadow: none; 42 | background: #b3d4fc; 43 | } 44 | 45 | @media print { 46 | code[class*="language-"], 47 | pre[class*="language-"] { 48 | text-shadow: none; 49 | } 50 | } 51 | 52 | /* Code blocks */ 53 | pre[class*="language-"] { 54 | padding: 1em; 55 | margin: .5em 0; 56 | overflow: auto; 57 | } 58 | 59 | :not(pre) > code[class*="language-"], 60 | pre[class*="language-"] { 61 | background: #f5f2f0; 62 | } 63 | 64 | /* Inline code */ 65 | :not(pre) > code[class*="language-"] { 66 | padding: .1em; 67 | border-radius: .3em; 68 | white-space: normal; 69 | } 70 | 71 | .token.comment, 72 | .token.prolog, 73 | .token.doctype, 74 | .token.cdata { 75 | color: slategray; 76 | } 77 | 78 | .token.punctuation { 79 | color: #999; 80 | } 81 | 82 | .token.namespace { 83 | opacity: .7; 84 | } 85 | 86 | .token.property, 87 | .token.tag, 88 | .token.boolean, 89 | .token.number, 90 | .token.constant, 91 | .token.symbol, 92 | .token.deleted { 93 | color: #905; 94 | } 95 | 96 | .token.selector, 97 | .token.attr-name, 98 | .token.string, 99 | .token.char, 100 | .token.builtin, 101 | .token.inserted { 102 | color: #690; 103 | } 104 | 105 | .token.operator, 106 | .token.entity, 107 | .token.url, 108 | .language-css .token.string, 109 | .style .token.string { 110 | color: #9a6e3a; 111 | /* This background color was intended by the author of this theme. */ 112 | background: hsla(0, 0%, 100%, .5); 113 | } 114 | 115 | .token.atrule, 116 | .token.attr-value, 117 | .token.keyword { 118 | color: #07a; 119 | } 120 | 121 | .token.function, 122 | .token.class-name { 123 | color: #DD4A68; 124 | } 125 | 126 | .token.regex, 127 | .token.important, 128 | .token.variable { 129 | color: #e90; 130 | } 131 | 132 | .token.important, 133 | .token.bold { 134 | font-weight: bold; 135 | } 136 | .token.italic { 137 | font-style: italic; 138 | } 139 | 140 | .token.entity { 141 | cursor: help; 142 | } 143 | 144 | pre[data-line] { 145 | position: relative; 146 | padding: 1em 0 1em 3em; 147 | } 148 | 149 | .line-highlight { 150 | position: absolute; 151 | left: 0; 152 | right: 0; 153 | padding: inherit 0; 154 | margin-top: 1em; /* Same as .prism’s padding-top */ 155 | 156 | background: hsla(24, 20%, 50%,.08); 157 | background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 158 | 159 | pointer-events: none; 160 | 161 | line-height: inherit; 162 | white-space: pre; 163 | } 164 | 165 | .line-highlight:before, 166 | .line-highlight[data-end]:after { 167 | content: attr(data-start); 168 | position: absolute; 169 | top: .4em; 170 | left: .6em; 171 | min-width: 1em; 172 | padding: 0 .5em; 173 | background-color: hsla(24, 20%, 50%,.4); 174 | color: hsl(24, 20%, 95%); 175 | font: bold 65%/1.5 sans-serif; 176 | text-align: center; 177 | vertical-align: .3em; 178 | border-radius: 999px; 179 | text-shadow: none; 180 | box-shadow: 0 1px white; 181 | } 182 | 183 | .line-highlight[data-end]:after { 184 | content: attr(data-end); 185 | top: auto; 186 | bottom: .4em; 187 | } 188 | 189 | .line-numbers .line-highlight:before, 190 | .line-numbers .line-highlight:after { 191 | content: none; 192 | } 193 | 194 | pre[id].linkable-line-numbers span.line-numbers-rows { 195 | pointer-events: all; 196 | } 197 | pre[id].linkable-line-numbers span.line-numbers-rows > span:before { 198 | cursor: pointer; 199 | } 200 | pre[id].linkable-line-numbers span.line-numbers-rows > span:hover:before { 201 | background-color: rgba(128, 128, 128, .2); 202 | } 203 | 204 | div.code-toolbar { 205 | position: relative; 206 | } 207 | 208 | div.code-toolbar > .toolbar { 209 | position: absolute; 210 | top: .3em; 211 | right: .2em; 212 | transition: opacity 0.3s ease-in-out; 213 | opacity: 0; 214 | } 215 | 216 | div.code-toolbar:hover > .toolbar { 217 | opacity: 1; 218 | } 219 | 220 | /* Separate line b/c rules are thrown out if selector is invalid. 221 | IE11 and old Edge versions don't support :focus-within. */ 222 | div.code-toolbar:focus-within > .toolbar { 223 | opacity: 1; 224 | } 225 | 226 | div.code-toolbar > .toolbar .toolbar-item { 227 | display: inline-block; 228 | } 229 | 230 | div.code-toolbar > .toolbar a { 231 | cursor: pointer; 232 | } 233 | 234 | div.code-toolbar > .toolbar button { 235 | background: none; 236 | border: 0; 237 | color: inherit; 238 | font: inherit; 239 | line-height: normal; 240 | overflow: visible; 241 | padding: 0; 242 | -webkit-user-select: none; /* for button */ 243 | -moz-user-select: none; 244 | -ms-user-select: none; 245 | } 246 | 247 | div.code-toolbar > .toolbar a, 248 | div.code-toolbar > .toolbar button, 249 | div.code-toolbar > .toolbar span { 250 | color: #bbb; 251 | font-size: .8em; 252 | padding: 0 .5em; 253 | background: #f5f2f0; 254 | background: rgba(224, 224, 224, 0.2); 255 | box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); 256 | border-radius: .5em; 257 | } 258 | 259 | div.code-toolbar > .toolbar a:hover, 260 | div.code-toolbar > .toolbar a:focus, 261 | div.code-toolbar > .toolbar button:hover, 262 | div.code-toolbar > .toolbar button:focus, 263 | div.code-toolbar > .toolbar span:hover, 264 | div.code-toolbar > .toolbar span:focus { 265 | color: inherit; 266 | text-decoration: none; 267 | } 268 | 269 | -------------------------------------------------------------------------------- /src/prism.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.21.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup&plugins=line-highlight+toolbar+copy-to-clipboard 3 | */ 4 | var _self = 5 | "undefined" != typeof window 6 | ? window 7 | : "undefined" != typeof WorkerGlobalScope && 8 | self instanceof WorkerGlobalScope 9 | ? self 10 | : {}, 11 | Prism = (function (u) { 12 | var c = /\blang(?:uage)?-([\w-]+)\b/i, 13 | n = 0, 14 | M = { 15 | manual: u.Prism && u.Prism.manual, 16 | disableWorkerMessageHandler: 17 | u.Prism && u.Prism.disableWorkerMessageHandler, 18 | util: { 19 | encode: function e(n) { 20 | return n instanceof W 21 | ? new W(n.type, e(n.content), n.alias) 22 | : Array.isArray(n) 23 | ? n.map(e) 24 | : n 25 | .replace(/&/g, "&") 26 | .replace(/= l.reach); 225 | k += y.value.length, y = y.next 226 | ) { 227 | var b = y.value; 228 | if (t.length > n.length) return; 229 | if (!(b instanceof W)) { 230 | var x = 1; 231 | if (h && y != t.tail.prev) { 232 | m.lastIndex = k; 233 | var w = m.exec(n); 234 | if (!w) break; 235 | var A = w.index + (f && w[1] ? w[1].length : 0), 236 | P = w.index + w[0].length, 237 | S = k; 238 | for (S += y.value.length; S <= A; ) 239 | (y = y.next), (S += y.value.length); 240 | if ( 241 | ((S -= y.value.length), 242 | (k = S), 243 | y.value instanceof W) 244 | ) 245 | continue; 246 | for ( 247 | var E = y; 248 | E !== t.tail && 249 | (S < P || "string" == typeof E.value); 250 | E = E.next 251 | ) 252 | x++, (S += E.value.length); 253 | x--, (b = n.slice(k, S)), (w.index -= k); 254 | } else { 255 | m.lastIndex = 0; 256 | var w = m.exec(b); 257 | } 258 | if (w) { 259 | f && (d = w[1] ? w[1].length : 0); 260 | var A = w.index + d, 261 | O = w[0].slice(d), 262 | P = A + O.length, 263 | L = b.slice(0, A), 264 | N = b.slice(P), 265 | j = k + b.length; 266 | l && j > l.reach && (l.reach = j); 267 | var C = y.prev; 268 | L && ((C = I(t, C, L)), (k += L.length)), z(t, C, x); 269 | var _ = new W(o, g ? M.tokenize(O, g) : O, v, O); 270 | (y = I(t, C, _)), 271 | N && I(t, y, N), 272 | 1 < x && 273 | e(n, t, r, y.prev, k, { 274 | cause: o + "," + u, 275 | reach: j, 276 | }); 277 | } 278 | } 279 | } 280 | } 281 | } 282 | })(e, a, n, a.head, 0), 283 | (function (e) { 284 | var n = [], 285 | t = e.head.next; 286 | for (; t !== e.tail; ) n.push(t.value), (t = t.next); 287 | return n; 288 | })(a) 289 | ); 290 | }, 291 | hooks: { 292 | all: {}, 293 | add: function (e, n) { 294 | var t = M.hooks.all; 295 | (t[e] = t[e] || []), t[e].push(n); 296 | }, 297 | run: function (e, n) { 298 | var t = M.hooks.all[e]; 299 | if (t && t.length) for (var r, a = 0; (r = t[a++]); ) r(n); 300 | }, 301 | }, 302 | Token: W, 303 | }; 304 | function W(e, n, t, r) { 305 | (this.type = e), 306 | (this.content = n), 307 | (this.alias = t), 308 | (this.length = 0 | (r || "").length); 309 | } 310 | function i() { 311 | var e = { value: null, prev: null, next: null }, 312 | n = { value: null, prev: e, next: null }; 313 | (e.next = n), (this.head = e), (this.tail = n), (this.length = 0); 314 | } 315 | function I(e, n, t) { 316 | var r = n.next, 317 | a = { value: t, prev: n, next: r }; 318 | return (n.next = a), (r.prev = a), e.length++, a; 319 | } 320 | function z(e, n, t) { 321 | for (var r = n.next, a = 0; a < t && r !== e.tail; a++) r = r.next; 322 | ((n.next = r).prev = n), (e.length -= a); 323 | } 324 | if ( 325 | ((u.Prism = M), 326 | (W.stringify = function n(e, t) { 327 | if ("string" == typeof e) return e; 328 | if (Array.isArray(e)) { 329 | var r = ""; 330 | return ( 331 | e.forEach(function (e) { 332 | r += n(e, t); 333 | }), 334 | r 335 | ); 336 | } 337 | var a = { 338 | type: e.type, 339 | content: n(e.content, t), 340 | tag: "span", 341 | classes: ["token", e.type], 342 | attributes: {}, 343 | language: t, 344 | }, 345 | i = e.alias; 346 | i && 347 | (Array.isArray(i) 348 | ? Array.prototype.push.apply(a.classes, i) 349 | : a.classes.push(i)), 350 | M.hooks.run("wrap", a); 351 | var l = ""; 352 | for (var o in a.attributes) 353 | l += 354 | " " + 355 | o + 356 | '="' + 357 | (a.attributes[o] || "").replace(/"/g, """) + 358 | '"'; 359 | return ( 360 | "<" + 361 | a.tag + 362 | ' class="' + 363 | a.classes.join(" ") + 364 | '"' + 365 | l + 366 | ">" + 367 | a.content + 368 | "" 371 | ); 372 | }), 373 | !u.document) 374 | ) 375 | return ( 376 | u.addEventListener && 377 | (M.disableWorkerMessageHandler || 378 | u.addEventListener( 379 | "message", 380 | function (e) { 381 | var n = JSON.parse(e.data), 382 | t = n.language, 383 | r = n.code, 384 | a = n.immediateClose; 385 | u.postMessage(M.highlight(r, M.languages[t], t)), 386 | a && u.close(); 387 | }, 388 | !1 389 | )), 390 | M 391 | ); 392 | var e = M.util.currentScript(); 393 | function t() { 394 | M.manual || M.highlightAll(); 395 | } 396 | if ( 397 | (e && 398 | ((M.filename = e.src), 399 | e.hasAttribute("data-manual") && (M.manual = !0)), 400 | !M.manual) 401 | ) { 402 | var r = document.readyState; 403 | "loading" === r || ("interactive" === r && e && e.defer) 404 | ? document.addEventListener("DOMContentLoaded", t) 405 | : window.requestAnimationFrame 406 | ? window.requestAnimationFrame(t) 407 | : window.setTimeout(t, 16); 408 | } 409 | return M; 410 | })(_self); 411 | "undefined" != typeof module && module.exports && (module.exports = Prism), 412 | "undefined" != typeof global && (global.Prism = Prism); 413 | (Prism.languages.markup = { 414 | comment: //, 415 | prolog: /<\?[\s\S]+?\?>/, 416 | doctype: { 417 | pattern: 418 | /"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i, 419 | greedy: !0, 420 | inside: { 421 | "internal-subset": { 422 | pattern: /(\[)[\s\S]+(?=\]>$)/, 423 | lookbehind: !0, 424 | greedy: !0, 425 | inside: null, 426 | }, 427 | string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 }, 428 | punctuation: /^$|[[\]]/, 429 | "doctype-tag": /^DOCTYPE/, 430 | name: /[^\s<>'"]+/, 431 | }, 432 | }, 433 | cdata: //i, 434 | tag: { 435 | pattern: 436 | /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/, 437 | greedy: !0, 438 | inside: { 439 | tag: { 440 | pattern: /^<\/?[^\s>\/]+/, 441 | inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ }, 442 | }, 443 | "attr-value": { 444 | pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, 445 | inside: { 446 | punctuation: [{ pattern: /^=/, alias: "attr-equals" }, /"|'/], 447 | }, 448 | }, 449 | punctuation: /\/?>/, 450 | "attr-name": { 451 | pattern: /[^\s>\/]+/, 452 | inside: { namespace: /^[^\s>\/:]+:/ }, 453 | }, 454 | }, 455 | }, 456 | entity: [ 457 | { pattern: /&[\da-z]{1,8};/i, alias: "named-entity" }, 458 | /&#x?[\da-f]{1,8};/i, 459 | ], 460 | }), 461 | (Prism.languages.markup.tag.inside["attr-value"].inside.entity = 462 | Prism.languages.markup.entity), 463 | (Prism.languages.markup.doctype.inside["internal-subset"].inside = 464 | Prism.languages.markup), 465 | Prism.hooks.add("wrap", function (a) { 466 | "entity" === a.type && 467 | (a.attributes.title = a.content.replace(/&/, "&")); 468 | }), 469 | Object.defineProperty(Prism.languages.markup.tag, "addInlined", { 470 | value: function (a, e) { 471 | var s = {}; 472 | (s["language-" + e] = { 473 | pattern: /(^$)/i, 474 | lookbehind: !0, 475 | inside: Prism.languages[e], 476 | }), 477 | (s.cdata = /^$/i); 478 | var n = { 479 | "included-cdata": { pattern: //i, inside: s }, 480 | }; 481 | n["language-" + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }; 482 | var t = {}; 483 | (t[a] = { 484 | pattern: RegExp( 485 | "(<__[^]*?>)(?:))*\\]\\]>|(?!)".replace( 486 | /__/g, 487 | function () { 488 | return a; 489 | } 490 | ), 491 | "i" 492 | ), 493 | lookbehind: !0, 494 | greedy: !0, 495 | inside: n, 496 | }), 497 | Prism.languages.insertBefore("markup", "cdata", t); 498 | }, 499 | }), 500 | (Prism.languages.html = Prism.languages.markup), 501 | (Prism.languages.mathml = Prism.languages.markup), 502 | (Prism.languages.svg = Prism.languages.markup), 503 | (Prism.languages.xml = Prism.languages.extend("markup", {})), 504 | (Prism.languages.ssml = Prism.languages.xml), 505 | (Prism.languages.atom = Prism.languages.xml), 506 | (Prism.languages.rss = Prism.languages.xml); 507 | !(function () { 508 | if ( 509 | "undefined" != typeof self && 510 | self.Prism && 511 | self.document && 512 | document.querySelector 513 | ) { 514 | var t, 515 | s = function () { 516 | if (void 0 === t) { 517 | var e = document.createElement("div"); 518 | (e.style.fontSize = "13px"), 519 | (e.style.lineHeight = "1.5"), 520 | (e.style.padding = "0"), 521 | (e.style.border = "0"), 522 | (e.innerHTML = " 
 "), 523 | document.body.appendChild(e), 524 | (t = 38 === e.offsetHeight), 525 | document.body.removeChild(e); 526 | } 527 | return t; 528 | }, 529 | l = !0, 530 | a = 0; 531 | Prism.hooks.add("before-sanity-check", function (e) { 532 | var t = e.element.parentNode, 533 | n = t && t.getAttribute("data-line"); 534 | if (t && n && /pre/i.test(t.nodeName)) { 535 | var i = 0; 536 | g(".line-highlight", t).forEach(function (e) { 537 | (i += e.textContent.length), e.parentNode.removeChild(e); 538 | }), 539 | i && 540 | /^( \n)+$/.test(e.code.slice(-i)) && 541 | (e.code = e.code.slice(0, -i)); 542 | } 543 | }), 544 | Prism.hooks.add("complete", function e(t) { 545 | var n = t.element.parentNode, 546 | i = n && n.getAttribute("data-line"); 547 | if (n && i && /pre/i.test(n.nodeName)) { 548 | clearTimeout(a); 549 | var r = Prism.plugins.lineNumbers, 550 | o = t.plugins && t.plugins.lineNumbers; 551 | if (b(n, "line-numbers") && r && !o) 552 | Prism.hooks.add("line-numbers", e); 553 | else u(n, i)(), (a = setTimeout(c, 1)); 554 | } 555 | }), 556 | window.addEventListener("hashchange", c), 557 | window.addEventListener("resize", function () { 558 | g("pre[data-line]") 559 | .map(function (e) { 560 | return u(e); 561 | }) 562 | .forEach(v); 563 | }); 564 | } 565 | function g(e, t) { 566 | return Array.prototype.slice.call((t || document).querySelectorAll(e)); 567 | } 568 | function b(e, t) { 569 | return ( 570 | (t = " " + t + " "), 571 | -1 < (" " + e.className + " ").replace(/[\n\t]/g, " ").indexOf(t) 572 | ); 573 | } 574 | function v(e) { 575 | e(); 576 | } 577 | function u(u, e, c) { 578 | var t = (e = "string" == typeof e ? e : u.getAttribute("data-line")) 579 | .replace(/\s+/g, "") 580 | .split(",") 581 | .filter(Boolean), 582 | d = +u.getAttribute("data-line-offset") || 0, 583 | f = (s() ? parseInt : parseFloat)(getComputedStyle(u).lineHeight), 584 | m = b(u, "line-numbers"), 585 | p = m ? u : u.querySelector("code") || u, 586 | h = []; 587 | t.forEach(function (e) { 588 | var t = e.split("-"), 589 | n = +t[0], 590 | i = +t[1] || n, 591 | r = 592 | u.querySelector('.line-highlight[data-range="' + e + '"]') || 593 | document.createElement("div"); 594 | if ( 595 | (h.push(function () { 596 | r.setAttribute("aria-hidden", "true"), 597 | r.setAttribute("data-range", e), 598 | (r.className = (c || "") + " line-highlight"); 599 | }), 600 | m && Prism.plugins.lineNumbers) 601 | ) { 602 | var o = Prism.plugins.lineNumbers.getLine(u, n), 603 | a = Prism.plugins.lineNumbers.getLine(u, i); 604 | if (o) { 605 | var s = o.offsetTop + "px"; 606 | h.push(function () { 607 | r.style.top = s; 608 | }); 609 | } 610 | if (a) { 611 | var l = a.offsetTop - o.offsetTop + a.offsetHeight + "px"; 612 | h.push(function () { 613 | r.style.height = l; 614 | }); 615 | } 616 | } else 617 | h.push(function () { 618 | r.setAttribute("data-start", n), 619 | n < i && r.setAttribute("data-end", i), 620 | (r.style.top = (n - d - 1) * f + "px"), 621 | (r.textContent = new Array(i - n + 2).join(" \n")); 622 | }); 623 | h.push(function () { 624 | p.appendChild(r); 625 | }); 626 | }); 627 | var i = u.id; 628 | if (m && i) { 629 | for (var n = "linkable-line-numbers", r = !1, o = u; o; ) { 630 | if (b(o, n)) { 631 | r = !0; 632 | break; 633 | } 634 | o = o.parentElement; 635 | } 636 | if (r) { 637 | b(u, n) || 638 | h.push(function () { 639 | u.className = (u.className + " " + n).trim(); 640 | }); 641 | var a = parseInt(u.getAttribute("data-start") || "1"); 642 | g(".line-numbers-rows > span", u).forEach(function (e, t) { 643 | var n = t + a; 644 | e.onclick = function () { 645 | var e = i + "." + n; 646 | (l = !1), 647 | (location.hash = e), 648 | setTimeout(function () { 649 | l = !0; 650 | }, 1); 651 | }; 652 | }); 653 | } 654 | } 655 | return function () { 656 | h.forEach(v); 657 | }; 658 | } 659 | function c() { 660 | var e = location.hash.slice(1); 661 | g(".temporary.line-highlight").forEach(function (e) { 662 | e.parentNode.removeChild(e); 663 | }); 664 | var t = (e.match(/\.([\d,-]+)$/) || [, ""])[1]; 665 | if (t && !document.getElementById(e)) { 666 | var n = e.slice(0, e.lastIndexOf(".")), 667 | i = document.getElementById(n); 668 | if (i) 669 | i.hasAttribute("data-line") || i.setAttribute("data-line", ""), 670 | u(i, t, "temporary ")(), 671 | l && 672 | document 673 | .querySelector(".temporary.line-highlight") 674 | .scrollIntoView(); 675 | } 676 | } 677 | })(); 678 | !(function () { 679 | if ("undefined" != typeof self && self.Prism && self.document) { 680 | var i = [], 681 | l = {}, 682 | c = function () {}; 683 | Prism.plugins.toolbar = {}; 684 | var e = (Prism.plugins.toolbar.registerButton = function (e, n) { 685 | var t; 686 | (t = 687 | "function" == typeof n 688 | ? n 689 | : function (e) { 690 | var t; 691 | return ( 692 | "function" == typeof n.onClick 693 | ? (((t = document.createElement("button")).type = "button"), 694 | t.addEventListener("click", function () { 695 | n.onClick.call(this, e); 696 | })) 697 | : "string" == typeof n.url 698 | ? ((t = document.createElement("a")).href = n.url) 699 | : (t = document.createElement("span")), 700 | n.className && t.classList.add(n.className), 701 | (t.textContent = n.text), 702 | t 703 | ); 704 | }), 705 | e in l 706 | ? console.warn( 707 | 'There is a button with the key "' + e + '" registered already.' 708 | ) 709 | : i.push((l[e] = t)); 710 | }), 711 | t = (Prism.plugins.toolbar.hook = function (a) { 712 | var e = a.element.parentNode; 713 | if ( 714 | e && 715 | /pre/i.test(e.nodeName) && 716 | !e.parentNode.classList.contains("code-toolbar") 717 | ) { 718 | var t = document.createElement("div"); 719 | t.classList.add("code-toolbar"), 720 | e.parentNode.insertBefore(t, e), 721 | t.appendChild(e); 722 | var r = document.createElement("div"); 723 | r.classList.add("toolbar"); 724 | var n = i, 725 | o = (function (e) { 726 | for (; e; ) { 727 | var t = e.getAttribute("data-toolbar-order"); 728 | if (null != t) 729 | return (t = t.trim()).length ? t.split(/\s*,\s*/g) : []; 730 | e = e.parentElement; 731 | } 732 | })(a.element); 733 | o && 734 | (n = o.map(function (e) { 735 | return l[e] || c; 736 | })), 737 | n.forEach(function (e) { 738 | var t = e(a); 739 | if (t) { 740 | var n = document.createElement("div"); 741 | n.classList.add("toolbar-item"), 742 | n.appendChild(t), 743 | r.appendChild(n); 744 | } 745 | }), 746 | t.appendChild(r); 747 | } 748 | }); 749 | e("label", function (e) { 750 | var t = e.element.parentNode; 751 | if (t && /pre/i.test(t.nodeName) && t.hasAttribute("data-label")) { 752 | var n, 753 | a, 754 | r = t.getAttribute("data-label"); 755 | try { 756 | a = document.querySelector("template#" + r); 757 | } catch (e) {} 758 | return ( 759 | a 760 | ? (n = a.content) 761 | : (t.hasAttribute("data-url") 762 | ? ((n = document.createElement("a")).href = 763 | t.getAttribute("data-url")) 764 | : (n = document.createElement("span")), 765 | (n.textContent = r)), 766 | n 767 | ); 768 | } 769 | }), 770 | Prism.hooks.add("complete", t); 771 | } 772 | })(); 773 | !(function () { 774 | if ("undefined" != typeof self && self.Prism && self.document) 775 | if (Prism.plugins.toolbar) { 776 | var i = window.ClipboardJS || void 0; 777 | i || "function" != typeof require || (i = require("clipboard")); 778 | var c = []; 779 | if (!i) { 780 | var o = document.createElement("script"), 781 | t = document.querySelector("head"); 782 | (o.onload = function () { 783 | if ((i = window.ClipboardJS)) for (; c.length; ) c.pop()(); 784 | }), 785 | (o.src = 786 | "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js"), 787 | t.appendChild(o); 788 | } 789 | Prism.plugins.toolbar.registerButton("copy-to-clipboard", function (o) { 790 | var t = document.createElement("button"); 791 | t.textContent = "Copy"; 792 | var e = o.element; 793 | return i ? n() : c.push(n), t; 794 | function n() { 795 | var o = new i(t, { 796 | text: function () { 797 | return e.textContent; 798 | }, 799 | }); 800 | o.on("success", function () { 801 | (t.textContent = "Copied!"), r(); 802 | }), 803 | o.on("error", function () { 804 | (t.textContent = "Press Ctrl+C to copy"), r(); 805 | }); 806 | } 807 | function r() { 808 | setTimeout(function () { 809 | t.textContent = "Copy"; 810 | }, 5e3); 811 | } 812 | }); 813 | } else 814 | console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."); 815 | })(); 816 | -------------------------------------------------------------------------------- /src/test/countries.ts: -------------------------------------------------------------------------------- 1 | export default ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Anguilla", "Antigua & Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia & Herzegovina", "Botswana", "Brazil", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Cape Verde", "Cayman Islands", "Chad", "Chile", "China", "Colombia", "Congo", "Cook Islands", "Costa Rica", "Cote D Ivoire", "Croatia", "Cruise Ship", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Estonia", "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", "French Polynesia", "French West Indies", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kuwait", "Kyrgyz Republic", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Mauritania", "Mauritius", "Mexico", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Namibia", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda", "Saint Pierre & Miquelon", "Samoa", "San Marino", "Satellite", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Sri Lanka", "St Kitts & Nevis", "St Lucia", "St Vincent", "St. Lucia", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor L'Este", "Togo", "Tonga", "Trinidad & Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks & Caicos", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "Uruguay", "Uzbekistan", "Venezuela", "Vietnam", "Virgin Islands (US)", "Yemen", "Zambia", "Zimbabwe"]; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "declaration": true, 7 | "declarationDir": "./dist", 8 | "outDir": "./dist", 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "importHelpers": true, 12 | "moduleResolution": "node", 13 | "experimentalDecorators": true, 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "sourceMap": true, 17 | "baseUrl": ".", 18 | "jsx": "preserve", 19 | "types": ["node", "vue"], 20 | "paths": { 21 | "@/*": ["src/*"] 22 | }, 23 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /types/smart-tagz.d.ts: -------------------------------------------------------------------------------- 1 | import { FunctionalComponent } from "vue"; 2 | 3 | interface Props { 4 | readOnly: boolean; 5 | defaultTags: string[]; 6 | width: string; 7 | sources: string[]; 8 | autosuggest: boolean; 9 | allowPaste: { 10 | delimiter: string; 11 | }, 12 | editable: boolean; 13 | allowDuplicates: boolean; 14 | maxTags: number; 15 | inputPlaceholder: string; 16 | quickDelete: boolean; 17 | theme: { 18 | primary: string; 19 | secondary: string; 20 | tagTextColor: string; 21 | } 22 | } 23 | 24 | interface SmartTagzComponent extends FunctionalComponent {} 25 | 26 | export const SmartTagz: SmartTagzComponent; -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from "@vitejs/plugin-vue"; 2 | 3 | export default { 4 | plugins: [vue()], 5 | }; 6 | --------------------------------------------------------------------------------