@malva-ui/editor 0.1.12 → 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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/fesm2022/malva-ui-editor-ai.mjs +17 -0
  4. package/fesm2022/malva-ui-editor-ai.mjs.map +1 -0
  5. package/fesm2022/malva-ui-editor.mjs +10227 -0
  6. package/fesm2022/malva-ui-editor.mjs.map +1 -0
  7. package/package.json +47 -26
  8. package/types/malva-ui-editor-ai.d.ts +1 -0
  9. package/types/malva-ui-editor.d.ts +2995 -0
  10. package/ai/index.d.ts +0 -12
  11. package/ai/malva-ui-editor-ai.d.ts +0 -5
  12. package/esm2022/ai/index.js +0 -12
  13. package/esm2022/ai/index.js.map +0 -1
  14. package/esm2022/ai/malva-ui-editor-ai.js +0 -5
  15. package/esm2022/ai/malva-ui-editor-ai.js.map +0 -1
  16. package/esm2022/index.js +0 -37
  17. package/esm2022/index.js.map +0 -1
  18. package/esm2022/lib/ai/editor-ai-actions.js +0 -69
  19. package/esm2022/lib/ai/editor-ai-actions.js.map +0 -1
  20. package/esm2022/lib/ai/editor-ai-context.js +0 -612
  21. package/esm2022/lib/ai/editor-ai-context.js.map +0 -1
  22. package/esm2022/lib/ai/editor-ai-menu.js +0 -288
  23. package/esm2022/lib/ai/editor-ai-menu.js.map +0 -1
  24. package/esm2022/lib/ai/editor-ai-review-bar.js +0 -223
  25. package/esm2022/lib/ai/editor-ai-review-bar.js.map +0 -1
  26. package/esm2022/lib/ai/editor-ai-stream.js +0 -677
  27. package/esm2022/lib/ai/editor-ai-stream.js.map +0 -1
  28. package/esm2022/lib/ai/editor-ai-suggestions.js +0 -751
  29. package/esm2022/lib/ai/editor-ai-suggestions.js.map +0 -1
  30. package/esm2022/lib/ai/editor-ai.tokens.js +0 -10
  31. package/esm2022/lib/ai/editor-ai.tokens.js.map +0 -1
  32. package/esm2022/lib/ai/editor-ai.types.js +0 -1
  33. package/esm2022/lib/ai/editor-ai.types.js.map +0 -1
  34. package/esm2022/lib/editor/editor.js +0 -985
  35. package/esm2022/lib/editor/editor.js.map +0 -1
  36. package/esm2022/lib/editor-extension-preflight.js +0 -83
  37. package/esm2022/lib/editor-extension-preflight.js.map +0 -1
  38. package/esm2022/lib/editor-serialization.js +0 -152
  39. package/esm2022/lib/editor-serialization.js.map +0 -1
  40. package/esm2022/lib/editor-toolbar-context.js +0 -259
  41. package/esm2022/lib/editor-toolbar-context.js.map +0 -1
  42. package/esm2022/lib/editor.tokens.js +0 -10
  43. package/esm2022/lib/editor.tokens.js.map +0 -1
  44. package/esm2022/lib/editor.types.js +0 -48
  45. package/esm2022/lib/editor.types.js.map +0 -1
  46. package/esm2022/lib/extensions/editor-block-handle.js +0 -760
  47. package/esm2022/lib/extensions/editor-block-handle.js.map +0 -1
  48. package/esm2022/lib/extensions/editor-extensions.js +0 -321
  49. package/esm2022/lib/extensions/editor-extensions.js.map +0 -1
  50. package/esm2022/lib/extensions/editor-markdown-compatibility.js +0 -162
  51. package/esm2022/lib/extensions/editor-markdown-compatibility.js.map +0 -1
  52. package/esm2022/lib/extensions/editor-upload-placeholder.js +0 -156
  53. package/esm2022/lib/extensions/editor-upload-placeholder.js.map +0 -1
  54. package/esm2022/lib/status/editor-status.js +0 -64
  55. package/esm2022/lib/status/editor-status.js.map +0 -1
  56. package/esm2022/lib/toolbar/editor-alignment.js +0 -203
  57. package/esm2022/lib/toolbar/editor-alignment.js.map +0 -1
  58. package/esm2022/lib/toolbar/editor-block-insert.js +0 -101
  59. package/esm2022/lib/toolbar/editor-block-insert.js.map +0 -1
  60. package/esm2022/lib/toolbar/editor-color-control.js +0 -71
  61. package/esm2022/lib/toolbar/editor-color-control.js.map +0 -1
  62. package/esm2022/lib/toolbar/editor-command-button.js +0 -115
  63. package/esm2022/lib/toolbar/editor-command-button.js.map +0 -1
  64. package/esm2022/lib/toolbar/editor-heading.js +0 -266
  65. package/esm2022/lib/toolbar/editor-heading.js.map +0 -1
  66. package/esm2022/lib/toolbar/editor-highlight.js +0 -84
  67. package/esm2022/lib/toolbar/editor-highlight.js.map +0 -1
  68. package/esm2022/lib/toolbar/editor-image-upload-dialog.js +0 -289
  69. package/esm2022/lib/toolbar/editor-image-upload-dialog.js.map +0 -1
  70. package/esm2022/lib/toolbar/editor-image-upload.js +0 -315
  71. package/esm2022/lib/toolbar/editor-image-upload.js.map +0 -1
  72. package/esm2022/lib/toolbar/editor-inline-marks.js +0 -124
  73. package/esm2022/lib/toolbar/editor-inline-marks.js.map +0 -1
  74. package/esm2022/lib/toolbar/editor-link.js +0 -265
  75. package/esm2022/lib/toolbar/editor-link.js.map +0 -1
  76. package/esm2022/lib/toolbar/editor-list.js +0 -249
  77. package/esm2022/lib/toolbar/editor-list.js.map +0 -1
  78. package/esm2022/lib/toolbar/editor-table.js +0 -292
  79. package/esm2022/lib/toolbar/editor-table.js.map +0 -1
  80. package/esm2022/lib/toolbar/editor-text-color.js +0 -82
  81. package/esm2022/lib/toolbar/editor-text-color.js.map +0 -1
  82. package/esm2022/lib/toolbar/editor-toolbar-widget.js +0 -41
  83. package/esm2022/lib/toolbar/editor-toolbar-widget.js.map +0 -1
  84. package/esm2022/lib/toolbar/editor-toolbar.defs.js +0 -60
  85. package/esm2022/lib/toolbar/editor-toolbar.defs.js.map +0 -1
  86. package/esm2022/lib/toolbar/editor-toolbar.js +0 -826
  87. package/esm2022/lib/toolbar/editor-toolbar.js.map +0 -1
  88. package/esm2022/lib/toolbar/editor-undo-redo.js +0 -72
  89. package/esm2022/lib/toolbar/editor-undo-redo.js.map +0 -1
  90. package/esm2022/lib/toolbar/editor-zoom.js +0 -330
  91. package/esm2022/lib/toolbar/editor-zoom.js.map +0 -1
  92. package/esm2022/lib/upload/editor-image-upload-coordinator.js +0 -480
  93. package/esm2022/lib/upload/editor-image-upload-coordinator.js.map +0 -1
  94. package/esm2022/malva-ui-editor.js +0 -5
  95. package/esm2022/malva-ui-editor.js.map +0 -1
  96. package/index.d.ts +0 -35
  97. package/lib/ai/editor-ai-actions.d.ts +0 -27
  98. package/lib/ai/editor-ai-context.d.ts +0 -307
  99. package/lib/ai/editor-ai-menu.d.ts +0 -142
  100. package/lib/ai/editor-ai-review-bar.d.ts +0 -116
  101. package/lib/ai/editor-ai-stream.d.ts +0 -207
  102. package/lib/ai/editor-ai-suggestions.d.ts +0 -219
  103. package/lib/ai/editor-ai.tokens.d.ts +0 -10
  104. package/lib/ai/editor-ai.types.d.ts +0 -176
  105. package/lib/editor/editor.d.ts +0 -243
  106. package/lib/editor-extension-preflight.d.ts +0 -23
  107. package/lib/editor-serialization.d.ts +0 -74
  108. package/lib/editor-toolbar-context.d.ts +0 -116
  109. package/lib/editor.tokens.d.ts +0 -6
  110. package/lib/editor.types.d.ts +0 -194
  111. package/lib/extensions/editor-block-handle.d.ts +0 -59
  112. package/lib/extensions/editor-extensions.d.ts +0 -165
  113. package/lib/extensions/editor-markdown-compatibility.d.ts +0 -11
  114. package/lib/extensions/editor-upload-placeholder.d.ts +0 -75
  115. package/lib/status/editor-status.d.ts +0 -24
  116. package/lib/toolbar/editor-alignment.d.ts +0 -61
  117. package/lib/toolbar/editor-block-insert.d.ts +0 -26
  118. package/lib/toolbar/editor-color-control.d.ts +0 -32
  119. package/lib/toolbar/editor-command-button.d.ts +0 -29
  120. package/lib/toolbar/editor-heading.d.ts +0 -68
  121. package/lib/toolbar/editor-highlight.d.ts +0 -21
  122. package/lib/toolbar/editor-image-upload-dialog.d.ts +0 -96
  123. package/lib/toolbar/editor-image-upload.d.ts +0 -71
  124. package/lib/toolbar/editor-inline-marks.d.ts +0 -30
  125. package/lib/toolbar/editor-link.d.ts +0 -78
  126. package/lib/toolbar/editor-list.d.ts +0 -67
  127. package/lib/toolbar/editor-table.d.ts +0 -133
  128. package/lib/toolbar/editor-text-color.d.ts +0 -21
  129. package/lib/toolbar/editor-toolbar-widget.d.ts +0 -19
  130. package/lib/toolbar/editor-toolbar.d.ts +0 -157
  131. package/lib/toolbar/editor-toolbar.defs.d.ts +0 -35
  132. package/lib/toolbar/editor-undo-redo.d.ts +0 -26
  133. package/lib/toolbar/editor-zoom.d.ts +0 -108
  134. package/lib/upload/editor-image-upload-coordinator.d.ts +0 -55
  135. package/malva-ui-editor.d.ts +0 -5
@@ -1 +0,0 @@
1
- {"version":3,"file":"editor-ai-context.js","sourceRoot":"","sources":["../../../../../../libs/editor/src/lib/ai/editor-ai-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { LiveAnnouncer } from '@angular/cdk/a11y';\nimport {\n computed,\n inject,\n Injectable,\n InjectionToken,\n signal,\n} from '@angular/core';\nimport type { Signal } from '@angular/core';\nimport type { Editor } from '@tiptap/core';\nimport type {} from '@tiptap/markdown';\nimport { MLV_EDITOR_I18N, MlvI18nResolverService } from '@malva-ui/i18n';\nimport { MLV_EDITOR_TOOLBAR_CONTEXT } from '../editor-toolbar-context';\nimport { runMlvEditorAiStream } from './editor-ai-stream';\nimport type { MlvEditorAiStreamHandle } from './editor-ai-stream';\nimport { applyMlvEditorAiSuggestions } from './editor-ai-suggestions';\nimport type { MlvEditorAiSuggestionsSession } from './editor-ai-suggestions';\nimport { MLV_EDITOR_AI_PROVIDER } from './editor-ai.tokens';\nimport type {\n MlvEditorAiOutputMode,\n MlvEditorAiProvider,\n MlvEditorAiRequest,\n MlvEditorAiReviewSuggestion,\n MlvEditorAiTransformKind,\n} from './editor-ai.types';\n\n/**\n * Lifecycle of the per-editor AI surface.\n *\n * `'idle'` — no AI work is in flight. `'running'` — a transform is streaming\n * into the document, or a `'review'` transform is collecting its result.\n * `'reviewing'` — a collected result was applied as tracked suggestions that\n * await per-change or bulk accept/reject; the state ends when the suggestion\n * set empties.\n */\nexport type MlvEditorAiStatus = 'idle' | 'running' | 'reviewing';\n\n/**\n * @internal English ICU fallback for the review-started announcement, used\n * when no `MLV_EDITOR_I18N` token is provided — the same template the English\n * pack ships.\n */\nconst REVIEW_STARTED_FALLBACK =\n '{count, plural, one {# AI suggestion ready for review} other {# AI suggestions ready for review}}';\n\n/**\n * @internal English ICU fallbacks for the current-suggestion descriptions,\n * used when no `MLV_EDITOR_I18N` token is provided — the same templates the\n * English pack ships.\n */\nconst CURRENT_SUGGESTION_FALLBACKS = {\n aiCurrentSuggestionReplace:\n 'Suggestion {index} of {count}: replaces \"{oldText}\" with \"{newText}\"',\n aiCurrentSuggestionInsert:\n 'Suggestion {index} of {count}: inserts \"{newText}\"',\n aiCurrentSuggestionDelete:\n 'Suggestion {index} of {count}: removes \"{oldText}\"',\n} as const;\n\n/** Options accepted by {@link MlvEditorAiContext.runTransform}. */\nexport interface MlvEditorAiTransformOptions {\n /**\n * Free-form transform argument: the prompt for `'custom'`, the target tone\n * for `'tone'`, or the target language for `'translate'`.\n */\n readonly instruction?: string;\n\n /**\n * Where the streamed result lands; defaults to `'replace-selection'`.\n * `'review'` collects the complete result without touching the document\n * and applies it as reviewable tracked suggestions on success.\n */\n readonly output?: MlvEditorAiOutputMode;\n}\n\n/**\n * @internal Supplies the owning `mlv-editor`'s `aiProvider` input signal to\n * the per-editor AI context. The input wins over `MLV_EDITOR_AI_PROVIDER`,\n * mirroring the image-uploader precedence rule. Deliberately excluded from\n * the public barrels.\n */\nexport const MLV_EDITOR_AI_INPUT_PROVIDER = new InjectionToken<\n Signal<MlvEditorAiProvider | undefined>\n>('MLV_EDITOR_AI_INPUT_PROVIDER');\n\n/**\n * Per-editor AI command state provided by `mlv-editor`.\n *\n * The context owns provider resolution, request shaping, and the streaming\n * and review session lifecycles; the framework-free engines\n * ({@link runMlvEditorAiStream}, {@link applyMlvEditorAiSuggestions}) own\n * document mutation. Every mutation is\n * gated by the toolbar context's guard, so readonly and disabled editors\n * refuse to start AI work — controls stay visible while a provider exists\n * (presence) and disable with the editor (executability), matching the\n * toolbar's presence-vs-executability rule.\n *\n * Failures never throw or reject: they surface as recoverable\n * {@link MlvEditorError} emissions through the owning editor's `editorError`\n * output. Cancellation stays silent.\n */\n@Injectable()\nexport class MlvEditorAiContext {\n /** @private Guarded command surface and error channel of the owning editor. */\n private readonly _toolbar = inject(MLV_EDITOR_TOOLBAR_CONTEXT);\n\n /** @private Application-level provider used when the input supplies none. */\n private readonly _injectedProvider = inject(MLV_EDITOR_AI_PROVIDER, {\n optional: true,\n });\n\n /** @private The owning editor's `aiProvider` input; wins over the token. */\n private readonly _inputProvider = inject(MLV_EDITOR_AI_INPUT_PROVIDER, {\n optional: true,\n });\n\n /** @private Announces streaming lifecycle politely; decorations are aria-hidden. */\n private readonly _liveAnnouncer = inject(LiveAnnouncer);\n\n /** @private Optional translated announcement strings. */\n private readonly _i18n = inject(MLV_EDITOR_I18N, { optional: true });\n\n /** @private Resolves the ICU review-count announcement template. */\n private readonly _resolver = inject(MlvI18nResolverService);\n\n /** @private Writable lifecycle state behind the readonly public signal. */\n private readonly _status = signal<MlvEditorAiStatus>('idle');\n\n /** @private Writable pending-suggestion projection behind the readonly signal. */\n private readonly _suggestions = signal<\n readonly MlvEditorAiReviewSuggestion[]\n >([]);\n\n /**\n * @private Latest streaming session. Retained after settlement so\n * {@link restoreCheckpoint} can undo a committed transform; replaced when\n * the next transform starts.\n */\n private _session: MlvEditorAiStreamHandle | null = null;\n\n /** @private Abort control for the in-flight provider request, if any. */\n private _abortController: AbortController | null = null;\n\n /**\n * @private Active review wiring: the engine session plus the teardown that\n * detaches the editor `transaction`/`destroy` listeners mirroring it into\n * {@link suggestions}. `null` outside `'reviewing'`.\n */\n private _review: {\n readonly session: MlvEditorAiSuggestionsSession;\n readonly detach: () => void;\n } | null = null;\n\n /**\n * @private Last suggestion id {@link revealSuggestion} announced. Repeated\n * reveals of the same suggestion (the review bar re-runs its outline effect\n * on unrelated signal updates) must not re-announce it.\n */\n private _revealedId: string | null = null;\n\n /** Lifecycle of the AI surface for this editor. */\n readonly status: Signal<MlvEditorAiStatus> = this._status.asReadonly();\n\n /**\n * Pending suggestions of the active review session, in document order.\n * Empty outside `'reviewing'`. Refreshed after every editor transaction, so\n * accept/reject calls, intersecting user edits, and an undo of the\n * application are all reflected immediately. Document ranges deliberately\n * stay internal — they remap on every transaction and would be stale the\n * moment a consumer stored them.\n */\n readonly suggestions: Signal<readonly MlvEditorAiReviewSuggestion[]> =\n this._suggestions.asReadonly();\n\n /**\n * Whether a review session with unresolved suggestions is active. Hosts\n * gate saves on this: during `'reviewing'` the document serializes in its\n * accepted-by-default shape (inserted text present, removed text absent).\n */\n readonly hasPendingSuggestions = computed(\n () => this._suggestions().length > 0,\n );\n\n /**\n * Whether an AI provider is resolvable for this editor, either through the\n * `aiProvider` input or `MLV_EDITOR_AI_PROVIDER`. AI UI modules hide\n * entirely without a provider.\n */\n readonly hasProvider = computed(() => this._resolveProvider() !== null);\n\n /**\n * Runs one selection transform through the resolved provider and lands the\n * result according to the output mode: `'replace-selection'` and\n * `'insert-below'` stream into the document, while `'review'` collects the\n * complete result without writing and then applies it over the selection\n * captured at start as reviewable tracked suggestions — status moves to\n * `'reviewing'` and the suggestion surface\n * ({@link suggestions}, {@link acceptSuggestion}, …) takes over. A review\n * result identical to the region applies nothing: status returns straight\n * to `'idle'` with a polite no-changes announcement and no error.\n *\n * The returned promise resolves after the session settles and never\n * rejects; every failure is reported as a recoverable error through the\n * editor's `editorError` output instead:\n *\n * - a transform already running, or a review still pending\n * (`configuration`) — concurrent calls are refused rather than queued,\n * so one editor never hosts two writers and pending suggestions are\n * never silently replaced;\n * - a readonly, disabled, or absent editor (`configuration`) — the toolbar\n * guard refuses to start, and a `'review'` run re-checks the same guard\n * after collection: an editor that became readonly or disabled mid-run\n * refuses to apply the collected suggestions (checkpoint untouched — a\n * review collection never wrote);\n * - no resolvable provider (`configuration`);\n * - a provider that throws or whose stream fails (`'ai-transport'`) — the\n * checkpoint is restored;\n * - unusable output (`'ai-result'`) — the checkpoint is restored. Both\n * restore paths are no-ops for `'review'`, which never wrote.\n *\n * Start, successful finish, and cancellation are announced through the CDK\n * `LiveAnnouncer` at `'polite'`; a successful `'review'` run announces the\n * pending suggestion count instead of the generic finish. A failure\n * announces nothing because the error channel already reports it.\n *\n * @param kind Transform requested from the provider.\n * @param options Optional instruction and output mode.\n */\n async runTransform(\n kind: MlvEditorAiTransformKind,\n options: MlvEditorAiTransformOptions = {},\n ): Promise<void> {\n if (this._status() !== 'idle') {\n this._reportConfiguration(\n this._status() === 'reviewing'\n ? 'An AI review is pending for this editor: resolve the suggestions before starting another transform.'\n : 'An AI transform is already running for this editor.',\n );\n return;\n }\n if (!this._toolbar.can(() => true)) {\n this._reportConfiguration(\n 'AI transforms require an editable editor: readonly and disabled editors refuse to start.',\n );\n return;\n }\n const editor = this._toolbar.editor();\n if (!editor) return;\n const provider = this._resolveProvider();\n if (!provider) {\n this._reportConfiguration(\n 'No AI provider is configured. Supply the aiProvider input or provide MLV_EDITOR_AI_PROVIDER.',\n );\n return;\n }\n const output = options.output ?? 'replace-selection';\n // Review applies over the selection captured at start. External edits\n // abandon the collection session, so a committed run still sees exactly\n // this document region.\n const reviewFrom = editor.state.selection.from;\n const reviewTo = editor.state.selection.to;\n\n const controller = new AbortController();\n const request: MlvEditorAiRequest = {\n kind,\n ...(options.instruction === undefined\n ? {}\n : { instruction: options.instruction }),\n context: {\n selection: this._serializeSelection(editor),\n document: this._serializeDocument(editor),\n },\n signal: controller.signal,\n };\n\n let chunks: AsyncIterable<string>;\n try {\n chunks = provider.stream(request);\n } catch (cause: unknown) {\n this._toolbar.reportError({\n code: 'ai-transport',\n message: 'The AI provider failed to start streaming.',\n recoverable: true,\n cause,\n });\n return;\n }\n\n this._abortController = controller;\n this._status.set('running');\n this._announce('aiStreamingStarted', 'AI generation started');\n\n const session = runMlvEditorAiStream(editor, { chunks, output });\n this._session = session;\n const result = await session.done;\n\n if (this._abortController === controller) this._abortController = null;\n // Cancellation, abandonment, and failure all release the provider: the\n // request signal aborts so a still-producing transport stops.\n if (result.status !== 'committed') controller.abort();\n\n switch (result.status) {\n case 'committed':\n if (output === 'review') {\n // The guard passed at start, but the editor may have become\n // readonly or disabled while the result was collected. Applying\n // suggestions is a document mutation, so it must re-pass the\n // toolbar guard here; on refusal the checkpoint is untouched — a\n // review collection never wrote.\n if (!this._toolbar.can(() => true)) {\n this._status.set('idle');\n this._reportConfiguration(\n 'The editor became readonly or disabled while the AI review result was collected: the suggestions were not applied.',\n );\n break;\n }\n this._startReview(editor, reviewFrom, reviewTo, result.text);\n } else {\n this._status.set('idle');\n this._announce('aiStreamingFinished', 'AI generation finished');\n }\n break;\n case 'cancelled':\n case 'abandoned':\n this._status.set('idle');\n this._announce('aiStreamingCancelled', 'AI generation cancelled');\n break;\n case 'failed':\n this._status.set('idle');\n this._toolbar.reportError({\n code: result.error ?? 'ai-result',\n message:\n result.error === 'ai-transport'\n ? 'The AI provider stream failed.'\n : 'The AI provider produced no usable output.',\n recoverable: true,\n });\n break;\n }\n }\n\n /**\n * Aborts the in-flight request and rolls the document back to the\n * checkpoint captured when the transform started.\n *\n * The provider's `AbortSignal` aborts, the streaming session restores the\n * checkpoint, and the transform resolves silently — no error is emitted\n * and only the cancellation announcement is made. A no-op while idle.\n */\n cancel(): void {\n this._abortController?.abort();\n this._abortController = null;\n this._session?.cancel();\n }\n\n /**\n * Restores the document to the checkpoint of the most recent transform.\n *\n * While a transform is running this behaves exactly like {@link cancel}.\n * After a committed transform it reverts the commit in one history-visible\n * step, so the restore is itself undoable. While `'reviewing'`, restoring\n * reverts the applied suggestions wholesale, which drops the suggestion\n * set and ends the review. The restore is a document mutation and\n * therefore passes the toolbar guard: readonly and disabled editors refuse\n * it. A no-op when no transform has run.\n */\n restoreCheckpoint(): void {\n if (this._status() === 'running') {\n this.cancel();\n return;\n }\n const session = this._session;\n if (!session) return;\n this._toolbar.run(() => session.restoreCheckpoint());\n }\n\n /**\n * Accepts one pending suggestion: the applied change stays in the document\n * and its decorations drop. Delegates to the review engine — a pure\n * plugin-state update that creates no history entry. Announces the\n * acceptance politely; when the last suggestion resolves the review ends\n * and {@link status} returns to `'idle'`.\n *\n * Refused silently — decorations stay visible, nothing changes — while no\n * review is active, the id is unknown, or the editor is readonly or\n * disabled (the same guard rule that blocks every other AI mutation).\n *\n * @param id Identifier from {@link suggestions}.\n */\n acceptSuggestion(id: string): void {\n const session = this._review?.session;\n if (!session || !this._toolbar.can(() => true)) return;\n if (session.accept(id)) {\n this._announce('aiSuggestionAccepted', 'AI suggestion accepted');\n }\n }\n\n /**\n * Rejects one pending suggestion: the original content is restored over\n * the suggestion's range in exactly one history-visible step and its\n * decorations drop. Announces the rejection politely; when the last\n * suggestion resolves the review ends and {@link status} returns to\n * `'idle'`.\n *\n * Refused silently while no review is active, the id is unknown, or the\n * editor is readonly or disabled — the restore is a document mutation and\n * runs through the toolbar guard.\n *\n * @param id Identifier from {@link suggestions}.\n */\n rejectSuggestion(id: string): void {\n const session = this._review?.session;\n if (!session) return;\n if (this._toolbar.run(() => session.reject(id))) {\n this._announce('aiSuggestionRejected', 'AI suggestion rejected');\n }\n }\n\n /**\n * Accepts every pending suggestion in one plugin-state update — no\n * document step, no history entry — then ends the review and returns\n * {@link status} to `'idle'`. Announced politely. Refused silently while\n * no review is active or the editor is readonly or disabled.\n */\n acceptAll(): void {\n const session = this._review?.session;\n if (!session || !this._toolbar.can(() => true)) return;\n if (session.acceptAll()) {\n this._announce('aiAllSuggestionsAccepted', 'All AI suggestions accepted');\n }\n }\n\n /**\n * Rejects every pending suggestion in exactly one history-visible step,\n * restoring the original content of the whole reviewed region, then ends\n * the review and returns {@link status} to `'idle'`. Announced politely.\n * Refused silently while no review is active or the editor is readonly or\n * disabled — the restore is a document mutation and runs through the\n * toolbar guard.\n */\n rejectAll(): void {\n const session = this._review?.session;\n if (!session) return;\n if (this._toolbar.run(() => session.rejectAll())) {\n this._announce('aiAllSuggestionsRejected', 'All AI suggestions rejected');\n }\n }\n\n /**\n * Outlines one pending suggestion as the current one under review and\n * scrolls its document range into view; `null` clears the outline (review\n * surfaces call that on teardown so no marker strands).\n *\n * The outline decorations are `aria-hidden`, so revealing a suggestion also\n * announces its {@link describeSuggestion} description politely — index,\n * count, and the removed/added text — the non-visual equivalent the spec\n * requires. Each suggestion is announced once per reveal: repeating the\n * current id moves nothing and re-announces nothing.\n *\n * Otherwise a pure view affordance: the outline is a decoration-only\n * plugin-state update, so no document step and no history entry is created\n * — which is why, unlike accept/reject, it stays available in readonly\n * editors, whose pending reviews remain visible and navigable. Refused\n * silently while no review is active or the id is unknown. The review bar\n * drives this from its navigation cursor.\n *\n * @param id Identifier from {@link suggestions}, or `null` to clear.\n */\n revealSuggestion(id: string | null): void {\n const session = this._review?.session;\n if (!session || !session.setCurrent(id)) return;\n if (id === null) {\n this._revealedId = null;\n return;\n }\n const editor = this._toolbar.editor();\n if (editor) {\n const target = session\n .suggestions()\n .find((suggestion) => suggestion.id === id);\n if (target) this._scrollPositionIntoView(editor, target.range.from);\n }\n if (this._revealedId !== id) {\n this._revealedId = id;\n const description = this.describeSuggestion(id);\n if (description) {\n void this._liveAnnouncer.announce(description, 'polite');\n }\n }\n }\n\n /**\n * Human-readable description of one pending suggestion — its one-based\n * position in the pending list plus what it replaces, inserts, or removes —\n * resolved through the `aiCurrentSuggestionReplace`/`-Insert`/`-Delete`\n * ICU templates.\n *\n * This is the non-visual equivalent of the `aria-hidden` suggestion\n * decorations: {@link revealSuggestion} announces it, and the review bar\n * additionally exposes it to its accept/reject controls through\n * `aria-describedby`, so the change under decision is inspectable without\n * sight of the decorations. Returns `null` while no review is active or\n * for an unknown id.\n *\n * @param id Identifier from {@link suggestions}.\n */\n describeSuggestion(id: string): string | null {\n const pending = this._suggestions();\n const index = pending.findIndex((suggestion) => suggestion.id === id);\n if (index < 0) return null;\n const suggestion = pending[index];\n const key =\n suggestion.kind === 'insert'\n ? 'aiCurrentSuggestionInsert'\n : suggestion.kind === 'delete'\n ? 'aiCurrentSuggestionDelete'\n : 'aiCurrentSuggestionReplace';\n const template = this._i18n?.()[key] ?? CURRENT_SUGGESTION_FALLBACKS[key];\n return this._resolver.resolve({ [key]: template }, key, {\n index: index + 1,\n count: pending.length,\n oldText: suggestion.oldText,\n newText: suggestion.newText,\n });\n }\n\n /**\n * @private Scrolls the rendered element at one document position into view.\n * Best effort: position resolution can fail transiently mid-transaction and\n * jsdom offers no `scrollIntoView`, so failure never fails the reveal.\n */\n private _scrollPositionIntoView(editor: Editor, position: number): void {\n try {\n const { node } = editor.view.domAtPos(position);\n const element = node instanceof HTMLElement ? node : node.parentElement;\n if (element && typeof element.scrollIntoView === 'function') {\n element.scrollIntoView({ block: 'nearest' });\n }\n } catch {\n // The outline already moved; scrolling is a progressive enhancement.\n }\n }\n\n /**\n * @private Routes a committed review collection through the suggestion\n * engine. An engine refusal — the replacement is identical to the region,\n * or otherwise nothing reviewable results — is the no-changes case: status\n * returns to `'idle'` with a polite announcement, no error, and no\n * document change. Otherwise the context enters `'reviewing'`, mirrors the\n * engine's suggestion set into {@link suggestions} after every editor\n * transaction (the session has no change notification of its own), and\n * announces the pending count.\n */\n private _startReview(\n editor: Editor,\n from: number,\n to: number,\n replacementMarkdown: string,\n ): void {\n const session = applyMlvEditorAiSuggestions(editor, {\n from,\n to,\n replacementMarkdown,\n });\n if (!session) {\n this._status.set('idle');\n this._announce('aiReviewNoChanges', 'The AI suggested no changes');\n return;\n }\n const synchronize = () => {\n if (this._review?.session !== session) return;\n const pending = this._projectSuggestions(session);\n this._suggestions.set(pending);\n if (pending.length === 0) this._endReview();\n };\n const onDestroy = () => {\n if (this._review?.session === session) this._endReview();\n };\n editor.on('transaction', synchronize);\n editor.on('destroy', onDestroy);\n this._revealedId = null;\n this._review = {\n session,\n detach: () => {\n editor.off('transaction', synchronize);\n editor.off('destroy', onDestroy);\n },\n };\n const pending = this._projectSuggestions(session);\n this._suggestions.set(pending);\n if (pending.length === 0) {\n // Defensive: the engine returns null rather than an empty session.\n this._endReview();\n this._announce('aiReviewNoChanges', 'The AI suggested no changes');\n return;\n }\n this._status.set('reviewing');\n this._announceReviewStarted(pending.length);\n }\n\n /**\n * @private Ends the review session exactly once: detaches the editor\n * listeners, clears the suggestion projection, and returns to `'idle'`.\n * Runs when the suggestion set empties — accept/reject calls, intersecting\n * edits dropping the last suggestion, or an undo of the application — and\n * when the editor is destroyed.\n */\n private _endReview(): void {\n const review = this._review;\n if (!review) return;\n this._review = null;\n this._revealedId = null;\n review.detach();\n this._suggestions.set([]);\n this._status.set('idle');\n }\n\n /** @private Projects engine suggestions into the range-free public shape. */\n private _projectSuggestions(\n session: MlvEditorAiSuggestionsSession,\n ): readonly MlvEditorAiReviewSuggestion[] {\n return session.suggestions().map(({ id, kind, oldText, newText }) => ({\n id,\n kind,\n oldText,\n newText,\n }));\n }\n\n /** @private Announces the pending review count politely through the ICU template. */\n private _announceReviewStarted(count: number): void {\n const template = this._i18n?.().aiReviewStarted ?? REVIEW_STARTED_FALLBACK;\n void this._liveAnnouncer.announce(\n this._resolver.resolve({ aiReviewStarted: template }, 'aiReviewStarted', {\n count,\n }),\n 'polite',\n );\n }\n\n /** @private Resolves the active provider; the input wins over the token. */\n private _resolveProvider(): MlvEditorAiProvider | null {\n return this._inputProvider?.() ?? this._injectedProvider ?? null;\n }\n\n /** @private Reports a recoverable configuration refusal through the editor. */\n private _reportConfiguration(message: string): void {\n this._toolbar.reportError({\n code: 'configuration',\n message,\n recoverable: true,\n });\n }\n\n /** @private Announces one localized AI lifecycle message politely. */\n private _announce(\n key:\n | 'aiStreamingStarted'\n | 'aiStreamingFinished'\n | 'aiStreamingCancelled'\n | 'aiReviewNoChanges'\n | 'aiSuggestionAccepted'\n | 'aiSuggestionRejected'\n | 'aiAllSuggestionsAccepted'\n | 'aiAllSuggestionsRejected',\n fallback: string,\n ): void {\n void this._liveAnnouncer.announce(\n this._i18n?.()[key] ?? fallback,\n 'polite',\n );\n }\n\n /**\n * @private Serializes the current selection as Markdown, or `null` when the\n * selection is empty. Uses the editor's Markdown manager — the same\n * serialization path as `getMarkdown()` — over the selection sliced into\n * its own document; a replacement extension set without Markdown falls back\n * to plain text so providers still receive usable context.\n */\n private _serializeSelection(editor: Editor): string | null {\n const { from, to } = editor.state.selection;\n if (from === to) return null;\n const manager = editor.markdown;\n if (manager) {\n try {\n const markdown = manager.serialize(\n editor.state.doc.cut(from, to).toJSON(),\n );\n if (markdown.length > 0) return markdown;\n } catch {\n // Fall back to plain text below.\n }\n }\n const text = editor.state.doc.textBetween(from, to, '\\n\\n');\n return text.length > 0 ? text : null;\n }\n\n /**\n * @private Serializes the whole document as Markdown, or `null` when the\n * document is semantically empty. The document window is currently the full\n * document; a bounded window is a later refinement. Falls back to plain\n * text when the extension set carries no Markdown manager.\n */\n private _serializeDocument(editor: Editor): string | null {\n if (editor.isEmpty) return null;\n if (typeof editor.getMarkdown === 'function' && editor.markdown) {\n try {\n const markdown = editor.getMarkdown();\n if (markdown.length > 0) return markdown;\n } catch {\n // Fall back to plain text below.\n }\n }\n const text = editor.getText();\n return text.length > 0 ? text : null;\n }\n}\n\n/** Injects the AI command state belonging to the nearest `mlv-editor`. */\nexport const MLV_EDITOR_AI_CONTEXT = new InjectionToken<MlvEditorAiContext>(\n 'MLV_EDITOR_AI_CONTEXT',\n);\n"]}
@@ -1,288 +0,0 @@
1
- import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
2
- import { coerceBooleanProperty } from '@angular/cdk/coercion';
3
- import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, computed, effect, inject, input, signal, viewChild, ViewEncapsulation, } from '@angular/core';
4
- import { LucideCircleStop, LucideSparkles } from '@lucide/angular';
5
- import { MlvButton, MlvButtonIcon } from '@malva-ui/core/button';
6
- import { MlvInput } from '@malva-ui/core/input';
7
- import { MlvListItem } from '@malva-ui/core/list';
8
- import { MlvMenu, MlvMenuItem, MlvMenuTrigger } from '@malva-ui/core/menu';
9
- import { MlvPopup, MlvPopupContainer, MlvPopupContent, } from '@malva-ui/core/popup';
10
- import { MlvRadio, MlvRadioGroup } from '@malva-ui/core/radio';
11
- import { MlvTooltip } from '@malva-ui/core/tooltip';
12
- import { MLV_EDITOR_I18N } from '@malva-ui/i18n';
13
- import { MLV_EDITOR_OVERLAY_REGISTRY, MLV_EDITOR_TOOLBAR_CONTEXT, } from '../editor-toolbar-context';
14
- import { MlvEditorToolbarWidget } from '../toolbar/editor-toolbar-widget';
15
- import { mlvEditorAiDefaultActions } from './editor-ai-actions';
16
- import { MLV_EDITOR_AI_CONTEXT } from './editor-ai-context';
17
- import * as i0 from "@angular/core";
18
- /**
19
- * Toolbar command group for the editor's AI transforms.
20
- *
21
- * A sparkles trigger opens a Malva menu with one item per action; an action
22
- * runs its kind through the context with `output: 'replace-selection'` unless
23
- * it says otherwise, while the custom-prompt item opens a Malva popup with an
24
- * instruction field and an output-mode choice (replace selection, insert
25
- * below, or land as reviewable suggestions). While a transform is running the
26
- * same trigger becomes a stop affordance that cancels the in-flight request,
27
- * so the control stays one roving tab stop throughout.
28
- *
29
- * The action list is a thin layer over {@link MlvEditorAiContext.runTransform}:
30
- * the seven built-in kinds are only the default. `actions` replaces them
31
- * literally with a host-authored list — any kind string, a fixed instruction,
32
- * a per-action output mode, or an own `run` callback — and
33
- * `showCustomPrompt="false"` drops the custom-prompt item and its popup.
34
- *
35
- * The group hides entirely when the nearest {@link MLV_EDITOR_AI_CONTEXT}
36
- * resolves no provider (presence), and its actions disable — without hiding —
37
- * while the editor is readonly or disabled or a transform is already running
38
- * (executability), matching the toolbar's presence-vs-executability rule.
39
- */
40
- export class MlvEditorAiMenu {
41
- /**
42
- * The menu's action list, replacing the built-in kinds literally — the
43
- * house rule for list-shaped inputs (`extensions`, the toolbar definitions):
44
- * a supplied array is never merged with the defaults.
45
- *
46
- * Omitted (the default), the menu renders the seven built-in transforms
47
- * labelled from `MLV_EDITOR_I18N`. To keep them and add to them, spread
48
- * `mlvEditorAiDefaultActions(copy)` — the very list this component uses.
49
- * The custom-prompt item is independent of this input; drop it with
50
- * {@link showCustomPrompt}.
51
- */
52
- actions = input(undefined, /* @ts-ignore */
53
- ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
54
- /**
55
- * Whether the built-in custom-prompt item and its popup exist at all.
56
- *
57
- * `false` removes the menu item and never renders the prompt popup, for
58
- * hosts whose action list already covers everything they want offered.
59
- */
60
- showCustomPrompt = input(true, { ...(ngDevMode ? { debugName: "showCustomPrompt" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
61
- /** @protected AI command state of the nearest editor, if provided. */
62
- _ai = inject(MLV_EDITOR_AI_CONTEXT, { optional: true });
63
- /** @protected Editor-scoped form state gating executability. */
64
- _context = inject(MLV_EDITOR_TOOLBAR_CONTEXT);
65
- /** @private Detached panel ownership for composite focus. */
66
- _overlays = inject(MLV_EDITOR_OVERLAY_REGISTRY);
67
- /** @private Document used to resolve the public menu panel id. */
68
- _document = inject(DOCUMENT);
69
- /** @private Cleans an active menu-overlay registration. */
70
- _destroyRef = inject(DestroyRef);
71
- /** @private Optional localized editor copy. */
72
- _i18n = inject(MLV_EDITOR_I18N, { optional: true });
73
- /** @protected Rendered Malva transforms menu. */
74
- _menu = viewChild.required('_menu', /* @ts-ignore */
75
- ...(ngDevMode ? [{ debugName: "_menu" }] : /* istanbul ignore next */ []));
76
- /** @protected Trigger owning the menu overlay. */
77
- _menuTrigger = viewChild.required(MlvMenuTrigger, /* @ts-ignore */
78
- ...(ngDevMode ? [{ debugName: "_menuTrigger" }] : /* istanbul ignore next */ []));
79
- /**
80
- * @protected Custom-prompt popup opened programmatically. Optional because
81
- * `showCustomPrompt="false"` removes it from the template entirely.
82
- */
83
- _popup = viewChild('_popup', /* @ts-ignore */
84
- ...(ngDevMode ? [{ debugName: "_popup" }] : /* istanbul ignore next */ []));
85
- /** @protected Native trigger restored after popup teardown. */
86
- _triggerElement = viewChild.required('_triggerElement', {
87
- read: ElementRef,
88
- });
89
- /** @protected Instruction field focused when the prompt opens. */
90
- _instructionInput = viewChild.required('_instructionInput', /* @ts-ignore */
91
- ...(ngDevMode ? [{ debugName: "_instructionInput" }] : /* istanbul ignore next */ []));
92
- /** @private Detached prompt panel queried through Angular rather than selectors. */
93
- _panel = viewChild('_panel', /* @ts-ignore */
94
- ...(ngDevMode ? [{ debugName: "_panel" }] : /* istanbul ignore next */ []));
95
- /** @protected Custom-prompt instruction draft, retained across opens. */
96
- _instruction = signal('', /* @ts-ignore */
97
- ...(ngDevMode ? [{ debugName: "_instruction" }] : /* istanbul ignore next */ []));
98
- /** @protected Output mode applied to the custom transform. */
99
- _output = signal('replace-selection', /* @ts-ignore */
100
- ...(ngDevMode ? [{ debugName: "_output" }] : /* istanbul ignore next */ []));
101
- /** @private Menu-overlay registration teardown. */
102
- _unregisterMenu;
103
- /** @private Guards queued menu registration after a quick close or destroy. */
104
- _menuOpen = false;
105
- /**
106
- * @protected Action entries rendered before the custom-prompt item: the
107
- * host's list when supplied, otherwise the localized built-ins. The factory
108
- * is the single source of the built-in label table.
109
- */
110
- _actions = computed(() => this.actions() ?? mlvEditorAiDefaultActions(this._i18n?.()), /* @ts-ignore */
111
- ...(ngDevMode ? [{ debugName: "_actions" }] : /* istanbul ignore next */ []));
112
- /** @protected Reactive localized copy for the trigger and prompt. */
113
- _copy = computed(() => {
114
- const copy = this._i18n?.();
115
- return {
116
- aiMenu: copy?.aiMenu ?? 'AI assist',
117
- aiCustom: copy?.aiCustom ?? 'Custom prompt',
118
- aiPromptPlaceholder: copy?.aiPromptPlaceholder ?? 'Describe what to do...',
119
- aiOutputMode: copy?.aiOutputMode ?? 'Output',
120
- aiReplaceSelection: copy?.aiReplaceSelection ?? 'Replace selection',
121
- aiInsertBelow: copy?.aiInsertBelow ?? 'Insert below',
122
- aiReviewChanges: copy?.aiReviewChanges ?? 'Review changes',
123
- aiApply: copy?.aiApply ?? 'Apply',
124
- aiCancel: copy?.aiCancel ?? 'Cancel',
125
- };
126
- }, /* @ts-ignore */
127
- ...(ngDevMode ? [{ debugName: "_copy" }] : /* istanbul ignore next */ []));
128
- /** @protected Whether an AI provider is resolvable; the group hides without one. */
129
- _hasProvider = computed(() => this._ai?.hasProvider() ?? false, /* @ts-ignore */
130
- ...(ngDevMode ? [{ debugName: "_hasProvider" }] : /* istanbul ignore next */ []));
131
- /** @protected Whether a transform is streaming, swapping the trigger to stop. */
132
- _running = computed(() => this._ai?.status() === 'running', /* @ts-ignore */
133
- ...(ngDevMode ? [{ debugName: "_running" }] : /* istanbul ignore next */ []));
134
- /** @protected Whether readonly/disabled state blocks every AI mutation. */
135
- _disabled = computed(() => this._context.disabled() || this._context.readonly(), /* @ts-ignore */
136
- ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
137
- /** @protected Whether menu items cannot currently start a transform. */
138
- _itemsDisabled = computed(() => (this._ai?.status() ?? 'idle') !== 'idle' || this._disabled(), /* @ts-ignore */
139
- ...(ngDevMode ? [{ debugName: "_itemsDisabled" }] : /* istanbul ignore next */ []));
140
- /** @protected Whether the apply action cannot submit the custom prompt. */
141
- _applyDisabled = computed(() => this._itemsDisabled() || this._instruction().trim().length === 0, /* @ts-ignore */
142
- ...(ngDevMode ? [{ debugName: "_applyDisabled" }] : /* istanbul ignore next */ []));
143
- constructor() {
144
- this._destroyRef.onDestroy(() => this._unregisterMenuOverlay());
145
- // Readonly and disabled editors refuse AI mutation, so the detached
146
- // prompt closes rather than lingering over a surface it cannot edit.
147
- effect(() => {
148
- const popup = this._popup();
149
- if (popup && this._disabled() && popup.opened()) {
150
- popup.opened.set(false);
151
- }
152
- });
153
- // The prompt panel joins the editor composite while rendered so focus
154
- // inside it never emits a false editor blur. Without the prompt there is
155
- // no panel to render, so nothing registers.
156
- effect((onCleanup) => {
157
- const panel = this._panel()?.nativeElement;
158
- if (!panel)
159
- return;
160
- const unregister = this._overlays.register(panel, () => this._popup()?.opened.set(false));
161
- onCleanup(unregister);
162
- });
163
- }
164
- /** @protected Stops the running transform; the menu trigger opens otherwise. */
165
- _onTriggerClick() {
166
- if (this._running())
167
- this._ai?.cancel();
168
- }
169
- /**
170
- * @protected Runs one action: its own `run` callback when it has one,
171
- * otherwise its kind through the context with the action's instruction and
172
- * output mode. The executability guard is re-checked here so a host `run`
173
- * callback cannot fire on a readonly, disabled, or busy editor either.
174
- */
175
- _runAction(action) {
176
- const ai = this._ai;
177
- if (!ai || this._itemsDisabled())
178
- return;
179
- if (action.run) {
180
- action.run(ai);
181
- return;
182
- }
183
- void ai.runTransform(action.kind, {
184
- ...(action.instruction === undefined
185
- ? {}
186
- : { instruction: action.instruction }),
187
- output: action.output ?? 'replace-selection',
188
- });
189
- }
190
- /** @protected Opens the custom-prompt popup from its menu item. */
191
- _openPrompt() {
192
- this._popup()?.opened.set(true);
193
- }
194
- /** @protected Closes the custom-prompt popup without running a transform. */
195
- _closePrompt() {
196
- this._popup()?.opened.set(false);
197
- }
198
- /** @protected Runs the custom transform with the drafted instruction and mode. */
199
- _apply() {
200
- if (this._applyDisabled())
201
- return;
202
- const instruction = this._instruction().trim();
203
- void this._ai?.runTransform('custom', {
204
- instruction,
205
- output: this._output(),
206
- });
207
- this._popup()?.opened.set(false);
208
- }
209
- /** @protected Focuses and selects the instruction draft once the prompt opens. */
210
- _onPromptOpened() {
211
- const popup = this._popup();
212
- if (!popup)
213
- return;
214
- if (this._disabled()) {
215
- popup.opened.set(false);
216
- return;
217
- }
218
- queueMicrotask(() => {
219
- if (!popup.opened())
220
- return;
221
- this._instructionInput().focus();
222
- this._instructionInput().select();
223
- });
224
- }
225
- /** @protected Restores the connected toolbar trigger after final teardown. */
226
- _onPromptClosed() {
227
- if (!this._disabled() && this._triggerElement().nativeElement.isConnected) {
228
- this._triggerElement().nativeElement.focus();
229
- }
230
- }
231
- /** @protected Narrows the radio group's untyped value to an output mode. */
232
- _onOutputChange(value) {
233
- if (value === 'replace-selection' ||
234
- value === 'insert-below' ||
235
- value === 'review') {
236
- this._output.set(value);
237
- }
238
- }
239
- /** @protected Registers the portaled menu panel with the editor composite. */
240
- _registerMenuOverlay() {
241
- this._menuOpen = true;
242
- queueMicrotask(() => {
243
- if (!this._menuOpen)
244
- return;
245
- const panel = this._document.getElementById(this._menu().panelId);
246
- if (!panel)
247
- return;
248
- this._unregisterMenu?.();
249
- this._unregisterMenu = this._overlays.register(panel, () => this._menuTrigger().close());
250
- });
251
- }
252
- /** @protected Removes menu panel focus ownership after close. */
253
- _unregisterMenuOverlay() {
254
- this._menuOpen = false;
255
- this._unregisterMenu?.();
256
- this._unregisterMenu = undefined;
257
- }
258
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
259
- 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: [".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"] }, { 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"], 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 });
260
- }
261
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MlvEditorAiMenu, decorators: [{
262
- type: Component,
263
- args: [{ selector: 'mlv-editor-ai-menu', imports: [
264
- NgTemplateOutlet,
265
- MlvButton,
266
- MlvButtonIcon,
267
- MlvTooltip,
268
- LucideCircleStop,
269
- LucideSparkles,
270
- MlvEditorToolbarWidget,
271
- MlvInput,
272
- MlvListItem,
273
- MlvMenu,
274
- MlvMenuItem,
275
- MlvMenuTrigger,
276
- MlvPopup,
277
- MlvPopupContainer,
278
- MlvPopupContent,
279
- MlvRadio,
280
- MlvRadioGroup,
281
- ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: {
282
- class: 'mlv-editor-ai-menu',
283
- '[hidden]': '!_hasProvider()',
284
- }, 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: [".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"] }]
285
- }], 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', { ...{
286
- read: ElementRef,
287
- }, isSignal: true }] }], _instructionInput: [{ type: i0.ViewChild, args: ['_instructionInput', { isSignal: true }] }], _panel: [{ type: i0.ViewChild, args: ['_panel', { isSignal: true }] }] } });
288
- //# sourceMappingURL=editor-ai-menu.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"editor-ai-menu.js","sourceRoot":"","sources":["../../../../../../libs/editor/src/lib/ai/editor-ai-menu.ts","../../../../../../libs/editor/src/lib/ai/editor-ai-menu.html"],"names":[],"mappings":"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","sourcesContent":["import { DOCUMENT, NgTemplateOutlet } from '@angular/common';\nimport type { BooleanInput } from '@angular/cdk/coercion';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport type { Signal } from '@angular/core';\nimport {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n ViewEncapsulation,\n} from '@angular/core';\nimport { LucideCircleStop, LucideSparkles } from '@lucide/angular';\nimport { MlvButton, MlvButtonIcon } from '@malva-ui/core/button';\nimport { MlvInput } from '@malva-ui/core/input';\nimport { MlvListItem } from '@malva-ui/core/list';\nimport { MlvMenu, MlvMenuItem, MlvMenuTrigger } from '@malva-ui/core/menu';\nimport {\n MlvPopup,\n MlvPopupContainer,\n MlvPopupContent,\n} from '@malva-ui/core/popup';\nimport { MlvRadio, MlvRadioGroup } from '@malva-ui/core/radio';\nimport { MlvTooltip } from '@malva-ui/core/tooltip';\nimport { MLV_EDITOR_I18N } 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 { MlvEditorToolbarWidget } from '../toolbar/editor-toolbar-widget';\nimport { mlvEditorAiDefaultActions } from './editor-ai-actions';\nimport { MLV_EDITOR_AI_CONTEXT } from './editor-ai-context';\nimport type {\n MlvEditorAiAction,\n MlvEditorAiOutputMode,\n} from './editor-ai.types';\n\n/**\n * Toolbar command group for the editor's AI transforms.\n *\n * A sparkles trigger opens a Malva menu with one item per action; an action\n * runs its kind through the context with `output: 'replace-selection'` unless\n * it says otherwise, while the custom-prompt item opens a Malva popup with an\n * instruction field and an output-mode choice (replace selection, insert\n * below, or land as reviewable suggestions). While a transform is running the\n * same trigger becomes a stop affordance that cancels the in-flight request,\n * so the control stays one roving tab stop throughout.\n *\n * The action list is a thin layer over {@link MlvEditorAiContext.runTransform}:\n * the seven built-in kinds are only the default. `actions` replaces them\n * literally with a host-authored list — any kind string, a fixed instruction,\n * a per-action output mode, or an own `run` callback — and\n * `showCustomPrompt=\"false\"` drops the custom-prompt item and its popup.\n *\n * The group hides entirely when the nearest {@link MLV_EDITOR_AI_CONTEXT}\n * resolves no provider (presence), and its actions disable — without hiding —\n * while the editor is readonly or disabled or a transform is already running\n * (executability), matching the toolbar's presence-vs-executability rule.\n */\n@Component({\n selector: 'mlv-editor-ai-menu',\n imports: [\n NgTemplateOutlet,\n MlvButton,\n MlvButtonIcon,\n MlvTooltip,\n LucideCircleStop,\n LucideSparkles,\n MlvEditorToolbarWidget,\n MlvInput,\n MlvListItem,\n MlvMenu,\n MlvMenuItem,\n MlvMenuTrigger,\n MlvPopup,\n MlvPopupContainer,\n MlvPopupContent,\n MlvRadio,\n MlvRadioGroup,\n ],\n templateUrl: './editor-ai-menu.html',\n styleUrl: './editor-ai-menu.scss',\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'mlv-editor-ai-menu',\n '[hidden]': '!_hasProvider()',\n },\n})\nexport class MlvEditorAiMenu {\n /**\n * The menu's action list, replacing the built-in kinds literally — the\n * house rule for list-shaped inputs (`extensions`, the toolbar definitions):\n * a supplied array is never merged with the defaults.\n *\n * Omitted (the default), the menu renders the seven built-in transforms\n * labelled from `MLV_EDITOR_I18N`. To keep them and add to them, spread\n * `mlvEditorAiDefaultActions(copy)` — the very list this component uses.\n * The custom-prompt item is independent of this input; drop it with\n * {@link showCustomPrompt}.\n */\n readonly actions = input<readonly MlvEditorAiAction[] | undefined>(undefined);\n\n /**\n * Whether the built-in custom-prompt item and its popup exist at all.\n *\n * `false` removes the menu item and never renders the prompt popup, for\n * hosts whose action list already covers everything they want offered.\n */\n readonly showCustomPrompt = input<BooleanInput, boolean | string>(true, {\n transform: coerceBooleanProperty,\n });\n\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 Detached panel ownership for composite focus. */\n private readonly _overlays = inject<MlvEditorOverlayRegistry>(\n MLV_EDITOR_OVERLAY_REGISTRY,\n );\n\n /** @private Document used to resolve the public menu panel id. */\n private readonly _document = inject(DOCUMENT);\n\n /** @private Cleans an active menu-overlay registration. */\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 /** @protected Rendered Malva transforms menu. */\n protected readonly _menu = viewChild.required<MlvMenu>('_menu');\n\n /** @protected Trigger owning the menu overlay. */\n protected readonly _menuTrigger = viewChild.required(MlvMenuTrigger);\n\n /**\n * @protected Custom-prompt popup opened programmatically. Optional because\n * `showCustomPrompt=\"false\"` removes it from the template entirely.\n */\n protected readonly _popup = viewChild<MlvPopup>('_popup');\n\n /** @protected Native trigger restored after popup teardown. */\n protected readonly _triggerElement = viewChild.required('_triggerElement', {\n read: ElementRef,\n }) as Signal<ElementRef<HTMLButtonElement>>;\n\n /** @protected Instruction field focused when the prompt opens. */\n protected readonly _instructionInput =\n viewChild.required<MlvInput>('_instructionInput');\n\n /** @private Detached prompt panel queried through Angular rather than selectors. */\n private readonly _panel = viewChild<ElementRef<HTMLElement>>('_panel');\n\n /** @protected Custom-prompt instruction draft, retained across opens. */\n protected readonly _instruction = signal('');\n\n /** @protected Output mode applied to the custom transform. */\n protected readonly _output =\n signal<MlvEditorAiOutputMode>('replace-selection');\n\n /** @private Menu-overlay registration teardown. */\n private _unregisterMenu: (() => void) | undefined;\n\n /** @private Guards queued menu registration after a quick close or destroy. */\n private _menuOpen = false;\n\n /**\n * @protected Action entries rendered before the custom-prompt item: the\n * host's list when supplied, otherwise the localized built-ins. The factory\n * is the single source of the built-in label table.\n */\n protected readonly _actions = computed<readonly MlvEditorAiAction[]>(\n () => this.actions() ?? mlvEditorAiDefaultActions(this._i18n?.()),\n );\n\n /** @protected Reactive localized copy for the trigger and prompt. */\n protected readonly _copy = computed(() => {\n const copy = this._i18n?.();\n return {\n aiMenu: copy?.aiMenu ?? 'AI assist',\n aiCustom: copy?.aiCustom ?? 'Custom prompt',\n aiPromptPlaceholder:\n copy?.aiPromptPlaceholder ?? 'Describe what to do...',\n aiOutputMode: copy?.aiOutputMode ?? 'Output',\n aiReplaceSelection: copy?.aiReplaceSelection ?? 'Replace selection',\n aiInsertBelow: copy?.aiInsertBelow ?? 'Insert below',\n aiReviewChanges: copy?.aiReviewChanges ?? 'Review changes',\n aiApply: copy?.aiApply ?? 'Apply',\n aiCancel: copy?.aiCancel ?? 'Cancel',\n };\n });\n\n /** @protected Whether an AI provider is resolvable; the group hides without one. */\n protected readonly _hasProvider = computed(\n () => this._ai?.hasProvider() ?? false,\n );\n\n /** @protected Whether a transform is streaming, swapping the trigger to stop. */\n protected readonly _running = computed(\n () => this._ai?.status() === 'running',\n );\n\n /** @protected Whether readonly/disabled state blocks every AI mutation. */\n protected readonly _disabled = computed(\n () => this._context.disabled() || this._context.readonly(),\n );\n\n /** @protected Whether menu items cannot currently start a transform. */\n protected readonly _itemsDisabled = computed(\n () => (this._ai?.status() ?? 'idle') !== 'idle' || this._disabled(),\n );\n\n /** @protected Whether the apply action cannot submit the custom prompt. */\n protected readonly _applyDisabled = computed(\n () => this._itemsDisabled() || this._instruction().trim().length === 0,\n );\n\n constructor() {\n this._destroyRef.onDestroy(() => this._unregisterMenuOverlay());\n\n // Readonly and disabled editors refuse AI mutation, so the detached\n // prompt closes rather than lingering over a surface it cannot edit.\n effect(() => {\n const popup = this._popup();\n if (popup && this._disabled() && popup.opened()) {\n popup.opened.set(false);\n }\n });\n\n // The prompt panel joins the editor composite while rendered so focus\n // inside it never emits a false editor blur. Without the prompt there is\n // no panel to render, so nothing registers.\n effect((onCleanup) => {\n const panel = this._panel()?.nativeElement;\n if (!panel) return;\n const unregister = this._overlays.register(panel, () =>\n this._popup()?.opened.set(false),\n );\n onCleanup(unregister);\n });\n }\n\n /** @protected Stops the running transform; the menu trigger opens otherwise. */\n protected _onTriggerClick(): void {\n if (this._running()) this._ai?.cancel();\n }\n\n /**\n * @protected Runs one action: its own `run` callback when it has one,\n * otherwise its kind through the context with the action's instruction and\n * output mode. The executability guard is re-checked here so a host `run`\n * callback cannot fire on a readonly, disabled, or busy editor either.\n */\n protected _runAction(action: MlvEditorAiAction): void {\n const ai = this._ai;\n if (!ai || this._itemsDisabled()) return;\n if (action.run) {\n action.run(ai);\n return;\n }\n void ai.runTransform(action.kind, {\n ...(action.instruction === undefined\n ? {}\n : { instruction: action.instruction }),\n output: action.output ?? 'replace-selection',\n });\n }\n\n /** @protected Opens the custom-prompt popup from its menu item. */\n protected _openPrompt(): void {\n this._popup()?.opened.set(true);\n }\n\n /** @protected Closes the custom-prompt popup without running a transform. */\n protected _closePrompt(): void {\n this._popup()?.opened.set(false);\n }\n\n /** @protected Runs the custom transform with the drafted instruction and mode. */\n protected _apply(): void {\n if (this._applyDisabled()) return;\n const instruction = this._instruction().trim();\n void this._ai?.runTransform('custom', {\n instruction,\n output: this._output(),\n });\n this._popup()?.opened.set(false);\n }\n\n /** @protected Focuses and selects the instruction draft once the prompt opens. */\n protected _onPromptOpened(): void {\n const popup = this._popup();\n if (!popup) return;\n if (this._disabled()) {\n popup.opened.set(false);\n return;\n }\n queueMicrotask(() => {\n if (!popup.opened()) return;\n this._instructionInput().focus();\n this._instructionInput().select();\n });\n }\n\n /** @protected Restores the connected toolbar trigger after final teardown. */\n protected _onPromptClosed(): void {\n if (!this._disabled() && this._triggerElement().nativeElement.isConnected) {\n this._triggerElement().nativeElement.focus();\n }\n }\n\n /** @protected Narrows the radio group's untyped value to an output mode. */\n protected _onOutputChange(value: unknown): void {\n if (\n value === 'replace-selection' ||\n value === 'insert-below' ||\n value === 'review'\n ) {\n this._output.set(value);\n }\n }\n\n /** @protected Registers the portaled menu panel with the editor composite. */\n protected _registerMenuOverlay(): void {\n this._menuOpen = true;\n queueMicrotask(() => {\n if (!this._menuOpen) return;\n const panel = this._document.getElementById(this._menu().panelId);\n if (!panel) return;\n this._unregisterMenu?.();\n this._unregisterMenu = this._overlays.register(panel, () =>\n this._menuTrigger().close(),\n );\n });\n }\n\n /** @protected Removes menu panel focus ownership after close. */\n protected _unregisterMenuOverlay(): void {\n this._menuOpen = false;\n this._unregisterMenu?.();\n this._unregisterMenu = undefined;\n }\n}\n","<!-- 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"]}