@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 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; }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@panphora/bevel",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Beveled controls in an editorial page. One stylesheet, one script, no build.",
5
5
  "license": "MIT-0",
6
6
  "author": "David Miranda",
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "richclay",
3
- "version": "0.7.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": "5434545ce62960fc27611edae1ef808f29dfe31f4e8bd8c8553c4eb118d83c0c",
7
- "richclay.css": "fc44ab9e71f8b66950fbda5a535774c7acb97d7e54aff2521ea4122d21422d47",
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
- const anchor = this.currentLinkElement();
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", close);
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
- close();
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
- if (event.inputType === "insertParagraph" || event.inputType === "insertLineBreak") {
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);