@tiny-mce-umbraco/backoffice 17.3.4 → 17.6.2

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 (54) hide show
  1. package/dist-cms/README.md +1 -0
  2. package/dist-cms/components/input-tiny-mce/input-tiny-mce.defaults.d.ts.map +1 -1
  3. package/dist-cms/components/input-tiny-mce/input-tiny-mce.defaults.js +175 -6
  4. package/dist-cms/components/input-tiny-mce/input-tiny-mce.element.d.ts +4 -1
  5. package/dist-cms/components/input-tiny-mce/input-tiny-mce.element.d.ts.map +1 -1
  6. package/dist-cms/components/input-tiny-mce/input-tiny-mce.element.js +97 -12
  7. package/dist-cms/components/input-tiny-mce/shadow-dom-selection.d.ts +16 -0
  8. package/dist-cms/components/input-tiny-mce/shadow-dom-selection.d.ts.map +1 -0
  9. package/dist-cms/components/input-tiny-mce/shadow-dom-selection.js +112 -0
  10. package/dist-cms/components/stylesheet-rule-input/stylesheet-rule-input.element.d.ts +1 -1
  11. package/dist-cms/entry-points/blob-download-router-fix.d.ts +56 -0
  12. package/dist-cms/entry-points/blob-download-router-fix.d.ts.map +1 -0
  13. package/dist-cms/entry-points/blob-download-router-fix.js +81 -0
  14. package/dist-cms/entry-points/entry-point.d.ts.map +1 -1
  15. package/dist-cms/entry-points/entry-point.js +7 -1
  16. package/dist-cms/localizations/ar.d.ts.map +1 -1
  17. package/dist-cms/localizations/ar.js +3 -0
  18. package/dist-cms/localizations/bs.d.ts.map +1 -1
  19. package/dist-cms/localizations/bs.js +3 -0
  20. package/dist-cms/localizations/cy.d.ts.map +1 -1
  21. package/dist-cms/localizations/cy.js +3 -0
  22. package/dist-cms/localizations/da.d.ts.map +1 -1
  23. package/dist-cms/localizations/da.js +4 -0
  24. package/dist-cms/localizations/en.d.ts.map +1 -1
  25. package/dist-cms/localizations/en.js +4 -0
  26. package/dist-cms/localizations/hr.d.ts.map +1 -1
  27. package/dist-cms/localizations/hr.js +3 -0
  28. package/dist-cms/localizations/it.d.ts.map +1 -1
  29. package/dist-cms/localizations/it.js +3 -0
  30. package/dist-cms/localizations/manifests.d.ts.map +1 -1
  31. package/dist-cms/localizations/manifests.js +24 -24
  32. package/dist-cms/localizations/nl.d.ts.map +1 -1
  33. package/dist-cms/localizations/nl.js +3 -0
  34. package/dist-cms/localizations/tr.d.ts.map +1 -1
  35. package/dist-cms/localizations/tr.js +3 -0
  36. package/dist-cms/plugins/manifests.d.ts.map +1 -1
  37. package/dist-cms/plugins/manifests.js +17 -0
  38. package/dist-cms/plugins/tiny-mce-block-picker.plugin.d.ts.map +1 -1
  39. package/dist-cms/plugins/tiny-mce-block-picker.plugin.js +20 -2
  40. package/dist-cms/plugins/tiny-mce-embeddedmedia.plugin.d.ts.map +1 -1
  41. package/dist-cms/plugins/tiny-mce-embeddedmedia.plugin.js +1 -0
  42. package/dist-cms/plugins/tiny-mce-mediapicker.plugin.d.ts.map +1 -1
  43. package/dist-cms/plugins/tiny-mce-mediapicker.plugin.js +127 -43
  44. package/dist-cms/property-editors/configEditor/property-editor-ui-tiny-mce-config-editor.element.d.ts.map +1 -1
  45. package/dist-cms/property-editors/configEditor/property-editor-ui-tiny-mce-config-editor.element.js +0 -1
  46. package/dist-cms/property-editors/manifests.js +3 -3
  47. package/dist-cms/property-editors/plugin/property-editor-ui-tiny-mce-plugin-configuration.element.d.ts.map +1 -1
  48. package/dist-cms/property-editors/plugin/property-editor-ui-tiny-mce-plugin-configuration.element.js +15 -5
  49. package/dist-cms/property-editors/tiny-mce/manifests.d.ts.map +1 -1
  50. package/dist-cms/property-editors/tiny-mce/manifests.js +2 -1
  51. package/dist-cms/utils/index.d.ts +15 -5
  52. package/dist-cms/utils/index.d.ts.map +1 -1
  53. package/dist-cms/utils/index.js +27 -5
  54. package/package.json +6 -6
@@ -0,0 +1 @@
1
+ This is an Umbraco CMS package that enables access to the paid features in the TinyMCE-based Rich Text Editor (RTE) in version 12+ with a license. It also adds a new TinyMCE Premium property editor with additional settings, providing a more targeted configuration setup for RTE Data Types in Umbraco. Finally, it allows for additional settings that support JSON directly in the configuration for .NET (appsettings.config).
@@ -1 +1 @@
1
- {"version":3,"file":"input-tiny-mce.defaults.d.ts","sourceRoot":"","sources":["../../../src/components/input-tiny-mce/input-tiny-mce.defaults.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAGjF,eAAO,MAAM,4CAA4C,sCAAsC,CAAC;AAEhG,eAAO,MAAM,yBAAyB,uQAmB5B,CAAC;AAMX,eAAO,MAAM,qBAAqB,EAAE,gBAiKnC,CAAC"}
1
+ {"version":3,"file":"input-tiny-mce.defaults.d.ts","sourceRoot":"","sources":["../../../src/components/input-tiny-mce/input-tiny-mce.defaults.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAKjF,eAAO,MAAM,4CAA4C,sCAAsC,CAAC;AAEhG,eAAO,MAAM,yBAAyB,uQAmB5B,CAAC;AAMX,eAAO,MAAM,qBAAqB,EAAE,gBA0UnC,CAAC"}
@@ -1,5 +1,7 @@
1
1
  import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from '@umbraco-cms/backoffice/context-api';
2
+ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
2
3
  import { UUIIconRequestEvent } from '@umbraco-cms/backoffice/external/uui';
4
+ import { umbLocalizationManager } from '@umbraco-cms/backoffice/localization-api';
3
5
  export const UMB_BLOCK_ENTRY_WEB_COMPONENTS_ABSOLUTE_PATH = '@umbraco-cms/backoffice/block-rte';
4
6
  export const defaultPremiumPluginsList = [
5
7
  'a11ychecker',
@@ -27,9 +29,9 @@ export const defaultPremiumPluginsList = [
27
29
  //if a user removes an element from ValidElements config it would still be allowed via this list.
28
30
  export const defaultFallbackConfig = {
29
31
  plugins: ['anchor', 'charmap', 'table', 'lists', 'advlist', 'autolink', 'directionality', 'searchreplace'],
30
- valid_elements: '+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-s[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style|lang],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption,cite,video[*],audio[*],picture[*],source[*],canvas[*],details[*],summary[*],-code',
32
+ valid_elements: '+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-s[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style|contenteditable|data-embed-url|data-embed-height|data-embed-width|data-embed-constrain],-span[class|align|style|lang],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption,cite,video[*],audio[*],picture[*],source[*],canvas[*],details[*],summary[*],-code',
31
33
  invalid_elements: 'font',
32
- extended_valid_elements: '@[id|class|style],+umb-rte-block[!data-content-key],+umb-rte-block-inline[!data-content-key],ins[datetime|cite],figure,figcaption',
34
+ extended_valid_elements: '@[id|class|style],+umb-rte-block[!data-content-key],+umb-rte-block-inline[!data-content-key],ins[datetime|cite],figure,figcaption,iframe[*]',
33
35
  custom_elements: 'umb-rte-block,~umb-rte-block-inline',
34
36
  toolbar: [
35
37
  'styles',
@@ -57,6 +59,12 @@ export const defaultFallbackConfig = {
57
59
  // });
58
60
  //},
59
61
  init_instance_callback: function (editor) {
62
+ // Everything below bootstraps the editor's *iframe* realm through `editor.dom.doc.head`. Inline
63
+ // mode has no iframe and `editor.dom.doc` IS the backoffice document, so running any of it would
64
+ // inject a duplicate import map and re-register live manifests into the running registry - and
65
+ // none of it is needed there, since the components it bootstraps already live in this realm.
66
+ if (!editor.iframeElement)
67
+ return;
60
68
  // The following code is the context api proxy. [NL]
61
69
  // It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL]
62
70
  editor.dom.doc.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event) => {
@@ -124,12 +132,173 @@ export const defaultFallbackConfig = {
124
132
  // Change the history!
125
133
  window.history.pushState(null, '', path);
126
134
  });
127
- // Load backoffice JS so we can get the umb-rte-block component registered inside the iframe [NL]
135
+ // Sync block editor localization keys into the iframe's module-scope umbLocalizationManager.
136
+ // The iframe has a completely separate module scope (and thus a separate umbLocalizationManager
137
+ // instance) from the outer document. Without this, requestDelete() in UmbBlockEntryContext
138
+ // falls back to returning raw keys because the iframe's manager has no registered translations.
139
+ const locSetsToSync = [];
140
+ // Keys needed by components that render INSIDE the TinyMCE iframe. The iframe has its own
141
+ // per-realm umbLocalizationManager, so anything not copied across renders blank there.
142
+ // The unsupported-block pair is read by Umbraco 17.6's <umb-unsupported-rte-block>.
143
+ const BLOCK_LOC_KEYS = [
144
+ // Rendered by the block components themselves.
145
+ 'blockEditor_confirmDeleteBlockTitle',
146
+ 'blockEditor_confirmDeleteBlockMessage',
147
+ 'blockEditor_unsupportedBlockName',
148
+ 'blockEditor_unsupportedBlockDescription',
149
+ // Labels declared by Umbraco's `blockAction` manifests, which `umb-block-action-list`
150
+ // renders inside the iframe. Enumerated from
151
+ // `packages/block/block/action/common/*/manifests.ts` — kept complete deliberately, so a
152
+ // newly reachable action does not show a raw `#key` the way copy-to-clipboard did.
153
+ 'clipboard_labelForCopyToClipboard',
154
+ 'general_delete',
155
+ 'general_edit',
156
+ 'general_settings',
157
+ 'actions_create',
158
+ ];
159
+ umbLocalizationManager.localizations.forEach((locSet, code) => {
160
+ const locSetAny = locSet;
161
+ const entry = {
162
+ $code: code,
163
+ $dir: locSetAny['$dir'] ?? 'ltr',
164
+ };
165
+ let hasKey = false;
166
+ for (const key of BLOCK_LOC_KEYS) {
167
+ const val = locSetAny[key];
168
+ if (typeof val === 'string') {
169
+ entry[key] = val;
170
+ hasKey = true;
171
+ }
172
+ }
173
+ if (hasKey)
174
+ locSetsToSync.push(entry);
175
+ });
176
+ // The active language must cross the realm boundary too, not just the dictionaries.
177
+ // Without this the iframe resolves every term through UmbLocalizationController's
178
+ // primary -> secondary -> `en` fallback chain, so the synced Danish set sits in the
179
+ // iframe's map unused and the placeholder renders in English.
180
+ //
181
+ // This must go through `umbLocalizationRegistry.loadLanguage()`, not a direct write to
182
+ // `umbLocalizationManager.documentLanguage`. The registry is a module side-effect
183
+ // (`export const umbLocalizationRegistry = new UmbLocalizationRegistry(...)`), and the
184
+ // block script below imports `@umbraco-cms/backoffice/block-rte`, whose module graph pulls
185
+ // the localization package into the iframe realm. Constructing the registry there emits
186
+ // its `UMB_DEFAULT_LOCALIZATION_CULTURE` bootstrap value synchronously, whose `tap` stamps
187
+ // `'en'` onto the manager — overwriting a direct assignment. Both scripts are dynamically
188
+ // inserted and therefore `async` with no guaranteed order, and the block script's larger
189
+ // graph reliably lands last. Importing the registry here instead means it is constructed
190
+ // before this script's body runs, so `loadLanguage()` always wins regardless of order.
191
+ // Nothing inside the iframe ever calls `loadLanguage()` otherwise — that is driven by
192
+ // `<umb-app>` and the current-user context, neither of which exists in this realm.
193
+ const outerLang = umbLocalizationManager.documentLanguage;
194
+ const outerDir = umbLocalizationManager.documentDirection;
195
+ if (locSetsToSync.length > 0) {
196
+ const locScript = document.createElement('script');
197
+ locScript.setAttribute('type', 'module');
198
+ locScript.text = `
199
+ import { umbLocalizationManager } from "@umbraco-cms/backoffice/localization-api";
200
+ import { umbLocalizationRegistry } from "@umbraco-cms/backoffice/localization";
201
+
202
+ // Sets documentLanguage via the registry's synchronous \`tap\`. The load that
203
+ // follows finds no localization extensions in this realm's registry, so the
204
+ // pipeline stops at its own length guard and never writes the language again.
205
+ umbLocalizationRegistry.loadLanguage(${JSON.stringify(outerLang)});
206
+ // Direction is only set by #setBrowserLanguage, which that stalled pipeline never
207
+ // reaches, so carry it across by hand — it matters for an RTL backoffice.
208
+ umbLocalizationManager.documentDirection = ${JSON.stringify(outerDir)};
209
+ // Register last: registerLocalization's keysChanged pass is what re-renders any
210
+ // consumer that connected before this script ran, and setting the language
211
+ // notifies nobody on its own.
212
+ ${JSON.stringify(locSetsToSync)}.forEach(s => umbLocalizationManager.registerLocalization(s));
213
+ `;
214
+ editor.dom.doc.head.appendChild(locScript);
215
+ }
216
+ // Expose the outer extension registry on the outer window so the injected module
217
+ // script below can sync blockAction manifests into the iframe's per-realm instance.
218
+ // umb-block-action-list reads umbExtensionsRegistry as a module-level singleton
219
+ // (not via context), so the context proxy alone cannot bridge it across realms.
220
+ window._umbOuterExtReg = umbExtensionsRegistry;
221
+ // Load backoffice JS so we can get the umb-rte-block component registered inside the iframe.
222
+ // Also:
223
+ // 1. Import @umbraco-cms/backoffice/block to register the blockAction kind definition
224
+ // and the <umb-block-action> custom element in the iframe's realm. These are NOT
225
+ // transitively imported by block-rte, but umb-block-action-list needs them to
226
+ // instantiate block action extensions (kind: 'default').
227
+ // 2. Sync blockAction manifests from the outer registry into the iframe's registry.
228
+ // Umbraco registers manifests via umbraco-package.json (not module imports), so they
229
+ // only exist in the outer registry — we must copy them into the iframe's per-realm
230
+ // umbExtensionsRegistry so umb-block-action-list can discover them.
128
231
  const script = document.createElement('script');
129
- script.type = 'text/javascript';
130
232
  script.setAttribute('type', 'module');
131
- script.text = `import "@umbraco-cms/backoffice/extension-registry";`;
132
- script.text = `import "${UMB_BLOCK_ENTRY_WEB_COMPONENTS_ABSOLUTE_PATH}";`;
233
+ script.text = `
234
+ import { umbExtensionsRegistry } from "@umbraco-cms/backoffice/extension-registry";
235
+ import { UMB_BLOCK_ACTION_DEFAULT_KIND_MANIFEST } from "@umbraco-cms/backoffice/block";
236
+ import "${UMB_BLOCK_ENTRY_WEB_COMPONENTS_ABSOLUTE_PATH}";
237
+ // Defines <umb-icon>, which umb-ref-rte-block renders for the block's element-type icon.
238
+ // block-rte does not pull it in transitively, so without this it stays an un-upgraded
239
+ // unknown element: the .name property binding still lands, but nothing ever renders.
240
+ // The existing UUIIconRequestEvent proxy is not involved — uui-icon is already defined
241
+ // in this realm, and the action-bar icons resolve through that proxy correctly.
242
+ // This is the whole components barrel because the export map exposes no narrower path.
243
+ import "@umbraco-cms/backoffice/components";
244
+
245
+ // Register the blockAction default kind definition in the inner realm's registry.
246
+ // UMB_BLOCK_ACTION_DEFAULT_KIND_MANIFEST is imported here (inner realm), so its
247
+ // element factory — element: () => import('./block-action.element.js') — captures
248
+ // the inner realm's module URL. When the extension system later calls that factory,
249
+ // block-action.element.js is loaded in the inner realm, registering <umb-block-action>
250
+ // in the inner customElements registry.
251
+ umbExtensionsRegistry.registerMany([UMB_BLOCK_ACTION_DEFAULT_KIND_MANIFEST]);
252
+
253
+ // Sync blockAction manifests from the outer registry into the inner realm's registry.
254
+ // Umbraco registers these via umbraco-package.json at backoffice startup; they only
255
+ // exist in the outer registry and must be copied so umb-block-action-list can discover them.
256
+ const outerReg = window.parent?._umbOuterExtReg;
257
+ if (outerReg) {
258
+ const blockActions = outerReg.getByType('blockAction') ?? [];
259
+ if (blockActions.length) umbExtensionsRegistry.registerMany(blockActions);
260
+
261
+ // Block actions declare their \`conditions\` by alias, and the extension system resolves
262
+ // each alias against the registry the action was registered in. Copying the actions
263
+ // without the condition manifests they name leaves every conditional action permanently
264
+ // un-initialized — delete, edit content, edit settings and expose content all have
265
+ // conditions, copy-to-clipboard does not, which is why it was the only one that rendered.
266
+ // Derived from the copied manifests rather than hard-coded, so an action that gains a
267
+ // new condition in a future Umbraco version is covered without another fix here.
268
+ const conditionAliases = new Set(
269
+ blockActions.flatMap((a) => (a.conditions ?? []).map((c) => c.alias)),
270
+ );
271
+ const conditions = (outerReg.getByType('condition') ?? []).filter((c) =>
272
+ conditionAliases.has(c.alias),
273
+ );
274
+ if (conditions.length) umbExtensionsRegistry.registerMany(conditions);
275
+ }
276
+
277
+ // Define the ufm-* elements in the inner realm so {=alias} block labels interpolate. Unlike
278
+ // umb-icon these are in no importable barrel - the outer document defines them by running
279
+ // each ufmComponent manifest's api(). Two constraints when editing this:
280
+ // 1. The manifests are on the package's \`manifests\` export, NOT \`extensions\`, which is a
281
+ // single bundle wrapper - filtering that matches nothing and fails silently.
282
+ // 2. This is the only top-level await in the script, so it must stay LAST or everything
283
+ // above it waits on the ufm package plus one dynamic import per component. Custom
284
+ // element upgrade is retroactive, so defining these last costs nothing.
285
+ try {
286
+ const ufmPkg = await import("/umbraco/backoffice/packages/ufm/umbraco-package.js");
287
+ const loaded = new Set();
288
+ for (const manifest of (ufmPkg.manifests ?? []).filter((m) => m.type === "ufmComponent")) {
289
+ if (loaded.has(manifest.alias)) continue;
290
+ loaded.add(manifest.alias);
291
+ try {
292
+ await manifest.api?.();
293
+ } catch {
294
+ // A single component failing to load should not break the rest.
295
+ }
296
+ }
297
+ } catch {
298
+ // If the ufm package is ever restructured, block labels degrade back to empty
299
+ // rather than breaking the editor.
300
+ }
301
+ `;
133
302
  editor.dom.doc.head.appendChild(script);
134
303
  },
135
304
  style_formats: [
@@ -1,7 +1,8 @@
1
+ import type { PropertyValues } from '@umbraco-cms/backoffice/external/lit';
1
2
  import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
2
3
  import type { Editor } from '@tiny-mce-umbraco/backoffice/external/tinymce';
3
4
  import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
4
- declare const UmbInputTinyMceElement_base: (new (...args: any[]) => import("@umbraco-ui/uui-base").UUIFormControlMixinElement<FormDataEntryValue | FormData>) & typeof UmbLitElement;
5
+ declare const UmbInputTinyMceElement_base: (new (...args: any[]) => import("@umbraco-ui/uui").UUIFormControlWithBasicsMixinElement<FormDataEntryValue | FormData>) & typeof UmbLitElement;
5
6
  export declare class UmbInputTinyMceElement extends UmbInputTinyMceElement_base {
6
7
  #private;
7
8
  configuration?: UmbPropertyEditorConfigCollection;
@@ -17,7 +18,9 @@ export declare class UmbInputTinyMceElement extends UmbInputTinyMceElement_base
17
18
  get readonly(): boolean;
18
19
  set readonly(value: boolean);
19
20
  private readonly _editorElement?;
21
+ private _inline;
20
22
  getEditor(): Editor | null | undefined;
23
+ willUpdate(changedProperties: PropertyValues): void;
21
24
  firstUpdated(): void;
22
25
  disconnectedCallback(): void;
23
26
  getFormatStyles(stylesheetPaths: Array<string>): Promise<any[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"input-tiny-mce.element.d.ts","sourceRoot":"","sources":["../../../src/components/input-tiny-mce/input-tiny-mce.element.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAIpE,OAAO,KAAK,EAAe,MAAM,EAAoB,MAAM,+CAA+C,CAAC;AAE3G,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,yCAAyC,CAAC;;AA8DjG,qBACa,sBAAuB,SAAQ,2BAAsC;;IAEjF,aAAa,CAAC,EAAE,iCAAiC,CAAC;cAS/B,cAAc;IAIjC,IAAa,KAAK,CAAC,QAAQ,EAAE,kBAAkB,GAAG,QAAQ,EAOzD;IAED,IAAa,KAAK,IAAI,kBAAkB,GAAG,QAAQ,CAElD;IAED;;;;;OAKG;IACH,IACW,QAAQ,YAElB;IACD,IAAW,QAAQ,CAAC,KAAK,SAAA,EAOxB;IAID,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAc;IAE9C,SAAS;IAIA,YAAY;IAsBZ,oBAAoB;IA0BvB,eAAe,CAAC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC;IA+UpD;;;OAGG;IACM,MAAM;IAIf,gBAAyB,MAAM,4BAkB7B;CACF;AAED,eAAe,sBAAsB,CAAC;AAEtC,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,oBAAoB,EAAE,sBAAsB,CAAC;KAC7C;CACD"}
1
+ {"version":3,"file":"input-tiny-mce.element.d.ts","sourceRoot":"","sources":["../../../src/components/input-tiny-mce/input-tiny-mce.element.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAO3E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAKpE,OAAO,KAAK,EAAe,MAAM,EAAoB,MAAM,+CAA+C,CAAC;AAE3G,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,yCAAyC,CAAC;;AA6EjG,qBACa,sBAAuB,SAAQ,2BAAsC;;IAEjF,aAAa,CAAC,EAAE,iCAAiC,CAAC;cAe/B,cAAc;IAIjC,IAAa,KAAK,CAAC,QAAQ,EAAE,kBAAkB,GAAG,QAAQ,EAOzD;IAED,IAAa,KAAK,IAAI,kBAAkB,GAAG,QAAQ,CAElD;IAED;;;;;OAKG;IACH,IACW,QAAQ,YAElB;IACD,IAAW,QAAQ,CAAC,KAAK,SAAA,EAOxB;IAID,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAc;IAG9C,OAAO,CAAC,OAAO,CAAS;IAExB,SAAS;IAIA,UAAU,CAAC,iBAAiB,EAAE,cAAc;IAU5C,YAAY;IA+BZ,oBAAoB;IA0BvB,eAAe,CAAC,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC;IAiXpD;;;OAGG;IACM,MAAM;IAMf,gBAAyB,MAAM,4BAiC7B;CACF;AAED,eAAe,sBAAsB,CAAC;AAEtC,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,oBAAoB,EAAE,sBAAsB,CAAC;KAC7C;CACD"}
@@ -15,13 +15,13 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
15
15
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
16
16
  return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
17
17
  };
18
- var _UmbInputTinyMceElement_instances, _UmbInputTinyMceElement_plugins, _UmbInputTinyMceElement_editorRef, _UmbInputTinyMceElement_sanitizeTinyMce, _UmbInputTinyMceElement_stylesheetRepository, _UmbInputTinyMceElement_umbStylesheetRuleManager, _UmbInputTinyMceElement_readonly, _UmbInputTinyMceElement_loadEditor, _UmbInputTinyMceElement_loadPlugins, _UmbInputTinyMceElement_getTinyMceConfig, _UmbInputTinyMceElement_setTinyConfig, _UmbInputTinyMceElement_getLanguage, _UmbInputTinyMceElement_editorSetup, _UmbInputTinyMceElement_onInit, _UmbInputTinyMceElement_onChange;
18
+ var _UmbInputTinyMceElement_instances, _UmbInputTinyMceElement_plugins, _UmbInputTinyMceElement_editorRef, _UmbInputTinyMceElement_sanitizeTinyMce, _UmbInputTinyMceElement_stylesheetRootPath, _UmbInputTinyMceElement_stylesheetRepository, _UmbInputTinyMceElement_umbStylesheetRuleManager, _UmbInputTinyMceElement_readonly, _UmbInputTinyMceElement_loadStylesheetRootPath, _UmbInputTinyMceElement_loadEditor, _UmbInputTinyMceElement_loadPlugins, _UmbInputTinyMceElement_getTinyMceConfig, _UmbInputTinyMceElement_setTinyConfig, _UmbInputTinyMceElement_getLanguage, _UmbInputTinyMceElement_editorSetup, _UmbInputTinyMceElement_onInit, _UmbInputTinyMceElement_onChange;
19
19
  import { availableLanguages } from '@tiny-mce-umbraco/backoffice/core';
20
20
  import { defaultFallbackConfig, defaultPremiumPluginsList } from '@tiny-mce-umbraco/backoffice/core';
21
21
  import { pastePreProcessHandler } from '@tiny-mce-umbraco/backoffice/core';
22
22
  import { uriAttributeSanitizer } from '@tiny-mce-umbraco/backoffice/core';
23
23
  import { UmbStylesheetRuleManager } from '@tiny-mce-umbraco/backoffice/core';
24
- import { css, customElement, html, property, query } from '@umbraco-cms/backoffice/external/lit';
24
+ import { css, customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit';
25
25
  import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api';
26
26
  import { getProcessedImageUrl, umbDeepMerge } from '@umbraco-cms/backoffice/utils';
27
27
  import { renderEditor } from '@umbraco-cms/backoffice/external/tinymce';
@@ -30,7 +30,9 @@ import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api
30
30
  import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
31
31
  import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
32
32
  import { UmbStylesheetDetailRepository } from '@umbraco-cms/backoffice/stylesheet';
33
+ import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server';
33
34
  import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
35
+ import { bridgeInlineEditor } from './shadow-dom-selection.js';
34
36
  import { TinyMceService } from '../../api/index.js';
35
37
  import { tryExecute } from '@umbraco-cms/backoffice/resources';
36
38
  // @ts-ignore
@@ -52,10 +54,24 @@ async function onResize(e) {
52
54
  });
53
55
  e.target.setAttribute('data-mce-src', resizedPath);
54
56
  }
57
+ // Merges plugin lists into the flat `string[]` TinyMCE expects, dropping anything that is not a
58
+ // string. That filtering is load-bearing, not defensive dressing: TinyMCE trims every plugin name with
59
+ // `String.replace` (`trim$4`, called from its plugin-list parser), so a single non-string entry throws
60
+ // during editor construction and the editor never renders at all.
61
+ //
62
+ // Non-strings do reach here from configuration. `Umbraco:CMS:RichTextEditor:CustomConfig` binds into an
63
+ // `IDictionary<string, object>`, so a value written as a real JSON array has no scalar to bind and
64
+ // arrives as an object rather than a list. The supported way to express a list there is a JSON *string*
65
+ // - `"plugins": "[\"fullscreen\"]"` - which `parseJsonStringValues` turns back into a real array.
55
66
  function mergeArrays(arr1, arr2) {
56
- const a1 = Array.isArray(arr1) ? arr1 : arr1 ? [arr1] : [];
57
- const a2 = Array.isArray(arr2) ? arr2 : arr2 ? [arr2] : [];
58
- return Array.from(new Set([...a1, ...a2]));
67
+ const toStringList = (value) => {
68
+ if (typeof value === 'string')
69
+ return [value];
70
+ if (!Array.isArray(value))
71
+ return [];
72
+ return value.filter((entry) => typeof entry === 'string');
73
+ };
74
+ return Array.from(new Set([...toStringList(arr1), ...toStringList(arr2)]));
59
75
  }
60
76
  // Some config paths (e.g. Umbraco:CMS:RichTextEditor:CustomConfig) can only store scalar string
61
77
  // values via the .NET config binder. Complex TinyMCE options like style_formats were historically
@@ -86,9 +102,15 @@ let UmbInputTinyMceElement = class UmbInputTinyMceElement extends UUIFormControl
86
102
  _UmbInputTinyMceElement_plugins.set(this, []);
87
103
  _UmbInputTinyMceElement_editorRef.set(this, null);
88
104
  _UmbInputTinyMceElement_sanitizeTinyMce.set(this, true);
105
+ /**
106
+ * Where the picked stylesheets are served from. Configurable as `Global:UmbracoCssPath`; `/css` is
107
+ * the fallback Umbraco itself uses when the server has not reported one yet.
108
+ */
109
+ _UmbInputTinyMceElement_stylesheetRootPath.set(this, '/css');
89
110
  _UmbInputTinyMceElement_stylesheetRepository.set(this, new UmbStylesheetDetailRepository(this));
90
111
  _UmbInputTinyMceElement_umbStylesheetRuleManager.set(this, new UmbStylesheetRuleManager());
91
112
  _UmbInputTinyMceElement_readonly.set(this, false);
113
+ this._inline = false;
92
114
  }
93
115
  getFormElement() {
94
116
  return this._editorElement?.querySelector('iframe') ?? undefined;
@@ -123,7 +145,16 @@ let UmbInputTinyMceElement = class UmbInputTinyMceElement extends UUIFormControl
123
145
  getEditor() {
124
146
  return __classPrivateFieldGet(this, _UmbInputTinyMceElement_editorRef, "f");
125
147
  }
148
+ willUpdate(changedProperties) {
149
+ super.willUpdate(changedProperties);
150
+ // Resolved before the first render so the target element carries the right classes from the
151
+ // start - TinyMCE takes it over during init and `render()` cannot usefully re-run after that.
152
+ if (changedProperties.has('configuration')) {
153
+ this._inline = this.configuration?.getValueByAlias('mode')?.toLocaleLowerCase() === 'inline';
154
+ }
155
+ }
126
156
  firstUpdated() {
157
+ __classPrivateFieldGet(this, _UmbInputTinyMceElement_instances, "m", _UmbInputTinyMceElement_loadStylesheetRootPath).call(this);
127
158
  __classPrivateFieldGet(this, _UmbInputTinyMceElement_instances, "m", _UmbInputTinyMceElement_loadEditor).call(this);
128
159
  }
129
160
  disconnectedCallback() {
@@ -177,16 +208,27 @@ let UmbInputTinyMceElement = class UmbInputTinyMceElement extends UUIFormControl
177
208
  * a target div and binds the RTE to that element
178
209
  */
179
210
  render() {
180
- return html `<div class="editor"></div>`;
211
+ // `umb-rte` stands in for `body_class`, which TinyMCE only applies to the iframe body - in
212
+ // inline mode this element *is* the editable body.
213
+ return html `<div class=${this._inline ? 'editor inline umb-rte' : 'editor'}></div>`;
181
214
  }
182
215
  };
183
216
  _UmbInputTinyMceElement_plugins = new WeakMap();
184
217
  _UmbInputTinyMceElement_editorRef = new WeakMap();
185
218
  _UmbInputTinyMceElement_sanitizeTinyMce = new WeakMap();
219
+ _UmbInputTinyMceElement_stylesheetRootPath = new WeakMap();
186
220
  _UmbInputTinyMceElement_stylesheetRepository = new WeakMap();
187
221
  _UmbInputTinyMceElement_umbStylesheetRuleManager = new WeakMap();
188
222
  _UmbInputTinyMceElement_readonly = new WeakMap();
189
223
  _UmbInputTinyMceElement_instances = new WeakSet();
224
+ _UmbInputTinyMceElement_loadStylesheetRootPath = function _UmbInputTinyMceElement_loadStylesheetRootPath() {
225
+ this.consumeContext(UMB_SERVER_CONTEXT, (serverContext) => {
226
+ this.observe(serverContext?.getServerConnection()?.umbracoCssPath, (umbracoCssPath) => {
227
+ if (umbracoCssPath)
228
+ __classPrivateFieldSet(this, _UmbInputTinyMceElement_stylesheetRootPath, umbracoCssPath, "f");
229
+ });
230
+ });
231
+ };
190
232
  _UmbInputTinyMceElement_loadEditor = async function _UmbInputTinyMceElement_loadEditor() {
191
233
  this.observe(umbExtensionsRegistry.byType('tinyMcePlugin'), async (manifests) => {
192
234
  __classPrivateFieldGet(this, _UmbInputTinyMceElement_plugins, "f").length = 0;
@@ -234,8 +276,16 @@ _UmbInputTinyMceElement_setTinyConfig = async function _UmbInputTinyMceElement_s
234
276
  const stylesheetPaths = this.configuration?.getValueByAlias('stylesheets') ?? [];
235
277
  const styleFormats = await this.getFormatStyles(stylesheetPaths);
236
278
  const preValueCustomConfig = this.configuration?.getValueByAlias('customConfig') ?? {};
237
- // Map the stylesheets with server url
238
- const stylesheets = stylesheetPaths?.map((stylesheetPath) => `/css${stylesheetPath.replace(/\\/g, '/')}`) ?? [];
279
+ // Map the stylesheets with server url, matching what Umbraco's own rich text editor does in
280
+ // `input-tiptap.element.ts`. Prefixing unconditionally is wrong in two cases: a path that is
281
+ // already rooted (older configurations stored the full `/css/foo.css`, which became
282
+ // `/css/css/foo.css` and 404'd), and an absolute URL to an external stylesheet.
283
+ const stylesheets = stylesheetPaths?.map((stylesheetPath) => {
284
+ const path = stylesheetPath.replace(/\\/g, '/');
285
+ return path.startsWith('http') || path.startsWith(__classPrivateFieldGet(this, _UmbInputTinyMceElement_stylesheetRootPath, "f"))
286
+ ? path
287
+ : `${__classPrivateFieldGet(this, _UmbInputTinyMceElement_stylesheetRootPath, "f")}${path}`;
288
+ }) ?? [];
239
289
  stylesheets.push('/umbraco/backoffice/css/rte-content.css');
240
290
  const appSettingsConfig = await __classPrivateFieldGet(this, _UmbInputTinyMceElement_instances, "m", _UmbInputTinyMceElement_getTinyMceConfig).call(this);
241
291
  let apiKey = 'no-origin';
@@ -300,9 +350,14 @@ _UmbInputTinyMceElement_setTinyConfig = async function _UmbInputTinyMceElement_s
300
350
  configurationOptions.toolbar = false;
301
351
  }
302
352
  // set the configured inline mode
303
- const mode = this.configuration?.getValueByAlias('mode');
304
- if (mode?.toLocaleLowerCase() === 'inline') {
353
+ if (this._inline) {
305
354
  configurationOptions.inline = true;
355
+ // Inline mode makes the target element itself the editable body, so there is no editor
356
+ // chrome to size and TinyMCE drops height/width on the floor. Left unset, the empty
357
+ // target div collapses to 0px and the editor is invisible until it happens to be
358
+ // clicked - so hand the sizing to our own CSS instead (see the `.editor.inline` rule).
359
+ delete configurationOptions.height;
360
+ delete configurationOptions.width;
306
361
  }
307
362
  // set the maximum image size
308
363
  const maxImageSize = this.configuration?.getValueByAlias('maxImageSize');
@@ -343,8 +398,15 @@ _UmbInputTinyMceElement_setTinyConfig = async function _UmbInputTinyMceElement_s
343
398
  }
344
399
  if (appSettingsConfig) {
345
400
  if (appSettingsConfig.richTextEditor) {
346
- const mergedPlugins = mergeArrays(appSettingsConfig.richTextEditor.plugins, config.plugins);
347
- config = umbDeepMerge(parseJsonStringValues(appSettingsConfig.richTextEditor.customConfig), config);
401
+ // Plugins reach this section from two places: the dedicated `Plugins` setting, and a
402
+ // `plugins` entry inside `CustomConfig`. Both must be folded in before the assignment
403
+ // below, which overwrites whatever the deep merge produced. Miss the CustomConfig one
404
+ // and a plugin configured only there is silently dropped - the toolbar still lists its
405
+ // button, because the merged `toolbar` does survive, but TinyMCE never loaded the
406
+ // plugin so nothing renders for it.
407
+ const richTextEditorCustomConfig = parseJsonStringValues(appSettingsConfig.richTextEditor.customConfig);
408
+ const mergedPlugins = mergeArrays(mergeArrays(appSettingsConfig.richTextEditor.plugins, richTextEditorCustomConfig['plugins']), config.plugins);
409
+ config = umbDeepMerge(richTextEditorCustomConfig, config);
348
410
  config.plugins = mergedPlugins;
349
411
  if (appSettingsConfig.richTextEditor.validElements.length > 0) {
350
412
  config.valid_elements = appSettingsConfig.richTextEditor.validElements;
@@ -446,6 +508,11 @@ _UmbInputTinyMceElement_editorSetup = async function _UmbInputTinyMceElement_edi
446
508
  //console.log('#editorSetup end');
447
509
  };
448
510
  _UmbInputTinyMceElement_onInit = function _UmbInputTinyMceElement_onInit(editor) {
511
+ // Inline mode leaves the editable element in the backoffice's shadow DOM, which TinyMCE's
512
+ // document-scoped selection and id lookups cannot reach - see `shadow-dom-selection.ts`.
513
+ if (this._inline) {
514
+ bridgeInlineEditor(editor);
515
+ }
449
516
  //enable browser based spell checking
450
517
  editor.getBody().setAttribute('spellcheck', 'true');
451
518
  uriAttributeSanitizer(editor);
@@ -470,6 +537,21 @@ UmbInputTinyMceElement.styles = [
470
537
  position: absolute;
471
538
  }
472
539
 
540
+ /* Inline mode edits this element directly - TinyMCE renders no chrome around it and
541
+ ignores the configured height, so it needs to look like a field on its own. */
542
+ .editor.inline {
543
+ box-sizing: border-box;
544
+ min-height: 100px;
545
+ padding: var(--uui-size-space-3, 9px);
546
+ background-color: var(--uui-color-surface, #fff);
547
+ border: var(--uui-input-border-width, 1px) solid var(--uui-input-border-color, var(--uui-color-border, #d8d7d9));
548
+ }
549
+
550
+ .editor.inline:focus {
551
+ outline: none;
552
+ border-color: var(--uui-color-focus, #3544b1);
553
+ }
554
+
473
555
  /* FIXME: Remove this workaround when https://github.com/tinymce/tinymce/issues/6431 has been fixed */
474
556
  .tox .tox-collection__item-label {
475
557
  line-height: 1 !important;
@@ -485,6 +567,9 @@ __decorate([
485
567
  __decorate([
486
568
  query('.editor', true)
487
569
  ], UmbInputTinyMceElement.prototype, "_editorElement", void 0);
570
+ __decorate([
571
+ state()
572
+ ], UmbInputTinyMceElement.prototype, "_inline", void 0);
488
573
  UmbInputTinyMceElement = __decorate([
489
574
  customElement('umb-input-tiny-mce')
490
575
  ], UmbInputTinyMceElement);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * TinyMCE's inline mode assumes its editable element is somewhere document-level APIs can reach. In the
3
+ * backoffice it sits some twenty shadow roots deep (`umb-input-tiny-mce` -> ... -> `umb-app`), so three
4
+ * of those APIs silently fail and are bridged here. Classic mode needs none of it: its editable body is
5
+ * a plain document body inside the editor's own iframe.
6
+ *
7
+ * Every failure mode below is silent - no exception, no stack trace. See **Inline Mode and Shadow DOM**
8
+ * in this project's CLAUDE.md for the symptoms and how to test them.
9
+ */
10
+ import type { Editor } from '@tiny-mce-umbraco/backoffice/external/tinymce';
11
+ /**
12
+ * Applies the shadow-DOM bridges to a freshly initialized inline editor. Call for inline editors only.
13
+ * @param editor The inline editor to bridge.
14
+ */
15
+ export declare function bridgeInlineEditor(editor: Editor): void;
16
+ //# sourceMappingURL=shadow-dom-selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow-dom-selection.d.ts","sourceRoot":"","sources":["../../../src/components/input-tiny-mce/shadow-dom-selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+CAA+C,CAAC;AAwG5E;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,QAUhD"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * TinyMCE's inline mode assumes its editable element is somewhere document-level APIs can reach. In the
3
+ * backoffice it sits some twenty shadow roots deep (`umb-input-tiny-mce` -> ... -> `umb-app`), so three
4
+ * of those APIs silently fail and are bridged here. Classic mode needs none of it: its editable body is
5
+ * a plain document body inside the editor's own iframe.
6
+ *
7
+ * Every failure mode below is silent - no exception, no stack trace. See **Inline Mode and Shadow DOM**
8
+ * in this project's CLAUDE.md for the symptoms and how to test them.
9
+ */
10
+ /** Editable elements of the live inline editors; read by both document-level bridges. */
11
+ const inlineBodies = new Set();
12
+ let installed = false;
13
+ /**
14
+ * Resolved per call, never cached from focus/blur events: TinyMCE moves the selection while its own
15
+ * focus bookkeeping is mid-flight, and a stale flag there sends `setRng` to the wrong selection. This is
16
+ * the same liveness check TinyMCE's own `hasInlineFocus` makes.
17
+ */
18
+ function getFocusedInlineBody() {
19
+ for (const body of inlineBodies) {
20
+ if (body.getRootNode().activeElement === body)
21
+ return body;
22
+ }
23
+ return null;
24
+ }
25
+ /**
26
+ * TinyMCE reads the caret through `win.getSelection()` (`getSel` in `Selection.getRng`), which in
27
+ * Chromium never descends into a shadow root; `ShadowRoot.getSelection()` does. Firefox is the reverse -
28
+ * see the note on the fallback below.
29
+ *
30
+ * Diverts only while one of our inline editors holds focus, so every other caller gets the native
31
+ * selection untouched.
32
+ */
33
+ function installShadowDomSelectionBridge() {
34
+ const nativeGetSelection = window.getSelection.bind(window);
35
+ window.getSelection = function () {
36
+ const body = getFocusedInlineBody();
37
+ if (!body)
38
+ return nativeGetSelection();
39
+ // `ShadowRoot.getSelection()` is Chromium-only. The fallback is not a safety net - it is the
40
+ // working path in Firefox, whose `window.getSelection()` already pierces shadow roots. Removing
41
+ // it breaks Firefox. (Safari has neither and is unsupported.)
42
+ const selection = body.getRootNode().getSelection?.();
43
+ return selection ?? nativeGetSelection();
44
+ };
45
+ }
46
+ /**
47
+ * `Selection.setRng` guards with `isValidRange` -> `isAttachedToDom` -> Sugar's `contains`, a plain
48
+ * `d1.contains(d2)` that does not cross shadow boundaries. Every range inside the editor is therefore
49
+ * judged detached, and `setRng` returns before it dispatches anything.
50
+ *
51
+ * Patched on the document object rather than `Node.prototype` because that is the exact call being
52
+ * made. Only ever turns `false` into `true`, and only for nodes inside a live inline editor.
53
+ */
54
+ function installShadowDomAttachmentBridge() {
55
+ const nativeContains = document.contains.bind(document);
56
+ document.contains = (node) => {
57
+ if (nativeContains(node))
58
+ return true;
59
+ if (!node)
60
+ return false;
61
+ for (const body of inlineBodies) {
62
+ if (body === node || body.contains(node))
63
+ return true;
64
+ }
65
+ return false;
66
+ };
67
+ }
68
+ /** Installs the document-level bridges, once per page. */
69
+ function installBridges() {
70
+ if (installed)
71
+ return;
72
+ installed = true;
73
+ installShadowDomSelectionBridge();
74
+ installShadowDomAttachmentBridge();
75
+ }
76
+ /**
77
+ * `DOMUtils.get(id)` is `doc.getElementById(id)`, which cannot see into a shadow root. TinyMCE's
78
+ * bookmarks depend on it: operations that restructure the DOM drop `<span data-mce-type="bookmark">`
79
+ * markers and call `moveToBookmark` to restore the caret afterwards. Unresolvable markers mean the caret
80
+ * is never restored *and* the markers are never removed - they accumulate in the content and get saved.
81
+ *
82
+ * Searching the editor body first also scopes ids per editor rather than first-match-in-document.
83
+ * @param editor The inline editor to patch.
84
+ */
85
+ function bridgeEditorLookups(editor) {
86
+ const dom = editor.dom;
87
+ const nativeGet = dom.get.bind(dom);
88
+ dom.get = (elm) => {
89
+ if (typeof elm !== 'string')
90
+ return nativeGet(elm);
91
+ const body = editor.getBody();
92
+ if (!body)
93
+ return nativeGet(elm);
94
+ // Inline mode's body is the target element itself, so it can be the id being asked for.
95
+ if (body.id === elm)
96
+ return body;
97
+ return body.querySelector(`#${CSS.escape(elm)}`) ?? nativeGet(elm);
98
+ };
99
+ }
100
+ /**
101
+ * Applies the shadow-DOM bridges to a freshly initialized inline editor. Call for inline editors only.
102
+ * @param editor The inline editor to bridge.
103
+ */
104
+ export function bridgeInlineEditor(editor) {
105
+ const body = editor.getBody();
106
+ if (!body)
107
+ return;
108
+ bridgeEditorLookups(editor);
109
+ inlineBodies.add(body);
110
+ installBridges();
111
+ editor.on('remove', () => inlineBodies.delete(body));
112
+ }
@@ -1,6 +1,6 @@
1
1
  import type { UmbStylesheetRule } from '../../types.js';
2
2
  import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
3
- declare const UmbStylesheetRuleInputElement_base: (new (...args: any[]) => import("@umbraco-ui/uui-base").UUIFormControlMixinElement<FormDataEntryValue | FormData>) & typeof UmbLitElement;
3
+ declare const UmbStylesheetRuleInputElement_base: (new (...args: any[]) => import("@umbraco-ui/uui").UUIFormControlWithBasicsMixinElement<FormDataEntryValue | FormData>) & typeof UmbLitElement;
4
4
  export declare class UmbStylesheetRuleInputElement extends UmbStylesheetRuleInputElement_base {
5
5
  #private;
6
6
  rules: UmbStylesheetRule[];