@panphora/clayjs 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +1228 -715
  11. package/dist/m/attrs/{save-freeze-MTFVVYV5.js → save-freeze-JZHKRQLR.js} +1 -1
  12. package/dist/m/{c-TV7JHSRL.js → c-24PMUJIV.js} +1 -1
  13. package/dist/m/{c-NBNRLZXO.js → c-2AYVYEI4.js} +1 -1
  14. package/dist/m/{c-VIW5X35H.js → c-2L6O5PDS.js} +1 -1
  15. package/dist/m/{c-FNIGX4FI.js → c-2RYOGUQM.js} +1 -1
  16. package/dist/m/{c-LDUNOR57.js → c-2Z22FCFK.js} +13 -25
  17. package/dist/m/{c-DKFSOJCV.js → c-2ZVOEU4M.js} +1 -1
  18. package/dist/m/{c-BJPZCWAV.js → c-37NAONZA.js} +1 -1
  19. package/dist/m/{c-CSNE5EIF.js → c-4PO3YGDC.js} +1 -1
  20. package/dist/m/{c-UWT4CXQ4.js → c-5OJ7YF2N.js} +1 -1
  21. package/dist/m/{c-CAWNFER6.js → c-5TIZ2I64.js} +1 -1
  22. package/dist/m/{c-ZO6VL2SC.js → c-5WMXEKFH.js} +1 -1
  23. package/dist/m/{c-4TWARTLS.js → c-6RDOKLN7.js} +1 -1
  24. package/dist/m/{c-6RC66MYY.js → c-6TYGD5AB.js} +1 -1
  25. package/dist/m/c-A4IP6MBC.js +1 -0
  26. package/dist/m/{c-ZAHTFCXQ.js → c-ALDVFPWL.js} +1 -1
  27. package/dist/m/{c-LV5MABYD.js → c-AYVGD6A6.js} +1 -1
  28. package/dist/m/{c-N33TPVTL.js → c-B6O7AJYG.js} +1 -1
  29. package/dist/m/{c-P3WATEDR.js → c-BCGWJYXW.js} +1 -1
  30. package/dist/m/{c-4OGEVKR3.js → c-BGKAT2FL.js} +1 -1
  31. package/dist/m/{c-N7Y52HJH.js → c-BXYSDKAN.js} +1 -1
  32. package/dist/m/c-CRPIDGXC.js +78 -0
  33. package/dist/m/c-CUMXGHVD.js +1 -0
  34. package/dist/m/{c-Q5KAQJAG.js → c-DTMZ3ZHI.js} +1 -1
  35. package/dist/m/{c-GY4ODHO2.js → c-ELYIIYRS.js} +1 -1
  36. package/dist/m/{c-67LGZ52V.js → c-FCSZ3DC5.js} +1 -1
  37. package/dist/m/{c-NLWEZYKJ.js → c-FQGHR32O.js} +1 -1
  38. package/dist/m/{c-PCEMXY2K.js → c-G4PWUDD5.js} +1 -1
  39. package/dist/m/{c-6KBKYLPO.js → c-G6COVIZB.js} +1 -1
  40. package/dist/m/c-GBTVCJXC.js +1 -0
  41. package/dist/m/c-GC2HICFJ.js +1 -0
  42. package/dist/m/{c-TMT5TL65.js → c-HCNWJE4A.js} +2 -2
  43. package/dist/m/{c-DZBZ57EE.js → c-JCOQVZYS.js} +1 -1
  44. package/dist/m/{c-FAXQU7MQ.js → c-JGHEACGK.js} +1 -1
  45. package/dist/m/{c-PWBJVHA4.js → c-JGLX6TBK.js} +1 -1
  46. package/dist/m/c-JNRKHYWO.js +1 -0
  47. package/dist/m/{c-6UEFPHHE.js → c-KDXIJEEF.js} +1 -1
  48. package/dist/m/{c-FMCPQ4VT.js → c-L4KMT7FO.js} +2 -2
  49. package/dist/m/{c-OI2BYWVV.js → c-LCYPMULG.js} +1 -1
  50. package/dist/m/{c-4RFZROCF.js → c-LIGQYZ7M.js} +4 -4
  51. package/dist/m/{c-QHVCUTJV.js → c-LKIWEPAV.js} +1 -1
  52. package/dist/m/{c-EDSPKYMA.js → c-LUXAB2XI.js} +1 -1
  53. package/dist/m/{c-DEAFCHDO.js → c-LWIDDWAR.js} +2 -2
  54. package/dist/m/c-MPSKIU2I.js +1 -0
  55. package/dist/m/{c-6PBXRWDX.js → c-NK33MMYF.js} +1 -1
  56. package/dist/m/{c-F5AER2ID.js → c-NQLWOHLH.js} +1 -1
  57. package/dist/m/{c-L7K5EXU2.js → c-NTHUTABN.js} +1 -1
  58. package/dist/m/{c-4PHDUV3H.js → c-NW54NHKE.js} +1 -1
  59. package/dist/m/{c-3AXJDL2P.js → c-OG6GFSUX.js} +1 -1
  60. package/dist/m/{c-MTQHWQC3.js → c-OKEGUICD.js} +1 -1
  61. package/dist/m/c-OPONJ2UK.js +128 -0
  62. package/dist/m/{c-LF6HNVPU.js → c-OWQWOGYI.js} +1 -1
  63. package/dist/m/{c-FDAXZ2PM.js → c-P6Q2ZTFQ.js} +1 -1
  64. package/dist/m/c-PKCCHJ5X.js +1 -0
  65. package/dist/m/c-PWJSJEJU.js +1 -0
  66. package/dist/m/{c-Z7RJANGO.js → c-PXLTUYZC.js} +1 -1
  67. package/dist/m/c-QEN6HZCA.js +1 -0
  68. package/dist/m/{c-26KVJS6J.js → c-QGAPG7OM.js} +1 -1
  69. package/dist/m/{c-HWR6GMGK.js → c-QIR5CAKM.js} +1 -1
  70. package/dist/m/{c-QJFYVCJO.js → c-QM5LXRDT.js} +1 -1
  71. package/dist/m/{c-MRURHLKV.js → c-QUGCKMWF.js} +1 -1
  72. package/dist/m/{c-KE33W4RE.js → c-QYYUVLVG.js} +1 -1
  73. package/dist/m/c-RO73MKKR.js +1 -0
  74. package/dist/m/c-SBCBXKOZ.js +26 -0
  75. package/dist/m/{c-XDVAZDUE.js → c-SGRIYOAJ.js} +1 -1
  76. package/dist/m/c-SKF5HL4I.js +4 -0
  77. package/dist/m/{c-IWTTOC2V.js → c-SRPC3VBE.js} +1 -1
  78. package/dist/m/{c-XZJ5IK6Q.js → c-T5NK2TVY.js} +1 -1
  79. package/dist/m/{c-VFES2BP5.js → c-TANBH7FI.js} +1 -1
  80. package/dist/m/{c-H6DXQEH2.js → c-THGQQDYY.js} +1 -1
  81. package/dist/m/{c-BV6FQQXM.js → c-TSBVMWAH.js} +1 -1
  82. package/dist/m/{c-QTC6VGA4.js → c-UD5JZTDL.js} +1 -1
  83. package/dist/m/{c-ZYEJLZEY.js → c-UDGEFWNU.js} +1 -1
  84. package/dist/m/{c-ZSCBEB7I.js → c-UHUNXWKN.js} +1 -1
  85. package/dist/m/{c-FWFJUU3T.js → c-VIRFF5HG.js} +1 -1
  86. package/dist/m/{c-GPTKI7AQ.js → c-VYJ7O4RI.js} +1 -1
  87. package/dist/m/{c-TUGVINJR.js → c-W5E5P2TZ.js} +1 -1
  88. package/dist/m/{c-WWE67K5Y.js → c-W7IPVCAA.js} +1 -1
  89. package/dist/m/{c-HAVGDXAL.js → c-WJ5DRBHT.js} +1 -1
  90. package/dist/m/{c-V2J5C5MT.js → c-WQZTI5P2.js} +1 -1
  91. package/dist/m/c-X5WC4PX5.js +1 -0
  92. package/dist/m/{c-ZKELCT4T.js → c-XCFQEWW6.js} +2 -2
  93. package/dist/m/c-XLQ3BDMM.js +1 -0
  94. package/dist/m/{c-FLBUYV3P.js → c-XX75FFNL.js} +1 -1
  95. package/dist/m/{c-JHJR5BQX.js → c-YK2L436K.js} +1 -1
  96. package/dist/m/{c-MEWUGR4D.js → c-YYMZWQLR.js} +1 -1
  97. package/dist/m/core/{admin-attrs-ZG7R5V3E.js → admin-attrs-U7IJNYBG.js} +1 -1
  98. package/dist/m/core/{autosave-GI3WNJYN.js → autosave-F5YQCKVL.js} +1 -1
  99. package/dist/m/core/{conflict-notice-CEI3XY6A.js → conflict-notice-42H4ASLA.js} +1 -1
  100. package/dist/m/core/{persist-3OPWJKZS.js → persist-CJXIL7D4.js} +1 -1
  101. package/dist/m/core/{save-OJ5NSLS7.js → save-ESPSQPLV.js} +1 -1
  102. package/dist/m/core/{save-core-DX3RFVO3.js → save-core-6SQHOR6W.js} +1 -1
  103. package/dist/m/core/snapshot-FJP5IJL3.js +1 -0
  104. package/dist/m/core/{unsaved-warning-HGYH7O4H.js → unsaved-warning-HNQVTA2D.js} +1 -1
  105. package/dist/m/internals/{index-V4WC44FU.js → index-NRPNDTSA.js} +1 -1
  106. package/dist/m/loader-FBDE2UAB.js +1 -0
  107. package/dist/m/manifest-1D2F5CA5.js +1 -0
  108. package/dist/m/manifest-edit-F7CC5CFD.js +1 -0
  109. package/dist/m/manifest.json +1090 -1020
  110. package/dist/m/page/{editor-BQEPCJLG.js → editor-OSKOW6RS.js} +2 -2
  111. package/dist/m/plugins/{ai-edit-45A2J6LC.js → ai-edit-VV4QTINH.js} +1 -1
  112. package/dist/m/plugins/{cms-IFTKWF4S.js → cms-H7DTMUQX.js} +1 -1
  113. package/dist/m/plugins/{indicator-IPZJ35K6.js → indicator-WVPGMOZ7.js} +1 -1
  114. package/dist/m/plugins/{live-GPQXSXSM.js → live-DCUBKXY4.js} +1 -1
  115. package/dist/m/plugins/menu-Q4MFFQ6E.js +1 -0
  116. package/dist/m/plugins/{page-BAVLWCP6.js → page-OAWUJQEL.js} +2 -2
  117. package/dist/m/plugins/{page-view-P3UGJFDZ.js → page-view-DZ26ZDDQ.js} +1 -1
  118. package/dist/m/plugins/{people-Z4RGVQAV.js → people-5XRNRP5U.js} +1 -1
  119. package/dist/m/plugins/{quickcrop-E34PUYA3.js → quickcrop-WWF5I422.js} +2 -2
  120. package/dist/m/plugins/{richclay-S4NVJYIK.js → richclay-PGOOJ4OK.js} +2 -2
  121. package/dist/m/plugins/{sortable-WJZZBM7R.js → sortable-266HY6TR.js} +1 -1
  122. package/dist/m/plugins/{source-KVN6YNTY.js → source-6BKMK4PO.js} +1 -1
  123. package/dist/m/plugins/{upgrade-KTSICB22.js → upgrade-BNZVFHMZ.js} +1 -1
  124. package/dist/m/plugins/visitor-drafts-2AUYKAIJ.js +20 -0
  125. package/dist/m/plugins/{visitor-drafts-owner-MZP45XSG.js → visitor-drafts-owner-Z5B2OQBR.js} +1 -1
  126. package/dist/m/plugins/visitor-menu-4LQMKLPP.js +1 -0
  127. package/dist/m/plugins/{wire-SYVIBNSE.js → wire-W2EV36KZ.js} +1 -1
  128. package/dist/m/{reader-core-RUIFH32Y.js → reader-core-POFQCKRV.js} +2 -2
  129. package/dist/m/sync/{live-sync-RGQMMWK2.js → live-sync-XGUPHI3I.js} +1 -1
  130. package/dist/m/ui/{index-EZGLEM5P.js → index-VGYAKAKI.js} +1 -1
  131. package/dist/offline-manifest.json +636 -570
  132. package/docs/guide.md +36 -25
  133. package/package.json +1 -1
  134. package/src/core/save-replace.js +5 -2
  135. package/src/core/serialize-steps.js +10 -2
  136. package/src/core/snapshot.js +37 -1
  137. package/src/core/upgrade-run.js +16 -2
  138. package/src/loader-logic.js +6 -2
  139. package/src/plugins/menu/dom-items.js +45 -0
  140. package/src/plugins/menu/looks.js +32 -0
  141. package/src/plugins/menu/shell-style.js +143 -0
  142. package/src/plugins/menu/shell.js +398 -0
  143. package/src/plugins/menu/style.js +15 -116
  144. package/src/plugins/menu/variants.js +56 -10
  145. package/src/plugins/menu/visitor.js +78 -0
  146. package/src/plugins/menu.js +21 -313
  147. package/src/plugins/visitor-drafts/inert.js +1 -0
  148. package/src/plugins/visitor-drafts/intro-style.js +6 -0
  149. package/src/plugins/visitor-drafts/intro.js +77 -25
  150. package/src/plugins/visitor-drafts/owner-chip.js +18 -23
  151. package/src/plugins/visitor-drafts/prompt.js +2 -2
  152. package/src/plugins/visitor-drafts/store.js +58 -5
  153. package/src/plugins/visitor-drafts.js +1 -0
  154. package/src/plugins/visitor-menu.js +13 -0
  155. package/src/sync/live-sync.js +2 -1
  156. package/src/sync/page-relay.js +2 -2
  157. package/src/ui/page-colour.js +17 -9
  158. package/dist/m/c-5AQYL27I.js +0 -1
  159. package/dist/m/c-DP4X4JID.js +0 -1
  160. package/dist/m/c-ECY5AZMH.js +0 -1
  161. package/dist/m/c-EGTIAL6P.js +0 -1
  162. package/dist/m/c-HDWZEJHM.js +0 -183
  163. package/dist/m/c-ILGQ3UXQ.js +0 -1
  164. package/dist/m/c-JFST6QE7.js +0 -1
  165. package/dist/m/c-JH5FF4NL.js +0 -20
  166. package/dist/m/c-KV7R47FR.js +0 -1
  167. package/dist/m/c-MHI3GYKF.js +0 -1
  168. package/dist/m/c-NNNR7OXA.js +0 -3
  169. package/dist/m/c-VSPAFOKU.js +0 -1
  170. package/dist/m/core/snapshot-O7II2AGG.js +0 -1
  171. package/dist/m/loader-V6VI46HU.js +0 -1
  172. package/dist/m/manifest-83C18DED.js +0 -1
  173. package/dist/m/manifest-edit-B28C1985.js +0 -1
  174. package/dist/m/plugins/menu-UN3KRCOF.js +0 -1
  175. package/dist/m/plugins/visitor-drafts-2AWNT23K.js +0 -20
  176. /package/dist/m/plugins/{quickcrop-E34PUYA3.js.LEGAL.txt → quickcrop-WWF5I422.js.LEGAL.txt} +0 -0
  177. /package/dist/m/plugins/{richclay-S4NVJYIK.js.LEGAL.txt → richclay-PGOOJ4OK.js.LEGAL.txt} +0 -0
@@ -0,0 +1,398 @@
1
+ import CLAYGOO from "./claygoo.svg.js";
2
+ import { mountEditorUI } from "../../ui/editor-ui.js";
3
+
4
+ const SKIN_VERSION = "1";
5
+ const EDGE_GAP = 8;
6
+ const DISMISS_ICON = '<svg viewBox="0 0 10 10" aria-hidden="true" focusable="false"><path d="M1 0 5 4l4-4 1 1-4 4 4 4-1 1-4-4-4 4-1-1 4-4-4-4z"/></svg>';
7
+ const ARROW_PREV = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" aria-hidden="true" focusable="false"><path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z"/></svg>';
8
+ const ARROW_NEXT = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" aria-hidden="true" focusable="false"><path d="M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z"/></svg>';
9
+ export const TRACK = '<span class="clay-menu-track" aria-hidden="true"><span class="clay-menu-thumb"></span></span>';
10
+ const NOTE_MS = 6000;
11
+
12
+ // The first [clay-menu-slot] on the page holds the menu. With none, it floats in a corner.
13
+ function findSlot() {
14
+ const slots = document.querySelectorAll("[clay-menu-slot]");
15
+ if (slots.length > 1) console.warn("[clay] This page has more than one clay-menu-slot. The menu uses the first.");
16
+ return slots[0] || null;
17
+ }
18
+
19
+ // A skin the page wrote, in <style clay-menu-skin="1" media="not all">. The media keeps it
20
+ // off the page itself. An unknown version means the default skin.
21
+ function pageSkin() {
22
+ const style = document.querySelector("style[clay-menu-skin]");
23
+ if (!style) return null;
24
+ if (style.getAttribute("clay-menu-skin") !== SKIN_VERSION) {
25
+ console.warn(`[clay] This runtime reads clay-menu-skin="${SKIN_VERSION}". The menu keeps its default look.`);
26
+ return null;
27
+ }
28
+ return style.textContent;
29
+ }
30
+
31
+ export function createShell({ capsuleStart = "", sections = [null] } = {}) {
32
+ const slotted = new Set(sections.flat().filter(Boolean));
33
+ const slot = findSlot();
34
+ const embedded = Boolean(slot);
35
+ const skin = embedded ? pageSkin() : null;
36
+
37
+ const root = document.createElement("div");
38
+ root.className = "clay-menu-dock";
39
+ root.setAttribute("clay", "editor-ui");
40
+ root.setAttribute("part", "dock");
41
+ if (embedded) root.dataset.embedded = "";
42
+ root.dataset.corner = "top-right";
43
+ root.innerHTML =
44
+ `<div class="clay-menu" part="capsule" clay="editor-ui">` +
45
+ capsuleStart +
46
+ `<button type="button" class="clay-menu-goo" part="button" aria-haspopup="menu" aria-expanded="false" aria-label="Clay menu">${CLAYGOO}</button>` +
47
+ `</div>` +
48
+ `<div class="clay-menu-list" part="list" role="menu" aria-label="Clay menu" hidden></div>` +
49
+ `<div class="clay-menu-note" part="note" role="status" hidden></div>`;
50
+
51
+ const goo = root.querySelector(".clay-menu-goo");
52
+ const list = root.querySelector(".clay-menu-list");
53
+ const noteEl = root.querySelector(".clay-menu-note");
54
+ const items = new Map();
55
+ const beforeOpen = [];
56
+
57
+ if (embedded) {
58
+ const icon = slot.querySelector("template[clay-menu-icon]");
59
+ if (icon) goo.replaceChildren(icon.content.cloneNode(true));
60
+ const name = slot.getAttribute("clay-menu-label");
61
+ if (name) {
62
+ goo.setAttribute("aria-label", name);
63
+ goo.title = name;
64
+ list.setAttribute("aria-label", name);
65
+ }
66
+ }
67
+
68
+ // Embedded, the list opens in the top layer, so a header's overflow cannot clip it.
69
+ const topLayer = embedded && typeof list.showPopover === "function";
70
+ if (topLayer) list.setAttribute("popover", "manual");
71
+
72
+ function placeList() {
73
+ const anchor = goo.getBoundingClientRect();
74
+ const { offsetWidth: width, offsetHeight: height } = list;
75
+ const left = Math.max(EDGE_GAP, Math.min(anchor.right - width, window.innerWidth - width - EDGE_GAP));
76
+ const below = anchor.bottom + EDGE_GAP;
77
+ const top = below + height > window.innerHeight - EDGE_GAP ? Math.max(EDGE_GAP, anchor.top - height - EDGE_GAP) : below;
78
+ list.style.left = `${Math.round(left)}px`;
79
+ list.style.top = `${Math.round(top)}px`;
80
+ }
81
+
82
+ // Focus inside the shadow root: document.activeElement only ever names the host.
83
+ const focusedEl = () => root.getRootNode().activeElement || null;
84
+ const isOpen = () => !list.hidden;
85
+
86
+ function setExpanded(open) {
87
+ list.hidden = !open;
88
+ goo.setAttribute("aria-expanded", open ? "true" : "false");
89
+ if (!topLayer) return;
90
+ if (open) {
91
+ list.showPopover();
92
+ placeList();
93
+ } else if (list.matches(":popover-open")) {
94
+ list.hidePopover();
95
+ }
96
+ }
97
+
98
+ const buttons = () => [...list.querySelectorAll('[role^="menuitem"]:not([aria-disabled="true"])')];
99
+
100
+ function focusItem(index) {
101
+ const all = buttons();
102
+ if (!all.length) return;
103
+ all[(index + all.length) % all.length].focus();
104
+ }
105
+
106
+ function close() {
107
+ if (!isOpen()) return;
108
+ setExpanded(false);
109
+ }
110
+
111
+ function open() {
112
+ if (isOpen()) return;
113
+ for (const fn of beforeOpen) fn();
114
+ render();
115
+ setExpanded(true);
116
+ }
117
+
118
+ function itemButton(item) {
119
+ const link = item.href && !item.disabled;
120
+ const button = document.createElement(link ? "a" : "button");
121
+ if (link) {
122
+ button.href = item.href;
123
+ button.target = "_blank";
124
+ button.rel = "noopener";
125
+ } else {
126
+ button.type = "button";
127
+ }
128
+ button.setAttribute("role", "menuitem");
129
+ button.setAttribute("part", item.quiet ? "item quiet" : "item");
130
+ button.dataset.menuId = item.id;
131
+ if (item.badge && item.id === "upgrade") {
132
+ const label = document.createElement("span");
133
+ label.className = "clay-menu-upgrade";
134
+ label.textContent = item.label;
135
+ button.append(label);
136
+ } else {
137
+ button.textContent = item.label;
138
+ }
139
+ if (item.keys) {
140
+ const kbd = document.createElement("kbd");
141
+ kbd.textContent = item.keys;
142
+ button.append(kbd);
143
+ }
144
+ if (item.toggle) {
145
+ button.setAttribute("role", "menuitemcheckbox");
146
+ button.setAttribute("aria-checked", item.checked ? "true" : "false");
147
+ button.classList.add("clay-menu-toggle");
148
+ button.insertAdjacentHTML("beforeend", TRACK);
149
+ }
150
+ if (item.disabled) button.setAttribute("aria-disabled", "true");
151
+ button.addEventListener("click", () => {
152
+ if (item.disabled) return;
153
+ if (!item.toggle && !item.keepOpen) close();
154
+ if (item.run) item.run({ anchor: goo });
155
+ });
156
+ return button;
157
+ }
158
+
159
+ function itemNode(item) {
160
+ if (item.text) {
161
+ const line = document.createElement("p");
162
+ line.className = "clay-menu-version";
163
+ line.setAttribute("part", "text");
164
+ line.dataset.menuId = item.id;
165
+ line.textContent = item.label;
166
+ return line;
167
+ }
168
+ if (item.pair) {
169
+ const pair = document.createElement("div");
170
+ pair.className = "clay-menu-pair";
171
+ pair.dataset.menuId = item.id;
172
+ for (const part of item.pair) pair.append(itemButton(part));
173
+ return pair;
174
+ }
175
+ if (item.stepper) return stepperRow(item);
176
+ if (item.dismiss) return dismissibleRow(item);
177
+ return itemButton(item);
178
+ }
179
+
180
+ function dismissibleRow(item) {
181
+ const row = document.createElement("div");
182
+ row.className = "clay-menu-row";
183
+ row.dataset.menuId = item.id;
184
+ const dismiss = document.createElement("button");
185
+ dismiss.type = "button";
186
+ dismiss.setAttribute("role", "menuitem");
187
+ dismiss.setAttribute("part", "dismiss");
188
+ dismiss.dataset.menuId = `${item.id}.dismiss`;
189
+ dismiss.setAttribute("aria-label", item.dismiss.label);
190
+ dismiss.title = item.dismiss.label;
191
+ dismiss.innerHTML = DISMISS_ICON;
192
+ dismiss.addEventListener("click", async () => {
193
+ dismiss.disabled = true;
194
+ let result;
195
+ try {
196
+ result = await item.dismiss.run?.();
197
+ } catch (error) {
198
+ console.warn("[clay] A menu item could not be removed.", error);
199
+ dismiss.disabled = false;
200
+ note("That didn't work. Try again.");
201
+ return;
202
+ }
203
+ if (result === false) {
204
+ dismiss.disabled = false;
205
+ return;
206
+ }
207
+ removeItem(item.id);
208
+ });
209
+ row.append(itemButton(item), dismiss);
210
+ return row;
211
+ }
212
+
213
+ function stepperRow(item) {
214
+ const { label, value, prevLabel, nextLabel } = item.stepper;
215
+ const row = document.createElement("div");
216
+ row.className = "clay-menu-stepper";
217
+ row.setAttribute("part", "stepper");
218
+ row.dataset.menuId = item.id;
219
+ const text = document.createElement("span");
220
+ text.className = "clay-menu-stepper-label";
221
+ text.textContent = label;
222
+ const shown = document.createElement("span");
223
+ shown.className = "clay-menu-stepper-value";
224
+ shown.setAttribute("aria-live", "polite");
225
+ shown.textContent = value;
226
+ const step = (suffix, icon, name) => {
227
+ const button = document.createElement("button");
228
+ button.type = "button";
229
+ button.setAttribute("role", "menuitem");
230
+ button.setAttribute("part", "step");
231
+ button.setAttribute("aria-label", name);
232
+ button.dataset.menuId = `${item.id}.${suffix}`;
233
+ button.innerHTML = icon;
234
+ button.addEventListener("click", () => items.get(item.id)?.stepper?.[suffix]?.());
235
+ return button;
236
+ };
237
+ row.append(text, step("prev", ARROW_PREV, prevLabel), shown, step("next", ARROW_NEXT, nextLabel));
238
+ return row;
239
+ }
240
+
241
+ function grouped() {
242
+ const inSection = (name) => [...items.values()].filter((item) => item.section === name);
243
+ const extra = [...items.keys()].filter((id) => !slotted.has(id) && !items.get(id).section);
244
+ const fixed = sections.map((ids) => (ids || extra).map((id) => items.get(id)).filter(Boolean));
245
+ return [inSection("start"), ...fixed, inSection("end")].filter((group) => group.length);
246
+ }
247
+
248
+ function render() {
249
+ const now = focusedEl();
250
+ const focused = now && list.contains(now) ? now.dataset.menuId : null;
251
+ list.textContent = "";
252
+ grouped().forEach((group, index) => {
253
+ if (index) {
254
+ const sep = document.createElement("div");
255
+ sep.className = "clay-menu-sep";
256
+ sep.setAttribute("role", "separator");
257
+ sep.setAttribute("part", "separator");
258
+ list.append(sep);
259
+ }
260
+ for (const item of group) list.append(itemNode(item));
261
+ });
262
+ const dot = goo.querySelector(".clay-menu-dot");
263
+ const wanted = [...items.values()].some((item) => item.badge);
264
+ if (wanted && !dot) {
265
+ const mark = document.createElement("span");
266
+ mark.className = "clay-menu-dot";
267
+ mark.setAttribute("aria-hidden", "true");
268
+ goo.append(mark);
269
+ } else if (!wanted && dot) {
270
+ dot.remove();
271
+ }
272
+ if (focused) {
273
+ const again = buttons().find((button) => button.dataset.menuId === focused);
274
+ if (again) again.focus();
275
+ else focusItem(0);
276
+ }
277
+ }
278
+
279
+ const clean = ({ id, label, run, badge, keys, disabled, href, keepOpen }) => ({
280
+ id,
281
+ href: typeof href === "string" ? href : "",
282
+ label: String(label ?? ""),
283
+ run: typeof run === "function" ? run : null,
284
+ badge: Boolean(badge),
285
+ keys: keys ? String(keys) : "",
286
+ disabled: Boolean(disabled),
287
+ keepOpen: Boolean(keepOpen),
288
+ });
289
+
290
+ function addItem({ id, label, run, badge, keys, disabled, text, pair, href, section, quiet, dismiss, toggle, checked, keepOpen, stepper } = {}) {
291
+ if (!id) return null;
292
+ const item = clean({ id, label, run, badge, keys, disabled, href, keepOpen });
293
+ if (section === "start" || section === "end") item.section = section;
294
+ if (quiet) item.quiet = true;
295
+ if (stepper) {
296
+ item.stepper = {
297
+ label: String(stepper.label ?? ""),
298
+ value: String(stepper.value ?? ""),
299
+ prev: typeof stepper.prev === "function" ? stepper.prev : null,
300
+ next: typeof stepper.next === "function" ? stepper.next : null,
301
+ prevLabel: String(stepper.prevLabel || "Previous"),
302
+ nextLabel: String(stepper.nextLabel || "Next"),
303
+ };
304
+ const shown = [...list.querySelectorAll(".clay-menu-stepper")].find((row) => row.dataset.menuId === id)?.querySelector(".clay-menu-stepper-value");
305
+ // Only the value changed: update it in place, so focus stays on the arrow. The
306
+ // buttons read their callbacks from the stored item when pressed.
307
+ const before = items.get(id);
308
+ const same = before?.stepper && ["label", "prevLabel", "nextLabel"].every((key) => before.stepper[key] === item.stepper[key]) && before.section === item.section && before.quiet === item.quiet;
309
+ if (same && shown) {
310
+ items.set(id, item);
311
+ shown.textContent = item.stepper.value;
312
+ return id;
313
+ }
314
+ }
315
+ if (text) item.text = true;
316
+ if (Array.isArray(pair)) item.pair = pair.map(clean);
317
+ if (toggle) {
318
+ item.toggle = true;
319
+ item.checked = Boolean(checked);
320
+ }
321
+ if (dismiss) item.dismiss = { label: String(dismiss.label || "Dismiss"), run: typeof dismiss.run === "function" ? dismiss.run : null };
322
+ items.set(id, item);
323
+ render();
324
+ return id;
325
+ }
326
+
327
+ function removeItem(id) {
328
+ if (items.delete(id)) render();
329
+ }
330
+
331
+ function note(text) {
332
+ if (!text) return;
333
+ noteEl.textContent = text;
334
+ noteEl.hidden = false;
335
+ clearTimeout(noteTimer);
336
+ noteTimer = setTimeout(() => {
337
+ noteEl.hidden = true;
338
+ noteEl.textContent = "";
339
+ }, NOTE_MS);
340
+ }
341
+
342
+ let noteTimer = 0;
343
+
344
+ goo.addEventListener("click", () => {
345
+ if (isOpen()) close();
346
+ else open();
347
+ });
348
+
349
+ document.addEventListener("click", (event) => {
350
+ if (isOpen() && !event.composedPath().includes(root)) close();
351
+ });
352
+
353
+ root.addEventListener("keydown", (event) => {
354
+ if (event.key === "Escape") {
355
+ if (!isOpen()) return;
356
+ event.preventDefault();
357
+ close();
358
+ goo.focus();
359
+ return;
360
+ }
361
+ if (event.target === goo && (event.key === "Enter" || event.key === " ")) {
362
+ event.preventDefault();
363
+ if (isOpen()) close();
364
+ else {
365
+ open();
366
+ focusItem(0);
367
+ }
368
+ return;
369
+ }
370
+ if (!isOpen()) return;
371
+ if (event.key === "Enter" && list.contains(event.target)) {
372
+ event.preventDefault();
373
+ event.target.click();
374
+ return;
375
+ }
376
+ const all = buttons();
377
+ if (!all.length) return;
378
+ const at = all.indexOf(focusedEl());
379
+ if (event.key === "ArrowDown") {
380
+ event.preventDefault();
381
+ focusItem(at < 0 ? 0 : at + 1);
382
+ } else if (event.key === "ArrowUp") {
383
+ event.preventDefault();
384
+ focusItem(at < 0 ? all.length - 1 : at - 1);
385
+ }
386
+ });
387
+
388
+ function mount(css) {
389
+ const ui = mountEditorUI({ name: "menu", content: root, css, parent: slot || document.body, attributes: { "clay-mode-toggle": "" } });
390
+ if (embedded) {
391
+ window.addEventListener("resize", close);
392
+ window.addEventListener("scroll", () => { if (topLayer && isOpen()) placeList(); }, { capture: true, passive: true });
393
+ }
394
+ return ui;
395
+ }
396
+
397
+ return { root, goo, list, slot, embedded, skin, items, addItem, removeItem, render, open, close, isOpen, note, beforeOpen, focusedEl, mount };
398
+ }
@@ -1,60 +1,20 @@
1
1
  // The menu's look: Bevel tokens scoped to the menu family, solid surfaces, one soft
2
2
  // shadow, light or dark by the scheme ui/page-colour.js reads from the page behind it.
3
+ import { SHELL_A, SHELL_B, SHELL_C, SHELL_FLOATING, MENU_SKIN, MENU_TOUCH } from "./shell-style.js";
4
+ export { MENU_SKIN, MENU_TOUCH } from "./shell-style.js";
5
+
3
6
  // The surface and muted ink below, per scheme, for ui/page-colour.js's tint.
4
7
  export const MENU_SURFACE = { light: [0xFF, 0xFC, 0xF6], dark: [0x1A, 0x1D, 0x2C], mutedLight: [0x6B, 0x59, 0x42], mutedDark: [0xA3, 0xA9, 0xC2] };
5
8
 
6
9
  // The parts of the look a page may replace, through <style clay-menu-skin="1">. Everything
7
- // else below stays: layout, hidden, focus rings, touch targets and reduced motion.
8
- export const MENU_BASE = `.clay-menu-dock, .clay-menu-spots, .clay-seo {
9
- --bevel-font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
10
- --bevel-font-serif: Newsreader, Georgia, "Times New Roman", serif;
11
- --bevel-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
12
- --bevel-ground: light-dark(#FDF8F0, #0B0C13);
13
- --bevel-surface: light-dark(#FFFCF6, #1A1D2C);
14
- --bevel-sunk: light-dark(#F4ECDF, #22263A);
15
- --bevel-ink: light-dark(#2B241B, #ECEAF2);
16
- --bevel-ink-2: light-dark(#4A4033, #C9CDE0);
17
- --bevel-muted: light-dark(#6B5942, #A3A9C2);
18
- --bevel-faint: light-dark(#9A8A70, #6A7090);
19
- --bevel-line: light-dark(#E8DCC6, #2C3149);
20
- --bevel-line-2: light-dark(#D8C8AF, #3A4058);
21
- --bevel-line-3: light-dark(#C4B192, #4A536F);
22
- --bevel-brass: light-dark(#8F6310, #D9A445);
23
- --bevel-brass-soft: light-dark(#F0E4CC, #3A2F17);
24
- --bevel-ox: light-dark(#A83A2C, #FF6B79);
25
- --bevel-ox-soft: light-dark(#F6DED6, #3A2226);
26
- --bevel-teal: light-dark(#256056, #4E9C8C);
27
- --bevel-teal-soft: light-dark(#DCEBE7, #17332E);
28
- --bevel-face: light-dark(#F1E7D4, #2A2F48);
29
- --bevel-edge-hi: light-dark(#FFF9ED, #454C6C);
30
- --bevel-edge-lo: light-dark(#D6C3A5, #171B2C);
31
- --bevel-face-in: light-dark(#E3D0B3, #20253B);
32
- --bevel-edge-in: light-dark(#B89F7B, #12152A);
33
- --bevel-edge-in-hi: light-dark(#F0E1C9, #4D587A);
34
- --bevel-knob: light-dark(#FCF8F1, #5A6286);
35
- --bevel-knob-hi: light-dark(#FFF9ED, #7A83A8);
36
- --clay-shadow: 0 1px 2px light-dark(rgb(43 36 27 / .07), rgb(0 0 0 / .35)), 0 10px 28px -10px light-dark(rgb(43 36 27 / .32), rgb(0 0 0 / .7));
37
- --clay-sheet-shadow: 0 16px 30px -22px light-dark(rgb(43 36 27 / .45), rgb(0 0 0 / .85));
38
- color-scheme: inherit;
39
- font-family: var(--bevel-font-sans);
40
- color: var(--bevel-ink);
41
- -webkit-text-size-adjust: 100%;
42
- text-size-adjust: 100%;
43
- }
44
-
45
- .clay-menu-dock { display: flex; align-items: center; gap: 6px; font-size: 13px; line-height: 1.4; }
46
- .clay-menu-dock[data-embedded] { position: relative; display: inline-flex; }
47
- .clay-menu-dock[hidden], .clay-menu-spots[hidden], .clay-menu-dock [hidden], .clay-seo [hidden] { display: none !important; }
48
-
49
- .clay-menu { box-sizing: border-box; display: inline-flex; align-items: center; gap: 2px; user-select: none; -webkit-user-select: none; }
50
-
51
- .clay-menu-edit { position: relative; display: inline-flex; align-items: center; gap: 8px; height: 28px; margin: 0; padding: 0 8px 0 5px; border: 0; background: none; font: 500 13px/1 var(--bevel-font-sans); color: var(--bevel-ink); cursor: pointer; }
10
+ // else stays: layout, hidden, focus rings, touch targets and reduced motion.
11
+ const OWNER_1 = `.clay-menu-edit { position: relative; display: inline-flex; align-items: center; gap: 8px; height: 28px; margin: 0; padding: 0 8px 0 5px; border: 0; background: none; font: 500 13px/1 var(--bevel-font-sans); color: var(--bevel-ink); cursor: pointer; }
52
12
  .clay-menu-edit::before { content: ""; position: absolute; inset: -8px 0; }
53
13
  .clay-menu-track { position: relative; display: block; box-sizing: border-box; width: 36px; height: 20px; border: 1px solid var(--bevel-line-2); background: var(--bevel-sunk); transition: background-color .14s, border-color .14s; }
54
14
  .clay-menu-thumb { position: absolute; top: 2px; left: 2px; box-sizing: border-box; width: 14px; height: 14px; border: 2px solid; border-color: var(--bevel-knob-hi) var(--bevel-edge-lo) var(--bevel-edge-lo) var(--bevel-knob-hi); background: var(--bevel-knob); transition: translate .15s cubic-bezier(.4, 0, .2, 1); }
55
- .clay-menu-edit:hover .clay-menu-track { border-color: var(--bevel-faint); }
56
- .clay-menu-edit[aria-checked="true"] .clay-menu-track { background: #256056; border-color: light-dark(#256056, #3C7E72); }
57
- .clay-menu-edit[aria-checked="true"] .clay-menu-thumb { translate: 16px 0; background: light-dark(var(--bevel-knob), #8EDCC5); border-color: light-dark(var(--bevel-knob-hi), #C2F0E2) light-dark(var(--bevel-edge-lo), #4F9C86) light-dark(var(--bevel-edge-lo), #4F9C86) light-dark(var(--bevel-knob-hi), #C2F0E2); }
15
+ :is(.clay-menu-edit, .clay-menu-toggle):hover .clay-menu-track { border-color: var(--bevel-faint); }
16
+ :is(.clay-menu-edit, .clay-menu-toggle)[aria-checked="true"] .clay-menu-track { background: #256056; border-color: light-dark(#256056, #3C7E72); }
17
+ :is(.clay-menu-edit, .clay-menu-toggle)[aria-checked="true"] .clay-menu-thumb { translate: 16px 0; background: light-dark(var(--bevel-knob), #8EDCC5); border-color: light-dark(var(--bevel-knob-hi), #C2F0E2) light-dark(var(--bevel-edge-lo), #4F9C86) light-dark(var(--bevel-edge-lo), #4F9C86) light-dark(var(--bevel-knob-hi), #C2F0E2); }
58
18
  .clay-menu-edit[aria-busy="true"] { cursor: progress; color: var(--bevel-muted); }
59
19
  .clay-menu-edit[aria-busy="true"] .clay-menu-track { background: var(--bevel-line); border-color: var(--bevel-line-2); }
60
20
  .clay-menu-edit[aria-busy="true"] .clay-menu-thumb { translate: 8px 0; opacity: .7; }
@@ -62,15 +22,9 @@ export const MENU_BASE = `.clay-menu-dock, .clay-menu-spots, .clay-seo {
62
22
 
63
23
  .clay-menu-divider { width: 1px; height: 18px; margin: 0 2px; background: var(--bevel-line-2); }
64
24
 
65
- .clay-menu-goo { position: relative; display: inline-grid; place-items: center; box-sizing: border-box; margin: 0; padding: 0; cursor: pointer; }
66
- .clay-menu-goo svg { display: block; }
67
- .clay-menu-dot { position: absolute; top: -1px; right: -2px; width: 8px; height: 8px; border-radius: 50%; background: var(--bevel-brass); box-shadow: 0 0 0 2px var(--bevel-surface); }
68
-
69
- .clay-menu :focus-visible, .clay-roster:focus-visible { outline: 2px solid var(--bevel-brass); outline-offset: 2px; }
70
- .clay-menu-edit:focus-visible { outline: none; }
71
- .clay-menu-edit:focus-visible .clay-menu-track { outline: 2px solid var(--bevel-brass); outline-offset: 2px; }
25
+ `;
72
26
 
73
- /* The roster, beside the capsule, only on a page that opts in. Flat, like the capsule. */
27
+ const OWNER_2 = `/* The roster, beside the capsule, only on a page that opts in. Flat, like the capsule. */
74
28
  .clay-roster { box-sizing: border-box; display: inline-flex; align-items: center; gap: 8px; height: 34px; margin: 0; padding: 0 10px 0 5px; border: 1px solid var(--bevel-line-3); background: var(--bevel-surface); box-shadow: var(--clay-shadow); font: 400 12px/1 var(--bevel-font-mono); color: var(--bevel-muted); cursor: pointer; }
75
29
  .clay-roster:hover, .clay-roster[aria-expanded="true"] { background: var(--bevel-sunk); }
76
30
  .clay-roster-faces { display: inline-flex; }
@@ -87,29 +41,9 @@ export const MENU_BASE = `.clay-menu-dock, .clay-menu-spots, .clay-seo {
87
41
  .clay-avatar[data-tone="brass"] { background: var(--bevel-brass-soft); color: var(--bevel-brass); border-color: color-mix(in srgb, var(--bevel-brass) 35%, var(--bevel-brass-soft)); }
88
42
  .clay-avatar[data-tone="teal"] { background: var(--bevel-teal-soft); color: var(--bevel-teal); border-color: color-mix(in srgb, var(--bevel-teal) 35%, var(--bevel-teal-soft)); }
89
43
 
90
- /* Popovers: the menu, the people panel, the status note. Below the dock, above it in a bottom corner. */
91
- .clay-menu-list, .clay-people, .clay-menu-note { position: absolute; z-index: 1; top: calc(100% + 8px); right: 0; box-sizing: border-box; margin: 0; color: var(--bevel-ink); text-align: left; }
92
- .clay-people, .clay-menu-note { border: 1px solid var(--bevel-line-2); background: var(--bevel-surface); box-shadow: var(--clay-shadow); }
93
-
94
- .clay-menu-list { width: max-content; max-width: calc(100vw - 24px); max-height: calc(100dvh - 72px); overflow-y: auto; overscroll-behavior: contain; }
95
- .clay-menu-list[popover] { position: fixed; inset: auto; margin: 0; }
96
- .clay-menu-list [role="menuitem"] { box-sizing: border-box; display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; margin: 0; border: 0; background: none; text-align: left; text-decoration: none; cursor: pointer; }
97
- .clay-menu-list [role="menuitem"]:hover:not([aria-disabled="true"]), .clay-menu-list [role="menuitem"]:focus-visible { outline: none; background: var(--bevel-sunk); }
98
- .clay-menu-list [role="menuitem"][aria-disabled="true"] { cursor: default; }
99
- .clay-menu-pair { display: flex; gap: 12px; padding: 3px 9px; }
100
- .clay-menu-list .clay-menu-pair [role="menuitem"] { flex: none; width: auto; justify-content: flex-start; gap: 9px; }
101
- .clay-menu-list kbd { flex: none; white-space: nowrap; }
102
- .clay-menu-row { display: flex; align-items: center; gap: 2px; }
103
- .clay-menu-list .clay-menu-row > [part~="item"] { flex: 1; width: auto; min-width: 0; }
104
- .clay-menu-list .clay-menu-row > [part~="dismiss"] { flex: none; width: 28px; justify-content: center; padding: 0; }
105
- .clay-menu-row > [part~="dismiss"] svg { display: block; width: 10px; height: 10px; fill: currentColor; }
106
- .clay-menu-upgrade { display: inline-flex; align-items: center; gap: 8px; }
107
- .clay-menu-upgrade::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--bevel-brass); }
108
- .clay-menu-version { margin: 0; }
109
-
110
- .clay-menu-note { z-index: 2; width: max-content; max-width: 264px; padding: 8px 12px; font-size: 13px; line-height: 1.45; }
44
+ `;
111
45
 
112
- /* The people panel: no lines between people, one line between sections. */
46
+ const OWNER_3 = `/* The people panel: no lines between people, one line between sections. */
113
47
  .clay-people { width: 300px; max-width: calc(100vw - 24px); padding: 0; }
114
48
  .clay-people-head { display: flex; align-items: center; gap: 10px; padding: 10px 8px 10px 14px; border-bottom: 1px solid var(--bevel-line); }
115
49
  .clay-people-title { flex: 1; margin: 0; font: 500 16px/1.2 var(--bevel-font-serif); }
@@ -143,13 +77,9 @@ export const MENU_BASE = `.clay-menu-dock, .clay-menu-spots, .clay-seo {
143
77
  .clay-menu-btn-primary:hover { background: color-mix(in srgb, var(--bevel-ink), var(--bevel-ground) 12%); }
144
78
  `;
145
79
 
146
- export const MENU_FLOATING = `/* The dock: the roster (when a page opts in) and the capsule, fixed in one corner. */
147
- .clay-menu-dock { position: fixed; z-index: 2147482000; top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); }
148
- .clay-menu-dock[data-corner$="left"] { right: auto; left: max(12px, env(safe-area-inset-left)); flex-direction: row-reverse; }
149
- .clay-menu-dock[data-corner^="bottom"] { top: auto; bottom: max(12px, env(safe-area-inset-bottom)); }
150
- .clay-menu-dock[data-corner$="left"] :is(.clay-menu-list, .clay-people, .clay-menu-note) { right: auto; left: 0; }
151
- .clay-menu-dock[data-corner^="bottom"] :is(.clay-menu-list, .clay-people, .clay-menu-note) { top: auto; bottom: calc(100% + 8px); }
152
- .clay-menu-dock[data-dragging] .clay-menu { border-color: var(--bevel-brass); cursor: grabbing; }
80
+ export const MENU_BASE = SHELL_A + OWNER_1 + SHELL_B + OWNER_2 + SHELL_C + OWNER_3;
81
+
82
+ export const MENU_FLOATING = SHELL_FLOATING + `.clay-menu-dock[data-dragging] .clay-menu { border-color: var(--bevel-brass); cursor: grabbing; }
153
83
  .clay-menu-grip { position: relative; display: inline-grid; place-items: center; width: 18px; height: 28px; margin: 0; padding: 0; border: 0; background: none; color: var(--bevel-faint); cursor: grab; touch-action: none; }
154
84
  .clay-menu-grip::before { content: ""; position: absolute; inset: -8px -2px -8px -10px; }
155
85
  .clay-menu-grip:hover { color: var(--bevel-ink-2); }
@@ -165,35 +95,4 @@ export const MENU_FLOATING = `/* The dock: the roster (when a page opts in) and
165
95
  .clay-menu-spot[data-near] { border: 2px solid var(--bevel-brass); background: var(--bevel-brass-soft); }
166
96
  `;
167
97
 
168
- export const MENU_SKIN = `.clay-menu { height: 34px; padding: 0 4px 0 2px; border: 1px solid var(--bevel-line-3); background: var(--bevel-surface); box-shadow: var(--clay-shadow); }
169
- .clay-menu-goo { width: 28px; height: 28px; min-height: 0; border-radius: 0; border: 2px solid transparent; background: none; color: var(--bevel-ink); }
170
- .clay-menu-goo::before { content: ""; position: absolute; inset: -8px -8px -8px -4px; }
171
- .clay-menu-goo svg { width: 18px; height: auto; fill: currentColor; }
172
- .clay-menu-goo:hover svg { translate: 0 -1px; }
173
- .clay-menu-goo:active, .clay-menu-goo[aria-expanded="true"] { background: var(--bevel-face-in); border-color: var(--bevel-edge-in) var(--bevel-edge-in-hi) var(--bevel-edge-in-hi) var(--bevel-edge-in); }
174
- .clay-menu-goo:active svg, .clay-menu-goo[aria-expanded="true"] svg { translate: 1px 1px; }
175
- .clay-menu-list { min-width: 284px; padding: 6px; border: 1px solid var(--bevel-line-2); background: var(--bevel-surface); box-shadow: var(--clay-shadow); }
176
- .clay-menu-list [role="menuitem"] { min-height: 32px; padding: 5px 9px; font: 400 13px/1.35 var(--bevel-font-sans); color: var(--bevel-ink); }
177
- .clay-menu-list [role="menuitem"][aria-disabled="true"] { color: var(--bevel-faint); }
178
- .clay-menu-list [role="menuitem"][aria-disabled="true"] kbd { opacity: .6; }
179
- .clay-menu-list .clay-menu-pair [role="menuitem"] { min-height: 30px; padding: 4px 6px 4px 9px; border: 1px solid var(--bevel-line); }
180
- .clay-menu-list kbd { font: 500 11px/1 var(--bevel-font-mono); padding: 3px 5px; border: 1px solid var(--bevel-line-2); background: var(--bevel-sunk); color: var(--bevel-muted); }
181
- .clay-menu-list [part~="quiet"] { font-size: 12px; color: var(--bevel-muted); }
182
- .clay-menu-sep { height: 1px; margin: 4px 2px; background: var(--bevel-line); }
183
- .clay-menu-version { padding: 6px 9px 4px; font: 400 11.5px/1.4 var(--bevel-font-mono); color: var(--bevel-muted); }
184
- `;
185
-
186
- // Last, so no skin can shrink a touch target or bring motion back.
187
- export const MENU_TOUCH = `@media (pointer: coarse), (max-width: 680px) {
188
- .clay-menu-list [role="menuitem"] { min-height: 44px; font-size: 15px; }
189
- .clay-menu-list .clay-menu-pair [role="menuitem"] { min-height: 44px; }
190
- :is(.clay-menu-dock, .clay-seo) :is(input, textarea, select) { font-size: 16px; }
191
- .clay-seo-input, .clay-people-input { min-height: 40px; }
192
- .clay-menu-btn { min-height: 40px; }
193
- }
194
- @media (prefers-reduced-motion: reduce) {
195
- .clay-menu-track, .clay-menu-thumb { transition: none; }
196
- }
197
- `;
198
-
199
98
  export default MENU_BASE + MENU_FLOATING + MENU_SKIN + MENU_TOUCH;