@malva-ui/editor 0.1.14 → 0.2.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.
@@ -1,7 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, Injectable, inject, computed, ElementRef, DestroyRef, Directive, input, viewChild, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, untracked, Injector, viewChildren, TemplateRef, model, PLATFORM_ID, output, contentChildren, forwardRef } from '@angular/core';
2
+ import { InjectionToken, signal, Injectable, computed, inject, DestroyRef, ElementRef, Directive, input, viewChild, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, TemplateRef, ViewContainerRef, PLATFORM_ID, afterNextRender, untracked, afterRenderEffect, Injector, viewChildren, model, output, contentChildren, forwardRef } from '@angular/core';
3
3
  import { LiveAnnouncer } from '@angular/cdk/a11y';
4
4
  import { MLV_EDITOR_I18N, MlvI18nResolverService, MLV_FILE_UPLOAD_I18N } from '@malva-ui/i18n';
5
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
+ import { Subscription, fromEvent, merge, take, delay } from 'rxjs';
5
7
  import { closeHistory } from '@tiptap/pm/history';
6
8
  import { Node as Node$1, Slice, Fragment } from '@tiptap/pm/model';
7
9
  import { PluginKey, Plugin, TextSelection, NodeSelection } from '@tiptap/pm/state';
@@ -16,12 +18,14 @@ import { MlvMenuTrigger, MlvMenu, MlvMenuItem } from '@malva-ui/core/menu';
16
18
  import { MlvPopup, MlvPopupContainer, MlvPopupContent, MlvPopupTrigger } from '@malva-ui/core/popup';
17
19
  import { MlvRadio, MlvRadioGroup } from '@malva-ui/core/radio';
18
20
  import { MlvTooltip } from '@malva-ui/core/tooltip';
19
- import { getExtensionField, flattenExtensions, findDuplicates, Extension, generateHTML, getMarkRange, Editor } from '@tiptap/core';
20
- import { MlvRtlService, MlvResizeObserverService, mlvNextId, MlvFade, MlvSpacer } from '@malva-ui/cdk/utils';
21
+ import { getExtensionField, flattenExtensions, findDuplicates, posToDOMRect, Extension, generateHTML, getMarkRange, Editor } from '@tiptap/core';
22
+ import { MlvRtlService, MlvResizeObserverService, mlvMirrorInlineOffsets, mlvNextId, MlvFade, MlvSpacer } from '@malva-ui/cdk/utils';
21
23
  import { MlvSelectionService, MlvSignalFormControlBase, MlvFormControlWrapper, MlvFormControlWrapperControl, MlvHint, MlvLabel, MlvMessage, MLV_FORM_CONTROL } from '@malva-ui/core/form-utils';
22
24
  import { MlvToolbar } from '@malva-ui/core/toolbar';
23
25
  import { MlvDivider } from '@malva-ui/core/divider';
24
26
  import { Markdown } from '@tiptap/markdown';
27
+ import { Overlay } from '@angular/cdk/overlay';
28
+ import { TemplatePortal } from '@angular/cdk/portal';
25
29
  import { FileHandlePlugin } from '@tiptap/extension-file-handler';
26
30
  import Image from '@tiptap/extension-image';
27
31
  import { TaskList, TaskItem } from '@tiptap/extension-list';
@@ -37,7 +41,6 @@ import { MlvDialogRef, MlvDialog, MlvDialogBody, MlvDialogFooter, MlvDialogHeade
37
41
  import { MlvProgress } from '@malva-ui/core/progress';
38
42
  import { MlvFileUpload } from '@malva-ui/core/file-upload';
39
43
  import { DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW } from '@angular/cdk/keycodes';
40
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
41
44
  import { MlvDropdownPanel } from '@malva-ui/core/dropdown';
42
45
 
43
46
  /** Optional dependency-injection token for a host-provided image uploader. */
@@ -173,10 +176,10 @@ class MlvEditorToolbarRevision {
173
176
  /** @internal Increments after every Tiptap transaction or selection update. */
174
177
  revision = signal(0, /* @ts-ignore */
175
178
  ...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
176
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRevision, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
177
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRevision });
179
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRevision, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
180
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRevision });
178
181
  }
179
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRevision, decorators: [{
182
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRevision, decorators: [{
180
183
  type: Injectable
181
184
  }] });
182
185
  /** @internal Supplies the built-in toolbar's per-editor invalidation signal. */
@@ -212,15 +215,41 @@ class MlvEditorToolbarRovingRegistry {
212
215
  this._disconnectObserver();
213
216
  };
214
217
  }
215
- /** @internal Resolves the single effective tab stop, even during DOM mutations. */
218
+ /**
219
+ * @internal Resolves the single effective tab stop, even during DOM mutations.
220
+ *
221
+ * Called once per registered widget on every invalidation, so it does no DOM
222
+ * work of its own: the enabled set and its order come from
223
+ * `_enabledWidgets()`, which is derived once per generation and carries the
224
+ * `_domStateRevision` dependency that keeps this reactive.
225
+ */
216
226
  isActive(widget) {
217
- this._domStateRevision();
227
+ const enabled = this._enabledWidgets();
218
228
  const active = this._active();
219
- const effectiveActive = active && this._enabled(active)
220
- ? active
221
- : (this._enabledWidgets()[0] ?? null);
222
- return (!this._disabled() && effectiveActive === widget && this._enabled(widget));
229
+ const effectiveActive = active && enabled.includes(active) ? active : (enabled[0] ?? null);
230
+ return (!this._disabled() &&
231
+ effectiveActive === widget &&
232
+ enabled.includes(widget));
223
233
  }
234
+ /**
235
+ * @internal Promotes a widget to the tab stop, gated on a **live** DOM read
236
+ * rather than on `_enabledWidgets()`.
237
+ *
238
+ * Deliberate, and the only place the two readings differ. `activate()` runs
239
+ * from a `focus` / `pointerdown` handler, so the question it has to answer is
240
+ * "is this widget interactive at this instant" — the live read is that
241
+ * question, and it is never wrong. The memo can only lag the DOM inside the
242
+ * window between a DOM write and the observer's microtask, and no input event
243
+ * is dispatched in that window, so the two agree today; keeping the live read
244
+ * means they still agree if some future code writes an attribute and calls
245
+ * `activate()` in the same turn, where the memo would be a generation behind
246
+ * and would refuse a widget the user can see is enabled.
247
+ *
248
+ * `move()` and `focusBoundary()` take the memo instead because they need the
249
+ * *order* of the enabled set, and an order assembled from N independent live
250
+ * reads is exactly what the memo exists to prevent. Do not "harmonise" the
251
+ * two: they are asking different questions.
252
+ */
224
253
  activate(widget) {
225
254
  if (this._widgets().includes(widget) && this._enabled(widget)) {
226
255
  this._hasInteracted = true;
@@ -273,14 +302,42 @@ class MlvEditorToolbarRovingRegistry {
273
302
  !widget.hasAttribute('disabled') &&
274
303
  widget.getAttribute('aria-disabled') !== 'true');
275
304
  }
276
- _enabledWidgets() {
305
+ /**
306
+ * @private Registered widgets that are currently interactive, in DOM order.
307
+ *
308
+ * Derived once per invalidation generation rather than once per widget.
309
+ * `isActive()` runs for every registered widget — 21 on the built-in
310
+ * toolbar — and Angular settles one `disabled` transition in about two
311
+ * change-detection passes, so the enabled order was assembled roughly
312
+ * `2N + 2` times, each assembly walking the DOM with `closest('[hidden]')`
313
+ * once per widget. The shape is therefore `(≈2N + c) derivations × N walks`,
314
+ * not the plain `N^2` an earlier draft of this comment claimed — `N^2` at
315
+ * N=21 is 441, and the measured cost is about twice that.
316
+ *
317
+ * Measured on the built-in toolbar at N=21, over `disabled` false → true:
318
+ * the code this replaced took 39 derivations / 841 walks; this file with only
319
+ * the `computed()` removed takes 44 / 924; as written it takes 1 / 21. The
320
+ * two pre-fix figures differ because the old `isActive()` short-circuited on
321
+ * a still-enabled `_active` and paid single `_enabled()` reads outside the
322
+ * derivations. 924 is the one the spec's guard is written against, since that
323
+ * ablation isolates this `computed()` and changes nothing else.
324
+ *
325
+ * `_domStateRevision` is what makes the memo correct. The enabled set is a
326
+ * DOM read, and the `MutationObserver` that bumps that signal is the only
327
+ * thing that tells the graph the DOM moved — the same dependency `isActive()`
328
+ * already relied on. Every consumer now shares one snapshot per generation
329
+ * instead of each re-reading the DOM at a different point in the same pass.
330
+ */
331
+ _enabledWidgets = computed(() => {
332
+ this._domStateRevision();
277
333
  return this._widgets()
278
334
  .filter((widget) => this._enabled(widget))
279
335
  .sort((left, right) => {
280
336
  const position = left.compareDocumentPosition(right);
281
337
  return position & 4 ? -1 : position & 2 ? 1 : 0;
282
338
  });
283
- }
339
+ }, /* @ts-ignore */
340
+ ...(ngDevMode ? [{ debugName: "_enabledWidgets" }] : /* istanbul ignore next */ []));
284
341
  _observeStateChanges() {
285
342
  if (this._observer ||
286
343
  typeof MutationObserver === 'undefined' ||
@@ -289,6 +346,21 @@ class MlvEditorToolbarRovingRegistry {
289
346
  !(this._root instanceof Node)) {
290
347
  return;
291
348
  }
349
+ // No debounce, deliberately, and the durable reason is the memo below, not
350
+ // the batching: after it, a redundant invalidation costs exactly one
351
+ // derivation — 21 `closest('[hidden]')` walks on the built-in toolbar — so
352
+ // even a coalescer that did merge something would be buying ~nothing, and
353
+ // would pay for it by leaving the tab stop a turn stale.
354
+ //
355
+ // A `queueMicrotask` coalescer also merges nothing here, in either
356
+ // direction. Mutations written in one turn are already one callback:
357
+ // `MutationObserver` delivers once per microtask checkpoint carrying every
358
+ // record accumulated since the last delivery, so disabling the editor
359
+ // writes 22 attributes and lands here once ("coalesces a whole batch of
360
+ // observed mutations into one invalidation" pins that). Mutations spread
361
+ // across separate microtasks are not merged either — each already lands in
362
+ // its own turn, which is precisely where a per-turn debounce flushes.
363
+ // The cost that was worth removing was the fan-out below.
292
364
  this._observer = new MutationObserver(() => {
293
365
  this._domStateRevision.update((revision) => revision + 1);
294
366
  this._synchronizeActiveWidget();
@@ -300,9 +372,23 @@ class MlvEditorToolbarRovingRegistry {
300
372
  subtree: true,
301
373
  });
302
374
  }
375
+ /**
376
+ * @private Re-resolves `_active` when the widget holding the tab stop is no
377
+ * longer part of the enabled set.
378
+ *
379
+ * Reactive-graph note: this reads `_enabledWidgets()`, where it used to read
380
+ * a non-reactive `_enabled(active)`, so `MlvEditorToolbarRoot`'s effect —
381
+ * which reaches here through `setDisabled()` — now tracks `_widgets` and
382
+ * `_domStateRevision` on *both* branches, not just on the fall-through into
383
+ * `_focusFirstWidget()`. The effect therefore re-runs on every widget
384
+ * registration and every observed mutation. That is deliberate and cheap
385
+ * post-memo (a re-run costs one cached memo read), and it does not loop:
386
+ * `_active` converges in at most two runs, and the effect already both read
387
+ * and wrote `_active` before this change.
388
+ */
303
389
  _synchronizeActiveWidget() {
304
390
  const active = this._active();
305
- if (active && this._enabled(active))
391
+ if (active && this._enabledWidgets().includes(active))
306
392
  return;
307
393
  this._focusFirstWidget();
308
394
  }
@@ -313,14 +399,28 @@ class MlvEditorToolbarRovingRegistry {
313
399
  this._observer?.disconnect();
314
400
  this._observer = undefined;
315
401
  }
316
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRovingRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
317
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRovingRegistry });
402
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRovingRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
403
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRovingRegistry });
318
404
  }
319
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRovingRegistry, decorators: [{
405
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRovingRegistry, decorators: [{
320
406
  type: Injectable
321
407
  }] });
322
408
  /** @internal Resolves the nearest editor's projected-toolbar roving registry. */
323
409
  const MLV_EDITOR_TOOLBAR_ROVING = new InjectionToken('MLV_EDITOR_TOOLBAR_ROVING');
410
+ /**
411
+ * @internal Focuses the editor content with its selection. `view.focus()`
412
+ * alone moves no DOM focus while the view is not editable (`readonly`), so the
413
+ * contenteditable root is focused first; ProseMirror then writes the selection
414
+ * back to the DOM.
415
+ */
416
+ function mlvEditorFocusContent(editor) {
417
+ if (editor.isDestroyed)
418
+ return;
419
+ if (!editor.view.editable) {
420
+ editor.view.dom.focus({ preventScroll: true });
421
+ }
422
+ editor.view.focus();
423
+ }
324
424
  /**
325
425
  * Internal per-editor registry for CDK overlay roots owned by toolbar controls.
326
426
  * It is intentionally excluded from the public barrel: public controls use it
@@ -330,6 +430,14 @@ class MlvEditorOverlayRegistry {
330
430
  _entries = new Map();
331
431
  _onFocusIn;
332
432
  _onFocusOut;
433
+ /**
434
+ * @private Net for the whole registry. Each entry is released the moment its
435
+ * overlay is removed — a destroy-scoped lifetime alone would hold every
436
+ * overlay ever opened — but this guarantees release even if a consumer never
437
+ * calls `destroy()` or `closeAll()`. The registry is provided by the editor
438
+ * component, so this ref is the editor's.
439
+ */
440
+ _destroyRef = inject(DestroyRef);
333
441
  /** @internal Wires the nearest editor's composite focus handlers. */
334
442
  setFocusHandlers(onFocusIn, onFocusOut) {
335
443
  this._onFocusIn = onFocusIn;
@@ -342,12 +450,17 @@ class MlvEditorOverlayRegistry {
342
450
  existing.close = close;
343
451
  return () => this._remove(root);
344
452
  }
345
- const onFocusIn = (event) => this._onFocusIn?.(event);
346
- const onFocusOut = (event) => this._onFocusOut?.(event);
347
- const entry = { root, close, onFocusIn, onFocusOut };
348
- this._entries.set(root, entry);
349
- root.addEventListener('focusin', onFocusIn, true);
350
- root.addEventListener('focusout', onFocusOut, true);
453
+ // Capture phase, matching the editor host's own boundary listeners: an
454
+ // overlay's content may stop focus events on its way back up, and the
455
+ // composite has to see the crossing regardless.
456
+ const listeners = new Subscription();
457
+ listeners.add(fromEvent(root, 'focusin', { capture: true })
458
+ .pipe(takeUntilDestroyed(this._destroyRef))
459
+ .subscribe((event) => this._onFocusIn?.(event)));
460
+ listeners.add(fromEvent(root, 'focusout', { capture: true })
461
+ .pipe(takeUntilDestroyed(this._destroyRef))
462
+ .subscribe((event) => this._onFocusOut?.(event)));
463
+ this._entries.set(root, { root, close, listeners });
351
464
  return () => this._remove(root);
352
465
  }
353
466
  /** @internal Whether a node is rendered in an editor-owned overlay. */
@@ -370,6 +483,45 @@ class MlvEditorOverlayRegistry {
370
483
  }
371
484
  }
372
485
  }
486
+ /**
487
+ * @internal Asks every owned overlay except `keep` to close and leaves each
488
+ * entry for its owner to release, unlike `closeAll()`: an entry with nothing
489
+ * to close (the AI review bar) keeps its focus listeners.
490
+ */
491
+ closeOthers(keep) {
492
+ for (const entry of [...this._entries.values()]) {
493
+ if (entry.root === keep)
494
+ continue;
495
+ try {
496
+ entry.close();
497
+ }
498
+ catch {
499
+ // A third-party overlay must not keep the others open.
500
+ }
501
+ }
502
+ }
503
+ /**
504
+ * @internal A toolbar surface is going away because the editor turned
505
+ * `readonly` (#498): the docked band, which the next render removes, or the
506
+ * selection bubble's pane, which it hides. Asks every owned overlay except
507
+ * `surface` to close, like `closeOthers()`, and keeps focus in the editor:
508
+ * focus in `surface`, or in a popup (a registered root portaled into an
509
+ * overlay pane that `content` does not share; inside a dialog it does), moves
510
+ * to the content through `focusContent` first, while both still exist, so a
511
+ * closing popup finds focus outside its pane and hands nothing back to a
512
+ * trigger in `surface`. Focus anywhere else (the content, the status row,
513
+ * the AI review bar) stays where it is.
514
+ */
515
+ closeForReadonly(surface, content, focusContent) {
516
+ const active = surface.ownerDocument.activeElement;
517
+ if (active &&
518
+ (surface.contains(active) ||
519
+ (this.contains(active) &&
520
+ !(active.closest('.cdk-overlay-pane')?.contains(content) ?? true)))) {
521
+ focusContent();
522
+ }
523
+ this.closeOthers(surface);
524
+ }
373
525
  /** @internal Removes listener resources without invoking overlay close callbacks. */
374
526
  destroy() {
375
527
  for (const entry of [...this._entries.values()])
@@ -380,13 +532,12 @@ class MlvEditorOverlayRegistry {
380
532
  if (!entry)
381
533
  return;
382
534
  this._entries.delete(root);
383
- entry.root.removeEventListener('focusin', entry.onFocusIn, true);
384
- entry.root.removeEventListener('focusout', entry.onFocusOut, true);
535
+ entry.listeners.unsubscribe();
385
536
  }
386
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorOverlayRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
387
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorOverlayRegistry });
537
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorOverlayRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
538
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorOverlayRegistry });
388
539
  }
389
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorOverlayRegistry, decorators: [{
540
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorOverlayRegistry, decorators: [{
390
541
  type: Injectable
391
542
  }] });
392
543
  /** @internal Injection token for editor-owned overlay registration. */
@@ -414,10 +565,10 @@ class MlvEditorUploadAbortRegistry {
414
565
  }
415
566
  }
416
567
  }
417
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorUploadAbortRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
418
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorUploadAbortRegistry });
568
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorUploadAbortRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
569
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorUploadAbortRegistry });
419
570
  }
420
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorUploadAbortRegistry, decorators: [{
571
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorUploadAbortRegistry, decorators: [{
421
572
  type: Injectable
422
573
  }] });
423
574
  /** @internal Injection token for the editor-owned upload abort registry. */
@@ -2472,10 +2623,10 @@ class MlvEditorAiContext {
2472
2623
  const text = editor.getText();
2473
2624
  return text.length > 0 ? text : null;
2474
2625
  }
2475
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2476
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiContext });
2626
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2627
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiContext });
2477
2628
  }
2478
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiContext, decorators: [{
2629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiContext, decorators: [{
2479
2630
  type: Injectable
2480
2631
  }] });
2481
2632
  /** Injects the AI command state belonging to the nearest `mlv-editor`. */
@@ -2504,10 +2655,10 @@ class MlvEditorToolbarWidget {
2504
2655
  _activateOnPointerDown() {
2505
2656
  this._registry.activate(this._element);
2506
2657
  }
2507
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarWidget, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2508
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorToolbarWidget, isStandalone: true, selector: "[mlvEditorToolbarWidget]", host: { listeners: { "focus": "_activate()", "pointerdown": "_activateOnPointerDown()" }, properties: { "attr.tabindex": "_tabIndex()" } }, ngImport: i0 });
2658
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarWidget, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2659
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorToolbarWidget, isStandalone: true, selector: "[mlvEditorToolbarWidget]", host: { listeners: { "focus": "_activate()", "pointerdown": "_activateOnPointerDown()" }, properties: { "attr.tabindex": "_tabIndex()" } }, ngImport: i0 });
2509
2660
  }
2510
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarWidget, decorators: [{
2661
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarWidget, decorators: [{
2511
2662
  type: Directive,
2512
2663
  args: [{
2513
2664
  selector: '[mlvEditorToolbarWidget]',
@@ -2540,6 +2691,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
2540
2691
  * resolves no provider (presence), and its actions disable — without hiding —
2541
2692
  * while the editor is readonly or disabled or a transform is already running
2542
2693
  * (executability), matching the toolbar's presence-vs-executability rule.
2694
+ * Projected into `mlv-editor`'s toolbar it is not rendered at all while the
2695
+ * editor is `readonly`, which renders no toolbar (#498).
2543
2696
  */
2544
2697
  class MlvEditorAiMenu {
2545
2698
  /**
@@ -2759,10 +2912,10 @@ class MlvEditorAiMenu {
2759
2912
  this._unregisterMenu?.();
2760
2913
  this._unregisterMenu = undefined;
2761
2914
  }
2762
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
2763
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorAiMenu, isStandalone: true, selector: "mlv-editor-ai-menu", inputs: { actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, showCustomPrompt: { classPropertyName: "showCustomPrompt", publicName: "showCustomPrompt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!_hasProvider()" }, classAttribute: "mlv-editor-ai-menu" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_menuTrigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }, { propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_triggerElement", first: true, predicate: ["_triggerElement"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_instructionInput", first: true, predicate: ["_instructionInput"], descendants: true, isSignal: true }, { propertyName: "_panel", first: true, predicate: ["_panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- The trigger is declared once and rendered by whichever branch applies:\n `mlv-popup-container` requires a popup content child, so it cannot wrap the\n trigger when the custom prompt is dropped. -->\n<ng-template #_trigger>\n <button\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvMenuTrigger]=\"_menu\"\n [menuTriggerDisabled]=\"_disabled() || _running()\"\n [attr.aria-label]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n [mlvTooltip]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n (click)=\"_onTriggerClick()\"\n (menuOpened)=\"_registerMenuOverlay()\"\n (menuClosed)=\"_unregisterMenuOverlay()\"\n >\n @if (_running()) {\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n } @else {\n <svg mlvButtonIcon lucideSparkles aria-hidden=\"true\" />\n }\n </button>\n</ng-template>\n\n@if (showCustomPrompt()) {\n<mlv-popup-container class=\"mlv-editor-ai-menu__anchor\">\n <ng-container [ngTemplateOutlet]=\"_trigger\" />\n\n <mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [position]=\"['bottom-start', 'top-start', 'bottom-end', 'top-end']\"\n [ariaLabel]=\"_copy().aiCustom\"\n (afterOpened)=\"_onPromptOpened()\"\n (afterClosed)=\"_onPromptClosed()\"\n >\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-ai-menu__panel\">\n <mlv-input\n #_instructionInput\n [label]=\"_copy().aiCustom\"\n [placeholder]=\"_copy().aiPromptPlaceholder\"\n [disabled]=\"_disabled()\"\n [value]=\"_instruction()\"\n (valueChange)=\"_instruction.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-radio-group\n class=\"mlv-editor-ai-menu__output\"\n [label]=\"_copy().aiOutputMode\"\n [value]=\"_output()\"\n (valueChange)=\"_onOutputChange($event)\"\n >\n <mlv-radio value=\"replace-selection\" [disabled]=\"_disabled()\">\n {{ _copy().aiReplaceSelection }}\n </mlv-radio>\n <mlv-radio value=\"insert-below\" [disabled]=\"_disabled()\">\n {{ _copy().aiInsertBelow }}\n </mlv-radio>\n <mlv-radio value=\"review\" [disabled]=\"_disabled()\">\n {{ _copy().aiReviewChanges }}\n </mlv-radio>\n </mlv-radio-group>\n <div class=\"mlv-editor-ai-menu__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_applyDisabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().aiApply }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n (click)=\"_closePrompt()\"\n >\n {{ _copy().aiCancel }}\n </button>\n </div>\n </div>\n </ng-template>\n </mlv-popup>\n</mlv-popup-container>\n} @else {\n<ng-container [ngTemplateOutlet]=\"_trigger\" />\n}\n\n<mlv-menu #_menu [label]=\"_copy().aiMenu\">\n <!-- Tracked by index: a host-authored list may repeat a kind, so the kind\n is not an identity. -->\n @for (action of _actions(); track $index) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_runAction(action)\"\n >\n {{ action.label }}\n </mlv-list-item>\n } @if (showCustomPrompt()) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_openPrompt()\"\n >\n {{ _copy().aiCustom }}\n </mlv-list-item>\n }\n</mlv-menu>\n", styles: ["@layer mlv.components{.mlv-editor-ai-menu{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-menu *{box-sizing:border-box}.mlv-editor-ai-menu,.mlv-editor-ai-menu__anchor{display:inline-flex}.mlv-editor-ai-menu__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-ai-menu__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideCircleStop, selector: "svg[lucideCircleStop], svg[lucideStopCircle]" }, { kind: "component", type: LucideSparkles, selector: "svg[lucideSparkles], svg[lucideStars]" }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "component", type: MlvPopupContainer, selector: "mlv-popup-container" }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "component", type: MlvRadio, selector: "mlv-radio", inputs: ["ariaLabel", "ariaLabelledBy", "value", "disabled"] }, { kind: "component", type: MlvRadioGroup, selector: "mlv-radio-group", inputs: ["value", "name"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2915
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
2916
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorAiMenu, isStandalone: true, selector: "mlv-editor-ai-menu", inputs: { actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, showCustomPrompt: { classPropertyName: "showCustomPrompt", publicName: "showCustomPrompt", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!_hasProvider()" }, classAttribute: "mlv-editor-ai-menu" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_menuTrigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }, { propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_triggerElement", first: true, predicate: ["_triggerElement"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_instructionInput", first: true, predicate: ["_instructionInput"], descendants: true, isSignal: true }, { propertyName: "_panel", first: true, predicate: ["_panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- The trigger is declared once and rendered by whichever branch applies:\n `mlv-popup-container` requires a popup content child, so it cannot wrap the\n trigger when the custom prompt is dropped. -->\n<ng-template #_trigger>\n <button\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvMenuTrigger]=\"_menu\"\n [menuTriggerDisabled]=\"_disabled() || _running()\"\n [attr.aria-label]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n [mlvTooltip]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n (click)=\"_onTriggerClick()\"\n (menuOpened)=\"_registerMenuOverlay()\"\n (menuClosed)=\"_unregisterMenuOverlay()\"\n >\n @if (_running()) {\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n } @else {\n <svg mlvButtonIcon lucideSparkles aria-hidden=\"true\" />\n }\n </button>\n</ng-template>\n\n@if (showCustomPrompt()) {\n <mlv-popup-container class=\"mlv-editor-ai-menu__anchor\">\n <ng-container [ngTemplateOutlet]=\"_trigger\" />\n\n <mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [position]=\"['bottom-start', 'top-start', 'bottom-end', 'top-end']\"\n [ariaLabel]=\"_copy().aiCustom\"\n (afterOpened)=\"_onPromptOpened()\"\n (afterClosed)=\"_onPromptClosed()\"\n >\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-ai-menu__panel\">\n <mlv-input\n #_instructionInput\n [label]=\"_copy().aiCustom\"\n [placeholder]=\"_copy().aiPromptPlaceholder\"\n [disabled]=\"_disabled()\"\n [value]=\"_instruction()\"\n (valueChange)=\"_instruction.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-radio-group\n class=\"mlv-editor-ai-menu__output\"\n [label]=\"_copy().aiOutputMode\"\n [value]=\"_output()\"\n (valueChange)=\"_onOutputChange($event)\"\n >\n <mlv-radio value=\"replace-selection\" [disabled]=\"_disabled()\">\n {{ _copy().aiReplaceSelection }}\n </mlv-radio>\n <mlv-radio value=\"insert-below\" [disabled]=\"_disabled()\">\n {{ _copy().aiInsertBelow }}\n </mlv-radio>\n <mlv-radio value=\"review\" [disabled]=\"_disabled()\">\n {{ _copy().aiReviewChanges }}\n </mlv-radio>\n </mlv-radio-group>\n <div class=\"mlv-editor-ai-menu__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_applyDisabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().aiApply }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n (click)=\"_closePrompt()\"\n >\n {{ _copy().aiCancel }}\n </button>\n </div>\n </div>\n </ng-template>\n </mlv-popup>\n </mlv-popup-container>\n} @else {\n <ng-container [ngTemplateOutlet]=\"_trigger\" />\n}\n\n<mlv-menu #_menu [label]=\"_copy().aiMenu\">\n <!-- Tracked by index: a host-authored list may repeat a kind, so the kind\n is not an identity. -->\n @for (action of _actions(); track $index) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_runAction(action)\"\n >\n {{ action.label }}\n </mlv-list-item>\n }\n @if (showCustomPrompt()) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_openPrompt()\"\n >\n {{ _copy().aiCustom }}\n </mlv-list-item>\n }\n</mlv-menu>\n", styles: ["@layer mlv.components{.mlv-editor-ai-menu{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-menu *{box-sizing:border-box}.mlv-editor-ai-menu,.mlv-editor-ai-menu__anchor{display:inline-flex}.mlv-editor-ai-menu__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-ai-menu__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideCircleStop, selector: "svg[lucideCircleStop], svg[lucideStopCircle]" }, { kind: "component", type: LucideSparkles, selector: "svg[lucideSparkles], svg[lucideStars]" }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "component", type: MlvPopupContainer, selector: "mlv-popup-container" }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "component", type: MlvRadio, selector: "mlv-radio", inputs: ["ariaLabel", "ariaLabelledBy", "value", "disabled"] }, { kind: "component", type: MlvRadioGroup, selector: "mlv-radio-group", inputs: ["value", "name"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2764
2917
  }
2765
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiMenu, decorators: [{
2918
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiMenu, decorators: [{
2766
2919
  type: Component,
2767
2920
  args: [{ selector: 'mlv-editor-ai-menu', imports: [
2768
2921
  NgTemplateOutlet,
@@ -2785,7 +2938,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
2785
2938
  ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
2786
2939
  class: 'mlv-editor-ai-menu',
2787
2940
  '[hidden]': '!_hasProvider()',
2788
- }, template: "<!-- The trigger is declared once and rendered by whichever branch applies:\n `mlv-popup-container` requires a popup content child, so it cannot wrap the\n trigger when the custom prompt is dropped. -->\n<ng-template #_trigger>\n <button\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvMenuTrigger]=\"_menu\"\n [menuTriggerDisabled]=\"_disabled() || _running()\"\n [attr.aria-label]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n [mlvTooltip]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n (click)=\"_onTriggerClick()\"\n (menuOpened)=\"_registerMenuOverlay()\"\n (menuClosed)=\"_unregisterMenuOverlay()\"\n >\n @if (_running()) {\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n } @else {\n <svg mlvButtonIcon lucideSparkles aria-hidden=\"true\" />\n }\n </button>\n</ng-template>\n\n@if (showCustomPrompt()) {\n<mlv-popup-container class=\"mlv-editor-ai-menu__anchor\">\n <ng-container [ngTemplateOutlet]=\"_trigger\" />\n\n <mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [position]=\"['bottom-start', 'top-start', 'bottom-end', 'top-end']\"\n [ariaLabel]=\"_copy().aiCustom\"\n (afterOpened)=\"_onPromptOpened()\"\n (afterClosed)=\"_onPromptClosed()\"\n >\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-ai-menu__panel\">\n <mlv-input\n #_instructionInput\n [label]=\"_copy().aiCustom\"\n [placeholder]=\"_copy().aiPromptPlaceholder\"\n [disabled]=\"_disabled()\"\n [value]=\"_instruction()\"\n (valueChange)=\"_instruction.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-radio-group\n class=\"mlv-editor-ai-menu__output\"\n [label]=\"_copy().aiOutputMode\"\n [value]=\"_output()\"\n (valueChange)=\"_onOutputChange($event)\"\n >\n <mlv-radio value=\"replace-selection\" [disabled]=\"_disabled()\">\n {{ _copy().aiReplaceSelection }}\n </mlv-radio>\n <mlv-radio value=\"insert-below\" [disabled]=\"_disabled()\">\n {{ _copy().aiInsertBelow }}\n </mlv-radio>\n <mlv-radio value=\"review\" [disabled]=\"_disabled()\">\n {{ _copy().aiReviewChanges }}\n </mlv-radio>\n </mlv-radio-group>\n <div class=\"mlv-editor-ai-menu__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_applyDisabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().aiApply }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n (click)=\"_closePrompt()\"\n >\n {{ _copy().aiCancel }}\n </button>\n </div>\n </div>\n </ng-template>\n </mlv-popup>\n</mlv-popup-container>\n} @else {\n<ng-container [ngTemplateOutlet]=\"_trigger\" />\n}\n\n<mlv-menu #_menu [label]=\"_copy().aiMenu\">\n <!-- Tracked by index: a host-authored list may repeat a kind, so the kind\n is not an identity. -->\n @for (action of _actions(); track $index) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_runAction(action)\"\n >\n {{ action.label }}\n </mlv-list-item>\n } @if (showCustomPrompt()) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_openPrompt()\"\n >\n {{ _copy().aiCustom }}\n </mlv-list-item>\n }\n</mlv-menu>\n", styles: ["@layer mlv.components{.mlv-editor-ai-menu{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-menu *{box-sizing:border-box}.mlv-editor-ai-menu,.mlv-editor-ai-menu__anchor{display:inline-flex}.mlv-editor-ai-menu__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-ai-menu__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"] }]
2941
+ }, template: "<!-- The trigger is declared once and rendered by whichever branch applies:\n `mlv-popup-container` requires a popup content child, so it cannot wrap the\n trigger when the custom prompt is dropped. -->\n<ng-template #_trigger>\n <button\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvMenuTrigger]=\"_menu\"\n [menuTriggerDisabled]=\"_disabled() || _running()\"\n [attr.aria-label]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n [mlvTooltip]=\"_running() ? _copy().aiCancel : _copy().aiMenu\"\n (click)=\"_onTriggerClick()\"\n (menuOpened)=\"_registerMenuOverlay()\"\n (menuClosed)=\"_unregisterMenuOverlay()\"\n >\n @if (_running()) {\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n } @else {\n <svg mlvButtonIcon lucideSparkles aria-hidden=\"true\" />\n }\n </button>\n</ng-template>\n\n@if (showCustomPrompt()) {\n <mlv-popup-container class=\"mlv-editor-ai-menu__anchor\">\n <ng-container [ngTemplateOutlet]=\"_trigger\" />\n\n <mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [position]=\"['bottom-start', 'top-start', 'bottom-end', 'top-end']\"\n [ariaLabel]=\"_copy().aiCustom\"\n (afterOpened)=\"_onPromptOpened()\"\n (afterClosed)=\"_onPromptClosed()\"\n >\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-ai-menu__panel\">\n <mlv-input\n #_instructionInput\n [label]=\"_copy().aiCustom\"\n [placeholder]=\"_copy().aiPromptPlaceholder\"\n [disabled]=\"_disabled()\"\n [value]=\"_instruction()\"\n (valueChange)=\"_instruction.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-radio-group\n class=\"mlv-editor-ai-menu__output\"\n [label]=\"_copy().aiOutputMode\"\n [value]=\"_output()\"\n (valueChange)=\"_onOutputChange($event)\"\n >\n <mlv-radio value=\"replace-selection\" [disabled]=\"_disabled()\">\n {{ _copy().aiReplaceSelection }}\n </mlv-radio>\n <mlv-radio value=\"insert-below\" [disabled]=\"_disabled()\">\n {{ _copy().aiInsertBelow }}\n </mlv-radio>\n <mlv-radio value=\"review\" [disabled]=\"_disabled()\">\n {{ _copy().aiReviewChanges }}\n </mlv-radio>\n </mlv-radio-group>\n <div class=\"mlv-editor-ai-menu__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_applyDisabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().aiApply }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n (click)=\"_closePrompt()\"\n >\n {{ _copy().aiCancel }}\n </button>\n </div>\n </div>\n </ng-template>\n </mlv-popup>\n </mlv-popup-container>\n} @else {\n <ng-container [ngTemplateOutlet]=\"_trigger\" />\n}\n\n<mlv-menu #_menu [label]=\"_copy().aiMenu\">\n <!-- Tracked by index: a host-authored list may repeat a kind, so the kind\n is not an identity. -->\n @for (action of _actions(); track $index) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_runAction(action)\"\n >\n {{ action.label }}\n </mlv-list-item>\n }\n @if (showCustomPrompt()) {\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_itemsDisabled()\"\n (itemClick)=\"_openPrompt()\"\n >\n {{ _copy().aiCustom }}\n </mlv-list-item>\n }\n</mlv-menu>\n", styles: ["@layer mlv.components{.mlv-editor-ai-menu{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-menu *{box-sizing:border-box}.mlv-editor-ai-menu,.mlv-editor-ai-menu__anchor{display:inline-flex}.mlv-editor-ai-menu__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-ai-menu__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"] }]
2789
2942
  }], ctorParameters: () => [], propDecorators: { actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], showCustomPrompt: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomPrompt", required: false }] }], _menu: [{ type: i0.ViewChild, args: ['_menu', { isSignal: true }] }], _menuTrigger: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MlvMenuTrigger), { isSignal: true }] }], _popup: [{ type: i0.ViewChild, args: ['_popup', { isSignal: true }] }], _triggerElement: [{ type: i0.ViewChild, args: ['_triggerElement', { ...{
2790
2943
  read: ElementRef,
2791
2944
  }, isSignal: true }] }], _instructionInput: [{ type: i0.ViewChild, args: ['_instructionInput', { isSignal: true }] }], _panel: [{ type: i0.ViewChild, args: ['_panel', { isSignal: true }] }] } });
@@ -2983,10 +3136,10 @@ class MlvEditorAiReviewBar {
2983
3136
  _rejectAll() {
2984
3137
  this._ai?.rejectAll();
2985
3138
  }
2986
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiReviewBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
2987
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorAiReviewBar, isStandalone: true, selector: "mlv-editor-ai-review-bar", host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "_copy().aiReviewBar", "hidden": "!_visible()" }, classAttribute: "mlv-editor-ai-review-bar" }, ngImport: i0, template: "@if (_running()) {\n<button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n class=\"mlv-editor-ai-review-bar__stop\"\n [disabled]=\"_disabled()\"\n [mlvTooltip]=\"_copy().aiStopGeneration\"\n (click)=\"_stop()\"\n>\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n {{ _copy().aiStopGeneration }}\n</button>\n} @if (_reviewing()) {\n<span class=\"mlv-editor-ai-review-bar__count\">{{ _countText() }}</span>\n<!-- Screen-reader-only description of the current suggestion: the removed\n text exists only in the aria-hidden strikethrough widget, so the\n accept/reject buttons point here via aria-describedby. -->\n<span\n class=\"mlv-editor-ai-review-bar__current-description\"\n [id]=\"_descriptionId\"\n >{{ _currentDescription() }}</span\n>\n<div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiPreviousSuggestion\"\n [mlvTooltip]=\"_copy().aiPreviousSuggestion\"\n (click)=\"_previous()\"\n >\n <svg mlvButtonIcon lucideChevronLeft aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiNextSuggestion\"\n [mlvTooltip]=\"_copy().aiNextSuggestion\"\n (click)=\"_next()\"\n >\n <svg mlvButtonIcon lucideChevronRight aria-hidden=\"true\" />\n </button>\n</div>\n<div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiAcceptSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiAcceptSuggestion\"\n (click)=\"_acceptCurrent()\"\n >\n <svg mlvButtonIcon lucideCheck aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiRejectSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiRejectSuggestion\"\n (click)=\"_rejectCurrent()\"\n >\n <svg mlvButtonIcon lucideX aria-hidden=\"true\" />\n </button>\n</div>\n<div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_acceptAll()\"\n >\n {{ _copy().aiAcceptAll }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_rejectAll()\"\n >\n {{ _copy().aiRejectAll }}\n </button>\n</div>\n}\n", styles: ["@layer mlv.components{.mlv-editor-ai-review-bar{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-review-bar *{box-sizing:border-box}.mlv-editor-ai-review-bar{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--mlv-spacing-2);font-size:var(--mlv-typography-body-s-size)}.mlv-editor-ai-review-bar[hidden]{display:none}.mlv-editor-ai-review-bar__count{color:var(--mlv-text-secondary);white-space:nowrap}.mlv-editor-ai-review-bar__group{display:inline-flex;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-ai-review-bar__stop{white-space:nowrap}.mlv-editor-ai-review-bar__current-description{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;padding:0;margin:-1px}}\n"], dependencies: [{ kind: "component", type: LucideCheck, selector: "svg[lucideCheck]" }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }, { kind: "component", type: LucideCircleStop, selector: "svg[lucideCircleStop], svg[lucideStopCircle]" }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiReviewBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
3140
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorAiReviewBar, isStandalone: true, selector: "mlv-editor-ai-review-bar", host: { attributes: { "role": "group" }, properties: { "attr.aria-label": "_copy().aiReviewBar", "hidden": "!_visible()" }, classAttribute: "mlv-editor-ai-review-bar" }, ngImport: i0, template: "@if (_running()) {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n class=\"mlv-editor-ai-review-bar__stop\"\n [disabled]=\"_disabled()\"\n (click)=\"_stop()\"\n >\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n {{ _copy().aiStopGeneration }}\n </button>\n}\n@if (_reviewing()) {\n <span class=\"mlv-editor-ai-review-bar__count\">{{ _countText() }}</span>\n <!-- Screen-reader-only description of the current suggestion: the removed\n text exists only in the aria-hidden strikethrough widget, so the\n accept/reject buttons point here via aria-describedby. -->\n <span\n class=\"mlv-editor-ai-review-bar__current-description\"\n [id]=\"_descriptionId\"\n >{{ _currentDescription() }}</span\n >\n <div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiPreviousSuggestion\"\n [mlvTooltip]=\"_copy().aiPreviousSuggestion\"\n (click)=\"_previous()\"\n >\n <svg mlvButtonIcon lucideChevronLeft aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiNextSuggestion\"\n [mlvTooltip]=\"_copy().aiNextSuggestion\"\n (click)=\"_next()\"\n >\n <svg mlvButtonIcon lucideChevronRight aria-hidden=\"true\" />\n </button>\n </div>\n <div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiAcceptSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiAcceptSuggestion\"\n (click)=\"_acceptCurrent()\"\n >\n <svg mlvButtonIcon lucideCheck aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiRejectSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiRejectSuggestion\"\n (click)=\"_rejectCurrent()\"\n >\n <svg mlvButtonIcon lucideX aria-hidden=\"true\" />\n </button>\n </div>\n <div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_acceptAll()\"\n >\n {{ _copy().aiAcceptAll }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_rejectAll()\"\n >\n {{ _copy().aiRejectAll }}\n </button>\n </div>\n}\n", styles: ["@layer mlv.components{.mlv-editor-ai-review-bar{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-review-bar *{box-sizing:border-box}.mlv-editor-ai-review-bar{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--mlv-spacing-2);font-size:var(--mlv-typography-body-s-size)}.mlv-editor-ai-review-bar[hidden]{display:none}.mlv-editor-ai-review-bar__count{color:var(--mlv-text-secondary);white-space:nowrap}.mlv-editor-ai-review-bar__group{display:inline-flex;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-ai-review-bar__stop{white-space:nowrap}.mlv-editor-ai-review-bar__current-description{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;padding:0;margin:-1px}}\n"], dependencies: [{ kind: "component", type: LucideCheck, selector: "svg[lucideCheck]" }, { kind: "component", type: LucideChevronLeft, selector: "svg[lucideChevronLeft]" }, { kind: "component", type: LucideChevronRight, selector: "svg[lucideChevronRight]" }, { kind: "component", type: LucideCircleStop, selector: "svg[lucideCircleStop], svg[lucideStopCircle]" }, { kind: "component", type: LucideX, selector: "svg[lucideX]" }, { kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2988
3141
  }
2989
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiReviewBar, decorators: [{
3142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAiReviewBar, decorators: [{
2990
3143
  type: Component,
2991
3144
  args: [{ selector: 'mlv-editor-ai-review-bar', imports: [
2992
3145
  LucideCheck,
@@ -3002,7 +3155,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
3002
3155
  role: 'group',
3003
3156
  '[attr.aria-label]': '_copy().aiReviewBar',
3004
3157
  '[hidden]': '!_visible()',
3005
- }, template: "@if (_running()) {\n<button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n class=\"mlv-editor-ai-review-bar__stop\"\n [disabled]=\"_disabled()\"\n [mlvTooltip]=\"_copy().aiStopGeneration\"\n (click)=\"_stop()\"\n>\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n {{ _copy().aiStopGeneration }}\n</button>\n} @if (_reviewing()) {\n<span class=\"mlv-editor-ai-review-bar__count\">{{ _countText() }}</span>\n<!-- Screen-reader-only description of the current suggestion: the removed\n text exists only in the aria-hidden strikethrough widget, so the\n accept/reject buttons point here via aria-describedby. -->\n<span\n class=\"mlv-editor-ai-review-bar__current-description\"\n [id]=\"_descriptionId\"\n >{{ _currentDescription() }}</span\n>\n<div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiPreviousSuggestion\"\n [mlvTooltip]=\"_copy().aiPreviousSuggestion\"\n (click)=\"_previous()\"\n >\n <svg mlvButtonIcon lucideChevronLeft aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiNextSuggestion\"\n [mlvTooltip]=\"_copy().aiNextSuggestion\"\n (click)=\"_next()\"\n >\n <svg mlvButtonIcon lucideChevronRight aria-hidden=\"true\" />\n </button>\n</div>\n<div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiAcceptSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiAcceptSuggestion\"\n (click)=\"_acceptCurrent()\"\n >\n <svg mlvButtonIcon lucideCheck aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiRejectSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiRejectSuggestion\"\n (click)=\"_rejectCurrent()\"\n >\n <svg mlvButtonIcon lucideX aria-hidden=\"true\" />\n </button>\n</div>\n<div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_acceptAll()\"\n >\n {{ _copy().aiAcceptAll }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_rejectAll()\"\n >\n {{ _copy().aiRejectAll }}\n </button>\n</div>\n}\n", styles: ["@layer mlv.components{.mlv-editor-ai-review-bar{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-review-bar *{box-sizing:border-box}.mlv-editor-ai-review-bar{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--mlv-spacing-2);font-size:var(--mlv-typography-body-s-size)}.mlv-editor-ai-review-bar[hidden]{display:none}.mlv-editor-ai-review-bar__count{color:var(--mlv-text-secondary);white-space:nowrap}.mlv-editor-ai-review-bar__group{display:inline-flex;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-ai-review-bar__stop{white-space:nowrap}.mlv-editor-ai-review-bar__current-description{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;padding:0;margin:-1px}}\n"] }]
3158
+ }, template: "@if (_running()) {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n class=\"mlv-editor-ai-review-bar__stop\"\n [disabled]=\"_disabled()\"\n (click)=\"_stop()\"\n >\n <svg mlvButtonIcon lucideCircleStop aria-hidden=\"true\" />\n {{ _copy().aiStopGeneration }}\n </button>\n}\n@if (_reviewing()) {\n <span class=\"mlv-editor-ai-review-bar__count\">{{ _countText() }}</span>\n <!-- Screen-reader-only description of the current suggestion: the removed\n text exists only in the aria-hidden strikethrough widget, so the\n accept/reject buttons point here via aria-describedby. -->\n <span\n class=\"mlv-editor-ai-review-bar__current-description\"\n [id]=\"_descriptionId\"\n >{{ _currentDescription() }}</span\n >\n <div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiPreviousSuggestion\"\n [mlvTooltip]=\"_copy().aiPreviousSuggestion\"\n (click)=\"_previous()\"\n >\n <svg mlvButtonIcon lucideChevronLeft aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiNextSuggestion\"\n [mlvTooltip]=\"_copy().aiNextSuggestion\"\n (click)=\"_next()\"\n >\n <svg mlvButtonIcon lucideChevronRight aria-hidden=\"true\" />\n </button>\n </div>\n <div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiAcceptSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiAcceptSuggestion\"\n (click)=\"_acceptCurrent()\"\n >\n <svg mlvButtonIcon lucideCheck aria-hidden=\"true\" />\n </button>\n <button\n mlvButton\n type=\"button\"\n shape=\"square\"\n variant=\"transparent\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().aiRejectSuggestion\"\n [attr.aria-describedby]=\"_descriptionId\"\n [mlvTooltip]=\"_copy().aiRejectSuggestion\"\n (click)=\"_rejectCurrent()\"\n >\n <svg mlvButtonIcon lucideX aria-hidden=\"true\" />\n </button>\n </div>\n <div class=\"mlv-editor-ai-review-bar__group\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_acceptAll()\"\n >\n {{ _copy().aiAcceptAll }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n mlvDensity=\"tight\"\n [disabled]=\"_disabled()\"\n (click)=\"_rejectAll()\"\n >\n {{ _copy().aiRejectAll }}\n </button>\n </div>\n}\n", styles: ["@layer mlv.components{.mlv-editor-ai-review-bar{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-ai-review-bar *{box-sizing:border-box}.mlv-editor-ai-review-bar{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--mlv-spacing-2);font-size:var(--mlv-typography-body-s-size)}.mlv-editor-ai-review-bar[hidden]{display:none}.mlv-editor-ai-review-bar__count{color:var(--mlv-text-secondary);white-space:nowrap}.mlv-editor-ai-review-bar__group{display:inline-flex;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-ai-review-bar__stop{white-space:nowrap}.mlv-editor-ai-review-bar__current-description{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;padding:0;margin:-1px}}\n"] }]
3006
3159
  }], ctorParameters: () => [] });
3007
3160
 
3008
3161
  /**
@@ -3238,18 +3391,793 @@ function preflightMlvEditorExtensions(extensions, initialFormat) {
3238
3391
  }
3239
3392
  }
3240
3393
 
3394
+ /**
3395
+ * @internal Resolves an `MlvEditor` sizing input to the value its custom
3396
+ * property receives, or `null` to write nothing.
3397
+ *
3398
+ * A finite number is px, matching `mlv-popup`. A string passes through
3399
+ * verbatim once trimmed, so any CSS length, `calc()`, `min()` or viewport unit
3400
+ * works. `undefined`, `null`, a blank string and a non-finite number all
3401
+ * resolve to `null`, so the host binding removes the property and the
3402
+ * stylesheet's `var()` fallback applies.
3403
+ */
3404
+ function mlvEditorCssLength(value) {
3405
+ if (typeof value === 'number') {
3406
+ return Number.isFinite(value) ? `${value}px` : null;
3407
+ }
3408
+ if (typeof value === 'string') {
3409
+ const trimmed = value.trim();
3410
+ return trimmed === '' ? null : trimmed;
3411
+ }
3412
+ return null;
3413
+ }
3414
+ /** @internal ProseMirror's own default `scrollMargin`, in px (prosemirror-view `scrollRectIntoView`). */
3415
+ const MLV_EDITOR_SCROLL_MARGIN = 5;
3416
+ /**
3417
+ * @internal ProseMirror `scrollMargin` / `scrollThreshold` for a toolbar that
3418
+ * obscures `obscured` px on its own side (WCAG 2.2 SC 2.4.11). The threshold is
3419
+ * the obscured extent: ProseMirror scrolls only once the caret enters it. The
3420
+ * margin leaves the default 5px beyond it. Every other side keeps
3421
+ * ProseMirror's defaults.
3422
+ */
3423
+ function mlvEditorScrollSides(kind, position, obscured) {
3424
+ const base = kind === 'margin' ? MLV_EDITOR_SCROLL_MARGIN : 0;
3425
+ const edge = base + Math.max(0, Math.ceil(obscured));
3426
+ return {
3427
+ top: position === 'top' ? edge : base,
3428
+ right: base,
3429
+ bottom: position === 'bottom' ? edge : base,
3430
+ left: base,
3431
+ };
3432
+ }
3433
+ /**
3434
+ * @internal A scroll-prop object whose sides re-resolve on every read.
3435
+ * ProseMirror reads `value[side]` lazily each time it scrolls a rect into view
3436
+ * (`getSide()` in prosemirror-view), so one stable object follows density,
3437
+ * configuration and consumer CSS offsets without `setProps` churn. Only the
3438
+ * toolbar's side measures anything.
3439
+ */
3440
+ function createMlvEditorLiveScrollSides(kind, position, obscured) {
3441
+ const base = kind === 'margin' ? MLV_EDITOR_SCROLL_MARGIN : 0;
3442
+ const side = (which) => position() === which
3443
+ ? mlvEditorScrollSides(kind, which, obscured())[which]
3444
+ : base;
3445
+ return {
3446
+ get top() {
3447
+ return side('top');
3448
+ },
3449
+ get right() {
3450
+ return base;
3451
+ },
3452
+ get bottom() {
3453
+ return side('bottom');
3454
+ },
3455
+ get left() {
3456
+ return base;
3457
+ },
3458
+ };
3459
+ }
3460
+ /**
3461
+ * @internal The part of `rect` inside `boundary`, or `null` once none of it
3462
+ * is. Edges count as inside, so a zero-size caret lying on the boundary is
3463
+ * still visible.
3464
+ *
3465
+ * The selection bubble anchors on this: while a capped viewport scrolls, it
3466
+ * follows the visible part of the selection, and when the selection has left
3467
+ * the viewport (or the window) it hides instead of floating over content the
3468
+ * selection is no longer in.
3469
+ */
3470
+ function mlvEditorVisibleRect(rect, boundary) {
3471
+ const top = Math.max(rect.top, boundary.top);
3472
+ const bottom = Math.min(rect.bottom, boundary.bottom);
3473
+ const left = Math.max(rect.left, boundary.left);
3474
+ const right = Math.min(rect.right, boundary.right);
3475
+ return top <= bottom && left <= right ? { top, bottom, left, right } : null;
3476
+ }
3477
+ /**
3478
+ * @internal Which side of `anchor` the selection bubble opens on: above,
3479
+ * unless a bubble `height` px tall, `gap` px from the anchor, would cross the
3480
+ * boundary's top edge (the window, or a capped scroller's edge).
3481
+ *
3482
+ * CDK's own fallback flips only on the window's edges; a capped viewport's top
3483
+ * edge is not one it knows about, so the preference is decided here and CDK
3484
+ * keeps the other side as its fallback.
3485
+ */
3486
+ function mlvEditorBubblePlacement(anchor, boundary, height, gap) {
3487
+ return anchor.top - gap - height >= boundary.top ? 'above' : 'below';
3488
+ }
3489
+
3490
+ /** @internal Gap between the selection and the bubble, in px. */
3491
+ const MLV_EDITOR_BUBBLE_GAP = 8;
3492
+ /** @internal Closest the bubble comes to a window edge, in px. */
3493
+ const MLV_EDITOR_BUBBLE_VIEWPORT_MARGIN = 8;
3494
+ /** @internal Pane class; the bubble's BEM block. */
3495
+ const MLV_EDITOR_BUBBLE_CLASS = 'mlv-editor-bubble';
3496
+ /** @internal Pane modifier while the bubble is off screen. */
3497
+ const MLV_EDITOR_BUBBLE_HIDDEN_CLASS = 'mlv-editor-bubble--hidden';
3498
+ /** @internal Pane modifier while CDK resolved the below position. */
3499
+ const MLV_EDITOR_BUBBLE_BELOW_CLASS = 'mlv-editor-bubble--below';
3500
+ /** @internal Centred above the selection. */
3501
+ const MLV_EDITOR_BUBBLE_ABOVE = {
3502
+ originX: 'center',
3503
+ originY: 'top',
3504
+ overlayX: 'center',
3505
+ overlayY: 'bottom',
3506
+ offsetY: -MLV_EDITOR_BUBBLE_GAP,
3507
+ };
3508
+ /** @internal Centred below the selection. */
3509
+ const MLV_EDITOR_BUBBLE_BELOW = {
3510
+ originX: 'center',
3511
+ originY: 'bottom',
3512
+ overlayX: 'center',
3513
+ overlayY: 'top',
3514
+ offsetY: MLV_EDITOR_BUBBLE_GAP,
3515
+ };
3516
+ /** @internal Above first; CDK falls back below on a window edge. */
3517
+ const MLV_EDITOR_BUBBLE_ABOVE_FIRST = [
3518
+ MLV_EDITOR_BUBBLE_ABOVE,
3519
+ MLV_EDITOR_BUBBLE_BELOW,
3520
+ ];
3521
+ /** @internal Below first, once above would cross the boundary. */
3522
+ const MLV_EDITOR_BUBBLE_BELOW_FIRST = [
3523
+ MLV_EDITOR_BUBBLE_BELOW,
3524
+ MLV_EDITOR_BUBBLE_ABOVE,
3525
+ ];
3526
+ /** @internal Elements a press may focus; a press anywhere else keeps the caret. */
3527
+ const MLV_EDITOR_BUBBLE_FOCUSABLE = 'button, a[href], input, select, textarea, [tabindex], [contenteditable="true"]';
3528
+ /**
3529
+ * @internal Renders its template in a CDK overlay that floats over the
3530
+ * nearest `mlv-editor`'s selection: `toolbarAppearance="floating"`.
3531
+ *
3532
+ * The contents are whatever the template holds, so the same host can carry a
3533
+ * different set of controls later; `MlvEditor` stamps its toolbar template.
3534
+ * The overlay is created once and kept attached, hidden by a pane modifier,
3535
+ * so the controls keep their state and their narrow-mode measurement between
3536
+ * appearances instead of being re-created per selection. The one exception is
3537
+ * a `readonly` flip: `MlvEditor` stamps no toolbar into the template while
3538
+ * `readonly` (#498), so the pane is empty then and the controls are
3539
+ * re-created on the way back.
3540
+ *
3541
+ * Shown only while all of these hold: focus is in the editor composite, the
3542
+ * selection is non-empty (or Alt+F10 summoned it at the caret), no pointer is
3543
+ * still selecting, no drag or IME composition is in progress, the editor is
3544
+ * enabled and not `readonly`, and the selection is visible inside the window
3545
+ * and, for a capped editor, inside its scrolling viewport. It prefers above
3546
+ * the selection and flips below only when above would cross that boundary's
3547
+ * top edge. Turning `readonly` on while it is shown hides it and closes every
3548
+ * popup the editor owns; focus inside either returns to the content.
3549
+ *
3550
+ * Keyboard: Alt+F10 in the content shows it and moves focus to its first
3551
+ * control through the toolbar's roving registry; Escape dismisses it and
3552
+ * returns focus to the content with the selection intact; Tab leaves it back
3553
+ * into the content rather than for the end of the document, where the portal
3554
+ * sits. Appearing never moves focus.
3555
+ *
3556
+ * Escape and Alt+F10 in the content go through a ProseMirror plugin, not a DOM
3557
+ * listener: ProseMirror cancels every Escape it sees, so a listener behind it
3558
+ * would find each one already `defaultPrevented`, and one in front of it
3559
+ * would take Escape from a content popup. The plugin offers both keys to
3560
+ * every plugin registered after it and acts only when none claims them, so a
3561
+ * consumer's `@tiptap/suggestion` list or an AI stream still gets Escape
3562
+ * first. A dismissing Escape stops propagating, so a dialog or drawer around
3563
+ * the editor stays open; the next one reaches it. While `readonly` the bubble
3564
+ * claims neither key.
3565
+ */
3566
+ class MlvEditorBubble {
3567
+ /** The editor's content viewport: the listener scope and the capped boundary. */
3568
+ viewport = input.required(/* @ts-ignore */
3569
+ ...(ngDevMode ? [{ debugName: "viewport" }] : /* istanbul ignore next */ []));
3570
+ /** Whether the viewport is the editor's scroll container, bounding where the selection counts as visible. */
3571
+ capped = input(false, { ...(ngDevMode ? { debugName: "capped" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
3572
+ /** @private Template rendered into the overlay pane. */
3573
+ _templateRef = inject(TemplateRef);
3574
+ /** @private Container the template view is created in, so it keeps the editor's injector. */
3575
+ _viewContainerRef = inject(ViewContainerRef);
3576
+ /** @private CDK overlay factory. */
3577
+ _overlay = inject(Overlay);
3578
+ /** @private Injected document; never the ambient global. */
3579
+ _document = inject(DOCUMENT);
3580
+ /** @private Directive lifetime; owns every listener below. */
3581
+ _destroyRef = inject(DestroyRef);
3582
+ /** @private Editor state: instance, focus, disabled and zoom. */
3583
+ _context = inject(MLV_EDITOR_TOOLBAR_CONTEXT);
3584
+ /** @private Bumped after every Tiptap transaction or selection update. */
3585
+ _revision = inject(MLV_EDITOR_TOOLBAR_REVISION, {
3586
+ optional: true,
3587
+ });
3588
+ /** @private The toolbar's roving focus; Alt+F10 enters through it. */
3589
+ _roving = inject(MLV_EDITOR_TOOLBAR_ROVING);
3590
+ /** @private Makes the pane part of the editor's composite focus while shown. */
3591
+ _overlays = inject(MLV_EDITOR_OVERLAY_REGISTRY);
3592
+ /** @private Scoped direction for the pane. */
3593
+ _rtl = inject(MlvRtlService);
3594
+ /** @private Pane size changes reposition the bubble. */
3595
+ _resizeObserver = inject(MlvResizeObserverService);
3596
+ /** @private Invalidates the render effect from DOM events. */
3597
+ _tick = signal(0, /* @ts-ignore */
3598
+ ...(ngDevMode ? [{ debugName: "_tick" }] : /* istanbul ignore next */ []));
3599
+ /** @private Identifies this instance's content-key plugin in the editor state. */
3600
+ _keyPluginKey = new PluginKey('mlvEditorBubbleKeys');
3601
+ /** @private The overlay, created after the first render in the browser. */
3602
+ _overlayRef = null;
3603
+ /** @private The overlay's position strategy, re-pointed at the selection. */
3604
+ _strategy = null;
3605
+ /** @private The position list last handed to the strategy. */
3606
+ _positions = null;
3607
+ /** @private Whether the bubble is on screen. */
3608
+ _shown = false;
3609
+ /** @private Releases the pane's composite-focus registration. */
3610
+ _unregister = null;
3611
+ /** @private Alt+F10 showed it at a bare caret; cleared when focus returns to the content. */
3612
+ _summoned = false;
3613
+ /** @private Escape dismissed it for this selection; any other selection clears it. */
3614
+ _dismissed = null;
3615
+ /** @private A primary pointer is down in the content, still selecting. */
3616
+ _pointerSelecting = false;
3617
+ /** @private A pointer is down on the bubble; covers browsers that blur on a button press. */
3618
+ _pressing = false;
3619
+ /** @private A native drag (block handle or text) is in progress. */
3620
+ _dragging = false;
3621
+ /** @private An IME composition is in progress. */
3622
+ _composing = false;
3623
+ /** @private Per-gesture listeners, replaced by the next gesture of the same kind. */
3624
+ _gestures = { select: null, press: null, drag: null };
3625
+ constructor() {
3626
+ if (!isPlatformBrowser(inject(PLATFORM_ID)))
3627
+ return;
3628
+ afterNextRender(() => this._create());
3629
+ // Registered once per editor instance and removed with this directive,
3630
+ // e.g. when `toolbarAppearance` returns to `'bar'`: left behind, it would
3631
+ // keep claiming Escape and Alt+F10 for a bubble that no longer exists.
3632
+ effect((onCleanup) => {
3633
+ const editor = this._context.editor();
3634
+ if (!editor || editor.isDestroyed)
3635
+ return;
3636
+ untracked(() => editor.registerPlugin(this._createKeyPlugin()));
3637
+ onCleanup(() => {
3638
+ if (!editor.isDestroyed)
3639
+ editor.unregisterPlugin(this._keyPluginKey);
3640
+ });
3641
+ });
3642
+ // Before the render effect below would hide the bubble, so focus in a
3643
+ // popup moves to the content while that popup still exists.
3644
+ effect(() => {
3645
+ if (!this._context.readonly())
3646
+ return;
3647
+ untracked(() => this._closeForReadonly());
3648
+ });
3649
+ afterRenderEffect(() => {
3650
+ this._tick();
3651
+ this._context.editor();
3652
+ this._context.disabled();
3653
+ this._context.readonly();
3654
+ this._context.focused();
3655
+ this._context.zoom();
3656
+ this._revision?.();
3657
+ this.capped();
3658
+ untracked(() => this._sync());
3659
+ });
3660
+ this._destroyRef.onDestroy(() => {
3661
+ for (const gesture of Object.values(this._gestures)) {
3662
+ gesture?.unsubscribe();
3663
+ }
3664
+ // Nothing is on screen once the overlay is gone.
3665
+ this._shown = false;
3666
+ this._unregister?.();
3667
+ this._unregister = null;
3668
+ this._overlayRef?.dispose();
3669
+ this._overlayRef = null;
3670
+ });
3671
+ }
3672
+ /** @private Creates the overlay and binds every listener, once. */
3673
+ _create() {
3674
+ const viewport = this.viewport();
3675
+ const direction = this._rtl.resolveDirection(viewport);
3676
+ const strategy = this._overlay
3677
+ .position()
3678
+ .flexibleConnectedTo({ x: 0, y: 0 })
3679
+ .withPositions(mlvMirrorInlineOffsets(MLV_EDITOR_BUBBLE_ABOVE_FIRST, direction))
3680
+ .withFlexibleDimensions(false)
3681
+ .withPush(true)
3682
+ .withViewportMargin(MLV_EDITOR_BUBBLE_VIEWPORT_MARGIN);
3683
+ const ref = this._overlay.create({
3684
+ positionStrategy: strategy,
3685
+ // Repositioned by `_sync()` from a document-level scroll listener, which
3686
+ // also sees the capped viewport's own scrolling; CDK's reposition
3687
+ // strategy only knows registered `cdkScrollable`s.
3688
+ scrollStrategy: this._overlay.scrollStrategies.noop(),
3689
+ direction,
3690
+ panelClass: [MLV_EDITOR_BUBBLE_CLASS, MLV_EDITOR_BUBBLE_HIDDEN_CLASS],
3691
+ hasBackdrop: false,
3692
+ });
3693
+ ref.attach(new TemplatePortal(this._templateRef, this._viewContainerRef));
3694
+ this._overlayRef = ref;
3695
+ this._strategy = strategy;
3696
+ this._positions = MLV_EDITOR_BUBBLE_ABOVE_FIRST;
3697
+ const pane = ref.overlayElement;
3698
+ strategy.positionChanges
3699
+ .pipe(takeUntilDestroyed(this._destroyRef))
3700
+ .subscribe((change) => pane.classList.toggle(MLV_EDITOR_BUBBLE_BELOW_CLASS, change.connectionPair.overlayY === 'top'));
3701
+ // Nothing re-parents an open pane, so a `[dir]` flip above the editor
3702
+ // re-mirrors it here.
3703
+ this._destroyRef.onDestroy(this._rtl.watchDirection(viewport, (next) => {
3704
+ ref.setDirection(next);
3705
+ this._positions = null;
3706
+ this._invalidate();
3707
+ }));
3708
+ this._resizeObserver
3709
+ .observe(pane)
3710
+ .pipe(takeUntilDestroyed(this._destroyRef))
3711
+ .subscribe(() => this._invalidate());
3712
+ this._bindViewportListeners(viewport);
3713
+ this._bindPaneListeners(pane);
3714
+ const window = this._document.defaultView;
3715
+ // Passive and capture: every scroll container's `scroll` reaches the
3716
+ // document in the capture phase, the capped viewport's included.
3717
+ fromEvent(this._document, 'scroll', { capture: true, passive: true })
3718
+ .pipe(takeUntilDestroyed(this._destroyRef))
3719
+ .subscribe(() => this._invalidateOnLayout());
3720
+ if (window) {
3721
+ fromEvent(window, 'resize', { passive: true })
3722
+ .pipe(takeUntilDestroyed(this._destroyRef))
3723
+ .subscribe(() => this._invalidateOnLayout());
3724
+ }
3725
+ this._sync();
3726
+ }
3727
+ /** @private Content-side gestures and keys. */
3728
+ _bindViewportListeners(viewport) {
3729
+ // Fallback behind the key plugin for ProseMirror's composition window: it
3730
+ // skips every key handler while it still counts as composing, and in
3731
+ // Safari for 500ms after `compositionend`, by when the bubble may already
3732
+ // be back. It cancels every key it does handle, so only a key it never
3733
+ // processed arrives here uncancelled. (A read-only view also skips them,
3734
+ // but the bubble claims no key there.)
3735
+ fromEvent(viewport, 'keydown')
3736
+ .pipe(takeUntilDestroyed(this._destroyRef))
3737
+ .subscribe((event) => {
3738
+ if (event.defaultPrevented)
3739
+ return;
3740
+ if (this._onContentKey(event))
3741
+ event.preventDefault();
3742
+ });
3743
+ fromEvent(viewport, 'focusin')
3744
+ .pipe(takeUntilDestroyed(this._destroyRef))
3745
+ .subscribe((event) => {
3746
+ if (!this._inContent(event.target))
3747
+ return;
3748
+ this._summoned = false;
3749
+ this._invalidate();
3750
+ });
3751
+ fromEvent(viewport, 'pointerdown')
3752
+ .pipe(takeUntilDestroyed(this._destroyRef))
3753
+ .subscribe((event) => {
3754
+ if (event.button !== 0 ||
3755
+ event.isPrimary === false ||
3756
+ !this._inContent(event.target)) {
3757
+ return;
3758
+ }
3759
+ this._pointerSelecting = true;
3760
+ this._summoned = false;
3761
+ this._invalidate();
3762
+ this._gesture('select', merge(fromEvent(this._document, 'pointerup', { capture: true }), fromEvent(this._document, 'pointercancel', { capture: true })).pipe(take(1)), () => {
3763
+ this._pointerSelecting = false;
3764
+ this._invalidate();
3765
+ });
3766
+ });
3767
+ fromEvent(viewport, 'compositionstart')
3768
+ .pipe(takeUntilDestroyed(this._destroyRef))
3769
+ .subscribe(() => {
3770
+ this._composing = true;
3771
+ this._invalidate();
3772
+ });
3773
+ fromEvent(viewport, 'compositionend')
3774
+ .pipe(takeUntilDestroyed(this._destroyRef))
3775
+ .subscribe(() => {
3776
+ this._composing = false;
3777
+ this._invalidate();
3778
+ });
3779
+ // Capture, so a drag source that stops `dragstart` still counts.
3780
+ fromEvent(viewport, 'dragstart', { capture: true })
3781
+ .pipe(takeUntilDestroyed(this._destroyRef))
3782
+ .subscribe(() => {
3783
+ this._dragging = true;
3784
+ this._invalidate();
3785
+ // `dragend` never reaches the document when the source left the DOM
3786
+ // mid-drag; the next press ends the wait instead.
3787
+ this._gesture('drag', merge(fromEvent(this._document, 'dragend', { capture: true }), fromEvent(this._document, 'drop', { capture: true }), fromEvent(this._document, 'pointerdown', { capture: true })).pipe(take(1)), () => {
3788
+ this._dragging = false;
3789
+ this._invalidate();
3790
+ });
3791
+ });
3792
+ }
3793
+ /** @private Bubble-side keys and presses. */
3794
+ _bindPaneListeners(pane) {
3795
+ fromEvent(pane, 'keydown')
3796
+ .pipe(takeUntilDestroyed(this._destroyRef))
3797
+ .subscribe((event) => {
3798
+ if (event.defaultPrevented)
3799
+ return;
3800
+ if (event.key === 'Escape') {
3801
+ event.preventDefault();
3802
+ // Kept from the page and any dialog around the editor: this Escape
3803
+ // belongs to the bubble.
3804
+ event.stopPropagation();
3805
+ this._dismiss();
3806
+ }
3807
+ else if (event.key === 'Tab') {
3808
+ // The pane is portaled to the end of the document; the next tab
3809
+ // stop after it is not the editor's.
3810
+ event.preventDefault();
3811
+ this._context.editor()?.view.focus();
3812
+ }
3813
+ });
3814
+ fromEvent(pane, 'pointerdown', { capture: true })
3815
+ .pipe(takeUntilDestroyed(this._destroyRef))
3816
+ .subscribe(() => {
3817
+ this._pressing = true;
3818
+ // Released a task after `pointerup`, so the `click` that follows
3819
+ // still finds the bubble on screen.
3820
+ this._gesture('press', merge(fromEvent(this._document, 'pointerup', { capture: true }), fromEvent(this._document, 'pointercancel', { capture: true })).pipe(take(1), delay(0)), () => {
3821
+ this._pressing = false;
3822
+ this._invalidate();
3823
+ });
3824
+ });
3825
+ // A press on the bubble's own padding keeps the caret and the selection
3826
+ // in the content; controls still take focus as usual.
3827
+ fromEvent(pane, 'mousedown')
3828
+ .pipe(takeUntilDestroyed(this._destroyRef))
3829
+ .subscribe((event) => {
3830
+ const target = event.target;
3831
+ if (target instanceof Element &&
3832
+ target.closest(MLV_EDITOR_BUBBLE_FOCUSABLE)) {
3833
+ return;
3834
+ }
3835
+ event.preventDefault();
3836
+ });
3837
+ }
3838
+ /**
3839
+ * @private The plugin carrying content-side Escape and Alt+F10. The loop is
3840
+ * what makes it the lowest-priority key handler, not its place in the list:
3841
+ * plugins before it run first anyway, and it offers each of those keys to
3842
+ * every plugin after it (an AI stream session registers its Escape claim
3843
+ * later) before acting. Once it has offered the key it acts and returns
3844
+ * `true` unconditionally, even if a declining later handler changed the
3845
+ * bubble's state meanwhile; returning `false` there would make ProseMirror
3846
+ * run those handlers a second time. Returning `true` makes ProseMirror
3847
+ * cancel the event.
3848
+ */
3849
+ _createKeyPlugin() {
3850
+ const key = this._keyPluginKey;
3851
+ return new Plugin({
3852
+ key,
3853
+ props: {
3854
+ handleKeyDown: (view, event) => {
3855
+ if (!this._ownsContentKey(event))
3856
+ return false;
3857
+ const plugins = view.state.plugins;
3858
+ const own = key.get(view.state);
3859
+ for (let index = own ? plugins.indexOf(own) + 1 : plugins.length; index < plugins.length; index++) {
3860
+ const plugin = plugins[index];
3861
+ if (plugin.props.handleKeyDown?.call(plugin, view, event)) {
3862
+ return true;
3863
+ }
3864
+ }
3865
+ this._act(event);
3866
+ return true;
3867
+ },
3868
+ },
3869
+ });
3870
+ }
3871
+ /**
3872
+ * @private Whether `event` is a key the bubble acts on right now: never
3873
+ * while disabled or `readonly`, where it cannot show.
3874
+ */
3875
+ _ownsContentKey(event) {
3876
+ if (this._context.disabled() || this._context.readonly())
3877
+ return false;
3878
+ return ((event.key === 'Escape' && this._shown) ||
3879
+ (event.key === 'F10' &&
3880
+ event.altKey &&
3881
+ !event.ctrlKey &&
3882
+ !event.metaKey &&
3883
+ !event.shiftKey));
3884
+ }
3885
+ /**
3886
+ * @private The DOM fallback's entry: acts only on a key the bubble owns.
3887
+ * Returns whether it acted; the caller cancels the event.
3888
+ */
3889
+ _onContentKey(event) {
3890
+ if (!this._ownsContentKey(event))
3891
+ return false;
3892
+ this._act(event);
3893
+ return true;
3894
+ }
3895
+ /**
3896
+ * @private Alt+F10 summons; Escape dismisses and stops there, so a dialog or
3897
+ * drawer around the editor does not close with it. Unchecked: callers
3898
+ * establish that the bubble owns the key.
3899
+ */
3900
+ _act(event) {
3901
+ if (event.key === 'Escape') {
3902
+ event.stopPropagation();
3903
+ this._dismiss();
3904
+ }
3905
+ else {
3906
+ this._summon();
3907
+ }
3908
+ }
3909
+ /** @private Shows the bubble at the caret or selection and focuses its first control. */
3910
+ _summon() {
3911
+ const ref = this._overlayRef;
3912
+ if (!ref || this._context.disabled() || !this._context.editor())
3913
+ return;
3914
+ this._summoned = true;
3915
+ this._dismissed = null;
3916
+ this._sync();
3917
+ if (!this._shown) {
3918
+ this._summoned = false;
3919
+ return;
3920
+ }
3921
+ this._roving.focusBoundary('start');
3922
+ if (!ref.overlayElement.contains(this._document.activeElement)) {
3923
+ this._summoned = false;
3924
+ this._sync();
3925
+ }
3926
+ }
3927
+ /** @private Hides the bubble for the current selection; focus returns to the content. */
3928
+ _dismiss() {
3929
+ this._dismissed = this._context.editor()?.state.selection ?? null;
3930
+ this._summoned = false;
3931
+ this._sync();
3932
+ }
3933
+ /** @private Shows, repositions or hides the bubble for the current state. */
3934
+ _sync() {
3935
+ const ref = this._overlayRef;
3936
+ const editor = this._context.editor();
3937
+ if (!ref || !editor || editor.isDestroyed || !this._wanted(editor)) {
3938
+ this._hide();
3939
+ return;
3940
+ }
3941
+ const windowRect = this._windowRect();
3942
+ const boundary = this.capped()
3943
+ ? mlvEditorVisibleRect(this.viewport().getBoundingClientRect(), windowRect)
3944
+ : windowRect;
3945
+ const selection = this._selectionRect(editor);
3946
+ const visible = boundary ? mlvEditorVisibleRect(selection, boundary) : null;
3947
+ // Focus in the bubble (or a popup it opened) keeps it on screen when the
3948
+ // selection scrolls away, pushed to the window edge; hiding it would drop
3949
+ // that focus.
3950
+ const pinned = this._summoned || !editor.view.hasFocus();
3951
+ const anchor = visible ?? (pinned ? selection : null);
3952
+ if (!anchor) {
3953
+ this._hide();
3954
+ return;
3955
+ }
3956
+ this._show(ref, anchor, boundary ?? windowRect);
3957
+ }
3958
+ /** @private Whether every visibility condition except the anchor holds. */
3959
+ _wanted(editor) {
3960
+ if (this._context.disabled() || this._context.readonly())
3961
+ return false;
3962
+ if (!this._context.focused() && !this._pressing)
3963
+ return false;
3964
+ if (this._pointerSelecting || this._dragging || this._composing) {
3965
+ return false;
3966
+ }
3967
+ const selection = editor.state.selection;
3968
+ if (this._dismissed) {
3969
+ if (selection.eq(this._dismissed))
3970
+ return false;
3971
+ this._dismissed = null;
3972
+ }
3973
+ return this._summoned || !selection.empty;
3974
+ }
3975
+ /** @private Places and reveals the bubble at `anchor`. */
3976
+ _show(ref, anchor, boundary) {
3977
+ const strategy = this._strategy;
3978
+ if (!strategy)
3979
+ return;
3980
+ const pane = ref.overlayElement;
3981
+ const positions = mlvEditorBubblePlacement(anchor, boundary, pane.getBoundingClientRect().height, MLV_EDITOR_BUBBLE_GAP) === 'above'
3982
+ ? MLV_EDITOR_BUBBLE_ABOVE_FIRST
3983
+ : MLV_EDITOR_BUBBLE_BELOW_FIRST;
3984
+ if (positions !== this._positions) {
3985
+ this._positions = positions;
3986
+ strategy.withPositions(mlvMirrorInlineOffsets(positions, ref.getDirection()));
3987
+ }
3988
+ strategy.setOrigin({
3989
+ x: anchor.left,
3990
+ y: anchor.top,
3991
+ width: anchor.right - anchor.left,
3992
+ height: anchor.bottom - anchor.top,
3993
+ });
3994
+ if (!this._shown) {
3995
+ this._shown = true;
3996
+ pane.classList.remove(MLV_EDITOR_BUBBLE_HIDDEN_CLASS);
3997
+ this._unregister = this._overlays.register(pane, () => {
3998
+ // The registry drops the entry itself after calling this.
3999
+ this._unregister = null;
4000
+ this._hide();
4001
+ });
4002
+ }
4003
+ ref.updatePosition();
4004
+ }
4005
+ /** @private Takes the bubble off screen, handing focus back to the content first. */
4006
+ _hide() {
4007
+ const ref = this._overlayRef;
4008
+ if (!ref || !this._shown)
4009
+ return;
4010
+ this._shown = false;
4011
+ const pane = ref.overlayElement;
4012
+ if (pane.contains(this._document.activeElement))
4013
+ this._focusContent();
4014
+ pane.classList.add(MLV_EDITOR_BUBBLE_HIDDEN_CLASS);
4015
+ this._unregister?.();
4016
+ this._unregister = null;
4017
+ }
4018
+ /**
4019
+ * @private `readonly` just turned on. While the bubble is shown, every popup
4020
+ * the editor owns closes (their controls no longer apply, and the ones the
4021
+ * bubble opened would float beside a hidden pane), and focus in the bubble
4022
+ * or in such a popup returns to the content before either goes away. The
4023
+ * docked bar goes through the same registry call (#498).
4024
+ */
4025
+ _closeForReadonly() {
4026
+ const ref = this._overlayRef;
4027
+ const editor = this._context.editor();
4028
+ if (!ref || !this._shown || !editor || editor.isDestroyed)
4029
+ return;
4030
+ this._overlays.closeForReadonly(ref.overlayElement, editor.view.dom, () => this._focusContent());
4031
+ this._sync();
4032
+ }
4033
+ /** @private Focuses the content with its selection, unless disabled. */
4034
+ _focusContent() {
4035
+ const editor = this._context.editor();
4036
+ if (!editor || editor.isDestroyed || this._context.disabled())
4037
+ return;
4038
+ mlvEditorFocusContent(editor);
4039
+ }
4040
+ /** @private The selection's client rect; a caret is zero-width. */
4041
+ _selectionRect(editor) {
4042
+ const { from, to } = editor.state.selection;
4043
+ try {
4044
+ const { top, bottom, left, right } = posToDOMRect(editor.view, from, to);
4045
+ return { top, bottom, left, right };
4046
+ }
4047
+ catch {
4048
+ // `coordsAtPos` throws where nothing is laid out; fall back to the
4049
+ // content's top edge.
4050
+ const rect = editor.view.dom.getBoundingClientRect();
4051
+ return {
4052
+ top: rect.top,
4053
+ bottom: rect.top,
4054
+ left: rect.left,
4055
+ right: rect.right,
4056
+ };
4057
+ }
4058
+ }
4059
+ /** @private The window's client rect. */
4060
+ _windowRect() {
4061
+ const window = this._document.defaultView;
4062
+ return {
4063
+ top: 0,
4064
+ left: 0,
4065
+ bottom: window?.innerHeight ?? 0,
4066
+ right: window?.innerWidth ?? 0,
4067
+ };
4068
+ }
4069
+ /** @private Whether `target` is inside the ProseMirror content. */
4070
+ _inContent(target) {
4071
+ const dom = this._context.editor()?.view.dom;
4072
+ return !!dom && target instanceof Node && dom.contains(target);
4073
+ }
4074
+ /**
4075
+ * @private Runs `done` on the first emission of `end`, replacing any wait
4076
+ * of the same kind still open. Per gesture, so released here and at
4077
+ * destroy rather than by `takeUntilDestroyed`, which would keep every
4078
+ * earlier generation alive.
4079
+ */
4080
+ _gesture(kind, end, done) {
4081
+ this._gestures[kind]?.unsubscribe();
4082
+ this._gestures[kind] = end.subscribe(() => {
4083
+ this._gestures[kind] = null;
4084
+ done();
4085
+ });
4086
+ }
4087
+ /** @private Schedules `_sync()` for the next render. */
4088
+ _invalidate() {
4089
+ this._tick.update((tick) => tick + 1);
4090
+ }
4091
+ /**
4092
+ * @private Scroll and resize only matter while the bubble is shown or could
4093
+ * show: summoned, or a non-empty selection that may scroll back into view.
4094
+ * A focused bare caret, and a disabled or `readonly` editor, skip the render
4095
+ * these events would schedule.
4096
+ */
4097
+ _invalidateOnLayout() {
4098
+ if (this._shown) {
4099
+ this._invalidate();
4100
+ return;
4101
+ }
4102
+ if (!untracked(this._context.focused) ||
4103
+ untracked(this._context.disabled) ||
4104
+ untracked(this._context.readonly)) {
4105
+ return;
4106
+ }
4107
+ const editor = untracked(this._context.editor);
4108
+ if (this._summoned ||
4109
+ (editor && !editor.isDestroyed && !editor.state.selection.empty)) {
4110
+ this._invalidate();
4111
+ }
4112
+ }
4113
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorBubble, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4114
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: MlvEditorBubble, isStandalone: true, selector: "ng-template[mlvEditorBubble]", inputs: { viewport: { classPropertyName: "viewport", publicName: "viewport", isSignal: true, isRequired: true, transformFunction: null }, capped: { classPropertyName: "capped", publicName: "capped", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
4115
+ }
4116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorBubble, decorators: [{
4117
+ type: Directive,
4118
+ args: [{
4119
+ selector: 'ng-template[mlvEditorBubble]',
4120
+ }]
4121
+ }], ctorParameters: () => [], propDecorators: { viewport: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewport", required: true }] }], capped: [{ type: i0.Input, args: [{ isSignal: true, alias: "capped", required: false }] }] } });
4122
+
3241
4123
  /** @internal Class of the floating drop indicator. */
3242
4124
  const MLV_EDITOR_DROP_INDICATOR_CLASS = 'mlv-editor__drop-indicator';
3243
4125
  /** @internal Class of the off-screen wrapper handed to `setDragImage`. */
3244
4126
  const MLV_EDITOR_DRAG_GHOST_CLASS = 'mlv-editor__drag-ghost';
3245
4127
  /**
3246
- * @internal Marks the editor DOM while a block drag is in flight, so the
3247
- * stylesheet — not this file — owns the partition's transition and its
3248
- * reduced-motion fallback. JavaScript writes only the `transform` itself.
4128
+ * @internal Marks the block currently on the cursor, dimmed in place. Applied
4129
+ * as a node decoration, never written onto the element — see
4130
+ * `MLV_EDITOR_DRAG_SOURCE_KEY`.
3249
4131
  */
3250
- const MLV_EDITOR_DRAGGING_CLASS = 'ProseMirror--block-dragging';
3251
- /** @internal Marks the block currently on the cursor, dimmed in place. */
3252
4132
  const MLV_EDITOR_DRAG_SOURCE_CLASS = 'mlv-editor__block--dragging';
4133
+ /**
4134
+ * @internal Plugin state holding the dimmed source's node decoration: empty
4135
+ * while no block drag is in flight, one `Decoration.node` while one is.
4136
+ *
4137
+ * The dim has to travel through ProseMirror's own render path because nothing
4138
+ * written directly onto a block's element survives a drag. ProseMirror's
4139
+ * `DOMObserver` treats an attribute mutation on any node it rendered — a class,
4140
+ * an inline style — as an external DOM change, marks that node dirty and
4141
+ * redraws it, **replacing** the element. Measured in Chromium, 22 of 25
4142
+ * top-level elements were replaced by the first `dragover`: the dim class and
4143
+ * the old partition's transforms all ended up on detached nodes, so none of it
4144
+ * was ever visible. A decoration is part of the view description instead, so a
4145
+ * redraw re-applies it rather than discarding it.
4146
+ */
4147
+ const MLV_EDITOR_DRAG_SOURCE_KEY = new PluginKey('mlvEditorBlockDragSource');
4148
+ /**
4149
+ * @internal Clears a source dim a freshly built plugin view inherited rather
4150
+ * than published.
4151
+ *
4152
+ * Plugin state outlives the plugin view. `EditorState.reconfigure` — what
4153
+ * Tiptap's `registerPlugin` / `unregisterPlugin` call, e.g. when an AI stream
4154
+ * or suggestion session settles — keeps every state field but destroys and
4155
+ * rebuilds every plugin view. A reconfigure mid-drag therefore takes the view
4156
+ * that owned the drag, and its `dragend` listener, while the dim stays in
4157
+ * state; the replacing view never saw the drag, so without this the block
4158
+ * would stay dimmed until the next document change.
4159
+ *
4160
+ * Deferred to a microtask because this runs from the view factory, inside
4161
+ * ProseMirror's `updatePluginViews`, where a dispatch would re-enter
4162
+ * `updateState` while the views are still being built. The old view's
4163
+ * `destroy` cannot clear it either: it runs inside that same update, or inside
4164
+ * `EditorView.destroy()` before `isDestroyed` turns true. Every other teardown
4165
+ * discards the state (a destroyed editor) or the field (the plugin removed)
4166
+ * along with the dim. The clear only drops the exact set inherited, so it can
4167
+ * never undo a newer drag's dim.
4168
+ */
4169
+ function clearInheritedDragSource(view) {
4170
+ const inherited = MLV_EDITOR_DRAG_SOURCE_KEY.getState(view.state);
4171
+ if (inherited === undefined || inherited === DecorationSet.empty)
4172
+ return;
4173
+ queueMicrotask(() => {
4174
+ if (view.isDestroyed)
4175
+ return;
4176
+ if (MLV_EDITOR_DRAG_SOURCE_KEY.getState(view.state) !== inherited)
4177
+ return;
4178
+ view.dispatch(view.state.tr.setMeta(MLV_EDITOR_DRAG_SOURCE_KEY, DecorationSet.empty));
4179
+ });
4180
+ }
3253
4181
  /** @internal Duration of the post-drop settle, matching `--mlv-duration-normal`. */
3254
4182
  const MLV_EDITOR_SETTLE_DURATION = 200;
3255
4183
  /** @internal Sub-pixel movement not worth animating after a drop. */
@@ -3274,13 +4202,12 @@ function insertAnchor(doc, from, to) {
3274
4202
  ? childStart(doc, to) + doc.child(to).nodeSize
3275
4203
  : childStart(doc, to);
3276
4204
  }
3277
- /** @internal Reads the live scale of the mount layer instead of trusting a CSS variable. */
3278
- function layerScale$1(mount) {
4205
+ /** @internal Measures the mount layer's frame from a single rect read. */
4206
+ function mountFrame(mount) {
4207
+ const rect = mount.getBoundingClientRect();
3279
4208
  const width = mount.offsetWidth;
3280
- if (!width)
3281
- return 1;
3282
- const scaled = mount.getBoundingClientRect().width;
3283
- return scaled > 0 ? scaled / width : 1;
4209
+ const scale = width && rect.width > 0 ? rect.width / width : 1;
4210
+ return { top: rect.top, scale };
3284
4211
  }
3285
4212
  /**
3286
4213
  * @internal Top-level child index for one rendered child of the editor DOM, or
@@ -3315,6 +4242,51 @@ function topLevelIndexOfDom(view, element) {
3315
4242
  return null;
3316
4243
  return element.contains(view.nodeDOM(childStart(doc, index))) ? index : null;
3317
4244
  }
4245
+ /**
4246
+ * @internal The hit one rendered child stands for, or null when that child is
4247
+ * not a top-level block.
4248
+ *
4249
+ * A widget decoration owns no top-level node, and its box is a sliver rather
4250
+ * than a block's. Letting one stand in for a block would pair a neighbouring
4251
+ * block's index with the widget's geometry, so `dropTargetIndex` would take
4252
+ * that block's midpoint from the wrong box and land the drop on the wrong side
4253
+ * of it — silently, in one undo step. `topLevelIndexOfDom` answering null is
4254
+ * what makes that a *category* rejection rather than a class-name test, so a
4255
+ * block-level decoration added later is covered without touching this file.
4256
+ *
4257
+ * Both edges come off one rect read, so nothing can pair a `top` from one
4258
+ * layout with a `bottom` from another.
4259
+ */
4260
+ function blockHit(view, element) {
4261
+ const index = topLevelIndexOfDom(view, element);
4262
+ if (index === null)
4263
+ return null;
4264
+ const rect = element.getBoundingClientRect();
4265
+ return { index, top: rect.top, bottom: rect.bottom };
4266
+ }
4267
+ /**
4268
+ * @internal One rendered child at or before `from` — but never before `floor` —
4269
+ * that is a top-level block, paired with its own child index. Null when that
4270
+ * span holds nothing but widgets.
4271
+ *
4272
+ * This is the only way the search below reaches a rect, and that is the point:
4273
+ * **no geometry is ever read off a child that owns no top-level node.** A
4274
+ * widget's box is a sliver where a block's is a block, and one taken out of
4275
+ * flow reports a box that says nothing about where it sits — `display: none`
4276
+ * gives an all-zero rect, and `prosemirror-gapcursor`'s shipped stylesheet
4277
+ * makes exactly that: `position: absolute; display: none`, lifted to `block`
4278
+ * only under `.ProseMirror-focused`, so a gap-cursor selection surviving a blur
4279
+ * parks an all-zero-rect child at top level. Comparing a pointer against that
4280
+ * box would drag a search past real blocks.
4281
+ */
4282
+ function blockAtOrBefore(view, children, from, floor) {
4283
+ for (let i = from; i >= floor; i -= 1) {
4284
+ const hit = blockHit(view, children[i]);
4285
+ if (hit)
4286
+ return { childIndex: i, hit };
4287
+ }
4288
+ return null;
4289
+ }
3318
4290
  /**
3319
4291
  * @internal Top-level block the given viewport y falls on, or the nearest one
3320
4292
  * above it. Null for an empty document, or for a point the view cannot map.
@@ -3327,49 +4299,85 @@ function topLevelIndexOfDom(view, element) {
3327
4299
  * test and returns null for every point outside it, in a real browser as much
3328
4300
  * as under a test DOM.
3329
4301
  *
3330
- * Rendered children lay out in document order, so the scan stops at the first
3331
- * one starting below the pointer, costing one `getBoundingClientRect()` and
3332
- * one index mapping per child down to the hovered one.
4302
+ * **Assumes the top-level blocks are vertically ordered and non-overlapping** —
4303
+ * `top` non-decreasing down the blocks, and no block's box reaching into the
4304
+ * next one's. Normal flow guarantees it for blocks, which are the only children
4305
+ * this reads a box from. Violating it (a float, `position: absolute`, a
4306
+ * negative margin on a *block*) resolves the handle to a neighbouring block
4307
+ * rather than throwing. `slotAt` assumes the same, and so did the scan this
4308
+ * replaced, which stopped at the first child starting below the pointer.
4309
+ *
4310
+ * Under that ordering the answer is *the last block starting at or above the
4311
+ * pointer*, clamped to the first block when every block starts below it. The
4312
+ * search probes child indices, but every comparison is against a **block**: a
4313
+ * probe landing on a widget resolves back to the nearest block at or before it,
4314
+ * and that block's own child index — not the widget's — bounds the next step.
4315
+ * Widget geometry therefore cannot move the search, which is the same category
4316
+ * rejection `blockHit` performs, extended to the probe itself. Pairing a
4317
+ * block's index with a widget's box is what that prevents: `dropTargetIndex`
4318
+ * would take the deciding midpoint from the wrong box and land the drop on the
4319
+ * wrong side of its neighbour — silently, in one undo step.
3333
4320
  *
3334
- * Mapping inside the loop is what makes widget decorations skippable as a
3335
- * category rather than by class name, and it is not free: `posAtDOM`,
3336
- * `nodeDOM`, and `childStart` each walk linearly to the child they address, so
3337
- * the scan down to index `k` is quadratic in `k` — measured at roughly 4x per
3338
- * doubling, against 2x for the map-only-the-winner scan this replaced. That
3339
- * one was abandoned because it could not tell a widget from a block, and
3340
- * pairing a block's index with a widget's box drops the block on the wrong
3341
- * side of its neighbour. Correctness first; the cost is only paid down to the
3342
- * *hovered* child, so it is deep hovering in a long document that degrades.
3343
- * Restoring the linear scan without losing the category skip means deferring
3344
- * the mapping until a winner is picked and walking back over the few
3345
- * consecutive widgets — the clamp above the first block is what makes that
3346
- * more than a one-liner, so it is deliberately left as follow-up.
4321
+ * Cost, against the mapping-per-child scan this replaced. `posAtDOM`, `nodeDOM`
4322
+ * and `childStart` each walk linearly to the child they address, so mapping the
4323
+ * child at index `k` is Θ(k) and scanning down to it was Θ(k²) — 500 mappings
4324
+ * and ~125k node walks for a hover at the bottom of a 500-block document, per
4325
+ * `mousemove`. Here it is Θ(log n) mappings, one per probe plus the widgets a
4326
+ * probe walks over, and ~4.5k node walks at the same size. Deferring to a
4327
+ * single mapping overall is only possible by trusting widget geometry, which
4328
+ * is the trade this deliberately refuses.
3347
4329
  */
3348
4330
  function blockAtPoint(view, clientY) {
3349
4331
  const children = view.dom.children;
3350
- let candidate = null;
3351
- for (let i = 0; i < children.length; i += 1) {
3352
- const element = children[i];
3353
- // A widget decoration owns no top-level node, and its box is a sliver
3354
- // rather than a block's. Letting one become the candidate would pair a
3355
- // neighbouring block's index with the widget's geometry, so
3356
- // `dropTargetIndex` would take that block's midpoint from the wrong box
3357
- // and land the drop on the wrong side of it — silently, in one undo step.
3358
- const index = topLevelIndexOfDom(view, element);
3359
- if (index === null)
3360
- continue;
3361
- const rect = element.getBoundingClientRect();
3362
- if (clientY < rect.top) {
3363
- // Above the first block clamps to it; in the gap between two blocks the
3364
- // preceding one was already recorded on the previous iteration.
3365
- candidate ??= { index, top: rect.top, bottom: rect.bottom };
3366
- break;
4332
+ // `high` is -1 for a view that renders no children at all, so both loops
4333
+ // below are skipped and the answer is null without a bounds check.
4334
+ let low = 0;
4335
+ let high = children.length - 1;
4336
+ let best = null;
4337
+ let bestChild = -1;
4338
+ while (low <= high) {
4339
+ const middle = (low + high) >> 1;
4340
+ const found = blockAtOrBefore(view, children, middle, low);
4341
+ if (found === null) {
4342
+ // Nothing but widgets in [low, middle]: every block still in range
4343
+ // starts after it. Discarding the span is what bounds the walk-backs.
4344
+ low = middle + 1;
4345
+ }
4346
+ else if (found.hit.top <= clientY) {
4347
+ // A candidate, and every block before it is one too; only a later block
4348
+ // can improve on it.
4349
+ best = found.hit;
4350
+ bestChild = found.childIndex;
4351
+ low = found.childIndex + 1;
3367
4352
  }
3368
- candidate = { index, top: rect.top, bottom: rect.bottom };
3369
- if (clientY <= rect.bottom)
3370
- break;
4353
+ else {
4354
+ high = found.childIndex - 1;
4355
+ }
4356
+ }
4357
+ if (best === null) {
4358
+ // Every block starts below the pointer, which is the clamp: offer the
4359
+ // first block rather than blink the handle out. Null only for a view
4360
+ // rendering no top-level block at all.
4361
+ for (let i = 0; i < children.length; i += 1) {
4362
+ const hit = blockHit(view, children[i]);
4363
+ if (hit)
4364
+ return hit;
4365
+ }
4366
+ return null;
3371
4367
  }
3372
- return candidate;
4368
+ // A pointer exactly on a block's leading edge belongs to the block above it
4369
+ // when the two boxes touch, which is where the scan this replaced ended up:
4370
+ // it broke on the first box whose `bottom` reached the pointer. The shipped
4371
+ // stylesheet keeps `0.75rem` between paragraph border boxes so nothing can
4372
+ // land here, but the answer is a silent one to change. The predecessor is
4373
+ // only resolved for a pointer exactly on `best.top`, so a well-formed
4374
+ // document pays for it on one pixel per block and never elsewhere.
4375
+ if (clientY === best.top && bestChild > 0) {
4376
+ const previous = blockAtOrBefore(view, children, bestChild - 1, 0);
4377
+ if (previous !== null && previous.hit.bottom >= clientY)
4378
+ return previous.hit;
4379
+ }
4380
+ return best;
3373
4381
  }
3374
4382
  /**
3375
4383
  * @internal Pre-move `to` index for dropping the block at `from` onto `hit`,
@@ -3390,30 +4398,32 @@ function dropTargetIndex(hit, clientY, from) {
3390
4398
  /**
3391
4399
  * @internal Measures every top-level block once, in the mount's unscaled space.
3392
4400
  *
3393
- * Taken at `dragstart` and read by everything downstream, which is a
3394
- * correctness requirement rather than an optimisation. The partition applies
3395
- * `transform` to blocks, and a transform *is* reflected in
3396
- * `getBoundingClientRect()`. Resolving the target from live rects after moving
3397
- * them would feed this plugin's own output back into its input: the gap opens,
3398
- * the pointer is now over a different block, the target changes, the gap moves.
3399
- * A snapshot taken before any transform breaks that loop.
4401
+ * Taken at `dragstart` and read by everything downstream — target resolution,
4402
+ * the indicator's `top` and the settle's "before" — so no pointer event during
4403
+ * the drag reads a block's rect. Mount-relative rather than viewport-relative
4404
+ * so drag-autoscroll cannot invalidate it: only the mount's own rect is re-read
4405
+ * per event, one layout read instead of one per block. Unscaled for the same
4406
+ * reason the handle's `top` is: a CSS length on a child of the zoom layer
4407
+ * applies in that layer's space, while `getBoundingClientRect()` reports scaled
4408
+ * screen pixels.
3400
4409
  *
3401
- * Mount-relative rather than viewport-relative so drag-autoscroll cannot
3402
- * invalidate it — only the mount's own rect is re-read per event, one layout
3403
- * read instead of one per block. Unscaled for the same reason the handle's
3404
- * `top` is: a CSS length on a child of the zoom layer applies in that layer's
3405
- * space, while `getBoundingClientRect()` reports scaled screen pixels.
4410
+ * Boxes only, deliberately **no element**. ProseMirror replaces top-level
4411
+ * elements during a drag (see `MLV_EDITOR_DRAG_SOURCE_KEY`), so an element
4412
+ * captured here is detached by the first `dragover`, and anything written to it
4413
+ * afterwards is written to nothing. Everything after `dragstart` that needs a
4414
+ * live element resolves it from a document position instead.
3406
4415
  */
3407
4416
  function snapshotBlocks(view, mount) {
3408
- const scale = layerScale$1(mount);
3409
- const mountTop = mount.getBoundingClientRect().top;
4417
+ const { top: mountTop, scale } = mountFrame(mount);
3410
4418
  const slots = [];
3411
4419
  const children = view.dom.children;
3412
4420
  for (let i = 0; i < children.length; i += 1) {
3413
4421
  const element = children[i];
3414
- // Same category check the hover scan uses: a widget decoration owns no
3415
- // top-level node and must not occupy a slot, or the partition would shift
3416
- // it as though it were a block.
4422
+ // Same category check the hover path uses: a widget decoration owns no
4423
+ // top-level node and must not occupy a slot, or its sliver of a box would
4424
+ // decide a neighbour's drop side. Mapping every child once here is what the
4425
+ // drag pays instead of the hover path's deferred single mapping — the
4426
+ // settle needs a box per block regardless.
3417
4427
  const index = topLevelIndexOfDom(view, element);
3418
4428
  if (index === null)
3419
4429
  continue;
@@ -3422,7 +4432,6 @@ function snapshotBlocks(view, mount) {
3422
4432
  index,
3423
4433
  top: (rect.top - mountTop) / scale,
3424
4434
  bottom: (rect.bottom - mountTop) / scale,
3425
- element: element,
3426
4435
  });
3427
4436
  }
3428
4437
  return slots;
@@ -3432,9 +4441,10 @@ function snapshotBlocks(view, mount) {
3432
4441
  *
3433
4442
  * Reproduces `blockAtPoint`'s clamping exactly — above the first block resolves
3434
4443
  * to the first, a point in the gap between two blocks resolves to the
3435
- * preceding one, below the last resolves to the last — but against a static
3436
- * array, so it is a binary search rather than a scan that maps every child it
3437
- * passes.
4444
+ * preceding one, below the last resolves to the last. Both are the same
4445
+ * "last block starting at or above the pointer" search on the same ordering
4446
+ * assumption; this one runs against an array the drag already mapped, so it
4447
+ * needs no widget walk-back and reads no rect at all.
3438
4448
  */
3439
4449
  function slotAt(slots, y) {
3440
4450
  if (slots.length === 0)
@@ -3444,6 +4454,9 @@ function slotAt(slots, y) {
3444
4454
  let low = 0;
3445
4455
  let high = slots.length - 1;
3446
4456
  while (low < high) {
4457
+ // `ceil`, not `floor`: this converges by raising `low` *to* the midpoint,
4458
+ // so a `floor` midpoint of `low` for an adjacent pair would set `low` to
4459
+ // itself and spin forever.
3447
4460
  const middle = Math.ceil((low + high) / 2);
3448
4461
  if (slots[middle].top <= y)
3449
4462
  low = middle;
@@ -3456,9 +4469,12 @@ function slotAt(slots, y) {
3456
4469
  * @internal Mount-space y of the boundary the given gap sits on, where `gap`
3457
4470
  * counts the `slots.length + 1` positions a block can be inserted at.
3458
4471
  *
3459
- * Deliberately the *pre-partition* boundary: the indicator is offset into the
3460
- * opened space by half the gap in CSS, so the gap's size stays a single token
3461
- * and this never has to resolve a rem into pixels.
4472
+ * An interior gap is the middle of the **natural** space between two blocks —
4473
+ * the midpoint of the previous block's bottom and the next block's top — since
4474
+ * nothing parts the blocks to preview a drop. The outer two sit on the first
4475
+ * block's top and the last block's bottom. The stylesheet centres the line on
4476
+ * this y (`translateY(-50%)`), so its own thickness never has to be resolved
4477
+ * here.
3462
4478
  */
3463
4479
  function gapOffset(slots, gap) {
3464
4480
  if (gap <= 0)
@@ -3476,9 +4492,9 @@ function gapOffset(slots, gap) {
3476
4492
  * an ancestor class chain that would have to track whichever selectors happen
3477
4493
  * to exist. Adapted from `@tiptap/extension-drag-handle`'s `cloneElement`.
3478
4494
  *
3479
- * Note what this cannot carry: computed styles are used values, and an
3480
- * ancestor `transform: scale()` never reaches them. The editor's zoom is
3481
- * applied to the wrapper instead — see `createGhostElement`.
4495
+ * Note what this cannot carry: computed styles are the element's own values
4496
+ * and never carry an ancestor's `zoom`. The editor's zoom is applied to the
4497
+ * wrapper instead — see `createGhostElement`.
3482
4498
  */
3483
4499
  function cloneWithComputedStyles(source) {
3484
4500
  const clone = source.cloneNode(true);
@@ -3513,7 +4529,8 @@ function cloneWithComputedStyles(source) {
3513
4529
  * requires — without ever being seen. `zoom` rather than `transform` carries
3514
4530
  * the editor's scale because it affects layout, so the wrapper's own box
3515
4531
  * reflects it before the browser rasterizes; a `transform` is honoured
3516
- * inconsistently across engines for drag images.
4532
+ * inconsistently across engines for drag images, and the source's computed
4533
+ * styles carry no ancestor `zoom`.
3517
4534
  */
3518
4535
  function createGhostElement(source, scale) {
3519
4536
  const wrapper = document.createElement('div');
@@ -3659,7 +4676,26 @@ const MlvEditorBlockHandle = Extension.create({
3659
4676
  const editor = this.editor;
3660
4677
  return [
3661
4678
  new Plugin({
4679
+ key: MLV_EDITOR_DRAG_SOURCE_KEY,
4680
+ state: {
4681
+ init: () => DecorationSet.empty,
4682
+ // Set and cleared only through a meta, from the plugin view (and
4683
+ // `clearInheritedDragSource`). A document change also ends the
4684
+ // drag (see `update` below), so the decoration is dropped in that
4685
+ // same transaction rather than mapped onto a block that may no
4686
+ // longer be the one on the pointer. That transaction therefore
4687
+ // never renders a set built against the previous document, and
4688
+ // `update`'s `endDrag` finds nothing left to dispatch.
4689
+ apply: (tr, decorations) => tr.getMeta(MLV_EDITOR_DRAG_SOURCE_KEY) ??
4690
+ (tr.docChanged ? DecorationSet.empty : decorations),
4691
+ },
4692
+ props: {
4693
+ decorations: (state) => MLV_EDITOR_DRAG_SOURCE_KEY.getState(state),
4694
+ },
3662
4695
  view: (view) => {
4696
+ // First, before the mount check: a rebuilt view with no mount
4697
+ // still inherits whatever dim its predecessor left in state.
4698
+ clearInheritedDragSource(view);
3663
4699
  const mount = options.mount();
3664
4700
  if (!mount)
3665
4701
  return { destroy: () => undefined };
@@ -3674,80 +4710,131 @@ const MlvEditorBlockHandle = Extension.create({
3674
4710
  // mount is a sibling of `view.dom`, so no serializer can reach it.
3675
4711
  const indicator = createIndicatorElement();
3676
4712
  mount.appendChild(indicator);
3677
- const hide = () => handle.setAttribute('data-visible', 'false');
4713
+ /**
4714
+ * What the handle currently publishes, or null while it is
4715
+ * retracted.
4716
+ *
4717
+ * Comparing against this is what keeps a pointer move that resolves
4718
+ * an unchanged hit from touching the DOM at all, and the write it
4719
+ * saves is the smaller half of the point: an attribute write on the
4720
+ * handle dirties the layout tree, so the *next* move's rect reads
4721
+ * have to force a fresh layout before they can answer. Skipping the
4722
+ * writes leaves layout clean, and a hover that stays on one block
4723
+ * then forces no layout at all.
4724
+ *
4725
+ * Keyed on the values written rather than on `hit.index`, because
4726
+ * two of the three can change while the index does not: scrolling
4727
+ * moves the block under a stationary pointer, and `label()` is a
4728
+ * signal read that answers differently on a locale change.
4729
+ */
4730
+ let published = null;
4731
+ const hide = () => {
4732
+ if (published === null)
4733
+ return;
4734
+ published = null;
4735
+ handle.setAttribute('data-visible', 'false');
4736
+ };
3678
4737
  const onMouseMove = (event) => {
3679
4738
  if (!options.enabled())
3680
4739
  return hide();
3681
4740
  const hit = blockAtPoint(view, event.clientY);
3682
4741
  if (!hit)
3683
4742
  return hide();
3684
- // `getBoundingClientRect()` reports scaled screen pixels, but a
3685
- // CSS `top` on a child of the scaled layer is applied in the
3686
- // layer's own unscaled space. Convert once, deriving the scale
3687
- // from the DOM rather than `--mlv-editor-zoom`, so the handle
3688
- // stays correct if anything else ever transforms this layer.
3689
- const scale = layerScale$1(mount);
3690
- const mountTop = mount.getBoundingClientRect().top;
3691
- handle.style.top = `${(hit.top - mountTop) / scale}px`;
3692
- handle.dataset['index'] = String(hit.index);
3693
- handle.title = options.label();
4743
+ // `getBoundingClientRect()` reports zoomed screen pixels, but a
4744
+ // CSS `top` on a child of the zoomed layer is applied in the
4745
+ // layer's own unzoomed space. Convert once, off one rect read of
4746
+ // the mount.
4747
+ const { top: mountTop, scale } = mountFrame(mount);
4748
+ const top = `${(hit.top - mountTop) / scale}px`;
4749
+ const index = String(hit.index);
4750
+ const label = options.label();
4751
+ if (published !== null &&
4752
+ published.top === top &&
4753
+ published.index === index &&
4754
+ published.label === label) {
4755
+ return;
4756
+ }
4757
+ published = { top, index, label };
4758
+ handle.style.top = top;
4759
+ handle.dataset['index'] = index;
4760
+ handle.title = label;
3694
4761
  handle.setAttribute('data-visible', 'true');
3695
4762
  };
3696
4763
  /** Top-level index being dragged, or null when no drag is active. */
3697
4764
  let source = null;
3698
- /** The dimmed element the drag started from. */
3699
- let sourceElement = null;
3700
4765
  /** Geometry measured once at `dragstart`; empty while no drag runs. */
3701
4766
  let slots = [];
3702
4767
  /** The off-screen element handed to `setDragImage`. */
3703
4768
  let ghost = null;
3704
- /** Gap the partition is currently opened at, or null when closed. */
4769
+ /** Gap the indicator currently marks, or null while it is retracted. */
3705
4770
  let gap = null;
3706
- /** Converts a viewport y into the mount's own unscaled space. */
3707
- const toMountSpace = (clientY) => (clientY - mount.getBoundingClientRect().top) / layerScale$1(mount);
4771
+ /**
4772
+ * Converts a viewport y into the mount's own unscaled space, off
4773
+ * one rect read — this runs on every `dragover`.
4774
+ */
4775
+ const toMountSpace = (clientY) => {
4776
+ const { top, scale } = mountFrame(mount);
4777
+ return (clientY - top) / scale;
4778
+ };
3708
4779
  /**
3709
4780
  * Gap a resolved target index sits on. `dropTargetIndex` folds the
3710
4781
  * `slots.length + 1` gaps onto `moveBlock`'s pre-move convention,
3711
4782
  * where `to` names the block the moved one ends up after when
3712
- * moving down; this is that fold inverted, so the previewed space
4783
+ * moving down; this is that fold inverted, so the previewed line
3713
4784
  * and the committed move come from one resolved index.
3714
4785
  */
3715
4786
  const gapForTarget = (to, from) => to <= from ? to : to + 1;
3716
- /** Opens the partition at `next`, or closes it when null. */
4787
+ /**
4788
+ * Moves the indicator to `next`, or retracts it when null. Only
4789
+ * the mount-owned line is written: nothing parts the blocks, so no
4790
+ * element ProseMirror rendered is touched during a drag.
4791
+ */
3717
4792
  const setGap = (next) => {
3718
4793
  if (gap === next)
3719
4794
  return;
3720
4795
  gap = next;
3721
- for (const slot of slots) {
3722
- // The gap is a token rather than a number here, so the size
3723
- // stays owned by the stylesheet.
3724
- slot.element.style.transform =
3725
- next !== null && slot.index >= next
3726
- ? 'translateY(var(--mlv-editor-drop-gap))'
3727
- : '';
3728
- }
3729
4796
  if (next === null) {
3730
4797
  indicator.setAttribute('data-visible', 'false');
3731
4798
  return;
3732
4799
  }
3733
- // The *pre-partition* boundary: the stylesheet offsets the line
3734
- // into the opened space by half the gap, so this never has to
3735
- // resolve the token into pixels.
3736
4800
  indicator.style.top = `${gapOffset(slots, next)}px`;
3737
4801
  indicator.setAttribute('data-visible', 'true');
3738
4802
  };
4803
+ /**
4804
+ * Publishes the source's dim, or clears it with `null`, through
4805
+ * plugin state. Skipped when nothing would change, so the several
4806
+ * paths that end one drag (`drop`, then `dragend`) dispatch once.
4807
+ */
4808
+ const setSourceDecoration = (from) => {
4809
+ const current = MLV_EDITOR_DRAG_SOURCE_KEY.getState(view.state);
4810
+ if (from === null && current === DecorationSet.empty)
4811
+ return;
4812
+ const node = from === null ? null : view.state.doc.nodeAt(from);
4813
+ const next = from === null || node === null
4814
+ ? DecorationSet.empty
4815
+ : DecorationSet.create(view.state.doc, [
4816
+ Decoration.node(from, from + node.nodeSize, {
4817
+ class: MLV_EDITOR_DRAG_SOURCE_CLASS,
4818
+ }),
4819
+ ]);
4820
+ // A meta-only transaction: no step, so nothing for history to
4821
+ // record (prosemirror-history returns its state unchanged for a
4822
+ // step-less transaction, so no `addToHistory` flag is needed)
4823
+ // and no model emission. `update` below compares documents by
4824
+ // identity, so this does not read as a document change and end
4825
+ // the drag.
4826
+ view.dispatch(view.state.tr.setMeta(MLV_EDITOR_DRAG_SOURCE_KEY, next));
4827
+ };
3739
4828
  const endDrag = () => {
3740
- for (const slot of slots)
3741
- slot.element.style.transform = '';
3742
4829
  indicator.setAttribute('data-visible', 'false');
3743
- view.dom.classList.remove(MLV_EDITOR_DRAGGING_CLASS);
3744
- sourceElement?.classList.remove(MLV_EDITOR_DRAG_SOURCE_CLASS);
3745
4830
  ghost?.remove();
3746
4831
  slots = [];
3747
4832
  gap = null;
3748
4833
  source = null;
3749
- sourceElement = null;
3750
4834
  ghost = null;
4835
+ // Last, once the closure no longer records a drag: the dispatch
4836
+ // re-enters `update`, which must not see one still in flight.
4837
+ setSourceDecoration(null);
3751
4838
  };
3752
4839
  /** Target index for the pointer, or null when the drop is a no-op. */
3753
4840
  const targetAt = (clientY) => {
@@ -3772,11 +4859,15 @@ const MlvEditorBlockHandle = Extension.create({
3772
4859
  *
3773
4860
  * Only the span between the two indices moved, so the walk is
3774
4861
  * bounded by the distance dragged rather than by document length.
4862
+ * Each element is resolved live through `nodeDOM` after the move,
4863
+ * never taken from the snapshot: ProseMirror has replaced most of
4864
+ * the elements measured at `dragstart` by now.
3775
4865
  *
3776
- * Uses the Web Animations API rather than a transition: nothing has
3777
- * to force a reflow between setting and clearing the offset, the
3778
- * animation cleans itself up, and it never collides with the inline
3779
- * `transform` the partition writes.
4866
+ * Uses the Web Animations API rather than a transition or an inline
4867
+ * `transform`: nothing has to force a reflow between setting and
4868
+ * clearing the offset, the animation cleans itself up, and it
4869
+ * writes no attribute — an inline style is a DOM mutation
4870
+ * ProseMirror would answer by redrawing the block mid-animation.
3780
4871
  */
3781
4872
  const settle = (tops, from, to) => {
3782
4873
  if (prefersReducedMotion())
@@ -3786,8 +4877,7 @@ const MlvEditorBlockHandle = Extension.create({
3786
4877
  order.splice(from, 1);
3787
4878
  order.splice(to, 0, from);
3788
4879
  const doc = view.state.doc;
3789
- const scale = layerScale$1(mount);
3790
- const mountTop = mount.getBoundingClientRect().top;
4880
+ const { top: mountTop, scale } = mountFrame(mount);
3791
4881
  const first = Math.min(from, to);
3792
4882
  const last = Math.max(from, to);
3793
4883
  for (let index = first; index <= last; index += 1) {
@@ -3824,11 +4914,18 @@ const MlvEditorBlockHandle = Extension.create({
3824
4914
  return;
3825
4915
  }
3826
4916
  const measured = snapshotBlocks(view, mount);
3827
- const slot = measured.find((candidate) => candidate.index === index);
4917
+ const from = index < view.state.doc.childCount
4918
+ ? childStart(view.state.doc, index)
4919
+ : null;
4920
+ // Resolved now, from the document position, and used only in
4921
+ // this handler — see `snapshotBlocks` for why no element is kept.
4922
+ const element = from === null ? null : view.nodeDOM(from);
3828
4923
  // The hovered index outliving its block means the snapshot and
3829
4924
  // `data-index` disagree; refusing is the same answer as a drag
3830
4925
  // that never hovered.
3831
- if (!slot) {
4926
+ if (from === null ||
4927
+ !(element instanceof HTMLElement) ||
4928
+ !measured.some((candidate) => candidate.index === index)) {
3832
4929
  event.preventDefault();
3833
4930
  return;
3834
4931
  }
@@ -3849,13 +4946,17 @@ const MlvEditorBlockHandle = Extension.create({
3849
4946
  // Cloned *before* the source is dimmed: the clone carries
3850
4947
  // resolved computed styles, so dimming first would bake the
3851
4948
  // reduced opacity into the drag image.
3852
- ghost = createGhostElement(slot.element, layerScale$1(mount));
4949
+ ghost = createGhostElement(element, mountFrame(mount).scale);
4950
+ // The pointer rides the image's inline-start edge: its right
4951
+ // edge in RTL. The offset is in CSS pixels against the image as
4952
+ // painted, and the wrapper carries the editor zoom as CSS
4953
+ // `zoom`, which `offsetWidth` leaves out (measured 475 against
4954
+ // 594 at 125%): only the rendered width puts the pointer on
4955
+ // that edge.
3853
4956
  event.dataTransfer?.setDragImage(ghost, getComputedStyle(view.dom).direction === 'rtl'
3854
- ? ghost.offsetWidth
4957
+ ? Math.round(ghost.getBoundingClientRect().width)
3855
4958
  : 0, 0);
3856
- sourceElement = slot.element;
3857
- sourceElement.classList.add(MLV_EDITOR_DRAG_SOURCE_CLASS);
3858
- view.dom.classList.add(MLV_EDITOR_DRAGGING_CLASS);
4959
+ setSourceDecoration(from);
3859
4960
  };
3860
4961
  const onDragOver = (event) => {
3861
4962
  if (source === null)
@@ -3893,8 +4994,8 @@ const MlvEditorBlockHandle = Extension.create({
3893
4994
  event.clientY > rect.bottom);
3894
4995
  };
3895
4996
  const onDragLeave = (event) => {
3896
- // Only the preview goes — the partition closes and the line
3897
- // retracts. The drag itself is still in flight, and re-entering
4997
+ // Only the line retracts; the source stays dimmed. The drag
4998
+ // itself is still in flight, and re-entering
3898
4999
  // the editor must resolve a target again rather than leave the
3899
5000
  // user dragging something that can no longer be dropped.
3900
5001
  if (source !== null && leavesMount(event))
@@ -3909,10 +5010,9 @@ const MlvEditorBlockHandle = Extension.create({
3909
5010
  // Resolved from the drop's own coordinates rather than the last
3910
5011
  // `dragover`, so the release position always wins.
3911
5012
  const to = targetAt(event.clientY);
3912
- // Captured before `endDrag` discards the snapshot. These are the
3913
- // pre-partition tops, which is exactly the FLIP's "before": the
3914
- // partition's transforms are cleared by `endDrag` in the same
3915
- // turn, so nothing the user saw shifted is measured as movement.
5013
+ // Captured before `endDrag` discards the snapshot. Nothing moves
5014
+ // a block during the drag, so the `dragstart` tops are exactly
5015
+ // the FLIP's "before".
3916
5016
  const tops = slots.map((slot) => slot.top);
3917
5017
  endDrag();
3918
5018
  if (to === null)
@@ -3924,6 +5024,27 @@ const MlvEditorBlockHandle = Extension.create({
3924
5024
  if (event.key === 'Escape' && source !== null)
3925
5025
  endDrag();
3926
5026
  };
5027
+ // These eight stay raw `addEventListener`s rather than becoming
5028
+ // `fromEvent` + `takeUntilDestroyed`, deliberately and for two
5029
+ // reasons.
5030
+ //
5031
+ // There is no `DestroyRef` to take. This file is a plain
5032
+ // ProseMirror plugin with no Angular import at all, constructed by
5033
+ // Tiptap outside any injection context, so `inject()` would throw.
5034
+ // The lifetime that matters here is not the editor component's
5035
+ // either: it is this plugin *view's*, which is torn down and
5036
+ // rebuilt whenever the editor is recreated (a format switch, an
5037
+ // extension-set change). `destroy()` below is that boundary, and it
5038
+ // unbinds all eight with the capture flags they were bound with.
5039
+ //
5040
+ // And the three capture-phase drag listeners are protocol-sensitive.
5041
+ // ProseMirror installs its own `dragover`/`drop` handlers on
5042
+ // `view.dom`, a descendant of `mount`; capturing on the ancestor is
5043
+ // what lets `onDrop` claim the drop and `stopPropagation()` before
5044
+ // ProseMirror inserts the dragged slice itself. Nothing in the unit
5045
+ // suite can drive a native HTML5 drag, so that ordering cannot be
5046
+ // pinned by a test here — and an unprovable reordering of block
5047
+ // drag-and-drop is not worth a stylistic conversion.
3927
5048
  mount.addEventListener('mousemove', onMouseMove);
3928
5049
  mount.addEventListener('mouseleave', hide);
3929
5050
  handle.addEventListener('dragstart', onDragStart);
@@ -3960,6 +5081,8 @@ const MlvEditorBlockHandle = Extension.create({
3960
5081
  // selection-only transaction carries the same reference — so
3961
5082
  // this is exactly `docChanged` at O(1), where a structural
3962
5083
  // compare would walk the whole document on every keystroke.
5084
+ // The same change has already emptied the source decoration in
5085
+ // plugin state (`apply`), so this `endDrag` dispatches nothing.
3963
5086
  if (previous.doc !== updated.state.doc && source !== null) {
3964
5087
  endDrag();
3965
5088
  }
@@ -3970,7 +5093,9 @@ const MlvEditorBlockHandle = Extension.create({
3970
5093
  // releasing the button after the revocation cannot reorder
3971
5094
  // anything. Previously the line went through `decorations`,
3972
5095
  // which read `enabled()` itself; a mount-owned element has to
3973
- // be told.
5096
+ // be told. Clearing the dim dispatches from inside `update`;
5097
+ // that re-entry finds no drag recorded and returns, so it runs
5098
+ // once.
3974
5099
  if (source !== null)
3975
5100
  endDrag();
3976
5101
  },
@@ -3988,6 +5113,8 @@ const MlvEditorBlockHandle = Extension.create({
3988
5113
  // A drag interrupted by teardown leaves its drag image on
3989
5114
  // `document.body`, outside everything else this removes.
3990
5115
  ghost?.remove();
5116
+ // The dim stays in plugin state, where this cannot dispatch;
5117
+ // see `clearInheritedDragSource`.
3991
5118
  },
3992
5119
  };
3993
5120
  },
@@ -4934,8 +6061,16 @@ class EditorImageUploadCoordinator extends MlvEditorImageUploadControl {
4934
6061
  catch {
4935
6062
  return undefined;
4936
6063
  }
4937
- const alt = typeof value['alt'] === 'string' ? value['alt'] : state.alt;
4938
- const title = typeof value['title'] === 'string' ? value['title'] : state.title;
6064
+ // The author's metadata states intent and wins; the adapter's only fills
6065
+ // what the author left out. An author `alt: ''` marks the image
6066
+ // decorative: the alt stays empty (WCAG 1.1.1) and no title is kept,
6067
+ // the author's or the adapter's, because an `alt=""` image with a title
6068
+ // is exposed as an unnamed image described by it (WCAG H67). Otherwise
6069
+ // an empty author title means no title, so the adapter's may fill it.
6070
+ const adapterAlt = typeof value['alt'] === 'string' ? value['alt'] : undefined;
6071
+ const adapterTitle = typeof value['title'] === 'string' ? value['title'] : undefined;
6072
+ const alt = state.alt ?? adapterAlt;
6073
+ const title = state.alt === '' ? undefined : state.title || adapterTitle;
4939
6074
  return {
4940
6075
  src,
4941
6076
  ...(alt === undefined ? {} : { alt }),
@@ -5196,8 +6331,8 @@ class MlvEditorAlignment {
5196
6331
  this._unregister?.();
5197
6332
  this._unregister = undefined;
5198
6333
  }
5199
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAlignment, deps: [], target: i0.ɵɵFactoryTarget.Component });
5200
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorAlignment, isStandalone: true, selector: "mlv-editor-alignment", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-alignment" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
6334
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAlignment, deps: [], target: i0.ɵɵFactoryTarget.Component });
6335
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorAlignment, isStandalone: true, selector: "mlv-editor-alignment", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-alignment" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
5201
6336
  <button
5202
6337
  mlvButton
5203
6338
  mlvEditorToolbarWidget
@@ -5229,7 +6364,7 @@ class MlvEditorAlignment {
5229
6364
  </mlv-menu>
5230
6365
  `, isInline: true, dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideAlignLeft, selector: "svg[lucideTextAlignStart], svg[lucideText], svg[lucideAlignLeft]" }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5231
6366
  }
5232
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAlignment, decorators: [{
6367
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorAlignment, decorators: [{
5233
6368
  type: Component,
5234
6369
  args: [{
5235
6370
  selector: 'mlv-editor-alignment',
@@ -5339,8 +6474,8 @@ class MlvEditorCommandButton {
5339
6474
  return;
5340
6475
  this._context.run(this.command());
5341
6476
  }
5342
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorCommandButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
5343
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: MlvEditorCommandButton, isStandalone: true, selector: "mlv-editor-command-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, supports: { classPropertyName: "supports", publicName: "supports", isSignal: true, isRequired: true, transformFunction: null }, command: { classPropertyName: "command", publicName: "command", isSignal: true, isRequired: true, transformFunction: null }, canCommand: { classPropertyName: "canCommand", publicName: "canCommand", isSignal: true, isRequired: false, transformFunction: null }, pressed: { classPropertyName: "pressed", publicName: "pressed", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!supported()" }, classAttribute: "mlv-editor-command-button" }, ngImport: i0, template: `
6477
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorCommandButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
6478
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: MlvEditorCommandButton, isStandalone: true, selector: "mlv-editor-command-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, supports: { classPropertyName: "supports", publicName: "supports", isSignal: true, isRequired: true, transformFunction: null }, command: { classPropertyName: "command", publicName: "command", isSignal: true, isRequired: true, transformFunction: null }, canCommand: { classPropertyName: "canCommand", publicName: "canCommand", isSignal: true, isRequired: false, transformFunction: null }, pressed: { classPropertyName: "pressed", publicName: "pressed", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!supported()" }, classAttribute: "mlv-editor-command-button" }, ngImport: i0, template: `
5344
6479
  <button
5345
6480
  mlvButton
5346
6481
  mlvEditorToolbarWidget
@@ -5360,7 +6495,7 @@ class MlvEditorCommandButton {
5360
6495
  </button>
5361
6496
  `, isInline: true, dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5362
6497
  }
5363
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorCommandButton, decorators: [{
6498
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorCommandButton, decorators: [{
5364
6499
  type: Component,
5365
6500
  args: [{
5366
6501
  selector: 'mlv-editor-command-button',
@@ -5418,8 +6553,8 @@ class MlvEditorBlockInsert {
5418
6553
  this._revision();
5419
6554
  return this._context.isActive(name);
5420
6555
  };
5421
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorBlockInsert, deps: [], target: i0.ɵɵFactoryTarget.Component });
5422
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorBlockInsert, isStandalone: true, selector: "mlv-editor-block-insert", host: { attributes: { "role": "group" }, classAttribute: "mlv-editor-block-insert" }, ngImport: i0, template: `
6556
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorBlockInsert, deps: [], target: i0.ɵɵFactoryTarget.Component });
6557
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorBlockInsert, isStandalone: true, selector: "mlv-editor-block-insert", host: { attributes: { "role": "group" }, classAttribute: "mlv-editor-block-insert" }, ngImport: i0, template: `
5423
6558
  <mlv-editor-command-button
5424
6559
  [label]="_copy().blockquote"
5425
6560
  [pressed]="_isActive('blockquote')"
@@ -5445,7 +6580,7 @@ class MlvEditorBlockInsert {
5445
6580
  /></mlv-editor-command-button>
5446
6581
  `, isInline: true, dependencies: [{ kind: "component", type: MlvEditorCommandButton, selector: "mlv-editor-command-button", inputs: ["label", "supports", "command", "canCommand", "pressed"] }, { kind: "component", type: LucideCode2, selector: "svg[lucideCodeXml], svg[lucideCode2]" }, { kind: "component", type: LucideMinus, selector: "svg[lucideMinus]" }, { kind: "component", type: LucideTextQuote, selector: "svg[lucideTextQuote]" }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5447
6582
  }
5448
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorBlockInsert, decorators: [{
6583
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorBlockInsert, decorators: [{
5449
6584
  type: Component,
5450
6585
  args: [{
5451
6586
  selector: 'mlv-editor-block-insert',
@@ -5642,8 +6777,8 @@ class MlvEditorHeading {
5642
6777
  this._unregister?.();
5643
6778
  this._unregister = undefined;
5644
6779
  }
5645
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorHeading, deps: [], target: i0.ɵɵFactoryTarget.Component });
5646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorHeading, isStandalone: true, selector: "mlv-editor-heading", inputs: { levels: { classPropertyName: "levels", publicName: "levels", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-heading" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
6780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorHeading, deps: [], target: i0.ɵɵFactoryTarget.Component });
6781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorHeading, isStandalone: true, selector: "mlv-editor-heading", inputs: { levels: { classPropertyName: "levels", publicName: "levels", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-heading" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
5647
6782
  <button
5648
6783
  mlvButton
5649
6784
  mlvEditorToolbarWidget
@@ -5712,7 +6847,7 @@ class MlvEditorHeading {
5712
6847
  </mlv-menu>
5713
6848
  `, isInline: true, dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideHeading, selector: "svg[lucideHeading]" }, { kind: "component", type: LucideHeading1, selector: "svg[lucideHeading1]" }, { kind: "component", type: LucideHeading2, selector: "svg[lucideHeading2]" }, { kind: "component", type: LucideHeading3, selector: "svg[lucideHeading3]" }, { kind: "component", type: LucideHeading4, selector: "svg[lucideHeading4]" }, { kind: "component", type: LucideHeading5, selector: "svg[lucideHeading5]" }, { kind: "component", type: LucideHeading6, selector: "svg[lucideHeading6]" }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5714
6849
  }
5715
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorHeading, decorators: [{
6850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorHeading, decorators: [{
5716
6851
  type: Component,
5717
6852
  args: [{
5718
6853
  selector: 'mlv-editor-heading',
@@ -5832,14 +6967,18 @@ class MlvEditorColorControl {
5832
6967
  const trigger = this._picker().triggerElement();
5833
6968
  if (!trigger)
5834
6969
  return;
6970
+ // Released from `onCleanup`, not `takeUntilDestroyed`: this effect
6971
+ // re-runs whenever the picker publishes a different trigger element, and
6972
+ // a destroy-scoped teardown would leave every superseded generation
6973
+ // subscribed for the rest of the control's life.
6974
+ const listeners = new Subscription();
5835
6975
  const activate = () => this._roving.activate(trigger);
5836
- trigger.addEventListener('focus', activate);
5837
- trigger.addEventListener('pointerdown', activate);
6976
+ listeners.add(fromEvent(trigger, 'focus').subscribe(activate));
6977
+ listeners.add(fromEvent(trigger, 'pointerdown').subscribe(activate));
5838
6978
  const unregister = untracked(() => this._roving.register(trigger));
5839
6979
  onCleanup(() => {
5840
6980
  unregister();
5841
- trigger.removeEventListener('focus', activate);
5842
- trigger.removeEventListener('pointerdown', activate);
6981
+ listeners.unsubscribe();
5843
6982
  });
5844
6983
  });
5845
6984
  effect(() => {
@@ -5871,10 +7010,10 @@ class MlvEditorColorControl {
5871
7010
  return;
5872
7011
  this._context.run((editor) => this._run(editor, selection, color));
5873
7012
  }
5874
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorColorControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5875
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.7", type: MlvEditorColorControl, isStandalone: true, viewQueries: [{ propertyName: "_picker", first: true, predicate: ["picker"], descendants: true, isSignal: true }], ngImport: i0 });
7013
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorColorControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7014
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.7", type: MlvEditorColorControl, isStandalone: true, viewQueries: [{ propertyName: "_picker", first: true, predicate: ["picker"], descendants: true, isSignal: true }], ngImport: i0 });
5876
7015
  }
5877
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorColorControl, decorators: [{
7016
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorColorControl, decorators: [{
5878
7017
  type: Directive
5879
7018
  }], ctorParameters: () => [], propDecorators: { _picker: [{ type: i0.ViewChild, args: ['picker', { isSignal: true }] }] } });
5880
7019
 
@@ -5907,8 +7046,8 @@ class MlvEditorHighlight extends MlvEditorColorControl {
5907
7046
  ? chain.toggleHighlight({ color }).run()
5908
7047
  : chain.unsetHighlight().run();
5909
7048
  }
5910
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorHighlight, deps: null, target: i0.ɵɵFactoryTarget.Component });
5911
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorHighlight, isStandalone: true, selector: "mlv-editor-highlight", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-highlight" }, usesInheritance: true, ngImport: i0, template: `
7049
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorHighlight, deps: null, target: i0.ɵɵFactoryTarget.Component });
7050
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorHighlight, isStandalone: true, selector: "mlv-editor-highlight", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-highlight" }, usesInheritance: true, ngImport: i0, template: `
5912
7051
  <mlv-color-picker-popup
5913
7052
  #picker
5914
7053
  presentation="icon"
@@ -5925,7 +7064,7 @@ class MlvEditorHighlight extends MlvEditorColorControl {
5925
7064
  </mlv-color-picker-popup>
5926
7065
  `, isInline: true, dependencies: [{ kind: "component", type: MlvColorPickerPopup, selector: "mlv-color-picker-popup", inputs: ["value", "presentation", "opened", "showOpacity", "defaultMode", "supportedFormats", "live"], outputs: ["valueChange", "openedChange", "afterOpened", "afterClosed", "colorChange"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "component", type: LucideHighlighter, selector: "svg[lucideHighlighter]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5927
7066
  }
5928
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorHighlight, decorators: [{
7067
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorHighlight, decorators: [{
5929
7068
  type: Component,
5930
7069
  args: [{
5931
7070
  selector: 'mlv-editor-highlight',
@@ -5984,8 +7123,8 @@ class MlvEditorInlineMarks {
5984
7123
  /** @protected Italic command. */ _italic = (editor) => editor.chain().focus().toggleItalic().run();
5985
7124
  /** @protected Strike command. */ _strike = (editor) => editor.chain().focus().toggleStrike().run();
5986
7125
  /** @protected Underline command. */ _underline = (editor) => editor.chain().focus().toggleUnderline().run();
5987
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorInlineMarks, deps: [], target: i0.ɵɵFactoryTarget.Component });
5988
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorInlineMarks, isStandalone: true, selector: "mlv-editor-inline-marks", host: { attributes: { "role": "group" }, classAttribute: "mlv-editor-inline-marks" }, ngImport: i0, template: `
7126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorInlineMarks, deps: [], target: i0.ɵɵFactoryTarget.Component });
7127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorInlineMarks, isStandalone: true, selector: "mlv-editor-inline-marks", host: { attributes: { "role": "group" }, classAttribute: "mlv-editor-inline-marks" }, ngImport: i0, template: `
5989
7128
  <mlv-editor-command-button
5990
7129
  [label]="_copy().bold"
5991
7130
  [pressed]="_isActive('bold')"
@@ -6020,7 +7159,7 @@ class MlvEditorInlineMarks {
6020
7159
  /></mlv-editor-command-button>
6021
7160
  `, isInline: true, dependencies: [{ kind: "component", type: MlvEditorCommandButton, selector: "mlv-editor-command-button", inputs: ["label", "supports", "command", "canCommand", "pressed"] }, { kind: "component", type: LucideBold, selector: "svg[lucideBold]" }, { kind: "component", type: LucideItalic, selector: "svg[lucideItalic]" }, { kind: "component", type: LucideStrikethrough, selector: "svg[lucideStrikethrough]" }, { kind: "component", type: LucideUnderline, selector: "svg[lucideUnderline]" }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
6022
7161
  }
6023
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorInlineMarks, decorators: [{
7162
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorInlineMarks, decorators: [{
6024
7163
  type: Component,
6025
7164
  args: [{
6026
7165
  selector: 'mlv-editor-inline-marks',
@@ -6182,8 +7321,8 @@ class MlvEditorList {
6182
7321
  this._unregister?.();
6183
7322
  this._unregister = undefined;
6184
7323
  }
6185
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorList, deps: [], target: i0.ɵɵFactoryTarget.Component });
6186
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorList, isStandalone: true, selector: "mlv-editor-list", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-list" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
7324
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorList, deps: [], target: i0.ɵɵFactoryTarget.Component });
7325
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorList, isStandalone: true, selector: "mlv-editor-list", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-list" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
6187
7326
  <button
6188
7327
  mlvButton
6189
7328
  mlvEditorToolbarWidget
@@ -6235,7 +7374,7 @@ class MlvEditorList {
6235
7374
  </mlv-menu>
6236
7375
  `, isInline: true, dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideList, selector: "svg[lucideList]" }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
6237
7376
  }
6238
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorList, decorators: [{
7377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorList, decorators: [{
6239
7378
  type: Component,
6240
7379
  args: [{
6241
7380
  selector: 'mlv-editor-list',
@@ -6538,15 +7677,14 @@ class MlvEditorLink {
6538
7677
  .map((protocol) => protocol.trim().toLowerCase().replace(/:+$/, ''))
6539
7678
  .filter(Boolean));
6540
7679
  }
6541
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
6542
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorLink, isStandalone: true, selector: "mlv-editor-link", inputs: { allowedProtocols: { classPropertyName: "allowedProtocols", publicName: "allowedProtocols", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-link" }, viewQueries: [{ propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, isSignal: true }, { propertyName: "_triggerElement", first: true, predicate: ["_triggerElement"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_urlInput", first: true, predicate: ["_urlInput"], descendants: true, isSignal: true }, { propertyName: "_panel", first: true, predicate: ["_panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #_trigger=\"mlvPopupTrigger\"\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().link\"\n [attr.aria-pressed]=\"_active()\"\n [mlvTooltip]=\"_copy().link\"\n>\n <svg mlvButtonIcon lucideLink aria-hidden=\"true\" />\n</button>\n\n<mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().link\"\n (afterOpened)=\"_onOpened()\"\n (afterClosed)=\"_onClosed()\"\n>\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-link__panel\">\n <mlv-input\n #_urlInput\n type=\"url\"\n [label]=\"_copy().linkUrl\"\n [value]=\"_urlDraft()\"\n [disabled]=\"_disabled()\"\n [state]=\"_invalid() ? 'error' : 'default'\"\n [message]=\"_invalid() ? _copy().invalidLink : ''\"\n (valueChange)=\"_onUrlChange($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-input\n [label]=\"_copy().linkText\"\n [value]=\"_textDraft()\"\n [disabled]=\"_disabled()\"\n (valueChange)=\"_textDraft.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-switch [label]=\"_copy().openInNewTab\" [(checked)]=\"_newTab\"\n >{{_copy().openInNewTab}}</mlv-switch\n >\n <div class=\"mlv-editor-link__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_disabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().applyLink }}\n </button>\n @if (_canRemove()) {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _copy().removeLink }}\n </button>\n }\n </div>\n </div>\n </ng-template>\n</mlv-popup>\n", styles: ["@layer mlv.components{.mlv-editor-link{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-link *{box-sizing:border-box}.mlv-editor-link{display:inline-flex}.mlv-editor-link__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-link__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideLink, selector: "svg[lucideLink]" }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "directive", type: MlvPopupTrigger, selector: "[mlvPopupTrigger]", inputs: ["mlvPopupTrigger", "triggerOn", "ariaHasPopup"], exportAs: ["mlvPopupTrigger"] }, { kind: "component", type: MlvSwitch, selector: "mlv-switch", inputs: ["ariaLabelledBy", "checked"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7680
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorLink, deps: [], target: i0.ɵɵFactoryTarget.Component });
7681
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorLink, isStandalone: true, selector: "mlv-editor-link", inputs: { allowedProtocols: { classPropertyName: "allowedProtocols", publicName: "allowedProtocols", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-link" }, viewQueries: [{ propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, isSignal: true }, { propertyName: "_triggerElement", first: true, predicate: ["_triggerElement"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_urlInput", first: true, predicate: ["_urlInput"], descendants: true, isSignal: true }, { propertyName: "_panel", first: true, predicate: ["_panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<button\n #_trigger=\"mlvPopupTrigger\"\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().link\"\n [attr.aria-pressed]=\"_active()\"\n [mlvTooltip]=\"_copy().link\"\n>\n <svg mlvButtonIcon lucideLink aria-hidden=\"true\" />\n</button>\n\n<mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().link\"\n (afterOpened)=\"_onOpened()\"\n (afterClosed)=\"_onClosed()\"\n>\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-link__panel\">\n <mlv-input\n #_urlInput\n type=\"url\"\n [label]=\"_copy().linkUrl\"\n [value]=\"_urlDraft()\"\n [disabled]=\"_disabled()\"\n [state]=\"_invalid() ? 'error' : 'default'\"\n [message]=\"_invalid() ? _copy().invalidLink : ''\"\n (valueChange)=\"_onUrlChange($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-input\n [label]=\"_copy().linkText\"\n [value]=\"_textDraft()\"\n [disabled]=\"_disabled()\"\n (valueChange)=\"_textDraft.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-switch [label]=\"_copy().openInNewTab\" [(checked)]=\"_newTab\">{{\n _copy().openInNewTab\n }}</mlv-switch>\n <div class=\"mlv-editor-link__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_disabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().applyLink }}\n </button>\n @if (_canRemove()) {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _copy().removeLink }}\n </button>\n }\n </div>\n </div>\n </ng-template>\n</mlv-popup>\n", styles: ["@layer mlv.components{.mlv-editor-link{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-link *{box-sizing:border-box}.mlv-editor-link{display:inline-flex}.mlv-editor-link__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-link__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideLink, selector: "svg[lucideLink]" }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "directive", type: MlvPopupTrigger, selector: "[mlvPopupTrigger]", inputs: ["mlvPopupTrigger", "triggerOn", "ariaHasPopup"], exportAs: ["mlvPopupTrigger"] }, { kind: "component", type: MlvSwitch, selector: "mlv-switch", inputs: ["ariaLabelledBy", "checked"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
6543
7682
  }
6544
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorLink, decorators: [{
7683
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorLink, decorators: [{
6545
7684
  type: Component,
6546
7685
  args: [{ selector: 'mlv-editor-link', imports: [
6547
7686
  MlvButton,
6548
7687
  MlvButtonIcon,
6549
- MlvButtonToggle,
6550
7688
  MlvTooltip,
6551
7689
  LucideLink,
6552
7690
  MlvEditorToolbarWidget,
@@ -6558,7 +7696,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
6558
7696
  ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
6559
7697
  class: 'mlv-editor-link',
6560
7698
  '[hidden]': '!_supported()',
6561
- }, template: "<button\n #_trigger=\"mlvPopupTrigger\"\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().link\"\n [attr.aria-pressed]=\"_active()\"\n [mlvTooltip]=\"_copy().link\"\n>\n <svg mlvButtonIcon lucideLink aria-hidden=\"true\" />\n</button>\n\n<mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().link\"\n (afterOpened)=\"_onOpened()\"\n (afterClosed)=\"_onClosed()\"\n>\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-link__panel\">\n <mlv-input\n #_urlInput\n type=\"url\"\n [label]=\"_copy().linkUrl\"\n [value]=\"_urlDraft()\"\n [disabled]=\"_disabled()\"\n [state]=\"_invalid() ? 'error' : 'default'\"\n [message]=\"_invalid() ? _copy().invalidLink : ''\"\n (valueChange)=\"_onUrlChange($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-input\n [label]=\"_copy().linkText\"\n [value]=\"_textDraft()\"\n [disabled]=\"_disabled()\"\n (valueChange)=\"_textDraft.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-switch [label]=\"_copy().openInNewTab\" [(checked)]=\"_newTab\"\n >{{_copy().openInNewTab}}</mlv-switch\n >\n <div class=\"mlv-editor-link__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_disabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().applyLink }}\n </button>\n @if (_canRemove()) {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _copy().removeLink }}\n </button>\n }\n </div>\n </div>\n </ng-template>\n</mlv-popup>\n", styles: ["@layer mlv.components{.mlv-editor-link{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-link *{box-sizing:border-box}.mlv-editor-link{display:inline-flex}.mlv-editor-link__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-link__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"] }]
7699
+ }, template: "<button\n #_trigger=\"mlvPopupTrigger\"\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().link\"\n [attr.aria-pressed]=\"_active()\"\n [mlvTooltip]=\"_copy().link\"\n>\n <svg mlvButtonIcon lucideLink aria-hidden=\"true\" />\n</button>\n\n<mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().link\"\n (afterOpened)=\"_onOpened()\"\n (afterClosed)=\"_onClosed()\"\n>\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-link__panel\">\n <mlv-input\n #_urlInput\n type=\"url\"\n [label]=\"_copy().linkUrl\"\n [value]=\"_urlDraft()\"\n [disabled]=\"_disabled()\"\n [state]=\"_invalid() ? 'error' : 'default'\"\n [message]=\"_invalid() ? _copy().invalidLink : ''\"\n (valueChange)=\"_onUrlChange($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-input\n [label]=\"_copy().linkText\"\n [value]=\"_textDraft()\"\n [disabled]=\"_disabled()\"\n (valueChange)=\"_textDraft.set($event)\"\n (keydown.enter)=\"_apply()\"\n />\n <mlv-switch [label]=\"_copy().openInNewTab\" [(checked)]=\"_newTab\">{{\n _copy().openInNewTab\n }}</mlv-switch>\n <div class=\"mlv-editor-link__actions\">\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [disabled]=\"_disabled()\"\n (click)=\"_apply()\"\n >\n {{ _copy().applyLink }}\n </button>\n @if (_canRemove()) {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _copy().removeLink }}\n </button>\n }\n </div>\n </div>\n </ng-template>\n</mlv-popup>\n", styles: ["@layer mlv.components{.mlv-editor-link{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-link *{box-sizing:border-box}.mlv-editor-link{display:inline-flex}.mlv-editor-link__panel{display:grid;width:min(22rem,100vw - 3.5rem);gap:var(--mlv-spacing-1-5);padding:var(--mlv-spacing-2)}.mlv-editor-link__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-3)}}\n"] }]
6562
7700
  }], ctorParameters: () => [], propDecorators: { allowedProtocols: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedProtocols", required: false }] }], _popup: [{ type: i0.ViewChild, args: ['_popup', { isSignal: true }] }], _trigger: [{ type: i0.ViewChild, args: ['_trigger', { isSignal: true }] }], _triggerElement: [{ type: i0.ViewChild, args: ['_triggerElement', { ...{
6563
7701
  read: ElementRef,
6564
7702
  }, isSignal: true }] }], _urlInput: [{ type: i0.ViewChild, args: ['_urlInput', { isSignal: true }] }], _panel: [{ type: i0.ViewChild, args: ['_panel', { isSignal: true }] }] } });
@@ -6711,6 +7849,8 @@ class MlvEditorImageUploadDialog {
6711
7849
  this._dialogRef.close();
6712
7850
  return;
6713
7851
  }
7852
+ // A removed failure: its "failed" text must not outlive it.
7853
+ this._announcement.set('');
6714
7854
  this._reset();
6715
7855
  });
6716
7856
  this._dialogRef.beforeClose().subscribe(() => {
@@ -6784,6 +7924,16 @@ class MlvEditorImageUploadDialog {
6784
7924
  this._progressBucket = -1;
6785
7925
  this._coordinator.retry(id);
6786
7926
  }
7927
+ /**
7928
+ * @protected Toggles Decorative. Turning it on clears the title draft: a
7929
+ * decorative image carries an empty alt and no title (WCAG H67), and the
7930
+ * title field stays disabled until it is turned off.
7931
+ */
7932
+ _onDecorativeChange(decorative) {
7933
+ this._decorative.set(decorative);
7934
+ if (decorative)
7935
+ this._title.set('');
7936
+ }
6787
7937
  /** @protected Removes the failed item and clears the draft. */
6788
7938
  _remove() {
6789
7939
  const id = this._activeId();
@@ -6822,10 +7972,10 @@ class MlvEditorImageUploadDialog {
6822
7972
  if (url)
6823
7973
  URL.revokeObjectURL(url);
6824
7974
  }
6825
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorImageUploadDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
6826
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorImageUploadDialog, isStandalone: true, selector: "mlv-editor-image-upload-dialog", host: { classAttribute: "mlv-editor-image-upload-dialog" }, ngImport: i0, template: "<mlv-dialog>\n <mlv-dialog-header />\n <mlv-dialog-body>\n <div class=\"mlv-editor-image-upload-dialog__body\">\n <mlv-file-upload\n [accept]=\"_accept()\"\n [maxSize]=\"_maxSize()\"\n [multiple]=\"false\"\n [disabled]=\"_disabled() || !!_activeId()\"\n [value]=\"_files()\"\n (filesChange)=\"_onFilesChange($event)\"\n />\n\n @if (_file(); as selected) { @if (selected.previewUrl) {\n <img\n class=\"mlv-editor-image-upload-dialog__preview\"\n [src]=\"selected.previewUrl\"\n [alt]=\"_previewAlt()\"\n />\n } @if (_item(); as item) { @if (item.status === 'uploading') {\n <mlv-progress\n [value]=\"item.progress\"\n [showPercentage]=\"true\"\n [ariaLabel]=\"_progressLabel(item.progress)\"\n />\n } @else {\n <p\n class=\"mlv-editor-image-upload-dialog__error\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n {{ _copy().failed }}\n </p>\n } } }\n\n <mlv-input\n [label]=\"_copy().alt\"\n [ariaLabel]=\"_copy().alt\"\n [value]=\"_alt()\"\n [disabled]=\"_disabled() || !!_activeId() || _decorative()\"\n [state]=\"_altInvalid() ? 'error' : 'default'\"\n [message]=\"_altInvalid() ? _copy().altRequired : ''\"\n (valueChange)=\"_alt.set($event)\"\n />\n <mlv-input\n [label]=\"_copy().imageTitle\"\n [ariaLabel]=\"_copy().imageTitle\"\n [value]=\"_title()\"\n [disabled]=\"_disabled() || !!_activeId()\"\n (valueChange)=\"_title.set($event)\"\n />\n <mlv-switch\n [ariaLabel]=\"_copy().decorative\"\n [checked]=\"_decorative()\"\n [disabled]=\"_disabled() || !!_activeId()\"\n (checkedChange)=\"_decorative.set($event)\"\n >\n {{ _copy().decorative }}\n </mlv-switch>\n\n <span\n class=\"mlv-editor-image-upload-dialog__live\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >{{ _announcement() }}</span\n >\n </div>\n </mlv-dialog-body>\n\n <mlv-dialog-footer>\n @if (_item()?.status === 'uploading') {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_copy().cancel\"\n (click)=\"_cancel()\"\n >\n {{ _copy().cancel }}\n </button>\n } @else if (_item()?.status === 'failed') {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().retry\"\n [disabled]=\"_disabled()\"\n (click)=\"_retry()\"\n >\n {{ _copy().retry }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_removeLabel()\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _removeLabel() }}\n </button>\n } @else {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().title\"\n [disabled]=\"_submitDisabled()\"\n (click)=\"_submit()\"\n >\n {{ _copy().title }}\n </button>\n }\n </mlv-dialog-footer>\n</mlv-dialog>\n", styles: ["@layer mlv.components{.mlv-editor-image-upload-dialog{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-dialog *{box-sizing:border-box}.mlv-editor-image-upload-dialog__body{display:grid;gap:var(--mlv-spacing-2)}.mlv-editor-image-upload-dialog__preview{display:block;max-block-size:12rem;max-inline-size:100%;margin-inline:auto;border-radius:var(--mlv-radius-card);object-fit:contain}.mlv-editor-image-upload-dialog__error{margin:0;color:var(--mlv-text-negative)}.mlv-editor-image-upload-dialog__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.mlv-editor-image-upload-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-status *{box-sizing:border-box}.mlv-editor-image-upload-status{display:block;padding:0 var(--mlv-spacing-4) var(--mlv-spacing-2)}.mlv-editor-image-upload-status__list{display:grid;gap:var(--mlv-spacing-1-5);padding:0;margin:0;list-style:none}.mlv-editor-image-upload-status__item{display:grid;grid-template-columns:minmax(0,1fr) minmax(8rem,2fr) auto auto;align-items:center;gap:var(--mlv-spacing-3)}.mlv-editor-image-upload-status__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mlv-editor-image-upload-status__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "component", type: MlvDialog, selector: "mlv-dialog" }, { kind: "component", type: MlvDialogBody, selector: "mlv-dialog-body, [mlvDialogBody]" }, { kind: "component", type: MlvDialogFooter, selector: "mlv-dialog-footer, [mlvDialogFooter]", inputs: ["align"] }, { kind: "component", type: MlvDialogHeader, selector: "mlv-dialog-header, [mlvDialogHeader]", inputs: ["title", "closable"] }, { kind: "component", type: MlvFileUpload, selector: "mlv-file-upload", inputs: ["value", "accept", "maxSize", "multiple", "title", "subtitle", "actionLabel", "compact", "previewMode"], outputs: ["valueChange", "filesChange"] }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvProgress, selector: "mlv-progress", inputs: ["shape", "value", "size", "tone", "showPercentage", "ariaLabel"] }, { kind: "component", type: MlvSwitch, selector: "mlv-switch", inputs: ["ariaLabelledBy", "checked"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7975
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorImageUploadDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
7976
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorImageUploadDialog, isStandalone: true, selector: "mlv-editor-image-upload-dialog", host: { classAttribute: "mlv-editor-image-upload-dialog" }, ngImport: i0, template: "<mlv-dialog>\n <mlv-dialog-header />\n <mlv-dialog-body>\n <div class=\"mlv-editor-image-upload-dialog__body\">\n <mlv-file-upload\n [accept]=\"_accept()\"\n [maxSize]=\"_maxSize()\"\n [multiple]=\"false\"\n [disabled]=\"_disabled() || !!_activeId()\"\n [value]=\"_files()\"\n (filesChange)=\"_onFilesChange($event)\"\n />\n\n @if (_file(); as selected) {\n @if (selected.previewUrl) {\n <img\n class=\"mlv-editor-image-upload-dialog__preview\"\n [src]=\"selected.previewUrl\"\n [alt]=\"_previewAlt()\"\n />\n }\n @if (_item(); as item) {\n @if (item.status === 'uploading') {\n <mlv-progress\n [value]=\"item.progress\"\n [showPercentage]=\"true\"\n [ariaLabel]=\"_progressLabel(item.progress)\"\n />\n } @else {\n <p\n class=\"mlv-editor-image-upload-dialog__error\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n {{ _copy().failed }}\n </p>\n }\n }\n }\n\n <mlv-input\n [label]=\"_copy().alt\"\n [ariaLabel]=\"_copy().alt\"\n [value]=\"_alt()\"\n [disabled]=\"_disabled() || !!_activeId() || _decorative()\"\n [state]=\"_altInvalid() ? 'error' : 'default'\"\n [message]=\"_altInvalid() ? _copy().altRequired : ''\"\n (valueChange)=\"_alt.set($event)\"\n />\n <mlv-input\n [label]=\"_copy().imageTitle\"\n [ariaLabel]=\"_copy().imageTitle\"\n [value]=\"_title()\"\n [disabled]=\"_disabled() || !!_activeId() || _decorative()\"\n (valueChange)=\"_title.set($event)\"\n />\n <mlv-switch\n [ariaLabel]=\"_copy().decorative\"\n [checked]=\"_decorative()\"\n [disabled]=\"_disabled() || !!_activeId()\"\n (checkedChange)=\"_onDecorativeChange($event)\"\n >\n {{ _copy().decorative }}\n </mlv-switch>\n\n <span\n class=\"mlv-editor-image-upload-dialog__live\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >{{ _announcement() }}</span\n >\n </div>\n </mlv-dialog-body>\n\n <mlv-dialog-footer>\n @if (_item()?.status === 'uploading') {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_copy().cancel\"\n (click)=\"_cancel()\"\n >\n {{ _copy().cancel }}\n </button>\n } @else if (_item()?.status === 'failed') {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().retry\"\n [disabled]=\"_disabled()\"\n (click)=\"_retry()\"\n >\n {{ _copy().retry }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_removeLabel()\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _removeLabel() }}\n </button>\n } @else {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().title\"\n [disabled]=\"_submitDisabled()\"\n (click)=\"_submit()\"\n >\n {{ _copy().title }}\n </button>\n }\n </mlv-dialog-footer>\n</mlv-dialog>\n", styles: ["@layer mlv.components{.mlv-editor-image-upload-dialog{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-dialog *{box-sizing:border-box}.mlv-editor-image-upload-dialog__body{display:grid;gap:var(--mlv-spacing-2)}.mlv-editor-image-upload-dialog__preview{display:block;max-block-size:12rem;max-inline-size:100%;margin-inline:auto;border-radius:var(--mlv-radius-card);object-fit:contain}.mlv-editor-image-upload-dialog__error{margin:0;color:var(--mlv-text-negative)}.mlv-editor-image-upload-dialog__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.mlv-editor-image-upload-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-status *{box-sizing:border-box}.mlv-editor-image-upload-status{display:block;padding:0 var(--mlv-spacing-4) var(--mlv-spacing-2)}.mlv-editor-image-upload-status__list{display:grid;gap:var(--mlv-spacing-1-5);padding:0;margin:0;list-style:none}.mlv-editor-image-upload-status__item{display:grid;grid-template-columns:minmax(0,1fr) minmax(8rem,2fr) auto auto;align-items:center;gap:var(--mlv-spacing-3)}.mlv-editor-image-upload-status__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mlv-editor-image-upload-status__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "component", type: MlvDialog, selector: "mlv-dialog" }, { kind: "component", type: MlvDialogBody, selector: "mlv-dialog-body, [mlvDialogBody]" }, { kind: "component", type: MlvDialogFooter, selector: "mlv-dialog-footer, [mlvDialogFooter]", inputs: ["align"] }, { kind: "component", type: MlvDialogHeader, selector: "mlv-dialog-header, [mlvDialogHeader]", inputs: ["title", "closable"] }, { kind: "component", type: MlvFileUpload, selector: "mlv-file-upload", inputs: ["value", "accept", "maxSize", "multiple", "title", "subtitle", "actionLabel", "compact", "previewMode"], outputs: ["valueChange", "filesChange"] }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvProgress, selector: "mlv-progress", inputs: ["shape", "value", "size", "tone", "showPercentage", "ariaLabel"] }, { kind: "component", type: MlvSwitch, selector: "mlv-switch", inputs: ["ariaLabelledBy", "checked"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
6827
7977
  }
6828
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorImageUploadDialog, decorators: [{
7978
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorImageUploadDialog, decorators: [{
6829
7979
  type: Component,
6830
7980
  args: [{ selector: 'mlv-editor-image-upload-dialog', imports: [
6831
7981
  MlvButton,
@@ -6837,7 +7987,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
6837
7987
  MlvInput,
6838
7988
  MlvProgress,
6839
7989
  MlvSwitch,
6840
- ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'mlv-editor-image-upload-dialog' }, template: "<mlv-dialog>\n <mlv-dialog-header />\n <mlv-dialog-body>\n <div class=\"mlv-editor-image-upload-dialog__body\">\n <mlv-file-upload\n [accept]=\"_accept()\"\n [maxSize]=\"_maxSize()\"\n [multiple]=\"false\"\n [disabled]=\"_disabled() || !!_activeId()\"\n [value]=\"_files()\"\n (filesChange)=\"_onFilesChange($event)\"\n />\n\n @if (_file(); as selected) { @if (selected.previewUrl) {\n <img\n class=\"mlv-editor-image-upload-dialog__preview\"\n [src]=\"selected.previewUrl\"\n [alt]=\"_previewAlt()\"\n />\n } @if (_item(); as item) { @if (item.status === 'uploading') {\n <mlv-progress\n [value]=\"item.progress\"\n [showPercentage]=\"true\"\n [ariaLabel]=\"_progressLabel(item.progress)\"\n />\n } @else {\n <p\n class=\"mlv-editor-image-upload-dialog__error\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n {{ _copy().failed }}\n </p>\n } } }\n\n <mlv-input\n [label]=\"_copy().alt\"\n [ariaLabel]=\"_copy().alt\"\n [value]=\"_alt()\"\n [disabled]=\"_disabled() || !!_activeId() || _decorative()\"\n [state]=\"_altInvalid() ? 'error' : 'default'\"\n [message]=\"_altInvalid() ? _copy().altRequired : ''\"\n (valueChange)=\"_alt.set($event)\"\n />\n <mlv-input\n [label]=\"_copy().imageTitle\"\n [ariaLabel]=\"_copy().imageTitle\"\n [value]=\"_title()\"\n [disabled]=\"_disabled() || !!_activeId()\"\n (valueChange)=\"_title.set($event)\"\n />\n <mlv-switch\n [ariaLabel]=\"_copy().decorative\"\n [checked]=\"_decorative()\"\n [disabled]=\"_disabled() || !!_activeId()\"\n (checkedChange)=\"_decorative.set($event)\"\n >\n {{ _copy().decorative }}\n </mlv-switch>\n\n <span\n class=\"mlv-editor-image-upload-dialog__live\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >{{ _announcement() }}</span\n >\n </div>\n </mlv-dialog-body>\n\n <mlv-dialog-footer>\n @if (_item()?.status === 'uploading') {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_copy().cancel\"\n (click)=\"_cancel()\"\n >\n {{ _copy().cancel }}\n </button>\n } @else if (_item()?.status === 'failed') {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().retry\"\n [disabled]=\"_disabled()\"\n (click)=\"_retry()\"\n >\n {{ _copy().retry }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_removeLabel()\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _removeLabel() }}\n </button>\n } @else {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().title\"\n [disabled]=\"_submitDisabled()\"\n (click)=\"_submit()\"\n >\n {{ _copy().title }}\n </button>\n }\n </mlv-dialog-footer>\n</mlv-dialog>\n", styles: ["@layer mlv.components{.mlv-editor-image-upload-dialog{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-dialog *{box-sizing:border-box}.mlv-editor-image-upload-dialog__body{display:grid;gap:var(--mlv-spacing-2)}.mlv-editor-image-upload-dialog__preview{display:block;max-block-size:12rem;max-inline-size:100%;margin-inline:auto;border-radius:var(--mlv-radius-card);object-fit:contain}.mlv-editor-image-upload-dialog__error{margin:0;color:var(--mlv-text-negative)}.mlv-editor-image-upload-dialog__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.mlv-editor-image-upload-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-status *{box-sizing:border-box}.mlv-editor-image-upload-status{display:block;padding:0 var(--mlv-spacing-4) var(--mlv-spacing-2)}.mlv-editor-image-upload-status__list{display:grid;gap:var(--mlv-spacing-1-5);padding:0;margin:0;list-style:none}.mlv-editor-image-upload-status__item{display:grid;grid-template-columns:minmax(0,1fr) minmax(8rem,2fr) auto auto;align-items:center;gap:var(--mlv-spacing-3)}.mlv-editor-image-upload-status__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mlv-editor-image-upload-status__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"] }]
7990
+ ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'mlv-editor-image-upload-dialog' }, template: "<mlv-dialog>\n <mlv-dialog-header />\n <mlv-dialog-body>\n <div class=\"mlv-editor-image-upload-dialog__body\">\n <mlv-file-upload\n [accept]=\"_accept()\"\n [maxSize]=\"_maxSize()\"\n [multiple]=\"false\"\n [disabled]=\"_disabled() || !!_activeId()\"\n [value]=\"_files()\"\n (filesChange)=\"_onFilesChange($event)\"\n />\n\n @if (_file(); as selected) {\n @if (selected.previewUrl) {\n <img\n class=\"mlv-editor-image-upload-dialog__preview\"\n [src]=\"selected.previewUrl\"\n [alt]=\"_previewAlt()\"\n />\n }\n @if (_item(); as item) {\n @if (item.status === 'uploading') {\n <mlv-progress\n [value]=\"item.progress\"\n [showPercentage]=\"true\"\n [ariaLabel]=\"_progressLabel(item.progress)\"\n />\n } @else {\n <p\n class=\"mlv-editor-image-upload-dialog__error\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n {{ _copy().failed }}\n </p>\n }\n }\n }\n\n <mlv-input\n [label]=\"_copy().alt\"\n [ariaLabel]=\"_copy().alt\"\n [value]=\"_alt()\"\n [disabled]=\"_disabled() || !!_activeId() || _decorative()\"\n [state]=\"_altInvalid() ? 'error' : 'default'\"\n [message]=\"_altInvalid() ? _copy().altRequired : ''\"\n (valueChange)=\"_alt.set($event)\"\n />\n <mlv-input\n [label]=\"_copy().imageTitle\"\n [ariaLabel]=\"_copy().imageTitle\"\n [value]=\"_title()\"\n [disabled]=\"_disabled() || !!_activeId() || _decorative()\"\n (valueChange)=\"_title.set($event)\"\n />\n <mlv-switch\n [ariaLabel]=\"_copy().decorative\"\n [checked]=\"_decorative()\"\n [disabled]=\"_disabled() || !!_activeId()\"\n (checkedChange)=\"_onDecorativeChange($event)\"\n >\n {{ _copy().decorative }}\n </mlv-switch>\n\n <span\n class=\"mlv-editor-image-upload-dialog__live\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >{{ _announcement() }}</span\n >\n </div>\n </mlv-dialog-body>\n\n <mlv-dialog-footer>\n @if (_item()?.status === 'uploading') {\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_copy().cancel\"\n (click)=\"_cancel()\"\n >\n {{ _copy().cancel }}\n </button>\n } @else if (_item()?.status === 'failed') {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().retry\"\n [disabled]=\"_disabled()\"\n (click)=\"_retry()\"\n >\n {{ _copy().retry }}\n </button>\n <button\n mlvButton\n type=\"button\"\n variant=\"secondary\"\n [attr.aria-label]=\"_removeLabel()\"\n [disabled]=\"_disabled()\"\n (click)=\"_remove()\"\n >\n {{ _removeLabel() }}\n </button>\n } @else {\n <button\n mlvButton\n type=\"button\"\n variant=\"primary\"\n [attr.aria-label]=\"_copy().title\"\n [disabled]=\"_submitDisabled()\"\n (click)=\"_submit()\"\n >\n {{ _copy().title }}\n </button>\n }\n </mlv-dialog-footer>\n</mlv-dialog>\n", styles: ["@layer mlv.components{.mlv-editor-image-upload-dialog{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-dialog *{box-sizing:border-box}.mlv-editor-image-upload-dialog__body{display:grid;gap:var(--mlv-spacing-2)}.mlv-editor-image-upload-dialog__preview{display:block;max-block-size:12rem;max-inline-size:100%;margin-inline:auto;border-radius:var(--mlv-radius-card);object-fit:contain}.mlv-editor-image-upload-dialog__error{margin:0;color:var(--mlv-text-negative)}.mlv-editor-image-upload-dialog__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.mlv-editor-image-upload-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-status *{box-sizing:border-box}.mlv-editor-image-upload-status{display:block;padding:0 var(--mlv-spacing-4) var(--mlv-spacing-2)}.mlv-editor-image-upload-status__list{display:grid;gap:var(--mlv-spacing-1-5);padding:0;margin:0;list-style:none}.mlv-editor-image-upload-status__item{display:grid;grid-template-columns:minmax(0,1fr) minmax(8rem,2fr) auto auto;align-items:center;gap:var(--mlv-spacing-3)}.mlv-editor-image-upload-status__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mlv-editor-image-upload-status__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"] }]
6841
7991
  }], ctorParameters: () => [] });
6842
7992
 
6843
7993
  /** Public toolbar entry point for the editor image-upload dialog. */
@@ -6931,10 +8081,10 @@ class MlvEditorImageUpload {
6931
8081
  }
6932
8082
  });
6933
8083
  }
6934
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorImageUpload, deps: [], target: i0.ɵɵFactoryTarget.Component });
6935
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MlvEditorImageUpload, isStandalone: true, selector: "mlv-editor-image-upload", host: { properties: { "hidden": "!_visible()" }, classAttribute: "mlv-editor-image-upload" }, viewQueries: [{ propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<button\n #_trigger\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy()\"\n [attr.aria-pressed]=\"false\"\n [mlvTooltip]=\"_copy()\"\n (click)=\"_open()\"\n>\n <svg mlvButtonIcon lucideImage aria-hidden=\"true\" />\n</button>\n", dependencies: [{ kind: "component", type: LucideImage, selector: "svg[lucideImage]" }, { kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8084
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorImageUpload, deps: [], target: i0.ɵɵFactoryTarget.Component });
8085
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: MlvEditorImageUpload, isStandalone: true, selector: "mlv-editor-image-upload", host: { properties: { "hidden": "!_visible()" }, classAttribute: "mlv-editor-image-upload" }, viewQueries: [{ propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<button\n #_trigger\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy()\"\n [attr.aria-pressed]=\"false\"\n [mlvTooltip]=\"_copy()\"\n (click)=\"_open()\"\n>\n <svg mlvButtonIcon lucideImage aria-hidden=\"true\" />\n</button>\n", dependencies: [{ kind: "component", type: LucideImage, selector: "svg[lucideImage]" }, { kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
6936
8086
  }
6937
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorImageUpload, decorators: [{
8087
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorImageUpload, decorators: [{
6938
8088
  type: Component,
6939
8089
  args: [{ selector: 'mlv-editor-image-upload', imports: [
6940
8090
  LucideImage,
@@ -6966,6 +8116,11 @@ class MlvEditorImageUploadStatus {
6966
8116
  ...(ngDevMode ? [{ debugName: "_announcement" }] : /* istanbul ignore next */ []));
6967
8117
  /** @private Last announced progress bucket for each active item. */
6968
8118
  _buckets = new Map();
8119
+ /**
8120
+ * @private Upload whose progress or failure the live region currently
8121
+ * reads, or `null` when it reads a terminal announcement (or nothing).
8122
+ */
8123
+ _announcedId = null;
6969
8124
  /** @protected Whether status actions are inert. */
6970
8125
  _disabled = computed(() => this._context.disabled() || this._context.readonly(), /* @ts-ignore */
6971
8126
  ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
@@ -6985,7 +8140,7 @@ class MlvEditorImageUploadStatus {
6985
8140
  const pending = this._pending();
6986
8141
  for (const item of pending) {
6987
8142
  if (item.status === 'failed') {
6988
- this._announcement.set(this._copy().failed);
8143
+ this._announce(this._copy().failed, item.id);
6989
8144
  this._buckets.delete(item.id);
6990
8145
  continue;
6991
8146
  }
@@ -6993,16 +8148,22 @@ class MlvEditorImageUploadStatus {
6993
8148
  if (this._buckets.get(item.id) === bucket)
6994
8149
  continue;
6995
8150
  this._buckets.set(item.id, bucket);
6996
- this._announcement.set(this._progressLabel(bucket));
8151
+ this._announce(this._progressLabel(bucket), item.id);
6997
8152
  }
6998
8153
  });
6999
8154
  const stopTerminalEvents = this._coordinator.onTerminal(({ id, reason }) => {
7000
8155
  this._buckets.delete(id);
7001
8156
  if (reason === 'success' && !this._coordinator.hasDialogOwner(id)) {
7002
- this._announcement.set(this._fileI18n().uploadComplete);
8157
+ this._announce(this._fileI18n().uploadComplete, null);
7003
8158
  }
7004
8159
  else if (reason === 'cancelled') {
7005
- this._announcement.set(this._copy().cancel);
8160
+ this._announce(this._copy().cancel, null);
8161
+ }
8162
+ else if (this._announcedId === id) {
8163
+ // A dialog-owned success (the dialog announces it), a lifecycle
8164
+ // abort or a removed failure: the ended upload's progress or
8165
+ // failure text must not outlive it. Another upload's text stays.
8166
+ this._announce('', null);
7006
8167
  }
7007
8168
  });
7008
8169
  inject(DestroyRef).onDestroy(stopTerminalEvents);
@@ -7011,6 +8172,11 @@ class MlvEditorImageUploadStatus {
7011
8172
  _progressLabel(progress) {
7012
8173
  return this._copy().progress.replace('{progress}', String(Math.floor(progress / 10) * 10));
7013
8174
  }
8175
+ /** @private Writes the live region and records which upload it describes. */
8176
+ _announce(text, id) {
8177
+ this._announcement.set(text);
8178
+ this._announcedId = id;
8179
+ }
7014
8180
  /** @protected Labels a remove action without exposing a bare icon. */
7015
8181
  _removeLabel(name) {
7016
8182
  return this._fileI18n().removeFile.replace('{name}', name);
@@ -7019,8 +8185,8 @@ class MlvEditorImageUploadStatus {
7019
8185
  _cancel(id) {
7020
8186
  this._coordinator.cancel(id);
7021
8187
  }
7022
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorImageUploadStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
7023
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorImageUploadStatus, isStandalone: true, selector: "mlv-editor-image-upload-status", host: { classAttribute: "mlv-editor-image-upload-status" }, ngImport: i0, template: `
8188
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorImageUploadStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
8189
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorImageUploadStatus, isStandalone: true, selector: "mlv-editor-image-upload-status", host: { classAttribute: "mlv-editor-image-upload-status" }, ngImport: i0, template: `
7024
8190
  @if (_pending().length) {
7025
8191
  <ul class="mlv-editor-image-upload-status__list">
7026
8192
  @for (item of _pending(); track item.id) {
@@ -7080,7 +8246,7 @@ class MlvEditorImageUploadStatus {
7080
8246
  >
7081
8247
  `, isInline: true, styles: ["@layer mlv.components{.mlv-editor-image-upload-dialog{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-dialog *{box-sizing:border-box}.mlv-editor-image-upload-dialog__body{display:grid;gap:var(--mlv-spacing-2)}.mlv-editor-image-upload-dialog__preview{display:block;max-block-size:12rem;max-inline-size:100%;margin-inline:auto;border-radius:var(--mlv-radius-card);object-fit:contain}.mlv-editor-image-upload-dialog__error{margin:0;color:var(--mlv-text-negative)}.mlv-editor-image-upload-dialog__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.mlv-editor-image-upload-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-image-upload-status *{box-sizing:border-box}.mlv-editor-image-upload-status{display:block;padding:0 var(--mlv-spacing-4) var(--mlv-spacing-2)}.mlv-editor-image-upload-status__list{display:grid;gap:var(--mlv-spacing-1-5);padding:0;margin:0;list-style:none}.mlv-editor-image-upload-status__item{display:grid;grid-template-columns:minmax(0,1fr) minmax(8rem,2fr) auto auto;align-items:center;gap:var(--mlv-spacing-3)}.mlv-editor-image-upload-status__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mlv-editor-image-upload-status__live{position:absolute;width:.0625rem;height:.0625rem;overflow:hidden;clip-path:inset(50%);white-space:nowrap}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "component", type: MlvProgress, selector: "mlv-progress", inputs: ["shape", "value", "size", "tone", "showPercentage", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7082
8248
  }
7083
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorImageUploadStatus, decorators: [{
8249
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorImageUploadStatus, decorators: [{
7084
8250
  type: Component,
7085
8251
  args: [{ selector: 'mlv-editor-image-upload-status', imports: [MlvButton, MlvProgress], template: `
7086
8252
  @if (_pending().length) {
@@ -7150,6 +8316,18 @@ class MlvEditorTable {
7150
8316
  _context = inject(MLV_EDITOR_TOOLBAR_CONTEXT);
7151
8317
  /** @private Normalizes horizontal grid movement for RTL. */
7152
8318
  _rtlService = inject(MlvRtlService);
8319
+ /**
8320
+ * @private Host element; the scope horizontal arrow keys resolve their
8321
+ * direction against. The grid renders in a popup pane portaled out of this
8322
+ * subtree, so the handler speaks for the host, never for `event.target`.
8323
+ */
8324
+ _elementRef = inject((ElementRef));
8325
+ /**
8326
+ * @private Direction applying to this control, resolved once and cached
8327
+ * behind the shared `dir` observer rather than re-walked on every arrow
8328
+ * keypress.
8329
+ */
8330
+ _direction = this._rtlService.elementDirection(this._elementRef);
7153
8331
  /** @private Transaction and selection invalidation bridge. */
7154
8332
  _revision = inject(MLV_EDITOR_TOOLBAR_REVISION, {
7155
8333
  optional: true,
@@ -7403,7 +8581,11 @@ class MlvEditorTable {
7403
8581
  _onGridKeydown(event) {
7404
8582
  const current = this._selectedSize();
7405
8583
  let next = current;
7406
- switch (this._rtlService.normalizeArrowKey(event) ?? event.key) {
8584
+ // Columns run along the inline axis, resolved against this control's own
8585
+ // host rather than the document, so a scoped `dir` subtree — or a CDK
8586
+ // overlay pane, which carries its own `dir` — mirrors the pair correctly.
8587
+ switch (this._rtlService.normalizeArrowKey(event, this._direction()) ??
8588
+ event.key) {
7407
8589
  case LEFT_ARROW:
7408
8590
  next = { ...current, cols: Math.max(1, current.cols - 1) };
7409
8591
  break;
@@ -7446,10 +8628,10 @@ class MlvEditorTable {
7446
8628
  const index = (selected.rows - 1) * TABLE_GRID_BOUNDARY + (selected.cols - 1);
7447
8629
  this._gridCells()[index]?.nativeElement.focus();
7448
8630
  }
7449
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
7450
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorTable, isStandalone: true, selector: "mlv-editor-table", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-table" }, viewQueries: [{ propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, isSignal: true }, { propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_menuTrigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }, { propertyName: "_triggerElement", first: true, predicate: ["_triggerElement"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_panel", first: true, predicate: ["_panel"], descendants: true, isSignal: true }, { propertyName: "_firstAction", first: true, predicate: ["_firstAction"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_gridCells", predicate: ["_gridCell"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "@if (_active()) {\n <button\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n selected\n [disabled]=\"_disabled()\"\n [mlvMenuTrigger]=\"_menu\"\n [menuTriggerDisabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().table\"\n [mlvTooltip]=\"_copy().table\"\n (menuOpened)=\"_registerMenuOverlay()\"\n (menuClosed)=\"_unregisterMenuOverlay()\"\n >\n <svg mlvButtonIcon lucideTable2 aria-hidden=\"true\" />\n </button>\n\n <mlv-menu #_menu [label]=\"_copy().table\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddRowBefore)\"\n (itemClick)=\"_run(_addRowBefore)\"\n >\n {{ _copy().addRowBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddRowAfter)\"\n (itemClick)=\"_run(_addRowAfter)\"\n >\n {{ _copy().addRowAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canDeleteRow)\"\n (itemClick)=\"_run(_deleteRow)\"\n >\n {{ _copy().deleteRow }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddColumnBefore)\"\n (itemClick)=\"_run(_addColumnBefore)\"\n >\n {{ _copy().addColumnBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddColumnAfter)\"\n (itemClick)=\"_run(_addColumnAfter)\"\n >\n {{ _copy().addColumnAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canDeleteColumn)\"\n (itemClick)=\"_run(_deleteColumn)\"\n >\n {{ _copy().deleteColumn }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canMergeCells)\"\n (itemClick)=\"_run(_mergeCells)\"\n >\n {{ _copy().mergeCells }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canSplitCell)\"\n (itemClick)=\"_run(_splitCell)\"\n >\n {{ _copy().splitCell }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canToggleHeaderRow)\"\n (itemClick)=\"_run(_toggleHeaderRow)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canToggleHeaderColumn)\"\n (itemClick)=\"_run(_toggleHeaderColumn)\"\n >\n {{ _copy().toggleHeaderColumn }}\n </mlv-list-item>\n <!-- `aria-current` marks the state the caret's cell is already in; the row\n paints itself from the shared selected pair. -->\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canToggleHeaderCell)\"\n [attr.aria-current]=\"_isHeaderCell() ? 'true' : null\"\n (itemClick)=\"_run(_toggleHeaderCell)\"\n >\n {{ _copy().toggleHeaderCell }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canDeleteTable)\"\n (itemClick)=\"_run(_deleteTable, true)\"\n >\n {{ _copy().deleteTable }}\n </mlv-list-item>\n </mlv-menu>\n} @else {\n <button\n #_trigger=\"mlvPopupTrigger\"\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().table\"\n [mlvTooltip]=\"_copy().table\"\n >\n <svg mlvButtonIcon lucideTable2 aria-hidden=\"true\" />\n </button>\n\n <mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().table\"\n (afterOpened)=\"_onOpened()\"\n (afterClosed)=\"_onClosed()\"\n >\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-table__panel\">\n <div\n class=\"mlv-editor-table__grid\"\n role=\"grid\"\n tabindex=\"-1\"\n [attr.aria-label]=\"_copy().insertTable\"\n (keydown)=\"_onGridKeydown($event)\"\n >\n @for (row of _gridRows; track $index) {\n <div class=\"mlv-editor-table__grid-row\" role=\"row\">\n @for (size of row; track size.cols) {\n <button\n #_gridCell\n class=\"mlv-editor-table__grid-cell\"\n type=\"button\"\n role=\"gridcell\"\n [class.mlv-editor-table__grid-cell--selected]=\"\n _sizeSelected(size)\n \"\n [attr.aria-label]=\"_sizeLabel(size)\"\n [attr.aria-selected]=\"_sizeSelected(size)\"\n [attr.tabindex]=\"\n size.rows === _selectedSize().rows &&\n size.cols === _selectedSize().cols\n ? 0\n : -1\n \"\n [disabled]=\"_disabled()\"\n (focus)=\"_selectSize(size)\"\n (mouseenter)=\"_selectSize(size)\"\n (click)=\"_insertSize(size)\"\n ></button>\n }\n </div>\n }\n </div>\n\n <div class=\"mlv-editor-table__footer\">\n <span class=\"mlv-editor-table__size\" aria-hidden=\"true\">\n {{ _selectedSize().rows }} \u00D7 {{ _selectedSize().cols }}\n </span>\n <mlv-button-toggle\n [pressed]=\"_withHeaderRow()\"\n [disabled]=\"_disabled()\"\n (pressedChange)=\"_withHeaderRow.set($event)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-button-toggle>\n </div>\n </div>\n </ng-template>\n </mlv-popup>\n}\n", styles: ["@layer mlv.components{.mlv-editor-table{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-table *{box-sizing:border-box}.mlv-editor-table{display:inline-flex}.mlv-editor-table__panel{display:grid;width:max-content;max-width:calc(100vw - 3.5rem);padding:var(--mlv-popover-inset);gap:var(--mlv-spacing-2)}.mlv-editor-table__actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:min(24rem,100vw - 4rem);gap:var(--mlv-spacing-2)}.mlv-editor-table__grid{display:grid;grid-template-columns:1fr;gap:.125rem}.mlv-editor-table__grid-row{display:grid;grid-template-columns:repeat(10,1.25rem);gap:.125rem}.mlv-editor-table__grid-cell{appearance:none;box-sizing:border-box;inline-size:1.25rem;block-size:1.25rem;margin:0;padding:0;border:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-radius:var(--mlv-radius-xs);background:var(--mlv-background-base);cursor:pointer;transition:background-color var(--mlv-duration-fast) var(--mlv-ease-default),border-color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor-table__grid-cell--selected{border-color:var(--mlv-border-focus);background:var(--mlv-background-selected)}.mlv-editor-table__grid-cell:disabled{cursor:default;opacity:var(--mlv-disabled-opacity)}.mlv-editor-table__grid-cell:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor-table__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--mlv-spacing-3)}.mlv-editor-table__size{color:var(--mlv-text-secondary);font-size:var(--mlv-typography-body-s-size);font-variant-numeric:tabular-nums;white-space:nowrap}@media(prefers-reduced-motion:reduce){.mlv-editor-table,.mlv-editor-table *,.mlv-editor-table:before,.mlv-editor-table:after,[class^=mlv-editor-table__],[class*=\" mlv-editor-table__\"],[class^=mlv-editor-table--],[class*=\" mlv-editor-table--\"]{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:var(--mlv-duration-instant)!important}}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "component", type: MlvButtonToggle, selector: "mlv-button-toggle", inputs: ["pressed", "disabled", "variant", "shape", "mlvDensity", "ariaLabel", "ariaLabelledBy"], outputs: ["pressedChange"] }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideTable2, selector: "svg[lucideTable2]" }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "directive", type: MlvPopupTrigger, selector: "[mlvPopupTrigger]", inputs: ["mlvPopupTrigger", "triggerOn", "ariaHasPopup"], exportAs: ["mlvPopupTrigger"] }, { kind: "component", type: MlvDivider, selector: "mlv-divider", inputs: ["orientation", "dashed", "muted"] }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8631
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
8632
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorTable, isStandalone: true, selector: "mlv-editor-table", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-table" }, viewQueries: [{ propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, isSignal: true }, { propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_menuTrigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }, { propertyName: "_triggerElement", first: true, predicate: ["_triggerElement"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_panel", first: true, predicate: ["_panel"], descendants: true, isSignal: true }, { propertyName: "_firstAction", first: true, predicate: ["_firstAction"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_gridCells", predicate: ["_gridCell"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "@if (_active()) {\n <button\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n selected\n [disabled]=\"_disabled()\"\n [mlvMenuTrigger]=\"_menu\"\n [menuTriggerDisabled]=\"_disabled()\"\n [attr.aria-label]=\"_copy().table\"\n [mlvTooltip]=\"_copy().table\"\n (menuOpened)=\"_registerMenuOverlay()\"\n (menuClosed)=\"_unregisterMenuOverlay()\"\n >\n <svg mlvButtonIcon lucideTable2 aria-hidden=\"true\" />\n </button>\n\n <mlv-menu #_menu [label]=\"_copy().table\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddRowBefore)\"\n (itemClick)=\"_run(_addRowBefore)\"\n >\n {{ _copy().addRowBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddRowAfter)\"\n (itemClick)=\"_run(_addRowAfter)\"\n >\n {{ _copy().addRowAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canDeleteRow)\"\n (itemClick)=\"_run(_deleteRow)\"\n >\n {{ _copy().deleteRow }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddColumnBefore)\"\n (itemClick)=\"_run(_addColumnBefore)\"\n >\n {{ _copy().addColumnBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canAddColumnAfter)\"\n (itemClick)=\"_run(_addColumnAfter)\"\n >\n {{ _copy().addColumnAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canDeleteColumn)\"\n (itemClick)=\"_run(_deleteColumn)\"\n >\n {{ _copy().deleteColumn }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canMergeCells)\"\n (itemClick)=\"_run(_mergeCells)\"\n >\n {{ _copy().mergeCells }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canSplitCell)\"\n (itemClick)=\"_run(_splitCell)\"\n >\n {{ _copy().splitCell }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canToggleHeaderRow)\"\n (itemClick)=\"_run(_toggleHeaderRow)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canToggleHeaderColumn)\"\n (itemClick)=\"_run(_toggleHeaderColumn)\"\n >\n {{ _copy().toggleHeaderColumn }}\n </mlv-list-item>\n <!-- `aria-current` marks the state the caret's cell is already in; the row\n paints itself from the shared selected pair. -->\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canToggleHeaderCell)\"\n [attr.aria-current]=\"_isHeaderCell() ? 'true' : null\"\n (itemClick)=\"_run(_toggleHeaderCell)\"\n >\n {{ _copy().toggleHeaderCell }}\n </mlv-list-item>\n\n <mlv-divider muted />\n\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_commandDisabled(_canDeleteTable)\"\n (itemClick)=\"_run(_deleteTable, true)\"\n >\n {{ _copy().deleteTable }}\n </mlv-list-item>\n </mlv-menu>\n} @else {\n <button\n #_trigger=\"mlvPopupTrigger\"\n #_triggerElement\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().table\"\n [mlvTooltip]=\"_copy().table\"\n >\n <svg mlvButtonIcon lucideTable2 aria-hidden=\"true\" />\n </button>\n\n <mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().table\"\n (afterOpened)=\"_onOpened()\"\n (afterClosed)=\"_onClosed()\"\n >\n <ng-template mlvPopupContent>\n <div #_panel class=\"mlv-editor-table__panel\">\n <div\n class=\"mlv-editor-table__grid\"\n role=\"grid\"\n tabindex=\"-1\"\n [attr.aria-label]=\"_copy().insertTable\"\n (keydown)=\"_onGridKeydown($event)\"\n >\n @for (row of _gridRows; track $index) {\n <div class=\"mlv-editor-table__grid-row\" role=\"row\">\n @for (size of row; track size.cols) {\n <button\n #_gridCell\n class=\"mlv-editor-table__grid-cell\"\n type=\"button\"\n role=\"gridcell\"\n [class.mlv-editor-table__grid-cell--selected]=\"\n _sizeSelected(size)\n \"\n [attr.aria-label]=\"_sizeLabel(size)\"\n [attr.aria-selected]=\"_sizeSelected(size)\"\n [attr.tabindex]=\"\n size.rows === _selectedSize().rows &&\n size.cols === _selectedSize().cols\n ? 0\n : -1\n \"\n [disabled]=\"_disabled()\"\n (focus)=\"_selectSize(size)\"\n (mouseenter)=\"_selectSize(size)\"\n (click)=\"_insertSize(size)\"\n ></button>\n }\n </div>\n }\n </div>\n\n <div class=\"mlv-editor-table__footer\">\n <span class=\"mlv-editor-table__size\" aria-hidden=\"true\">\n {{ _selectedSize().rows }} \u00D7 {{ _selectedSize().cols }}\n </span>\n <mlv-button-toggle\n [pressed]=\"_withHeaderRow()\"\n [disabled]=\"_disabled()\"\n (pressedChange)=\"_withHeaderRow.set($event)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-button-toggle>\n </div>\n </div>\n </ng-template>\n </mlv-popup>\n}\n", styles: ["@layer mlv.components{.mlv-editor-table{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-table *{box-sizing:border-box}.mlv-editor-table{display:inline-flex}.mlv-editor-table__panel{display:grid;width:max-content;max-width:calc(100vw - 3.5rem);padding:var(--mlv-popover-inset);gap:var(--mlv-spacing-2)}.mlv-editor-table__actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:min(24rem,100vw - 4rem);gap:var(--mlv-spacing-2)}.mlv-editor-table__grid{display:grid;grid-template-columns:1fr;gap:.125rem}.mlv-editor-table__grid-row{display:grid;grid-template-columns:repeat(10,1.25rem);gap:.125rem}.mlv-editor-table__grid-cell{appearance:none;box-sizing:border-box;inline-size:1.25rem;block-size:1.25rem;margin:0;padding:0;border:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-radius:var(--mlv-radius-xs);background:var(--mlv-background-base);cursor:pointer;transition:background-color var(--mlv-duration-fast) var(--mlv-ease-default),border-color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor-table__grid-cell--selected{border-color:var(--mlv-border-focus);background:var(--mlv-background-selected)}.mlv-editor-table__grid-cell:disabled{cursor:default;opacity:var(--mlv-disabled-opacity)}.mlv-editor-table__grid-cell:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor-table__footer{display:flex;align-items:center;justify-content:space-between;gap:var(--mlv-spacing-3)}.mlv-editor-table__size{color:var(--mlv-text-secondary);font-size:var(--mlv-typography-body-s-size);font-variant-numeric:tabular-nums;white-space:nowrap}@media(prefers-reduced-motion:reduce){.mlv-editor-table,.mlv-editor-table *,.mlv-editor-table:before,.mlv-editor-table:after,[class^=mlv-editor-table__],[class*=\" mlv-editor-table__\"],[class^=mlv-editor-table--],[class*=\" mlv-editor-table--\"]{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:var(--mlv-duration-instant)!important}}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "component", type: MlvButtonToggle, selector: "mlv-button-toggle", inputs: ["pressed", "disabled", "variant", "shape", "mlvDensity", "ariaLabel", "ariaLabelledBy"], outputs: ["pressedChange"] }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideTable2, selector: "svg[lucideTable2]" }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "directive", type: MlvPopupTrigger, selector: "[mlvPopupTrigger]", inputs: ["mlvPopupTrigger", "triggerOn", "ariaHasPopup"], exportAs: ["mlvPopupTrigger"] }, { kind: "component", type: MlvDivider, selector: "mlv-divider", inputs: ["orientation", "dashed", "muted"] }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7451
8633
  }
7452
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorTable, decorators: [{
8634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorTable, decorators: [{
7453
8635
  type: Component,
7454
8636
  args: [{ selector: 'mlv-editor-table', imports: [
7455
8637
  MlvButton,
@@ -7632,21 +8814,24 @@ class MlvEditorTableControls {
7632
8814
  ...(ngDevMode ? [{ debugName: "_enabled" }] : /* istanbul ignore next */ []));
7633
8815
  constructor() {
7634
8816
  const destroyRef = inject(DestroyRef);
7635
- const onPointerMove = (event) => this._track(event);
7636
- const onPointerLeave = () => this._release();
7637
- // Registered on the document rather than on the editor DOM: the ProseMirror
8817
+ // Subscribed on the document rather than on the editor DOM: the ProseMirror
7638
8818
  // element is replaced whenever the editor is recreated (a format switch, an
7639
8819
  // extension-set change), and a listener bound to the old node would go
7640
- // quiet without ever erroring. Capture is not needed — the events bubble.
7641
- document.addEventListener('pointermove', onPointerMove, { passive: true });
7642
- document.addEventListener('pointerleave', onPointerLeave, {
7643
- passive: true,
7644
- });
7645
- destroyRef.onDestroy(() => {
7646
- document.removeEventListener('pointermove', onPointerMove);
7647
- document.removeEventListener('pointerleave', onPointerLeave);
7648
- this._unregisterOverlay();
7649
- });
8820
+ // quiet without ever erroring. Capture is not needed — the events bubble,
8821
+ // and `passive` is carried through because neither handler calls
8822
+ // `preventDefault()`.
8823
+ //
8824
+ // The *injected* document, never the ambient global: this component is
8825
+ // constructed during server rendering too, where the two are different
8826
+ // objects, so the bare global would bind a per-render component to a
8827
+ // process-wide document that no teardown reaches.
8828
+ fromEvent(this._document, 'pointermove', { passive: true })
8829
+ .pipe(takeUntilDestroyed(destroyRef))
8830
+ .subscribe((event) => this._track(event));
8831
+ fromEvent(this._document, 'pointerleave', { passive: true })
8832
+ .pipe(takeUntilDestroyed(destroyRef))
8833
+ .subscribe(() => this._release());
8834
+ destroyRef.onDestroy(() => this._unregisterOverlay());
7650
8835
  }
7651
8836
  /** @protected Inline placement for one grip box. */
7652
8837
  _style(box) {
@@ -7819,10 +9004,10 @@ class MlvEditorTableControls {
7819
9004
  gripSize: GRIP_SIZE,
7820
9005
  }));
7821
9006
  }
7822
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorTableControls, deps: [], target: i0.ɵɵFactoryTarget.Component });
7823
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorTableControls, isStandalone: true, selector: "mlv-editor-table-controls", inputs: { layer: { classPropertyName: "layer", publicName: "layer", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, classAttribute: "mlv-editor-table-controls" }, ngImport: i0, template: "@if (_geometry(); as geometry) {\n <button\n class=\"mlv-editor-table-controls__grip mlv-editor-table-controls__grip--corner\"\n type=\"button\"\n tabindex=\"-1\"\n [style]=\"_style(geometry.corner)\"\n [mlvMenuTrigger]=\"_cornerMenu\"\n [attr.aria-label]=\"_copy().tableActions\"\n (menuOpened)=\"_openMenu(_cornerMenu)\"\n (menuClosed)=\"_closeMenu()\"\n >\n <svg lucideEllipsis [size]=\"10\" aria-hidden=\"true\" />\n </button>\n <mlv-menu #_cornerMenu [label]=\"_copy().tableActions\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderRow)\"\n (itemClick)=\"_run(_toggleHeaderRow)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderColumn)\"\n (itemClick)=\"_run(_toggleHeaderColumn)\"\n >\n {{ _copy().toggleHeaderColumn }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderCell)\"\n (itemClick)=\"_run(_toggleHeaderCell)\"\n >\n {{ _copy().toggleHeaderCell }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canMergeCells)\"\n (itemClick)=\"_run(_mergeCells)\"\n >\n {{ _copy().mergeCells }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canSplitCell)\"\n (itemClick)=\"_run(_splitCell)\"\n >\n {{ _copy().splitCell }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canDeleteTable)\"\n (itemClick)=\"_run(_deleteTable)\"\n >\n {{ _copy().deleteTable }}\n </mlv-list-item>\n </mlv-menu>\n\n <button\n class=\"mlv-editor-table-controls__grip mlv-editor-table-controls__grip--column\"\n type=\"button\"\n tabindex=\"-1\"\n [style]=\"_style(geometry.column)\"\n [mlvMenuTrigger]=\"_columnMenu\"\n [attr.aria-label]=\"_copy().columnActions\"\n (menuOpened)=\"_openMenu(_columnMenu)\"\n (menuClosed)=\"_closeMenu()\"\n >\n <svg lucideGripHorizontal [size]=\"12\" aria-hidden=\"true\" />\n </button>\n <mlv-menu #_columnMenu [label]=\"_copy().columnActions\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddColumnBefore)\"\n (itemClick)=\"_run(_addColumnBefore)\"\n >\n {{ _copy().addColumnBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddColumnAfter)\"\n (itemClick)=\"_run(_addColumnAfter)\"\n >\n {{ _copy().addColumnAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderColumn)\"\n (itemClick)=\"_run(_toggleHeaderColumn)\"\n >\n {{ _copy().toggleHeaderColumn }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canDeleteColumn)\"\n (itemClick)=\"_run(_deleteColumn)\"\n >\n {{ _copy().deleteColumn }}\n </mlv-list-item>\n </mlv-menu>\n\n <button\n class=\"mlv-editor-table-controls__grip mlv-editor-table-controls__grip--row\"\n type=\"button\"\n tabindex=\"-1\"\n [style]=\"_style(geometry.row)\"\n [mlvMenuTrigger]=\"_rowMenu\"\n [attr.aria-label]=\"_copy().rowActions\"\n (menuOpened)=\"_openMenu(_rowMenu)\"\n (menuClosed)=\"_closeMenu()\"\n >\n <svg lucideGripVertical [size]=\"12\" aria-hidden=\"true\" />\n </button>\n <mlv-menu #_rowMenu [label]=\"_copy().rowActions\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddRowBefore)\"\n (itemClick)=\"_run(_addRowBefore)\"\n >\n {{ _copy().addRowBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddRowAfter)\"\n (itemClick)=\"_run(_addRowAfter)\"\n >\n {{ _copy().addRowAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderRow)\"\n (itemClick)=\"_run(_toggleHeaderRow)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canDeleteRow)\"\n (itemClick)=\"_run(_deleteRow)\"\n >\n {{ _copy().deleteRow }}\n </mlv-list-item>\n </mlv-menu>\n}\n", styles: ["@layer mlv.components{.mlv-editor-table-controls{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-table-controls *{box-sizing:border-box}.mlv-editor-table-controls{display:contents;--mlv-editor-table-grip-size: 14px}.mlv-editor-table-controls__grip{position:absolute;display:flex;align-items:center;justify-content:center;padding:0;border:var(--mlv-stroke-width) solid var(--mlv-border-subtle);border-radius:var(--mlv-radius-xs);background:var(--mlv-background-neutral-1);color:var(--mlv-text-secondary);cursor:pointer;z-index:1;transition:background-color var(--mlv-duration-fast) var(--mlv-ease-default),color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor-table-controls__grip:hover{background:var(--mlv-background-neutral-1-hover);color:var(--mlv-text-primary)}.mlv-editor-table-controls__grip[aria-expanded=true]{background:var(--mlv-background-selected);color:var(--mlv-text-on-selected)}.mlv-editor-table-controls__grip--corner svg{opacity:.75}@media(prefers-reduced-motion:reduce){.mlv-editor-table-controls,.mlv-editor-table-controls *,.mlv-editor-table-controls:before,.mlv-editor-table-controls:after,[class^=mlv-editor-table-controls__],[class*=\" mlv-editor-table-controls__\"],[class^=mlv-editor-table-controls--],[class*=\" mlv-editor-table-controls--\"]{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:var(--mlv-duration-instant)!important}}}\n"], dependencies: [{ kind: "component", type: LucideEllipsis, selector: "svg[lucideEllipsis], svg[lucideMoreHorizontal]" }, { kind: "component", type: LucideGripHorizontal, selector: "svg[lucideGripHorizontal]" }, { kind: "component", type: LucideGripVertical, selector: "svg[lucideGripVertical]" }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9007
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorTableControls, deps: [], target: i0.ɵɵFactoryTarget.Component });
9008
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorTableControls, isStandalone: true, selector: "mlv-editor-table-controls", inputs: { layer: { classPropertyName: "layer", publicName: "layer", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "aria-hidden": "true" }, classAttribute: "mlv-editor-table-controls" }, ngImport: i0, template: "@if (_geometry(); as geometry) {\n <button\n class=\"mlv-editor-table-controls__grip mlv-editor-table-controls__grip--corner\"\n type=\"button\"\n tabindex=\"-1\"\n [style]=\"_style(geometry.corner)\"\n [mlvMenuTrigger]=\"_cornerMenu\"\n [attr.aria-label]=\"_copy().tableActions\"\n (menuOpened)=\"_openMenu(_cornerMenu)\"\n (menuClosed)=\"_closeMenu()\"\n >\n <svg lucideEllipsis [size]=\"10\" aria-hidden=\"true\" />\n </button>\n <mlv-menu #_cornerMenu [label]=\"_copy().tableActions\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderRow)\"\n (itemClick)=\"_run(_toggleHeaderRow)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderColumn)\"\n (itemClick)=\"_run(_toggleHeaderColumn)\"\n >\n {{ _copy().toggleHeaderColumn }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderCell)\"\n (itemClick)=\"_run(_toggleHeaderCell)\"\n >\n {{ _copy().toggleHeaderCell }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canMergeCells)\"\n (itemClick)=\"_run(_mergeCells)\"\n >\n {{ _copy().mergeCells }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canSplitCell)\"\n (itemClick)=\"_run(_splitCell)\"\n >\n {{ _copy().splitCell }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canDeleteTable)\"\n (itemClick)=\"_run(_deleteTable)\"\n >\n {{ _copy().deleteTable }}\n </mlv-list-item>\n </mlv-menu>\n\n <button\n class=\"mlv-editor-table-controls__grip mlv-editor-table-controls__grip--column\"\n type=\"button\"\n tabindex=\"-1\"\n [style]=\"_style(geometry.column)\"\n [mlvMenuTrigger]=\"_columnMenu\"\n [attr.aria-label]=\"_copy().columnActions\"\n (menuOpened)=\"_openMenu(_columnMenu)\"\n (menuClosed)=\"_closeMenu()\"\n >\n <svg lucideGripHorizontal [size]=\"12\" aria-hidden=\"true\" />\n </button>\n <mlv-menu #_columnMenu [label]=\"_copy().columnActions\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddColumnBefore)\"\n (itemClick)=\"_run(_addColumnBefore)\"\n >\n {{ _copy().addColumnBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddColumnAfter)\"\n (itemClick)=\"_run(_addColumnAfter)\"\n >\n {{ _copy().addColumnAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderColumn)\"\n (itemClick)=\"_run(_toggleHeaderColumn)\"\n >\n {{ _copy().toggleHeaderColumn }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canDeleteColumn)\"\n (itemClick)=\"_run(_deleteColumn)\"\n >\n {{ _copy().deleteColumn }}\n </mlv-list-item>\n </mlv-menu>\n\n <button\n class=\"mlv-editor-table-controls__grip mlv-editor-table-controls__grip--row\"\n type=\"button\"\n tabindex=\"-1\"\n [style]=\"_style(geometry.row)\"\n [mlvMenuTrigger]=\"_rowMenu\"\n [attr.aria-label]=\"_copy().rowActions\"\n (menuOpened)=\"_openMenu(_rowMenu)\"\n (menuClosed)=\"_closeMenu()\"\n >\n <svg lucideGripVertical [size]=\"12\" aria-hidden=\"true\" />\n </button>\n <mlv-menu #_rowMenu [label]=\"_copy().rowActions\">\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddRowBefore)\"\n (itemClick)=\"_run(_addRowBefore)\"\n >\n {{ _copy().addRowBefore }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canAddRowAfter)\"\n (itemClick)=\"_run(_addRowAfter)\"\n >\n {{ _copy().addRowAfter }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canToggleHeaderRow)\"\n (itemClick)=\"_run(_toggleHeaderRow)\"\n >\n {{ _copy().toggleHeaderRow }}\n </mlv-list-item>\n <mlv-list-item\n mlvMenuItem\n [disabled]=\"_disabled(_canDeleteRow)\"\n (itemClick)=\"_run(_deleteRow)\"\n >\n {{ _copy().deleteRow }}\n </mlv-list-item>\n </mlv-menu>\n}\n", styles: ["@layer mlv.components{.mlv-editor-table-controls{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-table-controls *{box-sizing:border-box}.mlv-editor-table-controls{display:contents;--mlv-editor-table-grip-size: 14px}.mlv-editor-table-controls__grip{position:absolute;display:flex;align-items:center;justify-content:center;padding:0;border:var(--mlv-stroke-width) solid var(--mlv-border-subtle);border-radius:var(--mlv-radius-xs);background:var(--mlv-background-neutral-1);color:var(--mlv-text-secondary);cursor:pointer;z-index:1;transition:background-color var(--mlv-duration-fast) var(--mlv-ease-default),color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor-table-controls__grip:hover{background:var(--mlv-background-neutral-1-hover);color:var(--mlv-text-primary)}.mlv-editor-table-controls__grip[aria-expanded=true]{background:var(--mlv-background-selected);color:var(--mlv-text-on-selected)}.mlv-editor-table-controls__grip--corner svg{opacity:.75}@media(prefers-reduced-motion:reduce){.mlv-editor-table-controls,.mlv-editor-table-controls *,.mlv-editor-table-controls:before,.mlv-editor-table-controls:after,[class^=mlv-editor-table-controls__],[class*=\" mlv-editor-table-controls__\"],[class^=mlv-editor-table-controls--],[class*=\" mlv-editor-table-controls--\"]{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:var(--mlv-duration-instant)!important}}}\n"], dependencies: [{ kind: "component", type: LucideEllipsis, selector: "svg[lucideEllipsis], svg[lucideMoreHorizontal]" }, { kind: "component", type: LucideGripHorizontal, selector: "svg[lucideGripHorizontal]" }, { kind: "component", type: LucideGripVertical, selector: "svg[lucideGripVertical]" }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7824
9009
  }
7825
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorTableControls, decorators: [{
9010
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorTableControls, decorators: [{
7826
9011
  type: Component,
7827
9012
  args: [{ selector: 'mlv-editor-table-controls', imports: [
7828
9013
  LucideEllipsis,
@@ -7848,10 +9033,10 @@ class MlvEditorToolbarDef {
7848
9033
  static ngTemplateContextGuard(_dir, _context) {
7849
9034
  return true;
7850
9035
  }
7851
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7852
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorToolbarDef, isStandalone: true, selector: "[mlvEditorToolbar]", ngImport: i0 });
9036
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9037
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorToolbarDef, isStandalone: true, selector: "[mlvEditorToolbar]", ngImport: i0 });
7853
9038
  }
7854
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarDef, decorators: [{
9039
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarDef, decorators: [{
7855
9040
  type: Directive,
7856
9041
  args: [{
7857
9042
  selector: '[mlvEditorToolbar]',
@@ -7867,10 +9052,10 @@ class MlvEditorToolbarStartDef {
7867
9052
  static ngTemplateContextGuard(_dir, _context) {
7868
9053
  return true;
7869
9054
  }
7870
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarStartDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7871
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorToolbarStartDef, isStandalone: true, selector: "[mlvEditorToolbarStart]", ngImport: i0 });
9055
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarStartDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9056
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorToolbarStartDef, isStandalone: true, selector: "[mlvEditorToolbarStart]", ngImport: i0 });
7872
9057
  }
7873
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarStartDef, decorators: [{
9058
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarStartDef, decorators: [{
7874
9059
  type: Directive,
7875
9060
  args: [{
7876
9061
  selector: '[mlvEditorToolbarStart]',
@@ -7886,10 +9071,10 @@ class MlvEditorToolbarEndDef {
7886
9071
  static ngTemplateContextGuard(_dir, _context) {
7887
9072
  return true;
7888
9073
  }
7889
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarEndDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7890
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorToolbarEndDef, isStandalone: true, selector: "[mlvEditorToolbarEnd]", ngImport: i0 });
9074
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarEndDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9075
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorToolbarEndDef, isStandalone: true, selector: "[mlvEditorToolbarEnd]", ngImport: i0 });
7891
9076
  }
7892
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarEndDef, decorators: [{
9077
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarEndDef, decorators: [{
7893
9078
  type: Directive,
7894
9079
  args: [{
7895
9080
  selector: '[mlvEditorToolbarEnd]',
@@ -7918,8 +9103,8 @@ class MlvEditorUndoRedo {
7918
9103
  _undo = (editor) => editor.chain().focus().undo().run();
7919
9104
  /** @protected Selection-restoring redo command. */
7920
9105
  _redo = (editor) => editor.chain().focus().redo().run();
7921
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorUndoRedo, deps: [], target: i0.ɵɵFactoryTarget.Component });
7922
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorUndoRedo, isStandalone: true, selector: "mlv-editor-undo-redo", host: { attributes: { "role": "group" }, classAttribute: "mlv-editor-undo-redo" }, ngImport: i0, template: `
9106
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorUndoRedo, deps: [], target: i0.ɵɵFactoryTarget.Component });
9107
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorUndoRedo, isStandalone: true, selector: "mlv-editor-undo-redo", host: { attributes: { "role": "group" }, classAttribute: "mlv-editor-undo-redo" }, ngImport: i0, template: `
7923
9108
  <mlv-editor-command-button
7924
9109
  [label]="_copy().undo"
7925
9110
  [supports]="_canUndo"
@@ -7936,7 +9121,7 @@ class MlvEditorUndoRedo {
7936
9121
  /></mlv-editor-command-button>
7937
9122
  `, isInline: true, dependencies: [{ kind: "component", type: MlvEditorCommandButton, selector: "mlv-editor-command-button", inputs: ["label", "supports", "command", "canCommand", "pressed"] }, { kind: "component", type: LucideRedo2, selector: "svg[lucideRedo2]" }, { kind: "component", type: LucideUndo2, selector: "svg[lucideUndo2]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
7938
9123
  }
7939
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorUndoRedo, decorators: [{
9124
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorUndoRedo, decorators: [{
7940
9125
  type: Component,
7941
9126
  args: [{
7942
9127
  selector: 'mlv-editor-undo-redo',
@@ -8307,10 +9492,10 @@ class MlvEditorZoom {
8307
9492
  const finite = Number.isFinite(value) ? value : DEFAULT_ZOOM;
8308
9493
  return Math.min(Math.max(finite, min), max);
8309
9494
  }
8310
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorZoom, deps: [], target: i0.ɵɵFactoryTarget.Component });
8311
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.7", type: MlvEditorZoom, isStandalone: true, selector: "mlv-editor-zoom", inputs: { zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, zoomLevels: { classPropertyName: "zoomLevels", publicName: "zoomLevels", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, fitToContainer: { classPropertyName: "fitToContainer", publicName: "fitToContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zoom: "zoomChange" }, host: { classAttribute: "mlv-editor-zoom" }, providers: [MlvSelectionService], viewQueries: [{ propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, isSignal: true }, { propertyName: "_input", first: true, predicate: ["_input"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<button\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_zoomOutDisabled()\"\n [attr.aria-label]=\"_copy().zoomOut\"\n [mlvTooltip]=\"_copy().zoomOut\"\n (click)=\"_zoomOut()\"\n>\n <svg mlvButtonIcon lucideZoomOut aria-hidden=\"true\" />\n</button>\n\n<button\n #_trigger=\"mlvPopupTrigger\"\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().zoom\"\n [mlvTooltip]=\"_copy().zoom\"\n>\n {{ zoom() }}%\n</button>\n\n<button\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_zoomInDisabled()\"\n [attr.aria-label]=\"_copy().zoomIn\"\n [mlvTooltip]=\"_copy().zoomIn\"\n (click)=\"_zoomIn()\"\n>\n <svg mlvButtonIcon lucideZoomIn aria-hidden=\"true\" />\n</button>\n\n<mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().zoom\"\n (afterOpened)=\"_onPopupOpened()\"\n (afterClosed)=\"_onPopupClosed()\"\n>\n <ng-template mlvPopupContent>\n <div [id]=\"_panelId\" class=\"mlv-editor-zoom__panel\">\n <div class=\"mlv-editor-zoom__field\">\n <mlv-input\n #_input\n type=\"number\"\n inputMode=\"numeric\"\n [ariaLabel]=\"_copy().zoom\"\n [min]=\"_bounds().min\"\n [max]=\"_bounds().max\"\n [disabled]=\"_disabled()\"\n [value]=\"_draft()\"\n (valueChange)=\"_draft.set($event)\"\n (keydown.enter)=\"_applyDraft($event)\"\n (inputBlur)=\"_applyDraft()\"\n />\n </div>\n <mlv-dropdown-panel\n class=\"mlv-editor-zoom__levels\"\n [options]=\"_levelOptions()\"\n [selectedValues]=\"_selectedLevels()\"\n [ariaLabel]=\"_copy().zoom\"\n [maxHeight]=\"_PANEL_MAX_HEIGHT\"\n (valueChange)=\"_selectLevel($event)\"\n />\n </div>\n </ng-template>\n</mlv-popup>\n", styles: ["@layer mlv.components{.mlv-editor-zoom{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size)}.mlv-editor-zoom *{box-sizing:border-box}.mlv-editor-zoom{display:inline-flex;align-items:center;gap:var(--mlv-spacing-1);white-space:nowrap}.mlv-editor-zoom__panel{display:grid;inline-size:11rem;padding:var(--mlv-popover-inset);gap:var(--mlv-spacing-1)}.mlv-editor-zoom__field,.mlv-editor-zoom__levels{display:block}.mlv-editor-zoom__levels.mlv-dropdown-panel .mlv-dropdown-panel__listbox{--mlv-list-padding-block: 0}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideZoomIn, selector: "svg[lucideZoomIn]" }, { kind: "component", type: LucideZoomOut, selector: "svg[lucideZoomOut]" }, { kind: "component", type: MlvDropdownPanel, selector: "mlv-dropdown-panel", inputs: ["scrollMode", "minHeight", "maxHeight", "options", "multiple", "selectedValues", "itemTemplate", "focusMode", "activeIndex", "listboxId", "ariaLabel", "highlightQuery", "loading", "loadingText", "loadingMore", "hasMore", "infiniteScrollThreshold"], outputs: ["loadMore", "valueChange"] }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "directive", type: MlvPopupTrigger, selector: "[mlvPopupTrigger]", inputs: ["mlvPopupTrigger", "triggerOn", "ariaHasPopup"], exportAs: ["mlvPopupTrigger"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9495
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorZoom, deps: [], target: i0.ɵɵFactoryTarget.Component });
9496
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: MlvEditorZoom, isStandalone: true, selector: "mlv-editor-zoom", inputs: { zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, zoomLevels: { classPropertyName: "zoomLevels", publicName: "zoomLevels", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, fitToContainer: { classPropertyName: "fitToContainer", publicName: "fitToContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { zoom: "zoomChange" }, host: { classAttribute: "mlv-editor-zoom" }, providers: [MlvSelectionService], viewQueries: [{ propertyName: "_popup", first: true, predicate: ["_popup"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: ["_trigger"], descendants: true, isSignal: true }, { propertyName: "_input", first: true, predicate: ["_input"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<button\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_zoomOutDisabled()\"\n [attr.aria-label]=\"_copy().zoomOut\"\n [mlvTooltip]=\"_copy().zoomOut\"\n (click)=\"_zoomOut()\"\n>\n <svg mlvButtonIcon lucideZoomOut aria-hidden=\"true\" />\n</button>\n\n<button\n #_trigger=\"mlvPopupTrigger\"\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_disabled()\"\n [mlvPopupTrigger]=\"_popup\"\n [ariaHasPopup]=\"'dialog'\"\n [attr.aria-label]=\"_copy().zoom\"\n [mlvTooltip]=\"_copy().zoom\"\n>\n {{ zoom() }}%\n</button>\n\n<button\n mlvButton\n mlvEditorToolbarWidget\n type=\"button\"\n shape=\"square\"\n mlvDensity=\"tight\"\n variant=\"transparent\"\n [disabled]=\"_zoomInDisabled()\"\n [attr.aria-label]=\"_copy().zoomIn\"\n [mlvTooltip]=\"_copy().zoomIn\"\n (click)=\"_zoomIn()\"\n>\n <svg mlvButtonIcon lucideZoomIn aria-hidden=\"true\" />\n</button>\n\n<mlv-popup\n #_popup\n panelRole=\"dialog\"\n [modal]=\"true\"\n [ariaLabel]=\"_copy().zoom\"\n (afterOpened)=\"_onPopupOpened()\"\n (afterClosed)=\"_onPopupClosed()\"\n>\n <ng-template mlvPopupContent>\n <div [id]=\"_panelId\" class=\"mlv-editor-zoom__panel\">\n <div class=\"mlv-editor-zoom__field\">\n <mlv-input\n #_input\n type=\"number\"\n inputMode=\"numeric\"\n [ariaLabel]=\"_copy().zoom\"\n [min]=\"_bounds().min\"\n [max]=\"_bounds().max\"\n [disabled]=\"_disabled()\"\n [value]=\"_draft()\"\n (valueChange)=\"_draft.set($event)\"\n (keydown.enter)=\"_applyDraft($event)\"\n (inputBlur)=\"_applyDraft()\"\n />\n </div>\n <mlv-dropdown-panel\n class=\"mlv-editor-zoom__levels\"\n [options]=\"_levelOptions()\"\n [selectedValues]=\"_selectedLevels()\"\n [ariaLabel]=\"_copy().zoom\"\n [maxHeight]=\"_PANEL_MAX_HEIGHT\"\n (valueChange)=\"_selectLevel($event)\"\n />\n </div>\n </ng-template>\n</mlv-popup>\n", styles: ["@layer mlv.components{.mlv-editor-zoom{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size)}.mlv-editor-zoom *{box-sizing:border-box}.mlv-editor-zoom{display:inline-flex;align-items:center;gap:var(--mlv-spacing-1);white-space:nowrap}.mlv-editor-zoom__panel{display:grid;inline-size:11rem;padding:var(--mlv-popover-inset);gap:var(--mlv-spacing-1)}.mlv-editor-zoom__field,.mlv-editor-zoom__levels{display:block}.mlv-editor-zoom__levels.mlv-dropdown-panel .mlv-dropdown-panel__listbox{--mlv-list-padding-block: 0}}\n"], dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideZoomIn, selector: "svg[lucideZoomIn]" }, { kind: "component", type: LucideZoomOut, selector: "svg[lucideZoomOut]" }, { kind: "component", type: MlvDropdownPanel, selector: "mlv-dropdown-panel", inputs: ["scrollMode", "minHeight", "maxHeight", "options", "multiple", "selectedValues", "compareWith", "itemTemplate", "focusMode", "activeIndex", "listboxId", "ariaLabel", "highlightQuery", "loading", "loadingText", "loadingMore", "hasMore", "infiniteScrollThreshold"], outputs: ["loadMore", "valueChange"] }, { kind: "component", type: MlvInput, selector: "mlv-input", inputs: ["type", "placeholder", "autocomplete", "inputMode", "maxLength", "minLength", "pattern", "name", "autofocus", "min", "max", "step", "bare", "projectControl", "role", "ariaExpanded", "ariaHasPopup", "ariaAutocomplete", "ariaControls", "ariaActiveDescendant", "ariaDescribedBy", "value"], outputs: ["valueChange", "inputFocus", "inputBlur"] }, { kind: "component", type: MlvPopup, selector: "mlv-popup", inputs: ["position", "positions", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "hasArrow", "hasShadow", "class", "mlvDensity", "flexibleDimensions", "scrollStrategy", "hasBackdrop", "restrictPosition", "panelRole", "modal", "ariaLabel", "dismissExcludeElements", "mobileMode", "mobileBreakpoint", "mobileTitle", "mobileCloseLabel", "opened"], outputs: ["openedChange", "afterOpened", "afterClosed"] }, { kind: "directive", type: MlvPopupContent, selector: "[mlvPopupContent]" }, { kind: "directive", type: MlvPopupTrigger, selector: "[mlvPopupTrigger]", inputs: ["mlvPopupTrigger", "triggerOn", "ariaHasPopup"], exportAs: ["mlvPopupTrigger"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8312
9497
  }
8313
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorZoom, decorators: [{
9498
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorZoom, decorators: [{
8314
9499
  type: Component,
8315
9500
  args: [{ selector: 'mlv-editor-zoom', imports: [
8316
9501
  MlvButton,
@@ -8356,8 +9541,8 @@ class MlvEditorTextColor extends MlvEditorColorControl {
8356
9541
  const chain = editor.chain().setTextSelection(selection);
8357
9542
  return color ? chain.setColor(color).run() : chain.unsetColor().run();
8358
9543
  }
8359
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorTextColor, deps: null, target: i0.ɵɵFactoryTarget.Component });
8360
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: MlvEditorTextColor, isStandalone: true, selector: "mlv-editor-text-color", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-text-color" }, usesInheritance: true, ngImport: i0, template: `
9544
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorTextColor, deps: null, target: i0.ɵɵFactoryTarget.Component });
9545
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MlvEditorTextColor, isStandalone: true, selector: "mlv-editor-text-color", host: { properties: { "hidden": "!_supported()" }, classAttribute: "mlv-editor-text-color" }, usesInheritance: true, ngImport: i0, template: `
8361
9546
  <mlv-color-picker-popup
8362
9547
  #picker
8363
9548
  presentation="icon"
@@ -8374,7 +9559,7 @@ class MlvEditorTextColor extends MlvEditorColorControl {
8374
9559
  </mlv-color-picker-popup>
8375
9560
  `, isInline: true, dependencies: [{ kind: "component", type: MlvColorPickerPopup, selector: "mlv-color-picker-popup", inputs: ["value", "presentation", "opened", "showOpacity", "defaultMode", "supportedFormats", "live"], outputs: ["valueChange", "openedChange", "afterOpened", "afterClosed", "colorChange"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "component", type: LucideType, selector: "svg[lucideType]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8376
9561
  }
8377
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorTextColor, decorators: [{
9562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorTextColor, decorators: [{
8378
9563
  type: Component,
8379
9564
  args: [{
8380
9565
  selector: 'mlv-editor-text-color',
@@ -8419,6 +9604,16 @@ class MlvEditorToolbarRoot {
8419
9604
  ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
8420
9605
  /** Whether arrow navigation wraps at the boundaries. */
8421
9606
  wrap = input(true, { ...(ngDevMode ? { debugName: "wrap" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
9607
+ /**
9608
+ * @internal Element whose width decides narrow mode, instead of this root's
9609
+ * own. `MlvEditor` passes its `.mlv-editor__surface`, for the docked bar
9610
+ * and the selection bubble alike. The bubble hugs its controls, so measuring
9611
+ * itself would be circular: hiding groups shrinks the bubble, which
9612
+ * un-narrows it. `undefined` observes this root, which the standalone
9613
+ * `MlvEditorToolbar` shell keeps doing.
9614
+ */
9615
+ measureTarget = input(undefined, /* @ts-ignore */
9616
+ ...(ngDevMode ? [{ debugName: "measureTarget" }] : /* istanbul ignore next */ []));
8422
9617
  /** @internal Editor-scoped focus registry. */
8423
9618
  _registry = inject(MLV_EDITOR_TOOLBAR_ROVING);
8424
9619
  /** @internal Transaction state that re-evaluates disabled toolbar widgets. */
@@ -8427,7 +9622,10 @@ class MlvEditorToolbarRoot {
8427
9622
  });
8428
9623
  /** @internal Optional localized defaults. */
8429
9624
  _i18n = inject(MLV_EDITOR_I18N, { optional: true });
8430
- /** @internal Actual composite-root element. */
9625
+ /**
9626
+ * @internal Actual composite-root element. Also the scope the horizontal
9627
+ * roving arrows resolve their direction against.
9628
+ */
8431
9629
  _element = inject(ElementRef);
8432
9630
  /** @internal Root teardown lifecycle. */
8433
9631
  _destroyRef = inject(DestroyRef);
@@ -8437,6 +9635,12 @@ class MlvEditorToolbarRoot {
8437
9635
  _resizeObserver = inject(MlvResizeObserverService);
8438
9636
  /** @internal Normalizes horizontal arrow meaning for RTL toolbars. */
8439
9637
  _rtlService = inject(MlvRtlService);
9638
+ /**
9639
+ * @internal Direction applying to this toolbar, resolved once and cached
9640
+ * behind the shared `dir` observer rather than re-walked on every arrow
9641
+ * keypress.
9642
+ */
9643
+ _direction = this._rtlService.elementDirection(this._element);
8440
9644
  constructor() {
8441
9645
  effect(() => {
8442
9646
  this._revision?.();
@@ -8444,15 +9648,23 @@ class MlvEditorToolbarRoot {
8444
9648
  });
8445
9649
  if (this._isBrowser) {
8446
9650
  this._destroyRef.onDestroy(this._registry.connect(this._element.nativeElement));
8447
- this._resizeObserver
8448
- .observe(this._element)
8449
- .pipe(takeUntilDestroyed(this._destroyRef))
9651
+ }
9652
+ effect((onCleanup) => {
9653
+ if (!this._isBrowser)
9654
+ return;
9655
+ const target = this.measureTarget() ?? this._element.nativeElement;
9656
+ // Re-created whenever the target changes, so it is released by this
9657
+ // effect's cleanup; `takeUntilDestroyed` would fire only at destroy and
9658
+ // leak every earlier generation (best-practices, "DOM Listeners").
9659
+ const subscription = this._resizeObserver
9660
+ .observe(target)
8450
9661
  .subscribe((entries) => {
8451
9662
  const width = entries[entries.length - 1]?.contentRect.width;
8452
9663
  if (width !== undefined && width > 0)
8453
9664
  this.narrow.set(width < 640);
8454
9665
  });
8455
- }
9666
+ onCleanup(() => subscription.unsubscribe());
9667
+ });
8456
9668
  }
8457
9669
  /** @internal Resolves the user or localized toolbar label. */
8458
9670
  resolvedAriaLabel() {
@@ -8470,7 +9682,10 @@ class MlvEditorToolbarRoot {
8470
9682
  this._registry.focusBoundary(event.key === 'Home' ? 'start' : 'end');
8471
9683
  return;
8472
9684
  }
8473
- const key = this._rtlService.normalizeArrowKey(event);
9685
+ // Resolved against the toolbar root, not the document: direction is scoped,
9686
+ // so a toolbar inside a `dir` subtree — or inside a CDK overlay pane, which
9687
+ // is stamped with its own `dir` — must mirror on its own reading.
9688
+ const key = this._rtlService.normalizeArrowKey(event, this._direction());
8474
9689
  const direction = this.orientation() === 'vertical'
8475
9690
  ? key === UP_ARROW
8476
9691
  ? -1
@@ -8487,10 +9702,10 @@ class MlvEditorToolbarRoot {
8487
9702
  event.preventDefault();
8488
9703
  this._registry.move(target, direction, this.wrap());
8489
9704
  }
8490
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8491
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: MlvEditorToolbarRoot, isStandalone: true, selector: "[mlvEditorToolbarRoot]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar" }, listeners: { "keydown": "handleKeydown($event)" }, properties: { "class.mlv-editor-toolbar--narrow": "narrow()", "attr.aria-label": "resolvedAriaLabel()", "attr.aria-orientation": "orientation()", "attr.aria-disabled": "disabled() || null" }, classAttribute: "mlv-editor-toolbar" }, exportAs: ["mlvEditorToolbarRoot"], ngImport: i0 });
9705
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive });
9706
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.7", type: MlvEditorToolbarRoot, isStandalone: true, selector: "[mlvEditorToolbarRoot]", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, measureTarget: { classPropertyName: "measureTarget", publicName: "measureTarget", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "toolbar" }, listeners: { "keydown": "handleKeydown($event)" }, properties: { "class.mlv-editor-toolbar--narrow": "narrow()", "attr.aria-label": "resolvedAriaLabel()", "attr.aria-orientation": "orientation()", "attr.aria-disabled": "disabled() || null" }, classAttribute: "mlv-editor-toolbar" }, exportAs: ["mlvEditorToolbarRoot"], ngImport: i0 });
8492
9707
  }
8493
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarRoot, decorators: [{
9708
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarRoot, decorators: [{
8494
9709
  type: Directive,
8495
9710
  args: [{
8496
9711
  selector: '[mlvEditorToolbarRoot]',
@@ -8505,7 +9720,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
8505
9720
  '(keydown)': 'handleKeydown($event)',
8506
9721
  },
8507
9722
  }]
8508
- }], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }] } });
9723
+ }], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }], measureTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "measureTarget", required: false }] }] } });
8509
9724
  /** @internal Responsive menu containing commands removed from the primary row. */
8510
9725
  class MlvEditorToolbarOverflow {
8511
9726
  /** @protected Responsive state owned by the enclosing toolbar root. */
@@ -8656,8 +9871,8 @@ class MlvEditorToolbarOverflow {
8656
9871
  this._unregister?.();
8657
9872
  this._unregister = undefined;
8658
9873
  }
8659
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarOverflow, deps: [], target: i0.ɵɵFactoryTarget.Component });
8660
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorToolbarOverflow, isStandalone: true, selector: "mlv-editor-toolbar-overflow", host: { properties: { "hidden": "!_root.narrow()" }, classAttribute: "mlv-editor-toolbar__overflow" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
9874
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarOverflow, deps: [], target: i0.ɵɵFactoryTarget.Component });
9875
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorToolbarOverflow, isStandalone: true, selector: "mlv-editor-toolbar-overflow", host: { properties: { "hidden": "!_root.narrow()" }, classAttribute: "mlv-editor-toolbar__overflow" }, viewQueries: [{ propertyName: "_menu", first: true, predicate: ["_menu"], descendants: true, isSignal: true }, { propertyName: "_trigger", first: true, predicate: MlvMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
8661
9876
  <button
8662
9877
  mlvButton
8663
9878
  mlvEditorToolbarWidget
@@ -8755,7 +9970,7 @@ class MlvEditorToolbarOverflow {
8755
9970
  </mlv-menu>
8756
9971
  `, isInline: true, dependencies: [{ kind: "component", type: MlvButton, selector: "button[mlvButton], a[mlvButton]", inputs: ["variant", "shape", "disabled", "loading", "selected"] }, { kind: "directive", type: MlvButtonIcon, selector: "[mlvButtonIcon]" }, { kind: "directive", type: MlvTooltip, selector: "[mlvTooltip]", inputs: ["mlvTooltip", "tooltipPlacement", "tooltipTone", "tooltipDelay", "tooltipDisabled", "tooltipArrow"] }, { kind: "component", type: LucideEllipsis, selector: "svg[lucideEllipsis], svg[lucideMoreHorizontal]" }, { kind: "component", type: MlvListItem, selector: "mlv-list-item", inputs: ["itemRole", "unread", "accent"] }, { kind: "component", type: MlvMenu, selector: "mlv-menu", inputs: ["label", "mlvDensity", "dataSource"], outputs: ["closed"] }, { kind: "directive", type: MlvMenuItem, selector: "mlv-list-item[mlvMenuItem]", inputs: ["disabled"], outputs: ["itemClick"] }, { kind: "directive", type: MlvMenuTrigger, selector: "[mlvMenuTrigger]", inputs: ["mlvMenuTrigger", "isSubmenuTrigger", "menuTriggerDisabled"], outputs: ["menuOpened", "menuClosed"] }, { kind: "directive", type: MlvEditorToolbarWidget, selector: "[mlvEditorToolbarWidget]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
8757
9972
  }
8758
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarOverflow, decorators: [{
9973
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarOverflow, decorators: [{
8759
9974
  type: Component,
8760
9975
  args: [{
8761
9976
  selector: 'mlv-editor-toolbar-overflow',
@@ -8926,10 +10141,10 @@ class MlvEditorToolbarContextProxy {
8926
10141
  reportError(error) {
8927
10142
  this._toolbar.context().reportError(error);
8928
10143
  }
8929
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarContextProxy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8930
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarContextProxy });
10144
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarContextProxy, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10145
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarContextProxy });
8931
10146
  }
8932
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbarContextProxy, decorators: [{
10147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbarContextProxy, decorators: [{
8933
10148
  type: Injectable
8934
10149
  }] });
8935
10150
  /** Public compatibility toolbar shell for standalone consumer use. */
@@ -8965,8 +10180,8 @@ class MlvEditorToolbar {
8965
10180
  });
8966
10181
  });
8967
10182
  }
8968
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
8969
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorToolbar, isStandalone: true, selector: "mlv-editor-toolbar", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, startTemplate: { classPropertyName: "startTemplate", publicName: "startTemplate", isSignal: true, isRequired: false, transformFunction: null }, endTemplate: { classPropertyName: "endTemplate", publicName: "endTemplate", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.mlv-editor-toolbar--disabled": "disabled() || context().disabled()" }, classAttribute: "mlv-editor-toolbar" }, providers: [
10183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
10184
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorToolbar, isStandalone: true, selector: "mlv-editor-toolbar", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, startTemplate: { classPropertyName: "startTemplate", publicName: "startTemplate", isSignal: true, isRequired: false, transformFunction: null }, endTemplate: { classPropertyName: "endTemplate", publicName: "endTemplate", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.mlv-editor-toolbar--disabled": "disabled() || context().disabled()" }, classAttribute: "mlv-editor-toolbar" }, providers: [
8970
10185
  MlvEditorOverlayRegistry,
8971
10186
  MlvEditorToolbarRevision,
8972
10187
  MlvEditorToolbarRovingRegistry,
@@ -9071,9 +10286,9 @@ class MlvEditorToolbar {
9071
10286
  />
9072
10287
  }
9073
10288
  <ng-content select="[mlvEditorToolbarEnd]" /><ng-content />
9074
- </div>`, isInline: true, styles: ["@layer mlv.components{.mlv-editor-toolbar{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size)}.mlv-editor-toolbar *{box-sizing:border-box}.mlv-editor-toolbar{display:flex;align-items:center;min-width:0;overflow:hidden}.mlv-editor-toolbar [hidden]{display:none!important}.mlv-editor-toolbar__scroll{flex:1 1 auto;min-width:0}.mlv-editor-toolbar__scroll>.mlv-toolbar{width:max-content;min-width:100%;flex-wrap:nowrap}.mlv-editor-toolbar__separator{flex:0 0 auto;align-self:stretch;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor-toolbar__overflow,.mlv-editor-toolbar .mlv-editor-undo-redo,.mlv-editor-toolbar .mlv-editor-inline-marks,.mlv-editor-toolbar .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-toolbar mlv-editor-zoom,.mlv-editor-toolbar mlv-editor-heading,.mlv-editor-toolbar mlv-editor-list,.mlv-editor-toolbar mlv-editor-text-color,.mlv-editor-toolbar mlv-editor-highlight,.mlv-editor-toolbar mlv-editor-alignment,.mlv-editor-toolbar mlv-editor-link,.mlv-editor-toolbar mlv-editor-table,.mlv-editor-toolbar mlv-editor-image-upload{flex:0 0 auto}.mlv-editor-toolbar--disabled{opacity:var(--mlv-disabled-opacity)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MlvFade, selector: "[mlvFade]", inputs: ["mlvFadeHeight", "mlvFadeSize", "mlvFadeOffset", "mlvFade"] }, { kind: "component", type: MlvToolbar, selector: "mlv-toolbar", inputs: ["gap", "equalSize"] }, { kind: "component", type: MlvDivider, selector: "mlv-divider", inputs: ["orientation", "dashed", "muted"] }, { kind: "directive", type: MlvEditorToolbarRoot, selector: "[mlvEditorToolbarRoot]", inputs: ["ariaLabel", "disabled", "orientation", "wrap"], exportAs: ["mlvEditorToolbarRoot"] }, { kind: "component", type: MlvEditorUndoRedo, selector: "mlv-editor-undo-redo" }, { kind: "component", type: MlvEditorZoom, selector: "mlv-editor-zoom", inputs: ["zoom", "zoomLevels", "min", "max", "fitToContainer"], outputs: ["zoomChange"] }, { kind: "component", type: MlvEditorHeading, selector: "mlv-editor-heading", inputs: ["levels"] }, { kind: "component", type: MlvEditorList, selector: "mlv-editor-list" }, { kind: "component", type: MlvEditorInlineMarks, selector: "mlv-editor-inline-marks" }, { kind: "component", type: MlvEditorTextColor, selector: "mlv-editor-text-color" }, { kind: "component", type: MlvEditorHighlight, selector: "mlv-editor-highlight" }, { kind: "component", type: MlvEditorLink, selector: "mlv-editor-link", inputs: ["allowedProtocols"] }, { kind: "component", type: MlvEditorImageUpload, selector: "mlv-editor-image-upload" }, { kind: "component", type: MlvEditorTable, selector: "mlv-editor-table" }, { kind: "component", type: MlvEditorAlignment, selector: "mlv-editor-alignment" }, { kind: "component", type: MlvEditorBlockInsert, selector: "mlv-editor-block-insert" }, { kind: "component", type: MlvEditorToolbarOverflow, selector: "mlv-editor-toolbar-overflow" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10289
+ </div>`, isInline: true, styles: ["@layer mlv.components{.mlv-editor-toolbar{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size)}.mlv-editor-toolbar *{box-sizing:border-box}.mlv-editor-toolbar{display:flex;align-items:center;min-width:0;overflow:hidden}.mlv-editor-toolbar [hidden]{display:none!important}.mlv-editor-toolbar__scroll{flex:1 1 auto;min-width:0}.mlv-editor-toolbar__scroll>.mlv-toolbar{width:max-content;min-width:100%;flex-wrap:nowrap}.mlv-editor-toolbar__separator{flex:0 0 auto;align-self:stretch;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor-toolbar__overflow,.mlv-editor-toolbar .mlv-editor-undo-redo,.mlv-editor-toolbar .mlv-editor-inline-marks,.mlv-editor-toolbar .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-toolbar mlv-editor-zoom,.mlv-editor-toolbar mlv-editor-heading,.mlv-editor-toolbar mlv-editor-list,.mlv-editor-toolbar mlv-editor-text-color,.mlv-editor-toolbar mlv-editor-highlight,.mlv-editor-toolbar mlv-editor-alignment,.mlv-editor-toolbar mlv-editor-link,.mlv-editor-toolbar mlv-editor-table,.mlv-editor-toolbar mlv-editor-image-upload{flex:0 0 auto}.mlv-editor-toolbar--disabled{opacity:var(--mlv-disabled-opacity)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MlvFade, selector: "[mlvFade]", inputs: ["mlvFadeHeight", "mlvFadeSize", "mlvFadeOffset", "mlvFade"] }, { kind: "component", type: MlvToolbar, selector: "mlv-toolbar", inputs: ["gap", "equalSize"] }, { kind: "component", type: MlvDivider, selector: "mlv-divider", inputs: ["orientation", "dashed", "muted"] }, { kind: "directive", type: MlvEditorToolbarRoot, selector: "[mlvEditorToolbarRoot]", inputs: ["ariaLabel", "disabled", "orientation", "wrap", "measureTarget"], exportAs: ["mlvEditorToolbarRoot"] }, { kind: "component", type: MlvEditorUndoRedo, selector: "mlv-editor-undo-redo" }, { kind: "component", type: MlvEditorZoom, selector: "mlv-editor-zoom", inputs: ["zoom", "zoomLevels", "min", "max", "fitToContainer"], outputs: ["zoomChange"] }, { kind: "component", type: MlvEditorHeading, selector: "mlv-editor-heading", inputs: ["levels"] }, { kind: "component", type: MlvEditorList, selector: "mlv-editor-list" }, { kind: "component", type: MlvEditorInlineMarks, selector: "mlv-editor-inline-marks" }, { kind: "component", type: MlvEditorTextColor, selector: "mlv-editor-text-color" }, { kind: "component", type: MlvEditorHighlight, selector: "mlv-editor-highlight" }, { kind: "component", type: MlvEditorLink, selector: "mlv-editor-link", inputs: ["allowedProtocols"] }, { kind: "component", type: MlvEditorImageUpload, selector: "mlv-editor-image-upload" }, { kind: "component", type: MlvEditorTable, selector: "mlv-editor-table" }, { kind: "component", type: MlvEditorAlignment, selector: "mlv-editor-alignment" }, { kind: "component", type: MlvEditorBlockInsert, selector: "mlv-editor-block-insert" }, { kind: "component", type: MlvEditorToolbarOverflow, selector: "mlv-editor-toolbar-overflow" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9075
10290
  }
9076
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorToolbar, decorators: [{
10291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorToolbar, decorators: [{
9077
10292
  type: Component,
9078
10293
  args: [{ selector: 'mlv-editor-toolbar', host: {
9079
10294
  class: 'mlv-editor-toolbar',
@@ -9254,15 +10469,15 @@ class MlvEditorStatus {
9254
10469
  return typeof storage?.characters === 'function' ? storage : null;
9255
10470
  }, /* @ts-ignore */
9256
10471
  ...(ngDevMode ? [{ debugName: "_storage" }] : /* istanbul ignore next */ []));
9257
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
9258
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditorStatus, isStandalone: true, selector: "mlv-editor-status", inputs: { characterLimit: { classPropertyName: "characterLimit", publicName: "characterLimit", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!available()" }, classAttribute: "mlv-editor-status" }, ngImport: i0, template: "@if (available()) {\n<span\n class=\"mlv-editor-status__summary\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"_ariaLabel()\"\n>\n @if (_limit(); as limit) {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }}/{{ limit }} characters</span\n >\n } @else {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }} characters</span\n >\n }\n <span aria-hidden=\"true\"> \u00B7 </span>\n <span class=\"mlv-editor-status__words\">{{ words() }} words</span>\n</span>\n}\n", styles: ["@layer mlv.components{.mlv-editor-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-status *{box-sizing:border-box}.mlv-editor-status{display:inline-flex;color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}.mlv-editor-status__summary{display:inline-flex;gap:var(--mlv-spacing-1)}.mlv-editor-status__summary *{color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10472
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorStatus, deps: [], target: i0.ɵɵFactoryTarget.Component });
10473
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditorStatus, isStandalone: true, selector: "mlv-editor-status", inputs: { characterLimit: { classPropertyName: "characterLimit", publicName: "characterLimit", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "hidden": "!available()" }, classAttribute: "mlv-editor-status" }, ngImport: i0, template: "@if (available()) {\n <span\n class=\"mlv-editor-status__summary\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"_ariaLabel()\"\n >\n @if (_limit(); as limit) {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }}/{{ limit }} characters</span\n >\n } @else {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }} characters</span\n >\n }\n <span aria-hidden=\"true\"> \u00B7 </span>\n <span class=\"mlv-editor-status__words\">{{ words() }} words</span>\n </span>\n}\n", styles: ["@layer mlv.components{.mlv-editor-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-status *{box-sizing:border-box}.mlv-editor-status{display:inline-flex;color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}.mlv-editor-status__summary{display:inline-flex;gap:var(--mlv-spacing-1)}.mlv-editor-status__summary *{color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9259
10474
  }
9260
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorStatus, decorators: [{
10475
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditorStatus, decorators: [{
9261
10476
  type: Component,
9262
10477
  args: [{ selector: 'mlv-editor-status', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
9263
10478
  class: 'mlv-editor-status',
9264
10479
  '[hidden]': '!available()',
9265
- }, template: "@if (available()) {\n<span\n class=\"mlv-editor-status__summary\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"_ariaLabel()\"\n>\n @if (_limit(); as limit) {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }}/{{ limit }} characters</span\n >\n } @else {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }} characters</span\n >\n }\n <span aria-hidden=\"true\"> \u00B7 </span>\n <span class=\"mlv-editor-status__words\">{{ words() }} words</span>\n</span>\n}\n", styles: ["@layer mlv.components{.mlv-editor-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-status *{box-sizing:border-box}.mlv-editor-status{display:inline-flex;color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}.mlv-editor-status__summary{display:inline-flex;gap:var(--mlv-spacing-1)}.mlv-editor-status__summary *{color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}}\n"] }]
10480
+ }, template: "@if (available()) {\n <span\n class=\"mlv-editor-status__summary\"\n role=\"status\"\n aria-live=\"polite\"\n [attr.aria-label]=\"_ariaLabel()\"\n >\n @if (_limit(); as limit) {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }}/{{ limit }} characters</span\n >\n } @else {\n <span class=\"mlv-editor-status__characters\"\n >{{ characters() }} characters</span\n >\n }\n <span aria-hidden=\"true\"> \u00B7 </span>\n <span class=\"mlv-editor-status__words\">{{ words() }} words</span>\n </span>\n}\n", styles: ["@layer mlv.components{.mlv-editor-status{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-status *{box-sizing:border-box}.mlv-editor-status{display:inline-flex;color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}.mlv-editor-status__summary{display:inline-flex;gap:var(--mlv-spacing-1)}.mlv-editor-status__summary *{color:var(--mlv-text-secondary);font-size:var(--mlv-typography-caption-size);line-height:var(--mlv-typography-caption-line-height)}}\n"] }]
9266
10481
  }], propDecorators: { characterLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "characterLimit", required: false }] }] } });
9267
10482
 
9268
10483
  /**
@@ -9285,6 +10500,87 @@ class MlvEditor extends MlvSignalFormControlBase {
9285
10500
  /** Width of the centred content column; a gutter is reserved at every value. */
9286
10501
  contentWidth = input('default', /* @ts-ignore */
9287
10502
  ...(ngDevMode ? [{ debugName: "contentWidth" }] : /* istanbul ignore next */ []));
10503
+ /**
10504
+ * Fixed block size of the editor surface (toolbar plus content viewport).
10505
+ * A number is px; a string is any CSS length, passed through verbatim.
10506
+ *
10507
+ * Setting it **caps** the editor: `.mlv-editor__viewport` becomes the only
10508
+ * scroll container. With neither `height` nor `maxHeight`, the editor grows
10509
+ * with its content, never shows a scrollbar, and never stops a scroll meant
10510
+ * for the page. Writing `--mlv-editor-height` in CSS alone does not cap.
10511
+ */
10512
+ height = input(undefined, /* @ts-ignore */
10513
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
10514
+ /**
10515
+ * Minimum block size of the editable content area; the viewport adds its
10516
+ * 1rem padding around it. A number is px; a string is any CSS length. `undefined` keeps the `8rem` floor. Setting only this input
10517
+ * leaves the editor in auto mode. `--mlv-editor-min-height` may also be set
10518
+ * in CSS.
10519
+ *
10520
+ * Under a cap (`height` / `maxHeight`) the cap wins, even over a larger
10521
+ * `minHeight`: the viewport shrinks to fit the surface, and the floor moves
10522
+ * to the content inside the scroller, at every zoom level. A `minHeight`
10523
+ * above what the cap leaves the content (the cap minus the toolbar band and
10524
+ * the viewport padding) scrolls an empty document; for a fixed size set
10525
+ * `height` rather than `minHeight` equal to `maxHeight`.
10526
+ */
10527
+ minHeight = input(undefined, /* @ts-ignore */
10528
+ ...(ngDevMode ? [{ debugName: "minHeight" }] : /* istanbul ignore next */ []));
10529
+ /**
10530
+ * Largest block size of the editor surface. The surface grows with its
10531
+ * content up to this cap, and then the viewport scrolls. A number is px; a
10532
+ * string is any CSS length. Like `height`, this caps the editor; writing
10533
+ * `--mlv-editor-max-height` in CSS alone does not.
10534
+ */
10535
+ maxHeight = input(undefined, /* @ts-ignore */
10536
+ ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
10537
+ /**
10538
+ * Docked-bar placement. The DOM order follows it, so the Tab order matches
10539
+ * the visual order (WCAG 2.4.3). Changing it re-creates the toolbar view,
10540
+ * which closes any open toolbar popup. It does not move the floating
10541
+ * selection bubble, which always prefers above the selection. While
10542
+ * `readonly` no bar renders in either position (see `toolbarAppearance`).
10543
+ */
10544
+ toolbarPosition = input('top', /* @ts-ignore */
10545
+ ...(ngDevMode ? [{ debugName: "toolbarPosition" }] : /* istanbul ignore next */ []));
10546
+ /**
10547
+ * Toolbar drawing: `'bar'` is the docked row with a hairline; `'floating'` is
10548
+ * a selection bubble. The bubble is a CDK overlay (`.mlv-editor-bubble`)
10549
+ * shown only while focus is in the editor, the selection is non-empty and
10550
+ * the editor is neither disabled nor `readonly`; it prefers above the
10551
+ * selection and flips below at the window's or a capped viewport's edge.
10552
+ * Alt+F10 in the content summons it at the caret and focuses its first
10553
+ * control (the content carries `aria-keyshortcuts="Alt+F10"` while floating
10554
+ * and editable); Escape dismisses it and returns focus to the content with
10555
+ * the selection intact. A plugin that claims Escape itself (a consumer's
10556
+ * `@tiptap/suggestion` list, an AI stream) gets it first, and the Escape
10557
+ * that dismisses the bubble goes no further, so a dialog or drawer around
10558
+ * the editor closes only on the next one. Turning `readonly` on while the
10559
+ * bubble is shown hides it and closes its popups.
10560
+ *
10561
+ * A `readonly` editor renders **no toolbar in either appearance** (#498):
10562
+ * no `.mlv-editor__toolbar-band`, and no space kept for one. Turning
10563
+ * `readonly` on tears the bar down, closes every popup the editor owns and
10564
+ * returns focus from the bar or such a popup to the content, with the
10565
+ * selection intact; turning it off stamps the bar again in its position.
10566
+ * A consumer toolbar (`[mlvEditorToolbar]`, `[mlvEditorToolbarStart]`,
10567
+ * `[mlvEditorToolbarEnd]`) is stamped in the same band and goes with it.
10568
+ */
10569
+ toolbarAppearance = input('bar', /* @ts-ignore */
10570
+ ...(ngDevMode ? [{ debugName: "toolbarAppearance" }] : /* istanbul ignore next */ []));
10571
+ /**
10572
+ * Keeps the docked bar `position: sticky` against the page (or the nearest
10573
+ * consumer scroll container) while the surface is on screen, offset by
10574
+ * `--mlv-editor-toolbar-sticky-offset` (default `0`).
10575
+ *
10576
+ * It applies only to an **uncapped** **bar**. A capped editor
10577
+ * (`height` / `maxHeight`) scrolls inside its own viewport, and the bar
10578
+ * sits outside that scroller, so there is nothing for it to stick against;
10579
+ * the floating bubble follows the selection instead; and a `readonly`
10580
+ * editor renders no bar at all (#498). In each case the input is ignored
10581
+ * and `.mlv-editor--toolbar-sticky` is not stamped.
10582
+ */
10583
+ toolbarSticky = input(false, { ...(ngDevMode ? { debugName: "toolbarSticky" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
9288
10584
  /** Complete Tiptap extension replacement; omit it for Malva's fresh preset. */
9289
10585
  extensions = input(undefined, /* @ts-ignore */
9290
10586
  ...(ngDevMode ? [{ debugName: "extensions" }] : /* istanbul ignore next */ []));
@@ -9322,6 +10618,57 @@ class MlvEditor extends MlvSignalFormControlBase {
9322
10618
  /** View-only zoom percentage shared with the nearest toolbar. */
9323
10619
  zoom = signal(100, /* @ts-ignore */
9324
10620
  ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
10621
+ /** @protected Resolved `height`, written into `--mlv-editor-height`; `null` writes nothing. */
10622
+ _heightStyle = computed(() => mlvEditorCssLength(this.height()), /* @ts-ignore */
10623
+ ...(ngDevMode ? [{ debugName: "_heightStyle" }] : /* istanbul ignore next */ []));
10624
+ /** @protected Resolved `minHeight`, written into `--mlv-editor-min-height`; `null` writes nothing. */
10625
+ _minHeightStyle = computed(() => mlvEditorCssLength(this.minHeight()), /* @ts-ignore */
10626
+ ...(ngDevMode ? [{ debugName: "_minHeightStyle" }] : /* istanbul ignore next */ []));
10627
+ /** @protected Resolved `maxHeight`, written into `--mlv-editor-max-height`; `null` writes nothing. */
10628
+ _maxHeightStyle = computed(() => mlvEditorCssLength(this.maxHeight()), /* @ts-ignore */
10629
+ ...(ngDevMode ? [{ debugName: "_maxHeightStyle" }] : /* istanbul ignore next */ []));
10630
+ /**
10631
+ * @protected Whether the editor is capped. Only `height` or `maxHeight`
10632
+ * switches it on; `minHeight` alone stays in auto mode. It is the one input
10633
+ * that turns `.mlv-editor__viewport` into a scroll container.
10634
+ */
10635
+ _capped = computed(() => this._heightStyle() !== null || this._maxHeightStyle() !== null, /* @ts-ignore */
10636
+ ...(ngDevMode ? [{ debugName: "_capped" }] : /* istanbul ignore next */ []));
10637
+ /**
10638
+ * @protected Whether the editor stamps its toolbar at all, in either
10639
+ * appearance: never while `readonly` (#498 owner ruling, "a readonly editor
10640
+ * renders no toolbar"). The docked bar is torn down and the selection
10641
+ * bubble's pane stays empty; both come back when `readonly` turns off.
10642
+ *
10643
+ * This is the one switch, and it covers a consumer toolbar too: a complete
10644
+ * `[mlvEditorToolbar]` replacement and the `[mlvEditorToolbarStart]` /
10645
+ * `[mlvEditorToolbarEnd]` slots are stamped inside the same band. That is
10646
+ * an assumption the owner may reverse. Keeping a complete replacement while
10647
+ * `readonly` is `!this.readonly() || this._hasToolbarDef()` here for the
10648
+ * bar, and the default groups inside the band then need their own readonly
10649
+ * hiding; `_closeBarForReadonly()`'s `_barRendered()` early return must
10650
+ * then key on the removed region, not the band, or focus on a removed
10651
+ * default control drops to `<body>`. The bubble also gates itself on
10652
+ * `readonly` in `MlvEditorBubble._wanted()` / `_ownsContentKey()`, and
10653
+ * `_editorAttributes()` drops `aria-keyshortcuts`.
10654
+ */
10655
+ _toolbarRendered = computed(() => !this.readonly(), /* @ts-ignore */
10656
+ ...(ngDevMode ? [{ debugName: "_toolbarRendered" }] : /* istanbul ignore next */ []));
10657
+ /**
10658
+ * @protected Whether the docked bar is stamped before or after the
10659
+ * viewport: the `'bar'` appearance while `_toolbarRendered()`. With it
10660
+ * false no band exists, so nothing reserves space for one and no sticky
10661
+ * extent is measured.
10662
+ */
10663
+ _barRendered = computed(() => this.toolbarAppearance() === 'bar' && this._toolbarRendered(), /* @ts-ignore */
10664
+ ...(ngDevMode ? [{ debugName: "_barRendered" }] : /* istanbul ignore next */ []));
10665
+ /**
10666
+ * @protected Whether the docked bar is actually sticky: `toolbarSticky` on an
10667
+ * uncapped bar that renders (not while `readonly`). Drives
10668
+ * `.mlv-editor--toolbar-sticky` and the caret scroll margin.
10669
+ */
10670
+ _stickyToolbar = computed(() => this.toolbarSticky() && this._barRendered() && !this._capped(), /* @ts-ignore */
10671
+ ...(ngDevMode ? [{ debugName: "_stickyToolbar" }] : /* istanbul ignore next */ []));
9325
10672
  /** Whether the mounted editor currently accepts document mutations. */
9326
10673
  editable = computed(() => this._editor() !== null && !this.computedDisabled() && !this.readonly(), /* @ts-ignore */
9327
10674
  ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
@@ -9354,7 +10701,7 @@ class MlvEditor extends MlvSignalFormControlBase {
9354
10701
  _content = viewChild.required('content', /* @ts-ignore */
9355
10702
  ...(ngDevMode ? [{ debugName: "_content" }] : /* istanbul ignore next */ []));
9356
10703
  /**
9357
- * @private Zoom-transformed layer the floating block handle is mounted into.
10704
+ * @private Zoomed layer (CSS `zoom`) the floating block handle is mounted into.
9358
10705
  *
9359
10706
  * Deliberately optional rather than `viewChild.required`: the block-handle
9360
10707
  * `mount` capability is typed `() => HTMLElement | null`, and the extension
@@ -9364,6 +10711,12 @@ class MlvEditor extends MlvSignalFormControlBase {
9364
10711
  */
9365
10712
  _view = viewChild('view', /* @ts-ignore */
9366
10713
  ...(ngDevMode ? [{ debugName: "_view" }] : /* istanbul ignore next */ []));
10714
+ /**
10715
+ * @private Toolbar band currently stamped before or after the viewport.
10716
+ * Queried by declaration, so it follows the band across position changes.
10717
+ */
10718
+ _toolbarBand = viewChild('toolbarBand', /* @ts-ignore */
10719
+ ...(ngDevMode ? [{ debugName: "_toolbarBand" }] : /* istanbul ignore next */ []));
9367
10720
  /** @protected Complete projected toolbar replacement, if the consumer provides one. */
9368
10721
  _toolbarDefs = contentChildren(MlvEditorToolbarDef, /* @ts-ignore */
9369
10722
  ...(ngDevMode ? [{ debugName: "_toolbarDefs" }] : /* istanbul ignore next */ []));
@@ -9392,6 +10745,16 @@ class MlvEditor extends MlvSignalFormControlBase {
9392
10745
  _uploadAbortRegistry = inject(MlvEditorUploadAbortRegistry);
9393
10746
  /** @private Physical host used to determine the composite focus boundary. */
9394
10747
  _host = inject(ElementRef);
10748
+ /**
10749
+ * @private Live ProseMirror `scrollMargin` (WCAG 2.2 SC 2.4.11). One stable
10750
+ * object passed on every `editorProps` write; its sides re-measure per read.
10751
+ * Read untracked: ProseMirror scrolls synchronously inside `updateState`,
10752
+ * which can run within `_synchronizeEditor`'s effect, and that effect must
10753
+ * not start depending on the toolbar inputs.
10754
+ */
10755
+ _scrollMargin = createMlvEditorLiveScrollSides('margin', () => untracked(this.toolbarPosition), () => untracked(() => this._obscuredToolbarExtent()));
10756
+ /** @private Live ProseMirror `scrollThreshold`, paired with `_scrollMargin`. */
10757
+ _scrollThreshold = createMlvEditorLiveScrollSides('threshold', () => untracked(this.toolbarPosition), () => untracked(() => this._obscuredToolbarExtent()));
9395
10758
  /** @private Optional translated accessible defaults. */
9396
10759
  _i18n = inject(MLV_EDITOR_I18N, { optional: true });
9397
10760
  /** @private Announces block moves politely; the editor content itself is not a live region. */
@@ -9418,13 +10781,7 @@ class MlvEditor extends MlvSignalFormControlBase {
9418
10781
  _blurCheckQueued = false;
9419
10782
  /** @private The last boundary event, retained until the microtask resolves focus ownership. */
9420
10783
  _pendingBlurEvent;
9421
- /** @private Stable capture listener for focus entering the physical host. */
9422
- _onHostFocusIn = (event) => this._handleCompositeFocusIn(event);
9423
- /** @private Stable capture listener for focus leaving the physical host. */
9424
- _onHostFocusOut = (event) => this._handleCompositeFocusOut(event);
9425
- /** @private Stable capture listener that blocks disabled projected controls. */
9426
- _onDisabledPointerOrClick = (event) => this._blockDisabledInteraction(event);
9427
- /** @private Stable capture listener that blocks disabled keyboard activation. */
10784
+ /** @private Blocks disabled keyboard activation; the other three subscribe to their handler directly. */
9428
10785
  _onDisabledKeydown = (event) => {
9429
10786
  if (event.key !== 'Enter' &&
9430
10787
  event.key !== ' ' &&
@@ -9467,6 +10824,10 @@ class MlvEditor extends MlvSignalFormControlBase {
9467
10824
  this._wasDisabled = disabled;
9468
10825
  if (readonly && !this._wasReadonly) {
9469
10826
  this._abortInFlightUploads();
10827
+ // Constructor effect: registered on the parent view, so it runs
10828
+ // before this component's template (and the wrapper-stamped band
10829
+ // `@if`) refreshes; focus in the band or its popups can still move.
10830
+ untracked(() => this._closeBarForReadonly());
9470
10831
  }
9471
10832
  this._wasReadonly = readonly;
9472
10833
  if (!editor)
@@ -9560,7 +10921,11 @@ class MlvEditor extends MlvSignalFormControlBase {
9560
10921
  extensions: selectedExtensions,
9561
10922
  editable: !this.computedDisabled() && !this.readonly(),
9562
10923
  enableContentCheck: true,
9563
- editorProps: { attributes: this._editorAttributes() },
10924
+ editorProps: {
10925
+ attributes: this._editorAttributes(),
10926
+ scrollMargin: this._scrollMargin,
10927
+ scrollThreshold: this._scrollThreshold,
10928
+ },
9564
10929
  onTransaction: ({ editor: changedEditor, transaction }) => {
9565
10930
  if (this._destroyed)
9566
10931
  return;
@@ -9604,6 +10969,23 @@ class MlvEditor extends MlvSignalFormControlBase {
9604
10969
  });
9605
10970
  }
9606
10971
  }
10972
+ /**
10973
+ * @protected Focuses the editable content when the control's own
10974
+ * `<mlv-label>` is clicked.
10975
+ *
10976
+ * ProseMirror's contenteditable root is focusable but not HTML-labelable, so
10977
+ * `<label for>` can never name it and the native click-to-focus a field
10978
+ * label owes its control never ran — the visible label was inert (#216), the
10979
+ * same gap the three date/time pickers close. `readonly` still focuses (a
10980
+ * read-only `<textarea>` does), `disabled` returns early (the host is
10981
+ * `inert` anyway), and Tiptap's `focus` command restores the stored
10982
+ * selection rather than dropping the caret at the document start.
10983
+ */
10984
+ _onLabelClick() {
10985
+ if (this.computedDisabled())
10986
+ return;
10987
+ this._editor()?.commands.focus();
10988
+ }
9607
10989
  /** Clears the document through Tiptap when the shared form wrapper is clearable. */
9608
10990
  clearValue() {
9609
10991
  if (this.computedDisabled() || this.readonly())
@@ -9690,7 +11072,15 @@ class MlvEditor extends MlvSignalFormControlBase {
9690
11072
  return;
9691
11073
  const requestedFormat = this.format();
9692
11074
  editor.setOptions({
9693
- editorProps: { attributes: this._editorAttributes() },
11075
+ // `setOptions` replaces Tiptap's stored `editorProps` wholesale. The
11076
+ // live view keeps its props (ProseMirror's `setProps` merges), but a
11077
+ // later `mount()` rebuilds the view from the stored copy, so the scroll
11078
+ // props travel with every call.
11079
+ editorProps: {
11080
+ attributes: this._editorAttributes(),
11081
+ scrollMargin: this._scrollMargin,
11082
+ scrollThreshold: this._scrollThreshold,
11083
+ },
9694
11084
  });
9695
11085
  this._synchronizeContentSurfaceState(editor);
9696
11086
  const rawIncoming = this.value();
@@ -9899,6 +11289,39 @@ class MlvEditor extends MlvSignalFormControlBase {
9899
11289
  recoverable: true,
9900
11290
  });
9901
11291
  }
11292
+ /**
11293
+ * @private Block extent the toolbar hides on its own side, in px.
11294
+ *
11295
+ * Only a sticky bar hides anything: its height plus its offset, at its
11296
+ * nearest scroll container. A docked bar sits outside the content, and the
11297
+ * floating bubble is an overlay that follows the selection, clear of it by
11298
+ * construction.
11299
+ * Sticky applies only to an uncapped bar (`_stickyToolbar`), so the margin
11300
+ * never lands on a capped viewport's own edge.
11301
+ *
11302
+ * Read by ProseMirror at scroll time, never for layout. ProseMirror applies
11303
+ * one margin at every scroll ancestor. For an uncapped editor the page is
11304
+ * usually the only one. With a consumer scroll container in between, the
11305
+ * band sticks to that container, where the margin is right; the page, one
11306
+ * scroll ancestor further out, also keeps the caret that far from its edge
11307
+ * although no band is there, which errs toward visibility.
11308
+ */
11309
+ _obscuredToolbarExtent() {
11310
+ if (!this._stickyToolbar())
11311
+ return 0;
11312
+ const band = this._toolbarBand()?.nativeElement;
11313
+ if (!band)
11314
+ return 0;
11315
+ const side = this.toolbarPosition() === 'top'
11316
+ ? 'inset-block-start'
11317
+ : 'inset-block-end';
11318
+ const view = band.ownerDocument.defaultView;
11319
+ const offset = view
11320
+ ? Number.parseFloat(view.getComputedStyle(band).getPropertyValue(side)) ||
11321
+ 0
11322
+ : 0;
11323
+ return band.getBoundingClientRect().height + offset;
11324
+ }
9902
11325
  /** @private Builds the exact accessible attributes assigned to Tiptap's contenteditable root. */
9903
11326
  _editorAttributes() {
9904
11327
  const attributes = {
@@ -9936,6 +11359,14 @@ class MlvEditor extends MlvSignalFormControlBase {
9936
11359
  if (this.readonly()) {
9937
11360
  attributes['aria-readonly'] = 'true';
9938
11361
  }
11362
+ // The selection bubble stays hidden until a selection exists, so the key
11363
+ // that reaches it is the only hint assistive technology gets. Disabled or
11364
+ // `readonly`, the bubble never shows and the key does nothing.
11365
+ if (this.toolbarAppearance() === 'floating' &&
11366
+ !this.computedDisabled() &&
11367
+ !this.readonly()) {
11368
+ attributes['aria-keyshortcuts'] = 'Alt+F10';
11369
+ }
9939
11370
  return attributes;
9940
11371
  }
9941
11372
  /** @internal Capture handler for focus entering the host or an owned overlay root. */
@@ -9988,21 +11419,39 @@ class MlvEditor extends MlvSignalFormControlBase {
9988
11419
  (this._host.nativeElement.contains(node) ||
9989
11420
  this._overlayRegistry.contains(node)));
9990
11421
  }
9991
- /** @private Installs native capture listeners because overlay focus is outside Angular's view tree. */
11422
+ /**
11423
+ * @private Subscribes the composite's host listeners, because overlay focus
11424
+ * happens outside Angular's view tree.
11425
+ *
11426
+ * Every one is registered in the **capture** phase, which is load-bearing
11427
+ * rather than incidental: ProseMirror binds its own handlers to `view.dom`,
11428
+ * a descendant of this host, so only a capture-phase listener on the host
11429
+ * runs ahead of them. The disabled handlers depend on that to call
11430
+ * `stopImmediatePropagation()` while the event is still travelling down —
11431
+ * from the bubble phase ProseMirror would already have acted on it. Capture
11432
+ * is carried through `fromEvent`'s third argument, and `editor-focus.spec.ts`
11433
+ * pins the ordering against descendant listeners.
11434
+ *
11435
+ * Called from `ngAfterViewInit`, which is not an injection context, so
11436
+ * `takeUntilDestroyed` is given the ref explicitly.
11437
+ */
9992
11438
  _bindCompositeFocusEvents() {
9993
11439
  const host = this._host.nativeElement;
9994
- host.addEventListener('focusin', this._onHostFocusIn, true);
9995
- host.addEventListener('focusout', this._onHostFocusOut, true);
9996
- host.addEventListener('pointerdown', this._onDisabledPointerOrClick, true);
9997
- host.addEventListener('click', this._onDisabledPointerOrClick, true);
9998
- host.addEventListener('keydown', this._onDisabledKeydown, true);
9999
- this._destroyRef.onDestroy(() => {
10000
- host.removeEventListener('focusin', this._onHostFocusIn, true);
10001
- host.removeEventListener('focusout', this._onHostFocusOut, true);
10002
- host.removeEventListener('pointerdown', this._onDisabledPointerOrClick, true);
10003
- host.removeEventListener('click', this._onDisabledPointerOrClick, true);
10004
- host.removeEventListener('keydown', this._onDisabledKeydown, true);
10005
- });
11440
+ fromEvent(host, 'focusin', { capture: true })
11441
+ .pipe(takeUntilDestroyed(this._destroyRef))
11442
+ .subscribe((event) => this._handleCompositeFocusIn(event));
11443
+ fromEvent(host, 'focusout', { capture: true })
11444
+ .pipe(takeUntilDestroyed(this._destroyRef))
11445
+ .subscribe((event) => this._handleCompositeFocusOut(event));
11446
+ fromEvent(host, 'pointerdown', { capture: true })
11447
+ .pipe(takeUntilDestroyed(this._destroyRef))
11448
+ .subscribe((event) => this._blockDisabledInteraction(event));
11449
+ fromEvent(host, 'click', { capture: true })
11450
+ .pipe(takeUntilDestroyed(this._destroyRef))
11451
+ .subscribe((event) => this._blockDisabledInteraction(event));
11452
+ fromEvent(host, 'keydown', { capture: true })
11453
+ .pipe(takeUntilDestroyed(this._destroyRef))
11454
+ .subscribe((event) => this._onDisabledKeydown(event));
10006
11455
  }
10007
11456
  /** @private Stops all pointer/click/activation-key command surfaces while disabled. */
10008
11457
  _blockDisabledInteraction(event) {
@@ -10033,6 +11482,7 @@ class MlvEditor extends MlvSignalFormControlBase {
10033
11482
  'aria-readonly',
10034
11483
  'tabindex',
10035
11484
  'aria-invalid',
11485
+ 'aria-keyshortcuts',
10036
11486
  ];
10037
11487
  for (const name of managedAttributes) {
10038
11488
  const value = attributes[name];
@@ -10044,6 +11494,29 @@ class MlvEditor extends MlvSignalFormControlBase {
10044
11494
  }
10045
11495
  }
10046
11496
  }
11497
+ /**
11498
+ * @private `readonly` just turned on over a docked bar, which this render
11499
+ * tears down (#498). Every popup the editor owns closes, and focus in the
11500
+ * band or in such a popup returns to the content first, through the same
11501
+ * registry call the selection bubble makes. Called from the constructor
11502
+ * effect, which is registered on the parent view, so it runs before this
11503
+ * component's template (and the wrapper-stamped band `@if`) refreshes: the
11504
+ * band and its focus are still there. A disabled editor is left alone, like
11505
+ * the bubble's own focus return: it takes no focus, even from a projected
11506
+ * consumer control, which the editor does not disable and which can hold
11507
+ * focus in the band of a disabled editor.
11508
+ */
11509
+ _closeBarForReadonly() {
11510
+ if (this.toolbarAppearance() !== 'bar' || this._barRendered())
11511
+ return;
11512
+ if (this.computedDisabled())
11513
+ return;
11514
+ const band = this._toolbarBand()?.nativeElement;
11515
+ const editor = this._editor();
11516
+ if (!band || !editor || editor.isDestroyed)
11517
+ return;
11518
+ this._overlayRegistry.closeForReadonly(band, editor.view.dom, () => mlvEditorFocusContent(editor));
11519
+ }
10047
11520
  /** @private Aborts registered upload work while preserving the document/editor instance. */
10048
11521
  _abortInFlightUploads() {
10049
11522
  this._uploadAbortRegistry.abortAll();
@@ -10056,8 +11529,8 @@ class MlvEditor extends MlvSignalFormControlBase {
10056
11529
  this._editor.set(null);
10057
11530
  editor?.destroy();
10058
11531
  }
10059
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
10060
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MlvEditor, isStandalone: true, selector: "mlv-editor", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: false, transformFunction: null }, extensions: { classPropertyName: "extensions", publicName: "extensions", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, characterLimit: { classPropertyName: "characterLimit", publicName: "characterLimit", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, imageUploader: { classPropertyName: "imageUploader", publicName: "imageUploader", isSignal: true, isRequired: false, transformFunction: null }, imageUploadOptions: { classPropertyName: "imageUploadOptions", publicName: "imageUploadOptions", isSignal: true, isRequired: false, transformFunction: null }, aiProvider: { classPropertyName: "aiProvider", publicName: "aiProvider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", editorReady: "editorReady", focus: "focus", blur: "blur", selectionChange: "selectionChange", transaction: "transaction", editorError: "editorError", imageUploadSuccess: "imageUploadSuccess", imageUploadFailure: "imageUploadFailure", imageUploadCancelled: "imageUploadCancelled" }, host: { properties: { "class": "\"mlv-editor--content-width-\" + contentWidth()", "class.mlv-editor--disabled": "computedDisabled()", "class.mlv-editor--readonly": "readonly()", "class.mlv-editor--focused": "focused()", "attr.aria-disabled": "computedDisabled() || null", "attr.inert": "computedDisabled() ? \"\" : null", "style.--mlv-editor-zoom": "zoom() / 100" }, classAttribute: "mlv-editor" }, providers: [
11532
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
11533
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: MlvEditor, isStandalone: true, selector: "mlv-editor", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, contentWidth: { classPropertyName: "contentWidth", publicName: "contentWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, toolbarPosition: { classPropertyName: "toolbarPosition", publicName: "toolbarPosition", isSignal: true, isRequired: false, transformFunction: null }, toolbarAppearance: { classPropertyName: "toolbarAppearance", publicName: "toolbarAppearance", isSignal: true, isRequired: false, transformFunction: null }, toolbarSticky: { classPropertyName: "toolbarSticky", publicName: "toolbarSticky", isSignal: true, isRequired: false, transformFunction: null }, extensions: { classPropertyName: "extensions", publicName: "extensions", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, characterLimit: { classPropertyName: "characterLimit", publicName: "characterLimit", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, imageUploader: { classPropertyName: "imageUploader", publicName: "imageUploader", isSignal: true, isRequired: false, transformFunction: null }, imageUploadOptions: { classPropertyName: "imageUploadOptions", publicName: "imageUploadOptions", isSignal: true, isRequired: false, transformFunction: null }, aiProvider: { classPropertyName: "aiProvider", publicName: "aiProvider", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", editorReady: "editorReady", focus: "focus", blur: "blur", selectionChange: "selectionChange", transaction: "transaction", editorError: "editorError", imageUploadSuccess: "imageUploadSuccess", imageUploadFailure: "imageUploadFailure", imageUploadCancelled: "imageUploadCancelled" }, host: { properties: { "class": "\"mlv-editor--content-width-\" + contentWidth()", "class.mlv-editor--disabled": "computedDisabled()", "class.mlv-editor--readonly": "readonly()", "class.mlv-editor--focused": "focused()", "class.mlv-editor--capped": "_capped()", "class.mlv-editor--toolbar-top": "toolbarPosition() === \"top\"", "class.mlv-editor--toolbar-bottom": "toolbarPosition() === \"bottom\"", "class.mlv-editor--toolbar-bar": "toolbarAppearance() === \"bar\"", "class.mlv-editor--toolbar-floating": "toolbarAppearance() === \"floating\"", "class.mlv-editor--toolbar-sticky": "_stickyToolbar()", "attr.aria-disabled": "computedDisabled() || null", "attr.inert": "computedDisabled() ? \"\" : null", "style.--mlv-editor-zoom": "zoom() / 100", "style.--mlv-editor-height": "_heightStyle()", "style.--mlv-editor-min-height": "_minHeightStyle()", "style.--mlv-editor-max-height": "_maxHeightStyle()" }, classAttribute: "mlv-editor" }, providers: [
10061
11534
  MlvEditorOverlayRegistry,
10062
11535
  MlvEditorToolbarRevision,
10063
11536
  MlvEditorToolbarRovingRegistry,
@@ -10116,9 +11589,9 @@ class MlvEditor extends MlvSignalFormControlBase {
10116
11589
  }, inject(MLV_EDITOR_IMAGE_UPLOADER, { optional: true }) ?? undefined, inject(MLV_EDITOR_UPLOAD_ABORT_REGISTRY));
10117
11590
  },
10118
11591
  },
10119
- ], queries: [{ propertyName: "_toolbarDefs", predicate: MlvEditorToolbarDef, isSignal: true }, { propertyName: "_toolbarStartDefs", predicate: MlvEditorToolbarStartDef, isSignal: true }, { propertyName: "_toolbarEndDefs", predicate: MlvEditorToolbarEndDef, isSignal: true }], viewQueries: [{ propertyName: "_content", first: true, predicate: ["content"], descendants: true, isSignal: true }, { propertyName: "_view", first: true, predicate: ["view"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<mlv-form-control-wrapper (clear)=\"clearValue()\">\n @if (label() || hint()) {\n <mlv-label [for]=\"id()\" [id]=\"_labelId()\">\n {{ label() }} @if (hint()) {\n <mlv-hint>{{ hint() }}</mlv-hint>\n }\n </mlv-label>\n }\n\n <ng-template mlvFormControlWrapperControl>\n <div class=\"mlv-editor__surface\">\n <div\n mlvEditorToolbarRoot\n #toolbarRoot=\"mlvEditorToolbarRoot\"\n class=\"mlv-editor__toolbar\"\n [ariaLabel]=\"_toolbarAriaLabel()\"\n [disabled]=\"computedDisabled()\"\n >\n @if (_hasToolbarDef()) {\n <ng-content select=\"[mlvEditorToolbar]\" />\n @for (definition of _toolbarDefs(); track definition) { @if\n (definition.templateRef; as toolbarTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"toolbarTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n } } } @else {\n <ng-content select=\"[mlvEditorToolbarStart]\" />\n @for (definition of _toolbarStartDefs(); track definition) { @if\n (definition.templateRef; as startTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"startTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n } }\n <div mlvFade class=\"mlv-editor__toolbar-scroll\">\n <mlv-toolbar>\n <mlv-editor-undo-redo />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-zoom />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-heading />\n <mlv-editor-list />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-inline-marks [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-text-color />\n <mlv-editor-highlight />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-alignment [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-link />\n <mlv-editor-table />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-block-insert [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-image-upload />\n </mlv-toolbar>\n </div>\n <mlv-editor-toolbar-overflow />\n @for (definition of _toolbarEndDefs(); track definition) { @if\n (definition.templateRef; as endTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"endTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n } }\n <ng-content select=\"[mlvEditorToolbarEnd]\" />\n }\n </div>\n\n <div class=\"mlv-editor__viewport\">\n <div #view class=\"mlv-editor__view\">\n <div #content class=\"mlv-editor__content\"></div>\n <mlv-editor-table-controls [layer]=\"view\" />\n </div>\n </div>\n <mlv-editor-image-upload-status />\n </div>\n </ng-template>\n\n @if (message()) {\n <mlv-message [state]=\"resolvedState()\" [id]=\"id() + '-message'\">\n {{ message() }}\n </mlv-message>\n }\n</mlv-form-control-wrapper>\n\n<div\n class=\"mlv-editor__status\"\n [attr.aria-disabled]=\"computedDisabled() || null\"\n>\n <ng-content select=\"[mlvEditorStatus]\" />\n <mlv-spacer />\n <mlv-editor-status [characterLimit]=\"characterLimit()\" />\n</div>\n", styles: ["@layer mlv.components{.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-6);--mlv-editor-measure: 45rem;--mlv-editor-drop-gap: 1.5rem;box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor *{box-sizing:border-box}.mlv-editor{display:block;color:var(--mlv-text-primary)}.mlv-editor .mlv-form-control-wrapper__control-container{height:auto;min-height:var(--form-ctrl-height);padding:0;overflow:hidden;background:var(--mlv-background-base);transition:border-color var(--mlv-duration-fast) var(--mlv-ease-default),box-shadow var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .mlv-form-control-wrapper__control-row{align-items:stretch}.mlv-form-control-wrapper--state-error .mlv-editor__surface{box-shadow:inset 0 0 0 var(--mlv-stroke-width) var(--mlv-border-error)}.mlv-editor__surface{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;width:100%;background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--mlv-spacing-1);min-width:0;padding:var(--mlv-padding-xs);overflow:hidden;border-bottom:var(--mlv-stroke-width) solid var(--mlv-border-normal);background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar [hidden]{display:none!important}.mlv-editor__toolbar-scroll{flex:1 1 auto;min-width:0}.mlv-editor__toolbar-scroll>.mlv-toolbar{width:max-content;min-width:100%;flex-wrap:nowrap}.mlv-editor .mlv-editor-toolbar__separator{flex:0 0 auto;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor .mlv-editor-toolbar__overflow,.mlv-editor .mlv-editor-undo-redo,.mlv-editor .mlv-editor-inline-marks,.mlv-editor .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor mlv-editor-zoom,.mlv-editor mlv-editor-heading,.mlv-editor mlv-editor-list,.mlv-editor mlv-editor-text-color,.mlv-editor mlv-editor-highlight,.mlv-editor mlv-editor-alignment,.mlv-editor mlv-editor-link,.mlv-editor mlv-editor-table,.mlv-editor mlv-editor-image-upload{flex:0 0 auto}.mlv-editor__viewport{min-block-size:8rem;overflow:auto;overscroll-behavior:contain}.mlv-editor__view{position:relative;min-inline-size:100%;transform:scale(var(--mlv-editor-zoom, 1));transform-origin:left top;transition:transform var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__content{min-block-size:8rem;padding-block:var(--mlv-spacing-2);padding-inline:var(--mlv-editor-gutter);border:0;outline:none}.mlv-editor__block-handle{position:absolute;inset-inline-start:max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2);display:inline-flex;align-items:center;justify-content:center;inline-size:var(--mlv-editor-gutter);block-size:var(--mlv-height-xs);border-radius:var(--mlv-radius-s);color:var(--mlv-text-tertiary);cursor:grab;opacity:0;pointer-events:none;transition:opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__block-handle svg{inline-size:var(--mlv-spacing-4);block-size:var(--mlv-spacing-4);fill:currentcolor}.mlv-editor__block-handle:hover{background-color:var(--mlv-background-neutral-1-hover);color:var(--mlv-text-secondary)}.mlv-editor__block-handle[data-visible=true]{opacity:1;pointer-events:auto}.mlv-editor__drop-indicator{position:absolute;inset-inline:calc(max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2) + var(--mlv-editor-gutter));block-size:var(--mlv-stroke-width-medium);border-radius:var(--mlv-radius-full);background-color:var(--mlv-border-focus);opacity:0;pointer-events:none;transform:translateY(calc(var(--mlv-editor-drop-gap) / 2 - var(--mlv-stroke-width-medium) / 2));transition:top var(--mlv-duration-normal) var(--mlv-ease-out),opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__drop-indicator[data-visible=true]{opacity:1}.mlv-editor .mlv-editor__block--dragging{opacity:var(--mlv-disabled-opacity)}.mlv-editor .ProseMirror--block-dragging>*{transition:transform var(--mlv-duration-normal) var(--mlv-ease-out)}.mlv-editor__status{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-2);min-block-size:var(--mlv-spacing-4);margin-block-start:var(--mlv-spacing-1)}.mlv-editor__upload-placeholder{position:relative;display:inline-block;inline-size:var(--mlv-height-l);block-size:var(--mlv-height-l);margin-inline:var(--mlv-spacing-1);border:var(--mlv-stroke-width) dashed var(--mlv-border-focus);border-radius:var(--mlv-radius-m);background:var(--mlv-background-accent-1-pale);vertical-align:middle}.mlv-editor__upload-placeholder:after{position:absolute;inset:var(--mlv-spacing-2);border:var(--mlv-stroke-width-medium) solid var(--mlv-border-normal);border-block-start-color:var(--mlv-border-focus);border-radius:var(--mlv-radius-full);animation:mlv-editor-upload-spin var(--mlv-duration-slower) var(--mlv-ease-linear) infinite;content:\"\"}.mlv-editor__ai-streaming{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-accent-1-pale)}.mlv-editor__ai-streaming-chunk{animation:mlv-editor-ai-chunk-in var(--mlv-duration-normal) var(--mlv-ease-out)}.mlv-editor__ai-caret{position:relative;display:inline-block;inline-size:0;block-size:1em;pointer-events:none}.mlv-editor__ai-caret:after{position:absolute;inset-block:0;inset-inline-start:.0625rem;inline-size:.35em;border-radius:var(--mlv-radius-xs);background:linear-gradient(to bottom,var(--mlv-background-accent-1),var(--mlv-background-accent-2));content:\"\";animation:mlv-editor-ai-caret-pulse var(--mlv-duration-sluggish) var(--mlv-ease-in-out) infinite}.mlv-editor__ai-suggestion-insert{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-success-pale)}.mlv-editor__ai-suggestion-delete{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-danger-1-pale);color:var(--mlv-text-negative);text-decoration-line:line-through}.mlv-editor__ai-suggestion-current{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor--content-width-default{--mlv-editor-measure: 45rem}.mlv-editor--content-width-wide{--mlv-editor-measure: 60rem}.mlv-editor--content-width-full{--mlv-editor-measure: 100%}.mlv-editor--focused .mlv-form-control-wrapper__control-container{border-color:var(--mlv-border-focus);box-shadow:var(--mlv-focus-ring)}.mlv-editor--readonly .ProseMirror{cursor:text}.mlv-editor--readonly .ProseMirror ::selection{background:var(--mlv-background-accent-1-pale)}.mlv-editor--disabled{opacity:var(--mlv-disabled-opacity)}.mlv-editor--disabled .mlv-editor__surface,.mlv-editor--disabled .mlv-editor__status{pointer-events:none}.mlv-editor--disabled .mlv-editor__content,.mlv-editor--disabled .ProseMirror{cursor:default;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror{min-block-size:inherit;max-inline-size:var(--mlv-editor-measure);margin-inline:auto;color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size);font-weight:var(--mlv-typography-body-m-weight);line-height:var(--mlv-typography-body-m-line-height);overflow-wrap:anywhere;white-space:pre-wrap}.mlv-editor .ProseMirror:focus-visible{outline:none}.mlv-editor .ProseMirror>*:first-child{margin-block-start:0}.mlv-editor .ProseMirror>*:last-child{margin-block-end:0}.mlv-editor .ProseMirror p{margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror h1,.mlv-editor .ProseMirror h2,.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{margin-block:var(--mlv-spacing-6) var(--mlv-spacing-3);color:var(--mlv-text-heading);font-family:var(--mlv-typography-family-heading)}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h1-size);font-weight:var(--mlv-typography-heading-h1-weight);line-height:var(--mlv-typography-heading-h1-line-height)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h2-size);font-weight:var(--mlv-typography-heading-h2-weight);line-height:var(--mlv-typography-heading-h2-line-height)}.mlv-editor .ProseMirror h3{font-size:var(--mlv-typography-heading-h3-size);font-weight:var(--mlv-typography-heading-h3-weight);line-height:var(--mlv-typography-heading-h3-line-height)}.mlv-editor .ProseMirror h4{font-size:var(--mlv-typography-heading-h4-size);font-weight:var(--mlv-typography-heading-h4-weight);line-height:var(--mlv-typography-heading-h4-line-height)}.mlv-editor .ProseMirror h5{font-size:var(--mlv-typography-heading-h5-size);font-weight:var(--mlv-typography-heading-h5-weight);line-height:var(--mlv-typography-heading-h5-line-height)}.mlv-editor .ProseMirror h6{font-size:var(--mlv-typography-heading-h6-size);font-weight:var(--mlv-typography-heading-h6-weight);line-height:var(--mlv-typography-heading-h6-line-height)}.mlv-editor .ProseMirror h1{letter-spacing:var(--mlv-typography-heading-h1-letter-spacing)}.mlv-editor .ProseMirror h2{letter-spacing:var(--mlv-typography-heading-h2-letter-spacing)}.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{letter-spacing:var(--mlv-letter-spacing-normal)}.mlv-editor .ProseMirror strong{font-weight:var(--mlv-font-weight-bold)}.mlv-editor .ProseMirror em{font-style:italic}.mlv-editor .ProseMirror s{text-decoration:line-through}.mlv-editor .ProseMirror u{text-underline-offset:var(--mlv-spacing-0-5)}.mlv-editor .ProseMirror a{color:var(--mlv-text-action);text-decoration:underline;text-underline-offset:var(--mlv-spacing-0-5);transition:color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .ProseMirror a:visited{color:var(--mlv-text-action)}.mlv-editor .ProseMirror a:hover{color:var(--mlv-text-action-hover)}.mlv-editor .ProseMirror a:focus-visible{border-radius:var(--mlv-radius-xs);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul,.mlv-editor .ProseMirror ol{padding-inline-start:var(--mlv-spacing-6);margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror ul ul,.mlv-editor .ProseMirror ul ol,.mlv-editor .ProseMirror ol ul,.mlv-editor .ProseMirror ol ol{margin-block:var(--mlv-spacing-1) 0}.mlv-editor .ProseMirror ul{list-style:disc}.mlv-editor .ProseMirror ul ul{list-style:circle}.mlv-editor .ProseMirror ol{list-style:decimal}.mlv-editor .ProseMirror li{margin-block:var(--mlv-spacing-1)}.mlv-editor .ProseMirror li>p{margin:0}.mlv-editor .ProseMirror ul[data-type=taskList]{padding:0;list-style:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]{display:flex;align-items:flex-start;gap:var(--mlv-spacing-2)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label{display:inline-flex;align-items:center;min-block-size:var(--mlv-height-xs);cursor:pointer;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input{accent-color:var(--mlv-background-accent-1)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>div{flex:1 1 auto;min-width:0}.mlv-editor .ProseMirror blockquote{padding-inline-start:var(--mlv-spacing-4);margin:var(--mlv-spacing-4) 0;border-inline-start:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);color:var(--mlv-text-secondary)}.mlv-editor .ProseMirror code{padding:var(--mlv-spacing-0-5) var(--mlv-spacing-1);border-radius:var(--mlv-radius-s);background:var(--mlv-background-neutral-1);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size)}.mlv-editor .ProseMirror pre{padding:var(--mlv-padding-m);margin-block:var(--mlv-spacing-4);overflow:auto;border:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-radius:var(--mlv-radius-m);background:var(--mlv-background-sunken);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size);line-height:var(--mlv-typography-body-m-line-height);tab-size:2}.mlv-editor .ProseMirror pre code{padding:0;background:transparent;color:inherit;font-size:inherit}.mlv-editor .ProseMirror hr{height:var(--mlv-stroke-width);margin-block:var(--mlv-spacing-6);border:0;background:var(--mlv-border-normal)}.mlv-editor .ProseMirror img{display:block;max-inline-size:100%;block-size:auto;margin-block:var(--mlv-spacing-4);border-radius:var(--mlv-radius-m)}.mlv-editor .ProseMirror img.ProseMirror-selectednode{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror .ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-state=true]{border-radius:var(--mlv-radius-s);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-spacing-1)}.mlv-editor .ProseMirror [data-resize-container]{margin-block:var(--mlv-spacing-4)}.mlv-editor .ProseMirror [data-resize-container] img{margin-block:0}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]{outline:none}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode>[data-resize-wrapper],.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]>[data-resize-wrapper]{border-radius:var(--mlv-radius-m);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror [data-resize-handle],.mlv-editor .ProseMirror .resize-handle{inline-size:var(--mlv-spacing-3);block-size:var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-background-base);border-radius:var(--mlv-radius-full);background:var(--mlv-background-accent-1);box-shadow:var(--mlv-shadow-raised)}.mlv-editor .ProseMirror [data-resize-handle]:focus-visible,.mlv-editor .ProseMirror .resize-handle:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror .is-editor-empty:first-child:before{float:left;height:0;color:var(--mlv-text-placeholder);content:attr(data-placeholder);pointer-events:none}.mlv-editor .tableWrapper{margin-block:var(--mlv-spacing-3);overflow-x:auto}.mlv-editor table{width:100%;border-collapse:collapse;table-layout:fixed}.mlv-editor th,.mlv-editor td{position:relative;min-width:3rem;padding:var(--mlv-spacing-2) var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-border-normal);vertical-align:top}.mlv-editor th{background:var(--mlv-background-neutral-1);font-weight:var(--mlv-font-weight-semibold)}.mlv-editor .selectedCell:after{position:absolute;inset:0;z-index:1;background:var(--mlv-background-accent-1-pale);content:\"\";opacity:.55;pointer-events:none}.mlv-editor .ProseMirror-focused .selectedCell:after{box-shadow:inset 0 0 0 var(--mlv-stroke-width-medium) var(--mlv-border-focus)}.mlv-editor .column-resize-handle{position:absolute;inset-block:0;inset-inline-end:calc(var(--mlv-stroke-width) * -1);z-index:2;width:calc(var(--mlv-stroke-width) * 2);background:var(--mlv-border-focus);pointer-events:none}.mlv-editor .resize-cursor{cursor:col-resize}@keyframes mlv-editor-upload-spin{to{transform:rotate(1turn)}}@keyframes mlv-editor-ai-chunk-in{0%{opacity:0;filter:blur(.125rem)}to{opacity:1;filter:blur(0)}}@keyframes mlv-editor-ai-caret-pulse{0%,to{opacity:1}50%{opacity:.35}}@media(max-width:40rem){.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-3)}.mlv-editor__toolbar{flex-wrap:nowrap;overflow-x:auto}.mlv-editor__content{padding-block:var(--mlv-spacing-1-5)}.mlv-editor__status{justify-content:flex-start}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h2-size)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h3-size)}}@media print{.mlv-editor__toolbar,.mlv-editor__status,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .mlv-editor-image-upload-status,.mlv-editor__upload-placeholder,.mlv-editor__ai-suggestion-delete,.mlv-editor__ai-caret{display:none}.mlv-editor__ai-suggestion-current{outline:none}.mlv-editor__ai-streaming-chunk{animation:none}.mlv-editor__viewport{overflow:visible}.mlv-editor__view{transform:none}.mlv-editor__content,.mlv-editor .ProseMirror{min-block-size:0;padding:0}.mlv-editor table{break-inside:avoid}}@media(hover:none){.mlv-editor__block-handle{display:none}}@media(prefers-reduced-motion:reduce){.mlv-editor .mlv-form-control-wrapper__control-container,.mlv-editor__view,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .ProseMirror--block-dragging>*,.mlv-editor .ProseMirror a{transition-duration:var(--mlv-duration-instant)}.mlv-editor__upload-placeholder:after,.mlv-editor__ai-streaming-chunk,.mlv-editor__ai-caret:after{animation:none}}.mlv-editor__drag-ghost{max-block-size:12rem;padding:var(--mlv-spacing-2);overflow:hidden;border-radius:var(--mlv-radius-m);background-color:var(--mlv-background-raised);box-shadow:var(--mlv-shadow-floating);opacity:.9;-webkit-mask-image:linear-gradient(to bottom,black 70%,transparent 100%);mask-image:linear-gradient(to bottom,black 70%,transparent 100%);scale:.85;transform-origin:top left}}\n"], dependencies: [{ kind: "component", type: MlvFormControlWrapper, selector: "mlv-form-control-wrapper", outputs: ["clear"] }, { kind: "directive", type: MlvFormControlWrapperControl, selector: "[mlvFormControlWrapperControl]" }, { kind: "component", type: MlvHint, selector: "mlv-hint" }, { kind: "component", type: MlvLabel, selector: "mlv-label", inputs: ["for", "required"] }, { kind: "component", type: MlvMessage, selector: "mlv-message", inputs: ["state"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MlvFade, selector: "[mlvFade]", inputs: ["mlvFadeHeight", "mlvFadeSize", "mlvFadeOffset", "mlvFade"] }, { kind: "component", type: MlvToolbar, selector: "mlv-toolbar", inputs: ["gap", "equalSize"] }, { kind: "component", type: MlvDivider, selector: "mlv-divider", inputs: ["orientation", "dashed", "muted"] }, { kind: "directive", type: MlvEditorToolbarRoot, selector: "[mlvEditorToolbarRoot]", inputs: ["ariaLabel", "disabled", "orientation", "wrap"], exportAs: ["mlvEditorToolbarRoot"] }, { kind: "component", type: MlvEditorToolbarOverflow, selector: "mlv-editor-toolbar-overflow" }, { kind: "component", type: MlvEditorUndoRedo, selector: "mlv-editor-undo-redo" }, { kind: "component", type: MlvEditorZoom, selector: "mlv-editor-zoom", inputs: ["zoom", "zoomLevels", "min", "max", "fitToContainer"], outputs: ["zoomChange"] }, { kind: "component", type: MlvEditorHeading, selector: "mlv-editor-heading", inputs: ["levels"] }, { kind: "component", type: MlvEditorList, selector: "mlv-editor-list" }, { kind: "component", type: MlvEditorInlineMarks, selector: "mlv-editor-inline-marks" }, { kind: "component", type: MlvEditorTextColor, selector: "mlv-editor-text-color" }, { kind: "component", type: MlvEditorStatus, selector: "mlv-editor-status", inputs: ["characterLimit"] }, { kind: "component", type: MlvEditorHighlight, selector: "mlv-editor-highlight" }, { kind: "component", type: MlvEditorLink, selector: "mlv-editor-link", inputs: ["allowedProtocols"] }, { kind: "component", type: MlvEditorImageUpload, selector: "mlv-editor-image-upload" }, { kind: "component", type: MlvEditorImageUploadStatus, selector: "mlv-editor-image-upload-status" }, { kind: "component", type: MlvEditorTable, selector: "mlv-editor-table" }, { kind: "component", type: MlvEditorTableControls, selector: "mlv-editor-table-controls", inputs: ["layer"] }, { kind: "component", type: MlvEditorAlignment, selector: "mlv-editor-alignment" }, { kind: "component", type: MlvEditorBlockInsert, selector: "mlv-editor-block-insert" }, { kind: "component", type: MlvSpacer, selector: "mlv-spacer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11592
+ ], queries: [{ propertyName: "_toolbarDefs", predicate: MlvEditorToolbarDef, isSignal: true }, { propertyName: "_toolbarStartDefs", predicate: MlvEditorToolbarStartDef, isSignal: true }, { propertyName: "_toolbarEndDefs", predicate: MlvEditorToolbarEndDef, isSignal: true }], viewQueries: [{ propertyName: "_content", first: true, predicate: ["content"], descendants: true, isSignal: true }, { propertyName: "_view", first: true, predicate: ["view"], descendants: true, isSignal: true }, { propertyName: "_toolbarBand", first: true, predicate: ["toolbarBand"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<mlv-form-control-wrapper (clear)=\"clearValue()\">\n @if (label() || hint()) {\n <mlv-label\n [for]=\"_ownLabelFor()\"\n [id]=\"_labelId()\"\n (click)=\"_onLabelClick()\"\n >\n {{ label() }}\n @if (hint()) {\n <mlv-hint>{{ hint() }}</mlv-hint>\n }\n </mlv-label>\n }\n\n <ng-template mlvFormControlWrapperControl>\n <div #surface class=\"mlv-editor__surface\">\n <!--\n Declared once and stamped by one outlet: before or after the viewport\n for the docked bar (#416), so the DOM order is always the visual\n order, with no CSS `order`; into the selection bubble's overlay for\n the floating appearance (#483). Re-stamping re-creates the view, which\n closes an open toolbar popup; projected slot content is re-attached.\n Neither stamps anything while `readonly` (#498): no band, no reserved\n space; `_toolbarRendered()` is the one switch.\n -->\n <ng-template #toolbarTemplate>\n <div #toolbarBand class=\"mlv-editor__toolbar-band\">\n <div\n mlvEditorToolbarRoot\n #toolbarRoot=\"mlvEditorToolbarRoot\"\n class=\"mlv-editor__toolbar\"\n [ariaLabel]=\"_toolbarAriaLabel()\"\n [disabled]=\"computedDisabled()\"\n [measureTarget]=\"surface\"\n >\n @if (_hasToolbarDef()) {\n <ng-content select=\"[mlvEditorToolbar]\" />\n @for (definition of _toolbarDefs(); track definition) {\n @if (definition.templateRef; as toolbarTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"toolbarTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n }\n }\n } @else {\n <ng-content select=\"[mlvEditorToolbarStart]\" />\n @for (definition of _toolbarStartDefs(); track definition) {\n @if (definition.templateRef; as startTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"startTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n }\n }\n <div mlvFade class=\"mlv-editor__toolbar-scroll\">\n <mlv-toolbar>\n <mlv-editor-undo-redo />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-zoom />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-heading />\n <mlv-editor-list />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-inline-marks [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-text-color />\n <mlv-editor-highlight />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-alignment [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-link />\n <mlv-editor-table />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-block-insert [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-image-upload />\n </mlv-toolbar>\n </div>\n <mlv-editor-toolbar-overflow />\n @for (definition of _toolbarEndDefs(); track definition) {\n @if (definition.templateRef; as endTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"endTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n }\n }\n <ng-content select=\"[mlvEditorToolbarEnd]\" />\n }\n </div>\n </div>\n </ng-template>\n\n @if (_barRendered() && toolbarPosition() === 'top') {\n <ng-container [ngTemplateOutlet]=\"toolbarTemplate\" />\n }\n\n <div #viewport class=\"mlv-editor__viewport\">\n <div #view class=\"mlv-editor__view\">\n <div #content class=\"mlv-editor__content\"></div>\n <mlv-editor-table-controls [layer]=\"view\" />\n </div>\n </div>\n @if (_barRendered() && toolbarPosition() === 'bottom') {\n <ng-container [ngTemplateOutlet]=\"toolbarTemplate\" />\n }\n @if (toolbarAppearance() === 'floating') {\n <ng-template mlvEditorBubble [viewport]=\"viewport\" [capped]=\"_capped()\">\n @if (_toolbarRendered()) {\n <ng-container [ngTemplateOutlet]=\"toolbarTemplate\" />\n }\n </ng-template>\n }\n <mlv-editor-image-upload-status />\n </div>\n </ng-template>\n\n @if (message()) {\n <mlv-message [state]=\"resolvedState()\" [id]=\"id() + '-message'\">\n {{ message() }}\n </mlv-message>\n }\n</mlv-form-control-wrapper>\n\n<div\n class=\"mlv-editor__status\"\n [attr.aria-disabled]=\"computedDisabled() || null\"\n>\n <ng-content select=\"[mlvEditorStatus]\" />\n <mlv-spacer />\n <mlv-editor-status [characterLimit]=\"characterLimit()\" />\n</div>\n", styles: ["@layer mlv.components{.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-6);--mlv-editor-measure: 45rem;box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor *{box-sizing:border-box}.mlv-editor{display:block;color:var(--mlv-text-primary)}.mlv-editor .mlv-form-control-wrapper__control-container{height:auto;min-height:var(--form-ctrl-height);padding:0;overflow:clip;background:var(--mlv-background-base);transition:border-color var(--mlv-duration-fast) var(--mlv-ease-default),box-shadow var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .mlv-form-control-wrapper__control-row{align-items:stretch}.mlv-form-control-wrapper--state-error .mlv-editor__surface{box-shadow:inset 0 0 0 var(--mlv-stroke-width) var(--mlv-border-error)}.mlv-editor__surface{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;width:100%;background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--mlv-spacing-1);min-width:0;padding:var(--mlv-padding-xs);overflow:hidden;border-block-end:var(--mlv-stroke-width) solid var(--mlv-border-normal);background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar-band{display:flex;flex:0 0 auto;flex-direction:column;min-inline-size:0}.mlv-editor--toolbar-bottom .mlv-editor__toolbar{border-block-start:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-block-end:0}.mlv-editor__toolbar [hidden]{display:none!important}.mlv-editor__toolbar-scroll{flex:1 1 auto;min-width:0}.mlv-editor__toolbar-scroll>.mlv-toolbar{width:max-content;min-width:100%;flex-wrap:nowrap}.mlv-editor .mlv-editor-toolbar__separator{flex:0 0 auto;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor .mlv-editor-toolbar__overflow,.mlv-editor .mlv-editor-undo-redo,.mlv-editor .mlv-editor-inline-marks,.mlv-editor .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor mlv-editor-zoom,.mlv-editor mlv-editor-heading,.mlv-editor mlv-editor-list,.mlv-editor mlv-editor-text-color,.mlv-editor mlv-editor-highlight,.mlv-editor mlv-editor-alignment,.mlv-editor mlv-editor-link,.mlv-editor mlv-editor-table,.mlv-editor mlv-editor-image-upload{flex:0 0 auto}.mlv-editor__viewport{flex:1 1 auto;min-block-size:var(--mlv-editor-min-height, 8rem)}.mlv-editor--capped .mlv-editor__surface{block-size:var(--mlv-editor-height, auto);max-block-size:var(--mlv-editor-max-height, none)}.mlv-editor--capped .mlv-editor__viewport{min-block-size:0;overflow:auto;overscroll-behavior:contain}.mlv-editor__view{position:relative;isolation:isolate;min-inline-size:100%;zoom:var(--mlv-editor-zoom, 1)}.mlv-editor--capped .mlv-editor__view{min-inline-size:0;inline-size:calc(100% * var(--mlv-editor-zoom, 1))}.mlv-editor__content{min-block-size:var(--mlv-editor-min-height, 8rem);padding-block:var(--mlv-spacing-2);padding-inline:var(--mlv-editor-gutter);border:0;outline:none}.mlv-editor--capped .mlv-editor__content{min-block-size:calc(var(--mlv-editor-min-height, 8rem) / var(--mlv-editor-zoom, 1))}.mlv-editor__block-handle{position:absolute;inset-inline-start:max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2);display:inline-flex;align-items:center;justify-content:center;inline-size:var(--mlv-editor-gutter);block-size:var(--mlv-height-xs);border-radius:var(--mlv-radius-s);color:var(--mlv-text-tertiary);cursor:grab;opacity:0;pointer-events:none;transition:opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__block-handle svg{inline-size:var(--mlv-spacing-4);block-size:var(--mlv-spacing-4);fill:currentcolor}.mlv-editor__block-handle:hover{background-color:var(--mlv-background-neutral-1-hover);color:var(--mlv-text-secondary)}.mlv-editor__block-handle[data-visible=true]{opacity:1;pointer-events:auto}.mlv-editor__drop-indicator{position:absolute;inset-inline:calc(max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2) + var(--mlv-editor-gutter));block-size:var(--mlv-stroke-width-medium);border-radius:var(--mlv-radius-full);background-color:var(--mlv-border-focus);opacity:0;pointer-events:none;transform:translateY(-50%);transition:top var(--mlv-duration-normal) var(--mlv-ease-out),opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__drop-indicator[data-visible=true]{opacity:1}.mlv-editor .mlv-editor__block--dragging{opacity:var(--mlv-disabled-opacity)}.mlv-editor__status{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-2);min-block-size:var(--mlv-spacing-4);margin-block-start:var(--mlv-spacing-1)}.mlv-editor__upload-placeholder{position:relative;display:inline-block;inline-size:var(--mlv-height-l);block-size:var(--mlv-height-l);margin-inline:var(--mlv-spacing-1);border:var(--mlv-stroke-width) dashed var(--mlv-border-focus);border-radius:var(--mlv-radius-m);background:var(--mlv-background-accent-1-pale);vertical-align:middle}.mlv-editor__upload-placeholder:after{position:absolute;inset:var(--mlv-spacing-2);border:var(--mlv-stroke-width-medium) solid var(--mlv-border-normal);border-block-start-color:var(--mlv-border-focus);border-radius:var(--mlv-radius-full);animation:mlv-editor-upload-spin var(--mlv-duration-slower) var(--mlv-ease-linear) infinite;content:\"\"}.mlv-editor__ai-streaming{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-accent-1-pale)}.mlv-editor__ai-streaming-chunk{animation:mlv-editor-ai-chunk-in var(--mlv-duration-normal) var(--mlv-ease-out)}.mlv-editor__ai-caret{position:relative;display:inline-block;inline-size:0;block-size:1em;pointer-events:none}.mlv-editor__ai-caret:after{position:absolute;inset-block:0;inset-inline-start:.0625rem;inline-size:.35em;border-radius:var(--mlv-radius-xs);background:linear-gradient(to bottom,var(--mlv-background-accent-1),var(--mlv-background-accent-2));content:\"\";animation:mlv-editor-ai-caret-pulse var(--mlv-duration-sluggish) var(--mlv-ease-in-out) infinite}.mlv-editor__ai-suggestion-insert{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-success-pale)}.mlv-editor__ai-suggestion-delete{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-danger-1-pale);color:var(--mlv-text-negative);text-decoration-line:line-through}.mlv-editor__ai-suggestion-current{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor--content-width-default{--mlv-editor-measure: 45rem}.mlv-editor--content-width-wide{--mlv-editor-measure: 60rem}.mlv-editor--content-width-full{--mlv-editor-measure: 100%}.mlv-editor--focused .mlv-form-control-wrapper__control-container{border-color:var(--mlv-border-focus);box-shadow:var(--mlv-focus-ring)}.mlv-editor--readonly .ProseMirror{cursor:text}.mlv-editor--readonly .ProseMirror ::selection{background:var(--mlv-background-accent-1-pale)}.mlv-editor--disabled{opacity:var(--mlv-disabled-opacity)}.mlv-editor--disabled .mlv-editor__surface,.mlv-editor--disabled .mlv-editor__status{pointer-events:none}.mlv-editor--disabled .mlv-editor__content,.mlv-editor--disabled .ProseMirror{cursor:default;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror{min-block-size:inherit;max-inline-size:var(--mlv-editor-measure);margin-inline:auto;color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size);font-weight:var(--mlv-typography-body-m-weight);line-height:var(--mlv-typography-body-m-line-height);overflow-wrap:anywhere;white-space:pre-wrap}.mlv-editor .ProseMirror:focus-visible{outline:none}.mlv-editor .ProseMirror>*:first-child{margin-block-start:0}.mlv-editor .ProseMirror>*:last-child{margin-block-end:0}.mlv-editor .ProseMirror p{margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror h1,.mlv-editor .ProseMirror h2,.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{margin-block:var(--mlv-spacing-6) var(--mlv-spacing-3);color:var(--mlv-text-heading);font-family:var(--mlv-typography-family-heading)}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h1-size);font-weight:var(--mlv-typography-heading-h1-weight);line-height:var(--mlv-typography-heading-h1-line-height)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h2-size);font-weight:var(--mlv-typography-heading-h2-weight);line-height:var(--mlv-typography-heading-h2-line-height)}.mlv-editor .ProseMirror h3{font-size:var(--mlv-typography-heading-h3-size);font-weight:var(--mlv-typography-heading-h3-weight);line-height:var(--mlv-typography-heading-h3-line-height)}.mlv-editor .ProseMirror h4{font-size:var(--mlv-typography-heading-h4-size);font-weight:var(--mlv-typography-heading-h4-weight);line-height:var(--mlv-typography-heading-h4-line-height)}.mlv-editor .ProseMirror h5{font-size:var(--mlv-typography-heading-h5-size);font-weight:var(--mlv-typography-heading-h5-weight);line-height:var(--mlv-typography-heading-h5-line-height)}.mlv-editor .ProseMirror h6{font-size:var(--mlv-typography-heading-h6-size);font-weight:var(--mlv-typography-heading-h6-weight);line-height:var(--mlv-typography-heading-h6-line-height)}.mlv-editor .ProseMirror h1{letter-spacing:var(--mlv-typography-heading-h1-letter-spacing)}.mlv-editor .ProseMirror h2{letter-spacing:var(--mlv-typography-heading-h2-letter-spacing)}.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{letter-spacing:var(--mlv-letter-spacing-normal)}.mlv-editor .ProseMirror strong{font-weight:var(--mlv-font-weight-bold)}.mlv-editor .ProseMirror em{font-style:italic}.mlv-editor .ProseMirror s{text-decoration:line-through}.mlv-editor .ProseMirror u{text-underline-offset:var(--mlv-spacing-0-5)}.mlv-editor .ProseMirror a{color:var(--mlv-text-action);text-decoration:underline;text-underline-offset:var(--mlv-spacing-0-5);transition:color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .ProseMirror a:visited{color:var(--mlv-text-action)}.mlv-editor .ProseMirror a:hover{color:var(--mlv-text-action-hover)}.mlv-editor .ProseMirror a:focus-visible{border-radius:var(--mlv-radius-xs);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul,.mlv-editor .ProseMirror ol{padding-inline-start:var(--mlv-spacing-6);margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror ul ul,.mlv-editor .ProseMirror ul ol,.mlv-editor .ProseMirror ol ul,.mlv-editor .ProseMirror ol ol{margin-block:var(--mlv-spacing-1) 0}.mlv-editor .ProseMirror ul{list-style:disc}.mlv-editor .ProseMirror ul ul{list-style:circle}.mlv-editor .ProseMirror ol{list-style:decimal}.mlv-editor .ProseMirror li{margin-block:var(--mlv-spacing-1)}.mlv-editor .ProseMirror li>p{margin:0}.mlv-editor .ProseMirror ul[data-type=taskList]{padding:0;list-style:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]{display:flex;align-items:flex-start;gap:var(--mlv-spacing-2)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label{display:inline-flex;align-items:center;min-block-size:var(--mlv-height-xs);cursor:pointer;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input{accent-color:var(--mlv-background-accent-1)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>div{flex:1 1 auto;min-width:0}.mlv-editor .ProseMirror blockquote{padding-inline-start:var(--mlv-spacing-4);margin:var(--mlv-spacing-4) 0;border-inline-start:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);color:var(--mlv-text-secondary)}.mlv-editor .ProseMirror code{padding:var(--mlv-spacing-0-5) var(--mlv-spacing-1);border-radius:var(--mlv-radius-s);background:var(--mlv-background-neutral-1);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size)}.mlv-editor .ProseMirror pre{padding:var(--mlv-padding-m);margin-block:var(--mlv-spacing-4);overflow:auto;border:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-radius:var(--mlv-radius-m);background:var(--mlv-background-sunken);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size);line-height:var(--mlv-typography-body-m-line-height);tab-size:2}.mlv-editor .ProseMirror pre code{padding:0;background:transparent;color:inherit;font-size:inherit}.mlv-editor .ProseMirror hr{height:var(--mlv-stroke-width);margin-block:var(--mlv-spacing-6);border:0;background:var(--mlv-border-normal)}.mlv-editor .ProseMirror img{display:block;max-inline-size:100%;block-size:auto;margin-block:var(--mlv-spacing-4);border-radius:var(--mlv-radius-m)}.mlv-editor .ProseMirror img.ProseMirror-selectednode{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror .ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-state=true]{border-radius:var(--mlv-radius-s);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-spacing-1)}.mlv-editor .ProseMirror [data-resize-container]{margin-block:var(--mlv-spacing-4)}.mlv-editor .ProseMirror [data-resize-container] img{margin-block:0}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]{outline:none}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode>[data-resize-wrapper],.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]>[data-resize-wrapper]{border-radius:var(--mlv-radius-m);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror [data-resize-handle],.mlv-editor .ProseMirror .resize-handle{inline-size:var(--mlv-spacing-3);block-size:var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-background-base);border-radius:var(--mlv-radius-full);background:var(--mlv-background-accent-1);box-shadow:var(--mlv-shadow-raised)}.mlv-editor .ProseMirror [data-resize-handle]:focus-visible,.mlv-editor .ProseMirror .resize-handle:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror .is-editor-empty:first-child:before{float:left;height:0;color:var(--mlv-text-placeholder);content:attr(data-placeholder);pointer-events:none}.mlv-editor .tableWrapper{margin-block:var(--mlv-spacing-3);overflow-x:auto}.mlv-editor table{width:100%;border-collapse:collapse;table-layout:fixed}.mlv-editor th,.mlv-editor td{position:relative;min-width:3rem;padding:var(--mlv-spacing-2) var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-border-normal);vertical-align:top}.mlv-editor th{background:var(--mlv-background-neutral-1);font-weight:var(--mlv-font-weight-semibold)}.mlv-editor .selectedCell:after{position:absolute;inset:0;z-index:1;background:var(--mlv-background-accent-1-pale);content:\"\";opacity:.55;pointer-events:none}.mlv-editor .ProseMirror-focused .selectedCell:after{box-shadow:inset 0 0 0 var(--mlv-stroke-width-medium) var(--mlv-border-focus)}.mlv-editor .column-resize-handle{position:absolute;inset-block:0;inset-inline-end:calc(var(--mlv-stroke-width) * -1);z-index:2;width:calc(var(--mlv-stroke-width) * 2);background:var(--mlv-border-focus);pointer-events:none}.mlv-editor .resize-cursor{cursor:col-resize}@keyframes mlv-editor-upload-spin{to{transform:rotate(1turn)}}@keyframes mlv-editor-ai-chunk-in{0%{opacity:0;filter:blur(.125rem)}to{opacity:1;filter:blur(0)}}@keyframes mlv-editor-ai-caret-pulse{0%,to{opacity:1}50%{opacity:.35}}.mlv-editor--toolbar-sticky.mlv-editor--toolbar-top .mlv-editor__toolbar-band{position:sticky;inset-block-start:var(--mlv-editor-toolbar-sticky-offset, 0);z-index:var(--mlv-z-raised)}.mlv-editor--toolbar-sticky.mlv-editor--toolbar-bottom .mlv-editor__toolbar-band{position:sticky;inset-block-end:var(--mlv-editor-toolbar-sticky-offset, 0);z-index:var(--mlv-z-raised)}.mlv-editor-bubble{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-bubble *{box-sizing:border-box}.mlv-editor-bubble .mlv-editor-toolbar__separator{flex:0 0 auto;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor-bubble .mlv-editor-toolbar__overflow,.mlv-editor-bubble .mlv-editor-undo-redo,.mlv-editor-bubble .mlv-editor-inline-marks,.mlv-editor-bubble .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-bubble mlv-editor-zoom,.mlv-editor-bubble mlv-editor-heading,.mlv-editor-bubble mlv-editor-list,.mlv-editor-bubble mlv-editor-text-color,.mlv-editor-bubble mlv-editor-highlight,.mlv-editor-bubble mlv-editor-alignment,.mlv-editor-bubble mlv-editor-link,.mlv-editor-bubble mlv-editor-table,.mlv-editor-bubble mlv-editor-image-upload{flex:0 0 auto}.mlv-editor-bubble{max-inline-size:calc(100vw - 2 * var(--mlv-spacing-2));border-radius:var(--mlv-radius-panel);background:var(--mlv-elevation-bg-4);box-shadow:var(--mlv-shadow-floating);color:var(--mlv-text-primary);animation:mlv-editor-bubble-enter var(--mlv-duration-fast) var(--mlv-ease-out)}.mlv-editor-bubble--hidden{visibility:hidden;animation:none}.mlv-editor-bubble .mlv-editor__toolbar-band{flex:1 1 auto;min-inline-size:0}.mlv-editor-bubble .mlv-editor__toolbar{border:0;border-radius:inherit;background:transparent}@keyframes mlv-editor-bubble-enter{0%{opacity:0}}@media(max-width:40rem){.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-3)}.mlv-editor__toolbar{flex-wrap:nowrap;overflow-x:auto}.mlv-editor__content{padding-block:var(--mlv-spacing-1-5)}.mlv-editor__status{justify-content:flex-start}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h2-size)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h3-size)}}@media print{.mlv-editor__toolbar-band,.mlv-editor__toolbar,.mlv-editor__status,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .mlv-editor-image-upload-status,.mlv-editor__upload-placeholder,.mlv-editor__ai-suggestion-delete,.mlv-editor__ai-caret{display:none}.mlv-editor__ai-suggestion-current{outline:none}.mlv-editor__ai-streaming-chunk{animation:none}.mlv-editor__viewport,.mlv-editor--capped .mlv-editor__viewport{overflow:visible;padding-block:0}.mlv-editor--capped .mlv-editor__surface{block-size:auto;max-block-size:none}.mlv-editor__view,.mlv-editor--capped .mlv-editor__view{zoom:1;inline-size:auto;min-inline-size:0}.mlv-editor__content,.mlv-editor--capped .mlv-editor__content,.mlv-editor .ProseMirror{min-block-size:0;padding:0}.mlv-editor table{break-inside:avoid}}@media print{.mlv-editor-bubble{display:none}}@media(hover:none){.mlv-editor__block-handle{display:none}}@media(prefers-reduced-motion:reduce){.mlv-editor .mlv-form-control-wrapper__control-container,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .ProseMirror a{transition-duration:var(--mlv-duration-instant)}.mlv-editor__upload-placeholder:after,.mlv-editor__ai-streaming-chunk,.mlv-editor__ai-caret:after{animation:none}.mlv-editor-bubble{animation:none}}.mlv-editor__drag-ghost{max-block-size:12rem;padding:var(--mlv-spacing-2);overflow:hidden;border-radius:var(--mlv-radius-m);background-color:var(--mlv-background-accent-1-pale);opacity:.9;-webkit-mask-image:linear-gradient(to bottom,black 70%,transparent 100%);mask-image:linear-gradient(to bottom,black 70%,transparent 100%)}}\n"], dependencies: [{ kind: "component", type: MlvFormControlWrapper, selector: "mlv-form-control-wrapper", outputs: ["clear"] }, { kind: "directive", type: MlvFormControlWrapperControl, selector: "[mlvFormControlWrapperControl]" }, { kind: "component", type: MlvHint, selector: "mlv-hint" }, { kind: "component", type: MlvLabel, selector: "mlv-label", inputs: ["for", "required"] }, { kind: "component", type: MlvMessage, selector: "mlv-message", inputs: ["state"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MlvFade, selector: "[mlvFade]", inputs: ["mlvFadeHeight", "mlvFadeSize", "mlvFadeOffset", "mlvFade"] }, { kind: "component", type: MlvToolbar, selector: "mlv-toolbar", inputs: ["gap", "equalSize"] }, { kind: "component", type: MlvDivider, selector: "mlv-divider", inputs: ["orientation", "dashed", "muted"] }, { kind: "directive", type: MlvEditorToolbarRoot, selector: "[mlvEditorToolbarRoot]", inputs: ["ariaLabel", "disabled", "orientation", "wrap", "measureTarget"], exportAs: ["mlvEditorToolbarRoot"] }, { kind: "component", type: MlvEditorToolbarOverflow, selector: "mlv-editor-toolbar-overflow" }, { kind: "component", type: MlvEditorUndoRedo, selector: "mlv-editor-undo-redo" }, { kind: "component", type: MlvEditorZoom, selector: "mlv-editor-zoom", inputs: ["zoom", "zoomLevels", "min", "max", "fitToContainer"], outputs: ["zoomChange"] }, { kind: "component", type: MlvEditorHeading, selector: "mlv-editor-heading", inputs: ["levels"] }, { kind: "component", type: MlvEditorList, selector: "mlv-editor-list" }, { kind: "component", type: MlvEditorInlineMarks, selector: "mlv-editor-inline-marks" }, { kind: "component", type: MlvEditorTextColor, selector: "mlv-editor-text-color" }, { kind: "component", type: MlvEditorStatus, selector: "mlv-editor-status", inputs: ["characterLimit"] }, { kind: "component", type: MlvEditorHighlight, selector: "mlv-editor-highlight" }, { kind: "component", type: MlvEditorLink, selector: "mlv-editor-link", inputs: ["allowedProtocols"] }, { kind: "component", type: MlvEditorImageUpload, selector: "mlv-editor-image-upload" }, { kind: "component", type: MlvEditorImageUploadStatus, selector: "mlv-editor-image-upload-status" }, { kind: "component", type: MlvEditorTable, selector: "mlv-editor-table" }, { kind: "component", type: MlvEditorTableControls, selector: "mlv-editor-table-controls", inputs: ["layer"] }, { kind: "component", type: MlvEditorAlignment, selector: "mlv-editor-alignment" }, { kind: "component", type: MlvEditorBlockInsert, selector: "mlv-editor-block-insert" }, { kind: "directive", type: MlvEditorBubble, selector: "ng-template[mlvEditorBubble]", inputs: ["viewport", "capped"] }, { kind: "component", type: MlvSpacer, selector: "mlv-spacer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10120
11593
  }
10121
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditor, decorators: [{
11594
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MlvEditor, decorators: [{
10122
11595
  type: Component,
10123
11596
  args: [{ selector: 'mlv-editor', imports: [
10124
11597
  MlvFormControlWrapper,
@@ -10147,6 +11620,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
10147
11620
  MlvEditorTableControls,
10148
11621
  MlvEditorAlignment,
10149
11622
  MlvEditorBlockInsert,
11623
+ MlvEditorBubble,
10150
11624
  MlvSpacer,
10151
11625
  ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
10152
11626
  MlvEditorOverlayRegistry,
@@ -10213,11 +11687,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
10213
11687
  '[class.mlv-editor--disabled]': 'computedDisabled()',
10214
11688
  '[class.mlv-editor--readonly]': 'readonly()',
10215
11689
  '[class.mlv-editor--focused]': 'focused()',
11690
+ '[class.mlv-editor--capped]': '_capped()',
11691
+ '[class.mlv-editor--toolbar-top]': 'toolbarPosition() === "top"',
11692
+ '[class.mlv-editor--toolbar-bottom]': 'toolbarPosition() === "bottom"',
11693
+ '[class.mlv-editor--toolbar-bar]': 'toolbarAppearance() === "bar"',
11694
+ '[class.mlv-editor--toolbar-floating]': 'toolbarAppearance() === "floating"',
11695
+ '[class.mlv-editor--toolbar-sticky]': '_stickyToolbar()',
10216
11696
  '[attr.aria-disabled]': 'computedDisabled() || null',
10217
11697
  '[attr.inert]': 'computedDisabled() ? "" : null',
10218
11698
  '[style.--mlv-editor-zoom]': 'zoom() / 100',
10219
- }, template: "<mlv-form-control-wrapper (clear)=\"clearValue()\">\n @if (label() || hint()) {\n <mlv-label [for]=\"id()\" [id]=\"_labelId()\">\n {{ label() }} @if (hint()) {\n <mlv-hint>{{ hint() }}</mlv-hint>\n }\n </mlv-label>\n }\n\n <ng-template mlvFormControlWrapperControl>\n <div class=\"mlv-editor__surface\">\n <div\n mlvEditorToolbarRoot\n #toolbarRoot=\"mlvEditorToolbarRoot\"\n class=\"mlv-editor__toolbar\"\n [ariaLabel]=\"_toolbarAriaLabel()\"\n [disabled]=\"computedDisabled()\"\n >\n @if (_hasToolbarDef()) {\n <ng-content select=\"[mlvEditorToolbar]\" />\n @for (definition of _toolbarDefs(); track definition) { @if\n (definition.templateRef; as toolbarTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"toolbarTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n } } } @else {\n <ng-content select=\"[mlvEditorToolbarStart]\" />\n @for (definition of _toolbarStartDefs(); track definition) { @if\n (definition.templateRef; as startTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"startTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n } }\n <div mlvFade class=\"mlv-editor__toolbar-scroll\">\n <mlv-toolbar>\n <mlv-editor-undo-redo />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-zoom />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-heading />\n <mlv-editor-list />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-inline-marks [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-text-color />\n <mlv-editor-highlight />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-alignment [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-link />\n <mlv-editor-table />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-block-insert [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-image-upload />\n </mlv-toolbar>\n </div>\n <mlv-editor-toolbar-overflow />\n @for (definition of _toolbarEndDefs(); track definition) { @if\n (definition.templateRef; as endTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"endTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n } }\n <ng-content select=\"[mlvEditorToolbarEnd]\" />\n }\n </div>\n\n <div class=\"mlv-editor__viewport\">\n <div #view class=\"mlv-editor__view\">\n <div #content class=\"mlv-editor__content\"></div>\n <mlv-editor-table-controls [layer]=\"view\" />\n </div>\n </div>\n <mlv-editor-image-upload-status />\n </div>\n </ng-template>\n\n @if (message()) {\n <mlv-message [state]=\"resolvedState()\" [id]=\"id() + '-message'\">\n {{ message() }}\n </mlv-message>\n }\n</mlv-form-control-wrapper>\n\n<div\n class=\"mlv-editor__status\"\n [attr.aria-disabled]=\"computedDisabled() || null\"\n>\n <ng-content select=\"[mlvEditorStatus]\" />\n <mlv-spacer />\n <mlv-editor-status [characterLimit]=\"characterLimit()\" />\n</div>\n", styles: ["@layer mlv.components{.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-6);--mlv-editor-measure: 45rem;--mlv-editor-drop-gap: 1.5rem;box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor *{box-sizing:border-box}.mlv-editor{display:block;color:var(--mlv-text-primary)}.mlv-editor .mlv-form-control-wrapper__control-container{height:auto;min-height:var(--form-ctrl-height);padding:0;overflow:hidden;background:var(--mlv-background-base);transition:border-color var(--mlv-duration-fast) var(--mlv-ease-default),box-shadow var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .mlv-form-control-wrapper__control-row{align-items:stretch}.mlv-form-control-wrapper--state-error .mlv-editor__surface{box-shadow:inset 0 0 0 var(--mlv-stroke-width) var(--mlv-border-error)}.mlv-editor__surface{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;width:100%;background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--mlv-spacing-1);min-width:0;padding:var(--mlv-padding-xs);overflow:hidden;border-bottom:var(--mlv-stroke-width) solid var(--mlv-border-normal);background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar [hidden]{display:none!important}.mlv-editor__toolbar-scroll{flex:1 1 auto;min-width:0}.mlv-editor__toolbar-scroll>.mlv-toolbar{width:max-content;min-width:100%;flex-wrap:nowrap}.mlv-editor .mlv-editor-toolbar__separator{flex:0 0 auto;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor .mlv-editor-toolbar__overflow,.mlv-editor .mlv-editor-undo-redo,.mlv-editor .mlv-editor-inline-marks,.mlv-editor .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor mlv-editor-zoom,.mlv-editor mlv-editor-heading,.mlv-editor mlv-editor-list,.mlv-editor mlv-editor-text-color,.mlv-editor mlv-editor-highlight,.mlv-editor mlv-editor-alignment,.mlv-editor mlv-editor-link,.mlv-editor mlv-editor-table,.mlv-editor mlv-editor-image-upload{flex:0 0 auto}.mlv-editor__viewport{min-block-size:8rem;overflow:auto;overscroll-behavior:contain}.mlv-editor__view{position:relative;min-inline-size:100%;transform:scale(var(--mlv-editor-zoom, 1));transform-origin:left top;transition:transform var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__content{min-block-size:8rem;padding-block:var(--mlv-spacing-2);padding-inline:var(--mlv-editor-gutter);border:0;outline:none}.mlv-editor__block-handle{position:absolute;inset-inline-start:max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2);display:inline-flex;align-items:center;justify-content:center;inline-size:var(--mlv-editor-gutter);block-size:var(--mlv-height-xs);border-radius:var(--mlv-radius-s);color:var(--mlv-text-tertiary);cursor:grab;opacity:0;pointer-events:none;transition:opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__block-handle svg{inline-size:var(--mlv-spacing-4);block-size:var(--mlv-spacing-4);fill:currentcolor}.mlv-editor__block-handle:hover{background-color:var(--mlv-background-neutral-1-hover);color:var(--mlv-text-secondary)}.mlv-editor__block-handle[data-visible=true]{opacity:1;pointer-events:auto}.mlv-editor__drop-indicator{position:absolute;inset-inline:calc(max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2) + var(--mlv-editor-gutter));block-size:var(--mlv-stroke-width-medium);border-radius:var(--mlv-radius-full);background-color:var(--mlv-border-focus);opacity:0;pointer-events:none;transform:translateY(calc(var(--mlv-editor-drop-gap) / 2 - var(--mlv-stroke-width-medium) / 2));transition:top var(--mlv-duration-normal) var(--mlv-ease-out),opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__drop-indicator[data-visible=true]{opacity:1}.mlv-editor .mlv-editor__block--dragging{opacity:var(--mlv-disabled-opacity)}.mlv-editor .ProseMirror--block-dragging>*{transition:transform var(--mlv-duration-normal) var(--mlv-ease-out)}.mlv-editor__status{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-2);min-block-size:var(--mlv-spacing-4);margin-block-start:var(--mlv-spacing-1)}.mlv-editor__upload-placeholder{position:relative;display:inline-block;inline-size:var(--mlv-height-l);block-size:var(--mlv-height-l);margin-inline:var(--mlv-spacing-1);border:var(--mlv-stroke-width) dashed var(--mlv-border-focus);border-radius:var(--mlv-radius-m);background:var(--mlv-background-accent-1-pale);vertical-align:middle}.mlv-editor__upload-placeholder:after{position:absolute;inset:var(--mlv-spacing-2);border:var(--mlv-stroke-width-medium) solid var(--mlv-border-normal);border-block-start-color:var(--mlv-border-focus);border-radius:var(--mlv-radius-full);animation:mlv-editor-upload-spin var(--mlv-duration-slower) var(--mlv-ease-linear) infinite;content:\"\"}.mlv-editor__ai-streaming{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-accent-1-pale)}.mlv-editor__ai-streaming-chunk{animation:mlv-editor-ai-chunk-in var(--mlv-duration-normal) var(--mlv-ease-out)}.mlv-editor__ai-caret{position:relative;display:inline-block;inline-size:0;block-size:1em;pointer-events:none}.mlv-editor__ai-caret:after{position:absolute;inset-block:0;inset-inline-start:.0625rem;inline-size:.35em;border-radius:var(--mlv-radius-xs);background:linear-gradient(to bottom,var(--mlv-background-accent-1),var(--mlv-background-accent-2));content:\"\";animation:mlv-editor-ai-caret-pulse var(--mlv-duration-sluggish) var(--mlv-ease-in-out) infinite}.mlv-editor__ai-suggestion-insert{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-success-pale)}.mlv-editor__ai-suggestion-delete{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-danger-1-pale);color:var(--mlv-text-negative);text-decoration-line:line-through}.mlv-editor__ai-suggestion-current{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor--content-width-default{--mlv-editor-measure: 45rem}.mlv-editor--content-width-wide{--mlv-editor-measure: 60rem}.mlv-editor--content-width-full{--mlv-editor-measure: 100%}.mlv-editor--focused .mlv-form-control-wrapper__control-container{border-color:var(--mlv-border-focus);box-shadow:var(--mlv-focus-ring)}.mlv-editor--readonly .ProseMirror{cursor:text}.mlv-editor--readonly .ProseMirror ::selection{background:var(--mlv-background-accent-1-pale)}.mlv-editor--disabled{opacity:var(--mlv-disabled-opacity)}.mlv-editor--disabled .mlv-editor__surface,.mlv-editor--disabled .mlv-editor__status{pointer-events:none}.mlv-editor--disabled .mlv-editor__content,.mlv-editor--disabled .ProseMirror{cursor:default;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror{min-block-size:inherit;max-inline-size:var(--mlv-editor-measure);margin-inline:auto;color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size);font-weight:var(--mlv-typography-body-m-weight);line-height:var(--mlv-typography-body-m-line-height);overflow-wrap:anywhere;white-space:pre-wrap}.mlv-editor .ProseMirror:focus-visible{outline:none}.mlv-editor .ProseMirror>*:first-child{margin-block-start:0}.mlv-editor .ProseMirror>*:last-child{margin-block-end:0}.mlv-editor .ProseMirror p{margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror h1,.mlv-editor .ProseMirror h2,.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{margin-block:var(--mlv-spacing-6) var(--mlv-spacing-3);color:var(--mlv-text-heading);font-family:var(--mlv-typography-family-heading)}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h1-size);font-weight:var(--mlv-typography-heading-h1-weight);line-height:var(--mlv-typography-heading-h1-line-height)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h2-size);font-weight:var(--mlv-typography-heading-h2-weight);line-height:var(--mlv-typography-heading-h2-line-height)}.mlv-editor .ProseMirror h3{font-size:var(--mlv-typography-heading-h3-size);font-weight:var(--mlv-typography-heading-h3-weight);line-height:var(--mlv-typography-heading-h3-line-height)}.mlv-editor .ProseMirror h4{font-size:var(--mlv-typography-heading-h4-size);font-weight:var(--mlv-typography-heading-h4-weight);line-height:var(--mlv-typography-heading-h4-line-height)}.mlv-editor .ProseMirror h5{font-size:var(--mlv-typography-heading-h5-size);font-weight:var(--mlv-typography-heading-h5-weight);line-height:var(--mlv-typography-heading-h5-line-height)}.mlv-editor .ProseMirror h6{font-size:var(--mlv-typography-heading-h6-size);font-weight:var(--mlv-typography-heading-h6-weight);line-height:var(--mlv-typography-heading-h6-line-height)}.mlv-editor .ProseMirror h1{letter-spacing:var(--mlv-typography-heading-h1-letter-spacing)}.mlv-editor .ProseMirror h2{letter-spacing:var(--mlv-typography-heading-h2-letter-spacing)}.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{letter-spacing:var(--mlv-letter-spacing-normal)}.mlv-editor .ProseMirror strong{font-weight:var(--mlv-font-weight-bold)}.mlv-editor .ProseMirror em{font-style:italic}.mlv-editor .ProseMirror s{text-decoration:line-through}.mlv-editor .ProseMirror u{text-underline-offset:var(--mlv-spacing-0-5)}.mlv-editor .ProseMirror a{color:var(--mlv-text-action);text-decoration:underline;text-underline-offset:var(--mlv-spacing-0-5);transition:color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .ProseMirror a:visited{color:var(--mlv-text-action)}.mlv-editor .ProseMirror a:hover{color:var(--mlv-text-action-hover)}.mlv-editor .ProseMirror a:focus-visible{border-radius:var(--mlv-radius-xs);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul,.mlv-editor .ProseMirror ol{padding-inline-start:var(--mlv-spacing-6);margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror ul ul,.mlv-editor .ProseMirror ul ol,.mlv-editor .ProseMirror ol ul,.mlv-editor .ProseMirror ol ol{margin-block:var(--mlv-spacing-1) 0}.mlv-editor .ProseMirror ul{list-style:disc}.mlv-editor .ProseMirror ul ul{list-style:circle}.mlv-editor .ProseMirror ol{list-style:decimal}.mlv-editor .ProseMirror li{margin-block:var(--mlv-spacing-1)}.mlv-editor .ProseMirror li>p{margin:0}.mlv-editor .ProseMirror ul[data-type=taskList]{padding:0;list-style:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]{display:flex;align-items:flex-start;gap:var(--mlv-spacing-2)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label{display:inline-flex;align-items:center;min-block-size:var(--mlv-height-xs);cursor:pointer;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input{accent-color:var(--mlv-background-accent-1)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>div{flex:1 1 auto;min-width:0}.mlv-editor .ProseMirror blockquote{padding-inline-start:var(--mlv-spacing-4);margin:var(--mlv-spacing-4) 0;border-inline-start:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);color:var(--mlv-text-secondary)}.mlv-editor .ProseMirror code{padding:var(--mlv-spacing-0-5) var(--mlv-spacing-1);border-radius:var(--mlv-radius-s);background:var(--mlv-background-neutral-1);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size)}.mlv-editor .ProseMirror pre{padding:var(--mlv-padding-m);margin-block:var(--mlv-spacing-4);overflow:auto;border:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-radius:var(--mlv-radius-m);background:var(--mlv-background-sunken);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size);line-height:var(--mlv-typography-body-m-line-height);tab-size:2}.mlv-editor .ProseMirror pre code{padding:0;background:transparent;color:inherit;font-size:inherit}.mlv-editor .ProseMirror hr{height:var(--mlv-stroke-width);margin-block:var(--mlv-spacing-6);border:0;background:var(--mlv-border-normal)}.mlv-editor .ProseMirror img{display:block;max-inline-size:100%;block-size:auto;margin-block:var(--mlv-spacing-4);border-radius:var(--mlv-radius-m)}.mlv-editor .ProseMirror img.ProseMirror-selectednode{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror .ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-state=true]{border-radius:var(--mlv-radius-s);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-spacing-1)}.mlv-editor .ProseMirror [data-resize-container]{margin-block:var(--mlv-spacing-4)}.mlv-editor .ProseMirror [data-resize-container] img{margin-block:0}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]{outline:none}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode>[data-resize-wrapper],.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]>[data-resize-wrapper]{border-radius:var(--mlv-radius-m);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror [data-resize-handle],.mlv-editor .ProseMirror .resize-handle{inline-size:var(--mlv-spacing-3);block-size:var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-background-base);border-radius:var(--mlv-radius-full);background:var(--mlv-background-accent-1);box-shadow:var(--mlv-shadow-raised)}.mlv-editor .ProseMirror [data-resize-handle]:focus-visible,.mlv-editor .ProseMirror .resize-handle:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror .is-editor-empty:first-child:before{float:left;height:0;color:var(--mlv-text-placeholder);content:attr(data-placeholder);pointer-events:none}.mlv-editor .tableWrapper{margin-block:var(--mlv-spacing-3);overflow-x:auto}.mlv-editor table{width:100%;border-collapse:collapse;table-layout:fixed}.mlv-editor th,.mlv-editor td{position:relative;min-width:3rem;padding:var(--mlv-spacing-2) var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-border-normal);vertical-align:top}.mlv-editor th{background:var(--mlv-background-neutral-1);font-weight:var(--mlv-font-weight-semibold)}.mlv-editor .selectedCell:after{position:absolute;inset:0;z-index:1;background:var(--mlv-background-accent-1-pale);content:\"\";opacity:.55;pointer-events:none}.mlv-editor .ProseMirror-focused .selectedCell:after{box-shadow:inset 0 0 0 var(--mlv-stroke-width-medium) var(--mlv-border-focus)}.mlv-editor .column-resize-handle{position:absolute;inset-block:0;inset-inline-end:calc(var(--mlv-stroke-width) * -1);z-index:2;width:calc(var(--mlv-stroke-width) * 2);background:var(--mlv-border-focus);pointer-events:none}.mlv-editor .resize-cursor{cursor:col-resize}@keyframes mlv-editor-upload-spin{to{transform:rotate(1turn)}}@keyframes mlv-editor-ai-chunk-in{0%{opacity:0;filter:blur(.125rem)}to{opacity:1;filter:blur(0)}}@keyframes mlv-editor-ai-caret-pulse{0%,to{opacity:1}50%{opacity:.35}}@media(max-width:40rem){.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-3)}.mlv-editor__toolbar{flex-wrap:nowrap;overflow-x:auto}.mlv-editor__content{padding-block:var(--mlv-spacing-1-5)}.mlv-editor__status{justify-content:flex-start}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h2-size)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h3-size)}}@media print{.mlv-editor__toolbar,.mlv-editor__status,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .mlv-editor-image-upload-status,.mlv-editor__upload-placeholder,.mlv-editor__ai-suggestion-delete,.mlv-editor__ai-caret{display:none}.mlv-editor__ai-suggestion-current{outline:none}.mlv-editor__ai-streaming-chunk{animation:none}.mlv-editor__viewport{overflow:visible}.mlv-editor__view{transform:none}.mlv-editor__content,.mlv-editor .ProseMirror{min-block-size:0;padding:0}.mlv-editor table{break-inside:avoid}}@media(hover:none){.mlv-editor__block-handle{display:none}}@media(prefers-reduced-motion:reduce){.mlv-editor .mlv-form-control-wrapper__control-container,.mlv-editor__view,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .ProseMirror--block-dragging>*,.mlv-editor .ProseMirror a{transition-duration:var(--mlv-duration-instant)}.mlv-editor__upload-placeholder:after,.mlv-editor__ai-streaming-chunk,.mlv-editor__ai-caret:after{animation:none}}.mlv-editor__drag-ghost{max-block-size:12rem;padding:var(--mlv-spacing-2);overflow:hidden;border-radius:var(--mlv-radius-m);background-color:var(--mlv-background-raised);box-shadow:var(--mlv-shadow-floating);opacity:.9;-webkit-mask-image:linear-gradient(to bottom,black 70%,transparent 100%);mask-image:linear-gradient(to bottom,black 70%,transparent 100%);scale:.85;transform-origin:top left}}\n"] }]
10220
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], contentWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentWidth", required: false }] }], extensions: [{ type: i0.Input, args: [{ isSignal: true, alias: "extensions", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], characterLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "characterLimit", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], imageUploader: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUploader", required: false }] }], imageUploadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUploadOptions", required: false }] }], aiProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiProvider", required: false }] }], editorReady: [{ type: i0.Output, args: ["editorReady"] }], focus: [{ type: i0.Output, args: ["focus"] }], blur: [{ type: i0.Output, args: ["blur"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], transaction: [{ type: i0.Output, args: ["transaction"] }], editorError: [{ type: i0.Output, args: ["editorError"] }], imageUploadSuccess: [{ type: i0.Output, args: ["imageUploadSuccess"] }], imageUploadFailure: [{ type: i0.Output, args: ["imageUploadFailure"] }], imageUploadCancelled: [{ type: i0.Output, args: ["imageUploadCancelled"] }], _content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], _view: [{ type: i0.ViewChild, args: ['view', { isSignal: true }] }], _toolbarDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MlvEditorToolbarDef), { isSignal: true }] }], _toolbarStartDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MlvEditorToolbarStartDef), { isSignal: true }] }], _toolbarEndDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MlvEditorToolbarEndDef), { isSignal: true }] }] } });
11699
+ '[style.--mlv-editor-height]': '_heightStyle()',
11700
+ '[style.--mlv-editor-min-height]': '_minHeightStyle()',
11701
+ '[style.--mlv-editor-max-height]': '_maxHeightStyle()',
11702
+ }, template: "<mlv-form-control-wrapper (clear)=\"clearValue()\">\n @if (label() || hint()) {\n <mlv-label\n [for]=\"_ownLabelFor()\"\n [id]=\"_labelId()\"\n (click)=\"_onLabelClick()\"\n >\n {{ label() }}\n @if (hint()) {\n <mlv-hint>{{ hint() }}</mlv-hint>\n }\n </mlv-label>\n }\n\n <ng-template mlvFormControlWrapperControl>\n <div #surface class=\"mlv-editor__surface\">\n <!--\n Declared once and stamped by one outlet: before or after the viewport\n for the docked bar (#416), so the DOM order is always the visual\n order, with no CSS `order`; into the selection bubble's overlay for\n the floating appearance (#483). Re-stamping re-creates the view, which\n closes an open toolbar popup; projected slot content is re-attached.\n Neither stamps anything while `readonly` (#498): no band, no reserved\n space; `_toolbarRendered()` is the one switch.\n -->\n <ng-template #toolbarTemplate>\n <div #toolbarBand class=\"mlv-editor__toolbar-band\">\n <div\n mlvEditorToolbarRoot\n #toolbarRoot=\"mlvEditorToolbarRoot\"\n class=\"mlv-editor__toolbar\"\n [ariaLabel]=\"_toolbarAriaLabel()\"\n [disabled]=\"computedDisabled()\"\n [measureTarget]=\"surface\"\n >\n @if (_hasToolbarDef()) {\n <ng-content select=\"[mlvEditorToolbar]\" />\n @for (definition of _toolbarDefs(); track definition) {\n @if (definition.templateRef; as toolbarTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"toolbarTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n }\n }\n } @else {\n <ng-content select=\"[mlvEditorToolbarStart]\" />\n @for (definition of _toolbarStartDefs(); track definition) {\n @if (definition.templateRef; as startTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"startTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n }\n }\n <div mlvFade class=\"mlv-editor__toolbar-scroll\">\n <mlv-toolbar>\n <mlv-editor-undo-redo />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-zoom />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n />\n <mlv-editor-heading />\n <mlv-editor-list />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-inline-marks [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-text-color />\n <mlv-editor-highlight />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-alignment [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-link />\n <mlv-editor-table />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-block-insert [hidden]=\"toolbarRoot.narrow()\" />\n <mlv-divider\n class=\"mlv-editor-toolbar__separator\"\n orientation=\"vertical\"\n muted\n [hidden]=\"toolbarRoot.narrow()\"\n />\n <mlv-editor-image-upload />\n </mlv-toolbar>\n </div>\n <mlv-editor-toolbar-overflow />\n @for (definition of _toolbarEndDefs(); track definition) {\n @if (definition.templateRef; as endTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"endTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: _toolbarContext }\"\n />\n }\n }\n <ng-content select=\"[mlvEditorToolbarEnd]\" />\n }\n </div>\n </div>\n </ng-template>\n\n @if (_barRendered() && toolbarPosition() === 'top') {\n <ng-container [ngTemplateOutlet]=\"toolbarTemplate\" />\n }\n\n <div #viewport class=\"mlv-editor__viewport\">\n <div #view class=\"mlv-editor__view\">\n <div #content class=\"mlv-editor__content\"></div>\n <mlv-editor-table-controls [layer]=\"view\" />\n </div>\n </div>\n @if (_barRendered() && toolbarPosition() === 'bottom') {\n <ng-container [ngTemplateOutlet]=\"toolbarTemplate\" />\n }\n @if (toolbarAppearance() === 'floating') {\n <ng-template mlvEditorBubble [viewport]=\"viewport\" [capped]=\"_capped()\">\n @if (_toolbarRendered()) {\n <ng-container [ngTemplateOutlet]=\"toolbarTemplate\" />\n }\n </ng-template>\n }\n <mlv-editor-image-upload-status />\n </div>\n </ng-template>\n\n @if (message()) {\n <mlv-message [state]=\"resolvedState()\" [id]=\"id() + '-message'\">\n {{ message() }}\n </mlv-message>\n }\n</mlv-form-control-wrapper>\n\n<div\n class=\"mlv-editor__status\"\n [attr.aria-disabled]=\"computedDisabled() || null\"\n>\n <ng-content select=\"[mlvEditorStatus]\" />\n <mlv-spacer />\n <mlv-editor-status [characterLimit]=\"characterLimit()\" />\n</div>\n", styles: ["@layer mlv.components{.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-6);--mlv-editor-measure: 45rem;box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor *{box-sizing:border-box}.mlv-editor{display:block;color:var(--mlv-text-primary)}.mlv-editor .mlv-form-control-wrapper__control-container{height:auto;min-height:var(--form-ctrl-height);padding:0;overflow:clip;background:var(--mlv-background-base);transition:border-color var(--mlv-duration-fast) var(--mlv-ease-default),box-shadow var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .mlv-form-control-wrapper__control-row{align-items:stretch}.mlv-form-control-wrapper--state-error .mlv-editor__surface{box-shadow:inset 0 0 0 var(--mlv-stroke-width) var(--mlv-border-error)}.mlv-editor__surface{display:flex;flex:1 1 auto;flex-direction:column;min-width:0;width:100%;background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar{display:flex;flex-wrap:nowrap;align-items:center;gap:var(--mlv-spacing-1);min-width:0;padding:var(--mlv-padding-xs);overflow:hidden;border-block-end:var(--mlv-stroke-width) solid var(--mlv-border-normal);background:var(--mlv-elevation-bg-3)}.mlv-editor__toolbar-band{display:flex;flex:0 0 auto;flex-direction:column;min-inline-size:0}.mlv-editor--toolbar-bottom .mlv-editor__toolbar{border-block-start:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-block-end:0}.mlv-editor__toolbar [hidden]{display:none!important}.mlv-editor__toolbar-scroll{flex:1 1 auto;min-width:0}.mlv-editor__toolbar-scroll>.mlv-toolbar{width:max-content;min-width:100%;flex-wrap:nowrap}.mlv-editor .mlv-editor-toolbar__separator{flex:0 0 auto;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor .mlv-editor-toolbar__overflow,.mlv-editor .mlv-editor-undo-redo,.mlv-editor .mlv-editor-inline-marks,.mlv-editor .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor mlv-editor-zoom,.mlv-editor mlv-editor-heading,.mlv-editor mlv-editor-list,.mlv-editor mlv-editor-text-color,.mlv-editor mlv-editor-highlight,.mlv-editor mlv-editor-alignment,.mlv-editor mlv-editor-link,.mlv-editor mlv-editor-table,.mlv-editor mlv-editor-image-upload{flex:0 0 auto}.mlv-editor__viewport{flex:1 1 auto;min-block-size:var(--mlv-editor-min-height, 8rem)}.mlv-editor--capped .mlv-editor__surface{block-size:var(--mlv-editor-height, auto);max-block-size:var(--mlv-editor-max-height, none)}.mlv-editor--capped .mlv-editor__viewport{min-block-size:0;overflow:auto;overscroll-behavior:contain}.mlv-editor__view{position:relative;isolation:isolate;min-inline-size:100%;zoom:var(--mlv-editor-zoom, 1)}.mlv-editor--capped .mlv-editor__view{min-inline-size:0;inline-size:calc(100% * var(--mlv-editor-zoom, 1))}.mlv-editor__content{min-block-size:var(--mlv-editor-min-height, 8rem);padding-block:var(--mlv-spacing-2);padding-inline:var(--mlv-editor-gutter);border:0;outline:none}.mlv-editor--capped .mlv-editor__content{min-block-size:calc(var(--mlv-editor-min-height, 8rem) / var(--mlv-editor-zoom, 1))}.mlv-editor__block-handle{position:absolute;inset-inline-start:max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2);display:inline-flex;align-items:center;justify-content:center;inline-size:var(--mlv-editor-gutter);block-size:var(--mlv-height-xs);border-radius:var(--mlv-radius-s);color:var(--mlv-text-tertiary);cursor:grab;opacity:0;pointer-events:none;transition:opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__block-handle svg{inline-size:var(--mlv-spacing-4);block-size:var(--mlv-spacing-4);fill:currentcolor}.mlv-editor__block-handle:hover{background-color:var(--mlv-background-neutral-1-hover);color:var(--mlv-text-secondary)}.mlv-editor__block-handle[data-visible=true]{opacity:1;pointer-events:auto}.mlv-editor__drop-indicator{position:absolute;inset-inline:calc(max(0rem,(100% - 2 * var(--mlv-editor-gutter) - var(--mlv-editor-measure)) / 2) + var(--mlv-editor-gutter));block-size:var(--mlv-stroke-width-medium);border-radius:var(--mlv-radius-full);background-color:var(--mlv-border-focus);opacity:0;pointer-events:none;transform:translateY(-50%);transition:top var(--mlv-duration-normal) var(--mlv-ease-out),opacity var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor__drop-indicator[data-visible=true]{opacity:1}.mlv-editor .mlv-editor__block--dragging{opacity:var(--mlv-disabled-opacity)}.mlv-editor__status{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--mlv-spacing-2);min-block-size:var(--mlv-spacing-4);margin-block-start:var(--mlv-spacing-1)}.mlv-editor__upload-placeholder{position:relative;display:inline-block;inline-size:var(--mlv-height-l);block-size:var(--mlv-height-l);margin-inline:var(--mlv-spacing-1);border:var(--mlv-stroke-width) dashed var(--mlv-border-focus);border-radius:var(--mlv-radius-m);background:var(--mlv-background-accent-1-pale);vertical-align:middle}.mlv-editor__upload-placeholder:after{position:absolute;inset:var(--mlv-spacing-2);border:var(--mlv-stroke-width-medium) solid var(--mlv-border-normal);border-block-start-color:var(--mlv-border-focus);border-radius:var(--mlv-radius-full);animation:mlv-editor-upload-spin var(--mlv-duration-slower) var(--mlv-ease-linear) infinite;content:\"\"}.mlv-editor__ai-streaming{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-accent-1-pale)}.mlv-editor__ai-streaming-chunk{animation:mlv-editor-ai-chunk-in var(--mlv-duration-normal) var(--mlv-ease-out)}.mlv-editor__ai-caret{position:relative;display:inline-block;inline-size:0;block-size:1em;pointer-events:none}.mlv-editor__ai-caret:after{position:absolute;inset-block:0;inset-inline-start:.0625rem;inline-size:.35em;border-radius:var(--mlv-radius-xs);background:linear-gradient(to bottom,var(--mlv-background-accent-1),var(--mlv-background-accent-2));content:\"\";animation:mlv-editor-ai-caret-pulse var(--mlv-duration-sluggish) var(--mlv-ease-in-out) infinite}.mlv-editor__ai-suggestion-insert{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-success-pale)}.mlv-editor__ai-suggestion-delete{border-radius:var(--mlv-radius-xs);background-color:var(--mlv-background-danger-1-pale);color:var(--mlv-text-negative);text-decoration-line:line-through}.mlv-editor__ai-suggestion-current{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor--content-width-default{--mlv-editor-measure: 45rem}.mlv-editor--content-width-wide{--mlv-editor-measure: 60rem}.mlv-editor--content-width-full{--mlv-editor-measure: 100%}.mlv-editor--focused .mlv-form-control-wrapper__control-container{border-color:var(--mlv-border-focus);box-shadow:var(--mlv-focus-ring)}.mlv-editor--readonly .ProseMirror{cursor:text}.mlv-editor--readonly .ProseMirror ::selection{background:var(--mlv-background-accent-1-pale)}.mlv-editor--disabled{opacity:var(--mlv-disabled-opacity)}.mlv-editor--disabled .mlv-editor__surface,.mlv-editor--disabled .mlv-editor__status{pointer-events:none}.mlv-editor--disabled .mlv-editor__content,.mlv-editor--disabled .ProseMirror{cursor:default;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror{min-block-size:inherit;max-inline-size:var(--mlv-editor-measure);margin-inline:auto;color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-text);font-size:var(--mlv-typography-body-m-size);font-weight:var(--mlv-typography-body-m-weight);line-height:var(--mlv-typography-body-m-line-height);overflow-wrap:anywhere;white-space:pre-wrap}.mlv-editor .ProseMirror:focus-visible{outline:none}.mlv-editor .ProseMirror>*:first-child{margin-block-start:0}.mlv-editor .ProseMirror>*:last-child{margin-block-end:0}.mlv-editor .ProseMirror p{margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror h1,.mlv-editor .ProseMirror h2,.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{margin-block:var(--mlv-spacing-6) var(--mlv-spacing-3);color:var(--mlv-text-heading);font-family:var(--mlv-typography-family-heading)}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h1-size);font-weight:var(--mlv-typography-heading-h1-weight);line-height:var(--mlv-typography-heading-h1-line-height)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h2-size);font-weight:var(--mlv-typography-heading-h2-weight);line-height:var(--mlv-typography-heading-h2-line-height)}.mlv-editor .ProseMirror h3{font-size:var(--mlv-typography-heading-h3-size);font-weight:var(--mlv-typography-heading-h3-weight);line-height:var(--mlv-typography-heading-h3-line-height)}.mlv-editor .ProseMirror h4{font-size:var(--mlv-typography-heading-h4-size);font-weight:var(--mlv-typography-heading-h4-weight);line-height:var(--mlv-typography-heading-h4-line-height)}.mlv-editor .ProseMirror h5{font-size:var(--mlv-typography-heading-h5-size);font-weight:var(--mlv-typography-heading-h5-weight);line-height:var(--mlv-typography-heading-h5-line-height)}.mlv-editor .ProseMirror h6{font-size:var(--mlv-typography-heading-h6-size);font-weight:var(--mlv-typography-heading-h6-weight);line-height:var(--mlv-typography-heading-h6-line-height)}.mlv-editor .ProseMirror h1{letter-spacing:var(--mlv-typography-heading-h1-letter-spacing)}.mlv-editor .ProseMirror h2{letter-spacing:var(--mlv-typography-heading-h2-letter-spacing)}.mlv-editor .ProseMirror h3,.mlv-editor .ProseMirror h4,.mlv-editor .ProseMirror h5,.mlv-editor .ProseMirror h6{letter-spacing:var(--mlv-letter-spacing-normal)}.mlv-editor .ProseMirror strong{font-weight:var(--mlv-font-weight-bold)}.mlv-editor .ProseMirror em{font-style:italic}.mlv-editor .ProseMirror s{text-decoration:line-through}.mlv-editor .ProseMirror u{text-underline-offset:var(--mlv-spacing-0-5)}.mlv-editor .ProseMirror a{color:var(--mlv-text-action);text-decoration:underline;text-underline-offset:var(--mlv-spacing-0-5);transition:color var(--mlv-duration-fast) var(--mlv-ease-default)}.mlv-editor .ProseMirror a:visited{color:var(--mlv-text-action)}.mlv-editor .ProseMirror a:hover{color:var(--mlv-text-action-hover)}.mlv-editor .ProseMirror a:focus-visible{border-radius:var(--mlv-radius-xs);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul,.mlv-editor .ProseMirror ol{padding-inline-start:var(--mlv-spacing-6);margin-block:0 var(--mlv-spacing-3)}.mlv-editor .ProseMirror ul ul,.mlv-editor .ProseMirror ul ol,.mlv-editor .ProseMirror ol ul,.mlv-editor .ProseMirror ol ol{margin-block:var(--mlv-spacing-1) 0}.mlv-editor .ProseMirror ul{list-style:disc}.mlv-editor .ProseMirror ul ul{list-style:circle}.mlv-editor .ProseMirror ol{list-style:decimal}.mlv-editor .ProseMirror li{margin-block:var(--mlv-spacing-1)}.mlv-editor .ProseMirror li>p{margin:0}.mlv-editor .ProseMirror ul[data-type=taskList]{padding:0;list-style:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]{display:flex;align-items:flex-start;gap:var(--mlv-spacing-2)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label{display:inline-flex;align-items:center;min-block-size:var(--mlv-height-xs);cursor:pointer;-webkit-user-select:none;user-select:none}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input{accent-color:var(--mlv-background-accent-1)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>label input:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror ul[data-type=taskList] li[data-type=taskItem]>div{flex:1 1 auto;min-width:0}.mlv-editor .ProseMirror blockquote{padding-inline-start:var(--mlv-spacing-4);margin:var(--mlv-spacing-4) 0;border-inline-start:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);color:var(--mlv-text-secondary)}.mlv-editor .ProseMirror code{padding:var(--mlv-spacing-0-5) var(--mlv-spacing-1);border-radius:var(--mlv-radius-s);background:var(--mlv-background-neutral-1);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size)}.mlv-editor .ProseMirror pre{padding:var(--mlv-padding-m);margin-block:var(--mlv-spacing-4);overflow:auto;border:var(--mlv-stroke-width) solid var(--mlv-border-normal);border-radius:var(--mlv-radius-m);background:var(--mlv-background-sunken);color:var(--mlv-text-primary);font-family:var(--mlv-typography-family-code);font-size:var(--mlv-typography-code-size);line-height:var(--mlv-typography-body-m-line-height);tab-size:2}.mlv-editor .ProseMirror pre code{padding:0;background:transparent;color:inherit;font-size:inherit}.mlv-editor .ProseMirror hr{height:var(--mlv-stroke-width);margin-block:var(--mlv-spacing-6);border:0;background:var(--mlv-border-normal)}.mlv-editor .ProseMirror img{display:block;max-inline-size:100%;block-size:auto;margin-block:var(--mlv-spacing-4);border-radius:var(--mlv-radius-m)}.mlv-editor .ProseMirror img.ProseMirror-selectednode{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror .ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-state=true]{border-radius:var(--mlv-radius-s);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-spacing-1)}.mlv-editor .ProseMirror [data-resize-container]{margin-block:var(--mlv-spacing-4)}.mlv-editor .ProseMirror [data-resize-container] img{margin-block:0}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode,.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]{outline:none}.mlv-editor .ProseMirror [data-resize-container].ProseMirror-selectednode>[data-resize-wrapper],.mlv-editor .ProseMirror [data-resize-container][data-resize-state=true]>[data-resize-wrapper]{border-radius:var(--mlv-radius-m);outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:0}.mlv-editor .ProseMirror [data-resize-handle],.mlv-editor .ProseMirror .resize-handle{inline-size:var(--mlv-spacing-3);block-size:var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-background-base);border-radius:var(--mlv-radius-full);background:var(--mlv-background-accent-1);box-shadow:var(--mlv-shadow-raised)}.mlv-editor .ProseMirror [data-resize-handle]:focus-visible,.mlv-editor .ProseMirror .resize-handle:focus-visible{outline:var(--mlv-stroke-width-medium) solid var(--mlv-border-focus);outline-offset:var(--mlv-focus-ring-offset)}.mlv-editor .ProseMirror .is-editor-empty:first-child:before{float:left;height:0;color:var(--mlv-text-placeholder);content:attr(data-placeholder);pointer-events:none}.mlv-editor .tableWrapper{margin-block:var(--mlv-spacing-3);overflow-x:auto}.mlv-editor table{width:100%;border-collapse:collapse;table-layout:fixed}.mlv-editor th,.mlv-editor td{position:relative;min-width:3rem;padding:var(--mlv-spacing-2) var(--mlv-spacing-3);border:var(--mlv-stroke-width) solid var(--mlv-border-normal);vertical-align:top}.mlv-editor th{background:var(--mlv-background-neutral-1);font-weight:var(--mlv-font-weight-semibold)}.mlv-editor .selectedCell:after{position:absolute;inset:0;z-index:1;background:var(--mlv-background-accent-1-pale);content:\"\";opacity:.55;pointer-events:none}.mlv-editor .ProseMirror-focused .selectedCell:after{box-shadow:inset 0 0 0 var(--mlv-stroke-width-medium) var(--mlv-border-focus)}.mlv-editor .column-resize-handle{position:absolute;inset-block:0;inset-inline-end:calc(var(--mlv-stroke-width) * -1);z-index:2;width:calc(var(--mlv-stroke-width) * 2);background:var(--mlv-border-focus);pointer-events:none}.mlv-editor .resize-cursor{cursor:col-resize}@keyframes mlv-editor-upload-spin{to{transform:rotate(1turn)}}@keyframes mlv-editor-ai-chunk-in{0%{opacity:0;filter:blur(.125rem)}to{opacity:1;filter:blur(0)}}@keyframes mlv-editor-ai-caret-pulse{0%,to{opacity:1}50%{opacity:.35}}.mlv-editor--toolbar-sticky.mlv-editor--toolbar-top .mlv-editor__toolbar-band{position:sticky;inset-block-start:var(--mlv-editor-toolbar-sticky-offset, 0);z-index:var(--mlv-z-raised)}.mlv-editor--toolbar-sticky.mlv-editor--toolbar-bottom .mlv-editor__toolbar-band{position:sticky;inset-block-end:var(--mlv-editor-toolbar-sticky-offset, 0);z-index:var(--mlv-z-raised)}.mlv-editor-bubble{box-sizing:border-box;padding:0;margin:0;font-family:var(--mlv-typography-family-text);font-size:var(--mlv-font-size-m)}.mlv-editor-bubble *{box-sizing:border-box}.mlv-editor-bubble .mlv-editor-toolbar__separator{flex:0 0 auto;min-height:var(--mlv-height-s);margin-inline:var(--mlv-spacing-1)}.mlv-editor-bubble .mlv-editor-toolbar__overflow,.mlv-editor-bubble .mlv-editor-undo-redo,.mlv-editor-bubble .mlv-editor-inline-marks,.mlv-editor-bubble .mlv-editor-block-insert{display:inline-flex;flex:0 0 auto;flex-direction:row;align-items:center;gap:var(--mlv-spacing-1)}.mlv-editor-bubble mlv-editor-zoom,.mlv-editor-bubble mlv-editor-heading,.mlv-editor-bubble mlv-editor-list,.mlv-editor-bubble mlv-editor-text-color,.mlv-editor-bubble mlv-editor-highlight,.mlv-editor-bubble mlv-editor-alignment,.mlv-editor-bubble mlv-editor-link,.mlv-editor-bubble mlv-editor-table,.mlv-editor-bubble mlv-editor-image-upload{flex:0 0 auto}.mlv-editor-bubble{max-inline-size:calc(100vw - 2 * var(--mlv-spacing-2));border-radius:var(--mlv-radius-panel);background:var(--mlv-elevation-bg-4);box-shadow:var(--mlv-shadow-floating);color:var(--mlv-text-primary);animation:mlv-editor-bubble-enter var(--mlv-duration-fast) var(--mlv-ease-out)}.mlv-editor-bubble--hidden{visibility:hidden;animation:none}.mlv-editor-bubble .mlv-editor__toolbar-band{flex:1 1 auto;min-inline-size:0}.mlv-editor-bubble .mlv-editor__toolbar{border:0;border-radius:inherit;background:transparent}@keyframes mlv-editor-bubble-enter{0%{opacity:0}}@media(max-width:40rem){.mlv-editor{--mlv-editor-gutter: var(--mlv-spacing-3)}.mlv-editor__toolbar{flex-wrap:nowrap;overflow-x:auto}.mlv-editor__content{padding-block:var(--mlv-spacing-1-5)}.mlv-editor__status{justify-content:flex-start}.mlv-editor .ProseMirror h1{font-size:var(--mlv-typography-heading-h2-size)}.mlv-editor .ProseMirror h2{font-size:var(--mlv-typography-heading-h3-size)}}@media print{.mlv-editor__toolbar-band,.mlv-editor__toolbar,.mlv-editor__status,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .mlv-editor-image-upload-status,.mlv-editor__upload-placeholder,.mlv-editor__ai-suggestion-delete,.mlv-editor__ai-caret{display:none}.mlv-editor__ai-suggestion-current{outline:none}.mlv-editor__ai-streaming-chunk{animation:none}.mlv-editor__viewport,.mlv-editor--capped .mlv-editor__viewport{overflow:visible;padding-block:0}.mlv-editor--capped .mlv-editor__surface{block-size:auto;max-block-size:none}.mlv-editor__view,.mlv-editor--capped .mlv-editor__view{zoom:1;inline-size:auto;min-inline-size:0}.mlv-editor__content,.mlv-editor--capped .mlv-editor__content,.mlv-editor .ProseMirror{min-block-size:0;padding:0}.mlv-editor table{break-inside:avoid}}@media print{.mlv-editor-bubble{display:none}}@media(hover:none){.mlv-editor__block-handle{display:none}}@media(prefers-reduced-motion:reduce){.mlv-editor .mlv-form-control-wrapper__control-container,.mlv-editor__block-handle,.mlv-editor__drop-indicator,.mlv-editor .ProseMirror a{transition-duration:var(--mlv-duration-instant)}.mlv-editor__upload-placeholder:after,.mlv-editor__ai-streaming-chunk,.mlv-editor__ai-caret:after{animation:none}.mlv-editor-bubble{animation:none}}.mlv-editor__drag-ghost{max-block-size:12rem;padding:var(--mlv-spacing-2);overflow:hidden;border-radius:var(--mlv-radius-m);background-color:var(--mlv-background-accent-1-pale);opacity:.9;-webkit-mask-image:linear-gradient(to bottom,black 70%,transparent 100%);mask-image:linear-gradient(to bottom,black 70%,transparent 100%)}}\n"] }]
11703
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], contentWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], toolbarPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarPosition", required: false }] }], toolbarAppearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarAppearance", required: false }] }], toolbarSticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarSticky", required: false }] }], extensions: [{ type: i0.Input, args: [{ isSignal: true, alias: "extensions", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], characterLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "characterLimit", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], imageUploader: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUploader", required: false }] }], imageUploadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUploadOptions", required: false }] }], aiProvider: [{ type: i0.Input, args: [{ isSignal: true, alias: "aiProvider", required: false }] }], editorReady: [{ type: i0.Output, args: ["editorReady"] }], focus: [{ type: i0.Output, args: ["focus"] }], blur: [{ type: i0.Output, args: ["blur"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], transaction: [{ type: i0.Output, args: ["transaction"] }], editorError: [{ type: i0.Output, args: ["editorError"] }], imageUploadSuccess: [{ type: i0.Output, args: ["imageUploadSuccess"] }], imageUploadFailure: [{ type: i0.Output, args: ["imageUploadFailure"] }], imageUploadCancelled: [{ type: i0.Output, args: ["imageUploadCancelled"] }], _content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], _view: [{ type: i0.ViewChild, args: ['view', { isSignal: true }] }], _toolbarBand: [{ type: i0.ViewChild, args: ['toolbarBand', { isSignal: true }] }], _toolbarDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MlvEditorToolbarDef), { isSignal: true }] }], _toolbarStartDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MlvEditorToolbarStartDef), { isSignal: true }] }], _toolbarEndDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MlvEditorToolbarEndDef), { isSignal: true }] }] } });
10221
11704
 
10222
11705
  /**
10223
11706
  * Generated bundle index. Do not edit.