@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,2962 @@
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 S = 'stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"';
29
+ var STHIN = 'stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"';
30
+ var F = 'fill="currentColor"';
31
+ var svg = (inner, viewBox = "0 0 18 18") => `<svg viewBox="${viewBox}" width="18" height="18" aria-hidden="true" focusable="false">${inner}</svg>`;
32
+ var linkInner = `<line ${S} x1="7" x2="11" y1="7" y2="11"/><path ${S} d="M8.9,4.577a3.476,3.476,0,0,1,.36,4.679A3.476,3.476,0,0,1,4.577,8.9C3.185,7.5,2.035,6.4,4.217,4.217S7.5,3.185,8.9,4.577Z"/><path ${S} d="M13.423,9.1a3.476,3.476,0,0,0-4.679-.36,3.476,3.476,0,0,0,.36,4.679c1.392,1.392,2.5,2.542,4.679.36S14.815,10.5,13.423,9.1Z"/>`;
33
+ var icons = {
34
+ bold: svg(`<path ${S} d="M5,4H9.5A2.5,2.5,0,0,1,12,6.5v0A2.5,2.5,0,0,1,9.5,9H5A0,0,0,0,1,5,9V4A0,0,0,0,1,5,4Z"/><path ${S} d="M5,9h5.5A2.5,2.5,0,0,1,13,11.5v0A2.5,2.5,0,0,1,10.5,14H5a0,0,0,0,1,0,0V9A0,0,0,0,1,5,9Z"/>`),
35
+ italic: svg(`<line ${S} x1="7" x2="13" y1="4" y2="4"/><line ${S} x1="5" x2="11" y1="14" y2="14"/><line ${S} x1="8" x2="10" y1="14" y2="4"/>`),
36
+ underline: svg(`<path ${S} d="M5,3V9a4.012,4.012,0,0,0,4,4H9a4.012,4.012,0,0,0,4-4V3"/><rect ${F} height="1" rx="0.5" ry="0.5" width="12" x="3" y="15"/>`),
37
+ strike: svg(`<path ${F} d="M9.007,8C6.542,7.791,6,7.519,6,6.5,6,5.792,7.283,5,9,5c1.571,0,2.765.679,2.969,1.309a1,1,0,0,0,1.9-.617C13.356,4.106,11.354,3,9,3,6.2,3,4,4.538,4,6.5a3.2,3.2,0,0,0,.5,1.843Z"/><path ${F} d="M8.984,10C11.457,10.208,12,10.479,12,11.5c0,0.708-1.283,1.5-3,1.5-1.571,0-2.765-.679-2.969-1.309a1,1,0,1,0-1.9.617C4.644,13.894,6.646,15,9,15c2.8,0,5-1.538,5-3.5a3.2,3.2,0,0,0-.5-1.843Z"/><line x1="2" y1="9" x2="16" y2="9" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="1.5"/>`),
38
+ link: svg(linkInner),
39
+ unlink: svg(`${linkInner}<line class="richclay-cut" x1="2.5" y1="15.5" x2="15.5" y2="2.5" stroke-linecap="round" stroke-width="3.5"/><line x1="4.06" y1="13.94" x2="13.94" y2="4.06" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="1.5"/>`),
40
+ ul: svg(`<line ${S} x1="6" x2="15" y1="4" y2="4"/><line ${S} x1="6" x2="15" y1="9" y2="9"/><line ${S} x1="6" x2="15" y1="14" y2="14"/><line ${S} x1="3" x2="3" y1="4" y2="4"/><line ${S} x1="3" x2="3" y1="9" y2="9"/><line ${S} x1="3" x2="3" y1="14" y2="14"/>`),
41
+ ol: svg(`<line ${S} x1="7" x2="15" y1="4" y2="4"/><line ${S} x1="7" x2="15" y1="9" y2="9"/><line ${S} x1="7" x2="15" y1="14" y2="14"/><line ${STHIN} x1="2.5" x2="4.5" y1="5.5" y2="5.5"/><path ${F} d="M3.5,6A0.5,0.5,0,0,1,3,5.5V3.085l-0.276.138A0.5,0.5,0,0,1,2.053,3c-0.124-.247-0.023-0.324.224-0.447l1-.5A0.5,0.5,0,0,1,4,2.5v3A0.5,0.5,0,0,1,3.5,6Z"/><path ${STHIN} d="M4.5,10.5h-2c0-.234,1.85-1.076,1.85-2.234A0.959,0.959,0,0,0,2.5,8.156"/><path ${STHIN} d="M2.5,14.846a0.959,0.959,0,0,0,1.85-.109A0.7,0.7,0,0,0,3.75,14a0.688,0.688,0,0,0,.6-0.736,0.959,0.959,0,0,0-1.85-.109"/>`),
42
+ quote: svg(`<path ${S} d="M10 10.8182L9 10.8182C8.80222 10.8182 8.60888 10.7649 8.44443 10.665C8.27998 10.5651 8.15181 10.4231 8.07612 10.257C8.00043 10.0909 7.98063 9.90808 8.01922 9.73174C8.0578 9.55539 8.15304 9.39341 8.29289 9.26627C8.43275 9.13913 8.61093 9.05255 8.80491 9.01747C8.99889 8.98239 9.19996 9.00039 9.38268 9.0692C9.56541 9.13801 9.72159 9.25453 9.83147 9.40403C9.94135 9.55353 10 9.72929 10 9.90909L10 12.1818C10 12.664 9.78929 13.1265 9.41421 13.4675C9.03914 13.8084 8.53043 14 8 14"/><path ${S} d="M16 10.8182L15 10.8182C14.8022 10.8182 14.6089 10.7649 14.4444 10.665C14.28 10.5651 14.1518 10.4231 14.0761 10.257C14.0004 10.0909 13.9806 9.90808 14.0192 9.73174C14.0578 9.55539 14.153 9.39341 14.2929 9.26627C14.4327 9.13913 14.6109 9.05255 14.8049 9.01747C14.9989 8.98239 15.2 9.00039 15.3827 9.0692C15.5654 9.13801 15.7216 9.25453 15.8315 9.40403C15.9414 9.55353 16 9.72929 16 9.90909L16 12.1818C16 12.664 15.7893 13.1265 15.4142 13.4675C15.0391 13.8084 14.5304 14 14 14"/>`, "2 2 20 20"),
43
+ undo: svg(`<polyline ${S} points="6.5 4 3.5 7 6.5 10"/><path ${S} d="M3.5 7H10a3.5 3.5 0 0 1 0 7H7"/>`),
44
+ redo: svg(`<polyline ${S} points="11.5 4 14.5 7 11.5 10"/><path ${S} d="M14.5 7H8a3.5 3.5 0 0 0 0 7h3"/>`),
45
+ clear: svg(`<line ${S} x1="5" x2="13" y1="3" y2="3"/><line ${S} x1="6" x2="9.35" y1="12" y2="3"/><line ${S} x1="11" x2="15" y1="11" y2="15"/><line ${S} x1="15" x2="11" y1="11" y2="15"/><rect ${F} height="1" rx="0.5" ry="0.5" width="7" x="2" y="14"/>`),
46
+ code: svg(`<polyline ${S} points="5 7 3 9 5 11"/><polyline ${S} points="13 7 15 9 13 11"/><line ${S} x1="10" x2="8" y1="5" y2="13"/>`),
47
+ indent: svg(`<line ${S} x1="3" x2="15" y1="4" y2="4"/><line ${S} x1="8" x2="15" y1="9" y2="9"/><line ${S} x1="3" x2="15" y1="14" y2="14"/><polyline ${S} points="3 7 5.5 9 3 11"/>`),
48
+ outdent: svg(`<line ${S} x1="3" x2="15" y1="4" y2="4"/><line ${S} x1="8" x2="15" y1="9" y2="9"/><line ${S} x1="3" x2="15" y1="14" y2="14"/><polyline ${S} points="5.5 7 3 9 5.5 11"/>`),
49
+ blocks: svg(`<line ${S} x1="3" x2="15" y1="4" y2="4"/><line ${S} x1="3" x2="15" y1="9" y2="9"/><line ${S} x1="3" x2="10" y1="14" y2="14"/>`)
50
+ };
51
+ var blocksOnly = (editor) => editor.blocksStayOut();
52
+ var notInsideLink = (editor) => Boolean(editor.element.closest("a"));
53
+ var presets = {
54
+ minimal: ["bold", "italic", "link", "unorderedList"],
55
+ inline: [
56
+ "bold",
57
+ "italic",
58
+ "underline",
59
+ "strikethrough",
60
+ "code",
61
+ "link",
62
+ "unlink",
63
+ "undo",
64
+ "redo",
65
+ "clearFormatting"
66
+ ],
67
+ standard: [
68
+ "blockMenu",
69
+ "bold",
70
+ "italic",
71
+ "underline",
72
+ "strikethrough",
73
+ "code",
74
+ "link",
75
+ "unlink",
76
+ "unorderedList",
77
+ "orderedList",
78
+ "quote",
79
+ "outdent",
80
+ "indent",
81
+ "undo",
82
+ "redo",
83
+ "clearFormatting"
84
+ ]
85
+ };
86
+ function createDefaultRegistry() {
87
+ return new Map(defaultButtons.map((button) => [button.id, button]));
88
+ }
89
+ function isApplePlatform(win = globalThis) {
90
+ const nav = win?.navigator;
91
+ if (!nav) return false;
92
+ return /Mac|iPhone|iPad|iPod/.test(nav.platform || nav.userAgent || "");
93
+ }
94
+ function formatShortcut(shortcut, win = globalThis) {
95
+ if (!shortcut) return "";
96
+ return shortcut.replace("Mod", isApplePlatform(win) ? "Cmd" : "Ctrl");
97
+ }
98
+ var defaultButtons = [
99
+ toggle("bold", "Bold", icons.bold, "Mod+B", "inline", (editor) => editor.toggleFormat("B", "bold", "removeBold")),
100
+ toggle(
101
+ "italic",
102
+ "Italic",
103
+ icons.italic,
104
+ "Mod+I",
105
+ "inline",
106
+ (editor) => editor.toggleFormat("I", "italic", "removeItalic")
107
+ ),
108
+ toggle(
109
+ "underline",
110
+ "Underline",
111
+ icons.underline,
112
+ "Mod+U",
113
+ "inline",
114
+ (editor) => editor.toggleFormat("U", "underline", "removeUnderline")
115
+ ),
116
+ toggle(
117
+ "strikethrough",
118
+ "Strikethrough",
119
+ icons.strike,
120
+ "Mod+Shift+7",
121
+ "inline",
122
+ (editor) => editor.toggleFormat("S", "strikethrough", "removeStrikethrough")
123
+ ),
124
+ // No shortcut. One keystroke turning a paragraph of prose into a code block is
125
+ // a sharp edge in an editor whose DOM is the saved file, and Mod+D also shadows
126
+ // the browser's own bookmark shortcut. The toolbar button stays.
127
+ toggle(
128
+ "code",
129
+ "Code",
130
+ icons.code,
131
+ null,
132
+ "inline",
133
+ (editor) => editor.toggleCode(),
134
+ (editor) => editor.selectionHasFormat("CODE") || editor.selectionHasFormat("PRE")
135
+ ),
136
+ {
137
+ id: "link",
138
+ label: "Link",
139
+ ariaLabel: "Insert or edit link",
140
+ icon: icons.link,
141
+ group: "links",
142
+ shortcut: "Mod+K",
143
+ mutates: false,
144
+ isDisabled: notInsideLink,
145
+ run: (editor) => editor.openLinkDialog(),
146
+ isActive: (editor) => editor.selectionHasFormat("A")
147
+ },
148
+ {
149
+ id: "unlink",
150
+ label: "Remove link",
151
+ ariaLabel: "Remove link",
152
+ icon: icons.unlink,
153
+ group: "links",
154
+ run: (editor) => editor.squire.removeLink()
155
+ },
156
+ toggle(
157
+ "unorderedList",
158
+ "Bulleted list",
159
+ icons.ul,
160
+ "Mod+Shift+8",
161
+ "lists",
162
+ (editor) => editor.toggleList("UL"),
163
+ (editor) => editor.pathHas("UL"),
164
+ blocksOnly
165
+ ),
166
+ toggle(
167
+ "orderedList",
168
+ "Numbered list",
169
+ icons.ol,
170
+ "Mod+Shift+9",
171
+ "lists",
172
+ (editor) => editor.toggleList("OL"),
173
+ (editor) => editor.pathHas("OL"),
174
+ blocksOnly
175
+ ),
176
+ toggle("quote", "Quote", icons.quote, null, "blocks", (editor) => {
177
+ if (editor.pathHas("BLOCKQUOTE")) return editor.squire.decreaseQuoteLevel();
178
+ return editor.squire.increaseQuoteLevel();
179
+ }, (editor) => editor.pathHas("BLOCKQUOTE"), blocksOnly),
180
+ {
181
+ id: "outdent",
182
+ label: "Outdent",
183
+ ariaLabel: "Decrease indent",
184
+ icon: icons.outdent,
185
+ group: "blocks",
186
+ shortcut: "Mod+[",
187
+ isDisabled: blocksOnly,
188
+ run: (editor) => editor.outdent()
189
+ },
190
+ {
191
+ id: "indent",
192
+ label: "Indent",
193
+ ariaLabel: "Increase indent",
194
+ icon: icons.indent,
195
+ group: "blocks",
196
+ shortcut: "Mod+]",
197
+ isDisabled: blocksOnly,
198
+ run: (editor) => editor.indent()
199
+ },
200
+ {
201
+ id: "undo",
202
+ label: "Undo",
203
+ ariaLabel: "Undo",
204
+ icon: icons.undo,
205
+ group: "history",
206
+ shortcut: "Mod+Z",
207
+ mutates: false,
208
+ run: (editor) => editor.squire.undo()
209
+ },
210
+ {
211
+ id: "redo",
212
+ label: "Redo",
213
+ ariaLabel: "Redo",
214
+ icon: icons.redo,
215
+ group: "history",
216
+ shortcut: "Mod+Shift+Z",
217
+ mutates: false,
218
+ run: (editor) => editor.squire.redo()
219
+ },
220
+ {
221
+ id: "clearFormatting",
222
+ label: "Clear formatting",
223
+ ariaLabel: "Clear formatting",
224
+ icon: icons.clear,
225
+ group: "cleanup",
226
+ run: (editor) => editor.squire.removeAllFormatting()
227
+ },
228
+ {
229
+ id: "blockMenu",
230
+ type: "menu",
231
+ label: "Block style",
232
+ ariaLabel: "Choose block style",
233
+ icon: icons.blocks,
234
+ group: "blocks",
235
+ isDisabled: blocksOnly,
236
+ options: [
237
+ blockOption("Paragraph", "P"),
238
+ blockOption("Heading 1", "H1"),
239
+ blockOption("Heading 2", "H2"),
240
+ blockOption("Heading 3", "H3"),
241
+ {
242
+ label: "Quote",
243
+ value: "BLOCKQUOTE",
244
+ isDisabled: blocksOnly,
245
+ run: (editor) => editor.squire.increaseQuoteLevel(),
246
+ isActive: (editor) => editor.pathHas("BLOCKQUOTE")
247
+ },
248
+ {
249
+ label: "Code block",
250
+ value: "PRE",
251
+ isDisabled: blocksOnly,
252
+ run: (editor) => editor.setBlockType("PRE"),
253
+ isActive: (editor) => editor.pathHas("PRE")
254
+ }
255
+ ]
256
+ }
257
+ ];
258
+ function toggle(id, label, icon, shortcut, group, run, isActive = (editor) => editor.selectionHasFormat(labelTag(id)), isDisabled) {
259
+ return {
260
+ id,
261
+ label,
262
+ ariaLabel: label,
263
+ icon,
264
+ group,
265
+ shortcut,
266
+ isDisabled,
267
+ run,
268
+ isActive
269
+ };
270
+ }
271
+ function labelTag(id) {
272
+ return {
273
+ bold: "B",
274
+ italic: "I",
275
+ underline: "U",
276
+ strikethrough: "S"
277
+ }[id];
278
+ }
279
+ function blockOption(label, tag) {
280
+ return {
281
+ label,
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 FOREIGN_INLINE_ROOTS = /* @__PURE__ */ new Set(["svg", "math"]);
297
+ function isInlineNode(node) {
298
+ const type = node.nodeType;
299
+ if (type === TEXT_NODE || type === COMMENT_NODE) return true;
300
+ if (type !== ELEMENT_NODE) return false;
301
+ if (FOREIGN_INLINE_ROOTS.has(node.nodeName)) return true;
302
+ if (!INLINE_NODE_NAMES.test(node.nodeName)) return false;
303
+ return Array.from(node.childNodes).every(isInlineNode);
304
+ }
305
+ function isBlockContainer(element) {
306
+ return Array.from(element.childNodes).some((node) => !isInlineNode(node));
307
+ }
308
+ var isBlockElement = (node) => node?.nodeType === ELEMENT_NODE && !FOREIGN_INLINE_ROOTS.has(node.nodeName) && !INLINE_NODE_NAMES.test(node.nodeName);
309
+ function flattenBlocks(parent, createBoundary) {
310
+ const children = Array.from(parent.childNodes);
311
+ let removed = 0;
312
+ children.forEach((child, index) => {
313
+ if (child.nodeType !== ELEMENT_NODE || FOREIGN_INLINE_ROOTS.has(child.nodeName)) return;
314
+ removed += flattenBlocks(child, createBoundary);
315
+ if (!isBlockElement(child)) return;
316
+ if (isBlockElement(children[index - 1])) parent.insertBefore(createBoundary(), child);
317
+ while (child.firstChild) parent.insertBefore(child.firstChild, child);
318
+ child.remove();
319
+ removed += 1;
320
+ });
321
+ return removed;
322
+ }
323
+ var TABLE_STRUCTURE = /* @__PURE__ */ new Set(["TABLE", "THEAD", "TBODY", "TFOOT", "TR", "COLGROUP"]);
324
+ function ejectsBlocks(root) {
325
+ return Boolean(root.closest?.("p")) || TABLE_STRUCTURE.has(root.nodeName);
326
+ }
327
+ var TEXT_LINE_ROOTS = /* @__PURE__ */ new Set([
328
+ "P",
329
+ "H1",
330
+ "H2",
331
+ "H3",
332
+ "H4",
333
+ "H5",
334
+ "H6",
335
+ "PRE",
336
+ "LABEL",
337
+ "LEGEND",
338
+ "LI",
339
+ "TD",
340
+ "TH",
341
+ "DT",
342
+ "DD",
343
+ "FIGCAPTION",
344
+ "SUMMARY",
345
+ "CAPTION"
346
+ ]);
347
+ function keepsTextShape(root) {
348
+ return TEXT_LINE_ROOTS.has(root.nodeName) || isInlineTag(root);
349
+ }
350
+ var VOID_ROOTS = /* @__PURE__ */ new Set([
351
+ "AREA",
352
+ "BASE",
353
+ "BR",
354
+ "COL",
355
+ "EMBED",
356
+ "HR",
357
+ "IMG",
358
+ "INPUT",
359
+ "LINK",
360
+ "META",
361
+ "PARAM",
362
+ "SOURCE",
363
+ "TRACK",
364
+ "WBR"
365
+ ]);
366
+ var RAW_TEXT_ROOTS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEXTAREA", "TITLE", "IFRAME", "NOSCRIPT", "XMP"]);
367
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
368
+ function isUnsupportedRootTag(root) {
369
+ 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;
370
+ }
371
+ function unsupportedRootReason(root) {
372
+ if (TABLE_STRUCTURE.has(root.nodeName)) {
373
+ return "a table element cannot be an editable region. Move the editable attribute to a <td>, a <th>, or an element inside one.";
374
+ }
375
+ if (root.namespaceURI && root.namespaceURI !== HTML_NS) {
376
+ 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.";
377
+ }
378
+ if (VOID_ROOTS.has(root.nodeName)) {
379
+ return "this element cannot contain anything, so there is nothing to edit. Move the editable attribute to an element that holds text.";
380
+ }
381
+ if (root.nodeName === "TEMPLATE") {
382
+ return "a <template> keeps its content out of the document, so edits to it are never saved.";
383
+ }
384
+ return "this element treats its content as plain text, so any edit would be saved as visible markup rather than formatting.";
385
+ }
386
+ function isInlineTag(node) {
387
+ return node.nodeType === ELEMENT_NODE && !FOREIGN_INLINE_ROOTS.has(node.nodeName) && INLINE_NODE_NAMES.test(node.nodeName);
388
+ }
389
+ function hasBlockDescendant(root) {
390
+ return Array.from(root.querySelectorAll("*")).some(isBlockElement);
391
+ }
392
+ function normalizeEditorRoot(root, options = {}) {
393
+ const {
394
+ blockTag = "DIV",
395
+ wrapBareRoot = false,
396
+ onBareRootWrapped,
397
+ blocksAllowed = !ejectsBlocks(root)
398
+ } = options;
399
+ dropFormattingWhitespace(root);
400
+ if (blocksAllowed) {
401
+ wrapStrayInlineChildren(root, blockTag, wrapBareRoot, onBareRootWrapped);
402
+ }
403
+ return root;
404
+ }
405
+ var needsWrapping = (node) => isInlineNode(node) && node.nodeType !== COMMENT_NODE;
406
+ var isAuthorContent = (node) => node.nodeType !== COMMENT_NODE && !(node.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(node.nodeValue));
407
+ function editorRootNeedsNormalization(root, { wrapBareRoot = false, blocksAllowed = !ejectsBlocks(root) } = {}) {
408
+ if (!blocksAllowed) return false;
409
+ if (!isBlockContainer(root)) return wrapBareRoot;
410
+ return Array.from(root.childNodes).some(needsWrapping) || hasNestedFormattingWhitespace(root);
411
+ }
412
+ function hasNestedFormattingWhitespace(element) {
413
+ return Array.from(element.children).some((child) => {
414
+ if (!isBlockContainer(child)) return false;
415
+ const ownWhitespace = !preservesWhitespace(child) && Array.from(child.childNodes).some(
416
+ (node) => node.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(node.nodeValue)
417
+ );
418
+ return ownWhitespace || hasNestedFormattingWhitespace(child);
419
+ });
420
+ }
421
+ function dropFormattingWhitespace(element) {
422
+ if (!isBlockContainer(element)) return;
423
+ if (!preservesWhitespace(element)) {
424
+ Array.from(element.childNodes).forEach((child) => {
425
+ if (child.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(child.nodeValue)) child.remove();
426
+ });
427
+ }
428
+ Array.from(element.children).forEach(dropFormattingWhitespace);
429
+ }
430
+ function wrapStrayInlineChildren(root, blockTag, wrapBareRoot, onBareRootWrapped) {
431
+ if (!isBlockContainer(root)) {
432
+ if (!wrapBareRoot) return;
433
+ const wrapper = root.ownerDocument.createElement(blockTag);
434
+ if (!Array.from(root.childNodes).some(isAuthorContent)) {
435
+ wrapper.appendChild(root.ownerDocument.createElement("BR"));
436
+ root.appendChild(wrapper);
437
+ return;
438
+ }
439
+ while (root.firstChild) wrapper.appendChild(root.firstChild);
440
+ root.appendChild(wrapper);
441
+ onBareRootWrapped?.(root, wrapper);
442
+ return;
443
+ }
444
+ let run = [];
445
+ const flush = () => {
446
+ const nodes = run;
447
+ run = [];
448
+ if (!nodes.some((node) => node.nodeType !== COMMENT_NODE)) return;
449
+ const block = root.ownerDocument.createElement(blockTag);
450
+ root.insertBefore(block, nodes[0]);
451
+ nodes.forEach((node) => block.appendChild(node));
452
+ };
453
+ Array.from(root.childNodes).forEach((child) => {
454
+ if (isInlineNode(child)) run.push(child);
455
+ else flush();
456
+ });
457
+ flush();
458
+ }
459
+ function preservesWhitespace(element) {
460
+ return element.nodeName === "PRE";
461
+ }
462
+ function captureRange(root, range) {
463
+ return {
464
+ start: captureBoundary(root, range.startContainer, range.startOffset),
465
+ end: captureBoundary(root, range.endContainer, range.endOffset),
466
+ collapsed: range.collapsed
467
+ };
468
+ }
469
+ function restoreRange(root, range, saved) {
470
+ applyBoundary(range, "setStart", root, saved.start);
471
+ if (saved.collapsed) range.collapse(true);
472
+ else applyBoundary(range, "setEnd", root, saved.end);
473
+ }
474
+ function captureBoundary(root, container, offset) {
475
+ if (!root.contains(container)) return { scope: root, after: [], before: [] };
476
+ if (container.nodeType === TEXT_NODE) {
477
+ if (!isDroppedWhitespace(container)) return { container, offset };
478
+ const scope = container.parentNode;
479
+ return siblingAnchor(scope, Array.from(scope.childNodes).indexOf(container));
480
+ }
481
+ return {
482
+ container,
483
+ offset,
484
+ childCount: container.childNodes.length,
485
+ ...siblingAnchor(container, offset)
486
+ };
487
+ }
488
+ function siblingAnchor(scope, index) {
489
+ const children = Array.from(scope.childNodes);
490
+ const at = Math.max(0, index);
491
+ return { scope, after: children.slice(at), before: children.slice(0, at).reverse() };
492
+ }
493
+ function applyBoundary(range, method, root, boundary) {
494
+ const { container, childCount } = boundary;
495
+ if (container && root.contains(container)) {
496
+ if (childCount === void 0 || childCount === container.childNodes.length) {
497
+ range[method](container, Math.min(boundary.offset, nodeLength(container)));
498
+ return;
499
+ }
500
+ }
501
+ const after = boundary.after?.find((node) => root.contains(node));
502
+ if (after) {
503
+ range[method](caretEdge(after, true), 0);
504
+ return;
505
+ }
506
+ const before = boundary.before?.find((node) => root.contains(node));
507
+ if (before) {
508
+ const edge = caretEdge(before, false);
509
+ range[method](edge, nodeLength(edge));
510
+ return;
511
+ }
512
+ const scope = boundary.scope && root.contains(boundary.scope) ? boundary.scope : root;
513
+ range[method](scope, 0);
514
+ }
515
+ function isDroppedWhitespace(node) {
516
+ return node.nodeType === TEXT_NODE && !NOT_WHITESPACE.test(node.nodeValue) && Boolean(node.parentNode) && node.parentNode.nodeType === ELEMENT_NODE && isBlockContainer(node.parentNode) && !preservesWhitespace(node.parentNode);
517
+ }
518
+ var isCaretHost = (node) => node.nodeType === ELEMENT_NODE || node.nodeType === TEXT_NODE;
519
+ function caretEdge(node, first = true) {
520
+ let current = node;
521
+ while (current.nodeType === ELEMENT_NODE && !FOREIGN_INLINE_ROOTS.has(current.nodeName)) {
522
+ const children = Array.from(current.childNodes).filter(isCaretHost);
523
+ const next = first ? children[0] : children[children.length - 1];
524
+ if (!next || LEAF_NODE_NAMES.has(next.nodeName)) break;
525
+ current = next;
526
+ }
527
+ return current;
528
+ }
529
+ function nodeLength(node) {
530
+ return node.nodeType === TEXT_NODE ? node.nodeValue.length : node.childNodes.length;
531
+ }
532
+
533
+ // src/hyperclay.js
534
+ var RICHCLAY_SELECTOR = "[data-richclay], [richclay], [editable], [clay-editable]";
535
+ var RICHCLAY_OPT_IN = "[data-richclay], [richclay], [clay-editable]";
536
+ var EDITABLE_ATTRS = ["editable", "clay-editable"];
537
+ var singleLineSelector = ':is([editable~="single-line"], [clay-editable~="single-line"])';
538
+ function isRichClayHost(el) {
539
+ if (!el || typeof el.matches !== "function") return false;
540
+ if (!el.tagName.includes("-")) return true;
541
+ return el.matches(RICHCLAY_OPT_IN);
542
+ }
543
+ var CHROME_SELECTOR = "[data-richclay-toolbar], [data-richclay-menu], [data-richclay-dialog], [data-richclay-live], [data-richclay-float]";
544
+ var runtimeClasses = [
545
+ "richclay-editor",
546
+ "richclay-inline",
547
+ "richclay-active",
548
+ "richclay-empty",
549
+ "richclay-focused"
550
+ ];
551
+ var installedWindows = /* @__PURE__ */ new WeakSet();
552
+ var armedWindows = /* @__PURE__ */ new WeakSet();
553
+ var PLATFORM_READY = ["clay:ready", "hyperclay:ready"];
554
+ function platformReadyPromise(win) {
555
+ return [win.clay?.ready, win.hyperclay?.ready].find(
556
+ (ready) => ready && typeof ready.then === "function"
557
+ ) || null;
558
+ }
559
+ function platformEditMode(win) {
560
+ if (typeof win.clay?.isEditMode === "boolean") return win.clay.isEditMode;
561
+ if (typeof win.hyperclay?.isEditMode === "boolean") return win.hyperclay.isEditMode;
562
+ return null;
563
+ }
564
+ function hasPlatformLifecycle(win) {
565
+ const modules = win.hyperclayModules;
566
+ const legacyHyperclayLoader = win.__hyperclayNoAutoExport === false && modules && typeof modules === "object" && typeof modules.nodeType !== "number";
567
+ return platformEditMode(win) !== null || Boolean(platformReadyPromise(win)) || Boolean(legacyHyperclayLoader);
568
+ }
569
+ function platformDocumentTransform(win) {
570
+ return win.clay?.addDocumentTransform || win.hyperclay?.beforeSave || null;
571
+ }
572
+ function platformSnapshotTransform(win) {
573
+ return win.clay?.onSnapshot || win.hyperclay?.onSnapshot || null;
574
+ }
575
+ var PRE_CONTAINMENT = {
576
+ boxSizing: "border-box",
577
+ minWidth: "100%",
578
+ overflow: "auto",
579
+ width: "0"
580
+ };
581
+ function shouldUseHyperclay(options = {}, win = window) {
582
+ if (options.hyperclay === false) return false;
583
+ if (options.hyperclay === true) return true;
584
+ return Boolean(hasPlatformLifecycle(win) || hasEditmodeSignal(win));
585
+ }
586
+ function isHyperclayEditMode(win = window) {
587
+ const fromQuery = readEditmodeParam(win);
588
+ if (fromQuery !== null) return fromQuery;
589
+ if (typeof win.__hyperclayEditMode === "boolean") {
590
+ return win.__hyperclayEditMode;
591
+ }
592
+ const fromPlatform = platformEditMode(win);
593
+ if (fromPlatform !== null) return fromPlatform;
594
+ return readEditmodeCookie(win);
595
+ }
596
+ function shouldActivateEditor(options = {}, win = window) {
597
+ if (options.readOnly) return false;
598
+ if (!shouldUseHyperclay(options, win)) return true;
599
+ return isHyperclayEditMode(win);
600
+ }
601
+ function parseEditableOptions(element) {
602
+ const name = EDITABLE_ATTRS.find((attr) => element.hasAttribute(attr));
603
+ if (!name) return null;
604
+ const tokens = new Set(
605
+ (element.getAttribute(name) || "").trim().split(/\s+/).filter(Boolean)
606
+ );
607
+ const options = {
608
+ inline: true,
609
+ singleLine: tokens.has("single-line"),
610
+ toolbarOnSelect: tokens.has("toolbar-on-select")
611
+ };
612
+ if (tokens.has("no-toolbar")) options.toolbar = false;
613
+ return options;
614
+ }
615
+ function installHyperclayBridge(win = window) {
616
+ if (installedWindows.has(win)) return;
617
+ const register = platformSnapshotTransform(win) || platformDocumentTransform(win);
618
+ if (typeof register !== "function") {
619
+ armBridgeRetry(win);
620
+ return;
621
+ }
622
+ register((docElem) => stripRichClayFromClone(docElem));
623
+ installedWindows.add(win);
624
+ }
625
+ function armBridgeRetry(win) {
626
+ if (armedWindows.has(win)) return;
627
+ const doc = win.document;
628
+ const ready = platformReadyPromise(win);
629
+ if (!doc && !ready) return;
630
+ armedWindows.add(win);
631
+ const retry = () => {
632
+ installHyperclayBridge(win);
633
+ if (installedWindows.has(win)) disarm();
634
+ };
635
+ const disarm = () => {
636
+ if (!doc) return;
637
+ PLATFORM_READY.forEach((name) => doc.removeEventListener(name, retry));
638
+ };
639
+ if (doc) PLATFORM_READY.forEach((name) => doc.addEventListener(name, retry));
640
+ if (ready) ready.then(retry, () => {
641
+ });
642
+ }
643
+ function stripRichClayFromClone(docElem) {
644
+ docElem.querySelectorAll?.(CHROME_SELECTOR).forEach((node) => node.remove());
645
+ docElem.querySelectorAll?.(RICHCLAY_SELECTOR).forEach((region) => {
646
+ if (region.getAttribute("data-richclay-active") !== "true") return;
647
+ removeRuntimeState(region, "save");
648
+ if (needsFlattening(region)) {
649
+ const doc = region.ownerDocument;
650
+ const singleLine = Boolean(region.matches?.(singleLineSelector));
651
+ flattenBlocks(
652
+ region,
653
+ () => singleLine ? doc.createTextNode(" ") : doc.createElement("br")
654
+ );
655
+ region.querySelectorAll(region.localName).forEach((nested) => {
656
+ while (nested.firstChild) nested.parentNode.insertBefore(nested.firstChild, nested);
657
+ nested.remove();
658
+ });
659
+ }
660
+ if (region.matches?.(singleLineSelector)) unwrapLoneSingleLineBlock(region);
661
+ region.querySelectorAll("#squire-selection-start, #squire-selection-end").forEach((node) => {
662
+ node.remove();
663
+ });
664
+ region.querySelectorAll(".squire-image-resize-container").forEach((node) => node.remove());
665
+ stripZeroWidthArtifacts(region);
666
+ const codeBlocks = (region.matches?.("pre") ? [region, ...region.querySelectorAll("pre")] : Array.from(region.querySelectorAll("pre"))).filter((pre) => pre.hasAttribute("data-richclay-pre"));
667
+ codeBlocks.forEach((pre) => {
668
+ Object.entries(PRE_CONTAINMENT).forEach(([property, value]) => {
669
+ if (!pre.style[property]) pre.style[property] = value;
670
+ });
671
+ pre.removeAttribute("data-richclay-pre");
672
+ });
673
+ });
674
+ docElem.querySelectorAll?.("#squire-selection-start, #squire-selection-end").forEach((node) => node.remove());
675
+ }
676
+ var BLOCK_SCOPE = "p, td, th, li, dl, dt, dd, table, blockquote, div, section, article, aside, main, header, footer, figure, figcaption";
677
+ var MEASURE_ATTR = "data-richclay-measure";
678
+ function ejectsOnReload(region) {
679
+ const doc = region.ownerDocument;
680
+ const scope = region.closest(BLOCK_SCOPE) || region;
681
+ region.setAttribute(MEASURE_ATTR, "");
682
+ const probe = doc.createElement("div");
683
+ try {
684
+ probe.innerHTML = scope.outerHTML;
685
+ } finally {
686
+ region.removeAttribute(MEASURE_ATTR);
687
+ }
688
+ const reparsed = probe.querySelector(`[${MEASURE_ATTR}]`);
689
+ return !reparsed || reparsed.textContent !== region.textContent;
690
+ }
691
+ var selfNests = (region) => Boolean(region.querySelector(region.localName));
692
+ var needsFlattening = (region) => Boolean(region.matches?.(singleLineSelector)) || (hasBlockDescendant(region) || selfNests(region)) && ejectsOnReload(region);
693
+ function removeRuntimeState(region, mode) {
694
+ const origin = region.getAttribute("data-richclay-runtime-contenteditable");
695
+ if (region.hasAttribute("contenteditable")) {
696
+ if (origin === "true") {
697
+ region.removeAttribute("contenteditable");
698
+ } else if (origin) {
699
+ if (mode === "destroy") {
700
+ region.setAttribute("contenteditable", origin);
701
+ } else {
702
+ region.setAttribute("inert-contenteditable", origin);
703
+ region.removeAttribute("contenteditable");
704
+ }
705
+ } else if (mode === "save") {
706
+ region.setAttribute("inert-contenteditable", region.getAttribute("contenteditable"));
707
+ region.removeAttribute("contenteditable");
708
+ }
709
+ }
710
+ runtimeClasses.forEach((className) => region.classList.remove(className));
711
+ if (region.getAttribute("class") === "") region.removeAttribute("class");
712
+ removeRuntimeAttribute(region, "role", "data-richclay-runtime-role");
713
+ removeRuntimeAttribute(region, "aria-multiline", "data-richclay-runtime-aria-multiline");
714
+ removeRuntimeAttribute(region, "no-undo", "data-richclay-runtime-no-undo");
715
+ if (region.getAttribute("data-richclay-runtime-display") === "true") {
716
+ region.style.removeProperty("display");
717
+ if (region.getAttribute("style") === "") region.removeAttribute("style");
718
+ }
719
+ removeRuntimeDescribedBy(region);
720
+ region.removeAttribute("data-richclay-active");
721
+ region.removeAttribute("data-richclay-placeholder");
722
+ region.removeAttribute("data-richclay-pre");
723
+ region.removeAttribute("data-richclay-runtime-role");
724
+ region.removeAttribute("data-richclay-runtime-aria-multiline");
725
+ region.removeAttribute("data-richclay-runtime-no-undo");
726
+ region.removeAttribute("data-richclay-runtime-describedby");
727
+ region.removeAttribute("data-richclay-runtime-contenteditable");
728
+ region.removeAttribute("data-richclay-runtime-display");
729
+ if (region.getAttribute("data-richclay-runtime-marker") === "true") {
730
+ region.removeAttribute("data-richclay");
731
+ }
732
+ region.removeAttribute("data-richclay-runtime-marker");
733
+ }
734
+ function stripZeroWidthArtifacts(region) {
735
+ const zwsp = String.fromCharCode(8203);
736
+ const stack = [region];
737
+ while (stack.length) {
738
+ const node = stack.pop();
739
+ node.childNodes.forEach((child) => {
740
+ if (child.nodeType === 3) {
741
+ if (child.nodeValue.includes(zwsp)) {
742
+ child.nodeValue = child.nodeValue.split(zwsp).join("");
743
+ }
744
+ } else if (child.nodeType === 1) {
745
+ stack.push(child);
746
+ }
747
+ });
748
+ }
749
+ region.querySelectorAll("b, i, u, s, em, strong, code, sub, sup, span").forEach((el) => {
750
+ if (el.children.length === 0 && (el.textContent || "") === "" && el.attributes.length === 0) {
751
+ el.remove();
752
+ }
753
+ });
754
+ }
755
+ function unwrapLoneSingleLineBlock(region) {
756
+ const meaningful = Array.from(region.childNodes).filter(
757
+ (node) => node.nodeType !== 3 || (node.nodeValue || "").trim() !== ""
758
+ );
759
+ if (meaningful.length !== 1) return;
760
+ const block = meaningful[0];
761
+ if (block.nodeType !== 1 || block.nodeName !== "P" || block.attributes.length > 0) return;
762
+ while (block.firstChild) region.insertBefore(block.firstChild, block);
763
+ block.remove();
764
+ }
765
+ function consumeInertContenteditable(element) {
766
+ if (!element.hasAttribute("inert-contenteditable")) return null;
767
+ let value = element.getAttribute("inert-contenteditable");
768
+ if (!["false", "plaintext-only"].includes(value)) value = "true";
769
+ element.setAttribute("contenteditable", value);
770
+ element.removeAttribute("inert-contenteditable");
771
+ if (value === "true") {
772
+ element.setAttribute("data-richclay-runtime-contenteditable", "true");
773
+ }
774
+ return value;
775
+ }
776
+ function markChrome(element) {
777
+ element.setAttribute("snapshot-remove", "");
778
+ element.setAttribute("no-watch", "");
779
+ return element;
780
+ }
781
+ function readEditmodeParam(win) {
782
+ try {
783
+ const params = new URLSearchParams(win.location?.search || "");
784
+ if (params.get("editmode") === "true") return true;
785
+ if (params.get("editmode") === "false") return false;
786
+ } catch {
787
+ return null;
788
+ }
789
+ return null;
790
+ }
791
+ function hasEditmodeSignal(win) {
792
+ return readEditmodeParam(win) !== null || typeof win.__hyperclayEditMode === "boolean";
793
+ }
794
+ function readEditmodeCookie(win) {
795
+ const cookie = win.document?.cookie || "";
796
+ const match = cookie.match(/(?:^|;\s*)isAdminOfCurrentResource=([^;]*)/);
797
+ return Boolean(match && match[1] !== "");
798
+ }
799
+ function removeRuntimeAttribute(region, attribute, marker) {
800
+ if (!region.hasAttribute(marker)) return;
801
+ region.removeAttribute(attribute);
802
+ }
803
+ function removeRuntimeDescribedBy(region) {
804
+ const id = region.getAttribute("data-richclay-runtime-describedby");
805
+ if (!id) return;
806
+ const ids = (region.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean).filter((value) => value !== id);
807
+ if (ids.length) {
808
+ region.setAttribute("aria-describedby", ids.join(" "));
809
+ } else {
810
+ region.removeAttribute("aria-describedby");
811
+ }
812
+ }
813
+
814
+ // src/a11y.js
815
+ var liveId = 0;
816
+ function createLiveRegion(doc = document) {
817
+ const region = doc.createElement("div");
818
+ region.id = `richclay-live-${++liveId}`;
819
+ region.className = "richclay-sr-only";
820
+ region.setAttribute("aria-live", "polite");
821
+ region.setAttribute("aria-atomic", "true");
822
+ region.setAttribute("data-richclay-live", "");
823
+ markChrome(region);
824
+ doc.body.appendChild(region);
825
+ return region;
826
+ }
827
+ function announce(region, message) {
828
+ if (!region || !message) return;
829
+ region.textContent = "";
830
+ region.ownerDocument.defaultView.setTimeout(() => {
831
+ region.textContent = message;
832
+ }, 20);
833
+ }
834
+ function preservePointerSelection(event) {
835
+ event.preventDefault();
836
+ }
837
+ function cloneRange(range) {
838
+ try {
839
+ return range?.cloneRange?.() || null;
840
+ } catch {
841
+ return null;
842
+ }
843
+ }
844
+ function getSquireSelection(squire) {
845
+ try {
846
+ return cloneRange(squire?.getSelection?.());
847
+ } catch {
848
+ return null;
849
+ }
850
+ }
851
+ function restoreSquireSelection(squire, range) {
852
+ if (!squire || !range) return false;
853
+ try {
854
+ squire.setSelection(range);
855
+ return true;
856
+ } catch {
857
+ return false;
858
+ }
859
+ }
860
+ function setRuntimeAttribute(element, attribute, value, marker) {
861
+ if (!element.hasAttribute(attribute)) {
862
+ element.setAttribute(marker, "true");
863
+ }
864
+ element.setAttribute(attribute, value);
865
+ }
866
+ function connectDescription(element, description) {
867
+ const ids = new Set((element.getAttribute("aria-describedby") || "").split(/\s+/).filter(Boolean));
868
+ ids.add(description.id);
869
+ element.setAttribute("aria-describedby", Array.from(ids).join(" "));
870
+ element.setAttribute("data-richclay-runtime-describedby", description.id);
871
+ }
872
+
873
+ // src/toolbar.js
874
+ var toolbarId = 0;
875
+ var Toolbar = class {
876
+ constructor(editor, controls, options = {}) {
877
+ this.editor = editor;
878
+ this.controls = controls;
879
+ this.options = options;
880
+ this.id = ++toolbarId;
881
+ this.items = [];
882
+ this.menus = /* @__PURE__ */ new Map();
883
+ this.activeIndex = 0;
884
+ this.root = this.createRoot();
885
+ this.onRootKeydown = (event) => this.handleRootKeydown(event);
886
+ this.onRootClick = (event) => this.handleRootClick(event);
887
+ this.onRootPointerDown = (event) => this.handleRootPointerDown(event);
888
+ this.onDocumentPointerDown = (event) => this.handleDocumentPointerDown(event);
889
+ this.render();
890
+ this.root.addEventListener("keydown", this.onRootKeydown);
891
+ this.root.addEventListener("click", this.onRootClick);
892
+ this.root.addEventListener("mousedown", this.onRootPointerDown);
893
+ this.root.ownerDocument.addEventListener("mousedown", this.onDocumentPointerDown);
894
+ }
895
+ destroy() {
896
+ this.closeMenus();
897
+ this.root.removeEventListener("keydown", this.onRootKeydown);
898
+ this.root.removeEventListener("click", this.onRootClick);
899
+ this.root.removeEventListener("mousedown", this.onRootPointerDown);
900
+ this.root.ownerDocument.removeEventListener("mousedown", this.onDocumentPointerDown);
901
+ this.root.remove();
902
+ }
903
+ update() {
904
+ this.items.forEach((item) => {
905
+ const { def, button } = item;
906
+ if (typeof def.isActive === "function") {
907
+ const active = Boolean(def.isActive(this.editor));
908
+ button.setAttribute("aria-pressed", active ? "true" : "false");
909
+ button.classList.toggle("is-active", active);
910
+ }
911
+ if (typeof def.isDisabled === "function") {
912
+ button.disabled = Boolean(def.isDisabled(this.editor));
913
+ }
914
+ });
915
+ this.ensureSingleTabStop();
916
+ }
917
+ focusFirst() {
918
+ this.activeIndex = 0;
919
+ this.ensureSingleTabStop();
920
+ this.items[0]?.button.focus();
921
+ }
922
+ createRoot() {
923
+ const doc = this.editor.element.ownerDocument;
924
+ const root = doc.createElement("div");
925
+ root.className = "richclay-toolbar";
926
+ root.setAttribute("role", "toolbar");
927
+ root.setAttribute("aria-label", this.options.ariaLabel || "Rich text formatting");
928
+ root.setAttribute("data-richclay-toolbar", "");
929
+ markChrome(root);
930
+ const container = resolveContainer(this.options.toolbarContainer, doc);
931
+ if (container) {
932
+ container.appendChild(root);
933
+ } else {
934
+ this.editor.element.insertAdjacentElement("beforebegin", root);
935
+ }
936
+ return root;
937
+ }
938
+ render() {
939
+ this.root.textContent = "";
940
+ this.items = [];
941
+ this.menus.clear();
942
+ let lastGroup = null;
943
+ this.visibleControls().forEach((def, index) => {
944
+ if (def.type === "separator") {
945
+ this.root.appendChild(createSeparator(this.root.ownerDocument));
946
+ lastGroup = null;
947
+ return;
948
+ }
949
+ if (lastGroup && def.group && def.group !== lastGroup) {
950
+ this.root.appendChild(createSeparator(this.root.ownerDocument));
951
+ }
952
+ lastGroup = def.group || lastGroup;
953
+ if (def.type === "menu") {
954
+ this.renderMenu(def, index);
955
+ } else {
956
+ this.renderButton(def, index);
957
+ }
958
+ });
959
+ this.ensureSingleTabStop();
960
+ this.update();
961
+ }
962
+ // A control disabled by the root is disabled for this editor's whole life, not
963
+ // for this selection, so it is left out of the toolbar rather than rendered
964
+ // greyed: a greyed button says "not right now" and invites clicking, an absent
965
+ // one just gives a simpler toolbar. It stays disabled at dispatch as well,
966
+ // because hiding a button stops neither its shortcut nor its menu item.
967
+ visibleControls() {
968
+ const kept = this.controls.filter(
969
+ (def) => def.type === "separator" || !def.isDisabled?.(this.editor)
970
+ );
971
+ const trimmed = [];
972
+ kept.forEach((def) => {
973
+ const previous = trimmed[trimmed.length - 1];
974
+ if (def.type === "separator" && (!previous || previous.type === "separator")) return;
975
+ trimmed.push(def);
976
+ });
977
+ while (trimmed[trimmed.length - 1]?.type === "separator") trimmed.pop();
978
+ return trimmed;
979
+ }
980
+ renderButton(def, index) {
981
+ const button = createToolbarButton(this.root.ownerDocument, def);
982
+ button.dataset.richclayControl = def.id;
983
+ button.dataset.richclayIndex = String(index);
984
+ if (typeof def.isActive === "function") {
985
+ button.setAttribute("aria-pressed", "false");
986
+ }
987
+ this.root.appendChild(button);
988
+ this.items.push({ def, button, type: "button" });
989
+ }
990
+ renderMenu(def, index) {
991
+ const doc = this.root.ownerDocument;
992
+ const button = createToolbarButton(doc, def);
993
+ const menuId = `richclay-menu-${this.id}-${def.id}`;
994
+ button.dataset.richclayControl = def.id;
995
+ button.dataset.richclayIndex = String(index);
996
+ button.setAttribute("aria-haspopup", "menu");
997
+ button.setAttribute("aria-expanded", "false");
998
+ button.setAttribute("aria-controls", menuId);
999
+ const menu = doc.createElement("div");
1000
+ menu.id = menuId;
1001
+ menu.className = "richclay-menu";
1002
+ menu.hidden = true;
1003
+ menu.setAttribute("role", "menu");
1004
+ menu.setAttribute("data-richclay-menu", "");
1005
+ markChrome(menu);
1006
+ def.options.forEach((option, optionIndex) => {
1007
+ const item = doc.createElement("button");
1008
+ item.type = "button";
1009
+ item.className = "richclay-menu-item";
1010
+ item.setAttribute("role", "menuitemradio");
1011
+ item.setAttribute("tabindex", "-1");
1012
+ item.dataset.richclayMenuItem = def.id;
1013
+ item.dataset.richclayOptionIndex = String(optionIndex);
1014
+ item.textContent = option.label;
1015
+ item.addEventListener("mousedown", preservePointerSelection);
1016
+ item.addEventListener("click", (event) => {
1017
+ event.preventDefault();
1018
+ this.chooseMenuItem(def, option, button);
1019
+ });
1020
+ item.addEventListener("keydown", (event) => this.handleMenuKeydown(event, def, button));
1021
+ menu.appendChild(item);
1022
+ });
1023
+ const wrap = doc.createElement("span");
1024
+ wrap.className = "richclay-menu-wrap";
1025
+ wrap.append(button, menu);
1026
+ this.root.appendChild(wrap);
1027
+ this.items.push({ def, button, type: "menu", menu });
1028
+ this.menus.set(def.id, { def, button, menu });
1029
+ }
1030
+ handleRootPointerDown(event) {
1031
+ const control = event.target.closest?.("[data-richclay-control]");
1032
+ if (!control || !this.root.contains(control)) return;
1033
+ preservePointerSelection(event);
1034
+ this.editor.saveSelection();
1035
+ }
1036
+ handleRootClick(event) {
1037
+ const button = event.target.closest?.("[data-richclay-control]");
1038
+ if (!button || !this.root.contains(button)) return;
1039
+ event.preventDefault();
1040
+ const item = this.items.find((candidate) => candidate.button === button);
1041
+ if (!item || button.disabled) return;
1042
+ this.activeIndex = this.items.indexOf(item);
1043
+ this.ensureSingleTabStop();
1044
+ if (item.type === "menu") {
1045
+ this.toggleMenu(item.def.id);
1046
+ return;
1047
+ }
1048
+ this.closeMenus();
1049
+ this.editor.runControl(item.def);
1050
+ }
1051
+ handleRootKeydown(event) {
1052
+ const key = event.key;
1053
+ const navigation = {
1054
+ ArrowRight: 1,
1055
+ ArrowDown: 1,
1056
+ ArrowLeft: -1,
1057
+ ArrowUp: -1
1058
+ };
1059
+ if (key in navigation) {
1060
+ event.preventDefault();
1061
+ this.moveFocus(navigation[key]);
1062
+ return;
1063
+ }
1064
+ if (key === "Home") {
1065
+ event.preventDefault();
1066
+ this.setFocusIndex(0);
1067
+ return;
1068
+ }
1069
+ if (key === "End") {
1070
+ event.preventDefault();
1071
+ this.setFocusIndex(this.items.length - 1);
1072
+ return;
1073
+ }
1074
+ const current = this.items[this.activeIndex];
1075
+ if (!current) return;
1076
+ if ((key === "Enter" || key === " ") && current.type === "menu") {
1077
+ event.preventDefault();
1078
+ this.openMenu(current.def.id, "first");
1079
+ }
1080
+ if (key === "Escape") {
1081
+ const hadOpenMenu = Array.from(this.menus.values()).some((entry) => !entry.menu.hidden);
1082
+ this.closeMenus();
1083
+ if (hadOpenMenu) current.button.focus();
1084
+ else this.editor.focus();
1085
+ }
1086
+ }
1087
+ handleMenuKeydown(event, def, trigger) {
1088
+ event.stopPropagation();
1089
+ const menu = this.menus.get(def.id)?.menu;
1090
+ const items = Array.from(menu.querySelectorAll(".richclay-menu-item"));
1091
+ const index = items.indexOf(event.currentTarget);
1092
+ let next = index;
1093
+ if (event.key === "ArrowDown") next = (index + 1) % items.length;
1094
+ if (event.key === "ArrowUp") next = (index - 1 + items.length) % items.length;
1095
+ if (event.key === "Home") next = 0;
1096
+ if (event.key === "End") next = items.length - 1;
1097
+ if (next !== index) {
1098
+ event.preventDefault();
1099
+ items[next].focus();
1100
+ return;
1101
+ }
1102
+ if (event.key === "Escape") {
1103
+ event.preventDefault();
1104
+ this.closeMenus();
1105
+ trigger.focus();
1106
+ return;
1107
+ }
1108
+ if (event.key === "Enter" || event.key === " ") {
1109
+ event.preventDefault();
1110
+ const option = def.options[index];
1111
+ this.chooseMenuItem(def, option, trigger);
1112
+ }
1113
+ }
1114
+ handleDocumentPointerDown(event) {
1115
+ if (this.root.contains(event.target)) return;
1116
+ this.closeMenus();
1117
+ }
1118
+ chooseMenuItem(def, option, trigger) {
1119
+ this.closeMenus();
1120
+ this.editor.runControl({
1121
+ ...option,
1122
+ id: `${def.id}:${option.value}`,
1123
+ ariaLabel: option.label
1124
+ });
1125
+ trigger.focus();
1126
+ }
1127
+ toggleMenu(id) {
1128
+ const menu = this.menus.get(id);
1129
+ if (!menu) return;
1130
+ if (menu.menu.hidden) this.openMenu(id, "first");
1131
+ else this.closeMenus();
1132
+ }
1133
+ openMenu(id, focus = "first") {
1134
+ this.closeMenus(id);
1135
+ const entry = this.menus.get(id);
1136
+ if (!entry) return;
1137
+ entry.menu.hidden = false;
1138
+ entry.button.setAttribute("aria-expanded", "true");
1139
+ this.updateMenuState(entry);
1140
+ const items = Array.from(entry.menu.querySelectorAll(".richclay-menu-item"));
1141
+ const active = items.find((item) => item.getAttribute("aria-checked") === "true");
1142
+ const target = focus === "active" ? active || items[0] : items[0];
1143
+ target?.focus();
1144
+ }
1145
+ closeMenus(exceptId = null) {
1146
+ this.menus.forEach((entry, id) => {
1147
+ if (id === exceptId) return;
1148
+ entry.menu.hidden = true;
1149
+ entry.button.setAttribute("aria-expanded", "false");
1150
+ });
1151
+ }
1152
+ updateMenuState(entry) {
1153
+ const items = Array.from(entry.menu.querySelectorAll(".richclay-menu-item"));
1154
+ items.forEach((item, index) => {
1155
+ const option = entry.def.options[index];
1156
+ const active = Boolean(option.isActive?.(this.editor));
1157
+ item.setAttribute("aria-checked", active ? "true" : "false");
1158
+ item.classList.toggle("is-active", active);
1159
+ });
1160
+ }
1161
+ moveFocus(delta) {
1162
+ if (!this.items.length) return;
1163
+ let next = this.activeIndex;
1164
+ do {
1165
+ next = (next + delta + this.items.length) % this.items.length;
1166
+ } while (this.items[next].button.disabled && next !== this.activeIndex);
1167
+ this.setFocusIndex(next);
1168
+ }
1169
+ setFocusIndex(index) {
1170
+ this.activeIndex = Math.max(0, Math.min(index, this.items.length - 1));
1171
+ this.ensureSingleTabStop();
1172
+ this.items[this.activeIndex]?.button.focus();
1173
+ }
1174
+ ensureSingleTabStop() {
1175
+ const enabled = this.items.filter((item) => !item.button.disabled);
1176
+ if (!enabled.length) return;
1177
+ if (!enabled.includes(this.items[this.activeIndex])) {
1178
+ this.activeIndex = this.items.indexOf(enabled[0]);
1179
+ }
1180
+ this.items.forEach((item, index) => {
1181
+ item.button.tabIndex = index === this.activeIndex ? 0 : -1;
1182
+ });
1183
+ }
1184
+ };
1185
+ function createToolbarButton(doc, def) {
1186
+ const button = doc.createElement("button");
1187
+ button.type = "button";
1188
+ button.className = "richclay-button";
1189
+ const shortcut = formatShortcut(def.shortcut, doc.defaultView);
1190
+ const label = shortcut ? `${def.ariaLabel || def.label} (${shortcut})` : def.ariaLabel || def.label;
1191
+ button.setAttribute("aria-label", label);
1192
+ button.title = label;
1193
+ button.innerHTML = `${def.icon || ""}<span class="richclay-sr-only">${def.label}</span>`;
1194
+ return button;
1195
+ }
1196
+ function createSeparator(doc) {
1197
+ const separator = doc.createElement("span");
1198
+ separator.className = "richclay-separator";
1199
+ separator.setAttribute("role", "separator");
1200
+ separator.setAttribute("aria-orientation", "vertical");
1201
+ return separator;
1202
+ }
1203
+ function resolveContainer(container, doc) {
1204
+ if (!container) return null;
1205
+ if (typeof container === "string") return doc.querySelector(container);
1206
+ return container;
1207
+ }
1208
+
1209
+ // src/toolbar-float.js
1210
+ var GAP = 16;
1211
+ var GAP_LADDER = [16, 8, 0];
1212
+ var VIEWPORT_INSET = 8;
1213
+ var PLACEMENT_SLACK = 4;
1214
+ function placeToolbar({ anchor, bar, rail, viewport, current = null }) {
1215
+ const slack = (mode) => current === mode ? PLACEMENT_SLACK : 0;
1216
+ if (anchor.bottom <= 0 || anchor.top >= viewport.height || anchor.right <= 0 || anchor.left >= viewport.width) {
1217
+ return { mode: "hidden", x: 0, y: 0 };
1218
+ }
1219
+ const clampX = (width) => Math.max(VIEWPORT_INSET, Math.min(anchor.left, viewport.width - width - VIEWPORT_INSET));
1220
+ const aboveY = anchor.top - GAP - bar.height;
1221
+ if (aboveY >= VIEWPORT_INSET - slack("above")) {
1222
+ return { mode: "above", x: clampX(bar.width), y: aboveY };
1223
+ }
1224
+ const belowY = anchor.bottom + GAP;
1225
+ if (belowY + bar.height <= viewport.height - VIEWPORT_INSET + slack("below")) {
1226
+ return { mode: "below", x: clampX(bar.width), y: belowY };
1227
+ }
1228
+ const rightSpace = viewport.width - anchor.right - VIEWPORT_INSET;
1229
+ const leftSpace = anchor.left - VIEWPORT_INSET;
1230
+ const sides = rightSpace >= leftSpace ? [["rail-right", rightSpace], ["rail-left", leftSpace]] : [["rail-left", leftSpace], ["rail-right", rightSpace]];
1231
+ for (const [mode, space] of sides) {
1232
+ for (const gap of GAP_LADDER) {
1233
+ if (rail.width + gap > space + slack(mode)) continue;
1234
+ 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);
1235
+ const maxY = Math.min(anchor.bottom, viewport.height - VIEWPORT_INSET) - rail.height;
1236
+ const y = Math.max(VIEWPORT_INSET, Math.min(Math.max(anchor.top, VIEWPORT_INSET), maxY));
1237
+ return { mode, x, y, gap };
1238
+ }
1239
+ }
1240
+ return { mode: "pinned", x: clampX(bar.width), y: VIEWPORT_INSET };
1241
+ }
1242
+ var FloatingToolbar = class {
1243
+ constructor(editor, controls) {
1244
+ this.editor = editor;
1245
+ this.mode = null;
1246
+ this.hidden = false;
1247
+ this.frame = 0;
1248
+ this.bar = { width: 0, height: 0 };
1249
+ this.rail = { width: 0, height: 0 };
1250
+ const doc = editor.element.ownerDocument;
1251
+ this.doc = doc;
1252
+ this.win = doc.defaultView || globalThis;
1253
+ this.root = doc.createElement("div");
1254
+ this.root.className = "richclay-float";
1255
+ this.root.setAttribute("data-richclay-float", "");
1256
+ this.root.setAttribute("save-remove", "");
1257
+ markChrome(this.root);
1258
+ doc.body.appendChild(this.root);
1259
+ this.toolbar = new Toolbar(editor, controls, { toolbarContainer: this.root });
1260
+ this.onScroll = () => this.schedule();
1261
+ this.onResize = () => {
1262
+ this.measure();
1263
+ this.schedule();
1264
+ };
1265
+ doc.addEventListener("scroll", this.onScroll, { capture: true, passive: true });
1266
+ this.win.addEventListener("resize", this.onResize, { passive: true });
1267
+ this.win.visualViewport?.addEventListener("resize", this.onResize, { passive: true });
1268
+ this.win.visualViewport?.addEventListener("scroll", this.onScroll, { passive: true });
1269
+ this.resizeObserver = typeof this.win.ResizeObserver === "function" ? new this.win.ResizeObserver(() => this.schedule()) : null;
1270
+ this.resizeObserver?.observe(editor.element);
1271
+ this.measure();
1272
+ this.reposition();
1273
+ }
1274
+ // Measure both orientations up front so the placement math can evaluate the
1275
+ // rail without first switching to it.
1276
+ measure() {
1277
+ const wasRail = this.root.classList.contains("richclay-float-rail");
1278
+ this.root.style.visibility = "hidden";
1279
+ this.root.classList.remove("richclay-float-rail");
1280
+ this.bar = { width: this.root.offsetWidth || 0, height: this.root.offsetHeight || 0 };
1281
+ this.root.classList.add("richclay-float-rail");
1282
+ this.rail = { width: this.root.offsetWidth || 0, height: this.root.offsetHeight || 0 };
1283
+ this.root.classList.toggle("richclay-float-rail", wasRail);
1284
+ this.root.style.visibility = "";
1285
+ }
1286
+ schedule() {
1287
+ if (!this.win.requestAnimationFrame) {
1288
+ this.reposition();
1289
+ return;
1290
+ }
1291
+ if (this.frame) return;
1292
+ this.frame = this.win.requestAnimationFrame(() => {
1293
+ this.frame = 0;
1294
+ this.reposition();
1295
+ });
1296
+ }
1297
+ setVisible(visible) {
1298
+ this.hidden = !visible;
1299
+ this.reposition();
1300
+ }
1301
+ reposition() {
1302
+ if (this.hidden) {
1303
+ this.root.style.display = "none";
1304
+ return;
1305
+ }
1306
+ const anchor = this.editor.element.getBoundingClientRect();
1307
+ const viewport = {
1308
+ width: this.win.visualViewport?.width ?? this.win.innerWidth,
1309
+ height: this.win.visualViewport?.height ?? this.win.innerHeight
1310
+ };
1311
+ const placement = placeToolbar({
1312
+ anchor,
1313
+ bar: this.bar,
1314
+ rail: this.rail,
1315
+ viewport,
1316
+ current: this.mode
1317
+ });
1318
+ this.mode = placement.mode;
1319
+ if (placement.mode === "hidden") {
1320
+ this.root.style.display = "none";
1321
+ return;
1322
+ }
1323
+ this.root.style.display = "";
1324
+ this.root.classList.toggle(
1325
+ "richclay-float-rail",
1326
+ placement.mode === "rail-left" || placement.mode === "rail-right"
1327
+ );
1328
+ this.root.classList.toggle("richclay-float-pinned", placement.mode === "pinned");
1329
+ this.root.style.transform = `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px)`;
1330
+ }
1331
+ destroy() {
1332
+ if (this.frame) this.win.cancelAnimationFrame?.(this.frame);
1333
+ this.frame = 0;
1334
+ this.doc.removeEventListener("scroll", this.onScroll, { capture: true });
1335
+ this.win.removeEventListener("resize", this.onResize);
1336
+ this.win.visualViewport?.removeEventListener("resize", this.onResize);
1337
+ this.win.visualViewport?.removeEventListener("scroll", this.onScroll);
1338
+ this.resizeObserver?.disconnect();
1339
+ this.toolbar.destroy();
1340
+ this.root.remove();
1341
+ }
1342
+ };
1343
+
1344
+ // src/sanitize.js
1345
+ var DEFAULT_SANITIZE_CONFIG = {
1346
+ ALLOWED_TAGS: [
1347
+ "a",
1348
+ "b",
1349
+ "blockquote",
1350
+ "br",
1351
+ "code",
1352
+ "div",
1353
+ "em",
1354
+ "h1",
1355
+ "h2",
1356
+ "h3",
1357
+ "h4",
1358
+ "h5",
1359
+ "h6",
1360
+ "i",
1361
+ "li",
1362
+ "ol",
1363
+ "p",
1364
+ "pre",
1365
+ "s",
1366
+ "span",
1367
+ "strong",
1368
+ "sub",
1369
+ "sup",
1370
+ "u",
1371
+ "ul"
1372
+ ],
1373
+ ALLOWED_ATTR: ["aria-label", "href", "rel", "target", "title"],
1374
+ ALLOW_DATA_ATTR: false,
1375
+ ALLOW_ARIA_ATTR: true,
1376
+ ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
1377
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
1378
+ FORBID_ATTR: ["style", "srcset", "onerror", "onclick", "onload"]
1379
+ };
1380
+ function createSanitizer(config = {}, doc = document) {
1381
+ const purify = resolveDOMPurify(doc);
1382
+ return {
1383
+ sanitizeHTML(html) {
1384
+ return sanitizeHTML(html, config, doc, purify);
1385
+ },
1386
+ sanitizeElement(element) {
1387
+ return sanitizeElement(element, config, purify);
1388
+ },
1389
+ sanitizeToDOMFragment(html, editor) {
1390
+ const ownerDocument = editor?.getRoot?.().ownerDocument || doc;
1391
+ return sanitizeToDOMFragment(html, config, ownerDocument, purify);
1392
+ }
1393
+ };
1394
+ }
1395
+ function sanitizeHTML(html, config = {}, doc = document, purify = resolveDOMPurify(doc)) {
1396
+ const template = doc.createElement("template");
1397
+ template.innerHTML = purify.sanitize(String(html || ""), mergeConfig(config));
1398
+ normalizeLinks(template.content);
1399
+ return template.innerHTML;
1400
+ }
1401
+ function sanitizeElement(element, config = {}, purify = resolveDOMPurify(element.ownerDocument)) {
1402
+ const doc = element.ownerDocument;
1403
+ const wrapper = doc.createElement("div");
1404
+ while (element.firstChild) wrapper.appendChild(element.firstChild);
1405
+ purify.sanitize(wrapper, {
1406
+ ...mergeConfig(config),
1407
+ IN_PLACE: true
1408
+ });
1409
+ normalizeLinks(wrapper);
1410
+ while (wrapper.firstChild) element.appendChild(wrapper.firstChild);
1411
+ return element;
1412
+ }
1413
+ function sanitizeToDOMFragment(html, config = {}, doc = document, purify = resolveDOMPurify(doc)) {
1414
+ const fragment = purify.sanitize(String(html || ""), {
1415
+ ...mergeConfig(config),
1416
+ RETURN_DOM_FRAGMENT: true
1417
+ });
1418
+ const clean = fragment?.ownerDocument === doc ? fragment : doc.importNode(fragment, true);
1419
+ normalizeLinks(clean);
1420
+ return clean || doc.createDocumentFragment();
1421
+ }
1422
+ function mergeConfig(config) {
1423
+ return { ...DEFAULT_SANITIZE_CONFIG, ...config };
1424
+ }
1425
+ var SAFE_URL_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
1426
+ var URL_CONTROL_CHARS = new RegExp("[\\u0000-\\u0020\\u007F-\\u009F]", "g");
1427
+ function isSafeUrl(url) {
1428
+ const stripped = String(url == null ? "" : url).replace(URL_CONTROL_CHARS, "");
1429
+ const scheme = (stripped.match(/^([a-z][a-z0-9+.\-]*):/i) || [])[1];
1430
+ return !scheme || SAFE_URL_SCHEMES.has(scheme.toLowerCase());
1431
+ }
1432
+ function normalizeUrl(value) {
1433
+ const raw = String(value == null ? "" : value).trim();
1434
+ if (!raw || !isSafeUrl(raw)) return "";
1435
+ if (/^(?:https?:|mailto:|tel:)/i.test(raw)) return raw;
1436
+ if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw)) return `mailto:${raw}`;
1437
+ if (/^(?:[#?]|\/|\.{1,2}\/)/.test(raw)) return raw;
1438
+ return `https://${raw}`;
1439
+ }
1440
+ function resolveDOMPurify(doc) {
1441
+ const supplied = globalThis.DOMPurify;
1442
+ if (supplied?.sanitize) return supplied;
1443
+ if (typeof supplied === "function") return supplied(doc.defaultView || globalThis);
1444
+ throw new Error(
1445
+ "RichClay requires DOMPurify. Load vendor/purify.min.js before richclay.js or provide globalThis.DOMPurify."
1446
+ );
1447
+ }
1448
+ function normalizeLinks(root) {
1449
+ root.querySelectorAll?.("a[href]").forEach((link) => {
1450
+ const href = link.getAttribute("href") || "";
1451
+ if (/^\s*javascript:/i.test(href)) {
1452
+ link.removeAttribute("href");
1453
+ }
1454
+ if (link.getAttribute("target") === "_blank") {
1455
+ const rel = new Set((link.getAttribute("rel") || "").split(/\s+/).filter(Boolean));
1456
+ rel.add("noopener");
1457
+ rel.add("noreferrer");
1458
+ link.setAttribute("rel", Array.from(rel).join(" "));
1459
+ }
1460
+ });
1461
+ }
1462
+ var INLINE_SANITIZE_EXTENSIONS = {
1463
+ ADD_TAGS: ["img"],
1464
+ ADD_ATTR: ["class", "id", "src", "alt", "width", "height"]
1465
+ };
1466
+ function inlineSanitizeConfig(config = {}) {
1467
+ return {
1468
+ ...config,
1469
+ ADD_TAGS: [.../* @__PURE__ */ new Set([...INLINE_SANITIZE_EXTENSIONS.ADD_TAGS, ...config.ADD_TAGS || []])],
1470
+ ADD_ATTR: [.../* @__PURE__ */ new Set([...INLINE_SANITIZE_EXTENSIONS.ADD_ATTR, ...config.ADD_ATTR || []])],
1471
+ ALLOW_DATA_ATTR: config.ALLOW_DATA_ATTR ?? true
1472
+ };
1473
+ }
1474
+ var FLATTEN_SELECTOR = "p, div, h1, h2, h3, h4, h5, h6, blockquote, pre, ul, ol, li, figure, figcaption, table, thead, tbody, tr, td, th";
1475
+ function flattenFragmentToSingleLine(fragment) {
1476
+ const doc = fragment.ownerDocument || document;
1477
+ fragment.querySelectorAll?.("br").forEach((br) => br.replaceWith(doc.createTextNode(" ")));
1478
+ let block = fragment.querySelector?.(FLATTEN_SELECTOR);
1479
+ while (block) {
1480
+ const parent = block.parentNode;
1481
+ parent.insertBefore(doc.createTextNode(" "), block);
1482
+ while (block.firstChild) parent.insertBefore(block.firstChild, block);
1483
+ parent.insertBefore(doc.createTextNode(" "), block);
1484
+ block.remove();
1485
+ block = fragment.querySelector(FLATTEN_SELECTOR);
1486
+ }
1487
+ collapseFragmentWhitespace(fragment, doc);
1488
+ return fragment;
1489
+ }
1490
+ function collapseFragmentWhitespace(fragment, doc) {
1491
+ fragment.normalize?.();
1492
+ const walker = doc.createTreeWalker(
1493
+ fragment,
1494
+ 4
1495
+ /* NodeFilter.SHOW_TEXT */
1496
+ );
1497
+ const nodes = [];
1498
+ while (walker.nextNode()) nodes.push(walker.currentNode);
1499
+ nodes.forEach((node) => {
1500
+ node.nodeValue = node.nodeValue.replace(/\s+/g, " ");
1501
+ });
1502
+ const first = fragment.firstChild;
1503
+ if (first?.nodeType === 3) first.nodeValue = first.nodeValue.replace(/^\s+/, "");
1504
+ const last = fragment.lastChild;
1505
+ if (last?.nodeType === 3) last.nodeValue = last.nodeValue.replace(/\s+$/, "");
1506
+ }
1507
+
1508
+ // src/styles.js
1509
+ var STYLE_ID = "richclay-styles";
1510
+ var styledDocs = /* @__PURE__ */ new WeakSet();
1511
+ var cssText = "";
1512
+ function setRichClayStyles(text) {
1513
+ cssText = text || "";
1514
+ }
1515
+ function ensureStyles(doc = document) {
1516
+ if (!doc || styledDocs.has(doc)) return;
1517
+ if (doc.getElementById(STYLE_ID)) {
1518
+ styledDocs.add(doc);
1519
+ return;
1520
+ }
1521
+ if (!cssText) return;
1522
+ const style = doc.createElement("style");
1523
+ style.id = STYLE_ID;
1524
+ style.setAttribute("save-remove", "");
1525
+ style.setAttribute("save-ignore", "");
1526
+ style.textContent = cssText;
1527
+ (doc.head || doc.documentElement).appendChild(style);
1528
+ styledDocs.add(doc);
1529
+ }
1530
+
1531
+ // src/richclay.js
1532
+ var instances = /* @__PURE__ */ new WeakMap();
1533
+ var autoInitWindows = /* @__PURE__ */ new WeakSet();
1534
+ var watchedWindows = /* @__PURE__ */ new WeakSet();
1535
+ var globalRegistry = createDefaultRegistry();
1536
+ var KEEP_FOCUS = Symbol("richclay-keep-focus");
1537
+ var ROOT_TRANSFER_EVENTS = ["cut", "paste", "drop"];
1538
+ var MODIFIER_ORDER = ["Alt", "Ctrl", "Meta", "Shift"];
1539
+ var dialogSeq = 0;
1540
+ var defaultOptions = {
1541
+ toolbar: "standard",
1542
+ toolbarContainer: null,
1543
+ sanitize: {},
1544
+ placeholder: "",
1545
+ readOnly: false,
1546
+ hyperclay: "auto",
1547
+ onChange: null,
1548
+ Squire: null,
1549
+ inline: false,
1550
+ singleLine: false,
1551
+ toolbarOnSelect: false
1552
+ };
1553
+ var RichClay = class _RichClay {
1554
+ static presets = presets;
1555
+ static selector = RICHCLAY_SELECTOR;
1556
+ constructor(element, options = {}) {
1557
+ if (!element) throw new Error("RichClay requires an element.");
1558
+ const existing = instances.get(element);
1559
+ if (existing) return existing;
1560
+ this.element = element;
1561
+ this.unsupported = conflictsWithExistingEditor(element) || refuseUnsupportedRoot(element);
1562
+ const derived = { ...parseEditableOptions(element), ...options };
1563
+ this.options = { ...defaultOptions, ...derived };
1564
+ if (this.options.singleLine && !("toolbar" in derived)) {
1565
+ this.options.toolbar = "inline";
1566
+ }
1567
+ this._blockTag = this.options.inline ? "DIV" : "P";
1568
+ this.registry = new Map(globalRegistry);
1569
+ this.toolbar = null;
1570
+ this.float = null;
1571
+ this._onToolbarKey = null;
1572
+ this._onFloatFocusOut = null;
1573
+ this._onDocPointerDown = null;
1574
+ this.liveRegion = null;
1575
+ this.description = null;
1576
+ this.dialog = null;
1577
+ this.savedSelection = null;
1578
+ this.path = "";
1579
+ this.active = false;
1580
+ this._squire = null;
1581
+ this._squireListeners = [];
1582
+ this._onBeforeInput = null;
1583
+ this._onRootBeforeInput = null;
1584
+ this._onRootKeydown = null;
1585
+ this._onRootTransfer = null;
1586
+ this._shortcutKeys = [];
1587
+ this._appleDeleteKeys = /* @__PURE__ */ new Set();
1588
+ this._authoredPres = /* @__PURE__ */ new WeakSet();
1589
+ this._warnedInlineBlock = false;
1590
+ this._onFocus = () => {
1591
+ this.element.classList.add("richclay-focused");
1592
+ if (this.options.inline) this.ensureFloatingToolbar();
1593
+ };
1594
+ this._onBlur = (event) => {
1595
+ this.element.classList.remove("richclay-focused");
1596
+ if (this.options.inline) this.scheduleFloatTeardown(event);
1597
+ };
1598
+ this.ensureMarker();
1599
+ this.hyperclay = shouldUseHyperclay(this.options, this.window);
1600
+ if (this.hyperclay) installHyperclayBridge(this.window);
1601
+ instances.set(element, this);
1602
+ this.sanitizeConfig = this.options.inline ? inlineSanitizeConfig(this.options.sanitize) : this.options.sanitize;
1603
+ this.sanitizer = createSanitizer(this.sanitizeConfig, this.element.ownerDocument);
1604
+ if (!this.options.inline) {
1605
+ sanitizeElement(this.element, this.sanitizeConfig);
1606
+ }
1607
+ if (shouldActivateEditor(this.options, this.window)) {
1608
+ this.activate();
1609
+ }
1610
+ }
1611
+ get squire() {
1612
+ return this._squire;
1613
+ }
1614
+ get window() {
1615
+ return this.element.ownerDocument.defaultView || window;
1616
+ }
1617
+ static init(selector = RICHCLAY_SELECTOR, options = {}) {
1618
+ const guard = selector === RICHCLAY_SELECTOR ? isRichClayHost : () => true;
1619
+ const elements = resolveElements(selector).filter(guard).filter((element) => !conflictsWithExistingEditor(element));
1620
+ return elements.map((element) => new _RichClay(element, options));
1621
+ }
1622
+ static autoInit(win = typeof window !== "undefined" ? window : void 0) {
1623
+ if (!win || !win.document || autoInitWindows.has(win)) return;
1624
+ autoInitWindows.add(win);
1625
+ const run = () => {
1626
+ if (shouldUseHyperclay({}, win) && isHyperclayEditMode(win)) {
1627
+ _RichClay.init();
1628
+ _RichClay.watch(win);
1629
+ }
1630
+ };
1631
+ if (win.document.readyState === "loading") {
1632
+ win.document.addEventListener("DOMContentLoaded", run, { once: true });
1633
+ } else {
1634
+ run();
1635
+ }
1636
+ }
1637
+ static watch(win = typeof window !== "undefined" ? window : void 0, options = {}) {
1638
+ if (!win || !win.document || watchedWindows.has(win)) return;
1639
+ watchedWindows.add(win);
1640
+ const mount = (element) => {
1641
+ if (!isMountable(element)) return;
1642
+ if (!instances.has(element) && !conflictsWithExistingEditor(element)) new _RichClay(element, options);
1643
+ };
1644
+ const unmount = (element) => instances.get(element)?.destroy();
1645
+ const observer = new win.MutationObserver((records) => {
1646
+ records.forEach((record) => {
1647
+ if (record.type === "attributes") {
1648
+ const target = record.target;
1649
+ if (isMountable(target)) mount(target);
1650
+ else unmount(target);
1651
+ return;
1652
+ }
1653
+ record.addedNodes.forEach((node) => {
1654
+ if (node.nodeType !== 1) return;
1655
+ if (node.matches?.(RICHCLAY_SELECTOR)) mount(node);
1656
+ node.querySelectorAll?.(RICHCLAY_SELECTOR).forEach(mount);
1657
+ });
1658
+ record.removedNodes.forEach((node) => {
1659
+ if (node.nodeType !== 1) return;
1660
+ const teardown = (el) => {
1661
+ if (!el.isConnected) instances.get(el)?.destroy();
1662
+ };
1663
+ if (node.matches?.(RICHCLAY_SELECTOR)) teardown(node);
1664
+ node.querySelectorAll?.(RICHCLAY_SELECTOR).forEach(teardown);
1665
+ });
1666
+ });
1667
+ });
1668
+ observer.observe(win.document.documentElement || win.document, {
1669
+ childList: true,
1670
+ subtree: true,
1671
+ attributes: true,
1672
+ attributeFilter: ["editable", "clay-editable", "data-richclay", "richclay"]
1673
+ });
1674
+ }
1675
+ static registerButton(def) {
1676
+ validateButton(def);
1677
+ globalRegistry.set(def.id, def);
1678
+ return _RichClay;
1679
+ }
1680
+ static unregisterButton(id) {
1681
+ globalRegistry.delete(id);
1682
+ return _RichClay;
1683
+ }
1684
+ static stripFromClone(docElem) {
1685
+ stripRichClayFromClone(docElem);
1686
+ }
1687
+ // Remove richclay's runtime state from ONE element, addressed by element
1688
+ // rather than by instance.
1689
+ //
1690
+ // A host that grows a list by cloning a row clones an active region with it,
1691
+ // and the copy carries contenteditable, the marker and the runtime classes.
1692
+ // Usually there is no editor behind them and destroy() cannot reach it,
1693
+ // because destroy belongs to an instance and that clone has none. But a clone
1694
+ // carrying the author's own data-richclay is mountable, so the watcher can
1695
+ // have given it an instance before the host gets here: this cannot assume the
1696
+ // element is instanceless.
1697
+ static stripElement(element) {
1698
+ if (!element || typeof element.getAttribute !== "function") return;
1699
+ const existing = instances.get(element);
1700
+ if (existing) {
1701
+ existing.destroy();
1702
+ return;
1703
+ }
1704
+ removeRuntimeState(element, "destroy");
1705
+ }
1706
+ registerButton(def) {
1707
+ validateButton(def);
1708
+ this.registry.set(def.id, def);
1709
+ if (this.active) this.renderToolbar();
1710
+ return this;
1711
+ }
1712
+ unregisterButton(id) {
1713
+ this.registry.delete(id);
1714
+ if (this.active) this.renderToolbar();
1715
+ return this;
1716
+ }
1717
+ activate() {
1718
+ if (this.unsupported) return;
1719
+ if (this.active) return;
1720
+ this.active = true;
1721
+ this.applyInlineBox();
1722
+ ensureStyles(this.element.ownerDocument);
1723
+ consumeInertContenteditable(this.element);
1724
+ const initialNodes = this.options.inline ? Array.from(this.element.childNodes) : null;
1725
+ const initialHTML = this.options.inline ? null : this.element.innerHTML;
1726
+ const Squire = this.options.Squire || this.window.Squire || globalThis.Squire;
1727
+ if (!Squire) {
1728
+ throw new Error("RichClay requires Squire. Load vendor/squire.js before richclay.js.");
1729
+ }
1730
+ this.setupEditorAttributes();
1731
+ this.liveRegion = createLiveRegion(this.element.ownerDocument);
1732
+ this._squire = new Squire(this.element, {
1733
+ blockTag: this._blockTag,
1734
+ sanitizeToDOMFragment: (html, editor) => {
1735
+ const fragment = this.sanitizer.sanitizeToDOMFragment(html, editor);
1736
+ return this.options.singleLine ? flattenFragmentToSingleLine(fragment) : fragment;
1737
+ },
1738
+ didError: (error) => {
1739
+ console.error("RichClay/Squire error", error);
1740
+ }
1741
+ });
1742
+ if (this.options.inline) {
1743
+ this.element.replaceChildren(...initialNodes);
1744
+ this.resetSquireUndoBaseline();
1745
+ } else {
1746
+ this._squire.setHTML(initialHTML);
1747
+ }
1748
+ this.bindSquire();
1749
+ if (this.options.singleLine) this.installSingleLineGuards();
1750
+ if (this.options.inline && !this.options.singleLine) this.installRootGuards();
1751
+ this.installAppleDeleteKeys();
1752
+ if (this.blocksStayOut()) this._squire._ensureBottomLine = () => {
1753
+ };
1754
+ this.maskSquireCodeShortcut();
1755
+ this.maskSquireBlockShortcuts();
1756
+ this.installShortcuts();
1757
+ if (this.options.inline) {
1758
+ this._onToolbarKey = (event) => {
1759
+ if (event.altKey && event.key === "F10") {
1760
+ event.preventDefault();
1761
+ this.ensureFloatingToolbar();
1762
+ this.float?.toolbar.focusFirst();
1763
+ }
1764
+ };
1765
+ this.element.addEventListener("keydown", this._onToolbarKey);
1766
+ }
1767
+ if (this.blocksStayOut()) {
1768
+ const willPaste = (event) => {
1769
+ const fragment = event.detail?.fragment;
1770
+ if (!fragment) return;
1771
+ const doc = this.element.ownerDocument;
1772
+ flattenBlocks(
1773
+ fragment,
1774
+ () => this.options.singleLine ? doc.createTextNode(" ") : doc.createElement("br")
1775
+ );
1776
+ };
1777
+ this._squire.addEventListener("willPaste", willPaste);
1778
+ this._squireListeners.push(["willPaste", willPaste]);
1779
+ }
1780
+ this.renderToolbar();
1781
+ this.updatePlaceholder();
1782
+ this.warnOnBlockInInlineRegion();
1783
+ this.element.querySelectorAll("pre").forEach((pre) => this._authoredPres.add(pre));
1784
+ }
1785
+ // Live sync morphs the page against an incoming copy, and that copy is clean:
1786
+ // contenteditable, the marker and the runtime attributes all come back off an
1787
+ // element this editor is still pointing at. The instance is fine and Squire is
1788
+ // still bound; only the element's own state is gone, and activate() cannot put
1789
+ // it back because it returns early on an instance that never stopped being
1790
+ // active. Rebuilding is not an option for a host that adopted an editor the
1791
+ // author mounted, so this re-applies the state instead.
1792
+ //
1793
+ // The marker is in here because it is the one whose absence is fatal rather
1794
+ // than cosmetic: without it the watcher stops recognising the element, which
1795
+ // is the failure ensureMarker's own comment describes.
1796
+ //
1797
+ // Returns whether it had to do anything, so a caller can tell a repair from a
1798
+ // no-op without asking the same question twice.
1799
+ reattach() {
1800
+ if (this.unsupported || !this.active) return false;
1801
+ const intact = this.element.getAttribute("contenteditable") === "true" && this.element.getAttribute("data-richclay-active") === "true" && isMountable(this.element);
1802
+ if (intact) return false;
1803
+ this.ensureMarker();
1804
+ this.applyInlineBox();
1805
+ consumeInertContenteditable(this.element);
1806
+ this.setupEditorAttributes();
1807
+ return true;
1808
+ }
1809
+ // A block inside an inline element renders as a run-on. This changes the box
1810
+ // and nothing else: whether a region survives a reload is decided by the
1811
+ // parser from tag names, before any CSS exists. Runtime only, so the author's
1812
+ // file keeps no style richclay put there.
1813
+ applyInlineBox() {
1814
+ if (isInlineTag(this.element) && !this.element.style.display) {
1815
+ this.element.style.display = "inline-block";
1816
+ this.element.setAttribute("data-richclay-runtime-display", "true");
1817
+ }
1818
+ }
1819
+ getHTML() {
1820
+ return this._squire ? this._squire.getHTML() : this.element.innerHTML;
1821
+ }
1822
+ setHTML(html) {
1823
+ if (this._squire) {
1824
+ this._squire.setHTML(String(html || ""));
1825
+ } else {
1826
+ this.element.innerHTML = sanitizeHTML(
1827
+ html,
1828
+ this.sanitizeConfig,
1829
+ this.element.ownerDocument
1830
+ );
1831
+ }
1832
+ this.updatePlaceholder();
1833
+ this.toolbar?.update();
1834
+ this.options.onChange?.(this.getHTML());
1835
+ return this;
1836
+ }
1837
+ focus() {
1838
+ if (this._squire) this._squire.focus();
1839
+ else this.element.focus();
1840
+ return this;
1841
+ }
1842
+ destroy() {
1843
+ this.closeLinkDialog();
1844
+ this.teardownFloatingToolbar();
1845
+ this.toolbar?.destroy();
1846
+ this.toolbar = null;
1847
+ if (this._onToolbarKey) {
1848
+ this.element.removeEventListener("keydown", this._onToolbarKey);
1849
+ this._onToolbarKey = null;
1850
+ }
1851
+ this.liveRegion?.remove();
1852
+ this.liveRegion = null;
1853
+ this.description?.remove();
1854
+ this.description = null;
1855
+ this._shortcutKeys.forEach((key) => this._squire?.setKeyHandler?.(key, null));
1856
+ this._squireListeners.forEach(([type, listener]) => {
1857
+ this._squire?.removeEventListener?.(type, listener);
1858
+ });
1859
+ this._squireListeners = [];
1860
+ this._shortcutKeys = [];
1861
+ if (this._onBeforeInput) {
1862
+ this.element.ownerDocument.removeEventListener("beforeinput", this._onBeforeInput, true);
1863
+ this._onBeforeInput = null;
1864
+ }
1865
+ const doc = this.element.ownerDocument;
1866
+ if (this._onRootBeforeInput) {
1867
+ doc.removeEventListener("beforeinput", this._onRootBeforeInput, true);
1868
+ this._onRootBeforeInput = null;
1869
+ }
1870
+ if (this._onRootKeydown) {
1871
+ doc.removeEventListener("keydown", this._onRootKeydown, true);
1872
+ this._onRootKeydown = null;
1873
+ }
1874
+ if (this._onRootTransfer) {
1875
+ ROOT_TRANSFER_EVENTS.forEach((type) => doc.removeEventListener(type, this._onRootTransfer, true));
1876
+ this._onRootTransfer = null;
1877
+ }
1878
+ this._squire?.destroy?.();
1879
+ this._squire = null;
1880
+ this.cleanupEditorAttributes();
1881
+ instances.delete(this.element);
1882
+ }
1883
+ saveSelection() {
1884
+ this.savedSelection = getSquireSelection(this._squire) || this.savedSelection;
1885
+ return this.savedSelection;
1886
+ }
1887
+ restoreSelection() {
1888
+ return restoreSquireSelection(this._squire, this.savedSelection);
1889
+ }
1890
+ // Squire's invariant is established on the first real edit rather than at
1891
+ // activation, so that opening a page in edit mode never rewrites it. These
1892
+ // listeners capture on the document, so the root is valid before Squire's own
1893
+ // handlers see the event. History inputs are skipped: repairing mid-undo would
1894
+ // fight the stack it is replaying.
1895
+ installRootGuards() {
1896
+ const doc = this.element.ownerDocument;
1897
+ const owns = (event) => this.element.contains(event.target);
1898
+ this._onRootBeforeInput = (event) => {
1899
+ if (!owns(event) || event.isComposing || /^history/.test(event.inputType || "")) return;
1900
+ this.ensureRootIsEditable();
1901
+ };
1902
+ this._onRootKeydown = (event) => {
1903
+ if (!owns(event) || event.defaultPrevented || event.isComposing) return;
1904
+ if (this.isEditingKey(event)) this.ensureRootIsEditable();
1905
+ };
1906
+ this._onRootTransfer = (event) => {
1907
+ if (owns(event)) this.ensureRootIsEditable();
1908
+ };
1909
+ doc.addEventListener("beforeinput", this._onRootBeforeInput, true);
1910
+ doc.addEventListener("keydown", this._onRootKeydown, true);
1911
+ ROOT_TRANSFER_EVENTS.forEach((type) => {
1912
+ doc.addEventListener(type, this._onRootTransfer, true);
1913
+ });
1914
+ }
1915
+ // Re-checked rather than latched, because Hyperclay's live sync morphs new DOM
1916
+ // into the region long after activation and can reintroduce a loose text node.
1917
+ ensureRootIsEditable() {
1918
+ if (!this._squire || this.options.singleLine) return;
1919
+ const wrapBareRoot = this.options.inline;
1920
+ const blocksAllowed = !this.blocksStayOut();
1921
+ if (!editorRootNeedsNormalization(this.element, { wrapBareRoot, blocksAllowed })) return;
1922
+ const range = getSquireSelection(this._squire);
1923
+ const saved = range ? captureRange(this.element, range) : null;
1924
+ const repair = () => {
1925
+ normalizeEditorRoot(this.element, {
1926
+ blockTag: this._blockTag,
1927
+ wrapBareRoot,
1928
+ blocksAllowed,
1929
+ onBareRootWrapped: (root, wrapper) => {
1930
+ console.warn(
1931
+ `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.`,
1932
+ root,
1933
+ wrapper
1934
+ );
1935
+ }
1936
+ });
1937
+ };
1938
+ if (typeof this._squire.modifyDocument === "function") this._squire.modifyDocument(repair);
1939
+ else repair();
1940
+ if (saved) {
1941
+ restoreRange(this.element, range, saved);
1942
+ restoreSquireSelection(this._squire, range);
1943
+ this.savedSelection = cloneRange(range);
1944
+ }
1945
+ }
1946
+ // Only keys that will actually edit. Repairing is not free on a root that has
1947
+ // not been repaired yet, which is every freshly opened page, so a blanket
1948
+ // "any single-character key" made Cmd+C and Cmd+S rewrite an untouched region.
1949
+ // The only modified keys that do edit are the ones this editor rebound itself,
1950
+ // which is why the set is asked rather than Squire's dispatch mirrored.
1951
+ isEditingKey(event) {
1952
+ if (["Backspace", "Delete", "Enter"].includes(event.key)) return true;
1953
+ if (event.key === "Tab") return this.caretIsInList();
1954
+ if (event.key.length !== 1) return false;
1955
+ if (!event.ctrlKey && !event.metaKey && !event.altKey) return true;
1956
+ return event.ctrlKey && !event.metaKey && !event.altKey && this._appleDeleteKeys.has(event.key.toLowerCase());
1957
+ }
1958
+ caretIsInList() {
1959
+ const range = this._squire?.getSelection();
1960
+ const container = range?.startContainer;
1961
+ if (!container) return false;
1962
+ const element = container.nodeType === 1 ? container : container.parentElement;
1963
+ return Boolean(element && this.element.contains(element) && element.closest("ul, ol"));
1964
+ }
1965
+ runControl(def) {
1966
+ if (!this._squire || typeof def.run !== "function") return;
1967
+ if (def.isDisabled?.(this)) return;
1968
+ this.restoreSelection();
1969
+ if (def.mutates !== false) this.ensureRootIsEditable();
1970
+ const result = def.run(this);
1971
+ this.element.querySelectorAll("pre:not([data-richclay-pre])").forEach((pre) => {
1972
+ if (!this._authoredPres.has(pre)) pre.setAttribute("data-richclay-pre", "");
1973
+ });
1974
+ if (def.mutates !== false) {
1975
+ this.ensureRootIsEditable();
1976
+ this.anchorSelectionInBlock();
1977
+ }
1978
+ this.saveSelection();
1979
+ this.toolbar?.update();
1980
+ this.updatePlaceholder();
1981
+ this.announceControl(def);
1982
+ if (result !== KEEP_FOCUS) {
1983
+ this.focus();
1984
+ }
1985
+ this.warnOnBlockInInlineRegion();
1986
+ }
1987
+ // Squire's modifyBlocks leaves the caret anchored on the root itself, between
1988
+ // blocks. From there getStartBlockOfRange finds no block, so the next block
1989
+ // command is a silent no-op — the "it stopped working" the user hit. Push a
1990
+ // collapsed caret back inside the block it is sitting in front of.
1991
+ anchorSelectionInBlock() {
1992
+ const range = getSquireSelection(this._squire);
1993
+ if (!range?.collapsed || range.startContainer !== this.element) return;
1994
+ const children = Array.from(this.element.childNodes);
1995
+ const atEnd = range.startOffset >= children.length;
1996
+ const child = atEnd ? children.filter(isCaretHost).pop() : children.slice(range.startOffset).find(isCaretHost) || children.slice(0, range.startOffset).filter(isCaretHost).pop();
1997
+ if (!child) return;
1998
+ const edge = caretEdge(child, !atEnd);
1999
+ range.setStart(edge, atEnd ? nodeLength(edge) : 0);
2000
+ range.collapse(true);
2001
+ restoreSquireSelection(this._squire, range);
2002
+ }
2003
+ selectionHasFormat(tag) {
2004
+ if (!tag || !this._squire?.hasFormat) return false;
2005
+ try {
2006
+ return this._squire.hasFormat(tag);
2007
+ } catch {
2008
+ return false;
2009
+ }
2010
+ }
2011
+ // Squire sets its path to "(selection)" whenever the selection spans more than
2012
+ // one node, so asking the path answered "no" for every multi-block selection.
2013
+ // Both boundaries must land in the same matching element, or a selection that
2014
+ // merely starts in a list would report as being in one.
2015
+ pathHas(tag) {
2016
+ const range = getSquireSelection(this._squire);
2017
+ if (!range) return false;
2018
+ const selector = tag.toLowerCase();
2019
+ const start = matchAtBoundary(this.element, range.startContainer, range.startOffset, true, selector);
2020
+ return Boolean(start) && start === matchAtBoundary(this.element, range.endContainer, range.endOffset, false, selector);
2021
+ }
2022
+ // Blocks stay out of this region for either of two reasons, and both are fixed
2023
+ // for the editor's life, which is why the toolbar can leave the controls out
2024
+ // rather than grey them. Either the parser would eject a block on the next page
2025
+ // load, or the author wrote the region as a line of text and richclay does not
2026
+ // rewrite what they wrote.
2027
+ blocksStayOut() {
2028
+ return this.options.singleLine || ejectsBlocks(this.element) || keepsTextShape(this.element);
2029
+ }
2030
+ // Advisory only: the markup is stable and saves correctly, it just will not
2031
+ // validate. Checked when the editor mounts and after any command, which covers
2032
+ // the ways an author actually produces one. Paste is deliberately not covered:
2033
+ // nothing hooks paste any more, and a console note is not worth reinstating a
2034
+ // hook that took three rounds to get wrong.
2035
+ warnOnBlockInInlineRegion() {
2036
+ if (this._warnedInlineBlock || !isInlineTag(this.element)) return;
2037
+ if (!hasBlockDescendant(this.element)) return;
2038
+ this._warnedInlineBlock = true;
2039
+ const tag = this.element.nodeName.toLowerCase();
2040
+ console.warn(
2041
+ `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.`,
2042
+ this.element
2043
+ );
2044
+ }
2045
+ toggleFormat(tag, addMethod, removeMethod) {
2046
+ if (this.selectionHasFormat(tag)) return this._squire[removeMethod]();
2047
+ return this._squire[addMethod]();
2048
+ }
2049
+ toggleList(tag) {
2050
+ if (this.pathHas(tag)) return this._squire.removeList();
2051
+ return tag === "UL" ? this._squire.makeUnorderedList() : this._squire.makeOrderedList();
2052
+ }
2053
+ indent() {
2054
+ if (this.pathHas("UL") || this.pathHas("OL")) return this._squire.increaseListLevel();
2055
+ return this._squire.increaseQuoteLevel();
2056
+ }
2057
+ outdent() {
2058
+ if (this.pathHas("UL") || this.pathHas("OL")) return this._squire.decreaseListLevel();
2059
+ return this._squire.decreaseQuoteLevel();
2060
+ }
2061
+ setBlockType(tag) {
2062
+ const target = tag.toUpperCase();
2063
+ if (target === "BLOCKQUOTE") return this._squire.increaseQuoteLevel();
2064
+ return this._squire.modifyBlocks((fragment) => {
2065
+ const doc = this.element.ownerDocument;
2066
+ const output = doc.createDocumentFragment();
2067
+ Array.from(fragment.childNodes).forEach((node) => {
2068
+ if (node.nodeType !== 1) {
2069
+ output.appendChild(node);
2070
+ return;
2071
+ }
2072
+ if (node.nodeName === target) {
2073
+ output.appendChild(node);
2074
+ return;
2075
+ }
2076
+ const replacement = doc.createElement(target);
2077
+ while (node.firstChild) replacement.appendChild(node.firstChild);
2078
+ output.appendChild(replacement);
2079
+ });
2080
+ return output;
2081
+ });
2082
+ }
2083
+ openLinkDialog() {
2084
+ this.saveSelection();
2085
+ this.closeLinkDialog();
2086
+ const doc = this.element.ownerDocument;
2087
+ const existing = this.currentLinkHref();
2088
+ const dialog = doc.createElement("form");
2089
+ const labelId = `richclay-link-label-${++dialogSeq}`;
2090
+ dialog.className = "richclay-dialog";
2091
+ dialog.setAttribute("role", "dialog");
2092
+ dialog.setAttribute("aria-modal", "true");
2093
+ dialog.setAttribute("aria-labelledby", labelId);
2094
+ dialog.setAttribute("data-richclay-dialog", "");
2095
+ markChrome(dialog);
2096
+ dialog.innerHTML = `
2097
+ <div class="richclay-dialog-title" id="${labelId}">${existing ? "Edit link" : "Insert link"}</div>
2098
+ <label class="richclay-field">
2099
+ <span>URL</span>
2100
+ <input class="richclay-input" name="url" type="text" inputmode="url" autocomplete="url" placeholder="https://example.com">
2101
+ </label>
2102
+ <div class="richclay-dialog-actions">
2103
+ <button type="button" class="richclay-secondary" data-richclay-cancel>Cancel</button>
2104
+ <button type="submit" class="richclay-primary">Apply</button>
2105
+ </div>
2106
+ `;
2107
+ const close = () => {
2108
+ this.closeLinkDialog();
2109
+ this.toolbar?.update();
2110
+ this.focus();
2111
+ };
2112
+ dialog.addEventListener("submit", (event) => {
2113
+ event.preventDefault();
2114
+ const data = new FormData(dialog);
2115
+ const href = normalizeUrl(data.get("url"));
2116
+ if (!href) {
2117
+ announce(this.liveRegion, "Enter a valid URL");
2118
+ return;
2119
+ }
2120
+ this.restoreSelection();
2121
+ const anchor = this.currentLinkElement();
2122
+ if (anchor) this.selectElement(anchor);
2123
+ this._squire.makeLink(href);
2124
+ announce(this.liveRegion, "Link applied");
2125
+ close();
2126
+ });
2127
+ dialog.querySelector("[data-richclay-cancel]").addEventListener("click", close);
2128
+ dialog.addEventListener("keydown", (event) => {
2129
+ if (event.key === "Escape") {
2130
+ event.preventDefault();
2131
+ close();
2132
+ } else if (event.key === "Tab") {
2133
+ trapTab(event, dialog);
2134
+ }
2135
+ });
2136
+ const mount = this.toolbar?.root || this.element;
2137
+ mount.insertAdjacentElement("afterend", dialog);
2138
+ this.dialog = dialog;
2139
+ const input = dialog.querySelector("input");
2140
+ input.value = existing;
2141
+ input.focus();
2142
+ input.select();
2143
+ return KEEP_FOCUS;
2144
+ }
2145
+ currentLinkElement() {
2146
+ const range = getSquireSelection(this._squire);
2147
+ let node = range?.commonAncestorContainer || null;
2148
+ if (node && node.nodeType !== 1) node = node.parentElement;
2149
+ return node?.closest?.("a[href]") || null;
2150
+ }
2151
+ currentLinkHref() {
2152
+ const anchor = this.currentLinkElement();
2153
+ return anchor ? anchor.getAttribute("href") || "" : "";
2154
+ }
2155
+ selectElement(node) {
2156
+ const range = node.ownerDocument.createRange();
2157
+ range.selectNode(node);
2158
+ this._squire.setSelection(range);
2159
+ this.saveSelection();
2160
+ return range;
2161
+ }
2162
+ closeLinkDialog() {
2163
+ this.dialog?.remove();
2164
+ this.dialog = null;
2165
+ }
2166
+ ensureMarker() {
2167
+ if (!isMountable(this.element)) {
2168
+ this.element.setAttribute("data-richclay-runtime-marker", "true");
2169
+ this.element.setAttribute("data-richclay", "");
2170
+ }
2171
+ }
2172
+ setupEditorAttributes() {
2173
+ this.element.classList.add(
2174
+ this.options.inline ? "richclay-inline" : "richclay-editor",
2175
+ "richclay-active"
2176
+ );
2177
+ this.element.setAttribute("data-richclay-active", "true");
2178
+ if (!this.element.hasAttribute("data-richclay-runtime-contenteditable")) {
2179
+ const original = this.element.getAttribute("contenteditable");
2180
+ if (original === null) {
2181
+ this.element.setAttribute("data-richclay-runtime-contenteditable", "true");
2182
+ } else if (original !== "true" && original !== "") {
2183
+ this.element.setAttribute("data-richclay-runtime-contenteditable", original);
2184
+ }
2185
+ }
2186
+ this.element.setAttribute("contenteditable", "true");
2187
+ if (!this.element.hasAttribute("role")) {
2188
+ setRuntimeAttribute(this.element, "role", "textbox", "data-richclay-runtime-role");
2189
+ }
2190
+ if (!this.element.hasAttribute("aria-multiline")) {
2191
+ setRuntimeAttribute(
2192
+ this.element,
2193
+ "aria-multiline",
2194
+ this.options.singleLine ? "false" : "true",
2195
+ "data-richclay-runtime-aria-multiline"
2196
+ );
2197
+ }
2198
+ if (!this.element.hasAttribute("no-undo")) {
2199
+ setRuntimeAttribute(this.element, "no-undo", "", "data-richclay-runtime-no-undo");
2200
+ }
2201
+ if (this.options.placeholder) {
2202
+ this.element.setAttribute("data-richclay-placeholder", this.options.placeholder);
2203
+ if (!this.description) {
2204
+ this.description = this.element.ownerDocument.createElement("div");
2205
+ this.description.id = `richclay-placeholder-${++dialogSeq}`;
2206
+ this.description.className = "richclay-sr-only";
2207
+ this.description.textContent = this.options.placeholder;
2208
+ this.description.setAttribute("data-richclay-live", "");
2209
+ markChrome(this.description);
2210
+ }
2211
+ if (!this.description.isConnected) {
2212
+ this.element.insertAdjacentElement("afterend", this.description);
2213
+ }
2214
+ connectDescription(this.element, this.description);
2215
+ }
2216
+ this.element.addEventListener("focus", this._onFocus);
2217
+ this.element.addEventListener("blur", this._onBlur);
2218
+ }
2219
+ cleanupEditorAttributes() {
2220
+ removeRuntimeState(this.element, "destroy");
2221
+ this.element.removeEventListener("focus", this._onFocus);
2222
+ this.element.removeEventListener("blur", this._onBlur);
2223
+ }
2224
+ bindSquire() {
2225
+ const input = () => {
2226
+ this.saveSelection();
2227
+ this.updatePlaceholder();
2228
+ this.options.onChange?.(this.getHTML());
2229
+ this.toolbar?.update();
2230
+ };
2231
+ const pathChange = (event) => {
2232
+ this.path = event.detail?.path || this._squire.getPath?.() || "";
2233
+ this.saveSelection();
2234
+ this.toolbar?.update();
2235
+ };
2236
+ const selection = () => {
2237
+ this.saveSelection();
2238
+ this.toolbar?.update();
2239
+ if (this.options.toolbarOnSelect) this.updateFloatVisibility();
2240
+ };
2241
+ const undoState = () => this.toolbar?.update();
2242
+ [
2243
+ ["input", input],
2244
+ ["pathChange", pathChange],
2245
+ ["select", selection],
2246
+ ["cursor", selection],
2247
+ ["undoStateChange", undoState]
2248
+ ].forEach(([type, listener]) => {
2249
+ this._squire.addEventListener(type, listener);
2250
+ this._squireListeners.push([type, listener]);
2251
+ });
2252
+ }
2253
+ renderToolbar() {
2254
+ if (!this.active || this.options.readOnly) return;
2255
+ if (this.options.inline) {
2256
+ if (this.float) {
2257
+ this.teardownFloatingToolbar();
2258
+ this.ensureFloatingToolbar();
2259
+ }
2260
+ return;
2261
+ }
2262
+ this.toolbar?.destroy();
2263
+ this.toolbar = null;
2264
+ const controls = this.resolveToolbarControls(this.options.toolbar);
2265
+ if (!controls.length) return;
2266
+ this.toolbar = new Toolbar(this, controls, {
2267
+ toolbarContainer: this.options.toolbarContainer
2268
+ });
2269
+ }
2270
+ ensureFloatingToolbar() {
2271
+ if (this.float || !this.active || this.options.readOnly) return;
2272
+ const controls = this.resolveToolbarControls(this.options.toolbar);
2273
+ if (!controls.length) return;
2274
+ this.float = new FloatingToolbar(this, controls);
2275
+ this._onFloatFocusOut = (event) => this.scheduleFloatTeardown(event);
2276
+ this.float.root.addEventListener("focusout", this._onFloatFocusOut);
2277
+ this._onDocPointerDown = (event) => {
2278
+ if (this.ownsFocusTarget(event.target)) return;
2279
+ const doc = this.element.ownerDocument;
2280
+ if (doc.activeElement && this.ownsFocusTarget(doc.activeElement)) {
2281
+ doc.activeElement.blur?.();
2282
+ }
2283
+ this.teardownFloatingToolbar();
2284
+ };
2285
+ this.element.ownerDocument.addEventListener("pointerdown", this._onDocPointerDown, true);
2286
+ this.toolbar = this.float.toolbar;
2287
+ this.toolbar.update();
2288
+ if (this.options.toolbarOnSelect) this.updateFloatVisibility();
2289
+ }
2290
+ teardownFloatingToolbar() {
2291
+ if (!this.float) return;
2292
+ if (this._onFloatFocusOut) {
2293
+ this.float.root.removeEventListener("focusout", this._onFloatFocusOut);
2294
+ this._onFloatFocusOut = null;
2295
+ }
2296
+ if (this._onDocPointerDown) {
2297
+ this.element.ownerDocument.removeEventListener("pointerdown", this._onDocPointerDown, true);
2298
+ this._onDocPointerDown = null;
2299
+ }
2300
+ this.float.destroy();
2301
+ this.float = null;
2302
+ this.toolbar = null;
2303
+ }
2304
+ scheduleFloatTeardown(event) {
2305
+ const next = event?.relatedTarget;
2306
+ if (next && this.ownsFocusTarget(next)) return;
2307
+ const doc = this.element.ownerDocument;
2308
+ const win = doc.defaultView || globalThis;
2309
+ win.setTimeout(() => {
2310
+ const active = doc.activeElement;
2311
+ if (active && this.ownsFocusTarget(active)) return;
2312
+ this.teardownFloatingToolbar();
2313
+ }, 0);
2314
+ }
2315
+ ownsFocusTarget(node) {
2316
+ return node === this.element || this.element.contains(node) || Boolean(this.float?.root.contains(node)) || Boolean(this.dialog?.contains(node));
2317
+ }
2318
+ updateFloatVisibility() {
2319
+ if (!this.float) return;
2320
+ const range = this.savedSelection;
2321
+ this.float.setVisible(Boolean(range && !range.collapsed));
2322
+ }
2323
+ resolveToolbarControls(toolbar) {
2324
+ if (toolbar === false || toolbar === null || toolbar === "none") return [];
2325
+ const requested = Array.isArray(toolbar) ? toolbar : presets[toolbar] || presets.standard;
2326
+ return requested.map((item) => {
2327
+ if (typeof item === "string") {
2328
+ const def = this.registry.get(item);
2329
+ if (!def) throw new Error(`Unknown RichClay toolbar control: ${item}`);
2330
+ return def;
2331
+ }
2332
+ if (item?.type === "separator") return item;
2333
+ validateButton(item);
2334
+ return item;
2335
+ });
2336
+ }
2337
+ installShortcuts() {
2338
+ const seen = /* @__PURE__ */ new Set();
2339
+ const registryDefs = [...this.registry.values()].filter(
2340
+ (def) => !this.options.singleLine || presets.inline.includes(def.id)
2341
+ );
2342
+ const defs = [...this.resolveToolbarControls(this.options.toolbar), ...registryDefs];
2343
+ defs.forEach((def) => {
2344
+ if (def.type === "menu" || def.type === "separator") return;
2345
+ if (!def.shortcut || !def.id || seen.has(def.id)) return;
2346
+ if (def.isDisabled?.(this)) {
2347
+ const masked = shortcutKey(def.shortcut, this.window);
2348
+ this._squire.setKeyHandler(masked, null);
2349
+ this._shortcutKeys.push(masked);
2350
+ return;
2351
+ }
2352
+ seen.add(def.id);
2353
+ const key = shortcutKey(def.shortcut, this.window);
2354
+ this._squire.setKeyHandler(key, (squire, event) => {
2355
+ event.preventDefault();
2356
+ this.runControl(def);
2357
+ });
2358
+ this._shortcutKeys.push(key);
2359
+ });
2360
+ }
2361
+ // macOS sends Ctrl+D and Ctrl+H to the browser's own forward/backward delete,
2362
+ // which merges two blocks by wrapping the moved text in a computed-style
2363
+ // <span> — permanent junk in an editor whose DOM is the saved document. Point
2364
+ // them at Squire's own handlers so they behave exactly like Delete and
2365
+ // Backspace. Reads Squire's key handler table; no-ops for other engines.
2366
+ // Squire binds Code to the platform modifier itself, on _keyHandlers' prototype,
2367
+ // so dropping richclay's own shortcut is not enough to retire it. An own
2368
+ // property of null shadows the inherited handler and the key falls through.
2369
+ maskSquireCodeShortcut() {
2370
+ const key = `${isApplePlatform(this.window) ? "Meta" : "Ctrl"}-d`;
2371
+ this._squire.setKeyHandler(key, null);
2372
+ this._shortcutKeys.push(key);
2373
+ }
2374
+ // Squire binds these on _keyHandlers' prototype, and richclay has no definition
2375
+ // carrying some of them, so filtering richclay's own shortcuts cannot reach
2376
+ // them: Mod+] kept building a <blockquote> in a <p editable> through every
2377
+ // round. An own property of null shadows the inherited handler and the key
2378
+ // falls through. The prefix must match the one Squire computed, which is why
2379
+ // the test harness has to agree about the platform.
2380
+ maskSquireBlockShortcuts() {
2381
+ if (!this.blocksStayOut()) return;
2382
+ const mod = isApplePlatform(this.window) ? "Meta" : "Ctrl";
2383
+ [`${mod}-]`, `${mod}-[`, `${mod}-Shift-8`, `${mod}-Shift-9`].forEach((key) => {
2384
+ this._squire.setKeyHandler(key, null);
2385
+ this._shortcutKeys.push(key);
2386
+ });
2387
+ }
2388
+ // Squire's toggleCode() makes a block <pre> when the selection is collapsed,
2389
+ // which inside a heading or a paragraph root means a code block where no block
2390
+ // can legally go. Those regions get inline <code> instead.
2391
+ toggleCode() {
2392
+ if (!this.blocksStayOut()) return this._squire.toggleCode();
2393
+ if (this.selectionHasFormat("CODE")) return this._squire.changeFormat(null, { tag: "CODE" });
2394
+ return this._squire.changeFormat({ tag: "CODE" }, null);
2395
+ }
2396
+ installAppleDeleteKeys() {
2397
+ if (!isApplePlatform(this.window)) return;
2398
+ const handlers = this._squire._keyHandlers;
2399
+ if (!handlers) return;
2400
+ [["Ctrl-d", "Delete"], ["Ctrl-h", "Backspace"]].forEach(([key, native]) => {
2401
+ if (typeof handlers[native] !== "function") return;
2402
+ this._squire.setKeyHandler(key, handlers[native]);
2403
+ this._shortcutKeys.push(key);
2404
+ this._appleDeleteKeys.add(key.slice("Ctrl-".length));
2405
+ });
2406
+ }
2407
+ installSingleLineGuards() {
2408
+ ["Enter", "Shift-Enter"].forEach((key) => {
2409
+ this._squire.setKeyHandler(key, (squire, event) => event.preventDefault());
2410
+ this._shortcutKeys.push(key);
2411
+ });
2412
+ this._onBeforeInput = (event) => {
2413
+ if (!this.element.contains(event.target)) return;
2414
+ if (event.inputType === "insertParagraph" || event.inputType === "insertLineBreak") {
2415
+ event.preventDefault();
2416
+ event.stopPropagation();
2417
+ }
2418
+ };
2419
+ this.element.ownerDocument.addEventListener("beforeinput", this._onBeforeInput, true);
2420
+ }
2421
+ // The fidelity restore bypasses setHTML, so Squire's undo baseline is still
2422
+ // the empty document its constructor recorded; the first undo could wipe the
2423
+ // region. Rebase the stack on the restored content. This touches Squire
2424
+ // internals (property names survive its esbuild minify) and no-ops for other
2425
+ // engines, like the tests' FakeSquire.
2426
+ resetSquireUndoBaseline() {
2427
+ const squire = this._squire;
2428
+ if (!squire || !Array.isArray(squire._undoStack) || typeof squire.saveUndoState !== "function") {
2429
+ return;
2430
+ }
2431
+ squire._undoStack.length = 0;
2432
+ squire._undoStackLength = 0;
2433
+ squire._undoIndex = -1;
2434
+ squire._isInUndoState = false;
2435
+ squire.saveUndoState();
2436
+ }
2437
+ updatePlaceholder() {
2438
+ const text = (this.element.textContent || "").replace(/\u200B/g, "").trim();
2439
+ const hasMeaningfulElement = this.element.querySelector("img, video, audio, iframe, table");
2440
+ const isEmpty = !text && !hasMeaningfulElement;
2441
+ this.element.classList.toggle("richclay-empty", isEmpty);
2442
+ }
2443
+ announceControl(def) {
2444
+ if (!this.liveRegion) return;
2445
+ const label = def.ariaLabel || def.label;
2446
+ if (!label) return;
2447
+ if (typeof def.isActive === "function") {
2448
+ announce(this.liveRegion, `${label} ${def.isActive(this) ? "on" : "off"}`);
2449
+ } else {
2450
+ announce(this.liveRegion, `${label} applied`);
2451
+ }
2452
+ }
2453
+ };
2454
+ function resolveElements(selector) {
2455
+ if (typeof selector === "string") {
2456
+ return Array.from(document.querySelectorAll(selector));
2457
+ }
2458
+ if (selector?.nodeType === 1) return [selector];
2459
+ return Array.from(selector || []);
2460
+ }
2461
+ function matchAtBoundary(root, container, offset, first, selector) {
2462
+ let node = container;
2463
+ if (node === root) {
2464
+ const children = Array.from(root.childNodes).filter(isCaretHost);
2465
+ if (!children.length) return null;
2466
+ const index = first ? Math.min(offset, children.length - 1) : Math.min(Math.max(offset - 1, 0), children.length - 1);
2467
+ node = caretEdge(children[index], first);
2468
+ }
2469
+ const element = node?.nodeType === 1 ? node : node?.parentElement;
2470
+ const match = element?.closest?.(selector);
2471
+ return match && root.contains(match) && match !== root ? match : null;
2472
+ }
2473
+ function closestConflictingAncestor(element) {
2474
+ let node = element.parentElement;
2475
+ while (node) {
2476
+ if (instances.has(node) || isMountable(node)) return node;
2477
+ node = node.parentElement;
2478
+ }
2479
+ return null;
2480
+ }
2481
+ function isMountable(element) {
2482
+ return Boolean(element.matches?.(RICHCLAY_SELECTOR)) && isRichClayHost(element);
2483
+ }
2484
+ function conflictsWithExistingEditor(element) {
2485
+ const host = closestConflictingAncestor(element);
2486
+ const nested = Array.from(element.querySelectorAll?.(RICHCLAY_SELECTOR) || []).find(
2487
+ (node) => instances.has(node)
2488
+ );
2489
+ const other = host || nested;
2490
+ if (!other) return false;
2491
+ console.warn(
2492
+ "richclay: nested editable regions are not supported, so this one was skipped. Remove the editable attribute from either it or the other region.",
2493
+ element,
2494
+ other
2495
+ );
2496
+ return true;
2497
+ }
2498
+ function refuseUnsupportedRoot(element) {
2499
+ if (!isUnsupportedRootTag(element)) return false;
2500
+ console.warn(`richclay: ${unsupportedRootReason(element)} This region was skipped.`, element);
2501
+ return true;
2502
+ }
2503
+ function validateButton(def) {
2504
+ if (!def || !def.id) throw new Error("RichClay button definitions require an id.");
2505
+ if (def.type !== "menu" && typeof def.run !== "function") {
2506
+ throw new Error(`RichClay button "${def.id}" requires a run(editor) function.`);
2507
+ }
2508
+ }
2509
+ function shortcutKey(shortcut, win) {
2510
+ const parts = shortcut.split("+");
2511
+ const keyPart = parts[parts.length - 1];
2512
+ const modifiers = new Set(parts.slice(0, -1).map(normalizeModifier));
2513
+ if (modifiers.delete("Mod")) modifiers.add(isApplePlatform(win) ? "Meta" : "Ctrl");
2514
+ const key = keyPart.length === 1 ? modifiers.has("Shift") ? keyPart.toUpperCase() : keyPart.toLowerCase() : keyPart;
2515
+ const prefix = MODIFIER_ORDER.filter((modifier) => modifiers.has(modifier)).join("-");
2516
+ return `${prefix ? `${prefix}-` : ""}${key}`;
2517
+ }
2518
+ function normalizeModifier(modifier) {
2519
+ return modifier === "Cmd" ? "Meta" : modifier;
2520
+ }
2521
+ function trapTab(event, container) {
2522
+ const focusables = container.querySelectorAll(
2523
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
2524
+ );
2525
+ if (!focusables.length) return;
2526
+ const first = focusables[0];
2527
+ const last = focusables[focusables.length - 1];
2528
+ const active = container.ownerDocument.activeElement;
2529
+ if (event.shiftKey && active === first) {
2530
+ event.preventDefault();
2531
+ last.focus();
2532
+ } else if (!event.shiftKey && active === last) {
2533
+ event.preventDefault();
2534
+ first.focus();
2535
+ }
2536
+ }
2537
+
2538
+ // richclay.css
2539
+ var richclay_default = `:root {
2540
+ --richclay-surface: #ffffff;
2541
+ --richclay-surface-alt: #f6f7f9;
2542
+ --richclay-text: #0b0e14;
2543
+ --richclay-muted: #667085;
2544
+ --richclay-border: #c9ced8;
2545
+ --richclay-accent: #0f766e;
2546
+ --richclay-accent-ink: #ffffff;
2547
+ --richclay-focus: #2563eb;
2548
+ --richclay-danger: #b42318;
2549
+ --richclay-radius: 6px;
2550
+ --richclay-control-size: 34px;
2551
+ --richclay-shadow: 0 10px 24px rgb(15 23 42 / 0.16);
2552
+ }
2553
+
2554
+ @media (prefers-color-scheme: dark) {
2555
+ :root {
2556
+ --richclay-surface: #16181d;
2557
+ --richclay-surface-alt: #22252c;
2558
+ --richclay-text: #ffffff;
2559
+ --richclay-muted: #a3aab8;
2560
+ --richclay-border: #4a5260;
2561
+ --richclay-accent: #2dd4bf;
2562
+ --richclay-accent-ink: #05201d;
2563
+ --richclay-focus: #93c5fd;
2564
+ --richclay-shadow: 0 12px 28px rgb(0 0 0 / 0.35);
2565
+ }
2566
+ }
2567
+
2568
+ .richclay-toolbar {
2569
+ align-items: center;
2570
+ background: var(--richclay-surface-alt);
2571
+ border: 1px solid var(--richclay-border);
2572
+ border-radius: var(--richclay-radius);
2573
+ color: var(--richclay-text);
2574
+ display: flex;
2575
+ flex-wrap: wrap;
2576
+ gap: 2px;
2577
+ margin-block: 0 6px;
2578
+ padding: 4px;
2579
+ }
2580
+
2581
+ /* Seamless card: in the default layout the toolbar sits directly above the
2582
+ editor. Fuse them into a single bordered card with one divider \u2014 the
2583
+ toolbar's bottom border \u2014 instead of two stacked, separated boxes. */
2584
+ .richclay-toolbar:has(+ .richclay-editor) {
2585
+ margin-block-end: 0;
2586
+ border-bottom-left-radius: 0;
2587
+ border-bottom-right-radius: 0;
2588
+ }
2589
+
2590
+ .richclay-toolbar + .richclay-editor {
2591
+ border-top: 0;
2592
+ border-top-left-radius: 0;
2593
+ border-top-right-radius: 0;
2594
+ }
2595
+
2596
+ /* In a fused card, draw the focus ring inside the body so it never crosses
2597
+ the toolbar divider. */
2598
+ .richclay-toolbar + .richclay-editor:focus-visible {
2599
+ outline-offset: -3px;
2600
+ }
2601
+
2602
+ .richclay-button {
2603
+ align-items: center;
2604
+ appearance: none;
2605
+ background: transparent;
2606
+ border: 1px solid transparent;
2607
+ border-radius: 5px;
2608
+ color: currentColor;
2609
+ display: inline-flex;
2610
+ height: var(--richclay-control-size);
2611
+ justify-content: center;
2612
+ margin: 0;
2613
+ min-width: var(--richclay-control-size);
2614
+ padding: 0;
2615
+ position: relative;
2616
+ }
2617
+
2618
+ .richclay-button:hover,
2619
+ .richclay-button[aria-expanded="true"],
2620
+ .richclay-menu-item:hover {
2621
+ background: var(--richclay-surface);
2622
+ border-color: var(--richclay-border);
2623
+ }
2624
+
2625
+ .richclay-button.is-active,
2626
+ .richclay-button[aria-pressed="true"],
2627
+ .richclay-menu-item.is-active {
2628
+ background: rgb(0 0 0 / 0.13);
2629
+ border-color: rgb(0 0 0 / 0.22);
2630
+ color: var(--richclay-text);
2631
+ box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.26);
2632
+ }
2633
+
2634
+ .richclay-button:focus-visible,
2635
+ .richclay-menu-item:focus-visible,
2636
+ .richclay-editor:focus-visible,
2637
+ .richclay-input:focus-visible,
2638
+ .richclay-primary:focus-visible,
2639
+ .richclay-secondary:focus-visible {
2640
+ outline: 3px solid var(--richclay-focus);
2641
+ outline-offset: 2px;
2642
+ }
2643
+
2644
+ .richclay-button:disabled {
2645
+ color: var(--richclay-muted);
2646
+ cursor: not-allowed;
2647
+ opacity: 0.55;
2648
+ }
2649
+
2650
+ /* Remove-link icon: the standard link glyph with a diagonal cut painted in the
2651
+ button's own background colour, so the chain reads as severed in every state.
2652
+ The cut tracks the background the button shows: the toolbar surface at rest,
2653
+ the raised surface on hover. */
2654
+ .richclay-cut {
2655
+ stroke: var(--richclay-surface-alt);
2656
+ }
2657
+
2658
+ .richclay-button:hover .richclay-cut,
2659
+ .richclay-button[aria-expanded="true"] .richclay-cut {
2660
+ stroke: var(--richclay-surface);
2661
+ }
2662
+
2663
+ .richclay-separator {
2664
+ align-self: stretch;
2665
+ background: var(--richclay-border);
2666
+ display: inline-block;
2667
+ margin: 5px 4px;
2668
+ width: 1px;
2669
+ }
2670
+
2671
+ .richclay-menu-wrap {
2672
+ display: inline-flex;
2673
+ position: relative;
2674
+ }
2675
+
2676
+ .richclay-menu {
2677
+ background: var(--richclay-surface);
2678
+ border: 1px solid var(--richclay-border);
2679
+ border-radius: var(--richclay-radius);
2680
+ box-shadow: var(--richclay-shadow);
2681
+ color: var(--richclay-text);
2682
+ display: grid;
2683
+ gap: 2px;
2684
+ inset-block-start: calc(100% + 4px);
2685
+ inset-inline-start: 0;
2686
+ min-width: 160px;
2687
+ padding: 4px;
2688
+ position: absolute;
2689
+ z-index: 1000;
2690
+ }
2691
+
2692
+ .richclay-menu[hidden] {
2693
+ display: none;
2694
+ }
2695
+
2696
+ .richclay-menu-item {
2697
+ appearance: none;
2698
+ background: transparent;
2699
+ border: 1px solid transparent;
2700
+ border-radius: 4px;
2701
+ color: inherit;
2702
+ font: inherit;
2703
+ min-height: 32px;
2704
+ padding: 5px 9px;
2705
+ text-align: start;
2706
+ }
2707
+
2708
+ .richclay-editor {
2709
+ background: var(--richclay-surface);
2710
+ border: 1px solid var(--richclay-border);
2711
+ border-radius: var(--richclay-radius);
2712
+ color: var(--richclay-text);
2713
+ min-height: 9rem;
2714
+ /* Break a long unbreakable string (e.g. a bare URL) in prose rather than let
2715
+ it widen the editor. \`anywhere\` also lowers min-content, so the editor can't
2716
+ grow even as a flex/grid item. Inert on the code <pre> (white-space: pre). */
2717
+ overflow-wrap: anywhere;
2718
+ padding: 12px;
2719
+ position: relative;
2720
+ }
2721
+
2722
+ .richclay-editor.richclay-empty::before {
2723
+ color: var(--richclay-muted);
2724
+ content: attr(data-richclay-placeholder);
2725
+ inset-block-start: 12px;
2726
+ inset-inline-start: 12px;
2727
+ pointer-events: none;
2728
+ position: absolute;
2729
+ }
2730
+
2731
+ .richclay-editor p,
2732
+ .richclay-editor h1,
2733
+ .richclay-editor h2,
2734
+ .richclay-editor h3,
2735
+ .richclay-editor h4,
2736
+ .richclay-editor h5,
2737
+ .richclay-editor h6,
2738
+ .richclay-editor blockquote,
2739
+ .richclay-editor pre,
2740
+ .richclay-editor ul,
2741
+ .richclay-editor ol {
2742
+ margin-block: 0 0.75em;
2743
+ }
2744
+
2745
+ .richclay-editor > :last-child {
2746
+ margin-block-end: 0;
2747
+ }
2748
+
2749
+ /* Inline editors ([editable]) inherit the page's own styling. richclay adds
2750
+ only a caret, a discoverability outline, and empty-state affordances. */
2751
+ .richclay-inline {
2752
+ caret-color: currentColor;
2753
+ }
2754
+
2755
+ .richclay-inline:hover {
2756
+ outline: 1px dashed var(--richclay-border);
2757
+ outline-offset: 2px;
2758
+ }
2759
+
2760
+ .richclay-inline.richclay-focused,
2761
+ .richclay-inline:focus-visible {
2762
+ outline: 2px dashed var(--richclay-focus);
2763
+ outline-offset: 2px;
2764
+ }
2765
+
2766
+ .richclay-inline.richclay-empty {
2767
+ min-height: 1em;
2768
+ min-width: 1ch;
2769
+ }
2770
+
2771
+ .richclay-inline.richclay-empty::before {
2772
+ color: var(--richclay-muted);
2773
+ content: attr(data-richclay-placeholder);
2774
+ pointer-events: none;
2775
+ }
2776
+
2777
+ /* Floating toolbar shell for inline editors. Fixed-position and body-mounted:
2778
+ fixed elements never create scrollbars, and body mounting avoids transformed
2779
+ ancestors silently turning fixed into ancestor-relative. */
2780
+ .richclay-float {
2781
+ left: 0;
2782
+ position: fixed;
2783
+ top: 0;
2784
+ will-change: transform;
2785
+ z-index: 99999;
2786
+ }
2787
+
2788
+ .richclay-float .richclay-toolbar {
2789
+ box-shadow: var(--richclay-shadow);
2790
+ margin-block: 0;
2791
+ }
2792
+
2793
+ .richclay-float .richclay-dialog {
2794
+ margin-block: 6px 0;
2795
+ }
2796
+
2797
+ /* Compact vertical rail for narrow margins. */
2798
+ .richclay-float-rail .richclay-toolbar {
2799
+ --richclay-control-size: 28px;
2800
+ flex-direction: column;
2801
+ flex-wrap: nowrap;
2802
+ }
2803
+
2804
+ .richclay-float-rail .richclay-toolbar svg {
2805
+ height: 15px;
2806
+ width: 15px;
2807
+ }
2808
+
2809
+ .richclay-float-rail .richclay-separator {
2810
+ align-self: stretch;
2811
+ height: 1px;
2812
+ margin: 4px 5px;
2813
+ width: auto;
2814
+ }
2815
+
2816
+ .richclay-float-rail .richclay-menu {
2817
+ inset-block-start: 0;
2818
+ inset-inline-start: calc(100% + 4px);
2819
+ }
2820
+
2821
+ .richclay-editor blockquote {
2822
+ border-inline-start: 3px solid var(--richclay-border);
2823
+ color: var(--richclay-muted);
2824
+ margin-inline: 0;
2825
+ padding-inline-start: 12px;
2826
+ }
2827
+
2828
+ /* Code blocks scroll horizontally inside the editor instead of widening it.
2829
+ \`width: 0; min-width: 100%\` zeroes the pre's intrinsic-width contribution so a
2830
+ long unbreakable line can't push an intrinsically-sized ancestor (flex/grid
2831
+ item, inline-block, table cell) wider, while still filling the editor and
2832
+ scrolling its own overflow. box-sizing keeps padding inside that 100%.
2833
+ Containment applies to inline editors too: \`white-space: pre\` never wraps, so
2834
+ without it one code block runs off the side of the page. Only the decoration
2835
+ below stays card-scoped, since inline mode keeps the page's own typography. */
2836
+ .richclay-editor pre,
2837
+ .richclay-inline pre {
2838
+ box-sizing: border-box;
2839
+ min-width: 100%;
2840
+ overflow: auto;
2841
+ width: 0;
2842
+ }
2843
+
2844
+ .richclay-editor pre {
2845
+ background: var(--richclay-surface-alt);
2846
+ border-radius: 4px;
2847
+ padding: 10px;
2848
+ }
2849
+
2850
+ .richclay-dialog {
2851
+ background: var(--richclay-surface);
2852
+ border: 1px solid var(--richclay-border);
2853
+ border-radius: var(--richclay-radius);
2854
+ box-shadow: var(--richclay-shadow);
2855
+ color: var(--richclay-text);
2856
+ display: grid;
2857
+ gap: 10px;
2858
+ margin-block: 6px;
2859
+ max-width: 420px;
2860
+ padding: 12px;
2861
+ }
2862
+
2863
+ .richclay-dialog-title {
2864
+ font-weight: 650;
2865
+ }
2866
+
2867
+ .richclay-field {
2868
+ display: grid;
2869
+ gap: 4px;
2870
+ }
2871
+
2872
+ .richclay-field span {
2873
+ color: var(--richclay-muted);
2874
+ font-size: 0.875rem;
2875
+ }
2876
+
2877
+ .richclay-input {
2878
+ background: var(--richclay-surface);
2879
+ border: 1px solid var(--richclay-border);
2880
+ border-radius: 4px;
2881
+ color: var(--richclay-text);
2882
+ font: inherit;
2883
+ min-height: 36px;
2884
+ padding: 6px 8px;
2885
+ }
2886
+
2887
+ .richclay-dialog-actions {
2888
+ display: flex;
2889
+ gap: 8px;
2890
+ justify-content: flex-end;
2891
+ }
2892
+
2893
+ .richclay-primary,
2894
+ .richclay-secondary {
2895
+ appearance: none;
2896
+ border: 1px solid var(--richclay-border);
2897
+ border-radius: 4px;
2898
+ font: inherit;
2899
+ min-height: 34px;
2900
+ padding: 5px 10px;
2901
+ }
2902
+
2903
+ .richclay-primary {
2904
+ background: var(--richclay-accent);
2905
+ border-color: var(--richclay-accent);
2906
+ color: var(--richclay-accent-ink);
2907
+ }
2908
+
2909
+ .richclay-secondary {
2910
+ background: var(--richclay-surface-alt);
2911
+ color: var(--richclay-text);
2912
+ }
2913
+
2914
+ .richclay-sr-only {
2915
+ border: 0;
2916
+ clip: rect(0 0 0 0);
2917
+ clip-path: inset(50%);
2918
+ height: 1px;
2919
+ margin: -1px;
2920
+ overflow: hidden;
2921
+ padding: 0;
2922
+ position: absolute;
2923
+ white-space: nowrap;
2924
+ width: 1px;
2925
+ }
2926
+
2927
+ @media (forced-colors: active) {
2928
+ .richclay-toolbar,
2929
+ .richclay-editor,
2930
+ .richclay-menu,
2931
+ .richclay-dialog,
2932
+ .richclay-input {
2933
+ border-color: CanvasText;
2934
+ }
2935
+
2936
+ .richclay-button.is-active,
2937
+ .richclay-button[aria-pressed="true"],
2938
+ .richclay-menu-item.is-active,
2939
+ .richclay-primary {
2940
+ background: Highlight;
2941
+ color: HighlightText;
2942
+ }
2943
+
2944
+ .richclay-button:focus-visible,
2945
+ .richclay-menu-item:focus-visible,
2946
+ .richclay-editor:focus-visible,
2947
+ .richclay-input:focus-visible {
2948
+ outline-color: Highlight;
2949
+ }
2950
+
2951
+ .richclay-cut {
2952
+ stroke: Canvas;
2953
+ }
2954
+ }
2955
+ `;
2956
+
2957
+ // src/browser-global.js
2958
+ setRichClayStyles(richclay_default);
2959
+ var browser_global_default = RichClay;
2960
+ return __toCommonJS(browser_global_exports);
2961
+ })();
2962
+ window.RichClay = RichClayBundle.default;