@panphora/bevel 0.3.2 → 0.3.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.
- package/bevel.css +1 -0
- package/dist/react/theme.css +4 -0
- package/package.json +1 -1
- package/vendor/richclay/provenance.json +3 -3
- package/vendor/richclay/richclay.css +24 -0
- package/vendor/richclay/richclay.js +288 -16
package/bevel.css
CHANGED
|
@@ -1595,6 +1595,7 @@
|
|
|
1595
1595
|
.bevel-fieldset { border: 1px solid var(--bevel-line); border-radius: 0; padding: 16px 20px 18px; margin: 0; min-width: 0; }
|
|
1596
1596
|
.bevel-fieldset > legend { padding: 0 8px; margin-left: -8px; }
|
|
1597
1597
|
.bevel-form { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 24px; }
|
|
1598
|
+
.bevel-form > .bevel-field + .bevel-field { margin-block-start: 0; }
|
|
1598
1599
|
.bevel-form__full { grid-column: 1 / -1; }
|
|
1599
1600
|
@media (max-width: 640px) { .bevel-form { grid-template-columns: 1fr; } }
|
|
1600
1601
|
.bevel-choices { display: flex; flex-wrap: wrap; gap: 12px 26px; }
|
package/dist/react/theme.css
CHANGED
|
@@ -4047,6 +4047,10 @@ a.bevel-r-file__name:where([data-bevel-appearance="bevel"]) { text-decoration: u
|
|
|
4047
4047
|
|
|
4048
4048
|
|
|
4049
4049
|
|
|
4050
|
+
.bevel-r-form > .bevel-r-field + .bevel-r-field:where([data-bevel-appearance="bevel"]) { margin-block-start: 0; }
|
|
4051
|
+
|
|
4052
|
+
|
|
4053
|
+
|
|
4050
4054
|
.bevel-r-form__full:where([data-bevel-appearance="bevel"]) { grid-column: 1 / -1; }
|
|
4051
4055
|
|
|
4052
4056
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richclay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"source": "Explicitly supplied local distribution; hashes identify the exact tested bytes, not a published release claim.",
|
|
5
5
|
"files": {
|
|
6
|
-
"richclay.js": "
|
|
7
|
-
"richclay.css": "
|
|
6
|
+
"richclay.js": "17d57ae64eac48f8fc57b90f0f18c9112fcb4194ef2b5b20096695d697e629a5",
|
|
7
|
+
"richclay.css": "b2aed9608266f6798ea25edfa40c344c84b8f37d65e19ce8484f93ad849cd3c8",
|
|
8
8
|
"LICENSE": "5312a8ef6ff9a844697b8b112612467efd12957e8afba098ce600dec0f8396c4",
|
|
9
9
|
"THIRD-PARTY-NOTICES.md": "3659529614b62297b8336ac7f6189ef9f67425911265bff1cde8d136df21562b",
|
|
10
10
|
"vendor/squire.js": "00f2b05431457a05f1b42e4f876b8a334c0e01841ed57a3aa47e6a0e463869c3",
|
|
@@ -314,6 +314,30 @@
|
|
|
314
314
|
font-weight: 650;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
.richclay-dialog.richclay-link-popover {
|
|
318
|
+
align-items: center;
|
|
319
|
+
display: flex;
|
|
320
|
+
gap: 6px;
|
|
321
|
+
max-width: min(420px, calc(100vw - 16px));
|
|
322
|
+
padding: 6px;
|
|
323
|
+
width: auto;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.richclay-link-address {
|
|
327
|
+
color: inherit;
|
|
328
|
+
font-size: 0.875rem;
|
|
329
|
+
max-width: 260px;
|
|
330
|
+
overflow: hidden;
|
|
331
|
+
padding: 0 6px;
|
|
332
|
+
text-overflow: ellipsis;
|
|
333
|
+
white-space: nowrap;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.richclay-link-popover .richclay-secondary {
|
|
337
|
+
min-height: 30px;
|
|
338
|
+
padding: 3px 9px;
|
|
339
|
+
}
|
|
340
|
+
|
|
317
341
|
.richclay-field {
|
|
318
342
|
display: grid;
|
|
319
343
|
gap: 4px;
|
|
@@ -542,7 +542,7 @@ var RichClayBundle = (() => {
|
|
|
542
542
|
if (!el.tagName.includes("-")) return true;
|
|
543
543
|
return el.matches(RICHCLAY_OPT_IN);
|
|
544
544
|
}
|
|
545
|
-
var CHROME_SELECTOR = "[data-richclay-toolbar], [data-richclay-menu], [data-richclay-dialog], [data-richclay-live], [data-richclay-float]";
|
|
545
|
+
var CHROME_SELECTOR = "[data-richclay-toolbar], [data-richclay-menu], [data-richclay-dialog], [data-richclay-link-popover], [data-richclay-live], [data-richclay-float]";
|
|
546
546
|
var runtimeClasses = [
|
|
547
547
|
"richclay-editor",
|
|
548
548
|
"richclay-inline",
|
|
@@ -612,6 +612,11 @@ var RichClayBundle = (() => {
|
|
|
612
612
|
toolbarOnSelect: tokens.has("toolbar-on-select")
|
|
613
613
|
};
|
|
614
614
|
if (tokens.has("no-toolbar")) options.toolbar = false;
|
|
615
|
+
if (tokens.has("link-label")) {
|
|
616
|
+
options.singleLine = true;
|
|
617
|
+
options.linkLabel = true;
|
|
618
|
+
options.toolbar = ["link", "undo", "redo"];
|
|
619
|
+
}
|
|
615
620
|
return options;
|
|
616
621
|
}
|
|
617
622
|
function installHyperclayBridge(win = window) {
|
|
@@ -1448,12 +1453,15 @@ var RichClayBundle = (() => {
|
|
|
1448
1453
|
const scheme = (stripped.match(/^([a-z][a-z0-9+.\-]*):/i) || [])[1];
|
|
1449
1454
|
return !scheme || SAFE_URL_SCHEMES.has(scheme.toLowerCase());
|
|
1450
1455
|
}
|
|
1456
|
+
var SITE_FILE = /^[^\s/?#:]+\.(?:html?|pdf|txt|csv|json|xml|svg|png|jpe?g|gif|webp|docx?|xlsx?|pptx?|bib|tex|zip)(?:[?#]\S*)?$/i;
|
|
1457
|
+
var SITE_PATH = /^[^\s/?#:.]+\/\S*$/;
|
|
1451
1458
|
function normalizeUrl(value) {
|
|
1452
1459
|
const raw = String(value == null ? "" : value).trim();
|
|
1453
1460
|
if (!raw || !isSafeUrl(raw)) return "";
|
|
1454
1461
|
if (/^(?:https?:|mailto:|tel:)/i.test(raw)) return raw;
|
|
1455
1462
|
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw)) return `mailto:${raw}`;
|
|
1456
1463
|
if (/^(?:[#?]|\/|\.{1,2}\/)/.test(raw)) return raw;
|
|
1464
|
+
if (SITE_FILE.test(raw) || SITE_PATH.test(raw)) return raw;
|
|
1457
1465
|
return `https://${raw}`;
|
|
1458
1466
|
}
|
|
1459
1467
|
function resolveDOMPurify(doc) {
|
|
@@ -1524,6 +1532,167 @@ var RichClayBundle = (() => {
|
|
|
1524
1532
|
if (last?.nodeType === 3) last.nodeValue = last.nodeValue.replace(/\s+$/, "");
|
|
1525
1533
|
}
|
|
1526
1534
|
|
|
1535
|
+
// src/link-popover.js
|
|
1536
|
+
var LINK_POPOVER_GRACE_MS = 300;
|
|
1537
|
+
var shownAddress = (href) => href.replace(/^mailto:/i, "").replace(/^https?:\/\//i, "");
|
|
1538
|
+
var LinkPopover = class {
|
|
1539
|
+
constructor(editor) {
|
|
1540
|
+
this.editor = editor;
|
|
1541
|
+
this.root = null;
|
|
1542
|
+
this.anchor = null;
|
|
1543
|
+
this.href = null;
|
|
1544
|
+
this.dismissed = null;
|
|
1545
|
+
this.hideTimer = null;
|
|
1546
|
+
this.pointerInside = false;
|
|
1547
|
+
this.cleanup = null;
|
|
1548
|
+
}
|
|
1549
|
+
currentAnchor() {
|
|
1550
|
+
const editor = this.editor;
|
|
1551
|
+
if (!editor.active || editor.options.readOnly || editor.dialog) return null;
|
|
1552
|
+
if (editor.element.ownerDocument.hidden) return null;
|
|
1553
|
+
return editor.currentLinkElement();
|
|
1554
|
+
}
|
|
1555
|
+
update() {
|
|
1556
|
+
const anchor = this.currentAnchor();
|
|
1557
|
+
if (anchor && anchor === this.dismissed) return;
|
|
1558
|
+
this.dismissed = null;
|
|
1559
|
+
if (!anchor) {
|
|
1560
|
+
this.scheduleHide();
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
this.cancelHide();
|
|
1564
|
+
this.show(anchor);
|
|
1565
|
+
}
|
|
1566
|
+
show(anchor) {
|
|
1567
|
+
if (!this.root) this.mount();
|
|
1568
|
+
const href = anchor.getAttribute("href") || "";
|
|
1569
|
+
if (anchor !== this.anchor || href !== this.href) {
|
|
1570
|
+
this.anchor = anchor;
|
|
1571
|
+
this.href = href;
|
|
1572
|
+
const address = this.root.querySelector(".richclay-link-address");
|
|
1573
|
+
address.textContent = shownAddress(href) || href;
|
|
1574
|
+
address.title = href;
|
|
1575
|
+
if (href && isSafeUrl(href)) address.setAttribute("href", href);
|
|
1576
|
+
else address.removeAttribute("href");
|
|
1577
|
+
if (/^(?:mailto|tel):/i.test(href)) address.removeAttribute("target");
|
|
1578
|
+
else address.setAttribute("target", "_blank");
|
|
1579
|
+
}
|
|
1580
|
+
this.position();
|
|
1581
|
+
}
|
|
1582
|
+
mount() {
|
|
1583
|
+
const editor = this.editor;
|
|
1584
|
+
const doc = editor.element.ownerDocument;
|
|
1585
|
+
const win = doc.defaultView || globalThis;
|
|
1586
|
+
const root = doc.createElement("div");
|
|
1587
|
+
root.className = "richclay-dialog richclay-link-popover";
|
|
1588
|
+
root.setAttribute("role", "group");
|
|
1589
|
+
root.setAttribute("aria-label", "Link");
|
|
1590
|
+
root.setAttribute("data-richclay-link-popover", "");
|
|
1591
|
+
markChrome(root);
|
|
1592
|
+
root.setAttribute("save-remove", "");
|
|
1593
|
+
root.innerHTML = `
|
|
1594
|
+
<a class="richclay-link-address" rel="noopener noreferrer"></a>
|
|
1595
|
+
<button type="button" class="richclay-secondary" data-richclay-link-edit>Edit</button>
|
|
1596
|
+
<button type="button" class="richclay-secondary" data-richclay-link-remove>Remove</button>
|
|
1597
|
+
`;
|
|
1598
|
+
if (editor.options.linkLabel) root.querySelector("[data-richclay-link-remove]").remove();
|
|
1599
|
+
root.addEventListener("pointerdown", preservePointerSelection);
|
|
1600
|
+
root.addEventListener("pointerenter", () => {
|
|
1601
|
+
this.pointerInside = true;
|
|
1602
|
+
this.cancelHide();
|
|
1603
|
+
});
|
|
1604
|
+
root.addEventListener("pointerleave", () => {
|
|
1605
|
+
this.pointerInside = false;
|
|
1606
|
+
this.update();
|
|
1607
|
+
});
|
|
1608
|
+
root.querySelector("[data-richclay-link-edit]").addEventListener("click", () => {
|
|
1609
|
+
const anchor = this.anchor;
|
|
1610
|
+
if (!anchor || !editor.element.contains(anchor)) return this.hide();
|
|
1611
|
+
editor.selectElement(anchor);
|
|
1612
|
+
editor.openLinkDialog();
|
|
1613
|
+
});
|
|
1614
|
+
root.querySelector("[data-richclay-link-remove]")?.addEventListener("click", () => {
|
|
1615
|
+
const anchor = this.anchor;
|
|
1616
|
+
this.hide();
|
|
1617
|
+
editor.removeLinkElement(anchor);
|
|
1618
|
+
editor.focus();
|
|
1619
|
+
});
|
|
1620
|
+
const host = editor.element.closest("dialog") || doc.body;
|
|
1621
|
+
const layered = typeof root.showPopover === "function";
|
|
1622
|
+
if (layered) root.setAttribute("popover", "manual");
|
|
1623
|
+
pin(root, "position", "fixed");
|
|
1624
|
+
pin(root, "inset", "0px auto auto 0px");
|
|
1625
|
+
pin(root, "margin", "0");
|
|
1626
|
+
pin(root, "overflow", "visible");
|
|
1627
|
+
host.appendChild(root);
|
|
1628
|
+
if (layered) root.showPopover();
|
|
1629
|
+
this.root = root;
|
|
1630
|
+
const reposition = () => this.position();
|
|
1631
|
+
const onKeydown = (event) => {
|
|
1632
|
+
if (event.key !== "Escape" || !this.root) return;
|
|
1633
|
+
this.dismissed = this.anchor;
|
|
1634
|
+
this.hide();
|
|
1635
|
+
};
|
|
1636
|
+
doc.addEventListener("scroll", reposition, { capture: true, passive: true });
|
|
1637
|
+
win.addEventListener("resize", reposition, { passive: true });
|
|
1638
|
+
editor.element.addEventListener("keydown", onKeydown);
|
|
1639
|
+
this.cleanup = () => {
|
|
1640
|
+
doc.removeEventListener("scroll", reposition, { capture: true });
|
|
1641
|
+
win.removeEventListener("resize", reposition);
|
|
1642
|
+
editor.element.removeEventListener("keydown", onKeydown);
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
position() {
|
|
1646
|
+
const root = this.root;
|
|
1647
|
+
if (!root || !this.anchor) return;
|
|
1648
|
+
const win = root.ownerDocument.defaultView || globalThis;
|
|
1649
|
+
const placement = placePopover({
|
|
1650
|
+
anchor: this.anchor.getBoundingClientRect(),
|
|
1651
|
+
box: { width: root.offsetWidth || 0, height: root.offsetHeight || 0 },
|
|
1652
|
+
viewport: {
|
|
1653
|
+
width: win.visualViewport?.width ?? win.innerWidth,
|
|
1654
|
+
height: win.visualViewport?.height ?? win.innerHeight
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
root.setAttribute("data-richclay-side", placement.side);
|
|
1658
|
+
root.style.setProperty("--richclay-dialog-arrow", `${Math.round(placement.arrow)}px`);
|
|
1659
|
+
pin(root, "transform", `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px)`);
|
|
1660
|
+
}
|
|
1661
|
+
scheduleHide() {
|
|
1662
|
+
if (!this.root || this.hideTimer !== null) return;
|
|
1663
|
+
const win = this.root.ownerDocument.defaultView || globalThis;
|
|
1664
|
+
this.hideTimer = win.setTimeout(() => {
|
|
1665
|
+
this.hideTimer = null;
|
|
1666
|
+
if (!this.pointerInside) this.hide();
|
|
1667
|
+
}, LINK_POPOVER_GRACE_MS);
|
|
1668
|
+
}
|
|
1669
|
+
cancelHide() {
|
|
1670
|
+
if (this.hideTimer === null) return;
|
|
1671
|
+
const win = this.editor.element.ownerDocument.defaultView || globalThis;
|
|
1672
|
+
win.clearTimeout(this.hideTimer);
|
|
1673
|
+
this.hideTimer = null;
|
|
1674
|
+
}
|
|
1675
|
+
onEditorBlur(event) {
|
|
1676
|
+
const next = event?.relatedTarget;
|
|
1677
|
+
if (next && this.editor.ownsFocusTarget(next)) return;
|
|
1678
|
+
this.hide();
|
|
1679
|
+
}
|
|
1680
|
+
hide() {
|
|
1681
|
+
this.cancelHide();
|
|
1682
|
+
this.cleanup?.();
|
|
1683
|
+
this.cleanup = null;
|
|
1684
|
+
this.root?.remove();
|
|
1685
|
+
this.root = null;
|
|
1686
|
+
this.anchor = null;
|
|
1687
|
+
this.href = null;
|
|
1688
|
+
this.pointerInside = false;
|
|
1689
|
+
}
|
|
1690
|
+
destroy() {
|
|
1691
|
+
this.hide();
|
|
1692
|
+
this.dismissed = null;
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1527
1696
|
// src/styles.js
|
|
1528
1697
|
var STYLE_ID = "richclay-styles";
|
|
1529
1698
|
var styledDocs = /* @__PURE__ */ new WeakSet();
|
|
@@ -1807,8 +1976,11 @@ var RichClayBundle = (() => {
|
|
|
1807
1976
|
Squire: null,
|
|
1808
1977
|
inline: false,
|
|
1809
1978
|
singleLine: false,
|
|
1810
|
-
toolbarOnSelect: false
|
|
1979
|
+
toolbarOnSelect: false,
|
|
1980
|
+
linkPopover: true,
|
|
1981
|
+
linkLabel: false
|
|
1811
1982
|
};
|
|
1983
|
+
var LINK_LABEL_CONTROLS = ["link", "undo", "redo"];
|
|
1812
1984
|
var RichClay = class _RichClay {
|
|
1813
1985
|
static presets = presets;
|
|
1814
1986
|
static selector = RICHCLAY_SELECTOR;
|
|
@@ -1834,6 +2006,7 @@ var RichClayBundle = (() => {
|
|
|
1834
2006
|
this.description = null;
|
|
1835
2007
|
this.dialog = null;
|
|
1836
2008
|
this.dialogCleanup = null;
|
|
2009
|
+
this.linkPopover = null;
|
|
1837
2010
|
this.savedSelection = null;
|
|
1838
2011
|
this.path = "";
|
|
1839
2012
|
this.active = false;
|
|
@@ -1853,6 +2026,7 @@ var RichClayBundle = (() => {
|
|
|
1853
2026
|
};
|
|
1854
2027
|
this._onBlur = (event) => {
|
|
1855
2028
|
this.element.classList.remove("richclay-focused");
|
|
2029
|
+
this.linkPopover?.onEditorBlur(event);
|
|
1856
2030
|
if (this.options.inline) this.scheduleFloatTeardown(event);
|
|
1857
2031
|
};
|
|
1858
2032
|
this._onLinkActivation = (event) => {
|
|
@@ -1864,6 +2038,13 @@ var RichClayBundle = (() => {
|
|
|
1864
2038
|
if (chrome && chrome !== this.element && this.element.contains(chrome) && chrome.contains(anchor)) return;
|
|
1865
2039
|
event.preventDefault();
|
|
1866
2040
|
};
|
|
2041
|
+
this._onExternalWrite = () => {
|
|
2042
|
+
if (!this._squire) return;
|
|
2043
|
+
this._squire._mutation?.takeRecords();
|
|
2044
|
+
if (this.options.linkLabel) this._labelHref = this.element.querySelector("a[href]")?.getAttribute("href") ?? this._labelHref;
|
|
2045
|
+
this.resetSquireUndoBaseline();
|
|
2046
|
+
this.updatePlaceholder();
|
|
2047
|
+
};
|
|
1867
2048
|
this.ensureMarker();
|
|
1868
2049
|
this.hyperclay = shouldUseHyperclay(this.options, this.window);
|
|
1869
2050
|
if (this.hyperclay) installHyperclayBridge(this.window);
|
|
@@ -2063,6 +2244,7 @@ var RichClayBundle = (() => {
|
|
|
2063
2244
|
const ignored = editor._ignoreChange;
|
|
2064
2245
|
editor._docWasChanged();
|
|
2065
2246
|
if (local && ignored) editor.fireEvent("input");
|
|
2247
|
+
if (this.options.linkLabel) this.keepOneLink();
|
|
2066
2248
|
} else if (local) {
|
|
2067
2249
|
editor._mayHaveZWS = true;
|
|
2068
2250
|
editor.fireEvent("input");
|
|
@@ -2091,6 +2273,7 @@ var RichClayBundle = (() => {
|
|
|
2091
2273
|
this.maskSquireCodeShortcut();
|
|
2092
2274
|
this.maskSquireBlockShortcuts();
|
|
2093
2275
|
this.installShortcuts();
|
|
2276
|
+
if (this.options.linkLabel) this.maskFormattingShortcuts();
|
|
2094
2277
|
if (this.options.inline) {
|
|
2095
2278
|
this._onToolbarKey = (event) => {
|
|
2096
2279
|
if (event.altKey && event.key === "F10") {
|
|
@@ -2114,6 +2297,17 @@ var RichClayBundle = (() => {
|
|
|
2114
2297
|
this._squire.addEventListener("willPaste", willPaste);
|
|
2115
2298
|
this._squireListeners.push(["willPaste", willPaste]);
|
|
2116
2299
|
}
|
|
2300
|
+
if (this.options.linkLabel) {
|
|
2301
|
+
const plainPaste = (event) => {
|
|
2302
|
+
const fragment = event.detail?.fragment;
|
|
2303
|
+
if (!fragment) return;
|
|
2304
|
+
const text = fragment.textContent.replace(/\s+/g, " ");
|
|
2305
|
+
fragment.replaceChildren(this.element.ownerDocument.createTextNode(text));
|
|
2306
|
+
};
|
|
2307
|
+
this._squire.addEventListener("willPaste", plainPaste);
|
|
2308
|
+
this._squireListeners.push(["willPaste", plainPaste]);
|
|
2309
|
+
this._labelHref = this.element.querySelector("a[href]")?.getAttribute("href") ?? null;
|
|
2310
|
+
}
|
|
2117
2311
|
this.renderToolbar();
|
|
2118
2312
|
this.updatePlaceholder();
|
|
2119
2313
|
this.warnOnBlockInInlineRegion();
|
|
@@ -2178,6 +2372,8 @@ var RichClayBundle = (() => {
|
|
|
2178
2372
|
}
|
|
2179
2373
|
destroy() {
|
|
2180
2374
|
this.closeLinkDialog();
|
|
2375
|
+
this.linkPopover?.destroy();
|
|
2376
|
+
this.linkPopover = null;
|
|
2181
2377
|
this.teardownFloatingToolbar();
|
|
2182
2378
|
this.toolbar?.destroy();
|
|
2183
2379
|
this.toolbar = null;
|
|
@@ -2420,6 +2616,7 @@ var RichClayBundle = (() => {
|
|
|
2420
2616
|
openLinkDialog() {
|
|
2421
2617
|
this.saveSelection();
|
|
2422
2618
|
this.closeLinkDialog();
|
|
2619
|
+
this.linkPopover?.hide();
|
|
2423
2620
|
const doc = this.element.ownerDocument;
|
|
2424
2621
|
const existingLink = this.currentLinkElement();
|
|
2425
2622
|
const existing = existingLink?.getAttribute("href") || "";
|
|
@@ -2439,7 +2636,7 @@ var RichClayBundle = (() => {
|
|
|
2439
2636
|
<input class="richclay-input" name="url" type="text" inputmode="url" autocomplete="url" placeholder="https://example.com">
|
|
2440
2637
|
</label>
|
|
2441
2638
|
<div class="richclay-dialog-actions">
|
|
2442
|
-
${existingLink ? '<button type="button" class="richclay-secondary" data-richclay-remove-link>Remove link</button>' : ""}
|
|
2639
|
+
${existingLink && !this.options.linkLabel ? '<button type="button" class="richclay-secondary" data-richclay-remove-link>Remove link</button>' : ""}
|
|
2443
2640
|
<button type="button" class="richclay-secondary" data-richclay-cancel>Cancel</button>
|
|
2444
2641
|
<button type="submit" class="richclay-primary">Apply</button>
|
|
2445
2642
|
</div>
|
|
@@ -2448,17 +2645,18 @@ var RichClayBundle = (() => {
|
|
|
2448
2645
|
this.closeLinkDialog();
|
|
2449
2646
|
this.toolbar?.update();
|
|
2450
2647
|
this.focus();
|
|
2648
|
+
this.updateLinkPopover();
|
|
2451
2649
|
};
|
|
2452
2650
|
const remove = () => {
|
|
2453
2651
|
this.restoreSelection();
|
|
2454
|
-
|
|
2455
|
-
if (anchor) {
|
|
2456
|
-
this.selectElement(anchor);
|
|
2457
|
-
this._squire.removeLink();
|
|
2458
|
-
announce(this.liveRegion, "Link removed");
|
|
2459
|
-
}
|
|
2652
|
+
this.removeLinkElement(this.currentLinkElement());
|
|
2460
2653
|
close();
|
|
2461
2654
|
};
|
|
2655
|
+
const cancel = () => {
|
|
2656
|
+
close();
|
|
2657
|
+
this.restoreSelection();
|
|
2658
|
+
this.updateLinkPopover();
|
|
2659
|
+
};
|
|
2462
2660
|
dialog.addEventListener("submit", (event) => {
|
|
2463
2661
|
event.preventDefault();
|
|
2464
2662
|
const data = new FormData(dialog);
|
|
@@ -2473,7 +2671,12 @@ var RichClayBundle = (() => {
|
|
|
2473
2671
|
return;
|
|
2474
2672
|
}
|
|
2475
2673
|
this.restoreSelection();
|
|
2476
|
-
const anchor = this.currentLinkElement();
|
|
2674
|
+
const anchor = this.options.linkLabel ? null : this.currentLinkElement();
|
|
2675
|
+
if (this.options.linkLabel) {
|
|
2676
|
+
const range = this.element.ownerDocument.createRange();
|
|
2677
|
+
range.selectNodeContents(this.element);
|
|
2678
|
+
this._squire.setSelection(range);
|
|
2679
|
+
}
|
|
2477
2680
|
if (anchor) {
|
|
2478
2681
|
this.selectElement(anchor);
|
|
2479
2682
|
this._squire.saveUndoState();
|
|
@@ -2485,11 +2688,11 @@ var RichClayBundle = (() => {
|
|
|
2485
2688
|
close();
|
|
2486
2689
|
});
|
|
2487
2690
|
dialog.querySelector("[data-richclay-remove-link]")?.addEventListener("click", remove);
|
|
2488
|
-
dialog.querySelector("[data-richclay-cancel]").addEventListener("click",
|
|
2691
|
+
dialog.querySelector("[data-richclay-cancel]").addEventListener("click", cancel);
|
|
2489
2692
|
dialog.addEventListener("keydown", (event) => {
|
|
2490
2693
|
if (event.key === "Escape") {
|
|
2491
2694
|
event.preventDefault();
|
|
2492
|
-
|
|
2695
|
+
cancel();
|
|
2493
2696
|
} else if (event.key === "Tab") {
|
|
2494
2697
|
trapTab(event, dialog);
|
|
2495
2698
|
}
|
|
@@ -2531,6 +2734,23 @@ var RichClayBundle = (() => {
|
|
|
2531
2734
|
input.select();
|
|
2532
2735
|
return KEEP_FOCUS;
|
|
2533
2736
|
}
|
|
2737
|
+
// Unwraps one link and keeps its text, as one undo step. The link form's Remove and the
|
|
2738
|
+
// link popover's Remove are this same call.
|
|
2739
|
+
removeLinkElement(anchor) {
|
|
2740
|
+
if (!anchor || !this.element.contains(anchor)) return false;
|
|
2741
|
+
this.selectElement(anchor);
|
|
2742
|
+
this._squire.removeLink();
|
|
2743
|
+
announce(this.liveRegion, "Link removed");
|
|
2744
|
+
return true;
|
|
2745
|
+
}
|
|
2746
|
+
updateLinkPopover() {
|
|
2747
|
+
if (this.options.linkPopover === false || this.options.readOnly || !this._squire) return;
|
|
2748
|
+
if (!this.linkPopover) {
|
|
2749
|
+
if (this.dialog || !this.currentLinkElement()) return;
|
|
2750
|
+
this.linkPopover = new LinkPopover(this);
|
|
2751
|
+
}
|
|
2752
|
+
this.linkPopover.update();
|
|
2753
|
+
}
|
|
2534
2754
|
currentLinkElement() {
|
|
2535
2755
|
const range = getSquireSelection(this._squire);
|
|
2536
2756
|
if (!range || !this.element.contains(range.commonAncestorContainer)) return null;
|
|
@@ -2649,6 +2869,7 @@ var RichClayBundle = (() => {
|
|
|
2649
2869
|
this.element.addEventListener("blur", this._onBlur);
|
|
2650
2870
|
this.element.addEventListener("click", this._onLinkActivation, true);
|
|
2651
2871
|
this.element.addEventListener("auxclick", this._onLinkActivation, true);
|
|
2872
|
+
this.element.addEventListener("clay:external-write", this._onExternalWrite);
|
|
2652
2873
|
}
|
|
2653
2874
|
cleanupEditorAttributes() {
|
|
2654
2875
|
removeRuntimeState(this.element, "destroy");
|
|
@@ -2656,6 +2877,41 @@ var RichClayBundle = (() => {
|
|
|
2656
2877
|
this.element.removeEventListener("blur", this._onBlur);
|
|
2657
2878
|
this.element.removeEventListener("click", this._onLinkActivation, true);
|
|
2658
2879
|
this.element.removeEventListener("auxclick", this._onLinkActivation, true);
|
|
2880
|
+
this.element.removeEventListener("clay:external-write", this._onExternalWrite);
|
|
2881
|
+
}
|
|
2882
|
+
// A link label holds one link around all of its text, the shape its store reads back
|
|
2883
|
+
// as text and address. Squire puts a space typed at a link's end, and a paste, outside
|
|
2884
|
+
// the link (or splits it), and typing over the whole label deletes it; each of those
|
|
2885
|
+
// is folded back into the one link, with the caret kept at the same character.
|
|
2886
|
+
keepOneLink() {
|
|
2887
|
+
const element = this.element;
|
|
2888
|
+
const doc = element.ownerDocument;
|
|
2889
|
+
const first = element.querySelector("a[href]");
|
|
2890
|
+
const href = first?.getAttribute("href") ?? this._labelHref;
|
|
2891
|
+
if (!href) return;
|
|
2892
|
+
this._labelHref = href;
|
|
2893
|
+
const text = element.textContent.replace(/\u200B/g, "").replace(/\s*[\r\n]+\s*/g, " ");
|
|
2894
|
+
const only = element.childNodes.length === 1 && element.firstChild === first ? first : null;
|
|
2895
|
+
const clean = only && [...only.childNodes].every((node) => node.nodeType === 3) && !only.textContent.includes("\u200B");
|
|
2896
|
+
if (clean || !text) return;
|
|
2897
|
+
const selection = this._squire.getSelection();
|
|
2898
|
+
let caret = text.length;
|
|
2899
|
+
if (selection && element.contains(selection.startContainer)) {
|
|
2900
|
+
const before = doc.createRange();
|
|
2901
|
+
before.selectNodeContents(element);
|
|
2902
|
+
before.setEnd(selection.startContainer, selection.startOffset);
|
|
2903
|
+
caret = before.toString().replace(/\u200B/g, "").replace(/\s*[\r\n]+\s*/g, " ").length;
|
|
2904
|
+
}
|
|
2905
|
+
this._squire.modifyDocument(() => {
|
|
2906
|
+
const anchor = first ?? doc.createElement("a");
|
|
2907
|
+
if (!first) anchor.setAttribute("href", href);
|
|
2908
|
+
anchor.replaceChildren(doc.createTextNode(text));
|
|
2909
|
+
element.replaceChildren(anchor);
|
|
2910
|
+
const range = doc.createRange();
|
|
2911
|
+
range.setStart(anchor.firstChild, Math.min(caret, text.length));
|
|
2912
|
+
range.collapse(true);
|
|
2913
|
+
this._squire.setSelection(range);
|
|
2914
|
+
});
|
|
2659
2915
|
}
|
|
2660
2916
|
bindSquire() {
|
|
2661
2917
|
const input = () => {
|
|
@@ -2663,16 +2919,19 @@ var RichClayBundle = (() => {
|
|
|
2663
2919
|
this.updatePlaceholder();
|
|
2664
2920
|
this.options.onChange?.(this.getHTML());
|
|
2665
2921
|
this.toolbar?.update();
|
|
2922
|
+
this.updateLinkPopover();
|
|
2666
2923
|
};
|
|
2667
2924
|
const pathChange = (event) => {
|
|
2668
2925
|
this.path = event.detail?.path || this._squire.getPath?.() || "";
|
|
2669
2926
|
this.saveSelection();
|
|
2670
2927
|
this.toolbar?.update();
|
|
2928
|
+
this.updateLinkPopover();
|
|
2671
2929
|
};
|
|
2672
2930
|
const selection = () => {
|
|
2673
2931
|
this.saveSelection();
|
|
2674
2932
|
this.toolbar?.update();
|
|
2675
2933
|
if (this.options.toolbarOnSelect) this.updateFloatVisibility();
|
|
2934
|
+
this.updateLinkPopover();
|
|
2676
2935
|
};
|
|
2677
2936
|
const undoState = () => this.toolbar?.update();
|
|
2678
2937
|
[
|
|
@@ -2749,7 +3008,7 @@ var RichClayBundle = (() => {
|
|
|
2749
3008
|
}, 0);
|
|
2750
3009
|
}
|
|
2751
3010
|
ownsFocusTarget(node) {
|
|
2752
|
-
return node === this.element || this.element.contains(node) || Boolean(this.float?.root.contains(node)) || Boolean(this.dialog?.contains(node));
|
|
3011
|
+
return node === this.element || this.element.contains(node) || Boolean(this.float?.root.contains(node)) || Boolean(this.dialog?.contains(node)) || Boolean(this.linkPopover?.root?.contains(node));
|
|
2753
3012
|
}
|
|
2754
3013
|
updateFloatVisibility() {
|
|
2755
3014
|
if (!this.float) return;
|
|
@@ -2773,7 +3032,7 @@ var RichClayBundle = (() => {
|
|
|
2773
3032
|
installShortcuts() {
|
|
2774
3033
|
const seen = /* @__PURE__ */ new Set();
|
|
2775
3034
|
const registryDefs = [...this.registry.values()].filter(
|
|
2776
|
-
(def) => !this.options.singleLine || presets.inline.includes(def.id)
|
|
3035
|
+
(def) => (!this.options.singleLine || presets.inline.includes(def.id)) && (!this.options.linkLabel || LINK_LABEL_CONTROLS.includes(def.id))
|
|
2777
3036
|
);
|
|
2778
3037
|
const defs = [...this.resolveToolbarControls(this.options.toolbar), ...registryDefs];
|
|
2779
3038
|
defs.forEach((def) => {
|
|
@@ -2794,6 +3053,18 @@ var RichClayBundle = (() => {
|
|
|
2794
3053
|
this._shortcutKeys.push(key);
|
|
2795
3054
|
});
|
|
2796
3055
|
}
|
|
3056
|
+
// Formatting typed into a link label would be dropped by the next save of the shared
|
|
3057
|
+
// store, which keeps only its text and address. Squire binds bold, italic and underline
|
|
3058
|
+
// on its own prototype, so each key is answered with preventDefault: a null handler
|
|
3059
|
+
// would hand it to the browser's own contenteditable formatting instead.
|
|
3060
|
+
maskFormattingShortcuts() {
|
|
3061
|
+
for (const def of this.registry.values()) {
|
|
3062
|
+
if (!def.shortcut || LINK_LABEL_CONTROLS.includes(def.id)) continue;
|
|
3063
|
+
const key = shortcutKey(def.shortcut, this.window);
|
|
3064
|
+
this._squire.setKeyHandler(key, (squire, event) => event.preventDefault());
|
|
3065
|
+
this._shortcutKeys.push(key);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
2797
3068
|
// macOS sends Ctrl+D and Ctrl+H to the browser's own forward/backward delete,
|
|
2798
3069
|
// which merges two blocks by wrapping the moved text in a computed-style
|
|
2799
3070
|
// <span> — permanent junk in an editor whose DOM is the saved document. Point
|
|
@@ -2847,7 +3118,8 @@ var RichClayBundle = (() => {
|
|
|
2847
3118
|
});
|
|
2848
3119
|
this._onBeforeInput = (event) => {
|
|
2849
3120
|
if (!this.element.contains(event.target)) return;
|
|
2850
|
-
|
|
3121
|
+
const formats = this.options.linkLabel && event.inputType.startsWith("format");
|
|
3122
|
+
if (event.inputType === "insertParagraph" || event.inputType === "insertLineBreak" || formats) {
|
|
2851
3123
|
event.preventDefault();
|
|
2852
3124
|
event.stopPropagation();
|
|
2853
3125
|
}
|
|
@@ -2973,7 +3245,7 @@ var RichClayBundle = (() => {
|
|
|
2973
3245
|
}
|
|
2974
3246
|
|
|
2975
3247
|
// richclay.css
|
|
2976
|
-
var richclay_default = ":root {\n --richclay-surface: #ffffff;\n --richclay-surface-alt: #f6f7f9;\n --richclay-text: #0b0e14;\n --richclay-muted: #667085;\n --richclay-border: #c9ced8;\n --richclay-accent: #0f766e;\n --richclay-accent-ink: #ffffff;\n --richclay-focus: #2563eb;\n --richclay-danger: #b42318;\n --richclay-radius: 6px;\n --richclay-control-size: 34px;\n --richclay-shadow: 0 10px 24px rgb(15 23 42 / 0.16);\n --richclay-dialog-arrow: 28px;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --richclay-surface: #16181d;\n --richclay-surface-alt: #22252c;\n --richclay-text: #ffffff;\n --richclay-muted: #a3aab8;\n --richclay-border: #4a5260;\n --richclay-accent: #2dd4bf;\n --richclay-accent-ink: #05201d;\n --richclay-focus: #93c5fd;\n --richclay-shadow: 0 12px 28px rgb(0 0 0 / 0.35);\n }\n}\n\n.richclay-toolbar {\n align-items: center;\n background: var(--richclay-surface-alt);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n margin-block: 0 6px;\n padding: 4px;\n}\n\n/* Seamless card: in the default layout the toolbar sits directly above the\n editor. Fuse them into a single bordered card with one divider \u2014 the\n toolbar's bottom border \u2014 instead of two stacked, separated boxes. */\n.richclay-toolbar:has(+ .richclay-editor) {\n margin-block-end: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.richclay-toolbar + .richclay-editor {\n border-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n/* In a fused card, draw the focus ring inside the body so it never crosses\n the toolbar divider. */\n.richclay-toolbar + .richclay-editor:focus-visible {\n outline-offset: -3px;\n}\n\n.richclay-button {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 5px;\n color: currentColor;\n display: inline-flex;\n height: var(--richclay-control-size);\n justify-content: center;\n margin: 0;\n min-width: var(--richclay-control-size);\n padding: 0;\n position: relative;\n}\n\n.richclay-button:hover,\n.richclay-button[aria-expanded=\"true\"],\n.richclay-menu-item:hover {\n background: var(--richclay-surface);\n border-color: var(--richclay-border);\n}\n\n.richclay-button.is-active,\n.richclay-button[aria-pressed=\"true\"],\n.richclay-menu-item.is-active {\n background: rgb(0 0 0 / 0.13);\n border-color: rgb(0 0 0 / 0.22);\n color: var(--richclay-text);\n box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.26);\n}\n\n.richclay-button:focus-visible,\n.richclay-menu-item:focus-visible,\n.richclay-editor:focus-visible,\n.richclay-input:focus-visible,\n.richclay-primary:focus-visible,\n.richclay-secondary:focus-visible {\n outline: 3px solid var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-button:disabled {\n color: var(--richclay-muted);\n cursor: not-allowed;\n opacity: 0.55;\n}\n\n.richclay-separator {\n align-self: stretch;\n background: var(--richclay-border);\n display: inline-block;\n margin: 5px 4px;\n width: 1px;\n}\n\n.richclay-menu-wrap {\n display: inline-flex;\n position: relative;\n}\n\n.richclay-menu {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 2px;\n inset-block-start: calc(100% + 4px);\n inset-inline-start: 0;\n min-width: 160px;\n padding: 4px;\n position: absolute;\n z-index: 1000;\n}\n\n.richclay-menu[hidden] {\n display: none;\n}\n\n.richclay-menu-item {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n color: inherit;\n display: flex;\n font: inherit;\n gap: 8px;\n min-height: 32px;\n padding: 5px 9px;\n text-align: start;\n}\n\n.richclay-editor {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n min-height: 9rem;\n /* Break a long unbreakable string (e.g. a bare URL) in prose rather than let\n it widen the editor. `anywhere` also lowers min-content, so the editor can't\n grow even as a flex/grid item. Inert on the code <pre> (white-space: pre). */\n overflow-wrap: anywhere;\n padding: 12px;\n position: relative;\n}\n\n.richclay-editor.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n inset-block-start: 12px;\n inset-inline-start: 12px;\n pointer-events: none;\n position: absolute;\n}\n\n.richclay-editor p,\n.richclay-editor h1,\n.richclay-editor h2,\n.richclay-editor h3,\n.richclay-editor h4,\n.richclay-editor h5,\n.richclay-editor h6,\n.richclay-editor blockquote,\n.richclay-editor pre,\n.richclay-editor ul,\n.richclay-editor ol {\n margin-block: 0 0.75em;\n}\n\n.richclay-editor > :last-child {\n margin-block-end: 0;\n}\n\n/* Inline editors ([editable]) inherit the page's own styling. richclay adds\n only a caret, a discoverability outline, and empty-state affordances. */\n.richclay-inline {\n caret-color: currentColor;\n}\n\n.richclay-inline:hover {\n outline: 1px dashed var(--richclay-border);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-focused,\n.richclay-inline:focus-visible {\n outline: 2px dashed var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-empty {\n min-height: 1em;\n min-width: 1ch;\n}\n\n.richclay-inline.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n pointer-events: none;\n}\n\n/* Floating toolbar shell for inline editors. Fixed-position and body-mounted:\n fixed elements never create scrollbars, and body mounting avoids transformed\n ancestors silently turning fixed into ancestor-relative. */\n.richclay-float {\n left: 0;\n position: fixed;\n top: 0;\n will-change: transform;\n z-index: 99999;\n}\n\n.richclay-float .richclay-toolbar {\n box-shadow: var(--richclay-shadow);\n margin-block: 0;\n}\n\n/* Compact vertical rail for narrow margins. */\n.richclay-float-rail .richclay-toolbar {\n --richclay-control-size: 28px;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n\n.richclay-float-rail .richclay-toolbar svg {\n height: 15px;\n width: 15px;\n}\n\n.richclay-float-rail .richclay-separator {\n align-self: stretch;\n height: 1px;\n margin: 4px 5px;\n width: auto;\n}\n\n.richclay-float-rail .richclay-menu {\n inset-block-start: 0;\n inset-inline-start: calc(100% + 4px);\n}\n\n.richclay-editor blockquote {\n border-inline-start: 3px solid var(--richclay-border);\n color: var(--richclay-muted);\n margin-inline: 0;\n padding-inline-start: 12px;\n}\n\n/* Code blocks scroll horizontally inside the editor instead of widening it.\n `width: 0; min-width: 100%` zeroes the pre's intrinsic-width contribution so a\n long unbreakable line can't push an intrinsically-sized ancestor (flex/grid\n item, inline-block, table cell) wider, while still filling the editor and\n scrolling its own overflow. box-sizing keeps padding inside that 100%.\n Containment applies to inline editors too: `white-space: pre` never wraps, so\n without it one code block runs off the side of the page. Only the decoration\n below stays card-scoped, since inline mode keeps the page's own typography. */\n.richclay-editor pre,\n.richclay-inline pre {\n box-sizing: border-box;\n min-width: 100%;\n overflow: auto;\n width: 0;\n}\n\n.richclay-editor pre {\n background: var(--richclay-surface-alt);\n border-radius: 4px;\n padding: 10px;\n}\n\n.richclay-dialog {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 10px;\n box-sizing: border-box;\n width: min(420px, calc(100vw - 16px));\n z-index: 99999;\n padding: 12px;\n}\n\n.richclay-dialog-title {\n font-weight: 650;\n}\n\n.richclay-field {\n display: grid;\n gap: 4px;\n}\n\n.richclay-field span {\n color: var(--richclay-muted);\n font-size: 0.875rem;\n}\n\n.richclay-input {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n color: var(--richclay-text);\n font: inherit;\n min-height: 36px;\n padding: 6px 8px;\n}\n\n.richclay-dialog-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.richclay-dialog-actions [data-richclay-remove-link] {\n margin-inline-end: auto;\n}\n\n.richclay-primary,\n.richclay-secondary {\n appearance: none;\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n font: inherit;\n min-height: 34px;\n padding: 5px 10px;\n}\n\n.richclay-primary {\n background: var(--richclay-accent);\n border-color: var(--richclay-accent);\n color: var(--richclay-accent-ink);\n}\n\n.richclay-secondary {\n background: var(--richclay-surface-alt);\n color: var(--richclay-text);\n}\n\n.richclay-sr-only {\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n\n@media (forced-colors: active) {\n .richclay-toolbar,\n .richclay-editor,\n .richclay-menu,\n .richclay-dialog,\n .richclay-input {\n border-color: CanvasText;\n }\n\n .richclay-button.is-active,\n .richclay-button[aria-pressed=\"true\"],\n .richclay-menu-item.is-active,\n .richclay-primary {\n background: Highlight;\n color: HighlightText;\n }\n\n .richclay-button:focus-visible,\n .richclay-menu-item:focus-visible,\n .richclay-editor:focus-visible,\n .richclay-input:focus-visible {\n outline-color: Highlight;\n }\n}\n";
|
|
3248
|
+
var richclay_default = ":root {\n --richclay-surface: #ffffff;\n --richclay-surface-alt: #f6f7f9;\n --richclay-text: #0b0e14;\n --richclay-muted: #667085;\n --richclay-border: #c9ced8;\n --richclay-accent: #0f766e;\n --richclay-accent-ink: #ffffff;\n --richclay-focus: #2563eb;\n --richclay-danger: #b42318;\n --richclay-radius: 6px;\n --richclay-control-size: 34px;\n --richclay-shadow: 0 10px 24px rgb(15 23 42 / 0.16);\n --richclay-dialog-arrow: 28px;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --richclay-surface: #16181d;\n --richclay-surface-alt: #22252c;\n --richclay-text: #ffffff;\n --richclay-muted: #a3aab8;\n --richclay-border: #4a5260;\n --richclay-accent: #2dd4bf;\n --richclay-accent-ink: #05201d;\n --richclay-focus: #93c5fd;\n --richclay-shadow: 0 12px 28px rgb(0 0 0 / 0.35);\n }\n}\n\n.richclay-toolbar {\n align-items: center;\n background: var(--richclay-surface-alt);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n margin-block: 0 6px;\n padding: 4px;\n}\n\n/* Seamless card: in the default layout the toolbar sits directly above the\n editor. Fuse them into a single bordered card with one divider \u2014 the\n toolbar's bottom border \u2014 instead of two stacked, separated boxes. */\n.richclay-toolbar:has(+ .richclay-editor) {\n margin-block-end: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.richclay-toolbar + .richclay-editor {\n border-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n/* In a fused card, draw the focus ring inside the body so it never crosses\n the toolbar divider. */\n.richclay-toolbar + .richclay-editor:focus-visible {\n outline-offset: -3px;\n}\n\n.richclay-button {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 5px;\n color: currentColor;\n display: inline-flex;\n height: var(--richclay-control-size);\n justify-content: center;\n margin: 0;\n min-width: var(--richclay-control-size);\n padding: 0;\n position: relative;\n}\n\n.richclay-button:hover,\n.richclay-button[aria-expanded=\"true\"],\n.richclay-menu-item:hover {\n background: var(--richclay-surface);\n border-color: var(--richclay-border);\n}\n\n.richclay-button.is-active,\n.richclay-button[aria-pressed=\"true\"],\n.richclay-menu-item.is-active {\n background: rgb(0 0 0 / 0.13);\n border-color: rgb(0 0 0 / 0.22);\n color: var(--richclay-text);\n box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.26);\n}\n\n.richclay-button:focus-visible,\n.richclay-menu-item:focus-visible,\n.richclay-editor:focus-visible,\n.richclay-input:focus-visible,\n.richclay-primary:focus-visible,\n.richclay-secondary:focus-visible {\n outline: 3px solid var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-button:disabled {\n color: var(--richclay-muted);\n cursor: not-allowed;\n opacity: 0.55;\n}\n\n.richclay-separator {\n align-self: stretch;\n background: var(--richclay-border);\n display: inline-block;\n margin: 5px 4px;\n width: 1px;\n}\n\n.richclay-menu-wrap {\n display: inline-flex;\n position: relative;\n}\n\n.richclay-menu {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 2px;\n inset-block-start: calc(100% + 4px);\n inset-inline-start: 0;\n min-width: 160px;\n padding: 4px;\n position: absolute;\n z-index: 1000;\n}\n\n.richclay-menu[hidden] {\n display: none;\n}\n\n.richclay-menu-item {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n color: inherit;\n display: flex;\n font: inherit;\n gap: 8px;\n min-height: 32px;\n padding: 5px 9px;\n text-align: start;\n}\n\n.richclay-editor {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n min-height: 9rem;\n /* Break a long unbreakable string (e.g. a bare URL) in prose rather than let\n it widen the editor. `anywhere` also lowers min-content, so the editor can't\n grow even as a flex/grid item. Inert on the code <pre> (white-space: pre). */\n overflow-wrap: anywhere;\n padding: 12px;\n position: relative;\n}\n\n.richclay-editor.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n inset-block-start: 12px;\n inset-inline-start: 12px;\n pointer-events: none;\n position: absolute;\n}\n\n.richclay-editor p,\n.richclay-editor h1,\n.richclay-editor h2,\n.richclay-editor h3,\n.richclay-editor h4,\n.richclay-editor h5,\n.richclay-editor h6,\n.richclay-editor blockquote,\n.richclay-editor pre,\n.richclay-editor ul,\n.richclay-editor ol {\n margin-block: 0 0.75em;\n}\n\n.richclay-editor > :last-child {\n margin-block-end: 0;\n}\n\n/* Inline editors ([editable]) inherit the page's own styling. richclay adds\n only a caret, a discoverability outline, and empty-state affordances. */\n.richclay-inline {\n caret-color: currentColor;\n}\n\n.richclay-inline:hover {\n outline: 1px dashed var(--richclay-border);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-focused,\n.richclay-inline:focus-visible {\n outline: 2px dashed var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-empty {\n min-height: 1em;\n min-width: 1ch;\n}\n\n.richclay-inline.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n pointer-events: none;\n}\n\n/* Floating toolbar shell for inline editors. Fixed-position and body-mounted:\n fixed elements never create scrollbars, and body mounting avoids transformed\n ancestors silently turning fixed into ancestor-relative. */\n.richclay-float {\n left: 0;\n position: fixed;\n top: 0;\n will-change: transform;\n z-index: 99999;\n}\n\n.richclay-float .richclay-toolbar {\n box-shadow: var(--richclay-shadow);\n margin-block: 0;\n}\n\n/* Compact vertical rail for narrow margins. */\n.richclay-float-rail .richclay-toolbar {\n --richclay-control-size: 28px;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n\n.richclay-float-rail .richclay-toolbar svg {\n height: 15px;\n width: 15px;\n}\n\n.richclay-float-rail .richclay-separator {\n align-self: stretch;\n height: 1px;\n margin: 4px 5px;\n width: auto;\n}\n\n.richclay-float-rail .richclay-menu {\n inset-block-start: 0;\n inset-inline-start: calc(100% + 4px);\n}\n\n.richclay-editor blockquote {\n border-inline-start: 3px solid var(--richclay-border);\n color: var(--richclay-muted);\n margin-inline: 0;\n padding-inline-start: 12px;\n}\n\n/* Code blocks scroll horizontally inside the editor instead of widening it.\n `width: 0; min-width: 100%` zeroes the pre's intrinsic-width contribution so a\n long unbreakable line can't push an intrinsically-sized ancestor (flex/grid\n item, inline-block, table cell) wider, while still filling the editor and\n scrolling its own overflow. box-sizing keeps padding inside that 100%.\n Containment applies to inline editors too: `white-space: pre` never wraps, so\n without it one code block runs off the side of the page. Only the decoration\n below stays card-scoped, since inline mode keeps the page's own typography. */\n.richclay-editor pre,\n.richclay-inline pre {\n box-sizing: border-box;\n min-width: 100%;\n overflow: auto;\n width: 0;\n}\n\n.richclay-editor pre {\n background: var(--richclay-surface-alt);\n border-radius: 4px;\n padding: 10px;\n}\n\n.richclay-dialog {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 10px;\n box-sizing: border-box;\n width: min(420px, calc(100vw - 16px));\n z-index: 99999;\n padding: 12px;\n}\n\n.richclay-dialog-title {\n font-weight: 650;\n}\n\n.richclay-dialog.richclay-link-popover {\n align-items: center;\n display: flex;\n gap: 6px;\n max-width: min(420px, calc(100vw - 16px));\n padding: 6px;\n width: auto;\n}\n\n.richclay-link-address {\n color: inherit;\n font-size: 0.875rem;\n max-width: 260px;\n overflow: hidden;\n padding: 0 6px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.richclay-link-popover .richclay-secondary {\n min-height: 30px;\n padding: 3px 9px;\n}\n\n.richclay-field {\n display: grid;\n gap: 4px;\n}\n\n.richclay-field span {\n color: var(--richclay-muted);\n font-size: 0.875rem;\n}\n\n.richclay-input {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n color: var(--richclay-text);\n font: inherit;\n min-height: 36px;\n padding: 6px 8px;\n}\n\n.richclay-dialog-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.richclay-dialog-actions [data-richclay-remove-link] {\n margin-inline-end: auto;\n}\n\n.richclay-primary,\n.richclay-secondary {\n appearance: none;\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n font: inherit;\n min-height: 34px;\n padding: 5px 10px;\n}\n\n.richclay-primary {\n background: var(--richclay-accent);\n border-color: var(--richclay-accent);\n color: var(--richclay-accent-ink);\n}\n\n.richclay-secondary {\n background: var(--richclay-surface-alt);\n color: var(--richclay-text);\n}\n\n.richclay-sr-only {\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n\n@media (forced-colors: active) {\n .richclay-toolbar,\n .richclay-editor,\n .richclay-menu,\n .richclay-dialog,\n .richclay-input {\n border-color: CanvasText;\n }\n\n .richclay-button.is-active,\n .richclay-button[aria-pressed=\"true\"],\n .richclay-menu-item.is-active,\n .richclay-primary {\n background: Highlight;\n color: HighlightText;\n }\n\n .richclay-button:focus-visible,\n .richclay-menu-item:focus-visible,\n .richclay-editor:focus-visible,\n .richclay-input:focus-visible {\n outline-color: Highlight;\n }\n}\n";
|
|
2977
3249
|
|
|
2978
3250
|
// src/browser-global.js
|
|
2979
3251
|
setRichClayStyles(richclay_default);
|