
| Current Path : /var/www/html/12park/vendor/twbs/bootstrap/site/src/libs/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/html/12park/vendor/twbs/bootstrap/site/src/libs/placeholder.ts |
import type { HTMLAttributes } from 'astro/types'
import * as htmlparser2 from 'htmlparser2'
import { getData } from './data'
const placeholderRegex = /<Placeholder\s+([^>]+)\/>/g
/**
* Generates all the placeholder attributes and options required to render a placeholder.
* @see src/components/shortcodes/Placeholder.astro
*/
export function getPlaceholder(userOptions: Partial<PlaceholderOptions>): Placeholder {
const options = getOptionsWithDefaults(userOptions)
const { class: className, height, markup, text, title, width } = options
const showText = text !== false
const showTitle = title !== false
const placeholderClassList = ['bd-placeholder-img', className].join(' ')
const placeholderRole = showTitle || showText ? 'img' : undefined
const placeholderAriaHidden = !showText && !showTitle ? 'true' : undefined
const placeholderLabel =
showText || showTitle
? `${showTitle ? title : ''}${showTitle && showText ? ': ' : ''}${showText ? text : ''}`
: undefined
const optionsWithVisibilities = { ...options, showText, showTitle }
if (markup === 'img') {
return {
type: 'img',
options: optionsWithVisibilities,
props: {
alt: placeholderLabel,
class: placeholderClassList,
height,
src: getPlaceholderSrc(showTitle, showText, options),
width
}
}
}
return {
type: 'svg',
options: optionsWithVisibilities,
props: {
'aria-hidden': placeholderAriaHidden,
'aria-label': placeholderLabel,
class: placeholderClassList,
height,
preserveAspectRatio: 'xMidYMid slice',
role: placeholderRole,
width,
xmlns: 'http://www.w3.org/2000/svg'
}
}
}
/**
* Replaces placeholders described using the `<Placeholder />` component in HTML markup with the expected HTML content.
* This is useful to render examples that have a pretty large set of constraints:
*
* - The provided HTML code is not valid MDX (e.g. unclosed void elements like <img>) but can contain the
* `<Placeholder />` Astro component. This means that we cannot use an Astro slot for example that requires valid
* MDX.
* - The provided HTML code cannot be parsed in a forgiving way with XML mode enabled (to not lose the structure due
* to self-closing MDX or Astro components) and serialized back to a string while closing all known void elements
* in order to render it as MDX using `@mdx-js/mdx` & `astro/jsx-runtime`. This works perfectly (tested) but the
* DOM needs to contains the exact same HTML markup (even indentation) provided to the example as it is used on the
* client to send the example to StackBlitz with the same indentation as the original example.
*
* If you are not sure if you need to use this function, you probably don't.
*/
export function replacePlaceholdersInHtml(html: string) {
return html.replace(placeholderRegex, (match) => {
const document = htmlparser2.parseDocument(match, { xmlMode: true })
const placeholderElement = document.firstChild
if (
document.children.length > 1 ||
!placeholderElement ||
placeholderElement.type !== htmlparser2.ElementType.Tag ||
placeholderElement.name !== 'Placeholder'
) {
throw new Error('Invalid placeholder element.')
}
const placeholder = getPlaceholder(sanitizeHtmlAttributesFromMdx(placeholderElement.attribs))
return renderPlaceholderToString(placeholder)
})
}
function renderPlaceholderToString(placeholder: Placeholder) {
let placeholderStr = `<${placeholder.type}`
for (const [key, value] of Object.entries(placeholder.props)) {
if (value === undefined) {
continue
}
placeholderStr = `${placeholderStr} ${key}="${value}"`
}
if (placeholder.type === 'img') {
return `${placeholderStr} />`
}
placeholderStr = `${placeholderStr}>`
if (placeholder.options.showTitle) {
placeholderStr = `${placeholderStr}<title>${placeholder.options.title}</title>`
}
placeholderStr = `${placeholderStr}<rect width="100%" height="100%" fill="${placeholder.options.background}" />`
if (placeholder.options.showText) {
placeholderStr = `${placeholderStr}<text x="50%" y="50%" fill="${placeholder.options.color}" dy=".3em">${placeholder.options.text}</text>`
}
return `${placeholderStr}</${placeholder.type}>`
}
function getOptionsWithDefaults(options: Partial<PlaceholderOptions>) {
const optionsWithDefaults = Object.assign(
{},
{
background: getData('grays')[5].hex,
color: getData('grays')[2].hex,
height: '180',
markup: 'svg',
title: 'Placeholder',
width: '100%'
},
options
)
if (optionsWithDefaults.text === undefined) {
optionsWithDefaults.text = `${optionsWithDefaults.width}x${optionsWithDefaults.height}`
}
return optionsWithDefaults as PlaceholderOptions
}
function getPlaceholderSrc(
showTitle: boolean,
showText: boolean,
{ background, color, text, title }: PlaceholderOptions
) {
// Sanitize the background and text colors by removing the leading hash if any.
const bgColor = background.replace(/^#/, '')
const textColor = color.replace(/^#/, '')
// Build the raw SVG string first
let svg = `<svg style='font-size: 1.125rem; font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; -webkit-user-select: none; -moz-user-select: none; user-select: none; text-anchor: middle;' width='200' height='200' xmlns='http://www.w3.org/2000/svg'>`
if (showTitle) {
svg += `<title>${title}</title>`
}
svg += `<rect width='100%' height='100%' fill='#${bgColor}'></rect>`
if (showText) {
svg += `<text x='50%' y='50%' fill='#${textColor}' dy='.3em'>${text}</text>`
}
svg += `</svg>`
const encodedSvg = encodeURIComponent(svg)
return `data:image/svg+xml,${encodedSvg}`
}
function sanitizeHtmlAttributesFromMdx(attributes: Record<string, unknown>) {
const sanitizedAttributes: typeof attributes = {}
for (const [key, value] of Object.entries(attributes)) {
if (value === undefined) {
continue
} else if (value === '{false}') {
sanitizedAttributes[key] = false
} else if (value === '{true}') {
sanitizedAttributes[key] = true
} else {
sanitizedAttributes[key] = value
}
}
return sanitizedAttributes
}
export interface PlaceholderOptions {
/**
* The SVG background color.
* @default "#868e96"
*/
background: string
/**
* CSS classes to append to `bd-placeholder-img` for the `svg` or `img` elements.
*/
class?: string
/**
* The text color (foreground).
* @default "#dee2e6"
*/
color: string
/**
* The placeholder height.
* @default "180"
*/
height: string
/**
* If it should render `svg` or `img` tags.
* @default "svg"
*/
markup: 'img' | 'svg'
/**
* The text to show in the image. You can explicitly pass the `false` boolean value (and not the string "false") to
* hide the text.
* @default "${width}x{$height)"
*/
text: string | false
/**
* Used in the SVG `title` tag. You can explicitly pass the `false` boolean value (and not the string "false") to
* hide the title.
* @default "Placeholder"
*/
title: string | false
/**
* The placeholder width.
* @default "100%"
*/
width: string
}
interface PlaceholderVisibilities {
showText: boolean
showTitle: boolean
}
type Placeholder =
| {
type: 'img'
options: PlaceholderOptions & PlaceholderVisibilities
props: HTMLAttributes<'img'>
}
| {
type: 'svg'
options: PlaceholderOptions & PlaceholderVisibilities
props: HTMLAttributes<'svg'>
}