From 13d4b7bedc1beffe3b7971648ecb7b4239f57d42 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 19 Nov 2021 13:52:38 +0200 Subject: [PATCH 01/20] feat: add custom-elements-manifest dependency --- package-lock.json | 156 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 + 2 files changed, 150 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9afd076..d276a7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@types/dompurify": "^2.3.1", "@types/marked": "^4.0.0", + "custom-elements-manifest": "^1.0.0", "dompurify": "^2.3.3", "highlight-ts": "9.12.1-2", "lit": "^2.0.0", @@ -18,6 +19,7 @@ "tslib": "^2.3.1" }, "devDependencies": { + "@custom-elements-manifest/analyzer": "^0.5.7", "@rollup/plugin-node-resolve": "^11.0.0", "@size-limit/preset-small-lib": "^6.0.4", "@typescript-eslint/eslint-plugin": "^5.3.1", @@ -367,6 +369,72 @@ "to-fast-properties": "^2.0.0" } }, + "node_modules/@custom-elements-manifest/analyzer": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.5.7.tgz", + "integrity": "sha512-pKQM0dAUPAaoAWJvPSpFtxAc1/pi0xb8wKYSUJbCBYwWA5L8vf/UowDaV0m9G3CtbiqkHI/4eKzFAqE14iY6vg==", + "dev": true, + "dependencies": { + "@web/config-loader": "0.1.3", + "chokidar": "3.5.2", + "command-line-args": "5.1.2", + "comment-parser": "1.2.4", + "custom-elements-manifest": "1.0.0", + "debounce": "1.2.1", + "globby": "11.0.4", + "typescript": "~4.3.2" + }, + "bin": { + "cem": "index.js", + "custom-elements-manifest": "index.js" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/array-back": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.0.tgz", + "integrity": "sha512-mixVv03GOOn/ubHE4STQ+uevX42ETdk0JoMVEjNkSOCT7WgERh7C8/+NyhWYNpE3BN69pxFyJIBcF7CxWz/+4A==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/command-line-args": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", + "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", + "dev": true, + "dependencies": { + "array-back": "^6.1.2", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", @@ -2708,6 +2776,15 @@ "node": ">= 12" } }, + "node_modules/comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -3111,12 +3188,16 @@ "node": ">=8.0.0" } }, + "node_modules/custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==" + }, "node_modules/debounce": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", - "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", - "dev": true, - "license": "MIT" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true }, "node_modules/debug": { "version": "4.3.2", @@ -11751,6 +11832,54 @@ "to-fast-properties": "^2.0.0" } }, + "@custom-elements-manifest/analyzer": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.5.7.tgz", + "integrity": "sha512-pKQM0dAUPAaoAWJvPSpFtxAc1/pi0xb8wKYSUJbCBYwWA5L8vf/UowDaV0m9G3CtbiqkHI/4eKzFAqE14iY6vg==", + "dev": true, + "requires": { + "@web/config-loader": "0.1.3", + "chokidar": "3.5.2", + "command-line-args": "5.1.2", + "comment-parser": "1.2.4", + "custom-elements-manifest": "1.0.0", + "debounce": "1.2.1", + "globby": "11.0.4", + "typescript": "~4.3.2" + }, + "dependencies": { + "array-back": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.0.tgz", + "integrity": "sha512-mixVv03GOOn/ubHE4STQ+uevX42ETdk0JoMVEjNkSOCT7WgERh7C8/+NyhWYNpE3BN69pxFyJIBcF7CxWz/+4A==", + "dev": true + }, + "command-line-args": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", + "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", + "dev": true, + "requires": { + "array-back": "^6.1.2", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, + "typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true + }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + } + } + }, "@discoveryjs/json-ext": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz", @@ -13529,6 +13658,12 @@ "integrity": "sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA==", "dev": true }, + "comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -13798,10 +13933,15 @@ "css-tree": "^1.1.2" } }, + "custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==" + }, "debounce": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz", - "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", "dev": true }, "debug": { diff --git a/package.json b/package.json index 8c9e58b..1a52cb3 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "dependencies": { "@types/dompurify": "^2.3.1", "@types/marked": "^4.0.0", + "custom-elements-manifest": "^1.0.0", "dompurify": "^2.3.3", "highlight-ts": "9.12.1-2", "lit": "^2.0.0", @@ -53,6 +54,7 @@ "tslib": "^2.3.1" }, "devDependencies": { + "@custom-elements-manifest/analyzer": "^0.5.7", "@rollup/plugin-node-resolve": "^11.0.0", "@size-limit/preset-small-lib": "^6.0.4", "@typescript-eslint/eslint-plugin": "^5.3.1", From c00a299cb3f6007ebc0430aad41e901dd2192f3f Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 19 Nov 2021 14:03:36 +0200 Subject: [PATCH 02/20] feat: add custom element manifest helpers --- .eslintrc.json | 1 + src/lib/manifest.ts | 87 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/lib/manifest.ts diff --git a/.eslintrc.json b/.eslintrc.json index 171f4b7..4928a99 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,6 +20,7 @@ "rules": { "@typescript-eslint/explicit-function-return-type": "off", "import/extensions": "off", + "import/no-import-module-exports": "off", "import/no-unresolved": "off", "import/prefer-default-export": "off", "lit/no-template-map": "off", diff --git a/src/lib/manifest.ts b/src/lib/manifest.ts new file mode 100644 index 0000000..44e8ed8 --- /dev/null +++ b/src/lib/manifest.ts @@ -0,0 +1,87 @@ +import type { + ClassField, + ClassLike, + ClassMember, + CustomElement, + CustomElementDeclaration, + CustomElementExport, + Export, + Package +} from 'custom-elements-manifest/schema'; + +export type PackageManifest = Promise; + +export function hasCustomElements( + manifest?: Package | null +): manifest is Package { + return ( + !!manifest && + Array.isArray(manifest.modules) && + !!manifest.modules.length && + manifest.modules.some( + (x) => + x.exports?.some((y) => y.kind === 'custom-element-definition') || + x.declarations?.some((z) => (z as CustomElement).customElement) + ) + ); +} + +export const isClassField = (x: ClassMember): x is ClassField => + x.kind === 'field'; + +export const isCustomElementExport = (y: Export): y is CustomElementExport => + y.kind === 'custom-element-definition'; + +export const isCustomElementDeclaration = (y: ClassLike): y is CustomElement => + (y as CustomElement).customElement; + +export function getCustomElements( + manifest?: Package | null +): CustomElementExport[] { + return (manifest?.modules ?? []).flatMap( + (x) => x.exports?.filter(isCustomElementExport) ?? [] + ); +} + +export const getElementData = ( + manifest: Package, + selected?: string +): CustomElement | null => { + const exports = manifest.modules.flatMap((m) => + m.exports?.filter(isCustomElementExport) + ); + const index = selected ? exports.findIndex((el) => el?.name === selected) : 0; + + const element = exports[index]; + + if (!element) return null; + + const decl = !element.declaration.module + ? manifest.modules + .flatMap((x) => x.declarations) + .find( + (y): y is CustomElementDeclaration => + y?.name === element.declaration.name + ) + : manifest.modules + .find((m) => m.path === element.declaration.module) + ?.declarations?.find((d) => d.name === element.declaration.name); + + if (!decl || !isCustomElementDeclaration(decl)) + throw new Error(`Could not find declaration for ${selected}`); + + return { + customElement: true, + name: element.name, + description: decl?.description, + slots: decl.slots ?? [], + attributes: decl.attributes ?? [], + members: decl.members ?? [], + events: decl.events ?? [], + cssParts: decl.cssParts ?? [], + // TODO: analyzer should sort CSS custom properties + cssProperties: [...(decl.cssProperties ?? [])].sort((a, b) => + a.name > b.name ? 1 : -1 + ) + }; +}; From bef4842de21894329e176aa9c45e9c5773169381 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 19 Nov 2021 16:09:26 +0200 Subject: [PATCH 03/20] refactor: use custom-element-manifest types --- src/api-demo-base.ts | 34 +++++++++++----- src/api-docs-base.ts | 31 +++++++++------ src/api-viewer-base.ts | 47 ++++++++++++++-------- src/api-viewer-demo.ts | 41 +++++++++---------- src/api-viewer-docs.ts | 59 +++++++++++++++------------- src/api-viewer-mixin.ts | 28 ++++++------- src/controllers/events-controller.ts | 4 +- src/controllers/slots-controller.ts | 8 ++-- src/controllers/styles-controller.ts | 12 +++--- src/lib/demo-controls.ts | 13 +++--- src/lib/demo-events.ts | 4 +- src/lib/demo-snippet.ts | 4 +- src/lib/knobs.ts | 41 +++++++++---------- src/lib/manifest.ts | 29 +++++++++++++- src/lib/renderer.ts | 3 +- src/lib/types.ts | 57 --------------------------- src/lib/utils.ts | 32 --------------- 17 files changed, 209 insertions(+), 238 deletions(-) delete mode 100644 src/lib/types.ts diff --git a/src/api-demo-base.ts b/src/api-demo-base.ts index 66dc09d..fbb2a13 100644 --- a/src/api-demo-base.ts +++ b/src/api-demo-base.ts @@ -1,25 +1,39 @@ import { LitElement, html, TemplateResult } from 'lit'; import { property } from 'lit/decorators/property.js'; import { until } from 'lit/directives/until.js'; -import { ElementPromise } from './lib/types.js'; -import { getElementData } from './lib/utils.js'; +import { + ClassField, + CustomElement, + getCustomElements, + getElementData, + hasCustomElements, + isClassField, + isPrivateOrProtected, + Package +} from './lib/manifest.js'; import { ApiViewerMixin, emptyDataWarning } from './api-viewer-mixin.js'; import './api-viewer-demo.js'; async function renderDemo( - jsonFetched: ElementPromise, + jsonFetched: Promise, onSelect: (e: CustomEvent) => void, selected?: string, id?: number, exclude = '' ): Promise { - const elements = await jsonFetched; + const manifest = await jsonFetched; - if (!elements.length) { + if (!hasCustomElements(manifest)) { return emptyDataWarning; } - const data = getElementData(elements, selected); + const elements = getCustomElements(manifest); + + const data = getElementData(manifest, selected) as CustomElement; + + const props = (data.members ?? []).filter( + (x): x is ClassField => isClassField(x) && !isPrivateOrProtected(x) + ); return html`
@@ -41,10 +55,10 @@ async function renderDemo(
diff --git a/src/api-docs-base.ts b/src/api-docs-base.ts index 9d5bb11..346b26f 100644 --- a/src/api-docs-base.ts +++ b/src/api-docs-base.ts @@ -1,23 +1,30 @@ import { LitElement, html, TemplateResult } from 'lit'; import { until } from 'lit/directives/until.js'; import { parse } from './lib/markdown.js'; -import { ElementPromise } from './lib/types.js'; -import { getElementData } from './lib/utils.js'; +import { + CustomElement, + getCustomElements, + getElementData, + hasCustomElements, + Package +} from './lib/manifest.js'; import { ApiViewerMixin, emptyDataWarning } from './api-viewer-mixin.js'; import './api-viewer-docs.js'; async function renderDocs( - jsonFetched: ElementPromise, + jsonFetched: Promise, onSelect: (e: CustomEvent) => void, selected?: string ): Promise { - const elements = await jsonFetched; + const manifest = await jsonFetched; - if (!elements.length) { + if (!hasCustomElements(manifest)) { return emptyDataWarning; } - const data = getElementData(elements, selected); + const elements = getCustomElements(manifest); + + const data = getElementData(manifest, selected) as CustomElement; return html`
@@ -42,12 +49,12 @@ async function renderDocs( `; } diff --git a/src/api-viewer-base.ts b/src/api-viewer-base.ts index e01e62b..325f80d 100644 --- a/src/api-viewer-base.ts +++ b/src/api-viewer-base.ts @@ -3,14 +3,23 @@ import { property } from 'lit/decorators/property.js'; import { cache } from 'lit/directives/cache.js'; import { until } from 'lit/directives/until.js'; import { parse } from './lib/markdown.js'; -import { ElementPromise } from './lib/types.js'; -import { getElementData, setTemplates } from './lib/utils.js'; +import { + ClassField, + CustomElement, + getCustomElements, + getElementData, + hasCustomElements, + isClassField, + isPrivateOrProtected, + Package +} from './lib/manifest.js'; +import { setTemplates } from './lib/utils.js'; import { ApiViewerMixin, emptyDataWarning } from './api-viewer-mixin.js'; import './api-viewer-docs.js'; import './api-viewer-demo.js'; async function renderDocs( - jsonFetched: ElementPromise, + jsonFetched: Promise, section: string, onSelect: (e: CustomEvent) => void, onToggle: (e: CustomEvent) => void, @@ -18,13 +27,19 @@ async function renderDocs( id?: number, exclude = '' ): Promise { - const elements = await jsonFetched; + const manifest = await jsonFetched; - if (!elements.length) { + if (!hasCustomElements(manifest)) { return emptyDataWarning; } - const data = getElementData(elements, selected); + const elements = getCustomElements(manifest); + + const data = getElementData(manifest, selected) as CustomElement; + + const props = (data.members ?? []).filter( + (x): x is ClassField => isClassField(x) && !isPrivateOrProtected(x) + ); return html`
@@ -72,21 +87,21 @@ async function renderDocs( ` : html` diff --git a/src/api-viewer-demo.ts b/src/api-viewer-demo.ts index cea09ca..ee9eb33 100644 --- a/src/api-viewer-demo.ts +++ b/src/api-viewer-demo.ts @@ -7,10 +7,12 @@ import { StylesController } from './controllers/styles-controller.js'; import { renderEvents } from './lib/demo-events.js'; import { renderSnippet } from './lib/demo-snippet.js'; import { + ComponentWithProps, getCustomKnobs, getInitialKnobs, getKnobs, - Knob + Knob, + KnobValue } from './lib/knobs.js'; import { renderer } from './lib/renderer.js'; import { @@ -19,15 +21,8 @@ import { renderKnobs, slotRenderer } from './lib/demo-controls.js'; -import { - ComponentWithProps, - CSSPropertyInfo, - EventInfo, - PropertyInfo, - PropertyValue, - SlotInfo -} from './lib/types.js'; -import { hasTemplate, isPropMatch, TemplateTypes } from './lib/utils.js'; +import { ClassField, CssCustomProperty, Event, Slot } from './lib/manifest.js'; +import { hasTemplate, TemplateTypes } from './lib/utils.js'; import './api-viewer-panel.js'; import './api-viewer-tab.js'; import './api-viewer-tabs.js'; @@ -36,31 +31,31 @@ class ApiViewerDemo extends LitElement { @property() copyBtnText = 'copy'; @property({ attribute: false }) - cssProps: CSSPropertyInfo[] = []; + cssProps: CssCustomProperty[] = []; @property({ attribute: false }) - events: EventInfo[] = []; + events: Event[] = []; @property({ attribute: false }) - slots: SlotInfo[] = []; + slots: Slot[] = []; @property() tag = ''; @property({ attribute: false }) - props: PropertyInfo[] = []; + props: ClassField[] = []; @property() exclude = ''; @property({ type: Number }) vid?: number; @property({ attribute: false }) - customKnobs!: Knob[]; + customKnobs!: Knob[]; @property({ attribute: false }) knobs!: Record; @property({ attribute: false }) - propKnobs!: Knob[]; + propKnobs!: Knob[]; private _whenDefined: Record> = {}; @@ -300,14 +295,14 @@ class ApiViewerDemo extends LitElement { } getKnob(name: string): { - knob: Knob; + knob: Knob; custom?: boolean; } { - const isMatch = isPropMatch(name); + const isMatch = (prop: ClassField): boolean => prop.name === name; let knob = this.propKnobs.find(isMatch); let custom = false; if (!knob) { - knob = this.customKnobs.find(isMatch) as Knob; + knob = this.customKnobs.find(isMatch) as Knob; custom = true; } return { knob, custom }; @@ -316,7 +311,7 @@ class ApiViewerDemo extends LitElement { private setKnobs( name: string, knobType: string, - value: PropertyValue, + value: KnobValue, attribute: string | undefined, custom = false ): void { @@ -331,7 +326,7 @@ class ApiViewerDemo extends LitElement { }; } - syncKnob(component: Element, changed: Knob): void { + syncKnob(component: Element, changed: Knob): void { const { name, knobType, attribute } = changed; const value = (component as unknown as ComponentWithProps)[name]; @@ -353,7 +348,7 @@ class ApiViewerDemo extends LitElement { this.stylesController.setValue(target.dataset.name as string, target.value); } - private _onPropChanged(e: Event): void { + private _onPropChanged(e: CustomEvent): void { const target = e.composedPath()[0] as HTMLInputElement; const { name, type } = target.dataset; @@ -381,7 +376,7 @@ class ApiViewerDemo extends LitElement { } } - private _onSlotChanged(e: Event): void { + private _onSlotChanged(e: CustomEvent): void { const target = e.composedPath()[0] as HTMLInputElement; this.slotsController.setValue(target.dataset.slot as string, target.value); } diff --git a/src/api-viewer-docs.ts b/src/api-viewer-docs.ts index 27d6af2..a0477c3 100644 --- a/src/api-viewer-docs.ts +++ b/src/api-viewer-docs.ts @@ -1,14 +1,15 @@ import { LitElement, html, nothing, PropertyValues, TemplateResult } from 'lit'; import { property } from 'lit/decorators/property.js'; import { - PropertyInfo, - SlotInfo, - AttributeInfo, - EventInfo, - CSSPartInfo, - CSSPropertyInfo -} from './lib/types.js'; -import { isPropMatch, unquote } from './lib/utils.js'; + Attribute, + ClassMember, + CssCustomProperty, + CssPart, + Event, + isClassField, + Slot +} from './lib/manifest.js'; +import { unquote } from './lib/utils.js'; import { parse } from './lib/markdown.js'; import './api-viewer-panel.js'; @@ -18,7 +19,7 @@ import './api-viewer-tabs.js'; const renderItem = ( prefix: string, name: string, - description: string, + description?: string, valueType?: string, value?: unknown, attribute?: string @@ -84,44 +85,45 @@ class ApiViewerDocs extends LitElement { @property() name = ''; @property({ attribute: false }) - props: PropertyInfo[] = []; + members: ClassMember[] = []; @property({ attribute: false }) - attrs: AttributeInfo[] = []; + attrs: Attribute[] = []; @property({ attribute: false }) - slots: SlotInfo[] = []; + slots: Slot[] = []; @property({ attribute: false }) - events: EventInfo[] = []; + events: Event[] = []; @property({ attribute: false }) - cssParts: CSSPartInfo[] = []; + cssParts: CssPart[] = []; @property({ attribute: false }) - cssProps: CSSPropertyInfo[] = []; + cssProps: CssCustomProperty[] = []; protected createRenderRoot(): this { return this; } protected render(): TemplateResult { - const { slots, props, attrs, events, cssParts, cssProps } = this; + const { slots, members, attrs, events, cssParts, cssProps } = this; - const properties = props || []; - const attributes = (attrs || []).filter( - ({ name }) => !properties.some(isPropMatch(name)) - ); + const properties = members.filter(isClassField); const emptyDocs = [ properties, - attributes, + attrs, slots, events, cssProps, cssParts ].every((arr) => arr.length === 0); + const attributes = (attrs || []).filter( + (x) => !properties.some((y) => y.name === x.fieldName) + ); + return emptyDocs ? html`
@@ -136,14 +138,17 @@ class ApiViewerDocs extends LitElement { properties, html` ${properties.map((prop) => { - const { name, description, type, attribute } = prop; + const { name, description, type } = prop; + const attribute = attributes.find( + (x) => x.fieldName === name + ); return renderItem( 'prop', name, description, - type, + type?.text, prop.default, - attribute + attribute?.name ); })} ` @@ -153,7 +158,7 @@ class ApiViewerDocs extends LitElement { attributes, html` ${attributes.map(({ name, description, type }) => - renderItem('attr', name, description, type) + renderItem('attr', name, description, type?.text) )} ` )} @@ -180,12 +185,12 @@ class ApiViewerDocs extends LitElement { cssProps, html` ${cssProps.map((prop) => { - const { name, description, type } = prop; + const { name, description } = prop; return renderItem( 'css', name, description, - type, + '', // TODO: manifest does not provide type for CSS custom properties unquote(prop.default) ); })} diff --git a/src/api-viewer-mixin.ts b/src/api-viewer-mixin.ts index cdb15da..691956c 100644 --- a/src/api-viewer-mixin.ts +++ b/src/api-viewer-mixin.ts @@ -1,6 +1,6 @@ import { LitElement, html } from 'lit'; import { property } from 'lit/decorators/property.js'; -import { ElementInfo, ElementPromise, ElementSetInfo } from './lib/types.js'; +import { hasCustomElements, Package } from './lib/manifest.js'; /* eslint-disable @typescript-eslint/no-explicit-any */ export type Constructor = new (...args: any[]) => T; @@ -8,27 +8,25 @@ export type Constructor = new (...args: any[]) => T; export interface ApiViewerInterface { src?: string; - elements?: ElementInfo[]; + manifest?: Package; selected?: string; - jsonFetched: ElementPromise; + jsonFetched: Promise; } -export async function fetchJson(src: string): ElementPromise { - let result: ElementInfo[] = []; +export async function fetchJson(src: string): Promise { try { const file = await fetch(src); - const json = (await file.json()) as ElementSetInfo; - if (Array.isArray(json.tags) && json.tags.length) { - result = json.tags; - } else { - console.error(`No element definitions found at ${src}`); + const manifest: Package = await file.json(); + if (hasCustomElements(manifest)) { + return manifest; } + throw new Error(`No element definitions found at ${src}`); } catch (e) { console.error(e); + return null; } - return result; } export const emptyDataWarning = html` @@ -42,20 +40,20 @@ export const ApiViewerMixin = >( @property() src?: string; @property({ attribute: false }) - elements?: ElementInfo[]; + manifest?: Package; @property() selected?: string; - jsonFetched: ElementPromise = Promise.resolve([]); + jsonFetched: Promise = Promise.resolve(null); private lastSrc?: string; willUpdate(): void { const { src } = this; - if (Array.isArray(this.elements)) { + if (Array.isArray(this.manifest)) { this.lastSrc = undefined; - this.jsonFetched = Promise.resolve(this.elements); + this.jsonFetched = Promise.resolve(this.manifest); } else if (src && this.lastSrc !== src) { this.lastSrc = src; this.jsonFetched = fetchJson(src); diff --git a/src/controllers/events-controller.ts b/src/controllers/events-controller.ts index f72350f..9f53a38 100644 --- a/src/controllers/events-controller.ts +++ b/src/controllers/events-controller.ts @@ -1,5 +1,5 @@ import { ReactiveController, ReactiveControllerHost } from 'lit'; -import { EventInfo } from '../lib/types.js'; +import { Event } from '../lib/manifest.js'; import { HasKnobs } from '../lib/knobs.js'; type EventsHost = HTMLElement & ReactiveControllerHost & HasKnobs; @@ -24,7 +24,7 @@ export class EventsController implements ReactiveController { constructor( host: ReactiveControllerHost, component: HTMLElement, - events: EventInfo[] + events: Event[] ) { (this.host = host as EventsHost).addController(this); diff --git a/src/controllers/slots-controller.ts b/src/controllers/slots-controller.ts index bf0ef48..df38658 100644 --- a/src/controllers/slots-controller.ts +++ b/src/controllers/slots-controller.ts @@ -1,5 +1,5 @@ import { ReactiveController, ReactiveControllerHost } from 'lit'; -import { SlotInfo, SlotValue } from '../lib/types.js'; +import { Slot, SlotValue } from '../lib/manifest.js'; import { getSlotContent, hasTemplate, TemplateTypes } from '../lib/utils.js'; export class SlotsController implements ReactiveController { @@ -43,13 +43,13 @@ export class SlotsController implements ReactiveController { host: ReactiveControllerHost, id: number, component: HTMLElement, - slots: SlotInfo[] + slots: Slot[] ) { (this.host = host).addController(this as ReactiveController); this.el = component; this.enabled = !hasTemplate(id, component.localName, TemplateTypes.SLOT); this.slots = slots - .sort((a: SlotInfo, b: SlotInfo) => { + .sort((a, b) => { if (a.name === '') { return 1; } @@ -58,7 +58,7 @@ export class SlotsController implements ReactiveController { } return a.name.localeCompare(b.name); }) - .map((slot: SlotInfo) => { + .map((slot) => { return { ...slot, content: getSlotContent(slot.name) diff --git a/src/controllers/styles-controller.ts b/src/controllers/styles-controller.ts index 35a23ff..64a431d 100644 --- a/src/controllers/styles-controller.ts +++ b/src/controllers/styles-controller.ts @@ -1,5 +1,5 @@ import { ReactiveController, ReactiveControllerHost } from 'lit'; -import { CSSPropertyInfo } from '../lib/types.js'; +import { CssCustomProperty, CssCustomPropertyValue } from '../lib/manifest.js'; import { unquote } from '../lib/utils.js'; export class StylesController implements ReactiveController { @@ -7,13 +7,13 @@ export class StylesController implements ReactiveController { el: HTMLElement; - private _css: CSSPropertyInfo[] = []; + private _css: CssCustomPropertyValue[] = []; - get css(): CSSPropertyInfo[] { + get css(): CssCustomPropertyValue[] { return this._css; } - set css(cssProps: CSSPropertyInfo[]) { + set css(cssProps: CssCustomPropertyValue[]) { this._css = cssProps; if (cssProps.length) { @@ -38,7 +38,7 @@ export class StylesController implements ReactiveController { constructor( host: HTMLElement & ReactiveControllerHost, component: HTMLElement, - cssProps: CSSPropertyInfo[] + cssProps: CssCustomProperty[] ) { (this.host = host).addController(this); this.el = component; @@ -51,7 +51,7 @@ export class StylesController implements ReactiveController { ? unquote(cssProp.default) : style.getPropertyValue(cssProp.name); - const result = cssProp; + const result: CssCustomPropertyValue = cssProp; if (value) { value = value.trim(); result.default = value; diff --git a/src/lib/demo-controls.ts b/src/lib/demo-controls.ts index aa70977..0aeada2 100644 --- a/src/lib/demo-controls.ts +++ b/src/lib/demo-controls.ts @@ -1,11 +1,11 @@ import { html, TemplateResult } from 'lit'; import { Knob, Knobable } from './knobs.js'; -import { CSSPropertyInfo, PropertyInfo, SlotValue } from './types.js'; +import { ClassField, CssCustomPropertyValue, SlotValue } from './manifest.js'; import { getSlotContent, normalizeType } from './utils.js'; type InputRenderer = (item: Knobable, id: string) => TemplateResult; -const getInputType = (type: string): 'checkbox' | 'number' | 'text' => { +const getInputType = (type?: string): 'checkbox' | 'number' | 'text' => { switch (normalizeType(type)) { case 'boolean': return 'checkbox'; @@ -20,7 +20,7 @@ export const cssPropRenderer: InputRenderer = ( knob: Knobable, id: string ): TemplateResult => { - const { name, value } = knob as CSSPropertyInfo; + const { name, value } = knob as CssCustomPropertyValue; return html` { - const { name, knobType, value, options } = knob as Knob; + const { name, knobType, type, value, options } = knob as Knob; let input; if (knobType === 'select' && Array.isArray(options)) { input = html` @@ -47,7 +47,7 @@ export const propRenderer: InputRenderer = ( )} `; - } else if (normalizeType(knobType) === 'boolean') { + } else if (normalizeType(knobType ?? type?.text) === 'boolean') { input = html` { const rows = items.map((item: Knobable) => { - const { name } = item as Knob; + // NOTE: type cast is fine, as we default it on next line + const { name } = item as Knob; const id = `${type}-${name || 'default'}`; const label = type === 'slot' ? getSlotContent(name) : name; return html` diff --git a/src/lib/demo-events.ts b/src/lib/demo-events.ts index 005ae55..0b21159 100644 --- a/src/lib/demo-events.ts +++ b/src/lib/demo-events.ts @@ -1,8 +1,8 @@ import { html, nothing, TemplateResult } from 'lit'; -import { PropertyValue } from './types.js'; +import { KnobValue } from './knobs.js'; interface EventDetail { - value: PropertyValue; + value: KnobValue; } const renderDetail = (detail: EventDetail): string => { diff --git a/src/lib/demo-snippet.ts b/src/lib/demo-snippet.ts index 627e181..cdb3fb3 100644 --- a/src/lib/demo-snippet.ts +++ b/src/lib/demo-snippet.ts @@ -4,7 +4,7 @@ import { htmlRender } from 'highlight-ts/es/render/html'; import { registerLanguages } from 'highlight-ts/es/languages'; import { XML } from 'highlight-ts/es/languages/xml'; import { init, process } from 'highlight-ts/es/process'; -import { CSSPropertyInfo, SlotValue } from './types.js'; +import { CssCustomPropertyValue, SlotValue } from './manifest.js'; import { Knob } from './knobs.js'; import { CSS } from './highlight-css.js'; import { @@ -55,7 +55,7 @@ export const renderSnippet = ( tag: string, values: Record, slots: SlotValue[], - cssProps: CSSPropertyInfo[] + cssProps: CssCustomPropertyValue[] ): TemplateResult => { let markup = ''; const prefix = getTemplate(id, tag, PREFIX); diff --git a/src/lib/knobs.ts b/src/lib/knobs.ts index fb2d5c6..3a29d24 100644 --- a/src/lib/knobs.ts +++ b/src/lib/knobs.ts @@ -1,10 +1,4 @@ -import { - ComponentWithProps, - CSSPropertyInfo, - PropertyInfo, - PropertyValue, - SlotValue -} from './types.js'; +import { ClassField, CssCustomProperty, SlotValue } from './manifest.js'; import { getTemplates, normalizeType, @@ -13,21 +7,28 @@ import { getTemplateNode } from './utils.js'; -export type Knobable = unknown | CSSPropertyInfo | PropertyInfo | SlotValue; +export type KnobValue = string | number | boolean | null | undefined; + +export type ComponentWithProps = { + [key: string]: KnobValue; +}; + +export type Knobable = unknown | (CssCustomProperty | ClassField | SlotValue); export type Knob = T & { attribute: string | undefined; - value: PropertyValue; + value: KnobValue; custom?: boolean; + options?: string[]; knobType: string; }; export interface HasKnobs { - getKnob(name: string): { knob: Knob; custom?: boolean }; - syncKnob(component: Element, changed: Knob): void; + getKnob(name: string): { knob: Knob; custom?: boolean }; + syncKnob(component: Element, changed: Knob): void; } -const getDefault = (prop: Knob): PropertyValue => { +const getDefault = (prop: Knob): KnobValue => { const { knobType, default: value } = prop; switch (knobType) { case 'boolean': @@ -65,20 +66,20 @@ const isGetter = ( export const getKnobs = ( tag: string, - props: PropertyInfo[], + props: ClassField[], exclude = '' -): Knob[] => { +): Knob[] => { // Exclude getters and specific properties let propKnobs = props.filter( ({ name }) => !exclude.includes(name) && !isGetter(customElements.get(tag), name) - ) as Knob[]; + ) as Knob[]; // Set knob types and default knobs values propKnobs = propKnobs.map((prop) => { const knob = { ...prop, - knobType: normalizeType(prop.type) + knobType: normalizeType(prop.type?.text) }; if (typeof knob.default === 'string') { @@ -94,7 +95,7 @@ export const getKnobs = ( export const getCustomKnobs = ( tag: string, vid?: number -): Knob[] => { +): Knob[] => { return getTemplates(vid as number, tag, TemplateTypes.KNOB) .map((template) => { const { attr, type } = template.dataset; @@ -126,15 +127,15 @@ export const getCustomKnobs = ( }; } } - return result as Knob; + return result as Knob; }) .filter(Boolean); }; export const getInitialKnobs = ( - propKnobs: Knob[], + propKnobs: Knob[], component: HTMLElement -): Knob[] => { +): Knob[] => { return propKnobs.filter((prop) => { const { name, knobType } = prop; const defaultValue = getDefault(prop); diff --git a/src/lib/manifest.ts b/src/lib/manifest.ts index 44e8ed8..13ae3b5 100644 --- a/src/lib/manifest.ts +++ b/src/lib/manifest.ts @@ -1,15 +1,37 @@ import type { + Attribute, ClassField, ClassLike, ClassMember, + CssCustomProperty, + CssPart, CustomElement, CustomElementDeclaration, CustomElementExport, + Event, Export, - Package + Package, + Slot } from 'custom-elements-manifest/schema'; -export type PackageManifest = Promise; +export { + Attribute, + ClassField, + ClassMember, + CssCustomProperty, + CssPart, + CustomElement, + Event, + Package, + Slot +}; + +export type CssCustomPropertyValue = CssCustomProperty & { value?: string }; + +export interface SlotValue { + name: string; + content: string; +} export function hasCustomElements( manifest?: Package | null @@ -35,6 +57,9 @@ export const isCustomElementExport = (y: Export): y is CustomElementExport => export const isCustomElementDeclaration = (y: ClassLike): y is CustomElement => (y as CustomElement).customElement; +export const isPrivateOrProtected = (x: ClassField): boolean => + x.privacy === 'private' || x.privacy === 'protected'; + export function getCustomElements( manifest?: Package | null ): CustomElementExport[] { diff --git a/src/lib/renderer.ts b/src/lib/renderer.ts index 46dd065..d9cee4c 100644 --- a/src/lib/renderer.ts +++ b/src/lib/renderer.ts @@ -2,8 +2,7 @@ import { ChildPart, html, noChange, nothing, TemplateResult } from 'lit'; import { directive, Directive, PartInfo, PartType } from 'lit/directive.js'; import { templateContent } from 'lit/directives/template-content.js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; -import { Knob } from './knobs.js'; -import { ComponentWithProps } from './types.js'; +import { ComponentWithProps, Knob } from './knobs.js'; import { getTemplate, getTemplateNode, diff --git a/src/lib/types.ts b/src/lib/types.ts deleted file mode 100644 index 4f1afbc..0000000 --- a/src/lib/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -export interface Info { - name: string; - description: string; -} - -export interface AttributeInfo extends Info { - type: string | undefined; -} - -export type PropertyValue = string | number | boolean | null | undefined; - -export interface PropertyInfo extends Info { - type: string; - attribute: string | undefined; - value: PropertyValue; - default: PropertyValue; - options?: string[]; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface SlotInfo extends Info {} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface EventInfo extends Info {} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CSSPartInfo extends Info {} - -export interface CSSPropertyInfo extends Info { - value?: string; - default?: string; - type?: string; -} - -export interface ElementInfo extends Info { - attributes: AttributeInfo[]; - events: EventInfo[]; - properties: PropertyInfo[]; - slots: SlotInfo[]; - cssProperties: CSSPropertyInfo[]; - cssParts: CSSPartInfo[]; -} - -export interface ElementSetInfo { - tags: ElementInfo[]; -} - -export type ElementPromise = Promise; - -export interface SlotValue { - name: string; - content: string; -} - -export type ComponentWithProps = { - [key: string]: PropertyValue; -}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e00f265..11276b9 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,3 @@ -import { Knob } from './knobs.js'; -import { ElementInfo, PropertyInfo } from './types'; - const templates: Array = []; export const setTemplates = (id: number, tpl: HTMLTemplateElement[]) => { @@ -44,11 +41,6 @@ export const getTemplates = ( export const hasTemplate = (id: number, name: string, type: string): boolean => templates[id].some(matchTemplate(name, type)); -export const isPropMatch = - (name: string) => - (prop: PropertyInfo | Knob): boolean => - prop.attribute === name || prop.name === name; - export const normalizeType = (type: string | undefined = ''): string => type.replace(' | undefined', '').replace(' | null', ''); @@ -57,30 +49,6 @@ export const unquote = (value?: string): string | undefined => ? value.slice(1, value.length - 1) : value; -const EMPTY_ELEMENT: ElementInfo = { - name: '', - description: '', - slots: [], - attributes: [], - properties: [], - events: [], - cssParts: [], - cssProperties: [] -}; - -export const getElementData = (elements: ElementInfo[], selected?: string) => { - const index = selected ? elements.findIndex((el) => el.name === selected) : 0; - - const result = { ...EMPTY_ELEMENT, ...elements[index] }; - - // TODO: analyzer should sort CSS custom properties - result.cssProperties = result.cssProperties.sort((a, b) => - a.name > b.name ? 1 : -1 - ); - - return result; -}; - const capitalize = (name: string): string => name[0].toUpperCase() + name.slice(1); From 9a6f89cafee4225a9035fd9e3f7e9ffc0e40d4b0 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 19 Nov 2021 16:19:27 +0200 Subject: [PATCH 04/20] chore: generate custom-elements.json for demo --- custom-elements.json | 267 ------------- demo/custom-elements.json | 762 ++++++++++++++++++++++++++++++++++++++ index.html | 8 +- package.json | 5 +- 4 files changed, 769 insertions(+), 273 deletions(-) delete mode 100644 custom-elements.json create mode 100644 demo/custom-elements.json diff --git a/custom-elements.json b/custom-elements.json deleted file mode 100644 index 98d3215..0000000 --- a/custom-elements.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "version": "experimental", - "tags": [ - { - "name": "expansion-panel", - "path": "./src/fixtures/expansion-panel.ts", - "description": "A custom element similar to the HTML5 `
` element.", - "attributes": [ - { - "name": "opened", - "description": "When true, the panel content is expanded and visible", - "type": "boolean | null | undefined", - "default": "false" - }, - { - "name": "disabled", - "description": "Disabled panel can not be expanded or collapsed", - "type": "boolean", - "default": "false" - }, - { - "name": "focused", - "description": "State attribute set when element has focus.", - "type": "boolean" - }, - { - "name": "focus-ring", - "description": "State attribute set when focused from keyboard.", - "type": "boolean" - } - ], - "properties": [ - { - "name": "opened", - "attribute": "opened", - "description": "When true, the panel content is expanded and visible", - "type": "boolean | null | undefined", - "default": "false" - }, - { - "name": "disabled", - "attribute": "disabled", - "description": "Disabled panel can not be expanded or collapsed", - "type": "boolean", - "default": "false" - } - ], - "events": [ - { - "name": "opened-changed", - "description": "Event fired when expanding / collapsing" - } - ], - "slots": [ - { - "name": "", - "description": "Slot fot panel content" - }, - { - "name": "header", - "description": "Slot for panel header" - } - ], - "cssProperties": [ - { - "name": "--panel-header-background", - "description": "Default panel header background color.", - "type": "Background", - "default": "\"#fff\"" - }, - { - "name": "--panel-header-min-height", - "description": "Panel header minimum height.", - "default": "\"48px\"" - }, - { - "name": "--panel-ripple-background", - "description": "Active toggle button ripple background.", - "type": "Background", - "default": "\"rgba(0, 0, 0, 0.38)\"" - } - ], - "cssParts": [ - { - "name": "header", - "description": "An element wrapping the `header` slot." - }, - { - "name": "toggle", - "description": "A toggle button, child of the header part." - }, - { - "name": "content", - "description": "An element wrapping the `content` slot." - } - ] - }, - { - "name": "fancy-accordion", - "path": "./src/fixtures/fancy-accordion.ts", - "description": "A custom element implementing the accordion widget: a vertically stacked set of expandable panels\nthat wraps several instances of the `` element. Only one panel can be opened\n(expanded) at a time.\n\nPanel headings function as controls that enable users to open (expand) or hide (collapse) their\nassociated sections of content. The user can toggle panels by mouse click, Enter and Space keys.\n\nThe component supports keyboard navigation and is aligned with the\n[WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion).", - "attributes": [ - { - "name": "opened-index", - "description": "Index of the currently opened panel. By default all the panels are closed.\nOnly one panel can be opened at the same time. Setting `null` or `undefined`\ncloses all the accordion panels.", - "type": "number | null | undefined" - } - ], - "properties": [ - { - "name": "openedIndex", - "attribute": "opened-index", - "description": "Index of the currently opened panel. By default all the panels are closed.\nOnly one panel can be opened at the same time. Setting `null` or `undefined`\ncloses all the accordion panels.", - "type": "number | null | undefined" - }, - { - "name": "focused", - "type": "Element | null" - } - ], - "events": [ - { - "name": "opened-index-changed", - "description": "Event fired when changing currently opened panel." - } - ], - "slots": [ - { - "name": "", - "description": "Slot fot panel elements." - } - ] - }, - { - "name": "intl-currency", - "path": "./src/fixtures/intl-currency.ts", - "description": "A custom element that formats currency using Intl.", - "attributes": [ - { - "name": "value", - "description": "Amount to be formatted.", - "type": "number", - "default": "0" - }, - { - "name": "currency", - "description": "Currency code used for formatting.", - "type": "string | null | undefined", - "default": "\"EUR\"" - }, - { - "name": "locale", - "description": "Locale code used for formatting.", - "type": "string | null | undefined", - "default": "\"en-GB\"" - } - ], - "properties": [ - { - "name": "value", - "attribute": "value", - "description": "Amount to be formatted.", - "type": "number", - "default": "0" - }, - { - "name": "currency", - "attribute": "currency", - "description": "Currency code used for formatting.", - "type": "string | null | undefined", - "default": "\"EUR\"" - }, - { - "name": "locale", - "attribute": "locale", - "description": "Locale code used for formatting.", - "type": "string | null | undefined", - "default": "\"en-GB\"" - } - ] - }, - { - "name": "progress-bar", - "path": "./src/fixtures/progress-bar.ts", - "description": "A custom element similar to the HTML5 `` element.", - "attributes": [ - { - "name": "value", - "description": "Current progress value.", - "type": "number", - "default": "0.5" - }, - { - "name": "min", - "description": "Minimum bound of the progress bar.", - "type": "number", - "default": "0" - }, - { - "name": "max", - "description": "Maximum bound of the progress bar.", - "type": "number", - "default": "1" - }, - { - "name": "indeterminate", - "description": "Indeterminate state of the progress bar.\nThis property takes precedence over other state properties (min, max, value).", - "type": "boolean", - "default": "false" - } - ], - "properties": [ - { - "name": "value", - "attribute": "value", - "description": "Current progress value.", - "type": "number", - "default": "0.5" - }, - { - "name": "min", - "attribute": "min", - "description": "Minimum bound of the progress bar.", - "type": "number", - "default": "0" - }, - { - "name": "max", - "attribute": "max", - "description": "Maximum bound of the progress bar.", - "type": "number", - "default": "1" - }, - { - "name": "indeterminate", - "attribute": "indeterminate", - "description": "Indeterminate state of the progress bar.\nThis property takes precedence over other state properties (min, max, value).", - "type": "boolean", - "default": "false" - } - ], - "cssProperties": [ - { - "name": "--progress-bar-fill-color", - "description": "Color of the filled progress bar part.", - "type": "Color", - "default": "\"#6200ee\"" - }, - { - "name": "--progress-bar-opacity", - "description": "Opacity set on the underlying track.", - "default": "0.16" - } - ], - "cssParts": [ - { - "name": "bar", - "description": "A progress bar background." - }, - { - "name": "value", - "description": "A progress bar foreground." - } - ] - } - ] -} \ No newline at end of file diff --git a/demo/custom-elements.json b/demo/custom-elements.json new file mode 100644 index 0000000..78ee9d9 --- /dev/null +++ b/demo/custom-elements.json @@ -0,0 +1,762 @@ +{ + "schemaVersion": "1.0.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "src/fixtures/expansion-panel.ts", + "declarations": [ + { + "kind": "class", + "description": "A custom element similar to the HTML5 `
` element.", + "name": "ExpansionPanel", + "cssProperties": [ + { + "type": { + "text": "Background" + }, + "description": "Default panel header background color.", + "name": "--panel-header-background", + "default": "#fff" + }, + { + "description": "Panel header minimum height.", + "name": "--panel-header-min-height", + "default": "48px" + }, + { + "type": { + "text": "Background" + }, + "description": "Active toggle button ripple background.", + "name": "--panel-ripple-background", + "default": "rgba(0, 0, 0, 0.38)" + } + ], + "cssParts": [ + { + "description": "An element wrapping the `header` slot.", + "name": "header" + }, + { + "description": "A toggle button, child of the header part.", + "name": "toggle" + }, + { + "description": "An element wrapping the `content` slot.", + "name": "content" + } + ], + "slots": [ + { + "description": "Slot fot panel content", + "name": "" + }, + { + "description": "Slot for panel header", + "name": "header" + } + ], + "members": [ + { + "kind": "field", + "name": "opened", + "type": { + "text": "boolean | null | undefined" + }, + "default": "false", + "description": "When true, the panel content is expanded and visible", + "attribute": "opened", + "reflects": true + }, + { + "kind": "field", + "name": "disabled", + "type": { + "text": "boolean" + }, + "default": "false", + "description": "Disabled panel can not be expanded or collapsed", + "attribute": "disabled", + "reflects": true + }, + { + "kind": "field", + "name": "header", + "type": { + "text": "HTMLDivElement | undefined" + }, + "privacy": "protected" + }, + { + "kind": "field", + "name": "_isShiftTabbing", + "type": { + "text": "boolean" + }, + "privacy": "protected", + "default": "false" + }, + { + "kind": "field", + "name": "_tabPressed", + "type": { + "text": "boolean" + }, + "privacy": "protected", + "default": "false" + }, + { + "kind": "field", + "name": "_boundBodyKeydown", + "privacy": "private" + }, + { + "kind": "field", + "name": "_boundBodyKeyup", + "privacy": "private" + }, + { + "kind": "method", + "name": "focus", + "return": { + "type": { + "text": "void" + } + } + }, + { + "kind": "method", + "name": "_setFocused", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "focused", + "type": { + "text": "boolean" + } + } + ] + }, + { + "kind": "method", + "name": "_onToggleClick", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + } + }, + { + "kind": "method", + "name": "_onToggleKeyDown", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "e", + "type": { + "text": "KeyboardEvent" + } + } + ] + }, + { + "kind": "method", + "name": "_onBodyKeydown", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "e", + "type": { + "text": "KeyboardEvent" + } + } + ] + }, + { + "kind": "method", + "name": "_onBodyKeyup", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + } + } + ], + "events": [ + { + "name": "opened-changed", + "type": { + "text": "CustomEvent" + }, + "description": "Event fired when expanding / collapsing" + } + ], + "attributes": [ + { + "type": { + "text": "boolean" + }, + "description": "State attribute set when element has focus.", + "name": "focused" + }, + { + "type": { + "text": "boolean" + }, + "description": "State attribute set when focused from keyboard.", + "name": "focus-ring" + }, + { + "name": "opened", + "type": { + "text": "boolean | null | undefined" + }, + "default": "false", + "description": "When true, the panel content is expanded and visible", + "fieldName": "opened" + }, + { + "name": "disabled", + "type": { + "text": "boolean" + }, + "default": "false", + "description": "Disabled panel can not be expanded or collapsed", + "fieldName": "disabled" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "expansion-panel", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "ExpansionPanel", + "declaration": { + "name": "ExpansionPanel", + "module": "src/fixtures/expansion-panel.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "expansion-panel", + "declaration": { + "name": "ExpansionPanel", + "module": "src/fixtures/expansion-panel.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/fixtures/fancy-accordion.ts", + "declarations": [ + { + "kind": "class", + "description": "A custom element implementing the accordion widget: a vertically stacked set of expandable panels\nthat wraps several instances of the `` element. Only one panel can be opened\n(expanded) at a time.\n\nPanel headings function as controls that enable users to open (expand) or hide (collapse) their\nassociated sections of content. The user can toggle panels by mouse click, Enter and Space keys.\n\nThe component supports keyboard navigation and is aligned with the\n[WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.1/#accordion).", + "name": "FancyAccordion", + "slots": [ + { + "description": "Slot fot panel elements.", + "name": "" + } + ], + "members": [ + { + "kind": "field", + "name": "openedIndex", + "type": { + "text": "number | null | undefined" + }, + "default": "null", + "description": "Index of the currently opened panel. By default all the panels are closed.\nOnly one panel can be opened at the same time. Setting `null` or `undefined`\ncloses all the accordion panels.", + "attribute": "opened-index" + }, + { + "kind": "field", + "name": "_items", + "type": { + "text": "ExpansionPanel[]" + }, + "privacy": "protected", + "default": "[]" + }, + { + "kind": "field", + "name": "_boundOnOpened", + "privacy": "private", + "default": "this._onOpened.bind(this)" + }, + { + "kind": "field", + "name": "focused", + "type": { + "text": "Element | null" + } + }, + { + "kind": "method", + "name": "_onKeydown", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "event", + "type": { + "text": "KeyboardEvent" + } + } + ] + }, + { + "kind": "method", + "name": "_getAvailableIndex", + "privacy": "private", + "return": { + "type": { + "text": "number" + } + }, + "parameters": [ + { + "name": "index", + "optional": true, + "type": { + "text": "number" + } + }, + { + "name": "increment", + "optional": true, + "type": { + "text": "number" + } + } + ] + }, + { + "kind": "method", + "name": "_onOpened", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "e", + "type": { + "text": "CustomEvent" + } + } + ] + }, + { + "kind": "method", + "name": "_notifyOpen", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + } + } + ], + "events": [ + { + "name": "opened-index-changed", + "type": { + "text": "CustomEvent" + }, + "description": "Event fired when changing currently opened panel." + } + ], + "attributes": [ + { + "name": "opened-index", + "type": { + "text": "number | null | undefined" + }, + "default": "null", + "description": "Index of the currently opened panel. By default all the panels are closed.\nOnly one panel can be opened at the same time. Setting `null` or `undefined`\ncloses all the accordion panels.", + "fieldName": "openedIndex" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "fancy-accordion", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "FancyAccordion", + "declaration": { + "name": "FancyAccordion", + "module": "src/fixtures/fancy-accordion.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "fancy-accordion", + "declaration": { + "name": "FancyAccordion", + "module": "src/fixtures/fancy-accordion.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/fixtures/intl-currency.ts", + "declarations": [ + { + "kind": "class", + "description": "A custom element that formats currency using Intl.", + "name": "IntlCurrency", + "members": [ + { + "kind": "field", + "name": "value", + "type": { + "text": "number" + }, + "default": "0", + "description": "Amount to be formatted.", + "attribute": "value" + }, + { + "kind": "field", + "name": "currency", + "type": { + "text": "string | null | undefined" + }, + "default": "'EUR'", + "description": "Currency code used for formatting.", + "attribute": "currency" + }, + { + "kind": "field", + "name": "locale", + "type": { + "text": "string | null | undefined" + }, + "default": "'en-GB'", + "description": "Locale code used for formatting.", + "attribute": "locale" + } + ], + "attributes": [ + { + "name": "value", + "type": { + "text": "number" + }, + "default": "0", + "description": "Amount to be formatted.", + "fieldName": "value" + }, + { + "name": "currency", + "type": { + "text": "string | null | undefined" + }, + "default": "'EUR'", + "description": "Currency code used for formatting.", + "fieldName": "currency" + }, + { + "name": "locale", + "type": { + "text": "string | null | undefined" + }, + "default": "'en-GB'", + "description": "Locale code used for formatting.", + "fieldName": "locale" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "intl-currency", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "IntlCurrency", + "declaration": { + "name": "IntlCurrency", + "module": "src/fixtures/intl-currency.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "intl-currency", + "declaration": { + "name": "IntlCurrency", + "module": "src/fixtures/intl-currency.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/fixtures/progress-bar.ts", + "declarations": [ + { + "kind": "class", + "description": "A custom element similar to the HTML5 `` element.", + "name": "ProgressBar", + "cssProperties": [ + { + "type": { + "text": "Color" + }, + "description": "Color of the filled progress bar part.", + "name": "--progress-bar-fill-color", + "default": "#6200ee" + }, + { + "description": "Opacity set on the underlying track.", + "name": "--progress-bar-opacity", + "default": "0.16" + } + ], + "cssParts": [ + { + "description": "A progress bar background.", + "name": "bar" + }, + { + "description": "A progress bar foreground.", + "name": "value" + } + ], + "members": [ + { + "kind": "field", + "name": "value", + "type": { + "text": "number" + }, + "default": "0.5", + "description": "Current progress value.", + "attribute": "value" + }, + { + "kind": "field", + "name": "min", + "type": { + "text": "number" + }, + "default": "0", + "description": "Minimum bound of the progress bar.", + "attribute": "min" + }, + { + "kind": "field", + "name": "max", + "type": { + "text": "number" + }, + "default": "1", + "description": "Maximum bound of the progress bar.", + "attribute": "max" + }, + { + "kind": "field", + "name": "indeterminate", + "type": { + "text": "boolean" + }, + "default": "false", + "description": "Indeterminate state of the progress bar.\nThis property takes precedence over other state properties (min, max, value).", + "attribute": "indeterminate", + "reflects": true + }, + { + "kind": "method", + "name": "_normalizedValueChanged", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "value", + "type": { + "text": "number" + } + }, + { + "name": "min", + "type": { + "text": "number" + } + }, + { + "name": "max", + "type": { + "text": "number" + } + } + ] + }, + { + "kind": "method", + "name": "_valueChanged", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "value", + "type": { + "text": "number" + } + } + ] + }, + { + "kind": "method", + "name": "_minChanged", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "min", + "type": { + "text": "number" + } + } + ] + }, + { + "kind": "method", + "name": "_maxChanged", + "privacy": "private", + "return": { + "type": { + "text": "void" + } + }, + "parameters": [ + { + "name": "max", + "type": { + "text": "number" + } + } + ] + } + ], + "attributes": [ + { + "name": "value", + "type": { + "text": "number" + }, + "default": "0.5", + "description": "Current progress value.", + "fieldName": "value" + }, + { + "name": "min", + "type": { + "text": "number" + }, + "default": "0", + "description": "Minimum bound of the progress bar.", + "fieldName": "min" + }, + { + "name": "max", + "type": { + "text": "number" + }, + "default": "1", + "description": "Maximum bound of the progress bar.", + "fieldName": "max" + }, + { + "name": "indeterminate", + "type": { + "text": "boolean" + }, + "default": "false", + "description": "Indeterminate state of the progress bar.\nThis property takes precedence over other state properties (min, max, value).", + "fieldName": "indeterminate" + } + ], + "superclass": { + "name": "LitElement", + "package": "lit" + }, + "tagName": "progress-bar", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "ProgressBar", + "declaration": { + "name": "ProgressBar", + "module": "src/fixtures/progress-bar.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "progress-bar", + "declaration": { + "name": "ProgressBar", + "module": "src/fixtures/progress-bar.ts" + } + } + ] + } + ] +} diff --git a/index.html b/index.html index cac1e05..91c5a4f 100755 --- a/index.html +++ b/index.html @@ -70,7 +70,7 @@

<api-viewer> element

- +