diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 456b89f..55a94e2 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -75,6 +75,11 @@ module.exports = {
'@vuepress/plugin-search',
'@vuepress/plugin-nprogress',
],
+ extraWatchFiles: [
+ '.vuepress/generateApiDocs/**/*.js',
+ '../src/**.*.js',
+ '../src/**.*.vue',
+ ],
themeConfig: {
repo: 'sagalbot/vue-select',
editLinks: true,
diff --git a/docs/.vuepress/generateApiDocs/clientDynamicModules.js b/docs/.vuepress/generateApiDocs/clientDynamicModules.js
new file mode 100644
index 0000000..91dfd22
--- /dev/null
+++ b/docs/.vuepress/generateApiDocs/clientDynamicModules.js
@@ -0,0 +1,23 @@
+const generator = require('./utils/node/generator');
+const {green} = require('chalk');
+
+/**
+ * Dynamically generates all API documentation with vue-docgen-api.
+ * The resulting object can be imported and used client-side via:
+ *
+ * import documentation from '@dynamic/api'
+ *
+ * @return {Promise<{name: string, content: string}>}
+ */
+async function clientDynamicModules (sourceDir) {
+ const docs = await generator(sourceDir);
+
+ console.log(green('✅ Generated API documentation for Select.vue'));
+
+ return {
+ name: 'api.js',
+ content: `export default ${JSON.stringify(docs)}`,
+ };
+}
+
+module.exports = clientDynamicModules;
diff --git a/docs/.vuepress/generateApiDocs/components/ApiSlots.vue b/docs/.vuepress/generateApiDocs/components/ApiSlots.vue
new file mode 100644
index 0000000..6ca3db5
--- /dev/null
+++ b/docs/.vuepress/generateApiDocs/components/ApiSlots.vue
@@ -0,0 +1,47 @@
+
+
+
+ -
+
+
+ {{ slot.name }}
+
+
+ -
+
Bindings
+
+ -
+
{{value}}: {{key}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/generateApiDocs/debug.js b/docs/.vuepress/generateApiDocs/debug.js
new file mode 100644
index 0000000..1c2005f
--- /dev/null
+++ b/docs/.vuepress/generateApiDocs/debug.js
@@ -0,0 +1,10 @@
+const getSlotDefinitions = require('./utils/node/getAdditionalSlotProperties');
+const generator = require('./utils/node/generator');
+
+(async () => {
+
+ const component = await generator('/Users/sagalbot/Sites/vue-select/docs');
+
+ debugger;
+
+})();
diff --git a/docs/.vuepress/generateApiDocs/enhanceApp.js b/docs/.vuepress/generateApiDocs/enhanceApp.js
index 06cf305..40657f7 100644
--- a/docs/.vuepress/generateApiDocs/enhanceApp.js
+++ b/docs/.vuepress/generateApiDocs/enhanceApp.js
@@ -1,8 +1,10 @@
// import docs from '@dynamic/api.js';
import ApiProps from './components/ApiProps';
import ApiEvents from './components/ApiEvents';
+import ApiSlots from './components/ApiSlots';
export default ({Vue, options, router, siteData}) => {
Vue.component('api-props', ApiProps);
+ Vue.component('api-slots', ApiSlots);
Vue.component('api-events', ApiEvents);
}
diff --git a/docs/.vuepress/generateApiDocs/extendPageData.js b/docs/.vuepress/generateApiDocs/extendPageData.js
index 66da226..85f3dcc 100644
--- a/docs/.vuepress/generateApiDocs/extendPageData.js
+++ b/docs/.vuepress/generateApiDocs/extendPageData.js
@@ -1,4 +1,4 @@
-const generator = require('./generator');
+const generator = require('./utils/node/generator');
module.exports = async function (page) {
const section = ['props', 'events', 'slots', 'methods'].find(
diff --git a/docs/.vuepress/generateApiDocs/generator.js b/docs/.vuepress/generateApiDocs/generator.js
deleted file mode 100644
index d360e9f..0000000
--- a/docs/.vuepress/generateApiDocs/generator.js
+++ /dev/null
@@ -1,39 +0,0 @@
-const docs = require('vue-docgen-api');
-const path = require('path');
-// const getMemberFilter = require('vue-docgen-api/dist/utils/getMemberFilter.js');
-const getMemberFilter = require('vue-docgen-api/dist/utils/getPropsFilter');
-const bt = require('@babel/types');
-const {NodePath} = require('ast-types');
-const {Documentation, ParseOptions, ComponentDoc} = require('vue-docgen-api');
-
-/**
- * Generate an object of API documentation.
- * @param documentationRootDir
- * @return {Promise}
- */
-module.exports = async (documentationRootDir) => {
- const file = path.resolve(documentationRootDir,
- '../src/components/Select.vue');
- return await docs.parse(file, {
- jsx: false,
- // addScriptHandlers: [
- // /**
- // * @param {Documentation} docs
- // * @param {NodePath} path
- // * @param {bt.File} astPath
- // * @param {ParseOptions} options
- // * @return {Promise}
- // */
- // function (docs, path, astPath, options) {
- // if (bt.isObjectExpression(path.node)) {
- // const propsPath = path
- // .get('properties')
- // .filter(nodePath => bt.isObjectProperty(nodePath.node) &&
- // getMemberFilter('props')(nodePath));
- //
- //
- // }
- // },
- // ],
- });
-};
diff --git a/docs/.vuepress/generateApiDocs/index.js b/docs/.vuepress/generateApiDocs/index.js
index c6535b9..50bd44b 100644
--- a/docs/.vuepress/generateApiDocs/index.js
+++ b/docs/.vuepress/generateApiDocs/index.js
@@ -1,7 +1,6 @@
const path = require('path');
-const chalk = require('chalk');
-const generator = require('./generator');
const extendPageData = require('./extendPageData');
+const clientDynamicModules = require('./clientDynamicModules');
/**
* @param options
@@ -12,22 +11,12 @@ module.exports = (options, {sourceDir}) => ({
name: 'vuepress-docgen',
/**
- * Dynamically generates all API documentation with vue-docgen-api.
- * The resulting object can be imported and used client-side via:
- *
- * import documentation from '@dynamic/api'
+ * Generates API documentation for use on the client side.
*
* @see https://vuepress.vuejs.org/plugin/option-api.html#clientdynamicmodules
* @return {Promise<{name: string, content: string}>}
*/
- async clientDynamicModules () {
- const docs = await generator(sourceDir);
- console.log(chalk.green('✅ Generated API documentation for Select.vue'));
- return {
- name: 'api.js',
- content: `export default ${JSON.stringify(docs)}`,
- };
- },
+ clientDynamicModules: async () => await clientDynamicModules(sourceDir),
/**
* @see https://vuepress.vuejs.org/plugin/option-api.html#enhanceappfiles
diff --git a/docs/.vuepress/generateApiDocs/utils/highlight.js b/docs/.vuepress/generateApiDocs/utils/highlight.js
index 93de56a..9c8d064 100644
--- a/docs/.vuepress/generateApiDocs/utils/highlight.js
+++ b/docs/.vuepress/generateApiDocs/utils/highlight.js
@@ -3,6 +3,9 @@ import { highlight, languages } from 'prismjs';
/**
* Returns code block with prism markup.
* @param {String} snippet
+ * @param {String} language
* @return {*}
*/
-export default snippet => highlight(snippet, languages.javascript, 'javascript')
+export default (snippet, language = 'javascript') => {
+ return highlight(snippet, languages[language], language);
+}
diff --git a/docs/.vuepress/generateApiDocs/utils/node/Slots.js b/docs/.vuepress/generateApiDocs/utils/node/Slots.js
new file mode 100644
index 0000000..f4a2561
--- /dev/null
+++ b/docs/.vuepress/generateApiDocs/utils/node/Slots.js
@@ -0,0 +1,18 @@
+module.exports = class Slots {
+ constructor (slots = {}) {
+ this.slots = slots;
+ }
+
+ add (name, slot) {
+ this.slots[name] = slot;
+ return this;
+ }
+
+ get definitions () {
+ return this.slots;
+ }
+
+ absorb (slots) {
+ this.slots.map()
+ }
+};
diff --git a/docs/.vuepress/generateApiDocs/utils/node/generator.js b/docs/.vuepress/generateApiDocs/utils/node/generator.js
new file mode 100644
index 0000000..fdc8f85
--- /dev/null
+++ b/docs/.vuepress/generateApiDocs/utils/node/generator.js
@@ -0,0 +1,50 @@
+const path = require('path');
+const bt = require('@babel/types');
+const docs = require('vue-docgen-api');
+const {NodePath} = require('ast-types');
+const {Documentation, ParseOptions, ComponentDoc} = require('vue-docgen-api');
+const additionalSlotProperties = require('./getAdditionalSlotProperties');
+
+/**
+ * Generate an object of API documentation.
+ * @param documentationRootDir
+ * @return {Promise}
+ */
+module.exports = async (documentationRootDir) => {
+
+ const file = path.resolve(
+ documentationRootDir,
+ '../src/components/Select.vue',
+ );
+
+ const documentation = await docs.parse(file, {
+ jsx: false,
+ addScriptHandlers: [
+ /**
+ * @param {Documentation} docs
+ * @param {NodePath} path
+ * @param {bt.File} astPath
+ * @param {ParseOptions} options
+ * @return {Promise}
+ */
+ function (docs, path, astPath, options) {
+
+ },
+ ],
+ addTemplateHandlers: [
+ /**
+ * @param {Documentation} docs
+ * @param {ASTElement} templateAst
+ * @param {TemplateParserOptions} options
+ */
+ function (docs, templateAst, options) {
+ },
+ ],
+ });
+
+ const definitions = additionalSlotProperties(file);
+
+ documentation.slots = documentation.slots.map(slot => ({ ...slot, ...definitions[slot.name] }));
+
+ return documentation;
+};
diff --git a/docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js b/docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js
new file mode 100644
index 0000000..85f1a9a
--- /dev/null
+++ b/docs/.vuepress/generateApiDocs/utils/node/getAdditionalSlotProperties.js
@@ -0,0 +1,45 @@
+const fs = require('fs');
+const path = require('path');
+const cheerio = require('cheerio');
+const pick = require('lodash/pick');
+const prettier = require('prettier');
+const compiler = require('vue-template-compiler');
+const Slots = require('./Slots');
+
+const t = require('@babel/types');
+const {parse} = require('@babel/parser');
+const traverse = require('@babel/traverse');
+
+function pickBindingsFromElement ({attribs}) {
+ return pick(
+ attribs,
+ Object.keys(attribs)
+ .filter(attr => attr.indexOf(':') === 0 || attr === 'v-bind'),
+ );
+}
+
+/**
+ * @param pathToComponent
+ * @return {Object}
+ */
+function getAdditionalSlotProperties (pathToComponent) {
+ const slots = new Slots();
+ const file = fs.readFileSync(path.resolve(pathToComponent)).toString();
+ const {template} = compiler.parseComponent(file);
+ const $ = cheerio.load(template.content);
+
+ $('slot').each(function (index, element) {
+ const bindings = pickBindingsFromElement(element) || {};
+ const slotName = element.attribs.name || 'default';
+ const content = prettier.format($.html(element), {parser: 'html'});
+
+ slots.add(slotName, {
+ content,
+ bindings,
+ });
+ });
+
+ return slots.definitions;
+}
+
+module.exports = getAdditionalSlotProperties;
diff --git a/docs/api/slots.md b/docs/api/slots.md
index 1f3a94f..bb75d63 100644
--- a/docs/api/slots.md
+++ b/docs/api/slots.md
@@ -1,65 +1,14 @@
-::: tip
-Vue Select leverages scoped slots to allow for total customization of the presentation layer.
-Slots can be used to change the look and feel of the UI, or to simply swap out text.
+---
+title: API/Slots
+api: slots
+---
+
+# Vue Select Slots
+
+::: tip Using These Docs
+The documentation below displays the slots **as they are written in the template**. This lets you
+see the default implementation as written in the Vue Select component, as well as any bindings that
+a scoped slot offers.
:::
-## Selected Option(s)
-
-### `selected-option`
-
-#### Scope:
-
-- `option {Object}` - A selected option
-
-```html
-
- {{ getOptionLabel(option) }}
-
-```
-
-### `selected-option-container`
-
-#### Scope:
-
-- `option {Object}` - A selected option
-- `deselect {Function}` - Method used to deselect a given option when `multiple` is true
-- `disabled {Boolean}` - Determine if the component is disabled
-- `multiple {Boolean}` - If the component supports the selection of multiple values
-
-```html
-
-
-
- {{ getOptionLabel(option) }}
-
-
-
-
-```
-
-## Component Actions
-
-### `spinner`
-
-```html
-
- Loading...
-
-```
-
-## Dropdown
-
-### `option`
-
-#### Scope:
-
-- `option {Object}` - The currently iterated option from `filteredOptions`
-
-```html
-
- {{ getOptionLabel(option) }}
-
-```
+
diff --git a/docs/package.json b/docs/package.json
index 395b9f8..6c155c8 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -18,15 +18,19 @@
"@vuepress/plugin-search": "^1.2.0",
"ast-types": "^0.13.2",
"chalk": "^3.0.0",
+ "cheerio": "^1.0.0-rc.3",
"cross-env": "^5.2.0",
"fuse.js": "^3.4.4",
"gh-pages": "^0.11.0",
+ "lodash": "^4.17.15",
"markdown-it": "^10.0.0",
"node-sass": "^4.12.0",
+ "prettier": "^1.19.1",
"prismjs": "^1.17.1",
"sass-loader": "^7.1.0",
"vue": "^2.6.10",
"vue-docgen-api": "^4.0.4",
+ "vue-template-compiler": "^2.6.10",
"vuepress": "^1.2.0",
"vuex": "^3.1.0"
}
diff --git a/docs/yarn.lock b/docs/yarn.lock
index 48788b7..9974979 100644
--- a/docs/yarn.lock
+++ b/docs/yarn.lock
@@ -2020,6 +2020,18 @@ character-parser@^2.1.1:
dependencies:
is-regex "^1.0.3"
+cheerio@^1.0.0-rc.3:
+ version "1.0.0-rc.3"
+ resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
+ integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==
+ dependencies:
+ css-select "~1.2.0"
+ dom-serializer "~0.1.1"
+ entities "~1.1.1"
+ htmlparser2 "^3.9.1"
+ lodash "^4.15.0"
+ parse5 "^3.0.1"
+
chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.6:
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@@ -2518,7 +2530,7 @@ css-select-base-adapter@^0.1.1:
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
-css-select@^1.1.0:
+css-select@^1.1.0, css-select@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
@@ -2915,6 +2927,14 @@ dom-serializer@0:
domelementtype "^2.0.1"
entities "^2.0.0"
+dom-serializer@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
+ integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==
+ dependencies:
+ domelementtype "^1.3.0"
+ entities "^1.1.1"
+
dom-walk@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
@@ -2925,7 +2945,7 @@ domain-browser@^1.1.1:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
-domelementtype@1, domelementtype@^1.3.1:
+domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
@@ -3926,7 +3946,7 @@ html-tags@^2.0.0:
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
-htmlparser2@^3.3.0:
+htmlparser2@^3.3.0, htmlparser2@^3.9.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
@@ -4779,7 +4799,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
+lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -5743,6 +5763,13 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
+parse5@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c"
+ integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==
+ dependencies:
+ "@types/node" "*"
+
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -6248,6 +6275,11 @@ prettier@1.16.3:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
+prettier@^1.19.1:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
+ integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+
pretty-bytes@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"