@malva-ui/editor 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +5 -5
  2. package/fesm2022/malva-ui-editor-ai.mjs +17 -0
  3. package/fesm2022/malva-ui-editor-ai.mjs.map +1 -0
  4. package/fesm2022/malva-ui-editor.mjs +10227 -0
  5. package/fesm2022/malva-ui-editor.mjs.map +1 -0
  6. package/package.json +35 -26
  7. package/types/malva-ui-editor-ai.d.ts +1 -0
  8. package/types/malva-ui-editor.d.ts +2995 -0
  9. package/ai/index.d.ts +0 -12
  10. package/ai/malva-ui-editor-ai.d.ts +0 -5
  11. package/esm2022/ai/index.js +0 -12
  12. package/esm2022/ai/index.js.map +0 -1
  13. package/esm2022/ai/malva-ui-editor-ai.js +0 -5
  14. package/esm2022/ai/malva-ui-editor-ai.js.map +0 -1
  15. package/esm2022/index.js +0 -37
  16. package/esm2022/index.js.map +0 -1
  17. package/esm2022/lib/ai/editor-ai-actions.js +0 -69
  18. package/esm2022/lib/ai/editor-ai-actions.js.map +0 -1
  19. package/esm2022/lib/ai/editor-ai-context.js +0 -612
  20. package/esm2022/lib/ai/editor-ai-context.js.map +0 -1
  21. package/esm2022/lib/ai/editor-ai-menu.js +0 -288
  22. package/esm2022/lib/ai/editor-ai-menu.js.map +0 -1
  23. package/esm2022/lib/ai/editor-ai-review-bar.js +0 -223
  24. package/esm2022/lib/ai/editor-ai-review-bar.js.map +0 -1
  25. package/esm2022/lib/ai/editor-ai-stream.js +0 -677
  26. package/esm2022/lib/ai/editor-ai-stream.js.map +0 -1
  27. package/esm2022/lib/ai/editor-ai-suggestions.js +0 -751
  28. package/esm2022/lib/ai/editor-ai-suggestions.js.map +0 -1
  29. package/esm2022/lib/ai/editor-ai.tokens.js +0 -10
  30. package/esm2022/lib/ai/editor-ai.tokens.js.map +0 -1
  31. package/esm2022/lib/ai/editor-ai.types.js +0 -1
  32. package/esm2022/lib/ai/editor-ai.types.js.map +0 -1
  33. package/esm2022/lib/editor/editor.js +0 -985
  34. package/esm2022/lib/editor/editor.js.map +0 -1
  35. package/esm2022/lib/editor-extension-preflight.js +0 -83
  36. package/esm2022/lib/editor-extension-preflight.js.map +0 -1
  37. package/esm2022/lib/editor-serialization.js +0 -152
  38. package/esm2022/lib/editor-serialization.js.map +0 -1
  39. package/esm2022/lib/editor-toolbar-context.js +0 -259
  40. package/esm2022/lib/editor-toolbar-context.js.map +0 -1
  41. package/esm2022/lib/editor.tokens.js +0 -10
  42. package/esm2022/lib/editor.tokens.js.map +0 -1
  43. package/esm2022/lib/editor.types.js +0 -48
  44. package/esm2022/lib/editor.types.js.map +0 -1
  45. package/esm2022/lib/extensions/editor-block-handle.js +0 -760
  46. package/esm2022/lib/extensions/editor-block-handle.js.map +0 -1
  47. package/esm2022/lib/extensions/editor-extensions.js +0 -321
  48. package/esm2022/lib/extensions/editor-extensions.js.map +0 -1
  49. package/esm2022/lib/extensions/editor-markdown-compatibility.js +0 -162
  50. package/esm2022/lib/extensions/editor-markdown-compatibility.js.map +0 -1
  51. package/esm2022/lib/extensions/editor-upload-placeholder.js +0 -156
  52. package/esm2022/lib/extensions/editor-upload-placeholder.js.map +0 -1
  53. package/esm2022/lib/status/editor-status.js +0 -64
  54. package/esm2022/lib/status/editor-status.js.map +0 -1
  55. package/esm2022/lib/toolbar/editor-alignment.js +0 -203
  56. package/esm2022/lib/toolbar/editor-alignment.js.map +0 -1
  57. package/esm2022/lib/toolbar/editor-block-insert.js +0 -101
  58. package/esm2022/lib/toolbar/editor-block-insert.js.map +0 -1
  59. package/esm2022/lib/toolbar/editor-color-control.js +0 -71
  60. package/esm2022/lib/toolbar/editor-color-control.js.map +0 -1
  61. package/esm2022/lib/toolbar/editor-command-button.js +0 -115
  62. package/esm2022/lib/toolbar/editor-command-button.js.map +0 -1
  63. package/esm2022/lib/toolbar/editor-heading.js +0 -266
  64. package/esm2022/lib/toolbar/editor-heading.js.map +0 -1
  65. package/esm2022/lib/toolbar/editor-highlight.js +0 -84
  66. package/esm2022/lib/toolbar/editor-highlight.js.map +0 -1
  67. package/esm2022/lib/toolbar/editor-image-upload-dialog.js +0 -289
  68. package/esm2022/lib/toolbar/editor-image-upload-dialog.js.map +0 -1
  69. package/esm2022/lib/toolbar/editor-image-upload.js +0 -315
  70. package/esm2022/lib/toolbar/editor-image-upload.js.map +0 -1
  71. package/esm2022/lib/toolbar/editor-inline-marks.js +0 -124
  72. package/esm2022/lib/toolbar/editor-inline-marks.js.map +0 -1
  73. package/esm2022/lib/toolbar/editor-link.js +0 -265
  74. package/esm2022/lib/toolbar/editor-link.js.map +0 -1
  75. package/esm2022/lib/toolbar/editor-list.js +0 -249
  76. package/esm2022/lib/toolbar/editor-list.js.map +0 -1
  77. package/esm2022/lib/toolbar/editor-table.js +0 -292
  78. package/esm2022/lib/toolbar/editor-table.js.map +0 -1
  79. package/esm2022/lib/toolbar/editor-text-color.js +0 -82
  80. package/esm2022/lib/toolbar/editor-text-color.js.map +0 -1
  81. package/esm2022/lib/toolbar/editor-toolbar-widget.js +0 -41
  82. package/esm2022/lib/toolbar/editor-toolbar-widget.js.map +0 -1
  83. package/esm2022/lib/toolbar/editor-toolbar.defs.js +0 -60
  84. package/esm2022/lib/toolbar/editor-toolbar.defs.js.map +0 -1
  85. package/esm2022/lib/toolbar/editor-toolbar.js +0 -826
  86. package/esm2022/lib/toolbar/editor-toolbar.js.map +0 -1
  87. package/esm2022/lib/toolbar/editor-undo-redo.js +0 -72
  88. package/esm2022/lib/toolbar/editor-undo-redo.js.map +0 -1
  89. package/esm2022/lib/toolbar/editor-zoom.js +0 -330
  90. package/esm2022/lib/toolbar/editor-zoom.js.map +0 -1
  91. package/esm2022/lib/upload/editor-image-upload-coordinator.js +0 -480
  92. package/esm2022/lib/upload/editor-image-upload-coordinator.js.map +0 -1
  93. package/esm2022/malva-ui-editor.js +0 -5
  94. package/esm2022/malva-ui-editor.js.map +0 -1
  95. package/index.d.ts +0 -35
  96. package/lib/ai/editor-ai-actions.d.ts +0 -27
  97. package/lib/ai/editor-ai-context.d.ts +0 -307
  98. package/lib/ai/editor-ai-menu.d.ts +0 -142
  99. package/lib/ai/editor-ai-review-bar.d.ts +0 -116
  100. package/lib/ai/editor-ai-stream.d.ts +0 -207
  101. package/lib/ai/editor-ai-suggestions.d.ts +0 -219
  102. package/lib/ai/editor-ai.tokens.d.ts +0 -10
  103. package/lib/ai/editor-ai.types.d.ts +0 -176
  104. package/lib/editor/editor.d.ts +0 -243
  105. package/lib/editor-extension-preflight.d.ts +0 -23
  106. package/lib/editor-serialization.d.ts +0 -74
  107. package/lib/editor-toolbar-context.d.ts +0 -116
  108. package/lib/editor.tokens.d.ts +0 -6
  109. package/lib/editor.types.d.ts +0 -194
  110. package/lib/extensions/editor-block-handle.d.ts +0 -59
  111. package/lib/extensions/editor-extensions.d.ts +0 -165
  112. package/lib/extensions/editor-markdown-compatibility.d.ts +0 -11
  113. package/lib/extensions/editor-upload-placeholder.d.ts +0 -75
  114. package/lib/status/editor-status.d.ts +0 -24
  115. package/lib/toolbar/editor-alignment.d.ts +0 -61
  116. package/lib/toolbar/editor-block-insert.d.ts +0 -26
  117. package/lib/toolbar/editor-color-control.d.ts +0 -32
  118. package/lib/toolbar/editor-command-button.d.ts +0 -29
  119. package/lib/toolbar/editor-heading.d.ts +0 -68
  120. package/lib/toolbar/editor-highlight.d.ts +0 -21
  121. package/lib/toolbar/editor-image-upload-dialog.d.ts +0 -96
  122. package/lib/toolbar/editor-image-upload.d.ts +0 -71
  123. package/lib/toolbar/editor-inline-marks.d.ts +0 -30
  124. package/lib/toolbar/editor-link.d.ts +0 -78
  125. package/lib/toolbar/editor-list.d.ts +0 -67
  126. package/lib/toolbar/editor-table.d.ts +0 -133
  127. package/lib/toolbar/editor-text-color.d.ts +0 -21
  128. package/lib/toolbar/editor-toolbar-widget.d.ts +0 -19
  129. package/lib/toolbar/editor-toolbar.d.ts +0 -157
  130. package/lib/toolbar/editor-toolbar.defs.d.ts +0 -35
  131. package/lib/toolbar/editor-undo-redo.d.ts +0 -26
  132. package/lib/toolbar/editor-zoom.d.ts +0 -108
  133. package/lib/upload/editor-image-upload-coordinator.d.ts +0 -55
  134. package/malva-ui-editor.d.ts +0 -5
@@ -1,223 +0,0 @@
1
- import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, ViewEncapsulation, computed, effect, inject, signal, } from '@angular/core';
2
- import { LucideCheck, LucideChevronLeft, LucideChevronRight, LucideCircleStop, LucideX, } from '@lucide/angular';
3
- import { MlvButton, MlvButtonIcon } from '@malva-ui/core/button';
4
- import { MlvTooltip } from '@malva-ui/core/tooltip';
5
- import { MLV_EDITOR_I18N, MlvI18nResolverService } from '@malva-ui/i18n';
6
- import { MLV_EDITOR_OVERLAY_REGISTRY, MLV_EDITOR_TOOLBAR_CONTEXT, } from '../editor-toolbar-context';
7
- import { MLV_EDITOR_AI_CONTEXT } from './editor-ai-context';
8
- import * as i0 from "@angular/core";
9
- /** @internal Source of unique description-element ids across bar instances. */
10
- let reviewBarDescriptionIdSequence = 0;
11
- /**
12
- * Review surface for pending AI suggestions.
13
- *
14
- * Rendered inside the editor composite — hosts project it into the status
15
- * region (`<mlv-editor-ai-review-bar mlvEditorStatus />`), following the
16
- * `MlvEditorStatus` placement. Hidden while the nearest
17
- * {@link MLV_EDITOR_AI_CONTEXT} is `'idle'` (or absent); while `'running'` it
18
- * shows a stop affordance that cancels the in-flight request, and while
19
- * `'reviewing'` it shows the pending-suggestion count, previous/next
20
- * navigation, accept/reject for the current suggestion, and accept-all/
21
- * reject-all.
22
- *
23
- * Navigation keeps an internal cursor over the context's suggestion list;
24
- * moving it outlines the current suggestion in the document
25
- * (`MLV_EDITOR_AI_SUGGESTION_CURRENT_CLASS` decoration) and scrolls its range
26
- * into view through {@link MlvEditorAiContext.revealSuggestion}. Accepting or
27
- * rejecting the current suggestion advances to the next remaining one (the
28
- * list shrinks under a clamped cursor); when the set empties the context
29
- * returns to `'idle'` and the bar disappears.
30
- *
31
- * Announcement choice: the count region is deliberately **not** an
32
- * `aria-live` region. The AI context already announces the review lifecycle —
33
- * `aiReviewStarted` with the count, and every accept/reject outcome — through
34
- * the CDK `LiveAnnouncer`; a live count would double-announce each change.
35
- *
36
- * The host element registers with the editor's composite-overlay registry, so
37
- * interacting with the bar never emits a false editor blur wherever a host
38
- * renders it. All controls are plain tab stops inside the composite. Accept,
39
- * reject, and navigation disable while the editor is readonly or disabled
40
- * (mutations are refused by the context anyway; the disabled state makes the
41
- * refusal visible), and the whole bar disables with the editor.
42
- */
43
- export class MlvEditorAiReviewBar {
44
- /** @protected AI command state of the nearest editor, if provided. */
45
- _ai = inject(MLV_EDITOR_AI_CONTEXT, { optional: true });
46
- /** @protected Editor-scoped form state gating executability. */
47
- _context = inject(MLV_EDITOR_TOOLBAR_CONTEXT);
48
- /** @private Composite focus ownership for the bar's host element. */
49
- _overlays = inject(MLV_EDITOR_OVERLAY_REGISTRY);
50
- /** @private Host element registered with the composite-overlay registry. */
51
- _host = inject(ElementRef);
52
- /** @private Clears the current-suggestion outline on teardown. */
53
- _destroyRef = inject(DestroyRef);
54
- /** @private Optional localized editor copy. */
55
- _i18n = inject(MLV_EDITOR_I18N, { optional: true });
56
- /** @private Resolves the ICU pending-count template. */
57
- _resolver = inject(MlvI18nResolverService);
58
- /**
59
- * @private Navigation cursor over the suggestion list. Reset for every new
60
- * review; reads clamp through {@link _index}, so a cursor left beyond the
61
- * end after the list shrinks points at the last remaining suggestion.
62
- */
63
- _cursor = signal(0, /* @ts-ignore */
64
- ...(ngDevMode ? [{ debugName: "_cursor" }] : /* istanbul ignore next */ []));
65
- /** @protected Reactive localized copy for every control and the count. */
66
- _copy = computed(() => {
67
- const copy = this._i18n?.();
68
- return {
69
- aiReviewBar: copy?.aiReviewBar ?? 'AI suggestion review',
70
- aiReviewCount: copy?.aiReviewCount ??
71
- '{count, plural, one {# suggestion} other {# suggestions}}',
72
- aiPreviousSuggestion: copy?.aiPreviousSuggestion ?? 'Previous suggestion',
73
- aiNextSuggestion: copy?.aiNextSuggestion ?? 'Next suggestion',
74
- aiAcceptSuggestion: copy?.aiAcceptSuggestion ?? 'Accept suggestion',
75
- aiRejectSuggestion: copy?.aiRejectSuggestion ?? 'Reject suggestion',
76
- aiAcceptAll: copy?.aiAcceptAll ?? 'Accept all',
77
- aiRejectAll: copy?.aiRejectAll ?? 'Reject all',
78
- aiStopGeneration: copy?.aiStopGeneration ?? 'Stop generating',
79
- };
80
- }, /* @ts-ignore */
81
- ...(ngDevMode ? [{ debugName: "_copy" }] : /* istanbul ignore next */ []));
82
- /** @protected Whether a transform is streaming, showing the stop affordance. */
83
- _running = computed(() => this._ai?.status() === 'running', /* @ts-ignore */
84
- ...(ngDevMode ? [{ debugName: "_running" }] : /* istanbul ignore next */ []));
85
- /** @protected Whether a review with pending suggestions is active. */
86
- _reviewing = computed(() => this._ai?.status() === 'reviewing', /* @ts-ignore */
87
- ...(ngDevMode ? [{ debugName: "_reviewing" }] : /* istanbul ignore next */ []));
88
- /** @protected The bar renders only while AI work is running or under review. */
89
- _visible = computed(() => this._running() || this._reviewing(), /* @ts-ignore */
90
- ...(ngDevMode ? [{ debugName: "_visible" }] : /* istanbul ignore next */ []));
91
- /** @protected Whether readonly/disabled state blocks every AI action. */
92
- _disabled = computed(() => this._context.disabled() || this._context.readonly(), /* @ts-ignore */
93
- ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
94
- /** @protected Pending suggestions mirrored from the AI context. */
95
- _suggestions = computed(() => this._ai?.suggestions() ?? [], /* @ts-ignore */
96
- ...(ngDevMode ? [{ debugName: "_suggestions" }] : /* istanbul ignore next */ []));
97
- /** @protected Pending-suggestion count shown in the bar. */
98
- _count = computed(() => this._suggestions().length, /* @ts-ignore */
99
- ...(ngDevMode ? [{ debugName: "_count" }] : /* istanbul ignore next */ []));
100
- /** @protected Clamped cursor index of the current suggestion. */
101
- _index = computed(() => Math.max(0, Math.min(this._cursor(), this._count() - 1)), /* @ts-ignore */
102
- ...(ngDevMode ? [{ debugName: "_index" }] : /* istanbul ignore next */ []));
103
- /** @private Identifier of the current suggestion, if any is pending. */
104
- _currentId = computed(() => this._suggestions()[this._index()]?.id ?? null, /* @ts-ignore */
105
- ...(ngDevMode ? [{ debugName: "_currentId" }] : /* istanbul ignore next */ []));
106
- /**
107
- * @protected Id of the visually hidden current-suggestion description
108
- * element the accept/reject buttons reference through `aria-describedby`.
109
- * Unique per bar instance so multiple editors on one page cannot collide.
110
- */
111
- _descriptionId = `mlv-editor-ai-review-bar-description-${++reviewBarDescriptionIdSequence}`;
112
- /**
113
- * @protected Non-visual description of the current suggestion — index,
114
- * count, and the removed/added text — from
115
- * {@link MlvEditorAiContext.describeSuggestion}. The removed text otherwise
116
- * exists only inside the `aria-hidden` strikethrough widget, so this is
117
- * what makes the accept/reject decision inspectable without sight.
118
- */
119
- _currentDescription = computed(() => {
120
- const id = this._currentId();
121
- return id === null ? '' : (this._ai?.describeSuggestion(id) ?? '');
122
- }, /* @ts-ignore */
123
- ...(ngDevMode ? [{ debugName: "_currentDescription" }] : /* istanbul ignore next */ []));
124
- /** @protected Localized pending count resolved through the ICU template. */
125
- _countText = computed(() => this._resolver.resolve({ aiReviewCount: this._copy().aiReviewCount }, 'aiReviewCount', { count: this._count() }), /* @ts-ignore */
126
- ...(ngDevMode ? [{ debugName: "_countText" }] : /* istanbul ignore next */ []));
127
- constructor() {
128
- // The bar joins the editor composite wherever a host renders it: the
129
- // registry adds its focus handlers to this element, so focus moving into
130
- // the bar never emits a false editor blur. Registered per enabled state
131
- // because disabling the editor force-clears the registry (`closeAll`),
132
- // which would silently drop a one-time registration.
133
- effect((onCleanup) => {
134
- if (this._context.disabled())
135
- return;
136
- const unregister = this._overlays.register(this._host.nativeElement,
137
- // Nothing to close: the bar is inline, its visibility follows the AI
138
- // context status.
139
- () => undefined);
140
- onCleanup(unregister);
141
- });
142
- // The navigation cursor resets whenever no review is active, so the next
143
- // review starts at its first suggestion.
144
- effect(() => {
145
- if (!this._reviewing())
146
- this._cursor.set(0);
147
- });
148
- // Outline follows the current suggestion: on review start, navigation,
149
- // and the auto-advance after accept/reject the context outlines the
150
- // suggestion and scrolls it into view. Ids are compared by value, so the
151
- // signal graph settles without re-firing on the mirror update the reveal
152
- // transaction itself causes.
153
- effect(() => {
154
- const id = this._currentId();
155
- if (id !== null)
156
- this._ai?.revealSuggestion(id);
157
- });
158
- this._destroyRef.onDestroy(() => this._ai?.revealSuggestion(null));
159
- }
160
- /** @protected Cancels the in-flight AI request from the stop affordance. */
161
- _stop() {
162
- this._ai?.cancel();
163
- }
164
- /** @protected Moves the cursor to the previous suggestion, wrapping at the start. */
165
- _previous() {
166
- const count = this._count();
167
- if (count === 0)
168
- return;
169
- this._cursor.set((this._index() - 1 + count) % count);
170
- }
171
- /** @protected Moves the cursor to the next suggestion, wrapping at the end. */
172
- _next() {
173
- const count = this._count();
174
- if (count === 0)
175
- return;
176
- this._cursor.set((this._index() + 1) % count);
177
- }
178
- /** @protected Accepts the current suggestion; the clamped cursor advances. */
179
- _acceptCurrent() {
180
- const id = this._currentId();
181
- if (id === null)
182
- return;
183
- this._cursor.set(this._index());
184
- this._ai?.acceptSuggestion(id);
185
- }
186
- /** @protected Rejects the current suggestion; the clamped cursor advances. */
187
- _rejectCurrent() {
188
- const id = this._currentId();
189
- if (id === null)
190
- return;
191
- this._cursor.set(this._index());
192
- this._ai?.rejectSuggestion(id);
193
- }
194
- /** @protected Accepts every pending suggestion. */
195
- _acceptAll() {
196
- this._ai?.acceptAll();
197
- }
198
- /** @protected Rejects every pending suggestion. */
199
- _rejectAll() {
200
- this._ai?.rejectAll();
201
- }
202
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiReviewBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
203
- 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: [".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"] }, { 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 });
204
- }
205
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiReviewBar, decorators: [{
206
- type: Component,
207
- args: [{ selector: 'mlv-editor-ai-review-bar', imports: [
208
- LucideCheck,
209
- LucideChevronLeft,
210
- LucideChevronRight,
211
- LucideCircleStop,
212
- LucideX,
213
- MlvButton,
214
- MlvButtonIcon,
215
- MlvTooltip,
216
- ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
217
- class: 'mlv-editor-ai-review-bar',
218
- role: 'group',
219
- '[attr.aria-label]': '_copy().aiReviewBar',
220
- '[hidden]': '!_visible()',
221
- }, 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: [".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"] }]
222
- }], ctorParameters: () => [] });
223
- //# sourceMappingURL=editor-ai-review-bar.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"editor-ai-review-bar.js","sourceRoot":"","sources":["../../../../../../libs/editor/src/lib/ai/editor-ai-review-bar.ts","../../../../../../libs/editor/src/lib/ai/editor-ai-review-bar.html"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n signal,\n} from '@angular/core';\nimport {\n LucideCheck,\n LucideChevronLeft,\n LucideChevronRight,\n LucideCircleStop,\n LucideX,\n} from '@lucide/angular';\nimport { MlvButton, MlvButtonIcon } from '@malva-ui/core/button';\nimport { MlvTooltip } from '@malva-ui/core/tooltip';\nimport { MLV_EDITOR_I18N, MlvI18nResolverService } from '@malva-ui/i18n';\nimport {\n MLV_EDITOR_OVERLAY_REGISTRY,\n MLV_EDITOR_TOOLBAR_CONTEXT,\n} from '../editor-toolbar-context';\nimport type { MlvEditorOverlayRegistry } from '../editor-toolbar-context';\nimport { MLV_EDITOR_AI_CONTEXT } from './editor-ai-context';\n\n/** @internal Source of unique description-element ids across bar instances. */\nlet reviewBarDescriptionIdSequence = 0;\n\n/**\n * Review surface for pending AI suggestions.\n *\n * Rendered inside the editor composite — hosts project it into the status\n * region (`<mlv-editor-ai-review-bar mlvEditorStatus />`), following the\n * `MlvEditorStatus` placement. Hidden while the nearest\n * {@link MLV_EDITOR_AI_CONTEXT} is `'idle'` (or absent); while `'running'` it\n * shows a stop affordance that cancels the in-flight request, and while\n * `'reviewing'` it shows the pending-suggestion count, previous/next\n * navigation, accept/reject for the current suggestion, and accept-all/\n * reject-all.\n *\n * Navigation keeps an internal cursor over the context's suggestion list;\n * moving it outlines the current suggestion in the document\n * (`MLV_EDITOR_AI_SUGGESTION_CURRENT_CLASS` decoration) and scrolls its range\n * into view through {@link MlvEditorAiContext.revealSuggestion}. Accepting or\n * rejecting the current suggestion advances to the next remaining one (the\n * list shrinks under a clamped cursor); when the set empties the context\n * returns to `'idle'` and the bar disappears.\n *\n * Announcement choice: the count region is deliberately **not** an\n * `aria-live` region. The AI context already announces the review lifecycle —\n * `aiReviewStarted` with the count, and every accept/reject outcome — through\n * the CDK `LiveAnnouncer`; a live count would double-announce each change.\n *\n * The host element registers with the editor's composite-overlay registry, so\n * interacting with the bar never emits a false editor blur wherever a host\n * renders it. All controls are plain tab stops inside the composite. Accept,\n * reject, and navigation disable while the editor is readonly or disabled\n * (mutations are refused by the context anyway; the disabled state makes the\n * refusal visible), and the whole bar disables with the editor.\n */\n@Component({\n selector: 'mlv-editor-ai-review-bar',\n imports: [\n LucideCheck,\n LucideChevronLeft,\n LucideChevronRight,\n LucideCircleStop,\n LucideX,\n MlvButton,\n MlvButtonIcon,\n MlvTooltip,\n ],\n templateUrl: './editor-ai-review-bar.html',\n styleUrl: './editor-ai-review-bar.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'mlv-editor-ai-review-bar',\n role: 'group',\n '[attr.aria-label]': '_copy().aiReviewBar',\n '[hidden]': '!_visible()',\n },\n})\nexport class MlvEditorAiReviewBar {\n /** @protected AI command state of the nearest editor, if provided. */\n protected readonly _ai = inject(MLV_EDITOR_AI_CONTEXT, { optional: true });\n\n /** @protected Editor-scoped form state gating executability. */\n protected readonly _context = inject(MLV_EDITOR_TOOLBAR_CONTEXT);\n\n /** @private Composite focus ownership for the bar's host element. */\n private readonly _overlays = inject<MlvEditorOverlayRegistry>(\n MLV_EDITOR_OVERLAY_REGISTRY,\n );\n\n /** @private Host element registered with the composite-overlay registry. */\n private readonly _host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** @private Clears the current-suggestion outline on teardown. */\n private readonly _destroyRef = inject(DestroyRef);\n\n /** @private Optional localized editor copy. */\n private readonly _i18n = inject(MLV_EDITOR_I18N, { optional: true });\n\n /** @private Resolves the ICU pending-count template. */\n private readonly _resolver = inject(MlvI18nResolverService);\n\n /**\n * @private Navigation cursor over the suggestion list. Reset for every new\n * review; reads clamp through {@link _index}, so a cursor left beyond the\n * end after the list shrinks points at the last remaining suggestion.\n */\n private readonly _cursor = signal(0);\n\n /** @protected Reactive localized copy for every control and the count. */\n protected readonly _copy = computed(() => {\n const copy = this._i18n?.();\n return {\n aiReviewBar: copy?.aiReviewBar ?? 'AI suggestion review',\n aiReviewCount:\n copy?.aiReviewCount ??\n '{count, plural, one {# suggestion} other {# suggestions}}',\n aiPreviousSuggestion: copy?.aiPreviousSuggestion ?? 'Previous suggestion',\n aiNextSuggestion: copy?.aiNextSuggestion ?? 'Next suggestion',\n aiAcceptSuggestion: copy?.aiAcceptSuggestion ?? 'Accept suggestion',\n aiRejectSuggestion: copy?.aiRejectSuggestion ?? 'Reject suggestion',\n aiAcceptAll: copy?.aiAcceptAll ?? 'Accept all',\n aiRejectAll: copy?.aiRejectAll ?? 'Reject all',\n aiStopGeneration: copy?.aiStopGeneration ?? 'Stop generating',\n };\n });\n\n /** @protected Whether a transform is streaming, showing the stop affordance. */\n protected readonly _running = computed(\n () => this._ai?.status() === 'running',\n );\n\n /** @protected Whether a review with pending suggestions is active. */\n protected readonly _reviewing = computed(\n () => this._ai?.status() === 'reviewing',\n );\n\n /** @protected The bar renders only while AI work is running or under review. */\n protected readonly _visible = computed(\n () => this._running() || this._reviewing(),\n );\n\n /** @protected Whether readonly/disabled state blocks every AI action. */\n protected readonly _disabled = computed(\n () => this._context.disabled() || this._context.readonly(),\n );\n\n /** @protected Pending suggestions mirrored from the AI context. */\n protected readonly _suggestions = computed(\n () => this._ai?.suggestions() ?? [],\n );\n\n /** @protected Pending-suggestion count shown in the bar. */\n protected readonly _count = computed(() => this._suggestions().length);\n\n /** @protected Clamped cursor index of the current suggestion. */\n protected readonly _index = computed(() =>\n Math.max(0, Math.min(this._cursor(), this._count() - 1)),\n );\n\n /** @private Identifier of the current suggestion, if any is pending. */\n private readonly _currentId = computed(\n () => this._suggestions()[this._index()]?.id ?? null,\n );\n\n /**\n * @protected Id of the visually hidden current-suggestion description\n * element the accept/reject buttons reference through `aria-describedby`.\n * Unique per bar instance so multiple editors on one page cannot collide.\n */\n protected readonly _descriptionId = `mlv-editor-ai-review-bar-description-${++reviewBarDescriptionIdSequence}`;\n\n /**\n * @protected Non-visual description of the current suggestion — index,\n * count, and the removed/added text — from\n * {@link MlvEditorAiContext.describeSuggestion}. The removed text otherwise\n * exists only inside the `aria-hidden` strikethrough widget, so this is\n * what makes the accept/reject decision inspectable without sight.\n */\n protected readonly _currentDescription = computed(() => {\n const id = this._currentId();\n return id === null ? '' : (this._ai?.describeSuggestion(id) ?? '');\n });\n\n /** @protected Localized pending count resolved through the ICU template. */\n protected readonly _countText = computed(() =>\n this._resolver.resolve(\n { aiReviewCount: this._copy().aiReviewCount },\n 'aiReviewCount',\n { count: this._count() },\n ),\n );\n\n constructor() {\n // The bar joins the editor composite wherever a host renders it: the\n // registry adds its focus handlers to this element, so focus moving into\n // the bar never emits a false editor blur. Registered per enabled state\n // because disabling the editor force-clears the registry (`closeAll`),\n // which would silently drop a one-time registration.\n effect((onCleanup) => {\n if (this._context.disabled()) return;\n const unregister = this._overlays.register(\n this._host.nativeElement,\n // Nothing to close: the bar is inline, its visibility follows the AI\n // context status.\n () => undefined,\n );\n onCleanup(unregister);\n });\n\n // The navigation cursor resets whenever no review is active, so the next\n // review starts at its first suggestion.\n effect(() => {\n if (!this._reviewing()) this._cursor.set(0);\n });\n\n // Outline follows the current suggestion: on review start, navigation,\n // and the auto-advance after accept/reject the context outlines the\n // suggestion and scrolls it into view. Ids are compared by value, so the\n // signal graph settles without re-firing on the mirror update the reveal\n // transaction itself causes.\n effect(() => {\n const id = this._currentId();\n if (id !== null) this._ai?.revealSuggestion(id);\n });\n\n this._destroyRef.onDestroy(() => this._ai?.revealSuggestion(null));\n }\n\n /** @protected Cancels the in-flight AI request from the stop affordance. */\n protected _stop(): void {\n this._ai?.cancel();\n }\n\n /** @protected Moves the cursor to the previous suggestion, wrapping at the start. */\n protected _previous(): void {\n const count = this._count();\n if (count === 0) return;\n this._cursor.set((this._index() - 1 + count) % count);\n }\n\n /** @protected Moves the cursor to the next suggestion, wrapping at the end. */\n protected _next(): void {\n const count = this._count();\n if (count === 0) return;\n this._cursor.set((this._index() + 1) % count);\n }\n\n /** @protected Accepts the current suggestion; the clamped cursor advances. */\n protected _acceptCurrent(): void {\n const id = this._currentId();\n if (id === null) return;\n this._cursor.set(this._index());\n this._ai?.acceptSuggestion(id);\n }\n\n /** @protected Rejects the current suggestion; the clamped cursor advances. */\n protected _rejectCurrent(): void {\n const id = this._currentId();\n if (id === null) return;\n this._cursor.set(this._index());\n this._ai?.rejectSuggestion(id);\n }\n\n /** @protected Accepts every pending suggestion. */\n protected _acceptAll(): void {\n this._ai?.acceptAll();\n }\n\n /** @protected Rejects every pending suggestion. */\n protected _rejectAll(): void {\n this._ai?.rejectAll();\n }\n}\n","@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"]}