@duskmoon-dev/el-markdown-input 0.8.1 → 0.8.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,1186 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __returnValue = (v) => v;
3
+ function __exportSetter(name, newValue) {
4
+ this[name] = __returnValue.bind(null, newValue);
5
+ }
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true,
11
+ configurable: true,
12
+ set: __exportSetter.bind(all, name)
13
+ });
14
+ };
15
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
16
+
17
+ // src/css.ts
18
+ import { css } from "@duskmoon-dev/el-base";
19
+ var elementStyles;
20
+ var init_css = __esm(() => {
21
+ elementStyles = css`
22
+ /* ── Custom property defaults with design-system fallbacks ─────────── */
23
+ :host {
24
+ --md-border: var(--color-outline, #d0d7de);
25
+ --md-border-focus: var(--color-primary, #0969da);
26
+ --md-bg: var(--color-surface, #ffffff);
27
+ --md-bg-toolbar: var(--color-surface-variant, #f6f8fa);
28
+ --md-bg-hover: var(--color-surface-container, #eaeef2);
29
+ --md-text: var(--color-on-surface, #1f2328);
30
+ --md-text-muted: var(--color-on-surface-variant, #656d76);
31
+ --md-accent: var(--color-primary, #0969da);
32
+ --md-radius: 6px;
33
+ --md-upload-bar: var(--color-primary, #0969da);
34
+ --md-color-warning: var(--color-warning, #d97706);
35
+ --md-color-error: var(--color-error, #dc2626);
36
+
37
+ display: block;
38
+ position: relative; /* establishes containing block for the ac-dropdown portal */
39
+ font-family: inherit;
40
+ }
41
+
42
+ :host([hidden]) {
43
+ display: none !important;
44
+ }
45
+
46
+ /* Dark-mode overrides (activated by [dark] attribute on host) */
47
+ :host([dark]) {
48
+ --md-border: #30363d;
49
+ --md-border-focus: #58a6ff;
50
+ --md-bg: #0d1117;
51
+ --md-bg-toolbar: #161b22;
52
+ --md-bg-hover: #21262d;
53
+ --md-text: #e6edf3;
54
+ --md-text-muted: #8b949e;
55
+ --md-accent: #58a6ff;
56
+ --md-upload-bar: #58a6ff;
57
+ --md-color-warning: #f59e0b;
58
+ --md-color-error: #fca5a5;
59
+ }
60
+
61
+ /* ── Editor chrome ──────────────────────────────────────────────────── */
62
+ .editor {
63
+ display: flex;
64
+ flex-direction: column;
65
+ border: 1px solid var(--md-border);
66
+ border-radius: var(--md-radius);
67
+ background: var(--md-bg);
68
+ color: var(--md-text);
69
+ overflow: hidden;
70
+ }
71
+
72
+ .editor:focus-within {
73
+ border-color: var(--md-border-focus);
74
+ outline: 2px solid var(--md-border-focus);
75
+ outline-offset: -1px;
76
+ }
77
+
78
+ /* ── Toolbar / tab bar ──────────────────────────────────────────────── */
79
+ .toolbar {
80
+ display: flex;
81
+ gap: 0;
82
+ background: var(--md-bg-toolbar);
83
+ border-bottom: 1px solid var(--md-border);
84
+ padding: 0 0.5rem;
85
+ }
86
+
87
+ .tab-btn {
88
+ padding: 0.5rem 0.875rem;
89
+ border: none;
90
+ background: transparent;
91
+ color: var(--md-text-muted);
92
+ font-family: inherit;
93
+ font-size: 0.875rem;
94
+ font-weight: 500;
95
+ cursor: pointer;
96
+ border-bottom: 2px solid transparent;
97
+ margin-bottom: -1px;
98
+ transition:
99
+ color 150ms ease,
100
+ border-color 150ms ease;
101
+ }
102
+
103
+ .tab-btn:hover {
104
+ color: var(--md-text);
105
+ background: var(--md-bg-hover);
106
+ }
107
+
108
+ .tab-btn[aria-selected='true'] {
109
+ color: var(--md-text);
110
+ border-bottom-color: var(--md-accent);
111
+ }
112
+
113
+ .tab-btn:focus-visible {
114
+ outline: 2px solid var(--md-accent);
115
+ outline-offset: -2px;
116
+ border-radius: 3px;
117
+ }
118
+
119
+ /* ── Write area (backdrop + textarea overlay) ───────────────────────── */
120
+ .write-area {
121
+ position: relative;
122
+ min-height: 12rem;
123
+ flex: 1;
124
+ }
125
+
126
+ /*
127
+ * Backdrop: renders syntax-highlighted HTML behind the transparent textarea.
128
+ * Must share IDENTICAL font metrics with the textarea to stay pixel-aligned.
129
+ */
130
+ .backdrop {
131
+ position: absolute;
132
+ inset: 0;
133
+ pointer-events: none;
134
+ /*
135
+ * Use overflow: auto (not overflow: hidden) so the backdrop reserves
136
+ * the same scrollbar gutter as the textarea when content overflows.
137
+ * Without this, the textarea scrollbar narrows its text area but the
138
+ * backdrop stays full-width — lines wrap at different points — causing
139
+ * the cursor to appear misaligned with the highlighted text.
140
+ */
141
+ overflow: auto;
142
+ scrollbar-width: none; /* Firefox */
143
+ border: none;
144
+ background: transparent;
145
+ /*
146
+ * Do NOT put white-space: pre-wrap here — the template HTML has a
147
+ * whitespace text node (newline + indent) between .backdrop and
148
+ * .backdrop-content. With pre-wrap on the parent that renders as a
149
+ * visible leading blank line, shifting content down by one line and
150
+ * misaligning the cursor vertically. pre-wrap lives on .backdrop-content.
151
+ */
152
+
153
+ font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
154
+ font-size: 0.875rem;
155
+ line-height: 1.6;
156
+ padding: 0.75rem;
157
+ color: var(--md-text);
158
+ }
159
+
160
+ .backdrop::-webkit-scrollbar {
161
+ display: none; /* Chrome / Safari */
162
+ }
163
+
164
+ .backdrop-content {
165
+ display: block;
166
+ white-space: pre-wrap;
167
+ word-wrap: break-word;
168
+ overflow-wrap: break-word;
169
+ /* Prism token colours are injected via a separate <style id="prism-theme"> */
170
+ }
171
+
172
+ textarea {
173
+ position: relative;
174
+ display: block;
175
+ width: 100%;
176
+ min-height: 12rem;
177
+ border: none;
178
+ outline: none;
179
+ resize: vertical;
180
+ background: transparent;
181
+ color: transparent;
182
+ caret-color: var(--md-text);
183
+ box-sizing: border-box;
184
+
185
+ /* MUST match .backdrop exactly */
186
+ font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
187
+ font-size: 0.875rem;
188
+ line-height: 1.6;
189
+ padding: 0.75rem;
190
+ white-space: pre-wrap;
191
+ word-wrap: break-word;
192
+ overflow-wrap: break-word;
193
+ }
194
+
195
+ textarea::placeholder {
196
+ color: var(--md-text-muted);
197
+ }
198
+
199
+ textarea:disabled {
200
+ cursor: not-allowed;
201
+ opacity: 0.6;
202
+ }
203
+
204
+ /* ── Preview panel ──────────────────────────────────────────────────── */
205
+ .preview-body {
206
+ padding: 0.75rem;
207
+ min-height: 12rem;
208
+ overflow-y: auto;
209
+ color: var(--md-text);
210
+ /* .markdown-body styles come from @duskmoon-dev/core via the element */
211
+ }
212
+
213
+ /* Code blocks in preview use theme vars so they adapt to moonlight/sunshine */
214
+ .preview-body pre {
215
+ background: var(--md-bg-toolbar) !important;
216
+ border: 1px solid var(--md-border) !important;
217
+ border-radius: var(--md-radius);
218
+ padding: 1rem;
219
+ overflow-x: auto;
220
+ }
221
+
222
+ .preview-body pre code {
223
+ background: transparent !important;
224
+ padding: 0;
225
+ font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
226
+ font-size: 0.875rem;
227
+ color: var(--md-text) !important;
228
+ }
229
+
230
+ /* Task list items */
231
+ .preview-body li.task-item {
232
+ list-style: none;
233
+ margin-left: -1.25rem;
234
+ }
235
+
236
+ .preview-body li.task-item input[type='checkbox'] {
237
+ margin-right: 0.4em;
238
+ vertical-align: -1px;
239
+ accent-color: var(--md-accent);
240
+ }
241
+
242
+ /* ── Status bar ─────────────────────────────────────────────────────── */
243
+ .status-bar {
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: space-between;
247
+ padding: 0.375rem 0.75rem;
248
+ border-top: 1px solid var(--md-border);
249
+ background: var(--md-bg-toolbar);
250
+ font-size: 0.75rem;
251
+ color: var(--md-text-muted);
252
+ gap: 0.5rem;
253
+ }
254
+
255
+ .attach-btn {
256
+ display: inline-flex;
257
+ align-items: center;
258
+ gap: 0.25rem;
259
+ padding: 0.25rem 0.5rem;
260
+ border: none;
261
+ background: transparent;
262
+ color: var(--md-text-muted);
263
+ font-family: inherit;
264
+ font-size: 0.75rem;
265
+ cursor: pointer;
266
+ border-radius: 4px;
267
+ transition:
268
+ color 150ms ease,
269
+ background 150ms ease;
270
+ }
271
+
272
+ .attach-btn:hover {
273
+ color: var(--md-text);
274
+ background: var(--md-bg-hover);
275
+ }
276
+
277
+ .attach-btn:focus-visible {
278
+ outline: 2px solid var(--md-accent);
279
+ outline-offset: 1px;
280
+ }
281
+
282
+ .status-bar-count {
283
+ margin-left: auto;
284
+ white-space: nowrap;
285
+ }
286
+
287
+ .status-bar-count .warning {
288
+ color: var(--md-color-warning);
289
+ }
290
+
291
+ .status-bar-count .error {
292
+ color: var(--md-color-error);
293
+ }
294
+
295
+ .file-input {
296
+ display: none;
297
+ }
298
+
299
+ /* ── Autocomplete dropdown ──────────────────────────────────────────── */
300
+ /*
301
+ * The dropdown is a direct child of :host (outside .editor) so it is not
302
+ * clipped by .editor's overflow: hidden. :host has position: relative which
303
+ * establishes the containing block for this absolute positioning.
304
+ */
305
+ .ac-dropdown {
306
+ position: absolute;
307
+ z-index: 100;
308
+ left: 0.75rem;
309
+ /* Align to bottom of the editor chrome; the editor fills 100% of :host height */
310
+ bottom: calc(var(--md-status-bar-height, 2rem) + 4px);
311
+ min-width: 16rem;
312
+ max-width: 28rem;
313
+ max-height: 16rem;
314
+ overflow-y: auto;
315
+ margin: 0;
316
+ padding: 0.25rem 0;
317
+ list-style: none;
318
+ background: var(--md-bg);
319
+ border: 1px solid var(--md-border);
320
+ border-radius: var(--md-radius);
321
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
322
+ }
323
+
324
+ .ac-item {
325
+ display: flex;
326
+ flex-direction: column;
327
+ padding: 0.5rem 0.75rem;
328
+ cursor: pointer;
329
+ transition: background 100ms ease;
330
+ }
331
+
332
+ .ac-item:hover,
333
+ .ac-item[aria-selected='true'] {
334
+ background: var(--md-bg-hover);
335
+ }
336
+
337
+ .ac-item-label {
338
+ font-size: 0.875rem;
339
+ color: var(--md-text);
340
+ font-weight: 500;
341
+ }
342
+
343
+ .ac-item-subtitle {
344
+ font-size: 0.75rem;
345
+ color: var(--md-text-muted);
346
+ margin-top: 1px;
347
+ }
348
+
349
+ /* ── Upload progress rows ───────────────────────────────────────────── */
350
+ .upload-list {
351
+ display: flex;
352
+ flex-direction: column;
353
+ gap: 0;
354
+ }
355
+
356
+ .upload-row {
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 0.5rem;
360
+ padding: 0.375rem 0.75rem;
361
+ border-top: 1px solid var(--md-border);
362
+ background: var(--md-bg-toolbar);
363
+ font-size: 0.75rem;
364
+ color: var(--md-text-muted);
365
+ }
366
+
367
+ .upload-filename {
368
+ flex: 1;
369
+ overflow: hidden;
370
+ text-overflow: ellipsis;
371
+ white-space: nowrap;
372
+ }
373
+
374
+ .upload-bar-track {
375
+ width: 6rem;
376
+ height: 3px;
377
+ background: var(--md-border);
378
+ border-radius: 2px;
379
+ overflow: hidden;
380
+ flex-shrink: 0;
381
+ }
382
+
383
+ .upload-bar {
384
+ height: 100%;
385
+ background: var(--md-upload-bar);
386
+ border-radius: 2px;
387
+ transition: width 150ms ease;
388
+ }
389
+
390
+ .upload-error-row {
391
+ display: flex;
392
+ align-items: center;
393
+ gap: 0.5rem;
394
+ padding: 0.375rem 0.75rem;
395
+ border-top: 1px solid var(--md-border);
396
+ background: oklch(97% 0.02 25);
397
+ color: var(--md-color-error);
398
+ font-size: 0.75rem;
399
+ }
400
+
401
+ :host([dark]) .upload-error-row {
402
+ background: oklch(20% 0.03 25);
403
+ }
404
+
405
+ .upload-error-msg {
406
+ flex: 1;
407
+ overflow: hidden;
408
+ text-overflow: ellipsis;
409
+ white-space: nowrap;
410
+ }
411
+ `;
412
+ });
413
+
414
+ // src/highlight.ts
415
+ function _loadScript(src) {
416
+ return new Promise((resolve) => {
417
+ const script = document.createElement("script");
418
+ script.src = src;
419
+ script.onload = () => resolve();
420
+ script.onerror = () => resolve();
421
+ document.head.appendChild(script);
422
+ });
423
+ }
424
+ function ensurePrism() {
425
+ if (window.Prism)
426
+ return Promise.resolve();
427
+ if (_prismReady)
428
+ return _prismReady;
429
+ _prismReady = _loadScript(PRISM_CORE_URL).then(() => {
430
+ if (!window.Prism)
431
+ return;
432
+ window.Prism.manual = true;
433
+ return _loadScript(PRISM_AUTOLOADER_URL).then(() => {
434
+ if (window.Prism?.plugins?.autoloader) {
435
+ window.Prism.plugins.autoloader.languages_path = `${PRISM_BASE}/components/`;
436
+ }
437
+ });
438
+ });
439
+ return _prismReady;
440
+ }
441
+ function escapeHtml(text) {
442
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
443
+ }
444
+ function highlightMarkdown(text) {
445
+ const escaped = escapeHtml(text);
446
+ if (!window.Prism?.languages?.markdown) {
447
+ return escaped + " ";
448
+ }
449
+ try {
450
+ const highlighted = window.Prism.highlight(text, window.Prism.languages.markdown, "markdown");
451
+ return highlighted + " ";
452
+ } catch {
453
+ return escaped + " ";
454
+ }
455
+ }
456
+ function applyPrismTheme(shadowRoot, dark) {
457
+ const themeUrl = dark ? PRISM_THEME_DARK_URL : PRISM_THEME_LIGHT_URL;
458
+ let styleEl = shadowRoot.getElementById("prism-theme");
459
+ if (!styleEl) {
460
+ styleEl = document.createElement("style");
461
+ styleEl.id = "prism-theme";
462
+ shadowRoot.appendChild(styleEl);
463
+ }
464
+ const expected = `@import url("${themeUrl}");`;
465
+ if (styleEl.textContent !== expected) {
466
+ styleEl.textContent = expected;
467
+ }
468
+ }
469
+ var PRISM_BASE = "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0", PRISM_CORE_URL, PRISM_AUTOLOADER_URL, PRISM_THEME_DARK_URL, PRISM_THEME_LIGHT_URL, _prismReady = null;
470
+ var init_highlight = __esm(() => {
471
+ PRISM_CORE_URL = `${PRISM_BASE}/prism.min.js`;
472
+ PRISM_AUTOLOADER_URL = `${PRISM_BASE}/plugins/autoloader/prism-autoloader.min.js`;
473
+ PRISM_THEME_DARK_URL = `${PRISM_BASE}/themes/prism-tomorrow.min.css`;
474
+ PRISM_THEME_LIGHT_URL = `${PRISM_BASE}/themes/prism-coy.min.css`;
475
+ });
476
+
477
+ // src/upload.ts
478
+ function isAcceptedType(file) {
479
+ const type = file.type.toLowerCase();
480
+ if (ACCEPTED_MIME_PREFIXES.some((p) => type.startsWith(p)))
481
+ return true;
482
+ if (ACCEPTED_MIME_EXACT.includes(type))
483
+ return true;
484
+ const name = file.name.toLowerCase();
485
+ if (ACCEPTED_EXTENSIONS.some((ext) => name.endsWith(ext)))
486
+ return true;
487
+ return false;
488
+ }
489
+ function fileToMarkdown(file, url) {
490
+ if (file.type.startsWith("image/")) {
491
+ return `![${file.name}](${url})`;
492
+ }
493
+ return `[${file.name}](${url})`;
494
+ }
495
+ function uploadFile(file, uploadUrl, onProgress) {
496
+ return new Promise((resolve, reject) => {
497
+ const xhr = new XMLHttpRequest;
498
+ const body = new FormData;
499
+ body.append("file", file);
500
+ xhr.upload.addEventListener("progress", (e) => {
501
+ if (e.lengthComputable) {
502
+ onProgress(Math.round(e.loaded / e.total * 100));
503
+ }
504
+ });
505
+ xhr.addEventListener("load", () => {
506
+ if (xhr.status >= 200 && xhr.status < 300) {
507
+ try {
508
+ const data = JSON.parse(xhr.responseText);
509
+ if (data.url) {
510
+ resolve(data.url);
511
+ } else {
512
+ reject("Upload response missing url field");
513
+ }
514
+ } catch {
515
+ reject("Upload response is not valid JSON");
516
+ }
517
+ } else {
518
+ reject(`Upload failed with status ${xhr.status}`);
519
+ }
520
+ });
521
+ xhr.addEventListener("error", () => reject("Network error during upload"));
522
+ xhr.addEventListener("abort", () => reject("Upload aborted"));
523
+ xhr.open("POST", uploadUrl);
524
+ xhr.send(body);
525
+ });
526
+ }
527
+ var ACCEPTED_MIME_PREFIXES, ACCEPTED_MIME_EXACT, ACCEPTED_EXTENSIONS;
528
+ var init_upload = __esm(() => {
529
+ ACCEPTED_MIME_PREFIXES = ["image/"];
530
+ ACCEPTED_MIME_EXACT = ["application/pdf"];
531
+ ACCEPTED_EXTENSIONS = [".zip", ".txt", ".csv", ".json", ".md"];
532
+ });
533
+
534
+ // src/autocomplete.ts
535
+ function detectTrigger(value, cursorPos) {
536
+ let i = cursorPos - 1;
537
+ while (i >= 0) {
538
+ const ch = value[i];
539
+ if (ch === "@" || ch === "#") {
540
+ const query = value.slice(i + 1, cursorPos);
541
+ if (!/\s/.test(query)) {
542
+ const before = i > 0 ? value[i - 1] : null;
543
+ if (before === null || /[\s\n]/.test(before)) {
544
+ return { trigger: ch, query, triggerPos: i };
545
+ }
546
+ }
547
+ return null;
548
+ }
549
+ if (/[\s\n]/.test(ch)) {
550
+ return null;
551
+ }
552
+ i--;
553
+ }
554
+ return null;
555
+ }
556
+ function confirmSuggestion(value, triggerPos, cursorPos, trigger, replacement) {
557
+ const before = value.slice(0, triggerPos);
558
+ const after = value.slice(cursorPos);
559
+ const inserted = `${trigger}${replacement}`;
560
+ const newValue = before + inserted + after;
561
+ const newCursorPos = triggerPos + inserted.length;
562
+ return { newValue, newCursorPos };
563
+ }
564
+ function renderDropdown(suggestions, selectedIndex) {
565
+ if (suggestions.length === 0)
566
+ return "";
567
+ const items = suggestions.map((s, i) => {
568
+ const selected = i === selectedIndex ? ' aria-selected="true"' : ' aria-selected="false"';
569
+ const subtitle = s.subtitle ? `<span class="ac-item-subtitle">${escapeHtml2(s.subtitle)}</span>` : "";
570
+ return `<li id="ac-item-${i}" class="ac-item" role="option" data-ac-index="${i}"${selected}>
571
+ <span class="ac-item-label">${escapeHtml2(s.label)}</span>${subtitle}
572
+ </li>`;
573
+ }).join("");
574
+ return items;
575
+ }
576
+ function escapeHtml2(text) {
577
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
578
+ }
579
+
580
+ // src/status-bar.ts
581
+ function countWords(text) {
582
+ if (!text.trim())
583
+ return 0;
584
+ return text.trim().split(/\s+/).filter(Boolean).length;
585
+ }
586
+ function countColour(wordCount, maxWords) {
587
+ if (!maxWords)
588
+ return "normal";
589
+ const pct = wordCount / maxWords * 100;
590
+ if (pct >= 100)
591
+ return "error";
592
+ if (pct >= 90)
593
+ return "warning";
594
+ return "normal";
595
+ }
596
+ function renderStatusCount(wordCount, charCount, maxWords) {
597
+ const cap = maxWords ?? null;
598
+ const colour = countColour(wordCount, cap);
599
+ const colourClass = colour !== "normal" ? ` class="${colour}"` : "";
600
+ if (cap) {
601
+ return `<span${colourClass}>${wordCount} / ${cap} words · ${charCount} chars</span>`;
602
+ }
603
+ return `<span>${wordCount} words · ${charCount} chars</span>`;
604
+ }
605
+
606
+ // src/element.ts
607
+ var exports_element = {};
608
+ __export(exports_element, {
609
+ ElDmMarkdownInput: () => ElDmMarkdownInput
610
+ });
611
+ import { BaseElement } from "@duskmoon-dev/el-base";
612
+ import { css as markdownBodyCSS } from "@duskmoon-dev/core/components/markdown-body";
613
+ import { css as css2 } from "@duskmoon-dev/el-base";
614
+ function sanitizeUrl(url) {
615
+ const trimmed = url.trim();
616
+ if (!/^[a-z][a-z\d+\-.]*:/i.test(trimmed))
617
+ return trimmed;
618
+ if (/^https?:/i.test(trimmed))
619
+ return trimmed;
620
+ return "#";
621
+ }
622
+ function escapeHtmlStr(s) {
623
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
624
+ }
625
+ var coreMarkdownStyles, markdownBodySheet, ElDmMarkdownInput;
626
+ var init_element = __esm(() => {
627
+ init_css();
628
+ init_highlight();
629
+ init_upload();
630
+ coreMarkdownStyles = markdownBodyCSS.replace(/@layer\s+components\s*\{/, "").replace(/\}\s*$/, "");
631
+ markdownBodySheet = css2`
632
+ ${coreMarkdownStyles}
633
+ `;
634
+ ElDmMarkdownInput = class ElDmMarkdownInput extends BaseElement {
635
+ static formAssociated = true;
636
+ static properties = {
637
+ name: { type: String, reflect: true, default: "" },
638
+ value: { type: String, default: "" },
639
+ placeholder: { type: String, reflect: true, default: "Write markdown…" },
640
+ disabled: { type: Boolean, reflect: true },
641
+ uploadUrl: { type: String, reflect: true, attribute: "upload-url" },
642
+ maxWords: { type: Number, reflect: true, attribute: "max-words" },
643
+ dark: { type: Boolean, reflect: true }
644
+ };
645
+ #internals;
646
+ #initialized = false;
647
+ #activeTab = "write";
648
+ #highlightTimer = null;
649
+ #statusTimer = null;
650
+ #textarea = null;
651
+ #backdrop = null;
652
+ #backdropContent = null;
653
+ #writeArea = null;
654
+ #previewBody = null;
655
+ #statusCount = null;
656
+ #acDropdown = null;
657
+ #uploadList = null;
658
+ #fileInput = null;
659
+ #resizeObserver = null;
660
+ #acSuggestions = [];
661
+ #acSelectedIndex = -1;
662
+ #acTriggerPos = -1;
663
+ #acTrigger = null;
664
+ #uploadIdCounter = 0;
665
+ constructor() {
666
+ super();
667
+ this.#internals = this.attachInternals();
668
+ this.attachStyles([elementStyles, markdownBodySheet]);
669
+ }
670
+ connectedCallback() {
671
+ super.connectedCallback();
672
+ const initial = this.value ?? "";
673
+ if (initial && this.#textarea) {
674
+ this.#textarea.value = initial;
675
+ this.#syncFormValue();
676
+ }
677
+ }
678
+ disconnectedCallback() {
679
+ this.#resizeObserver?.disconnect();
680
+ super.disconnectedCallback();
681
+ }
682
+ update() {
683
+ if (!this.#initialized) {
684
+ super.update();
685
+ this.#initialized = true;
686
+ this.#cacheDOMRefs();
687
+ this.#attachEventHandlers();
688
+ this.#initHighlight();
689
+ this.#updateStatusBarNow();
690
+ const initVal = this.value ?? "";
691
+ if (initVal && this.#textarea) {
692
+ this.#textarea.value = initVal;
693
+ this.#syncFormValue();
694
+ this.#scheduleHighlight();
695
+ }
696
+ return;
697
+ }
698
+ this.#patchDynamicRegions();
699
+ }
700
+ #patchDynamicRegions() {
701
+ const ta = this.#textarea;
702
+ if (!ta)
703
+ return;
704
+ const placeholder = this.placeholder ?? "Write markdown…";
705
+ ta.placeholder = placeholder;
706
+ ta.disabled = !!this.disabled;
707
+ const propVal = this.value ?? "";
708
+ if (propVal !== ta.value) {
709
+ ta.value = propVal;
710
+ this.#syncFormValue();
711
+ this.#scheduleHighlight();
712
+ }
713
+ applyPrismTheme(this.shadowRoot, this.#isDark());
714
+ this.#updateStatusBarNow();
715
+ }
716
+ render() {
717
+ const ph = this.placeholder ?? "Write markdown…";
718
+ const disabled = !!this.disabled;
719
+ return `
720
+ <div class="editor">
721
+ <div class="toolbar" role="tablist" aria-label="Editor mode">
722
+ <button
723
+ class="tab-btn"
724
+ data-tab="write"
725
+ role="tab"
726
+ aria-selected="true"
727
+ aria-controls="write-panel"
728
+ >Write</button>
729
+ <button
730
+ class="tab-btn"
731
+ data-tab="preview"
732
+ role="tab"
733
+ aria-selected="false"
734
+ aria-controls="preview-panel"
735
+ >Preview</button>
736
+ </div>
737
+
738
+ <div class="write-area" id="write-panel" role="tabpanel" aria-label="Markdown editor">
739
+ <div class="backdrop" aria-hidden="true">
740
+ <div class="backdrop-content"></div>
741
+ </div>
742
+ <textarea
743
+ aria-label="Markdown editor"
744
+ aria-haspopup="listbox"
745
+ aria-autocomplete="list"
746
+ aria-controls="ac-dropdown"
747
+ placeholder="${ph}"
748
+ ${disabled ? "disabled" : ""}
749
+ spellcheck="false"
750
+ autocomplete="off"
751
+ autocorrect="off"
752
+ autocapitalize="off"
753
+ ></textarea>
754
+ </div>
755
+
756
+ <div
757
+ class="preview-body markdown-body"
758
+ id="preview-panel"
759
+ role="tabpanel"
760
+ aria-label="Markdown preview"
761
+ hidden
762
+ ></div>
763
+
764
+ <div class="status-bar">
765
+ <button class="attach-btn" type="button" aria-label="Attach files">
766
+ &#128206; Attach files
767
+ </button>
768
+ <span class="status-bar-count" aria-live="polite"></span>
769
+ <input
770
+ type="file"
771
+ class="file-input"
772
+ multiple
773
+ accept="image/*,application/pdf,.zip,.txt,.csv,.json,.md"
774
+ aria-hidden="true"
775
+ tabindex="-1"
776
+ >
777
+ </div>
778
+
779
+ <div class="upload-list"></div>
780
+ </div>
781
+ <ul id="ac-dropdown" class="ac-dropdown" role="listbox" aria-label="Suggestions" hidden></ul>
782
+ `;
783
+ }
784
+ #cacheDOMRefs() {
785
+ this.#textarea = this.shadowRoot.querySelector("textarea");
786
+ this.#backdrop = this.shadowRoot.querySelector(".backdrop");
787
+ this.#backdropContent = this.shadowRoot.querySelector(".backdrop-content");
788
+ this.#writeArea = this.shadowRoot.querySelector(".write-area");
789
+ this.#previewBody = this.shadowRoot.querySelector(".preview-body");
790
+ this.#statusCount = this.shadowRoot.querySelector(".status-bar-count");
791
+ this.#acDropdown = this.shadowRoot.querySelector(".ac-dropdown");
792
+ this.#uploadList = this.shadowRoot.querySelector(".upload-list");
793
+ this.#fileInput = this.shadowRoot.querySelector(".file-input");
794
+ }
795
+ #attachEventHandlers() {
796
+ const ta = this.#textarea;
797
+ if (!ta)
798
+ return;
799
+ ta.addEventListener("input", () => {
800
+ this.#syncFormValue();
801
+ this.emit("change", { value: ta.value });
802
+ this.#scheduleHighlight();
803
+ this.#scheduleStatusUpdate();
804
+ this.#handleAutocompleteInput();
805
+ });
806
+ ta.addEventListener("scroll", () => {
807
+ if (this.#backdrop) {
808
+ this.#backdrop.scrollTop = ta.scrollTop;
809
+ this.#backdrop.scrollLeft = ta.scrollLeft;
810
+ }
811
+ });
812
+ ta.addEventListener("blur", () => {
813
+ setTimeout(() => {
814
+ if (!this.shadowRoot?.activeElement) {
815
+ this.#closeDropdown();
816
+ }
817
+ }, 150);
818
+ });
819
+ ta.addEventListener("keydown", (e) => {
820
+ if (this.#acSuggestions.length > 0 && !this.#acDropdown?.hidden) {
821
+ this.#handleDropdownKeydown(e);
822
+ }
823
+ if (e.ctrlKey && e.shiftKey && e.key === "P") {
824
+ e.preventDefault();
825
+ this.#switchTab(this.#activeTab === "write" ? "preview" : "write");
826
+ }
827
+ });
828
+ const writeArea = this.#writeArea;
829
+ if (writeArea) {
830
+ writeArea.addEventListener("dragover", (e) => {
831
+ e.preventDefault();
832
+ writeArea.style.opacity = "0.8";
833
+ });
834
+ writeArea.addEventListener("dragleave", () => {
835
+ writeArea.style.opacity = "";
836
+ });
837
+ writeArea.addEventListener("drop", (e) => {
838
+ e.preventDefault();
839
+ writeArea.style.opacity = "";
840
+ const files = Array.from(e.dataTransfer?.files ?? []).filter(isAcceptedType);
841
+ files.forEach((f) => this.#startUpload(f));
842
+ });
843
+ }
844
+ ta.addEventListener("paste", (e) => {
845
+ const imageFiles = Array.from(e.clipboardData?.files ?? []).filter((f) => f.type.startsWith("image/"));
846
+ if (imageFiles.length > 0) {
847
+ e.preventDefault();
848
+ imageFiles.forEach((f) => this.#startUpload(f));
849
+ }
850
+ });
851
+ const toolbar = this.shadowRoot.querySelector(".toolbar");
852
+ toolbar?.addEventListener("click", (e) => {
853
+ const btn = e.target.closest(".tab-btn");
854
+ const tab = btn?.dataset.tab;
855
+ if (tab)
856
+ this.#switchTab(tab);
857
+ });
858
+ const attachBtn = this.shadowRoot.querySelector(".attach-btn");
859
+ attachBtn?.addEventListener("click", () => this.#fileInput?.click());
860
+ this.#fileInput?.addEventListener("change", () => {
861
+ const files = Array.from(this.#fileInput?.files ?? []).filter(isAcceptedType);
862
+ files.forEach((f) => this.#startUpload(f));
863
+ if (this.#fileInput)
864
+ this.#fileInput.value = "";
865
+ });
866
+ this.#acDropdown?.addEventListener("click", (e) => {
867
+ const item = e.target.closest("[data-ac-index]");
868
+ if (item) {
869
+ const idx = parseInt(item.dataset.acIndex ?? "-1", 10);
870
+ if (idx >= 0) {
871
+ this.#acSelectedIndex = idx;
872
+ this.#confirmAutocomplete();
873
+ }
874
+ }
875
+ });
876
+ if (typeof ResizeObserver !== "undefined") {
877
+ this.#resizeObserver = new ResizeObserver(() => {
878
+ if (this.#backdrop && this.#textarea) {
879
+ this.#backdrop.style.height = `${this.#textarea.offsetHeight}px`;
880
+ }
881
+ });
882
+ this.#resizeObserver.observe(ta);
883
+ }
884
+ }
885
+ #isDark() {
886
+ if (this.dark)
887
+ return true;
888
+ const pageTheme = document.documentElement.dataset.theme;
889
+ if (pageTheme === "moonlight")
890
+ return true;
891
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
892
+ }
893
+ #initHighlight() {
894
+ const dark = this.#isDark();
895
+ applyPrismTheme(this.shadowRoot, dark);
896
+ ensurePrism().then(() => {
897
+ if (this.#textarea && this.#backdropContent) {
898
+ this.#backdropContent.innerHTML = highlightMarkdown(this.#textarea.value);
899
+ }
900
+ });
901
+ }
902
+ #scheduleHighlight() {
903
+ if (this.#highlightTimer !== null)
904
+ clearTimeout(this.#highlightTimer);
905
+ this.#highlightTimer = setTimeout(() => {
906
+ this.#highlightTimer = null;
907
+ if (this.#backdropContent && this.#textarea) {
908
+ this.#backdropContent.innerHTML = highlightMarkdown(this.#textarea.value);
909
+ }
910
+ if (this.#backdrop && this.#textarea) {
911
+ this.#backdrop.scrollTop = this.#textarea.scrollTop;
912
+ }
913
+ }, 60);
914
+ }
915
+ #switchTab(tab) {
916
+ if (tab === this.#activeTab)
917
+ return;
918
+ this.#activeTab = tab;
919
+ const writeBtns = this.shadowRoot.querySelectorAll(".tab-btn");
920
+ writeBtns.forEach((btn) => {
921
+ const isActive = btn.dataset.tab === tab;
922
+ btn.setAttribute("aria-selected", String(isActive));
923
+ });
924
+ if (tab === "preview") {
925
+ this.#writeArea?.setAttribute("hidden", "");
926
+ if (this.#previewBody) {
927
+ this.#previewBody.removeAttribute("hidden");
928
+ this.#previewBody.innerHTML = this.#renderMarkdown(this.#textarea?.value ?? "");
929
+ }
930
+ } else {
931
+ this.#writeArea?.removeAttribute("hidden");
932
+ this.#previewBody?.setAttribute("hidden", "");
933
+ }
934
+ }
935
+ #renderMarkdown(md) {
936
+ if (!md.trim())
937
+ return "<p></p>";
938
+ const CB_PH = "⁠CB";
939
+ const IC_PH = "⁠IC";
940
+ const codeBlocks = [];
941
+ let html = md.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) => {
942
+ const escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
943
+ const idx = codeBlocks.push(`<pre><code class="language-${lang || "text"}">${escaped}</code></pre>`) - 1;
944
+ return `${CB_PH}${idx}${CB_PH}`;
945
+ });
946
+ const inlineCodes = [];
947
+ html = html.replace(/`([^`\n]+)`/g, (_, code) => {
948
+ const escaped = code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
949
+ const idx = inlineCodes.push(`<code>${escaped}</code>`) - 1;
950
+ return `${IC_PH}${idx}${IC_PH}`;
951
+ });
952
+ html = html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
953
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, url) => `<img src="${sanitizeUrl(url)}" alt="${alt}">`);
954
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => `<a href="${sanitizeUrl(url)}">${text}</a>`);
955
+ html = html.replace(/^###### (.+)$/gm, "<h6>$1</h6>").replace(/^##### (.+)$/gm, "<h5>$1</h5>").replace(/^#### (.+)$/gm, "<h4>$1</h4>").replace(/^### (.+)$/gm, "<h3>$1</h3>").replace(/^## (.+)$/gm, "<h2>$1</h2>").replace(/^# (.+)$/gm, "<h1>$1</h1>");
956
+ html = html.replace(/^[-*_]{3,}$/gm, "<hr>");
957
+ html = html.replace(/^&gt; (.+)$/gm, "<blockquote>$1</blockquote>");
958
+ html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>").replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/___(.+?)___/g, "<strong><em>$1</em></strong>").replace(/__(.+?)__/g, "<strong>$1</strong>").replace(/_(.+?)_/g, "<em>$1</em>");
959
+ html = html.replace(/~~(.+?)~~/g, "<del>$1</del>");
960
+ html = html.replace(/^[ \t]*[-*+] \[[xX]\] (.+)$/gm, '<li data-list="ul" class="task-item task-done"><input type="checkbox" checked disabled> $1</li>');
961
+ html = html.replace(/^[ \t]*[-*+] \[ \] (.+)$/gm, '<li data-list="ul" class="task-item"><input type="checkbox" disabled> $1</li>');
962
+ html = html.replace(/^[ \t]*[-*+] (.+)$/gm, '<li data-list="ul">$1</li>');
963
+ html = html.replace(/^[ \t]*\d+\. (.+)$/gm, '<li data-list="ol">$1</li>');
964
+ html = html.replace(/(<li data-list="ul"[^>]*>[^\n]*<\/li>(?:\n<li data-list="ul"[^>]*>[^\n]*<\/li>)*)/g, (match) => "<ul>" + match.replace(/ data-list="ul"/g, "") + "</ul>");
965
+ html = html.replace(/(<li data-list="ol">[^\n]*<\/li>(?:\n<li data-list="ol">[^\n]*<\/li>)*)/g, (match) => "<ol>" + match.replace(/ data-list="ol"/g, "") + "</ol>");
966
+ const lines = html.split(`
967
+
968
+ `);
969
+ html = lines.map((block) => {
970
+ const t = block.trim();
971
+ if (!t)
972
+ return "";
973
+ if (/^<(h[1-6]|ul|ol|li|blockquote|hr|pre|img)/.test(t) || t.startsWith(CB_PH))
974
+ return t;
975
+ return `<p>${t.replace(/\n/g, "<br>")}</p>`;
976
+ }).filter(Boolean).join(`
977
+ `);
978
+ html = html.replace(new RegExp(`${CB_PH}(\\d+)${CB_PH}`, "g"), (_, i) => codeBlocks[parseInt(i, 10)] ?? "");
979
+ html = html.replace(new RegExp(`${IC_PH}(\\d+)${IC_PH}`, "g"), (_, i) => inlineCodes[parseInt(i, 10)] ?? "");
980
+ return html;
981
+ }
982
+ #syncFormValue() {
983
+ this.#internals?.setFormValue(this.#textarea?.value ?? "");
984
+ }
985
+ #startUpload(file) {
986
+ this.emit("upload-start", { file });
987
+ const id = `upload-${++this.#uploadIdCounter}`;
988
+ const uploadUrl = this.uploadUrl;
989
+ if (!uploadUrl) {
990
+ this.emit("upload-error", { file, error: "no upload-url set" });
991
+ this.#showUploadError(file, "no upload-url set");
992
+ return;
993
+ }
994
+ this.#addProgressRow(id, file.name);
995
+ uploadFile(file, uploadUrl, (pct) => {
996
+ this.#updateProgressRow(id, pct);
997
+ }).then((url) => {
998
+ this.#removeUploadRow(id);
999
+ const markdown = fileToMarkdown(file, url);
1000
+ this.insertText(markdown);
1001
+ this.emit("upload-done", { file, url, markdown });
1002
+ }).catch((err) => {
1003
+ this.#removeUploadRow(id);
1004
+ const errorMsg = typeof err === "string" ? err : "Upload failed";
1005
+ this.emit("upload-error", { file, error: errorMsg });
1006
+ this.#showUploadError(file, errorMsg);
1007
+ });
1008
+ }
1009
+ #addProgressRow(id, filename) {
1010
+ if (!this.#uploadList)
1011
+ return;
1012
+ const row = document.createElement("div");
1013
+ row.className = "upload-row";
1014
+ row.id = id;
1015
+ row.innerHTML = `
1016
+ <span class="upload-filename">${escapeHtmlStr(filename)}</span>
1017
+ <div class="upload-bar-track">
1018
+ <div class="upload-bar" style="width: 0%"></div>
1019
+ </div>
1020
+ `;
1021
+ this.#uploadList.appendChild(row);
1022
+ }
1023
+ #updateProgressRow(id, pct) {
1024
+ const bar = this.#uploadList?.querySelector(`#${id} .upload-bar`);
1025
+ if (bar)
1026
+ bar.style.width = `${pct}%`;
1027
+ }
1028
+ #removeUploadRow(id) {
1029
+ this.#uploadList?.querySelector(`#${id}`)?.remove();
1030
+ }
1031
+ #showUploadError(file, message) {
1032
+ if (!this.#uploadList)
1033
+ return;
1034
+ const row = document.createElement("div");
1035
+ row.className = "upload-error-row";
1036
+ row.innerHTML = `
1037
+ <span class="upload-error-msg">${escapeHtmlStr(file.name)}: ${escapeHtmlStr(message)}</span>
1038
+ `;
1039
+ this.#uploadList.appendChild(row);
1040
+ setTimeout(() => row.remove(), 4000);
1041
+ }
1042
+ #handleAutocompleteInput() {
1043
+ const ta = this.#textarea;
1044
+ if (!ta)
1045
+ return;
1046
+ const result = detectTrigger(ta.value, ta.selectionStart ?? 0);
1047
+ if (!result) {
1048
+ this.#closeDropdown();
1049
+ return;
1050
+ }
1051
+ const { trigger, query, triggerPos } = result;
1052
+ this.#acTrigger = trigger;
1053
+ this.#acTriggerPos = triggerPos;
1054
+ const resolve = (list) => this.setSuggestions(list);
1055
+ if (trigger === "@") {
1056
+ this.emit("mention-query", { trigger, query, resolve });
1057
+ } else {
1058
+ this.emit("reference-query", { trigger, query, resolve });
1059
+ }
1060
+ }
1061
+ #handleDropdownKeydown(e) {
1062
+ const len = this.#acSuggestions.length;
1063
+ if (len === 0)
1064
+ return;
1065
+ switch (e.key) {
1066
+ case "ArrowDown":
1067
+ e.preventDefault();
1068
+ this.#acSelectedIndex = (this.#acSelectedIndex + 1) % len;
1069
+ this.#updateDropdown();
1070
+ break;
1071
+ case "ArrowUp":
1072
+ e.preventDefault();
1073
+ this.#acSelectedIndex = (this.#acSelectedIndex - 1 + len) % len;
1074
+ this.#updateDropdown();
1075
+ break;
1076
+ case "Enter":
1077
+ case "Tab":
1078
+ if (this.#acSelectedIndex >= 0) {
1079
+ e.preventDefault();
1080
+ this.#confirmAutocomplete();
1081
+ }
1082
+ break;
1083
+ case "Escape":
1084
+ this.#closeDropdown();
1085
+ break;
1086
+ }
1087
+ }
1088
+ #confirmAutocomplete() {
1089
+ const ta = this.#textarea;
1090
+ if (!ta || this.#acSelectedIndex < 0 || !this.#acTrigger)
1091
+ return;
1092
+ const suggestion = this.#acSuggestions[this.#acSelectedIndex];
1093
+ if (!suggestion)
1094
+ return;
1095
+ const { newValue, newCursorPos } = confirmSuggestion(ta.value, this.#acTriggerPos, ta.selectionStart ?? ta.value.length, this.#acTrigger, suggestion.id);
1096
+ ta.value = newValue;
1097
+ ta.setSelectionRange(newCursorPos, newCursorPos);
1098
+ this.#syncFormValue();
1099
+ this.emit("change", { value: ta.value });
1100
+ this.#scheduleHighlight();
1101
+ this.#scheduleStatusUpdate();
1102
+ this.#closeDropdown();
1103
+ }
1104
+ #closeDropdown() {
1105
+ this.#acSuggestions = [];
1106
+ this.#acSelectedIndex = -1;
1107
+ this.#acTrigger = null;
1108
+ this.#acTriggerPos = -1;
1109
+ if (this.#acDropdown) {
1110
+ this.#acDropdown.innerHTML = "";
1111
+ this.#acDropdown.hidden = true;
1112
+ }
1113
+ }
1114
+ #updateDropdown() {
1115
+ if (!this.#acDropdown)
1116
+ return;
1117
+ if (this.#acSuggestions.length === 0) {
1118
+ this.#acDropdown.hidden = true;
1119
+ this.#textarea?.removeAttribute("aria-activedescendant");
1120
+ return;
1121
+ }
1122
+ this.#acDropdown.innerHTML = renderDropdown(this.#acSuggestions, this.#acSelectedIndex);
1123
+ this.#acDropdown.hidden = false;
1124
+ if (this.#acSelectedIndex >= 0) {
1125
+ this.#textarea?.setAttribute("aria-activedescendant", `ac-item-${this.#acSelectedIndex}`);
1126
+ } else {
1127
+ this.#textarea?.removeAttribute("aria-activedescendant");
1128
+ }
1129
+ }
1130
+ #scheduleStatusUpdate() {
1131
+ if (this.#statusTimer !== null)
1132
+ clearTimeout(this.#statusTimer);
1133
+ this.#statusTimer = setTimeout(() => {
1134
+ this.#statusTimer = null;
1135
+ this.#updateStatusBarNow();
1136
+ }, 100);
1137
+ }
1138
+ #updateStatusBarNow() {
1139
+ if (!this.#statusCount)
1140
+ return;
1141
+ const text = this.#textarea?.value ?? "";
1142
+ const words = countWords(text);
1143
+ const chars = text.length;
1144
+ const maxWords = this.maxWords ?? null;
1145
+ this.#statusCount.innerHTML = renderStatusCount(words, chars, maxWords);
1146
+ }
1147
+ getValue() {
1148
+ return this.#textarea?.value ?? "";
1149
+ }
1150
+ setValue(str) {
1151
+ if (this.#textarea) {
1152
+ this.#textarea.value = str;
1153
+ this.#syncFormValue();
1154
+ this.#scheduleHighlight();
1155
+ this.#updateStatusBarNow();
1156
+ } else {
1157
+ this.value = str;
1158
+ }
1159
+ }
1160
+ insertText(str) {
1161
+ const ta = this.#textarea;
1162
+ if (!ta)
1163
+ return;
1164
+ const start = ta.selectionStart ?? ta.value.length;
1165
+ const end = ta.selectionEnd ?? ta.value.length;
1166
+ ta.value = ta.value.slice(0, start) + str + ta.value.slice(end);
1167
+ const newPos = start + str.length;
1168
+ ta.setSelectionRange(newPos, newPos);
1169
+ ta.dispatchEvent(new Event("input", { bubbles: true }));
1170
+ }
1171
+ setSuggestions(list) {
1172
+ this.#acSuggestions = list;
1173
+ this.#acSelectedIndex = list.length > 0 ? 0 : -1;
1174
+ this.#updateDropdown();
1175
+ }
1176
+ };
1177
+ });
1178
+
1179
+ // src/register.ts
1180
+ init_element();
1181
+ if (!customElements.get("el-dm-markdown-input")) {
1182
+ customElements.define("el-dm-markdown-input", ElDmMarkdownInput);
1183
+ }
1184
+
1185
+ //# debugId=C2DA55F5D5D8E9FF64756E2164756E21
1186
+ //# sourceMappingURL=register.js.map