@duskmoon-dev/el-markdown-input 0.8.1 → 0.8.3

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