From 64c8647deebe707f759ec6e1f462e2eb154a2afa Mon Sep 17 00:00:00 2001 From: Konnor Rogers Date: Wed, 16 Jul 2025 17:37:47 -0400 Subject: [PATCH 1/3] build speed improvements and add optional incremental builds (#1183) * build speed improvments * fix button * prettier * move things to plugins / transformers * final fixes * build fixes * fix build issues * add comment * build sstuf * prettier * fix badge.css * fix build stuff * fix eleventy stuff * prettier --- package-lock.json | 2 +- packages/webawesome/docs/.eleventy.js | 154 ++++++----- .../docs/{_utils => _plugins}/replace-text.js | 0 .../docs/{_utils => _plugins}/search.js | 37 ++- .../docs/_transformers/anchor-headings.js | 81 ++++++ .../code-examples.js | 55 ++-- .../docs/_transformers/copy-code.js | 38 +++ .../{_utils => _transformers}/current-link.js | 27 +- .../{_utils => _transformers}/format-code.js | 0 .../highlight-code.js | 37 ++- .../webawesome/docs/_transformers/outline.js | 64 +++++ .../webawesome/docs/_utils/anchor-headings.js | 85 ------ packages/webawesome/docs/_utils/copy-code.js | 40 --- packages/webawesome/docs/_utils/outline.js | 69 ----- .../docs/_utils/simulate-webawesome-app.js | 18 ++ .../webawesome/docs/docs/components/button.md | 2 +- .../docs/docs/components/copy-button.md | 3 +- packages/webawesome/scripts/build.js | 126 +++++---- packages/webawesome/scripts/docs.js | 261 +++++++++++++++++- 19 files changed, 703 insertions(+), 396 deletions(-) rename packages/webawesome/docs/{_utils => _plugins}/replace-text.js (100%) rename packages/webawesome/docs/{_utils => _plugins}/search.js (63%) create mode 100644 packages/webawesome/docs/_transformers/anchor-headings.js rename packages/webawesome/docs/{_utils => _transformers}/code-examples.js (55%) create mode 100644 packages/webawesome/docs/_transformers/copy-code.js rename packages/webawesome/docs/{_utils => _transformers}/current-link.js (52%) rename packages/webawesome/docs/{_utils => _transformers}/format-code.js (100%) rename packages/webawesome/docs/{_utils => _transformers}/highlight-code.js (60%) create mode 100644 packages/webawesome/docs/_transformers/outline.js delete mode 100644 packages/webawesome/docs/_utils/anchor-headings.js delete mode 100644 packages/webawesome/docs/_utils/copy-code.js delete mode 100644 packages/webawesome/docs/_utils/outline.js create mode 100644 packages/webawesome/docs/_utils/simulate-webawesome-app.js diff --git a/package-lock.json b/package-lock.json index d318953cc..f4b5f79cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13995,7 +13995,7 @@ }, "packages/webawesome-pro": { "name": "@shoelace-style/webawesome-pro", - "version": "3.0.0-beta.1", + "version": "3.0.0-beta.2", "license": "TODO", "dependencies": { "@ctrl/tinycolor": "^4.1.0", diff --git a/packages/webawesome/docs/.eleventy.js b/packages/webawesome/docs/.eleventy.js index 0b79aea50..0f92054ce 100644 --- a/packages/webawesome/docs/.eleventy.js +++ b/packages/webawesome/docs/.eleventy.js @@ -1,30 +1,63 @@ +import { parse as HTMLParse } from 'node-html-parser'; import * as fs from 'node:fs'; import * as path from 'node:path'; -import { parse } from 'path'; -import { anchorHeadingsPlugin } from './_utils/anchor-headings.js'; -import { codeExamplesPlugin } from './_utils/code-examples.js'; -import { copyCodePlugin } from './_utils/copy-code.js'; -import { currentLink } from './_utils/current-link.js'; -import { highlightCodePlugin } from './_utils/highlight-code.js'; +import { anchorHeadingsTransformer } from './_transformers/anchor-headings.js'; +import { codeExamplesTransformer } from './_transformers/code-examples.js'; +import { copyCodeTransformer } from './_transformers/copy-code.js'; +import { currentLinkTransformer } from './_transformers/current-link.js'; +import { highlightCodeTransformer } from './_transformers/highlight-code.js'; +import { outlineTransformer } from './_transformers/outline.js'; import { getComponents } from './_utils/manifest.js'; import { markdown } from './_utils/markdown.js'; -// import { formatCodePlugin } from './_utils/format-code.js'; +import { SimulateWebAwesomeApp } from './_utils/simulate-webawesome-app.js'; +// import { formatCodePlugin } from './_plugins/format-code.js'; // import litPlugin from '@lit-labs/eleventy-plugin-lit'; import { readFile } from 'fs/promises'; -import nunjucks from 'nunjucks'; import process from 'process'; import * as url from 'url'; -import { outlinePlugin } from './_utils/outline.js'; -import { replaceTextPlugin } from './_utils/replace-text.js'; -import { searchPlugin } from './_utils/search.js'; +import { replaceTextPlugin } from './_plugins/replace-text.js'; +import { searchPlugin } from './_plugins/search.js'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); const isDev = process.argv.includes('--develop'); const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4']; +async function getPackageData() { + return JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8')); +} + export default async function (eleventyConfig) { - const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8')); const docsDir = path.join(process.env.BASE_DIR || '.', 'docs'); - const allComponents = getComponents(); + let packageData = await getPackageData(); + let allComponents = getComponents(); + + const distDir = process.env.UNBUNDLED_DIST_DIRECTORY || path.resolve(__dirname, '../dist'); + const customElementsManifest = path.join(distDir, 'custom-elements.json'); + const stylesheets = path.join(distDir, 'styles'); + + eleventyConfig.addWatchTarget(customElementsManifest); + eleventyConfig.setWatchThrottleWaitTime(10); // in milliseconds + + eleventyConfig.on('eleventy.beforeWatch', async function (changedFiles) { + let updatePackageData = false; + let updateComponentData = false; + changedFiles.forEach(file => { + if (file.includes('package.json')) { + updatePackageData = true; + } + + if (file.includes('custom-elements.json')) { + updateComponentData = true; + } + }); + + if (updatePackageData) { + packageData = await getPackageData(); + } + + if (updateComponentData) { + allComponents = getComponents(); + } + }); /** * If you plan to add or remove any of these extensions, make sure to let either Konnor or Cory know as these @@ -52,7 +85,7 @@ export default async function (eleventyConfig) { // Template filters - {{ content | filter }} eleventyConfig.addFilter('inlineMarkdown', content => markdown.renderInline(content || '')); eleventyConfig.addFilter('markdown', content => markdown.render(content || '')); - eleventyConfig.addFilter('stripExtension', string => parse(string + '').name); + eleventyConfig.addFilter('stripExtension', string => path.parse(string + '').name); eleventyConfig.addFilter('stripPrefix', content => content.replace(/^wa-/, '')); // Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited. // With Prettier 3, this means a leading pipe will exist be present when the line wraps. @@ -109,31 +142,6 @@ export default async function (eleventyConfig) { return ''; }); - eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) { - // For a server build, we expect a server to run the second transform. - if (serverBuild) { - return content; - } - - // Only run the transform on files nunjucks would transform. - if (!this.page.inputPath.match(/.(md|html|njk)$/)) { - return content; - } - - /** This largely mimics what an app would do and just stubs out what we don't care about. */ - return nunjucks.renderString(content, { - // Stub the server EJS shortcodes. - currentUser: { - hasPro: false, - }, - server: { - head: '', - loginOrAvatar: '', - flashes: '', - }, - }); - }); - // Paired shortcodes - {% shortCode %}content{% endShortCode %} eleventyConfig.addPairedShortcode('markdown', content => markdown.render(content || '')); @@ -152,33 +160,33 @@ export default async function (eleventyConfig) { eleventyConfig.setLibrary('md', markdown); // Add anchors to headings - eleventyConfig.addPlugin(anchorHeadingsPlugin({ container: '#content' })); + eleventyConfig.addTransform('doc-transforms', function (content) { + let doc = HTMLParse(content, { blockTextElements: { code: true } }); - // Add an outline to the page - eleventyConfig.addPlugin( - outlinePlugin({ - container: '#content', - target: '.outline-links', - selector: 'h2, h3', - ifEmpty: doc => { - doc.querySelector('#outline')?.remove(); - }, - }), - ); + const transformers = [ + anchorHeadingsTransformer({ container: '#content' }), + outlineTransformer({ + container: '#content', + target: '.outline-links', + selector: 'h2, h3', + ifEmpty: doc => { + doc.querySelector('#outline')?.remove(); + }, + }), + // Add current link classes + currentLinkTransformer(), + codeExamplesTransformer(), + highlightCodeTransformer(), + copyCodeTransformer(), + ]; - // Add current link classes - eleventyConfig.addPlugin(currentLink()); + for (const transformer of transformers) { + transformer.call(this, doc); + } - // Add code examples for `` blocks - eleventyConfig.addPlugin(codeExamplesPlugin()); + return doc.toString(); + }); - // Highlight code blocks with Prism - eleventyConfig.addPlugin(highlightCodePlugin()); - - // Add copy code buttons to code blocks - eleventyConfig.addPlugin(copyCodePlugin); - - // Various text replacements eleventyConfig.addPlugin( replaceTextPlugin([ { @@ -222,15 +230,14 @@ export default async function (eleventyConfig) { // } let assetsDir = path.join(process.env.BASE_DIR || 'docs', 'assets'); - fs.cpSync(assetsDir, path.join(eleventyConfig.directories.output, 'assets'), { recursive: true }); + const siteAssetsDir = path.join(eleventyConfig.directories.output, 'assets'); + fs.cpSync(assetsDir, siteAssetsDir, { recursive: true }); for (let glob of passThrough) { eleventyConfig.addPassthroughCopy(glob); } // // SSR plugin - // // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with - // // the nunjucks 2nd transform. // if (!isDev) { // // // // Problematic components in SSR land: @@ -253,6 +260,23 @@ export default async function (eleventyConfig) { // componentModules, // }); // } + + if (!isDev) { + eleventyConfig.addTransform('simulate-webawesome-app', function (content) { + // For a server build, we expect a server to run the second transform. + if (serverBuild) { + return content; + } + + // Only run the transform on files nunjucks would transform. + if (!this.page.inputPath.match(/.(md|html|njk)$/)) { + return content; + } + + /** This largely mimics what an app would do and just stubs out what we don't care about. */ + return SimulateWebAwesomeApp(content); + }); + } } export const config = { diff --git a/packages/webawesome/docs/_utils/replace-text.js b/packages/webawesome/docs/_plugins/replace-text.js similarity index 100% rename from packages/webawesome/docs/_utils/replace-text.js rename to packages/webawesome/docs/_plugins/replace-text.js diff --git a/packages/webawesome/docs/_utils/search.js b/packages/webawesome/docs/_plugins/search.js similarity index 63% rename from packages/webawesome/docs/_utils/search.js rename to packages/webawesome/docs/_plugins/search.js index 90d06c40e..cf3eaf908 100644 --- a/packages/webawesome/docs/_utils/search.js +++ b/packages/webawesome/docs/_plugins/search.js @@ -1,4 +1,5 @@ /* eslint-disable no-invalid-this */ +import { readFileSync } from 'fs'; import { mkdir, writeFile } from 'fs/promises'; import lunr from 'lunr'; import { parse } from 'node-html-parser'; @@ -23,19 +24,22 @@ export function searchPlugin(options = {}) { ...options, }; + // Hoist above so that it can "cache" properly for incremental builds. return function (eleventyConfig) { - const pagesToIndex = new Map(); + let pagesToIndex = new Map(); eleventyConfig.addPreprocessor('exclude-unlisted-from-search', '*', function (data, content) { if (data.unlisted) { // no-op + pagesToIndex.delete(data.page.inputPath); } else { - pagesToIndex.set(data.page.inputPath, {}); + pagesToIndex.set(data.page.inputPath, true); } return content; }); + // With incremental builds we need this to be last in case stuff was added from metadata. _BUT_ in incremental builds, not every page is added to the "transform". eleventyConfig.addTransform('search', function (content) { if (!pagesToIndex.has(this.page.inputPath)) { return content; @@ -67,11 +71,30 @@ export function searchPlugin(options = {}) { return content; }); - eleventyConfig.on('eleventy.after', ({ directories }) => { + eleventyConfig.on('eleventy.after', async ({ directories }) => { const { output } = directories; const outputFilename = path.resolve(join(output, 'search.json')); + const cachedPages = path.resolve(join(output, 'cached_pages.json')); + + function getCachedPages() { + let content = { pages: [] }; + try { + content = JSON.parse(readFileSync(cachedPages)); + } catch (e) {} + + const cachedPagesMap = new Map(content.pages); + for (const [key, value] of cachedPagesMap.entries()) { + // A page uses a cached value if `true` and it didnt get its value set in the "transform" hook. This is to get around the limitation of incremental builds not going over every file in transform. + if (pagesToIndex.get(key) === true) { + pagesToIndex.set(key, value); + } + } + } + const map = []; - const searchIndex = lunr(async function () { + + getCachedPages(); + const searchIndex = lunr(function () { let index = 0; this.ref('id'); @@ -84,9 +107,11 @@ export function searchPlugin(options = {}) { map[index] = { title: page.title, description: page.description, url: page.url }; index++; } - await mkdir(dirname(outputFilename), { recursive: true }); - await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8'); }); + + await mkdir(dirname(outputFilename), { recursive: true }); + await writeFile(outputFilename, JSON.stringify({ searchIndex, map }), 'utf-8'); + await writeFile(cachedPages, JSON.stringify({ pages: [...pagesToIndex.entries()] }, null, 2)); }); }; } diff --git a/packages/webawesome/docs/_transformers/anchor-headings.js b/packages/webawesome/docs/_transformers/anchor-headings.js new file mode 100644 index 000000000..12b150ff7 --- /dev/null +++ b/packages/webawesome/docs/_transformers/anchor-headings.js @@ -0,0 +1,81 @@ +import { parse } from 'node-html-parser'; +import slugify from 'slugify'; +import { v4 as uuid } from 'uuid'; + +function createId(text) { + let slug = slugify(String(text), { + remove: /[^\w|\s]/g, + lower: true, + }); + + // ids must start with a letter + if (!/^[a-z]/i.test(slug)) { + slug = `wa_${slug}`; + } + + return slug; +} + +/** + * Eleventy plugin to add anchors to headings to content. + */ +export function anchorHeadingsTransformer(options = {}) { + options = { + container: 'body', + headingSelector: 'h2, h3, h4, h5, h6', + anchorLabel: 'Jump to heading', + ...options, + }; + + /** doc is a parsed HTML document */ + return function (doc) { + const container = doc.querySelector(options.container); + + if (!container) { + return doc; + } + + // Look for headings + let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`; + container.querySelectorAll(selector).forEach(heading => { + const hasAnchor = heading.querySelector('a'); + const existingId = heading.getAttribute('id'); + const clone = parse(heading.outerHTML); + + // Create a clone of the heading so we can remove [data-no-anchor] elements from the text content + clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove()); + + if (hasAnchor) { + return; + } + + let id = existingId; + if (!id) { + const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12); + id = slug; + let suffix = 1; + + // Make sure the slug is unique in the document + while (doc.getElementById(id) !== null) { + id = `${slug}-${++suffix}`; + } + } + + // Create the anchor + const anchor = parse(` + + + + + `); + anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel; + + // Update the heading + if (!existingId) { + heading.setAttribute('id', id); + } + heading.classList.add('anchor-heading'); + heading.appendChild(anchor); + }); + }; +} diff --git a/packages/webawesome/docs/_utils/code-examples.js b/packages/webawesome/docs/_transformers/code-examples.js similarity index 55% rename from packages/webawesome/docs/_utils/code-examples.js rename to packages/webawesome/docs/_transformers/code-examples.js index 84b2d28d4..3b4849801 100644 --- a/packages/webawesome/docs/_utils/code-examples.js +++ b/packages/webawesome/docs/_transformers/code-examples.js @@ -1,40 +1,46 @@ import { parse } from 'node-html-parser'; import { v4 as uuid } from 'uuid'; -import { markdown } from '../_utils/markdown.js'; +import { copyCode } from './copy-code.js'; +import { highlightCode } from './highlight-code.js'; /** * Eleventy plugin to turn `` blocks into live examples. */ -export function codeExamplesPlugin(options = {}) { +export function codeExamplesTransformer(options = {}) { options = { container: 'body', ...options, }; - return function (eleventyConfig) { - eleventyConfig.addTransform('code-examples', content => { - const doc = parse(content, { blockTextElements: { code: true } }); - const container = doc.querySelector(options.container); + return function (doc) { + const container = doc.querySelector(options.container); - if (!container) { - return content; - } + if (!container) { + return; + } - // Look for external links - container.querySelectorAll('code.example').forEach(code => { - const pre = code.closest('pre'); - const hasButtons = !code.classList.contains('no-buttons'); - const isOpen = code.classList.contains('open') || !hasButtons; - const noEdit = code.classList.contains('no-edit'); - const id = `code-example-${uuid().slice(-12)}`; - let preview = pre.textContent; + // Look for external links + container.querySelectorAll('code.example').forEach(code => { + let pre = code.closest('pre'); + const hasButtons = !code.classList.contains('no-buttons'); + const isOpen = code.classList.contains('open') || !hasButtons; + const noEdit = code.classList.contains('no-edit'); + const id = `code-example-${uuid().slice(-12)}`; + let preview = pre.textContent; - // Run preview scripts as modules to prevent collisions - const root = parse(preview, { blockTextElements: { script: true } }); - root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module')); - preview = root.toString(); + const langClass = [...code.classList.values()].find(val => val.startsWith('language-')); + const lang = langClass ? langClass.replace(/^language-/, '') : 'plain'; - const codeExample = parse(` + code.innerHTML = highlightCode(code.textContent ?? '', lang); + + // Run preview scripts as modules to prevent collisions + const root = parse(preview, { blockTextElements: { script: true } }); + root.querySelectorAll('script').forEach(script => script.setAttribute('type', 'module')); + preview = root.toString(); + + copyCode(code); + + const codeExample = parse(`
@@ -79,10 +85,7 @@ export function codeExamplesPlugin(options = {}) {
`); - pre.replaceWith(codeExample); - }); - - return doc.toString(); + pre.replaceWith(codeExample); }); }; } diff --git a/packages/webawesome/docs/_transformers/copy-code.js b/packages/webawesome/docs/_transformers/copy-code.js new file mode 100644 index 000000000..83664818c --- /dev/null +++ b/packages/webawesome/docs/_transformers/copy-code.js @@ -0,0 +1,38 @@ +export function copyCode(code) { + const pre = code.closest('pre'); + let preId = pre.getAttribute('id') || `code-block-${crypto.randomUUID()}`; + let codeId = code.getAttribute('id') || `${preId}-inner`; + + if (!code.getAttribute('id')) { + code.setAttribute('id', codeId); + } + if (!pre.getAttribute('id')) { + pre.setAttribute('id', preId); + } + + // Add a copy button + pre.innerHTML += ``; +} + +/** + * Eleventy plugin to add copy buttons to code blocks. + */ +export function copyCodeTransformer(options = {}) { + options = { + container: 'body', + ...options, + }; + + return function (doc) { + const container = doc.querySelector(options.container); + + if (!container) { + return; + } + + // Look for code blocks + container.querySelectorAll('pre > code').forEach(code => { + copyCode(code); + }); + }; +} diff --git a/packages/webawesome/docs/_utils/current-link.js b/packages/webawesome/docs/_transformers/current-link.js similarity index 52% rename from packages/webawesome/docs/_utils/current-link.js rename to packages/webawesome/docs/_transformers/current-link.js index d1de10324..e7f7af0ad 100644 --- a/packages/webawesome/docs/_utils/current-link.js +++ b/packages/webawesome/docs/_transformers/current-link.js @@ -24,30 +24,25 @@ function normalize(pathname) { /** * Eleventy plugin to decorate current links with a custom class. */ -export function currentLink(options = {}) { +export function currentLinkTransformer(options = {}) { options = { container: 'body', className: 'current', ...options, }; - return function (eleventyConfig) { - eleventyConfig.addTransform('current-link', function (content) { - const doc = parse(content); - const container = doc.querySelector(options.container); + return function (doc) { + const container = doc.querySelector(options.container); - if (!container) { - return content; + if (!container) { + return; + } + + // Compare the href attribute to 11ty's page URL + container.querySelectorAll('a[href]').forEach(a => { + if (normalize(a.getAttribute('href')) === normalize(this.page.url)) { + a.classList.add(options.className); } - - // Compare the href attribute to 11ty's page URL - container.querySelectorAll('a[href]').forEach(a => { - if (normalize(a.getAttribute('href')) === normalize(this.page.url)) { - a.classList.add(options.className); - } - }); - - return doc.toString(); }); }; } diff --git a/packages/webawesome/docs/_utils/format-code.js b/packages/webawesome/docs/_transformers/format-code.js similarity index 100% rename from packages/webawesome/docs/_utils/format-code.js rename to packages/webawesome/docs/_transformers/format-code.js diff --git a/packages/webawesome/docs/_utils/highlight-code.js b/packages/webawesome/docs/_transformers/highlight-code.js similarity index 60% rename from packages/webawesome/docs/_utils/highlight-code.js rename to packages/webawesome/docs/_transformers/highlight-code.js index 79def428a..3774c8452 100644 --- a/packages/webawesome/docs/_utils/highlight-code.js +++ b/packages/webawesome/docs/_transformers/highlight-code.js @@ -37,36 +37,31 @@ export function highlightCode(code, language = 'plain') { * Eleventy plugin to highlight code blocks with the `language-*` attribute using Prism.js. Unlike most plugins, this * works on the entire document — not just markdown content. */ -export function highlightCodePlugin(options = {}) { +export function highlightCodeTransformer(options = {}) { options = { container: 'body', ...options, }; - return function (eleventyConfig) { - eleventyConfig.addTransform('highlight-code', content => { - const doc = parse(content, { blockTextElements: { code: true } }); - const container = doc.querySelector(options.container); + return function (doc) { + const container = doc.querySelector(options.container); - if (!container) { - return content; - } + if (!container) { + return; + } - // Look for and highlight each one - container.querySelectorAll('code[class*="language-"]').forEach(code => { - const langClass = [...code.classList.values()].find(val => val.startsWith('language-')); - const lang = langClass ? langClass.replace(/^language-/, '') : 'plain'; + // Look for and highlight each one + container.querySelectorAll('code[class*="language-"]').forEach(code => { + const langClass = [...code.classList.values()].find(val => val.startsWith('language-')); + const lang = langClass ? langClass.replace(/^language-/, '') : 'plain'; - try { - code.innerHTML = highlightCode(code.textContent ?? '', lang); - } catch (err) { - if (!options.ignoreMissingLangs) { - throw new Error(err.message); - } + try { + code.innerHTML = highlightCode(code.textContent ?? '', lang); + } catch (err) { + if (!options.ignoreMissingLangs) { + throw new Error(err.message); } - }); - - return doc.toString(); + } }); }; } diff --git a/packages/webawesome/docs/_transformers/outline.js b/packages/webawesome/docs/_transformers/outline.js new file mode 100644 index 000000000..8632cc45b --- /dev/null +++ b/packages/webawesome/docs/_transformers/outline.js @@ -0,0 +1,64 @@ +import { parse } from 'node-html-parser'; + +/** + * Eleventy plugin to add an outline (table of contents) to the page. Headings must have an id, otherwise they won't be + * included in the outline. An unordered list containing links will be appended to the target element. + * + * If no headings are found for the outline, the `ifEmpty()` function will be called with a `node-html-parser` object as + * the first argument. This can be used to toggle classes or remove elements when the outline is empty. + * + * See the `node-html-parser` docs for more details: https://www.npmjs.com/package/node-html-parser + */ +export function outlineTransformer(options = {}) { + options = { + container: 'body', + target: '.outline', + selector: 'h2,h3', + ifEmpty: () => null, + ...options, + }; + + return function (doc) { + const container = doc.querySelector(options.container); + const ul = parse('
    '); + let numLinks = 0; + + if (!container) { + return; + } + + container.querySelectorAll(options.selector).forEach(heading => { + const id = heading.getAttribute('id'); + const level = heading.tagName.slice(1); + const clone = parse(heading.outerHTML); + + if (heading.closest('[data-no-outline]')) { + return; + } + + // Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content + clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove()); + clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove()); + + // Generate the link + const li = parse(`
  • `); + const a = li.querySelector('a'); + a.setAttribute('href', `#${encodeURIComponent(id)}`); + a.textContent = clone.textContent.trim().replace(/#$/, ''); + + // Add it to the list + ul.firstChild.appendChild(li); + numLinks++; + }); + + if (numLinks > 0) { + // Append the list to all matching targets + doc.querySelectorAll(options.target).forEach(target => { + target.appendChild(parse(ul.outerHTML)); + }); + } else { + // Remove if empty + options.ifEmpty(doc); + } + }; +} diff --git a/packages/webawesome/docs/_utils/anchor-headings.js b/packages/webawesome/docs/_utils/anchor-headings.js deleted file mode 100644 index 5fac0cc20..000000000 --- a/packages/webawesome/docs/_utils/anchor-headings.js +++ /dev/null @@ -1,85 +0,0 @@ -import { parse } from 'node-html-parser'; -import slugify from 'slugify'; -import { v4 as uuid } from 'uuid'; - -function createId(text) { - let slug = slugify(String(text), { - remove: /[^\w|\s]/g, - lower: true, - }); - - // ids must start with a letter - if (!/^[a-z]/i.test(slug)) { - slug = `wa_${slug}`; - } - - return slug; -} - -/** - * Eleventy plugin to add anchors to headings to content. - */ -export function anchorHeadingsPlugin(options = {}) { - options = { - container: 'body', - headingSelector: 'h2, h3, h4, h5, h6', - anchorLabel: 'Jump to heading', - ...options, - }; - - return function (eleventyConfig) { - eleventyConfig.addTransform('anchor-headings', content => { - const doc = parse(content); - const container = doc.querySelector(options.container); - - if (!container) { - return content; - } - - // Look for headings - let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`; - container.querySelectorAll(selector).forEach(heading => { - const hasAnchor = heading.querySelector('a'); - const existingId = heading.getAttribute('id'); - const clone = parse(heading.outerHTML); - - // Create a clone of the heading so we can remove [data-no-anchor] elements from the text content - clone.querySelectorAll('[data-no-anchor]').forEach(el => el.remove()); - - if (hasAnchor) { - return; - } - - let id = existingId; - if (!id) { - const slug = createId(clone.textContent ?? '') ?? uuid().slice(-12); - id = slug; - let suffix = 1; - - // Make sure the slug is unique in the document - while (doc.getElementById(id) !== null) { - id = `${slug}-${++suffix}`; - } - } - - // Create the anchor - const anchor = parse(` - - - - - `); - anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel; - - // Update the heading - if (!existingId) { - heading.setAttribute('id', id); - } - heading.classList.add('anchor-heading'); - heading.appendChild(anchor); - }); - - return doc.toString(); - }); - }; -} diff --git a/packages/webawesome/docs/_utils/copy-code.js b/packages/webawesome/docs/_utils/copy-code.js deleted file mode 100644 index d189387fa..000000000 --- a/packages/webawesome/docs/_utils/copy-code.js +++ /dev/null @@ -1,40 +0,0 @@ -import { parse } from 'node-html-parser'; - -/** - * Eleventy plugin to add copy buttons to code blocks. - */ -export function copyCodePlugin(eleventyConfig, options = {}) { - options = { - container: 'body', - ...options, - }; - - let codeCount = 0; - eleventyConfig.addTransform('copy-code', content => { - const doc = parse(content, { blockTextElements: { code: true } }); - const container = doc.querySelector(options.container); - - if (!container) { - return content; - } - - // Look for code blocks - container.querySelectorAll('pre > code').forEach(code => { - const pre = code.closest('pre'); - let preId = pre.getAttribute('id') || `code-block-${++codeCount}`; - let codeId = code.getAttribute('id') || `${preId}-inner`; - - if (!code.getAttribute('id')) { - code.setAttribute('id', codeId); - } - if (!pre.getAttribute('id')) { - pre.setAttribute('id', preId); - } - - // Add a copy button - pre.innerHTML += ``; - }); - - return doc.toString(); - }); -} diff --git a/packages/webawesome/docs/_utils/outline.js b/packages/webawesome/docs/_utils/outline.js deleted file mode 100644 index 026a59e00..000000000 --- a/packages/webawesome/docs/_utils/outline.js +++ /dev/null @@ -1,69 +0,0 @@ -import { parse } from 'node-html-parser'; - -/** - * Eleventy plugin to add an outline (table of contents) to the page. Headings must have an id, otherwise they won't be - * included in the outline. An unordered list containing links will be appended to the target element. - * - * If no headings are found for the outline, the `ifEmpty()` function will be called with a `node-html-parser` object as - * the first argument. This can be used to toggle classes or remove elements when the outline is empty. - * - * See the `node-html-parser` docs for more details: https://www.npmjs.com/package/node-html-parser - */ -export function outlinePlugin(options = {}) { - options = { - container: 'body', - target: '.outline', - selector: 'h2,h3', - ifEmpty: () => null, - ...options, - }; - - return function (eleventyConfig) { - eleventyConfig.addTransform('outline', content => { - const doc = parse(content); - const container = doc.querySelector(options.container); - const ul = parse('
      '); - let numLinks = 0; - - if (!container) { - return content; - } - - container.querySelectorAll(options.selector).forEach(heading => { - const id = heading.getAttribute('id'); - const level = heading.tagName.slice(1); - const clone = parse(heading.outerHTML); - - if (heading.closest('[data-no-outline]')) { - return; - } - - // Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content - clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove()); - clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove()); - - // Generate the link - const li = parse(`
    • `); - const a = li.querySelector('a'); - a.setAttribute('href', `#${encodeURIComponent(id)}`); - a.textContent = clone.textContent.trim().replace(/#$/, ''); - - // Add it to the list - ul.firstChild.appendChild(li); - numLinks++; - }); - - if (numLinks > 0) { - // Append the list to all matching targets - doc.querySelectorAll(options.target).forEach(target => { - target.appendChild(parse(ul.outerHTML)); - }); - } else { - // Remove if empty - options.ifEmpty(doc); - } - - return doc.toString(); - }); - }; -} diff --git a/packages/webawesome/docs/_utils/simulate-webawesome-app.js b/packages/webawesome/docs/_utils/simulate-webawesome-app.js new file mode 100644 index 000000000..d40cca18a --- /dev/null +++ b/packages/webawesome/docs/_utils/simulate-webawesome-app.js @@ -0,0 +1,18 @@ +import nunjucks from 'nunjucks'; + +/** + * This function simulates what a server would do running "on top" of eleventy. + */ +export function SimulateWebAwesomeApp(str) { + return nunjucks.renderString(str, { + // Stub the server EJS shortcodes. + currentUser: { + hasPro: false, + }, + server: { + head: '', + loginOrAvatar: '', + flashes: '', + }, + }); +} diff --git a/packages/webawesome/docs/docs/components/button.md b/packages/webawesome/docs/docs/components/button.md index fc2c2b7ed..83b88b48a 100644 --- a/packages/webawesome/docs/docs/components/button.md +++ b/packages/webawesome/docs/docs/components/button.md @@ -251,4 +251,4 @@ This example demonstrates how to style buttons using a custom class. This is the outline-offset: 4px; } -``` +``` \ No newline at end of file diff --git a/packages/webawesome/docs/docs/components/copy-button.md b/packages/webawesome/docs/docs/components/copy-button.md index bdc486973..286ad82fd 100644 --- a/packages/webawesome/docs/docs/components/copy-button.md +++ b/packages/webawesome/docs/docs/components/copy-button.md @@ -90,6 +90,7 @@ Copy buttons can be disabled by adding the `disabled` attribute. A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute. + ```html {.example} ``` @@ -134,4 +135,4 @@ You can customize the button to your liking with CSS. outline-offset: 4px; } -``` +``` \ No newline at end of file diff --git a/packages/webawesome/scripts/build.js b/packages/webawesome/scripts/build.js index 5ef2acec0..c5d8d9435 100644 --- a/packages/webawesome/scripts/build.js +++ b/packages/webawesome/scripts/build.js @@ -4,19 +4,20 @@ import { execSync } from 'child_process'; import { deleteAsync } from 'del'; import esbuild from 'esbuild'; import { replace } from 'esbuild-plugin-replace'; - import { mkdir, readFile } from 'fs/promises'; import getPort, { portNumbers } from 'get-port'; import { globby } from 'globby'; -import { dirname, join, posix, relative } from 'node:path'; +import { dirname, extname, join, posix, relative } from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; import ora from 'ora'; import copy from 'recursive-copy'; -import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir, runScript } from './utils.js'; +import { SimulateWebAwesomeApp } from '../docs/_utils/simulate-webawesome-app.js'; +import { generateDocs } from './docs.js'; +import { getCdnDir, getDistDir, getDocsDir, getRootDir, getSiteDir } from './utils.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const isDeveloping = process.argv.includes('--develop'); + const spinner = ora({ text: 'Web Awesome', color: 'cyan' }).start(); const getPackageData = async () => JSON.parse(await readFile(join(getRootDir(), 'package.json'), 'utf-8')); const getVersion = async () => JSON.stringify((await getPackageData()).version.toString()); @@ -25,6 +26,10 @@ let buildContexts = { unbundledContext: {}, }; +const debugPerf = process.env.DEBUG_PERFORMANCE === '1'; + +const isDeveloping = process.argv.includes('--develop'); + /** * @typedef {Object} BuildOptions * @property {Array} [watchedSrcDirectories] @@ -44,6 +49,8 @@ export async function build(options = {}) { options.watchedDocsDirectories = [getDocsDir()]; } + function measureStep() {} + /** * Runs the full build. */ @@ -51,17 +58,24 @@ export async function build(options = {}) { const start = Date.now(); try { - await cleanup(); - await generateManifest(); - await generateReactWrappers(); - await generateTypes(); - await generateStyles(); + const steps = [cleanup, generateManifest, generateReactWrappers, generateTypes, generateStyles]; + + for (const step of steps) { + if (debugPerf) { + const stepStart = Date.now(); + await step(); + const elapsedTime = (Date.now() - stepStart) / 1000 + 's'; + spinner.succeed(`${step.name}: ${elapsedTime}`); + } else { + await step(); + } + } // copy everything to unbundled before we generate bundles. await copy(getCdnDir(), getDistDir(), { overwrite: true }); await generateBundle(); - await generateDocs(); + await generateDocs({ spinner }); const time = (Date.now() - start) / 1000 + 's'; spinner.succeed(`The build is complete ${chalk.gray(`(finished in ${time})`)}`); @@ -258,49 +272,6 @@ export async function build(options = {}) { spinner.succeed(); } - /** - * Generates the documentation site. - */ - async function generateDocs() { - /** - * Used by the webawesome-app to skip doc generation since it will do its own. - */ - if (process.env.SKIP_ELEVENTY === 'true') { - return; - } - - spinner.start('Writing the docs'); - - const args = []; - if (isDeveloping) args.push('--develop'); - - let output; - try { - // 11ty - output = (await runScript(join(__dirname, 'docs.js'), args, { env: process.env })) - // Cleanup the output - .replace('[11ty]', '') - .replace(' seconds', 's') - .replace(/\(.*?\)/, '') - .toLowerCase() - .trim(); - - // Copy dist (production only) - if (!isDeveloping) { - await copy(getCdnDir(), join(getSiteDir(), 'dist')); - } - - spinner.succeed(`Writing the docs ${chalk.gray(`(${output}`)})`); - } catch (error) { - console.error('\n\n' + chalk.red(error) + '\n'); - - spinner.fail(chalk.red(`Error while writing the docs.`)); - if (!isDeveloping) { - process.exit(1); - } - } - } - // Initial build await buildAll(); @@ -338,6 +309,46 @@ export async function build(options = {}) { '/dist/': './dist-cdn/', }, }, + middleware: [ + function simulateWebawesomeApp(req, res, next) { + // Accumulator for strings so we can pass them through nunjucks a second time similar to how the webawesome-app + // will be running nunjucks twice. + const finalString = []; + const encoding = 'utf-8'; + + if (!next) { + return; + } + + if (!req.url) { + next(); + return; + } + + const extension = extname(req.url); + if (extension !== '' && extension !== '.html') { + // Assume its something like .svg / .png / .css etc. that we don't want to transform. + next(); + return; + } + + const _write = res.write; + + res.write = function (chunk, encoding) { + // Buffer chunks into an array so that we do a single transform. + finalString.push(chunk.toString()); + }; + + const _end = res.end; + res.end = function (...args) { + const transformedStr = SimulateWebAwesomeApp(finalString.join('')); + _write.call(res, transformedStr, encoding); + _end.call(res, ...args); + }; + + next(); + }, + ], callbacks: { ready: (_err, instance) => { // 404 errors @@ -397,7 +408,6 @@ export async function build(options = {}) { if (typeof options.onWatchEvent === 'function') { await options.onWatchEvent(evt, filename); } - await regenerateBundle(); // Copy stylesheets when CSS files change if (isCssStylesheet) { @@ -409,8 +419,12 @@ export async function build(options = {}) { await generateManifest(); } + // copy everything to unbundled before we generate bundles. + await copy(getCdnDir(), getDistDir(), { overwrite: true }); + await regenerateBundle(); + // This needs to be outside of "isComponent" check because SSR needs to run on CSS files too. - await generateDocs(); + await generateDocs({ spinner }); reload(); } catch (err) { @@ -438,7 +452,7 @@ export async function build(options = {}) { if (typeof options.onWatchEvent === 'function') { await options.onWatchEvent(evt, filename); } - await generateDocs(); + await generateDocs({ spinner }); reload(); }; } diff --git a/packages/webawesome/scripts/docs.js b/packages/webawesome/scripts/docs.js index 3e13ce615..570624614 100644 --- a/packages/webawesome/scripts/docs.js +++ b/packages/webawesome/scripts/docs.js @@ -1,14 +1,257 @@ import Eleventy from '@11ty/eleventy'; + +import copy from 'recursive-copy'; + +import chalk from 'chalk'; import { deleteAsync } from 'del'; -import { getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js'; +import { join } from 'path'; +import { getCdnDir, getDocsDir, getEleventyConfigPath, getSiteDir } from './utils.js'; -const elev = new Eleventy(getDocsDir(), getSiteDir(), { - quietMode: true, - configPath: getEleventyConfigPath(), -}); +let eleventyBuildResolver; +let eleventyBuildPromise; -// Cleanup -await deleteAsync(getSiteDir()); +function queueBuild() { + eleventyBuildPromise = new Promise(resolve => { + eleventyBuildResolver = resolve; + }); +} -// Write it -await elev.write(); +// 11ty +export async function createEleventy(options = {}) { + let { isIncremental, isDeveloping, rootDir } = options; + + isDeveloping ??= process.argv.includes('--develop'); + isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental'); + + const eleventy = new Eleventy(rootDir || getDocsDir(), getSiteDir(), { + quietMode: true, + configPath: getEleventyConfigPath(), + config: eleventyConfig => { + if (isDeveloping || isIncremental) { + eleventyConfig.setUseTemplateCache(false); + + eleventyConfig.on('eleventy.before', function () { + queueBuild(); + }); + eleventyConfig.on('eleventy.beforeWatch', async function () { + queueBuild(); + }); + eleventyConfig.on('eleventy.after', async function () { + eleventyBuildResolver(); + }); + } + }, + source: 'script', + runMode: isIncremental ? 'watch' : 'build', + }); + eleventy.setIncrementalBuild(isIncremental); + + await eleventy.init(); + + eleventy.logger.isChalkEnabled = false; + eleventy.logger.overrideLogger(new CustomLogger()); + + if (isIncremental) { + await eleventy.watch(); + + process.on('SIGINT', async () => { + await eleventy.stopWatch(); + process.exitCode = 0; + }); + } + + return eleventy; +} + +/** + * Generates the documentation site. + */ +export async function generateDocs(options = {}) { + let { spinner, isIncremental, isDeveloping } = options; + + isDeveloping ??= process.argv.includes('--develop'); + isIncremental ??= isDeveloping && !process.argv.includes('--no-incremental'); + + let eleventy = globalThis.eleventy; + /** + * Used by the webawesome-app to skip doc generation since it will do its own. + */ + if (process.env.SKIP_ELEVENTY === 'true') { + return; + } + + spinner?.start?.('Writing the docs'); + + const outputs = { + warn: [], + }; + + function stubConsole(key) { + const originalFn = console[key]; + console[key] = function (...args) { + outputs[key].push(...args); + }; + return originalFn; + } + + // Works around a bug in 11ty where it still prints warnings despite the logger being overriden and in quietMode. + const originalWarn = stubConsole('warn'); + + let output = ''; + + try { + if (isIncremental) { + if (!globalThis.eleventy) { + // First run + globalThis.eleventy = await createEleventy(options); + eleventy = globalThis.eleventy; + output = chalk.gray(`(${eleventy.logFinished()})`); + } else { + // eleventy incremental does its own writing, so we just kinda trust it for right now. + eleventy = globalThis.eleventy; + + await eleventyBuildPromise; + let info = eleventy.logger.logger.outputs.log; + + // TODO: The first write with incremental seems to be 1 behind. Not sure why. But its good enough for now. + info = info.filter(line => { + return !line.includes('Watching'); + }); + const lastLine = info[info.length - 1]; + output = chalk.gray(`(${lastLine})`); + eleventy.logger.logger.reset(); + } + } else { + // Cleanup + await deleteAsync(getSiteDir()); + + globalThis.eleventy = await createEleventy(options); + eleventy = globalThis.eleventy; + + // Write it + await eleventy.write(); + output = chalk.gray(`(${eleventy.logFinished()})`); + } + + // Copy dist (production only) + if (!isDeveloping) { + await copy(getCdnDir(), join(getSiteDir(), 'dist')); + } + spinner?.succeed?.(`Writing the docs ${output}`); + } catch (error) { + console.warn = originalWarn; + + console.error('\n\n' + chalk.red(error) + '\n'); + + spinner?.fail?.(chalk.red(`Error while writing the docs.`)); + if (!isDeveloping) { + process.exit(1); + } + } +} + +/** + * Much of this code is taken from 11ty's ConsoleLogger here: + * https://github.com/11ty/eleventy/blob/main/src/Util/ConsoleLogger.js + * + * Patches 11ty logger so it doesnt log everything, but we can still use its output for our own build. + * @typedef {'error'|'log'|'warn'|'info'} LogType + */ +class CustomLogger { + #outputStream; + + constructor() { + this.reset(); + } + + flush() { + Object.keys(this.outputs).forEach(outputType => { + console[outputType](this.outputs[outputType].join('')); + }); + this.reset(); + } + + reset() { + this.outputs = { + log: [], + info: [], + warn: [], + error: [], + }; + } + + /** @param {string} msg */ + log(msg) { + this.message(msg); + } + + /** + * @typedef LogOptions + * @property {string} message + * @property {string=} prefix + * @property {LogType=} type + * @property {string=} color + * @property {boolean=} force + * @param {LogOptions} options + */ + logWithOptions({ message, type, prefix, color, force }) { + this.message(message, type, color, force, prefix); + } + + /** @param {string} msg */ + forceLog(msg) { + this.message(msg, undefined, undefined, true); + } + + /** @param {string} msg */ + info(msg) { + this.message(msg, 'info', 'blue'); + } + + /** @param {string} msg */ + warn(msg) { + this.message(msg, 'warn', 'yellow'); + } + + /** @param {string} msg */ + error(msg) { + this.message(msg, 'error', 'red'); + } + + get outputStream() { + if (!this.#outputStream) { + this.#outputStream = new Readable({ + read() {}, + }); + } + return this.#outputStream; + } + + /** @param {string} msg */ + toStream(msg) { + this.outputStream.push(msg); + } + + closeStream() { + this.outputStream.push(null); + return this.outputStream; + } + + /** + * Formats the message to log. + * + * @param {string} message - The raw message to log. + * @param {LogType} [type='log'] - The error level to log. + * @param {string|undefined} [chalkColor=undefined] - Color name or falsy to disable + * @param {boolean} [forceToConsole=false] - Enforce a log on console instead of specified target. + */ + message(message, type = 'log', chalkColor = undefined, _forceToConsole = false, prefix = '') { + // if (chalkColor && this.isChalkEnabled) { + // message = `${chalk.gray(prefix)} ${message.split("\n").join(`\n${chalk.gray(prefix)} `)}`; + // this.outputs[type].push(chalk[chalkColor](message)); + // } else { + message = `${prefix}${message.split('\n').join(`\n${prefix}`)}`; + this.outputs[type].push(message); + // } + } +} From c9e6895ef70fdcef2ce66dad6c4421a954323c23 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Thu, 17 Jul 2025 11:28:57 -0400 Subject: [PATCH 2/3] fix pill buttons in groups; closes #1165 (#1191) --- packages/webawesome/docs/docs/components/button-group.md | 2 +- packages/webawesome/docs/docs/resources/changelog.md | 3 +++ .../src/components/button-group/button-group.css | 3 +-- .../src/components/button-group/button-group.ts | 5 ++++- packages/webawesome/src/components/button/button.css | 8 ++++---- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/webawesome/docs/docs/components/button-group.md b/packages/webawesome/docs/docs/components/button-group.md index 095195c85..f3f57f7fc 100644 --- a/packages/webawesome/docs/docs/components/button-group.md +++ b/packages/webawesome/docs/docs/components/button-group.md @@ -55,7 +55,7 @@ it is rarely a good idea to mix sizes within the same button group. Set the `orientation` attribute to `vertical` to make a vertical button group. ```html {.example} - + New diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 5a59b9a98..0bc6c254a 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -11,6 +11,7 @@ Components with the Experimental badge sh ## Next ### New Features {data-no-outline} + - Added `--track-height` custom property to `` [pr:1154] - Added `--pulse-color` custom property to `` [pr:1173] @@ -21,6 +22,8 @@ Components with the Experimental badge sh - Fixed the missing `nanoid` dependency in `package.json` [discuss:1139] - Fixed a bug in `` that prevented the hint from showing up [discuss:1172] - Fixed a bug in `` where setting `resize="auto"` caused the height of the textarea to double [issue:1155] +- Fixed a bug in `` that prevented pill buttons from rendering corners properly [issue:1165] +- Fixed a bug in `` that caused some vertical groups to appear horizontal [issue:1152] ## 3.0.0-beta.2 diff --git a/packages/webawesome/src/components/button-group/button-group.css b/packages/webawesome/src/components/button-group/button-group.css index 185162a89..ee7e152c0 100644 --- a/packages/webawesome/src/components/button-group/button-group.css +++ b/packages/webawesome/src/components/button-group/button-group.css @@ -26,8 +26,7 @@ z-index: 2 !important; } } - -:host([orientation='vertical']) { +:host([orientation='vertical']) .button-group { flex-direction: column; } diff --git a/packages/webawesome/src/components/button-group/button-group.ts b/packages/webawesome/src/components/button-group/button-group.ts index b37dcf52d..b0027fe23 100644 --- a/packages/webawesome/src/components/button-group/button-group.ts +++ b/packages/webawesome/src/components/button-group/button-group.ts @@ -101,7 +101,10 @@ export default class WaButtonGroup extends WebAwesomeElement { return html` Date: Thu, 17 Jul 2025 11:55:26 -0400 Subject: [PATCH 3/3] fix media rounding; closes #1107 (#1193) --- packages/webawesome/docs/docs/resources/changelog.md | 1 + packages/webawesome/src/components/card/card.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 0bc6c254a..2afa8a106 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -22,6 +22,7 @@ Components with the Experimental badge sh - Fixed the missing `nanoid` dependency in `package.json` [discuss:1139] - Fixed a bug in `` that prevented the hint from showing up [discuss:1172] - Fixed a bug in `` where setting `resize="auto"` caused the height of the textarea to double [issue:1155] +- Fixed a bug in `` that caused slotted media to have incorrectly rounded corners [issue:1107] - Fixed a bug in `` that prevented pill buttons from rendering corners properly [issue:1165] - Fixed a bug in `` that caused some vertical groups to appear horizontal [issue:1152] diff --git a/packages/webawesome/src/components/card/card.css b/packages/webawesome/src/components/card/card.css index 105a3b2de..2df00d27a 100644 --- a/packages/webawesome/src/components/card/card.css +++ b/packages/webawesome/src/components/card/card.css @@ -63,7 +63,7 @@ &::slotted(*) { display: block; width: 100%; - border-radius: 0; + border-radius: 0 !important; } }