2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-22 10:30:34 +03:00

docs(slots): generate slot documentation with extra properties

This commit is contained in:
Jeff
2019-11-22 17:43:19 -08:00
parent ad14fa3014
commit c7e06ad1b3
15 changed files with 260 additions and 122 deletions
+5
View File
@@ -75,6 +75,11 @@ module.exports = {
'@vuepress/plugin-search', '@vuepress/plugin-search',
'@vuepress/plugin-nprogress', '@vuepress/plugin-nprogress',
], ],
extraWatchFiles: [
'.vuepress/generateApiDocs/**/*.js',
'../src/**.*.js',
'../src/**.*.vue',
],
themeConfig: { themeConfig: {
repo: 'sagalbot/vue-select', repo: 'sagalbot/vue-select',
editLinks: true, editLinks: true,
@@ -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;
@@ -0,0 +1,47 @@
<template>
<div>
<ul>
<li v-for="slot in slots">
<h2 :id="slot.name">
<a :href="`#${slot.name}`" aria-hidden="true" class="header-anchor">#</a>
{{ slot.name }}
</h2>
<ul v-if="slot.bindings">
<li>
<h4>Bindings</h4>
</li>
<li v-for="(key, value) in slot.bindings">
<code>{{value}}: {{key}}</code>
</li>
</ul>
<pre><code v-html="slot.rendered"></code></pre>
<Content :slot-key="slot.name"></Content>
</li>
</ul>
</div>
</template>
<script>
import docs from "@dynamic/api";
import markdown from "../utils/markdown";
import highlight from "../utils/highlight";
export default {
name: "ApiSlots",
methods: {
markdown
},
computed: {
slots() {
return docs.slots
.map(slot => ({
...slot,
rendered: highlight(slot.content, "html"),
}))
.sort((a, b) => a.name > b.name);
}
}
};
</script>
<style scoped src="../assets/listing.css"></style>
+10
View File
@@ -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;
})();
@@ -1,8 +1,10 @@
// import docs from '@dynamic/api.js'; // import docs from '@dynamic/api.js';
import ApiProps from './components/ApiProps'; import ApiProps from './components/ApiProps';
import ApiEvents from './components/ApiEvents'; import ApiEvents from './components/ApiEvents';
import ApiSlots from './components/ApiSlots';
export default ({Vue, options, router, siteData}) => { export default ({Vue, options, router, siteData}) => {
Vue.component('api-props', ApiProps); Vue.component('api-props', ApiProps);
Vue.component('api-slots', ApiSlots);
Vue.component('api-events', ApiEvents); Vue.component('api-events', ApiEvents);
} }
@@ -1,4 +1,4 @@
const generator = require('./generator'); const generator = require('./utils/node/generator');
module.exports = async function (page) { module.exports = async function (page) {
const section = ['props', 'events', 'slots', 'methods'].find( const section = ['props', 'events', 'slots', 'methods'].find(
@@ -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<ComponentDoc>}
*/
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<void>}
// */
// function (docs, path, astPath, options) {
// if (bt.isObjectExpression(path.node)) {
// const propsPath = path
// .get('properties')
// .filter(nodePath => bt.isObjectProperty(nodePath.node) &&
// getMemberFilter('props')(nodePath));
//
//
// }
// },
// ],
});
};
+3 -14
View File
@@ -1,7 +1,6 @@
const path = require('path'); const path = require('path');
const chalk = require('chalk');
const generator = require('./generator');
const extendPageData = require('./extendPageData'); const extendPageData = require('./extendPageData');
const clientDynamicModules = require('./clientDynamicModules');
/** /**
* @param options * @param options
@@ -12,22 +11,12 @@ module.exports = (options, {sourceDir}) => ({
name: 'vuepress-docgen', name: 'vuepress-docgen',
/** /**
* Dynamically generates all API documentation with vue-docgen-api. * Generates API documentation for use on the client side.
* The resulting object can be imported and used client-side via:
*
* import documentation from '@dynamic/api'
* *
* @see https://vuepress.vuejs.org/plugin/option-api.html#clientdynamicmodules * @see https://vuepress.vuejs.org/plugin/option-api.html#clientdynamicmodules
* @return {Promise<{name: string, content: string}>} * @return {Promise<{name: string, content: string}>}
*/ */
async clientDynamicModules () { clientDynamicModules: async () => await clientDynamicModules(sourceDir),
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)}`,
};
},
/** /**
* @see https://vuepress.vuejs.org/plugin/option-api.html#enhanceappfiles * @see https://vuepress.vuejs.org/plugin/option-api.html#enhanceappfiles
@@ -3,6 +3,9 @@ import { highlight, languages } from 'prismjs';
/** /**
* Returns code block with prism markup. * Returns code block with prism markup.
* @param {String} snippet * @param {String} snippet
* @param {String} language
* @return {*} * @return {*}
*/ */
export default snippet => highlight(snippet, languages.javascript, 'javascript') export default (snippet, language = 'javascript') => {
return highlight(snippet, languages[language], language);
}
@@ -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()
}
};
@@ -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<ComponentDoc>}
*/
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<void>}
*/
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;
};
@@ -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;
+12 -63
View File
@@ -1,65 +1,14 @@
::: tip ---
Vue Select leverages scoped slots to allow for total customization of the presentation layer. title: API/Slots
Slots can be used to change the look and feel of the UI, or to simply swap out text. 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) <api-slots />
### `selected-option`
#### Scope:
- `option {Object}` - A selected option
```html
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
```
### `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
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
</slot>
```
## Component Actions
### `spinner`
```html
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
</slot>
```
## Dropdown
### `option`
#### Scope:
- `option {Object}` - The currently iterated option from `filteredOptions`
```html
<slot name="option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
```
+4
View File
@@ -18,15 +18,19 @@
"@vuepress/plugin-search": "^1.2.0", "@vuepress/plugin-search": "^1.2.0",
"ast-types": "^0.13.2", "ast-types": "^0.13.2",
"chalk": "^3.0.0", "chalk": "^3.0.0",
"cheerio": "^1.0.0-rc.3",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"fuse.js": "^3.4.4", "fuse.js": "^3.4.4",
"gh-pages": "^0.11.0", "gh-pages": "^0.11.0",
"lodash": "^4.17.15",
"markdown-it": "^10.0.0", "markdown-it": "^10.0.0",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"prettier": "^1.19.1",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-docgen-api": "^4.0.4", "vue-docgen-api": "^4.0.4",
"vue-template-compiler": "^2.6.10",
"vuepress": "^1.2.0", "vuepress": "^1.2.0",
"vuex": "^3.1.0" "vuex": "^3.1.0"
} }
+36 -4
View File
@@ -2020,6 +2020,18 @@ character-parser@^2.1.1:
dependencies: dependencies:
is-regex "^1.0.3" 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: chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.6:
version "2.1.8" version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" 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" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
css-select@^1.1.0: css-select@^1.1.0, css-select@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
@@ -2915,6 +2927,14 @@ dom-serializer@0:
domelementtype "^2.0.1" domelementtype "^2.0.1"
entities "^2.0.0" 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: dom-walk@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" 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" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== 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" version "1.3.1"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== 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" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
htmlparser2@^3.3.0: htmlparser2@^3.3.0, htmlparser2@^3.9.1:
version "3.10.1" version "3.10.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== 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" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= 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" version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -5743,6 +5763,13 @@ parse-json@^4.0.0:
error-ex "^1.3.1" error-ex "^1.3.1"
json-parse-better-errors "^1.0.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: parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3" version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 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" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw== 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: pretty-bytes@^5.1.0:
version "5.3.0" version "5.3.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"