@panphora/bevel 0.1.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 (539) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +226 -0
  3. package/bevel-fonts.css +16 -0
  4. package/bevel.css +3075 -0
  5. package/bevel.js +2126 -0
  6. package/dist/react/components/activity-feed.d.ts +89 -0
  7. package/dist/react/components/activity-feed.js +54 -0
  8. package/dist/react/components/alert-dialog.d.ts +14 -0
  9. package/dist/react/components/alert-dialog.js +19 -0
  10. package/dist/react/components/app-shell.d.ts +98 -0
  11. package/dist/react/components/app-shell.js +86 -0
  12. package/dist/react/components/app-toolbar.d.ts +19 -0
  13. package/dist/react/components/app-toolbar.js +22 -0
  14. package/dist/react/components/avatar-group.d.ts +67 -0
  15. package/dist/react/components/avatar-group.js +49 -0
  16. package/dist/react/components/badge-and-pill.d.ts +34 -0
  17. package/dist/react/components/badge-and-pill.js +12 -0
  18. package/dist/react/components/breadcrumbs.d.ts +56 -0
  19. package/dist/react/components/breadcrumbs.js +44 -0
  20. package/dist/react/components/bulk-bar.d.ts +22 -0
  21. package/dist/react/components/bulk-bar.js +59 -0
  22. package/dist/react/components/button.d.ts +15 -0
  23. package/dist/react/components/button.js +20 -0
  24. package/dist/react/components/callout-and-hint.d.ts +17 -0
  25. package/dist/react/components/callout-and-hint.js +8 -0
  26. package/dist/react/components/checkbox.d.ts +5 -0
  27. package/dist/react/components/checkbox.js +13 -0
  28. package/dist/react/components/chip.d.ts +149 -0
  29. package/dist/react/components/chip.js +29 -0
  30. package/dist/react/components/code-block.d.ts +50 -0
  31. package/dist/react/components/code-block.js +77 -0
  32. package/dist/react/components/code-editor.d.ts +122 -0
  33. package/dist/react/components/code-editor.js +695 -0
  34. package/dist/react/components/column-manager.d.ts +28 -0
  35. package/dist/react/components/column-manager.js +85 -0
  36. package/dist/react/components/combobox.d.ts +32 -0
  37. package/dist/react/components/combobox.js +37 -0
  38. package/dist/react/components/data-grid.d.ts +108 -0
  39. package/dist/react/components/data-grid.js +159 -0
  40. package/dist/react/components/data-table.d.ts +2 -0
  41. package/dist/react/components/data-table.js +6 -0
  42. package/dist/react/components/date.d.ts +23 -0
  43. package/dist/react/components/date.js +11 -0
  44. package/dist/react/components/details.d.ts +13 -0
  45. package/dist/react/components/details.js +20 -0
  46. package/dist/react/components/dialog.d.ts +74 -0
  47. package/dist/react/components/dialog.js +31 -0
  48. package/dist/react/components/diff-view.d.ts +73 -0
  49. package/dist/react/components/diff-view.js +68 -0
  50. package/dist/react/components/documents.d.ts +70 -0
  51. package/dist/react/components/documents.js +10 -0
  52. package/dist/react/components/drawer.d.ts +45 -0
  53. package/dist/react/components/drawer.js +35 -0
  54. package/dist/react/components/editable-grid-cells.d.ts +32 -0
  55. package/dist/react/components/editable-grid-cells.js +116 -0
  56. package/dist/react/components/eyebrow-kicker-lede-pull-quote.d.ts +39 -0
  57. package/dist/react/components/eyebrow-kicker-lede-pull-quote.js +12 -0
  58. package/dist/react/components/field.d.ts +32 -0
  59. package/dist/react/components/field.js +27 -0
  60. package/dist/react/components/file-tree.d.ts +75 -0
  61. package/dist/react/components/file-tree.js +247 -0
  62. package/dist/react/components/file.d.ts +43 -0
  63. package/dist/react/components/file.js +73 -0
  64. package/dist/react/components/files-table.d.ts +174 -0
  65. package/dist/react/components/files-table.js +237 -0
  66. package/dist/react/components/filter-builder.d.ts +37 -0
  67. package/dist/react/components/filter-builder.js +109 -0
  68. package/dist/react/components/form-error-summary.d.ts +16 -0
  69. package/dist/react/components/form-error-summary.js +49 -0
  70. package/dist/react/components/icon-button.d.ts +8 -0
  71. package/dist/react/components/icon-button.js +14 -0
  72. package/dist/react/components/icon.d.ts +7 -0
  73. package/dist/react/components/icon.js +15 -0
  74. package/dist/react/components/image.d.ts +72 -0
  75. package/dist/react/components/image.js +24 -0
  76. package/dist/react/components/inline-edit.d.ts +70 -0
  77. package/dist/react/components/inline-edit.js +149 -0
  78. package/dist/react/components/input.d.ts +29 -0
  79. package/dist/react/components/input.js +20 -0
  80. package/dist/react/components/kanban-board.d.ts +140 -0
  81. package/dist/react/components/kanban-board.js +374 -0
  82. package/dist/react/components/layout.d.ts +51 -0
  83. package/dist/react/components/layout.js +28 -0
  84. package/dist/react/components/list.d.ts +28 -0
  85. package/dist/react/components/list.js +6 -0
  86. package/dist/react/components/masthead-and-verdict.d.ts +60 -0
  87. package/dist/react/components/masthead-and-verdict.js +8 -0
  88. package/dist/react/components/menu.d.ts +26 -0
  89. package/dist/react/components/menu.js +31 -0
  90. package/dist/react/components/multi-select-picker.d.ts +49 -0
  91. package/dist/react/components/multi-select-picker.js +20 -0
  92. package/dist/react/components/note.d.ts +33 -0
  93. package/dist/react/components/note.js +8 -0
  94. package/dist/react/components/number-field.d.ts +16 -0
  95. package/dist/react/components/number-field.js +17 -0
  96. package/dist/react/components/pagination.d.ts +45 -0
  97. package/dist/react/components/pagination.js +87 -0
  98. package/dist/react/components/persistent-action-banner.d.ts +71 -0
  99. package/dist/react/components/persistent-action-banner.js +39 -0
  100. package/dist/react/components/popconfirm.d.ts +42 -0
  101. package/dist/react/components/popconfirm.js +59 -0
  102. package/dist/react/components/popover.d.ts +16 -0
  103. package/dist/react/components/popover.js +21 -0
  104. package/dist/react/components/progress-and-gauge.d.ts +44 -0
  105. package/dist/react/components/progress-and-gauge.js +19 -0
  106. package/dist/react/components/radio.d.ts +7 -0
  107. package/dist/react/components/radio.js +13 -0
  108. package/dist/react/components/rich-text-toolbar.d.ts +26 -0
  109. package/dist/react/components/rich-text-toolbar.js +64 -0
  110. package/dist/react/components/rows-and-card.d.ts +88 -0
  111. package/dist/react/components/rows-and-card.js +10 -0
  112. package/dist/react/components/save-status-indicator.d.ts +11 -0
  113. package/dist/react/components/save-status-indicator.js +33 -0
  114. package/dist/react/components/segmented-control.d.ts +21 -0
  115. package/dist/react/components/segmented-control.js +15 -0
  116. package/dist/react/components/select.d.ts +29 -0
  117. package/dist/react/components/select.js +29 -0
  118. package/dist/react/components/selectable-cards.d.ts +30 -0
  119. package/dist/react/components/selectable-cards.js +29 -0
  120. package/dist/react/components/settings-rows.d.ts +51 -0
  121. package/dist/react/components/settings-rows.js +8 -0
  122. package/dist/react/components/sidebar-nav.d.ts +77 -0
  123. package/dist/react/components/sidebar-nav.js +41 -0
  124. package/dist/react/components/skeleton-and-empty.d.ts +51 -0
  125. package/dist/react/components/skeleton-and-empty.js +14 -0
  126. package/dist/react/components/slider.d.ts +19 -0
  127. package/dist/react/components/slider.js +17 -0
  128. package/dist/react/components/small-charts.d.ts +60 -0
  129. package/dist/react/components/small-charts.js +76 -0
  130. package/dist/react/components/sortable.d.ts +30 -0
  131. package/dist/react/components/sortable.js +85 -0
  132. package/dist/react/components/split-button.d.ts +38 -0
  133. package/dist/react/components/split-button.js +43 -0
  134. package/dist/react/components/split-panes.d.ts +35 -0
  135. package/dist/react/components/split-panes.js +102 -0
  136. package/dist/react/components/stat-tiles.d.ts +54 -0
  137. package/dist/react/components/stat-tiles.js +8 -0
  138. package/dist/react/components/steps.d.ts +11 -0
  139. package/dist/react/components/steps.js +13 -0
  140. package/dist/react/components/switch.d.ts +6 -0
  141. package/dist/react/components/switch.js +13 -0
  142. package/dist/react/components/table.d.ts +61 -0
  143. package/dist/react/components/table.js +6 -0
  144. package/dist/react/components/tabs.d.ts +7 -0
  145. package/dist/react/components/tabs.js +13 -0
  146. package/dist/react/components/tags.d.ts +33 -0
  147. package/dist/react/components/tags.js +83 -0
  148. package/dist/react/components/textarea.d.ts +10 -0
  149. package/dist/react/components/textarea.js +6 -0
  150. package/dist/react/components/toast.d.ts +35 -0
  151. package/dist/react/components/toast.js +90 -0
  152. package/dist/react/components/tooltip-and-key.d.ts +20 -0
  153. package/dist/react/components/tooltip-and-key.js +20 -0
  154. package/dist/react/components/tree-view.d.ts +34 -0
  155. package/dist/react/components/tree-view.js +118 -0
  156. package/dist/react/components/upload-dropzone.d.ts +92 -0
  157. package/dist/react/components/upload-dropzone.js +116 -0
  158. package/dist/react/components/wizard-shell.d.ts +78 -0
  159. package/dist/react/components/wizard-shell.js +176 -0
  160. package/dist/react/fonts.css.d.ts +1 -0
  161. package/dist/react/index.d.ts +76 -0
  162. package/dist/react/index.js +77 -0
  163. package/dist/react/integrations/quickcrop.d.ts +58 -0
  164. package/dist/react/integrations/quickcrop.js +137 -0
  165. package/dist/react/integrations/richclay.d.ts +43 -0
  166. package/dist/react/integrations/richclay.js +129 -0
  167. package/dist/react/internal/classes.d.ts +3 -0
  168. package/dist/react/internal/classes.js +10 -0
  169. package/dist/react/internal/close-mark.d.ts +1 -0
  170. package/dist/react/internal/close-mark.js +10 -0
  171. package/dist/react/internal/controllable-state.d.ts +9 -0
  172. package/dist/react/internal/controllable-state.js +22 -0
  173. package/dist/react/internal/drop-effect.d.ts +28 -0
  174. package/dist/react/internal/drop-effect.js +8 -0
  175. package/dist/react/internal/form-reset.d.ts +2 -0
  176. package/dist/react/internal/form-reset.js +22 -0
  177. package/dist/react/internal/format.d.ts +1 -0
  178. package/dist/react/internal/format.js +14 -0
  179. package/dist/react/internal/link.d.ts +2 -0
  180. package/dist/react/internal/link.js +8 -0
  181. package/dist/react/internal/part.d.ts +20 -0
  182. package/dist/react/internal/part.js +47 -0
  183. package/dist/react/internal/pointer-session.d.ts +8 -0
  184. package/dist/react/internal/pointer-session.js +60 -0
  185. package/dist/react/internal/refs.d.ts +2 -0
  186. package/dist/react/internal/refs.js +22 -0
  187. package/dist/react/internal/rich-text.d.ts +28 -0
  188. package/dist/react/internal/rich-text.js +4 -0
  189. package/dist/react/internal/table-context.d.ts +39 -0
  190. package/dist/react/internal/table-context.js +12 -0
  191. package/dist/react/internal/use-object-url.d.ts +1 -0
  192. package/dist/react/internal/use-object-url.js +15 -0
  193. package/dist/react/layout.css +274 -0
  194. package/dist/react/layout.css.d.ts +1 -0
  195. package/dist/react/models/filter.d.ts +31 -0
  196. package/dist/react/models/filter.js +79 -0
  197. package/dist/react/models/kanban.d.ts +51 -0
  198. package/dist/react/models/kanban.js +35 -0
  199. package/dist/react/models/reorder.d.ts +9 -0
  200. package/dist/react/models/reorder.js +23 -0
  201. package/dist/react/models/table.d.ts +85 -0
  202. package/dist/react/models/table.js +144 -0
  203. package/dist/react/models/tree.d.ts +16 -0
  204. package/dist/react/models/tree.js +26 -0
  205. package/dist/react/provider.d.ts +18 -0
  206. package/dist/react/provider.js +31 -0
  207. package/dist/react/recipes/context-hypercms.d.ts +17 -0
  208. package/dist/react/recipes/context-hypercms.js +71 -0
  209. package/dist/react/recipes/context-quickcrop.d.ts +8 -0
  210. package/dist/react/recipes/context-quickcrop.js +49 -0
  211. package/dist/react/theme.css +8004 -0
  212. package/dist/react/theme.css.d.ts +1 -0
  213. package/examples/README.md +34 -0
  214. package/examples/components/activity-feed.html +28 -0
  215. package/examples/components/app-shell.html +12 -0
  216. package/examples/components/app-toolbar.html +38 -0
  217. package/examples/components/avatar-group.html +27 -0
  218. package/examples/components/breadcrumbs.html +26 -0
  219. package/examples/components/bulk-bar.html +22 -0
  220. package/examples/components/code-block.html +12 -0
  221. package/examples/components/code-editor.html +25 -0
  222. package/examples/components/column-manager.html +9 -0
  223. package/examples/components/combobox.html +13 -0
  224. package/examples/components/context-hypercms.css +41 -0
  225. package/examples/components/context-hypercms.html +40 -0
  226. package/examples/components/context-hypercms.js +62 -0
  227. package/examples/components/context-quickcrop.html +137 -0
  228. package/examples/components/diff-view.html +9 -0
  229. package/examples/components/drawer.html +11 -0
  230. package/examples/components/editable-grid-cells.html +9 -0
  231. package/examples/components/file-browser.html +53 -0
  232. package/examples/components/file-tree.html +34 -0
  233. package/examples/components/files-table.html +80 -0
  234. package/examples/components/filter-builder.html +11 -0
  235. package/examples/components/form-error-summary.html +11 -0
  236. package/examples/components/index.html +51 -0
  237. package/examples/components/inline-edit.html +42 -0
  238. package/examples/components/kanban-board.html +42 -0
  239. package/examples/components/manifest.json +3279 -0
  240. package/examples/components/modal.html +11 -0
  241. package/examples/components/multi-select-picker.html +13 -0
  242. package/examples/components/pagination.html +32 -0
  243. package/examples/components/persistent-action-banner.html +23 -0
  244. package/examples/components/popconfirm.html +13 -0
  245. package/examples/components/popover-menu.html +13 -0
  246. package/examples/components/reference/baseline/DECISIONS.md +85 -0
  247. package/examples/components/reference/baseline/README.md +58 -0
  248. package/examples/components/reference/baseline/bevel-fonts.css +16 -0
  249. package/examples/components/reference/baseline/bevel.css +1339 -0
  250. package/examples/components/reference/baseline/bevel.js +496 -0
  251. package/examples/components/reference/baseline/fonts/LICENSE-ibm-plex-mono.txt +93 -0
  252. package/examples/components/reference/baseline/fonts/LICENSE-ibm-plex-sans.txt +93 -0
  253. package/examples/components/reference/baseline/fonts/LICENSE-newsreader.txt +93 -0
  254. package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  255. package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  256. package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  257. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
  258. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  259. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  260. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  261. package/examples/components/reference/baseline/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  262. package/examples/components/reference/baseline/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  263. package/examples/components/reference/fable/IMPROVEMENTS.md +106 -0
  264. package/examples/components/reference/fable/PARENT-REPAIRS.md +17 -0
  265. package/examples/components/reference/fable/__pycache__/build_repairs.cpython-311.pyc +0 -0
  266. package/examples/components/reference/fable/__pycache__/kanban.cpython-311.pyc +0 -0
  267. package/examples/components/reference/fable/as-authored/IMPROVEMENTS-completed.md +106 -0
  268. package/examples/components/reference/fable/as-authored/base-astra.css +17 -0
  269. package/examples/components/reference/fable/as-authored/base-parent.css +416 -0
  270. package/examples/components/reference/fable/as-authored/build.py +858 -0
  271. package/examples/components/reference/fable/as-authored/fable.css +823 -0
  272. package/examples/components/reference/fable/as-authored/kanban-completed.css +235 -0
  273. package/examples/components/reference/fable/as-authored/kanban-completed.html +53 -0
  274. package/examples/components/reference/fable/as-authored/kanban-completed.py +212 -0
  275. package/examples/components/reference/fable/as-authored/kanban.py +207 -0
  276. package/examples/components/reference/fable/base-astra.css +17 -0
  277. package/examples/components/reference/fable/base-parent.css +416 -0
  278. package/examples/components/reference/fable/build.py +858 -0
  279. package/examples/components/reference/fable/build_repairs.py +25 -0
  280. package/examples/components/reference/fable/components/activity-feed.html +3 -0
  281. package/examples/components/reference/fable/components/app-toolbar.html +3 -0
  282. package/examples/components/reference/fable/components/avatar-group.html +4 -0
  283. package/examples/components/reference/fable/components/breadcrumbs.html +3 -0
  284. package/examples/components/reference/fable/components/code-block.html +9 -0
  285. package/examples/components/reference/fable/components/column-manager.html +3 -0
  286. package/examples/components/reference/fable/components/combobox.html +6 -0
  287. package/examples/components/reference/fable/components/context-hypercms.html +3 -0
  288. package/examples/components/reference/fable/components/context-quickcrop.html +4 -0
  289. package/examples/components/reference/fable/components/diff-view.html +13 -0
  290. package/examples/components/reference/fable/components/drawer.html +3 -0
  291. package/examples/components/reference/fable/components/editable-grid-cells.html +3 -0
  292. package/examples/components/reference/fable/components/filter-builder.html +3 -0
  293. package/examples/components/reference/fable/components/form-error-summary.html +4 -0
  294. package/examples/components/reference/fable/components/inline-edit.html +3 -0
  295. package/examples/components/reference/fable/components/kanban-board.html +53 -0
  296. package/examples/components/reference/fable/components/modal.html +3 -0
  297. package/examples/components/reference/fable/components/multi-select-picker.html +5 -0
  298. package/examples/components/reference/fable/components/pagination.html +3 -0
  299. package/examples/components/reference/fable/components/persistent-action-banner.html +4 -0
  300. package/examples/components/reference/fable/components/popconfirm.html +5 -0
  301. package/examples/components/reference/fable/components/popover-menu.html +3 -0
  302. package/examples/components/reference/fable/components/rich-text-toolbar.html +3 -0
  303. package/examples/components/reference/fable/components/save-status-indicator.html +2 -0
  304. package/examples/components/reference/fable/components/selectable-cards.html +3 -0
  305. package/examples/components/reference/fable/components/settings-rows.html +3 -0
  306. package/examples/components/reference/fable/components/sidebar-nav.html +4 -0
  307. package/examples/components/reference/fable/components/small-charts.html +5 -0
  308. package/examples/components/reference/fable/components/split-button.html +4 -0
  309. package/examples/components/reference/fable/components/split-panes.html +8 -0
  310. package/examples/components/reference/fable/components/stat-tiles.html +3 -0
  311. package/examples/components/reference/fable/components/toast-stack.html +5 -0
  312. package/examples/components/reference/fable/components/tree-view.html +4 -0
  313. package/examples/components/reference/fable/components/upload-dropzone.html +6 -0
  314. package/examples/components/reference/fable/components/wizard-shell.html +3 -0
  315. package/examples/components/reference/fable/coverage.json +414 -0
  316. package/examples/components/reference/fable/fable.css +823 -0
  317. package/examples/components/reference/fable/index.html +5 -0
  318. package/examples/components/reference/fable/kanban.css +237 -0
  319. package/examples/components/reference/fable/kanban.py +212 -0
  320. package/examples/components/reference/fable/parent-repairs.css +9 -0
  321. package/examples/components/reference/fixtures/data.json +102 -0
  322. package/examples/components/reference/fixtures/host.js +47 -0
  323. package/examples/components/reference/fixtures/static-host.js +39 -0
  324. package/examples/components/reference/fixtures/workspace.jpg +0 -0
  325. package/examples/components/reference/parent/CHANGES.md +179 -0
  326. package/examples/components/reference/parent/__pycache__/kanban.cpython-311.pyc +0 -0
  327. package/examples/components/reference/parent/base-astra.css +17 -0
  328. package/examples/components/reference/parent/base-parent.css +416 -0
  329. package/examples/components/reference/parent/build.py +148 -0
  330. package/examples/components/reference/parent/components/activity-feed.html +2 -0
  331. package/examples/components/reference/parent/components/app-toolbar.html +2 -0
  332. package/examples/components/reference/parent/components/avatar-group.html +4 -0
  333. package/examples/components/reference/parent/components/breadcrumbs.html +2 -0
  334. package/examples/components/reference/parent/components/code-block.html +8 -0
  335. package/examples/components/reference/parent/components/column-manager.html +2 -0
  336. package/examples/components/reference/parent/components/combobox.html +6 -0
  337. package/examples/components/reference/parent/components/context-hypercms.html +2 -0
  338. package/examples/components/reference/parent/components/context-quickcrop.html +4 -0
  339. package/examples/components/reference/parent/components/diff-view.html +4 -0
  340. package/examples/components/reference/parent/components/drawer.html +2 -0
  341. package/examples/components/reference/parent/components/editable-grid-cells.html +2 -0
  342. package/examples/components/reference/parent/components/filter-builder.html +2 -0
  343. package/examples/components/reference/parent/components/form-error-summary.html +2 -0
  344. package/examples/components/reference/parent/components/inline-edit.html +2 -0
  345. package/examples/components/reference/parent/components/kanban-board.html +84 -0
  346. package/examples/components/reference/parent/components/modal.html +2 -0
  347. package/examples/components/reference/parent/components/multi-select-picker.html +5 -0
  348. package/examples/components/reference/parent/components/pagination.html +2 -0
  349. package/examples/components/reference/parent/components/persistent-action-banner.html +4 -0
  350. package/examples/components/reference/parent/components/popconfirm.html +5 -0
  351. package/examples/components/reference/parent/components/popover-menu.html +2 -0
  352. package/examples/components/reference/parent/components/rich-text-toolbar.html +2 -0
  353. package/examples/components/reference/parent/components/save-status-indicator.html +2 -0
  354. package/examples/components/reference/parent/components/selectable-cards.html +2 -0
  355. package/examples/components/reference/parent/components/settings-rows.html +2 -0
  356. package/examples/components/reference/parent/components/sidebar-nav.html +4 -0
  357. package/examples/components/reference/parent/components/small-charts.html +2 -0
  358. package/examples/components/reference/parent/components/split-button.html +5 -0
  359. package/examples/components/reference/parent/components/split-panes.html +8 -0
  360. package/examples/components/reference/parent/components/stat-tiles.html +2 -0
  361. package/examples/components/reference/parent/components/toast-stack.html +5 -0
  362. package/examples/components/reference/parent/components/tree-view.html +4 -0
  363. package/examples/components/reference/parent/components/upload-dropzone.html +6 -0
  364. package/examples/components/reference/parent/components/wizard-shell.html +2 -0
  365. package/examples/components/reference/parent/coverage.json +414 -0
  366. package/examples/components/reference/parent/index.html +1 -0
  367. package/examples/components/reference/parent/kanban.css +114 -0
  368. package/examples/components/reference/parent/kanban.py +55 -0
  369. package/examples/components/reference/parent/refinement.css +53 -0
  370. package/examples/components/reference/selected/build.py +70 -0
  371. package/examples/components/reference/selected/components/activity-feed.html +3 -0
  372. package/examples/components/reference/selected/components/app-toolbar.html +3 -0
  373. package/examples/components/reference/selected/components/avatar-group.html +4 -0
  374. package/examples/components/reference/selected/components/breadcrumbs.html +3 -0
  375. package/examples/components/reference/selected/components/code-block.html +9 -0
  376. package/examples/components/reference/selected/components/column-manager.html +3 -0
  377. package/examples/components/reference/selected/components/combobox.html +6 -0
  378. package/examples/components/reference/selected/components/context-hypercms.html +3 -0
  379. package/examples/components/reference/selected/components/context-quickcrop.html +4 -0
  380. package/examples/components/reference/selected/components/diff-view.html +13 -0
  381. package/examples/components/reference/selected/components/drawer.html +3 -0
  382. package/examples/components/reference/selected/components/editable-grid-cells.html +3 -0
  383. package/examples/components/reference/selected/components/filter-builder.html +3 -0
  384. package/examples/components/reference/selected/components/form-error-summary.html +4 -0
  385. package/examples/components/reference/selected/components/inline-edit.html +3 -0
  386. package/examples/components/reference/selected/components/kanban-board.html +53 -0
  387. package/examples/components/reference/selected/components/modal.html +2 -0
  388. package/examples/components/reference/selected/components/multi-select-picker.html +5 -0
  389. package/examples/components/reference/selected/components/pagination.html +3 -0
  390. package/examples/components/reference/selected/components/persistent-action-banner.html +4 -0
  391. package/examples/components/reference/selected/components/popconfirm.html +5 -0
  392. package/examples/components/reference/selected/components/popover-menu.html +3 -0
  393. package/examples/components/reference/selected/components/rich-text-toolbar.html +3 -0
  394. package/examples/components/reference/selected/components/save-status-indicator.html +2 -0
  395. package/examples/components/reference/selected/components/selectable-cards.html +3 -0
  396. package/examples/components/reference/selected/components/settings-rows.html +3 -0
  397. package/examples/components/reference/selected/components/sidebar-nav.html +4 -0
  398. package/examples/components/reference/selected/components/small-charts.html +5 -0
  399. package/examples/components/reference/selected/components/split-button.html +4 -0
  400. package/examples/components/reference/selected/components/split-panes.html +8 -0
  401. package/examples/components/reference/selected/components/stat-tiles.html +3 -0
  402. package/examples/components/reference/selected/components/toast-stack.html +5 -0
  403. package/examples/components/reference/selected/components/tree-view.html +4 -0
  404. package/examples/components/reference/selected/components/upload-dropzone.html +6 -0
  405. package/examples/components/reference/selected/components/wizard-shell.html +3 -0
  406. package/examples/components/reference/selected/corrections.css +11 -0
  407. package/examples/components/reference/selected/coverage.json +413 -0
  408. package/examples/components/reference/selected/gallery-build.mjs +49 -0
  409. package/examples/components/reference/selected/gallery-spec.json +12 -0
  410. package/examples/components/reference/selected/index.html +1 -0
  411. package/examples/components/reference/selected/kanban-comparison.json +6 -0
  412. package/examples/components/reference/selected/manifest.json +476 -0
  413. package/examples/components/reference/sha256.json +142 -0
  414. package/examples/components/reference/sources/diagonal-close.svg +1 -0
  415. package/examples/components/reference/sources/hyperclay/public-assets/css/hyperclay-app-source.css +614 -0
  416. package/examples/components/reference/sources/hyperclay/public-assets/js/app.js +94 -0
  417. package/examples/components/reference/sources/hyperclayjs/src/ui/theModal.js +897 -0
  418. package/examples/components/reference/sources/hypercms/scripts/build-theme.js +203 -0
  419. package/examples/components/reference/sources/hypercms/src/anchor.js +24 -0
  420. package/examples/components/reference/sources/hypercms/src/apply-loop.js +167 -0
  421. package/examples/components/reference/sources/hypercms/src/diagnostics.js +44 -0
  422. package/examples/components/reference/sources/hypercms/src/enhance.js +173 -0
  423. package/examples/components/reference/sources/hypercms/src/events.js +955 -0
  424. package/examples/components/reference/sources/hypercms/src/form-builder.js +518 -0
  425. package/examples/components/reference/sources/hypercms/src/form-rules.js +208 -0
  426. package/examples/components/reference/sources/hypercms/src/hooks.js +100 -0
  427. package/examples/components/reference/sources/hypercms/src/hypercms-bundle.js +10 -0
  428. package/examples/components/reference/sources/hypercms/src/hypercms.js +347 -0
  429. package/examples/components/reference/sources/hypercms/src/morph.js +10 -0
  430. package/examples/components/reference/sources/hypercms/src/path.js +61 -0
  431. package/examples/components/reference/sources/hypercms/src/place.js +131 -0
  432. package/examples/components/reference/sources/hypercms/src/platform.js +131 -0
  433. package/examples/components/reference/sources/hypercms/src/refresh.js +87 -0
  434. package/examples/components/reference/sources/hypercms/src/richclay-bridge.js +140 -0
  435. package/examples/components/reference/sources/hypercms/src/row-identity.js +111 -0
  436. package/examples/components/reference/sources/hypercms/src/scaffold.js +10 -0
  437. package/examples/components/reference/sources/hypercms/src/session.js +303 -0
  438. package/examples/components/reference/sources/hypercms/src/shell.js +248 -0
  439. package/examples/components/reference/sources/hypercms/src/styles.css +298 -0
  440. package/examples/components/reference/sources/hypercms/src/targets.js +144 -0
  441. package/examples/components/reference/sources/hypercms/src/templates.js +656 -0
  442. package/examples/components/reference/sources/hypercms/src/theme/pixel-quiet.overrides.css +761 -0
  443. package/examples/components/reference/sources/hypercms/src/theme.generated.css +1772 -0
  444. package/examples/components/reference/sources/hypercms/src/toggle.js +666 -0
  445. package/examples/components/reference/sources/hypercms/src/unresolved.js +211 -0
  446. package/examples/components/reference/sources/hypercms/src/vendor/mirk.vendor.js +212 -0
  447. package/examples/components/reference/sources/hypercms/src/views/inline-layer.js +421 -0
  448. package/examples/components/reference/sources/hypercms/src/views/inline.js +1072 -0
  449. package/examples/components/reference/sources/hypercms/src/views/sidebar.js +97 -0
  450. package/examples/components/reference/sources/modal-reference.html +301 -0
  451. package/examples/components/reference/sources/quickcrop/quickcrop.css +124 -0
  452. package/examples/components/reference/sources/quickcrop/quickcrop.js +400 -0
  453. package/examples/components/reference/sources/richclay/richclay.css +416 -0
  454. package/examples/components/reference/sources/richclay/richclay.js +2962 -0
  455. package/examples/components/reference/sources/richclay/src/a11y.js +67 -0
  456. package/examples/components/reference/sources/richclay/src/browser-global.js +7 -0
  457. package/examples/components/reference/sources/richclay/src/buttons.js +254 -0
  458. package/examples/components/reference/sources/richclay/src/hyperclay.js +470 -0
  459. package/examples/components/reference/sources/richclay/src/normalize.js +380 -0
  460. package/examples/components/reference/sources/richclay/src/richclay.js +1356 -0
  461. package/examples/components/reference/sources/richclay/src/sanitize.js +189 -0
  462. package/examples/components/reference/sources/richclay/src/styles.js +23 -0
  463. package/examples/components/reference/sources/richclay/src/toolbar-float.js +175 -0
  464. package/examples/components/reference/sources/richclay/src/toolbar.js +394 -0
  465. package/examples/components/reference/sources/richclay/src/vendor-bundle.js +15 -0
  466. package/examples/components/reference/sources/richclay/vendor/DOMPURIFY-LICENSE +202 -0
  467. package/examples/components/reference/sources/richclay/vendor/DOMPURIFY-LICENSE-MPL +373 -0
  468. package/examples/components/reference/sources/richclay/vendor/SQUIRE-LICENSE +21 -0
  469. package/examples/components/reference/sources/richclay/vendor/purify.es.mjs +2249 -0
  470. package/examples/components/reference/sources/richclay/vendor/purify.min.js +3 -0
  471. package/examples/components/reference/sources/richclay/vendor/squire.js +22 -0
  472. package/examples/components/reference/sources/richclay/vendor/squire.mjs +22 -0
  473. package/examples/components/reference/sources/richclay/vendor/squire.provenance.json +19 -0
  474. package/examples/components/rich-text-toolbar.html +43 -0
  475. package/examples/components/save-status-indicator.html +11 -0
  476. package/examples/components/selectable-cards.html +9 -0
  477. package/examples/components/settings-rows.html +10 -0
  478. package/examples/components/sidebar-nav.html +9 -0
  479. package/examples/components/small-charts.html +21 -0
  480. package/examples/components/split-button.html +13 -0
  481. package/examples/components/split-panes-editor.html +6 -0
  482. package/examples/components/split-panes.html +9 -0
  483. package/examples/components/stat-tiles.html +23 -0
  484. package/examples/components/toast-stack.html +21 -0
  485. package/examples/components/tree-view.html +9 -0
  486. package/examples/components/upload-dropzone.html +88 -0
  487. package/examples/components/wizard-shell.html +50 -0
  488. package/examples/datastar/index.html +38 -0
  489. package/examples/datastar/server.mjs +31 -0
  490. package/examples/fixtures/field-guide.pdf +14 -0
  491. package/examples/fixtures/workspace.jpg +0 -0
  492. package/examples/setup-peers.mjs +35 -0
  493. package/fonts/LICENSE-ibm-plex-mono.txt +93 -0
  494. package/fonts/LICENSE-ibm-plex-sans.txt +93 -0
  495. package/fonts/LICENSE-newsreader.txt +93 -0
  496. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  497. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  498. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  499. package/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
  500. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  501. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  502. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  503. package/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  504. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  505. package/icons/LICENSE +21 -0
  506. package/icons/house.json +71 -0
  507. package/icons/icons.d.ts +17 -0
  508. package/icons/icons.js +648 -0
  509. package/icons/icons.svg +126 -0
  510. package/icons/marks/build-tray.source.mjs +45 -0
  511. package/icons/marks/hyperclay-local.svg +11 -0
  512. package/icons/phosphor.json +53 -0
  513. package/icons/round.json +8 -0
  514. package/integrations/datastar.js +202 -0
  515. package/integrations/quickcrop.css +26 -0
  516. package/integrations/quickcrop.js +56 -0
  517. package/integrations/richclay.css +100 -0
  518. package/integrations/richclay.js +131 -0
  519. package/package.json +630 -0
  520. package/react/README.md +244 -0
  521. package/src/README.md +83 -0
  522. package/vendor/datastar/datastar-1.0.3.js +10 -0
  523. package/vendor/datastar/provenance.json +6 -0
  524. package/vendor/floating-ui/LICENSE-core +20 -0
  525. package/vendor/floating-ui/LICENSE-dom +20 -0
  526. package/vendor/floating-ui/floating-ui.core.umd.min.js +1 -0
  527. package/vendor/floating-ui/floating-ui.dom.umd.min.js +1 -0
  528. package/vendor/floating-ui/provenance.json +17 -0
  529. package/vendor/richclay/LICENSE +16 -0
  530. package/vendor/richclay/THIRD-PARTY-NOTICES.md +45 -0
  531. package/vendor/richclay/provenance.json +17 -0
  532. package/vendor/richclay/richclay.css +404 -0
  533. package/vendor/richclay/richclay.js +2940 -0
  534. package/vendor/richclay/vendor/DOMPURIFY-LICENSE +202 -0
  535. package/vendor/richclay/vendor/DOMPURIFY-LICENSE-MPL +373 -0
  536. package/vendor/richclay/vendor/SQUIRE-LICENSE +21 -0
  537. package/vendor/richclay/vendor/purify.min.js +3 -0
  538. package/vendor/richclay/vendor/squire.js +22 -0
  539. package/vendor/richclay/vendor/squire.provenance.json +30 -0
@@ -0,0 +1,2940 @@
1
+ /* richclay global build. Source lives in src/. Run npm run build to regenerate. */
2
+ var RichClayBundle = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/browser-global.js
22
+ var browser_global_exports = {};
23
+ __export(browser_global_exports, {
24
+ default: () => browser_global_default
25
+ });
26
+
27
+ // src/buttons.js
28
+ var svg = (inner, viewBox = "0 0 18 18") => `<svg viewBox="${viewBox}" width="18" height="18" aria-hidden="true" focusable="false">${inner}</svg>`;
29
+ var icons = {
30
+ bold: svg(`<path fill="currentColor" d="M178.48,115.7A44,44,0,0,0,148,40H80a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8h80a48,48,0,0,0,18.48-92.3ZM88,56h60a28,28,0,0,1,0,56H88Zm72,136H88V128h72a32,32,0,0,1,0,64Z"/>`, "0 0 256 256"),
31
+ italic: svg(`<path fill="currentColor" d="M200,56a8,8,0,0,1-8,8H157.77L115.1,192H144a8,8,0,0,1,0,16H64a8,8,0,0,1,0-16H98.23L140.9,64H112a8,8,0,0,1,0-16h80A8,8,0,0,1,200,56Z"/>`, "0 0 256 256"),
32
+ underline: svg(`<path fill="currentColor" d="M200,224a8,8,0,0,1-8,8H64a8,8,0,0,1,0-16H192A8,8,0,0,1,200,224Zm-72-24a64.07,64.07,0,0,0,64-64V56a8,8,0,0,0-16,0v80a48,48,0,0,1-96,0V56a8,8,0,0,0-16,0v80A64.07,64.07,0,0,0,128,200Z"/>`, "0 0 256 256"),
33
+ strike: svg(`<path fill="currentColor" d="M224,128a8,8,0,0,1-8,8H175.93c9.19,7.11,16.07,17.2,16.07,32,0,13.34-7,25.7-19.75,34.79C160.33,211.31,144.61,216,128,216s-32.33-4.69-44.25-13.21C71,193.7,64,181.34,64,168a8,8,0,0,1,16,0c0,17.35,22,32,48,32s48-14.65,48-32c0-14.85-10.54-23.58-38.77-32H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM76.33,104a8,8,0,0,0,7.61-10.49A17.3,17.3,0,0,1,83.11,88c0-18.24,19.3-32,44.89-32,18.84,0,34.16,7.42,41,19.85a8,8,0,0,0,14-7.7C173.33,50.52,152.77,40,128,40,93.29,40,67.11,60.63,67.11,88a33.73,33.73,0,0,0,1.62,10.49A8,8,0,0,0,76.33,104Z"/>`, "0 0 256 256"),
34
+ link: svg(`<path fill="currentColor" d="M240,88.23a54.43,54.43,0,0,1-16,37L189.25,160a54.27,54.27,0,0,1-38.63,16h-.05A54.63,54.63,0,0,1,96,119.84a8,8,0,0,1,16,.45A38.62,38.62,0,0,0,150.58,160h0a38.39,38.39,0,0,0,27.31-11.31l34.75-34.75a38.63,38.63,0,0,0-54.63-54.63l-11,11A8,8,0,0,1,135.7,59l11-11A54.65,54.65,0,0,1,224,48,54.86,54.86,0,0,1,240,88.23ZM109,185.66l-11,11A38.41,38.41,0,0,1,70.6,208h0a38.63,38.63,0,0,1-27.29-65.94L78,107.31A38.63,38.63,0,0,1,144,135.71a8,8,0,0,0,16,.45A54.86,54.86,0,0,0,144,96a54.65,54.65,0,0,0-77.27,0L32,130.75A54.62,54.62,0,0,0,70.56,224h0a54.28,54.28,0,0,0,38.64-16l11-11A8,8,0,0,0,109,185.66Z"/>`, "0 0 256 256"),
35
+ unlink: svg(`<path fill="currentColor" d="M198.63,57.37a32,32,0,0,0-45.19-.06L141.79,69.52a8,8,0,0,1-11.58-11l11.72-12.29a1.59,1.59,0,0,1,.13-.13,48,48,0,0,1,67.88,67.88,1.59,1.59,0,0,1-.13.13l-12.29,11.72a8,8,0,0,1-11-11.58l12.21-11.65A32,32,0,0,0,198.63,57.37ZM114.21,186.48l-11.65,12.21a32,32,0,0,1-45.25-45.25l12.21-11.65a8,8,0,0,0-11-11.58L46.19,141.93a1.59,1.59,0,0,0-.13.13,48,48,0,0,0,67.88,67.88,1.59,1.59,0,0,0,.13-.13l11.72-12.29a8,8,0,1,0-11.58-11ZM216,152H192a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16ZM40,104H64a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm120,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V192A8,8,0,0,0,160,184ZM96,72a8,8,0,0,0,8-8V40a8,8,0,0,0-16,0V64A8,8,0,0,0,96,72Z"/>`, "0 0 256 256"),
36
+ ul: svg(`<path fill="currentColor" d="M80,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H88A8,8,0,0,1,80,64Zm136,56H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H88a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM44,52A12,12,0,1,0,56,64,12,12,0,0,0,44,52Zm0,64a12,12,0,1,0,12,12A12,12,0,0,0,44,116Zm0,64a12,12,0,1,0,12,12A12,12,0,0,0,44,180Z"/>`, "0 0 256 256"),
37
+ ol: svg(`<path fill="currentColor" d="M224,128a8,8,0,0,1-8,8H104a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM104,72H216a8,8,0,0,0,0-16H104a8,8,0,0,0,0,16ZM216,184H104a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM43.58,55.16,48,52.94V104a8,8,0,0,0,16,0V40a8,8,0,0,0-11.58-7.16l-16,8a8,8,0,0,0,7.16,14.32ZM79.77,156.72a23.73,23.73,0,0,0-9.6-15.95,24.86,24.86,0,0,0-34.11,4.7,23.63,23.63,0,0,0-3.57,6.46,8,8,0,1,0,15,5.47,7.84,7.84,0,0,1,1.18-2.13,8.76,8.76,0,0,1,12-1.59A7.91,7.91,0,0,1,63.93,159a7.64,7.64,0,0,1-1.57,5.78,1,1,0,0,0-.08.11L33.59,203.21A8,8,0,0,0,40,216H72a8,8,0,0,0,0-16H56l19.08-25.53A23.47,23.47,0,0,0,79.77,156.72Z"/>`, "0 0 256 256"),
38
+ quote: svg(`<path fill="currentColor" d="M100,56H40A16,16,0,0,0,24,72v64a16,16,0,0,0,16,16h60v8a32,32,0,0,1-32,32,8,8,0,0,0,0,16,48.05,48.05,0,0,0,48-48V72A16,16,0,0,0,100,56Zm0,80H40V72h60ZM216,56H156a16,16,0,0,0-16,16v64a16,16,0,0,0,16,16h60v8a32,32,0,0,1-32,32,8,8,0,0,0,0,16,48.05,48.05,0,0,0,48-48V72A16,16,0,0,0,216,56Zm0,80H156V72h60Z"/>`, "0 0 256 256"),
39
+ undo: svg(`<path fill="currentColor" d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z"/>`, "0 0 256 256"),
40
+ redo: svg(`<path fill="currentColor" d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z"/>`, "0 0 256 256"),
41
+ clear: svg(`<path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="square" stroke-linejoin="miter" d="M3 3l18 18M5 5v3M9 5h10v3M12 5v3M12 12v7M8 19h8"/>`, "0 0 24 24"),
42
+ code: svg(`<path fill="currentColor" d="M69.12,94.15,28.5,128l40.62,33.85a8,8,0,1,1-10.24,12.29l-48-40a8,8,0,0,1,0-12.29l48-40a8,8,0,0,1,10.24,12.3Zm176,27.7-48-40a8,8,0,1,0-10.24,12.3L227.5,128l-40.62,33.85a8,8,0,1,0,10.24,12.29l48-40a8,8,0,0,0,0-12.29ZM162.73,32.48a8,8,0,0,0-10.25,4.79l-64,176a8,8,0,0,0,4.79,10.26A8.14,8.14,0,0,0,96,224a8,8,0,0,0,7.52-5.27l64-176A8,8,0,0,0,162.73,32.48Z"/>`, "0 0 256 256"),
43
+ indent: svg(`<path fill="currentColor" d="M224,128a8,8,0,0,1-8,8H112a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM112,72H216a8,8,0,0,0,0-16H112a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM34.34,141.66a8,8,0,0,0,11.32,0l40-40a8,8,0,0,0,0-11.32l-40-40A8,8,0,0,0,34.34,61.66L68.69,96,34.34,130.34A8,8,0,0,0,34.34,141.66Z"/>`, "0 0 256 256"),
44
+ outdent: svg(`<path fill="currentColor" d="M224,128a8,8,0,0,1-8,8H112a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM112,72H216a8,8,0,0,0,0-16H112a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM72,144a8,8,0,0,0,5.66-13.66L43.31,96,77.66,61.66A8,8,0,0,0,66.34,50.34l-40,40a8,8,0,0,0,0,11.32l40,40A8,8,0,0,0,72,144Z"/>`, "0 0 256 256"),
45
+ blocks: svg(`<path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="square" stroke-linejoin="miter" d="M19 4h-9a4.5 4.5 0 0 0 0 9h3M13 4v16M17 4v16"/>`, "0 0 24 24"),
46
+ h1: svg(`<path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M3.5 5.5v12M12 5.5v12M3.5 11.5H12M17 12l3-2v10"/>`, "0 0 24 24"),
47
+ h2: svg(`<path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M3.5 5.5v12M12 5.5v12M3.5 11.5H12M16 12.5c0-3.5 6-3.5 6 0 0 2-2.5 3.5-6 7.5h6"/>`, "0 0 24 24"),
48
+ h3: svg(`<path fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M3.5 5.5v12M12 5.5v12M3.5 11.5H12M16 11c1.5-1.5 6-1.5 6 1.5 0 1.5-1.5 2.5-3 2.5h-1M19 15c1.5 0 3 1 3 2.5 0 3-4.5 3-6 1.5"/>`, "0 0 24 24")
49
+ };
50
+ var blocksOnly = (editor) => editor.blocksStayOut();
51
+ var notInsideLink = (editor) => Boolean(editor.element.closest("a"));
52
+ var presets = {
53
+ minimal: ["bold", "italic", "link", "unorderedList"],
54
+ inline: [
55
+ "bold",
56
+ "italic",
57
+ "underline",
58
+ "strikethrough",
59
+ "code",
60
+ "link",
61
+ "undo",
62
+ "redo",
63
+ "clearFormatting"
64
+ ],
65
+ standard: [
66
+ "blockMenu",
67
+ "bold",
68
+ "italic",
69
+ "underline",
70
+ "strikethrough",
71
+ "code",
72
+ "link",
73
+ "unorderedList",
74
+ "orderedList",
75
+ "quote",
76
+ "outdent",
77
+ "indent",
78
+ "undo",
79
+ "redo",
80
+ "clearFormatting"
81
+ ]
82
+ };
83
+ function createDefaultRegistry() {
84
+ return new Map(defaultButtons.map((button) => [button.id, button]));
85
+ }
86
+ function isApplePlatform(win = globalThis) {
87
+ const nav = win?.navigator;
88
+ if (!nav) return false;
89
+ return /Mac|iPhone|iPad|iPod/.test(nav.platform || nav.userAgent || "");
90
+ }
91
+ function formatShortcut(shortcut, win = globalThis) {
92
+ if (!shortcut) return "";
93
+ return shortcut.replace("Mod", isApplePlatform(win) ? "Cmd" : "Ctrl");
94
+ }
95
+ var defaultButtons = [
96
+ toggle("bold", "Bold", icons.bold, "Mod+B", "inline", (editor) => editor.toggleFormat("B", "bold", "removeBold")),
97
+ toggle(
98
+ "italic",
99
+ "Italic",
100
+ icons.italic,
101
+ "Mod+I",
102
+ "inline",
103
+ (editor) => editor.toggleFormat("I", "italic", "removeItalic")
104
+ ),
105
+ toggle(
106
+ "underline",
107
+ "Underline",
108
+ icons.underline,
109
+ "Mod+U",
110
+ "inline",
111
+ (editor) => editor.toggleFormat("U", "underline", "removeUnderline")
112
+ ),
113
+ toggle(
114
+ "strikethrough",
115
+ "Strikethrough",
116
+ icons.strike,
117
+ "Mod+Shift+7",
118
+ "inline",
119
+ (editor) => editor.toggleFormat("S", "strikethrough", "removeStrikethrough")
120
+ ),
121
+ // No shortcut. One keystroke turning a paragraph of prose into a code block is
122
+ // a sharp edge in an editor whose DOM is the saved file, and Mod+D also shadows
123
+ // the browser's own bookmark shortcut. The toolbar button stays.
124
+ toggle(
125
+ "code",
126
+ "Code",
127
+ icons.code,
128
+ null,
129
+ "inline",
130
+ (editor) => editor.toggleCode(),
131
+ (editor) => editor.selectionHasFormat("CODE") || editor.selectionHasFormat("PRE")
132
+ ),
133
+ {
134
+ id: "link",
135
+ label: "Link",
136
+ ariaLabel: "Insert or edit link",
137
+ icon: icons.link,
138
+ group: "links",
139
+ shortcut: "Mod+K",
140
+ mutates: false,
141
+ isDisabled: notInsideLink,
142
+ run: (editor) => editor.openLinkDialog(),
143
+ isActive: (editor) => Boolean(editor.currentLinkElement())
144
+ },
145
+ {
146
+ id: "unlink",
147
+ label: "Remove link",
148
+ ariaLabel: "Remove link",
149
+ icon: icons.unlink,
150
+ group: "links",
151
+ run: (editor) => editor.squire.removeLink()
152
+ },
153
+ toggle(
154
+ "unorderedList",
155
+ "Bulleted list",
156
+ icons.ul,
157
+ "Mod+Shift+8",
158
+ "lists",
159
+ (editor) => editor.toggleList("UL"),
160
+ (editor) => editor.pathHas("UL"),
161
+ blocksOnly
162
+ ),
163
+ toggle(
164
+ "orderedList",
165
+ "Numbered list",
166
+ icons.ol,
167
+ "Mod+Shift+9",
168
+ "lists",
169
+ (editor) => editor.toggleList("OL"),
170
+ (editor) => editor.pathHas("OL"),
171
+ blocksOnly
172
+ ),
173
+ toggle("quote", "Quote", icons.quote, null, "blocks", (editor) => {
174
+ if (editor.pathHas("BLOCKQUOTE")) return editor.squire.decreaseQuoteLevel();
175
+ return editor.squire.increaseQuoteLevel();
176
+ }, (editor) => editor.pathHas("BLOCKQUOTE"), blocksOnly),
177
+ {
178
+ id: "outdent",
179
+ label: "Outdent",
180
+ ariaLabel: "Decrease indent",
181
+ icon: icons.outdent,
182
+ group: "blocks",
183
+ shortcut: "Mod+[",
184
+ isDisabled: blocksOnly,
185
+ run: (editor) => editor.outdent()
186
+ },
187
+ {
188
+ id: "indent",
189
+ label: "Indent",
190
+ ariaLabel: "Increase indent",
191
+ icon: icons.indent,
192
+ group: "blocks",
193
+ shortcut: "Mod+]",
194
+ isDisabled: blocksOnly,
195
+ run: (editor) => editor.indent()
196
+ },
197
+ {
198
+ id: "undo",
199
+ label: "Undo",
200
+ ariaLabel: "Undo",
201
+ icon: icons.undo,
202
+ group: "history",
203
+ shortcut: "Mod+Z",
204
+ mutates: false,
205
+ run: (editor) => editor.squire.undo()
206
+ },
207
+ {
208
+ id: "redo",
209
+ label: "Redo",
210
+ ariaLabel: "Redo",
211
+ icon: icons.redo,
212
+ group: "history",
213
+ shortcut: "Mod+Shift+Z",
214
+ mutates: false,
215
+ run: (editor) => editor.squire.redo()
216
+ },
217
+ {
218
+ id: "clearFormatting",
219
+ label: "Clear formatting",
220
+ ariaLabel: "Clear formatting",
221
+ icon: icons.clear,
222
+ group: "cleanup",
223
+ run: (editor) => editor.squire.removeAllFormatting()
224
+ },
225
+ {
226
+ id: "blockMenu",
227
+ type: "menu",
228
+ label: "Block style",
229
+ ariaLabel: "Choose block style",
230
+ icon: icons.blocks,
231
+ group: "blocks",
232
+ isDisabled: blocksOnly,
233
+ options: [
234
+ blockOption("Paragraph", "P", icons.blocks),
235
+ blockOption("Heading 1", "H1", icons.h1),
236
+ blockOption("Heading 2", "H2", icons.h2),
237
+ blockOption("Heading 3", "H3", icons.h3),
238
+ {
239
+ label: "Quote",
240
+ icon: icons.quote,
241
+ value: "BLOCKQUOTE",
242
+ isDisabled: blocksOnly,
243
+ run: (editor) => editor.squire.increaseQuoteLevel(),
244
+ isActive: (editor) => editor.pathHas("BLOCKQUOTE")
245
+ },
246
+ {
247
+ label: "Code block",
248
+ icon: icons.code,
249
+ value: "PRE",
250
+ isDisabled: blocksOnly,
251
+ run: (editor) => editor.setBlockType("PRE"),
252
+ isActive: (editor) => editor.pathHas("PRE")
253
+ }
254
+ ]
255
+ }
256
+ ];
257
+ function toggle(id, label, icon, shortcut, group, run, isActive = (editor) => editor.selectionHasFormat(labelTag(id)), isDisabled) {
258
+ return {
259
+ id,
260
+ label,
261
+ ariaLabel: label,
262
+ icon,
263
+ group,
264
+ shortcut,
265
+ isDisabled,
266
+ run,
267
+ isActive
268
+ };
269
+ }
270
+ function labelTag(id) {
271
+ return {
272
+ bold: "B",
273
+ italic: "I",
274
+ underline: "U",
275
+ strikethrough: "S"
276
+ }[id];
277
+ }
278
+ function blockOption(label, tag, icon) {
279
+ return {
280
+ label,
281
+ icon,
282
+ value: tag,
283
+ isDisabled: blocksOnly,
284
+ run: (editor) => editor.setBlockType(tag),
285
+ isActive: (editor) => editor.pathHas(tag)
286
+ };
287
+ }
288
+
289
+ // src/normalize.js
290
+ var INLINE_NODE_NAMES = /^(?:#text|A(?:BBR|CRONYM)?|B(?:R|D[IO])?|C(?:ITE|ODE)|D(?:ATA|EL|FN)|EM|FONT|HR|I(?:FRAME|MG|NPUT|NS)?|KBD|Q|R(?:P|T|UBY)|S(?:AMP|MALL|PAN|TR(?:IKE|ONG)|U[BP])?|TIME|U|VAR|WBR)$/;
291
+ var NOT_WHITESPACE = /[^ \t\r\n]/;
292
+ var ELEMENT_NODE = 1;
293
+ var TEXT_NODE = 3;
294
+ var COMMENT_NODE = 8;
295
+ var LEAF_NODE_NAMES = /* @__PURE__ */ new Set(["BR", "HR", "IFRAME", "IMG", "INPUT", "WBR"]);
296
+ var isEditorUi = (node) => node?.nodeType === ELEMENT_NODE && node.matches('[editor-ui],[clay~="editor-ui"]');
297
+ var FOREIGN_INLINE_ROOTS = /* @__PURE__ */ new Set(["svg", "math"]);
298
+ function isInlineNode(node) {
299
+ const type = node.nodeType;
300
+ if (type === TEXT_NODE || type === COMMENT_NODE) return true;
301
+ if (type !== ELEMENT_NODE) return false;
302
+ if (FOREIGN_INLINE_ROOTS.has(node.nodeName)) return true;
303
+ if (!INLINE_NODE_NAMES.test(node.nodeName)) return false;
304
+ return Array.from(node.childNodes).every(isInlineNode);
305
+ }
306
+ function isBlockContainer(element) {
307
+ return Array.from(element.childNodes).some((node) => !isInlineNode(node));
308
+ }
309
+ var isBlockElement = (node) => node?.nodeType === ELEMENT_NODE && !FOREIGN_INLINE_ROOTS.has(node.nodeName) && !INLINE_NODE_NAMES.test(node.nodeName);
310
+ function flattenBlocks(parent, createBoundary) {
311
+ const children = Array.from(parent.childNodes);
312
+ let removed = 0;
313
+ children.forEach((child, index) => {
314
+ if (child.nodeType !== ELEMENT_NODE || FOREIGN_INLINE_ROOTS.has(child.nodeName)) return;
315
+ removed += flattenBlocks(child, createBoundary);
316
+ if (!isBlockElement(child)) return;
317
+ if (isBlockElement(children[index - 1])) parent.insertBefore(createBoundary(), child);
318
+ while (child.firstChild) parent.insertBefore(child.firstChild, child);
319
+ child.remove();
320
+ removed += 1;
321
+ });
322
+ return removed;
323
+ }
324
+ var TABLE_STRUCTURE = /* @__PURE__ */ new Set(["TABLE", "THEAD", "TBODY", "TFOOT", "TR", "COLGROUP"]);
325
+ function ejectsBlocks(root) {
326
+ return Boolean(root.closest?.("p")) || TABLE_STRUCTURE.has(root.nodeName);
327
+ }
328
+ var TEXT_LINE_ROOTS = /* @__PURE__ */ new Set([
329
+ "P",
330
+ "H1",
331
+ "H2",
332
+ "H3",
333
+ "H4",
334
+ "H5",
335
+ "H6",
336
+ "PRE",
337
+ "LABEL",
338
+ "LEGEND",
339
+ "LI",
340
+ "TD",
341
+ "TH",
342
+ "DT",
343
+ "DD",
344
+ "FIGCAPTION",
345
+ "SUMMARY",
346
+ "CAPTION"
347
+ ]);
348
+ function keepsTextShape(root) {
349
+ return TEXT_LINE_ROOTS.has(root.nodeName) || isInlineTag(root);
350
+ }
351
+ var VOID_ROOTS = /* @__PURE__ */ new Set([
352
+ "AREA",
353
+ "BASE",
354
+ "BR",
355
+ "COL",
356
+ "EMBED",
357
+ "HR",
358
+ "IMG",
359
+ "INPUT",
360
+ "LINK",
361
+ "META",
362
+ "PARAM",
363
+ "SOURCE",
364
+ "TRACK",
365
+ "WBR"
366
+ ]);
367
+ var RAW_TEXT_ROOTS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEXTAREA", "TITLE", "IFRAME", "NOSCRIPT", "XMP"]);
368
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
369
+ function isUnsupportedRootTag(root) {
370
+ return TABLE_STRUCTURE.has(root.nodeName) || VOID_ROOTS.has(root.nodeName) || RAW_TEXT_ROOTS.has(root.nodeName) || root.nodeName === "TEMPLATE" || Boolean(root.namespaceURI) && root.namespaceURI !== HTML_NS;
371
+ }
372
+ function unsupportedRootReason(root) {
373
+ if (TABLE_STRUCTURE.has(root.nodeName)) {
374
+ return "a table element cannot be an editable region. Move the editable attribute to a <td>, a <th>, or an element inside one.";
375
+ }
376
+ if (root.namespaceURI && root.namespaceURI !== HTML_NS) {
377
+ return "an SVG or MathML element cannot be an editable region: an edit puts an HTML block inside it, and the next page load moves that text out of the graphic entirely. Put a <foreignObject> in the SVG and mark an HTML element inside it instead.";
378
+ }
379
+ if (VOID_ROOTS.has(root.nodeName)) {
380
+ return "this element cannot contain anything, so there is nothing to edit. Move the editable attribute to an element that holds text.";
381
+ }
382
+ if (root.nodeName === "TEMPLATE") {
383
+ return "a <template> keeps its content out of the document, so edits to it are never saved.";
384
+ }
385
+ return "this element treats its content as plain text, so any edit would be saved as visible markup rather than formatting.";
386
+ }
387
+ function isInlineTag(node) {
388
+ return node.nodeType === ELEMENT_NODE && !FOREIGN_INLINE_ROOTS.has(node.nodeName) && INLINE_NODE_NAMES.test(node.nodeName);
389
+ }
390
+ function hasBlockDescendant(root) {
391
+ return Array.from(root.querySelectorAll("*")).some(isBlockElement);
392
+ }
393
+ function normalizeEditorRoot(root, options = {}) {
394
+ const {
395
+ blockTag = "DIV",
396
+ wrapBareRoot = false,
397
+ onBareRootWrapped,
398
+ blocksAllowed = !ejectsBlocks(root)
399
+ } = options;
400
+ dropFormattingWhitespace(root);
401
+ if (blocksAllowed) {
402
+ wrapStrayInlineChildren(root, blockTag, wrapBareRoot, onBareRootWrapped);
403
+ }
404
+ return root;
405
+ }
406
+ var needsWrapping = (node) => isInlineNode(node) && node.nodeType !== COMMENT_NODE && !isEditorUi(node);
407
+ var isAuthorContent = (node) => node.nodeType !== COMMENT_NODE && !isEditorUi(node) && !(node.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(node.nodeValue));
408
+ function editorRootNeedsNormalization(root, { wrapBareRoot = false, blocksAllowed = !ejectsBlocks(root) } = {}) {
409
+ if (!blocksAllowed) return false;
410
+ if (!isBlockContainer(root)) return wrapBareRoot;
411
+ return Array.from(root.childNodes).some(needsWrapping) || hasNestedFormattingWhitespace(root);
412
+ }
413
+ function hasNestedFormattingWhitespace(element) {
414
+ return Array.from(element.children).some((child) => {
415
+ if (!isBlockContainer(child)) return false;
416
+ const ownWhitespace = !preservesWhitespace(child) && Array.from(child.childNodes).some(
417
+ (node) => node.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(node.nodeValue)
418
+ );
419
+ return ownWhitespace || hasNestedFormattingWhitespace(child);
420
+ });
421
+ }
422
+ function dropFormattingWhitespace(element) {
423
+ if (!isBlockContainer(element)) return;
424
+ if (!preservesWhitespace(element)) {
425
+ Array.from(element.childNodes).forEach((child) => {
426
+ if (child.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(child.nodeValue)) child.remove();
427
+ });
428
+ }
429
+ Array.from(element.children).forEach(dropFormattingWhitespace);
430
+ }
431
+ function wrapStrayInlineChildren(root, blockTag, wrapBareRoot, onBareRootWrapped) {
432
+ if (!isBlockContainer(root)) {
433
+ if (!wrapBareRoot) return;
434
+ const wrapper = root.ownerDocument.createElement(blockTag);
435
+ if (!Array.from(root.childNodes).some(isAuthorContent)) {
436
+ wrapper.appendChild(root.ownerDocument.createElement("BR"));
437
+ root.appendChild(wrapper);
438
+ return;
439
+ }
440
+ while (root.firstChild) wrapper.appendChild(root.firstChild);
441
+ root.appendChild(wrapper);
442
+ onBareRootWrapped?.(root, wrapper);
443
+ return;
444
+ }
445
+ let run = [];
446
+ const flush = () => {
447
+ const nodes = run;
448
+ run = [];
449
+ if (!nodes.some((node) => node.nodeType !== COMMENT_NODE)) return;
450
+ const block = root.ownerDocument.createElement(blockTag);
451
+ root.insertBefore(block, nodes[0]);
452
+ nodes.forEach((node) => block.appendChild(node));
453
+ };
454
+ Array.from(root.childNodes).forEach((child) => {
455
+ if (isEditorUi(child)) flush();
456
+ else if (isInlineNode(child)) run.push(child);
457
+ else flush();
458
+ });
459
+ flush();
460
+ }
461
+ function preservesWhitespace(element) {
462
+ return element.nodeName === "PRE";
463
+ }
464
+ function captureRange(root, range) {
465
+ return {
466
+ start: captureBoundary(root, range.startContainer, range.startOffset),
467
+ end: captureBoundary(root, range.endContainer, range.endOffset),
468
+ collapsed: range.collapsed
469
+ };
470
+ }
471
+ function restoreRange(root, range, saved) {
472
+ applyBoundary(range, "setStart", root, saved.start);
473
+ if (saved.collapsed) range.collapse(true);
474
+ else applyBoundary(range, "setEnd", root, saved.end);
475
+ }
476
+ function captureBoundary(root, container, offset) {
477
+ if (!root.contains(container)) return { scope: root, after: [], before: [] };
478
+ if (container.nodeType === TEXT_NODE) {
479
+ if (!isDroppedWhitespace(container)) return { container, offset };
480
+ const scope = container.parentNode;
481
+ return siblingAnchor(scope, Array.from(scope.childNodes).indexOf(container));
482
+ }
483
+ return {
484
+ container,
485
+ offset,
486
+ childCount: container.childNodes.length,
487
+ ...siblingAnchor(container, offset)
488
+ };
489
+ }
490
+ function siblingAnchor(scope, index) {
491
+ const children = Array.from(scope.childNodes);
492
+ const at = Math.max(0, index);
493
+ return { scope, after: children.slice(at), before: children.slice(0, at).reverse() };
494
+ }
495
+ function applyBoundary(range, method, root, boundary) {
496
+ const { container, childCount } = boundary;
497
+ if (container && root.contains(container)) {
498
+ if (childCount === void 0 || childCount === container.childNodes.length) {
499
+ range[method](container, Math.min(boundary.offset, nodeLength(container)));
500
+ return;
501
+ }
502
+ }
503
+ const after = boundary.after?.find((node) => root.contains(node));
504
+ if (after) {
505
+ range[method](caretEdge(after, true), 0);
506
+ return;
507
+ }
508
+ const before = boundary.before?.find((node) => root.contains(node));
509
+ if (before) {
510
+ const edge = caretEdge(before, false);
511
+ range[method](edge, nodeLength(edge));
512
+ return;
513
+ }
514
+ const scope = boundary.scope && root.contains(boundary.scope) ? boundary.scope : root;
515
+ range[method](scope, 0);
516
+ }
517
+ function isDroppedWhitespace(node) {
518
+ return node.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(node.nodeValue) && Boolean(node.parentNode) && node.parentNode.nodeType === ELEMENT_NODE && isBlockContainer(node.parentNode) && !preservesWhitespace(node.parentNode);
519
+ }
520
+ var isCaretHost = (node) => node.nodeType === ELEMENT_NODE || node.nodeType === TEXT_NODE;
521
+ function caretEdge(node, first = true) {
522
+ let current = node;
523
+ while (current.nodeType === ELEMENT_NODE && !FOREIGN_INLINE_ROOTS.has(current.nodeName)) {
524
+ const children = Array.from(current.childNodes).filter(isCaretHost);
525
+ const next = first ? children[0] : children[children.length - 1];
526
+ if (!next || LEAF_NODE_NAMES.has(next.nodeName)) break;
527
+ current = next;
528
+ }
529
+ return current;
530
+ }
531
+ function nodeLength(node) {
532
+ return node.nodeType === TEXT_NODE ? node.nodeValue.length : node.childNodes.length;
533
+ }
534
+
535
+ // src/hyperclay.js
536
+ var RICHCLAY_SELECTOR = "[data-richclay], [richclay], [editable], [clay-editable]";
537
+ var RICHCLAY_OPT_IN = "[data-richclay], [richclay], [clay-editable]";
538
+ var EDITABLE_ATTRS = ["editable", "clay-editable"];
539
+ var singleLineSelector = ':is([editable~="single-line"], [clay-editable~="single-line"])';
540
+ function isRichClayHost(el) {
541
+ if (!el || typeof el.matches !== "function") return false;
542
+ if (!el.tagName.includes("-")) return true;
543
+ return el.matches(RICHCLAY_OPT_IN);
544
+ }
545
+ var CHROME_SELECTOR = "[data-richclay-toolbar], [data-richclay-menu], [data-richclay-dialog], [data-richclay-live], [data-richclay-float]";
546
+ var runtimeClasses = [
547
+ "richclay-editor",
548
+ "richclay-inline",
549
+ "richclay-active",
550
+ "richclay-empty",
551
+ "richclay-focused"
552
+ ];
553
+ var installedWindows = /* @__PURE__ */ new WeakSet();
554
+ var armedWindows = /* @__PURE__ */ new WeakSet();
555
+ var PLATFORM_READY = ["clay:ready", "hyperclay:ready"];
556
+ function platformReadyPromise(win) {
557
+ return [win.clay?.ready, win.hyperclay?.ready].find(
558
+ (ready) => ready && typeof ready.then === "function"
559
+ ) || null;
560
+ }
561
+ function platformEditMode(win) {
562
+ if (typeof win.clay?.isEditMode === "boolean") return win.clay.isEditMode;
563
+ if (typeof win.hyperclay?.isEditMode === "boolean") return win.hyperclay.isEditMode;
564
+ return null;
565
+ }
566
+ function hasPlatformLifecycle(win) {
567
+ const modules = win.hyperclayModules;
568
+ const legacyHyperclayLoader = win.__hyperclayNoAutoExport === false && modules && typeof modules === "object" && typeof modules.nodeType !== "number";
569
+ return platformEditMode(win) !== null || Boolean(platformReadyPromise(win)) || Boolean(legacyHyperclayLoader);
570
+ }
571
+ function platformDocumentTransform(win) {
572
+ return win.clay?.addDocumentTransform || win.hyperclay?.beforeSave || null;
573
+ }
574
+ function platformSnapshotTransform(win) {
575
+ return win.clay?.onSnapshot || win.hyperclay?.onSnapshot || null;
576
+ }
577
+ var PRE_CONTAINMENT = {
578
+ boxSizing: "border-box",
579
+ minWidth: "100%",
580
+ overflow: "auto",
581
+ width: "0"
582
+ };
583
+ function shouldUseHyperclay(options = {}, win = window) {
584
+ if (options.hyperclay === false) return false;
585
+ if (options.hyperclay === true) return true;
586
+ return Boolean(hasPlatformLifecycle(win) || hasEditmodeSignal(win));
587
+ }
588
+ function isHyperclayEditMode(win = window) {
589
+ const fromQuery = readEditmodeParam(win);
590
+ if (fromQuery !== null) return fromQuery;
591
+ if (typeof win.__hyperclayEditMode === "boolean") {
592
+ return win.__hyperclayEditMode;
593
+ }
594
+ const fromPlatform = platformEditMode(win);
595
+ if (fromPlatform !== null) return fromPlatform;
596
+ return readEditmodeCookie(win);
597
+ }
598
+ function shouldActivateEditor(options = {}, win = window) {
599
+ if (options.readOnly) return false;
600
+ if (!shouldUseHyperclay(options, win)) return true;
601
+ return isHyperclayEditMode(win);
602
+ }
603
+ function parseEditableOptions(element) {
604
+ const name = EDITABLE_ATTRS.find((attr) => element.hasAttribute(attr));
605
+ if (!name) return null;
606
+ const tokens = new Set(
607
+ (element.getAttribute(name) || "").trim().split(/\s+/).filter(Boolean)
608
+ );
609
+ const options = {
610
+ inline: true,
611
+ singleLine: tokens.has("single-line"),
612
+ toolbarOnSelect: tokens.has("toolbar-on-select")
613
+ };
614
+ if (tokens.has("no-toolbar")) options.toolbar = false;
615
+ return options;
616
+ }
617
+ function installHyperclayBridge(win = window) {
618
+ if (installedWindows.has(win)) return;
619
+ const register = platformSnapshotTransform(win) || platformDocumentTransform(win);
620
+ if (typeof register !== "function") {
621
+ armBridgeRetry(win);
622
+ return;
623
+ }
624
+ register((docElem) => stripRichClayFromClone(docElem));
625
+ installedWindows.add(win);
626
+ }
627
+ function armBridgeRetry(win) {
628
+ if (armedWindows.has(win)) return;
629
+ const doc = win.document;
630
+ const ready = platformReadyPromise(win);
631
+ if (!doc && !ready) return;
632
+ armedWindows.add(win);
633
+ const retry = () => {
634
+ installHyperclayBridge(win);
635
+ if (installedWindows.has(win)) disarm();
636
+ };
637
+ const disarm = () => {
638
+ if (!doc) return;
639
+ PLATFORM_READY.forEach((name) => doc.removeEventListener(name, retry));
640
+ };
641
+ if (doc) PLATFORM_READY.forEach((name) => doc.addEventListener(name, retry));
642
+ if (ready) ready.then(retry, () => {
643
+ });
644
+ }
645
+ function stripRichClayFromClone(docElem) {
646
+ docElem.querySelectorAll?.(CHROME_SELECTOR).forEach((node) => node.remove());
647
+ docElem.querySelectorAll?.(RICHCLAY_SELECTOR).forEach((region) => {
648
+ if (region.getAttribute("data-richclay-active") !== "true") return;
649
+ removeRuntimeState(region, "save");
650
+ if (needsFlattening(region)) {
651
+ const doc = region.ownerDocument;
652
+ const singleLine = Boolean(region.matches?.(singleLineSelector));
653
+ flattenBlocks(
654
+ region,
655
+ () => singleLine ? doc.createTextNode(" ") : doc.createElement("br")
656
+ );
657
+ region.querySelectorAll(region.localName).forEach((nested) => {
658
+ while (nested.firstChild) nested.parentNode.insertBefore(nested.firstChild, nested);
659
+ nested.remove();
660
+ });
661
+ }
662
+ if (region.matches?.(singleLineSelector)) unwrapLoneSingleLineBlock(region);
663
+ region.querySelectorAll("#squire-selection-start, #squire-selection-end").forEach((node) => {
664
+ node.remove();
665
+ });
666
+ region.querySelectorAll(".squire-image-resize-container").forEach((node) => node.remove());
667
+ stripZeroWidthArtifacts(region);
668
+ const codeBlocks = (region.matches?.("pre") ? [region, ...region.querySelectorAll("pre")] : Array.from(region.querySelectorAll("pre"))).filter((pre) => pre.hasAttribute("data-richclay-pre"));
669
+ codeBlocks.forEach((pre) => {
670
+ Object.entries(PRE_CONTAINMENT).forEach(([property, value]) => {
671
+ if (!pre.style[property]) pre.style[property] = value;
672
+ });
673
+ pre.removeAttribute("data-richclay-pre");
674
+ });
675
+ });
676
+ docElem.querySelectorAll?.("#squire-selection-start, #squire-selection-end").forEach((node) => node.remove());
677
+ }
678
+ var BLOCK_SCOPE = "p, td, th, li, dl, dt, dd, table, blockquote, div, section, article, aside, main, header, footer, figure, figcaption";
679
+ var MEASURE_ATTR = "data-richclay-measure";
680
+ function ejectsOnReload(region) {
681
+ const doc = region.ownerDocument;
682
+ const scope = region.closest(BLOCK_SCOPE) || region;
683
+ region.setAttribute(MEASURE_ATTR, "");
684
+ const probe = doc.createElement("div");
685
+ try {
686
+ probe.innerHTML = scope.outerHTML;
687
+ } finally {
688
+ region.removeAttribute(MEASURE_ATTR);
689
+ }
690
+ const reparsed = probe.querySelector(`[${MEASURE_ATTR}]`);
691
+ return !reparsed || reparsed.textContent !== region.textContent;
692
+ }
693
+ var selfNests = (region) => Boolean(region.querySelector(region.localName));
694
+ var needsFlattening = (region) => Boolean(region.matches?.(singleLineSelector)) || (hasBlockDescendant(region) || selfNests(region)) && ejectsOnReload(region);
695
+ function removeRuntimeState(region, mode) {
696
+ const origin = region.getAttribute("data-richclay-runtime-contenteditable");
697
+ if (region.hasAttribute("contenteditable")) {
698
+ if (origin === "true") {
699
+ region.removeAttribute("contenteditable");
700
+ } else if (origin) {
701
+ if (mode === "destroy") {
702
+ region.setAttribute("contenteditable", origin);
703
+ } else {
704
+ region.setAttribute("inert-contenteditable", origin);
705
+ region.removeAttribute("contenteditable");
706
+ }
707
+ } else if (mode === "save") {
708
+ region.setAttribute("inert-contenteditable", region.getAttribute("contenteditable"));
709
+ region.removeAttribute("contenteditable");
710
+ }
711
+ }
712
+ runtimeClasses.forEach((className) => region.classList.remove(className));
713
+ if (region.getAttribute("class") === "") region.removeAttribute("class");
714
+ removeRuntimeAttribute(region, "role", "data-richclay-runtime-role");
715
+ removeRuntimeAttribute(region, "aria-multiline", "data-richclay-runtime-aria-multiline");
716
+ removeRuntimeAttribute(region, "no-undo", "data-richclay-runtime-no-undo");
717
+ if (region.getAttribute("data-richclay-runtime-display") === "true") {
718
+ region.style.removeProperty("display");
719
+ if (region.getAttribute("style") === "") region.removeAttribute("style");
720
+ }
721
+ removeRuntimeDescribedBy(region);
722
+ region.removeAttribute("data-richclay-active");
723
+ region.removeAttribute("data-richclay-placeholder");
724
+ region.removeAttribute("data-richclay-pre");
725
+ region.removeAttribute("data-richclay-runtime-role");
726
+ region.removeAttribute("data-richclay-runtime-aria-multiline");
727
+ region.removeAttribute("data-richclay-runtime-no-undo");
728
+ region.removeAttribute("data-richclay-runtime-describedby");
729
+ region.removeAttribute("data-richclay-runtime-contenteditable");
730
+ region.removeAttribute("data-richclay-runtime-display");
731
+ if (region.getAttribute("data-richclay-runtime-marker") === "true") {
732
+ region.removeAttribute("data-richclay");
733
+ }
734
+ region.removeAttribute("data-richclay-runtime-marker");
735
+ }
736
+ function stripZeroWidthArtifacts(region) {
737
+ const zwsp = String.fromCharCode(8203);
738
+ const stack = [region];
739
+ while (stack.length) {
740
+ const node = stack.pop();
741
+ node.childNodes.forEach((child) => {
742
+ if (child.nodeType === 3) {
743
+ if (child.nodeValue.includes(zwsp)) {
744
+ child.nodeValue = child.nodeValue.split(zwsp).join("");
745
+ }
746
+ } else if (child.nodeType === 1) {
747
+ stack.push(child);
748
+ }
749
+ });
750
+ }
751
+ region.querySelectorAll("b, i, u, s, em, strong, code, sub, sup, span").forEach((el) => {
752
+ if (el.children.length === 0 && (el.textContent || "") === "" && el.attributes.length === 0) {
753
+ el.remove();
754
+ }
755
+ });
756
+ }
757
+ function unwrapLoneSingleLineBlock(region) {
758
+ const meaningful = Array.from(region.childNodes).filter(
759
+ (node) => node.nodeType !== 3 || (node.nodeValue || "").trim() !== ""
760
+ );
761
+ if (meaningful.length !== 1) return;
762
+ const block = meaningful[0];
763
+ if (block.nodeType !== 1 || block.nodeName !== "P" || block.attributes.length > 0) return;
764
+ while (block.firstChild) region.insertBefore(block.firstChild, block);
765
+ block.remove();
766
+ }
767
+ function consumeInertContenteditable(element) {
768
+ if (!element.hasAttribute("inert-contenteditable")) return null;
769
+ let value = element.getAttribute("inert-contenteditable");
770
+ if (!["false", "plaintext-only"].includes(value)) value = "true";
771
+ element.setAttribute("contenteditable", value);
772
+ element.removeAttribute("inert-contenteditable");
773
+ if (value === "true") {
774
+ element.setAttribute("data-richclay-runtime-contenteditable", "true");
775
+ }
776
+ return value;
777
+ }
778
+ function markChrome(element) {
779
+ element.setAttribute("snapshot-remove", "");
780
+ element.setAttribute("no-watch", "");
781
+ return element;
782
+ }
783
+ function readEditmodeParam(win) {
784
+ try {
785
+ const params = new URLSearchParams(win.location?.search || "");
786
+ if (params.get("editmode") === "true") return true;
787
+ if (params.get("editmode") === "false") return false;
788
+ } catch {
789
+ return null;
790
+ }
791
+ return null;
792
+ }
793
+ function hasEditmodeSignal(win) {
794
+ return readEditmodeParam(win) !== null || typeof win.__hyperclayEditMode === "boolean";
795
+ }
796
+ function readEditmodeCookie(win) {
797
+ const cookie = win.document?.cookie || "";
798
+ const match = cookie.match(/(?:^|;\s*)isAdminOfCurrentResource=([^;]*)/);
799
+ return Boolean(match && match[1] !== "");
800
+ }
801
+ function removeRuntimeAttribute(region, attribute, marker) {
802
+ if (!region.hasAttribute(marker)) return;
803
+ region.removeAttribute(attribute);
804
+ }
805
+ function removeRuntimeDescribedBy(region) {
806
+ const id = region.getAttribute("data-richclay-runtime-describedby");
807
+ if (!id) return;
808
+ const ids = (region.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean).filter((value) => value !== id);
809
+ if (ids.length) {
810
+ region.setAttribute("aria-describedby", ids.join(" "));
811
+ } else {
812
+ region.removeAttribute("aria-describedby");
813
+ }
814
+ }
815
+
816
+ // src/a11y.js
817
+ var liveId = 0;
818
+ function createLiveRegion(doc = document) {
819
+ const region = doc.createElement("div");
820
+ region.id = `richclay-live-${++liveId}`;
821
+ region.className = "richclay-sr-only";
822
+ region.setAttribute("aria-live", "polite");
823
+ region.setAttribute("aria-atomic", "true");
824
+ region.setAttribute("data-richclay-live", "");
825
+ markChrome(region);
826
+ doc.body.appendChild(region);
827
+ return region;
828
+ }
829
+ function announce(region, message) {
830
+ if (!region || !message) return;
831
+ region.textContent = "";
832
+ region.ownerDocument.defaultView.setTimeout(() => {
833
+ region.textContent = message;
834
+ }, 20);
835
+ }
836
+ function preservePointerSelection(event) {
837
+ event.preventDefault();
838
+ }
839
+ function cloneRange(range) {
840
+ try {
841
+ return range?.cloneRange?.() || null;
842
+ } catch {
843
+ return null;
844
+ }
845
+ }
846
+ function getSquireSelection(squire) {
847
+ try {
848
+ return cloneRange(squire?.getSelection?.());
849
+ } catch {
850
+ return null;
851
+ }
852
+ }
853
+ function restoreSquireSelection(squire, range) {
854
+ if (!squire || !range) return false;
855
+ try {
856
+ squire.setSelection(range);
857
+ return true;
858
+ } catch {
859
+ return false;
860
+ }
861
+ }
862
+ function setRuntimeAttribute(element, attribute, value, marker) {
863
+ if (!element.hasAttribute(attribute)) {
864
+ element.setAttribute(marker, "true");
865
+ }
866
+ element.setAttribute(attribute, value);
867
+ }
868
+ function connectDescription(element, description) {
869
+ const ids = new Set((element.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean));
870
+ ids.add(description.id);
871
+ element.setAttribute("aria-describedby", Array.from(ids).join(" "));
872
+ element.setAttribute("data-richclay-runtime-describedby", description.id);
873
+ }
874
+
875
+ // src/toolbar.js
876
+ var toolbarId = 0;
877
+ var Toolbar = class {
878
+ constructor(editor, controls, options = {}) {
879
+ this.editor = editor;
880
+ this.controls = controls;
881
+ this.options = options;
882
+ this.id = ++toolbarId;
883
+ this.items = [];
884
+ this.menus = /* @__PURE__ */ new Map();
885
+ this.activeIndex = 0;
886
+ this.root = this.createRoot();
887
+ this.onRootKeydown = (event) => this.handleRootKeydown(event);
888
+ this.onRootClick = (event) => this.handleRootClick(event);
889
+ this.onRootPointerDown = (event) => this.handleRootPointerDown(event);
890
+ this.onDocumentPointerDown = (event) => this.handleDocumentPointerDown(event);
891
+ this.render();
892
+ this.root.addEventListener("keydown", this.onRootKeydown);
893
+ this.root.addEventListener("click", this.onRootClick);
894
+ this.root.addEventListener("mousedown", this.onRootPointerDown);
895
+ this.root.ownerDocument.addEventListener("mousedown", this.onDocumentPointerDown);
896
+ }
897
+ destroy() {
898
+ this.closeMenus();
899
+ this.root.removeEventListener("keydown", this.onRootKeydown);
900
+ this.root.removeEventListener("click", this.onRootClick);
901
+ this.root.removeEventListener("mousedown", this.onRootPointerDown);
902
+ this.root.ownerDocument.removeEventListener("mousedown", this.onDocumentPointerDown);
903
+ this.root.remove();
904
+ }
905
+ update() {
906
+ this.items.forEach((item) => {
907
+ const { def, button } = item;
908
+ if (typeof def.isActive === "function") {
909
+ const active = Boolean(def.isActive(this.editor));
910
+ button.setAttribute("aria-pressed", active ? "true" : "false");
911
+ button.classList.toggle("is-active", active);
912
+ }
913
+ if (typeof def.isDisabled === "function") {
914
+ button.disabled = Boolean(def.isDisabled(this.editor));
915
+ }
916
+ });
917
+ this.ensureSingleTabStop();
918
+ }
919
+ focusFirst() {
920
+ this.activeIndex = 0;
921
+ this.ensureSingleTabStop();
922
+ this.items[0]?.button.focus();
923
+ }
924
+ createRoot() {
925
+ const doc = this.editor.element.ownerDocument;
926
+ const root = doc.createElement("div");
927
+ root.className = "richclay-toolbar";
928
+ root.setAttribute("role", "toolbar");
929
+ root.setAttribute("aria-label", this.options.ariaLabel || "Rich text formatting");
930
+ root.setAttribute("data-richclay-toolbar", "");
931
+ markChrome(root);
932
+ const container = resolveContainer(this.options.toolbarContainer, doc);
933
+ if (container) {
934
+ container.appendChild(root);
935
+ } else {
936
+ this.editor.element.insertAdjacentElement("beforebegin", root);
937
+ }
938
+ return root;
939
+ }
940
+ render() {
941
+ this.root.textContent = "";
942
+ this.items = [];
943
+ this.menus.clear();
944
+ let lastGroup = null;
945
+ this.visibleControls().forEach((def, index) => {
946
+ if (def.type === "separator") {
947
+ this.root.appendChild(createSeparator(this.root.ownerDocument));
948
+ lastGroup = null;
949
+ return;
950
+ }
951
+ if (lastGroup && def.group && def.group !== lastGroup) {
952
+ this.root.appendChild(createSeparator(this.root.ownerDocument));
953
+ }
954
+ lastGroup = def.group || lastGroup;
955
+ if (def.type === "menu") {
956
+ this.renderMenu(def, index);
957
+ } else {
958
+ this.renderButton(def, index);
959
+ }
960
+ });
961
+ this.ensureSingleTabStop();
962
+ this.update();
963
+ }
964
+ // A control disabled by the root is disabled for this editor's whole life, not
965
+ // for this selection, so it is left out of the toolbar rather than rendered
966
+ // greyed: a greyed button says "not right now" and invites clicking, an absent
967
+ // one just gives a simpler toolbar. It stays disabled at dispatch as well,
968
+ // because hiding a button stops neither its shortcut nor its menu item.
969
+ visibleControls() {
970
+ const kept = this.controls.filter(
971
+ (def) => def.type === "separator" || !def.isDisabled?.(this.editor)
972
+ );
973
+ const trimmed = [];
974
+ kept.forEach((def) => {
975
+ const previous = trimmed[trimmed.length - 1];
976
+ if (def.type === "separator" && (!previous || previous.type === "separator")) return;
977
+ trimmed.push(def);
978
+ });
979
+ while (trimmed[trimmed.length - 1]?.type === "separator") trimmed.pop();
980
+ return trimmed;
981
+ }
982
+ renderButton(def, index) {
983
+ const button = createToolbarButton(this.root.ownerDocument, def);
984
+ button.dataset.richclayControl = def.id;
985
+ button.dataset.richclayIndex = String(index);
986
+ if (typeof def.isActive === "function") {
987
+ button.setAttribute("aria-pressed", "false");
988
+ }
989
+ this.root.appendChild(button);
990
+ this.items.push({ def, button, type: "button" });
991
+ }
992
+ renderMenu(def, index) {
993
+ const doc = this.root.ownerDocument;
994
+ const button = createToolbarButton(doc, def);
995
+ const menuId = `richclay-menu-${this.id}-${def.id}`;
996
+ button.dataset.richclayControl = def.id;
997
+ button.dataset.richclayIndex = String(index);
998
+ button.setAttribute("aria-haspopup", "menu");
999
+ button.setAttribute("aria-expanded", "false");
1000
+ button.setAttribute("aria-controls", menuId);
1001
+ const menu = doc.createElement("div");
1002
+ menu.id = menuId;
1003
+ menu.className = "richclay-menu";
1004
+ menu.hidden = true;
1005
+ menu.setAttribute("role", "menu");
1006
+ menu.setAttribute("data-richclay-menu", "");
1007
+ markChrome(menu);
1008
+ def.options.forEach((option, optionIndex) => {
1009
+ const item = doc.createElement("button");
1010
+ item.type = "button";
1011
+ item.className = "richclay-menu-item";
1012
+ item.setAttribute("role", "menuitemradio");
1013
+ item.setAttribute("tabindex", "-1");
1014
+ item.dataset.richclayMenuItem = def.id;
1015
+ item.dataset.richclayOptionIndex = String(optionIndex);
1016
+ item.innerHTML = option.icon || "";
1017
+ item.appendChild(doc.createTextNode(option.label));
1018
+ item.addEventListener("mousedown", preservePointerSelection);
1019
+ item.addEventListener("click", (event) => {
1020
+ event.preventDefault();
1021
+ this.chooseMenuItem(def, option, button);
1022
+ });
1023
+ item.addEventListener("keydown", (event) => this.handleMenuKeydown(event, def, button));
1024
+ menu.appendChild(item);
1025
+ });
1026
+ const wrap = doc.createElement("span");
1027
+ wrap.className = "richclay-menu-wrap";
1028
+ wrap.append(button, menu);
1029
+ this.root.appendChild(wrap);
1030
+ this.items.push({ def, button, type: "menu", menu });
1031
+ this.menus.set(def.id, { def, button, menu });
1032
+ }
1033
+ handleRootPointerDown(event) {
1034
+ const control = event.target.closest?.("[data-richclay-control]");
1035
+ if (!control || !this.root.contains(control)) return;
1036
+ preservePointerSelection(event);
1037
+ this.editor.saveSelection();
1038
+ }
1039
+ handleRootClick(event) {
1040
+ const button = event.target.closest?.("[data-richclay-control]");
1041
+ if (!button || !this.root.contains(button)) return;
1042
+ event.preventDefault();
1043
+ const item = this.items.find((candidate) => candidate.button === button);
1044
+ if (!item || button.disabled) return;
1045
+ this.activeIndex = this.items.indexOf(item);
1046
+ this.ensureSingleTabStop();
1047
+ if (item.type === "menu") {
1048
+ this.toggleMenu(item.def.id);
1049
+ return;
1050
+ }
1051
+ this.closeMenus();
1052
+ this.editor.runControl(item.def);
1053
+ }
1054
+ handleRootKeydown(event) {
1055
+ const key = event.key;
1056
+ const navigation = {
1057
+ ArrowRight: 1,
1058
+ ArrowDown: 1,
1059
+ ArrowLeft: -1,
1060
+ ArrowUp: -1
1061
+ };
1062
+ if (key in navigation) {
1063
+ event.preventDefault();
1064
+ this.moveFocus(navigation[key]);
1065
+ return;
1066
+ }
1067
+ if (key === "Home") {
1068
+ event.preventDefault();
1069
+ this.setFocusIndex(0);
1070
+ return;
1071
+ }
1072
+ if (key === "End") {
1073
+ event.preventDefault();
1074
+ this.setFocusIndex(this.items.length - 1);
1075
+ return;
1076
+ }
1077
+ const current = this.items[this.activeIndex];
1078
+ if (!current) return;
1079
+ if ((key === "Enter" || key === " ") && current.type === "menu") {
1080
+ event.preventDefault();
1081
+ this.openMenu(current.def.id, "first");
1082
+ }
1083
+ if (key === "Escape") {
1084
+ const hadOpenMenu = Array.from(this.menus.values()).some((entry) => !entry.menu.hidden);
1085
+ this.closeMenus();
1086
+ if (hadOpenMenu) current.button.focus();
1087
+ else this.editor.focus();
1088
+ }
1089
+ }
1090
+ handleMenuKeydown(event, def, trigger) {
1091
+ event.stopPropagation();
1092
+ const menu = this.menus.get(def.id)?.menu;
1093
+ const items = Array.from(menu.querySelectorAll(".richclay-menu-item"));
1094
+ const index = items.indexOf(event.currentTarget);
1095
+ let next = index;
1096
+ if (event.key === "ArrowDown") next = (index + 1) % items.length;
1097
+ if (event.key === "ArrowUp") next = (index - 1 + items.length) % items.length;
1098
+ if (event.key === "Home") next = 0;
1099
+ if (event.key === "End") next = items.length - 1;
1100
+ if (next !== index) {
1101
+ event.preventDefault();
1102
+ items[next].focus();
1103
+ return;
1104
+ }
1105
+ if (event.key === "Escape") {
1106
+ event.preventDefault();
1107
+ this.closeMenus();
1108
+ trigger.focus();
1109
+ return;
1110
+ }
1111
+ if (event.key === "Enter" || event.key === " ") {
1112
+ event.preventDefault();
1113
+ const option = def.options[index];
1114
+ this.chooseMenuItem(def, option, trigger);
1115
+ }
1116
+ }
1117
+ handleDocumentPointerDown(event) {
1118
+ if (this.root.contains(event.target)) return;
1119
+ this.closeMenus();
1120
+ }
1121
+ chooseMenuItem(def, option, trigger) {
1122
+ this.closeMenus();
1123
+ this.editor.runControl({
1124
+ ...option,
1125
+ id: `${def.id}:${option.value}`,
1126
+ ariaLabel: option.label
1127
+ });
1128
+ trigger.focus();
1129
+ }
1130
+ toggleMenu(id) {
1131
+ const menu = this.menus.get(id);
1132
+ if (!menu) return;
1133
+ if (menu.menu.hidden) this.openMenu(id, "first");
1134
+ else this.closeMenus();
1135
+ }
1136
+ openMenu(id, focus = "first") {
1137
+ this.closeMenus(id);
1138
+ const entry = this.menus.get(id);
1139
+ if (!entry) return;
1140
+ entry.menu.hidden = false;
1141
+ entry.button.setAttribute("aria-expanded", "true");
1142
+ this.updateMenuState(entry);
1143
+ const items = Array.from(entry.menu.querySelectorAll(".richclay-menu-item"));
1144
+ const active = items.find((item) => item.getAttribute("aria-checked") === "true");
1145
+ const target = focus === "active" ? active || items[0] : items[0];
1146
+ target?.focus();
1147
+ }
1148
+ closeMenus(exceptId = null) {
1149
+ this.menus.forEach((entry, id) => {
1150
+ if (id === exceptId) return;
1151
+ entry.menu.hidden = true;
1152
+ entry.button.setAttribute("aria-expanded", "false");
1153
+ });
1154
+ }
1155
+ updateMenuState(entry) {
1156
+ const items = Array.from(entry.menu.querySelectorAll(".richclay-menu-item"));
1157
+ items.forEach((item, index) => {
1158
+ const option = entry.def.options[index];
1159
+ const active = Boolean(option.isActive?.(this.editor));
1160
+ item.setAttribute("aria-checked", active ? "true" : "false");
1161
+ item.classList.toggle("is-active", active);
1162
+ });
1163
+ }
1164
+ moveFocus(delta) {
1165
+ if (!this.items.length) return;
1166
+ let next = this.activeIndex;
1167
+ do {
1168
+ next = (next + delta + this.items.length) % this.items.length;
1169
+ } while (this.items[next].button.disabled && next !== this.activeIndex);
1170
+ this.setFocusIndex(next);
1171
+ }
1172
+ setFocusIndex(index) {
1173
+ this.activeIndex = Math.max(0, Math.min(index, this.items.length - 1));
1174
+ this.ensureSingleTabStop();
1175
+ this.items[this.activeIndex]?.button.focus();
1176
+ }
1177
+ ensureSingleTabStop() {
1178
+ const enabled = this.items.filter((item) => !item.button.disabled);
1179
+ if (!enabled.length) return;
1180
+ if (!enabled.includes(this.items[this.activeIndex])) {
1181
+ this.activeIndex = this.items.indexOf(enabled[0]);
1182
+ }
1183
+ this.items.forEach((item, index) => {
1184
+ item.button.tabIndex = index === this.activeIndex ? 0 : -1;
1185
+ });
1186
+ }
1187
+ };
1188
+ function createToolbarButton(doc, def) {
1189
+ const button = doc.createElement("button");
1190
+ button.type = "button";
1191
+ button.className = "richclay-button";
1192
+ const shortcut = formatShortcut(def.shortcut, doc.defaultView);
1193
+ const label = shortcut ? `${def.ariaLabel || def.label} (${shortcut})` : def.ariaLabel || def.label;
1194
+ button.setAttribute("aria-label", label);
1195
+ button.title = label;
1196
+ button.innerHTML = `${def.icon || ""}<span class="richclay-sr-only">${def.label}</span>`;
1197
+ return button;
1198
+ }
1199
+ function createSeparator(doc) {
1200
+ const separator = doc.createElement("span");
1201
+ separator.className = "richclay-separator";
1202
+ separator.setAttribute("role", "separator");
1203
+ separator.setAttribute("aria-orientation", "vertical");
1204
+ return separator;
1205
+ }
1206
+ function resolveContainer(container, doc) {
1207
+ if (!container) return null;
1208
+ if (typeof container === "string") return doc.querySelector(container);
1209
+ return container;
1210
+ }
1211
+
1212
+ // src/toolbar-float.js
1213
+ var GAP = 16;
1214
+ var GAP_LADDER = [16, 8, 0];
1215
+ var VIEWPORT_INSET = 8;
1216
+ var PLACEMENT_SLACK = 4;
1217
+ function placeToolbar({ anchor, bar, rail, viewport, current = null }) {
1218
+ const slack = (mode) => current === mode ? PLACEMENT_SLACK : 0;
1219
+ if (anchor.bottom <= 0 || anchor.top >= viewport.height || anchor.right <= 0 || anchor.left >= viewport.width) {
1220
+ return { mode: "hidden", x: 0, y: 0 };
1221
+ }
1222
+ const clampX = (width) => Math.max(VIEWPORT_INSET, Math.min(anchor.left, viewport.width - width - VIEWPORT_INSET));
1223
+ const aboveY = anchor.top - GAP - bar.height;
1224
+ if (aboveY >= VIEWPORT_INSET - slack("above")) {
1225
+ return { mode: "above", x: clampX(bar.width), y: aboveY };
1226
+ }
1227
+ const belowY = anchor.bottom + GAP;
1228
+ if (belowY + bar.height <= viewport.height - VIEWPORT_INSET + slack("below")) {
1229
+ return { mode: "below", x: clampX(bar.width), y: belowY };
1230
+ }
1231
+ const rightSpace = viewport.width - anchor.right - VIEWPORT_INSET;
1232
+ const leftSpace = anchor.left - VIEWPORT_INSET;
1233
+ const sides = rightSpace >= leftSpace ? [["rail-right", rightSpace], ["rail-left", leftSpace]] : [["rail-left", leftSpace], ["rail-right", rightSpace]];
1234
+ for (const [mode, space] of sides) {
1235
+ for (const gap of GAP_LADDER) {
1236
+ if (rail.width + gap > space + slack(mode)) continue;
1237
+ const x = mode === "rail-right" ? Math.min(anchor.right + gap, viewport.width - rail.width - VIEWPORT_INSET) : Math.max(anchor.left - gap - rail.width, VIEWPORT_INSET);
1238
+ const maxY = Math.min(anchor.bottom, viewport.height - VIEWPORT_INSET) - rail.height;
1239
+ const y = Math.max(VIEWPORT_INSET, Math.min(Math.max(anchor.top, VIEWPORT_INSET), maxY));
1240
+ return { mode, x, y, gap };
1241
+ }
1242
+ }
1243
+ return { mode: "pinned", x: clampX(bar.width), y: VIEWPORT_INSET };
1244
+ }
1245
+ var POPOVER_GAP = 10;
1246
+ var POPOVER_ARROW_INSET = 28;
1247
+ function placePopover({ anchor, box, viewport, gap = POPOVER_GAP }) {
1248
+ const spaceBelow = viewport.height - VIEWPORT_INSET - anchor.bottom - gap;
1249
+ const spaceAbove = anchor.top - gap - VIEWPORT_INSET;
1250
+ const side = spaceBelow >= box.height || spaceBelow >= spaceAbove ? "below" : "above";
1251
+ const rawY = side === "below" ? anchor.bottom + gap : anchor.top - gap - box.height;
1252
+ const y = Math.max(VIEWPORT_INSET, Math.min(rawY, viewport.height - VIEWPORT_INSET - box.height));
1253
+ const center = (anchor.left + anchor.right) / 2;
1254
+ const x = Math.max(VIEWPORT_INSET, Math.min(center - POPOVER_ARROW_INSET, viewport.width - VIEWPORT_INSET - box.width));
1255
+ const arrow = Math.max(14, Math.min(center - x, box.width - 14));
1256
+ return { side, x, y, arrow };
1257
+ }
1258
+ function pin(el, prop, value) {
1259
+ el.style.setProperty(prop, value, "important");
1260
+ }
1261
+ var FloatingToolbar = class {
1262
+ constructor(editor, controls) {
1263
+ this.editor = editor;
1264
+ this.mode = null;
1265
+ this.hidden = false;
1266
+ this.frame = 0;
1267
+ this.bar = { width: 0, height: 0 };
1268
+ this.rail = { width: 0, height: 0 };
1269
+ const doc = editor.element.ownerDocument;
1270
+ this.doc = doc;
1271
+ this.win = doc.defaultView || globalThis;
1272
+ this.root = doc.createElement("div");
1273
+ this.root.className = "richclay-float";
1274
+ this.root.setAttribute("data-richclay-float", "");
1275
+ this.root.setAttribute("save-remove", "");
1276
+ markChrome(this.root);
1277
+ doc.body.appendChild(this.root);
1278
+ this.toolbar = new Toolbar(editor, controls, { toolbarContainer: this.root });
1279
+ this.onScroll = () => this.schedule();
1280
+ this.onResize = () => {
1281
+ this.measure();
1282
+ this.schedule();
1283
+ };
1284
+ doc.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
1285
+ this.win.addEventListener("resize", this.onResize, { passive: true });
1286
+ this.win.visualViewport?.addEventListener("resize", this.onResize, { passive: true });
1287
+ this.win.visualViewport?.addEventListener("scroll", this.onScroll, { passive: true });
1288
+ this.resizeObserver = typeof this.win.ResizeObserver === "function" ? new this.win.ResizeObserver(() => this.schedule()) : null;
1289
+ this.resizeObserver?.observe(editor.element);
1290
+ this.measure();
1291
+ this.reposition();
1292
+ }
1293
+ // Measure both orientations up front so the placement math can evaluate the
1294
+ // rail without first switching to it.
1295
+ measure() {
1296
+ const wasRail = this.root.classList.contains("richclay-float-rail");
1297
+ pin(this.root, "visibility", "hidden");
1298
+ this.root.classList.remove("richclay-float-rail");
1299
+ this.bar = { width: this.root.offsetWidth || 0, height: this.root.offsetHeight || 0 };
1300
+ this.root.classList.add("richclay-float-rail");
1301
+ this.rail = { width: this.root.offsetWidth || 0, height: this.root.offsetHeight || 0 };
1302
+ this.root.classList.toggle("richclay-float-rail", wasRail);
1303
+ this.root.style.removeProperty("visibility");
1304
+ }
1305
+ schedule() {
1306
+ if (!this.win.requestAnimationFrame) {
1307
+ this.reposition();
1308
+ return;
1309
+ }
1310
+ if (this.frame) return;
1311
+ this.frame = this.win.requestAnimationFrame(() => {
1312
+ this.frame = 0;
1313
+ this.reposition();
1314
+ });
1315
+ }
1316
+ setVisible(visible) {
1317
+ this.hidden = !visible;
1318
+ this.reposition();
1319
+ }
1320
+ reposition() {
1321
+ if (this.hidden) {
1322
+ pin(this.root, "display", "none");
1323
+ return;
1324
+ }
1325
+ const anchor = this.editor.element.getBoundingClientRect();
1326
+ const viewport = {
1327
+ width: this.win.visualViewport?.width ?? this.win.innerWidth,
1328
+ height: this.win.visualViewport?.height ?? this.win.innerHeight
1329
+ };
1330
+ const placement = placeToolbar({
1331
+ anchor,
1332
+ bar: this.bar,
1333
+ rail: this.rail,
1334
+ viewport,
1335
+ current: this.mode
1336
+ });
1337
+ this.mode = placement.mode;
1338
+ if (placement.mode === "hidden") {
1339
+ pin(this.root, "display", "none");
1340
+ return;
1341
+ }
1342
+ this.root.style.removeProperty("display");
1343
+ this.root.classList.toggle(
1344
+ "richclay-float-rail",
1345
+ placement.mode === "rail-left" || placement.mode === "rail-right"
1346
+ );
1347
+ this.root.classList.toggle("richclay-float-pinned", placement.mode === "pinned");
1348
+ pin(this.root, "transform", `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px)`);
1349
+ }
1350
+ destroy() {
1351
+ if (this.frame) this.win.cancelAnimationFrame?.(this.frame);
1352
+ this.frame = 0;
1353
+ this.doc.removeEventListener("scroll", this.onScroll, { capture: true });
1354
+ this.win.removeEventListener("resize", this.onResize);
1355
+ this.win.visualViewport?.removeEventListener("resize", this.onResize);
1356
+ this.win.visualViewport?.removeEventListener("scroll", this.onScroll);
1357
+ this.resizeObserver?.disconnect();
1358
+ this.toolbar.destroy();
1359
+ this.root.remove();
1360
+ }
1361
+ };
1362
+
1363
+ // src/sanitize.js
1364
+ var DEFAULT_SANITIZE_CONFIG = {
1365
+ ALLOWED_TAGS: [
1366
+ "a",
1367
+ "b",
1368
+ "blockquote",
1369
+ "br",
1370
+ "code",
1371
+ "div",
1372
+ "em",
1373
+ "h1",
1374
+ "h2",
1375
+ "h3",
1376
+ "h4",
1377
+ "h5",
1378
+ "h6",
1379
+ "i",
1380
+ "li",
1381
+ "ol",
1382
+ "p",
1383
+ "pre",
1384
+ "s",
1385
+ "span",
1386
+ "strong",
1387
+ "sub",
1388
+ "sup",
1389
+ "u",
1390
+ "ul"
1391
+ ],
1392
+ ALLOWED_ATTR: ["aria-label", "href", "rel", "target", "title"],
1393
+ ALLOW_DATA_ATTR: false,
1394
+ ALLOW_ARIA_ATTR: true,
1395
+ ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
1396
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
1397
+ FORBID_ATTR: ["style", "srcset", "onerror", "onclick", "onload"]
1398
+ };
1399
+ function createSanitizer(config = {}, doc = document) {
1400
+ const purify = resolveDOMPurify(doc);
1401
+ return {
1402
+ sanitizeHTML(html) {
1403
+ return sanitizeHTML(html, config, doc, purify);
1404
+ },
1405
+ sanitizeElement(element) {
1406
+ return sanitizeElement(element, config, purify);
1407
+ },
1408
+ sanitizeToDOMFragment(html, editor) {
1409
+ const ownerDocument = editor?.getRoot?.().ownerDocument || doc;
1410
+ return sanitizeToDOMFragment(html, config, ownerDocument, purify);
1411
+ }
1412
+ };
1413
+ }
1414
+ function sanitizeHTML(html, config = {}, doc = document, purify = resolveDOMPurify(doc)) {
1415
+ const template = doc.createElement("template");
1416
+ template.innerHTML = purify.sanitize(String(html || ""), mergeConfig(config));
1417
+ normalizeLinks(template.content);
1418
+ return template.innerHTML;
1419
+ }
1420
+ function sanitizeElement(element, config = {}, purify = resolveDOMPurify(element.ownerDocument)) {
1421
+ const doc = element.ownerDocument;
1422
+ const wrapper = doc.createElement("div");
1423
+ while (element.firstChild) wrapper.appendChild(element.firstChild);
1424
+ purify.sanitize(wrapper, {
1425
+ ...mergeConfig(config),
1426
+ IN_PLACE: true
1427
+ });
1428
+ normalizeLinks(wrapper);
1429
+ while (wrapper.firstChild) element.appendChild(wrapper.firstChild);
1430
+ return element;
1431
+ }
1432
+ function sanitizeToDOMFragment(html, config = {}, doc = document, purify = resolveDOMPurify(doc)) {
1433
+ const fragment = purify.sanitize(String(html || ""), {
1434
+ ...mergeConfig(config),
1435
+ RETURN_DOM_FRAGMENT: true
1436
+ });
1437
+ const clean = fragment?.ownerDocument === doc ? fragment : doc.importNode(fragment, true);
1438
+ normalizeLinks(clean);
1439
+ return clean || doc.createDocumentFragment();
1440
+ }
1441
+ function mergeConfig(config) {
1442
+ return { ...DEFAULT_SANITIZE_CONFIG, ...config };
1443
+ }
1444
+ var SAFE_URL_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
1445
+ var URL_CONTROL_CHARS = new RegExp("[\\u0000-\\u0020\\u007F-\\u009F]", "g");
1446
+ function isSafeUrl(url) {
1447
+ const stripped = String(url == null ? "" : url).replace(URL_CONTROL_CHARS, "");
1448
+ const scheme = (stripped.match(/^([a-z][a-z0-9+.\-]*):/i) || [])[1];
1449
+ return !scheme || SAFE_URL_SCHEMES.has(scheme.toLowerCase());
1450
+ }
1451
+ function normalizeUrl(value) {
1452
+ const raw = String(value == null ? "" : value).trim();
1453
+ if (!raw || !isSafeUrl(raw)) return "";
1454
+ if (/^(?:https?:|mailto:|tel:)/i.test(raw)) return raw;
1455
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw)) return `mailto:${raw}`;
1456
+ if (/^(?:[#?]|\/|\.{1,2}\/)/.test(raw)) return raw;
1457
+ return `https://${raw}`;
1458
+ }
1459
+ function resolveDOMPurify(doc) {
1460
+ const supplied = globalThis.DOMPurify;
1461
+ if (supplied?.sanitize) return supplied;
1462
+ if (typeof supplied === "function") return supplied(doc.defaultView || globalThis);
1463
+ throw new Error(
1464
+ "RichClay requires DOMPurify. Load vendor/purify.min.js before richclay.js or provide globalThis.DOMPurify."
1465
+ );
1466
+ }
1467
+ function normalizeLinks(root) {
1468
+ root.querySelectorAll?.("a[href]").forEach((link) => {
1469
+ const href = link.getAttribute("href") || "";
1470
+ if (/^\s*javascript:/i.test(href)) {
1471
+ link.removeAttribute("href");
1472
+ }
1473
+ if (link.getAttribute("target") === "_blank") {
1474
+ const rel = new Set((link.getAttribute("rel") || "").split(/\s+/).filter(Boolean));
1475
+ rel.add("noopener");
1476
+ rel.add("noreferrer");
1477
+ link.setAttribute("rel", Array.from(rel).join(" "));
1478
+ }
1479
+ });
1480
+ }
1481
+ var INLINE_SANITIZE_EXTENSIONS = {
1482
+ ADD_TAGS: ["img"],
1483
+ ADD_ATTR: ["class", "id", "src", "alt", "width", "height"]
1484
+ };
1485
+ function inlineSanitizeConfig(config = {}) {
1486
+ return {
1487
+ ...config,
1488
+ ADD_TAGS: [.../* @__PURE__ */ new Set([...INLINE_SANITIZE_EXTENSIONS.ADD_TAGS, ...config.ADD_TAGS || []])],
1489
+ ADD_ATTR: [.../* @__PURE__ */ new Set([...INLINE_SANITIZE_EXTENSIONS.ADD_ATTR, ...config.ADD_ATTR || []])],
1490
+ ALLOW_DATA_ATTR: config.ALLOW_DATA_ATTR ?? true
1491
+ };
1492
+ }
1493
+ var FLATTEN_SELECTOR = "p, div, h1, h2, h3, h4, h5, h6, blockquote, pre, ul, ol, li, figure, figcaption, table, thead, tbody, tr, td, th";
1494
+ function flattenFragmentToSingleLine(fragment) {
1495
+ const doc = fragment.ownerDocument || document;
1496
+ fragment.querySelectorAll?.("br").forEach((br) => br.replaceWith(doc.createTextNode(" ")));
1497
+ let block = fragment.querySelector?.(FLATTEN_SELECTOR);
1498
+ while (block) {
1499
+ const parent = block.parentNode;
1500
+ parent.insertBefore(doc.createTextNode(" "), block);
1501
+ while (block.firstChild) parent.insertBefore(block.firstChild, block);
1502
+ parent.insertBefore(doc.createTextNode(" "), block);
1503
+ block.remove();
1504
+ block = fragment.querySelector(FLATTEN_SELECTOR);
1505
+ }
1506
+ collapseFragmentWhitespace(fragment, doc);
1507
+ return fragment;
1508
+ }
1509
+ function collapseFragmentWhitespace(fragment, doc) {
1510
+ fragment.normalize?.();
1511
+ const walker = doc.createTreeWalker(
1512
+ fragment,
1513
+ 4
1514
+ /* NodeFilter.SHOW_TEXT */
1515
+ );
1516
+ const nodes = [];
1517
+ while (walker.nextNode()) nodes.push(walker.currentNode);
1518
+ nodes.forEach((node) => {
1519
+ node.nodeValue = node.nodeValue.replace(/\s+/g, " ");
1520
+ });
1521
+ const first = fragment.firstChild;
1522
+ if (first?.nodeType === 3) first.nodeValue = first.nodeValue.replace(/^\s+/, "");
1523
+ const last = fragment.lastChild;
1524
+ if (last?.nodeType === 3) last.nodeValue = last.nodeValue.replace(/\s+$/, "");
1525
+ }
1526
+
1527
+ // src/styles.js
1528
+ var STYLE_ID = "richclay-styles";
1529
+ var styledDocs = /* @__PURE__ */ new WeakSet();
1530
+ var cssText = "";
1531
+ function setRichClayStyles(text) {
1532
+ cssText = text || "";
1533
+ }
1534
+ function ensureStyles(doc = document) {
1535
+ if (!doc || styledDocs.has(doc)) return;
1536
+ if (doc.getElementById(STYLE_ID)) {
1537
+ styledDocs.add(doc);
1538
+ return;
1539
+ }
1540
+ if (!cssText) return;
1541
+ const style = doc.createElement("style");
1542
+ style.id = STYLE_ID;
1543
+ style.setAttribute("save-remove", "");
1544
+ style.setAttribute("save-ignore", "");
1545
+ style.textContent = cssText;
1546
+ (doc.head || doc.documentElement).appendChild(style);
1547
+ styledDocs.add(doc);
1548
+ }
1549
+
1550
+ // src/lib/region-capabilities.js
1551
+ var CAPABILITIES = Object.freeze({
1552
+ data: Object.freeze({ tokens: ["no-data"], bundles: ["editor-ui"] }),
1553
+ save: Object.freeze({ tokens: ["no-save"], bundles: ["editor-ui"] }),
1554
+ snapshot: Object.freeze({ tokens: ["no-snapshot"], bundles: ["editor-ui"] }),
1555
+ watch: Object.freeze({ tokens: ["no-watch"], bundles: ["editor-ui"] }),
1556
+ undo: Object.freeze({ tokens: ["no-undo"], bundles: ["editor-ui"] }),
1557
+ history: Object.freeze({ tokens: [], bundles: ["editor-ui"] })
1558
+ });
1559
+ var BUNDLES = Object.freeze({
1560
+ "editor-ui": Object.freeze(["no-data", "no-save", "no-snapshot", "no-watch", "no-undo"])
1561
+ });
1562
+ var POLICY_TOKENS = Object.freeze([
1563
+ "no-save",
1564
+ "no-snapshot",
1565
+ "no-trigger-autosave",
1566
+ "no-dirty",
1567
+ "no-watch",
1568
+ "no-undo",
1569
+ "no-data",
1570
+ "freeze",
1571
+ "editor-ui"
1572
+ ]);
1573
+ function hasPolicyToken(element, token) {
1574
+ if (!element || element.nodeType !== 1) return false;
1575
+ const clay = element.getAttribute?.("clay");
1576
+ return !!(clay && clay.split(/\s+/).includes(token) || element.hasAttribute?.(token));
1577
+ }
1578
+ function expandsTo(element, token) {
1579
+ if (hasPolicyToken(element, token)) return true;
1580
+ for (const [bundle, members] of Object.entries(BUNDLES)) {
1581
+ if (members.includes(token) && hasPolicyToken(element, bundle)) return true;
1582
+ }
1583
+ return false;
1584
+ }
1585
+ function capabilitySelector(capability) {
1586
+ const definition = CAPABILITIES[capability];
1587
+ if (!definition) throw new Error(`Unknown region capability: ${capability}`);
1588
+ return [...definition.tokens, ...definition.bundles].flatMap((token) => [`[clay~="${token}"]`, `[${token}]`]).join(", ");
1589
+ }
1590
+ function closestWithCapability(node, capability) {
1591
+ let element = node && node.nodeType === 1 ? node : node?.parentElement;
1592
+ while (element && element.nodeType === 1) {
1593
+ const definition = CAPABILITIES[capability];
1594
+ if (!definition) throw new Error(`Unknown region capability: ${capability}`);
1595
+ if (definition.tokens.some((token) => expandsTo(element, token)) || definition.bundles.some((bundle) => hasPolicyToken(element, bundle))) return element;
1596
+ element = element.parentElement;
1597
+ }
1598
+ return null;
1599
+ }
1600
+ function hasCapability(node, capability) {
1601
+ return !!closestWithCapability(node, capability);
1602
+ }
1603
+
1604
+ // src/lib/content-dom.js
1605
+ var UNSUPPORTED_SELECTOR = /:(?:focus(?:-within|-visible)?|hover|active|visited|defined)\b/i;
1606
+ function copyControlState(source, copy) {
1607
+ if (!/^(INPUT|TEXTAREA|SELECT|OPTION)$/.test(source.tagName || "")) return;
1608
+ const type = (source.getAttribute?.("type") || "").toLowerCase();
1609
+ if ("value" in source && "value" in copy && source.tagName !== "OPTION" && type !== "checkbox" && type !== "radio" && type !== "file") copy.value = source.value;
1610
+ if (source.tagName === "INPUT" && (type === "checkbox" || type === "radio")) copy.checked = source.checked;
1611
+ if (source.tagName === "OPTION") copy.selected = source.selected;
1612
+ if (source.tagName === "SELECT") {
1613
+ for (let i = 0; i < source.options.length; i++) copy.options[i].selected = source.options[i].selected;
1614
+ }
1615
+ if ("indeterminate" in source && "indeterminate" in copy) copy.indeterminate = source.indeterminate;
1616
+ }
1617
+ function matchesWithin(node, selector, boundary, inherit) {
1618
+ if (inherit) return !!node.closest?.(selector);
1619
+ let current = node;
1620
+ while (current?.nodeType === 1) {
1621
+ if (current.matches(selector)) return true;
1622
+ if (current === boundary) break;
1623
+ current = current.parentElement;
1624
+ }
1625
+ return false;
1626
+ }
1627
+ function importTree(source, targetDocument, capability, capabilityMatch, exclude, boundary, inherit, maps) {
1628
+ if (source.nodeType === 1 && ((inherit ? hasCapability(source, capability) : matchesWithin(source, capabilityMatch, boundary, false)) || exclude && (inherit && source !== boundary ? source.matches(exclude) : matchesWithin(source, exclude, boundary, inherit)))) return null;
1629
+ const copy = targetDocument.importNode(source, false);
1630
+ maps.cloneToLive.set(copy, source);
1631
+ maps.liveToClone.set(source, copy);
1632
+ const sourceChildren = source.nodeType === 1 && source.tagName === "TEMPLATE" ? source.content : source;
1633
+ const copyChildren = copy.nodeType === 1 && copy.tagName === "TEMPLATE" ? copy.content : copy;
1634
+ if (sourceChildren !== source) {
1635
+ maps.cloneToLive.set(copyChildren, sourceChildren);
1636
+ maps.liveToClone.set(sourceChildren, copyChildren);
1637
+ }
1638
+ for (const child of sourceChildren.childNodes || []) {
1639
+ const childCopy = importTree(child, targetDocument, capability, capabilityMatch, exclude, boundary, inherit, maps);
1640
+ if (childCopy) {
1641
+ copyChildren.appendChild(childCopy);
1642
+ if (child.nodeType === 1) copyControlState(child, childCopy);
1643
+ }
1644
+ }
1645
+ if (source.nodeType === 1) copyControlState(source, copy);
1646
+ return copy;
1647
+ }
1648
+ function createContentView(context, { capability = "data", exclude = null, inherit = true } = {}) {
1649
+ if (!context) throw new TypeError("createContentView requires a DOM context");
1650
+ const sourceDocument = context.nodeType === 9 ? context : context.ownerDocument;
1651
+ if (!sourceDocument?.implementation?.createHTMLDocument) {
1652
+ throw new TypeError("createContentView requires an HTML DOM implementation");
1653
+ }
1654
+ const inertDocument = sourceDocument.implementation.createHTMLDocument("");
1655
+ const cloneToLive = /* @__PURE__ */ new WeakMap();
1656
+ const liveToClone = /* @__PURE__ */ new WeakMap();
1657
+ const sourceRoot = context.nodeType === 9 ? context.documentElement : context;
1658
+ if (exclude) inertDocument.documentElement.matches(exclude);
1659
+ const capabilityMatch = capabilitySelector(capability);
1660
+ const root = importTree(sourceRoot, inertDocument, capability, capabilityMatch, exclude, sourceRoot, inherit, { cloneToLive, liveToClone });
1661
+ if (context.nodeType === 9 && root) {
1662
+ inertDocument.replaceChild(root, inertDocument.documentElement);
1663
+ liveToClone.set(context, inertDocument);
1664
+ cloneToLive.set(inertDocument, context);
1665
+ }
1666
+ const assertSelector = (selector) => {
1667
+ if (UNSUPPORTED_SELECTOR.test(selector)) {
1668
+ throw new Error(`Filtered content queries do not support stateful selector: ${selector}`);
1669
+ }
1670
+ };
1671
+ return {
1672
+ root,
1673
+ document: inertDocument,
1674
+ capability,
1675
+ selector: capabilitySelector(capability),
1676
+ cloneToLive,
1677
+ liveToClone,
1678
+ original(node) {
1679
+ return cloneToLive.get(node) || null;
1680
+ },
1681
+ cloneOf(node) {
1682
+ return liveToClone.get(node) || null;
1683
+ },
1684
+ query(selector, queryRoot = root) {
1685
+ assertSelector(selector);
1686
+ return Array.from(queryRoot.querySelectorAll(selector), (node) => cloneToLive.get(node) || node);
1687
+ },
1688
+ text(node = root) {
1689
+ const projected = node === root ? root : liveToClone.get(node);
1690
+ return projected?.textContent || "";
1691
+ },
1692
+ html(node = root) {
1693
+ const projected = node === root ? root : liveToClone.get(node);
1694
+ return projected?.innerHTML ?? "";
1695
+ },
1696
+ clone(node = root) {
1697
+ const projected = node === root ? root : liveToClone.get(node);
1698
+ return projected ? inertDocument.importNode(projected, true) : null;
1699
+ }
1700
+ };
1701
+ }
1702
+
1703
+ // src/richclay.js
1704
+ var EDITOR_UI_SELECTOR = capabilitySelector("history");
1705
+ function stripIncomingEditorUi(html, doc) {
1706
+ const template = doc.createElement("template");
1707
+ template.innerHTML = String(html || "");
1708
+ for (const node of template.content.querySelectorAll(EDITOR_UI_SELECTOR)) node.remove();
1709
+ return template.innerHTML;
1710
+ }
1711
+ function clipboardHTML(html, doc) {
1712
+ return stripIncomingEditorUi(html, doc);
1713
+ }
1714
+ function clipboardText(html, doc) {
1715
+ const template = doc.createElement("template");
1716
+ template.innerHTML = clipboardHTML(html, doc);
1717
+ const blocks = /* @__PURE__ */ new Set(["ADDRESS", "ARTICLE", "ASIDE", "BLOCKQUOTE", "DIV", "DL", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HR", "LI", "MAIN", "NAV", "OL", "P", "PRE", "SECTION", "TABLE", "UL"]);
1718
+ let text = "";
1719
+ const visit = (node) => {
1720
+ if (node.nodeType === 3) text += node.data;
1721
+ else if (node.nodeType === 1 && node.tagName === "BR") text += "\n";
1722
+ else {
1723
+ const block = node.nodeType === 1 && blocks.has(node.tagName);
1724
+ if (block && text && !text.endsWith("\n")) text += "\n";
1725
+ for (const child of node.childNodes || []) visit(child);
1726
+ if (block && text && !text.endsWith("\n")) text += "\n";
1727
+ }
1728
+ };
1729
+ visit(template.content);
1730
+ return text.replace(/\n{3,}/g, "\n\n").replace(/^\n|\n$/g, "");
1731
+ }
1732
+ function serializeEditorRoot(root, editor, range = null) {
1733
+ const view = createContentView(root, { capability: "history", inherit: false });
1734
+ const clone = view.root;
1735
+ const boundaryFor = (container, offset) => {
1736
+ while (container && !view.cloneOf(container)) {
1737
+ const parent = container.parentNode;
1738
+ if (!parent) return { container: clone, offset: 0 };
1739
+ offset = Array.from(parent.childNodes).indexOf(container);
1740
+ container = parent;
1741
+ }
1742
+ const projected = view.cloneOf(container) || clone;
1743
+ if (container?.nodeType === 3) return { container: projected, offset: Math.min(offset, projected.length) };
1744
+ const before = Array.from(container?.childNodes || []).slice(0, offset).filter((node) => view.cloneOf(node)).length;
1745
+ return { container: projected, offset: Math.min(before, projected.childNodes.length) };
1746
+ };
1747
+ const insert = (id, container, offset) => {
1748
+ const point = boundaryFor(container, offset);
1749
+ const marker = clone.ownerDocument.createElement("input");
1750
+ marker.id = id;
1751
+ marker.type = "hidden";
1752
+ const markerRange = clone.ownerDocument.createRange();
1753
+ markerRange.setStart(point.container, point.offset);
1754
+ markerRange.collapse(true);
1755
+ markerRange.insertNode(marker);
1756
+ };
1757
+ if (range) {
1758
+ insert(editor.endSelectionId, range.endContainer, range.endOffset);
1759
+ insert(editor.startSelectionId, range.startContainer, range.startOffset);
1760
+ } else {
1761
+ for (const id of [editor.startSelectionId, editor.endSelectionId]) {
1762
+ if (clone.querySelector(`#${id}`)) continue;
1763
+ const marker = root.querySelector(`#${id}`);
1764
+ if (!marker) continue;
1765
+ insert(id, marker.parentNode, Array.from(marker.parentNode.childNodes).indexOf(marker));
1766
+ }
1767
+ }
1768
+ return clone.innerHTML;
1769
+ }
1770
+ var instances = /* @__PURE__ */ new WeakMap();
1771
+ var autoInitWindows = /* @__PURE__ */ new WeakSet();
1772
+ var watchedWindows = /* @__PURE__ */ new WeakSet();
1773
+ var globalRegistry = createDefaultRegistry();
1774
+ var KEEP_FOCUS = Symbol("richclay-keep-focus");
1775
+ var ROOT_TRANSFER_EVENTS = ["cut", "paste", "drop"];
1776
+ var MODIFIER_ORDER = ["Alt", "Ctrl", "Meta", "Shift"];
1777
+ var dialogSeq = 0;
1778
+ var defaultOptions = {
1779
+ toolbar: "standard",
1780
+ toolbarContainer: null,
1781
+ sanitize: {},
1782
+ placeholder: "",
1783
+ readOnly: false,
1784
+ hyperclay: "auto",
1785
+ onChange: null,
1786
+ Squire: null,
1787
+ inline: false,
1788
+ singleLine: false,
1789
+ toolbarOnSelect: false
1790
+ };
1791
+ var RichClay = class _RichClay {
1792
+ static presets = presets;
1793
+ static selector = RICHCLAY_SELECTOR;
1794
+ constructor(element, options = {}) {
1795
+ if (!element) throw new Error("RichClay requires an element.");
1796
+ const existing = instances.get(element);
1797
+ if (existing) return existing;
1798
+ this.element = element;
1799
+ this.unsupported = conflictsWithExistingEditor(element) || refuseUnsupportedRoot(element);
1800
+ const derived = { ...parseEditableOptions(element), ...options };
1801
+ this.options = { ...defaultOptions, ...derived };
1802
+ if (this.options.singleLine && !("toolbar" in derived)) {
1803
+ this.options.toolbar = "inline";
1804
+ }
1805
+ this._blockTag = this.options.inline ? "DIV" : "P";
1806
+ this.registry = new Map(globalRegistry);
1807
+ this.toolbar = null;
1808
+ this.float = null;
1809
+ this._onToolbarKey = null;
1810
+ this._onFloatFocusOut = null;
1811
+ this._onDocPointerDown = null;
1812
+ this.liveRegion = null;
1813
+ this.description = null;
1814
+ this.dialog = null;
1815
+ this.dialogCleanup = null;
1816
+ this.savedSelection = null;
1817
+ this.path = "";
1818
+ this.active = false;
1819
+ this._squire = null;
1820
+ this._squireListeners = [];
1821
+ this._onBeforeInput = null;
1822
+ this._onRootBeforeInput = null;
1823
+ this._onRootKeydown = null;
1824
+ this._onRootTransfer = null;
1825
+ this._shortcutKeys = [];
1826
+ this._appleDeleteKeys = /* @__PURE__ */ new Set();
1827
+ this._authoredPres = /* @__PURE__ */ new WeakSet();
1828
+ this._warnedInlineBlock = false;
1829
+ this._onFocus = () => {
1830
+ this.element.classList.add("richclay-focused");
1831
+ if (this.options.inline) this.ensureFloatingToolbar();
1832
+ };
1833
+ this._onBlur = (event) => {
1834
+ this.element.classList.remove("richclay-focused");
1835
+ if (this.options.inline) this.scheduleFloatTeardown(event);
1836
+ };
1837
+ this._onLinkActivation = (event) => {
1838
+ if (!this.active || this.options.readOnly) return;
1839
+ const target = event.target?.nodeType === 1 ? event.target : event.target?.parentElement;
1840
+ const anchor = target?.closest("a[href]");
1841
+ if (!anchor) return;
1842
+ const chrome = closestWithCapability(target, "history");
1843
+ if (chrome && chrome !== this.element && this.element.contains(chrome) && chrome.contains(anchor)) return;
1844
+ event.preventDefault();
1845
+ };
1846
+ this.ensureMarker();
1847
+ this.hyperclay = shouldUseHyperclay(this.options, this.window);
1848
+ if (this.hyperclay) installHyperclayBridge(this.window);
1849
+ instances.set(element, this);
1850
+ this.sanitizeConfig = this.options.inline ? inlineSanitizeConfig(this.options.sanitize) : this.options.sanitize;
1851
+ this.sanitizer = createSanitizer(this.sanitizeConfig, this.element.ownerDocument);
1852
+ if (!this.options.inline) {
1853
+ sanitizeElement(this.element, this.sanitizeConfig);
1854
+ }
1855
+ if (shouldActivateEditor(this.options, this.window)) {
1856
+ this.activate();
1857
+ }
1858
+ }
1859
+ get squire() {
1860
+ return this._squire;
1861
+ }
1862
+ get window() {
1863
+ return this.element.ownerDocument.defaultView || window;
1864
+ }
1865
+ static init(selector = RICHCLAY_SELECTOR, options = {}) {
1866
+ const guard = selector === RICHCLAY_SELECTOR ? isRichClayHost : () => true;
1867
+ const elements = resolveElements(selector).filter(guard).filter((element) => !conflictsWithExistingEditor(element));
1868
+ return elements.map((element) => new _RichClay(element, options));
1869
+ }
1870
+ static autoInit(win = typeof window !== "undefined" ? window : void 0) {
1871
+ if (!win || !win.document || autoInitWindows.has(win)) return;
1872
+ autoInitWindows.add(win);
1873
+ const run = () => {
1874
+ if (shouldUseHyperclay({}, win) && isHyperclayEditMode(win)) {
1875
+ _RichClay.init();
1876
+ _RichClay.watch(win);
1877
+ }
1878
+ };
1879
+ if (win.document.readyState === "loading") {
1880
+ win.document.addEventListener("DOMContentLoaded", run, { once: true });
1881
+ } else {
1882
+ run();
1883
+ }
1884
+ }
1885
+ static watch(win = typeof window !== "undefined" ? window : void 0, options = {}) {
1886
+ if (!win || !win.document || watchedWindows.has(win)) return;
1887
+ watchedWindows.add(win);
1888
+ const mount = (element) => {
1889
+ if (!isMountable(element)) return;
1890
+ if (!instances.has(element) && !conflictsWithExistingEditor(element)) new _RichClay(element, options);
1891
+ };
1892
+ const unmount = (element) => instances.get(element)?.destroy();
1893
+ const observer = new win.MutationObserver((records) => {
1894
+ records.forEach((record) => {
1895
+ if (record.type === "attributes") {
1896
+ const target = record.target;
1897
+ if (isMountable(target)) mount(target);
1898
+ else unmount(target);
1899
+ return;
1900
+ }
1901
+ record.addedNodes.forEach((node) => {
1902
+ if (node.nodeType !== 1) return;
1903
+ if (node.matches?.(RICHCLAY_SELECTOR)) mount(node);
1904
+ node.querySelectorAll?.(RICHCLAY_SELECTOR).forEach(mount);
1905
+ });
1906
+ record.removedNodes.forEach((node) => {
1907
+ if (node.nodeType !== 1) return;
1908
+ const teardown = (el) => {
1909
+ if (!el.isConnected) instances.get(el)?.destroy();
1910
+ };
1911
+ if (node.matches?.(RICHCLAY_SELECTOR)) teardown(node);
1912
+ node.querySelectorAll?.(RICHCLAY_SELECTOR).forEach(teardown);
1913
+ });
1914
+ });
1915
+ });
1916
+ observer.observe(win.document.documentElement || win.document, {
1917
+ childList: true,
1918
+ subtree: true,
1919
+ attributes: true,
1920
+ attributeFilter: ["editable", "clay-editable", "data-richclay", "richclay"]
1921
+ });
1922
+ }
1923
+ static registerButton(def) {
1924
+ validateButton(def);
1925
+ globalRegistry.set(def.id, def);
1926
+ return _RichClay;
1927
+ }
1928
+ static unregisterButton(id) {
1929
+ globalRegistry.delete(id);
1930
+ return _RichClay;
1931
+ }
1932
+ static stripFromClone(docElem) {
1933
+ stripRichClayFromClone(docElem);
1934
+ }
1935
+ // Remove richclay's runtime state from ONE element, addressed by element
1936
+ // rather than by instance.
1937
+ //
1938
+ // A host that grows a list by cloning a row clones an active region with it,
1939
+ // and the copy carries contenteditable, the marker and the runtime classes.
1940
+ // Usually there is no editor behind them and destroy() cannot reach it,
1941
+ // because destroy belongs to an instance and that clone has none. But a clone
1942
+ // carrying the author's own data-richclay is mountable, so the watcher can
1943
+ // have given it an instance before the host gets here: this cannot assume the
1944
+ // element is instanceless.
1945
+ static stripElement(element) {
1946
+ if (!element || typeof element.getAttribute !== "function") return;
1947
+ const existing = instances.get(element);
1948
+ if (existing) {
1949
+ existing.destroy();
1950
+ return;
1951
+ }
1952
+ removeRuntimeState(element, "destroy");
1953
+ }
1954
+ registerButton(def) {
1955
+ validateButton(def);
1956
+ this.registry.set(def.id, def);
1957
+ if (this.active) this.renderToolbar();
1958
+ return this;
1959
+ }
1960
+ unregisterButton(id) {
1961
+ this.registry.delete(id);
1962
+ if (this.active) this.renderToolbar();
1963
+ return this;
1964
+ }
1965
+ activate() {
1966
+ if (this.unsupported) return;
1967
+ if (this.active) return;
1968
+ this.active = true;
1969
+ this.applyInlineBox();
1970
+ ensureStyles(this.element.ownerDocument);
1971
+ consumeInertContenteditable(this.element);
1972
+ const initialNodes = this.options.inline ? Array.from(this.element.childNodes) : null;
1973
+ const initialHTML = this.options.inline ? null : this.element.innerHTML;
1974
+ const Squire = this.options.Squire || this.window.Squire || globalThis.Squire;
1975
+ if (!Squire) {
1976
+ throw new Error("RichClay requires Squire. Load vendor/squire.js before richclay.js.");
1977
+ }
1978
+ this.setupEditorAttributes();
1979
+ this.liveRegion = createLiveRegion(this.element.ownerDocument);
1980
+ this._squire = new Squire(this.element, {
1981
+ blockTag: this._blockTag,
1982
+ serializeRoot: serializeEditorRoot,
1983
+ willCutCopy: (html) => clipboardHTML(html, this.element.ownerDocument),
1984
+ toPlainText: (html) => clipboardText(html, this.element.ownerDocument),
1985
+ replaceRoot: (root, html, editor) => {
1986
+ const morph = this.window.clay?.morph || this.window.hyperclay?.morph;
1987
+ if (typeof morph !== "function") {
1988
+ if (root.querySelector('[editor-ui], [clay~="editor-ui"]')) {
1989
+ throw new Error("RichClay requires a retention-aware morph host to replay history containing editor-ui.");
1990
+ }
1991
+ root.innerHTML = html;
1992
+ return;
1993
+ }
1994
+ morph(root, html, {
1995
+ morphStyle: "innerHTML",
1996
+ restoreFocus: false,
1997
+ scripts: { handle: false, merge: false },
1998
+ policy: "history"
1999
+ });
2000
+ editor._ensureBottomLine();
2001
+ editor._mutation?.takeRecords();
2002
+ },
2003
+ onMutations: (records, editor) => {
2004
+ const excluded = (node, capability) => {
2005
+ const boundary = closestWithCapability(node, capability);
2006
+ return !!boundary && boundary !== this.element && this.element.contains(boundary);
2007
+ };
2008
+ let authored = false;
2009
+ let local = false;
2010
+ for (const record of records) {
2011
+ if (excluded(record.target, "history")) continue;
2012
+ const nodes = record.type === "childList" ? [...record.addedNodes, ...record.removedNodes] : [record.target];
2013
+ for (const node of nodes) {
2014
+ if (excluded(node, "history")) continue;
2015
+ if (excluded(record.target, "undo") || excluded(node, "undo")) local = true;
2016
+ else authored = true;
2017
+ }
2018
+ }
2019
+ if (authored) {
2020
+ const ignored = editor._ignoreChange;
2021
+ editor._docWasChanged();
2022
+ if (local && ignored) editor.fireEvent("input");
2023
+ } else if (local) {
2024
+ editor._mayHaveZWS = true;
2025
+ editor.fireEvent("input");
2026
+ } else editor._ignoreChange = false;
2027
+ },
2028
+ sanitizeToDOMFragment: (html, editor) => {
2029
+ const fragment = this.sanitizer.sanitizeToDOMFragment(stripIncomingEditorUi(html, this.element.ownerDocument), editor);
2030
+ return this.options.singleLine ? flattenFragmentToSingleLine(fragment) : fragment;
2031
+ },
2032
+ didError: (error) => {
2033
+ console.error("RichClay/Squire error", error);
2034
+ }
2035
+ });
2036
+ if (this.options.inline) {
2037
+ this.element.replaceChildren(...initialNodes);
2038
+ this.resetSquireUndoBaseline();
2039
+ } else {
2040
+ this._squire.setHTML(initialHTML);
2041
+ }
2042
+ this.bindSquire();
2043
+ if (this.options.singleLine) this.installSingleLineGuards();
2044
+ if (this.options.inline && !this.options.singleLine) this.installRootGuards();
2045
+ this.installAppleDeleteKeys();
2046
+ if (this.blocksStayOut()) this._squire._ensureBottomLine = () => {
2047
+ };
2048
+ this.maskSquireCodeShortcut();
2049
+ this.maskSquireBlockShortcuts();
2050
+ this.installShortcuts();
2051
+ if (this.options.inline) {
2052
+ this._onToolbarKey = (event) => {
2053
+ if (event.altKey && event.key === "F10") {
2054
+ event.preventDefault();
2055
+ this.ensureFloatingToolbar();
2056
+ this.float?.toolbar.focusFirst();
2057
+ }
2058
+ };
2059
+ this.element.addEventListener("keydown", this._onToolbarKey);
2060
+ }
2061
+ if (this.blocksStayOut()) {
2062
+ const willPaste = (event) => {
2063
+ const fragment = event.detail?.fragment;
2064
+ if (!fragment) return;
2065
+ const doc = this.element.ownerDocument;
2066
+ flattenBlocks(
2067
+ fragment,
2068
+ () => this.options.singleLine ? doc.createTextNode(" ") : doc.createElement("br")
2069
+ );
2070
+ };
2071
+ this._squire.addEventListener("willPaste", willPaste);
2072
+ this._squireListeners.push(["willPaste", willPaste]);
2073
+ }
2074
+ this.renderToolbar();
2075
+ this.updatePlaceholder();
2076
+ this.warnOnBlockInInlineRegion();
2077
+ this.element.querySelectorAll("pre").forEach((pre) => this._authoredPres.add(pre));
2078
+ }
2079
+ // Live sync morphs the page against an incoming copy, and that copy is clean:
2080
+ // contenteditable, the marker and the runtime attributes all come back off an
2081
+ // element this editor is still pointing at. The instance is fine and Squire is
2082
+ // still bound; only the element's own state is gone, and activate() cannot put
2083
+ // it back because it returns early on an instance that never stopped being
2084
+ // active. Rebuilding is not an option for a host that adopted an editor the
2085
+ // author mounted, so this re-applies the state instead.
2086
+ //
2087
+ // The marker is in here because it is the one whose absence is fatal rather
2088
+ // than cosmetic: without it the watcher stops recognising the element, which
2089
+ // is the failure ensureMarker's own comment describes.
2090
+ //
2091
+ // Returns whether it had to do anything, so a caller can tell a repair from a
2092
+ // no-op without asking the same question twice.
2093
+ reattach() {
2094
+ if (this.unsupported || !this.active) return false;
2095
+ const intact = this.element.getAttribute("contenteditable") === "true" && this.element.getAttribute("data-richclay-active") === "true" && isMountable(this.element);
2096
+ if (intact) return false;
2097
+ this.ensureMarker();
2098
+ this.applyInlineBox();
2099
+ consumeInertContenteditable(this.element);
2100
+ this.setupEditorAttributes();
2101
+ return true;
2102
+ }
2103
+ // A block inside an inline element renders as a run-on. This changes the box
2104
+ // and nothing else: whether a region survives a reload is decided by the
2105
+ // parser from tag names, before any CSS exists. Runtime only, so the author's
2106
+ // file keeps no style richclay put there.
2107
+ applyInlineBox() {
2108
+ if (isInlineTag(this.element) && !this.element.style.display) {
2109
+ this.element.style.display = "inline-block";
2110
+ this.element.setAttribute("data-richclay-runtime-display", "true");
2111
+ }
2112
+ }
2113
+ getHTML() {
2114
+ return this._squire ? this._squire.getHTML() : this.element.innerHTML;
2115
+ }
2116
+ setHTML(html) {
2117
+ if (this._squire) {
2118
+ this._squire.setHTML(String(html || ""));
2119
+ } else {
2120
+ this.element.innerHTML = sanitizeHTML(
2121
+ html,
2122
+ this.sanitizeConfig,
2123
+ this.element.ownerDocument
2124
+ );
2125
+ }
2126
+ this.updatePlaceholder();
2127
+ this.toolbar?.update();
2128
+ this.options.onChange?.(this.getHTML());
2129
+ return this;
2130
+ }
2131
+ focus() {
2132
+ if (this._squire) this._squire.focus();
2133
+ else this.element.focus();
2134
+ return this;
2135
+ }
2136
+ destroy() {
2137
+ this.closeLinkDialog();
2138
+ this.teardownFloatingToolbar();
2139
+ this.toolbar?.destroy();
2140
+ this.toolbar = null;
2141
+ if (this._onToolbarKey) {
2142
+ this.element.removeEventListener("keydown", this._onToolbarKey);
2143
+ this._onToolbarKey = null;
2144
+ }
2145
+ this.liveRegion?.remove();
2146
+ this.liveRegion = null;
2147
+ this.description?.remove();
2148
+ this.description = null;
2149
+ this._shortcutKeys.forEach((key) => this._squire?.setKeyHandler?.(key, null));
2150
+ this._squireListeners.forEach(([type, listener]) => {
2151
+ this._squire?.removeEventListener?.(type, listener);
2152
+ });
2153
+ this._squireListeners = [];
2154
+ this._shortcutKeys = [];
2155
+ if (this._onBeforeInput) {
2156
+ this.element.ownerDocument.removeEventListener("beforeinput", this._onBeforeInput, true);
2157
+ this._onBeforeInput = null;
2158
+ }
2159
+ const doc = this.element.ownerDocument;
2160
+ if (this._onRootBeforeInput) {
2161
+ doc.removeEventListener("beforeinput", this._onRootBeforeInput, true);
2162
+ this._onRootBeforeInput = null;
2163
+ }
2164
+ if (this._onRootKeydown) {
2165
+ doc.removeEventListener("keydown", this._onRootKeydown, true);
2166
+ this._onRootKeydown = null;
2167
+ }
2168
+ if (this._onRootTransfer) {
2169
+ ROOT_TRANSFER_EVENTS.forEach((type) => doc.removeEventListener(type, this._onRootTransfer, true));
2170
+ this._onRootTransfer = null;
2171
+ }
2172
+ this._squire?.destroy?.();
2173
+ this._squire = null;
2174
+ this.cleanupEditorAttributes();
2175
+ instances.delete(this.element);
2176
+ }
2177
+ saveSelection() {
2178
+ this.savedSelection = getSquireSelection(this._squire) || this.savedSelection;
2179
+ return this.savedSelection;
2180
+ }
2181
+ restoreSelection() {
2182
+ return restoreSquireSelection(this._squire, this.savedSelection);
2183
+ }
2184
+ // Squire's invariant is established on the first real edit rather than at
2185
+ // activation, so that opening a page in edit mode never rewrites it. These
2186
+ // listeners capture on the document, so the root is valid before Squire's own
2187
+ // handlers see the event. History inputs are skipped: repairing mid-undo would
2188
+ // fight the stack it is replaying.
2189
+ installRootGuards() {
2190
+ const doc = this.element.ownerDocument;
2191
+ const owns = (event) => this.element.contains(event.target);
2192
+ this._onRootBeforeInput = (event) => {
2193
+ if (!owns(event) || event.isComposing || /^history/.test(event.inputType || "")) return;
2194
+ this.ensureRootIsEditable();
2195
+ };
2196
+ this._onRootKeydown = (event) => {
2197
+ if (!owns(event) || event.defaultPrevented || event.isComposing) return;
2198
+ if (this.isEditingKey(event)) this.ensureRootIsEditable();
2199
+ };
2200
+ this._onRootTransfer = (event) => {
2201
+ if (owns(event)) this.ensureRootIsEditable();
2202
+ };
2203
+ doc.addEventListener("beforeinput", this._onRootBeforeInput, true);
2204
+ doc.addEventListener("keydown", this._onRootKeydown, true);
2205
+ ROOT_TRANSFER_EVENTS.forEach((type) => {
2206
+ doc.addEventListener(type, this._onRootTransfer, true);
2207
+ });
2208
+ }
2209
+ // Re-checked rather than latched, because Hyperclay's live sync morphs new DOM
2210
+ // into the region long after activation and can reintroduce a loose text node.
2211
+ ensureRootIsEditable() {
2212
+ if (!this._squire || this.options.singleLine) return;
2213
+ const wrapBareRoot = this.options.inline;
2214
+ const blocksAllowed = !this.blocksStayOut();
2215
+ if (!editorRootNeedsNormalization(this.element, { wrapBareRoot, blocksAllowed })) return;
2216
+ const range = getSquireSelection(this._squire);
2217
+ const saved = range ? captureRange(this.element, range) : null;
2218
+ const repair = () => {
2219
+ normalizeEditorRoot(this.element, {
2220
+ blockTag: this._blockTag,
2221
+ wrapBareRoot,
2222
+ blocksAllowed,
2223
+ onBareRootWrapped: (root, wrapper) => {
2224
+ console.warn(
2225
+ `richclay: wrapped this region's loose text in a <div> so multi-line editing works. Wrap the content in your own block element to keep the markup you wrote, or use editable="single-line" if it is meant to be one line.`,
2226
+ root,
2227
+ wrapper
2228
+ );
2229
+ }
2230
+ });
2231
+ };
2232
+ if (typeof this._squire.modifyDocument === "function") this._squire.modifyDocument(repair);
2233
+ else repair();
2234
+ if (saved) {
2235
+ restoreRange(this.element, range, saved);
2236
+ restoreSquireSelection(this._squire, range);
2237
+ this.savedSelection = cloneRange(range);
2238
+ }
2239
+ }
2240
+ // Only keys that will actually edit. Repairing is not free on a root that has
2241
+ // not been repaired yet, which is every freshly opened page, so a blanket
2242
+ // "any single-character key" made Cmd+C and Cmd+S rewrite an untouched region.
2243
+ // The only modified keys that do edit are the ones this editor rebound itself,
2244
+ // which is why the set is asked rather than Squire's dispatch mirrored.
2245
+ isEditingKey(event) {
2246
+ if (["Backspace", "Delete", "Enter"].includes(event.key)) return true;
2247
+ if (event.key === "Tab") return this.caretIsInList();
2248
+ if (event.key.length !== 1) return false;
2249
+ if (!event.ctrlKey && !event.metaKey && !event.altKey) return true;
2250
+ return event.ctrlKey && !event.metaKey && !event.altKey && this._appleDeleteKeys.has(event.key.toLowerCase());
2251
+ }
2252
+ caretIsInList() {
2253
+ const range = this._squire?.getSelection();
2254
+ const container = range?.startContainer;
2255
+ if (!container) return false;
2256
+ const element = container.nodeType === 1 ? container : container.parentElement;
2257
+ return Boolean(element && this.element.contains(element) && element.closest("ul, ol"));
2258
+ }
2259
+ runControl(def) {
2260
+ if (!this._squire || typeof def.run !== "function") return;
2261
+ if (def.isDisabled?.(this)) return;
2262
+ this.restoreSelection();
2263
+ if (def.mutates !== false) this.ensureRootIsEditable();
2264
+ const result = def.run(this);
2265
+ this.element.querySelectorAll("pre:not([data-richclay-pre])").forEach((pre) => {
2266
+ if (!this._authoredPres.has(pre)) pre.setAttribute("data-richclay-pre", "");
2267
+ });
2268
+ if (def.mutates !== false) {
2269
+ this.ensureRootIsEditable();
2270
+ this.anchorSelectionInBlock();
2271
+ }
2272
+ this.saveSelection();
2273
+ this.toolbar?.update();
2274
+ this.updatePlaceholder();
2275
+ this.announceControl(def);
2276
+ if (result !== KEEP_FOCUS) {
2277
+ this.focus();
2278
+ }
2279
+ this.warnOnBlockInInlineRegion();
2280
+ }
2281
+ // Squire's modifyBlocks leaves the caret anchored on the root itself, between
2282
+ // blocks. From there getStartBlockOfRange finds no block, so the next block
2283
+ // command is a silent no-op — the "it stopped working" the user hit. Push a
2284
+ // collapsed caret back inside the block it is sitting in front of.
2285
+ anchorSelectionInBlock() {
2286
+ const range = getSquireSelection(this._squire);
2287
+ if (!range?.collapsed || range.startContainer !== this.element) return;
2288
+ const children = Array.from(this.element.childNodes);
2289
+ const atEnd = range.startOffset >= children.length;
2290
+ const child = atEnd ? children.filter(isCaretHost).pop() : children.slice(range.startOffset).find(isCaretHost) || children.slice(0, range.startOffset).filter(isCaretHost).pop();
2291
+ if (!child) return;
2292
+ const edge = caretEdge(child, !atEnd);
2293
+ range.setStart(edge, atEnd ? nodeLength(edge) : 0);
2294
+ range.collapse(true);
2295
+ restoreSquireSelection(this._squire, range);
2296
+ }
2297
+ selectionHasFormat(tag) {
2298
+ if (!tag || !this._squire?.hasFormat) return false;
2299
+ try {
2300
+ return this._squire.hasFormat(tag);
2301
+ } catch {
2302
+ return false;
2303
+ }
2304
+ }
2305
+ // Squire sets its path to "(selection)" whenever the selection spans more than
2306
+ // one node, so asking the path answered "no" for every multi-block selection.
2307
+ // Both boundaries must land in the same matching element, or a selection that
2308
+ // merely starts in a list would report as being in one.
2309
+ pathHas(tag) {
2310
+ const range = getSquireSelection(this._squire);
2311
+ if (!range) return false;
2312
+ const selector = tag.toLowerCase();
2313
+ const start = matchAtBoundary(this.element, range.startContainer, range.startOffset, true, selector);
2314
+ return Boolean(start) && start === matchAtBoundary(this.element, range.endContainer, range.endOffset, false, selector);
2315
+ }
2316
+ // Blocks stay out of this region for either of two reasons, and both are fixed
2317
+ // for the editor's life, which is why the toolbar can leave the controls out
2318
+ // rather than grey them. Either the parser would eject a block on the next page
2319
+ // load, or the author wrote the region as a line of text and richclay does not
2320
+ // rewrite what they wrote.
2321
+ blocksStayOut() {
2322
+ return this.options.singleLine || ejectsBlocks(this.element) || keepsTextShape(this.element);
2323
+ }
2324
+ // Advisory only: the markup is stable and saves correctly, it just will not
2325
+ // validate. Checked when the editor mounts and after any command, which covers
2326
+ // the ways an author actually produces one. Paste is deliberately not covered:
2327
+ // nothing hooks paste any more, and a console note is not worth reinstating a
2328
+ // hook that took three rounds to get wrong.
2329
+ warnOnBlockInInlineRegion() {
2330
+ if (this._warnedInlineBlock || !isInlineTag(this.element)) return;
2331
+ if (!hasBlockDescendant(this.element)) return;
2332
+ this._warnedInlineBlock = true;
2333
+ const tag = this.element.nodeName.toLowerCase();
2334
+ console.warn(
2335
+ `richclay: a block element inside <${tag} editable> is not valid HTML. It stays where it is and saves correctly, but a validator will flag it. Put the editable attribute on a block element if you want blocks here.`,
2336
+ this.element
2337
+ );
2338
+ }
2339
+ toggleFormat(tag, addMethod, removeMethod) {
2340
+ if (this.selectionHasFormat(tag)) return this._squire[removeMethod]();
2341
+ return this._squire[addMethod]();
2342
+ }
2343
+ toggleList(tag) {
2344
+ if (this.pathHas(tag)) return this._squire.removeList();
2345
+ return tag === "UL" ? this._squire.makeUnorderedList() : this._squire.makeOrderedList();
2346
+ }
2347
+ indent() {
2348
+ if (this.pathHas("UL") || this.pathHas("OL")) return this._squire.increaseListLevel();
2349
+ return this._squire.increaseQuoteLevel();
2350
+ }
2351
+ outdent() {
2352
+ if (this.pathHas("UL") || this.pathHas("OL")) return this._squire.decreaseListLevel();
2353
+ return this._squire.decreaseQuoteLevel();
2354
+ }
2355
+ setBlockType(tag) {
2356
+ const target = tag.toUpperCase();
2357
+ if (target === "BLOCKQUOTE") return this._squire.increaseQuoteLevel();
2358
+ return this._squire.modifyBlocks((fragment) => {
2359
+ const doc = this.element.ownerDocument;
2360
+ const output = doc.createDocumentFragment();
2361
+ Array.from(fragment.childNodes).forEach((node) => {
2362
+ if (node.nodeType !== 1) {
2363
+ output.appendChild(node);
2364
+ return;
2365
+ }
2366
+ if (node.nodeName === target) {
2367
+ output.appendChild(node);
2368
+ return;
2369
+ }
2370
+ const replacement = doc.createElement(target);
2371
+ while (node.firstChild) replacement.appendChild(node.firstChild);
2372
+ output.appendChild(replacement);
2373
+ });
2374
+ return output;
2375
+ });
2376
+ }
2377
+ openLinkDialog() {
2378
+ this.saveSelection();
2379
+ this.closeLinkDialog();
2380
+ const doc = this.element.ownerDocument;
2381
+ const existingLink = this.currentLinkElement();
2382
+ const existing = existingLink?.getAttribute("href") || "";
2383
+ const dialog = doc.createElement("form");
2384
+ const labelId = `richclay-link-label-${++dialogSeq}`;
2385
+ dialog.className = "richclay-dialog";
2386
+ dialog.setAttribute("role", "dialog");
2387
+ dialog.setAttribute("aria-modal", "true");
2388
+ dialog.setAttribute("aria-labelledby", labelId);
2389
+ dialog.setAttribute("data-richclay-dialog", "");
2390
+ markChrome(dialog);
2391
+ dialog.setAttribute("save-remove", "");
2392
+ dialog.innerHTML = `
2393
+ <div class="richclay-dialog-title" id="${labelId}">${existingLink ? "Edit link" : "Insert link"}</div>
2394
+ <label class="richclay-field">
2395
+ <span>URL</span>
2396
+ <input class="richclay-input" name="url" type="text" inputmode="url" autocomplete="url" placeholder="https://example.com">
2397
+ </label>
2398
+ <div class="richclay-dialog-actions">
2399
+ ${existingLink ? '<button type="button" class="richclay-secondary" data-richclay-remove-link>Remove link</button>' : ""}
2400
+ <button type="button" class="richclay-secondary" data-richclay-cancel>Cancel</button>
2401
+ <button type="submit" class="richclay-primary">Apply</button>
2402
+ </div>
2403
+ `;
2404
+ const close = () => {
2405
+ this.closeLinkDialog();
2406
+ this.toolbar?.update();
2407
+ this.focus();
2408
+ };
2409
+ const remove = () => {
2410
+ this.restoreSelection();
2411
+ const anchor = this.currentLinkElement();
2412
+ if (anchor) {
2413
+ this.selectElement(anchor);
2414
+ this._squire.removeLink();
2415
+ announce(this.liveRegion, "Link removed");
2416
+ }
2417
+ close();
2418
+ };
2419
+ dialog.addEventListener("submit", (event) => {
2420
+ event.preventDefault();
2421
+ const data = new FormData(dialog);
2422
+ const value = String(data.get("url") || "").trim();
2423
+ if (!value && existingLink) {
2424
+ remove();
2425
+ return;
2426
+ }
2427
+ const href = normalizeUrl(value);
2428
+ if (!href) {
2429
+ announce(this.liveRegion, "Enter a valid URL");
2430
+ return;
2431
+ }
2432
+ this.restoreSelection();
2433
+ const anchor = this.currentLinkElement();
2434
+ if (anchor) {
2435
+ this.selectElement(anchor);
2436
+ this._squire.saveUndoState();
2437
+ anchor.setAttribute("href", href);
2438
+ } else {
2439
+ this._squire.makeLink(href);
2440
+ }
2441
+ announce(this.liveRegion, "Link applied");
2442
+ close();
2443
+ });
2444
+ dialog.querySelector("[data-richclay-remove-link]")?.addEventListener("click", remove);
2445
+ dialog.querySelector("[data-richclay-cancel]").addEventListener("click", close);
2446
+ dialog.addEventListener("keydown", (event) => {
2447
+ if (event.key === "Escape") {
2448
+ event.preventDefault();
2449
+ close();
2450
+ } else if (event.key === "Tab") {
2451
+ trapTab(event, dialog);
2452
+ }
2453
+ });
2454
+ const host = this.element.closest("dialog") || doc.body;
2455
+ const layered = typeof dialog.showPopover === "function";
2456
+ if (layered) dialog.setAttribute("popover", "manual");
2457
+ pin(dialog, "position", "fixed");
2458
+ pin(dialog, "inset", "0px auto auto 0px");
2459
+ pin(dialog, "margin", "0");
2460
+ pin(dialog, "overflow", "visible");
2461
+ host.appendChild(dialog);
2462
+ if (layered) dialog.showPopover();
2463
+ this.dialog = dialog;
2464
+ const win = doc.defaultView || globalThis;
2465
+ const reposition = () => this.positionLinkDialog();
2466
+ const dismiss = (event) => {
2467
+ if (!dialog.contains(event.target)) this.closeLinkDialog();
2468
+ };
2469
+ const resizeObserver = typeof win.ResizeObserver === "function" ? new win.ResizeObserver(reposition) : null;
2470
+ resizeObserver?.observe(dialog);
2471
+ doc.addEventListener("scroll", reposition, { capture: true, passive: true });
2472
+ win.addEventListener("resize", reposition, { passive: true });
2473
+ win.visualViewport?.addEventListener("resize", reposition, { passive: true });
2474
+ win.visualViewport?.addEventListener("scroll", reposition, { passive: true });
2475
+ doc.addEventListener("pointerdown", dismiss, true);
2476
+ this.dialogCleanup = () => {
2477
+ resizeObserver?.disconnect();
2478
+ doc.removeEventListener("scroll", reposition, { capture: true });
2479
+ win.removeEventListener("resize", reposition);
2480
+ win.visualViewport?.removeEventListener("resize", reposition);
2481
+ win.visualViewport?.removeEventListener("scroll", reposition);
2482
+ doc.removeEventListener("pointerdown", dismiss, true);
2483
+ };
2484
+ this.positionLinkDialog();
2485
+ const input = dialog.querySelector("input");
2486
+ input.value = existing;
2487
+ input.focus();
2488
+ input.select();
2489
+ return KEEP_FOCUS;
2490
+ }
2491
+ currentLinkElement() {
2492
+ const range = getSquireSelection(this._squire);
2493
+ if (!range || !this.element.contains(range.commonAncestorContainer)) return null;
2494
+ if (range.collapsed) {
2495
+ const node = range.startContainer;
2496
+ const anchor = (node.nodeType === 1 ? node : node.parentElement)?.closest("a[href]");
2497
+ return anchor && anchor !== this.element && this.element.contains(anchor) ? anchor : null;
2498
+ }
2499
+ for (const anchor of this.element.querySelectorAll("a[href]")) {
2500
+ const contents = anchor.ownerDocument.createRange();
2501
+ let first = anchor;
2502
+ let last = anchor;
2503
+ while (first.firstChild) first = first.firstChild;
2504
+ while (last.lastChild) last = last.lastChild;
2505
+ contents.setStart(first, 0);
2506
+ contents.setEnd(last, last.nodeType === 3 ? last.length : last.childNodes.length);
2507
+ if (range.compareBoundaryPoints(range.END_TO_START, contents) < 0 && range.compareBoundaryPoints(range.START_TO_END, contents) > 0) return anchor;
2508
+ }
2509
+ return null;
2510
+ }
2511
+ currentLinkHref() {
2512
+ const anchor = this.currentLinkElement();
2513
+ return anchor ? anchor.getAttribute("href") || "" : "";
2514
+ }
2515
+ selectElement(node) {
2516
+ const range = node.ownerDocument.createRange();
2517
+ range.selectNode(node);
2518
+ this._squire.setSelection(range);
2519
+ this.saveSelection();
2520
+ return range;
2521
+ }
2522
+ // The selected text, or the caret, as the dialog's anchor. A saved range keeps
2523
+ // tracking the text it covers, so this stays right as the page scrolls.
2524
+ linkAnchorRect() {
2525
+ const range = this.savedSelection;
2526
+ if (typeof range?.getClientRects === "function") {
2527
+ const rects = [...range.getClientRects()].filter((rect) => rect.width || rect.height);
2528
+ if (rects.length) return range.collapsed ? rects[0] : range.getBoundingClientRect();
2529
+ }
2530
+ const button = this.toolbar?.items.find((item) => item.def.id === "link")?.button;
2531
+ return (button || this.toolbar?.root || this.element).getBoundingClientRect();
2532
+ }
2533
+ positionLinkDialog() {
2534
+ const dialog = this.dialog;
2535
+ if (!dialog) return;
2536
+ const win = dialog.ownerDocument.defaultView || globalThis;
2537
+ const placement = placePopover({
2538
+ anchor: this.linkAnchorRect(),
2539
+ box: { width: dialog.offsetWidth || 0, height: dialog.offsetHeight || 0 },
2540
+ viewport: {
2541
+ width: win.visualViewport?.width ?? win.innerWidth,
2542
+ height: win.visualViewport?.height ?? win.innerHeight
2543
+ }
2544
+ });
2545
+ dialog.setAttribute("data-richclay-side", placement.side);
2546
+ dialog.style.setProperty("--richclay-dialog-arrow", `${Math.round(placement.arrow)}px`);
2547
+ pin(dialog, "transform", `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px)`);
2548
+ }
2549
+ closeLinkDialog() {
2550
+ this.dialogCleanup?.();
2551
+ this.dialogCleanup = null;
2552
+ this.dialog?.remove();
2553
+ this.dialog = null;
2554
+ }
2555
+ ensureMarker() {
2556
+ if (!isMountable(this.element)) {
2557
+ this.element.setAttribute("data-richclay-runtime-marker", "true");
2558
+ this.element.setAttribute("data-richclay", "");
2559
+ }
2560
+ }
2561
+ setupEditorAttributes() {
2562
+ this.element.classList.add(
2563
+ this.options.inline ? "richclay-inline" : "richclay-editor",
2564
+ "richclay-active"
2565
+ );
2566
+ this.element.setAttribute("data-richclay-active", "true");
2567
+ if (!this.element.hasAttribute("data-richclay-runtime-contenteditable")) {
2568
+ const original = this.element.getAttribute("contenteditable");
2569
+ if (original === null) {
2570
+ this.element.setAttribute("data-richclay-runtime-contenteditable", "true");
2571
+ } else if (original !== "true" && original !== "") {
2572
+ this.element.setAttribute("data-richclay-runtime-contenteditable", original);
2573
+ }
2574
+ }
2575
+ this.element.setAttribute("contenteditable", "true");
2576
+ if (!this.element.hasAttribute("role")) {
2577
+ setRuntimeAttribute(this.element, "role", "textbox", "data-richclay-runtime-role");
2578
+ }
2579
+ if (!this.element.hasAttribute("aria-multiline")) {
2580
+ setRuntimeAttribute(
2581
+ this.element,
2582
+ "aria-multiline",
2583
+ this.options.singleLine ? "false" : "true",
2584
+ "data-richclay-runtime-aria-multiline"
2585
+ );
2586
+ }
2587
+ if (!this.element.hasAttribute("no-undo")) {
2588
+ setRuntimeAttribute(this.element, "no-undo", "", "data-richclay-runtime-no-undo");
2589
+ }
2590
+ if (this.options.placeholder) {
2591
+ this.element.setAttribute("data-richclay-placeholder", this.options.placeholder);
2592
+ if (!this.description) {
2593
+ this.description = this.element.ownerDocument.createElement("div");
2594
+ this.description.id = `richclay-placeholder-${++dialogSeq}`;
2595
+ this.description.className = "richclay-sr-only";
2596
+ this.description.textContent = this.options.placeholder;
2597
+ this.description.setAttribute("data-richclay-live", "");
2598
+ markChrome(this.description);
2599
+ }
2600
+ if (!this.description.isConnected) {
2601
+ this.element.insertAdjacentElement("afterend", this.description);
2602
+ }
2603
+ connectDescription(this.element, this.description);
2604
+ }
2605
+ this.element.addEventListener("focus", this._onFocus);
2606
+ this.element.addEventListener("blur", this._onBlur);
2607
+ this.element.addEventListener("click", this._onLinkActivation, true);
2608
+ this.element.addEventListener("auxclick", this._onLinkActivation, true);
2609
+ }
2610
+ cleanupEditorAttributes() {
2611
+ removeRuntimeState(this.element, "destroy");
2612
+ this.element.removeEventListener("focus", this._onFocus);
2613
+ this.element.removeEventListener("blur", this._onBlur);
2614
+ this.element.removeEventListener("click", this._onLinkActivation, true);
2615
+ this.element.removeEventListener("auxclick", this._onLinkActivation, true);
2616
+ }
2617
+ bindSquire() {
2618
+ const input = () => {
2619
+ this.saveSelection();
2620
+ this.updatePlaceholder();
2621
+ this.options.onChange?.(this.getHTML());
2622
+ this.toolbar?.update();
2623
+ };
2624
+ const pathChange = (event) => {
2625
+ this.path = event.detail?.path || this._squire.getPath?.() || "";
2626
+ this.saveSelection();
2627
+ this.toolbar?.update();
2628
+ };
2629
+ const selection = () => {
2630
+ this.saveSelection();
2631
+ this.toolbar?.update();
2632
+ if (this.options.toolbarOnSelect) this.updateFloatVisibility();
2633
+ };
2634
+ const undoState = () => this.toolbar?.update();
2635
+ [
2636
+ ["input", input],
2637
+ ["pathChange", pathChange],
2638
+ ["select", selection],
2639
+ ["cursor", selection],
2640
+ ["undoStateChange", undoState]
2641
+ ].forEach(([type, listener]) => {
2642
+ this._squire.addEventListener(type, listener);
2643
+ this._squireListeners.push([type, listener]);
2644
+ });
2645
+ }
2646
+ renderToolbar() {
2647
+ if (!this.active || this.options.readOnly) return;
2648
+ if (this.options.inline) {
2649
+ if (this.float) {
2650
+ this.teardownFloatingToolbar();
2651
+ this.ensureFloatingToolbar();
2652
+ }
2653
+ return;
2654
+ }
2655
+ this.toolbar?.destroy();
2656
+ this.toolbar = null;
2657
+ const controls = this.resolveToolbarControls(this.options.toolbar);
2658
+ if (!controls.length) return;
2659
+ this.toolbar = new Toolbar(this, controls, {
2660
+ toolbarContainer: this.options.toolbarContainer
2661
+ });
2662
+ }
2663
+ ensureFloatingToolbar() {
2664
+ if (this.float || !this.active || this.options.readOnly) return;
2665
+ const controls = this.resolveToolbarControls(this.options.toolbar);
2666
+ if (!controls.length) return;
2667
+ this.float = new FloatingToolbar(this, controls);
2668
+ this._onFloatFocusOut = (event) => this.scheduleFloatTeardown(event);
2669
+ this.float.root.addEventListener("focusout", this._onFloatFocusOut);
2670
+ this._onDocPointerDown = (event) => {
2671
+ if (this.ownsFocusTarget(event.target)) return;
2672
+ const doc = this.element.ownerDocument;
2673
+ if (doc.activeElement && this.ownsFocusTarget(doc.activeElement)) {
2674
+ doc.activeElement.blur?.();
2675
+ }
2676
+ this.teardownFloatingToolbar();
2677
+ };
2678
+ this.element.ownerDocument.addEventListener("pointerdown", this._onDocPointerDown, true);
2679
+ this.toolbar = this.float.toolbar;
2680
+ this.toolbar.update();
2681
+ if (this.options.toolbarOnSelect) this.updateFloatVisibility();
2682
+ }
2683
+ teardownFloatingToolbar() {
2684
+ if (!this.float) return;
2685
+ if (this._onFloatFocusOut) {
2686
+ this.float.root.removeEventListener("focusout", this._onFloatFocusOut);
2687
+ this._onFloatFocusOut = null;
2688
+ }
2689
+ if (this._onDocPointerDown) {
2690
+ this.element.ownerDocument.removeEventListener("pointerdown", this._onDocPointerDown, true);
2691
+ this._onDocPointerDown = null;
2692
+ }
2693
+ this.float.destroy();
2694
+ this.float = null;
2695
+ this.toolbar = null;
2696
+ }
2697
+ scheduleFloatTeardown(event) {
2698
+ const next = event?.relatedTarget;
2699
+ if (next && this.ownsFocusTarget(next)) return;
2700
+ const doc = this.element.ownerDocument;
2701
+ const win = doc.defaultView || globalThis;
2702
+ win.setTimeout(() => {
2703
+ const active = doc.activeElement;
2704
+ if (active && this.ownsFocusTarget(active)) return;
2705
+ this.teardownFloatingToolbar();
2706
+ }, 0);
2707
+ }
2708
+ ownsFocusTarget(node) {
2709
+ return node === this.element || this.element.contains(node) || Boolean(this.float?.root.contains(node)) || Boolean(this.dialog?.contains(node));
2710
+ }
2711
+ updateFloatVisibility() {
2712
+ if (!this.float) return;
2713
+ const range = this.savedSelection;
2714
+ this.float.setVisible(Boolean(range && !range.collapsed));
2715
+ }
2716
+ resolveToolbarControls(toolbar) {
2717
+ if (toolbar === false || toolbar === null || toolbar === "none") return [];
2718
+ const requested = Array.isArray(toolbar) ? toolbar : presets[toolbar] || presets.standard;
2719
+ return requested.map((item) => {
2720
+ if (typeof item === "string") {
2721
+ const def = this.registry.get(item);
2722
+ if (!def) throw new Error(`Unknown RichClay toolbar control: ${item}`);
2723
+ return def;
2724
+ }
2725
+ if (item?.type === "separator") return item;
2726
+ validateButton(item);
2727
+ return item;
2728
+ });
2729
+ }
2730
+ installShortcuts() {
2731
+ const seen = /* @__PURE__ */ new Set();
2732
+ const registryDefs = [...this.registry.values()].filter(
2733
+ (def) => !this.options.singleLine || presets.inline.includes(def.id)
2734
+ );
2735
+ const defs = [...this.resolveToolbarControls(this.options.toolbar), ...registryDefs];
2736
+ defs.forEach((def) => {
2737
+ if (def.type === "menu" || def.type === "separator") return;
2738
+ if (!def.shortcut || !def.id || seen.has(def.id)) return;
2739
+ if (def.isDisabled?.(this)) {
2740
+ const masked = shortcutKey(def.shortcut, this.window);
2741
+ this._squire.setKeyHandler(masked, null);
2742
+ this._shortcutKeys.push(masked);
2743
+ return;
2744
+ }
2745
+ seen.add(def.id);
2746
+ const key = shortcutKey(def.shortcut, this.window);
2747
+ this._squire.setKeyHandler(key, (squire, event) => {
2748
+ event.preventDefault();
2749
+ this.runControl(def);
2750
+ });
2751
+ this._shortcutKeys.push(key);
2752
+ });
2753
+ }
2754
+ // macOS sends Ctrl+D and Ctrl+H to the browser's own forward/backward delete,
2755
+ // which merges two blocks by wrapping the moved text in a computed-style
2756
+ // <span> — permanent junk in an editor whose DOM is the saved document. Point
2757
+ // them at Squire's own handlers so they behave exactly like Delete and
2758
+ // Backspace. Reads Squire's key handler table; no-ops for other engines.
2759
+ // Squire binds Code to the platform modifier itself, on _keyHandlers' prototype,
2760
+ // so dropping richclay's own shortcut is not enough to retire it. An own
2761
+ // property of null shadows the inherited handler and the key falls through.
2762
+ maskSquireCodeShortcut() {
2763
+ const key = `${isApplePlatform(this.window) ? "Meta" : "Ctrl"}-d`;
2764
+ this._squire.setKeyHandler(key, null);
2765
+ this._shortcutKeys.push(key);
2766
+ }
2767
+ // Squire binds these on _keyHandlers' prototype, and richclay has no definition
2768
+ // carrying some of them, so filtering richclay's own shortcuts cannot reach
2769
+ // them: Mod+] kept building a <blockquote> in a <p editable> through every
2770
+ // round. An own property of null shadows the inherited handler and the key
2771
+ // falls through. The prefix must match the one Squire computed, which is why
2772
+ // the test harness has to agree about the platform.
2773
+ maskSquireBlockShortcuts() {
2774
+ if (!this.blocksStayOut()) return;
2775
+ const mod = isApplePlatform(this.window) ? "Meta" : "Ctrl";
2776
+ [`${mod}-]`, `${mod}-[`, `${mod}-Shift-8`, `${mod}-Shift-9`].forEach((key) => {
2777
+ this._squire.setKeyHandler(key, null);
2778
+ this._shortcutKeys.push(key);
2779
+ });
2780
+ }
2781
+ // Squire's toggleCode() makes a block <pre> when the selection is collapsed,
2782
+ // which inside a heading or a paragraph root means a code block where no block
2783
+ // can legally go. Those regions get inline <code> instead.
2784
+ toggleCode() {
2785
+ if (!this.blocksStayOut()) return this._squire.toggleCode();
2786
+ if (this.selectionHasFormat("CODE")) return this._squire.changeFormat(null, { tag: "CODE" });
2787
+ return this._squire.changeFormat({ tag: "CODE" }, null);
2788
+ }
2789
+ installAppleDeleteKeys() {
2790
+ if (!isApplePlatform(this.window)) return;
2791
+ const handlers = this._squire._keyHandlers;
2792
+ if (!handlers) return;
2793
+ [["Ctrl-d", "Delete"], ["Ctrl-h", "Backspace"]].forEach(([key, native]) => {
2794
+ if (typeof handlers[native] !== "function") return;
2795
+ this._squire.setKeyHandler(key, handlers[native]);
2796
+ this._shortcutKeys.push(key);
2797
+ this._appleDeleteKeys.add(key.slice("Ctrl-".length));
2798
+ });
2799
+ }
2800
+ installSingleLineGuards() {
2801
+ ["Enter", "Shift-Enter"].forEach((key) => {
2802
+ this._squire.setKeyHandler(key, (squire, event) => event.preventDefault());
2803
+ this._shortcutKeys.push(key);
2804
+ });
2805
+ this._onBeforeInput = (event) => {
2806
+ if (!this.element.contains(event.target)) return;
2807
+ if (event.inputType === "insertParagraph" || event.inputType === "insertLineBreak") {
2808
+ event.preventDefault();
2809
+ event.stopPropagation();
2810
+ }
2811
+ };
2812
+ this.element.ownerDocument.addEventListener("beforeinput", this._onBeforeInput, true);
2813
+ }
2814
+ // The fidelity restore bypasses setHTML, so Squire's undo baseline is still
2815
+ // the empty document its constructor recorded; the first undo could wipe the
2816
+ // region. Rebase the stack on the restored content. This touches Squire
2817
+ // internals (property names survive its esbuild minify) and no-ops for other
2818
+ // engines, like the tests' FakeSquire.
2819
+ resetSquireUndoBaseline() {
2820
+ const squire = this._squire;
2821
+ if (!squire || !Array.isArray(squire._undoStack) || typeof squire.saveUndoState !== "function") {
2822
+ return;
2823
+ }
2824
+ squire._undoStack.length = 0;
2825
+ squire._undoStackLength = 0;
2826
+ squire._undoIndex = -1;
2827
+ squire._isInUndoState = false;
2828
+ squire.saveUndoState();
2829
+ }
2830
+ updatePlaceholder() {
2831
+ const view = createContentView(this.element, { capability: "history", inherit: false });
2832
+ const text = view.text().replace(/\u200B/g, "").trim();
2833
+ const hasMeaningfulElement = view.root.querySelector("img, video, audio, iframe, table");
2834
+ const isEmpty = !text && !hasMeaningfulElement;
2835
+ this.element.classList.toggle("richclay-empty", isEmpty);
2836
+ }
2837
+ announceControl(def) {
2838
+ if (!this.liveRegion) return;
2839
+ const label = def.ariaLabel || def.label;
2840
+ if (!label) return;
2841
+ if (typeof def.isActive === "function") {
2842
+ announce(this.liveRegion, `${label} ${def.isActive(this) ? "on" : "off"}`);
2843
+ } else {
2844
+ announce(this.liveRegion, `${label} applied`);
2845
+ }
2846
+ }
2847
+ };
2848
+ function resolveElements(selector) {
2849
+ if (typeof selector === "string") {
2850
+ return Array.from(document.querySelectorAll(selector));
2851
+ }
2852
+ if (selector?.nodeType === 1) return [selector];
2853
+ return Array.from(selector || []);
2854
+ }
2855
+ function matchAtBoundary(root, container, offset, first, selector) {
2856
+ let node = container;
2857
+ if (node === root) {
2858
+ const children = Array.from(root.childNodes).filter(isCaretHost);
2859
+ if (!children.length) return null;
2860
+ const index = first ? Math.min(offset, children.length - 1) : Math.min(Math.max(offset - 1, 0), children.length - 1);
2861
+ node = caretEdge(children[index], first);
2862
+ }
2863
+ const element = node?.nodeType === 1 ? node : node?.parentElement;
2864
+ const match = element?.closest?.(selector);
2865
+ return match && root.contains(match) && match !== root ? match : null;
2866
+ }
2867
+ function closestConflictingAncestor(element) {
2868
+ let node = element.parentElement;
2869
+ while (node) {
2870
+ if (instances.has(node) || isMountable(node)) return node;
2871
+ node = node.parentElement;
2872
+ }
2873
+ return null;
2874
+ }
2875
+ function isMountable(element) {
2876
+ return Boolean(element.matches?.(RICHCLAY_SELECTOR)) && isRichClayHost(element);
2877
+ }
2878
+ function conflictsWithExistingEditor(element) {
2879
+ const host = closestConflictingAncestor(element);
2880
+ const nested = Array.from(element.querySelectorAll?.(RICHCLAY_SELECTOR) || []).find(
2881
+ (node) => instances.has(node)
2882
+ );
2883
+ const other = host || nested;
2884
+ if (!other) return false;
2885
+ console.warn(
2886
+ "richclay: nested editable regions are not supported, so this one was skipped. Remove the editable attribute from either it or the other region.",
2887
+ element,
2888
+ other
2889
+ );
2890
+ return true;
2891
+ }
2892
+ function refuseUnsupportedRoot(element) {
2893
+ if (!isUnsupportedRootTag(element)) return false;
2894
+ console.warn(`richclay: ${unsupportedRootReason(element)} This region was skipped.`, element);
2895
+ return true;
2896
+ }
2897
+ function validateButton(def) {
2898
+ if (!def || !def.id) throw new Error("RichClay button definitions require an id.");
2899
+ if (def.type !== "menu" && typeof def.run !== "function") {
2900
+ throw new Error(`RichClay button "${def.id}" requires a run(editor) function.`);
2901
+ }
2902
+ }
2903
+ function shortcutKey(shortcut, win) {
2904
+ const parts = shortcut.split("+");
2905
+ const keyPart = parts[parts.length - 1];
2906
+ const modifiers = new Set(parts.slice(0, -1).map(normalizeModifier));
2907
+ if (modifiers.delete("Mod")) modifiers.add(isApplePlatform(win) ? "Meta" : "Ctrl");
2908
+ const key = keyPart.length === 1 ? modifiers.has("Shift") ? keyPart.toUpperCase() : keyPart.toLowerCase() : keyPart;
2909
+ const prefix = MODIFIER_ORDER.filter((modifier) => modifiers.has(modifier)).join("-");
2910
+ return `${prefix ? `${prefix}-` : ""}${key}`;
2911
+ }
2912
+ function normalizeModifier(modifier) {
2913
+ return modifier === "Cmd" ? "Meta" : modifier;
2914
+ }
2915
+ function trapTab(event, container) {
2916
+ const focusables = container.querySelectorAll(
2917
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
2918
+ );
2919
+ if (!focusables.length) return;
2920
+ const first = focusables[0];
2921
+ const last = focusables[focusables.length - 1];
2922
+ const active = container.ownerDocument.activeElement;
2923
+ if (event.shiftKey && active === first) {
2924
+ event.preventDefault();
2925
+ last.focus();
2926
+ } else if (!event.shiftKey && active === last) {
2927
+ event.preventDefault();
2928
+ first.focus();
2929
+ }
2930
+ }
2931
+
2932
+ // richclay.css
2933
+ 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";
2934
+
2935
+ // src/browser-global.js
2936
+ setRichClayStyles(richclay_default);
2937
+ var browser_global_default = RichClay;
2938
+ return __toCommonJS(browser_global_exports);
2939
+ })();
2940
+ window.RichClay = RichClayBundle.default;