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