@duskmoon-dev/el-markdown-input 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +156 -35
- package/dist/cjs/index.js.map +5 -5
- package/dist/cjs/register.js +156 -35
- package/dist/cjs/register.js.map +5 -5
- package/dist/esm/index.js +156 -35
- package/dist/esm/index.js.map +5 -5
- package/dist/esm/register.js +156 -35
- package/dist/esm/register.js.map +5 -5
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/css.d.ts.map +1 -1
- package/dist/types/element.d.ts +6 -0
- package/dist/types/element.d.ts.map +1 -1
- package/dist/types/pairs.d.ts +17 -2
- package/dist/types/pairs.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/esm/index.js
CHANGED
|
@@ -236,6 +236,7 @@ var elementStyles = css`
|
|
|
236
236
|
color: var(--md-text);
|
|
237
237
|
overflow: hidden;
|
|
238
238
|
height: inherit;
|
|
239
|
+
min-height: 12rem;
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
.editor:focus-within {
|
|
@@ -287,13 +288,18 @@ var elementStyles = css`
|
|
|
287
288
|
|
|
288
289
|
/* ── Write area (render-layer + textarea overlay) ──────────────────── */
|
|
289
290
|
/*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
291
|
+
* CSS grid overlay model: both .render-layer and textarea occupy the same
|
|
292
|
+
* grid cell (grid-area: 1/1), making them normal-flow siblings. The
|
|
293
|
+
* render-layer drives the cell's height; the textarea stretches to match.
|
|
294
|
+
* The write-area is the scroll container — both layers scroll together
|
|
295
|
+
* with no JS sync required. This fixes overflow when the editor has a
|
|
296
|
+
* fixed height set by the consumer.
|
|
293
297
|
*/
|
|
294
298
|
.write-area {
|
|
295
299
|
position: relative;
|
|
296
|
-
|
|
300
|
+
display: grid;
|
|
301
|
+
overflow-y: auto;
|
|
302
|
+
min-height: 0;
|
|
297
303
|
flex: 1 1 auto;
|
|
298
304
|
}
|
|
299
305
|
|
|
@@ -302,15 +308,13 @@ var elementStyles = css`
|
|
|
302
308
|
}
|
|
303
309
|
|
|
304
310
|
/*
|
|
305
|
-
* Render layer: highlighted HTML
|
|
306
|
-
* pointer-events: none lets clicks pass through to the textarea
|
|
311
|
+
* Render layer: highlighted HTML that drives the grid cell height.
|
|
312
|
+
* pointer-events: none lets clicks pass through to the textarea on top.
|
|
307
313
|
* Font metrics MUST match the textarea exactly for pixel-aligned overlay.
|
|
308
314
|
*/
|
|
309
315
|
.render-layer {
|
|
310
|
-
|
|
311
|
-
z-index: 1;
|
|
316
|
+
grid-area: 1 / 1;
|
|
312
317
|
pointer-events: none;
|
|
313
|
-
min-height: 12rem;
|
|
314
318
|
font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
315
319
|
font-size: 0.875rem;
|
|
316
320
|
line-height: 1.6;
|
|
@@ -322,14 +326,13 @@ var elementStyles = css`
|
|
|
322
326
|
}
|
|
323
327
|
|
|
324
328
|
/*
|
|
325
|
-
* Textarea:
|
|
329
|
+
* Textarea: grid overlay on top of the render layer. Transparent text
|
|
326
330
|
* lets highlighted content show through; caret-color keeps cursor visible.
|
|
327
|
-
* overflow: hidden — the
|
|
331
|
+
* overflow: hidden — the write-area is the scroll container, not textarea.
|
|
328
332
|
*/
|
|
329
333
|
textarea {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
z-index: 2;
|
|
334
|
+
grid-area: 1 / 1;
|
|
335
|
+
z-index: 1;
|
|
333
336
|
display: block;
|
|
334
337
|
width: 100%;
|
|
335
338
|
height: 100%;
|
|
@@ -354,6 +357,17 @@ var elementStyles = css`
|
|
|
354
357
|
color: var(--md-text-muted);
|
|
355
358
|
}
|
|
356
359
|
|
|
360
|
+
/*
|
|
361
|
+
* Selection: keep text transparent so the render layer stays visible, but
|
|
362
|
+
* apply a semi-transparent highlight so selected regions are clearly marked.
|
|
363
|
+
* Without this rule the browser's default opaque selection background covers
|
|
364
|
+
* the render layer, making selected text appear invisible.
|
|
365
|
+
*/
|
|
366
|
+
textarea::selection {
|
|
367
|
+
color: transparent;
|
|
368
|
+
background-color: var(--md-selection-bg, color-mix(in srgb, var(--md-accent) 35%, transparent));
|
|
369
|
+
}
|
|
370
|
+
|
|
357
371
|
textarea:disabled {
|
|
358
372
|
cursor: not-allowed;
|
|
359
373
|
opacity: 0.6;
|
|
@@ -362,8 +376,7 @@ var elementStyles = css`
|
|
|
362
376
|
/* ── Preview panel ──────────────────────────────────────────────────── */
|
|
363
377
|
.preview-body {
|
|
364
378
|
padding: 0.75rem;
|
|
365
|
-
min-height:
|
|
366
|
-
height: stretch;
|
|
379
|
+
min-height: 0; /* allow flex item to shrink and scroll */
|
|
367
380
|
flex: 1 1 auto;
|
|
368
381
|
display: flex;
|
|
369
382
|
flex-direction: column;
|
|
@@ -376,6 +389,15 @@ var elementStyles = css`
|
|
|
376
389
|
display: none;
|
|
377
390
|
}
|
|
378
391
|
|
|
392
|
+
/*
|
|
393
|
+
* Markdown content children must not shrink below their natural height.
|
|
394
|
+
* Without this, flex-shrink:1 (default) compresses all children to fit
|
|
395
|
+
* the 329px container instead of letting overflow-y:auto scroll them.
|
|
396
|
+
*/
|
|
397
|
+
.preview-body > * {
|
|
398
|
+
flex-shrink: 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
379
401
|
/* ── Preview skeleton (shown while render pipeline loads) ──────────── */
|
|
380
402
|
.preview-skeleton {
|
|
381
403
|
display: flex;
|
|
@@ -529,9 +551,9 @@ var elementStyles = css`
|
|
|
529
551
|
.ac-dropdown {
|
|
530
552
|
position: absolute;
|
|
531
553
|
z-index: 100;
|
|
532
|
-
left
|
|
533
|
-
|
|
534
|
-
|
|
554
|
+
/* top and left are set dynamically by #updateDropdown() via #getCaretCoords() */
|
|
555
|
+
top: 0;
|
|
556
|
+
left: 0;
|
|
535
557
|
min-width: 16rem;
|
|
536
558
|
max-width: 28rem;
|
|
537
559
|
max-height: 16rem;
|
|
@@ -633,6 +655,20 @@ var elementStyles = css`
|
|
|
633
655
|
white-space: nowrap;
|
|
634
656
|
}
|
|
635
657
|
|
|
658
|
+
/* ── Resizable editor ──────────────────────────────────────────────── */
|
|
659
|
+
/* resize attribute mirrors the CSS resize property: vertical | horizontal | both */
|
|
660
|
+
:host([resize='vertical']) .editor {
|
|
661
|
+
resize: vertical;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
:host([resize='horizontal']) .editor {
|
|
665
|
+
resize: horizontal;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
:host([resize='both']) .editor {
|
|
669
|
+
resize: both;
|
|
670
|
+
}
|
|
671
|
+
|
|
636
672
|
/* ── Reduced motion: disable all transitions and animations ──────── */
|
|
637
673
|
@media (prefers-reduced-motion: reduce) {
|
|
638
674
|
.tab-btn,
|
|
@@ -848,6 +884,14 @@ function escapeHtml2(text) {
|
|
|
848
884
|
}
|
|
849
885
|
|
|
850
886
|
// src/pairs.ts
|
|
887
|
+
function replaceRange(ta, from, to, text, cursorStart, cursorEnd) {
|
|
888
|
+
ta.setSelectionRange(from, to);
|
|
889
|
+
const execOk = typeof document.execCommand === "function" && document.execCommand("insertText", false, text);
|
|
890
|
+
if (!execOk) {
|
|
891
|
+
ta.value = ta.value.slice(0, from) + text + ta.value.slice(to);
|
|
892
|
+
}
|
|
893
|
+
ta.setSelectionRange(cursorStart, cursorEnd ?? cursorStart);
|
|
894
|
+
}
|
|
851
895
|
function handlePairKey(ta, key) {
|
|
852
896
|
if (key !== "`")
|
|
853
897
|
return false;
|
|
@@ -856,17 +900,38 @@ function handlePairKey(ta, key) {
|
|
|
856
900
|
const value = ta.value;
|
|
857
901
|
if (start !== end) {
|
|
858
902
|
const selected = value.slice(start, end);
|
|
859
|
-
ta
|
|
860
|
-
ta.setSelectionRange(start + 1, end + 1);
|
|
903
|
+
replaceRange(ta, start, end, "`" + selected + "`", start + 1, end + 1);
|
|
861
904
|
return true;
|
|
862
905
|
}
|
|
863
906
|
if (start >= 2 && value.slice(start - 2, start) === "``") {
|
|
864
|
-
|
|
865
|
-
|
|
907
|
+
let consumeEnd = end;
|
|
908
|
+
while (consumeEnd < value.length && value[consumeEnd] === "`")
|
|
909
|
+
consumeEnd++;
|
|
910
|
+
replaceRange(ta, start, consumeEnd, "`\n\n```", start + 2);
|
|
866
911
|
return true;
|
|
867
912
|
}
|
|
868
|
-
ta
|
|
869
|
-
|
|
913
|
+
replaceRange(ta, start, end, "``", start + 1);
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
var INDENT = " ";
|
|
917
|
+
function handleTabKey(ta, e) {
|
|
918
|
+
if (e.key !== "Tab")
|
|
919
|
+
return false;
|
|
920
|
+
e.preventDefault();
|
|
921
|
+
const { selectionStart: start, selectionEnd: end, value } = ta;
|
|
922
|
+
if (start === end && !e.shiftKey) {
|
|
923
|
+
replaceRange(ta, start, end, INDENT, start + INDENT.length);
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
const lineStart = value.lastIndexOf(`
|
|
927
|
+
`, start - 1) + 1;
|
|
928
|
+
const block = value.slice(lineStart, end);
|
|
929
|
+
const transformed = e.shiftKey ? block.replace(/^ {1,2}/gm, "") : block.replace(/^/gm, INDENT);
|
|
930
|
+
const delta = transformed.length - block.length;
|
|
931
|
+
const firstLineLeading = block.split(`
|
|
932
|
+
`)[0].match(/^ */)?.[0].length ?? 0;
|
|
933
|
+
const firstLineDelta = e.shiftKey ? -Math.min(2, firstLineLeading) : INDENT.length;
|
|
934
|
+
replaceRange(ta, lineStart, end, transformed, Math.max(lineStart, start + firstLineDelta), end + delta);
|
|
870
935
|
return true;
|
|
871
936
|
}
|
|
872
937
|
function handleEnterKey(ta, e) {
|
|
@@ -884,15 +949,11 @@ function handleEnterKey(ta, e) {
|
|
|
884
949
|
return false;
|
|
885
950
|
e.preventDefault();
|
|
886
951
|
if (result.eraseCurrentLine) {
|
|
887
|
-
|
|
888
|
-
ta.value = newValue;
|
|
889
|
-
ta.setSelectionRange(lineStart, lineStart);
|
|
952
|
+
replaceRange(ta, lineStart, pos, "", lineStart);
|
|
890
953
|
} else {
|
|
891
|
-
const
|
|
892
|
-
` + result.prefix
|
|
893
|
-
|
|
894
|
-
ta.value = newValue;
|
|
895
|
-
ta.setSelectionRange(newPos, newPos);
|
|
954
|
+
const insert = `
|
|
955
|
+
` + result.prefix;
|
|
956
|
+
replaceRange(ta, pos, pos, insert, pos + insert.length);
|
|
896
957
|
}
|
|
897
958
|
return true;
|
|
898
959
|
}
|
|
@@ -954,7 +1015,8 @@ class ElDmMarkdownInput extends BaseElement {
|
|
|
954
1015
|
livePreview: { type: Boolean, reflect: true, attribute: "live-preview" },
|
|
955
1016
|
debounce: { type: Number, reflect: true, default: 300 },
|
|
956
1017
|
katexCssUrl: { type: String, reflect: true, attribute: "katex-css-url" },
|
|
957
|
-
mermaidSrc: { type: String, reflect: true, attribute: "mermaid-src" }
|
|
1018
|
+
mermaidSrc: { type: String, reflect: true, attribute: "mermaid-src" },
|
|
1019
|
+
resize: { type: String, reflect: true, default: "none" }
|
|
958
1020
|
};
|
|
959
1021
|
#internals;
|
|
960
1022
|
#initialized = false;
|
|
@@ -1170,6 +1232,14 @@ class ElDmMarkdownInput extends BaseElement {
|
|
|
1170
1232
|
this.#scheduleHighlight();
|
|
1171
1233
|
return;
|
|
1172
1234
|
}
|
|
1235
|
+
if (e.key === "Tab" && !e.ctrlKey && !e.metaKey) {
|
|
1236
|
+
if (handleTabKey(ta, e)) {
|
|
1237
|
+
this.#syncFormValue();
|
|
1238
|
+
this.emit("change", { value: ta.value });
|
|
1239
|
+
this.#scheduleHighlight();
|
|
1240
|
+
}
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1173
1243
|
if (e.key === "Enter" && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
1174
1244
|
if (handleEnterKey(ta, e)) {
|
|
1175
1245
|
this.#syncFormValue();
|
|
@@ -1540,6 +1610,57 @@ class ElDmMarkdownInput extends BaseElement {
|
|
|
1540
1610
|
} else {
|
|
1541
1611
|
this.#textarea?.removeAttribute("aria-activedescendant");
|
|
1542
1612
|
}
|
|
1613
|
+
const coords = this.#getCaretCoords();
|
|
1614
|
+
if (coords) {
|
|
1615
|
+
this.#acDropdown.style.top = `${coords.top}px`;
|
|
1616
|
+
this.#acDropdown.style.left = `${coords.left}px`;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
#getCaretCoords() {
|
|
1620
|
+
const ta = this.#textarea;
|
|
1621
|
+
if (!ta)
|
|
1622
|
+
return null;
|
|
1623
|
+
const pos = ta.selectionStart ?? 0;
|
|
1624
|
+
const cs = getComputedStyle(ta);
|
|
1625
|
+
const taRect = ta.getBoundingClientRect();
|
|
1626
|
+
const mirror = document.createElement("div");
|
|
1627
|
+
Object.assign(mirror.style, {
|
|
1628
|
+
position: "fixed",
|
|
1629
|
+
visibility: "hidden",
|
|
1630
|
+
pointerEvents: "none",
|
|
1631
|
+
top: `${taRect.top}px`,
|
|
1632
|
+
left: `${taRect.left}px`,
|
|
1633
|
+
width: `${taRect.width}px`,
|
|
1634
|
+
font: cs.font,
|
|
1635
|
+
letterSpacing: cs.letterSpacing,
|
|
1636
|
+
paddingTop: cs.paddingTop,
|
|
1637
|
+
paddingRight: cs.paddingRight,
|
|
1638
|
+
paddingBottom: cs.paddingBottom,
|
|
1639
|
+
paddingLeft: cs.paddingLeft,
|
|
1640
|
+
borderTopWidth: cs.borderTopWidth,
|
|
1641
|
+
borderRightWidth: cs.borderRightWidth,
|
|
1642
|
+
borderBottomWidth: cs.borderBottomWidth,
|
|
1643
|
+
borderLeftWidth: cs.borderLeftWidth,
|
|
1644
|
+
boxSizing: cs.boxSizing,
|
|
1645
|
+
whiteSpace: "pre-wrap",
|
|
1646
|
+
wordBreak: "break-word",
|
|
1647
|
+
overflowWrap: cs.overflowWrap,
|
|
1648
|
+
overflow: "hidden"
|
|
1649
|
+
});
|
|
1650
|
+
const before = document.createTextNode(ta.value.substring(0, pos));
|
|
1651
|
+
const marker = document.createElement("span");
|
|
1652
|
+
marker.textContent = "";
|
|
1653
|
+
mirror.appendChild(before);
|
|
1654
|
+
mirror.appendChild(marker);
|
|
1655
|
+
document.body.appendChild(mirror);
|
|
1656
|
+
const markerRect = marker.getBoundingClientRect();
|
|
1657
|
+
document.body.removeChild(mirror);
|
|
1658
|
+
const hostRect = this.getBoundingClientRect();
|
|
1659
|
+
const lineHeight = parseFloat(cs.lineHeight) || 20;
|
|
1660
|
+
return {
|
|
1661
|
+
top: markerRect.top - hostRect.top - ta.scrollTop + lineHeight,
|
|
1662
|
+
left: Math.max(0, markerRect.left - hostRect.left)
|
|
1663
|
+
};
|
|
1543
1664
|
}
|
|
1544
1665
|
#scheduleStatusUpdate() {
|
|
1545
1666
|
if (this.#statusTimer !== null)
|
|
@@ -1633,5 +1754,5 @@ export {
|
|
|
1633
1754
|
ElDmMarkdownInput
|
|
1634
1755
|
};
|
|
1635
1756
|
|
|
1636
|
-
//# debugId=
|
|
1757
|
+
//# debugId=97DFE6DBE61B280764756E2164756E21
|
|
1637
1758
|
//# sourceMappingURL=index.js.map
|