@lynx-js/web-elements 0.12.0 → 0.12.2

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,1233 @@
1
+ import { __esDecorate, __runInitializers } from "tslib";
2
+ import { boostedQueueMicrotask, genDomGetter, registerAttributeHandler, } from '../../element-reactive/index.js';
3
+ let MarkdownItLoaded;
4
+ let DOMPurifyLoaded;
5
+ let depsLoading;
6
+ let depsLoaded = false;
7
+ let depsError;
8
+ const loadDeps = () => {
9
+ if (depsLoaded)
10
+ return Promise.resolve();
11
+ if (depsLoading)
12
+ return depsLoading;
13
+ depsLoading = import(
14
+ /* webpackChunkName: "xmarkdown-deps" */
15
+ './XMarkdownDeps.js').then((deps) => {
16
+ MarkdownItLoaded = deps.MarkdownIt;
17
+ DOMPurifyLoaded = deps.createDOMPurify;
18
+ depsLoaded = true;
19
+ }).catch((err) => {
20
+ depsError = err;
21
+ });
22
+ return depsLoading;
23
+ };
24
+ const escapeHtml = (value) => value
25
+ .replaceAll('&', '&')
26
+ .replaceAll('<', '&lt;')
27
+ .replaceAll('>', '&gt;');
28
+ const escapeHtmlAttr = (value) => value
29
+ .replaceAll('&', '&amp;')
30
+ .replaceAll('<', '&lt;')
31
+ .replaceAll('>', '&gt;')
32
+ .replaceAll('"', '&quot;')
33
+ .replaceAll('\'', '&#39;');
34
+ const decodeHtmlEntities = (value) => value
35
+ .replaceAll('&quot;', '"')
36
+ .replaceAll('&#34;', '"')
37
+ .replaceAll('&apos;', '\'')
38
+ .replaceAll('&#39;', '\'')
39
+ .replaceAll('&amp;', '&')
40
+ .replaceAll('&lt;', '<')
41
+ .replaceAll('&gt;', '>');
42
+ const normalizeClassList = (value) => {
43
+ const parts = value.trim().split(/\s+/).filter(Boolean);
44
+ const safe = parts.filter((part) => /^[A-Za-z0-9_-]+$/.test(part));
45
+ return safe.join(' ');
46
+ };
47
+ const sanitizeAllowedHtml = (value) => {
48
+ if (!value)
49
+ return value;
50
+ if (value.includes('<!--')
51
+ || value.includes('<!')
52
+ || value.includes('<?')
53
+ || value.includes('<%')) {
54
+ return escapeHtml(value);
55
+ }
56
+ const tagNameRe = /<\s*\/?\s*([A-Za-z][\w-]*)\b[^>]*>/g;
57
+ let match = null;
58
+ let hasTag = false;
59
+ while ((match = tagNameRe.exec(value))) {
60
+ hasTag = true;
61
+ const name = (match[1] ?? '').toLowerCase();
62
+ if (name !== 'span' && name !== 'p') {
63
+ return escapeHtml(value);
64
+ }
65
+ }
66
+ if (!hasTag) {
67
+ return value.includes('<') ? escapeHtml(value) : value;
68
+ }
69
+ const openTagRe = /<(span|p)\b([^>]*?)(\/?)>/gi;
70
+ return value.replace(openTagRe, (_m, rawName, rawAttrs, rawSelfClose) => {
71
+ const name = String(rawName).toLowerCase();
72
+ const attrs = decodeHtmlEntities(String(rawAttrs ?? ''));
73
+ const classMatch = /\bclass\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(attrs);
74
+ const rawClass = (classMatch?.[1] ?? classMatch?.[2] ?? classMatch?.[3])
75
+ ?? '';
76
+ const normalized = normalizeClassList(rawClass);
77
+ const classAttr = normalized
78
+ ? ` class="${escapeHtmlAttr(normalized)}"`
79
+ : '';
80
+ const selfClose = String(rawSelfClose ?? '') === '/';
81
+ if (selfClose)
82
+ return `<${name}${classAttr}></${name}>`;
83
+ return `<${name}${classAttr}>`;
84
+ });
85
+ };
86
+ const applyRawHtmlPolicy = (tokens) => {
87
+ for (const token of tokens) {
88
+ if (token?.type === 'html_inline' || token?.type === 'html_block') {
89
+ token.content = sanitizeAllowedHtml(String(token.content ?? ''));
90
+ }
91
+ if (Array.isArray(token?.children)) {
92
+ applyRawHtmlPolicy(token.children);
93
+ }
94
+ }
95
+ };
96
+ const renderMarkdown = (parser, content) => {
97
+ const env = {};
98
+ const tokens = parser.parse(content, env);
99
+ applyRawHtmlPolicy(tokens);
100
+ return parser.renderer.render(tokens, parser.options, env);
101
+ };
102
+ let markdownParser = null;
103
+ let markdownParserError;
104
+ let htmlSanitizer = null;
105
+ const getMarkdownParser = () => {
106
+ if (markdownParser || markdownParserError)
107
+ return markdownParser;
108
+ if (!MarkdownItLoaded)
109
+ return null;
110
+ try {
111
+ markdownParser = new MarkdownItLoaded({
112
+ html: true,
113
+ linkify: true,
114
+ });
115
+ markdownParser.enable(['table', 'strikethrough']);
116
+ }
117
+ catch (error) {
118
+ markdownParserError = error;
119
+ }
120
+ return markdownParser;
121
+ };
122
+ const getHtmlSanitizer = () => {
123
+ if (htmlSanitizer)
124
+ return htmlSanitizer;
125
+ if (!DOMPurifyLoaded)
126
+ return null;
127
+ htmlSanitizer = DOMPurifyLoaded(window);
128
+ return htmlSanitizer;
129
+ };
130
+ const sanitizeHtml = (value) => {
131
+ const sanitizer = getHtmlSanitizer();
132
+ if (!sanitizer)
133
+ return value;
134
+ return sanitizer.sanitize(value, {
135
+ USE_PROFILES: { html: true },
136
+ });
137
+ };
138
+ const emptySelectionDetail = () => ({
139
+ start: -1,
140
+ end: -1,
141
+ direction: 'forward',
142
+ });
143
+ const getComposedRange = (selection, shadowRoot) => {
144
+ const getComposedRanges = selection
145
+ .getComposedRanges;
146
+ if (!shadowRoot || typeof getComposedRanges !== 'function')
147
+ return null;
148
+ try {
149
+ return getComposedRanges.call(selection, {
150
+ shadowRoots: [shadowRoot],
151
+ })[0] ?? null;
152
+ }
153
+ catch {
154
+ try {
155
+ return getComposedRanges.call(selection, shadowRoot)[0] ?? null;
156
+ }
157
+ catch {
158
+ return null;
159
+ }
160
+ }
161
+ };
162
+ const createRangeByOffsets = (doc, root, start, end) => {
163
+ const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT);
164
+ let pos = 0;
165
+ let startNode = null;
166
+ let startOffset = 0;
167
+ let endNode = null;
168
+ let endOffset = 0;
169
+ let node = walker.nextNode();
170
+ while (node) {
171
+ const len = node.nodeValue?.length ?? 0;
172
+ if (!startNode && pos + len >= start) {
173
+ startNode = node;
174
+ startOffset = start - pos;
175
+ }
176
+ if (pos + len >= end) {
177
+ endNode = node;
178
+ endOffset = end - pos;
179
+ break;
180
+ }
181
+ pos += len;
182
+ node = walker.nextNode();
183
+ }
184
+ if (!startNode)
185
+ return null;
186
+ if (!endNode) {
187
+ endNode = startNode;
188
+ endOffset = startOffset;
189
+ }
190
+ const range = doc.createRange();
191
+ range.setStart(startNode, Math.max(0, Math.min(startOffset, startNode.length)));
192
+ range.setEnd(endNode, Math.max(0, Math.min(endOffset, endNode.length)));
193
+ return range;
194
+ };
195
+ const isSelectionNodeInsideHost = (dom, shadowRoot, node) => !!node
196
+ && (node === dom
197
+ || node === shadowRoot
198
+ || dom.contains(node)
199
+ || !!shadowRoot?.contains(node));
200
+ const getRangeInRoot = (doc, dom, root, shadowRoot, selection) => {
201
+ if (!selection)
202
+ return null;
203
+ const sourceRange = getComposedRange(selection, shadowRoot)
204
+ ?? (selection.rangeCount > 0 ? selection.getRangeAt(0) : null);
205
+ if (!sourceRange)
206
+ return null;
207
+ if (!root.contains(sourceRange.startContainer)
208
+ || !root.contains(sourceRange.endContainer)) {
209
+ if (!isSelectionNodeInsideHost(dom, shadowRoot, sourceRange.startContainer)
210
+ || !isSelectionNodeInsideHost(dom, shadowRoot, sourceRange.endContainer)
211
+ || !isSelectionNodeInsideHost(dom, shadowRoot, selection.anchorNode)
212
+ || !isSelectionNodeInsideHost(dom, shadowRoot, selection.focusNode)) {
213
+ return null;
214
+ }
215
+ const selectedText = selection.toString();
216
+ if (!selectedText) {
217
+ return null;
218
+ }
219
+ const start = root.textContent?.indexOf(selectedText) ?? -1;
220
+ if (start < 0)
221
+ return null;
222
+ return createRangeByOffsets(doc, root, start, start + selectedText.length);
223
+ }
224
+ const range = doc.createRange();
225
+ range.setStart(sourceRange.startContainer, sourceRange.startOffset);
226
+ range.setEnd(sourceRange.endContainer, sourceRange.endOffset);
227
+ return range;
228
+ };
229
+ const getBoundaryOffset = (doc, root, node, offset) => {
230
+ if (!node || !root.contains(node))
231
+ return null;
232
+ const range = doc.createRange();
233
+ range.selectNodeContents(root);
234
+ try {
235
+ range.setEnd(node, offset);
236
+ }
237
+ catch {
238
+ return null;
239
+ }
240
+ return range.toString().length;
241
+ };
242
+ const getSelectionDetail = (dom, root) => {
243
+ const doc = dom.ownerDocument;
244
+ const shadowRoot = dom.shadowRoot;
245
+ const shadowSelection = shadowRoot?.getSelection?.()
246
+ ?? null;
247
+ const documentSelection = doc.getSelection();
248
+ const selectionCandidates = [
249
+ shadowSelection,
250
+ documentSelection,
251
+ ].filter((selection, index, selections) => !!selection && selections.indexOf(selection) === index);
252
+ let range = null;
253
+ let selection = null;
254
+ for (const candidate of selectionCandidates) {
255
+ const nextRange = getRangeInRoot(doc, dom, root, shadowRoot, candidate);
256
+ if (nextRange && !nextRange.collapsed) {
257
+ range = nextRange;
258
+ selection = candidate;
259
+ break;
260
+ }
261
+ }
262
+ if (!range || !selection) {
263
+ return emptySelectionDetail();
264
+ }
265
+ const start = getBoundaryOffset(doc, root, range.startContainer, range.startOffset);
266
+ const end = getBoundaryOffset(doc, root, range.endContainer, range.endOffset);
267
+ if (start === null || end === null || start === end) {
268
+ return emptySelectionDetail();
269
+ }
270
+ const anchor = getBoundaryOffset(doc, root, selection.anchorNode, selection.anchorOffset);
271
+ const focus = getBoundaryOffset(doc, root, selection.focusNode, selection.focusOffset);
272
+ return {
273
+ start: Math.min(start, end),
274
+ end: Math.max(start, end),
275
+ direction: anchor !== null && focus !== null && anchor > focus
276
+ ? 'backward'
277
+ : 'forward',
278
+ };
279
+ };
280
+ const preprocessInlineView = (html) => html.replace(/src\s*=\s*"inlineview:\/\/([^"]+)"/g, (_m, id) => `data-inlineview="${id}"`);
281
+ const unitlessCssProperties = new Set([
282
+ 'font-weight',
283
+ 'opacity',
284
+ 'z-index',
285
+ 'flex',
286
+ 'flex-grow',
287
+ 'flex-shrink',
288
+ 'order',
289
+ ]);
290
+ const selectorMap = {
291
+ normalText: '.markdown-body',
292
+ link: '.markdown-body a',
293
+ inlineCode: '.markdown-body code:not(pre code)',
294
+ codeBlock: ['.markdown-body pre', '.markdown-body pre code'],
295
+ h1: '.markdown-body h1',
296
+ h2: '.markdown-body h2',
297
+ h3: '.markdown-body h3',
298
+ h4: '.markdown-body h4',
299
+ h5: '.markdown-body h5',
300
+ h6: '.markdown-body h6',
301
+ quote: '.markdown-body blockquote',
302
+ orderedList: '.markdown-body ol',
303
+ unorderedList: '.markdown-body ul',
304
+ listItem: '.markdown-body li',
305
+ image: '.markdown-body img',
306
+ span: '.markdown-body span',
307
+ p: '.markdown-body p',
308
+ // Extended selectors
309
+ table: '.markdown-body table',
310
+ thead: '.markdown-body thead',
311
+ tbody: '.markdown-body tbody',
312
+ tr: '.markdown-body tr',
313
+ th: '.markdown-body th',
314
+ td: '.markdown-body td',
315
+ imageCaption: '.markdown-body .md-image-caption',
316
+ };
317
+ const normalizeColor = (value) => {
318
+ const hex = value.trim();
319
+ if (/^[0-9a-fA-F]{6,8}$/.test(hex)) {
320
+ return `#${hex}`;
321
+ }
322
+ return value;
323
+ };
324
+ const camelToKebab = (value) => value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
325
+ const toCssValue = (property, value) => {
326
+ if (value === null || value === undefined)
327
+ return null;
328
+ if (typeof value === 'number') {
329
+ return unitlessCssProperties.has(property) ? `${value}` : `${value}px`;
330
+ }
331
+ if (typeof value === 'string') {
332
+ if (property.includes('color')) {
333
+ return normalizeColor(value);
334
+ }
335
+ return value;
336
+ }
337
+ return null;
338
+ };
339
+ export const parseMarkdownStyle = (value) => {
340
+ if (!value)
341
+ return {};
342
+ try {
343
+ const parsed = JSON.parse(value);
344
+ if (parsed && typeof parsed === 'object') {
345
+ return parsed;
346
+ }
347
+ }
348
+ catch {
349
+ return {};
350
+ }
351
+ return {};
352
+ };
353
+ export const serializeMarkdownStyle = (value) => {
354
+ if (value === null || value === undefined)
355
+ return null;
356
+ return typeof value === 'string' ? value : JSON.stringify(value);
357
+ };
358
+ const buildMarkdownStyleCss = (style) => {
359
+ const rules = [];
360
+ for (const [key, properties] of Object.entries(style)) {
361
+ if (!properties || typeof properties !== 'object')
362
+ continue;
363
+ let selectors = [];
364
+ if (key.startsWith('.') || key.startsWith('#')) {
365
+ selectors = [`.markdown-body ${key}`];
366
+ }
367
+ else if (selectorMap[key]) {
368
+ selectors = Array.isArray(selectorMap[key])
369
+ ? selectorMap[key]
370
+ : [selectorMap[key]];
371
+ }
372
+ else {
373
+ continue;
374
+ }
375
+ const declarations = Object.entries(properties)
376
+ .map(([property, value]) => {
377
+ const cssProperty = camelToKebab(property);
378
+ const cssValue = toCssValue(cssProperty, value);
379
+ return cssValue ? `${cssProperty}: ${cssValue};` : null;
380
+ })
381
+ .filter(Boolean)
382
+ .join('');
383
+ if (!declarations)
384
+ continue;
385
+ for (const selector of selectors) {
386
+ rules.push(`${selector} {${declarations}}`);
387
+ }
388
+ }
389
+ return rules.join('\n');
390
+ };
391
+ let XMarkdownAttributes = (() => {
392
+ let _instanceExtraInitializers = [];
393
+ let __handleContent_decorators;
394
+ let __handleMarkdownEffect_decorators;
395
+ let __handleMarkdownStyle_decorators;
396
+ let __handleContentId_decorators;
397
+ let __handleTextSelection_decorators;
398
+ let __handleAnimationType_decorators;
399
+ let __handleAnimationVelocity_decorators;
400
+ let __handleAnimationPaused_decorators;
401
+ let __handleInitialAnimationStep_decorators;
402
+ let __handleContentComplete_decorators;
403
+ let __handleTypewriterDynamicHeight_decorators;
404
+ let __handleTypewriterHeightTransition_decorators;
405
+ let __handleTextMaxline_decorators;
406
+ let __handleContentRange_decorators;
407
+ return class XMarkdownAttributes {
408
+ static {
409
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
410
+ __handleContent_decorators = [registerAttributeHandler('content', true)];
411
+ __handleMarkdownEffect_decorators = [registerAttributeHandler('markdown-effect', true)];
412
+ __handleMarkdownStyle_decorators = [registerAttributeHandler('markdown-style', true)];
413
+ __handleContentId_decorators = [registerAttributeHandler('content-id', true)];
414
+ __handleTextSelection_decorators = [registerAttributeHandler('text-selection', true)];
415
+ __handleAnimationType_decorators = [registerAttributeHandler('animation-type', true)];
416
+ __handleAnimationVelocity_decorators = [registerAttributeHandler('animation-velocity', true)];
417
+ __handleAnimationPaused_decorators = [registerAttributeHandler('animation-paused', true)];
418
+ __handleInitialAnimationStep_decorators = [registerAttributeHandler('initial-animation-step', true)];
419
+ __handleContentComplete_decorators = [registerAttributeHandler('content-complete', true)];
420
+ __handleTypewriterDynamicHeight_decorators = [registerAttributeHandler('typewriter-dynamic-height', true)];
421
+ __handleTypewriterHeightTransition_decorators = [registerAttributeHandler('typewriter-height-transition-duration', true)];
422
+ __handleTextMaxline_decorators = [registerAttributeHandler('text-maxline', true)];
423
+ __handleContentRange_decorators = [registerAttributeHandler('content-range', true)];
424
+ __esDecorate(this, null, __handleContent_decorators, { kind: "method", name: "_handleContent", static: false, private: false, access: { has: obj => "_handleContent" in obj, get: obj => obj._handleContent }, metadata: _metadata }, null, _instanceExtraInitializers);
425
+ __esDecorate(this, null, __handleMarkdownEffect_decorators, { kind: "method", name: "_handleMarkdownEffect", static: false, private: false, access: { has: obj => "_handleMarkdownEffect" in obj, get: obj => obj._handleMarkdownEffect }, metadata: _metadata }, null, _instanceExtraInitializers);
426
+ __esDecorate(this, null, __handleMarkdownStyle_decorators, { kind: "method", name: "_handleMarkdownStyle", static: false, private: false, access: { has: obj => "_handleMarkdownStyle" in obj, get: obj => obj._handleMarkdownStyle }, metadata: _metadata }, null, _instanceExtraInitializers);
427
+ __esDecorate(this, null, __handleContentId_decorators, { kind: "method", name: "_handleContentId", static: false, private: false, access: { has: obj => "_handleContentId" in obj, get: obj => obj._handleContentId }, metadata: _metadata }, null, _instanceExtraInitializers);
428
+ __esDecorate(this, null, __handleTextSelection_decorators, { kind: "method", name: "_handleTextSelection", static: false, private: false, access: { has: obj => "_handleTextSelection" in obj, get: obj => obj._handleTextSelection }, metadata: _metadata }, null, _instanceExtraInitializers);
429
+ __esDecorate(this, null, __handleAnimationType_decorators, { kind: "method", name: "_handleAnimationType", static: false, private: false, access: { has: obj => "_handleAnimationType" in obj, get: obj => obj._handleAnimationType }, metadata: _metadata }, null, _instanceExtraInitializers);
430
+ __esDecorate(this, null, __handleAnimationVelocity_decorators, { kind: "method", name: "_handleAnimationVelocity", static: false, private: false, access: { has: obj => "_handleAnimationVelocity" in obj, get: obj => obj._handleAnimationVelocity }, metadata: _metadata }, null, _instanceExtraInitializers);
431
+ __esDecorate(this, null, __handleAnimationPaused_decorators, { kind: "method", name: "_handleAnimationPaused", static: false, private: false, access: { has: obj => "_handleAnimationPaused" in obj, get: obj => obj._handleAnimationPaused }, metadata: _metadata }, null, _instanceExtraInitializers);
432
+ __esDecorate(this, null, __handleInitialAnimationStep_decorators, { kind: "method", name: "_handleInitialAnimationStep", static: false, private: false, access: { has: obj => "_handleInitialAnimationStep" in obj, get: obj => obj._handleInitialAnimationStep }, metadata: _metadata }, null, _instanceExtraInitializers);
433
+ __esDecorate(this, null, __handleContentComplete_decorators, { kind: "method", name: "_handleContentComplete", static: false, private: false, access: { has: obj => "_handleContentComplete" in obj, get: obj => obj._handleContentComplete }, metadata: _metadata }, null, _instanceExtraInitializers);
434
+ __esDecorate(this, null, __handleTypewriterDynamicHeight_decorators, { kind: "method", name: "_handleTypewriterDynamicHeight", static: false, private: false, access: { has: obj => "_handleTypewriterDynamicHeight" in obj, get: obj => obj._handleTypewriterDynamicHeight }, metadata: _metadata }, null, _instanceExtraInitializers);
435
+ __esDecorate(this, null, __handleTypewriterHeightTransition_decorators, { kind: "method", name: "_handleTypewriterHeightTransition", static: false, private: false, access: { has: obj => "_handleTypewriterHeightTransition" in obj, get: obj => obj._handleTypewriterHeightTransition }, metadata: _metadata }, null, _instanceExtraInitializers);
436
+ __esDecorate(this, null, __handleTextMaxline_decorators, { kind: "method", name: "_handleTextMaxline", static: false, private: false, access: { has: obj => "_handleTextMaxline" in obj, get: obj => obj._handleTextMaxline }, metadata: _metadata }, null, _instanceExtraInitializers);
437
+ __esDecorate(this, null, __handleContentRange_decorators, { kind: "method", name: "_handleContentRange", static: false, private: false, access: { has: obj => "_handleContentRange" in obj, get: obj => obj._handleContentRange }, metadata: _metadata }, null, _instanceExtraInitializers);
438
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
439
+ }
440
+ static observedAttributes = [
441
+ 'content',
442
+ 'markdown-style',
443
+ 'content-id',
444
+ // Optional: allow enabling text selection within content
445
+ 'text-selection',
446
+ // Typewriter / animation
447
+ 'animation-type',
448
+ 'animation-velocity',
449
+ 'animation-paused',
450
+ 'initial-animation-step',
451
+ 'content-complete',
452
+ 'typewriter-dynamic-height',
453
+ 'typewriter-height-transition-duration',
454
+ // Overflow
455
+ 'text-maxline',
456
+ // Range rendering
457
+ 'content-range',
458
+ // Effect
459
+ 'markdown-effect',
460
+ ];
461
+ #dom = __runInitializers(this, _instanceExtraInitializers);
462
+ #root = genDomGetter(() => this.#dom.shadowRoot, '#markdown-root');
463
+ #style = genDomGetter(() => this.#dom.shadowRoot, '#markdown-style');
464
+ #pendingRender = false;
465
+ #content = '';
466
+ #renderedContent = '';
467
+ #contentId;
468
+ #eventsAttached = false;
469
+ #selectionEventAttached = false;
470
+ #appendRemainder = '';
471
+ #appendFlushTimer;
472
+ #appendFlushDelay = 60;
473
+ #currentMarkdownCss = '';
474
+ #textSelection = false;
475
+ #selectionSyncTimer;
476
+ #lastSelectionSignature;
477
+ #truncationConfig = null;
478
+ #truncationEl;
479
+ // Typewriter state
480
+ #animationType = 'none';
481
+ #animationVelocity = 40; // chars per second
482
+ #animationTimer;
483
+ #animationStep = 0;
484
+ #animationStarted = false;
485
+ #animationPaused = false;
486
+ #contentComplete = true;
487
+ #maxAnimationStep = 0;
488
+ #typewriterDynamicHeight = false;
489
+ #typewriterHeightTransition = 0; // seconds
490
+ #typewriterCursorConfig = null;
491
+ #typewriterCursorEl;
492
+ // Overflow state
493
+ #textMaxline = 0;
494
+ #overflowEmitted = false;
495
+ // Range rendering: inclusive [start, end)
496
+ #contentRange;
497
+ // Markdown effect
498
+ #markdownEffect = null;
499
+ constructor(dom) {
500
+ this.#dom = dom;
501
+ }
502
+ connectedCallback() {
503
+ this.#ensureEvents();
504
+ }
505
+ dispose() {
506
+ this.#clearAppendFlushTimer();
507
+ this.#stopTypewriterTimer();
508
+ clearTimeout(this.#selectionSyncTimer);
509
+ this.#selectionSyncTimer = undefined;
510
+ if (this.#eventsAttached) {
511
+ const root = this.#root();
512
+ root.removeEventListener('click', this.#handleClick);
513
+ this.#eventsAttached = false;
514
+ }
515
+ if (this.#selectionEventAttached) {
516
+ document.removeEventListener('selectionchange', this.#handleSelectionChange);
517
+ document.removeEventListener('mouseup', this.#handleSelectionGestureEnd);
518
+ document.removeEventListener('touchend', this.#handleSelectionGestureEnd);
519
+ document.removeEventListener('keyup', this.#handleSelectionGestureEnd);
520
+ this.#selectionEventAttached = false;
521
+ }
522
+ }
523
+ #ensureEvents() {
524
+ if (this.#eventsAttached)
525
+ return;
526
+ const root = this.#root();
527
+ root.addEventListener('click', this.#handleClick);
528
+ this.#eventsAttached = true;
529
+ if (!this.#selectionEventAttached) {
530
+ document.addEventListener('selectionchange', this.#handleSelectionChange);
531
+ document.addEventListener('mouseup', this.#handleSelectionGestureEnd);
532
+ document.addEventListener('touchend', this.#handleSelectionGestureEnd);
533
+ document.addEventListener('keyup', this.#handleSelectionGestureEnd);
534
+ this.#selectionEventAttached = true;
535
+ }
536
+ }
537
+ #handleClick = (event) => {
538
+ const target = event.target;
539
+ if (!target)
540
+ return;
541
+ const root = this.#root();
542
+ const anchor = target.closest('a');
543
+ if (anchor && root.contains(anchor)) {
544
+ event.preventDefault();
545
+ this.#dom.dispatchEvent(new CustomEvent('bindlink', {
546
+ detail: {
547
+ url: anchor.getAttribute('href') ?? '',
548
+ content: anchor.textContent ?? '',
549
+ contentId: this.#contentId,
550
+ },
551
+ bubbles: true,
552
+ composed: true,
553
+ }));
554
+ return;
555
+ }
556
+ const image = target.closest('img');
557
+ if (image && root.contains(image)) {
558
+ this.#dom.dispatchEvent(new CustomEvent('bindimageTap', {
559
+ detail: {
560
+ url: image.getAttribute('src') ?? '',
561
+ contentId: this.#contentId,
562
+ },
563
+ bubbles: true,
564
+ composed: true,
565
+ }));
566
+ }
567
+ };
568
+ #emitSelectionChange() {
569
+ if (!this.#textSelection)
570
+ return;
571
+ try {
572
+ const root = this.#root();
573
+ const detail = getSelectionDetail(this.#dom, root);
574
+ const signature = `${detail.start}:${detail.end}:${detail.direction}`;
575
+ if (signature === this.#lastSelectionSignature)
576
+ return;
577
+ this.#lastSelectionSignature = signature;
578
+ this.#dom.dispatchEvent(new CustomEvent('bindselectionchange', {
579
+ detail,
580
+ bubbles: true,
581
+ composed: true,
582
+ }));
583
+ }
584
+ catch {
585
+ /* noop */
586
+ }
587
+ }
588
+ #scheduleSelectionSync = () => {
589
+ clearTimeout(this.#selectionSyncTimer);
590
+ this.#selectionSyncTimer = setTimeout(() => {
591
+ this.#selectionSyncTimer = undefined;
592
+ this.#emitSelectionChange();
593
+ }, 0);
594
+ };
595
+ #handleSelectionChange = () => {
596
+ this.#emitSelectionChange();
597
+ this.#scheduleSelectionSync();
598
+ };
599
+ #handleSelectionGestureEnd = () => {
600
+ this.#emitSelectionChange();
601
+ this.#scheduleSelectionSync();
602
+ };
603
+ #scheduleRender() {
604
+ if (this.#pendingRender)
605
+ return;
606
+ this.#pendingRender = true;
607
+ if (!depsLoaded && !depsError) {
608
+ loadDeps().then(() => {
609
+ this.#pendingRender = false;
610
+ this.#render();
611
+ });
612
+ return;
613
+ }
614
+ boostedQueueMicrotask(() => {
615
+ this.#pendingRender = false;
616
+ this.#render();
617
+ });
618
+ }
619
+ #render() {
620
+ const root = this.#root();
621
+ if (!this.#content) {
622
+ this.#resetTypewriterState();
623
+ root.innerHTML = '';
624
+ this.#renderedContent = '';
625
+ this.#appendRemainder = '';
626
+ this.#clearAppendFlushTimer();
627
+ return;
628
+ }
629
+ // Typewriter animation takes precedence over incremental append
630
+ if (this.#animationType === 'typewriter') {
631
+ this.#renderTypewriter(root);
632
+ return;
633
+ }
634
+ const parser = getMarkdownParser();
635
+ if (!parser) {
636
+ const content = this.#contentRange
637
+ ? this.#content.slice(this.#contentRange[0], this.#contentRange[1])
638
+ : this.#content;
639
+ root.textContent = content;
640
+ this.#renderedContent = content;
641
+ this.#appendRemainder = '';
642
+ this.#clearAppendFlushTimer();
643
+ return;
644
+ }
645
+ if (this.#canAppendIncrementally()) {
646
+ this.#appendIncrementally(root);
647
+ return;
648
+ }
649
+ const content = this.#contentRange
650
+ ? this.#content.slice(this.#contentRange[0], this.#contentRange[1])
651
+ : this.#content;
652
+ const rendered = renderMarkdown(parser, content);
653
+ root.innerHTML = sanitizeHtml(preprocessInlineView(rendered));
654
+ this.#injectInlineViews(root);
655
+ this.#renderedContent = content;
656
+ this.#appendRemainder = '';
657
+ this.#clearAppendFlushTimer();
658
+ this.#dispatchParseEnd();
659
+ this.#applyPostRenderPolicies(root);
660
+ }
661
+ #canAppendIncrementally() {
662
+ if (!this.#renderedContent)
663
+ return false;
664
+ if (!this.#content.startsWith(this.#renderedContent))
665
+ return false;
666
+ if (this.#content.length === this.#renderedContent.length)
667
+ return false;
668
+ return true;
669
+ }
670
+ #appendIncrementally(root) {
671
+ const delta = this.#content.slice(this.#renderedContent.length);
672
+ if (!delta)
673
+ return;
674
+ const lastNewlineIndex = delta.lastIndexOf('\n');
675
+ if (lastNewlineIndex === -1) {
676
+ this.#appendRemainder = delta;
677
+ this.#scheduleAppendFlush();
678
+ return;
679
+ }
680
+ const chunk = delta.slice(0, lastNewlineIndex + 1);
681
+ if (chunk) {
682
+ const parser = getMarkdownParser();
683
+ if (!parser)
684
+ return;
685
+ const html = renderMarkdown(parser, chunk);
686
+ this.#appendHtml(root, html);
687
+ this.#renderedContent += chunk;
688
+ }
689
+ this.#appendRemainder = delta.slice(lastNewlineIndex + 1);
690
+ if (this.#appendRemainder) {
691
+ this.#scheduleAppendFlush();
692
+ }
693
+ else {
694
+ this.#clearAppendFlushTimer();
695
+ }
696
+ }
697
+ #scheduleAppendFlush() {
698
+ this.#clearAppendFlushTimer();
699
+ this.#appendFlushTimer = setTimeout(() => {
700
+ this.#appendFlushTimer = undefined;
701
+ this.#flushAppendRemainder();
702
+ }, this.#appendFlushDelay);
703
+ }
704
+ #clearAppendFlushTimer() {
705
+ if (this.#appendFlushTimer) {
706
+ clearTimeout(this.#appendFlushTimer);
707
+ this.#appendFlushTimer = undefined;
708
+ }
709
+ }
710
+ #resetTypewriterState() {
711
+ this.#stopTypewriterTimer();
712
+ this.#animationStarted = false;
713
+ this.#animationStep = 0;
714
+ this.#maxAnimationStep = 0;
715
+ this.#removeTypewriterCursor();
716
+ }
717
+ #flushAppendRemainder() {
718
+ if (!this.#appendRemainder)
719
+ return;
720
+ if (!this.#content.startsWith(this.#renderedContent))
721
+ return;
722
+ const expectedLength = this.#renderedContent.length
723
+ + this.#appendRemainder.length;
724
+ if (this.#content.length !== expectedLength)
725
+ return;
726
+ const root = this.#root();
727
+ const parser = getMarkdownParser();
728
+ if (!parser)
729
+ return;
730
+ const html = renderMarkdown(parser, this.#appendRemainder);
731
+ this.#appendHtml(root, html);
732
+ this.#renderedContent += this.#appendRemainder;
733
+ this.#appendRemainder = '';
734
+ this.#dispatchParseEnd();
735
+ this.#applyPostRenderPolicies(this.#root());
736
+ }
737
+ #appendHtml(root, html) {
738
+ const template = document.createElement('template');
739
+ template.innerHTML = sanitizeHtml(preprocessInlineView(html));
740
+ root.append(template.content);
741
+ this.#applyPostRenderPolicies(root);
742
+ this.#injectInlineViews(root);
743
+ }
744
+ #applyMarkdownEffect(root) {
745
+ if (!this.#markdownEffect || this.#markdownEffect.type !== 'text-mask') {
746
+ return;
747
+ }
748
+ if (this.#contentComplete !== false
749
+ && this.#animationStep >= this.#maxAnimationStep) {
750
+ return;
751
+ }
752
+ const { color, rangeStart, rangeEnd } = this.#markdownEffect;
753
+ if (!color || typeof rangeStart !== 'number') {
754
+ return;
755
+ }
756
+ const existingEffects = root.querySelectorAll('.md-text-mask-effect');
757
+ existingEffects.forEach((effect) => {
758
+ const content = effect.querySelector('.md-text-mask-effect-content');
759
+ const parent = effect.parentNode;
760
+ if (!content || !parent)
761
+ return;
762
+ while (content.firstChild) {
763
+ parent.insertBefore(content.firstChild, effect);
764
+ }
765
+ parent.removeChild(effect);
766
+ });
767
+ // Collect all text nodes
768
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
769
+ const textNodes = [];
770
+ while (walker.nextNode()) {
771
+ const node = walker.currentNode;
772
+ // Ignore text nodes inside the typewriter cursor or truncation marker
773
+ const parent = node.parentElement;
774
+ if (parent === root
775
+ && (node.nodeValue?.trim() ?? '') === '') {
776
+ continue;
777
+ }
778
+ if (parent
779
+ && (parent.closest('.md-typewriter-cursor')
780
+ || parent.closest('.md-truncation')
781
+ || parent.closest('.md-text-mask-effect-overlay'))) {
782
+ continue;
783
+ }
784
+ textNodes.push(node);
785
+ }
786
+ const totalLength = textNodes.reduce((sum, node) => sum + (node.nodeValue?.length ?? 0), 0);
787
+ const normalizedStart = rangeStart < 0
788
+ ? totalLength + rangeStart
789
+ : rangeStart;
790
+ const normalizedEndRaw = typeof rangeEnd === 'number'
791
+ ? (rangeEnd < 0 ? totalLength + rangeEnd : rangeEnd)
792
+ : normalizedStart;
793
+ const effectStart = Math.max(0, Math.min(totalLength, normalizedStart));
794
+ const effectEnd = Math.max(effectStart, Math.min(totalLength, normalizedEndRaw + 1));
795
+ if (effectEnd <= effectStart) {
796
+ return;
797
+ }
798
+ let cursor = 0;
799
+ const nodesToWrap = [];
800
+ textNodes.forEach((textNode) => {
801
+ const textLength = textNode.nodeValue?.length ?? 0;
802
+ const nodeStart = cursor;
803
+ const nodeEnd = cursor + textLength;
804
+ const startOffset = Math.max(effectStart, nodeStart) - nodeStart;
805
+ const endOffset = Math.min(effectEnd, nodeEnd) - nodeStart;
806
+ if (startOffset < endOffset) {
807
+ nodesToWrap.push({
808
+ node: textNode,
809
+ startOffset,
810
+ endOffset,
811
+ });
812
+ }
813
+ cursor = nodeEnd;
814
+ });
815
+ if (nodesToWrap.length === 0) {
816
+ return;
817
+ }
818
+ // Wrap them
819
+ const appliedEffects = [];
820
+ for (const { node, startOffset, endOffset } of nodesToWrap.reverse()) {
821
+ let targetNode = node;
822
+ if (startOffset > 0) {
823
+ targetNode = node.splitText(startOffset);
824
+ }
825
+ const targetLength = endOffset - startOffset;
826
+ if (targetLength < (targetNode.nodeValue?.length ?? 0)) {
827
+ targetNode.splitText(targetLength);
828
+ }
829
+ const originalText = targetNode.nodeValue ?? '';
830
+ const span = document.createElement('span');
831
+ span.className = 'md-text-mask-effect';
832
+ const content = document.createElement('span');
833
+ content.className = 'md-text-mask-effect-content';
834
+ const overlay = document.createElement('span');
835
+ overlay.className = 'md-text-mask-effect-overlay';
836
+ overlay.setAttribute('aria-hidden', 'true');
837
+ overlay.textContent = originalText;
838
+ overlay.style.background = color;
839
+ const parent = targetNode.parentNode;
840
+ if (parent) {
841
+ parent.insertBefore(span, targetNode);
842
+ content.appendChild(targetNode);
843
+ span.append(content, overlay);
844
+ appliedEffects.unshift({
845
+ content,
846
+ overlay,
847
+ });
848
+ }
849
+ }
850
+ if (appliedEffects.length === 0) {
851
+ return;
852
+ }
853
+ const segmentWidths = appliedEffects.map(({ content }) => content.getBoundingClientRect().width);
854
+ const totalEffectWidth = segmentWidths.reduce((sum, width) => sum + width, 0);
855
+ if (totalEffectWidth <= 0) {
856
+ return;
857
+ }
858
+ let offsetX = 0;
859
+ appliedEffects.forEach(({ overlay }, index) => {
860
+ overlay.style.backgroundRepeat = 'no-repeat';
861
+ overlay.style.backgroundSize = `${totalEffectWidth}px 100%`;
862
+ overlay.style.backgroundPosition = `${-offsetX}px 0`;
863
+ offsetX += segmentWidths[index] ?? 0;
864
+ });
865
+ }
866
+ #dispatchParseEnd() {
867
+ this.#dom.dispatchEvent(new CustomEvent('bindparseEnd', {
868
+ detail: { id: this.#contentId },
869
+ bubbles: true,
870
+ composed: true,
871
+ }));
872
+ }
873
+ #applyPostRenderPolicies(root) {
874
+ this.#applyMarkdownEffect(root);
875
+ // Overflow detection for line clamp
876
+ if (this.#textMaxline > 0) {
877
+ // give layout a tick
878
+ queueMicrotask(() => {
879
+ try {
880
+ const overflow = root.scrollHeight > root.clientHeight + 1;
881
+ if (overflow && !this.#overflowEmitted) {
882
+ this.#overflowEmitted = true;
883
+ this.#dom.dispatchEvent(new CustomEvent('bindoverflow', {
884
+ detail: { type: 'ellipsis' },
885
+ bubbles: true,
886
+ composed: true,
887
+ }));
888
+ }
889
+ }
890
+ catch {
891
+ /* noop */
892
+ }
893
+ });
894
+ }
895
+ else {
896
+ this.#overflowEmitted = false;
897
+ }
898
+ // Dynamic height transition for typewriter
899
+ if (this.#animationType === 'typewriter' && this.#typewriterDynamicHeight) {
900
+ const dur = this.#typewriterHeightTransition;
901
+ if (dur > 0) {
902
+ root.style.transition = `height ${dur}s ease`;
903
+ }
904
+ }
905
+ else {
906
+ root.style.transition = '';
907
+ }
908
+ // Truncation tail marker
909
+ if (this.#textMaxline > 0) {
910
+ if (!this.#ensureTruncationMarker(root)) {
911
+ this.#removeTruncationMarker();
912
+ }
913
+ }
914
+ else {
915
+ this.#removeTruncationMarker();
916
+ }
917
+ }
918
+ #renderTypewriter(root) {
919
+ // Prepare max steps considering content range
920
+ const [start, end] = this.#contentRange
921
+ ? this.#contentRange
922
+ : [0, this.#content.length];
923
+ this.#maxAnimationStep = Math.max(0, end - start);
924
+ // Start if not started
925
+ if (!this.#animationStarted) {
926
+ this.#animationStarted = true;
927
+ this.#dom.dispatchEvent(new CustomEvent('binddrawStart', { bubbles: true, composed: true }));
928
+ if (!this.#animationPaused) {
929
+ this.#startTypewriterTimer();
930
+ }
931
+ }
932
+ const visible = this.#content.slice(start, start + this.#animationStep);
933
+ const parser = getMarkdownParser();
934
+ if (!parser) {
935
+ root.textContent = visible;
936
+ }
937
+ else {
938
+ root.innerHTML = sanitizeHtml(preprocessInlineView(renderMarkdown(parser, visible)));
939
+ this.#injectInlineViews(root);
940
+ }
941
+ this.#appendTypewriterCursor(root);
942
+ this.#applyPostRenderPolicies(root);
943
+ if (this.#animationStep >= this.#maxAnimationStep) {
944
+ this.#stopTypewriterTimer();
945
+ if (this.#contentComplete !== false) {
946
+ this.#dom.dispatchEvent(new CustomEvent('binddrawEnd', { bubbles: true, composed: true }));
947
+ }
948
+ }
949
+ }
950
+ #startTypewriterTimer() {
951
+ this.#stopTypewriterTimer();
952
+ const interval = Math.max(10, Math.floor(1000 / Math.max(1, this.#animationVelocity)));
953
+ this.#animationTimer = setInterval(() => {
954
+ if (this.#animationStep < this.#maxAnimationStep) {
955
+ this.#animationStep += 1;
956
+ this.#dom.dispatchEvent(new CustomEvent('bindanimationStep', {
957
+ detail: {
958
+ animationStep: this.#animationStep,
959
+ maxAnimationStep: this.#maxAnimationStep,
960
+ },
961
+ bubbles: true,
962
+ composed: true,
963
+ }));
964
+ // Re-render slice only
965
+ const root = this.#root();
966
+ this.#renderTypewriter(root);
967
+ }
968
+ else {
969
+ this.#stopTypewriterTimer();
970
+ }
971
+ }, interval);
972
+ }
973
+ #stopTypewriterTimer() {
974
+ if (this.#animationTimer) {
975
+ clearInterval(this.#animationTimer);
976
+ this.#animationTimer = undefined;
977
+ }
978
+ }
979
+ #applyMarkdownStyle(value) {
980
+ const parsed = parseMarkdownStyle(value);
981
+ this.#truncationConfig = parsed.truncation ?? null;
982
+ this.#typewriterCursorConfig = parsed.typewriterCursor ?? null;
983
+ const { truncation, typewriterCursor, ...cssParts } = parsed;
984
+ this.#currentMarkdownCss = buildMarkdownStyleCss(cssParts);
985
+ this.#updateStyleTag();
986
+ }
987
+ #updateStyleTag() {
988
+ const styleTag = this.#style();
989
+ let css = this.#currentMarkdownCss || '';
990
+ if (this.#textSelection) {
991
+ css += '\n.markdown-body { user-select: text; }';
992
+ }
993
+ if (this.#textMaxline > 0) {
994
+ css +=
995
+ `\n.markdown-body { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: ${this.#textMaxline}; overflow: hidden; }`;
996
+ }
997
+ styleTag.textContent = css;
998
+ }
999
+ _handleContent(newVal) {
1000
+ const content = newVal ?? '';
1001
+ const contentChanged = content !== this.#content;
1002
+ this.#content = content;
1003
+ if (contentChanged && this.#animationType === 'typewriter') {
1004
+ this.#resetTypewriterState();
1005
+ }
1006
+ this.#scheduleRender();
1007
+ }
1008
+ _handleMarkdownEffect(newVal) {
1009
+ try {
1010
+ this.#markdownEffect = newVal ? JSON.parse(newVal) : null;
1011
+ }
1012
+ catch {
1013
+ this.#markdownEffect = null;
1014
+ }
1015
+ this.#scheduleRender();
1016
+ }
1017
+ _handleMarkdownStyle(newVal) {
1018
+ this.#applyMarkdownStyle(newVal);
1019
+ }
1020
+ _handleContentId(newVal) {
1021
+ this.#contentId = newVal ?? undefined;
1022
+ }
1023
+ _handleTextSelection(newVal) {
1024
+ // Treat presence and value !== 'false' as true
1025
+ this.#textSelection = !!(newVal && newVal !== 'false');
1026
+ if (!this.#textSelection) {
1027
+ clearTimeout(this.#selectionSyncTimer);
1028
+ this.#selectionSyncTimer = undefined;
1029
+ this.#lastSelectionSignature = undefined;
1030
+ }
1031
+ this.#updateStyleTag();
1032
+ }
1033
+ _handleAnimationType(newVal) {
1034
+ const v = (newVal ?? 'none').toLowerCase();
1035
+ this.#animationType = v === 'typewriter' ? 'typewriter' : 'none';
1036
+ this.#animationStarted = false;
1037
+ this.#stopTypewriterTimer();
1038
+ this.#scheduleRender();
1039
+ }
1040
+ _handleAnimationVelocity(newVal) {
1041
+ const n = Number(newVal);
1042
+ if (!Number.isNaN(n) && n > 0)
1043
+ this.#animationVelocity = n;
1044
+ if (this.#animationType === 'typewriter'
1045
+ && !this.#animationPaused
1046
+ && this.#animationTimer) {
1047
+ this.#startTypewriterTimer();
1048
+ }
1049
+ }
1050
+ _handleAnimationPaused(newVal) {
1051
+ this.#animationPaused = !!(newVal && newVal !== 'false');
1052
+ if (this.#animationPaused) {
1053
+ this.#stopTypewriterTimer();
1054
+ return;
1055
+ }
1056
+ if (this.#animationType === 'typewriter'
1057
+ && this.#animationStarted
1058
+ && this.#animationStep < this.#maxAnimationStep) {
1059
+ this.#startTypewriterTimer();
1060
+ }
1061
+ }
1062
+ _handleInitialAnimationStep(newVal) {
1063
+ const n = Number(newVal);
1064
+ this.#animationStep = Number.isNaN(n) ? 0 : Math.max(0, Math.floor(n));
1065
+ }
1066
+ _handleContentComplete(newVal) {
1067
+ this.#contentComplete = !(newVal === 'false');
1068
+ }
1069
+ _handleTypewriterDynamicHeight(newVal) {
1070
+ this.#typewriterDynamicHeight = !!(newVal && newVal !== 'false');
1071
+ }
1072
+ _handleTypewriterHeightTransition(newVal) {
1073
+ const n = Number(newVal);
1074
+ this.#typewriterHeightTransition = Number.isNaN(n) ? 0 : Math.max(0, n);
1075
+ }
1076
+ _handleTextMaxline(newVal) {
1077
+ const n = Number(newVal);
1078
+ this.#textMaxline = Number.isNaN(n) ? 0 : Math.max(0, Math.floor(n));
1079
+ this.#updateStyleTag();
1080
+ }
1081
+ _handleContentRange(newVal) {
1082
+ if (!newVal) {
1083
+ this.#contentRange = undefined;
1084
+ }
1085
+ else {
1086
+ try {
1087
+ const parsed = JSON.parse(newVal);
1088
+ if (Array.isArray(parsed) && parsed.length === 2) {
1089
+ const s = Number(parsed[0]);
1090
+ const e = Number(parsed[1]);
1091
+ if (!Number.isNaN(s) && !Number.isNaN(e) && e >= s) {
1092
+ this.#contentRange = [Math.max(0, s), Math.max(0, e)];
1093
+ }
1094
+ }
1095
+ }
1096
+ catch {
1097
+ // Support "start,end" form
1098
+ const parts = newVal.split(',');
1099
+ if (parts.length === 2) {
1100
+ const s = Number(parts[0]);
1101
+ const e = Number(parts[1]);
1102
+ if (!Number.isNaN(s) && !Number.isNaN(e) && e >= s) {
1103
+ this.#contentRange = [Math.max(0, s), Math.max(0, e)];
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ this.#scheduleRender();
1109
+ }
1110
+ #injectInlineViews(root) {
1111
+ try {
1112
+ const imgs = Array.from(root.querySelectorAll('img'));
1113
+ for (const img of imgs) {
1114
+ const inlineId = img.getAttribute('data-inlineview');
1115
+ if (inlineId) {
1116
+ const id = inlineId;
1117
+ const container = document.createElement('span');
1118
+ container.className = 'md-inline-view';
1119
+ const slot = document.createElement('slot');
1120
+ slot.name = id;
1121
+ container.appendChild(slot);
1122
+ img.replaceWith(container);
1123
+ }
1124
+ }
1125
+ }
1126
+ catch {
1127
+ /* noop */
1128
+ }
1129
+ }
1130
+ #ensureTruncationMarker(root) {
1131
+ const overflow = root.scrollHeight > root.clientHeight + 1;
1132
+ if (!overflow)
1133
+ return false;
1134
+ if (!this.#truncationEl) {
1135
+ const span = document.createElement('span');
1136
+ span.className = 'md-truncation';
1137
+ this.#truncationEl = span;
1138
+ root.appendChild(span);
1139
+ }
1140
+ const cfg = this.#truncationConfig || {};
1141
+ const span = this.#truncationEl;
1142
+ span.textContent = '';
1143
+ span.innerHTML = '';
1144
+ if (cfg.truncationType === 'view' && cfg.content) {
1145
+ const view = this.#dom.querySelector(`#${CSS.escape(String(cfg.content))}`);
1146
+ if (view)
1147
+ span.appendChild(view);
1148
+ else
1149
+ span.textContent = '…';
1150
+ }
1151
+ else if (cfg.content) {
1152
+ span.textContent = String(cfg.content);
1153
+ }
1154
+ else {
1155
+ span.textContent = '…';
1156
+ }
1157
+ return true;
1158
+ }
1159
+ #removeTruncationMarker() {
1160
+ if (this.#truncationEl && this.#truncationEl.parentNode) {
1161
+ this.#truncationEl.parentNode.removeChild(this.#truncationEl);
1162
+ }
1163
+ this.#truncationEl = undefined;
1164
+ }
1165
+ #appendTypewriterCursor(root) {
1166
+ if (this.#contentComplete !== false
1167
+ && this.#animationStep >= this.#maxAnimationStep) {
1168
+ this.#removeTypewriterCursor();
1169
+ return;
1170
+ }
1171
+ const cfg = this.#typewriterCursorConfig || {};
1172
+ const customCursor = cfg.customCursor;
1173
+ if (customCursor === 'none') {
1174
+ this.#removeTypewriterCursor();
1175
+ return;
1176
+ }
1177
+ if (!this.#typewriterCursorEl) {
1178
+ const span = document.createElement('span');
1179
+ span.className = 'md-typewriter-cursor';
1180
+ this.#typewriterCursorEl = span;
1181
+ if (cfg.verticalAlign) {
1182
+ span.style.verticalAlign = cfg.verticalAlign;
1183
+ }
1184
+ if (customCursor) {
1185
+ // Look in light DOM or shadow DOM
1186
+ const view = this.#dom.querySelector(`#${CSS.escape(customCursor)}`)
1187
+ || root.querySelector(`#${CSS.escape(customCursor)}`);
1188
+ if (view) {
1189
+ span.appendChild(view);
1190
+ }
1191
+ else {
1192
+ span.textContent = '…';
1193
+ }
1194
+ }
1195
+ else {
1196
+ span.textContent = '…';
1197
+ }
1198
+ }
1199
+ // Find the right place to insert the cursor
1200
+ let target = root;
1201
+ while (true) {
1202
+ let lastNode = target.lastChild;
1203
+ // Skip empty text nodes at the end
1204
+ while (lastNode) {
1205
+ if (lastNode.nodeType === Node.TEXT_NODE
1206
+ && (!lastNode.textContent || lastNode.textContent.trim() === '')) {
1207
+ lastNode = lastNode.previousSibling;
1208
+ }
1209
+ else {
1210
+ break;
1211
+ }
1212
+ }
1213
+ if (lastNode
1214
+ && lastNode.nodeType === Node.ELEMENT_NODE
1215
+ && !['IMG', 'BR', 'HR', 'INPUT', 'TABLE', 'PRE', 'CODE'].includes(lastNode.tagName)) {
1216
+ target = lastNode;
1217
+ }
1218
+ else {
1219
+ break;
1220
+ }
1221
+ }
1222
+ target.appendChild(this.#typewriterCursorEl);
1223
+ }
1224
+ #removeTypewriterCursor() {
1225
+ if (this.#typewriterCursorEl && this.#typewriterCursorEl.parentNode) {
1226
+ this.#typewriterCursorEl.parentNode.removeChild(this.#typewriterCursorEl);
1227
+ }
1228
+ this.#typewriterCursorEl = undefined;
1229
+ }
1230
+ };
1231
+ })();
1232
+ export { XMarkdownAttributes };
1233
+ //# sourceMappingURL=XMarkdownAttributes.js.map