(() => { const isDev = location.hostname === 'localhost'; const isNext = location.hostname === 'next.shoelace.style'; const customElements = fetch('/dist/custom-elements.json') .then(res => res.json()) .catch(err => console.error(err)); function createPropsTable(props) { const table = document.createElement('table'); table.innerHTML = ` Name Description Reflects Type Default ${props .map(prop => { const hasAttribute = !!prop.attribute; const isAttributeDifferent = prop.attribute !== prop.name; let attributeInfo = ''; if (!hasAttribute) { attributeInfo = `
(property only)`; } else if (isAttributeDifferent) { attributeInfo = `
${escapeHtml(prop.attribute)} `; } return ` ${escapeHtml(prop.name)} ${attributeInfo} ${escapeHtml(prop.description)} ${ prop.reflects ? '' : '' } ${prop.type?.text ? `${escapeHtml(prop.type?.text || '')}` : '-'} ${prop.default ? `${escapeHtml(prop.default)}` : '-'} `; }) .join('')} `; return table.outerHTML; } function createEventsTable(events) { const table = document.createElement('table'); table.innerHTML = ` Name Description Event Detail ${events .map( event => ` ${escapeHtml(event.name)} ${escapeHtml(event.description)} ${event.type?.text ? `${escapeHtml(event.type?.text)}` : '-'} ` ) .join('')} `; return table.outerHTML; } function createMethodsTable(methods) { const table = document.createElement('table'); table.innerHTML = ` Name Description Arguments ${methods .map( method => ` ${escapeHtml(method.name)} ${escapeHtml(method.description)} ${ method.parameters?.length ? ` ${escapeHtml( method.parameters.map(param => `${param.name}: ${param.type.text}`).join(', ') )} ` : '-' } ` ) .join('')} `; return table.outerHTML; } function createSlotsTable(slots) { const table = document.createElement('table'); table.innerHTML = ` Name Description ${slots .map( slot => ` ${slot.name ? `${escapeHtml(slot.name)}` : '(default)'} ${escapeHtml(slot.description)} ` ) .join('')} `; return table.outerHTML; } function createCustomPropertiesTable(styles) { const table = document.createElement('table'); table.innerHTML = ` Name Description ${styles .map( style => ` ${escapeHtml(style.name)} ${escapeHtml(style.description)} ` ) .join('')} `; return table.outerHTML; } function createPartsTable(parts) { const table = document.createElement('table'); table.innerHTML = ` Name Description ${parts .map( part => ` ${escapeHtml(part.name)} ${escapeHtml(part.description)} ` ) .join('')} `; return table.outerHTML; } function createAnimationsTable(animations) { const table = document.createElement('table'); table.innerHTML = ` Name Description ${animations .map( animation => ` ${escapeHtml(animation.name)} ${escapeHtml(animation.description)} ` ) .join('')} `; return table.outerHTML; } function createDependenciesList(targetComponent, allComponents) { const ul = document.createElement('ul'); const dependencies = []; // Recursively fetch subdependencies function getDependencies(tag) { const component = allComponents.find(c => c.tagName === tag); if (!component || !Array.isArray(component.dependencies)) { return []; } component.dependencies?.map(tag => { if (!dependencies.includes(tag)) { dependencies.push(tag); } getDependencies(tag); }); } getDependencies(targetComponent); dependencies.sort().map(tag => { const li = document.createElement('li'); li.innerHTML = `<${tag}>`; ul.appendChild(li); }); return ul.outerHTML; } function escapeHtml(html) { return (html + '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/`(.*?)`/g, '$1'); } function getAllComponents(metadata) { const allComponents = []; metadata.modules?.map(module => { module.declarations?.map(declaration => { if (declaration.customElement) { // Generate the dist path based on the src path and attach it to the component declaration.path = module.path.replace(/^src\//, 'dist/').replace(/\.ts$/, '.js'); allComponents.push(declaration); } }); }); return allComponents; } function getComponent(metadata, tagName) { return getAllComponents(metadata).find(component => component.tagName === tagName); } if (!window.$docsify) { throw new Error('Docsify must be loaded before installing this plugin.'); } window.$docsify.plugins.push((hook, vm) => { hook.mounted(async function () { const metadata = await customElements; const target = document.querySelector('.app-name'); // Add version const version = document.createElement('div'); version.classList.add('sidebar-version'); version.textContent = isDev ? 'Development' : isNext ? 'Next' : metadata.package.version; target.appendChild(version); // Store version for reuse sessionStorage.setItem('sl-version', metadata.package.version); // Add repo buttons const buttons = document.createElement('div'); buttons.classList.add('sidebar-buttons'); buttons.innerHTML = ` Sponsor Star Follow `; target.appendChild(buttons); }); hook.beforeEach(async function (content, next) { const metadata = await customElements; // Replace %VERSION% placeholders content = content.replace(/%VERSION%/g, metadata.package.version); // Handle [component-header] tags content = content.replace(/\[component-header:([a-z-]+)\]/g, (match, tag) => { const component = getComponent(metadata, tag); let result = ''; if (!component) { console.error('Component not found in metadata: ' + tag); return next(content); } let badgeType = 'neutral'; if (component.status === 'stable') badgeType = 'primary'; if (component.status === 'experimental') badgeType = 'warning'; if (component.status === 'planned') badgeType = 'neutral'; if (component.status === 'deprecated') badgeType = 'danger'; result += `
<${component.tagName}> | ${component.name}
Since ${component.since || '?'} ${component.status}
`; return result.replace(/^ +| +$/gm, ''); }); // Handle [component-metadata] tags content = content.replace(/\[component-metadata:([a-z-]+)\]/g, (match, tag) => { const component = getComponent(metadata, tag); let result = ''; if (!component) { console.error('Component not found in metadata: ' + tag); return next(content); } // Remove members that are private or don't have a description const members = component.members?.filter(member => member.description && member.privacy !== 'private'); const methods = members?.filter(prop => prop.kind === 'method' && prop.privacy !== 'private'); const props = members?.filter(prop => { // Look for a corresponding attribute const attribute = component.attributes?.find(attr => attr.fieldName === prop.name); if (attribute) { prop.attribute = attribute.name || attribute.fieldName; } return prop.kind === 'field' && prop.privacy !== 'private'; }); if (component.path) { /* prettier-ignore */ result += ` ## Importing CDN Bundler React \n To cherry pick this component from [the CDN](https://www.jsdelivr.com/package/npm/@shoelace-style/shoelace): \`\`\`js import 'https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@${metadata.package.version}/${component.path}'; \`\`\` \n To import this component using [a bundler](/getting-started/installation#bundling): \`\`\`js import '@shoelace-style/shoelace/${component.path}'; \`\`\` \n To import this component as a [React component](/frameworks/react): \`\`\`js import { ${component.name} } from '@shoelace-style/shoelace/dist/react'; \`\`\` `; } if (props?.length) { result += ` ## Properties ${createPropsTable(props)} `; } if (component.events?.length) { result += ` ## Events ${createEventsTable(component.events)} `; } if (methods?.length) { result += ` ## Methods ${createMethodsTable(methods)} `; } if (component.slots?.length) { result += ` ## Slots ${createSlotsTable(component.slots)} `; } if (component.cssProperties?.length) { result += ` ## CSS Custom Properties ${createCustomPropertiesTable(component.cssProperties)} `; } if (component.cssParts?.length) { result += ` ## CSS Parts ${createPartsTable(component.cssParts)} `; } if (component.animations?.length) { result += ` ## Animations ${createAnimationsTable(component.animations)} Learn how to [customize animations](/getting-started/customizing#animations). `; } if (component.dependencies?.length) { result += ` ## Dependencies This component imports the following dependencies. ${createDependenciesList(component.tagName, getAllComponents(metadata))} `; } // Strip whitespace so markdown doesn't process things as code blocks return result.replace(/^ +| +$/gm, ''); }); next(content); }); // Wrap tables so we can scroll them horizontally when needed hook.doneEach(function () { const content = document.querySelector('.content'); const tables = [...content.querySelectorAll('table')]; tables.map(table => { table.outerHTML = `
${table.outerHTML}
`; }); }); }); })();