├── .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 | [](https://dev.azure.com/prabhummurthy/smart-tagz/_build/latest?definitionId=4&branchName=master)
11 | [](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 | [](https://deepscan.io/dashboard#view=project&tid=10074&pid=13324&bid=220204)
13 | [](https://lgtm.com/projects/g/prabhuignoto/smart-tagz/context:javascript)
14 | 
15 | [](https://depfu.com/github/prabhuignoto/smart-tagz?project_id=18158)
16 |
17 |
18 |
19 |
20 |
21 | [](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 |
66 |
76 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
10 |
11 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
48 |
--------------------------------------------------------------------------------
/src/components/Demo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 | A Smart input tags component built for
18 | Vue 3 .
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
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 |
2 |
54 |
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 |
2 |
3 |
9 |
16 | {{ item }}
17 |
18 |
19 |
20 |
21 |
22 |
95 |
96 |
132 |
--------------------------------------------------------------------------------
/src/components/Tag.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
17 | {{ name }}
23 |
29 |
30 |
31 |
32 |
33 |
34 |
143 |
144 |
193 |
--------------------------------------------------------------------------------
/src/components/Tags.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
24 |
25 |
26 |
27 |
97 |
98 |
116 |
--------------------------------------------------------------------------------
/src/examples/autocomplete.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | When set to true, the autosuggest prop suggests values in the dropdown as
8 | you type. You also need to set the sources prop for this to work. The
9 | sources prop should be an Array of strings.
10 |
11 |
12 | {{ code }}
13 |
14 |
15 |
33 |
34 |
35 |
36 |
37 |
70 |
--------------------------------------------------------------------------------
/src/examples/basic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | smart-tagz has some great defaults for starters. Following is a very basic
8 | variant that takes a custom placeholder message.
9 |
10 |
11 | {{ code }}
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
46 |
--------------------------------------------------------------------------------
/src/examples/defaults.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | We can initialize smart-tagz with a set of default tags. This setting will
8 | can be used along with the readonly prop to create tags for display only
9 | purposes.
10 |
11 |
12 | {{ code }}
13 |
14 |
15 |
35 |
36 |
37 |
38 |
39 |
60 |
--------------------------------------------------------------------------------
/src/examples/duplicates.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Allow / Disallow Duplicate Tags
5 |
6 |
7 | You can decide how to manage duplicate tags by either allowing or
8 | disallowing them completely. When set to false no duplicate values are
9 | allowed.
10 |
11 |
12 | {{ code }}
13 |
14 |
15 |
34 |
35 |
36 |
37 |
38 |
66 |
--------------------------------------------------------------------------------
/src/examples/editable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | The tags are not editable by default, but this setting can be changed with
8 | the editable prop. Double click the tag to get into edit mode, make your
9 | changes and hit enter to save.
10 |
11 |
12 | {{ code }}
13 |
14 |
15 |
31 |
32 |
33 |
34 |
35 |
62 |
--------------------------------------------------------------------------------
/src/examples/maxtags.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | The component can be configured to accept the maximum tags that can be
8 | created. Once the threshold is reached, the textbox input will be hidden
9 | from the user.
10 |
11 |
12 | {{ code }}
13 |
14 |
15 |
35 |
36 |
37 |
38 |
39 |
68 |
--------------------------------------------------------------------------------
/src/examples/paste.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | The component can parse delimited input strings and automatically create
8 | tags for you. The default delimiter is "," but you can change this by
9 | manually setting the delimiter option. Try pasting the following delimited
10 | string into the input box.
11 |
12 |
France;Germany;Poland
13 |
14 | {{ code }}
15 |
16 |
17 |
26 |
27 |
28 |
29 |
30 |
63 |
--------------------------------------------------------------------------------
/src/examples/quickdelete.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 | The quick delete function allows to select all the tags and delete them in
8 | one go. From the input box, try pressing CTR + A to select all the tags
9 | and press DEL or BACKSPACE to delete all the Tags.
10 |
11 |
12 | {{ code }}
13 |
14 |
15 |
35 |
36 |
37 |
38 |
39 |
68 |
--------------------------------------------------------------------------------
/src/examples/readonly.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
Use the readonly prop to make the component readonly.
7 |
8 | {{ code }}
9 |
10 |
11 |
29 |
30 |
31 |
32 |
33 |
60 |
--------------------------------------------------------------------------------
/src/examples/theme.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Custom Color Scheme
5 |
6 |
7 | The components color scheme can be customized by passing a custom theme
8 | prop.
9 |
10 |
11 |
12 | primary - targets the background color of the tag and the background of
13 | the autosuggest dropdown.
14 |
15 | background - targets the background of the main container.
16 | tagTextColor - targets the forecolor of the tag.
17 |
18 |
19 | {{ code }}
20 |
21 |
22 |
46 |
47 |
48 |
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 | "" +
369 | a.tag +
370 | ">"
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 | /?[\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 |
--------------------------------------------------------------------------------