@vonage/vivid 5.20.0 → 5.21.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 (65) hide show
  1. package/alert/index.cjs +1 -1
  2. package/alert/index.js +1 -1
  3. package/banner/index.cjs +1 -1
  4. package/banner/index.js +1 -1
  5. package/bundled/definition18.cjs +1 -1
  6. package/bundled/definition18.js +1 -1
  7. package/bundled/definition6.cjs +1 -1
  8. package/bundled/definition6.js +1 -1
  9. package/bundled/vivid-element.cjs +1 -1
  10. package/bundled/vivid-element.js +1 -1
  11. package/combobox/index.cjs +1 -1
  12. package/combobox/index.js +1 -1
  13. package/custom-elements.json +2635 -2626
  14. package/file-picker/index.cjs +1 -1
  15. package/file-picker/index.js +1 -1
  16. package/lib/rich-text-editor/rte/features/base.d.ts +1 -0
  17. package/lib/rich-text-editor/rte/features/internal/history.d.ts +6 -0
  18. package/lib/rich-text-editor/rte/utils/ui.d.ts +2 -0
  19. package/locales/de-DE.cjs +178 -4
  20. package/locales/de-DE.js +179 -2
  21. package/locales/en-GB.cjs +9 -4
  22. package/locales/en-GB.js +10 -2
  23. package/locales/en-US.cjs +2 -270
  24. package/locales/en-US.js +1 -267
  25. package/locales/ja-JP.cjs +171 -4
  26. package/locales/ja-JP.js +172 -2
  27. package/locales/zh-CN.cjs +172 -4
  28. package/locales/zh-CN.js +173 -2
  29. package/package.json +6 -5
  30. package/rich-text-editor/index.cjs +12 -12
  31. package/rich-text-editor/index.js +935 -916
  32. package/searchable-select/index.cjs +1 -1
  33. package/searchable-select/index.js +1 -1
  34. package/switch/index.cjs +1 -1
  35. package/switch/index.js +1 -1
  36. package/tabs/index.cjs +1 -1
  37. package/tabs/index.js +1 -1
  38. package/unbundled/chunk.cjs +15 -0
  39. package/unbundled/chunk.js +13 -0
  40. package/unbundled/definition17.cjs +1 -1
  41. package/unbundled/definition17.js +1 -1
  42. package/unbundled/definition30.cjs +1 -1
  43. package/unbundled/definition30.js +1 -1
  44. package/unbundled/definition43.cjs +1 -1
  45. package/unbundled/definition43.js +1 -1
  46. package/unbundled/definition60.cjs +801 -767
  47. package/unbundled/definition60.js +801 -767
  48. package/unbundled/definition61.cjs +1 -1
  49. package/unbundled/definition61.js +1 -1
  50. package/unbundled/definition63.cjs +1 -1
  51. package/unbundled/definition63.js +1 -1
  52. package/unbundled/definition69.cjs +1 -1
  53. package/unbundled/definition69.js +1 -1
  54. package/unbundled/definition7.cjs +1 -1
  55. package/unbundled/definition7.js +1 -1
  56. package/unbundled/definition73.cjs +1 -1
  57. package/unbundled/definition73.js +1 -1
  58. package/unbundled/definition9.cjs +1 -1
  59. package/unbundled/definition9.js +1 -1
  60. package/unbundled/en-US.cjs +449 -0
  61. package/unbundled/en-US.js +445 -0
  62. package/unbundled/localized.cjs +2 -2
  63. package/unbundled/localized.js +1 -1
  64. package/unbundled/vivid-element.cjs +1 -1
  65. package/unbundled/vivid-element.js +1 -1
@@ -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";
@@ -238,645 +238,333 @@ var FeatureState = class {
238
238
  //#region src/lib/rich-text-editor/rte/features/internal/core.style.scss?inline
239
239
  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
240
  //#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 = [];
254
- }
255
- evalProp(prop) {
256
- return isPropBinding(prop) ? prop(this) : prop;
241
+ //#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
242
+ var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
243
+ constructor(..._args) {
244
+ super(..._args);
245
+ this.name = "RteForeignHtmlFeature";
257
246
  }
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);
247
+ getPlugins(rte) {
248
+ return [this.contribution(new Plugin({ props: {
249
+ transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
250
+ clipboardParser: rte.foreignHtmlParser[impl].parser,
251
+ clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
252
+ } }))];
265
253
  }
266
- updateBindings() {
267
- for (const binding of this.bindings) binding();
254
+ };
255
+ //#endregion
256
+ //#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
257
+ /**
258
+ * When the cursor is positioned after an inline atom node at the end of a line,
259
+ * browsers may display the cursor inside the atom.
260
+ */
261
+ var atomCursorFix = ($cursor) => {
262
+ if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
263
+ const nodeBefore = $cursor.nodeBefore;
264
+ if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
265
+ return null;
266
+ };
267
+ var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
268
+ constructor(..._args) {
269
+ super(..._args);
270
+ this.name = "RteCursorFix";
268
271
  }
269
- bindToEl(target, props = {}, events = [], children = []) {
270
- for (const name in props) this.bindProp(props[name], (value) => {
271
- target[name] = value;
272
- });
273
- for (const [name, value, bindFn] of events) if (value) target.addEventListener(name, bindFn(value));
274
- for (const child of children) target.appendChild(child);
272
+ getPlugins(rte) {
273
+ const cursorFixes = [atomCursorFix];
274
+ for (const markType of Object.values(rte.schema.marks)) {
275
+ const spec = markType.spec;
276
+ if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
277
+ }
278
+ return [this.contribution(new Plugin({ props: { decorations: (state) => {
279
+ const { $cursor } = state.selection;
280
+ if (!$cursor) return null;
281
+ let cursorFix = null;
282
+ for (const fn of cursorFixes) {
283
+ const result = fn($cursor, state);
284
+ if (result) Object.assign(cursorFix ??= {}, result);
285
+ }
286
+ if (!cursorFix) return null;
287
+ return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
288
+ const span = document.createElement("span");
289
+ span.textContent = "​";
290
+ for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
291
+ return span;
292
+ }, { side: -1 })]);
293
+ } } }))];
275
294
  }
276
295
  };
277
- var createDiv = (ctx, props) => {
278
- const div = document.createElement("div");
279
- ctx.bindToEl(div, {
280
- className: props.className,
281
- slot: props.slot
282
- }, [], props.children);
283
- return div;
296
+ featureFacade(RteCursorFixFeatureImpl);
297
+ //#endregion
298
+ //#region src/lib/rich-text-editor/rte/features/internal/core.ts
299
+ /**
300
+ * Plugin to bring state from the host web component into ProseMirror.
301
+ */
302
+ var hostBridgePlugin = new Plugin({ state: {
303
+ init() {
304
+ return null;
305
+ },
306
+ apply(tr, value) {
307
+ const meta = tr.getMeta(hostBridgePlugin);
308
+ if (meta) return meta;
309
+ else return value;
310
+ }
311
+ } });
312
+ var RteCoreImpl = class extends RteFeatureImpl {
313
+ constructor(..._args) {
314
+ super(..._args);
315
+ this.name = "RteCore";
316
+ this.disabled = new FeatureState(false);
317
+ }
318
+ getStyles() {
319
+ return [
320
+ this.contribution(prosemirror_default),
321
+ this.contribution(core_style_default),
322
+ this.contribution(ui_style_default)
323
+ ];
324
+ }
325
+ getPlugins(rte) {
326
+ const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
327
+ if (!atEnd) return {
328
+ type: node.type,
329
+ attrs: node.attrs
330
+ };
331
+ return {
332
+ type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
333
+ attrs: rte.textblockAttrs.extractFromNode(node)
334
+ };
335
+ }));
336
+ return [
337
+ this.contribution(this.disabled.plugin),
338
+ this.contribution(new Plugin({
339
+ props: {
340
+ editable: () => !this.disabled.getValue(rte),
341
+ handleDOMEvents: { click: (_view, event) => {
342
+ if (this.disabled.getValue(rte)) {
343
+ event.preventDefault();
344
+ return true;
345
+ }
346
+ return false;
347
+ } }
348
+ },
349
+ view: (view) => {
350
+ const popovers = view.dom.getRootNode().querySelector(".popovers");
351
+ const updateDisabled = () => {
352
+ const disabled = this.disabled.getValue(rte);
353
+ popovers.classList.toggle("popovers--disabled", disabled);
354
+ view.dom.parentElement.classList.toggle("editor--disabled", disabled);
355
+ };
356
+ updateDisabled();
357
+ return { update: () => {
358
+ updateDisabled();
359
+ } };
360
+ }
361
+ })),
362
+ this.contribution(keymap({
363
+ ...baseKeymap,
364
+ Enter: enterKeyChainCommands,
365
+ "Shift-Enter": enterKeyChainCommands
366
+ })),
367
+ this.contribution(dropCursor()),
368
+ this.contribution(gapCursor()),
369
+ this.contribution(hostBridgePlugin)
370
+ ];
371
+ }
372
+ getFeatures() {
373
+ return [
374
+ this,
375
+ new RteForeignHtmlFeatureImpl(),
376
+ new RteCursorFixFeatureImpl()
377
+ ];
378
+ }
284
379
  };
285
- var createAnchor = (ctx, props) => {
380
+ featureFacade(RteCoreImpl);
381
+ //#endregion
382
+ //#region src/lib/rich-text-editor/rte/utils/sanitization.ts
383
+ var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
384
+ var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
385
+ var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
386
+ var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
387
+ /**
388
+ * Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
389
+ * Returns empty string if the URL is unsafe.
390
+ */
391
+ var sanitizeLinkHref = (url) => {
392
+ if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
286
393
  const anchor = document.createElement("a");
287
- ctx.bindToEl(anchor, {
288
- href: props.href,
289
- target: props.target,
290
- rel: props.rel,
291
- className: props.className
292
- }, [], props.children);
293
- return anchor;
394
+ anchor.setAttribute("href", url);
395
+ /* v8 ignore next -- since href is already validated it's probably always present @preserve */
396
+ return DOMPurify.sanitize(anchor, {
397
+ RETURN_DOM: true,
398
+ ...domPurifyConfig
399
+ }).querySelector("a").getAttribute("href") ?? "";
294
400
  };
295
- var wrapperTargets = /* @__PURE__ */ new WeakMap();
296
- var createOptionalTooltip = (ctx, props) => {
297
- const tooltip = ctx.rte.createComponent(Tooltip);
298
- tooltip.setAttribute("exportparts", "vvd-theme-alternate");
299
- ctx.bindToEl(tooltip, {
300
- className: "ui-tooltip",
301
- text: props.label,
302
- placement: ctx.props.popupPlacement
303
- });
304
- ctx.bindProp(props.enabled, (enabled) => {
305
- if (!enabled) tooltip.open = false;
306
- tooltip.anchor = enabled ? props.anchor : void 0;
307
- });
308
- const wrapper = createDiv(ctx, {
309
- className: "ui-tooltip-wrapper",
310
- slot: props.slot,
311
- children: [props.anchor, tooltip]
312
- });
313
- wrapperTargets.set(wrapper, props.anchor);
314
- return wrapper;
401
+ /**
402
+ * Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
403
+ * Returns empty string if the URL is unsafe.
404
+ */
405
+ var sanitizeImageSrc = (url) => {
406
+ const img = document.createElement("img");
407
+ img.setAttribute("src", url);
408
+ return DOMPurify.sanitize(img, {
409
+ RETURN_DOM: true,
410
+ ...domPurifyConfig
411
+ }).querySelector("img").getAttribute("src") ?? "";
315
412
  };
316
- var createButton = (ctx, props) => {
317
- const variant = () => ctx.evalProp(props.variant) ?? "toolbar";
318
- const size = () => variant() === "toolbar" ? "super-condensed" : "condensed";
319
- const appearanceInactive = () => variant() === "popover-primary" ? "outlined" : "ghost-light";
320
- const appearanceActive = () => variant() === "toolbar-menu" || variant() === "popover" ? "filled" : appearanceInactive();
321
- const appearance = () => ctx.evalProp(props.active) ? appearanceActive() : appearanceInactive();
322
- const connotation = () => ctx.evalProp(props.connotation) ?? (variant() === "toolbar-menu" && ctx.evalProp(props.active) ? "cta" : void 0);
323
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
324
- const button = ctx.rte.createComponent(Button);
325
- ctx.bindToEl(button, {
326
- size,
327
- appearance,
328
- disabled,
329
- icon: props.icon,
330
- autofocus: props.autofocus,
331
- connotation,
332
- [props.icon ? "ariaLabel" : "label"]: props.label,
333
- pressed: () => Boolean(ctx.evalProp(props.active)),
334
- ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
335
- }, [on("click", props.onClick, (onClick) => () => {
336
- if (!onClick()) ctx.view.focus();
337
- })]);
338
- return createOptionalTooltip(ctx, {
339
- enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
340
- label: props.label,
341
- anchor: button,
342
- slot: props.slot
343
- });
344
- };
345
- var createMenu = (ctx, props) => {
346
- const menu = ctx.rte.createComponent(Menu);
347
- ctx.bindToEl(menu, {
348
- className: "ui-menu",
349
- autoDismiss: true,
350
- ariaLabel: props.label,
351
- anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
352
- placement: ctx.props.popupPlacement,
353
- offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
354
- }, [], props.children);
355
- const fragment = document.createDocumentFragment();
356
- fragment.appendChild(props.trigger);
357
- fragment.appendChild(menu);
358
- return fragment;
359
- };
360
- var createMenuItem = (ctx, props) => {
361
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
362
- const item = ctx.rte.createComponent(MenuItem);
363
- ctx.bindToEl(item, {
364
- text: props.text,
365
- checked: props.checked,
366
- disabled,
367
- controlType: "radio",
368
- checkedAppearance: "tick-only"
369
- }, [on("change", props.onSelect, (onSelect) => () => {
370
- if (item.checked && !item.disabled) {
371
- if (ctx.evalProp(props.checked) !== item.checked) {
372
- onSelect();
373
- ctx.view.focus();
374
- }
375
- }
376
- })]);
377
- return item;
378
- };
379
- var createButtonGroup = (ctx, props) => createDiv(ctx, {
380
- slot: props.slot,
381
- className: "ui-button-group",
382
- children: props.children
413
+ /**
414
+ * Escapes a CSS value for insertion into style attribute.
415
+ * E.g. "15px; background: red" => "15px"
416
+ */
417
+ var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
418
+ //#endregion
419
+ //#region src/lib/rich-text-editor/rte/html-parser.ts
420
+ var copy = (obj) => ({ ...obj });
421
+ var parseRulesFromSchema = (schema) => ({
422
+ nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
423
+ marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
383
424
  });
384
- function markActive(state, type) {
385
- const { from, $from, to, empty } = state.selection;
386
- if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
387
- else return state.doc.rangeHasMark(from, to, type);
388
- }
389
- var createMarkToggle = (ctx, props) => createButton(ctx, {
390
- label: props.label,
391
- icon: props.icon,
392
- active: () => markActive(ctx.view.state, props.markType),
393
- disabled: () => !toggleMark(props.markType)(ctx.view.state),
394
- onClick: () => {
395
- toggleMark(props.markType)(ctx.view.state, ctx.view.dispatch);
425
+ var RteHtmlParser = class {
426
+ constructor(config, options) {
427
+ this[impl] = new RteHtmlParserImpl(config[impl], options);
428
+ }
429
+ /**
430
+ * Converts an HTML string to an RteDocument.
431
+ */
432
+ parseDocument(html, options) {
433
+ return this[impl].parser.parse(this.parseHtml(html, options), { preserveWhitespace: true }).toJSON();
434
+ }
435
+ /**
436
+ * Converts an HTML string to an RteFragment.
437
+ */
438
+ parseFragment(html, options) {
439
+ return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
440
+ }
441
+ parseHtml(html, options) {
442
+ const dom = DOMPurify.sanitize(html, {
443
+ RETURN_DOM: true,
444
+ ...domPurifyConfig
445
+ });
446
+ const container = document.createDocumentFragment();
447
+ container.appendChild(dom);
448
+ options?.modifyDom?.(container);
449
+ return container;
396
450
  }
397
- });
398
- var createOption = (ctx, props) => {
399
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
400
- const option = ctx.rte.createComponent(ListboxOption);
401
- ctx.bindToEl(option, {
402
- value: props.value,
403
- text: props.text,
404
- disabled
405
- });
406
- return option;
407
451
  };
408
- var createSelect = (ctx, props) => {
409
- const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled));
410
- const select = ctx.rte.createComponent(Select);
411
- select.setAttribute("data-class", "ui-select");
412
- ctx.bindToEl(select, {
413
- placeholder: " ",
414
- ariaLabel: props.label,
415
- appearance: "ghost",
416
- disabled
417
- }, [on("change", props.onSelect, (onSelect) => () => {
418
- const value = select.value;
419
- if (value) {
420
- onSelect(value);
421
- ctx.view.focus();
452
+ var RteHtmlParserImpl = class {
453
+ constructor(config, options) {
454
+ const rules = parseRulesFromSchema(config.schema);
455
+ options?.modifyParseRules?.(rules);
456
+ this.parser = buildDomParser(config.schema, rules);
457
+ }
458
+ transform(html) {
459
+ return DOMPurify.sanitize(html);
460
+ }
461
+ };
462
+ var buildDomParser = (schema, { marks, nodes }) => {
463
+ const parserRules = [];
464
+ const priority = (rule) => rule.priority ?? 50;
465
+ function insert(rule) {
466
+ let i = 0;
467
+ for (; i < parserRules.length; i++) {
468
+ const next = parserRules[i];
469
+ if (priority(next) < priority(rule)) break;
422
470
  }
423
- })], props.children);
424
- queueMicrotask(() => {
425
- ctx.bindProp(props.value, (value) => select.value = value);
426
- });
427
- let hideTooltip = false;
428
- select.addEventListener("vwc-popup:open", () => {
429
- hideTooltip = true;
430
- ctx.updateBindings();
431
- });
432
- select.addEventListener("vwc-popup:close", () => {
433
- hideTooltip = false;
434
- ctx.updateBindings();
471
+ parserRules.splice(i, 0, rule);
472
+ }
473
+ for (const name in marks) marks[name]?.forEach((rule) => {
474
+ insert(rule = copy(rule));
475
+ if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
435
476
  });
436
- return createOptionalTooltip(ctx, {
437
- enabled: () => !hideTooltip && !disabled(),
438
- label: props.label,
439
- anchor: select
477
+ for (const name in nodes) nodes[name]?.forEach((rule) => {
478
+ insert(rule = copy(rule));
479
+ if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
440
480
  });
441
- };
442
- var createDivider = (ctx) => {
443
- const divider = ctx.rte.createComponent(Divider);
444
- ctx.bindToEl(divider, {
445
- className: "ui-divider",
446
- orientation: "vertical"
481
+ parserRules.push({
482
+ tag: "br",
483
+ closeParent: true
447
484
  });
448
- return divider;
449
- };
450
- var createTextField = (ctx, props) => {
451
- const textField = ctx.rte.createComponent(TextField);
452
- ctx.bindToEl(textField, {
453
- label: props.label,
454
- value: props.value,
455
- placeholder: props.placeholder,
456
- slot: props.slot,
457
- autofocus: props.autofocus,
458
- type: props.type,
459
- helperText: props.helperText
460
- }, [on("input", props.onInput, (onInput) => () => {
461
- onInput(textField.value);
462
- })]);
463
- return textField;
464
- };
465
- var createText = (ctx, props) => {
466
- const textNode = document.createTextNode("");
467
- ctx.bindToEl(textNode, { textContent: props.text });
468
- return textNode;
485
+ return new DOMParser(schema, parserRules);
469
486
  };
470
- var createSingleSlot = (ctx, props) => {
471
- const slot = document.createElement("slot");
472
- slot.name = props.name;
473
- let currentEl = null;
474
- const listeners = [];
475
- function cleanup() {
476
- for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
477
- listeners.length = 0;
478
- currentEl = null;
487
+ //#endregion
488
+ //#region src/lib/rich-text-editor/rte/html-serializer.ts
489
+ var RteHtmlSerializer = class {
490
+ constructor(config, options) {
491
+ this[impl] = new RteHtmlSerializerImpl(config[impl], options);
479
492
  }
480
- for (const [key, prop] of Object.entries(props.assignedProps)) ctx.bindProp(prop, (value) => {
481
- if (currentEl) currentEl[key] = value;
482
- });
483
- function applyPropsAndEvents(el) {
484
- for (const [key, prop] of Object.entries(props.assignedProps)) el[key] = ctx.evalProp(prop);
485
- for (const [type, handler] of Object.entries(props.assignedEvents)) {
486
- const listener = (e) => {
487
- handler(e);
488
- ctx.view.focus();
489
- };
490
- el.addEventListener(type, listener);
491
- listeners.push({
492
- el,
493
- type,
494
- handler: listener
495
- });
496
- }
493
+ /**
494
+ * Converts an RteDocument to an HTML string.
495
+ */
496
+ serializeDocument(doc, options) {
497
+ return this[impl].serializeFragment(doc.content, options);
498
+ }
499
+ /**
500
+ * Converts an RteFragment to an HTML string.
501
+ */
502
+ serializeFragment(fragment, options) {
503
+ return this[impl].serializeFragment(fragment, options);
497
504
  }
498
- const processSlot = () => {
499
- const first = slot.assignedElements({ flatten: true })[0];
500
- if (first === currentEl) return;
501
- cleanup();
502
- if (first) {
503
- currentEl = first;
504
- applyPropsAndEvents(first);
505
- }
506
- };
507
- slot.addEventListener("slotchange", processSlot);
508
- queueMicrotask(processSlot);
509
- return slot;
510
505
  };
511
- //#endregion
512
- //#region src/lib/rich-text-editor/rte/features/internal/history.ts
513
- var RteHistoryFeatureImpl = class extends RteFeatureImpl {
514
- constructor(..._args) {
515
- super(..._args);
516
- this.name = "RteHistoryFeature";
506
+ var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
507
+ constructor(config, options) {
508
+ this.config = config;
509
+ const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
510
+ Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
511
+ Object.assign(serializers.marks, options?.serializers?.marks ?? {});
512
+ this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
517
513
  }
518
- getPlugins() {
519
- return [this.contribution(history()), this.contribution(keymap({
520
- "Mod-z": undo,
521
- "Ctrl-y": redo,
522
- "Cmd-Z": redo
523
- }))];
524
- }
525
- getToolbarItems() {
526
- return [this.contribution({
527
- section: "history",
528
- render: (ctx) => createButton(ctx, {
529
- label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
530
- icon: "undo-line",
531
- disabled: (ctx) => !undo(ctx.view.state),
532
- onClick: () => {
533
- const { state, dispatch } = ctx.view;
534
- undo(state, dispatch);
535
- }
536
- })
537
- }, 1), this.contribution({
538
- section: "history",
539
- render: (ctx) => createButton(ctx, {
540
- label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
541
- icon: "redo-line",
542
- disabled: (ctx) => !redo(ctx.view.state),
543
- onClick: () => {
544
- const { state, dispatch } = ctx.view;
545
- redo(state, dispatch);
546
- }
547
- })
548
- }, 2)];
549
- }
550
- };
551
- //#endregion
552
- //#region src/lib/rich-text-editor/rte/features/internal/foreign-html.ts
553
- var RteForeignHtmlFeatureImpl = class extends RteFeatureImpl {
554
- constructor(..._args) {
555
- super(..._args);
556
- this.name = "RteForeignHtmlFeature";
557
- }
558
- getPlugins(rte) {
559
- return [this.contribution(new Plugin({ props: {
560
- transformPastedHTML: (html) => rte.foreignHtmlParser[impl].transform(html),
561
- clipboardParser: rte.foreignHtmlParser[impl].parser,
562
- clipboardSerializer: rte.foreignHtmlSerializer[impl].serializer
563
- } }))];
564
- }
565
- };
566
- //#endregion
567
- //#region src/lib/rich-text-editor/rte/features/internal/cursor-fix.ts
568
- /**
569
- * When the cursor is positioned after an inline atom node at the end of a line,
570
- * browsers may display the cursor inside the atom.
571
- */
572
- var atomCursorFix = ($cursor) => {
573
- if (!($cursor.parentOffset === $cursor.parent.content.size)) return null;
574
- const nodeBefore = $cursor.nodeBefore;
575
- if (nodeBefore && nodeBefore.isInline && nodeBefore.isAtom && !nodeBefore.isText) return {};
576
- return null;
577
- };
578
- var RteCursorFixFeatureImpl = class extends RteFeatureImpl {
579
- constructor(..._args) {
580
- super(..._args);
581
- this.name = "RteCursorFix";
582
- }
583
- getPlugins(rte) {
584
- const cursorFixes = [atomCursorFix];
585
- for (const markType of Object.values(rte.schema.marks)) {
586
- const spec = markType.spec;
587
- if (spec.cursorFix) cursorFixes.push(spec.cursorFix);
514
+ static domSerializersFromSchema(schema) {
515
+ const result = {
516
+ nodes: {},
517
+ marks: {}
518
+ };
519
+ for (const name in schema.marks) {
520
+ const toDOM = schema.marks[name].spec.toDOM;
521
+ if (toDOM) result.marks[name] = toDOM;
588
522
  }
589
- return [this.contribution(new Plugin({ props: { decorations: (state) => {
590
- const { $cursor } = state.selection;
591
- if (!$cursor) return null;
592
- let cursorFix = null;
593
- for (const fn of cursorFixes) {
594
- const result = fn($cursor, state);
595
- if (result) Object.assign(cursorFix ??= {}, result);
596
- }
597
- if (!cursorFix) return null;
598
- return DecorationSet.create(state.doc, [Decoration.widget($cursor.pos, () => {
599
- const span = document.createElement("span");
600
- span.textContent = "​";
601
- for (const [prop, value] of Object.entries(cursorFix)) span.style.setProperty(prop, value);
602
- return span;
603
- }, { side: -1 })]);
604
- } } }))];
605
- }
606
- };
607
- featureFacade(RteCursorFixFeatureImpl);
608
- //#endregion
609
- //#region src/lib/rich-text-editor/rte/features/internal/core.ts
610
- /**
611
- * Plugin to bring state from the host web component into ProseMirror.
612
- */
613
- var hostBridgePlugin = new Plugin({ state: {
614
- init() {
615
- return null;
616
- },
617
- apply(tr, value) {
618
- const meta = tr.getMeta(hostBridgePlugin);
619
- if (meta) return meta;
620
- else return value;
621
- }
622
- } });
623
- var RteCoreImpl = class extends RteFeatureImpl {
624
- constructor(..._args) {
625
- super(..._args);
626
- this.name = "RteCore";
627
- this.disabled = new FeatureState(false);
628
- }
629
- getStyles() {
630
- return [
631
- this.contribution(prosemirror_default),
632
- this.contribution(core_style_default),
633
- this.contribution(ui_style_default)
634
- ];
635
- }
636
- getPlugins(rte) {
637
- const enterKeyChainCommands = chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlockAs((node, atEnd, $from) => {
638
- if (!atEnd) return {
639
- type: node.type,
640
- attrs: node.attrs
641
- };
642
- return {
643
- type: defaultTextblockForMatch($from.node($from.depth - 1).contentMatchAt($from.indexAfter($from.depth - 1))),
644
- attrs: rte.textblockAttrs.extractFromNode(node)
645
- };
646
- }));
647
- return [
648
- this.contribution(this.disabled.plugin),
649
- this.contribution(new Plugin({
650
- props: {
651
- editable: () => !this.disabled.getValue(rte),
652
- handleDOMEvents: { click: (_view, event) => {
653
- if (this.disabled.getValue(rte)) {
654
- event.preventDefault();
655
- return true;
656
- }
657
- return false;
658
- } }
659
- },
660
- view: (view) => {
661
- const popovers = view.dom.getRootNode().querySelector(".popovers");
662
- const updateDisabled = () => {
663
- const disabled = this.disabled.getValue(rte);
664
- popovers.classList.toggle("popovers--disabled", disabled);
665
- view.dom.parentElement.classList.toggle("editor--disabled", disabled);
666
- };
667
- updateDisabled();
668
- return { update: () => {
669
- updateDisabled();
670
- } };
671
- }
672
- })),
673
- this.contribution(keymap({
674
- ...baseKeymap,
675
- Enter: enterKeyChainCommands,
676
- "Shift-Enter": enterKeyChainCommands
677
- })),
678
- this.contribution(dropCursor()),
679
- this.contribution(gapCursor()),
680
- this.contribution(hostBridgePlugin)
681
- ];
523
+ for (const name in schema.nodes) {
524
+ const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
525
+ if (toDOM) result.nodes[name] = toDOM;
526
+ }
527
+ result.nodes.text = (node) => document.createTextNode(node.text);
528
+ return result;
682
529
  }
683
- getFeatures() {
684
- return [
685
- this,
686
- new RteHistoryFeatureImpl(),
687
- new RteForeignHtmlFeatureImpl(),
688
- new RteCursorFixFeatureImpl()
689
- ];
530
+ serializeFragment(fragment, options) {
531
+ const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
532
+ const serializedFragment = this.serializer.serializeFragment(parsedFragment);
533
+ const container = document.createDocumentFragment();
534
+ container.appendChild(serializedFragment);
535
+ options?.modifyDom?.(container);
536
+ const output = document.createElement("div");
537
+ output.appendChild(container);
538
+ return output.innerHTML;
690
539
  }
691
540
  };
692
- featureFacade(RteCoreImpl);
693
541
  //#endregion
694
- //#region src/lib/rich-text-editor/rte/utils/sanitization.ts
695
- var DEFAULT_ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
696
- var ALLOWED_URI_REGEXP_WITH_BLOB = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
697
- var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
698
- var domPurifyConfig = { ALLOWED_URI_REGEXP: ALLOWED_URI_REGEXP_WITH_BLOB };
699
- /**
700
- * Sanitize potentially dangerous URLs, like "javascript:", in anchor href attributes.
701
- * Returns empty string if the URL is unsafe.
702
- */
703
- var sanitizeLinkHref = (url) => {
704
- if (!DEFAULT_ALLOWED_URI_REGEXP.test(url.replace(ATTR_WHITESPACE, ""))) return "";
705
- const anchor = document.createElement("a");
706
- anchor.setAttribute("href", url);
707
- /* v8 ignore next -- since href is already validated it's probably always present @preserve */
708
- return DOMPurify.sanitize(anchor, {
709
- RETURN_DOM: true,
710
- ...domPurifyConfig
711
- }).querySelector("a").getAttribute("href") ?? "";
712
- };
713
- /**
714
- * Sanitize potentially dangerous URLs, like "javascript:", in image src attributes.
715
- * Returns empty string if the URL is unsafe.
716
- */
717
- var sanitizeImageSrc = (url) => {
718
- const img = document.createElement("img");
719
- img.setAttribute("src", url);
720
- return DOMPurify.sanitize(img, {
721
- RETURN_DOM: true,
722
- ...domPurifyConfig
723
- }).querySelector("img").getAttribute("src") ?? "";
542
+ //#region src/lib/rich-text-editor/rte/instance.ts
543
+ var parseDocument = (schema, doc) => {
544
+ const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
545
+ if (!node) throw new Error("Document could not be parsed");
546
+ node.check();
547
+ return node;
724
548
  };
725
- /**
726
- * Escapes a CSS value for insertion into style attribute.
727
- * E.g. "15px; background: red" => "15px"
728
- */
729
- var escapeCssProperty = (value) => value.replace(/[;!].*/, "");
730
- //#endregion
731
- //#region src/lib/rich-text-editor/rte/html-parser.ts
732
- var copy = (obj) => ({ ...obj });
733
- var parseRulesFromSchema = (schema) => ({
734
- nodes: Object.fromEntries(Object.keys(schema.nodes).map((name) => [name, (schema.nodes[name].spec.parseDOM ?? []).map(copy)])),
735
- marks: Object.fromEntries(Object.keys(schema.marks).map((name) => [name, (schema.marks[name].spec.parseDOM ?? []).map(copy)]))
736
- });
737
- var RteHtmlParser = class {
549
+ var RteInstance = class {
738
550
  constructor(config, options) {
739
- this[impl] = new RteHtmlParserImpl(config[impl], options);
551
+ this.options = options;
552
+ this.feature = (Feature, featureId) => {
553
+ return this[impl].getPublicInterface(Feature, featureId);
554
+ };
555
+ this[impl] = new RteInstanceImpl(this, config, options);
740
556
  }
741
557
  /**
742
- * Converts an HTML string to an RteDocument.
558
+ * Returns the current document state.
743
559
  */
744
- parseDocument(html, options) {
745
- return this[impl].parser.parse(this.parseHtml(html, options), { preserveWhitespace: true }).toJSON();
560
+ getDocument() {
561
+ return this[impl].state.doc.toJSON();
746
562
  }
747
563
  /**
748
- * Converts an HTML string to an RteFragment.
564
+ * Reset the editor to its initial state. Optionally, an initial document can be provided.
749
565
  */
750
- parseFragment(html, options) {
751
- return this[impl].parser.parseSlice(this.parseHtml(html, options), { preserveWhitespace: true }).content.toJSON() ?? [];
752
- }
753
- parseHtml(html, options) {
754
- const dom = DOMPurify.sanitize(html, {
755
- RETURN_DOM: true,
756
- ...domPurifyConfig
757
- });
758
- const container = document.createDocumentFragment();
759
- container.appendChild(dom);
760
- options?.modifyDom?.(container);
761
- return container;
762
- }
763
- };
764
- var RteHtmlParserImpl = class {
765
- constructor(config, options) {
766
- const rules = parseRulesFromSchema(config.schema);
767
- options?.modifyParseRules?.(rules);
768
- this.parser = buildDomParser(config.schema, rules);
769
- }
770
- transform(html) {
771
- return DOMPurify.sanitize(html);
772
- }
773
- };
774
- var buildDomParser = (schema, { marks, nodes }) => {
775
- const parserRules = [];
776
- const priority = (rule) => rule.priority ?? 50;
777
- function insert(rule) {
778
- let i = 0;
779
- for (; i < parserRules.length; i++) {
780
- const next = parserRules[i];
781
- if (priority(next) < priority(rule)) break;
782
- }
783
- parserRules.splice(i, 0, rule);
784
- }
785
- for (const name in marks) marks[name]?.forEach((rule) => {
786
- insert(rule = copy(rule));
787
- if (!(rule.mark || rule.ignore || rule.clearMark)) rule.mark = name;
788
- });
789
- for (const name in nodes) nodes[name]?.forEach((rule) => {
790
- insert(rule = copy(rule));
791
- if (!(rule.node || rule.ignore || rule.mark)) rule.node = name;
792
- });
793
- parserRules.push({
794
- tag: "br",
795
- closeParent: true
796
- });
797
- return new DOMParser(schema, parserRules);
798
- };
799
- //#endregion
800
- //#region src/lib/rich-text-editor/rte/html-serializer.ts
801
- var RteHtmlSerializer = class {
802
- constructor(config, options) {
803
- this[impl] = new RteHtmlSerializerImpl(config[impl], options);
804
- }
805
- /**
806
- * Converts an RteDocument to an HTML string.
807
- */
808
- serializeDocument(doc, options) {
809
- return this[impl].serializeFragment(doc.content, options);
810
- }
811
- /**
812
- * Converts an RteFragment to an HTML string.
813
- */
814
- serializeFragment(fragment, options) {
815
- return this[impl].serializeFragment(fragment, options);
816
- }
817
- };
818
- var RteHtmlSerializerImpl = class RteHtmlSerializerImpl {
819
- constructor(config, options) {
820
- this.config = config;
821
- const serializers = RteHtmlSerializerImpl.domSerializersFromSchema(config.schema);
822
- Object.assign(serializers.nodes, options?.serializers?.nodes ?? {});
823
- Object.assign(serializers.marks, options?.serializers?.marks ?? {});
824
- this.serializer = new DOMSerializer(serializers.nodes, serializers.marks);
825
- }
826
- static domSerializersFromSchema(schema) {
827
- const result = {
828
- nodes: {},
829
- marks: {}
830
- };
831
- for (const name in schema.marks) {
832
- const toDOM = schema.marks[name].spec.toDOM;
833
- if (toDOM) result.marks[name] = toDOM;
834
- }
835
- for (const name in schema.nodes) {
836
- const toDOM = schema.nodes[name].spec.serializeToDOM ?? schema.nodes[name].spec.toDOM;
837
- if (toDOM) result.nodes[name] = toDOM;
838
- }
839
- result.nodes.text = (node) => document.createTextNode(node.text);
840
- return result;
841
- }
842
- serializeFragment(fragment, options) {
843
- const parsedFragment = Fragment.fromJSON(this.config.schema, fragment);
844
- const serializedFragment = this.serializer.serializeFragment(parsedFragment);
845
- const container = document.createDocumentFragment();
846
- container.appendChild(serializedFragment);
847
- options?.modifyDom?.(container);
848
- const output = document.createElement("div");
849
- output.appendChild(container);
850
- return output.innerHTML;
851
- }
852
- };
853
- //#endregion
854
- //#region src/lib/rich-text-editor/rte/instance.ts
855
- var parseDocument = (schema, doc) => {
856
- const node = schema.topNodeType.createAndFill(null, doc ? Fragment.fromJSON(schema, doc.content) : null);
857
- if (!node) throw new Error("Document could not be parsed");
858
- node.check();
859
- return node;
860
- };
861
- var RteInstance = class {
862
- constructor(config, options) {
863
- this.options = options;
864
- this.feature = (Feature, featureId) => {
865
- return this[impl].getPublicInterface(Feature, featureId);
866
- };
867
- this[impl] = new RteInstanceImpl(this, config, options);
868
- }
869
- /**
870
- * Returns the current document state.
871
- */
872
- getDocument() {
873
- return this[impl].state.doc.toJSON();
874
- }
875
- /**
876
- * Reset the editor to its initial state. Optionally, an initial document can be provided.
877
- */
878
- reset(initialDocument) {
879
- this[impl].reset(initialDocument);
566
+ reset(initialDocument) {
567
+ this[impl].reset(initialDocument);
880
568
  }
881
569
  /**
882
570
  * Replaces the current selection with the given content.
@@ -991,171 +679,487 @@ var RteInstanceImpl = class {
991
679
  this.dispatchTransaction(this.tr.setMeta(hostBridgePlugin, hostState));
992
680
  }
993
681
  };
994
- //#endregion
995
- //#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
996
- var TextblockAttrs = class {
997
- constructor(specs) {
998
- this.specs = specs;
999
- this.attrs = {};
1000
- for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
1001
- }
1002
- fromDOM(dom) {
1003
- return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
1004
- }
1005
- getStyle(node) {
1006
- return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
1007
- }
1008
- getDOMAttrsProperties(node) {
1009
- if (!this.specs.length) return {};
1010
- return { style: this.getStyle(node) };
1011
- }
1012
- getDOMAttrs(node) {
1013
- if (!this.specs.length) return [];
1014
- return [this.getDOMAttrsProperties(node)];
1015
- }
1016
- extractFromNode(node) {
1017
- return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
1018
- }
682
+ //#endregion
683
+ //#region src/lib/rich-text-editor/rte/utils/textblock-attrs.ts
684
+ var TextblockAttrs = class {
685
+ constructor(specs) {
686
+ this.specs = specs;
687
+ this.attrs = {};
688
+ for (const spec of specs) this.attrs[spec.name] = { default: spec.default };
689
+ }
690
+ fromDOM(dom) {
691
+ return Object.assign({}, ...this.specs.map((s) => ({ [s.name]: s.fromDOM(dom) })));
692
+ }
693
+ getStyle(node) {
694
+ return this.specs.flatMap((s) => s.toStyles(node)).join("; ");
695
+ }
696
+ getDOMAttrsProperties(node) {
697
+ if (!this.specs.length) return {};
698
+ return { style: this.getStyle(node) };
699
+ }
700
+ getDOMAttrs(node) {
701
+ if (!this.specs.length) return [];
702
+ return [this.getDOMAttrsProperties(node)];
703
+ }
704
+ extractFromNode(node) {
705
+ return Object.assign({}, ...Object.keys(this.attrs).map((attr) => ({ [attr]: node.attrs[attr] })));
706
+ }
707
+ };
708
+ //#endregion
709
+ //#region src/lib/rich-text-editor/rte/utils/textblock-marks.ts
710
+ var TextblockMarks = class {
711
+ constructor(specs) {
712
+ this.specs = specs;
713
+ }
714
+ getAllowedMarksForNode(nodeName) {
715
+ return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
716
+ }
717
+ getReferencedNodeNames() {
718
+ return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
719
+ }
720
+ };
721
+ //#endregion
722
+ //#region src/lib/rich-text-editor/rte/view.ts
723
+ /**
724
+ * Converts an RteDocument to an RteView tree for rendering outside the editor.
725
+ * The logic is adapted from ProseMirror's DOM serialization to ensure the resulting structure
726
+ * matches the editor's HTML structure.
727
+ */
728
+ var convertToView = (doc, ctx) => {
729
+ const schema = ctx.config[impl].schema;
730
+ const getMarkRank = (mark) => schema.marks[mark.type].rank;
731
+ const marksEqual = (a, b) => {
732
+ if (a.type !== b.type) return false;
733
+ const attrsA = a.attrs;
734
+ const attrsB = b.attrs;
735
+ if (attrsA === attrsB) return true;
736
+ /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
737
+ if (!attrsA || !attrsB) return false;
738
+ const keysA = Object.keys(attrsA);
739
+ const keysB = Object.keys(attrsB);
740
+ /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
741
+ if (keysA.length !== keysB.length) return false;
742
+ for (const key of keysA) if (attrsA[key] !== attrsB[key]) return false;
743
+ return true;
744
+ };
745
+ const convertFragment = (nodes) => {
746
+ const result = [];
747
+ const active = [];
748
+ const activeContents = [result];
749
+ for (const node of nodes) {
750
+ const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
751
+ let keep = 0;
752
+ let rendered = 0;
753
+ while (keep < active.length && rendered < nodeMarks.length) {
754
+ if (!marksEqual(active[keep], nodeMarks[rendered])) break;
755
+ keep++;
756
+ rendered++;
757
+ }
758
+ while (active.length > keep) {
759
+ active.pop();
760
+ activeContents.pop();
761
+ }
762
+ while (rendered < nodeMarks.length) {
763
+ const mark = nodeMarks[rendered];
764
+ const markContent = [];
765
+ activeContents[activeContents.length - 1].push({
766
+ type: "mark",
767
+ mark,
768
+ children: {
769
+ type: "fragment",
770
+ content: markContent,
771
+ [impl]: ctx
772
+ },
773
+ [impl]: ctx
774
+ });
775
+ active.push(mark);
776
+ activeContents.push(markContent);
777
+ rendered++;
778
+ }
779
+ activeContents[activeContents.length - 1].push({
780
+ type: "node",
781
+ node,
782
+ children: convertFragment(node.content ?? []),
783
+ [impl]: ctx
784
+ });
785
+ }
786
+ return {
787
+ type: "fragment",
788
+ content: result,
789
+ [impl]: ctx
790
+ };
791
+ };
792
+ return {
793
+ type: "node",
794
+ node: doc,
795
+ children: convertFragment(doc.content),
796
+ [impl]: ctx
797
+ };
798
+ };
799
+ //#endregion
800
+ //#region src/lib/rich-text-editor/rte/config.ts
801
+ var RteConfig = class {
802
+ constructor(features) {
803
+ this[impl] = new RteConfigImpl(features);
804
+ }
805
+ instantiateEditor(options) {
806
+ return new RteInstance(this, options);
807
+ }
808
+ instantiateView(document, options) {
809
+ return convertToView(document, {
810
+ config: this,
811
+ options: { ...options }
812
+ });
813
+ }
814
+ };
815
+ var RteConfigImpl = class {
816
+ constructor(featuresFacades) {
817
+ const resolveFeatures = (features) => features.flatMap((f) => f.getFeatures().flatMap((subFeature) => subFeature === f ? f : resolveFeatures([subFeature])));
818
+ this.features = resolveFeatures(featuresFacades.map(getFeatureImpl));
819
+ this.featureMap = /* @__PURE__ */ new Map();
820
+ for (const f of this.features) {
821
+ if (this.featureMap.has(f.name)) throw new Error(`Duplicate feature: ${f.name}`);
822
+ this.featureMap.set(f.name, f);
823
+ }
824
+ this.featureFacadesMap = /* @__PURE__ */ new Map();
825
+ for (const facade of featuresFacades) {
826
+ const FacadeClass = facade.constructor;
827
+ const feature = getFeatureImpl(facade);
828
+ const instances = this.featureFacadesMap.get(FacadeClass);
829
+ if (instances) instances.push(feature);
830
+ else this.featureFacadesMap.set(FacadeClass, [feature]);
831
+ }
832
+ if (!this.featureMap.has("RteBase")) throw new Error("RteBase feature is required");
833
+ if (this.featureMap.has("RteLinkFeature") && !this.featureMap.has("RteToolbarFeature")) throw new Error("RteToolbarFeature is required for RteLinkFeature");
834
+ this.textblockAttrs = new TextblockAttrs(sortedContributions(this.features.flatMap((f) => f.getTextblockAttrs())));
835
+ this.textblockMarks = new TextblockMarks(sortedContributions(this.features.flatMap((f) => f.getTextblockMarks())));
836
+ const schemaContributions = sortedContributions(this.features.flatMap((f) => f.getSchema(this.textblockAttrs, this.textblockMarks)));
837
+ const schemaSpec = {
838
+ nodes: {},
839
+ marks: {}
840
+ };
841
+ for (const schema of schemaContributions) {
842
+ Object.assign(schemaSpec.nodes, schema.nodes ?? {});
843
+ Object.assign(schemaSpec.marks, schema.marks ?? {});
844
+ }
845
+ for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
846
+ this.schema = new Schema(schemaSpec);
847
+ }
848
+ };
849
+ //#endregion
850
+ //#region src/lib/rich-text-editor/rte/utils/ui.ts
851
+ var isPropBinding = (prop) => typeof prop === "function";
852
+ var on = (event, prop, handler) => [
853
+ event,
854
+ prop,
855
+ handler
856
+ ];
857
+ var UiCtx = class {
858
+ constructor(view, rte, props) {
859
+ this.view = view;
860
+ this.rte = rte;
861
+ this.props = props;
862
+ this.bindings = [];
863
+ }
864
+ evalProp(prop) {
865
+ return isPropBinding(prop) ? prop(this) : prop;
866
+ }
867
+ bindProp(prop, bindFn) {
868
+ if (prop === void 0) return;
869
+ else if (isPropBinding(prop)) {
870
+ const binding = () => bindFn(prop(this));
871
+ this.bindings.push(binding);
872
+ binding();
873
+ } else bindFn(prop);
874
+ }
875
+ updateBindings() {
876
+ for (const binding of this.bindings) binding();
877
+ }
878
+ shouldReturnFocusToEditor() {
879
+ return this.evalProp(this.props.shouldReturnFocusToEditor);
880
+ }
881
+ bindToEl(target, props = {}, events = [], children = []) {
882
+ for (const name in props) this.bindProp(props[name], (value) => {
883
+ target[name] = value;
884
+ });
885
+ for (const [name, value, bindFn] of events) if (value) target.addEventListener(name, bindFn(value));
886
+ for (const child of children) target.appendChild(child);
887
+ }
888
+ };
889
+ var createDiv = (ctx, props) => {
890
+ const div = document.createElement("div");
891
+ ctx.bindToEl(div, {
892
+ className: props.className,
893
+ slot: props.slot
894
+ }, [], props.children);
895
+ return div;
896
+ };
897
+ var createAnchor = (ctx, props) => {
898
+ const anchor = document.createElement("a");
899
+ ctx.bindToEl(anchor, {
900
+ href: props.href,
901
+ target: props.target,
902
+ rel: props.rel,
903
+ className: props.className
904
+ }, [], props.children);
905
+ return anchor;
906
+ };
907
+ var wrapperTargets = /* @__PURE__ */ new WeakMap();
908
+ var createOptionalTooltip = (ctx, props) => {
909
+ const tooltip = ctx.rte.createComponent(Tooltip);
910
+ tooltip.setAttribute("exportparts", "vvd-theme-alternate");
911
+ ctx.bindToEl(tooltip, {
912
+ className: "ui-tooltip",
913
+ text: props.label,
914
+ placement: ctx.props.popupPlacement
915
+ });
916
+ ctx.bindProp(props.enabled, (enabled) => {
917
+ if (!enabled) tooltip.open = false;
918
+ tooltip.anchor = enabled ? props.anchor : void 0;
919
+ });
920
+ const wrapper = createDiv(ctx, {
921
+ className: "ui-tooltip-wrapper",
922
+ slot: props.slot,
923
+ children: [props.anchor, tooltip]
924
+ });
925
+ wrapperTargets.set(wrapper, props.anchor);
926
+ return wrapper;
927
+ };
928
+ var createButton = (ctx, props) => {
929
+ const variant = () => ctx.evalProp(props.variant) ?? "toolbar";
930
+ const size = () => variant() === "toolbar" ? "super-condensed" : "condensed";
931
+ const appearanceInactive = () => variant() === "popover-primary" ? "outlined" : "ghost-light";
932
+ const appearanceActive = () => variant() === "toolbar-menu" || variant() === "popover" ? "filled" : appearanceInactive();
933
+ const appearance = () => ctx.evalProp(props.active) ? appearanceActive() : appearanceInactive();
934
+ const connotation = () => ctx.evalProp(props.connotation) ?? (variant() === "toolbar-menu" && ctx.evalProp(props.active) ? "cta" : void 0);
935
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
936
+ const button = ctx.rte.createComponent(Button);
937
+ ctx.bindToEl(button, {
938
+ size,
939
+ appearance,
940
+ disabled,
941
+ icon: props.icon,
942
+ autofocus: props.autofocus,
943
+ connotation,
944
+ [props.icon ? "ariaLabel" : "label"]: props.label,
945
+ pressed: () => Boolean(ctx.evalProp(props.active)),
946
+ ariaPressed: props.active !== void 0 ? () => ctx.evalProp(props.active) ? "true" : "false" : void 0
947
+ }, [on("click", props.onClick, (onClick) => () => {
948
+ if (!onClick() && ctx.shouldReturnFocusToEditor()) ctx.view.focus();
949
+ })]);
950
+ return createOptionalTooltip(ctx, {
951
+ enabled: () => Boolean(ctx.evalProp(props.icon) && !ctx.evalProp(props.noTooltip) && !disabled()),
952
+ label: props.label,
953
+ anchor: button,
954
+ slot: props.slot
955
+ });
956
+ };
957
+ var createMenu = (ctx, props) => {
958
+ const menu = ctx.rte.createComponent(Menu);
959
+ ctx.bindToEl(menu, {
960
+ className: "ui-menu",
961
+ autoDismiss: true,
962
+ ariaLabel: props.label,
963
+ anchor: wrapperTargets.get(props.trigger) ?? props.trigger,
964
+ placement: ctx.props.popupPlacement,
965
+ offset: () => ctx.evalProp(ctx.props.menuOffset) ?? null
966
+ }, [], props.children);
967
+ const fragment = document.createDocumentFragment();
968
+ fragment.appendChild(props.trigger);
969
+ fragment.appendChild(menu);
970
+ return fragment;
971
+ };
972
+ var createMenuItem = (ctx, props) => {
973
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
974
+ const item = ctx.rte.createComponent(MenuItem);
975
+ ctx.bindToEl(item, {
976
+ text: props.text,
977
+ checked: props.checked,
978
+ disabled,
979
+ controlType: "radio",
980
+ checkedAppearance: "tick-only"
981
+ }, [on("change", props.onSelect, (onSelect) => () => {
982
+ if (item.checked && !item.disabled) {
983
+ if (ctx.evalProp(props.checked) !== item.checked) {
984
+ onSelect();
985
+ if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
986
+ }
987
+ }
988
+ })]);
989
+ return item;
990
+ };
991
+ var createButtonGroup = (ctx, props) => createDiv(ctx, {
992
+ slot: props.slot,
993
+ className: "ui-button-group",
994
+ children: props.children
995
+ });
996
+ function markActive(state, type) {
997
+ const { from, $from, to, empty } = state.selection;
998
+ if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
999
+ else return state.doc.rangeHasMark(from, to, type);
1000
+ }
1001
+ var createMarkToggle = (ctx, props) => createButton(ctx, {
1002
+ label: props.label,
1003
+ icon: props.icon,
1004
+ active: () => markActive(ctx.view.state, props.markType),
1005
+ disabled: () => !toggleMark(props.markType)(ctx.view.state),
1006
+ onClick: () => {
1007
+ toggleMark(props.markType)(ctx.view.state, ctx.view.dispatch);
1008
+ }
1009
+ });
1010
+ var createOption = (ctx, props) => {
1011
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled) || ctx.evalProp(props.disabled));
1012
+ const option = ctx.rte.createComponent(ListboxOption);
1013
+ ctx.bindToEl(option, {
1014
+ value: props.value,
1015
+ text: props.text,
1016
+ disabled
1017
+ });
1018
+ return option;
1019
+ };
1020
+ var createSelect = (ctx, props) => {
1021
+ const disabled = () => Boolean(ctx.evalProp(ctx.props.disabled));
1022
+ const select = ctx.rte.createComponent(Select);
1023
+ select.setAttribute("data-class", "ui-select");
1024
+ ctx.bindToEl(select, {
1025
+ placeholder: " ",
1026
+ ariaLabel: props.label,
1027
+ appearance: "ghost",
1028
+ disabled
1029
+ }, [on("change", props.onSelect, (onSelect) => () => {
1030
+ const value = select.value;
1031
+ if (value) {
1032
+ onSelect(value);
1033
+ if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
1034
+ }
1035
+ })], props.children);
1036
+ queueMicrotask(() => {
1037
+ ctx.bindProp(props.value, (value) => select.value = value);
1038
+ });
1039
+ let hideTooltip = false;
1040
+ select.addEventListener("vwc-popup:open", () => {
1041
+ hideTooltip = true;
1042
+ ctx.updateBindings();
1043
+ });
1044
+ select.addEventListener("vwc-popup:close", () => {
1045
+ hideTooltip = false;
1046
+ ctx.updateBindings();
1047
+ });
1048
+ return createOptionalTooltip(ctx, {
1049
+ enabled: () => !hideTooltip && !disabled(),
1050
+ label: props.label,
1051
+ anchor: select
1052
+ });
1053
+ };
1054
+ var createDivider = (ctx) => {
1055
+ const divider = ctx.rte.createComponent(Divider);
1056
+ ctx.bindToEl(divider, {
1057
+ className: "ui-divider",
1058
+ orientation: "vertical"
1059
+ });
1060
+ return divider;
1061
+ };
1062
+ var createTextField = (ctx, props) => {
1063
+ const textField = ctx.rte.createComponent(TextField);
1064
+ ctx.bindToEl(textField, {
1065
+ label: props.label,
1066
+ value: props.value,
1067
+ placeholder: props.placeholder,
1068
+ slot: props.slot,
1069
+ autofocus: props.autofocus,
1070
+ type: props.type,
1071
+ helperText: props.helperText
1072
+ }, [on("input", props.onInput, (onInput) => () => {
1073
+ onInput(textField.value);
1074
+ })]);
1075
+ return textField;
1019
1076
  };
1020
- //#endregion
1021
- //#region src/lib/rich-text-editor/rte/utils/textblock-marks.ts
1022
- var TextblockMarks = class {
1023
- constructor(specs) {
1024
- this.specs = specs;
1025
- }
1026
- getAllowedMarksForNode(nodeName) {
1027
- return this.specs.filter((spec) => !spec.onNodeName || spec.onNodeName === nodeName).map((spec) => spec.markName);
1028
- }
1029
- getReferencedNodeNames() {
1030
- return new Set(this.specs.map((spec) => spec.onNodeName).filter(Boolean));
1031
- }
1077
+ var createText = (ctx, props) => {
1078
+ const textNode = document.createTextNode("");
1079
+ ctx.bindToEl(textNode, { textContent: props.text });
1080
+ return textNode;
1032
1081
  };
1033
- //#endregion
1034
- //#region src/lib/rich-text-editor/rte/view.ts
1035
- /**
1036
- * Converts an RteDocument to an RteView tree for rendering outside the editor.
1037
- * The logic is adapted from ProseMirror's DOM serialization to ensure the resulting structure
1038
- * matches the editor's HTML structure.
1039
- */
1040
- var convertToView = (doc, ctx) => {
1041
- const schema = ctx.config[impl].schema;
1042
- const getMarkRank = (mark) => schema.marks[mark.type].rank;
1043
- const marksEqual = (a, b) => {
1044
- if (a.type !== b.type) return false;
1045
- const attrsA = a.attrs;
1046
- const attrsB = b.attrs;
1047
- if (attrsA === attrsB) return true;
1048
- /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
1049
- if (!attrsA || !attrsB) return false;
1050
- const keysA = Object.keys(attrsA);
1051
- const keysB = Object.keys(attrsB);
1052
- /* v8 ignore next -- not currently reachable since all attrs are required @preserve */
1053
- if (keysA.length !== keysB.length) return false;
1054
- for (const key of keysA) if (attrsA[key] !== attrsB[key]) return false;
1055
- return true;
1056
- };
1057
- const convertFragment = (nodes) => {
1058
- const result = [];
1059
- const active = [];
1060
- const activeContents = [result];
1061
- for (const node of nodes) {
1062
- const nodeMarks = [...node.marks ?? []].sort((a, b) => getMarkRank(a) - getMarkRank(b));
1063
- let keep = 0;
1064
- let rendered = 0;
1065
- while (keep < active.length && rendered < nodeMarks.length) {
1066
- if (!marksEqual(active[keep], nodeMarks[rendered])) break;
1067
- keep++;
1068
- rendered++;
1069
- }
1070
- while (active.length > keep) {
1071
- active.pop();
1072
- activeContents.pop();
1073
- }
1074
- while (rendered < nodeMarks.length) {
1075
- const mark = nodeMarks[rendered];
1076
- const markContent = [];
1077
- activeContents[activeContents.length - 1].push({
1078
- type: "mark",
1079
- mark,
1080
- children: {
1081
- type: "fragment",
1082
- content: markContent,
1083
- [impl]: ctx
1084
- },
1085
- [impl]: ctx
1086
- });
1087
- active.push(mark);
1088
- activeContents.push(markContent);
1089
- rendered++;
1090
- }
1091
- activeContents[activeContents.length - 1].push({
1092
- type: "node",
1093
- node,
1094
- children: convertFragment(node.content ?? []),
1095
- [impl]: ctx
1082
+ var createSingleSlot = (ctx, props) => {
1083
+ const slot = document.createElement("slot");
1084
+ slot.name = props.name;
1085
+ let currentEl = null;
1086
+ const listeners = [];
1087
+ function cleanup() {
1088
+ for (const { el, type, handler } of listeners) el.removeEventListener(type, handler);
1089
+ listeners.length = 0;
1090
+ currentEl = null;
1091
+ }
1092
+ for (const [key, prop] of Object.entries(props.assignedProps)) ctx.bindProp(prop, (value) => {
1093
+ if (currentEl) currentEl[key] = value;
1094
+ });
1095
+ function applyPropsAndEvents(el) {
1096
+ for (const [key, prop] of Object.entries(props.assignedProps)) el[key] = ctx.evalProp(prop);
1097
+ for (const [type, handler] of Object.entries(props.assignedEvents)) {
1098
+ const listener = (e) => {
1099
+ handler(e);
1100
+ if (ctx.shouldReturnFocusToEditor()) ctx.view.focus();
1101
+ };
1102
+ el.addEventListener(type, listener);
1103
+ listeners.push({
1104
+ el,
1105
+ type,
1106
+ handler: listener
1096
1107
  });
1097
1108
  }
1098
- return {
1099
- type: "fragment",
1100
- content: result,
1101
- [impl]: ctx
1102
- };
1103
- };
1104
- return {
1105
- type: "node",
1106
- node: doc,
1107
- children: convertFragment(doc.content),
1108
- [impl]: ctx
1109
+ }
1110
+ const processSlot = () => {
1111
+ const first = slot.assignedElements({ flatten: true })[0];
1112
+ if (first === currentEl) return;
1113
+ cleanup();
1114
+ if (first) {
1115
+ currentEl = first;
1116
+ applyPropsAndEvents(first);
1117
+ }
1109
1118
  };
1119
+ slot.addEventListener("slotchange", processSlot);
1120
+ queueMicrotask(processSlot);
1121
+ return slot;
1110
1122
  };
1111
1123
  //#endregion
1112
- //#region src/lib/rich-text-editor/rte/config.ts
1113
- var RteConfig = class {
1114
- constructor(features) {
1115
- this[impl] = new RteConfigImpl(features);
1116
- }
1117
- instantiateEditor(options) {
1118
- return new RteInstance(this, options);
1124
+ //#region src/lib/rich-text-editor/rte/features/internal/history.ts
1125
+ var RteHistoryFeatureImpl = class extends RteFeatureImpl {
1126
+ constructor(config) {
1127
+ super();
1128
+ this.config = config;
1129
+ this.name = "RteHistoryFeature";
1119
1130
  }
1120
- instantiateView(document, options) {
1121
- return convertToView(document, {
1122
- config: this,
1123
- options: { ...options }
1124
- });
1131
+ getPlugins() {
1132
+ return [this.contribution(history()), this.contribution(keymap({
1133
+ "Mod-z": undo,
1134
+ "Ctrl-y": redo,
1135
+ "Cmd-Z": redo
1136
+ }))];
1125
1137
  }
1126
- };
1127
- var RteConfigImpl = class {
1128
- constructor(featuresFacades) {
1129
- const resolveFeatures = (features) => features.flatMap((f) => f.getFeatures().flatMap((subFeature) => subFeature === f ? f : resolveFeatures([subFeature])));
1130
- this.features = resolveFeatures(featuresFacades.map(getFeatureImpl));
1131
- this.featureMap = /* @__PURE__ */ new Map();
1132
- for (const f of this.features) {
1133
- if (this.featureMap.has(f.name)) throw new Error(`Duplicate feature: ${f.name}`);
1134
- this.featureMap.set(f.name, f);
1135
- }
1136
- this.featureFacadesMap = /* @__PURE__ */ new Map();
1137
- for (const facade of featuresFacades) {
1138
- const FacadeClass = facade.constructor;
1139
- const feature = getFeatureImpl(facade);
1140
- const instances = this.featureFacadesMap.get(FacadeClass);
1141
- if (instances) instances.push(feature);
1142
- else this.featureFacadesMap.set(FacadeClass, [feature]);
1143
- }
1144
- if (!this.featureMap.has("RteBase")) throw new Error("RteBase feature is required");
1145
- if (this.featureMap.has("RteLinkFeature") && !this.featureMap.has("RteToolbarFeature")) throw new Error("RteToolbarFeature is required for RteLinkFeature");
1146
- this.textblockAttrs = new TextblockAttrs(sortedContributions(this.features.flatMap((f) => f.getTextblockAttrs())));
1147
- this.textblockMarks = new TextblockMarks(sortedContributions(this.features.flatMap((f) => f.getTextblockMarks())));
1148
- const schemaContributions = sortedContributions(this.features.flatMap((f) => f.getSchema(this.textblockAttrs, this.textblockMarks)));
1149
- const schemaSpec = {
1150
- nodes: {},
1151
- marks: {}
1152
- };
1153
- for (const schema of schemaContributions) {
1154
- Object.assign(schemaSpec.nodes, schema.nodes ?? {});
1155
- Object.assign(schemaSpec.marks, schema.marks ?? {});
1156
- }
1157
- for (const referencedNodeName of this.textblockMarks.getReferencedNodeNames()) if (!(referencedNodeName in schemaSpec.nodes)) throw new Error(`Unknown node "${referencedNodeName}"`);
1158
- this.schema = new Schema(schemaSpec);
1138
+ getToolbarItems() {
1139
+ if (!this.config.showToolbarButtons) return [];
1140
+ return [this.contribution({
1141
+ section: "history",
1142
+ render: (ctx) => createButton(ctx, {
1143
+ label: (ctx) => ctx.rte.getLocale().richTextEditor.undo,
1144
+ icon: "undo-line",
1145
+ disabled: (ctx) => !undo(ctx.view.state),
1146
+ onClick: () => {
1147
+ const { state, dispatch } = ctx.view;
1148
+ undo(state, dispatch);
1149
+ }
1150
+ })
1151
+ }, 1), this.contribution({
1152
+ section: "history",
1153
+ render: (ctx) => createButton(ctx, {
1154
+ label: (ctx) => ctx.rte.getLocale().richTextEditor.redo,
1155
+ icon: "redo-line",
1156
+ disabled: (ctx) => !redo(ctx.view.state),
1157
+ onClick: () => {
1158
+ const { state, dispatch } = ctx.view;
1159
+ redo(state, dispatch);
1160
+ }
1161
+ })
1162
+ }, 2)];
1159
1163
  }
1160
1164
  };
1161
1165
  //#endregion
@@ -1341,6 +1345,7 @@ var RteBaseImpl = class extends RteFeatureImpl {
1341
1345
  return [
1342
1346
  this,
1343
1347
  new RteCoreImpl(),
1348
+ new RteHistoryFeatureImpl({ showToolbarButtons: this.config?.historyToolbarButtons ?? true }),
1344
1349
  new RteBasicTextBlocksImpl({
1345
1350
  heading1: this.config?.heading1 ?? false,
1346
1351
  heading2: this.config?.heading2 ?? false,
@@ -1421,12 +1426,28 @@ var RteToolbarFeatureImpl = class extends RteFeatureImpl {
1421
1426
  for (const toolbarItem of sortedContributions(rte.features.flatMap((f) => f.getToolbarItems(rte)))) itemsBySection.get(toolbarItem.section).push(toolbarItem);
1422
1427
  const core = rte.getFeature("RteCore");
1423
1428
  return [this.contribution(this.hidden.plugin), this.contribution(new Plugin({ view: (view) => {
1429
+ let focusCameFromEditorWithMouse = false;
1424
1430
  const ctx = new UiCtx(view, rte, {
1425
1431
  popupPlacement: this.config?.popupDirection === "outward" ? "bottom" : "top",
1426
1432
  menuOffset: 8,
1427
- disabled: () => core.disabled.getValue(rte)
1433
+ disabled: () => core.disabled.getValue(rte),
1434
+ shouldReturnFocusToEditor: () => focusCameFromEditorWithMouse
1428
1435
  });
1429
1436
  const toolbar = view.dom.getRootNode().querySelector(".toolbar");
1437
+ let mouseDownTime = -Infinity;
1438
+ const onMouseDown = () => {
1439
+ if (view.hasFocus()) focusCameFromEditorWithMouse = true;
1440
+ mouseDownTime = Date.now();
1441
+ };
1442
+ const onFocusIn = () => {
1443
+ if (Date.now() - mouseDownTime > 250) focusCameFromEditorWithMouse = false;
1444
+ };
1445
+ const onFocusOut = (e) => {
1446
+ if (!toolbar.contains(e.relatedTarget)) focusCameFromEditorWithMouse = false;
1447
+ };
1448
+ toolbar.addEventListener("mousedown", onMouseDown, { capture: true });
1449
+ toolbar.addEventListener("focusin", onFocusIn);
1450
+ toolbar.addEventListener("focusout", onFocusOut);
1430
1451
  ctx.bindProp(() => this.hidden.getValue(rte), (hidden) => {
1431
1452
  toolbar.classList.toggle("toolbar--hidden", hidden);
1432
1453
  });
@@ -1448,6 +1469,10 @@ var RteToolbarFeatureImpl = class extends RteFeatureImpl {
1448
1469
  },
1449
1470
  destroy() {
1450
1471
  toolbar.innerHTML = "";
1472
+ toolbar.removeEventListener("mousedown", onMouseDown, { capture: true });
1473
+ toolbar.removeEventListener("focusin", onFocusIn);
1474
+ toolbar.removeEventListener("focusout", onFocusOut);
1475
+ focusCameFromEditorWithMouse = false;
1451
1476
  }
1452
1477
  };
1453
1478
  } }))];
@@ -2400,7 +2425,10 @@ var RteLinkFeatureImpl = class extends RteFeatureImpl {
2400
2425
  return null;
2401
2426
  } },
2402
2427
  view: (view) => {
2403
- const ctx = new UiCtx(view, rte, { popupPlacement: "bottom" });
2428
+ const ctx = new UiCtx(view, rte, {
2429
+ popupPlacement: "bottom",
2430
+ shouldReturnFocusToEditor: true
2431
+ });
2404
2432
  popup = rte.createComponent(Popover);
2405
2433
  popup.anchorId = "current-link";
2406
2434
  const content = createDiv(ctx, {
@@ -2525,6 +2553,7 @@ var RteLinkFeatureImpl = class extends RteFeatureImpl {
2525
2553
  onClick: () => {
2526
2554
  const { state, dispatch } = ctx.view;
2527
2555
  this.insertLink(rte, urlField.value, textField.value)(state, dispatch);
2556
+ this.toolbarMenu.open = false;
2528
2557
  }
2529
2558
  });
2530
2559
  const updateValidation = () => {
@@ -2775,7 +2804,10 @@ var RteInlineImageFeatureImpl = class extends RteFeatureImpl {
2775
2804
  return [this.contribution(new Plugin({
2776
2805
  props: { nodeViews: { inlineImage: (node, view, getPos) => new InlineImageView(node, view, getPos, this.config) } },
2777
2806
  view: (view) => {
2778
- const ctx = new UiCtx(view, rte, { popupPlacement: "bottom" });
2807
+ const ctx = new UiCtx(view, rte, {
2808
+ popupPlacement: "bottom",
2809
+ shouldReturnFocusToEditor: true
2810
+ });
2779
2811
  popover = rte.createComponent(Popover);
2780
2812
  popover.offset = 4;
2781
2813
  const content = createDiv(ctx, {
@@ -3193,7 +3225,6 @@ var RteInputRuleFeature = featureFacade(RteInputRuleFeatureImpl);
3193
3225
  //#endregion
3194
3226
  //#region src/lib/rich-text-editor/rte/features/keyboard-shortcuts.ts
3195
3227
  function toCommand(handler, rteInstance) {
3196
- if (handler.length === 0) return (_state, _dispatch) => handler();
3197
3228
  return (_state, _dispatch) => handler(rteInstance);
3198
3229
  }
3199
3230
  var RteKeyboardShortcutsFeatureImpl = class extends RteFeatureImpl {
@@ -3356,7 +3387,10 @@ var RteSuggestFeatureImpl = class extends RteFeatureImpl {
3356
3387
  return suggestState.loadingDecoration.add(state.doc, [Decoration.inline(suggestState.match.start, suggestState.match.end, { id: `suggest-anchor-${this.featureId}` })]);
3357
3388
  } },
3358
3389
  view: (view) => {
3359
- const ctx = new UiCtx(view, rte, { popupPlacement: "bottom" });
3390
+ const ctx = new UiCtx(view, rte, {
3391
+ popupPlacement: "bottom",
3392
+ shouldReturnFocusToEditor: true
3393
+ });
3360
3394
  const popover = rte.createComponent(Popover);
3361
3395
  popover.anchorId = `suggest-anchor-${this.featureId}`;
3362
3396
  popover.kind = "autocomplete";