@muxima-ui/core 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,575 @@
1
+ import * as i0 from '@angular/core';
2
+ import { model, input, numberAttribute, booleanAttribute, inject, viewChild, Injector, computed, signal, afterNextRender, DestroyRef, effect, untracked, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgControl, Validators } from '@angular/forms';
4
+ import { DomSanitizer } from '@angular/platform-browser';
5
+ import { Editor } from '@tiptap/core';
6
+ import StarterKit from '@tiptap/starter-kit';
7
+ import { Placeholder, CharacterCount } from '@tiptap/extensions';
8
+ import { MxFormField, mxUniqueId, MX_FORM_FIELD_CONTROL } from '@muxima-ui/core/form-field';
9
+
10
+ const MX_EDITOR_DEFAULT_TOOLS = [
11
+ 'bold',
12
+ 'italic',
13
+ 'underline',
14
+ 'strike',
15
+ '|',
16
+ 'heading2',
17
+ 'heading3',
18
+ '|',
19
+ 'bulletList',
20
+ 'orderedList',
21
+ 'blockquote',
22
+ '|',
23
+ 'link',
24
+ '|',
25
+ 'undo',
26
+ 'redo',
27
+ ];
28
+ const I = (d) => `<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
29
+ const TOOLS = {
30
+ bold: {
31
+ label: 'Negrito',
32
+ shortcut: 'Control+B',
33
+ icon: I('<path d="M4.5 3h4.2a2.5 2.5 0 0 1 0 5H4.5zM4.5 8h4.8a2.5 2.5 0 0 1 0 5H4.5z"/>'),
34
+ run: (e) => e.chain().focus().toggleBold().run(),
35
+ active: (e) => e.isActive('bold'),
36
+ toggle: true,
37
+ },
38
+ italic: {
39
+ label: 'Itálico',
40
+ shortcut: 'Control+I',
41
+ icon: I('<path d="M10 3H6.5M9.5 13H6M9 3l-2 10"/>'),
42
+ run: (e) => e.chain().focus().toggleItalic().run(),
43
+ active: (e) => e.isActive('italic'),
44
+ toggle: true,
45
+ },
46
+ underline: {
47
+ label: 'Sublinhado',
48
+ shortcut: 'Control+U',
49
+ icon: I('<path d="M4.5 3v4.5a3.5 3.5 0 0 0 7 0V3M4 13.5h8"/>'),
50
+ run: (e) => e.chain().focus().toggleUnderline().run(),
51
+ active: (e) => e.isActive('underline'),
52
+ toggle: true,
53
+ },
54
+ strike: {
55
+ label: 'Riscado',
56
+ icon: I('<path d="M3 8h10M10.5 4.5c-.5-1-1.5-1.5-2.7-1.5C6.2 3 5 3.9 5 5.2c0 .9.5 1.5 1.3 1.9M5.5 11.5c.5 1 1.6 1.5 2.9 1.5 1.7 0 2.9-.9 2.9-2.3 0-.5-.1-.9-.4-1.2"/>'),
57
+ run: (e) => e.chain().focus().toggleStrike().run(),
58
+ active: (e) => e.isActive('strike'),
59
+ toggle: true,
60
+ },
61
+ code: {
62
+ label: 'Código',
63
+ icon: I('<path d="M6 4.5L2.5 8 6 11.5M10 4.5L13.5 8 10 11.5"/>'),
64
+ run: (e) => e.chain().focus().toggleCode().run(),
65
+ active: (e) => e.isActive('code'),
66
+ toggle: true,
67
+ },
68
+ heading2: {
69
+ label: 'Título',
70
+ icon: I('<path d="M3 3.5v9M9 3.5v9M3 8h6M11.5 7.5c.3-.6.9-1 1.6-1 .9 0 1.4.6 1.4 1.3 0 1.3-3 2.4-3 4.7h3"/>'),
71
+ run: (e) => e.chain().focus().toggleHeading({ level: 2 }).run(),
72
+ active: (e) => e.isActive('heading', { level: 2 }),
73
+ toggle: true,
74
+ },
75
+ heading3: {
76
+ label: 'Subtítulo',
77
+ icon: I('<path d="M3 3.5v9M9 3.5v9M3 8h6M11.5 7c.3-.5.8-.7 1.4-.7.8 0 1.3.5 1.3 1.1 0 .7-.6 1.1-1.3 1.1.8 0 1.5.4 1.5 1.2 0 .8-.6 1.3-1.5 1.3-.6 0-1.1-.3-1.4-.8"/>'),
78
+ run: (e) => e.chain().focus().toggleHeading({ level: 3 }).run(),
79
+ active: (e) => e.isActive('heading', { level: 3 }),
80
+ toggle: true,
81
+ },
82
+ paragraph: {
83
+ label: 'Parágrafo',
84
+ icon: I('<path d="M10 13V3M12.5 13V3M13.5 3H7.5a2.5 2.5 0 0 0 0 5H10"/>'),
85
+ run: (e) => e.chain().focus().setParagraph().run(),
86
+ active: (e) => e.isActive('paragraph'),
87
+ toggle: true,
88
+ },
89
+ bulletList: {
90
+ label: 'Lista',
91
+ icon: I('<path d="M6.5 4h7M6.5 8h7M6.5 12h7"/><circle cx="3" cy="4" r=".6" fill="currentColor"/><circle cx="3" cy="8" r=".6" fill="currentColor"/><circle cx="3" cy="12" r=".6" fill="currentColor"/>'),
92
+ run: (e) => e.chain().focus().toggleBulletList().run(),
93
+ active: (e) => e.isActive('bulletList'),
94
+ toggle: true,
95
+ },
96
+ orderedList: {
97
+ label: 'Lista numerada',
98
+ icon: I('<path d="M6.5 4h7M6.5 8h7M6.5 12h7M2.5 3l1-.5V6M2.5 9.5c.2-.5.6-.7 1-.7.5 0 .9.3.9.8 0 .8-1.9 1.3-1.9 2.4h1.9"/>'),
99
+ run: (e) => e.chain().focus().toggleOrderedList().run(),
100
+ active: (e) => e.isActive('orderedList'),
101
+ toggle: true,
102
+ },
103
+ blockquote: {
104
+ label: 'Citação',
105
+ icon: I('<path d="M3 4.5h4v4H4.5L3 11.5zM9 4.5h4v4h-2.5L9 11.5z"/>'),
106
+ run: (e) => e.chain().focus().toggleBlockquote().run(),
107
+ active: (e) => e.isActive('blockquote'),
108
+ toggle: true,
109
+ },
110
+ link: {
111
+ label: 'Ligação',
112
+ shortcut: 'Control+K',
113
+ icon: I('<path d="M6.5 9.5l3-3M7 4.5l1-1a2.8 2.8 0 0 1 4 4l-1 1M9 11.5l-1 1a2.8 2.8 0 0 1-4-4l1-1"/>'),
114
+ run: () => { },
115
+ active: (e) => e.isActive('link'),
116
+ toggle: true,
117
+ },
118
+ undo: {
119
+ label: 'Anular',
120
+ shortcut: 'Control+Z',
121
+ icon: I('<path d="M5.5 3.5L2.5 6.5l3 3M2.5 6.5H10a3.5 3.5 0 0 1 0 7H7"/>'),
122
+ run: (e) => e.chain().focus().undo().run(),
123
+ enabled: (e) => e.can().undo(),
124
+ },
125
+ redo: {
126
+ label: 'Refazer',
127
+ shortcut: 'Control+Shift+Z',
128
+ icon: I('<path d="M10.5 3.5l3 3-3 3M13.5 6.5H6a3.5 3.5 0 0 0 0 7h3"/>'),
129
+ run: (e) => e.chain().focus().redo().run(),
130
+ enabled: (e) => e.can().redo(),
131
+ },
132
+ };
133
+ /**
134
+ * Editor de texto com formatação, sobre o Tiptap (ProseMirror). O Tiptap é uma dependência
135
+ * opcional: instala-o só se usares este ponto de entrada.
136
+ *
137
+ * ```bash
138
+ * npm install @tiptap/core @tiptap/pm @tiptap/starter-kit @tiptap/extensions
139
+ * ```
140
+ * ```html
141
+ * <mx-form-field>
142
+ * <label mxLabel>Notas da factura</label>
143
+ * <mx-editor formControlName="notas" placeholder="Escreve aqui..." [maxLength]="2000" />
144
+ * </mx-form-field>
145
+ * ```
146
+ *
147
+ * O valor é HTML (string; vazio quando não há texto). A barra é um role=toolbar:
148
+ * setas entre botões, Alt + F10 vai do texto para a barra e Esc volta ao texto.
149
+ */
150
+ class MxEditor {
151
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
152
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
153
+ tools = TOOLS;
154
+ toolbar = input(MX_EDITOR_DEFAULT_TOOLS, ...(ngDevMode ? [{ debugName: "toolbar" }] : /* istanbul ignore next */ []));
155
+ /** Máximo de caracteres; mostra a contagem. */
156
+ maxLength = input(null, { ...(ngDevMode ? { debugName: "maxLength" } : /* istanbul ignore next */ {}), transform: (v) => (v == null || v === '' ? null : numberAttribute(v)) });
157
+ minHeight = input('140px', ...(ngDevMode ? [{ debugName: "minHeight" }] : /* istanbul ignore next */ []));
158
+ maxHeight = input('420px', ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
159
+ readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
160
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
161
+ required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
162
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
163
+ id = input(undefined, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
164
+ ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
165
+ toolbarLabel = input('Formatação', ...(ngDevMode ? [{ debugName: "toolbarLabel" }] : /* istanbul ignore next */ []));
166
+ linkLabel = input('Endereço da ligação', ...(ngDevMode ? [{ debugName: "linkLabel" }] : /* istanbul ignore next */ []));
167
+ field = inject(MxFormField, { optional: true });
168
+ ngControl = inject(NgControl, { self: true, optional: true });
169
+ content = viewChild.required('content');
170
+ toolbarEl = viewChild('toolbar', ...(ngDevMode ? [{ debugName: "toolbarEl" }] : /* istanbul ignore next */ []));
171
+ linkInput = viewChild('linkInput', ...(ngDevMode ? [{ debugName: "linkInput" }] : /* istanbul ignore next */ []));
172
+ injector = inject(Injector);
173
+ generatedId = mxUniqueId('mx-editor');
174
+ controlId = computed(() => this.id() ?? this.generatedId, ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
175
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
176
+ isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
177
+ toolList = computed(() => this.toolbar(), ...(ngDevMode ? [{ debugName: "toolList" }] : /* istanbul ignore next */ []));
178
+ focusIndex = signal(0, ...(ngDevMode ? [{ debugName: "focusIndex" }] : /* istanbul ignore next */ []));
179
+ /** Muda a cada transacção, para os botões reflectirem o estado. */
180
+ tick = signal(0, ...(ngDevMode ? [{ debugName: "tick" }] : /* istanbul ignore next */ []));
181
+ count = signal(0, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
182
+ linkOpen = signal(false, ...(ngDevMode ? [{ debugName: "linkOpen" }] : /* istanbul ignore next */ []));
183
+ linkUrl = signal('', ...(ngDevMode ? [{ debugName: "linkUrl" }] : /* istanbul ignore next */ []));
184
+ linkActive = signal(false, ...(ngDevMode ? [{ debugName: "linkActive" }] : /* istanbul ignore next */ []));
185
+ /** O editor do Tiptap, para comandos próprios (editor()?.chain()...). */
186
+ editor = signal(null, ...(ngDevMode ? [{ debugName: "editor" }] : /* istanbul ignore next */ []));
187
+ writing = false;
188
+ onChange = () => { };
189
+ onTouched = () => { };
190
+ constructor() {
191
+ if (this.ngControl)
192
+ this.ngControl.valueAccessor = this;
193
+ afterNextRender(() => this.create());
194
+ inject(DestroyRef).onDestroy(() => this.editor()?.destroy());
195
+ effect(() => {
196
+ const e = this.editor();
197
+ const editable = !this.isDisabled() && !this.readonly();
198
+ if (e && e.isEditable !== editable)
199
+ e.setEditable(editable);
200
+ });
201
+ // aria do conteúdo acompanha o form field
202
+ effect(() => {
203
+ const e = this.editor();
204
+ this.tick();
205
+ if (!e)
206
+ return;
207
+ const el = e.view.dom;
208
+ const set = (k, v) => v == null ? el.removeAttribute(k) : el.setAttribute(k, v);
209
+ set('aria-labelledby', this.field?.labelId() ?? null);
210
+ set('aria-label', this.field?.labelId() ? null : (this.ariaLabel() ?? null));
211
+ set('aria-describedby', this.field?.describedBy() ?? null);
212
+ set('aria-invalid', this.errorState() ? 'true' : null);
213
+ set('aria-required', this.isRequired() ? 'true' : null);
214
+ set('aria-readonly', this.readonly() || this.isDisabled() ? 'true' : null);
215
+ });
216
+ }
217
+ create() {
218
+ const editor = new Editor({
219
+ element: this.content().nativeElement,
220
+ content: untracked(() => this.value()) || '',
221
+ editable: !this.isDisabled() && !this.readonly(),
222
+ extensions: [
223
+ StarterKit.configure({
224
+ heading: { levels: [2, 3, 4] },
225
+ link: { openOnClick: false, autolink: true, defaultProtocol: 'https' },
226
+ }),
227
+ Placeholder.configure({ placeholder: () => this.placeholder() }),
228
+ CharacterCount.configure({ limit: null }),
229
+ ],
230
+ editorProps: {
231
+ attributes: {
232
+ id: this.controlId(),
233
+ role: 'textbox',
234
+ 'aria-multiline': 'true',
235
+ class: 'mx-editor-prose',
236
+ },
237
+ handleKeyDown: (_view, event) => {
238
+ if (event.altKey && event.key === 'F10') {
239
+ event.preventDefault();
240
+ this.focusToolbar();
241
+ return true;
242
+ }
243
+ if ((event.ctrlKey || event.metaKey) &&
244
+ event.key.toLowerCase() === 'k' &&
245
+ this.toolList().includes('link')) {
246
+ event.preventDefault();
247
+ // dentro do texto, Ctrl + K é a ligação (não o atalho global da aplicação)
248
+ event.stopPropagation();
249
+ this.openLink();
250
+ return true;
251
+ }
252
+ return false;
253
+ },
254
+ },
255
+ onTransaction: () => this.tick.update((t) => t + 1),
256
+ onUpdate: ({ editor: e }) => {
257
+ this.count.set(e.storage.characterCount?.characters?.() ?? e.getText().length);
258
+ if (this.writing)
259
+ return;
260
+ const html = e.isEmpty ? '' : e.getHTML();
261
+ this.value.set(html);
262
+ this.onChange(html);
263
+ },
264
+ onBlur: () => this.onTouched(),
265
+ });
266
+ this.count.set(editor.storage.characterCount?.characters?.() ?? editor.getText().length);
267
+ this.editor.set(editor);
268
+ }
269
+ sanitizer = inject(DomSanitizer);
270
+ icons = new Map();
271
+ icon(t) {
272
+ // os desenhos são constantes deste ficheiro, nunca texto de fora
273
+ if (!this.icons.has(t))
274
+ this.icons.set(t, this.sanitizer.bypassSecurityTrustHtml(TOOLS[t].icon));
275
+ return this.icons.get(t);
276
+ }
277
+ isActive(t) {
278
+ this.tick();
279
+ const e = this.editor();
280
+ return !!e && !!TOOLS[t].active?.(e);
281
+ }
282
+ isEnabled(t) {
283
+ this.tick();
284
+ const e = this.editor();
285
+ return !!e && (TOOLS[t].enabled ? TOOLS[t].enabled(e) : true);
286
+ }
287
+ runTool(t, index) {
288
+ this.focusIndex.set(index);
289
+ const e = this.editor();
290
+ if (!e)
291
+ return;
292
+ if (t === 'link') {
293
+ this.openLink();
294
+ return;
295
+ }
296
+ TOOLS[t].run(e);
297
+ }
298
+ // ---- ligações ----
299
+ openLink() {
300
+ const e = this.editor();
301
+ if (!e)
302
+ return;
303
+ this.linkActive.set(e.isActive('link'));
304
+ this.linkUrl.set(e.getAttributes('link')['href'] ?? '');
305
+ this.linkOpen.set(true);
306
+ afterNextRender(() => this.linkInput()?.nativeElement.focus(), { injector: this.injector });
307
+ }
308
+ applyLink() {
309
+ const e = this.editor();
310
+ const url = this.linkUrl().trim();
311
+ if (!e)
312
+ return;
313
+ if (!url)
314
+ this.removeLink();
315
+ else {
316
+ const href = /^(https?:|mailto:|tel:|\/|#)/i.test(url) ? url : `https://${url}`;
317
+ e.chain().focus().extendMarkRange('link').setLink({ href }).run();
318
+ }
319
+ this.linkOpen.set(false);
320
+ }
321
+ removeLink() {
322
+ this.editor()?.chain().focus().extendMarkRange('link').unsetLink().run();
323
+ this.linkOpen.set(false);
324
+ }
325
+ closeLink() {
326
+ this.linkOpen.set(false);
327
+ this.editor()?.commands.focus();
328
+ }
329
+ // ---- barra (roving tabindex) ----
330
+ buttons() {
331
+ return Array.from(this.toolbarEl()?.nativeElement.querySelectorAll('.mx-editor-tool:not(:disabled)') ?? []);
332
+ }
333
+ focusToolbar() {
334
+ const b = this.buttons();
335
+ const current = this.toolbarEl()?.nativeElement.querySelector('.mx-editor-tool[tabindex="0"]:not(:disabled)');
336
+ (current ?? b[0])?.focus();
337
+ }
338
+ onToolbarKey(event) {
339
+ const b = this.buttons();
340
+ const i = b.indexOf(document.activeElement);
341
+ let next = -1;
342
+ if (event.key === 'ArrowRight')
343
+ next = (i + 1) % b.length;
344
+ else if (event.key === 'ArrowLeft')
345
+ next = (i - 1 + b.length) % b.length;
346
+ else if (event.key === 'Home')
347
+ next = 0;
348
+ else if (event.key === 'End')
349
+ next = b.length - 1;
350
+ else if (event.key === 'Escape') {
351
+ event.preventDefault();
352
+ this.editor()?.commands.focus();
353
+ return;
354
+ }
355
+ if (next < 0 || !b[next])
356
+ return;
357
+ event.preventDefault();
358
+ const all = Array.from(this.toolbarEl().nativeElement.querySelectorAll('.mx-editor-tool, .mx-editor-sep'));
359
+ this.focusIndex.set(all.indexOf(b[next]));
360
+ b[next].focus();
361
+ }
362
+ /** Põe o foco no texto. */
363
+ focus() {
364
+ this.editor()?.commands.focus();
365
+ }
366
+ // ---- MxFormFieldControl ----
367
+ errorState() {
368
+ if (this.invalid())
369
+ return true;
370
+ const max = this.maxLength();
371
+ if (max && this.count() > max)
372
+ return true;
373
+ const c = this.ngControl;
374
+ return !!c && !!c.invalid && !!(c.touched || c.dirty);
375
+ }
376
+ isRequired() {
377
+ return this.required() || !!this.ngControl?.control?.hasValidator?.(Validators.required);
378
+ }
379
+ // ---- ControlValueAccessor ----
380
+ writeValue(value) {
381
+ const html = value ?? '';
382
+ this.value.set(html);
383
+ const e = this.editor();
384
+ if (e && (e.isEmpty ? '' : e.getHTML()) !== html) {
385
+ this.writing = true;
386
+ e.commands.setContent(html, { emitUpdate: false });
387
+ this.count.set(e.storage.characterCount?.characters?.() ?? e.getText().length);
388
+ this.writing = false;
389
+ }
390
+ }
391
+ registerOnChange(fn) {
392
+ this.onChange = fn;
393
+ }
394
+ registerOnTouched(fn) {
395
+ this.onTouched = fn;
396
+ }
397
+ setDisabledState(isDisabled) {
398
+ this.formDisabled.set(isDisabled);
399
+ }
400
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
401
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MxEditor, isStandalone: true, selector: "mx-editor", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, toolbarLabel: { classPropertyName: "toolbarLabel", publicName: "toolbarLabel", isSignal: true, isRequired: false, transformFunction: null }, linkLabel: { classPropertyName: "linkLabel", publicName: "linkLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "attr.data-invalid": "errorState() ? \"\" : null", "attr.data-disabled": "isDisabled() ? \"\" : null", "attr.data-readonly": "readonly() ? \"\" : null", "attr.aria-label": "null" }, classAttribute: "mx-editor" }, providers: [{ provide: MX_FORM_FIELD_CONTROL, useExisting: MxEditor }], viewQueries: [{ propertyName: "content", first: true, predicate: ["content"], descendants: true, isSignal: true }, { propertyName: "toolbarEl", first: true, predicate: ["toolbar"], descendants: true, isSignal: true }, { propertyName: "linkInput", first: true, predicate: ["linkInput"], descendants: true, isSignal: true }], exportAs: ["mxEditor"], ngImport: i0, template: `
402
+ @if (!readonly()) {
403
+ <div
404
+ class="mx-editor-toolbar"
405
+ role="toolbar"
406
+ #toolbar
407
+ [attr.aria-label]="toolbarLabel()"
408
+ [attr.aria-controls]="controlId()"
409
+ (keydown)="onToolbarKey($event)"
410
+ >
411
+ @for (t of toolList(); track $index) {
412
+ @if (t === '|') {
413
+ <span class="mx-editor-sep" role="separator" aria-orientation="vertical"></span>
414
+ } @else {
415
+ <button
416
+ type="button"
417
+ class="mx-editor-tool"
418
+ [tabIndex]="$index === focusIndex() ? 0 : -1"
419
+ [attr.data-tool]="t"
420
+ [attr.aria-label]="tools[t].label"
421
+ [attr.aria-keyshortcuts]="tools[t].shortcut ?? null"
422
+ [attr.aria-pressed]="tools[t].toggle ? isActive(t) : null"
423
+ [disabled]="isDisabled() || !isEnabled(t)"
424
+ (mousedown)="$event.preventDefault()"
425
+ (click)="runTool(t, $index)"
426
+ >
427
+ <span class="mx-editor-icon" [innerHTML]="icon(t)"></span>
428
+ </button>
429
+ }
430
+ }
431
+ </div>
432
+ @if (linkOpen()) {
433
+ <div class="mx-editor-linkbar" role="group" [attr.aria-label]="linkLabel()">
434
+ <input
435
+ #linkInput
436
+ class="mx-input"
437
+ data-size="sm"
438
+ type="url"
439
+ placeholder="https://"
440
+ [attr.aria-label]="linkLabel()"
441
+ [value]="linkUrl()"
442
+ (input)="linkUrl.set($any($event.target).value)"
443
+ (keydown.enter)="$event.preventDefault(); applyLink()"
444
+ (keydown.escape)="$event.preventDefault(); closeLink()"
445
+ />
446
+ <button type="button" class="mx-editor-text-button" (click)="applyLink()">Aplicar</button>
447
+ @if (linkActive()) {
448
+ <button type="button" class="mx-editor-text-button" (click)="removeLink()">
449
+ Remover
450
+ </button>
451
+ }
452
+ <button type="button" class="mx-editor-text-button" (click)="closeLink()">
453
+ Cancelar
454
+ </button>
455
+ </div>
456
+ }
457
+ }
458
+ <div
459
+ class="mx-editor-content"
460
+ #content
461
+ [style.min-height]="minHeight()"
462
+ [style.max-height]="maxHeight()"
463
+ ></div>
464
+ @if (maxLength()) {
465
+ <div class="mx-editor-footer">
466
+ <span
467
+ class="mx-editor-count"
468
+ [attr.data-over]="count() > maxLength()! ? '' : null"
469
+ [attr.aria-live]="count() >= maxLength()! * 0.9 ? 'polite' : null"
470
+ >
471
+ {{ count() }} / {{ maxLength() }}<span class="mx-visually-hidden"> caracteres</span>
472
+ </span>
473
+ </div>
474
+ }
475
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
476
+ }
477
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MxEditor, decorators: [{
478
+ type: Component,
479
+ args: [{
480
+ selector: 'mx-editor',
481
+ exportAs: 'mxEditor',
482
+ providers: [{ provide: MX_FORM_FIELD_CONTROL, useExisting: MxEditor }],
483
+ changeDetection: ChangeDetectionStrategy.OnPush,
484
+ encapsulation: ViewEncapsulation.None,
485
+ host: {
486
+ class: 'mx-editor',
487
+ '[attr.data-invalid]': 'errorState() ? "" : null',
488
+ '[attr.data-disabled]': 'isDisabled() ? "" : null',
489
+ '[attr.data-readonly]': 'readonly() ? "" : null',
490
+ '[attr.aria-label]': 'null',
491
+ },
492
+ template: `
493
+ @if (!readonly()) {
494
+ <div
495
+ class="mx-editor-toolbar"
496
+ role="toolbar"
497
+ #toolbar
498
+ [attr.aria-label]="toolbarLabel()"
499
+ [attr.aria-controls]="controlId()"
500
+ (keydown)="onToolbarKey($event)"
501
+ >
502
+ @for (t of toolList(); track $index) {
503
+ @if (t === '|') {
504
+ <span class="mx-editor-sep" role="separator" aria-orientation="vertical"></span>
505
+ } @else {
506
+ <button
507
+ type="button"
508
+ class="mx-editor-tool"
509
+ [tabIndex]="$index === focusIndex() ? 0 : -1"
510
+ [attr.data-tool]="t"
511
+ [attr.aria-label]="tools[t].label"
512
+ [attr.aria-keyshortcuts]="tools[t].shortcut ?? null"
513
+ [attr.aria-pressed]="tools[t].toggle ? isActive(t) : null"
514
+ [disabled]="isDisabled() || !isEnabled(t)"
515
+ (mousedown)="$event.preventDefault()"
516
+ (click)="runTool(t, $index)"
517
+ >
518
+ <span class="mx-editor-icon" [innerHTML]="icon(t)"></span>
519
+ </button>
520
+ }
521
+ }
522
+ </div>
523
+ @if (linkOpen()) {
524
+ <div class="mx-editor-linkbar" role="group" [attr.aria-label]="linkLabel()">
525
+ <input
526
+ #linkInput
527
+ class="mx-input"
528
+ data-size="sm"
529
+ type="url"
530
+ placeholder="https://"
531
+ [attr.aria-label]="linkLabel()"
532
+ [value]="linkUrl()"
533
+ (input)="linkUrl.set($any($event.target).value)"
534
+ (keydown.enter)="$event.preventDefault(); applyLink()"
535
+ (keydown.escape)="$event.preventDefault(); closeLink()"
536
+ />
537
+ <button type="button" class="mx-editor-text-button" (click)="applyLink()">Aplicar</button>
538
+ @if (linkActive()) {
539
+ <button type="button" class="mx-editor-text-button" (click)="removeLink()">
540
+ Remover
541
+ </button>
542
+ }
543
+ <button type="button" class="mx-editor-text-button" (click)="closeLink()">
544
+ Cancelar
545
+ </button>
546
+ </div>
547
+ }
548
+ }
549
+ <div
550
+ class="mx-editor-content"
551
+ #content
552
+ [style.min-height]="minHeight()"
553
+ [style.max-height]="maxHeight()"
554
+ ></div>
555
+ @if (maxLength()) {
556
+ <div class="mx-editor-footer">
557
+ <span
558
+ class="mx-editor-count"
559
+ [attr.data-over]="count() > maxLength()! ? '' : null"
560
+ [attr.aria-live]="count() >= maxLength()! * 0.9 ? 'polite' : null"
561
+ >
562
+ {{ count() }} / {{ maxLength() }}<span class="mx-visually-hidden"> caracteres</span>
563
+ </span>
564
+ </div>
565
+ }
566
+ `,
567
+ }]
568
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], toolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbar", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], toolbarLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarLabel", required: false }] }], linkLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "linkLabel", required: false }] }], content: [{ type: i0.ViewChild, args: ['content', { isSignal: true }] }], toolbarEl: [{ type: i0.ViewChild, args: ['toolbar', { isSignal: true }] }], linkInput: [{ type: i0.ViewChild, args: ['linkInput', { isSignal: true }] }] } });
569
+
570
+ /**
571
+ * Generated bundle index. Do not edit.
572
+ */
573
+
574
+ export { MX_EDITOR_DEFAULT_TOOLS, MxEditor };
575
+ //# sourceMappingURL=muxima-ui-core-editor.mjs.map