@elasticias/ui 1.0.53 → 1.0.54

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,336 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, EventEmitter, inject, ElementRef, linkedSignal, signal, effect, untracked, Output, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { NgControl } from '@angular/forms';
5
+ import * as i1 from '@ngx-translate/core';
6
+ import { TranslateModule } from '@ngx-translate/core';
7
+ import { merge } from 'rxjs';
8
+ import { Editor } from 'primeng/editor';
9
+ import { AbstractEfFormControl, EfLabelComponent, EF_RICH_TEXT } from '@elasticias/ui';
10
+
11
+ /** The tags ef-rich-text's toolbar can make. */
12
+ const ALLOWED = new Set(['P', 'BR', 'STRONG', 'EM', 'U', 'OL', 'UL', 'LI', 'A']);
13
+ /** Removed with everything inside them. */
14
+ const DROPPED = new Set(['SCRIPT', 'STYLE', 'IFRAME', 'OBJECT', 'EMBED', 'TEMPLATE', 'NOSCRIPT', 'SVG', 'MATH']);
15
+ const SAFE_HREF = /^(https?:|mailto:|tel:)/i;
16
+ /**
17
+ * Whitelists the HTML an ef-rich-text produces: the toolbar's tags, a link's
18
+ * `href` when it is http(s), mailto or tel (with `rel` and `target` set by us),
19
+ * and no other attribute, so no style, class or event handler leaves the
20
+ * browser. Other tags are unwrapped, keeping their text; script, style and
21
+ * frames go with their content. Quill's non-breaking spaces between words
22
+ * become spaces. An editor holding no text returns `''`, never `<p><br></p>`.
23
+ *
24
+ * Browser only: it parses into a `<template>` and so needs `document`.
25
+ *
26
+ * Security note: `npm audit` flags quill 2.0.3 (GHSA-v3m3-f69x-jf25, XSS via
27
+ * HTML export; low severity, no patched release). It is mitigated here
28
+ * because every value ef-rich-text emits goes through this function, and
29
+ * incoming HTML goes through it before Quill parses it. Never render
30
+ * `getSemanticHTML()` without it.
31
+ */
32
+ function sanitizeRichHtml(html) {
33
+ if (!html)
34
+ return '';
35
+ const tpl = document.createElement('template');
36
+ tpl.innerHTML = html;
37
+ clean(tpl.content);
38
+ if (!(tpl.content.textContent ?? '').trim())
39
+ return '';
40
+ const out = document.createElement('div');
41
+ out.appendChild(tpl.content);
42
+ return out.innerHTML;
43
+ }
44
+ function clean(parent) {
45
+ for (const node of Array.from(parent.childNodes)) {
46
+ if (node.nodeType === Node.TEXT_NODE) {
47
+ node.textContent = (node.textContent ?? '').replace(/\u00a0/g, ' ');
48
+ continue;
49
+ }
50
+ if (node.nodeType !== Node.ELEMENT_NODE) {
51
+ node.parentNode?.removeChild(node);
52
+ continue;
53
+ }
54
+ const el = node;
55
+ const tag = el.tagName.toUpperCase();
56
+ if (DROPPED.has(tag) || el.classList.contains('ql-ui')) {
57
+ el.remove();
58
+ continue;
59
+ }
60
+ clean(el);
61
+ if (!ALLOWED.has(tag)) {
62
+ el.replaceWith(...Array.from(el.childNodes));
63
+ continue;
64
+ }
65
+ const href = tag === 'A' ? (el.getAttribute('href') ?? '').trim() : '';
66
+ for (const attr of Array.from(el.attributes))
67
+ el.removeAttribute(attr.name);
68
+ if (tag === 'A') {
69
+ if (!SAFE_HREF.test(href)) {
70
+ el.replaceWith(...Array.from(el.childNodes));
71
+ continue;
72
+ }
73
+ el.setAttribute('href', href);
74
+ el.setAttribute('rel', 'noopener noreferrer');
75
+ el.setAttribute('target', '_blank');
76
+ }
77
+ }
78
+ }
79
+
80
+ /** What the toolbar can make, so a paste cannot bring in headings, images or colours. */
81
+ const FORMATS = ['bold', 'italic', 'underline', 'list', 'link'];
82
+ /** Tab leaves the editor instead of inserting a tab or indenting a list (WCAG 2.1.2). */
83
+ const LEAVE_ON_TAB = { key: 'Tab', handler: () => true };
84
+ const MODULES = {
85
+ keyboard: {
86
+ bindings: {
87
+ tab: LEAVE_ON_TAB,
88
+ 'indent code-block': null,
89
+ 'outdent code-block': null,
90
+ 'remove tab': null,
91
+ indent: { ...LEAVE_ON_TAB, format: ['blockquote', 'indent', 'list'] },
92
+ outdent: { ...LEAVE_ON_TAB, shiftKey: true, format: ['blockquote', 'indent', 'list'] },
93
+ },
94
+ },
95
+ };
96
+ let nextId = 0;
97
+ /**
98
+ * A rich text field: PrimeNG's Editor (Quill 2) with a small toolbar (bold,
99
+ * italic, underline, bulleted and numbered lists, link, clear formatting) and
100
+ * sanitised HTML out (`sanitizeRichHtml`): the toolbar's tags only, safe links,
101
+ * no script, style, class or event attribute, and `''` for an empty editor.
102
+ *
103
+ * It lives in `@elasticias/ui/rich-text` so that only apps that use it bundle
104
+ * the editor and need `quill` (a peer dependency of this entry). PrimeNG loads
105
+ * Quill with a dynamic import the first time an editor renders, so a screen
106
+ * without one never downloads it. `ef-localized-input`'s `rich` mode renders
107
+ * it through `EF_RICH_TEXT`; provide that with `provideEfRichText()`.
108
+ *
109
+ * - `dir` and `lang` apply to the editing area only; the toolbar is page chrome.
110
+ * - Disabled by `[disabled]`, a form's `disable()` or `[efReadOnly]`; `readonly`
111
+ * keeps the text selectable. The toolbar is disabled with it.
112
+ * - The editing area is a `role="textbox"` named by the label and described
113
+ * by the hint, the errors and `describedByExtra`.
114
+ * - An incoming value equal to what it last emitted is ignored, so binding
115
+ * the output back never resets the caret.
116
+ *
117
+ * i18n keys the app's bundles must define (toolbar names and the link bubble,
118
+ * whose Quill texts are otherwise hardcoded English):
119
+ *
120
+ * | key | fr | ar | en |
121
+ * |---|---|---|---|
122
+ * | `ef_rich_text_bold` | Gras | غامق | Bold |
123
+ * | `ef_rich_text_italic` | Italique | مائل | Italic |
124
+ * | `ef_rich_text_underline` | Souligné | تسطير | Underline |
125
+ * | `ef_rich_text_bullet_list` | Liste à puces | قائمة نقطية | Bulleted list |
126
+ * | `ef_rich_text_ordered_list` | Liste numérotée | قائمة مرقمة | Numbered list |
127
+ * | `ef_rich_text_link` | Lien | رابط | Link |
128
+ * | `ef_rich_text_clean` | Effacer la mise en forme | مسح التنسيق | Clear formatting |
129
+ * | `ef_rich_text_toolbar` | Mise en forme | التنسيق | Formatting |
130
+ * | `ef_rich_text_link_enter` | Adresse du lien : | عنوان الرابط: | Link address: |
131
+ * | `ef_rich_text_link_visit` | Ouvrir : | فتح: | Open: |
132
+ * | `ef_rich_text_link_edit` | Modifier | تعديل | Edit |
133
+ * | `ef_rich_text_link_remove` | Retirer | إزالة | Remove |
134
+ * | `ef_rich_text_link_save` | Enregistrer | حفظ | Save |
135
+ *
136
+ * ```html
137
+ * <ef-rich-text name="description" labelKey="product_description" dir="rtl" lang="ar"
138
+ * [value]="html()" (valueChangeEvent)="html.set($event)" />
139
+ * ```
140
+ */
141
+ class EfRichTextComponent extends AbstractEfFormControl {
142
+ value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
143
+ /** Direction of the text (`rtl` for Arabic), on the editing area only. */
144
+ dir = input(null, ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
145
+ /** Language of the text, on the editing area only. */
146
+ lang = input(null, ...(ngDevMode ? [{ debugName: "lang" }] : /* istanbul ignore next */ []));
147
+ /** Id(s) naming the editing area when the label is rendered elsewhere
148
+ * (`ef-localized-input` draws one label above its culture tabs). */
149
+ labelledBy = input(null, ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
150
+ /** Visible height of the editing area, in lines. */
151
+ rows = input(4, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
152
+ /** Sanitised HTML, `''` when the editor holds no text. */
153
+ valueChangeEvent = new EventEmitter();
154
+ ngControl = inject(NgControl, { self: true, optional: true });
155
+ host = inject(ElementRef);
156
+ formats = FORMATS;
157
+ modules = MODULES;
158
+ labelId = `ef-rich-text-${nextId++}-label`;
159
+ current = linkedSignal(() => this.value() ?? '', ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
160
+ quill = signal(null, ...(ngDevMode ? [{ debugName: "quill" }] : /* istanbul ignore next */ []));
161
+ lastEmitted = null;
162
+ onChange = () => { };
163
+ onTouched = () => { };
164
+ constructor() {
165
+ super();
166
+ if (this.ngControl)
167
+ this.ngControl.valueAccessor = this;
168
+ effect(() => {
169
+ const html = this.current();
170
+ const q = this.quill();
171
+ if (q)
172
+ untracked(() => this.apply(q, html));
173
+ });
174
+ // Attributes that follow signals are written when those change, not
175
+ // on every check of the parent.
176
+ effect(() => {
177
+ const q = this.quill();
178
+ if (q)
179
+ this.decorateFromInputs(q.root);
180
+ });
181
+ // Translations can load after the editor is up (a lazy bundle, a slow
182
+ // fetch), which changes the texts without changing the language.
183
+ merge(this.translateService.onLangChange, this.translateService.onTranslationChange).pipe(takeUntilDestroyed()).subscribe(() => {
184
+ const q = this.quill();
185
+ if (!q)
186
+ return;
187
+ this.translateBubble();
188
+ this.decorateFromInputs(q.root);
189
+ });
190
+ }
191
+ get isInvalid() {
192
+ if (this.resolvedExternalErrors()?.length)
193
+ return true;
194
+ const c = this.ngControl?.control;
195
+ return !!(c?.invalid && (c.touched || c.dirty));
196
+ }
197
+ get serverErrors() {
198
+ return this.resolvedExternalErrors() ?? null;
199
+ }
200
+ get showRequired() {
201
+ const c = this.ngControl?.control;
202
+ return !!(c?.hasError('required') && c.touched);
203
+ }
204
+ /** Read-only or disabled: Quill and the toolbar stop editing. */
205
+ locked() {
206
+ return this.isDisabled() || this.readonly();
207
+ }
208
+ onInit(event) {
209
+ const q = event.editor;
210
+ this.decorateFromForm(q.root);
211
+ this.translateBubble();
212
+ this.quill.set(q);
213
+ }
214
+ onTextChange() {
215
+ const q = this.quill();
216
+ if (!q)
217
+ return;
218
+ const html = sanitizeRichHtml(q.getSemanticHTML());
219
+ if (html === this.lastEmitted)
220
+ return;
221
+ this.lastEmitted = html;
222
+ this.current.set(html);
223
+ this.onChange(html);
224
+ this.valueChangeEvent.emit(html);
225
+ }
226
+ handleBlur() {
227
+ this.onTouched();
228
+ }
229
+ ngDoCheck() {
230
+ // The form control's state is not a signal, so it is read per check;
231
+ // these are two attributes, written only when they change.
232
+ const q = this.quill();
233
+ if (q)
234
+ this.decorateFromForm(q.root);
235
+ }
236
+ writeValue(v) {
237
+ this.current.set(v ?? '');
238
+ }
239
+ registerOnChange(fn) {
240
+ this.onChange = fn;
241
+ }
242
+ registerOnTouched(fn) {
243
+ this.onTouched = fn;
244
+ }
245
+ setDisabledState(disabled) {
246
+ this.updateDisabledState(disabled);
247
+ }
248
+ /** Replaces the content unless it is our own echo or the same HTML. */
249
+ apply(q, html) {
250
+ if (html === this.lastEmitted)
251
+ return;
252
+ if (sanitizeRichHtml(html) === sanitizeRichHtml(q.getSemanticHTML()))
253
+ return;
254
+ // Our own allowlist first, so the guarantee does not rest on Quill's
255
+ // clipboard and link sanitiser alone.
256
+ q.setContents(q.clipboard.convert({ html: sanitizeRichHtml(html) }), 'silent');
257
+ }
258
+ /** Role, name, state, direction and language: everything that follows signals. */
259
+ decorateFromInputs(root) {
260
+ const set = setter(root);
261
+ set('role', 'textbox');
262
+ set('aria-multiline', 'true');
263
+ set('id', this.effectiveId);
264
+ set('aria-labelledby', this.labelKey() || this.label() ? this.labelId : this.labelledBy());
265
+ set('aria-label', this.effectiveAriaLabel);
266
+ set('aria-required', this.required() ? 'true' : null);
267
+ // A read-only editor stays focusable, so its hint is announced and its
268
+ // text can be selected; a disabled one leaves the tab order.
269
+ const readOnlyOnly = this.readonly() && !this.isDisabled();
270
+ set('aria-readonly', readOnlyOnly ? 'true' : null);
271
+ set('aria-disabled', this.isDisabled() ? 'true' : null);
272
+ set('tabindex', readOnlyOnly ? '0' : null);
273
+ set('dir', this.dir());
274
+ set('lang', this.lang());
275
+ }
276
+ /** Validity and what describes the field, which follow the form control. */
277
+ decorateFromForm(root) {
278
+ const set = setter(root);
279
+ set('aria-describedby', this.describedBy(this.showRequired || !!this.serverErrors));
280
+ set('aria-invalid', this.isInvalid ? 'true' : null);
281
+ }
282
+ /** Quill's link bubble texts are hardcoded English in its CSS; ours come from attributes. */
283
+ translateBubble() {
284
+ const tooltip = this.host.nativeElement.querySelector('.ql-tooltip');
285
+ if (!tooltip)
286
+ return;
287
+ const t = (key) => this.translateService.instant(key);
288
+ tooltip.dataset['efEnter'] = t('ef_rich_text_link_enter');
289
+ tooltip.dataset['efVisit'] = t('ef_rich_text_link_visit');
290
+ const action = tooltip.querySelector('a.ql-action');
291
+ if (action) {
292
+ action.dataset['efEdit'] = t('ef_rich_text_link_edit');
293
+ action.dataset['efSave'] = t('ef_rich_text_link_save');
294
+ }
295
+ const remove = tooltip.querySelector('a.ql-remove');
296
+ if (remove)
297
+ remove.dataset['efRemove'] = t('ef_rich_text_link_remove');
298
+ tooltip.querySelector('input')?.setAttribute('data-link', 'https://');
299
+ }
300
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfRichTextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
301
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfRichTextComponent, isStandalone: true, selector: "ef-rich-text", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, lang: { classPropertyName: "lang", publicName: "lang", isSignal: true, isRequired: false, transformFunction: null }, labelledBy: { classPropertyName: "labelledBy", publicName: "labelledBy", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChangeEvent: "valueChangeEvent" }, host: { properties: { "style.--ef-rich-text-rows": "rows()", "attr.lang": "null" } }, usesInheritance: true, ngImport: i0, template: "@if (labelKey() || label()) {\n <!-- A contenteditable area cannot be the target of label[for], so the\n editing area points at this wrapper with aria-labelledby. -->\n <span class=\"ef-rich-text__label\" [id]=\"labelId\">\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [required]=\"required()\" />\n </span>\n}\n<div class=\"ef-field-control\">\n <div #box class=\"ef-rich-text__box\" [class.is-invalid]=\"isInvalid\" [class.is-locked]=\"locked()\">\n <p-editor\n [bounds]=\"box\"\n [formats]=\"formats\"\n [modules]=\"modules\"\n [readonly]=\"locked()\"\n [placeholder]=\"effectivePlaceholder\"\n (onInit)=\"onInit($event)\"\n (onTextChange)=\"onTextChange()\"\n (onBlur)=\"handleBlur()\"\n >\n <ng-template #header>\n <span class=\"ql-formats\" role=\"group\" [attr.aria-label]=\"'ef_rich_text_toolbar' | translate\">\n <button type=\"button\" class=\"ql-bold\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_bold' | translate\" [title]=\"'ef_rich_text_bold' | translate\"></button>\n <button type=\"button\" class=\"ql-italic\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_italic' | translate\" [title]=\"'ef_rich_text_italic' | translate\"></button>\n <button type=\"button\" class=\"ql-underline\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_underline' | translate\" [title]=\"'ef_rich_text_underline' | translate\"></button>\n </span>\n <span class=\"ql-formats\">\n <button type=\"button\" class=\"ql-list\" value=\"bullet\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_bullet_list' | translate\" [title]=\"'ef_rich_text_bullet_list' | translate\"></button>\n <button type=\"button\" class=\"ql-list\" value=\"ordered\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_ordered_list' | translate\" [title]=\"'ef_rich_text_ordered_list' | translate\"></button>\n </span>\n <span class=\"ql-formats\">\n <button type=\"button\" class=\"ql-link\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_link' | translate\" [title]=\"'ef_rich_text_link' | translate\"></button>\n <button type=\"button\" class=\"ql-clean\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_clean' | translate\" [title]=\"'ef_rich_text_clean' | translate\"></button>\n </span>\n </ng-template>\n </p-editor>\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: ["ef-rich-text{display:block;min-width:0}ef-rich-text[dir]{direction:inherit}ef-rich-text .ef-rich-text__label{display:block}ef-rich-text .ef-rich-text__box{min-width:0;border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper)}ef-rich-text .ef-rich-text__box:focus-within{border-color:var(--ink-active)}ef-rich-text .ef-rich-text__box.is-invalid{border-color:var(--st-cancelled-fg)}ef-rich-text .ef-rich-text__box.is-locked{background:var(--paper-alt)}ef-rich-text .p-editor,ef-rich-text .p-editor .p-editor-toolbar,ef-rich-text .p-editor .p-editor-content{border:0;border-radius:0;background:transparent}ef-rich-text .ql-toolbar.ql-snow{display:flex;flex-wrap:wrap;align-items:center;gap:2px var(--s-2);padding:4px;border:0;border-bottom:1px solid var(--rule);background:var(--paper-alt);font-family:inherit;border-radius:var(--r-md) var(--r-md) 0 0}ef-rich-text .ql-snow .ql-formats{display:inline-flex;gap:2px;margin:0}ef-rich-text .ql-snow.ql-toolbar button{float:none;display:inline-grid;place-items:center;width:var(--hit, 32px);height:var(--hit, 32px);padding:7px;border-radius:var(--r-sm);color:var(--text-mute)}ef-rich-text .ql-snow .ql-stroke{stroke:currentColor}ef-rich-text .ql-snow .ql-fill,ef-rich-text .ql-snow .ql-stroke.ql-fill{fill:currentColor}ef-rich-text .ql-snow.ql-toolbar button:hover:not([disabled]),ef-rich-text .ql-snow.ql-toolbar button:focus-visible,ef-rich-text .ql-snow.ql-toolbar button.ql-active{color:var(--text);background:var(--paper)}ef-rich-text .ql-snow.ql-toolbar button:focus-visible{outline:0;box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}ef-rich-text .ql-snow.ql-toolbar button[disabled]{opacity:.4;cursor:not-allowed}ef-rich-text .ql-container.ql-snow{border:0;border-radius:0 0 var(--r-md) var(--r-md);font-family:inherit;font-size:13.5px;color:var(--text)}ef-rich-text .ql-editor{min-height:calc(var(--ef-rich-text-rows, 4) * 1.55em + 24px);line-height:1.55;text-align:start}ef-rich-text .p-editor .p-editor-content .ql-editor{color:var(--text);background:transparent}ef-rich-text .ql-editor.ql-blank:before{color:var(--text-mute);font-style:normal}ef-rich-text .ql-snow .ql-editor a{color:inherit}ef-rich-text .ql-editor{counter-reset:list-0}ef-rich-text .ql-editor p{counter-set:list-0}ef-rich-text .ql-editor ol{margin:0;padding-inline-start:0}ef-rich-text .ql-editor li[data-list]{position:relative;list-style-type:none;padding-inline-start:1.5em;padding-inline-end:0}ef-rich-text .ql-editor li[data-list]:before{content:none}ef-rich-text .ql-editor li[data-list]>.ql-ui{position:absolute}ef-rich-text .ql-editor li[data-list]>.ql-ui:before{display:inline-block;width:1.2em;margin-inline-start:-1.5em;margin-inline-end:.3em;text-align:end;white-space:nowrap}ef-rich-text .ql-editor li[data-list=bullet]{counter-increment:none}ef-rich-text .ql-editor li[data-list=bullet]>.ql-ui:before{content:\"\\2022\"}ef-rich-text .ql-editor li[data-list=ordered]{counter-increment:list-0}ef-rich-text .ql-editor li[data-list=ordered]>.ql-ui:before{content:counter(list-0,decimal) \". \"}ef-rich-text .ql-snow .ql-tooltip{background:var(--paper);border:1px solid var(--rule);border-radius:var(--r-sm);box-shadow:0 4px 16px color-mix(in srgb,var(--ink-active, #0f1115) 14%,transparent);color:var(--text);max-width:calc(100% - 16px)}ef-rich-text .ql-snow .ql-tooltip input[type=text]{border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper);color:var(--text)}ef-rich-text .ql-snow .ql-tooltip:before{content:attr(data-ef-visit)}ef-rich-text .ql-snow .ql-tooltip[data-mode=link]:before{content:attr(data-ef-enter)}ef-rich-text .ql-snow .ql-tooltip a.ql-action:after{content:attr(data-ef-edit);border-color:var(--rule)}ef-rich-text .ql-snow .ql-tooltip.ql-editing a.ql-action:after{content:attr(data-ef-save)}ef-rich-text .ql-snow .ql-tooltip a.ql-remove:before{content:attr(data-ef-remove)}@media(max-width:767px),(pointer:coarse){ef-rich-text .ql-snow.ql-toolbar button{width:var(--hit-touch);height:var(--hit-touch);padding:12px}}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "component", type: Editor, selector: "p-editor", inputs: ["style", "styleClass", "placeholder", "formats", "modules", "bounds", "scrollingContainer", "debug", "readonly"], outputs: ["onInit", "onTextChange", "onSelectionChange", "onEditorChange", "onFocus", "onBlur"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "labelParams", "for", "styleClass", "required"] }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
302
+ }
303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfRichTextComponent, decorators: [{
304
+ type: Component,
305
+ args: [{ selector: 'ef-rich-text', standalone: true, imports: [TranslateModule, Editor, EfLabelComponent], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, host: { '[style.--ef-rich-text-rows]': 'rows()', '[attr.lang]': 'null' }, template: "@if (labelKey() || label()) {\n <!-- A contenteditable area cannot be the target of label[for], so the\n editing area points at this wrapper with aria-labelledby. -->\n <span class=\"ef-rich-text__label\" [id]=\"labelId\">\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [required]=\"required()\" />\n </span>\n}\n<div class=\"ef-field-control\">\n <div #box class=\"ef-rich-text__box\" [class.is-invalid]=\"isInvalid\" [class.is-locked]=\"locked()\">\n <p-editor\n [bounds]=\"box\"\n [formats]=\"formats\"\n [modules]=\"modules\"\n [readonly]=\"locked()\"\n [placeholder]=\"effectivePlaceholder\"\n (onInit)=\"onInit($event)\"\n (onTextChange)=\"onTextChange()\"\n (onBlur)=\"handleBlur()\"\n >\n <ng-template #header>\n <span class=\"ql-formats\" role=\"group\" [attr.aria-label]=\"'ef_rich_text_toolbar' | translate\">\n <button type=\"button\" class=\"ql-bold\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_bold' | translate\" [title]=\"'ef_rich_text_bold' | translate\"></button>\n <button type=\"button\" class=\"ql-italic\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_italic' | translate\" [title]=\"'ef_rich_text_italic' | translate\"></button>\n <button type=\"button\" class=\"ql-underline\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_underline' | translate\" [title]=\"'ef_rich_text_underline' | translate\"></button>\n </span>\n <span class=\"ql-formats\">\n <button type=\"button\" class=\"ql-list\" value=\"bullet\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_bullet_list' | translate\" [title]=\"'ef_rich_text_bullet_list' | translate\"></button>\n <button type=\"button\" class=\"ql-list\" value=\"ordered\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_ordered_list' | translate\" [title]=\"'ef_rich_text_ordered_list' | translate\"></button>\n </span>\n <span class=\"ql-formats\">\n <button type=\"button\" class=\"ql-link\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_link' | translate\" [title]=\"'ef_rich_text_link' | translate\"></button>\n <button type=\"button\" class=\"ql-clean\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_clean' | translate\" [title]=\"'ef_rich_text_clean' | translate\"></button>\n </span>\n </ng-template>\n </p-editor>\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n", styles: ["ef-rich-text{display:block;min-width:0}ef-rich-text[dir]{direction:inherit}ef-rich-text .ef-rich-text__label{display:block}ef-rich-text .ef-rich-text__box{min-width:0;border:1px solid var(--rule);border-radius:var(--r-md);background:var(--paper)}ef-rich-text .ef-rich-text__box:focus-within{border-color:var(--ink-active)}ef-rich-text .ef-rich-text__box.is-invalid{border-color:var(--st-cancelled-fg)}ef-rich-text .ef-rich-text__box.is-locked{background:var(--paper-alt)}ef-rich-text .p-editor,ef-rich-text .p-editor .p-editor-toolbar,ef-rich-text .p-editor .p-editor-content{border:0;border-radius:0;background:transparent}ef-rich-text .ql-toolbar.ql-snow{display:flex;flex-wrap:wrap;align-items:center;gap:2px var(--s-2);padding:4px;border:0;border-bottom:1px solid var(--rule);background:var(--paper-alt);font-family:inherit;border-radius:var(--r-md) var(--r-md) 0 0}ef-rich-text .ql-snow .ql-formats{display:inline-flex;gap:2px;margin:0}ef-rich-text .ql-snow.ql-toolbar button{float:none;display:inline-grid;place-items:center;width:var(--hit, 32px);height:var(--hit, 32px);padding:7px;border-radius:var(--r-sm);color:var(--text-mute)}ef-rich-text .ql-snow .ql-stroke{stroke:currentColor}ef-rich-text .ql-snow .ql-fill,ef-rich-text .ql-snow .ql-stroke.ql-fill{fill:currentColor}ef-rich-text .ql-snow.ql-toolbar button:hover:not([disabled]),ef-rich-text .ql-snow.ql-toolbar button:focus-visible,ef-rich-text .ql-snow.ql-toolbar button.ql-active{color:var(--text);background:var(--paper)}ef-rich-text .ql-snow.ql-toolbar button:focus-visible{outline:0;box-shadow:0 0 0 2px color-mix(in srgb,var(--ink-active, #0f1115) 40%,transparent)}ef-rich-text .ql-snow.ql-toolbar button[disabled]{opacity:.4;cursor:not-allowed}ef-rich-text .ql-container.ql-snow{border:0;border-radius:0 0 var(--r-md) var(--r-md);font-family:inherit;font-size:13.5px;color:var(--text)}ef-rich-text .ql-editor{min-height:calc(var(--ef-rich-text-rows, 4) * 1.55em + 24px);line-height:1.55;text-align:start}ef-rich-text .p-editor .p-editor-content .ql-editor{color:var(--text);background:transparent}ef-rich-text .ql-editor.ql-blank:before{color:var(--text-mute);font-style:normal}ef-rich-text .ql-snow .ql-editor a{color:inherit}ef-rich-text .ql-editor{counter-reset:list-0}ef-rich-text .ql-editor p{counter-set:list-0}ef-rich-text .ql-editor ol{margin:0;padding-inline-start:0}ef-rich-text .ql-editor li[data-list]{position:relative;list-style-type:none;padding-inline-start:1.5em;padding-inline-end:0}ef-rich-text .ql-editor li[data-list]:before{content:none}ef-rich-text .ql-editor li[data-list]>.ql-ui{position:absolute}ef-rich-text .ql-editor li[data-list]>.ql-ui:before{display:inline-block;width:1.2em;margin-inline-start:-1.5em;margin-inline-end:.3em;text-align:end;white-space:nowrap}ef-rich-text .ql-editor li[data-list=bullet]{counter-increment:none}ef-rich-text .ql-editor li[data-list=bullet]>.ql-ui:before{content:\"\\2022\"}ef-rich-text .ql-editor li[data-list=ordered]{counter-increment:list-0}ef-rich-text .ql-editor li[data-list=ordered]>.ql-ui:before{content:counter(list-0,decimal) \". \"}ef-rich-text .ql-snow .ql-tooltip{background:var(--paper);border:1px solid var(--rule);border-radius:var(--r-sm);box-shadow:0 4px 16px color-mix(in srgb,var(--ink-active, #0f1115) 14%,transparent);color:var(--text);max-width:calc(100% - 16px)}ef-rich-text .ql-snow .ql-tooltip input[type=text]{border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper);color:var(--text)}ef-rich-text .ql-snow .ql-tooltip:before{content:attr(data-ef-visit)}ef-rich-text .ql-snow .ql-tooltip[data-mode=link]:before{content:attr(data-ef-enter)}ef-rich-text .ql-snow .ql-tooltip a.ql-action:after{content:attr(data-ef-edit);border-color:var(--rule)}ef-rich-text .ql-snow .ql-tooltip.ql-editing a.ql-action:after{content:attr(data-ef-save)}ef-rich-text .ql-snow .ql-tooltip a.ql-remove:before{content:attr(data-ef-remove)}@media(max-width:767px),(pointer:coarse){ef-rich-text .ql-snow.ql-toolbar button{width:var(--hit-touch);height:var(--hit-touch);padding:12px}}\n"] }]
306
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], dir: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], lang: [{ type: i0.Input, args: [{ isSignal: true, alias: "lang", required: false }] }], labelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelledBy", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], valueChangeEvent: [{
307
+ type: Output
308
+ }] } });
309
+ /** Writes an attribute only when it changes; null removes it. */
310
+ function setter(el) {
311
+ return (name, value) => {
312
+ if (value === null)
313
+ el.removeAttribute(name);
314
+ else if (el.getAttribute(name) !== value)
315
+ el.setAttribute(name, value);
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Turns on `ef-localized-input`'s `rich` mode for the app. Quill still loads
321
+ * lazily, the first time an editor renders.
322
+ *
323
+ * ```ts
324
+ * providers: [provideEfRichText()]
325
+ * ```
326
+ */
327
+ function provideEfRichText() {
328
+ return { provide: EF_RICH_TEXT, useValue: () => Promise.resolve(EfRichTextComponent) };
329
+ }
330
+
331
+ /**
332
+ * Generated bundle index. Do not edit.
333
+ */
334
+
335
+ export { EfRichTextComponent, provideEfRichText, sanitizeRichHtml };
336
+ //# sourceMappingURL=elasticias-ui-rich-text.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elasticias-ui-rich-text.mjs","sources":["../../../../libs/ui/rich-text/src/sanitize-rich-html.ts","../../../../libs/ui/rich-text/src/ef-rich-text.component.ts","../../../../libs/ui/rich-text/src/ef-rich-text.component.html","../../../../libs/ui/rich-text/src/provide-ef-rich-text.ts","../../../../libs/ui/rich-text/src/elasticias-ui-rich-text.ts"],"sourcesContent":["/** The tags ef-rich-text's toolbar can make. */\nconst ALLOWED = new Set(['P', 'BR', 'STRONG', 'EM', 'U', 'OL', 'UL', 'LI', 'A']);\n\n/** Removed with everything inside them. */\nconst DROPPED = new Set(['SCRIPT', 'STYLE', 'IFRAME', 'OBJECT', 'EMBED', 'TEMPLATE', 'NOSCRIPT', 'SVG', 'MATH']);\n\nconst SAFE_HREF = /^(https?:|mailto:|tel:)/i;\n\n/**\n * Whitelists the HTML an ef-rich-text produces: the toolbar's tags, a link's\n * `href` when it is http(s), mailto or tel (with `rel` and `target` set by us),\n * and no other attribute, so no style, class or event handler leaves the\n * browser. Other tags are unwrapped, keeping their text; script, style and\n * frames go with their content. Quill's non-breaking spaces between words\n * become spaces. An editor holding no text returns `''`, never `<p><br></p>`.\n *\n * Browser only: it parses into a `<template>` and so needs `document`.\n *\n * Security note: `npm audit` flags quill 2.0.3 (GHSA-v3m3-f69x-jf25, XSS via\n * HTML export; low severity, no patched release). It is mitigated here\n * because every value ef-rich-text emits goes through this function, and\n * incoming HTML goes through it before Quill parses it. Never render\n * `getSemanticHTML()` without it.\n */\nexport function sanitizeRichHtml(html: string | null | undefined): string {\n if (!html) return '';\n const tpl = document.createElement('template');\n tpl.innerHTML = html;\n clean(tpl.content);\n if (!(tpl.content.textContent ?? '').trim()) return '';\n const out = document.createElement('div');\n out.appendChild(tpl.content);\n return out.innerHTML;\n}\n\nfunction clean(parent: Node): void {\n for (const node of Array.from(parent.childNodes)) {\n if (node.nodeType === Node.TEXT_NODE) {\n node.textContent = (node.textContent ?? '').replace(/\\u00a0/g, ' ');\n continue;\n }\n if (node.nodeType !== Node.ELEMENT_NODE) {\n node.parentNode?.removeChild(node);\n continue;\n }\n const el = node as Element;\n const tag = el.tagName.toUpperCase();\n if (DROPPED.has(tag) || el.classList.contains('ql-ui')) {\n el.remove();\n continue;\n }\n clean(el);\n if (!ALLOWED.has(tag)) {\n el.replaceWith(...Array.from(el.childNodes));\n continue;\n }\n const href = tag === 'A' ? (el.getAttribute('href') ?? '').trim() : '';\n for (const attr of Array.from(el.attributes)) el.removeAttribute(attr.name);\n if (tag === 'A') {\n if (!SAFE_HREF.test(href)) {\n el.replaceWith(...Array.from(el.childNodes));\n continue;\n }\n el.setAttribute('href', href);\n el.setAttribute('rel', 'noopener noreferrer');\n el.setAttribute('target', '_blank');\n }\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n DoCheck,\n ElementRef,\n EventEmitter,\n Output,\n ViewEncapsulation,\n effect,\n inject,\n input,\n linkedSignal,\n signal,\n untracked,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor, NgControl } from '@angular/forms';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { merge } from 'rxjs';\nimport { Editor, EditorInitEvent } from 'primeng/editor';\nimport { AbstractEfFormControl, EfLabelComponent } from '@elasticias/ui';\nimport { sanitizeRichHtml } from './sanitize-rich-html';\n\n/** The part of Quill this wrapper uses. Quill is never imported here: PrimeNG loads it. */\ninterface QuillLike {\n root: HTMLElement;\n container: HTMLElement;\n clipboard: { convert(v: { html: string }): unknown };\n setContents(delta: unknown, source?: string): void;\n getSemanticHTML(): string;\n focus(): void;\n}\n\n/** What the toolbar can make, so a paste cannot bring in headings, images or colours. */\nconst FORMATS = ['bold', 'italic', 'underline', 'list', 'link'];\n\n/** Tab leaves the editor instead of inserting a tab or indenting a list (WCAG 2.1.2). */\nconst LEAVE_ON_TAB = { key: 'Tab', handler: () => true };\nconst MODULES = {\n keyboard: {\n bindings: {\n tab: LEAVE_ON_TAB,\n 'indent code-block': null,\n 'outdent code-block': null,\n 'remove tab': null,\n indent: { ...LEAVE_ON_TAB, format: ['blockquote', 'indent', 'list'] },\n outdent: { ...LEAVE_ON_TAB, shiftKey: true, format: ['blockquote', 'indent', 'list'] },\n },\n },\n};\n\nlet nextId = 0;\n\n/**\n * A rich text field: PrimeNG's Editor (Quill 2) with a small toolbar (bold,\n * italic, underline, bulleted and numbered lists, link, clear formatting) and\n * sanitised HTML out (`sanitizeRichHtml`): the toolbar's tags only, safe links,\n * no script, style, class or event attribute, and `''` for an empty editor.\n *\n * It lives in `@elasticias/ui/rich-text` so that only apps that use it bundle\n * the editor and need `quill` (a peer dependency of this entry). PrimeNG loads\n * Quill with a dynamic import the first time an editor renders, so a screen\n * without one never downloads it. `ef-localized-input`'s `rich` mode renders\n * it through `EF_RICH_TEXT`; provide that with `provideEfRichText()`.\n *\n * - `dir` and `lang` apply to the editing area only; the toolbar is page chrome.\n * - Disabled by `[disabled]`, a form's `disable()` or `[efReadOnly]`; `readonly`\n * keeps the text selectable. The toolbar is disabled with it.\n * - The editing area is a `role=\"textbox\"` named by the label and described\n * by the hint, the errors and `describedByExtra`.\n * - An incoming value equal to what it last emitted is ignored, so binding\n * the output back never resets the caret.\n *\n * i18n keys the app's bundles must define (toolbar names and the link bubble,\n * whose Quill texts are otherwise hardcoded English):\n *\n * | key | fr | ar | en |\n * |---|---|---|---|\n * | `ef_rich_text_bold` | Gras | غامق | Bold |\n * | `ef_rich_text_italic` | Italique | مائل | Italic |\n * | `ef_rich_text_underline` | Souligné | تسطير | Underline |\n * | `ef_rich_text_bullet_list` | Liste à puces | قائمة نقطية | Bulleted list |\n * | `ef_rich_text_ordered_list` | Liste numérotée | قائمة مرقمة | Numbered list |\n * | `ef_rich_text_link` | Lien | رابط | Link |\n * | `ef_rich_text_clean` | Effacer la mise en forme | مسح التنسيق | Clear formatting |\n * | `ef_rich_text_toolbar` | Mise en forme | التنسيق | Formatting |\n * | `ef_rich_text_link_enter` | Adresse du lien : | عنوان الرابط: | Link address: |\n * | `ef_rich_text_link_visit` | Ouvrir : | فتح: | Open: |\n * | `ef_rich_text_link_edit` | Modifier | تعديل | Edit |\n * | `ef_rich_text_link_remove` | Retirer | إزالة | Remove |\n * | `ef_rich_text_link_save` | Enregistrer | حفظ | Save |\n *\n * ```html\n * <ef-rich-text name=\"description\" labelKey=\"product_description\" dir=\"rtl\" lang=\"ar\"\n * [value]=\"html()\" (valueChangeEvent)=\"html.set($event)\" />\n * ```\n */\n@Component({\n selector: 'ef-rich-text',\n standalone: true,\n imports: [TranslateModule, Editor, EfLabelComponent],\n templateUrl: './ef-rich-text.component.html',\n styleUrl: './ef-rich-text.component.scss',\n // Quill builds its toolbar and editing area outside Angular's templates,\n // so the rules are global and every selector starts with ef-rich-text.\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n // A static lang=\"ar\" belongs to the editing area only, not to the label around it.\n host: { '[style.--ef-rich-text-rows]': 'rows()', '[attr.lang]': 'null' },\n})\nexport class EfRichTextComponent extends AbstractEfFormControl implements ControlValueAccessor, DoCheck {\n readonly value = input<string | null | undefined>('');\n /** Direction of the text (`rtl` for Arabic), on the editing area only. */\n readonly dir = input<'ltr' | 'rtl' | 'auto' | null>(null);\n /** Language of the text, on the editing area only. */\n readonly lang = input<string | null>(null);\n /** Id(s) naming the editing area when the label is rendered elsewhere\n * (`ef-localized-input` draws one label above its culture tabs). */\n readonly labelledBy = input<string | null>(null);\n /** Visible height of the editing area, in lines. */\n readonly rows = input(4);\n\n /** Sanitised HTML, `''` when the editor holds no text. */\n @Output() readonly valueChangeEvent = new EventEmitter<string>();\n\n readonly ngControl = inject(NgControl, { self: true, optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n protected readonly formats = FORMATS;\n protected readonly modules = MODULES;\n protected readonly labelId = `ef-rich-text-${nextId++}-label`;\n\n private readonly current = linkedSignal(() => this.value() ?? '');\n private readonly quill = signal<QuillLike | null>(null);\n private lastEmitted: string | null = null;\n\n private onChange: (v: string) => void = () => { /* noop */ };\n private onTouched: () => void = () => { /* noop */ };\n\n constructor() {\n super();\n if (this.ngControl) this.ngControl.valueAccessor = this;\n effect(() => {\n const html = this.current();\n const q = this.quill();\n if (q) untracked(() => this.apply(q, html));\n });\n // Attributes that follow signals are written when those change, not\n // on every check of the parent.\n effect(() => {\n const q = this.quill();\n if (q) this.decorateFromInputs(q.root);\n });\n // Translations can load after the editor is up (a lazy bundle, a slow\n // fetch), which changes the texts without changing the language.\n merge(this.translateService.onLangChange, this.translateService.onTranslationChange).pipe(takeUntilDestroyed()).subscribe(() => {\n const q = this.quill();\n if (!q) return;\n this.translateBubble();\n this.decorateFromInputs(q.root);\n });\n }\n\n get isInvalid(): boolean {\n if (this.resolvedExternalErrors()?.length) return true;\n const c = this.ngControl?.control;\n return !!(c?.invalid && (c.touched || c.dirty));\n }\n\n get serverErrors(): string[] | null {\n return this.resolvedExternalErrors() ?? null;\n }\n\n get showRequired(): boolean {\n const c = this.ngControl?.control;\n return !!(c?.hasError('required') && c.touched);\n }\n\n /** Read-only or disabled: Quill and the toolbar stop editing. */\n protected locked(): boolean {\n return this.isDisabled() || this.readonly();\n }\n\n protected onInit(event: EditorInitEvent): void {\n const q = event.editor as QuillLike;\n this.decorateFromForm(q.root);\n this.translateBubble();\n this.quill.set(q);\n }\n\n protected onTextChange(): void {\n const q = this.quill();\n if (!q) return;\n const html = sanitizeRichHtml(q.getSemanticHTML());\n if (html === this.lastEmitted) return;\n this.lastEmitted = html;\n this.current.set(html);\n this.onChange(html);\n this.valueChangeEvent.emit(html);\n }\n\n protected handleBlur(): void {\n this.onTouched();\n }\n\n ngDoCheck(): void {\n // The form control's state is not a signal, so it is read per check;\n // these are two attributes, written only when they change.\n const q = this.quill();\n if (q) this.decorateFromForm(q.root);\n }\n\n writeValue(v: string | null): void {\n this.current.set(v ?? '');\n }\n\n registerOnChange(fn: (v: string) => void): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n setDisabledState(disabled: boolean): void {\n this.updateDisabledState(disabled);\n }\n\n /** Replaces the content unless it is our own echo or the same HTML. */\n private apply(q: QuillLike, html: string): void {\n if (html === this.lastEmitted) return;\n if (sanitizeRichHtml(html) === sanitizeRichHtml(q.getSemanticHTML())) return;\n // Our own allowlist first, so the guarantee does not rest on Quill's\n // clipboard and link sanitiser alone.\n q.setContents(q.clipboard.convert({ html: sanitizeRichHtml(html) }), 'silent');\n }\n\n /** Role, name, state, direction and language: everything that follows signals. */\n private decorateFromInputs(root: HTMLElement): void {\n const set = setter(root);\n set('role', 'textbox');\n set('aria-multiline', 'true');\n set('id', this.effectiveId);\n set('aria-labelledby', this.labelKey() || this.label() ? this.labelId : this.labelledBy());\n set('aria-label', this.effectiveAriaLabel);\n set('aria-required', this.required() ? 'true' : null);\n // A read-only editor stays focusable, so its hint is announced and its\n // text can be selected; a disabled one leaves the tab order.\n const readOnlyOnly = this.readonly() && !this.isDisabled();\n set('aria-readonly', readOnlyOnly ? 'true' : null);\n set('aria-disabled', this.isDisabled() ? 'true' : null);\n set('tabindex', readOnlyOnly ? '0' : null);\n set('dir', this.dir());\n set('lang', this.lang());\n }\n\n /** Validity and what describes the field, which follow the form control. */\n private decorateFromForm(root: HTMLElement): void {\n const set = setter(root);\n set('aria-describedby', this.describedBy(this.showRequired || !!this.serverErrors));\n set('aria-invalid', this.isInvalid ? 'true' : null);\n }\n\n /** Quill's link bubble texts are hardcoded English in its CSS; ours come from attributes. */\n private translateBubble(): void {\n const tooltip = this.host.nativeElement.querySelector<HTMLElement>('.ql-tooltip');\n if (!tooltip) return;\n const t = (key: string) => this.translateService.instant(key);\n tooltip.dataset['efEnter'] = t('ef_rich_text_link_enter');\n tooltip.dataset['efVisit'] = t('ef_rich_text_link_visit');\n const action = tooltip.querySelector<HTMLElement>('a.ql-action');\n if (action) {\n action.dataset['efEdit'] = t('ef_rich_text_link_edit');\n action.dataset['efSave'] = t('ef_rich_text_link_save');\n }\n const remove = tooltip.querySelector<HTMLElement>('a.ql-remove');\n if (remove) remove.dataset['efRemove'] = t('ef_rich_text_link_remove');\n tooltip.querySelector('input')?.setAttribute('data-link', 'https://');\n }\n}\n\n/** Writes an attribute only when it changes; null removes it. */\nfunction setter(el: HTMLElement) {\n return (name: string, value: string | null) => {\n if (value === null) el.removeAttribute(name);\n else if (el.getAttribute(name) !== value) el.setAttribute(name, value);\n };\n}\n","@if (labelKey() || label()) {\n <!-- A contenteditable area cannot be the target of label[for], so the\n editing area points at this wrapper with aria-labelledby. -->\n <span class=\"ef-rich-text__label\" [id]=\"labelId\">\n <ef-label [labelKey]=\"labelKey()\" [labelParams]=\"labelParams()\" [label]=\"label()\" [required]=\"required()\" />\n </span>\n}\n<div class=\"ef-field-control\">\n <div #box class=\"ef-rich-text__box\" [class.is-invalid]=\"isInvalid\" [class.is-locked]=\"locked()\">\n <p-editor\n [bounds]=\"box\"\n [formats]=\"formats\"\n [modules]=\"modules\"\n [readonly]=\"locked()\"\n [placeholder]=\"effectivePlaceholder\"\n (onInit)=\"onInit($event)\"\n (onTextChange)=\"onTextChange()\"\n (onBlur)=\"handleBlur()\"\n >\n <ng-template #header>\n <span class=\"ql-formats\" role=\"group\" [attr.aria-label]=\"'ef_rich_text_toolbar' | translate\">\n <button type=\"button\" class=\"ql-bold\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_bold' | translate\" [title]=\"'ef_rich_text_bold' | translate\"></button>\n <button type=\"button\" class=\"ql-italic\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_italic' | translate\" [title]=\"'ef_rich_text_italic' | translate\"></button>\n <button type=\"button\" class=\"ql-underline\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_underline' | translate\" [title]=\"'ef_rich_text_underline' | translate\"></button>\n </span>\n <span class=\"ql-formats\">\n <button type=\"button\" class=\"ql-list\" value=\"bullet\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_bullet_list' | translate\" [title]=\"'ef_rich_text_bullet_list' | translate\"></button>\n <button type=\"button\" class=\"ql-list\" value=\"ordered\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_ordered_list' | translate\" [title]=\"'ef_rich_text_ordered_list' | translate\"></button>\n </span>\n <span class=\"ql-formats\">\n <button type=\"button\" class=\"ql-link\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_link' | translate\" [title]=\"'ef_rich_text_link' | translate\"></button>\n <button type=\"button\" class=\"ql-clean\" [disabled]=\"locked()\" [attr.aria-label]=\"'ef_rich_text_clean' | translate\" [title]=\"'ef_rich_text_clean' | translate\"></button>\n </span>\n </ng-template>\n </p-editor>\n </div>\n @if (effectiveHint) {\n <small class=\"ef-field-hint\" [id]=\"hintId\">{{ effectiveHint }}</small>\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n</div>\n","import { Provider } from '@angular/core';\nimport { EF_RICH_TEXT } from '@elasticias/ui';\nimport { EfRichTextComponent } from './ef-rich-text.component';\n\n/**\n * Turns on `ef-localized-input`'s `rich` mode for the app. Quill still loads\n * lazily, the first time an editor renders.\n *\n * ```ts\n * providers: [provideEfRichText()]\n * ```\n */\nexport function provideEfRichText(): Provider {\n return { provide: EF_RICH_TEXT, useValue: () => Promise.resolve(EfRichTextComponent) };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}