New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

api-viewer-element

Package Overview
Dependencies
Maintainers
1
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

api-viewer-element - npm Package Compare versions

Comparing version 0.3.13 to 0.4.0

lib/api-demo-base.d.ts

25

CHANGELOG.md

@@ -19,2 +19,27 @@ # Change Log

## [0.4.0] - 2020-03-10
### Added
- Two new elements: `<api-docs>` and `<api-demo>`
- Public `elements` property for passing data directly
- Public `exclude-knobs` property for excluding knobs
- Public `setTemplates` method for using custom templates
- New shadow part `header-title`, placed inside `header`
- Docs: custom CSS properties type and default value
- Docs: add `part` attribute to rendered markdown elements
- Demo: `prefix`, `suffix` and `wrapper` templates
- Demo: custom attribute knobs (string, boolean, select)
### Fixed
- Demo: fixed quotes in default value for string knobs
### Changed
- Templates are now stored separately for each instance
- Demo: do not wrap default slot content with `<div>`
- Demo: default slot knob value changed to "Content"
- Docs: removed `id` from headers in rendered markdown
## [0.3.13] - 2020-02-16

@@ -21,0 +46,0 @@

12

lib/api-viewer-base.d.ts
import { LitElement, TemplateResult } from 'lit-element';
import './api-viewer-content.js';
export declare class ApiViewerBase extends LitElement {
src?: string;
declare const ApiViewerBase_base: typeof LitElement & import("./api-viewer-mixin.js").Constructor<import("./api-viewer-mixin.js").ApiViewerInterface>;
export declare class ApiViewerBase extends ApiViewerBase_base {
section: string;
selected?: string;
private jsonFetched;
private lastSrc?;
excludeKnobs?: string;
protected _id?: number;
constructor();
protected render(): TemplateResult;
protected firstUpdated(): void;
setTemplates(templates?: HTMLTemplateElement[]): void;
}
export {};
//# sourceMappingURL=api-viewer-base.d.ts.map
import { __decorate } from "tslib";
import { LitElement, html, property } from 'lit-element';
import { until } from 'lit-html/directives/until.js';
import { queryTemplates } from './lib/utils.js';
import { setTemplates } from './lib/utils.js';
import { ApiViewerMixin, emptyDataWarning } from './api-viewer-mixin.js';
import './api-viewer-content.js';
async function fetchJson(src) {
let result = [];
try {
const file = await fetch(src);
const json = (await file.json());
if (Array.isArray(json.tags) && json.tags.length) {
result = json.tags;
}
else {
console.error(`No element definitions found at ${src}`);
}
}
catch (e) {
console.error(e);
}
return result;
}
async function renderDocs(jsonFetched, section, selected) {
async function renderDocs(jsonFetched, section, selected, id, exclude = '') {
const elements = await jsonFetched;

@@ -32,39 +16,33 @@ const index = elements.findIndex(el => el.name === selected);

.selected="${index >= 0 ? index : 0}"
.exclude="${exclude}"
.vid="${id}"
></api-viewer-content>
`
: html `
<div part="warning">
No custom elements found in the JSON file.
</div>
`;
: emptyDataWarning;
}
export class ApiViewerBase extends LitElement {
let id = 0;
export class ApiViewerBase extends ApiViewerMixin(LitElement) {
constructor() {
super(...arguments);
super();
this.section = 'docs';
this.jsonFetched = Promise.resolve([]);
this._id = id++;
}
render() {
const { src } = this;
if (src && this.lastSrc !== src) {
this.lastSrc = src;
this.jsonFetched = fetchJson(src);
}
return html `
${until(renderDocs(this.jsonFetched, this.section, this.selected))}
${until(renderDocs(this.jsonFetched, this.section, this.selected, this._id, this.excludeKnobs))}
`;
}
firstUpdated() {
queryTemplates(this);
this.setTemplates();
}
setTemplates(templates) {
setTemplates(this._id, templates || Array.from(this.querySelectorAll('template')));
}
}
__decorate([
property({ type: String })
], ApiViewerBase.prototype, "src", void 0);
__decorate([
property({ type: String })
], ApiViewerBase.prototype, "section", void 0);
__decorate([
property({ type: String })
], ApiViewerBase.prototype, "selected", void 0);
property({ type: String, attribute: 'exclude-knobs' })
], ApiViewerBase.prototype, "excludeKnobs", void 0);
//# sourceMappingURL=api-viewer-base.js.map

@@ -9,4 +9,4 @@ import { LitElement } from 'lit-element';

section: string;
protected _id?: number;
constructor();
exclude: string;
vid?: number;
protected createRenderRoot(): this;

@@ -13,0 +13,0 @@ protected render(): import("lit-element").TemplateResult;

@@ -8,10 +8,9 @@ import { __decorate } from "tslib";

import './api-viewer-demo.js';
let radioId = 0;
let ApiViewerContent = class ApiViewerContent extends LitElement {
constructor() {
super();
super(...arguments);
this.elements = [];
this.selected = 0;
this.section = 'docs';
this._id = ++radioId;
this.exclude = '';
}

@@ -22,3 +21,3 @@ createRenderRoot() {

render() {
const { elements, selected, section } = this;
const { elements, selected, section, exclude, vid } = this;
const { name, description, properties, attributes, slots, events, cssParts, cssProperties } = { ...EMPTY_ELEMENT, ...(elements[selected] || {}) };

@@ -29,3 +28,3 @@ // TODO: analyzer should sort CSS custom properties

<header part="header">
<div class="tag-name">&lt;${name}&gt;</div>
<div part="header-title">&lt;${name}&gt;</div>
<nav>

@@ -35,3 +34,3 @@ <input

type="radio"
name="section-${this._id}"
name="section-${this.vid}"
value="docs"

@@ -46,3 +45,3 @@ ?checked="${section === 'docs'}"

type="radio"
name="section-${this._id}"
name="section-${this.vid}"
value="demo"

@@ -92,2 +91,4 @@ ?checked="${section === 'demo'}"

.cssProps="${cssProps}"
.exclude="${exclude}"
.vid="${vid}"
></api-viewer-demo>

@@ -113,2 +114,8 @@ `)}

], ApiViewerContent.prototype, "section", void 0);
__decorate([
property({ type: String })
], ApiViewerContent.prototype, "exclude", void 0);
__decorate([
property({ type: Number })
], ApiViewerContent.prototype, "vid", void 0);
ApiViewerContent = __decorate([

@@ -115,0 +122,0 @@ customElement('api-viewer-content')

@@ -54,3 +54,3 @@ import { __decorate } from "tslib";

__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoEvents.prototype, "log", void 0);

@@ -57,0 +57,0 @@ ApiViewerDemoEvents = __decorate([

@@ -14,7 +14,11 @@ import { LitElement, PropertyValues } from 'lit-element';

cssProps: CSSPropertyInfo[];
exclude: string;
vid?: number;
protected processedSlots: SlotValue[];
protected processedCss: CSSPropertyInfo[];
protected eventLog: CustomEvent[];
customKnobs: PropertyInfo[];
knobs: KnobValues;
protected copyBtnText: string;
private _savedProps;
protected createRenderRoot(): this;

@@ -24,2 +28,4 @@ protected render(): import("lit-element").TemplateResult;

protected updated(props: PropertyValues): void;
private _getCustomKnobs;
private _filterProps;
private _getProp;

@@ -26,0 +32,0 @@ private _onLogClear;

@@ -5,3 +5,3 @@ import { __decorate } from "tslib";

import { cssPropRenderer, propRenderer, renderKnobs, slotRenderer } from './lib/knobs.js';
import { getSlotTitle, hasHostTemplate, hasSlotTemplate, isEmptyArray, normalizeType } from './lib/utils.js';
import { getSlotContent, getTemplates, hasTemplate, isEmptyArray, isPropMatch, normalizeType, TemplateTypes, unquote, getTemplateNode } from './lib/utils.js';
import './api-viewer-demo-snippet.js';

@@ -13,11 +13,13 @@ import './api-viewer-demo-events.js';

const getDefault = (prop) => {
switch (normalizeType(prop.type)) {
const { type, default: value } = prop;
switch (normalizeType(type)) {
case 'boolean':
return prop.default !== 'false';
return value !== 'false';
case 'number':
return Number(prop.default);
return Number(value);
default:
return prop.default;
return unquote(value);
}
};
const { HOST, KNOB, SLOT } = TemplateTypes;
// TODO: remove when analyzer outputs "readOnly" to JSON

@@ -45,7 +47,10 @@ const isGetter = (element, prop) => {

this.cssProps = [];
this.exclude = '';
this.processedSlots = [];
this.processedCss = [];
this.eventLog = [];
this.customKnobs = [];
this.knobs = {};
this.copyBtnText = 'copy';
this._savedProps = [];
}

@@ -59,6 +64,9 @@ createRenderRoot() {

const noSlots = isEmptyArray(this.slots);
const noKnobs = isEmptyArray(this.props) && noSlots;
const noCustomKnobs = isEmptyArray(this.customKnobs);
const noKnobs = isEmptyArray(this.props) && noCustomKnobs && noSlots;
const id = this.vid;
const slots = this.processedSlots;
return html `
<div part="demo-output" @rendered="${this._onRendered}">
${renderer(this.tag, this.knobs, this.processedSlots, this.processedCss)}
${renderer(id, this.tag, this.knobs, slots, this.processedCss)}
</div>

@@ -74,4 +82,5 @@ <api-viewer-tabs part="demo-tabs">

.knobs="${this.knobs}"
.slots="${this.processedSlots}"
.slots="${slots}"
.cssProps="${this.processedCss}"
.vid="${this.vid}"
></api-viewer-demo-snippet>

@@ -90,5 +99,7 @@ </api-viewer-panel>

${renderKnobs(this.props, 'prop', propRenderer)}
<h3 part="knobs-header" ?hidden="${noCustomKnobs}">Attributes</h3>
${renderKnobs(this.customKnobs, 'prop', propRenderer)}
</section>
<section
?hidden="${hasSlotTemplate(this.tag) || noSlots}"
?hidden="${noSlots || hasTemplate(id, this.tag, SLOT)}"
part="knobs-column"

@@ -98,3 +109,3 @@ @change="${this._onSlotChanged}"

<h3 part="knobs-header">Slots</h3>
${renderKnobs(this.processedSlots, 'slot', slotRenderer)}
${renderKnobs(slots, 'slot', slotRenderer)}
</section>

@@ -136,19 +147,16 @@ </div>

firstUpdated(props) {
// When a selected tag name is changed by the user,
// the whole api-viewer-demo component is re-rendered,
// so this callback is invoked again for new element.
if (props.has('props')) {
const element = document.createElement(this.tag);
// Apply default property values from analyzer
// Do not include getters to prevent exception
this.props = this.props
.filter(({ name }) => !isGetter(element, name))
.map((prop) => {
return typeof prop.default === 'string'
? {
...prop,
value: getDefault(prop)
}
: prop;
});
// Store properties without getters
this._savedProps = this.props.filter(({ name }) => !isGetter(element, name));
}
this.customKnobs = this._getCustomKnobs();
}
updated(props) {
if (props.has('exclude')) {
this.props = this._filterProps();
}
if (props.has('slots') && this.slots) {

@@ -168,3 +176,3 @@ this.processedSlots = this.slots

...slot,
content: getSlotTitle(slot.name)
content: getSlotContent(slot.name)
};

@@ -174,4 +182,58 @@ });

}
_getCustomKnobs() {
return getTemplates(this.vid, this.tag, KNOB)
.map(template => {
const { attr, type } = template.dataset;
let result = null;
if (attr) {
if (type === 'select') {
const node = getTemplateNode(template);
const options = node
? Array.from(node.children)
.filter((c) => c instanceof HTMLOptionElement)
.map(option => option.value)
: [];
if (node instanceof HTMLSelectElement && options.length > 1) {
result = {
name: attr,
attribute: attr,
type,
options
};
}
}
if (type === 'string' || type === 'boolean') {
result = {
name: attr,
attribute: attr,
type
};
}
}
return result;
})
.filter(Boolean);
}
_filterProps() {
const exclude = this.exclude.split(',');
return this._savedProps
.filter(({ name }) => !exclude.includes(name))
.map((prop) => {
return typeof prop.default === 'string'
? {
...prop,
value: getDefault(prop)
}
: prop;
});
}
_getProp(name) {
return this.props.find(p => p.attribute === name || p.name === name);
const isMatch = isPropMatch(name);
const prop = this.props.find(isMatch);
return prop
? { prop }
: {
prop: this.customKnobs.find(isMatch),
custom: true
};
}

@@ -236,6 +298,10 @@ _onLogClear() {

}
const { attribute } = this._getProp(name);
this.knobs = Object.assign(this.knobs, {
[name]: { type, value, attribute }
});
const { prop, custom } = this._getProp(name);
if (prop) {
const { attribute } = prop;
this.knobs = {
...this.knobs,
[name]: { type, value, attribute, custom }
};
}
}

@@ -257,3 +323,3 @@ _onSlotChanged(e) {

const { component } = e.detail;
if (hasHostTemplate(this.tag)) {
if (hasTemplate(this.vid, this.tag, HOST)) {
// Apply property values from template

@@ -277,7 +343,9 @@ this.props

this.processedCss = this.cssProps.map(cssProp => {
let value = style.getPropertyValue(cssProp.name);
let value = cssProp.default
? unquote(cssProp.default)
: style.getPropertyValue(cssProp.name);
const result = cssProp;
if (value) {
value = value.trim();
result.defaultValue = value;
result.default = value;
result.value = value;

@@ -293,3 +361,3 @@ }

if (event.endsWith(s)) {
const prop = this._getProp(event.replace(s, ''));
const { prop } = this._getProp(event.replace(s, ''));
if (prop) {

@@ -299,3 +367,3 @@ this._syncKnob(component, prop);

}
this.eventLog.push(e);
this.eventLog = [...this.eventLog, e];
}));

@@ -307,5 +375,6 @@ }

// update knobs to avoid duplicate event
this.knobs = Object.assign(this.knobs, {
this.knobs = {
...this.knobs,
[name]: { type, value, attribute }
});
};
this.props = this.props.map(prop => {

@@ -325,24 +394,33 @@ return prop.name === name

__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "props", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "slots", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "events", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "cssProps", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ type: String })
], ApiViewerDemoLayout.prototype, "exclude", void 0);
__decorate([
property({ type: Number })
], ApiViewerDemoLayout.prototype, "vid", void 0);
__decorate([
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "processedSlots", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "processedCss", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "eventLog", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "customKnobs", void 0);
__decorate([
property({ attribute: false })
], ApiViewerDemoLayout.prototype, "knobs", void 0);

@@ -349,0 +427,0 @@ __decorate([

@@ -8,2 +8,3 @@ import { LitElement, TemplateResult } from 'lit-element';

cssProps: CSSPropertyInfo[];
vid?: number;
static get styles(): import("lit-element").CSSResult[];

@@ -10,0 +11,0 @@ protected render(): TemplateResult;

@@ -9,3 +9,3 @@ import { __decorate } from "tslib";

import { CSS } from './lib/highlight-css.js';
import { getSlotTemplate, normalizeType } from './lib/utils.js';
import { getTemplate, getTemplateNode, isTemplate, normalizeType, TemplateTypes } from './lib/utils.js';
import highlightTheme from './lib/highlight-theme.js';

@@ -18,4 +18,5 @@ // register languages

});
const { PREFIX, SLOT, SUFFIX, WRAPPER } = TemplateTypes;
const INDENT = ' ';
const unindent = (text) => {
const unindent = (text, prepend) => {
if (!text)

@@ -33,17 +34,41 @@ return text;

}, null);
return lines.map(l => INDENT + l.substr(indent)).join('\n');
return lines.map(l => prepend + l.substr(indent)).join('\n');
};
const renderSnippet = (tag, values, slots, cssProps) => {
let markup = `<${tag}`;
const getTplContent = (template, prepend) => {
const tpl = template.innerHTML.replace(/\s+$/, '').replace(/(="")/g, '');
return unindent(tpl, prepend);
};
const renderSnippet = (id, tag, values, slots, cssProps) => {
let markup = '';
const prefix = getTemplate(id, tag, PREFIX);
if (isTemplate(prefix)) {
markup += `${getTplContent(prefix, '').trim()}\n`;
}
let prepend = '';
let wrap = null;
const wrapper = getTemplate(id, tag, WRAPPER);
const wrapNode = getTemplateNode(wrapper);
if (wrapNode) {
prepend = INDENT;
const match = wrapNode.outerHTML.match(/<([a-z]+)[^>]*(?<!\/)>/);
if (match) {
wrap = wrapNode.tagName.toLowerCase();
markup += `${match[0]}\n${INDENT}`;
}
}
markup += `<${tag}`;
Object.keys(values)
.sort((a, b) => (a > b ? 1 : -1))
.forEach((key) => {
const knob = values[key];
const attr = knob.attribute || key;
switch (normalizeType(knob.type)) {
const { value, type, attribute } = values[key];
const attr = attribute || key;
switch (normalizeType(type)) {
case 'boolean':
markup += knob.value ? ` ${attr}` : '';
markup += value ? ` ${attr}` : '';
break;
case 'select':
markup += value !== '' ? ` ${attr}="${value}"` : '';
break;
default:
markup += knob.value != null ? ` ${attr}="${knob.value}"` : '';
markup += value != null ? ` ${attr}="${value}"` : '';
break;

@@ -53,18 +78,28 @@ }

markup += `>`;
const template = getSlotTemplate(tag);
if (template instanceof HTMLTemplateElement) {
const tpl = template.innerHTML.replace(/\s+$/, '').replace(/(="")/g, '');
markup += unindent(tpl);
markup += `\n`;
const template = getTemplate(id, tag, SLOT);
if (isTemplate(template)) {
markup += `${getTplContent(template, `${prepend}${INDENT}`)}\n${prepend}`;
}
else if (slots.length) {
slots.forEach(slot => {
const { name, content } = slot;
const div = name ? `<div slot="${name}">` : '<div>';
markup += `\n${INDENT}${div}${content}</div>`;
});
markup += `\n`;
if (slots.length === 1 && !slots[0].name) {
markup += slots[0].content;
}
else {
markup += slots.reduce((result, slot) => {
const { name, content } = slot;
const line = name ? `<div slot="${name}">${content}</div>` : content;
return `${result}\n${prepend}${INDENT}${line}`;
}, '');
markup += `\n${prepend}`;
}
}
markup += `</${tag}>`;
const cssValues = cssProps.filter(p => p.value !== p.defaultValue);
if (wrap) {
markup += `\n</${wrap}>`;
}
const suffix = getTemplate(id, tag, SUFFIX);
if (isTemplate(suffix)) {
markup += `\n${getTplContent(suffix, '').trim()}\n`;
}
const cssValues = cssProps.filter(p => p.value !== p.default);
if (cssValues.length) {

@@ -105,3 +140,3 @@ markup += `\n<style>\n${INDENT}${tag} {\n`;

return html `
${renderSnippet(this.tag, this.knobs, this.slots, this.cssProps)}
${renderSnippet(this.vid, this.tag, this.knobs, this.slots, this.cssProps)}
`;

@@ -117,10 +152,13 @@ }

__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoSnippet.prototype, "knobs", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoSnippet.prototype, "slots", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemoSnippet.prototype, "cssProps", void 0);
__decorate([
property({ type: Number })
], ApiViewerDemoSnippet.prototype, "vid", void 0);
ApiViewerDemoSnippet = __decorate([

@@ -127,0 +165,0 @@ customElement('api-viewer-demo-snippet')

@@ -10,2 +10,4 @@ import { LitElement, TemplateResult } from 'lit-element';

cssProps: CSSPropertyInfo[];
exclude: string;
vid?: number;
private whenDefined;

@@ -12,0 +14,0 @@ private lastName?;

@@ -13,2 +13,3 @@ import { __decorate } from "tslib";

this.cssProps = [];
this.exclude = '';
this.whenDefined = Promise.resolve();

@@ -25,2 +26,4 @@ }

.cssProps="${this.cssProps}"
.exclude="${this.exclude}"
.vid="${this.vid}"
></api-viewer-demo-layout>

@@ -51,13 +54,19 @@ `;

__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemo.prototype, "props", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemo.prototype, "slots", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemo.prototype, "events", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDemo.prototype, "cssProps", void 0);
__decorate([
property({ type: String })
], ApiViewerDemo.prototype, "exclude", void 0);
__decorate([
property({ type: Number })
], ApiViewerDemo.prototype, "vid", void 0);
ApiViewerDemo = __decorate([

@@ -64,0 +73,0 @@ customElement('api-viewer-demo')

import { __decorate } from "tslib";
import { LitElement, html, customElement, property } from 'lit-element';
import { nothing } from 'lit-html';
import { isEmptyArray } from './lib/utils.js';
import { isEmptyArray, isPropMatch, unquote } from './lib/utils.js';
import { parse } from './lib/markdown.js';

@@ -12,10 +12,7 @@ import './api-viewer-panel.js';

/* eslint-enable import/no-duplicates */
const processAttrs = (attrs, props) => {
return attrs.filter(({ name }) => !props.some(prop => prop.attribute === name || prop.name === name));
};
const renderItem = (name, description, valueType, attribute, value) => {
const renderItem = (prefix, name, description, valueType, value, attribute) => {
return html `
<div part="docs-item">
<div part="docs-row">
<div part="docs-column">
<div part="docs-column" class="column-name-${prefix}">
<div part="docs-label">Name</div>

@@ -32,3 +29,3 @@ <div part="docs-value" class="accent">${name}</div>

`}
${valueType === undefined
${valueType === undefined && value === undefined
? nothing

@@ -39,3 +36,4 @@ : html `

<div part="docs-value">
${valueType}
${valueType ||
(Number.isNaN(Number(value)) ? typeof value : 'number')}
${value === undefined

@@ -88,3 +86,3 @@ ? nothing

const properties = props || [];
const attributes = processAttrs(attrs || [], properties);
const attributes = (attrs || []).filter(({ name }) => !properties.some(isPropMatch(name)));
const emptyDocs = [

@@ -110,19 +108,22 @@ properties,

const { name, description, type, attribute } = prop;
return renderItem(name, description, type, attribute, prop.default);
return renderItem('prop', name, description, type, prop.default, attribute);
})}
`)}
${renderTab('Attributes', attributes.length, html `
${attributes.map(({ name, description, type }) => renderItem(name, description, type))}
${attributes.map(({ name, description, type }) => renderItem('attr', name, description, type))}
`)}
${renderTab('Slots', slots.length, html `
${slots.map(({ name, description }) => renderItem(name, description))}
${slots.map(({ name, description }) => renderItem('slot', name, description))}
`)}
${renderTab('Events', events.length, html `
${events.map(({ name, description }) => renderItem(name, description))}
${events.map(({ name, description }) => renderItem('event', name, description))}
`)}
${renderTab('CSS Custom Properties', cssProps.length, html `
${cssProps.map(({ name, description }) => renderItem(name, description))}
${cssProps.map(prop => {
const { name, description, type } = prop;
return renderItem('css', name, description, type, unquote(prop.default));
})}
`)}
${renderTab('CSS Shadow Parts', cssParts.length, html `
${cssParts.map(({ name, description }) => renderItem(name, description))}
${cssParts.map(({ name, description }) => renderItem('part', name, description))}
`)}

@@ -145,18 +146,18 @@ </api-viewer-tabs>

__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDocs.prototype, "props", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDocs.prototype, "attrs", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDocs.prototype, "slots", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDocs.prototype, "events", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDocs.prototype, "cssParts", void 0);
__decorate([
property({ attribute: false, hasChanged: () => true })
property({ attribute: false })
], ApiViewerDocs.prototype, "cssProps", void 0);

@@ -163,0 +164,0 @@ ApiViewerDocs = __decorate([

import { css } from 'lit-element';
import sharedStyles from './shared-styles.js';
import docsStyles from './api-docs-styles.js';
import demoStyles from './api-demo-styles.js';
export default css `
:host {
display: block;
text-align: left;
box-sizing: border-box;
max-width: 800px;
min-width: 360px;
font-size: 1rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Oxygen-Sans', Ubuntu, Cantarell, sans-serif;
border: 1px solid var(--ave-border-color);
border-radius: var(--ave-border-radius);
${sharedStyles}
${docsStyles}
${demoStyles}
--ave-primary-color: #01579b;
--ave-accent-color: #d63200;
--ave-border-color: rgba(0, 0, 0, 0.12);
--ave-border-radius: 4px;
--ave-header-color: #fff;
--ave-item-color: rgba(0, 0, 0, 0.87);
--ave-label-color: #424242;
--ave-link-color: #01579b;
--ave-link-hover-color: #d63200;
--ave-tab-indicator-size: 2px;
--ave-tab-color: rgba(0, 0, 0, 0.54);
--ave-monospace-font: Menlo, 'DejaVu Sans Mono', 'Liberation Mono', Consolas,
'Courier New', monospace;
}
[hidden] {
display: none !important;
}
p,
ul,
ol {
margin: 1rem 0;
font-size: 0.9375rem;
line-height: 1.5;
}
pre {
white-space: pre-wrap;
}
a {
color: var(--ave-link-color);
}
a:hover {
color: var(--ave-link-hover-color);
}
button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
text-transform: uppercase;
border: none;
border-radius: 0.25em;
cursor: pointer;
background: var(--ave-button-background, rgba(0, 0, 0, 0.3));
color: var(--ave-button-color, #fff);
}
button:focus,
button:hover {
background: var(--ave-button-active-background, rgba(0, 0, 0, 0.6));
}
api-viewer-content,
api-viewer-docs,
api-viewer-demo,
api-viewer-demo-layout {
api-viewer-content {
display: block;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem;
background: var(--ave-primary-color);
border-top-left-radius: var(--ave-border-radius);
border-top-right-radius: var(--ave-border-radius);
}
.tag-name {
color: var(--ave-header-color);
font-family: var(--ave-monospace-font);
font-size: 0.875rem;
line-height: 1.5rem;
}
nav {
display: flex;
align-items: center;
}
[part='warning'] {
padding: 1rem;
}
[part='radio-label'] {

@@ -109,157 +19,3 @@ margin: 0 0.75rem 0 0.25rem;

}
[part='select-label'] {
margin-left: 0.5rem;
}
/* Docs styles */
[part='tab'][heading^='CSS'] {
font-size: 0.8125rem;
}
[part='docs-item'] {
display: block;
padding: 0.5rem;
color: var(--ave-item-color);
}
[part='docs-description'] {
display: block;
padding: 0 1rem;
border-bottom: solid 1px var(--ave-border-color);
}
[part='docs-row'] {
display: flex;
flex-wrap: wrap;
margin-bottom: 1rem;
}
[part='docs-column'] {
box-sizing: border-box;
flex-basis: 25%;
padding-right: 0.5rem;
}
[part='docs-column']:only-child {
flex-basis: 100%;
}
.column-type {
flex-basis: 50%;
}
[part='docs-label'] {
color: var(--ave-label-color);
font-size: 0.75rem;
line-height: 1rem;
letter-spacing: 0.1rem;
}
[part='docs-value'] {
font-family: var(--ave-monospace-font);
font-size: 0.875rem;
line-height: 1.5rem;
}
[part='docs-markdown'] p,
[part='docs-markdown'] ul,
[part='docs-markdown'] ol {
margin: 0.5rem 0;
}
.accent {
color: var(--ave-accent-color);
}
/* Demo styles */
[part='docs-item']:not(:first-of-type),
[part='demo-tabs'],
[part='demo-output'] {
border-top: solid 1px var(--ave-border-color);
}
[part='demo-tabs'] [part='tab-panel'] {
box-sizing: border-box;
position: relative;
background: #fafafa;
}
[part='demo-output'] {
padding: 1.5rem;
text-align: initial;
transform: translateZ(0);
overflow: hidden;
}
.source {
position: relative;
}
[part='knobs'] {
display: flex;
padding: 1rem;
}
[part='knobs-column'] {
width: 50%;
}
[part='knobs-header'] {
font-size: 1rem;
font-weight: bold;
margin: 0 0 0.25rem;
}
td {
padding: 0.25rem 0.25rem 0.25rem 0;
font-size: 0.9375rem;
white-space: nowrap;
}
[part='event-log'] {
display: block;
padding: 0 1rem;
min-height: 50px;
max-height: 200px;
overflow: auto;
}
[part='event-record'] {
margin: 0 0 0.25rem;
font-family: var(--ave-monospace-font);
font-size: 0.875rem;
}
[part='event-record']:first-of-type {
margin-top: 1rem;
}
[part='event-record']:last-of-type {
margin-bottom: 1rem;
}
@media (max-width: 480px) {
header {
flex-direction: column;
}
nav {
margin-top: 0.5rem;
}
.api-col-type {
flex-basis: 100%;
margin-top: 1rem;
}
.columns {
flex-direction: column;
}
[part='knobs-column']:not(:last-child) {
margin-bottom: 1rem;
}
}
`;
//# sourceMappingURL=api-viewer-styles.js.map
import { ApiViewerBase } from './api-viewer-base.js';
export declare class ApiViewer extends ApiViewerBase {
static get styles(): import("lit-element").CSSResult;
protected firstUpdated(): void;
setTemplates(templates?: HTMLTemplateElement[]): void;
}

@@ -5,0 +7,0 @@ declare global {

import { __decorate } from "tslib";
import { customElement } from 'lit-element';
import { ApiViewerBase } from './api-viewer-base.js';
import { setTemplates } from './lib/utils.js';
import styles from './api-viewer-styles.js';

@@ -9,2 +10,8 @@ let ApiViewer = class ApiViewer extends ApiViewerBase {

}
firstUpdated() {
this.setTemplates();
}
setTemplates(templates) {
setTemplates(this._id, templates || Array.from(this.querySelectorAll('template')));
}
};

@@ -11,0 +18,0 @@ ApiViewer = __decorate([

@@ -26,7 +26,16 @@ import { html } from 'lit-html';

export const propRenderer = (knob, id) => {
const { name, type, value } = knob;
const { name, type, value, options } = knob;
const inputType = getInputType(type);
let input;
if (value === undefined) {
if (type === 'select' && Array.isArray(options)) {
input = html `
<select id="${id}" data-name="${name}" data-type="${type}" part="select">
${options.map(option => html `
<option value="${option}">${option}</option>
`)}
</select>
`;
}
else if (value === undefined) {
input = html `
<input

@@ -33,0 +42,0 @@ id="${id}"

@@ -6,2 +6,3 @@ import { html } from 'lit-element';

import DOMPurify from 'dompurify';
marked.setOptions({ headerIds: false });
export const parse = (markdown) => {

@@ -14,5 +15,5 @@ if (!markdown) {

return html `
${unsafeHTML(DOMPurify.sanitize(marked(markdown)))}
${unsafeHTML(DOMPurify.sanitize(marked(markdown)).replace(/<(h[1-6]|a|p|ul|ol|li|pre|code|strong|em|blockquote|del)(\s+href="[^"]+")*>/g, '<$1 part="md-$1"$2>'))}
`;
};
//# sourceMappingURL=markdown.js.map
import { Part } from 'lit-html';
import { CSSPropertyInfo, KnobValues, SlotValue } from './types.js';
export declare const renderer: (tag: string, knobs: KnobValues, slots: SlotValue[], cssProps: CSSPropertyInfo[]) => (part: Part) => void;
export declare const renderer: (id: number, tag: string, knobs: KnobValues, slots: SlotValue[], cssProps: CSSPropertyInfo[]) => (part: Part) => void;
//# sourceMappingURL=renderer.d.ts.map
import { directive, NodePart } from 'lit-html';
import { getHostTemplateNode, getSlotTemplate, hasSlotTemplate, normalizeType } from './utils.js';
import { getTemplate, getTemplateNode, hasTemplate, isTemplate, normalizeType, TemplateTypes } from './utils.js';
const { HOST, PREFIX, SLOT, SUFFIX, WRAPPER } = TemplateTypes;
const caches = new WeakMap();
const applyKnobs = (component, knobs) => {
Object.keys(knobs).forEach((key) => {
const { type, attribute, value } = knobs[key];
if (normalizeType(type) === 'boolean') {
const { type, attribute, value, custom } = knobs[key];
if (custom && attribute) {
if (typeof value === 'string' && value) {
component.setAttribute(attribute, value);
}
else {
component.removeAttribute(attribute);
}
}
else if (normalizeType(type) === 'boolean') {
component.toggleAttribute(attribute || key, Boolean(value));

@@ -20,9 +29,13 @@ }

slots.forEach(slot => {
const div = document.createElement('div');
let node;
const { name, content } = slot;
if (name) {
div.setAttribute('slot', name);
node = document.createElement('div');
node.setAttribute('slot', name);
node.textContent = content;
}
div.textContent = content;
component.appendChild(div);
else {
node = document.createTextNode(content);
}
component.appendChild(node);
});

@@ -34,3 +47,3 @@ };

if (value) {
if (value === prop.defaultValue) {
if (value === prop.default) {
component.style.removeProperty(name);

@@ -70,3 +83,9 @@ }

}
export const renderer = directive((tag, knobs, slots, cssProps) => (part) => {
const makePart = (part) => {
const newPart = new NodePart(part.options);
newPart.appendIntoPart(part);
return newPart;
};
const importTemplate = (tpl) => document.importNode(tpl.content, true);
export const renderer = directive((id, tag, knobs, slots, cssProps) => (part) => {
if (!(part instanceof NodePart)) {

@@ -77,3 +96,10 @@ throw new Error('renderer can only be used in text bindings');

if (component === undefined || component.tagName.toLowerCase() !== tag) {
const node = getHostTemplateNode(tag);
const [host, prefix, suffix, slot, wrapper] = [
HOST,
PREFIX,
SUFFIX,
SLOT,
WRAPPER
].map(type => getTemplate(id, tag, type));
const node = getTemplateNode(host);
if (node) {

@@ -85,11 +111,35 @@ component = node.cloneNode(true);

}
part.setValue(component);
part.commit();
const template = getSlotTemplate(tag);
if (template instanceof HTMLTemplateElement) {
const clone = document.importNode(template.content, true);
component.appendChild(clone);
let targetPart = part;
if (isTemplate(prefix)) {
const prefixPart = makePart(part);
prefixPart.setValue(importTemplate(prefix));
prefixPart.commit();
}
const wrapNode = getTemplateNode(wrapper);
if (wrapNode) {
const wrapperPart = makePart(part);
const clone = wrapNode.cloneNode(true);
clone.innerHTML = '';
wrapperPart.setValue(clone);
wrapperPart.commit();
const innerPart = new NodePart(part.options);
innerPart.appendInto(clone);
targetPart = innerPart;
}
else if (isTemplate(prefix) || isTemplate(suffix)) {
const contentPart = makePart(part);
targetPart = contentPart;
}
targetPart.setValue(component);
targetPart.commit();
if (isTemplate(suffix)) {
const suffixPart = makePart(part);
suffixPart.setValue(importTemplate(suffix));
suffixPart.commit();
}
if (isTemplate(slot)) {
component.appendChild(importTemplate(slot));
}
caches.set(part, component);
const instance = part.value;
const instance = targetPart.value;
// wait for rendering

@@ -107,3 +157,3 @@ elementUpdated(instance).then(() => {

applyKnobs(component, knobs);
if (!hasSlotTemplate(tag) && slots.length) {
if (!hasTemplate(id, tag, SLOT) && slots.length) {
applySlots(component, slots);

@@ -110,0 +160,0 @@ }

@@ -12,2 +12,3 @@ export interface ElementSetInfo {

}
export declare type ElementPromise = Promise<ElementInfo[]>;
export interface Info {

@@ -25,2 +26,3 @@ name: string;

default: string | number | boolean | null | undefined;
options?: string[];
}

@@ -31,2 +33,3 @@ export interface KnobValue {

value: string | number | boolean | null;
custom?: boolean;
}

@@ -48,3 +51,4 @@ export declare type KnobValues = {

value?: string;
defaultValue?: string;
default?: string;
type?: string;
}

@@ -51,0 +55,0 @@ export declare type ComponentWithProps = {

@@ -0,9 +1,22 @@

import { PropertyInfo } from './types';
export declare const getSlotContent: (name: string) => string;
export declare const getSlotTitle: (name: string) => string;
export declare const queryTemplates: (node: Element) => void;
export declare const getSlotTemplate: (name: string) => HTMLTemplateElement | undefined;
export declare const hasSlotTemplate: (name: string) => boolean;
export declare const getHostTemplateNode: (name: string) => Element | null | undefined;
export declare const hasHostTemplate: (name: string) => boolean;
export declare const setTemplates: (id: number, tpl: HTMLTemplateElement[]) => void;
export declare const TemplateTypes: Readonly<{
HOST: string;
KNOB: string;
SLOT: string;
PREFIX: string;
SUFFIX: string;
WRAPPER: string;
}>;
export declare const isTemplate: (node: unknown) => node is HTMLTemplateElement;
export declare const getTemplateNode: (node: unknown) => false | Element | null;
export declare const getTemplate: (id: number, name: string, type: string) => HTMLTemplateElement | undefined;
export declare const getTemplates: (id: number, name: string, type: string) => HTMLTemplateElement[];
export declare const hasTemplate: (id: number, name: string, type: string) => boolean;
export declare const isEmptyArray: (array: unknown[]) => boolean;
export declare const isPropMatch: (name: string) => (prop: PropertyInfo) => boolean;
export declare const normalizeType: (type?: string | undefined) => string;
export declare const unquote: (value?: string | undefined) => string | undefined;
//# sourceMappingURL=utils.d.ts.map

@@ -1,29 +0,33 @@

export const getSlotTitle = (name) => {
return name === '' ? 'Default' : name[0].toUpperCase() + name.slice(1);
const getSlotDefault = (name, initial) => {
return name === '' ? initial : name[0].toUpperCase() + name.slice(1);
};
let templates = [];
export const queryTemplates = (node) => {
templates = Array.from(node.querySelectorAll('template'));
export const getSlotContent = (name) => getSlotDefault(name, 'Content');
export const getSlotTitle = (name) => getSlotDefault(name, 'Default');
const templates = [];
export const setTemplates = (id, tpl) => {
templates[id] = tpl;
};
const isTemplate = (tpl, name) => {
return tpl.dataset.element === name;
export const TemplateTypes = Object.freeze({
HOST: 'host',
KNOB: 'knob',
SLOT: 'slot',
PREFIX: 'prefix',
SUFFIX: 'suffix',
WRAPPER: 'wrapper'
});
export const isTemplate = (node) => node instanceof HTMLTemplateElement;
const matchTemplate = (name, type) => (tpl) => {
const { element, target } = tpl.dataset;
return element === name && target === type;
};
const isHostTemplate = (tpl) => {
return tpl.dataset.target === 'host';
};
export const getSlotTemplate = (name) => {
return templates.find(t => isTemplate(t, name) && !isHostTemplate(t));
};
export const hasSlotTemplate = (name) => {
return templates.some(t => isTemplate(t, name) && !isHostTemplate(t));
};
export const getHostTemplateNode = (name) => {
const tpl = templates.find(t => isTemplate(t, name) && isHostTemplate(t));
return tpl && tpl.content.firstElementChild;
};
export const hasHostTemplate = (name) => {
return templates.some(tpl => isTemplate(tpl, name) && isHostTemplate(tpl));
};
export const getTemplateNode = (node) => isTemplate(node) && node.content.firstElementChild;
export const getTemplate = (id, name, type) => templates[id].find(matchTemplate(name, type));
export const getTemplates = (id, name, type) => templates[id].filter(matchTemplate(name, type));
export const hasTemplate = (id, name, type) => templates[id].some(matchTemplate(name, type));
export const isEmptyArray = (array) => array.length === 0;
export const isPropMatch = (name) => (prop) => prop.attribute === name || prop.name === name;
export const normalizeType = (type = '') => type.replace(' | undefined', '').replace(' | null', '');
export const unquote = (value) => typeof value === 'string' && value.startsWith('"') && value.endsWith('"')
? value.slice(1, value.length - 1)
: value;
//# sourceMappingURL=utils.js.map
{
"name": "api-viewer-element",
"version": "0.3.13",
"version": "0.4.0",
"description": "Web Components API viewer element",

@@ -31,3 +31,3 @@ "author": "Serhii Kulykov <iamkulykov@gmail.com>",

"@types/dompurify": "^2.0.1",
"dompurify": "^2.0.7",
"dompurify": "^2.0.8",
"highlight-ts": "9.12.1-2",

@@ -40,31 +40,31 @@ "lit-element": "^2.0.0",

"devDependencies": {
"@open-wc/building-rollup": "^0.18.1",
"@size-limit/preset-big-lib": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.15.0",
"@typescript-eslint/parser": "^2.15.0",
"@webcomponents/webcomponentsjs": "^2.4.0",
"es-dev-server": "^1.36.1",
"@open-wc/building-rollup": "^0.22.4",
"@size-limit/preset-small-lib": "^4.3.1",
"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"es-dev-server": "^1.43.1",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-import": "^2.19.1",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-lit": "^1.2.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-wc": "^1.2.0",
"husky": "^4.0.3",
"lint-staged": "^9.5.0",
"lit-analyzer": "^1.1.9",
"husky": "^4.2.3",
"lint-staged": "^10.0.8",
"lit-analyzer": "^1.1.10",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.1",
"rimraf": "^3.0.0",
"rimraf": "^3.0.2",
"rollup": "^1.29.0",
"rollup-plugin-cpy": "^2.0.1",
"stylelint": "^12.0.1",
"stylelint-config-prettier": "^8.0.0",
"stylelint-config-standard": "^19.0.0",
"size-limit": "^4.3.1",
"stylelint": "^13.2.1",
"stylelint-config-prettier": "^8.0.1",
"stylelint-config-standard": "^20.0.0",
"stylelint-config-styled-components": "^0.1.1",
"stylelint-processor-styled-components": "1.9.0",
"tsc-watch": "^4.0.0",
"typescript": "^3.7.4",
"web-component-analyzer": "^1.0.1"
"stylelint-processor-styled-components": "1.10.0",
"tsc-watch": "^4.2.3",
"typescript": "^3.8.3",
"web-component-analyzer": "^1.0.3"
},

@@ -79,4 +79,3 @@ "husky": {

"eslint --fix",
"prettier --write",
"git add"
"prettier --write"
]

@@ -87,5 +86,5 @@ },

"path": "lib/api-viewer.js",
"limit": "32.5 KB"
"limit": "34 KB"
}
]
}

@@ -76,2 +76,30 @@ # &lt;api-viewer&gt;

## Usage options
Starting from `0.4.0` release, the following components are available:
### `<api-viewer>` element
A custom element that provides both API docs and live playground.
```js
import 'api-viewer-element';
```
### `<api-docs>` element
A custom element that only provides API docs (no live playground).
```js
import 'api-viewer-element/lib/api-docs.js';
```
### `<api-demo>` element
A custom element that only provides live playground (no API docs).
```js
import 'api-viewer-element/lib/api-demo.js';
```
## Playground

@@ -147,2 +175,43 @@

#### `exclude-knobs`
Use `exclude-knobs` attribute to exclude properties from demo:
```html
<api-viewer src="./custom-elements.json" exclude-knobs="autofocus,items"></api-viewer>
```
#### `elements`
Use `elements` property instead of `src` to pass data directly:
```html
<api-viewer></api-viewer>
<script>
fetch('./custom-elements.json')
.then(res => res.json())
.then(data => {
document.querySelector('api-viewer').elements = data.tags;
});
</script>
```
### Methods
#### `setTemplates`
Use `setTemplates` method to override `<template>` elements:
```js
// gather the template elements placed in the DOM
const templates = document.querySelectorAll('template[data-target]');
// configure the api-viewer to use the templates
document.querySelector('api-viewer').setTemplates(templates);
```
*Note*: the method is available on `api-viewer` and `api-demo` elements only. Corresponding base
classes do not have it. When extending a base class, you have to re-implement it yourself if you
need to preserve the existing behavior.
### Templates

@@ -218,2 +287,3 @@

| `header` | Header containing element name and navigation controls |
| `header-title` | Title element placed in the header (element tag name) |
| `tab` | `<api-viewer-tab>` component used in docs and demo |

@@ -234,2 +304,19 @@ | `tab-panel` | `<api-viewer-panel>` component used in docs and demo |

| `docs-value` | Sibling of a `docs-label` part (name, attribute, type) |
| `md-h1` | Markdown `<h1>` elements |
| `md-h2` | Markdown `<h2>` elements |
| `md-h3` | Markdown `<h3>` elements |
| `md-h4` | Markdown `<h4>` elements |
| `md-h5` | Markdown `<h5>` elements |
| `md-h6` | Markdown `<h6>` elements |
| `md-a` | Markdown `<a>` elements |
| `md-p` | Markdown `<p>` elements |
| `md-ul` | Markdown `<ul>` elements |
| `md-ol` | Markdown `<ol>` elements |
| `md-li` | Markdown `<li>` elements |
| `md-pre` | Markdown `<pre>` elements |
| `md-code` | Markdown `<code>` elements |
| `md-strong` | Markdown `<strong>` elements |
| `md-em` | Markdown `<em>` elements |
| `md-blockquote` | Markdown `<blockquote>` elements |
| `md-del` | Markdown `<del>` elements |

@@ -236,0 +323,0 @@ #### Live demo

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc