@obvi/blueprint 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +443 -0
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/dist/blueprint.css +2689 -0
- package/dist/blueprint.js +416 -0
- package/dist/code-highlighting/blueprint-code.css +406 -0
- package/dist/code-highlighting/blueprint-code.js +19 -0
- package/package.json +52 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// Optional behavior for TOC current state and reading progress.
|
|
2
|
+
export function initializeBlueprintRuntime(doc = document, win = window) {
|
|
3
|
+
const root = doc.documentElement
|
|
4
|
+
if (root.dataset.blueprintRuntime === 'ready') return
|
|
5
|
+
root.dataset.blueprintRuntime = 'ready'
|
|
6
|
+
|
|
7
|
+
wireSectionHeadingLinks(doc)
|
|
8
|
+
wireFigureDiagramSize(doc)
|
|
9
|
+
|
|
10
|
+
// The shell becomes the scroll container only while it is fixed (wide
|
|
11
|
+
// layout). At ≤700px the CSS reverts it to static and the window scrolls, so
|
|
12
|
+
// resolve the scroller live rather than caching it — a wide→narrow resize
|
|
13
|
+
// otherwise leaves the scroll listener bound to an element that no longer
|
|
14
|
+
// scrolls, silently freezing reading-progress and TOC highlighting.
|
|
15
|
+
const shell = doc.querySelector('.bp-shell')
|
|
16
|
+
const resolveScroller = () =>
|
|
17
|
+
shell && win.getComputedStyle(shell).position === 'fixed' ? shell : null
|
|
18
|
+
let shellScroller = resolveScroller()
|
|
19
|
+
const scrollTop = () => (shellScroller ? shellScroller.scrollTop : win.scrollY)
|
|
20
|
+
const scrollHeight = () =>
|
|
21
|
+
shellScroller ? shellScroller.scrollHeight : root.scrollHeight
|
|
22
|
+
const clientHeight = () =>
|
|
23
|
+
shellScroller ? shellScroller.clientHeight : win.innerHeight
|
|
24
|
+
|
|
25
|
+
const progress = doc.querySelector('.scroll-progress')
|
|
26
|
+
// Only the fixed rails track an active entry. A full-width .bp-contents
|
|
27
|
+
// index (typically parked at the top of a document) has no "current" entry,
|
|
28
|
+
// so it stays a static index and is intentionally excluded here.
|
|
29
|
+
const entries = [...doc.querySelectorAll('.bp-sidebar a[href^="#"], .bp-toc a[href^="#"], .sidebar a[href^="#"]')]
|
|
30
|
+
.map((link) => ({
|
|
31
|
+
link,
|
|
32
|
+
section: doc.getElementById(decodeURIComponent(link.hash.slice(1))),
|
|
33
|
+
}))
|
|
34
|
+
.filter(({ section }) => section)
|
|
35
|
+
// Unique target sections in DOCUMENT order — independent of the order links
|
|
36
|
+
// appear across a sidebar and one or more inline .bp-contents blocks.
|
|
37
|
+
const sections = [...new Set(entries.map(({ section }) => section))].sort((a, b) =>
|
|
38
|
+
a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1
|
|
39
|
+
)
|
|
40
|
+
let scheduled = false
|
|
41
|
+
let scrollingTo = null
|
|
42
|
+
|
|
43
|
+
// Position each contents list's gliding marker + read-so-far fill to the
|
|
44
|
+
// active entry. Pure progressive enhancement: with JS off the aria-current
|
|
45
|
+
// rail still marks the active section.
|
|
46
|
+
const placeMarker = (link) => {
|
|
47
|
+
const container = link.closest('.bp-sidebar, .bp-toc, .sidebar')
|
|
48
|
+
// Top-level list is normally a direct child, but may sit inside a wrapper
|
|
49
|
+
// (e.g. a sliding panel); fall back to the first list in the container.
|
|
50
|
+
const list = container?.querySelector(':scope > ul') ?? container?.querySelector('ul')
|
|
51
|
+
if (!list) return
|
|
52
|
+
list.style.setProperty('--bp-toc-y', `${link.offsetTop}px`)
|
|
53
|
+
list.style.setProperty('--bp-toc-h', `${link.offsetHeight}px`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Mark every entry that points at the active section, so multiple fixed
|
|
57
|
+
// rails on a page stay in sync, each showing exactly one current link.
|
|
58
|
+
const setCurrent = (section) => {
|
|
59
|
+
for (const item of entries) {
|
|
60
|
+
if (section && item.section === section) {
|
|
61
|
+
item.link.setAttribute('aria-current', 'location')
|
|
62
|
+
placeMarker(item.link)
|
|
63
|
+
} else {
|
|
64
|
+
item.link.removeAttribute('aria-current')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const update = () => {
|
|
69
|
+
scheduled = false
|
|
70
|
+
if (progress) {
|
|
71
|
+
const max = scrollHeight() - clientHeight()
|
|
72
|
+
progress.style.transform = `scaleX(${max > 0 ? scrollTop() / max : 0})`
|
|
73
|
+
}
|
|
74
|
+
if (sections.length === 0) return
|
|
75
|
+
if (scrollingTo) {
|
|
76
|
+
const top = scrollingTo.getBoundingClientRect().top
|
|
77
|
+
if (top <= win.innerHeight * 0.35 && top >= -8) scrollingTo = null
|
|
78
|
+
else {
|
|
79
|
+
setCurrent(scrollingTo)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (scrollTop() + clientHeight() >= scrollHeight() - 2) {
|
|
84
|
+
setCurrent(sections.at(-1))
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
const threshold = win.innerHeight * 0.35
|
|
88
|
+
setCurrent(
|
|
89
|
+
sections.reduce(
|
|
90
|
+
(current, section) => (section.getBoundingClientRect().top <= threshold ? section : current),
|
|
91
|
+
sections[0]
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
const scheduleUpdate = () => {
|
|
96
|
+
if (scheduled) return
|
|
97
|
+
scheduled = true
|
|
98
|
+
win.requestAnimationFrame(update)
|
|
99
|
+
}
|
|
100
|
+
const updateFromHash = () => {
|
|
101
|
+
const match = entries.find(({ link }) => link.hash === win.location.hash)
|
|
102
|
+
if (match) {
|
|
103
|
+
scrollingTo = match.section
|
|
104
|
+
setCurrent(match.section)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
scrollingTo = null
|
|
108
|
+
scheduleUpdate()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const navLinkSelector =
|
|
112
|
+
'.bp-sidebar a[href^="#"], .bp-toc a[href^="#"], .sidebar a[href^="#"], .bp-contents a[href^="#"]'
|
|
113
|
+
const prefersReducedMotion = () => win.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
114
|
+
|
|
115
|
+
const scrollToSection = (section) => {
|
|
116
|
+
scrollingTo = section
|
|
117
|
+
section.scrollIntoView({
|
|
118
|
+
behavior: prefersReducedMotion() ? 'auto' : 'smooth',
|
|
119
|
+
block: 'start',
|
|
120
|
+
})
|
|
121
|
+
const finish = () => {
|
|
122
|
+
if (scrollingTo === section) scrollingTo = null
|
|
123
|
+
}
|
|
124
|
+
if ('onscrollend' in win) {
|
|
125
|
+
const scrollTarget = shellScroller ?? win
|
|
126
|
+
scrollTarget.addEventListener('scrollend', finish, { once: true })
|
|
127
|
+
} else {
|
|
128
|
+
win.setTimeout(finish, prefersReducedMotion() ? 0 : 800)
|
|
129
|
+
}
|
|
130
|
+
scheduleUpdate()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
doc.addEventListener('click', (event) => {
|
|
134
|
+
const link = event.target.closest?.(navLinkSelector)
|
|
135
|
+
if (!link?.hash) return
|
|
136
|
+
if (
|
|
137
|
+
event.defaultPrevented ||
|
|
138
|
+
event.button !== 0 ||
|
|
139
|
+
event.metaKey ||
|
|
140
|
+
event.ctrlKey ||
|
|
141
|
+
event.shiftKey ||
|
|
142
|
+
event.altKey
|
|
143
|
+
) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
const section = doc.getElementById(decodeURIComponent(link.hash.slice(1)))
|
|
147
|
+
if (!section) return
|
|
148
|
+
event.preventDefault()
|
|
149
|
+
if (win.location.hash !== link.hash) win.history.pushState(null, '', link.hash)
|
|
150
|
+
if (entries.some(({ section: target }) => target === section)) setCurrent(section)
|
|
151
|
+
scrollToSection(section)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Keep the scroll listener on whichever element currently scrolls, moving it
|
|
155
|
+
// when a resize flips the shell between fixed and static.
|
|
156
|
+
let scrollSource = shellScroller ?? win
|
|
157
|
+
scrollSource.addEventListener('scroll', scheduleUpdate, { passive: true })
|
|
158
|
+
const syncScrollSource = () => {
|
|
159
|
+
shellScroller = resolveScroller()
|
|
160
|
+
const nextSource = shellScroller ?? win
|
|
161
|
+
if (nextSource !== scrollSource) {
|
|
162
|
+
scrollSource.removeEventListener('scroll', scheduleUpdate, { passive: true })
|
|
163
|
+
nextSource.addEventListener('scroll', scheduleUpdate, { passive: true })
|
|
164
|
+
scrollSource = nextSource
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
win.addEventListener('hashchange', updateFromHash)
|
|
169
|
+
win.addEventListener('popstate', updateFromHash)
|
|
170
|
+
win.addEventListener('resize', () => {
|
|
171
|
+
syncScrollSource()
|
|
172
|
+
scheduleUpdate()
|
|
173
|
+
}, { passive: true })
|
|
174
|
+
updateFromHash()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Section heading permalink — pixelarticons in the main-column gutter; link on
|
|
178
|
+
// heading hover, copy on icon hover, check after a successful copy.
|
|
179
|
+
const HEADING_LINK_ICON =
|
|
180
|
+
'M4 6h7v2H4zm0 10h7v2H4zM2 8h2v8H2zm18-2h-7v2h7zm0 10h-7v2h7zm2-8h-2v8h2zM7 11h10v2H7z'
|
|
181
|
+
const HEADING_COPY_ICON =
|
|
182
|
+
'M8 6h12v2H8zM4 2h12v2H4zm2 6h2v12H6zM2 4h2v12H2zm6 16h12v2H8zM20 8h2v12h-2zm-4-4h2v2h-2zM4 16h2v2H4z'
|
|
183
|
+
const HEADING_CHECK_ICON =
|
|
184
|
+
'M10 18H8v-2h2v2Zm-2-2H6v-2h2v2Zm4-2v2h-2v-2h2Zm-6 0H4v-2h2v2Zm8 0h-2v-2h2v2Zm2-2h-2v-2h2v2Zm2-2h-2V8h2v2Zm2-2h-2V6h2v2Z'
|
|
185
|
+
const headingLinkTimers = new WeakMap()
|
|
186
|
+
|
|
187
|
+
function createHeadingPixelIcon(doc, name, pathD) {
|
|
188
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
189
|
+
svg.setAttribute('viewBox', '0 0 24 24')
|
|
190
|
+
svg.setAttribute('width', '16')
|
|
191
|
+
svg.setAttribute('height', '16')
|
|
192
|
+
svg.setAttribute('fill', 'currentColor')
|
|
193
|
+
svg.setAttribute('aria-hidden', 'true')
|
|
194
|
+
svg.classList.add('bp-heading-link__icon', `bp-heading-link__icon--${name}`)
|
|
195
|
+
const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
196
|
+
path.setAttribute('d', pathD)
|
|
197
|
+
svg.append(path)
|
|
198
|
+
return svg
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createHeadingLinkButton(doc, label) {
|
|
202
|
+
const button = doc.createElement('button')
|
|
203
|
+
button.type = 'button'
|
|
204
|
+
button.className = 'bp-heading-link'
|
|
205
|
+
button.setAttribute('aria-label', `Copy link to ${label}`)
|
|
206
|
+
|
|
207
|
+
const icons = doc.createElement('span')
|
|
208
|
+
icons.className = 'bp-heading-link__icons'
|
|
209
|
+
icons.append(
|
|
210
|
+
createHeadingPixelIcon(doc, 'link', HEADING_LINK_ICON),
|
|
211
|
+
createHeadingPixelIcon(doc, 'copy', HEADING_COPY_ICON),
|
|
212
|
+
createHeadingPixelIcon(doc, 'check', HEADING_CHECK_ICON)
|
|
213
|
+
)
|
|
214
|
+
button.append(icons)
|
|
215
|
+
return button
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function copySectionUrl(url) {
|
|
219
|
+
if (!globalThis.isSecureContext || !navigator.clipboard?.writeText) {
|
|
220
|
+
throw new Error('The Clipboard API requires a secure context')
|
|
221
|
+
}
|
|
222
|
+
await navigator.clipboard.writeText(url)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function showHeadingLinkState(button, state, label) {
|
|
226
|
+
const previousTimer = headingLinkTimers.get(button)
|
|
227
|
+
if (previousTimer) clearTimeout(previousTimer)
|
|
228
|
+
|
|
229
|
+
const heading = button.closest('h2')
|
|
230
|
+
|
|
231
|
+
if (state === 'idle') {
|
|
232
|
+
delete button.dataset.bpCopyState
|
|
233
|
+
button.setAttribute('aria-label', `Copy link to ${label}`)
|
|
234
|
+
headingLinkTimers.delete(button)
|
|
235
|
+
if (heading && !heading.matches(':hover') && !heading.matches(':focus-within')) {
|
|
236
|
+
delete heading.dataset.headingLink
|
|
237
|
+
}
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
button.dataset.bpCopyState = state
|
|
242
|
+
button.setAttribute(
|
|
243
|
+
'aria-label',
|
|
244
|
+
state === 'copied' ? 'Link copied' : 'Copy failed'
|
|
245
|
+
)
|
|
246
|
+
if (heading) heading.dataset.headingLink = 'visible'
|
|
247
|
+
|
|
248
|
+
headingLinkTimers.set(
|
|
249
|
+
button,
|
|
250
|
+
setTimeout(() => showHeadingLinkState(button, 'idle', label), 1800)
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function wireFigureDiagramSize(doc) {
|
|
255
|
+
for (const svg of doc.querySelectorAll('figure svg[viewBox]')) {
|
|
256
|
+
const { width } = svg.viewBox.baseVal
|
|
257
|
+
if (width > 0) svg.style.setProperty('--bp-diagram-w', `${width}px`)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function wireSectionHeadingLinks(doc, win = doc.defaultView) {
|
|
262
|
+
for (const section of doc.querySelectorAll('section[id]')) {
|
|
263
|
+
const heading =
|
|
264
|
+
section.querySelector(':scope > h2:first-child') ??
|
|
265
|
+
section.querySelector(':scope > hgroup:first-child > h2')
|
|
266
|
+
if (!heading || heading.querySelector(':scope > .bp-heading-row')) continue
|
|
267
|
+
|
|
268
|
+
const label = heading.textContent.trim()
|
|
269
|
+
heading.replaceChildren()
|
|
270
|
+
|
|
271
|
+
const row = doc.createElement('span')
|
|
272
|
+
row.className = 'bp-heading-row'
|
|
273
|
+
|
|
274
|
+
const button = createHeadingLinkButton(doc, label)
|
|
275
|
+
|
|
276
|
+
const title = doc.createElement('span')
|
|
277
|
+
title.className = 'bp-heading-title'
|
|
278
|
+
title.textContent = label
|
|
279
|
+
|
|
280
|
+
row.append(button, title)
|
|
281
|
+
heading.append(row)
|
|
282
|
+
|
|
283
|
+
let hideTimer = 0
|
|
284
|
+
const showLink = () => {
|
|
285
|
+
win.clearTimeout(hideTimer)
|
|
286
|
+
heading.dataset.headingLink = 'visible'
|
|
287
|
+
}
|
|
288
|
+
const scheduleHide = (event) => {
|
|
289
|
+
const next = event?.relatedTarget
|
|
290
|
+
if (next instanceof Node && heading.contains(next)) return
|
|
291
|
+
hideTimer = win.setTimeout(() => {
|
|
292
|
+
if (button.dataset.bpCopyState) return
|
|
293
|
+
if (heading.matches(':hover') || button.matches(':hover')) return
|
|
294
|
+
delete heading.dataset.headingLink
|
|
295
|
+
}, 120)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
heading.addEventListener('mouseenter', showLink)
|
|
299
|
+
heading.addEventListener('mouseleave', scheduleHide)
|
|
300
|
+
button.addEventListener('mouseenter', showLink)
|
|
301
|
+
button.addEventListener('mouseleave', scheduleHide)
|
|
302
|
+
heading.addEventListener('focusin', showLink)
|
|
303
|
+
heading.addEventListener('focusout', (event) => {
|
|
304
|
+
const next = event.relatedTarget
|
|
305
|
+
if (next instanceof Node && heading.contains(next)) return
|
|
306
|
+
if (!button.dataset.bpCopyState) scheduleHide(event)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
button.addEventListener('click', (event) => {
|
|
310
|
+
event.preventDefault()
|
|
311
|
+
const url = new URL(`#${section.id}`, win?.location.href ?? '').href
|
|
312
|
+
copySectionUrl(url)
|
|
313
|
+
.then(() => showHeadingLinkState(button, 'copied', label))
|
|
314
|
+
.catch(() => showHeadingLinkState(button, 'error', label))
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---------------------------------------------------------------------
|
|
320
|
+
// <bp-callout> — typed callout (drafting corner-tick family).
|
|
321
|
+
//
|
|
322
|
+
// Authoring shrinks to a single element; the component expands to the
|
|
323
|
+
// .bp-callout structure (corner ticks come from blueprint.css) with the
|
|
324
|
+
// matching type icon and label. Light DOM, so the stylesheet applies
|
|
325
|
+
// directly and the raw .bp-callout markup stays equally valid.
|
|
326
|
+
//
|
|
327
|
+
// <bp-callout type="invariant">
|
|
328
|
+
// The fence token is monotonic and never reused.
|
|
329
|
+
// </bp-callout>
|
|
330
|
+
//
|
|
331
|
+
// type: locked | invariant | ref (alias: reference). Default: locked.
|
|
332
|
+
// label: overrides the caption text. icon="none" drops the glyph.
|
|
333
|
+
// ---------------------------------------------------------------------
|
|
334
|
+
const CALLOUT_ICONS = {
|
|
335
|
+
locked:
|
|
336
|
+
'M5 8h14v2H5zm0 12h14v2H5zM3 10h2v10H3zm16 0h2v10h-2zM7 4h2v4H7zm2-2h6v2H9zm6 2h2v4h-2z',
|
|
337
|
+
invariant:
|
|
338
|
+
'M4 2h16v2H4zM2 4h2v10H2zm18 0h2v10h-2zM4 14h2v2H4zm2 2h2v2H6zm4 4h4v2h-4zm10-6h-2v2h2zm-2 2h-2v2h2zm-2 2h-2v2h2zm-6 0H8v2h2z',
|
|
339
|
+
ref:
|
|
340
|
+
'M4 2h16v2H4zm2 5h2v2H6zm4 0h8v2h-8zm-4 4h2v2H6zm4 0h8v2h-8zm-4 4h2v2H6zm4 0h8v2h-8zm-6 5h16v2H4zM2 4h2v16H2zm18 0h2v16h-2z',
|
|
341
|
+
}
|
|
342
|
+
const CALLOUT_TYPES = {
|
|
343
|
+
locked: { mod: 'locked', label: 'Locked', icon: 'locked' },
|
|
344
|
+
invariant: { mod: 'invariant', label: 'Invariant', icon: 'invariant' },
|
|
345
|
+
ref: { mod: 'ref', label: 'Reference', icon: 'ref' },
|
|
346
|
+
reference: { mod: 'ref', label: 'Reference', icon: 'ref' },
|
|
347
|
+
}
|
|
348
|
+
// Author content that already carries its own block boxes passes straight
|
|
349
|
+
// through; anything else (bare text, inline runs) gets wrapped in a <p>.
|
|
350
|
+
const CALLOUT_BLOCK = new Set([
|
|
351
|
+
'P', 'UL', 'OL', 'DL', 'DIV', 'PRE', 'BLOCKQUOTE', 'TABLE', 'FIGURE',
|
|
352
|
+
'SECTION', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BP-SOURCE', 'BP-CITE',
|
|
353
|
+
])
|
|
354
|
+
|
|
355
|
+
function createCalloutIcon(doc, pathD) {
|
|
356
|
+
const svg = doc.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
357
|
+
svg.setAttribute('viewBox', '0 0 24 24')
|
|
358
|
+
svg.setAttribute('fill', 'currentColor')
|
|
359
|
+
svg.setAttribute('aria-hidden', 'true')
|
|
360
|
+
const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
361
|
+
path.setAttribute('d', pathD)
|
|
362
|
+
svg.append(path)
|
|
363
|
+
return svg
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
class BlueprintCalloutElement extends HTMLElement {
|
|
367
|
+
#rendered = false
|
|
368
|
+
|
|
369
|
+
connectedCallback() {
|
|
370
|
+
if (this.#rendered) return
|
|
371
|
+
this.#rendered = true
|
|
372
|
+
|
|
373
|
+
const doc = this.ownerDocument
|
|
374
|
+
const key = (this.getAttribute('type') || 'locked').toLowerCase()
|
|
375
|
+
const spec = CALLOUT_TYPES[key] ?? CALLOUT_TYPES.locked
|
|
376
|
+
const label = this.getAttribute('label') ?? spec.label
|
|
377
|
+
|
|
378
|
+
const aside = doc.createElement('aside')
|
|
379
|
+
aside.className = `bp-callout bp-callout--${spec.mod}`
|
|
380
|
+
|
|
381
|
+
const tag = doc.createElement('span')
|
|
382
|
+
tag.className = 'bp-ctag'
|
|
383
|
+
const iconPath = CALLOUT_ICONS[spec.icon]
|
|
384
|
+
if (iconPath && this.getAttribute('icon') !== 'none') {
|
|
385
|
+
tag.append(createCalloutIcon(doc, iconPath))
|
|
386
|
+
}
|
|
387
|
+
if (label) tag.append(doc.createTextNode(label))
|
|
388
|
+
aside.append(tag)
|
|
389
|
+
|
|
390
|
+
const body = [...this.childNodes]
|
|
391
|
+
const hasBlock = body.some(
|
|
392
|
+
(node) => node.nodeType === 1 && CALLOUT_BLOCK.has(node.tagName)
|
|
393
|
+
)
|
|
394
|
+
if (hasBlock) {
|
|
395
|
+
aside.append(...body)
|
|
396
|
+
} else {
|
|
397
|
+
const p = doc.createElement('p')
|
|
398
|
+
p.append(...body)
|
|
399
|
+
if (p.childNodes.length) aside.append(p)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this.replaceChildren(aside)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (typeof customElements !== 'undefined' && !customElements.get('bp-callout')) {
|
|
407
|
+
customElements.define('bp-callout', BlueprintCalloutElement)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (typeof document !== 'undefined') {
|
|
411
|
+
if (document.readyState === 'loading') {
|
|
412
|
+
document.addEventListener('DOMContentLoaded', () => initializeBlueprintRuntime(), { once: true })
|
|
413
|
+
} else {
|
|
414
|
+
initializeBlueprintRuntime()
|
|
415
|
+
}
|
|
416
|
+
}
|