@vonage/vivid 5.20.1 → 5.22.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 (302) hide show
  1. package/accordion/index.cjs +1 -1
  2. package/accordion/index.js +2 -2
  3. package/alert/index.cjs +1 -1
  4. package/alert/index.js +2 -2
  5. package/audio-player/index.cjs +3 -5
  6. package/audio-player/index.js +30 -32
  7. package/avatar/index.cjs +1 -1
  8. package/avatar/index.js +1 -1
  9. package/banner/index.cjs +1 -1
  10. package/banner/index.js +2 -2
  11. package/bundled/affix.cjs +4 -4
  12. package/bundled/affix.js +4 -4
  13. package/bundled/base-color-picker.cjs +2 -2
  14. package/bundled/base-color-picker.js +2 -2
  15. package/bundled/button.cjs +1 -1
  16. package/bundled/button.js +1 -1
  17. package/bundled/definition10.cjs +1 -1
  18. package/bundled/definition10.js +1 -1
  19. package/bundled/definition13.cjs +1 -1
  20. package/bundled/definition13.js +1 -1
  21. package/bundled/definition15.cjs +1 -1
  22. package/bundled/definition15.js +2 -2
  23. package/bundled/definition16.cjs +2 -2
  24. package/bundled/definition16.js +2 -2
  25. package/bundled/definition17.cjs +1 -1
  26. package/bundled/definition17.js +1 -1
  27. package/bundled/definition18.cjs +2 -2
  28. package/bundled/definition18.js +15 -6
  29. package/bundled/definition19.cjs +1 -1
  30. package/bundled/definition19.js +1 -1
  31. package/bundled/definition20.cjs +1 -1
  32. package/bundled/definition20.js +1 -1
  33. package/bundled/definition21.cjs +1 -1
  34. package/bundled/definition21.js +2 -2
  35. package/bundled/definition24.cjs +1 -1
  36. package/bundled/definition24.js +1 -1
  37. package/bundled/definition3.cjs +1 -1
  38. package/bundled/definition3.js +3 -3
  39. package/bundled/definition4.cjs +1 -1
  40. package/bundled/definition4.js +1 -1
  41. package/bundled/definition5.cjs +1 -1
  42. package/bundled/definition5.js +1 -1
  43. package/bundled/definition6.cjs +1 -1
  44. package/bundled/definition6.js +2 -2
  45. package/bundled/definition8.cjs +1 -1
  46. package/bundled/definition8.js +12 -6
  47. package/bundled/definition9.cjs +1 -1
  48. package/bundled/definition9.js +1 -1
  49. package/bundled/form-associated.cjs +1 -1
  50. package/bundled/form-associated.js +2 -2
  51. package/bundled/listbox.cjs +1 -1
  52. package/bundled/listbox.js +5 -5
  53. package/bundled/numberConverter.cjs +1 -1
  54. package/bundled/numberConverter.js +1 -1
  55. package/bundled/slider.template.cjs +1 -1
  56. package/bundled/slider.template.js +1 -1
  57. package/bundled/vivid-element.cjs +1 -1
  58. package/bundled/vivid-element.js +1 -1
  59. package/bundled/with-contextual-help.cjs +1 -1
  60. package/bundled/with-contextual-help.js +1 -1
  61. package/calendar/index.cjs +1 -1
  62. package/calendar/index.js +3 -3
  63. package/color-picker/index.cjs +20 -20
  64. package/color-picker/index.js +22 -22
  65. package/combobox/index.cjs +1 -1
  66. package/combobox/index.js +7 -7
  67. package/country-group/index.cjs +1 -1
  68. package/country-group/index.js +1 -1
  69. package/custom-elements.json +227 -185
  70. package/data-grid/index.cjs +2 -2
  71. package/data-grid/index.js +12 -10
  72. package/dial-pad/index.cjs +1 -1
  73. package/dial-pad/index.js +2 -2
  74. package/dialog/index.cjs +1 -1
  75. package/dialog/index.js +1 -1
  76. package/file-picker/index.cjs +1 -1
  77. package/file-picker/index.js +1 -1
  78. package/header/index.cjs +1 -1
  79. package/header/index.js +1 -1
  80. package/lib/audio-player/audio-player.d.ts +0 -1
  81. package/lib/calendar/helpers/calendar.keyboard-interactions.d.ts +1 -1
  82. package/lib/checkbox/checkbox.d.ts +16 -16
  83. package/lib/color-picker/color-picker.d.ts +16 -16
  84. package/lib/combobox/combobox.d.ts +21 -21
  85. package/lib/date-picker/date-picker.d.ts +26 -26
  86. package/lib/date-range-picker/date-range-picker.d.ts +14 -14
  87. package/lib/date-time-picker/date-time-picker.d.ts +24 -24
  88. package/lib/dial-pad/dial-pad.d.ts +1 -1
  89. package/lib/file-picker/file-picker.d.ts +21 -21
  90. package/lib/menu/menu.d.ts +1 -1
  91. package/lib/number-field/number-field.d.ts +22 -22
  92. package/lib/popup/popup.d.ts +2 -1
  93. package/lib/radio/radio.d.ts +16 -16
  94. package/lib/range-slider/range-slider.d.ts +10 -10
  95. package/lib/rich-text-editor/popover.d.ts +1 -1
  96. package/lib/rich-text-editor/rte/exports.d.ts +1 -0
  97. package/lib/rich-text-editor/rte/feature.d.ts +5 -5
  98. package/lib/rich-text-editor/rte/features/base.d.ts +1 -0
  99. package/lib/rich-text-editor/rte/features/drop-handler.d.ts +1 -2
  100. package/lib/rich-text-editor/rte/features/internal/core.d.ts +8 -0
  101. package/lib/rich-text-editor/rte/features/internal/history.d.ts +6 -0
  102. package/lib/rich-text-editor/rte/features/keyboard-shortcuts.d.ts +1 -1
  103. package/lib/rich-text-editor/rte/features/text-color-picker.d.ts +1 -1
  104. package/lib/rich-text-editor/rte/instance.d.ts +1 -0
  105. package/lib/rich-text-editor/rte/public-interface.d.ts +3 -0
  106. package/lib/rich-text-editor/rte/utils/ui.d.ts +1 -1
  107. package/lib/searchable-select/searchable-select.d.ts +21 -21
  108. package/lib/select/select.d.ts +22 -22
  109. package/lib/simple-color-picker/simple-color-picker.d.ts +5 -5
  110. package/lib/slider/slider.d.ts +5 -5
  111. package/lib/switch/switch.d.ts +5 -5
  112. package/lib/table/table-body.template.d.ts +1 -2
  113. package/lib/table/table-cell.template.d.ts +1 -2
  114. package/lib/table/table-head.template.d.ts +1 -2
  115. package/lib/table/table-row.template.d.ts +1 -2
  116. package/lib/table/table.template.d.ts +1 -2
  117. package/lib/text-area/text-area.d.ts +21 -21
  118. package/lib/text-field/text-field.d.ts +22 -22
  119. package/lib/time-picker/time-picker.d.ts +12 -12
  120. package/locales/de-DE.cjs +178 -4
  121. package/locales/de-DE.js +179 -2
  122. package/locales/en-GB.cjs +9 -4
  123. package/locales/en-GB.js +10 -2
  124. package/locales/en-US.cjs +2 -270
  125. package/locales/en-US.js +1 -267
  126. package/locales/ja-JP.cjs +171 -4
  127. package/locales/ja-JP.js +172 -2
  128. package/locales/zh-CN.cjs +172 -4
  129. package/locales/zh-CN.js +173 -2
  130. package/nav-disclosure/index.cjs +1 -1
  131. package/nav-disclosure/index.js +1 -1
  132. package/nav-item/index.cjs +1 -1
  133. package/nav-item/index.js +1 -1
  134. package/package.json +7 -4
  135. package/pagination/index.cjs +1 -1
  136. package/pagination/index.js +9 -2
  137. package/popover/index.cjs +1 -1
  138. package/popover/index.js +1 -1
  139. package/progress/index.cjs +1 -1
  140. package/progress/index.js +1 -1
  141. package/radio-group/index.cjs +1 -1
  142. package/radio-group/index.js +21 -19
  143. package/range-slider/index.cjs +1 -1
  144. package/range-slider/index.js +1 -1
  145. package/rich-text-editor/index.cjs +13 -12
  146. package/rich-text-editor/index.js +1085 -1057
  147. package/searchable-select/index.cjs +1 -1
  148. package/searchable-select/index.js +1 -1
  149. package/selectable-box/index.cjs +1 -1
  150. package/selectable-box/index.js +6 -1
  151. package/shared/color-picker/base-color-picker.d.ts +5 -5
  152. package/shared/feedback/mixins.d.ts +1 -1
  153. package/shared/foundation/button/button.d.ts +5 -5
  154. package/shared/foundation/form-associated/form-associated.d.ts +10 -10
  155. package/shared/foundation/listbox/listbox.d.ts +1 -1
  156. package/shared/patterns/form-elements/form-element.d.ts +5 -5
  157. package/shared/patterns/form-elements/with-contextual-help.d.ts +5 -5
  158. package/shared/patterns/form-elements/with-error-text.d.ts +6 -6
  159. package/shared/picker-field/mixins/calendar-picker.d.ts +6 -6
  160. package/shared/picker-field/mixins/calendar-picker.template.d.ts +6 -6
  161. package/shared/picker-field/mixins/min-max-calendar-picker.d.ts +8 -8
  162. package/shared/picker-field/mixins/single-date-picker.d.ts +6 -6
  163. package/shared/picker-field/mixins/single-value-picker.d.ts +6 -6
  164. package/shared/picker-field/mixins/time-selection-picker.d.ts +6 -6
  165. package/shared/picker-field/mixins/time-selection-picker.template.d.ts +12 -12
  166. package/shared/picker-field/picker-field.d.ts +21 -21
  167. package/shared/utils/slottable-request.d.ts +1 -1
  168. package/switch/index.cjs +2 -2
  169. package/switch/index.js +2 -2
  170. package/table/index.cjs +5 -5
  171. package/table/index.js +5 -5
  172. package/tabs/index.cjs +1 -1
  173. package/tabs/index.js +3 -3
  174. package/tag/index.cjs +1 -1
  175. package/tag/index.js +1 -1
  176. package/tree-view/index.cjs +1 -1
  177. package/tree-view/index.js +0 -2
  178. package/unbundled/affix.cjs +4 -4
  179. package/unbundled/affix.js +4 -4
  180. package/unbundled/base-color-picker.cjs +3 -3
  181. package/unbundled/base-color-picker.js +3 -3
  182. package/unbundled/button.cjs +5 -5
  183. package/unbundled/button.js +5 -5
  184. package/unbundled/chunk.cjs +15 -0
  185. package/unbundled/chunk.js +13 -0
  186. package/unbundled/definition10.cjs +15 -4
  187. package/unbundled/definition10.js +15 -4
  188. package/unbundled/definition11.cjs +3 -3
  189. package/unbundled/definition11.js +3 -3
  190. package/unbundled/definition12.cjs +1 -1
  191. package/unbundled/definition12.js +1 -1
  192. package/unbundled/definition13.cjs +2 -2
  193. package/unbundled/definition13.js +2 -2
  194. package/unbundled/definition14.cjs +73 -76
  195. package/unbundled/definition14.js +73 -76
  196. package/unbundled/definition15.cjs +1 -1
  197. package/unbundled/definition15.js +1 -1
  198. package/unbundled/definition17.cjs +2 -2
  199. package/unbundled/definition17.js +2 -2
  200. package/unbundled/definition19.cjs +2 -1
  201. package/unbundled/definition19.js +2 -1
  202. package/unbundled/definition2.cjs +1 -1
  203. package/unbundled/definition2.js +1 -1
  204. package/unbundled/definition21.cjs +3 -3
  205. package/unbundled/definition21.js +3 -3
  206. package/unbundled/definition23.cjs +2 -2
  207. package/unbundled/definition23.js +2 -2
  208. package/unbundled/definition24.cjs +1 -1
  209. package/unbundled/definition24.js +1 -1
  210. package/unbundled/definition27.cjs +1 -1
  211. package/unbundled/definition27.js +1 -1
  212. package/unbundled/definition28.cjs +23 -23
  213. package/unbundled/definition28.js +23 -23
  214. package/unbundled/definition29.cjs +1 -1
  215. package/unbundled/definition29.js +1 -1
  216. package/unbundled/definition3.cjs +2 -2
  217. package/unbundled/definition3.js +2 -2
  218. package/unbundled/definition30.cjs +8 -8
  219. package/unbundled/definition30.js +8 -8
  220. package/unbundled/definition32.cjs +1 -1
  221. package/unbundled/definition32.js +1 -1
  222. package/unbundled/definition34.cjs +9 -8
  223. package/unbundled/definition34.js +9 -8
  224. package/unbundled/definition35.cjs +1 -1
  225. package/unbundled/definition35.js +1 -1
  226. package/unbundled/definition39.cjs +2 -2
  227. package/unbundled/definition39.js +2 -2
  228. package/unbundled/definition40.cjs +1 -1
  229. package/unbundled/definition40.js +1 -1
  230. package/unbundled/definition43.cjs +1 -1
  231. package/unbundled/definition43.js +1 -1
  232. package/unbundled/definition44.cjs +1 -1
  233. package/unbundled/definition44.js +1 -1
  234. package/unbundled/definition48.cjs +1 -1
  235. package/unbundled/definition48.js +1 -1
  236. package/unbundled/definition49.cjs +1 -1
  237. package/unbundled/definition49.js +1 -1
  238. package/unbundled/definition5.cjs +1 -1
  239. package/unbundled/definition5.js +1 -1
  240. package/unbundled/definition53.cjs +10 -3
  241. package/unbundled/definition53.js +10 -3
  242. package/unbundled/definition55.cjs +1 -1
  243. package/unbundled/definition55.js +1 -1
  244. package/unbundled/definition56.cjs +1 -1
  245. package/unbundled/definition56.js +1 -1
  246. package/unbundled/definition57.cjs +4 -5
  247. package/unbundled/definition57.js +4 -5
  248. package/unbundled/definition58.cjs +20 -19
  249. package/unbundled/definition58.js +20 -19
  250. package/unbundled/definition59.cjs +1 -1
  251. package/unbundled/definition59.js +1 -1
  252. package/unbundled/definition60.cjs +890 -851
  253. package/unbundled/definition60.js +890 -851
  254. package/unbundled/definition61.cjs +13 -7
  255. package/unbundled/definition61.js +13 -7
  256. package/unbundled/definition63.cjs +1 -1
  257. package/unbundled/definition63.js +1 -1
  258. package/unbundled/definition64.cjs +4 -1
  259. package/unbundled/definition64.js +4 -1
  260. package/unbundled/definition65.cjs +2 -1
  261. package/unbundled/definition65.js +2 -1
  262. package/unbundled/definition69.cjs +4 -3
  263. package/unbundled/definition69.js +4 -3
  264. package/unbundled/definition7.cjs +2 -2
  265. package/unbundled/definition7.js +2 -2
  266. package/unbundled/definition71.cjs +1 -1
  267. package/unbundled/definition71.js +1 -1
  268. package/unbundled/definition72.cjs +5 -5
  269. package/unbundled/definition72.js +5 -5
  270. package/unbundled/definition73.cjs +3 -3
  271. package/unbundled/definition73.js +3 -3
  272. package/unbundled/definition75.cjs +1 -1
  273. package/unbundled/definition75.js +1 -1
  274. package/unbundled/definition78.cjs +1 -1
  275. package/unbundled/definition78.js +1 -1
  276. package/unbundled/definition79.cjs +0 -2
  277. package/unbundled/definition79.js +0 -2
  278. package/unbundled/definition80.cjs +1 -1
  279. package/unbundled/definition80.js +1 -1
  280. package/unbundled/definition9.cjs +2 -2
  281. package/unbundled/definition9.js +2 -2
  282. package/unbundled/en-US.cjs +449 -0
  283. package/unbundled/en-US.js +445 -0
  284. package/unbundled/form-associated.cjs +2 -2
  285. package/unbundled/form-associated.js +2 -2
  286. package/unbundled/listbox.cjs +7 -7
  287. package/unbundled/listbox.js +7 -7
  288. package/unbundled/localized.cjs +2 -2
  289. package/unbundled/localized.js +1 -1
  290. package/unbundled/numberConverter.cjs +1 -1
  291. package/unbundled/numberConverter.js +1 -1
  292. package/unbundled/slider.template.cjs +1 -1
  293. package/unbundled/slider.template.js +1 -1
  294. package/unbundled/slottable-request.cjs +5 -5
  295. package/unbundled/slottable-request.js +5 -5
  296. package/unbundled/vivid-element.cjs +1 -1
  297. package/unbundled/vivid-element.js +1 -1
  298. package/unbundled/with-contextual-help.cjs +4 -1
  299. package/unbundled/with-contextual-help.js +4 -1
  300. package/video-player/index.cjs +1 -1
  301. package/video-player/index.js +1 -1
  302. package/vivid.api.json +60 -18
@@ -22,8 +22,8 @@ import { keymap } from "prosemirror-keymap";
22
22
  import { autoJoin, baseKeymap, chainCommands, createParagraphNear, liftEmptyBlock, newlineInCode, splitBlockAs, toggleMark } from "prosemirror-commands";
23
23
  import { dropCursor } from "prosemirror-dropcursor";
24
24
  import { gapCursor } from "prosemirror-gapcursor";
25
- import { closeHistory, history, redo, undo } from "prosemirror-history";
26
25
  import DOMPurify from "dompurify";
26
+ import { closeHistory, history, redo, undo } from "prosemirror-history";
27
27
  import { InputRule, inputRules, undoInputRule } from "prosemirror-inputrules";
28
28
  import { RemoveMarkStep, ReplaceAroundStep, dropPoint } from "prosemirror-transform";
29
29
  import { marks } from "prosemirror-schema-basic";
@@ -111,12 +111,20 @@ var Kind = {
111
111
  default: {
112
112
  placement: void 0,
113
113
  placementStrategy: PlacementStrategy.AutoPlacementHorizontal,
114
+ shiftCrossAxis: false,
114
115
  strategy: "absolute"
115
116
  },
116
117
  autocomplete: {
117
118
  placement: "bottom-start",
118
119
  placementStrategy: PlacementStrategy.Flip,
120
+ shiftCrossAxis: false,
119
121
  strategy: "fixed"
122
+ },
123
+ imageOptions: {
124
+ placement: "bottom",
125
+ placementStrategy: PlacementStrategy.Flip,
126
+ shiftCrossAxis: true,
127
+ strategy: "absolute"
120
128
  }
121
129
  };
122
130
  var popoverTemplate = (context) => {
@@ -125,6 +133,7 @@ var popoverTemplate = (context) => {
125
133
  :anchor="${(x) => x.anchorEl}"
126
134
  :placement="${(x) => Kind[x.kind].placement}"
127
135
  :placementStrategy="${(x) => Kind[x.kind].placementStrategy}"
136
+ :shiftCrossAxis="${(x) => Kind[x.kind].shiftCrossAxis}"
128
137
  :open="${(x) => x.open}"
129
138
  :offset="${(x) => x.offset}"
130
139
  :strategy="${(x) => Kind[x.kind].strategy}"
@@ -238,927 +247,942 @@ var FeatureState = class {
238
247
  //#region src/lib/rich-text-editor/rte/features/internal/core.style.scss?inline
239
248
  var core_style_default = ".ProseMirror{box-sizing:border-box;padding:var(--editor-padding-block) var(--editor-padding-inline);outline:none;flex:1 0 0}.ProseMirror-selectednode{--focus-stroke-gap-color:transparent;--focus-border-radius:2px;outline:none;position:relative}.ProseMirror-selectednode:after{box-shadow:0 0 0 4px color-mix(in srgb, var(--focus-stroke-color,var(--vvd-color-cta-500)), transparent 85%), inset 0 0 0 3px var(--focus-stroke-gap-color,currentColor);outline:1px solid var(--focus-stroke-color,var(--vvd-color-cta-500));outline-offset:calc(-1px - var(--focus-inset,0px));border-radius:var(--focus-border-radius,inherit);block-size:calc(100% + var(--focus-block-size-addition,4px));content:\"\";inline-size:calc(100% + var(--focus-block-size-addition,4px));display:block;position:absolute;inset-block-start:50%;inset-inline-start:50%;transform:translate(-50%,-50%)}.editor--disabled .ProseMirror{color:var(--vvd-color-neutral-300);cursor:not-allowed}";
240
249
  //#endregion
241
- //#region src/lib/rich-text-editor/rte/utils/ui.ts
242
- var isPropBinding = (prop) => typeof prop === "function";
243
- var on = (event, prop, handler) => [
244
- event,
245
- prop,
246
- handler
247
- ];
248
- var UiCtx = class {
249
- constructor(view, rte, props) {
250
- this.view = view;
251
- this.rte = rte;
252
- this.props = props;
253
- this.bindings = [];
250
+ //#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
251
+ var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
252
+ constructor(..._args) {
253
+ super(..._args);
254
+ this.name = "RteForeignHtmlFeature";
254
255
  }
255
- evalProp(prop) {
256
- return isPropBinding(prop) ? prop(this) : prop;
256
+ getPlugins(rte) {
257
+ return [this.contribution(new Plugin({ props: {
258
+ transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
259
+ clipboardParser: rte.foreignHtmlParser[impl].parser,
260
+ clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
261
+ } }))];
257
262
  }
258
- bindProp(prop, bindFn) {
259
- if (prop === void 0) return;
260
- else if (isPropBinding(prop)) {
261
- const binding = () => bindFn(prop(this));
262
- this.bindings.push(binding);
263
- binding();
264
- } else bindFn(prop);
263
+ };
264
+ //#endregion
265
+ //#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
266
+ /**
267
+ * When the cursor is positioned after an inline atom node at the end of a line,
268
+ * browsers may display the cursor inside the atom.
269
+ */
270
+ var atomCursorFix = ($cursor) => {
271
+ if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
272
+ const nodeBefore = $cursor.nodeBefore;
273
+ if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
274
+ return null;
275
+ };
276
+ var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
277
+ constructor(..._args) {
278
+ super(..._args);
279
+ this.name = "RteCursorFix";
265
280
  }
266
- updateBindings() {
267
- for (const binding of this.bindings) binding();
281
+ getPlugins(rte) {
282
+ const cursorFixes = [atomCursorFix];
283
+ for (const markType of Object.values(rte.schema.marks)) {
284
+ const spec = markType.spec;
285
+ if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
286
+ }
287
+ return [this.contribution(new Plugin({ props: { decorations: (state) => {
288
+ const { $cursor } = state.selection;
289
+ if (!$cursor) return null;
290
+ let cursorFix = null;
291
+ for (const fn of cursorFixes) {
292
+ const result = fn($cursor, state);
293
+ if (result) Object.assign(cursorFix ??= {}, result);
294
+ }
295
+ if (!cursorFix) return null;
296
+ return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
297
+ const span = document.createElement("span");
298
+ span.textContent = "​";
299
+ for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
300
+ return span;
301
+ }, { side: -1 })]);
302
+ } } }))];
268
303
  }
269
- shouldReturnFocusToEditor() {
270
- return this.evalProp(this.props.shouldReturnFocusToEditor);
304
+ };
305
+ featureFacade(RteCursorFixFeatureImpl);
306
+ //#endregion
307
+ //#region src/lib/rich-text-editor/rte/features/internal/core.ts
308
+ /**
309
+ * Plugin to bring state from the host web component into ProseMirror.
310
+ */
311
+ var hostBridgePlugin = new Plugin({ state: {
312
+ init() {
313
+ return null;
314
+ },
315
+ apply(tr, value) {
316
+ const meta = tr.getMeta(hostBridgePlugin);
317
+ if (meta) return meta;
318
+ else return value;
271
319
  }
272
- bindToEl(target, props = {}, events = [], children = []) {
273
- for (const name in props) this.bindProp(props[name], (value) => {
274
- target[name] = value;
275
- });
276
- for (const [name, value, bindFn] of events) if (value) target.addEventListener(name, bindFn(value));
277
- for (const child of children) target.appendChild(child);
320
+ } });
321
+ var RteCoreImpl = class extends RteFeatureImpl {
322
+ constructor(..._args) {
323
+ super(..._args);
324
+ this.name = "RteCore";
325
+ this.disabled = new FeatureState(false);
326
+ this.scrollThreshold = void 0;
327
+ this.scrollMargin = void 0;
328
+ }
329
+ getStyles() {
330
+ return [
331
+ this.contribution(prosemirror_default),
332
+ this.contribution(core_style_default),
333
+ this.contribution(ui_style_default)
334
+ ];
335
+ }
336
+ getPlugins(rte) {
337
+ const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
338
+ if (!atEnd) return {
339
+ type: node.type,
340
+ attrs: node.attrs
341
+ };
342
+ return {
343
+ type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
344
+ attrs: rte.textblockAttrs.extractFromNode(node)
345
+ };
346
+ }));
347
+ return [
348
+ this.contribution(this.disabled.plugin),
349
+ this.contribution(new Plugin({
350
+ props: {
351
+ editable: () => !this.disabled.getValue(rte),
352
+ handleDOMEvents: { click: (_view, event) => {
353
+ if (this.disabled.getValue(rte)) {
354
+ event.preventDefault();
355
+ return true;
356
+ }
357
+ return false;
358
+ } }
359
+ },
360
+ view: (view) => {
361
+ const popovers = view.dom.getRootNode().querySelector(".popovers");
362
+ const updateDisabled = () => {
363
+ const disabled = this.disabled.getValue(rte);
364
+ popovers.classList.toggle("popovers--disabled", disabled);
365
+ view.dom.parentElement.classList.toggle("editor--disabled", disabled);
366
+ };
367
+ updateDisabled();
368
+ return { update: () => {
369
+ updateDisabled();
370
+ } };
371
+ }
372
+ })),
373
+ this.contribution(keymap({
374
+ ...baseKeymap,
375
+ Enter: enterKeyChainCommands,
376
+ "Shift-Enter": enterKeyChainCommands
377
+ })),
378
+ this.contribution(dropCursor()),
379
+ this.contribution(gapCursor()),
380
+ this.contribution(hostBridgePlugin)
381
+ ];
382
+ }
383
+ getFeatures() {
384
+ return [
385
+ this,
386
+ new RteForeignHtmlFeatureImpl(),
387
+ new RteCursorFixFeatureImpl()
388
+ ];
278
389
  }
279
390
  };
280
- var createDiv = (ctx, props) => {
281
- const div = document.createElement("div");
282
- ctx.bindToEl(div, {
283
- className: props.className,
284
- slot: props.slot
285
- }, [], props.children);
286
- return div;
287
- };
288
- var createAnchor = (ctx, props) => {
391
+ featureFacade(RteCoreImpl);
392
+ //#endregion
393
+ //#region src/lib/rich-text-editor/rte/utils/sanitization.ts
394
+ var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
395
+ var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
396
+ var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
397
+ var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
398
+ /**
399
+ * Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
400
+ * Returns empty string if the URL is unsafe.
401
+ */
402
+ var sanitizeLinkHref = (url) => {
403
+ if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
289
404
  const anchor = document.createElement("a");
290
- ctx.bindToEl(anchor, {
291
- href: props.href,
292
- target: props.target,
293
- rel: props.rel,
294
- className: props.className
295
- }, [], props.children);
296
- return anchor;
405
+ anchor.setAttribute("href", url);
406
+ /* v8 ignore next -- since href is already validated it's probably always present @preserve */
407
+ return DOMPurify.sanitize(anchor, {
408
+ RETURN_DOM: true,
409
+ ...domPurifyConfig
410
+ }).querySelector("a").getAttribute("href") ?? "";
297
411
  };
298
- var wrapperTargets = /* @__PURE__ */ new WeakMap();
299
- var createOptionalTooltip = (ctx, props) => {
300
- const tooltip = ctx.rte.createComponent(Tooltip);
301
- tooltip.setAttribute("exportparts", "vvd-theme-alternate");
302
- ctx.bindToEl(tooltip, {
303
- className: "ui-tooltip",
304
- text: props.label,
305
- placement: ctx.props.popupPlacement
306
- });
307
- ctx.bindProp(props.enabled, (enabled) => {
308
- if (!enabled) tooltip.open = false;
309
- tooltip.anchor = enabled ? props.anchor : void 0;
310
- });
311
- const wrapper = createDiv(ctx, {
312
- className: "ui-tooltip-wrapper",
313
- slot: props.slot,
314
- children: [props.anchor, tooltip]
315
- });
316
- wrapperTargets.set(wrapper, props.anchor);
317
- return wrapper;
412
+ /**
413
+ * Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
414
+ * Returns empty string if the URL is unsafe.
415
+ */
416
+ var sanitizeImageSrc = (url) => {
417
+ const img = document.createElement("img");
418
+ img.setAttribute("src", url);
419
+ return DOMPurify.sanitize(img, {
420
+ RETURN_DOM: true,
421
+ ...domPurifyConfig
422
+ }).querySelector("img").getAttribute("src") ?? "";
318
423
  };
319
- var createButton = (ctx, props) => {
320
- const variant = () => ctx.evalProp(props.variant) ?? "toolbar";
321
- const size = () => variant() === "toolbar" ? "super-condensed" : "condensed";
322
- const appearanceInactive = () => variant() === "popover-primary" ? "outlined" : "ghost-light";
323
- const appearanceActive = () => variant() === "toolbar-menu" || variant() === "popover" ? "filled" : appearanceInactive();
324
- const appearance = () => ctx.evalProp(props.active) ? appearanceActive() : appearanceInactive();
325
- const connotation = () => ctx.evalProp(props.connotation) ?? (variant() === "toolbar-menu" && ctx.evalProp(props.active) ? "cta" : void 0);
326
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
327
- const button = ctx.rte.createComponent(Button);
328
- ctx.bindToEl(button, {
329
- size,
330
- appearance,
331
- disabled,
332
- icon: props.icon,
333
- autofocus: props.autofocus,
334
- connotation,
335
- [props.icon ? "ariaLabel" : "label"]: props.label,
336
- pressed: () => Boolean(ctx.evalProp(props.active)),
337
- ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
338
- }, [on("click", props.onClick, (onClick) => () => {
339
- if (!onClick() && ctx.shouldReturnFocusToEditor()) ctx.view.focus();
340
- })]);
341
- return createOptionalTooltip(ctx, {
342
- enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
343
- label: props.label,
344
- anchor: button,
345
- slot: props.slot
346
- });
347
- };
348
- var createMenu = (ctx, props) => {
349
- const menu = ctx.rte.createComponent(Menu);
350
- ctx.bindToEl(menu, {
351
- className: "ui-menu",
352
- autoDismiss: true,
353
- ariaLabel: props.label,
354
- anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
355
- placement: ctx.props.popupPlacement,
356
- offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
357
- }, [], props.children);
358
- const fragment = document.createDocumentFragment();
359
- fragment.appendChild(props.trigger);
360
- fragment.appendChild(menu);
361
- return fragment;
362
- };
363
- var createMenuItem = (ctx, props) => {
364
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
365
- const item = ctx.rte.createComponent(MenuItem);
366
- ctx.bindToEl(item, {
367
- text: props.text,
368
- checked: props.checked,
369
- disabled,
370
- controlType: "radio",
371
- checkedAppearance: "tick-only"
372
- }, [on("change", props.onSelect, (onSelect) => () => {
373
- if (item.checked && !item.disabled) {
374
- if (ctx.evalProp(props.checked) !== item.checked) {
375
- onSelect();
376
- if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
377
- }
378
- }
379
- })]);
380
- return item;
381
- };
382
- var createButtonGroup = (ctx, props) => createDiv(ctx, {
383
- slot: props.slot,
384
- className: "ui-button-group",
385
- children: props.children
424
+ /**
425
+ * Escapes a CSS value for insertion into style attribute.
426
+ * E.g. "15px; background: red" => "15px"
427
+ */
428
+ var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
429
+ //#endregion
430
+ //#region src/lib/rich-text-editor/rte/html-parser.ts
431
+ var copy = (obj) => ({ ...obj });
432
+ var parseRulesFromSchema = (schema) => ({
433
+ nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
434
+ marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
386
435
  });
387
- function markActive(state, type) {
388
- const { from, $from, to, empty } = state.selection;
389
- if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
390
- else return state.doc.rangeHasMark(from, to, type);
391
- }
392
- var createMarkToggle = (ctx, props) => createButton(ctx, {
393
- label: props.label,
394
- icon: props.icon,
395
- active: () => markActive(ctx.view.state, props.markType),
396
- disabled: () => !toggleMark(props.markType)(ctx.view.state),
397
- onClick: () => {
398
- toggleMark(props.markType)(ctx.view.state, ctx.view.dispatch);
436
+ var RteHtmlParser = class {
437
+ constructor(config, options) {
438
+ this[impl] = new RteHtmlParserImpl(config[impl], options);
439
+ }
440
+ /**
441
+ * Converts an HTML string to an RteDocument.
442
+ */
443
+ parseDocument(html, options) {
444
+ return this[impl].parser.parse(this.parseHtml(html, options), { preserveWhitespace: true }).toJSON();
445
+ }
446
+ /**
447
+ * Converts an HTML string to an RteFragment.
448
+ */
449
+ parseFragment(html, options) {
450
+ return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
451
+ }
452
+ parseHtml(html, options) {
453
+ const dom = DOMPurify.sanitize(html, {
454
+ RETURN_DOM: true,
455
+ ...domPurifyConfig
456
+ });
457
+ const container = document.createDocumentFragment();
458
+ container.appendChild(dom);
459
+ options?.modifyDom?.(container);
460
+ return container;
399
461
  }
400
- });
401
- var createOption = (ctx, props) => {
402
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
403
- const option = ctx.rte.createComponent(ListboxOption);
404
- ctx.bindToEl(option, {
405
- value: props.value,
406
- text: props.text,
407
- disabled
408
- });
409
- return option;
410
462
  };
411
- var createSelect = (ctx, props) => {
412
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled));
413
- const select = ctx.rte.createComponent(Select);
414
- select.setAttribute("data-class", "ui-select");
415
- ctx.bindToEl(select, {
416
- placeholder: " ",
417
- ariaLabel: props.label,
418
- appearance: "ghost",
419
- disabled
420
- }, [on("change", props.onSelect, (onSelect) => () => {
421
- const value = select.value;
422
- if (value) {
423
- onSelect(value);
424
- if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
463
+ var RteHtmlParserImpl = class {
464
+ constructor(config, options) {
465
+ const rules = parseRulesFromSchema(config.schema);
466
+ options?.modifyParseRules?.(rules);
467
+ this.parser = buildDomParser(config.schema, rules);
468
+ }
469
+ transform(html) {
470
+ return DOMPurify.sanitize(html);
471
+ }
472
+ };
473
+ var buildDomParser = (schema, { marks, nodes }) => {
474
+ const parserRules = [];
475
+ const priority = (rule) => rule.priority ?? 50;
476
+ function insert(rule) {
477
+ let i = 0;
478
+ for (; i < parserRules.length; i++) {
479
+ const next = parserRules[i];
480
+ if (priority(next) < priority(rule)) break;
425
481
  }
426
- })], props.children);
427
- queueMicrotask(() => {
428
- ctx.bindProp(props.value, (value) => select.value = value);
429
- });
430
- let hideTooltip = false;
431
- select.addEventListener("vwc-popup:open", () => {
432
- hideTooltip = true;
433
- ctx.updateBindings();
434
- });
435
- select.addEventListener("vwc-popup:close", () => {
436
- hideTooltip = false;
437
- ctx.updateBindings();
482
+ parserRules.splice(i, 0, rule);
483
+ }
484
+ for (const name in marks) marks[name]?.forEach((rule) => {
485
+ insert(rule = copy(rule));
486
+ if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
438
487
  });
439
- return createOptionalTooltip(ctx, {
440
- enabled: () => !hideTooltip && !disabled(),
441
- label: props.label,
442
- anchor: select
488
+ for (const name in nodes) nodes[name]?.forEach((rule) => {
489
+ insert(rule = copy(rule));
490
+ if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
443
491
  });
444
- };
445
- var createDivider = (ctx) => {
446
- const divider = ctx.rte.createComponent(Divider);
447
- ctx.bindToEl(divider, {
448
- className: "ui-divider",
449
- orientation: "vertical"
492
+ parserRules.push({
493
+ tag: "br",
494
+ closeParent: true
450
495
  });
451
- return divider;
452
- };
453
- var createTextField = (ctx, props) => {
454
- const textField = ctx.rte.createComponent(TextField);
455
- ctx.bindToEl(textField, {
456
- label: props.label,
457
- value: props.value,
458
- placeholder: props.placeholder,
459
- slot: props.slot,
460
- autofocus: props.autofocus,
461
- type: props.type,
462
- helperText: props.helperText
463
- }, [on("input", props.onInput, (onInput) => () => {
464
- onInput(textField.value);
465
- })]);
466
- return textField;
496
+ return new DOMParser(schema, parserRules);
467
497
  };
468
- var createText = (ctx, props) => {
469
- const textNode = document.createTextNode("");
470
- ctx.bindToEl(textNode, { textContent: props.text });
471
- return textNode;
498
+ //#endregion
499
+ //#region src/lib/rich-text-editor/rte/html-serializer.ts
500
+ var RteHtmlSerializer = class {
501
+ constructor(config, options) {
502
+ this[impl] = new RteHtmlSerializerImpl(config[impl], options);
503
+ }
504
+ /**
505
+ * Converts an RteDocument to an HTML string.
506
+ */
507
+ serializeDocument(doc, options) {
508
+ return this[impl].serializeFragment(doc.content, options);
509
+ }
510
+ /**
511
+ * Converts an RteFragment to an HTML string.
512
+ */
513
+ serializeFragment(fragment, options) {
514
+ return this[impl].serializeFragment(fragment, options);
515
+ }
472
516
  };
473
- var createSingleSlot = (ctx, props) => {
474
- const slot = document.createElement("slot");
475
- slot.name = props.name;
476
- let currentEl = null;
477
- const listeners = [];
478
- function cleanup() {
479
- for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
480
- listeners.length = 0;
481
- currentEl = null;
517
+ var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
518
+ constructor(config, options) {
519
+ this.config = config;
520
+ const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
521
+ Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
522
+ Object.assign(serializers.marks, options?.serializers?.marks ?? {});
523
+ this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
482
524
  }
483
- for (const [key, prop] of Object.entries(props.assignedProps)) ctx.bindProp(prop, (value) => {
484
- if (currentEl) currentEl[key] = value;
485
- });
486
- function applyPropsAndEvents(el) {
487
- for (const [key, prop] of Object.entries(props.assignedProps)) el[key] = ctx.evalProp(prop);
488
- for (const [type, handler] of Object.entries(props.assignedEvents)) {
489
- const listener = (e) => {
490
- handler(e);
491
- if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
492
- };
493
- el.addEventListener(type, listener);
494
- listeners.push({
495
- el,
496
- type,
497
- handler: listener
498
- });
525
+ static domSerializersFromSchema(schema) {
526
+ const result = {
527
+ nodes: {},
528
+ marks: {}
529
+ };
530
+ for (const name in schema.marks) {
531
+ const toDOM = schema.marks[name].spec.toDOM;
532
+ if (toDOM) result.marks[name] = toDOM;
499
533
  }
534
+ for (const name in schema.nodes) {
535
+ const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
536
+ if (toDOM) result.nodes[name] = toDOM;
537
+ }
538
+ result.nodes.text = (node) => document.createTextNode(node.text);
539
+ return result;
540
+ }
541
+ serializeFragment(fragment, options) {
542
+ const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
543
+ const serializedFragment = this.serializer.serializeFragment(parsedFragment);
544
+ const container = document.createDocumentFragment();
545
+ container.appendChild(serializedFragment);
546
+ options?.modifyDom?.(container);
547
+ const output = document.createElement("div");
548
+ output.appendChild(container);
549
+ return output.innerHTML;
500
550
  }
501
- const processSlot = () => {
502
- const first = slot.assignedElements({ flatten: true })[0];
503
- if (first === currentEl) return;
504
- cleanup();
505
- if (first) {
506
- currentEl = first;
507
- applyPropsAndEvents(first);
508
- }
509
- };
510
- slot.addEventListener("slotchange", processSlot);
511
- queueMicrotask(processSlot);
512
- return slot;
513
551
  };
514
552
  //#endregion
515
- //#region src/lib/rich-text-editor/rte/features/internal/history.ts
516
- var RteHistoryFeatureImpl = class extends RteFeatureImpl {
517
- constructor(..._args) {
518
- super(..._args);
519
- this.name = "RteHistoryFeature";
553
+ //#region src/lib/rich-text-editor/rte/instance.ts
554
+ var parseDocument = (schema, doc) => {
555
+ const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
556
+ if (!node) throw new Error("Document could not be parsed");
557
+ node.check();
558
+ return node;
559
+ };
560
+ var RteInstance = class {
561
+ constructor(config, options) {
562
+ this.options = options;
563
+ this.feature = (Feature, featureId) => {
564
+ return this[impl].getPublicInterface(Feature, featureId);
565
+ };
566
+ this[impl] = new RteInstanceImpl(this, config, options);
520
567
  }
521
- getPlugins() {
522
- return [this.contribution(history()), this.contribution(keymap({
523
- "Mod-z": undo,
524
- "Ctrl-y": redo,
525
- "Cmd-Z": redo
526
- }))];
568
+ /**
569
+ * Returns the current document state.
570
+ */
571
+ getDocument() {
572
+ return this[impl].state.doc.toJSON();
527
573
  }
528
- getToolbarItems() {
529
- return [this.contribution({
530
- section: "history",
531
- render: (ctx) => createButton(ctx, {
532
- label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
533
- icon: "undo-line",
534
- disabled: (ctx) => !undo(ctx.view.state),
535
- onClick: () => {
536
- const { state, dispatch } = ctx.view;
537
- undo(state, dispatch);
538
- }
539
- })
540
- }, 1), this.contribution({
541
- section: "history",
542
- render: (ctx) => createButton(ctx, {
543
- label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
544
- icon: "redo-line",
545
- disabled: (ctx) => !redo(ctx.view.state),
546
- onClick: () => {
547
- const { state, dispatch } = ctx.view;
548
- redo(state, dispatch);
549
- }
550
- })
551
- }, 2)];
574
+ /**
575
+ * Reset the editor to its initial state. Optionally, an initial document can be provided.
576
+ */
577
+ reset(initialDocument) {
578
+ this[impl].reset(initialDocument);
552
579
  }
553
- };
554
- //#endregion
555
- //#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
556
- var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
557
- constructor(..._args) {
558
- super(..._args);
559
- this.name = "RteForeignHtmlFeature";
580
+ /**
581
+ * Replaces the current selection with the given content.
582
+ * If no text is selected, this inserts the content at the cursor position.
583
+ */
584
+ replaceSelection(content, options) {
585
+ const { state, tr, schema, dispatchTransaction } = this[impl];
586
+ tr.replaceSelection(new Slice(Fragment.fromJSON(schema, content), 0, 0));
587
+ tr.doc.check();
588
+ const $from = tr.doc.resolve(tr.mapping.map(state.selection.from));
589
+ const $to = tr.doc.resolve(tr.selection.to);
590
+ const [$head, $anchor] = options?.cursorPlacement === "start" ? [$from, $to] : [$to, $from];
591
+ if (options?.selectContent) tr.setSelection(state.selection instanceof AllSelection ? new AllSelection(tr.doc) : TextSelection.between($anchor, $head));
592
+ else tr.setSelection(TextSelection.between($head, $head));
593
+ dispatchTransaction(tr);
560
594
  }
561
- getPlugins(rte) {
562
- return [this.contribution(new Plugin({ props: {
563
- transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
564
- clipboardParser: rte.foreignHtmlParser[impl].parser,
565
- clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
566
- } }))];
595
+ /**
596
+ * Replaces the entire document with the given new document.
597
+ * Unlike reset, this preserves the rest of the editor state. The undo history is preserved, so the user can undo the replacement.
598
+ */
599
+ replaceDocument(newDocument, options) {
600
+ const { state, tr, schema, dispatchTransaction } = this[impl];
601
+ tr.replaceWith(0, state.doc.content.size, parseDocument(schema, newDocument));
602
+ if (options?.selectContent) tr.setSelection(new AllSelection(tr.doc));
603
+ else if (options?.cursorPlacement === "end") tr.setSelection(TextSelection.atEnd(tr.doc));
604
+ else tr.setSelection(TextSelection.atStart(tr.doc));
605
+ dispatchTransaction(tr);
606
+ }
607
+ /**
608
+ * Scrolls the cursor into view.
609
+ * Has no effect if the editor is not currently mounted.
610
+ */
611
+ scrollIntoView() {
612
+ const instance = this[impl];
613
+ if (!instance.view) return;
614
+ instance.dispatchTransaction(instance.tr.scrollIntoView());
567
615
  }
568
616
  };
569
- //#endregion
570
- //#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
571
- /**
572
- * When the cursor is positioned after an inline atom node at the end of a line,
573
- * browsers may display the cursor inside the atom.
574
- */
575
- var atomCursorFix = ($cursor) => {
576
- if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
577
- const nodeBefore = $cursor.nodeBefore;
578
- if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
579
- return null;
580
- };
581
- var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
582
- constructor(..._args) {
583
- super(..._args);
584
- this.name = "RteCursorFix";
617
+ var RteInstanceImpl = class {
618
+ getFeature(name) {
619
+ const f = this.config.featureMap.get(name);
620
+ if (!f) throw new Error(`Feature not found: ${name}`);
621
+ return f;
585
622
  }
586
- getPlugins(rte) {
587
- const cursorFixes = [atomCursorFix];
588
- for (const markType of Object.values(rte.schema.marks)) {
589
- const spec = markType.spec;
590
- if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
623
+ getPublicInterface(Feature, featureId) {
624
+ const instances = this.config.featureFacadesMap.get(Feature);
625
+ if (!instances || instances.length === 0) throw new Error(`Feature not found`);
626
+ if (instances[0].featureId !== void 0) {
627
+ if (featureId === void 0) throw new Error(`No featureId provided for multi-instance feature.`);
628
+ const instance = instances.find((f) => f.featureId === featureId);
629
+ if (!instance) throw new Error(`Feature with id "${featureId}" not found`);
630
+ return instance.getPublicInterface(this);
591
631
  }
592
- return [this.contribution(new Plugin({ props: { decorations: (state) => {
593
- const { $cursor } = state.selection;
594
- if (!$cursor) return null;
595
- let cursorFix = null;
596
- for (const fn of cursorFixes) {
597
- const result = fn($cursor, state);
598
- if (result) Object.assign(cursorFix ??= {}, result);
599
- }
600
- if (!cursorFix) return null;
601
- return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
602
- const span = document.createElement("span");
603
- span.textContent = "​";
604
- for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
605
- return span;
606
- }, { side: -1 })]);
607
- } } }))];
632
+ if (featureId !== void 0) throw new Error(`Feature does not support featureId`);
633
+ return instances[0].getPublicInterface(this);
608
634
  }
609
- };
610
- featureFacade(RteCursorFixFeatureImpl);
611
- //#endregion
612
- //#region src/lib/rich-text-editor/rte/features/internal/core.ts
613
- /**
614
- * Plugin to bring state from the host web component into ProseMirror.
615
- */
616
- var hostBridgePlugin = new Plugin({ state: {
617
- init() {
618
- return null;
619
- },
620
- apply(tr, value) {
621
- const meta = tr.getMeta(hostBridgePlugin);
622
- if (meta) return meta;
623
- else return value;
635
+ constructor(instanceFacade, configFacade, options) {
636
+ this.options = options;
637
+ this.view = null;
638
+ this.dispatchTransaction = (tr) => {
639
+ const prevState = this.state;
640
+ this.state = this.state.apply(tr);
641
+ this.view?.updateState(this.state);
642
+ if (prevState.doc !== this.state.doc) this.options?.onChange?.();
643
+ return this.state;
644
+ };
645
+ this.facade = instanceFacade;
646
+ const config = configFacade[impl];
647
+ this.config = config;
648
+ this.schema = config.schema;
649
+ this.textblockAttrs = config.textblockAttrs;
650
+ this.features = config.features;
651
+ this.foreignHtmlParser = options?.foreignHtmlParser ?? new RteHtmlParser(configFacade);
652
+ this.foreignHtmlSerializer = options?.foreignHtmlSerializer ?? new RteHtmlSerializer(configFacade);
653
+ this.styles = new ElementStyles(sortedContributions(config.features.flatMap((f) => f.getStyles())));
654
+ this.initState(options?.initialDocument);
624
655
  }
625
- } });
626
- var RteCoreImpl = class extends RteFeatureImpl {
627
- constructor(..._args) {
628
- super(..._args);
629
- this.name = "RteCore";
630
- this.disabled = new FeatureState(false);
656
+ initState(initialDoc) {
657
+ this.state = EditorState.create({
658
+ doc: parseDocument(this.schema, initialDoc),
659
+ schema: this.config.schema,
660
+ plugins: sortedContributions(this.config.features.flatMap((feature) => feature.getPlugins(this)))
661
+ });
631
662
  }
632
- getStyles() {
633
- return [
634
- this.contribution(prosemirror_default),
635
- this.contribution(core_style_default),
636
- this.contribution(ui_style_default)
637
- ];
663
+ reset(initialDocument) {
664
+ const currentHostState = hostBridgePlugin.getState(this.state);
665
+ this.initState(initialDocument);
666
+ this.updateHostState(currentHostState);
667
+ this.view?.updateState(this.state);
638
668
  }
639
- getPlugins(rte) {
640
- const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
641
- if (!atEnd) return {
642
- type: node.type,
643
- attrs: node.attrs
644
- };
645
- return {
646
- type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
647
- attrs: rte.textblockAttrs.extractFromNode(node)
648
- };
649
- }));
650
- return [
651
- this.contribution(this.disabled.plugin),
652
- this.contribution(new Plugin({
653
- props: {
654
- editable: () => !this.disabled.getValue(rte),
655
- handleDOMEvents: { click: (_view, event) => {
656
- if (this.disabled.getValue(rte)) {
657
- event.preventDefault();
658
- return true;
659
- }
660
- return false;
661
- } }
662
- },
663
- view: (view) => {
664
- const popovers = view.dom.getRootNode().querySelector(".popovers");
665
- const updateDisabled = () => {
666
- const disabled = this.disabled.getValue(rte);
667
- popovers.classList.toggle("popovers--disabled", disabled);
668
- view.dom.parentElement.classList.toggle("editor--disabled", disabled);
669
- };
670
- updateDisabled();
671
- return { update: () => {
672
- updateDisabled();
673
- } };
674
- }
675
- })),
676
- this.contribution(keymap({
677
- ...baseKeymap,
678
- Enter: enterKeyChainCommands,
679
- "Shift-Enter": enterKeyChainCommands
680
- })),
681
- this.contribution(dropCursor()),
682
- this.contribution(gapCursor()),
683
- this.contribution(hostBridgePlugin)
684
- ];
685
- }
686
- getFeatures() {
687
- return [
688
- this,
689
- new RteHistoryFeatureImpl(),
690
- new RteForeignHtmlFeatureImpl(),
691
- new RteCursorFixFeatureImpl()
692
- ];
693
- }
694
- };
695
- featureFacade(RteCoreImpl);
696
- //#endregion
697
- //#region src/lib/rich-text-editor/rte/utils/sanitization.ts
698
- var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
699
- var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
700
- var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
701
- var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
702
- /**
703
- * Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
704
- * Returns empty string if the URL is unsafe.
705
- */
706
- var sanitizeLinkHref = (url) => {
707
- if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
708
- const anchor = document.createElement("a");
709
- anchor.setAttribute("href", url);
710
- /* v8 ignore next -- since href is already validated it's probably always present @preserve */
711
- return DOMPurify.sanitize(anchor, {
712
- RETURN_DOM: true,
713
- ...domPurifyConfig
714
- }).querySelector("a").getAttribute("href") ?? "";
715
- };
716
- /**
717
- * Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
718
- * Returns empty string if the URL is unsafe.
719
- */
720
- var sanitizeImageSrc = (url) => {
721
- const img = document.createElement("img");
722
- img.setAttribute("src", url);
723
- return DOMPurify.sanitize(img, {
724
- RETURN_DOM: true,
725
- ...domPurifyConfig
726
- }).querySelector("img").getAttribute("src") ?? "";
727
- };
728
- /**
729
- * Escapes a CSS value for insertion into style attribute.
730
- * E.g. "15px; background: red" => "15px"
731
- */
732
- var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
733
- //#endregion
734
- //#region src/lib/rich-text-editor/rte/html-parser.ts
735
- var copy = (obj) => ({ ...obj });
736
- var parseRulesFromSchema = (schema) => ({
737
- nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
738
- marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
739
- });
740
- var RteHtmlParser = class {
741
- constructor(config, options) {
742
- this[impl] = new RteHtmlParserImpl(config[impl], options);
669
+ createView(target) {
670
+ const core = this.config.featureMap.get("RteCore");
671
+ this.view = new EditorView((editor) => {
672
+ editor.part = "editor";
673
+ target.appendChild(editor);
674
+ }, {
675
+ state: this.state,
676
+ dispatchTransaction: this.dispatchTransaction,
677
+ ...core?.scrollThreshold !== void 0 && { scrollThreshold: core.scrollThreshold },
678
+ ...core?.scrollMargin !== void 0 && { scrollMargin: core.scrollMargin }
679
+ });
743
680
  }
744
- /**
745
- * Converts an HTML string to an RteDocument.
746
- */
747
- parseDocument(html, options) {
748
- return this[impl].parser.parse(this.parseHtml(html, options), { preserveWhitespace: true }).toJSON();
681
+ destroyViewIfNeeded() {
682
+ if (this.view) {
683
+ this.view.destroy();
684
+ this.view = null;
685
+ }
749
686
  }
750
- /**
751
- * Converts an HTML string to an RteFragment.
752
- */
753
- parseFragment(html, options) {
754
- return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
687
+ createComponent(type) {
688
+ return document.createElement(this.hostState().ctx.tagFor(type, true));
755
689
  }
756
- parseHtml(html, options) {
757
- const dom = DOMPurify.sanitize(html, {
758
- RETURN_DOM: true,
759
- ...domPurifyConfig
760
- });
761
- const container = document.createDocumentFragment();
762
- container.appendChild(dom);
763
- options?.modifyDom?.(container);
764
- return container;
690
+ getLocale() {
691
+ return this.hostState().locale;
765
692
  }
766
- };
767
- var RteHtmlParserImpl = class {
768
- constructor(config, options) {
769
- const rules = parseRulesFromSchema(config.schema);
770
- options?.modifyParseRules?.(rules);
771
- this.parser = buildDomParser(config.schema, rules);
693
+ get tr() {
694
+ return this.state.tr;
772
695
  }
773
- transform(html) {
774
- return DOMPurify.sanitize(html);
696
+ hostState() {
697
+ const state = hostBridgePlugin.getState(this.state);
698
+ if (!state) throw new Error("No host state available");
699
+ return state;
775
700
  }
776
- };
777
- var buildDomParser = (schema, { marks, nodes }) => {
778
- const parserRules = [];
779
- const priority = (rule) => rule.priority ?? 50;
780
- function insert(rule) {
781
- let i = 0;
782
- for (; i < parserRules.length; i++) {
783
- const next = parserRules[i];
784
- if (priority(next) < priority(rule)) break;
785
- }
786
- parserRules.splice(i, 0, rule);
701
+ updateHostState(hostState) {
702
+ this.dispatchTransaction(this.tr.setMeta(hostBridgePlugin, hostState));
787
703
  }
788
- for (const name in marks) marks[name]?.forEach((rule) => {
789
- insert(rule = copy(rule));
790
- if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
791
- });
792
- for (const name in nodes) nodes[name]?.forEach((rule) => {
793
- insert(rule = copy(rule));
794
- if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
795
- });
796
- parserRules.push({
797
- tag: "br",
798
- closeParent: true
799
- });
800
- return new DOMParser(schema, parserRules);
801
704
  };
802
705
  //#endregion
803
- //#region src/lib/rich-text-editor/rte/html-serializer.ts
804
- var RteHtmlSerializer = class {
805
- constructor(config, options) {
806
- this[impl] = new RteHtmlSerializerImpl(config[impl], options);
706
+ //#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
707
+ var TextblockAttrs = class {
708
+ constructor(specs) {
709
+ this.specs = specs;
710
+ this.attrs = {};
711
+ for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
807
712
  }
808
- /**
809
- * Converts an RteDocument to an HTML string.
810
- */
811
- serializeDocument(doc, options) {
812
- return this[impl].serializeFragment(doc.content, options);
713
+ fromDOM(dom) {
714
+ return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
813
715
  }
814
- /**
815
- * Converts an RteFragment to an HTML string.
816
- */
817
- serializeFragment(fragment, options) {
818
- return this[impl].serializeFragment(fragment, options);
716
+ getStyle(node) {
717
+ return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
819
718
  }
820
- };
821
- var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
822
- constructor(config, options) {
823
- this.config = config;
824
- const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
825
- Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
826
- Object.assign(serializers.marks, options?.serializers?.marks ?? {});
827
- this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
719
+ getDOMAttrsProperties(node) {
720
+ if (!this.specs.length) return {};
721
+ return { style: this.getStyle(node) };
828
722
  }
829
- static domSerializersFromSchema(schema) {
830
- const result = {
831
- nodes: {},
832
- marks: {}
833
- };
834
- for (const name in schema.marks) {
835
- const toDOM = schema.marks[name].spec.toDOM;
836
- if (toDOM) result.marks[name] = toDOM;
837
- }
838
- for (const name in schema.nodes) {
839
- const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
840
- if (toDOM) result.nodes[name] = toDOM;
841
- }
842
- result.nodes.text = (node) => document.createTextNode(node.text);
843
- return result;
723
+ getDOMAttrs(node) {
724
+ if (!this.specs.length) return [];
725
+ return [this.getDOMAttrsProperties(node)];
844
726
  }
845
- serializeFragment(fragment, options) {
846
- const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
847
- const serializedFragment = this.serializer.serializeFragment(parsedFragment);
848
- const container = document.createDocumentFragment();
849
- container.appendChild(serializedFragment);
850
- options?.modifyDom?.(container);
851
- const output = document.createElement("div");
852
- output.appendChild(container);
853
- return output.innerHTML;
727
+ extractFromNode(node) {
728
+ return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
854
729
  }
855
730
  };
856
731
  //#endregion
857
- //#region src/lib/rich-text-editor/rte/instance.ts
858
- var parseDocument = (schema, doc) => {
859
- const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
860
- if (!node) throw new Error("Document could not be parsed");
861
- node.check();
862
- return node;
863
- };
864
- var RteInstance = class {
865
- constructor(config, options) {
866
- this.options = options;
867
- this.feature = (Feature, featureId) => {
868
- return this[impl].getPublicInterface(Feature, featureId);
869
- };
870
- this[impl] = new RteInstanceImpl(this, config, options);
732
+ //#region src/lib/rich-text-editor/rte/utils/textblock-marks.ts
733
+ var TextblockMarks = class {
734
+ constructor(specs) {
735
+ this.specs = specs;
871
736
  }
872
- /**
873
- * Returns the current document state.
874
- */
875
- getDocument() {
876
- return this[impl].state.doc.toJSON();
737
+ getAllowedMarksForNode(nodeName) {
738
+ return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
877
739
  }
878
- /**
879
- * Reset the editor to its initial state. Optionally, an initial document can be provided.
880
- */
881
- reset(initialDocument) {
882
- this[impl].reset(initialDocument);
883
- }
884
- /**
885
- * Replaces the current selection with the given content.
886
- * If no text is selected, this inserts the content at the cursor position.
887
- */
888
- replaceSelection(content, options) {
889
- const { state, tr, schema, dispatchTransaction } = this[impl];
890
- tr.replaceSelection(new Slice(Fragment.fromJSON(schema, content), 0, 0));
891
- tr.doc.check();
892
- const $from = tr.doc.resolve(tr.mapping.map(state.selection.from));
893
- const $to = tr.doc.resolve(tr.selection.to);
894
- const [$head, $anchor] = options?.cursorPlacement === "start" ? [$from, $to] : [$to, $from];
895
- if (options?.selectContent) tr.setSelection(state.selection instanceof AllSelection ? new AllSelection(tr.doc) : TextSelection.between($anchor, $head));
896
- else tr.setSelection(TextSelection.between($head, $head));
897
- dispatchTransaction(tr);
898
- }
899
- /**
900
- * Replaces the entire document with the given new document.
901
- * Unlike reset, this preserves the rest of the editor state. The undo history is preserved, so the user can undo the replacement.
902
- */
903
- replaceDocument(newDocument, options) {
904
- const { state, tr, schema, dispatchTransaction } = this[impl];
905
- tr.replaceWith(0, state.doc.content.size, parseDocument(schema, newDocument));
906
- if (options?.selectContent) tr.setSelection(new AllSelection(tr.doc));
907
- else if (options?.cursorPlacement === "end") tr.setSelection(TextSelection.atEnd(tr.doc));
908
- else tr.setSelection(TextSelection.atStart(tr.doc));
909
- dispatchTransaction(tr);
740
+ getReferencedNodeNames() {
741
+ return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
910
742
  }
911
743
  };
912
- var RteInstanceImpl = class {
913
- getFeature(name) {
914
- const f = this.config.featureMap.get(name);
915
- if (!f) throw new Error(`Feature not found: ${name}`);
916
- return f;
917
- }
918
- getPublicInterface(Feature, featureId) {
919
- const instances = this.config.featureFacadesMap.get(Feature);
920
- if (!instances || instances.length === 0) throw new Error(`Feature not found`);
921
- if (instances[0].featureId !== void 0) {
922
- if (featureId === void 0) throw new Error(`No featureId provided for multi-instance feature.`);
923
- const instance = instances.find((f) => f.featureId === featureId);
924
- if (!instance) throw new Error(`Feature with id "${featureId}" not found`);
925
- return instance.getPublicInterface(this);
744
+ //#endregion
745
+ //#region src/lib/rich-text-editor/rte/view.ts
746
+ /**
747
+ * Converts an RteDocument to an RteView tree for rendering outside the editor.
748
+ * The logic is adapted from ProseMirror's DOM serialization to ensure the resulting structure
749
+ * matches the editor's HTML structure.
750
+ */
751
+ var convertToView = (doc, ctx) => {
752
+ const schema = ctx.config[impl].schema;
753
+ const getMarkRank = (mark) => schema.marks[mark.type].rank;
754
+ const marksEqual = (a, b) => {
755
+ if (a.type !== b.type) return false;
756
+ const attrsA = a.attrs;
757
+ const attrsB = b.attrs;
758
+ if (attrsA === attrsB) return true;
759
+ /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
760
+ if (!attrsA || !attrsB) return false;
761
+ const keysA = Object.keys(attrsA);
762
+ const keysB = Object.keys(attrsB);
763
+ /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
764
+ if (keysA.length !== keysB.length) return false;
765
+ for (const key of keysA) if (attrsA[key] !== attrsB[key]) return false;
766
+ return true;
767
+ };
768
+ const convertFragment = (nodes) => {
769
+ const result = [];
770
+ const active = [];
771
+ const activeContents = [result];
772
+ for (const node of nodes) {
773
+ const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
774
+ let keep = 0;
775
+ let rendered = 0;
776
+ while (keep < active.length && rendered < nodeMarks.length) {
777
+ if (!marksEqual(active[keep], nodeMarks[rendered])) break;
778
+ keep++;
779
+ rendered++;
780
+ }
781
+ while (active.length > keep) {
782
+ active.pop();
783
+ activeContents.pop();
784
+ }
785
+ while (rendered < nodeMarks.length) {
786
+ const mark = nodeMarks[rendered];
787
+ const markContent = [];
788
+ activeContents[activeContents.length - 1].push({
789
+ type: "mark",
790
+ mark,
791
+ children: {
792
+ type: "fragment",
793
+ content: markContent,
794
+ [impl]: ctx
795
+ },
796
+ [impl]: ctx
797
+ });
798
+ active.push(mark);
799
+ activeContents.push(markContent);
800
+ rendered++;
801
+ }
802
+ activeContents[activeContents.length - 1].push({
803
+ type: "node",
804
+ node,
805
+ children: convertFragment(node.content ?? []),
806
+ [impl]: ctx
807
+ });
926
808
  }
927
- if (featureId !== void 0) throw new Error(`Feature does not support featureId`);
928
- return instances[0].getPublicInterface(this);
929
- }
930
- constructor(instanceFacade, configFacade, options) {
931
- this.options = options;
932
- this.view = null;
933
- this.dispatchTransaction = (tr) => {
934
- const prevState = this.state;
935
- this.state = this.state.apply(tr);
936
- this.view?.updateState(this.state);
937
- if (prevState.doc !== this.state.doc) this.options?.onChange?.();
938
- return this.state;
809
+ return {
810
+ type: "fragment",
811
+ content: result,
812
+ [impl]: ctx
939
813
  };
940
- this.facade = instanceFacade;
941
- const config = configFacade[impl];
942
- this.config = config;
943
- this.schema = config.schema;
944
- this.textblockAttrs = config.textblockAttrs;
945
- this.features = config.features;
946
- this.foreignHtmlParser = options?.foreignHtmlParser ?? new RteHtmlParser(configFacade);
947
- this.foreignHtmlSerializer = options?.foreignHtmlSerializer ?? new RteHtmlSerializer(configFacade);
948
- this.styles = new ElementStyles(sortedContributions(config.features.flatMap((f) => f.getStyles())));
949
- this.initState(options?.initialDocument);
950
- }
951
- initState(initialDoc) {
952
- this.state = EditorState.create({
953
- doc: parseDocument(this.schema, initialDoc),
954
- schema: this.config.schema,
955
- plugins: sortedContributions(this.config.features.flatMap((feature) => feature.getPlugins(this)))
956
- });
814
+ };
815
+ return {
816
+ type: "node",
817
+ node: doc,
818
+ children: convertFragment(doc.content),
819
+ [impl]: ctx
820
+ };
821
+ };
822
+ //#endregion
823
+ //#region src/lib/rich-text-editor/rte/config.ts
824
+ var RteConfig = class {
825
+ constructor(features) {
826
+ this[impl] = new RteConfigImpl(features);
957
827
  }
958
- reset(initialDocument) {
959
- const currentHostState = hostBridgePlugin.getState(this.state);
960
- this.initState(initialDocument);
961
- this.updateHostState(currentHostState);
962
- this.view?.updateState(this.state);
828
+ instantiateEditor(options) {
829
+ return new RteInstance(this, options);
963
830
  }
964
- createView(target) {
965
- this.view = new EditorView((editor) => {
966
- editor.part = "editor";
967
- target.appendChild(editor);
968
- }, {
969
- state: this.state,
970
- dispatchTransaction: this.dispatchTransaction
831
+ instantiateView(document, options) {
832
+ return convertToView(document, {
833
+ config: this,
834
+ options: { ...options }
971
835
  });
972
836
  }
973
- destroyViewIfNeeded() {
974
- if (this.view) {
975
- this.view.destroy();
976
- this.view = null;
837
+ };
838
+ var RteConfigImpl = class {
839
+ constructor(featuresFacades) {
840
+ const resolveFeatures = (features) => features.flatMap((f) => f.getFeatures().flatMap((subFeature) => subFeature === f ? f : resolveFeatures([subFeature])));
841
+ this.features = resolveFeatures(featuresFacades.map(getFeatureImpl));
842
+ this.featureMap = /* @__PURE__ */ new Map();
843
+ for (const f of this.features) {
844
+ if (this.featureMap.has(f.name)) throw new Error(`Duplicate feature: ${f.name}`);
845
+ this.featureMap.set(f.name, f);
977
846
  }
847
+ this.featureFacadesMap = /* @__PURE__ */ new Map();
848
+ for (const facade of featuresFacades) {
849
+ const FacadeClass = facade.constructor;
850
+ const feature = getFeatureImpl(facade);
851
+ const instances = this.featureFacadesMap.get(FacadeClass);
852
+ if (instances) instances.push(feature);
853
+ else this.featureFacadesMap.set(FacadeClass, [feature]);
854
+ }
855
+ if (!this.featureMap.has("RteBase")) throw new Error("RteBase feature is required");
856
+ if (this.featureMap.has("RteLinkFeature") && !this.featureMap.has("RteToolbarFeature")) throw new Error("RteToolbarFeature is required for RteLinkFeature");
857
+ this.textblockAttrs = new TextblockAttrs(sortedContributions(this.features.flatMap((f) => f.getTextblockAttrs())));
858
+ this.textblockMarks = new TextblockMarks(sortedContributions(this.features.flatMap((f) => f.getTextblockMarks())));
859
+ const schemaContributions = sortedContributions(this.features.flatMap((f) => f.getSchema(this.textblockAttrs, this.textblockMarks)));
860
+ const schemaSpec = {
861
+ nodes: {},
862
+ marks: {}
863
+ };
864
+ for (const schema of schemaContributions) {
865
+ Object.assign(schemaSpec.nodes, schema.nodes ?? {});
866
+ Object.assign(schemaSpec.marks, schema.marks ?? {});
867
+ }
868
+ for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
869
+ this.schema = new Schema(schemaSpec);
978
870
  }
979
- createComponent(type) {
980
- return document.createElement(this.hostState().ctx.tagFor(type, true));
871
+ };
872
+ //#endregion
873
+ //#region src/lib/rich-text-editor/rte/utils/ui.ts
874
+ var isPropBinding = (prop) => typeof prop === "function";
875
+ var on = (event, prop, handler) => [
876
+ event,
877
+ prop,
878
+ handler
879
+ ];
880
+ var UiCtx = class {
881
+ constructor(view, rte, props) {
882
+ this.view = view;
883
+ this.rte = rte;
884
+ this.props = props;
885
+ this.bindings = [];
981
886
  }
982
- getLocale() {
983
- return this.hostState().locale;
887
+ evalProp(prop) {
888
+ return isPropBinding(prop) ? prop(this) : prop;
984
889
  }
985
- get tr() {
986
- return this.state.tr;
890
+ bindProp(prop, bindFn) {
891
+ if (prop === void 0) return;
892
+ else if (isPropBinding(prop)) {
893
+ const binding = () => bindFn(prop(this));
894
+ this.bindings.push(binding);
895
+ binding();
896
+ } else bindFn(prop);
987
897
  }
988
- hostState() {
989
- const state = hostBridgePlugin.getState(this.state);
990
- if (!state) throw new Error("No host state available");
991
- return state;
898
+ updateBindings() {
899
+ for (const binding of this.bindings) binding();
992
900
  }
993
- updateHostState(hostState) {
994
- this.dispatchTransaction(this.tr.setMeta(hostBridgePlugin, hostState));
901
+ shouldReturnFocusToEditor() {
902
+ return this.evalProp(this.props.shouldReturnFocusToEditor);
903
+ }
904
+ bindToEl(target, props = {}, events = [], children = []) {
905
+ for (const name in props) this.bindProp(props[name], (value) => {
906
+ target[name] = value;
907
+ });
908
+ for (const [name, value, bindFn] of events) if (value) target.addEventListener(name, bindFn(value));
909
+ for (const child of children) target.appendChild(child);
910
+ }
911
+ };
912
+ var createDiv = (ctx, props) => {
913
+ const div = document.createElement("div");
914
+ ctx.bindToEl(div, {
915
+ className: props.className,
916
+ slot: props.slot
917
+ }, [], props.children);
918
+ return div;
919
+ };
920
+ var createAnchor = (ctx, props) => {
921
+ const anchor = document.createElement("a");
922
+ ctx.bindToEl(anchor, {
923
+ href: props.href,
924
+ target: props.target,
925
+ rel: props.rel,
926
+ className: props.className
927
+ }, [], props.children);
928
+ return anchor;
929
+ };
930
+ var wrapperTargets = /* @__PURE__ */ new WeakMap();
931
+ var createOptionalTooltip = (ctx, props) => {
932
+ const tooltip = ctx.rte.createComponent(Tooltip);
933
+ tooltip.setAttribute("exportparts", "vvd-theme-alternate");
934
+ ctx.bindToEl(tooltip, {
935
+ className: "ui-tooltip",
936
+ text: props.label,
937
+ placement: ctx.props.popupPlacement
938
+ });
939
+ ctx.bindProp(props.enabled, (enabled) => {
940
+ if (!enabled) tooltip.open = false;
941
+ tooltip.anchor = enabled ? props.anchor : void 0;
942
+ });
943
+ const wrapper = createDiv(ctx, {
944
+ className: "ui-tooltip-wrapper",
945
+ slot: props.slot,
946
+ children: [props.anchor, tooltip]
947
+ });
948
+ wrapperTargets.set(wrapper, props.anchor);
949
+ return wrapper;
950
+ };
951
+ var createButton = (ctx, props) => {
952
+ const variant = () => ctx.evalProp(props.variant) ?? "toolbar";
953
+ const size = () => variant() === "toolbar" ? "super-condensed" : "condensed";
954
+ const appearanceInactive = () => variant() === "popover-primary" ? "outlined" : "ghost-light";
955
+ const appearanceActive = () => variant() === "toolbar-menu" || variant() === "popover" ? "filled" : appearanceInactive();
956
+ const appearance = () => ctx.evalProp(props.active) ? appearanceActive() : appearanceInactive();
957
+ const connotation = () => ctx.evalProp(props.connotation) ?? (variant() === "toolbar-menu" && ctx.evalProp(props.active) ? "cta" : void 0);
958
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
959
+ const button = ctx.rte.createComponent(Button);
960
+ ctx.bindToEl(button, {
961
+ size,
962
+ appearance,
963
+ disabled,
964
+ icon: props.icon,
965
+ autofocus: props.autofocus,
966
+ connotation,
967
+ [props.icon ? "ariaLabel" : "label"]: props.label,
968
+ pressed: () => Boolean(ctx.evalProp(props.active)),
969
+ ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
970
+ }, [on("click", props.onClick, (onClick) => () => {
971
+ if (!onClick() && ctx.shouldReturnFocusToEditor()) ctx.view.focus();
972
+ })]);
973
+ return createOptionalTooltip(ctx, {
974
+ enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
975
+ label: props.label,
976
+ anchor: button,
977
+ slot: props.slot
978
+ });
979
+ };
980
+ var createMenu = (ctx, props) => {
981
+ const menu = ctx.rte.createComponent(Menu);
982
+ ctx.bindToEl(menu, {
983
+ className: "ui-menu",
984
+ autoDismiss: true,
985
+ ariaLabel: props.label,
986
+ anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
987
+ placement: ctx.props.popupPlacement,
988
+ offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
989
+ }, [], props.children);
990
+ const fragment = document.createDocumentFragment();
991
+ fragment.appendChild(props.trigger);
992
+ fragment.appendChild(menu);
993
+ return fragment;
994
+ };
995
+ var createMenuItem = (ctx, props) => {
996
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
997
+ const item = ctx.rte.createComponent(MenuItem);
998
+ ctx.bindToEl(item, {
999
+ text: props.text,
1000
+ checked: props.checked,
1001
+ disabled,
1002
+ controlType: "radio",
1003
+ checkedAppearance: "tick-only"
1004
+ }, [on("change", props.onSelect, (onSelect) => () => {
1005
+ if (item.checked && !item.disabled) {
1006
+ if (ctx.evalProp(props.checked) !== item.checked) {
1007
+ onSelect();
1008
+ if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
1009
+ }
1010
+ }
1011
+ })]);
1012
+ return item;
1013
+ };
1014
+ var createButtonGroup = (ctx, props) => createDiv(ctx, {
1015
+ slot: props.slot,
1016
+ className: "ui-button-group",
1017
+ children: props.children
1018
+ });
1019
+ function markActive(state, type) {
1020
+ const { from, $from, to, empty } = state.selection;
1021
+ if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
1022
+ else return state.doc.rangeHasMark(from, to, type);
1023
+ }
1024
+ var createMarkToggle = (ctx, props) => createButton(ctx, {
1025
+ label: props.label,
1026
+ icon: props.icon,
1027
+ active: () => markActive(ctx.view.state, props.markType),
1028
+ disabled: () => !toggleMark(props.markType)(ctx.view.state),
1029
+ onClick: () => {
1030
+ toggleMark(props.markType)(ctx.view.state, ctx.view.dispatch);
995
1031
  }
1032
+ });
1033
+ var createOption = (ctx, props) => {
1034
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
1035
+ const option = ctx.rte.createComponent(ListboxOption);
1036
+ ctx.bindToEl(option, {
1037
+ value: props.value,
1038
+ text: props.text,
1039
+ disabled
1040
+ });
1041
+ return option;
1042
+ };
1043
+ var createSelect = (ctx, props) => {
1044
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled));
1045
+ const select = ctx.rte.createComponent(Select);
1046
+ select.setAttribute("data-class", "ui-select");
1047
+ ctx.bindToEl(select, {
1048
+ placeholder: " ",
1049
+ ariaLabel: props.label,
1050
+ appearance: "ghost",
1051
+ disabled
1052
+ }, [on("change", props.onSelect, (onSelect) => () => {
1053
+ const value = select.value;
1054
+ if (value) {
1055
+ onSelect(value);
1056
+ if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
1057
+ }
1058
+ })], props.children);
1059
+ queueMicrotask(() => {
1060
+ ctx.bindProp(props.value, (value) => select.value = value);
1061
+ });
1062
+ let hideTooltip = false;
1063
+ select.addEventListener("vwc-popup:open", () => {
1064
+ hideTooltip = true;
1065
+ ctx.updateBindings();
1066
+ });
1067
+ select.addEventListener("vwc-popup:close", () => {
1068
+ hideTooltip = false;
1069
+ ctx.updateBindings();
1070
+ });
1071
+ return createOptionalTooltip(ctx, {
1072
+ enabled: () => !hideTooltip && !disabled(),
1073
+ label: props.label,
1074
+ anchor: select
1075
+ });
1076
+ };
1077
+ var createDivider = (ctx) => {
1078
+ const divider = ctx.rte.createComponent(Divider);
1079
+ ctx.bindToEl(divider, {
1080
+ className: "ui-divider",
1081
+ orientation: "vertical"
1082
+ });
1083
+ return divider;
996
1084
  };
997
- //#endregion
998
- //#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
999
- var TextblockAttrs = class {
1000
- constructor(specs) {
1001
- this.specs = specs;
1002
- this.attrs = {};
1003
- for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
1004
- }
1005
- fromDOM(dom) {
1006
- return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
1007
- }
1008
- getStyle(node) {
1009
- return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
1010
- }
1011
- getDOMAttrsProperties(node) {
1012
- if (!this.specs.length) return {};
1013
- return { style: this.getStyle(node) };
1014
- }
1015
- getDOMAttrs(node) {
1016
- if (!this.specs.length) return [];
1017
- return [this.getDOMAttrsProperties(node)];
1018
- }
1019
- extractFromNode(node) {
1020
- return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
1021
- }
1085
+ var createTextField = (ctx, props) => {
1086
+ const textField = ctx.rte.createComponent(TextField);
1087
+ ctx.bindToEl(textField, {
1088
+ label: props.label,
1089
+ value: props.value,
1090
+ placeholder: props.placeholder,
1091
+ slot: props.slot,
1092
+ autofocus: props.autofocus,
1093
+ type: props.type,
1094
+ helperText: props.helperText
1095
+ }, [on("input", props.onInput, (onInput) => () => {
1096
+ onInput(textField.value);
1097
+ })]);
1098
+ return textField;
1022
1099
  };
1023
- //#endregion
1024
- //#region src/lib/rich-text-editor/rte/utils/textblock-marks.ts
1025
- var TextblockMarks = class {
1026
- constructor(specs) {
1027
- this.specs = specs;
1028
- }
1029
- getAllowedMarksForNode(nodeName) {
1030
- return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
1031
- }
1032
- getReferencedNodeNames() {
1033
- return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
1034
- }
1100
+ var createText = (ctx, props) => {
1101
+ const textNode = document.createTextNode("");
1102
+ ctx.bindToEl(textNode, { textContent: props.text });
1103
+ return textNode;
1035
1104
  };
1036
- //#endregion
1037
- //#region src/lib/rich-text-editor/rte/view.ts
1038
- /**
1039
- * Converts an RteDocument to an RteView tree for rendering outside the editor.
1040
- * The logic is adapted from ProseMirror's DOM serialization to ensure the resulting structure
1041
- * matches the editor's HTML structure.
1042
- */
1043
- var convertToView = (doc, ctx) => {
1044
- const schema = ctx.config[impl].schema;
1045
- const getMarkRank = (mark) => schema.marks[mark.type].rank;
1046
- const marksEqual = (a, b) => {
1047
- if (a.type !== b.type) return false;
1048
- const attrsA = a.attrs;
1049
- const attrsB = b.attrs;
1050
- if (attrsA === attrsB) return true;
1051
- /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
1052
- if (!attrsA || !attrsB) return false;
1053
- const keysA = Object.keys(attrsA);
1054
- const keysB = Object.keys(attrsB);
1055
- /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
1056
- if (keysA.length !== keysB.length) return false;
1057
- for (const key of keysA) if (attrsA[key] !== attrsB[key]) return false;
1058
- return true;
1059
- };
1060
- const convertFragment = (nodes) => {
1061
- const result = [];
1062
- const active = [];
1063
- const activeContents = [result];
1064
- for (const node of nodes) {
1065
- const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
1066
- let keep = 0;
1067
- let rendered = 0;
1068
- while (keep < active.length && rendered < nodeMarks.length) {
1069
- if (!marksEqual(active[keep], nodeMarks[rendered])) break;
1070
- keep++;
1071
- rendered++;
1072
- }
1073
- while (active.length > keep) {
1074
- active.pop();
1075
- activeContents.pop();
1076
- }
1077
- while (rendered < nodeMarks.length) {
1078
- const mark = nodeMarks[rendered];
1079
- const markContent = [];
1080
- activeContents[activeContents.length - 1].push({
1081
- type: "mark",
1082
- mark,
1083
- children: {
1084
- type: "fragment",
1085
- content: markContent,
1086
- [impl]: ctx
1087
- },
1088
- [impl]: ctx
1089
- });
1090
- active.push(mark);
1091
- activeContents.push(markContent);
1092
- rendered++;
1093
- }
1094
- activeContents[activeContents.length - 1].push({
1095
- type: "node",
1096
- node,
1097
- children: convertFragment(node.content ?? []),
1098
- [impl]: ctx
1105
+ var createSingleSlot = (ctx, props) => {
1106
+ const slot = document.createElement("slot");
1107
+ slot.name = props.name;
1108
+ let currentEl = null;
1109
+ const listeners = [];
1110
+ function cleanup() {
1111
+ for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
1112
+ listeners.length = 0;
1113
+ currentEl = null;
1114
+ }
1115
+ for (const [key, prop] of Object.entries(props.assignedProps)) ctx.bindProp(prop, (value) => {
1116
+ if (currentEl) currentEl[key] = value;
1117
+ });
1118
+ function applyPropsAndEvents(el) {
1119
+ for (const [key, prop] of Object.entries(props.assignedProps)) el[key] = ctx.evalProp(prop);
1120
+ for (const [type, handler] of Object.entries(props.assignedEvents)) {
1121
+ const listener = (e) => {
1122
+ handler(e);
1123
+ if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
1124
+ };
1125
+ el.addEventListener(type, listener);
1126
+ listeners.push({
1127
+ el,
1128
+ type,
1129
+ handler: listener
1099
1130
  });
1100
1131
  }
1101
- return {
1102
- type: "fragment",
1103
- content: result,
1104
- [impl]: ctx
1105
- };
1106
- };
1107
- return {
1108
- type: "node",
1109
- node: doc,
1110
- children: convertFragment(doc.content),
1111
- [impl]: ctx
1132
+ }
1133
+ const processSlot = () => {
1134
+ const first = slot.assignedElements({ flatten: true })[0];
1135
+ if (first === currentEl) return;
1136
+ cleanup();
1137
+ if (first) {
1138
+ currentEl = first;
1139
+ applyPropsAndEvents(first);
1140
+ }
1112
1141
  };
1142
+ slot.addEventListener("slotchange", processSlot);
1143
+ queueMicrotask(processSlot);
1144
+ return slot;
1113
1145
  };
1114
1146
  //#endregion
1115
- //#region src/lib/rich-text-editor/rte/config.ts
1116
- var RteConfig = class {
1117
- constructor(features) {
1118
- this[impl] = new RteConfigImpl(features);
1119
- }
1120
- instantiateEditor(options) {
1121
- return new RteInstance(this, options);
1147
+ //#region src/lib/rich-text-editor/rte/features/internal/history.ts
1148
+ var RteHistoryFeatureImpl = class extends RteFeatureImpl {
1149
+ constructor(config) {
1150
+ super();
1151
+ this.config = config;
1152
+ this.name = "RteHistoryFeature";
1122
1153
  }
1123
- instantiateView(document, options) {
1124
- return convertToView(document, {
1125
- config: this,
1126
- options: { ...options }
1127
- });
1154
+ getPlugins() {
1155
+ return [this.contribution(history()), this.contribution(keymap({
1156
+ "Mod-z": undo,
1157
+ "Ctrl-y": redo,
1158
+ "Cmd-Z": redo
1159
+ }))];
1128
1160
  }
1129
- };
1130
- var RteConfigImpl = class {
1131
- constructor(featuresFacades) {
1132
- const resolveFeatures = (features) => features.flatMap((f) => f.getFeatures().flatMap((subFeature) => subFeature === f ? f : resolveFeatures([subFeature])));
1133
- this.features = resolveFeatures(featuresFacades.map(getFeatureImpl));
1134
- this.featureMap = /* @__PURE__ */ new Map();
1135
- for (const f of this.features) {
1136
- if (this.featureMap.has(f.name)) throw new Error(`Duplicate feature: ${f.name}`);
1137
- this.featureMap.set(f.name, f);
1138
- }
1139
- this.featureFacadesMap = /* @__PURE__ */ new Map();
1140
- for (const facade of featuresFacades) {
1141
- const FacadeClass = facade.constructor;
1142
- const feature = getFeatureImpl(facade);
1143
- const instances = this.featureFacadesMap.get(FacadeClass);
1144
- if (instances) instances.push(feature);
1145
- else this.featureFacadesMap.set(FacadeClass, [feature]);
1146
- }
1147
- if (!this.featureMap.has("RteBase")) throw new Error("RteBase feature is required");
1148
- if (this.featureMap.has("RteLinkFeature") && !this.featureMap.has("RteToolbarFeature")) throw new Error("RteToolbarFeature is required for RteLinkFeature");
1149
- this.textblockAttrs = new TextblockAttrs(sortedContributions(this.features.flatMap((f) => f.getTextblockAttrs())));
1150
- this.textblockMarks = new TextblockMarks(sortedContributions(this.features.flatMap((f) => f.getTextblockMarks())));
1151
- const schemaContributions = sortedContributions(this.features.flatMap((f) => f.getSchema(this.textblockAttrs, this.textblockMarks)));
1152
- const schemaSpec = {
1153
- nodes: {},
1154
- marks: {}
1155
- };
1156
- for (const schema of schemaContributions) {
1157
- Object.assign(schemaSpec.nodes, schema.nodes ?? {});
1158
- Object.assign(schemaSpec.marks, schema.marks ?? {});
1159
- }
1160
- for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
1161
- this.schema = new Schema(schemaSpec);
1161
+ getToolbarItems() {
1162
+ if (!this.config.showToolbarButtons) return [];
1163
+ return [this.contribution({
1164
+ section: "history",
1165
+ render: (ctx) => createButton(ctx, {
1166
+ label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
1167
+ icon: "undo-line",
1168
+ disabled: (ctx) => !undo(ctx.view.state),
1169
+ onClick: () => {
1170
+ const { state, dispatch } = ctx.view;
1171
+ undo(state, dispatch);
1172
+ }
1173
+ })
1174
+ }, 1), this.contribution({
1175
+ section: "history",
1176
+ render: (ctx) => createButton(ctx, {
1177
+ label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
1178
+ icon: "redo-line",
1179
+ disabled: (ctx) => !redo(ctx.view.state),
1180
+ onClick: () => {
1181
+ const { state, dispatch } = ctx.view;
1182
+ redo(state, dispatch);
1183
+ }
1184
+ })
1185
+ }, 2)];
1162
1186
  }
1163
1187
  };
1164
1188
  //#endregion
@@ -1344,6 +1368,7 @@ var RteBaseImpl = class extends RteFeatureImpl {
1344
1368
  return [
1345
1369
  this,
1346
1370
  new RteCoreImpl(),
1371
+ new RteHistoryFeatureImpl({ showToolbarButtons: this.config?.historyToolbarButtons ?? true }),
1347
1372
  new RteBasicTextBlocksImpl({
1348
1373
  heading1: this.config?.heading1 ?? false,
1349
1374
  heading2: this.config?.heading2 ?? false,
@@ -1361,6 +1386,20 @@ var RteBaseImpl = class extends RteFeatureImpl {
1361
1386
  },
1362
1387
  set disabled(value) {
1363
1388
  core.disabled.setValue(rte, value);
1389
+ },
1390
+ get scrollThreshold() {
1391
+ return core.scrollThreshold;
1392
+ },
1393
+ set scrollThreshold(value) {
1394
+ core.scrollThreshold = value;
1395
+ rte.view?.setProps({ scrollThreshold: value });
1396
+ },
1397
+ get scrollMargin() {
1398
+ return core.scrollMargin;
1399
+ },
1400
+ set scrollMargin(value) {
1401
+ core.scrollMargin = value;
1402
+ rte.view?.setProps({ scrollMargin: value });
1364
1403
  }
1365
1404
  };
1366
1405
  }
@@ -1951,7 +1990,7 @@ var RteTextColorPickerFeatureImpl = class extends RteFeatureImpl {
1951
1990
  if (selectionColor === null) selectionColor = observedColor ?? null;
1952
1991
  else if (observedColor !== selectionColor) selectionColor = mixedColor;
1953
1992
  };
1954
- state.doc.nodesBetween(from, to, (node, pos, parent) => {
1993
+ state.doc.nodesBetween(from, to, (node, _pos, parent) => {
1955
1994
  if (selectionColor === mixedColor) return false;
1956
1995
  if (!node.isLeaf) {
1957
1996
  if (node.type.allowsMarkType(textColor) && node.childCount === 0) observeColor(this.defaultColorForNode?.[node.type.name]);
@@ -2700,7 +2739,7 @@ var InlineImageView = class {
2700
2739
  this.img.className = "inline-image";
2701
2740
  this.update(node);
2702
2741
  this.img.addEventListener("load", () => {
2703
- const pos = this.getPos?.();
2742
+ const pos = this.getPos();
2704
2743
  if (pos) {
2705
2744
  const { state, dispatch } = this.view;
2706
2745
  const currentNode = state.doc.nodeAt(pos);
@@ -2807,6 +2846,7 @@ var RteInlineImageFeatureImpl = class extends RteFeatureImpl {
2807
2846
  shouldReturnFocusToEditor: true
2808
2847
  });
2809
2848
  popover = rte.createComponent(Popover);
2849
+ popover.kind = "imageOptions";
2810
2850
  popover.offset = 4;
2811
2851
  const content = createDiv(ctx, {
2812
2852
  className: "inline-image-popover",
@@ -2867,7 +2907,7 @@ var RteInlineImageFeatureImpl = class extends RteFeatureImpl {
2867
2907
  } }))];
2868
2908
  }
2869
2909
  calculateSmallWidth(naturalWidth) {
2870
- return Math.min(300, naturalWidth / 2) + "px";
2910
+ return `${Math.min(300, naturalWidth / 2)}px`;
2871
2911
  }
2872
2912
  getSelectedInlineImage(state) {
2873
2913
  const sel = state.selection;
@@ -3000,13 +3040,13 @@ var RteFileHandlerFeatureImpl = class extends RteFeatureImpl {
3000
3040
  if (resolved.type === "error") console.error("Error in handleFiles handler:", resolved.error);
3001
3041
  };
3002
3042
  return [this.contribution(insertPointPlaceholderPlugin), this.contribution(new Plugin({ props: {
3003
- handlePaste: (view, event) => {
3043
+ handlePaste: (_view, event) => {
3004
3044
  const files = Array.from(event.clipboardData.files);
3005
3045
  if (!files.length) return false;
3006
3046
  handleFiles(files, { type: "selection" });
3007
3047
  return true;
3008
3048
  },
3009
- handleDrop: (view, event, slice, moved) => {
3049
+ handleDrop: (view, event) => {
3010
3050
  const files = Array.from(event.dataTransfer.files);
3011
3051
  if (!files.length) return false;
3012
3052
  let target = view.posAtCoords({
@@ -3035,7 +3075,7 @@ var RteDropHandlerFeatureImpl = class extends RteFeatureImpl {
3035
3075
  this.config = config;
3036
3076
  this.name = "RteDebugFeature";
3037
3077
  }
3038
- getPlugins(rte) {
3078
+ getPlugins() {
3039
3079
  const dragOverResults = /* @__PURE__ */ new WeakMap();
3040
3080
  let lastResult = false;
3041
3081
  const onDragOver = (event) => {
@@ -3073,7 +3113,7 @@ var RteDropHandlerFeatureImpl = class extends RteFeatureImpl {
3073
3113
  viewport.removeEventListener("dragleave", onDragLeave);
3074
3114
  } };
3075
3115
  } })), this.contribution(new Plugin({ props: { handleDOMEvents: {
3076
- dragover: (view, event) => {
3116
+ dragover: (_view, event) => {
3077
3117
  const result = this.config.onViewportDragOver?.(event) ?? false;
3078
3118
  dragOverResults.set(event, result);
3079
3119
  lastResult = result;
@@ -3084,7 +3124,7 @@ var RteDropHandlerFeatureImpl = class extends RteFeatureImpl {
3084
3124
  }
3085
3125
  return false;
3086
3126
  },
3087
- drop: (view, event) => {
3127
+ drop: () => {
3088
3128
  return lastResult;
3089
3129
  }
3090
3130
  } } }), contributionPriority.high)];
@@ -3223,7 +3263,6 @@ var RteInputRuleFeature = featureFacade(RteInputRuleFeatureImpl);
3223
3263
  //#endregion
3224
3264
  //#region src/lib/rich-text-editor/rte/features/keyboard-shortcuts.ts
3225
3265
  function toCommand(handler, rteInstance) {
3226
- if (handler.length === 0) return (_state, _dispatch) => handler();
3227
3266
  return (_state, _dispatch) => handler(rteInstance);
3228
3267
  }
3229
3268
  var RteKeyboardShortcutsFeatureImpl = class extends RteFeatureImpl {