@peaceroad/markdown-it-renderer-fence 0.4.1 → 0.6.0

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.
@@ -0,0 +1,712 @@
1
+ import {
2
+ collectTextSegments,
3
+ findTextPosition,
4
+ getPayloadMapFromScript,
5
+ styleToHighlightCss,
6
+ } from '../custom-highlight/runtime-utils.js'
7
+ import {
8
+ customHighlightAppliedAttr,
9
+ customHighlightAppliedSelector,
10
+ customHighlightCodeSelector,
11
+ customHighlightDataScriptId,
12
+ customHighlightPayloadSupportedVersions,
13
+ customHighlightPreAttr,
14
+ customHighlightPreSelector,
15
+ customHighlightStyleTagId,
16
+ } from './render-api-constants.js'
17
+
18
+ const globalRuntimeInsertedScopeStyles = new Map()
19
+ const runtimeInsertedScopeStylesByDoc = new WeakMap()
20
+ const runtimeApplyStateByRoot = new WeakMap()
21
+ const validColorSchemeSet = new Set(['light', 'dark', 'auto'])
22
+ const highlightNameUnsafeReg = /[^A-Za-z0-9_-]+/g
23
+ const hyphenMultiReg = /-+/g
24
+
25
+ const sanitizeHighlightName = (name) => {
26
+ const raw = String(name || '')
27
+ let safe = raw.replace(highlightNameUnsafeReg, '-').replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
28
+ if (!safe) safe = 'scope'
29
+ if (/^[0-9]/.test(safe)) safe = 'x-' + safe
30
+ if (safe.startsWith('--')) safe = safe.slice(2) || 'scope'
31
+ return safe
32
+ }
33
+
34
+ const getCustomHighlightBlockId = (node) => {
35
+ if (!node || typeof node.getAttribute !== 'function') return null
36
+ return node.getAttribute(customHighlightPreAttr)
37
+ }
38
+
39
+ const getRuntimeInsertedScopeStyles = (doc) => {
40
+ if (!doc || typeof doc !== 'object') return globalRuntimeInsertedScopeStyles
41
+ let map = runtimeInsertedScopeStylesByDoc.get(doc)
42
+ if (!map) {
43
+ map = new Map()
44
+ runtimeInsertedScopeStylesByDoc.set(doc, map)
45
+ }
46
+ return map
47
+ }
48
+
49
+ const ensureHighlightStyleTag = (doc) => {
50
+ if (!doc || typeof doc.getElementById !== 'function' || typeof doc.createElement !== 'function') return null
51
+ let styleTag = doc.getElementById(customHighlightStyleTagId)
52
+ if (styleTag) return styleTag
53
+ styleTag = doc.createElement('style')
54
+ styleTag.id = customHighlightStyleTagId
55
+ if (doc.head) doc.head.appendChild(styleTag)
56
+ else if (doc.documentElement) doc.documentElement.appendChild(styleTag)
57
+ else return null
58
+ return styleTag
59
+ }
60
+
61
+ const clearAppliedHighlightNames = (queryRoot) => {
62
+ if (!queryRoot || typeof queryRoot.querySelectorAll !== 'function') return 0
63
+ if (typeof CSS === 'undefined' || !CSS.highlights) return 0
64
+ const nodes = queryRoot.querySelectorAll(customHighlightAppliedSelector)
65
+ const removed = new Set()
66
+ for (const pre of nodes) {
67
+ const names = String(pre.getAttribute(customHighlightAppliedAttr) || '').split(/\s+/).filter(Boolean)
68
+ for (const name of names) {
69
+ if (removed.has(name)) continue
70
+ CSS.highlights.delete(name)
71
+ removed.add(name)
72
+ }
73
+ pre.removeAttribute(customHighlightAppliedAttr)
74
+ }
75
+ return removed.size
76
+ }
77
+
78
+ const getCachedPayloadString = (payload, cache) => {
79
+ if (!cache || payload == null || (typeof payload !== 'object' && typeof payload !== 'function')) {
80
+ return JSON.stringify(payload)
81
+ }
82
+ if (cache.has(payload)) return cache.get(payload)
83
+ const text = JSON.stringify(payload)
84
+ cache.set(payload, text)
85
+ return text
86
+ }
87
+
88
+ const createPayloadDigest = (payloadMap, cache) => {
89
+ const keys = Object.keys(payloadMap || {}).sort()
90
+ const parts = new Array(keys.length)
91
+ for (let i = 0; i < keys.length; i++) {
92
+ const key = keys[i]
93
+ const payload = payloadMap[key]
94
+ parts[i] = key + '=' + getCachedPayloadString(payload, cache) + ';'
95
+ }
96
+ return parts.join('')
97
+ }
98
+
99
+ const getRuntimeApplyState = (queryRoot) => {
100
+ if (!queryRoot || typeof queryRoot !== 'object') return null
101
+ return runtimeApplyStateByRoot.get(queryRoot) || null
102
+ }
103
+
104
+ const setRuntimeApplyState = (queryRoot, state) => {
105
+ if (!queryRoot || typeof queryRoot !== 'object') return
106
+ runtimeApplyStateByRoot.set(queryRoot, state)
107
+ }
108
+
109
+ const clearRuntimeApplyState = (queryRoot) => {
110
+ if (!queryRoot || typeof queryRoot !== 'object') return
111
+ runtimeApplyStateByRoot.delete(queryRoot)
112
+ }
113
+
114
+ const addMediaQueryChangeListener = (queryList, listener) => {
115
+ if (!queryList || typeof listener !== 'function') return null
116
+ if (typeof queryList.addEventListener === 'function' && typeof queryList.removeEventListener === 'function') {
117
+ queryList.addEventListener('change', listener)
118
+ return () => {
119
+ try {
120
+ queryList.removeEventListener('change', listener)
121
+ } catch (e) {}
122
+ }
123
+ }
124
+ if (typeof queryList.addListener === 'function' && typeof queryList.removeListener === 'function') {
125
+ queryList.addListener(listener)
126
+ return () => {
127
+ try {
128
+ queryList.removeListener(listener)
129
+ } catch (e) {}
130
+ }
131
+ }
132
+ return null
133
+ }
134
+
135
+ const getRuntimeSupportedPayloadVersions = (options = {}) => {
136
+ const set = new Set()
137
+ if (options.strictVersion === true) {
138
+ for (const v of customHighlightPayloadSupportedVersions) set.add(v)
139
+ }
140
+ if (Number.isSafeInteger(options.supportedVersion)) set.add(options.supportedVersion)
141
+ if (Array.isArray(options.supportedVersions)) {
142
+ for (const v of options.supportedVersions) {
143
+ if (Number.isSafeInteger(v)) set.add(v)
144
+ }
145
+ }
146
+ if (set.size === 0) return null
147
+ return set
148
+ }
149
+
150
+ const resolveRuntimeColorScheme = (options = {}, doc) => {
151
+ const raw = String(options.colorScheme || 'auto').trim().toLowerCase()
152
+ const mode = validColorSchemeSet.has(raw) ? raw : 'auto'
153
+ if (mode === 'light' || mode === 'dark') return mode
154
+ const view = doc && doc.defaultView
155
+ const matchMediaFn = (options.matchMedia && typeof options.matchMedia === 'function')
156
+ ? options.matchMedia
157
+ : (view && typeof view.matchMedia === 'function' ? view.matchMedia.bind(view) : null)
158
+ if (!matchMediaFn) return 'light'
159
+ try {
160
+ return matchMediaFn('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
161
+ } catch (e) {
162
+ return 'light'
163
+ }
164
+ }
165
+
166
+ const resolvePayloadVariantView = (payload, colorScheme) => {
167
+ const baseScopes = Array.isArray(payload && payload.scopes) ? payload.scopes : []
168
+ const baseRanges = Array.isArray(payload && payload.ranges) ? payload.ranges : []
169
+ const baseScopeStyles = Array.isArray(payload && payload.scopeStyles) ? payload.scopeStyles : null
170
+ const variants = payload && payload.variants && typeof payload.variants === 'object' ? payload.variants : null
171
+ if (!variants) {
172
+ return {
173
+ scopes: baseScopes,
174
+ ranges: baseRanges,
175
+ scopeStyles: baseScopeStyles,
176
+ variantKey: '',
177
+ }
178
+ }
179
+ const availableKeys = Object.keys(variants)
180
+ if (!availableKeys.length) {
181
+ return {
182
+ scopes: baseScopes,
183
+ ranges: baseRanges,
184
+ scopeStyles: baseScopeStyles,
185
+ variantKey: '',
186
+ }
187
+ }
188
+ let key = ''
189
+ if (colorScheme && variants[colorScheme]) key = colorScheme
190
+ if (!key && typeof payload.defaultVariant === 'string' && variants[payload.defaultVariant]) key = payload.defaultVariant
191
+ if (!key) key = availableKeys[0]
192
+ const variant = variants[key] && typeof variants[key] === 'object' ? variants[key] : {}
193
+ const scopes = Array.isArray(variant.scopes) ? variant.scopes : baseScopes
194
+ const ranges = Array.isArray(variant.ranges) ? variant.ranges : baseRanges
195
+ const scopeStyles = Array.isArray(variant.scopeStyles) ? variant.scopeStyles : baseScopeStyles
196
+ return {
197
+ scopes,
198
+ ranges,
199
+ scopeStyles,
200
+ variantKey: key || '',
201
+ }
202
+ }
203
+
204
+ const getRuntimeScopeName = (scopeName, variantKey) => {
205
+ const base = sanitizeHighlightName(scopeName)
206
+ if (!variantKey) return base
207
+ const suffix = sanitizeHighlightName(variantKey)
208
+ return `${base}-v-${suffix}`
209
+ }
210
+
211
+ const getCodeBoundaryRefs = (codeEl) => {
212
+ if (!codeEl || typeof codeEl !== 'object') return { first: null, last: null }
213
+ if ('firstChild' in codeEl || 'lastChild' in codeEl) {
214
+ return { first: codeEl.firstChild || null, last: codeEl.lastChild || null }
215
+ }
216
+ const textNodes = Array.isArray(codeEl.__textNodes) ? codeEl.__textNodes : null
217
+ if (!textNodes || textNodes.length === 0) return { first: null, last: null }
218
+ return { first: textNodes[0], last: textNodes[textNodes.length - 1] }
219
+ }
220
+
221
+ const createSegmentPositionResolver = (segments) => {
222
+ const count = Array.isArray(segments) ? segments.length : 0
223
+ let hint = 0
224
+ let lastOffset = -1
225
+ return (offset) => {
226
+ if (!Number.isSafeInteger(offset) || count === 0) return null
227
+ if (offset >= lastOffset && hint < count) {
228
+ let i = hint
229
+ while (i < count) {
230
+ const seg = segments[i]
231
+ if (offset < seg.start) break
232
+ if (offset <= seg.end) {
233
+ hint = i
234
+ lastOffset = offset
235
+ return { node: seg.node, offset: offset - seg.start }
236
+ }
237
+ i++
238
+ }
239
+ }
240
+ const pos = findTextPosition(segments, offset)
241
+ if (pos) lastOffset = offset
242
+ return pos
243
+ }
244
+ }
245
+
246
+ const applyCustomHighlights = (root, options = {}) => {
247
+ const scopeRoot = root || (typeof document !== 'undefined' ? document : null)
248
+ if (!scopeRoot) return { appliedBlocks: 0, appliedRanges: 0, reason: 'no-document' }
249
+ if (typeof CSS === 'undefined' || !CSS.highlights || typeof Highlight === 'undefined' || typeof Range === 'undefined') {
250
+ return { appliedBlocks: 0, appliedRanges: 0, reason: 'api-unsupported' }
251
+ }
252
+
253
+ const queryRoot = scopeRoot.querySelectorAll ? scopeRoot : (scopeRoot.ownerDocument || document)
254
+ const doc = queryRoot.ownerDocument || (typeof queryRoot.createElement === 'function' ? queryRoot : (typeof document !== 'undefined' ? document : null))
255
+ if (!doc) return { appliedBlocks: 0, appliedRanges: 0, reason: 'no-document' }
256
+ const onRuntimeDiagnostic = (typeof options.onRuntimeDiagnostic === 'function') ? options.onRuntimeDiagnostic : null
257
+ const emitDiag = onRuntimeDiagnostic
258
+ ? (data) => {
259
+ try {
260
+ onRuntimeDiagnostic(data)
261
+ } catch (e) {}
262
+ }
263
+ : null
264
+ const codeNodes = queryRoot.querySelectorAll(customHighlightCodeSelector)
265
+ if (!codeNodes || codeNodes.length === 0) {
266
+ clearRuntimeApplyState(queryRoot)
267
+ return { appliedBlocks: 0, appliedRanges: 0 }
268
+ }
269
+ const payloadMap = options.payloadMap && typeof options.payloadMap === 'object'
270
+ ? options.payloadMap
271
+ : getPayloadMapFromScript(queryRoot, options.dataScriptId || customHighlightDataScriptId)
272
+ const resolvedColorScheme = resolveRuntimeColorScheme(options, doc)
273
+ const supportedVersions = getRuntimeSupportedPayloadVersions(options)
274
+ const incremental = options.incremental === true
275
+ const payloadDigestByBlock = incremental ? new Map() : null
276
+ const payloadStringifyCache = incremental ? new Map() : null
277
+ let blockRefs = null
278
+ let digest = null
279
+ let prevBlockCache = null
280
+ let prevScopeMetaMap = null
281
+ let nextBlockCache = null
282
+ let nextScopeMetaMap = null
283
+ if (incremental) {
284
+ blockRefs = new Map()
285
+ for (const codeEl of codeNodes) {
286
+ const pre = codeEl.parentElement
287
+ if (!pre) continue
288
+ const blockId = getCustomHighlightBlockId(pre)
289
+ if (!blockId) continue
290
+ blockRefs.set(blockId, codeEl)
291
+ }
292
+ digest = (options.payloadDigest || createPayloadDigest(payloadMap, payloadStringifyCache)) + '|ids=' + Array.from(blockRefs.keys()).sort().join(',') + `|scheme=${resolvedColorScheme}`
293
+ const prevState = getRuntimeApplyState(queryRoot)
294
+ if (prevState && prevState.blockCache instanceof Map) prevBlockCache = prevState.blockCache
295
+ if (prevState && prevState.scopeMetaMap instanceof Map) prevScopeMetaMap = prevState.scopeMetaMap
296
+ nextBlockCache = new Map()
297
+ nextScopeMetaMap = new Map()
298
+ if (prevState && prevState.digest === digest) {
299
+ const prevRefs = prevState.refs
300
+ if (prevRefs && prevRefs.size === blockRefs.size) {
301
+ let sameRefs = true
302
+ for (const [blockId, codeEl] of blockRefs) {
303
+ if (prevRefs.get(blockId) !== codeEl) {
304
+ sameRefs = false
305
+ break
306
+ }
307
+ }
308
+ if (sameRefs) {
309
+ if (emitDiag) emitDiag({ type: 'runtime-skip', reason: 'unchanged' })
310
+ return { appliedBlocks: 0, appliedRanges: 0, skipped: true, reason: 'unchanged' }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ let styleTag = null
316
+ let insertedScopeStyleMap = null
317
+ if (!incremental) clearAppliedHighlightNames(queryRoot)
318
+ const allScopeRanges = new Map()
319
+ let appliedBlocks = 0
320
+ let appliedRanges = 0
321
+ let pendingCss = ''
322
+
323
+ for (const codeEl of codeNodes) {
324
+ const pre = codeEl.parentElement
325
+ if (!pre) continue
326
+ const blockId = getCustomHighlightBlockId(pre)
327
+ if (!blockId) {
328
+ if (emitDiag) emitDiag({ type: 'block-skip', reason: 'missing-block-id' })
329
+ continue
330
+ }
331
+ const payload = payloadMap[blockId]
332
+ if (!payload || !Array.isArray(payload.ranges) || !Array.isArray(payload.scopes)) {
333
+ pre.removeAttribute(customHighlightAppliedAttr)
334
+ if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'missing-payload' })
335
+ continue
336
+ }
337
+ if (supportedVersions && !supportedVersions.has(payload.v)) {
338
+ pre.removeAttribute(customHighlightAppliedAttr)
339
+ if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'unsupported-version', version: payload.v })
340
+ continue
341
+ }
342
+ const activePayload = resolvePayloadVariantView(payload, resolvedColorScheme)
343
+ if (activePayload.ranges.length === 0 || activePayload.scopes.length === 0) {
344
+ pre.removeAttribute(customHighlightAppliedAttr)
345
+ if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'empty-payload' })
346
+ continue
347
+ }
348
+
349
+ let blockPayloadDigest = ''
350
+ if (payloadDigestByBlock) {
351
+ blockPayloadDigest = payloadDigestByBlock.get(blockId)
352
+ if (blockPayloadDigest === undefined) {
353
+ const prevCached = prevBlockCache ? prevBlockCache.get(blockId) : null
354
+ if (prevCached && prevCached.payloadRef === payload && prevCached.variantKey === activePayload.variantKey) {
355
+ blockPayloadDigest = prevCached.payloadDigest || ''
356
+ } else {
357
+ blockPayloadDigest = getCachedPayloadString(payload, payloadStringifyCache) + `|variant=${activePayload.variantKey || ''}`
358
+ }
359
+ payloadDigestByBlock.set(blockId, blockPayloadDigest)
360
+ }
361
+ }
362
+ const textSnapshot = typeof codeEl.textContent === 'string' ? codeEl.textContent : null
363
+ const boundaryRefs = getCodeBoundaryRefs(codeEl)
364
+ const cached = prevBlockCache ? prevBlockCache.get(blockId) : null
365
+ if (cached &&
366
+ cached.codeEl === codeEl &&
367
+ cached.payloadDigest === blockPayloadDigest &&
368
+ cached.textSnapshot === textSnapshot &&
369
+ cached.firstRef === boundaryRefs.first &&
370
+ cached.lastRef === boundaryRefs.last &&
371
+ cached.scopeRanges instanceof Map) {
372
+ for (const [runtimeName, ranges] of cached.scopeRanges) {
373
+ let target = allScopeRanges.get(runtimeName)
374
+ if (!target) {
375
+ target = []
376
+ allScopeRanges.set(runtimeName, target)
377
+ }
378
+ for (let i = 0; i < ranges.length; i++) target.push(ranges[i])
379
+ }
380
+ if (cached.appliedAttrText) {
381
+ pre.setAttribute(customHighlightAppliedAttr, cached.appliedAttrText)
382
+ appliedBlocks++
383
+ } else {
384
+ pre.removeAttribute(customHighlightAppliedAttr)
385
+ }
386
+ appliedRanges += cached.appliedRangeCount || 0
387
+ if (nextScopeMetaMap && cached.scopeMeta instanceof Map) {
388
+ for (const [runtimeName, scopeMeta] of cached.scopeMeta) {
389
+ nextScopeMetaMap.set(runtimeName, (nextScopeMetaMap.get(runtimeName) || '') + `|${blockId}:${scopeMeta}`)
390
+ }
391
+ }
392
+ if (nextBlockCache) nextBlockCache.set(blockId, cached)
393
+ continue
394
+ }
395
+
396
+ const segments = collectTextSegments(codeEl)
397
+ const resolveTextPos = createSegmentPositionResolver(segments)
398
+ const textLength = segments.length ? segments[segments.length - 1].end : 0
399
+ if (Number.isSafeInteger(payload.textLength) && payload.textLength !== textLength) {
400
+ pre.removeAttribute(customHighlightAppliedAttr)
401
+ if (emitDiag) {
402
+ emitDiag({
403
+ type: 'block-skip',
404
+ blockId,
405
+ reason: 'text-length-mismatch',
406
+ payloadTextLength: payload.textLength,
407
+ actualTextLength: textLength,
408
+ })
409
+ }
410
+ continue
411
+ }
412
+ const appliedNames = new Set()
413
+ const appliedNameList = []
414
+ const scopeRuntimeNames = new Array(activePayload.scopes.length)
415
+ const scopeStyles = Array.isArray(activePayload.scopeStyles) ? activePayload.scopeStyles : null
416
+ const blockScopeRanges = nextBlockCache ? new Map() : null
417
+ const blockScopeMetaParts = nextBlockCache ? new Map() : null
418
+ let blockAppliedRangeCount = 0
419
+
420
+ for (let tupleIdx = 0; tupleIdx < activePayload.ranges.length; tupleIdx++) {
421
+ const tuple = activePayload.ranges[tupleIdx]
422
+ if (!Array.isArray(tuple) || tuple.length < 3) {
423
+ if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'invalid-tuple', tupleIndex: tupleIdx })
424
+ continue
425
+ }
426
+ const scopeIdx = tuple[0]
427
+ const start = tuple[1]
428
+ const end = tuple[2]
429
+ if (!Number.isSafeInteger(scopeIdx) || !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || end <= start) {
430
+ if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'invalid-range', tupleIndex: tupleIdx })
431
+ continue
432
+ }
433
+ if (scopeIdx < 0 || scopeIdx >= activePayload.scopes.length) {
434
+ if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'invalid-scope-index', tupleIndex: tupleIdx, scopeIdx })
435
+ continue
436
+ }
437
+ const scopeName = activePayload.scopes[scopeIdx]
438
+ if (!scopeName) {
439
+ if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'missing-scope', tupleIndex: tupleIdx, scopeIdx })
440
+ continue
441
+ }
442
+ const startPos = resolveTextPos(start)
443
+ const endPos = resolveTextPos(end)
444
+ if (!startPos || !endPos) {
445
+ if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'range-out-of-bounds', tupleIndex: tupleIdx, start, end })
446
+ continue
447
+ }
448
+ let range
449
+ try {
450
+ range = doc.createRange()
451
+ range.setStart(startPos.node, startPos.offset)
452
+ range.setEnd(endPos.node, endPos.offset)
453
+ } catch (e) {
454
+ if (emitDiag) emitDiag({ type: 'range-skip', blockId, reason: 'range-create-failed', tupleIndex: tupleIdx })
455
+ continue
456
+ }
457
+ let runtimeName = scopeRuntimeNames[scopeIdx]
458
+ if (!runtimeName) {
459
+ runtimeName = getRuntimeScopeName(scopeName, activePayload.variantKey)
460
+ scopeRuntimeNames[scopeIdx] = runtimeName
461
+ if (scopeStyles) {
462
+ const css = styleToHighlightCss(scopeStyles[scopeIdx])
463
+ if (css) {
464
+ if (!insertedScopeStyleMap) insertedScopeStyleMap = getRuntimeInsertedScopeStyles(doc)
465
+ if (insertedScopeStyleMap.get(runtimeName) !== css) {
466
+ if (!styleTag) styleTag = ensureHighlightStyleTag(doc)
467
+ if (styleTag) {
468
+ pendingCss += `\n::highlight(${runtimeName}){${css};}`
469
+ insertedScopeStyleMap.set(runtimeName, css)
470
+ }
471
+ }
472
+ }
473
+ }
474
+ }
475
+ let scopeRanges = allScopeRanges.get(runtimeName)
476
+ if (!scopeRanges) {
477
+ scopeRanges = []
478
+ allScopeRanges.set(runtimeName, scopeRanges)
479
+ }
480
+ scopeRanges.push(range)
481
+ if (blockScopeRanges) {
482
+ let blockRanges = blockScopeRanges.get(runtimeName)
483
+ if (!blockRanges) {
484
+ blockRanges = []
485
+ blockScopeRanges.set(runtimeName, blockRanges)
486
+ }
487
+ blockRanges.push(range)
488
+ }
489
+ if (blockScopeMetaParts) {
490
+ let rangeMeta = blockScopeMetaParts.get(runtimeName)
491
+ if (!rangeMeta) {
492
+ rangeMeta = []
493
+ blockScopeMetaParts.set(runtimeName, rangeMeta)
494
+ }
495
+ rangeMeta.push(`${start}-${end}`)
496
+ }
497
+ appliedRanges++
498
+ blockAppliedRangeCount++
499
+ if (!appliedNames.has(runtimeName)) {
500
+ appliedNames.add(runtimeName)
501
+ appliedNameList.push(runtimeName)
502
+ }
503
+ }
504
+
505
+ const appliedAttrText = appliedNameList.join(' ')
506
+ if (appliedNames.size > 0) {
507
+ pre.setAttribute(customHighlightAppliedAttr, appliedAttrText)
508
+ appliedBlocks++
509
+ } else {
510
+ pre.removeAttribute(customHighlightAppliedAttr)
511
+ if (emitDiag) emitDiag({ type: 'block-skip', blockId, reason: 'no-valid-ranges' })
512
+ }
513
+ let blockScopeMeta = null
514
+ if (blockScopeMetaParts) {
515
+ blockScopeMeta = new Map()
516
+ for (const [runtimeName, parts] of blockScopeMetaParts) {
517
+ const scopeMeta = parts.join(',')
518
+ blockScopeMeta.set(runtimeName, scopeMeta)
519
+ if (nextScopeMetaMap) nextScopeMetaMap.set(runtimeName, (nextScopeMetaMap.get(runtimeName) || '') + `|${blockId}:${scopeMeta}`)
520
+ }
521
+ }
522
+ if (nextBlockCache) {
523
+ nextBlockCache.set(blockId, {
524
+ codeEl,
525
+ payloadRef: payload,
526
+ variantKey: activePayload.variantKey || '',
527
+ payloadDigest: blockPayloadDigest,
528
+ textSnapshot,
529
+ firstRef: boundaryRefs.first,
530
+ lastRef: boundaryRefs.last,
531
+ scopeRanges: blockScopeRanges || new Map(),
532
+ scopeMeta: blockScopeMeta || new Map(),
533
+ appliedRangeCount: blockAppliedRangeCount,
534
+ appliedAttrText,
535
+ })
536
+ }
537
+ }
538
+ if (incremental) {
539
+ const prevMap = prevScopeMetaMap || new Map()
540
+ const nextMap = nextScopeMetaMap || new Map()
541
+ const names = new Set()
542
+ for (const name of prevMap.keys()) names.add(name)
543
+ for (const name of nextMap.keys()) names.add(name)
544
+ for (const runtimeName of names) {
545
+ const prevMeta = prevMap.get(runtimeName)
546
+ const nextMeta = nextMap.get(runtimeName)
547
+ if (nextMeta == null) {
548
+ CSS.highlights.delete(runtimeName)
549
+ continue
550
+ }
551
+ if (nextMeta === prevMeta) continue
552
+ const scopeRanges = allScopeRanges.get(runtimeName)
553
+ if (!scopeRanges || scopeRanges.length === 0) {
554
+ CSS.highlights.delete(runtimeName)
555
+ continue
556
+ }
557
+ CSS.highlights.set(runtimeName, new Highlight(...scopeRanges))
558
+ }
559
+ } else {
560
+ for (const [runtimeName, scopeRanges] of allScopeRanges) {
561
+ if (!scopeRanges.length) continue
562
+ CSS.highlights.set(runtimeName, new Highlight(...scopeRanges))
563
+ }
564
+ }
565
+ if (pendingCss && styleTag) styleTag.textContent += pendingCss
566
+ if (incremental) {
567
+ setRuntimeApplyState(queryRoot, { digest, refs: blockRefs, blockCache: nextBlockCache, scopeMetaMap: nextScopeMetaMap })
568
+ } else {
569
+ clearRuntimeApplyState(queryRoot)
570
+ }
571
+ return { appliedBlocks, appliedRanges }
572
+ }
573
+
574
+ const observeCustomHighlights = (root, options = {}) => {
575
+ const scopeRoot = root || (typeof document !== 'undefined' ? document : null)
576
+ if (!scopeRoot) {
577
+ return {
578
+ supported: false,
579
+ reason: 'no-document',
580
+ observe: () => 0,
581
+ disconnect: () => {},
582
+ applyNow: () => ({ appliedBlocks: 0, appliedRanges: 0, reason: 'no-document' }),
583
+ }
584
+ }
585
+
586
+ const ObserverCtor =
587
+ (typeof options.IntersectionObserver === 'function' && options.IntersectionObserver) ||
588
+ ((typeof IntersectionObserver !== 'undefined') ? IntersectionObserver : null)
589
+ if (typeof ObserverCtor !== 'function') {
590
+ return {
591
+ supported: false,
592
+ reason: 'observer-unsupported',
593
+ observe: () => 0,
594
+ disconnect: () => {},
595
+ applyNow: () => applyCustomHighlights(scopeRoot, options.applyOptions || {}),
596
+ }
597
+ }
598
+
599
+ const queryRoot = scopeRoot.querySelectorAll ? scopeRoot : (scopeRoot.ownerDocument || document)
600
+ if (!queryRoot || typeof queryRoot.querySelectorAll !== 'function') {
601
+ return {
602
+ supported: false,
603
+ reason: 'invalid-root',
604
+ observe: () => 0,
605
+ disconnect: () => {},
606
+ applyNow: () => ({ appliedBlocks: 0, appliedRanges: 0, reason: 'invalid-root' }),
607
+ }
608
+ }
609
+
610
+ const selector = String(options.selector || customHighlightPreSelector)
611
+ const applyOptions = options.applyOptions && typeof options.applyOptions === 'object' ? options.applyOptions : {}
612
+ const runtimeApplyOptions = (typeof options.matchMedia === 'function' && typeof applyOptions.matchMedia !== 'function')
613
+ ? Object.assign({}, applyOptions, { matchMedia: options.matchMedia })
614
+ : applyOptions
615
+ const once = options.once !== false
616
+ let isDisposed = false
617
+ let isObserverStopped = false
618
+ let hasApplied = false
619
+ let removeColorSchemeListener = null
620
+ const applyNow = () => {
621
+ const result = applyCustomHighlights(queryRoot, runtimeApplyOptions)
622
+ hasApplied = true
623
+ return result
624
+ }
625
+
626
+ const observer = new ObserverCtor((entries) => {
627
+ if (isDisposed || !Array.isArray(entries)) return
628
+ let shouldApply = false
629
+ for (let i = 0; i < entries.length; i++) {
630
+ const entry = entries[i]
631
+ if (entry && (entry.isIntersecting || entry.intersectionRatio > 0)) {
632
+ shouldApply = true
633
+ break
634
+ }
635
+ }
636
+ if (!shouldApply) return
637
+ applyNow()
638
+ if (once) {
639
+ observer.disconnect()
640
+ isObserverStopped = true
641
+ }
642
+ }, {
643
+ root: options.root || null,
644
+ rootMargin: options.rootMargin || '200px 0px',
645
+ threshold: options.threshold == null ? 0 : options.threshold,
646
+ })
647
+
648
+ const observe = () => {
649
+ if (isDisposed || isObserverStopped) return 0
650
+ const targets = queryRoot.querySelectorAll(selector)
651
+ for (const target of targets) observer.observe(target)
652
+ return targets.length
653
+ }
654
+ const disconnect = () => {
655
+ if (isDisposed) return
656
+ if (removeColorSchemeListener) {
657
+ removeColorSchemeListener()
658
+ removeColorSchemeListener = null
659
+ }
660
+ observer.disconnect()
661
+ isObserverStopped = true
662
+ isDisposed = true
663
+ }
664
+
665
+ const watchColorScheme = options.watchColorScheme === true
666
+ const autoMode = String(applyOptions.colorScheme || 'auto').trim().toLowerCase() === 'auto'
667
+ if (watchColorScheme && autoMode) {
668
+ const doc = queryRoot.ownerDocument || (typeof queryRoot.createElement === 'function' ? queryRoot : null)
669
+ const view = doc && doc.defaultView
670
+ const matchMediaFn = (options.matchMedia && typeof options.matchMedia === 'function')
671
+ ? options.matchMedia
672
+ : (view && typeof view.matchMedia === 'function' ? view.matchMedia.bind(view) : null)
673
+ if (matchMediaFn) {
674
+ let queryList = null
675
+ try {
676
+ queryList = matchMediaFn('(prefers-color-scheme: dark)')
677
+ } catch (e) {
678
+ queryList = null
679
+ }
680
+ if (queryList) {
681
+ removeColorSchemeListener = addMediaQueryChangeListener(queryList, () => {
682
+ if (isDisposed || !hasApplied) return
683
+ applyNow()
684
+ })
685
+ }
686
+ }
687
+ }
688
+
689
+ if (options.autoStart !== false) observe()
690
+ return {
691
+ supported: true,
692
+ observe,
693
+ disconnect,
694
+ applyNow,
695
+ observer,
696
+ }
697
+ }
698
+
699
+ const clearCustomHighlights = (root) => {
700
+ const scopeRoot = root || (typeof document !== 'undefined' ? document : null)
701
+ if (!scopeRoot) return { cleared: 0, reason: 'no-document' }
702
+ if (typeof CSS === 'undefined' || !CSS.highlights) return { cleared: 0, reason: 'api-unsupported' }
703
+ const queryRoot = scopeRoot.querySelectorAll ? scopeRoot : (scopeRoot.ownerDocument || document)
704
+ clearRuntimeApplyState(queryRoot)
705
+ return { cleared: clearAppliedHighlightNames(queryRoot) }
706
+ }
707
+
708
+ export {
709
+ applyCustomHighlights,
710
+ clearCustomHighlights,
711
+ observeCustomHighlights,
712
+ }