2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-10 07:52:23 +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-nprogress',
],
extraWatchFiles: [
'.vuepress/generateApiDocs/**/*.js',
'../src/**.*.js',
'../src/**.*.vue',
],
themeConfig: {
repo: 'sagalbot/vue-select',
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 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);
}
@@ -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(
@@ -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 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
@@ -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);
}
@@ -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.
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
<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>
```
<api-slots />
+4
View File
@@ -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"
}
+36 -4
View File
@@ -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"