@webitel/ui-sdk 26.8.118 → 26.8.119

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 (33) hide show
  1. package/dist/{clients-DWcx-Mfk.js → clients-CjVXyqY8.js} +1502 -170
  2. package/dist/{components-CeOlovz8.js → components-jZ2GzWbv.js} +741 -747
  3. package/dist/ui-sdk.css +1 -1
  4. package/dist/ui-sdk.js +1 -1
  5. package/dist/ui-sdk.umd.cjs +2152 -2113
  6. package/dist/{wt-chat-emoji-v9gpIuHe.js → wt-chat-emoji-RHMx_k1y.js} +1 -1
  7. package/dist/{wt-display-chip-items-Bob7miL_.js → wt-display-chip-items-D_iKQXSB.js} +1 -1
  8. package/dist/wt-label-unpxAKnu.js +28 -0
  9. package/dist/wt-rich-text-editor-B-iHNpqD.js +119 -0
  10. package/dist/{wt-send-message-popup-9O69XY0L.js → wt-send-message-popup-B2lqrtGF.js} +2 -2
  11. package/dist/{wt-type-extension-value-input-55YmFcBp.js → wt-type-extension-value-input-CJWynqhC.js} +2 -2
  12. package/dist/{wt-vidstack-player-ETPSioGH.js → wt-vidstack-player-BL6xL6yo.js} +1 -1
  13. package/package.json +3 -1
  14. package/src/components/index.js +14 -0
  15. package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.js +121 -0
  16. package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.spec.js +154 -0
  17. package/src/components/on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue +54 -0
  18. package/src/components/on-demand/wt-rich-text-editor/wt-rich-text-editor.vue +225 -0
  19. package/types/.tsbuildinfo +1 -1
  20. package/types/components/index.d.ts +7 -0
  21. package/types/components/on-demand/wt-call-media-action/wt-call-media-action.vue.d.ts +2 -2
  22. package/types/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.d.ts +1 -0
  23. package/types/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.spec.d.ts +1 -0
  24. package/types/components/on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue.d.ts +3 -0
  25. package/types/components/on-demand/wt-rich-text-editor/wt-rich-text-editor.vue.d.ts +78 -0
  26. package/types/components/transitions/cases/wt-replace-transition.vue.d.ts +1 -1
  27. package/types/components/wt-datepicker/wt-datepicker.vue.d.ts +2 -2
  28. package/types/components/wt-input-text/wt-input-text.vue.d.ts +1 -1
  29. package/types/components/wt-player/wt-player.vue.d.ts +1 -1
  30. package/types/components/wt-search-bar/wt-search-bar.vue.d.ts +2 -2
  31. package/types/components/wt-table/wt-table.vue.d.ts +1 -1
  32. package/types/components/wt-textarea/wt-textarea.vue.d.ts +1 -1
  33. package/types/components/wt-time-input/wt-time-input.vue.d.ts +1 -1
@@ -1,4 +1,4 @@
1
- import "./components-CeOlovz8.js";
1
+ import "./components-jZ2GzWbv.js";
2
2
  import { t as e } from "./_plugin-vue_export-helper-B3ysoDQm.js";
3
3
  import { m as t } from "./enums-CsINFPBu.js";
4
4
  import { t as n } from "./wt-button-rUZKbswL.js";
@@ -1,4 +1,4 @@
1
- import { C as e, i as t } from "./components-CeOlovz8.js";
1
+ import { C as e, i as t } from "./components-jZ2GzWbv.js";
2
2
  import { t as n } from "./_plugin-vue_export-helper-B3ysoDQm.js";
3
3
  import { t as r } from "./utils-DHSDTqJG.js";
4
4
  import { Fragment as i, computed as a, createCommentVNode as o, createElementBlock as s, createTextVNode as c, createVNode as l, defineComponent as u, openBlock as d, renderList as f, renderSlot as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
@@ -0,0 +1,28 @@
1
+ import { t as e } from "./_plugin-vue_export-helper-B3ysoDQm.js";
2
+ import { createBlock as t, createCommentVNode as n, createElementBlock as r, createTextVNode as i, defineComponent as a, normalizeClass as o, openBlock as s, renderSlot as c, resolveComponent as l, toDisplayString as u, withCtx as d } from "vue";
3
+ //#endregion
4
+ //#region src/components/wt-label/wt-label.vue
5
+ var f = /*#__PURE__*/ e(/* @__PURE__ */ a({
6
+ __name: "wt-label",
7
+ props: {
8
+ disabled: { type: Boolean },
9
+ invalid: { type: Boolean },
10
+ required: { type: Boolean },
11
+ hint: {}
12
+ },
13
+ setup(e) {
14
+ return (a, f) => {
15
+ let p = l("wt-hint");
16
+ return s(), r("label", { class: o([{
17
+ "wt-label--invalid": e.invalid,
18
+ "wt-label--disabled": e.disabled,
19
+ "wt-label--required": e.required
20
+ }, "wt-label typo-subtitle-2"]) }, [c(a.$slots, "default", {}, void 0, !0), e.hint ? (s(), t(p, { key: 0 }, {
21
+ default: d(() => [i(u(e.hint), 1)], void 0, !0),
22
+ _: 1
23
+ })) : n("", !0)], 2);
24
+ };
25
+ }
26
+ }), [["__scopeId", "data-v-7d80b998"]]);
27
+ //#endregion
28
+ export { f as t };
@@ -0,0 +1,119 @@
1
+ import { t as e } from "./_plugin-vue_export-helper-B3ysoDQm.js";
2
+ import { t } from "./wt-label-unpxAKnu.js";
3
+ import { computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createTextVNode as o, createVNode as s, defineComponent as c, guardReactiveProps as l, mergeModels as u, mergeProps as d, normalizeProps as f, onBeforeUnmount as p, openBlock as m, renderSlot as h, toDisplayString as g, unref as _, useId as v, useModel as y, withCtx as b } from "vue";
4
+ import x from "@tinymce/tinymce-vue";
5
+ import "tinymce/tinymce";
6
+ import "tinymce/icons/default";
7
+ import "tinymce/models/dom";
8
+ import "tinymce/themes/silver";
9
+ import "tinymce/plugins/advlist";
10
+ import "tinymce/plugins/emoticons";
11
+ import "tinymce/plugins/emoticons/js/emojis";
12
+ import "tinymce/plugins/fullscreen";
13
+ import "tinymce/plugins/image";
14
+ import "tinymce/plugins/link";
15
+ import "tinymce/plugins/lists";
16
+ import "tinymce/plugins/table";
17
+ import "tinymce/skins/ui/oxide/skin.css";
18
+ //#region src/components/on-demand/wt-rich-text-editor/wt-rich-text-editor.vue?vue&type=script&setup=true&lang.ts
19
+ var S = { class: "wt-rich-text-editor" }, C = /*#__PURE__*/ e(/* @__PURE__ */ c({
20
+ compatConfig: { MODE: 3 },
21
+ __name: "wt-rich-text-editor",
22
+ props: /*@__PURE__*/ u({
23
+ label: {},
24
+ labelProps: {},
25
+ output: { default: "html" },
26
+ height: { default: 300 },
27
+ disabled: { type: Boolean },
28
+ placeholder: {},
29
+ toolbar: {},
30
+ plugins: {}
31
+ }, {
32
+ modelValue: {},
33
+ modelModifiers: {}
34
+ }),
35
+ emits: ["update:modelValue"],
36
+ setup(e) {
37
+ let c = {
38
+ ...x,
39
+ name: "TinyMceEditor",
40
+ compatConfig: { MODE: 3 }
41
+ }, u = e, C = y(e, "modelValue"), w = v(), T = [
42
+ "advlist",
43
+ "emoticons",
44
+ "fullscreen",
45
+ "image",
46
+ "link",
47
+ "lists",
48
+ "table"
49
+ ], E = [
50
+ "undo redo",
51
+ "bold italic underline strikethrough",
52
+ "fullscreen",
53
+ "fontfamily fontsize",
54
+ "alignleft aligncenter alignright alignjustify",
55
+ "numlist bullist",
56
+ "forecolor backcolor removeformat",
57
+ "emoticons image link table"
58
+ ].join(" | "), D = n(() => u.output === "html"), O = n(() => u.plugins ?? (D.value ? T : [])), k = n(() => u.toolbar ?? (D.value ? E : "undo redo"));
59
+ function A() {
60
+ let e = getComputedStyle(document.documentElement);
61
+ return {
62
+ backgroundColor: e.getPropertyValue("--content-wrapper-color").trim(),
63
+ color: e.getPropertyValue("--wt-text-field-text-color").trim()
64
+ };
65
+ }
66
+ let j = n(() => {
67
+ let { backgroundColor: e, color: t } = A();
68
+ return {
69
+ height: Number(u.height),
70
+ menubar: !1,
71
+ statusbar: D.value,
72
+ toolbar_mode: "sliding",
73
+ toolbar_sticky: !0,
74
+ placeholder: u.placeholder,
75
+ skin: !1,
76
+ content_css: !1,
77
+ content_style: `body { background-color: ${e}; color: ${t}; }`
78
+ };
79
+ }), M = null, N = new MutationObserver(() => {
80
+ M?.getBody() && M.dom.setStyles(M.getBody(), A());
81
+ });
82
+ function P(e, t) {
83
+ M = t, N.observe(document.documentElement, {
84
+ attributes: !0,
85
+ attributeFilter: ["class"]
86
+ });
87
+ }
88
+ return p(() => {
89
+ N.disconnect(), M = null;
90
+ }), (n, u) => (m(), a("div", S, [e.label || n.$slots.label ? (m(), r(t, d({ key: 0 }, e.labelProps, {
91
+ disabled: e.disabled,
92
+ for: _(w)
93
+ }), {
94
+ default: b(() => [h(n.$slots, "label", f(l({ label: e.label })), () => [o(g(e.label), 1)], !0)], void 0, !0),
95
+ _: 3
96
+ }, 16, ["disabled", "for"])) : i("", !0), s(c, {
97
+ id: _(w),
98
+ modelValue: C.value,
99
+ "onUpdate:modelValue": u[0] ||= (e) => C.value = e,
100
+ disabled: e.disabled,
101
+ init: j.value,
102
+ "output-format": e.output,
103
+ plugins: O.value,
104
+ toolbar: k.value,
105
+ "license-key": "gpl",
106
+ onInit: P
107
+ }, null, 8, [
108
+ "id",
109
+ "modelValue",
110
+ "disabled",
111
+ "init",
112
+ "output-format",
113
+ "plugins",
114
+ "toolbar"
115
+ ])]));
116
+ }
117
+ }), [["__scopeId", "data-v-d8327512"]]);
118
+ //#endregion
119
+ export { C as default };
@@ -1,6 +1,6 @@
1
- import { n as e } from "./components-CeOlovz8.js";
1
+ import { n as e } from "./components-jZ2GzWbv.js";
2
2
  import "./convertDuration-CW5qzlwH.js";
3
- import { i as t, r as n } from "./clients-DWcx-Mfk.js";
3
+ import { i as t, r as n } from "./clients-CjVXyqY8.js";
4
4
  import { n as r } from "./vue-i18n-VBiROMZ4.js";
5
5
  import { n as i } from "./useEventBus-B-ZeklLu.js";
6
6
  import { createBlock as a, createTextVNode as o, createVNode as s, defineComponent as c, mergeProps as l, onMounted as u, openBlock as d, ref as f, resolveComponent as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
@@ -1,8 +1,8 @@
1
1
  import { a as e, c as t, i as n, n as r, r as i, s as a, t as o } from "./dist-EXdgfpvN.js";
2
2
  import { n as s } from "./genClient-CvWlQ0TZ.js";
3
3
  import { a as c } from "./caseConverters-DGri4uJo.js";
4
- import { a as l, o as u, r as d, s as f } from "./components-CeOlovz8.js";
5
- import { a as p, n as m, t as h } from "./clients-DWcx-Mfk.js";
4
+ import { a as l, o as u, r as d, s as f } from "./components-jZ2GzWbv.js";
5
+ import { a as p, n as m, t as h } from "./clients-CjVXyqY8.js";
6
6
  import { a as g, b as _, d as v, f as y, g as b, i as x, n as S, o as C, r as w, s as T, t as E, y as D } from "./enums-CsINFPBu.js";
7
7
  import { n as ee, t as te } from "./vue-i18n-VBiROMZ4.js";
8
8
  import "./wt-toast-pXV44Y3v.js";
@@ -1,4 +1,4 @@
1
- import { w as e } from "./components-CeOlovz8.js";
1
+ import { w as e } from "./components-jZ2GzWbv.js";
2
2
  import "./convertDuration-CW5qzlwH.js";
3
3
  import { t } from "./_plugin-vue_export-helper-B3ysoDQm.js";
4
4
  import { m as n } from "./enums-CsINFPBu.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "26.8.118",
3
+ "version": "26.8.119",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "npm run docs:dev",
@@ -62,6 +62,7 @@
62
62
  "@morev/vue-transitions": "^3.0.5",
63
63
  "@primeuix/themes": "^1.1.1",
64
64
  "@tailwindcss/vite": "^4.1.7",
65
+ "@tinymce/tinymce-vue": "^6.3.0",
65
66
  "@vuepic/vue-datepicker": "^4.5.1",
66
67
  "@vueuse/components": "^14.0.0",
67
68
  "autosize": "^6.0.1",
@@ -79,6 +80,7 @@
79
80
  "qs-esm": "^7.0.3",
80
81
  "sortablejs": "^1.15.3",
81
82
  "tailwindcss": "^4.1.7",
83
+ "tinymce": "^8.9.2",
82
84
  "vue-multiselect": "^3.4.0",
83
85
  "vue-observe-visibility": "^2.0.0-alpha.1",
84
86
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
@@ -1,5 +1,6 @@
1
1
  import { defineAsyncComponent } from 'vue';
2
2
 
3
+ import WtRichTextEditorSkeleton from './on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue';
3
4
  import WtReplaceTransition from './transitions/cases/wt-replace-transition.vue';
4
5
  import WtAppHeader from './wt-app-header/wt-app-header.vue';
5
6
  import WtAppNavigator from './wt-app-header/wt-app-navigator.vue';
@@ -181,6 +182,17 @@ const WtNavigationMenu = defineAsyncComponent(
181
182
  () =>
182
183
  import('./on-demand/wt-navigation-menu/components/wt-navigation-menu.vue'),
183
184
  );
185
+ /**
186
+ * TinyMCE lives in this chunk alone: an app that never renders an editor never
187
+ * downloads it. The skeleton covers the download.
188
+ * @type {typeof import('./on-demand/wt-rich-text-editor/wt-rich-text-editor.vue').default}
189
+ */
190
+ const WtRichTextEditor = defineAsyncComponent({
191
+ loader: () =>
192
+ import('./on-demand/wt-rich-text-editor/wt-rich-text-editor.vue'),
193
+ loadingComponent: WtRichTextEditorSkeleton,
194
+ delay: 0,
195
+ });
184
196
  /** @type {typeof import('./on-demand/wt-selection-popup/wt-selection-popup.vue').default} */
185
197
  const WtSelectionPopup = defineAsyncComponent(
186
198
  () => import('./on-demand/wt-selection-popup/wt-selection-popup.vue'),
@@ -233,6 +245,7 @@ const Components = {
233
245
  WtLoader,
234
246
  WtRoundedAction,
235
247
  WtReplaceTransition,
248
+ WtRichTextEditor,
236
249
  WtCheckbox,
237
250
  WtDatepicker,
238
251
  WtIconBtn,
@@ -364,6 +377,7 @@ export {
364
377
  WtProgressBar,
365
378
  WtRadio,
366
379
  WtReplaceTransition,
380
+ WtRichTextEditor,
367
381
  WtRoundedAction,
368
382
  WtScreenRecordingsAction,
369
383
  WtSearchBar,
@@ -0,0 +1,121 @@
1
+ /**
2
+ * cc-workspaces runs on the @vue/compat runtime. There, a component given a
3
+ * `modelValue` prop has it rewritten to `value` / `onModelCompat:input` unless
4
+ * the component is marked as Vue 3 — and tinymce-vue binds the model only when
5
+ * `onUpdate:modelValue` is in its attrs, so it silently stops emitting
6
+ * (WTEL-4477). This runs the editor on the compat runtime (one instance of it:
7
+ * @vue/test-utils would bring the real `vue`, so mounting goes through compat's
8
+ * own createApp) to pin that the listener still arrives.
9
+ */
10
+ vi.mock('vue', async () => await import('@vue/compat'));
11
+
12
+ vi.mock('tinymce/tinymce', () => ({}));
13
+ vi.mock('tinymce/icons/default', () => ({}));
14
+ vi.mock('tinymce/models/dom', () => ({}));
15
+ vi.mock('tinymce/themes/silver', () => ({}));
16
+ vi.mock('tinymce/plugins/advlist', () => ({}));
17
+ vi.mock('tinymce/plugins/emoticons', () => ({}));
18
+ vi.mock('tinymce/plugins/emoticons/js/emojis', () => ({}));
19
+ vi.mock('tinymce/plugins/fullscreen', () => ({}));
20
+ vi.mock('tinymce/plugins/image', () => ({}));
21
+ vi.mock('tinymce/plugins/link', () => ({}));
22
+ vi.mock('tinymce/plugins/lists', () => ({}));
23
+ vi.mock('tinymce/plugins/table', () => ({}));
24
+ vi.mock('tinymce/skins/ui/oxide/skin.css', () => ({}));
25
+
26
+ // Stands in for tinymce-vue's Editor: no declared emits, model read from attrs.
27
+ const seenAttrs = [];
28
+ const seenModel = [];
29
+ let emitEdit = () => {};
30
+ vi.mock('@tinymce/tinymce-vue', async () => {
31
+ const { defineComponent, h } = await import('@vue/compat');
32
+ return {
33
+ default: defineComponent({
34
+ name: 'Editor',
35
+ props: [
36
+ 'id',
37
+ 'modelValue',
38
+ 'init',
39
+ 'plugins',
40
+ 'toolbar',
41
+ 'outputFormat',
42
+ 'disabled',
43
+ 'licenseKey',
44
+ ],
45
+ setup(props, ctx) {
46
+ seenAttrs.push(Object.keys(ctx.attrs));
47
+ seenModel.push(props.modelValue);
48
+ // lets a test type into the stand-in
49
+ emitEdit = (value) => ctx.attrs['onUpdate:modelValue']?.(value);
50
+ return () => h('div');
51
+ },
52
+ }),
53
+ };
54
+ });
55
+
56
+ const { configureCompat, createApp, h } = await import('@vue/compat');
57
+ const { default: TinyMceVueEditor } = await import('@tinymce/tinymce-vue');
58
+ const { default: WtRichTextEditor } = await import(
59
+ '../wt-rich-text-editor.vue'
60
+ );
61
+
62
+ function render(component, props) {
63
+ const app = createApp({
64
+ render: () => h(component, props),
65
+ });
66
+ // the label is resolved globally in apps; not under test here
67
+ app.component('WtLabel', {
68
+ render: () => null,
69
+ });
70
+ app.mount(document.createElement('div'));
71
+ return app;
72
+ }
73
+
74
+ describe('WtRichTextEditor on @vue/compat', () => {
75
+ beforeAll(() => {
76
+ configureCompat({
77
+ MODE: 2,
78
+ });
79
+ });
80
+
81
+ afterAll(() => {
82
+ configureCompat({
83
+ MODE: 3,
84
+ });
85
+ });
86
+
87
+ beforeEach(() => {
88
+ seenAttrs.length = 0;
89
+ seenModel.length = 0;
90
+ });
91
+
92
+ it('control: compat takes the model listener away from plain tinymce-vue', () => {
93
+ render(TinyMceVueEditor, {
94
+ modelValue: '',
95
+ 'onUpdate:modelValue': () => {},
96
+ }).unmount();
97
+
98
+ expect(seenAttrs[0]).not.toContain('onUpdate:modelValue');
99
+ });
100
+
101
+ it('keeps the model listener on its editor', () => {
102
+ render(WtRichTextEditor, {
103
+ modelValue: '',
104
+ 'onUpdate:modelValue': () => {},
105
+ }).unmount();
106
+
107
+ expect(seenAttrs[0]).toContain('onUpdate:modelValue');
108
+ });
109
+
110
+ it('takes its own v-model on compat: the value in, edits out', () => {
111
+ const onUpdate = vi.fn();
112
+ render(WtRichTextEditor, {
113
+ modelValue: '<p>Seed</p>',
114
+ 'onUpdate:modelValue': onUpdate,
115
+ });
116
+
117
+ expect(seenModel[0]).toBe('<p>Seed</p>');
118
+ emitEdit('<p>Edited</p>');
119
+ expect(onUpdate).toHaveBeenCalledWith('<p>Edited</p>');
120
+ });
121
+ });
@@ -0,0 +1,154 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { nextTick } from 'vue';
3
+
4
+ // TinyMCE cannot run in jsdom: its side-effect imports are stubbed, and the
5
+ // Vue wrapper is replaced by a stub that records its props.
6
+ vi.mock('tinymce/tinymce', () => ({}));
7
+ vi.mock('tinymce/icons/default', () => ({}));
8
+ vi.mock('tinymce/models/dom', () => ({}));
9
+ vi.mock('tinymce/themes/silver', () => ({}));
10
+ vi.mock('tinymce/plugins/advlist', () => ({}));
11
+ vi.mock('tinymce/plugins/emoticons', () => ({}));
12
+ vi.mock('tinymce/plugins/emoticons/js/emojis', () => ({}));
13
+ vi.mock('tinymce/plugins/fullscreen', () => ({}));
14
+ vi.mock('tinymce/plugins/image', () => ({}));
15
+ vi.mock('tinymce/plugins/link', () => ({}));
16
+ vi.mock('tinymce/plugins/lists', () => ({}));
17
+ vi.mock('tinymce/plugins/table', () => ({}));
18
+ vi.mock('tinymce/skins/ui/oxide/skin.css', () => ({}));
19
+ // setup-based like the real Editor, so wrapping it the wrong way shows up
20
+ vi.mock('@tinymce/tinymce-vue', async () => {
21
+ const { h } = await import('vue');
22
+ return {
23
+ default: {
24
+ name: 'Editor',
25
+ props: [
26
+ 'id',
27
+ 'modelValue',
28
+ 'init',
29
+ 'plugins',
30
+ 'toolbar',
31
+ 'outputFormat',
32
+ 'disabled',
33
+ 'licenseKey',
34
+ ],
35
+ emits: [
36
+ 'update:modelValue',
37
+ 'init',
38
+ ],
39
+ setup: () => () =>
40
+ h('div', {
41
+ class: 'editor-stub',
42
+ }),
43
+ },
44
+ };
45
+ });
46
+
47
+ const { default: WtRichTextEditor } = await import(
48
+ '../wt-rich-text-editor.vue'
49
+ );
50
+
51
+ const mountEditor = (props = {}) =>
52
+ mount(WtRichTextEditor, {
53
+ props,
54
+ });
55
+
56
+ const editorStub = (wrapper) =>
57
+ wrapper.findComponent({
58
+ name: 'TinyMceEditor',
59
+ });
60
+
61
+ describe('WtRichTextEditor', () => {
62
+ afterEach(() => {
63
+ document.documentElement.classList.remove('theme--dark');
64
+ });
65
+
66
+ it('shows a label only when given one', () => {
67
+ expect(mountEditor().find('.wt-label').exists()).toBe(false);
68
+ expect(
69
+ mountEditor({
70
+ label: 'Reply',
71
+ }).text(),
72
+ ).toContain('Reply');
73
+ });
74
+
75
+ it('edits html with the full toolbar by default', () => {
76
+ const editor = editorStub(mountEditor());
77
+
78
+ expect(editor.props('outputFormat')).toBe('html');
79
+ expect(editor.props('plugins')).toContain('table');
80
+ expect(editor.props('toolbar')).toContain('bold italic');
81
+ expect(editor.props('init').height).toBe(300);
82
+ expect(editor.props('licenseKey')).toBe('gpl');
83
+ });
84
+
85
+ it('drops the formatting tools for plain text', () => {
86
+ const editor = editorStub(
87
+ mountEditor({
88
+ output: 'text',
89
+ }),
90
+ );
91
+
92
+ expect(editor.props('plugins')).toEqual([]);
93
+ expect(editor.props('toolbar')).toBe('undo redo');
94
+ expect(editor.props('init').statusbar).toBe(false);
95
+ });
96
+
97
+ it('takes toolbar and plugin overrides', () => {
98
+ const editor = editorStub(
99
+ mountEditor({
100
+ toolbar: 'bold italic | link',
101
+ plugins: [
102
+ 'link',
103
+ ],
104
+ }),
105
+ );
106
+
107
+ expect(editor.props('toolbar')).toBe('bold italic | link');
108
+ expect(editor.props('plugins')).toEqual([
109
+ 'link',
110
+ ]);
111
+ });
112
+
113
+ it('passes the model through both ways', async () => {
114
+ const wrapper = mountEditor({
115
+ modelValue: '<p>Hi</p>',
116
+ });
117
+ expect(editorStub(wrapper).props('modelValue')).toBe('<p>Hi</p>');
118
+
119
+ await editorStub(wrapper).vm.$emit('update:modelValue', '<p>Hello</p>');
120
+
121
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([
122
+ '<p>Hello</p>',
123
+ ]);
124
+ });
125
+
126
+ it('restyles the editable body when the app switches theme', async () => {
127
+ const body = {};
128
+ const setStyles = vi.fn();
129
+ const wrapper = mountEditor();
130
+
131
+ editorStub(wrapper).vm.$emit(
132
+ 'init',
133
+ {},
134
+ {
135
+ getBody: () => body,
136
+ dom: {
137
+ setStyles,
138
+ },
139
+ },
140
+ );
141
+ document.documentElement.classList.add('theme--dark');
142
+ // MutationObserver callbacks run as microtasks
143
+ await nextTick();
144
+ await Promise.resolve();
145
+
146
+ expect(setStyles).toHaveBeenCalledWith(
147
+ body,
148
+ expect.objectContaining({
149
+ backgroundColor: expect.any(String),
150
+ color: expect.any(String),
151
+ }),
152
+ );
153
+ });
154
+ });
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <!-- shown while the editor chunk (TinyMCE) downloads -->
3
+ <div
4
+ aria-busy="true"
5
+ class="wt-rich-text-editor-skeleton"
6
+ >
7
+ <div class="wt-rich-text-editor-skeleton__toolbar" />
8
+ <div class="wt-rich-text-editor-skeleton__body">
9
+ <div class="wt-rich-text-editor-skeleton__line" />
10
+ <div class="wt-rich-text-editor-skeleton__line" />
11
+ <div class="wt-rich-text-editor-skeleton__line wt-rich-text-editor-skeleton__line--short" />
12
+ </div>
13
+ </div>
14
+ </template>
15
+
16
+ <style scoped>
17
+ .wt-rich-text-editor-skeleton {
18
+ display: flex;
19
+ flex-direction: column;
20
+ height: 200px;
21
+ cursor: progress;
22
+ border-radius: var(--border-radius);
23
+ box-shadow: var(--elevation-4);
24
+ }
25
+
26
+ .wt-rich-text-editor-skeleton__toolbar {
27
+ height: 40px;
28
+ box-shadow: var(--elevation-2);
29
+ }
30
+
31
+ .wt-rich-text-editor-skeleton__body {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--spacing-xs);
35
+ padding: var(--spacing-sm);
36
+ }
37
+
38
+ .wt-rich-text-editor-skeleton__line {
39
+ height: 12px;
40
+ border-radius: var(--border-radius);
41
+ background: var(--skeleton-color, var(--dp-20-surface-color));
42
+ animation: wt-rich-text-editor-skeleton-pulse 1.2s ease-in-out infinite;
43
+ }
44
+
45
+ .wt-rich-text-editor-skeleton__line--short {
46
+ width: 60%;
47
+ }
48
+
49
+ @keyframes wt-rich-text-editor-skeleton-pulse {
50
+ 50% {
51
+ opacity: 0.5;
52
+ }
53
+ }
54
+ </style>