@leanix/components 0.4.587 → 0.4.589

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 (36) hide show
  1. package/fesm2022/leanix-components.mjs +1905 -427
  2. package/fesm2022/leanix-components.mjs.map +1 -1
  3. package/index.d.ts +10 -0
  4. package/lib/forms-ui/components/rich-text-editor/components/rich-text-editor/rich-text-editor.component.d.ts +29 -0
  5. package/lib/forms-ui/components/rich-text-editor/components/rich-text-editor-toolbar/rich-text-editor-toolbar.component.d.ts +23 -0
  6. package/lib/forms-ui/components/rich-text-editor/directives/focus-editor.directive.d.ts +8 -0
  7. package/lib/forms-ui/components/rich-text-editor/directives/tiptap-editor.directive.d.ts +23 -0
  8. package/lib/forms-ui/components/rich-text-editor/extensions/highlight-term/highlight-term-state.plugin.d.ts +8 -0
  9. package/lib/forms-ui/components/rich-text-editor/extensions/highlight-term/highlight-term.directive.d.ts +8 -0
  10. package/lib/forms-ui/components/rich-text-editor/extensions/highlight-term/highlight-term.extension.d.ts +9 -0
  11. package/lib/forms-ui/components/rich-text-editor/extensions/highlight-term/highlight-term.plugin.d.ts +5 -0
  12. package/lib/forms-ui/components/rich-text-editor/extensions/link/components/link-modal/link-modal.component.d.ts +18 -0
  13. package/lib/forms-ui/components/rich-text-editor/extensions/link/link.extension.d.ts +8 -0
  14. package/lib/forms-ui/components/rich-text-editor/extensions/link/link.plugin.d.ts +10 -0
  15. package/lib/forms-ui/components/rich-text-editor/extensions/link/url-validator.directive.d.ts +7 -0
  16. package/lib/forms-ui/components/rich-text-editor/extensions/table/index.d.ts +2 -0
  17. package/lib/forms-ui/components/rich-text-editor/extensions/table/table-bubble-menu/table-bubble-menu.component.d.ts +13 -0
  18. package/lib/forms-ui/components/rich-text-editor/extensions/table/table-extensions.d.ts +4 -0
  19. package/lib/forms-ui/components/rich-text-editor/extensions/table/utils.d.ts +28 -0
  20. package/lib/forms-ui/components/rich-text-editor/extensions/tracking/tracking.directive.d.ts +8 -0
  21. package/lib/forms-ui/components/rich-text-editor/extensions/tracking/tracking.extension.d.ts +10 -0
  22. package/lib/forms-ui/components/rich-text-editor/extensions/tracking/tracking.plugin.d.ts +4 -0
  23. package/lib/forms-ui/components/rich-text-editor/extensions/truncate/index.d.ts +2 -0
  24. package/lib/forms-ui/components/rich-text-editor/extensions/truncate/truncate-button.component.d.ts +22 -0
  25. package/lib/forms-ui/components/rich-text-editor/extensions/truncate/truncate.directive.d.ts +9 -0
  26. package/lib/forms-ui/components/rich-text-editor/extensions/truncate/truncate.extension.d.ts +6 -0
  27. package/lib/forms-ui/components/rich-text-editor/extensions/truncate/truncate.plugin.d.ts +10 -0
  28. package/lib/forms-ui/components/rich-text-editor/ngx-tiptap/AngularRenderer.d.ts +12 -0
  29. package/lib/forms-ui/components/rich-text-editor/ngx-tiptap/NodeViewRenderer.d.ts +18 -0
  30. package/lib/forms-ui/components/rich-text-editor/ngx-tiptap/editor.directive.d.ts +24 -0
  31. package/lib/forms-ui/components/rich-text-editor/ngx-tiptap/node-view.component.d.ts +14 -0
  32. package/lib/forms-ui/components/rich-text-editor/pipes/extension-enabled.pipe.d.ts +8 -0
  33. package/lib/forms-ui/components/rich-text-editor/pipes/remove-markdown.pipe.d.ts +14 -0
  34. package/lib/forms-ui/components/rich-text-editor/utils/extensions-builder.d.ts +13 -0
  35. package/lib/forms-ui/components/rich-text-editor/utils/features.d.ts +3 -0
  36. package/package.json +30 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Input, Component, signal, ChangeDetectionStrategy, HostBinding, inject, input, computed, EventEmitter, HostListener, Output, Injectable, ElementRef, ViewChild, Inject, model, Directive, Optional, Pipe, NgModule, DestroyRef, ChangeDetectorRef, effect, ContentChild, afterRenderEffect, ContentChildren, ViewChildren, forwardRef, TemplateRef, viewChild, booleanAttribute, SecurityContext, Self, Host } from '@angular/core';
2
+ import { InjectionToken, Input, Component, signal, ChangeDetectionStrategy, HostBinding, inject, input, computed, EventEmitter, HostListener, Output, Injectable, ElementRef, ViewChild, Inject, model, Directive, Optional, Pipe, NgModule, DestroyRef, ChangeDetectorRef, effect, ContentChild, afterRenderEffect, ContentChildren, ViewChildren, forwardRef, TemplateRef, viewChild, booleanAttribute, SecurityContext, Self, Host, Injector, output, ApplicationRef, createComponent } from '@angular/core';
3
3
  import * as i1 from '@ngx-translate/core';
4
4
  import { TranslatePipe, TranslateModule } from '@ngx-translate/core';
5
5
  import { NgTemplateOutlet, NgClass, AsyncPipe, UpperCasePipe, DecimalPipe, CommonModule, formatDate } from '@angular/common';
@@ -30,12 +30,42 @@ import * as i1$3 from '@ncstate/sat-popover';
30
30
  import { SatPopoverModule, SatPopoverComponent } from '@ncstate/sat-popover';
31
31
  import { ClipboardModule } from '@angular/cdk/clipboard';
32
32
  import * as i1$5 from '@angular/forms';
33
- import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS, ReactiveFormsModule, UntypedFormControl, Validators, FormControl } from '@angular/forms';
33
+ import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS, ReactiveFormsModule, UntypedFormControl, NgForm, Validators, FormControl } from '@angular/forms';
34
34
  import * as i1$4 from 'ngx-infinite-scroll';
35
35
  import { InfiniteScrollModule } from 'ngx-infinite-scroll';
36
36
  import * as i1$6 from '@angular/cdk/drag-drop';
37
37
  import { moveItemInArray, CdkDropList, CdkDrag, DragDropModule } from '@angular/cdk/drag-drop';
38
38
  import * as i1$7 from '@angular/platform-browser';
39
+ import { Extension, getMarkRange, findParentNode, Editor, NodeView } from '@tiptap/core';
40
+ import Bold from '@tiptap/extension-bold';
41
+ import BulletList from '@tiptap/extension-bullet-list';
42
+ import Code from '@tiptap/extension-code';
43
+ import CodeBlock from '@tiptap/extension-code-block';
44
+ import Document from '@tiptap/extension-document';
45
+ import Dropcursor from '@tiptap/extension-dropcursor';
46
+ import Gapcursor from '@tiptap/extension-gapcursor';
47
+ import HardBreak from '@tiptap/extension-hard-break';
48
+ import Heading from '@tiptap/extension-heading';
49
+ import History from '@tiptap/extension-history';
50
+ import HorizontalRule from '@tiptap/extension-horizontal-rule';
51
+ import Italic from '@tiptap/extension-italic';
52
+ import ListItem from '@tiptap/extension-list-item';
53
+ import OrderedList from '@tiptap/extension-ordered-list';
54
+ import Paragraph from '@tiptap/extension-paragraph';
55
+ import Strike from '@tiptap/extension-strike';
56
+ import Text from '@tiptap/extension-text';
57
+ import TextAlign from '@tiptap/extension-text-align';
58
+ import Underline from '@tiptap/extension-underline';
59
+ import { Markdown } from 'tiptap-markdown';
60
+ import { PluginKey, Plugin } from '@tiptap/pm/state';
61
+ import { DecorationSet, Decoration } from '@tiptap/pm/view';
62
+ import { Link } from '@tiptap/extension-link';
63
+ import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
64
+ import Table, { Table as Table$1 } from '@tiptap/extension-table';
65
+ import { CellSelection, TableMap } from '@tiptap/pm/tables';
66
+ import TableCell from '@tiptap/extension-table-cell';
67
+ import TiptapTableHeader from '@tiptap/extension-table-header';
68
+ import TableRow from '@tiptap/extension-table-row';
39
69
  import { trigger, transition, style, animate } from '@angular/animations';
40
70
  import { coerceNumberProperty } from '@angular/cdk/coercion';
41
71
 
@@ -9025,497 +9055,1945 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
9025
9055
  }]
9026
9056
  }] });
9027
9057
 
9028
- class MaxLengthCounterDirective {
9029
- constructor(el, renderer, ngControl) {
9030
- this.el = el;
9058
+ const highlightTermStatePluginKey = new PluginKey('highlight-term-state');
9059
+ function highlightTermState(_editor) {
9060
+ return new Plugin({
9061
+ key: highlightTermStatePluginKey,
9062
+ state: {
9063
+ init() {
9064
+ return { term: null };
9065
+ },
9066
+ apply: (tr, state) => {
9067
+ const meta = tr.getMeta(highlightTermStatePluginKey);
9068
+ return meta ? { ...state, ...meta } : state;
9069
+ }
9070
+ }
9071
+ });
9072
+ }
9073
+
9074
+ const highlightTermPluginKey = new PluginKey('highlight-term');
9075
+ function highlightTerm(_editor, options) {
9076
+ return new Plugin({
9077
+ key: highlightTermPluginKey,
9078
+ state: {
9079
+ init(_, state) {
9080
+ const term = highlightTermStatePluginKey.getState(state)?.term ?? null;
9081
+ return createDecorations(state.doc, term ?? null, options);
9082
+ },
9083
+ apply(tr, oldDecorationSet, state) {
9084
+ const meta = tr.getMeta(highlightTermStatePluginKey);
9085
+ const oldTerm = highlightTermStatePluginKey.getState(state)?.term ?? null;
9086
+ const termChanged = meta?.term && meta.term !== oldTerm;
9087
+ if (tr.docChanged || termChanged) {
9088
+ return createDecorations(tr.doc, meta?.term || oldTerm, options);
9089
+ }
9090
+ return oldDecorationSet.map(tr.mapping, tr.doc);
9091
+ }
9092
+ },
9093
+ props: {
9094
+ decorations(state) {
9095
+ return this.getState(state);
9096
+ }
9097
+ }
9098
+ });
9099
+ }
9100
+ function createDecorations(doc, term, options) {
9101
+ const decorations = [];
9102
+ if (!term)
9103
+ return DecorationSet.empty;
9104
+ const regex = prepareRegex(options, term);
9105
+ doc.descendants((node, pos) => {
9106
+ if (!node.isText || !node.text)
9107
+ return;
9108
+ const text = node.text;
9109
+ let match;
9110
+ while ((match = regex.exec(text)) !== null) {
9111
+ const start = pos + match.index;
9112
+ const end = start + match[0].length;
9113
+ // TODO: replace #fafda5 with $searchHighlightingColor
9114
+ decorations.push(Decoration.inline(start, end, { style: 'background-color: #fafda5' }));
9115
+ }
9116
+ });
9117
+ return DecorationSet.create(doc, decorations);
9118
+ }
9119
+ /**
9120
+ * This regex is copied from highlight-term.pipe.ts
9121
+ * TODO: get rid of duplication
9122
+ */
9123
+ function prepareRegex(options, term) {
9124
+ const STANDARD_TOKENIZER_SEPERATORS = /[^a-zA-Z\d\s]/g;
9125
+ let pattern = options.exactMatch ? term.trim() : term.replace(STANDARD_TOKENIZER_SEPERATORS, ' ');
9126
+ // replace special chars for a backslash for RegExp
9127
+ pattern = pattern.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
9128
+ pattern = options.exactMatch
9129
+ ? pattern
9130
+ : pattern
9131
+ .split(' ')
9132
+ .filter((t) => {
9133
+ return t.length > 0;
9134
+ })
9135
+ .join('|');
9136
+ return new RegExp(pattern, 'gi');
9137
+ }
9138
+
9139
+ /**
9140
+ * This extension replicates the search term highlight mechanism
9141
+ * implemented by highlight-term.pipe.ts for tiptap editor.
9142
+ */
9143
+ const HighlightTerm = Extension.create({
9144
+ name: 'highlight-term',
9145
+ addOptions() {
9146
+ return {
9147
+ exactMatch: false
9148
+ };
9149
+ },
9150
+ addProseMirrorPlugins() {
9151
+ const { editor, options } = this;
9152
+ return [highlightTermState(editor), highlightTerm(editor, options)];
9153
+ }
9154
+ });
9155
+
9156
+ const linkPluginKey = new PluginKey('link');
9157
+ function linkPlugin() {
9158
+ return new Plugin({
9159
+ key: linkPluginKey,
9160
+ state: {
9161
+ init() {
9162
+ return { open: false, text: null, url: null };
9163
+ },
9164
+ apply: (tr, state) => {
9165
+ const meta = tr.getMeta(linkPluginKey);
9166
+ return meta ? { ...state, ...meta } : state;
9167
+ }
9168
+ },
9169
+ props: {
9170
+ handleClickOn: (view, pos, _node, _nodePos, event) => {
9171
+ const state = view.state;
9172
+ const $pos = state.doc.resolve(pos);
9173
+ const linkMark = $pos.marks().find((mark) => mark.type.name === 'link');
9174
+ if (!linkMark)
9175
+ return false;
9176
+ const markRange = getMarkRange($pos, linkMark.type);
9177
+ if (!markRange)
9178
+ return false;
9179
+ const { from, to } = markRange;
9180
+ const text = state.doc.textBetween(from, to, ' ');
9181
+ const href = linkMark.attrs['href'];
9182
+ if (!href || !view.editable)
9183
+ return false;
9184
+ event.preventDefault();
9185
+ const pluginState = linkPluginKey.getState(state);
9186
+ view.dispatch(state.tr.setMeta(linkPluginKey, {
9187
+ ...pluginState,
9188
+ open: true,
9189
+ text,
9190
+ url: href
9191
+ }));
9192
+ return true;
9193
+ }
9194
+ }
9195
+ });
9196
+ }
9197
+ function dispatchLinkState(editor, props) {
9198
+ const pluginState = linkPluginKey.getState(editor.state);
9199
+ editor.view.dispatch(editor.view.state.tr.setMeta(linkPluginKey, {
9200
+ ...pluginState,
9201
+ ...props
9202
+ }));
9203
+ }
9204
+
9205
+ const CustomLink = Link.extend({
9206
+ addAttributes() {
9207
+ return {
9208
+ ...this.parent?.(),
9209
+ href: {
9210
+ ...this.parent?.()?.href,
9211
+ renderHTML: (attributes) => {
9212
+ return {
9213
+ href: attributes['href'],
9214
+ target: '_blank',
9215
+ rel: 'noopener noreferrer',
9216
+ 'data-link': attributes['href']
9217
+ };
9218
+ }
9219
+ }
9220
+ };
9221
+ },
9222
+ addCommands() {
9223
+ return {
9224
+ ...this.parent?.(),
9225
+ openLinkModal: () => ({ editor, state }) => {
9226
+ const selection = this.editor.state.selection;
9227
+ if (selection.empty) {
9228
+ const mark = selection.$head.marks().find(isLinkMark);
9229
+ if (!mark) {
9230
+ dispatchLinkState(editor, { url: null, text: null, open: true });
9231
+ return true;
9232
+ }
9233
+ const node = getNodeByMark(editor.state.doc, mark);
9234
+ if (!node) {
9235
+ dispatchLinkState(editor, { url: null, text: null, open: true });
9236
+ return true;
9237
+ }
9238
+ const url = mark.attrs['href'];
9239
+ const text = node.text ?? null;
9240
+ dispatchLinkState(editor, { url, text, open: true });
9241
+ return true;
9242
+ }
9243
+ const { from, to } = this.editor.state.selection;
9244
+ const text = this.editor.state.doc.textBetween(from, to, ' ');
9245
+ let url = '';
9246
+ state.doc.nodesBetween(from, to, (node, _pos) => {
9247
+ const linkMark = node.marks.find((mark) => mark.type.name === 'link');
9248
+ if (linkMark) {
9249
+ url = linkMark.attrs['href'];
9250
+ return false;
9251
+ }
9252
+ return true;
9253
+ });
9254
+ dispatchLinkState(editor, { url, text, open: true });
9255
+ return true;
9256
+ }
9257
+ };
9258
+ },
9259
+ addProseMirrorPlugins() {
9260
+ const editor = this.editor;
9261
+ const defaultPlugins = this.parent?.() ?? [];
9262
+ if (!editor.isEditable) {
9263
+ return [...defaultPlugins];
9264
+ }
9265
+ return [...defaultPlugins, linkPlugin()];
9266
+ }
9267
+ })
9268
+ .configure({ autolink: true, openOnClick: false })
9269
+ .extend({ inclusive: false });
9270
+ function getNodeByMark(doc, targetMark) {
9271
+ let result;
9272
+ doc.descendants((node) => {
9273
+ if (!node.isText)
9274
+ return true;
9275
+ if (targetMark.isInSet(node.marks)) {
9276
+ result = node;
9277
+ return false;
9278
+ }
9279
+ return true;
9280
+ });
9281
+ return result;
9282
+ }
9283
+ function isLinkMark(mark) {
9284
+ return mark.type.name === 'link';
9285
+ }
9286
+
9287
+ const isCellSelection = (selection) => selection instanceof CellSelection;
9288
+ const getTableMapFromSelection = (selection) => TableMap.get(selection.$anchorCell.node(-1));
9289
+ const checkIfRectIsFullySelected = (rect, selection) => {
9290
+ const map = getTableMapFromSelection(selection);
9291
+ const offset = selection.$anchorCell.start(-1);
9292
+ const rectCells = map.cellsInRect(rect);
9293
+ const selectedCells = map.cellsInRect(map.rectBetween(selection.$anchorCell.pos - offset, selection.$headCell.pos - offset));
9294
+ return rectCells.every((cell) => selectedCells.includes(cell));
9295
+ };
9296
+ const getTableNodeFromSelection = (selection) => findParentNode((node) => node.type.spec['tableRole'] === 'table')(selection);
9297
+ const isColumnSelected = (columnIndex, selection) => {
9298
+ return isCellSelection(selection)
9299
+ ? checkIfRectIsFullySelected({
9300
+ left: columnIndex,
9301
+ right: columnIndex + 1,
9302
+ top: 0,
9303
+ bottom: getTableMapFromSelection(selection).height
9304
+ }, selection)
9305
+ : false;
9306
+ };
9307
+ const isRowSelected = (rowIndex, selection) => {
9308
+ return isCellSelection(selection)
9309
+ ? checkIfRectIsFullySelected({
9310
+ left: 0,
9311
+ right: getTableMapFromSelection(selection).width,
9312
+ top: rowIndex,
9313
+ bottom: rowIndex + 1
9314
+ }, selection)
9315
+ : false;
9316
+ };
9317
+ const isTableSelected = (selection) => {
9318
+ return isCellSelection(selection)
9319
+ ? checkIfRectIsFullySelected({
9320
+ left: 0,
9321
+ right: getTableMapFromSelection(selection).width,
9322
+ top: 0,
9323
+ bottom: getTableMapFromSelection(selection).height
9324
+ }, selection)
9325
+ : false;
9326
+ };
9327
+ const getCellsInColumn = (columnIndex, selection) => {
9328
+ const table = getTableNodeFromSelection(selection);
9329
+ if (!table)
9330
+ return null;
9331
+ const map = TableMap.get(table.node);
9332
+ const columns = Array.isArray(columnIndex) ? columnIndex : [columnIndex];
9333
+ return columns.flatMap((col) => {
9334
+ if (col < 0 || col >= map.width)
9335
+ return [];
9336
+ const cells = map.cellsInRect({
9337
+ left: col,
9338
+ right: col + 1,
9339
+ top: 0,
9340
+ bottom: map.height
9341
+ });
9342
+ return cells.map((cellPos) => {
9343
+ const node = table.node.nodeAt(cellPos);
9344
+ const absPos = cellPos + table.start;
9345
+ return { pos: absPos, start: absPos + 1, node };
9346
+ });
9347
+ });
9348
+ };
9349
+ const getCellsInRow = (rowIndex, selection) => {
9350
+ const table = getTableNodeFromSelection(selection);
9351
+ if (!table)
9352
+ return null;
9353
+ const map = TableMap.get(table.node);
9354
+ const rows = Array.isArray(rowIndex) ? rowIndex : [rowIndex];
9355
+ return rows.flatMap((row) => {
9356
+ if (row < 0 || row >= map.height)
9357
+ return [];
9358
+ const cells = map.cellsInRect({
9359
+ left: 0,
9360
+ right: map.width,
9361
+ top: row,
9362
+ bottom: row + 1
9363
+ });
9364
+ return cells.map((cellPos) => {
9365
+ const node = table.node.nodeAt(cellPos);
9366
+ const absPos = cellPos + table.start;
9367
+ return { pos: absPos, start: absPos + 1, node };
9368
+ });
9369
+ });
9370
+ };
9371
+ const createSelectionForIndex = (type, index, tr) => {
9372
+ const table = getTableNodeFromSelection(tr.selection);
9373
+ if (!table)
9374
+ return tr;
9375
+ const map = TableMap.get(table.node);
9376
+ const isRow = type === 'row';
9377
+ const maxIndex = isRow ? map.height : map.width;
9378
+ if (index < 0 || index >= maxIndex)
9379
+ return tr;
9380
+ const rect = {
9381
+ left: isRow ? 0 : index,
9382
+ top: isRow ? index : 0,
9383
+ right: isRow ? map.width : index + 1,
9384
+ bottom: isRow ? index + 1 : map.height
9385
+ };
9386
+ const edgeCellsStart = map.cellsInRect(rect);
9387
+ const edgeCellsEnd = rect.bottom - rect.top === 1
9388
+ ? edgeCellsStart
9389
+ : map.cellsInRect({
9390
+ left: isRow ? 0 : rect.right - 1,
9391
+ top: isRow ? rect.bottom - 1 : 0,
9392
+ right: rect.right,
9393
+ bottom: rect.bottom
9394
+ });
9395
+ if (edgeCellsStart.length && edgeCellsEnd.length) {
9396
+ const startCell = edgeCellsStart[0];
9397
+ const endCell = edgeCellsEnd[edgeCellsEnd.length - 1];
9398
+ if (startCell != null && endCell != null && table?.start != null) {
9399
+ const head = table.start + startCell;
9400
+ const anchor = table.start + endCell;
9401
+ return tr.setSelection(new CellSelection(tr.doc.resolve(anchor), tr.doc.resolve(head)));
9402
+ }
9403
+ }
9404
+ return tr;
9405
+ };
9406
+ const selectColumn = (index, tr) => createSelectionForIndex('column', index, tr);
9407
+ const selectRow = (index, tr) => createSelectionForIndex('row', index, tr);
9408
+ const isSelectorActive = ({ editor, view, state, from, selectorType }) => {
9409
+ if (!editor.isActive(Table.name) || isTableSelected(state.selection)) {
9410
+ return false;
9411
+ }
9412
+ let resolvedNode = null;
9413
+ try {
9414
+ resolvedNode = view.nodeDOM(from) || view.domAtPos(from).node;
9415
+ }
9416
+ catch {
9417
+ return false;
9418
+ }
9419
+ if (!resolvedNode || !(resolvedNode instanceof HTMLElement))
9420
+ return false;
9421
+ // Traverse up the DOM tree to find the table cell (TD or TH)
9422
+ const cellElement = resolvedNode.closest?.('td, th');
9423
+ if (!cellElement)
9424
+ return false;
9425
+ // Check if a selected anchor exists within this cell
9426
+ return !!cellElement.querySelector(`button.${selectorType}.selected`);
9427
+ };
9428
+ const isColumnSelectorActive = (params) => {
9429
+ return isSelectorActive({ ...params, selectorType: 'selector-column' });
9430
+ };
9431
+ const isRowSelectorActive = (params) => {
9432
+ return isSelectorActive({ ...params, selectorType: 'selector-row' });
9433
+ };
9434
+
9435
+ class TableBubbleMenuComponent {
9436
+ constructor() {
9437
+ this.NAME = 'RichTextEditorTableActions';
9438
+ }
9439
+ ngAfterViewInit() {
9440
+ this.editor.registerPlugin(BubbleMenuPlugin({
9441
+ pluginKey: 'bubbleMenuRow',
9442
+ editor: this.editor,
9443
+ element: this.bubbleMenuRowRef.nativeElement,
9444
+ tippyOptions: {
9445
+ appendTo: () => document.body,
9446
+ placement: 'left',
9447
+ offset: () => {
9448
+ // A hack to fix wrong offset by tippy for first row's bubble menu
9449
+ if (document.querySelector('.selector-row.selected.first')) {
9450
+ return [43, 19];
9451
+ }
9452
+ return [37, 8];
9453
+ },
9454
+ popperOptions: {
9455
+ modifiers: [{ name: 'flip', enabled: false }]
9456
+ }
9457
+ },
9458
+ shouldShow: ({ editor, view, state, from }) => {
9459
+ return isRowSelectorActive({ editor, view, state, from: from || 0 });
9460
+ }
9461
+ }));
9462
+ this.editor.registerPlugin(BubbleMenuPlugin({
9463
+ pluginKey: 'bubbleMenuColumn',
9464
+ editor: this.editor,
9465
+ element: this.bubbleMenuColumnRef.nativeElement,
9466
+ tippyOptions: {
9467
+ appendTo: () => document.body,
9468
+ offset: [0, 8],
9469
+ popperOptions: {
9470
+ modifiers: [{ name: 'flip', enabled: false }]
9471
+ }
9472
+ },
9473
+ shouldShow: ({ editor, view, state, from }) => {
9474
+ return isColumnSelectorActive({ editor, view, state, from: from || 0 });
9475
+ }
9476
+ }));
9477
+ this.editor.registerPlugin(BubbleMenuPlugin({
9478
+ pluginKey: 'bubbleMenuTable',
9479
+ editor: this.editor,
9480
+ element: this.bubbleMenuTableRef.nativeElement,
9481
+ tippyOptions: {
9482
+ appendTo: () => document.body,
9483
+ offset: [0, 8],
9484
+ popperOptions: {
9485
+ modifiers: [{ name: 'flip', enabled: false }]
9486
+ }
9487
+ },
9488
+ shouldShow: ({ editor, state }) => {
9489
+ return editor.isEditable && isTableSelected(state.selection);
9490
+ }
9491
+ }));
9492
+ }
9493
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TableBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9494
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TableBubbleMenuComponent, isStandalone: true, selector: "lx-table-bubble-menu", inputs: { editor: "editor" }, viewQueries: [{ propertyName: "bubbleMenuRowRef", first: true, predicate: ["bubbleMenuRow"], descendants: true }, { propertyName: "bubbleMenuColumnRef", first: true, predicate: ["bubbleMenuColumn"], descendants: true }, { propertyName: "bubbleMenuTableRef", first: true, predicate: ["bubbleMenuTable"], descendants: true }], ngImport: i0, template: "<div #bubbleMenuRow class=\"bubble-menu-row\">\n <button (click)=\"editor.chain().focus().addRowBefore().run()\">{{ NAME + '.addRowBefore' | translate }}</button>\n <button (click)=\"editor.chain().focus().addRowAfter().run()\">{{ NAME + '.addRowAfter' | translate }}</button>\n <button (click)=\"editor.chain().focus().deleteRow().run()\">{{ NAME + '.deleteRow' | translate }}</button>\n</div>\n\n<div #bubbleMenuColumn class=\"bubble-menu-column\">\n <button (click)=\"editor.chain().focus().addColumnBefore().run()\">{{ NAME + '.addColumnBefore' | translate }}</button>\n <button (click)=\"editor.chain().focus().addColumnAfter().run()\">{{ NAME + '.addColumnAfter' | translate }}</button>\n <button (click)=\"editor.chain().focus().deleteColumn().run()\">{{ NAME + '.deleteColumn' | translate }}</button>\n</div>\n\n<div #bubbleMenuTable class=\"bubble-menu-table\">\n <button (click)=\"editor.chain().focus().addRowAfter().run()\">{{ NAME + '.addRow' | translate }}</button>\n <button (click)=\"editor.chain().focus().addColumnAfter().run()\">{{ NAME + '.addColumn' | translate }}</button>\n <button (click)=\"editor.chain().focus().deleteTable().run()\">{{ NAME + '.deleteTable' | translate }}</button>\n</div>\n", styles: [".bubble-menu-column,.bubble-menu-row,.bubble-menu-table{display:flex;flex-direction:column;background:#fff;border:1px solid #e1e5eb;border-radius:3px;z-index:10;box-shadow:0 8px 12px 2px #00000026;min-width:160px}.bubble-menu-column button,.bubble-menu-row button,.bubble-menu-table button{background:none;border:none;padding:8px;text-align:left;width:100%;font-size:var(--lxFontSize);color:#2a303d;cursor:pointer;border-radius:3px;transition:background-color .2s}.bubble-menu-column button:hover,.bubble-menu-row button:hover,.bubble-menu-table button:hover{background-color:#e1e5eb}\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9495
+ }
9496
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TableBubbleMenuComponent, decorators: [{
9497
+ type: Component,
9498
+ args: [{ selector: 'lx-table-bubble-menu', imports: [TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div #bubbleMenuRow class=\"bubble-menu-row\">\n <button (click)=\"editor.chain().focus().addRowBefore().run()\">{{ NAME + '.addRowBefore' | translate }}</button>\n <button (click)=\"editor.chain().focus().addRowAfter().run()\">{{ NAME + '.addRowAfter' | translate }}</button>\n <button (click)=\"editor.chain().focus().deleteRow().run()\">{{ NAME + '.deleteRow' | translate }}</button>\n</div>\n\n<div #bubbleMenuColumn class=\"bubble-menu-column\">\n <button (click)=\"editor.chain().focus().addColumnBefore().run()\">{{ NAME + '.addColumnBefore' | translate }}</button>\n <button (click)=\"editor.chain().focus().addColumnAfter().run()\">{{ NAME + '.addColumnAfter' | translate }}</button>\n <button (click)=\"editor.chain().focus().deleteColumn().run()\">{{ NAME + '.deleteColumn' | translate }}</button>\n</div>\n\n<div #bubbleMenuTable class=\"bubble-menu-table\">\n <button (click)=\"editor.chain().focus().addRowAfter().run()\">{{ NAME + '.addRow' | translate }}</button>\n <button (click)=\"editor.chain().focus().addColumnAfter().run()\">{{ NAME + '.addColumn' | translate }}</button>\n <button (click)=\"editor.chain().focus().deleteTable().run()\">{{ NAME + '.deleteTable' | translate }}</button>\n</div>\n", styles: [".bubble-menu-column,.bubble-menu-row,.bubble-menu-table{display:flex;flex-direction:column;background:#fff;border:1px solid #e1e5eb;border-radius:3px;z-index:10;box-shadow:0 8px 12px 2px #00000026;min-width:160px}.bubble-menu-column button,.bubble-menu-row button,.bubble-menu-table button{background:none;border:none;padding:8px;text-align:left;width:100%;font-size:var(--lxFontSize);color:#2a303d;cursor:pointer;border-radius:3px;transition:background-color .2s}.bubble-menu-column button:hover,.bubble-menu-row button:hover,.bubble-menu-table button:hover{background-color:#e1e5eb}\n"] }]
9499
+ }], propDecorators: { editor: [{
9500
+ type: Input,
9501
+ args: [{ required: true }]
9502
+ }], bubbleMenuRowRef: [{
9503
+ type: ViewChild,
9504
+ args: ['bubbleMenuRow']
9505
+ }], bubbleMenuColumnRef: [{
9506
+ type: ViewChild,
9507
+ args: ['bubbleMenuColumn']
9508
+ }], bubbleMenuTableRef: [{
9509
+ type: ViewChild,
9510
+ args: ['bubbleMenuTable']
9511
+ }] } });
9512
+
9513
+ const CustomTable = Table$1.configure({
9514
+ resizable: true,
9515
+ HTMLAttributes: {
9516
+ class: 'table-base-class'
9517
+ }
9518
+ });
9519
+ const CustomTableRow = TableRow.extend({
9520
+ allowGapCursor: false,
9521
+ content: '(tableCell | tableHeader)*'
9522
+ });
9523
+ const CustomTableCell = TableCell.extend({
9524
+ addProseMirrorPlugins() {
9525
+ return [
9526
+ new Plugin({
9527
+ props: {
9528
+ decorations: (state) => {
9529
+ if (!this.editor.isEditable) {
9530
+ return DecorationSet.empty;
9531
+ }
9532
+ const { doc, selection } = state;
9533
+ const columnCells = getCellsInColumn(0, selection);
9534
+ const decoList = [];
9535
+ if (columnCells?.length) {
9536
+ columnCells.forEach(({ pos }, rowIndex) => {
9537
+ const isSelected = isRowSelected(rowIndex, selection);
9538
+ const button = document.createElement('button');
9539
+ const classList = ['selector-row'];
9540
+ if (isSelected)
9541
+ classList.push('selected');
9542
+ if (rowIndex === 0)
9543
+ classList.push('first');
9544
+ if (rowIndex === columnCells.length - 1)
9545
+ classList.push('last');
9546
+ button.ariaLabel = `select row ${rowIndex + 1}`;
9547
+ button.className = classList.join(' ');
9548
+ button.addEventListener('mousedown', (e) => {
9549
+ e.preventDefault();
9550
+ e.stopImmediatePropagation();
9551
+ const tr = selectRow(rowIndex, this.editor.state.tr);
9552
+ this.editor.view.dispatch(tr);
9553
+ });
9554
+ decoList.push(Decoration.widget(pos + 1, () => button));
9555
+ });
9556
+ }
9557
+ return DecorationSet.create(doc, decoList);
9558
+ }
9559
+ }
9560
+ })
9561
+ ];
9562
+ }
9563
+ });
9564
+ const CustomTableHeader = TiptapTableHeader.extend({
9565
+ addAttributes() {
9566
+ return {
9567
+ colspan: {
9568
+ default: 1
9569
+ },
9570
+ rowspan: {
9571
+ default: 1
9572
+ },
9573
+ colwidth: {
9574
+ default: null,
9575
+ parseHTML: (element) => {
9576
+ const raw = element.getAttribute('colwidth');
9577
+ return raw ? raw.split(',').map(Number) : null;
9578
+ }
9579
+ },
9580
+ style: {
9581
+ default: null
9582
+ }
9583
+ };
9584
+ },
9585
+ addProseMirrorPlugins() {
9586
+ return [
9587
+ new Plugin({
9588
+ key: new PluginKey('custom-table-header'),
9589
+ props: {
9590
+ decorations: (state) => {
9591
+ if (!this.editor.isEditable) {
9592
+ return DecorationSet.empty;
9593
+ }
9594
+ const { doc, selection } = state;
9595
+ const headerCells = getCellsInRow(0, selection);
9596
+ const allDecorations = [];
9597
+ if (headerCells?.length) {
9598
+ headerCells.forEach(({ pos }, columnIndex) => {
9599
+ const isSelected = isColumnSelected(columnIndex, selection);
9600
+ const button = document.createElement('button');
9601
+ const classes = ['selector-column'];
9602
+ if (isSelected)
9603
+ classes.push('selected');
9604
+ if (columnIndex === 0)
9605
+ classes.push('first');
9606
+ if (columnIndex === headerCells.length - 1)
9607
+ classes.push('last');
9608
+ button.ariaLabel = `select column ${columnIndex + 1}`;
9609
+ button.className = classes.join(' ');
9610
+ button.addEventListener('mousedown', (e) => {
9611
+ e.preventDefault();
9612
+ e.stopImmediatePropagation();
9613
+ const tr = selectColumn(columnIndex, this.editor.state.tr);
9614
+ this.editor.view.dispatch(tr);
9615
+ });
9616
+ allDecorations.push(Decoration.widget(pos + 1, () => button));
9617
+ });
9618
+ }
9619
+ return DecorationSet.create(doc, allDecorations);
9620
+ }
9621
+ }
9622
+ })
9623
+ ];
9624
+ }
9625
+ });
9626
+
9627
+ const trackingPluginKey = new PluginKey('tracking');
9628
+ function tracking() {
9629
+ return new Plugin({
9630
+ key: trackingPluginKey
9631
+ });
9632
+ }
9633
+
9634
+ const Tracking = Extension.create({
9635
+ name: 'tracking',
9636
+ addProseMirrorPlugins() {
9637
+ return [tracking()];
9638
+ },
9639
+ addCommands() {
9640
+ return {
9641
+ track: (trackingEvent) => ({ tr }) => {
9642
+ tr.setMeta(trackingPluginKey, trackingEvent);
9643
+ return true;
9644
+ }
9645
+ };
9646
+ }
9647
+ });
9648
+
9649
+ const truncatePluginKey = new PluginKey('truncate');
9650
+ function truncate(editor) {
9651
+ return new Plugin({
9652
+ key: truncatePluginKey,
9653
+ state: {
9654
+ init() {
9655
+ return { collapsed: true, activated: false, showToggleButton: true, maxLines: 1 };
9656
+ },
9657
+ apply: (tr, state) => {
9658
+ const meta = tr.getMeta(truncatePluginKey);
9659
+ return meta ? { ...state, ...meta } : state;
9660
+ }
9661
+ },
9662
+ view(view) {
9663
+ const resizeObserver = new ResizeObserver(([entry]) => {
9664
+ const state = truncatePluginKey.getState(view.state);
9665
+ if (state.activated || !entry || !entry.target) {
9666
+ return;
9667
+ }
9668
+ if (entry.target.clientHeight < entry.target.scrollHeight) {
9669
+ view.dispatch(view.state.tr.setMeta(truncatePluginKey, { ...state, activated: true }));
9670
+ }
9671
+ });
9672
+ resizeObserver.observe(view.dom);
9673
+ return {
9674
+ destroy() {
9675
+ resizeObserver.disconnect();
9676
+ }
9677
+ };
9678
+ },
9679
+ props: {
9680
+ attributes(state) {
9681
+ if (editor.storage['mode'] !== 'view') {
9682
+ return { style: '' };
9683
+ }
9684
+ const collapsed = truncatePluginKey.getState(state).collapsed;
9685
+ const clampStyles = `
9686
+ display: -webkit-box;
9687
+ -webkit-box-orient: vertical;
9688
+ -webkit-line-clamp: ${truncatePluginKey.getState(state).maxLines};
9689
+ overflow: hidden;
9690
+ `;
9691
+ return {
9692
+ style: collapsed ? clampStyles : ''
9693
+ };
9694
+ }
9695
+ }
9696
+ });
9697
+ }
9698
+
9699
+ class TruncateButtonComponent {
9700
+ constructor() {
9701
+ this.NAME = 'TruncateButtonComponent';
9702
+ this.cdr = inject(ChangeDetectorRef);
9703
+ this.update = (event) => {
9704
+ if (event.transaction.getMeta(truncatePluginKey)) {
9705
+ this.cdr.markForCheck();
9706
+ }
9707
+ };
9708
+ }
9709
+ get collapsed() {
9710
+ return truncatePluginKey.getState(this.editor.state).collapsed;
9711
+ }
9712
+ get key() {
9713
+ return this.collapsed ? '.showMore' : '.showLess';
9714
+ }
9715
+ get activated() {
9716
+ return truncatePluginKey.getState(this.editor.state).activated;
9717
+ }
9718
+ get showToggleButton() {
9719
+ return truncatePluginKey.getState(this.editor.state).showToggleButton;
9720
+ }
9721
+ ngOnInit() {
9722
+ this.editor.on('transaction', this.update);
9723
+ }
9724
+ ngOnDestroy() {
9725
+ this.editor.off('transaction', this.update);
9726
+ }
9727
+ onClick(event) {
9728
+ event.stopPropagation();
9729
+ const state = truncatePluginKey.getState(this.editor.view.state);
9730
+ this.editor.view.dispatch(this.editor.view.state.tr.setMeta(truncatePluginKey, {
9731
+ ...state,
9732
+ collapsed: !state.collapsed
9733
+ }));
9734
+ }
9735
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9736
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: TruncateButtonComponent, isStandalone: true, selector: "lx-truncate-button", inputs: { editor: "editor" }, ngImport: i0, template: "@if (showToggleButton && activated) {\n <button lx-button color=\"primary\" mode=\"link\" (click)=\"onClick($event)\">\n {{ NAME + key | translate }}\n </button>\n}\n", dependencies: [{ kind: "component", type: ButtonComponent, selector: "button[lx-button]", inputs: ["size", "color", "mode", "pressed", "selected", "square", "circle", "disabled", "icon", "endIcon", "showSpinner"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9737
+ }
9738
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateButtonComponent, decorators: [{
9739
+ type: Component,
9740
+ args: [{ selector: 'lx-truncate-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ButtonComponent, TranslatePipe], template: "@if (showToggleButton && activated) {\n <button lx-button color=\"primary\" mode=\"link\" (click)=\"onClick($event)\">\n {{ NAME + key | translate }}\n </button>\n}\n" }]
9741
+ }], propDecorators: { editor: [{
9742
+ type: Input,
9743
+ args: [{ required: true }]
9744
+ }] } });
9745
+
9746
+ const Truncate = Extension.create({
9747
+ name: 'truncate',
9748
+ addOptions() {
9749
+ return {
9750
+ maxLines: 1,
9751
+ showToggleButton: true
9752
+ };
9753
+ },
9754
+ addProseMirrorPlugins() {
9755
+ const { editor } = this;
9756
+ return [truncate(editor)];
9757
+ }
9758
+ });
9759
+
9760
+ class ExtensionsBuilder {
9761
+ constructor() {
9762
+ this.injector = inject(Injector);
9763
+ this.pluginRegistry = {
9764
+ Bold: [Bold],
9765
+ Truncate: [Truncate],
9766
+ Heading: [Heading.configure({ levels: [1, 2, 3, 4] })],
9767
+ Italic: [Italic],
9768
+ Underline: [Underline],
9769
+ Strike: [Strike],
9770
+ List: [BulletList, OrderedList],
9771
+ TextAlign: [TextAlign.configure({ types: ['heading', 'paragraph'] })],
9772
+ Code: [Code, CodeBlock],
9773
+ HighlightTerm: [HighlightTerm],
9774
+ Table: [CustomTable, CustomTableCell, CustomTableHeader, CustomTableRow],
9775
+ Link: [CustomLink]
9776
+ };
9777
+ }
9778
+ build(features, output) {
9779
+ const extensions = [Document, Dropcursor, Gapcursor, History, HardBreak, ListItem, Paragraph, Text, Tracking];
9780
+ if (output === 'markdown') {
9781
+ extensions.push(...[
9782
+ Markdown.configure({
9783
+ transformPastedText: true, // Allow to paste markdown text in the editor
9784
+ breaks: true
9785
+ })
9786
+ ]);
9787
+ }
9788
+ if (output === 'json') {
9789
+ extensions.push(...[HorizontalRule]);
9790
+ }
9791
+ for (const feature of features) {
9792
+ extensions.push(...(this.pluginRegistry[feature] ?? []));
9793
+ }
9794
+ return extensions;
9795
+ }
9796
+ buildCustomExtensions(extensionFactories) {
9797
+ const extensions = [];
9798
+ for (const extension of extensionFactories) {
9799
+ const node = extension(this.injector);
9800
+ if (node) {
9801
+ extensions.push(node);
9802
+ }
9803
+ }
9804
+ return extensions;
9805
+ }
9806
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionsBuilder, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9807
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionsBuilder }); }
9808
+ }
9809
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionsBuilder, decorators: [{
9810
+ type: Injectable
9811
+ }] });
9812
+
9813
+ const EDITOR_FEATURES = {
9814
+ markdown: ['Bold', 'Italic', 'List', 'Truncate', 'Link', 'Strike', 'Underline', 'HighlightTerm'],
9815
+ json: ['Heading', 'Bold', 'Italic', 'Link', 'Underline', 'Strike', 'List', 'TextAlign', 'Code', 'Table']
9816
+ };
9817
+
9818
+ class TipTapEditorDirective {
9819
+ constructor() {
9820
+ this.NAME = 'TipTapEditorDirective';
9821
+ this.outputFormat = input.required();
9822
+ this.ariaLabelledBy = input(null);
9823
+ this.additionalFeatures = input([]);
9824
+ this.customExtensions = input([]);
9825
+ this.mode = input.required();
9826
+ this.features = computed(() => EDITOR_FEATURES[this.outputFormat()]);
9827
+ this.attributes = computed(() => {
9828
+ const attributes = {
9829
+ role: 'textbox' // make conenteditable div behave as textarea, needed for a11y
9830
+ };
9831
+ if (this.ariaLabelledBy()) {
9832
+ attributes['aria-labelledby'] = this.ariaLabelledBy();
9833
+ }
9834
+ return attributes;
9835
+ });
9836
+ this.editor = computed(() => {
9837
+ const extensions = [
9838
+ ...this.extensionsBuilder.build([...this.features(), ...this.additionalFeatures()], this.outputFormat()),
9839
+ ...this.extensionsBuilder.buildCustomExtensions(this.customExtensions())
9840
+ ];
9841
+ return new Editor({
9842
+ extensions,
9843
+ editorProps: {
9844
+ attributes: {
9845
+ ...this.attributes()
9846
+ }
9847
+ }
9848
+ });
9849
+ });
9850
+ this.extensionsBuilder = inject(ExtensionsBuilder);
9851
+ afterRenderEffect(() => {
9852
+ this.editor().storage['mode'] = this.mode();
9853
+ });
9854
+ }
9855
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TipTapEditorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
9856
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.6", type: TipTapEditorDirective, isStandalone: true, selector: "lx-rich-text-editor[lxEditor]", inputs: { outputFormat: { classPropertyName: "outputFormat", publicName: "outputFormat", isSignal: true, isRequired: true, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, additionalFeatures: { classPropertyName: "additionalFeatures", publicName: "additionalFeatures", isSignal: true, isRequired: false, transformFunction: null }, customExtensions: { classPropertyName: "customExtensions", publicName: "customExtensions", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
9857
+ }
9858
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TipTapEditorDirective, decorators: [{
9859
+ type: Directive,
9860
+ args: [{
9861
+ selector: 'lx-rich-text-editor[lxEditor]'
9862
+ }]
9863
+ }], ctorParameters: () => [] });
9864
+
9865
+ class TrackingDirective {
9866
+ constructor() {
9867
+ this.trackEvent = output();
9868
+ this.editor = inject(TipTapEditorDirective).editor;
9869
+ afterRenderEffect((onCleanup) => {
9870
+ this.editor().on('transaction', ({ transaction }) => {
9871
+ const meta = transaction.getMeta(trackingPluginKey);
9872
+ if (meta) {
9873
+ this.trackEvent.emit(meta);
9874
+ }
9875
+ onCleanup(() => {
9876
+ this.editor().off('transaction');
9877
+ });
9878
+ });
9879
+ });
9880
+ }
9881
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TrackingDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
9882
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: TrackingDirective, isStandalone: true, selector: "lx-rich-text-editor[lxTracking]", outputs: { trackEvent: "trackEvent" }, ngImport: i0 }); }
9883
+ }
9884
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TrackingDirective, decorators: [{
9885
+ type: Directive,
9886
+ args: [{
9887
+ selector: 'lx-rich-text-editor[lxTracking]'
9888
+ }]
9889
+ }], ctorParameters: () => [] });
9890
+
9891
+ class EditorDirective {
9892
+ constructor(elRef, renderer, changeDetectorRef) {
9893
+ this.elRef = elRef;
9894
+ this.renderer = renderer;
9895
+ this.changeDetectorRef = changeDetectorRef;
9896
+ this.blur = new EventEmitter();
9897
+ this.onChange = () => {
9898
+ /** */
9899
+ };
9900
+ this.onTouched = () => {
9901
+ /** */
9902
+ };
9903
+ this.handleChange = ({ editor, transaction }) => {
9904
+ if (!transaction.docChanged) {
9905
+ return;
9906
+ }
9907
+ // Needed for ChangeDetectionStrategy.OnPush to get notified about changes
9908
+ this.changeDetectorRef.markForCheck();
9909
+ if (this.outputFormat === 'markdown') {
9910
+ this.onChange(editor.storage['markdown'].getMarkdown());
9911
+ return;
9912
+ }
9913
+ this.onChange(editor.getJSON());
9914
+ };
9915
+ }
9916
+ // Writes a new value to the element.
9917
+ // This methods is called when programmatic changes from model to view are requested.
9918
+ writeValue(value) {
9919
+ this.editor.chain().setContent(value, false).run();
9920
+ }
9921
+ // Registers a callback function that is called when the control's value changes in the UI.
9922
+ registerOnChange(fn) {
9923
+ this.onChange = fn;
9924
+ }
9925
+ // Registers a callback function that is called by the forms API on initialization to update the form model on blur.
9926
+ registerOnTouched(fn) {
9927
+ this.onTouched = fn;
9928
+ }
9929
+ // Called by the forms api to enable or disable the element
9930
+ setDisabledState(isDisabled) {
9931
+ this.editor.setEditable(!isDisabled);
9932
+ this.renderer.setProperty(this.elRef.nativeElement, 'disabled', isDisabled);
9933
+ }
9934
+ ngOnInit() {
9935
+ // take the inner contents and clear the block
9936
+ const { innerHTML } = this.elRef.nativeElement;
9937
+ this.elRef.nativeElement.innerHTML = '';
9938
+ // insert the editor in the dom
9939
+ this.elRef.nativeElement.append(...Array.from(this.editor.options.element.childNodes));
9940
+ // update the options for the editor
9941
+ this.editor.setOptions({ element: this.elRef.nativeElement });
9942
+ // update content to the editor
9943
+ if (innerHTML) {
9944
+ this.editor.chain().setContent(innerHTML, false).run();
9945
+ }
9946
+ // register blur handler to update `touched` property
9947
+ this.editor.on('blur', () => {
9948
+ this.blur.emit();
9949
+ this.onTouched();
9950
+ });
9951
+ // register update handler to listen to changes on update
9952
+ this.editor.on('update', this.handleChange);
9953
+ // Needed for ChangeDetectionStrategy.OnPush to get notified
9954
+ this.editor.on('selectionUpdate', () => this.changeDetectorRef.markForCheck());
9955
+ }
9956
+ ngAfterViewInit() {
9957
+ this.changeDetectorRef.detectChanges();
9958
+ }
9959
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EditorDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
9960
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: EditorDirective, isStandalone: true, selector: "tiptap-editor[editor]", inputs: { editor: "editor", outputFormat: "outputFormat" }, outputs: { blur: "blur" }, providers: [
9961
+ {
9962
+ provide: NG_VALUE_ACCESSOR,
9963
+ useExisting: forwardRef(() => EditorDirective),
9964
+ multi: true
9965
+ }
9966
+ ], ngImport: i0 }); }
9967
+ }
9968
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EditorDirective, decorators: [{
9969
+ type: Directive,
9970
+ args: [{
9971
+ // eslint-disable-next-line @angular-eslint/directive-selector
9972
+ selector: 'tiptap-editor[editor]',
9973
+ providers: [
9974
+ {
9975
+ provide: NG_VALUE_ACCESSOR,
9976
+ useExisting: forwardRef(() => EditorDirective),
9977
+ multi: true
9978
+ }
9979
+ ]
9980
+ }]
9981
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }], propDecorators: { editor: [{
9982
+ type: Input,
9983
+ args: [{ required: true }]
9984
+ }], outputFormat: [{
9985
+ type: Input,
9986
+ args: [{ required: true }]
9987
+ }], blur: [{
9988
+ type: Output
9989
+ }] } });
9990
+
9991
+ class ExtensionEnabledPipe {
9992
+ transform(name, editor) {
9993
+ return !!editor.extensionManager.extensions.find((extension) => extension.name === name);
9994
+ }
9995
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionEnabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
9996
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: ExtensionEnabledPipe, isStandalone: true, name: "lxExtensionEnabled" }); }
9997
+ }
9998
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionEnabledPipe, decorators: [{
9999
+ type: Pipe,
10000
+ args: [{
10001
+ name: 'lxExtensionEnabled'
10002
+ }]
10003
+ }] });
10004
+
10005
+ class ModalFooterComponent {
10006
+ constructor() {
10007
+ this.border = false;
10008
+ }
10009
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10010
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: ModalFooterComponent, isStandalone: true, selector: "lx-modal-footer", inputs: { border: "border" }, ngImport: i0, template: "<div class=\"footerContainer\" [class.border]=\"border\">\n <ng-content />\n</div>\n", styles: [":host{display:block;text-align:right;--modal-footer-padding-left: 90px;--modal-footer-padding-right: 0;--modal-footer-display: block}:host-context(.fullscreen) .footerContainer{padding-left:var(--modal-footer-padding-left);padding-right:var(--modal-footer-padding-right);height:70px}:host-context(.dialog) .footerContainer,:host-context(.dialog-large) .footerContainer{padding:16px;height:64px;border-radius:0 0 6px 6px;display:var(--modal-footer-display)}.footerContainer.border{border-top:1px solid #cfd5df}\n"] }); }
10011
+ }
10012
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalFooterComponent, decorators: [{
10013
+ type: Component,
10014
+ args: [{ selector: 'lx-modal-footer', template: "<div class=\"footerContainer\" [class.border]=\"border\">\n <ng-content />\n</div>\n", styles: [":host{display:block;text-align:right;--modal-footer-padding-left: 90px;--modal-footer-padding-right: 0;--modal-footer-display: block}:host-context(.fullscreen) .footerContainer{padding-left:var(--modal-footer-padding-left);padding-right:var(--modal-footer-padding-right);height:70px}:host-context(.dialog) .footerContainer,:host-context(.dialog-large) .footerContainer{padding:16px;height:64px;border-radius:0 0 6px 6px;display:var(--modal-footer-display)}.footerContainer.border{border-top:1px solid #cfd5df}\n"] }]
10015
+ }], propDecorators: { border: [{
10016
+ type: Input
10017
+ }] } });
10018
+
10019
+ class ModalHeaderComponent {
10020
+ constructor() {
10021
+ this.title = '';
10022
+ this.bottomBorder = true;
10023
+ }
10024
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10025
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: ModalHeaderComponent, isStandalone: true, selector: "lx-modal-header", inputs: { title: "title", bottomBorder: "bottomBorder" }, ngImport: i0, template: "<div class=\"headerContainer\" [class.bottomBorder]=\"bottomBorder\">\n <ng-content />\n @if (title.length > 0) {\n <h1 class=\"lx-heading-2\">{{ title }}</h1>\n }\n</div>\n", styles: [":host{display:block}:host.lxModalHeaderOneLine h1{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:var(--lx-modal-header-max-width)}:host-context(.fullscreen) .headerContainer{padding:24px 90px;border-bottom:none}:host-context(.fullscreen) .headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}:host-context(.dialog) .headerContainer,:host-context(.dialog-large) .headerContainer{padding:24px 16px;border-radius:6px 6px 0 0}:host-context(.dialog) .headerContainer h1,:host-context(.dialog-large) .headerContainer h1{word-break:break-word}h1{margin:0 auto;padding:0;color:#2a303d;text-align:center}.headerContainer{display:flex}.headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}\n"] }); }
10026
+ }
10027
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalHeaderComponent, decorators: [{
10028
+ type: Component,
10029
+ args: [{ selector: 'lx-modal-header', template: "<div class=\"headerContainer\" [class.bottomBorder]=\"bottomBorder\">\n <ng-content />\n @if (title.length > 0) {\n <h1 class=\"lx-heading-2\">{{ title }}</h1>\n }\n</div>\n", styles: [":host{display:block}:host.lxModalHeaderOneLine h1{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:var(--lx-modal-header-max-width)}:host-context(.fullscreen) .headerContainer{padding:24px 90px;border-bottom:none}:host-context(.fullscreen) .headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}:host-context(.dialog) .headerContainer,:host-context(.dialog-large) .headerContainer{padding:24px 16px;border-radius:6px 6px 0 0}:host-context(.dialog) .headerContainer h1,:host-context(.dialog-large) .headerContainer h1{word-break:break-word}h1{margin:0 auto;padding:0;color:#2a303d;text-align:center}.headerContainer{display:flex}.headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}\n"] }]
10030
+ }], propDecorators: { title: [{
10031
+ type: Input
10032
+ }], bottomBorder: [{
10033
+ type: Input
10034
+ }] } });
10035
+
10036
+ class ModalContentDirective {
10037
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10038
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: ModalContentDirective, isStandalone: true, selector: "[lxModalContent]", ngImport: i0 }); }
10039
+ }
10040
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, decorators: [{
10041
+ type: Directive,
10042
+ args: [{
10043
+ selector: '[lxModalContent]'
10044
+ }]
10045
+ }] });
10046
+
10047
+ const MODAL_CLOSE = new InjectionToken('MODAL_CLOSE');
10048
+ /**
10049
+ * An enum to track how the modal was closed
10050
+ * Escape - Esc key press
10051
+ * CloseButton - top right close button (x)
10052
+ * OutsideClick - click outside the modal
10053
+ * Other - modal close with the trigger through closeModal$ subject
10054
+ */
10055
+ var ModalCloseClickLocation;
10056
+ (function (ModalCloseClickLocation) {
10057
+ ModalCloseClickLocation["Escape"] = "escape";
10058
+ ModalCloseClickLocation["CloseButton"] = "closeButton";
10059
+ ModalCloseClickLocation["OutsideClick"] = "outsideClick";
10060
+ ModalCloseClickLocation["CancelButton"] = "cancelButton";
10061
+ ModalCloseClickLocation["Other"] = "other";
10062
+ })(ModalCloseClickLocation || (ModalCloseClickLocation = {}));
10063
+
10064
+ /**
10065
+ * This documentation provides details on the usage and configuration of the Modal.
10066
+ *
10067
+ * ## Usage
10068
+ *
10069
+ * 1. Import `LxModalModule` and `LxCoreUiModule` modules from `@leanix/components` in your module where you want to use the component.
10070
+ *
10071
+ * ```ts
10072
+ * import { LxModalModule, LxCoreUiModule } from '@leanix/components';
10073
+ * ```
10074
+ *
10075
+ * 2. Use the **lx-modal** component in your template with the parameters described below.
10076
+ *
10077
+ * - **`open`**: Whether the modal is open or closed.
10078
+ * - **`size`**: 'dialog' | 'dialog-large'.
10079
+ * - **`verticalScroll`**: Whether the modal is scrollable or not.
10080
+ * - **`showHeader`**: Whether the modal has a header or not.
10081
+ * - **`showFooter`**: Whether the modal has a footer or not.
10082
+ * - **`showCloseButton`**: Whether to show the close button.
10083
+ * - **`showBackButton`**: Whether to show the back button.
10084
+ *
10085
+ * 3. Use optional **lx-modal-header** component in your template with the parameters described below.
10086
+ *
10087
+ * - **`title`**: Title of the modal.
10088
+ * - **`subtitle`**: Subtitle of the modal.
10089
+ * - **`bottomBorder`**: Whether to show a bottom border.
10090
+ *
10091
+ * 4. Use optional **lx-modal-footer** component in your template with the parameters described below.
10092
+ *
10093
+ * - **`border`**: Whether to show the footer at the bottom of the modal.
10094
+ *
10095
+ * **GLOBAL PROVIDERS** required for this component:
10096
+ * - `provideAnimations()`
10097
+ *
10098
+ * **ATTENTION - SCROLLABLE DIALOG**:
10099
+ * The <lx-modal> component when used as "dialog" is not designed to work with a
10100
+ * scrollable body (via `overflow: auto | scroll`) in combination with dropdowns.
10101
+ * The overflow on the body will also clip the dropdowns, which is expected.
10102
+ *
10103
+ * Reasoning:
10104
+ * The contents within the dialog should be just a few elements which fit and
10105
+ * justify the usage of a dialog. If the content is larger than the dialog, and thus
10106
+ * requires scrolling, we should discuss whether to put it into a dialog at all
10107
+ * and rather think about putting the content on a separate route or
10108
+ * using the fullscreen version of the modal.
10109
+ */
10110
+ class ModalComponent {
10111
+ get content() {
10112
+ return this.explicitContent || this.implicitContent;
10113
+ }
10114
+ /** @internal */
10115
+ onEscape() {
10116
+ if (this.open && this.showCloseButton) {
10117
+ this.closeModal(ModalCloseClickLocation.Escape);
10118
+ }
10119
+ }
10120
+ constructor(overlay, renderer, closeModal$, focusTrap) {
10121
+ this.overlay = overlay;
9031
10122
  this.renderer = renderer;
9032
- this.ngControl = ngControl;
9033
- this.lxMaxLengthCounter = null;
9034
- this.counterElement = null;
9035
- this.destroy$ = new Subject();
10123
+ this.closeModal$ = closeModal$;
10124
+ this.focusTrap = focusTrap;
10125
+ /** @internal */
10126
+ this.NAME = 'ModalComponent';
10127
+ /** Whether the modal is open or closed. */
10128
+ this.open = false;
10129
+ /** Whether to show the close button. */
10130
+ this.showCloseButton = true;
10131
+ /** Whether to show the back button. */
10132
+ this.showBackButton = false;
10133
+ /*
10134
+ * If true, then the content area scrolls vertically instead of expanding its height.
10135
+ * This can be a problem if the content has dropdowns or date inputs, but can be good if the content has a huge amount of text.
10136
+ */
10137
+ this.verticalScroll = false;
10138
+ /** The size of the modal. */
10139
+ this.size = 'fullscreen';
10140
+ /**
10141
+ * Minimum width of the modal.
10142
+ *
10143
+ * _NB: Some modal implementations rely on this minWidth being 600px_
10144
+ */
10145
+ this.minWidth = '600px';
10146
+ /** Whether the modal is a focus trap. */
10147
+ this.isFocusTrap = false;
10148
+ /** Event emitted when the modal is closed. */
10149
+ this.close = new EventEmitter();
10150
+ /** Event emitted when the back button is clicked. */
10151
+ this.back = new EventEmitter();
10152
+ /** @internal */
10153
+ this.closeLocation = ModalCloseClickLocation;
10154
+ /** @internal */
10155
+ this.destroyed$ = new Subject();
10156
+ }
10157
+ ngOnInit() {
10158
+ this.closeModal$
10159
+ ?.pipe(takeUntil(this.destroyed$))
10160
+ .subscribe((closeLocation) => this.closeModal(closeLocation));
10161
+ if (this.size === 'fullscreen') {
10162
+ this.overlayRef = this.overlay.create({
10163
+ panelClass: this.size,
10164
+ width: '100%',
10165
+ height: '100vh'
10166
+ });
10167
+ }
10168
+ else if (this.size === 'dialog-large') {
10169
+ const positionStrategy = this.overlay.position().global().top('4vh').centerHorizontally();
10170
+ this.overlayRef = this.overlay.create({
10171
+ panelClass: this.size,
10172
+ positionStrategy,
10173
+ hasBackdrop: true,
10174
+ width: '90%',
10175
+ height: '90vh'
10176
+ });
10177
+ }
10178
+ else {
10179
+ // size 'dialog'
10180
+ const positionStrategy = this.overlay.position().global().top('8vh').centerHorizontally();
10181
+ this.overlayRef = this.overlay.create({
10182
+ panelClass: this.size,
10183
+ minWidth: this.minWidth,
10184
+ positionStrategy,
10185
+ hasBackdrop: true,
10186
+ scrollStrategy: this.overlay.scrollStrategies.block()
10187
+ });
10188
+ }
10189
+ if (this.size !== 'fullscreen') {
10190
+ this.overlayRef
10191
+ .backdropClick()
10192
+ .pipe(takeUntil(this.destroyed$))
10193
+ .subscribe(() => this.closeModal(ModalCloseClickLocation.OutsideClick));
10194
+ }
10195
+ }
10196
+ ngOnChanges() {
10197
+ if (this.open && this.overlayRef && !this.overlayRef.hasAttached()) {
10198
+ this.openModal();
10199
+ }
10200
+ if (!this.open && this.overlayRef && this.overlayRef.hasAttached()) {
10201
+ this.closeModal(ModalCloseClickLocation.Other);
10202
+ }
10203
+ if (this.open && this.overlayRef && this.overlayRef.hasAttached() && this.isFocusTrap) {
10204
+ this.trapFocusInModal(this.overlayRef.hostElement);
10205
+ }
10206
+ }
10207
+ ngAfterViewInit() {
10208
+ if (this.open) {
10209
+ timer(0)
10210
+ .pipe(takeUntil(this.destroyed$))
10211
+ .subscribe(() => this.openModal());
10212
+ }
10213
+ }
10214
+ /** @internal */
10215
+ openModal() {
10216
+ this.oldOverflow = document.documentElement.style.overflowY;
10217
+ if (this.size === 'fullscreen') {
10218
+ this.renderer.setStyle(document.documentElement, 'overflowY', 'hidden');
10219
+ }
10220
+ this.overlayRef.attach(this.cdkPortal);
10221
+ this.trapFocusInModal(this.overlayRef.hostElement);
10222
+ }
10223
+ /** @internal */
10224
+ emitBack() {
10225
+ this.back.emit();
10226
+ }
10227
+ ngOnDestroy() {
10228
+ this.destroyed$.next();
10229
+ if (this.size === 'fullscreen') {
10230
+ this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
10231
+ }
10232
+ if (this.overlayRef) {
10233
+ this.overlayRef.dispose();
10234
+ }
10235
+ }
10236
+ /** @internal */
10237
+ async closeModal(closeLocation) {
10238
+ if (!this.canModalBeClosed || (await this.canModalBeClosed(closeLocation))) {
10239
+ this.open = false;
10240
+ this.overlayRef.detach();
10241
+ if (this.size === 'fullscreen') {
10242
+ this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
10243
+ }
10244
+ this.close.emit(typeof closeLocation === 'boolean' ? ModalCloseClickLocation.Other : closeLocation);
10245
+ }
10246
+ }
10247
+ trapFocusInModal(hostElement) {
10248
+ this.focusTrap.create(hostElement);
10249
+ }
10250
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalComponent, deps: [{ token: i1$2.Overlay }, { token: i0.Renderer2 }, { token: MODAL_CLOSE, optional: true }, { token: i2.ConfigurableFocusTrapFactory }], target: i0.ɵɵFactoryTarget.Component }); }
10251
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: ModalComponent, isStandalone: true, selector: "lx-modal", inputs: { open: "open", showCloseButton: "showCloseButton", showBackButton: "showBackButton", verticalScroll: "verticalScroll", size: "size", minWidth: "minWidth", isFocusTrap: "isFocusTrap", canModalBeClosed: "canModalBeClosed" }, outputs: { close: "close", back: "back" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, queries: [{ propertyName: "header", first: true, predicate: ModalHeaderComponent, descendants: true }, { propertyName: "footer", first: true, predicate: ModalFooterComponent, descendants: true }, { propertyName: "explicitContent", first: true, predicate: ModalContentDirective, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "cdkPortal", first: true, predicate: CdkPortal, descendants: true, static: true }, { propertyName: "implicitContent", first: true, predicate: ["implicitContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-template cdkPortal>\n @if (open) {\n <div\n role=\"dialog\"\n class=\"lxmodal\"\n [class.lxmodal--fullscreen]=\"size === 'fullscreen'\"\n [class.lxmodal--dialog]=\"size === 'dialog'\"\n [class.lxmodal--dialog-large]=\"size === 'dialog-large'\"\n [class.lxmodal--withFooter]=\"!!footer\"\n [class.lxmodal--verticalScroll]=\"verticalScroll\"\n @modal\n >\n @if (size === 'fullscreen' && showBackButton) {\n <div (click)=\"emitBack()\" (keyup.enter)=\"emitBack()\" tabindex=\"0\" role=\"button\" class=\"fal fa-long-arrow-left\"></div>\n }\n @if (showCloseButton) {\n <!-- TODO lx-button doesn't comply with modal close button style yet -->\n <!-- eslint-disable-next-line @nx/workspace-no-icon-in-button-content -->\n <button\n (click)=\"closeModal(closeLocation.CloseButton)\"\n [attr.aria-label]=\"NAME + '.close' | translate\"\n class=\"fal fa-times closeButton\"\n ></button>\n }\n @if (header) {\n <ng-content select=\"lx-modal-header\" />\n }\n <div class=\"modalContentContainer\" [class.lxThinScrollbar]=\"verticalScroll\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n @if (footer) {\n <ng-content select=\"lx-modal-footer\" />\n }\n </div>\n }\n</ng-template>\n<ng-template #implicitContent>\n <ng-content />\n</ng-template>\n", styles: ["@keyframes subtleScaleUpKeyFrames{0%{transform:scale(.95);opacity:0}}.lxmodal{background:#fff;width:100%}.lxmodal--withFooter.lxmodal--fullscreen.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--withFooter.lxmodal--fullscreen:not(.lxmodal--verticalScroll) .modalContentContainer{bottom:70px;overflow:hidden}.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--fullscreen{height:100%;display:flex;flex-direction:column}.lxmodal--fullscreen .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;right:36px;top:12px;z-index:1}.lxmodal--fullscreen .closeButton:before{cursor:pointer}.lxmodal--fullscreen .closeButton:hover,.lxmodal--fullscreen .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .closeButton:focus{outline:0}.lxmodal--fullscreen .fa-long-arrow-left{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;left:36px;top:16px}.lxmodal--fullscreen .fa-long-arrow-left:before{cursor:pointer}.lxmodal--fullscreen .fa-long-arrow-left:hover,.lxmodal--fullscreen .fa-long-arrow-left:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .fa-long-arrow-left:focus{outline:0}.lxmodal--dialog,.lxmodal--dialog-large{display:block;position:relative;border-radius:6px;box-shadow:0 8px 20px #0000003d}.lxmodal--dialog .modalContentContainer,.lxmodal--dialog-large .modalContentContainer{position:relative}.lxmodal--dialog .closeButton,.lxmodal--dialog-large .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;height:32px;width:32px;z-index:999;right:10px;top:20px}.lxmodal--dialog .closeButton:before,.lxmodal--dialog-large .closeButton:before{cursor:pointer}.lxmodal--dialog .closeButton:hover,.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:hover,.lxmodal--dialog-large .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:focus{outline:0}.lxmodal--dialog .modalContentContainer{padding:16px 16px 0}.lxmodal--dialog.lxmodal--verticalScroll .modalContentContainer{padding:16px;max-height:calc(84vh - 136px)}.lxmodal--dialog-large .modalContentContainer{padding:16px;height:calc(100% - 136px)}.modalContentContainer{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i3.CdkPortal, selector: "[cdkPortal]", exportAs: ["cdkPortal"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: CommonModule }], animations: [
10252
+ trigger('modal', [
10253
+ transition(':enter', [style({ opacity: 0 }), animate('0.15s', style({ opacity: 1 }))]),
10254
+ transition(':leave', animate('0.15s', style({ opacity: 0 })))
10255
+ ])
10256
+ ] }); }
10257
+ }
10258
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalComponent, decorators: [{
10259
+ type: Component,
10260
+ args: [{ selector: 'lx-modal', animations: [
10261
+ trigger('modal', [
10262
+ transition(':enter', [style({ opacity: 0 }), animate('0.15s', style({ opacity: 1 }))]),
10263
+ transition(':leave', animate('0.15s', style({ opacity: 0 })))
10264
+ ])
10265
+ ], imports: [PortalModule, NgTemplateOutlet, TranslateModule, CommonModule], template: "<ng-template cdkPortal>\n @if (open) {\n <div\n role=\"dialog\"\n class=\"lxmodal\"\n [class.lxmodal--fullscreen]=\"size === 'fullscreen'\"\n [class.lxmodal--dialog]=\"size === 'dialog'\"\n [class.lxmodal--dialog-large]=\"size === 'dialog-large'\"\n [class.lxmodal--withFooter]=\"!!footer\"\n [class.lxmodal--verticalScroll]=\"verticalScroll\"\n @modal\n >\n @if (size === 'fullscreen' && showBackButton) {\n <div (click)=\"emitBack()\" (keyup.enter)=\"emitBack()\" tabindex=\"0\" role=\"button\" class=\"fal fa-long-arrow-left\"></div>\n }\n @if (showCloseButton) {\n <!-- TODO lx-button doesn't comply with modal close button style yet -->\n <!-- eslint-disable-next-line @nx/workspace-no-icon-in-button-content -->\n <button\n (click)=\"closeModal(closeLocation.CloseButton)\"\n [attr.aria-label]=\"NAME + '.close' | translate\"\n class=\"fal fa-times closeButton\"\n ></button>\n }\n @if (header) {\n <ng-content select=\"lx-modal-header\" />\n }\n <div class=\"modalContentContainer\" [class.lxThinScrollbar]=\"verticalScroll\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n @if (footer) {\n <ng-content select=\"lx-modal-footer\" />\n }\n </div>\n }\n</ng-template>\n<ng-template #implicitContent>\n <ng-content />\n</ng-template>\n", styles: ["@keyframes subtleScaleUpKeyFrames{0%{transform:scale(.95);opacity:0}}.lxmodal{background:#fff;width:100%}.lxmodal--withFooter.lxmodal--fullscreen.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--withFooter.lxmodal--fullscreen:not(.lxmodal--verticalScroll) .modalContentContainer{bottom:70px;overflow:hidden}.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--fullscreen{height:100%;display:flex;flex-direction:column}.lxmodal--fullscreen .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;right:36px;top:12px;z-index:1}.lxmodal--fullscreen .closeButton:before{cursor:pointer}.lxmodal--fullscreen .closeButton:hover,.lxmodal--fullscreen .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .closeButton:focus{outline:0}.lxmodal--fullscreen .fa-long-arrow-left{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;left:36px;top:16px}.lxmodal--fullscreen .fa-long-arrow-left:before{cursor:pointer}.lxmodal--fullscreen .fa-long-arrow-left:hover,.lxmodal--fullscreen .fa-long-arrow-left:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .fa-long-arrow-left:focus{outline:0}.lxmodal--dialog,.lxmodal--dialog-large{display:block;position:relative;border-radius:6px;box-shadow:0 8px 20px #0000003d}.lxmodal--dialog .modalContentContainer,.lxmodal--dialog-large .modalContentContainer{position:relative}.lxmodal--dialog .closeButton,.lxmodal--dialog-large .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;height:32px;width:32px;z-index:999;right:10px;top:20px}.lxmodal--dialog .closeButton:before,.lxmodal--dialog-large .closeButton:before{cursor:pointer}.lxmodal--dialog .closeButton:hover,.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:hover,.lxmodal--dialog-large .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:focus{outline:0}.lxmodal--dialog .modalContentContainer{padding:16px 16px 0}.lxmodal--dialog.lxmodal--verticalScroll .modalContentContainer{padding:16px;max-height:calc(84vh - 136px)}.lxmodal--dialog-large .modalContentContainer{padding:16px;height:calc(100% - 136px)}.modalContentContainer{flex:1}\n"] }]
10266
+ }], ctorParameters: () => [{ type: i1$2.Overlay }, { type: i0.Renderer2 }, { type: i5.Observable, decorators: [{
10267
+ type: Optional
10268
+ }, {
10269
+ type: Inject,
10270
+ args: [MODAL_CLOSE]
10271
+ }] }, { type: i2.ConfigurableFocusTrapFactory }], propDecorators: { open: [{
10272
+ type: Input
10273
+ }], showCloseButton: [{
10274
+ type: Input
10275
+ }], showBackButton: [{
10276
+ type: Input
10277
+ }], verticalScroll: [{
10278
+ type: Input
10279
+ }], size: [{
10280
+ type: Input
10281
+ }], minWidth: [{
10282
+ type: Input
10283
+ }], isFocusTrap: [{
10284
+ type: Input
10285
+ }], canModalBeClosed: [{
10286
+ type: Input
10287
+ }], close: [{
10288
+ type: Output
10289
+ }], back: [{
10290
+ type: Output
10291
+ }], header: [{
10292
+ type: ContentChild,
10293
+ args: [ModalHeaderComponent]
10294
+ }], footer: [{
10295
+ type: ContentChild,
10296
+ args: [ModalFooterComponent]
10297
+ }], cdkPortal: [{
10298
+ type: ViewChild,
10299
+ args: [CdkPortal, { static: true }]
10300
+ }], implicitContent: [{
10301
+ type: ViewChild,
10302
+ args: ['implicitContent', { static: true }]
10303
+ }], explicitContent: [{
10304
+ type: ContentChild,
10305
+ args: [ModalContentDirective, { read: TemplateRef, static: true }]
10306
+ }], onEscape: [{
10307
+ type: HostListener,
10308
+ args: ['document:keydown.escape']
10309
+ }] } });
10310
+
10311
+ class UrlValidatorDirective {
10312
+ validate(control) {
10313
+ const urlPattern = /^(https?:\/\/)?([\w-]+\.)*[\w-]+\.[a-z]{2,}(\/[^\s]*)?$/i;
10314
+ return urlPattern.test(control.value || '') ? null : { invalidUrl: true };
9036
10315
  }
9037
- ngOnInit() {
9038
- this.setupMaxLengthValidation();
9039
- this.connectCounter();
9040
- this.updateCounter();
9041
- this.subscribeToValueChanges();
9042
- this.setupInputRestriction();
9043
- this.checkForInitialOversize();
10316
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UrlValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10317
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: UrlValidatorDirective, isStandalone: true, selector: "[lxUrl][ngModel]", providers: [
10318
+ {
10319
+ provide: NG_VALIDATORS,
10320
+ useExisting: UrlValidatorDirective,
10321
+ multi: true
10322
+ }
10323
+ ], ngImport: i0 }); }
10324
+ }
10325
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UrlValidatorDirective, decorators: [{
10326
+ type: Directive,
10327
+ args: [{
10328
+ selector: '[lxUrl][ngModel]',
10329
+ providers: [
10330
+ {
10331
+ provide: NG_VALIDATORS,
10332
+ useExisting: UrlValidatorDirective,
10333
+ multi: true
10334
+ }
10335
+ ]
10336
+ }]
10337
+ }] });
10338
+
10339
+ class LinkModalComponent {
10340
+ constructor() {
10341
+ this.NAME = 'LinkModalComponent';
10342
+ this.ngForm = viewChild.required(NgForm);
10343
+ this.cd = inject(ChangeDetectorRef);
9044
10344
  }
9045
- ngOnDestroy() {
9046
- this.destroy$.next();
9047
- this.destroy$.complete();
10345
+ get open() {
10346
+ return linkPluginKey.getState(this.editor.state).open;
9048
10347
  }
9049
- setupMaxLengthValidation() {
9050
- const maxLength = this.getMaxLength();
9051
- const control = this.ngControl.control;
9052
- if (maxLength !== null && control) {
9053
- const validators = control.validator ? [control.validator, Validators.maxLength(maxLength)] : Validators.maxLength(maxLength);
9054
- control.setValidators(validators);
9055
- control.updateValueAndValidity();
9056
- }
10348
+ get text() {
10349
+ return linkPluginKey.getState(this.editor.state).text;
9057
10350
  }
9058
- getMaxLength() {
9059
- if (this.lxMaxLengthCounter) {
9060
- return this.lxMaxLengthCounter;
9061
- }
9062
- const control = this.ngControl.control;
9063
- const validatorFn = control?.validator;
9064
- const errors = validatorFn?.(new FormControl({ length: Infinity }));
9065
- const requiredLength = errors?.['maxlength']['requiredLength'];
9066
- this.lxMaxLengthCounter = requiredLength ? requiredLength.toString() : null;
9067
- if (this.lxMaxLengthCounter === null) {
9068
- console.warn('lxMaxLength directive is used without a value or a control with a maxLength validator');
9069
- }
9070
- return requiredLength;
10351
+ get url() {
10352
+ return linkPluginKey.getState(this.editor.state).url;
9071
10353
  }
9072
- connectCounter() {
9073
- this.el.nativeElement.classList.add('lx-max-length-counter-input');
9074
- if (this.lxMaxLengthCounterRef) {
9075
- this.counterElement = this.lxMaxLengthCounterRef;
9076
- }
9077
- else {
9078
- this.createCounter();
9079
- }
10354
+ removeLink() {
10355
+ const editorChain = this.editor.chain().focus().extendMarkRange('link');
10356
+ editorChain.focus().extendMarkRange('link').unsetLink().run();
10357
+ this.closeModal();
9080
10358
  }
9081
- createCounter() {
9082
- this.counterElement = this.renderer.createElement('span');
9083
- this.renderer.addClass(this.counterElement, 'lx-max-length-counter');
9084
- const nextSibling = this.el.nativeElement.nextSibling;
9085
- if (nextSibling) {
9086
- this.renderer.insertBefore(this.el.nativeElement.parentNode, this.counterElement, nextSibling);
10359
+ saveLink() {
10360
+ if (!this.ngForm().valid) {
10361
+ this.ngForm().form.markAllAsTouched();
10362
+ return;
10363
+ }
10364
+ const formData = this.ngForm().value;
10365
+ const href = ensureHttpProtocol(formData.url);
10366
+ this.editor.chain().focus().extendMarkRange('link').run(); // Ensure that selection is expanded to the whole link mark
10367
+ const { from, to, empty } = this.editor.state.selection;
10368
+ const tr = this.editor.state.tr;
10369
+ const linkMark = this.editor.schema.marks['link'].create({ href });
10370
+ if (empty) {
10371
+ tr.insertText(formData.text, from);
9087
10372
  }
9088
10373
  else {
9089
- this.renderer.appendChild(this.el.nativeElement.parentNode, this.counterElement);
10374
+ tr.insertText(formData.text, from, to);
9090
10375
  }
9091
- this.destroy$.subscribe(() => {
9092
- this.renderer.removeChild(this.el.nativeElement.parentNode, this.counterElement);
9093
- });
10376
+ tr.addMark(from, from + formData.text.length, linkMark);
10377
+ this.editor.view.dispatch(tr);
10378
+ this.closeModal();
9094
10379
  }
9095
- updateCounter() {
9096
- if (this.counterElement) {
9097
- const currentLength = this.ngControl.value ? this.ngControl.value.length : 0;
9098
- const maxLength = this.getMaxLength();
9099
- this.renderer.setProperty(this.counterElement, 'textContent', `${currentLength} / ${maxLength}`);
9100
- }
10380
+ closeModal() {
10381
+ dispatchLinkState(this.editor, { open: false, url: null, text: null });
9101
10382
  }
9102
- subscribeToValueChanges() {
9103
- const control = this.ngControl.control;
9104
- if (control) {
9105
- control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
9106
- this.updateCounter();
9107
- });
9108
- }
10383
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: LinkModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10384
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: LinkModalComponent, isStandalone: true, selector: "lx-link-modal", inputs: { editor: "editor" }, viewQueries: [{ propertyName: "ngForm", first: true, predicate: NgForm, descendants: true, isSignal: true }], ngImport: i0, template: "<lx-modal size=\"dialog\" [open]=\"true\" (close)=\"closeModal()\">\n <lx-modal-header [title]=\"NAME + '.link' | translate\" />\n\n <form #form=\"ngForm\" (submit)=\"saveLink()\">\n <div class=\"link-modal-body\">\n <div class=\"form-group required\">\n <label for=\"text\">{{ NAME + '.linkText' | translate }}</label>\n <input\n #titleInput=\"ngModel\"\n class=\"form-control\"\n name=\"text\"\n type=\"text\"\n [ngModel]=\"text\"\n [lxAutofocus]=\"true\"\n [lxAutofocusTimeout]=\"10\"\n [lxMarkInvalid]=\"form.submitted && titleInput.control.touched && titleInput.hasError('required')\"\n required\n />\n </div>\n\n <div class=\"form-group required\">\n <label for=\"url\">{{ NAME + '.url' | translate }}</label>\n <input\n #urlInput=\"ngModel\"\n class=\"form-control\"\n name=\"url\"\n type=\"text\"\n [ngModel]=\"url\"\n [lxMarkInvalid]=\"form.submitted && urlInput.control.touched && urlInput.hasError('invalidUrl')\"\n lxUrl\n />\n @if (form.submitted && urlInput.control.touched && urlInput.hasError('invalidUrl')) {\n <lx-error-message>\n {{ NAME + '.invalidUrl' | translate }}\n </lx-error-message>\n }\n </div>\n </div>\n\n <lx-modal-footer>\n <div class=\"link-modal-actions\">\n <div class=\"link-modal-left-buttons\">\n @if (url) {\n <button lx-button type=\"button\" size=\"large\" color=\"danger\" (click)=\"removeLink()\">\n {{ NAME + '.removeLink' | translate }}\n </button>\n }\n </div>\n\n <div class=\"link-modal-right-buttons\">\n <button lx-button type=\"button\" size=\"large\" (click)=\"closeModal()\">\n {{ NAME + '.cancel' | translate }}\n </button>\n <button lx-button type=\"submit\" [disabled]=\"!titleInput.control.value || !urlInput.control.value\" color=\"primary\" size=\"large\">\n {{ NAME + '.ok' | translate }}\n </button>\n </div>\n </div>\n </lx-modal-footer>\n </form>\n</lx-modal>\n", styles: [".link-modal-body{display:flex;flex-direction:column;padding:20px 64px}.link-modal-actions{display:flex;justify-content:space-between}.link-modal-right-buttons{display:flex;gap:10px}\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lx-modal", inputs: ["open", "showCloseButton", "showBackButton", "verticalScroll", "size", "minWidth", "isFocusTrap", "canModalBeClosed"], outputs: ["close", "back"] }, { kind: "component", type: ModalHeaderComponent, selector: "lx-modal-header", inputs: ["title", "bottomBorder"] }, { kind: "component", type: ModalFooterComponent, selector: "lx-modal-footer", inputs: ["border"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$5.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$5.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$5.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: ButtonComponent, selector: "button[lx-button]", inputs: ["size", "color", "mode", "pressed", "selected", "square", "circle", "disabled", "icon", "endIcon", "showSpinner"] }, { kind: "directive", type: UrlValidatorDirective, selector: "[lxUrl][ngModel]" }, { kind: "component", type: ErrorMessageComponent, selector: "lx-error-message" }, { kind: "directive", type: MarkInvalidDirective, selector: "[lxMarkInvalid]", inputs: ["lxMarkInvalid"] }, { kind: "directive", type: AutofocusDirective, selector: "[lxAutofocus]", inputs: ["lxAutofocus", "lxAutofocusPreventScroll", "lxAutofocusTimeout"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10385
+ }
10386
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: LinkModalComponent, decorators: [{
10387
+ type: Component,
10388
+ args: [{ selector: 'lx-link-modal', imports: [
10389
+ ModalComponent,
10390
+ ModalHeaderComponent,
10391
+ ModalFooterComponent,
10392
+ TranslateModule,
10393
+ FormsModule,
10394
+ ButtonComponent,
10395
+ UrlValidatorDirective,
10396
+ ErrorMessageComponent,
10397
+ MarkInvalidDirective,
10398
+ AutofocusDirective
10399
+ ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<lx-modal size=\"dialog\" [open]=\"true\" (close)=\"closeModal()\">\n <lx-modal-header [title]=\"NAME + '.link' | translate\" />\n\n <form #form=\"ngForm\" (submit)=\"saveLink()\">\n <div class=\"link-modal-body\">\n <div class=\"form-group required\">\n <label for=\"text\">{{ NAME + '.linkText' | translate }}</label>\n <input\n #titleInput=\"ngModel\"\n class=\"form-control\"\n name=\"text\"\n type=\"text\"\n [ngModel]=\"text\"\n [lxAutofocus]=\"true\"\n [lxAutofocusTimeout]=\"10\"\n [lxMarkInvalid]=\"form.submitted && titleInput.control.touched && titleInput.hasError('required')\"\n required\n />\n </div>\n\n <div class=\"form-group required\">\n <label for=\"url\">{{ NAME + '.url' | translate }}</label>\n <input\n #urlInput=\"ngModel\"\n class=\"form-control\"\n name=\"url\"\n type=\"text\"\n [ngModel]=\"url\"\n [lxMarkInvalid]=\"form.submitted && urlInput.control.touched && urlInput.hasError('invalidUrl')\"\n lxUrl\n />\n @if (form.submitted && urlInput.control.touched && urlInput.hasError('invalidUrl')) {\n <lx-error-message>\n {{ NAME + '.invalidUrl' | translate }}\n </lx-error-message>\n }\n </div>\n </div>\n\n <lx-modal-footer>\n <div class=\"link-modal-actions\">\n <div class=\"link-modal-left-buttons\">\n @if (url) {\n <button lx-button type=\"button\" size=\"large\" color=\"danger\" (click)=\"removeLink()\">\n {{ NAME + '.removeLink' | translate }}\n </button>\n }\n </div>\n\n <div class=\"link-modal-right-buttons\">\n <button lx-button type=\"button\" size=\"large\" (click)=\"closeModal()\">\n {{ NAME + '.cancel' | translate }}\n </button>\n <button lx-button type=\"submit\" [disabled]=\"!titleInput.control.value || !urlInput.control.value\" color=\"primary\" size=\"large\">\n {{ NAME + '.ok' | translate }}\n </button>\n </div>\n </div>\n </lx-modal-footer>\n </form>\n</lx-modal>\n", styles: [".link-modal-body{display:flex;flex-direction:column;padding:20px 64px}.link-modal-actions{display:flex;justify-content:space-between}.link-modal-right-buttons{display:flex;gap:10px}\n"] }]
10400
+ }], propDecorators: { editor: [{
10401
+ type: Input,
10402
+ args: [{ required: true }]
10403
+ }] } });
10404
+ const ensureHttpProtocol = (url) => {
10405
+ if (!url)
10406
+ return '';
10407
+ return url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`;
10408
+ };
10409
+
10410
+ class RichTextEditorToolbarComponent {
10411
+ constructor() {
10412
+ this.NAME = 'RichTextEditorToolbarComponent';
10413
+ this.headingLevels = [];
10414
+ this.cd = inject(ChangeDetectorRef);
10415
+ this.isLinkModalOpen$ = new BehaviorSubject(false);
10416
+ this.detectChanges = () => {
10417
+ this.cd.markForCheck();
10418
+ };
10419
+ this.update = (event) => {
10420
+ if (event.transaction.getMeta(linkPluginKey)) {
10421
+ this.isLinkModalOpen$.next(linkPluginKey.getState(event.editor.state).open);
10422
+ }
10423
+ };
9109
10424
  }
9110
- setupInputRestriction() {
9111
- const maxLength = this.getMaxLength();
9112
- if (maxLength !== null) {
9113
- this.renderer.listen(this.el.nativeElement, 'beforeinput', (event) => {
9114
- const input = event.target;
9115
- const selectionStart = input.selectionStart || 0;
9116
- const selectionEnd = input.selectionEnd || 0;
9117
- const newValue = input.value.slice(0, selectionStart) + (event.data || '') + input.value.slice(selectionEnd);
9118
- // Prevent input if the new value exceeds the max length and grows
9119
- if (newValue.length > maxLength && newValue.length > input.value.length) {
9120
- event.preventDefault();
9121
- }
9122
- });
9123
- this.renderer.listen(this.el.nativeElement, 'input', () => {
9124
- this.updateCounter();
9125
- });
9126
- }
10425
+ ngOnInit() {
10426
+ this.headingLevels = this.editor.extensionManager.extensions.find((ext) => ext.name === 'heading')?.options?.levels || [];
10427
+ this.editor.on('selectionUpdate', this.detectChanges);
10428
+ this.editor.on('transaction', this.update);
9127
10429
  }
9128
- checkForInitialOversize() {
9129
- const maxLength = this.getMaxLength();
9130
- const value = this.ngControl.value;
9131
- if (maxLength !== null && value && value.length > maxLength) {
9132
- this.ngControl.control?.markAsDirty({ onlySelf: true });
9133
- this.ngControl.control?.updateValueAndValidity({ onlySelf: true });
9134
- }
10430
+ ngOnDestroy() {
10431
+ this.editor.off('selectionUpdate', this.detectChanges);
10432
+ this.editor.off('transaction', this.update);
9135
10433
  }
9136
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MaxLengthCounterDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1$5.NgControl, self: true }], target: i0.ɵɵFactoryTarget.Directive }); }
9137
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: MaxLengthCounterDirective, isStandalone: true, selector: "[lxMaxLengthCounter]", inputs: { lxMaxLengthCounter: "lxMaxLengthCounter", lxMaxLengthCounterRef: "lxMaxLengthCounterRef" }, ngImport: i0 }); }
10434
+ insertTable() {
10435
+ this.editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: false }).track('INSERT_TABLE').run();
10436
+ }
10437
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10438
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: RichTextEditorToolbarComponent, isStandalone: true, selector: "lx-rich-text-editor-toolbar", inputs: { editor: "editor" }, ngImport: i0, template: "<div class=\"toolbar\">\n <div class=\"toolbarItemGroup\">\n @if ('heading' | lxExtensionEnabled: editor) {\n <lx-options-dropdown>\n <button\n class=\"toolbarItem toolbar-heading\"\n lxKeyboardActionSource\n lx-button\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"false\"\n [pressed]=\"editor.isActive('heading') || editor.isActive('paragraph')\"\n endIcon=\"fa-angle-down\"\n [disabled]=\"!editor.isActive('heading') && !editor.isActive('paragraph')\"\n >\n {{ editor.isActive('heading') ? `${NAME + '.heading' | translate} ${editor.getAttributes('heading')['level']}` : `${NAME + '.paragraph' | translate}` }}\n </button>\n\n @for (level of headingLevels; track headingLevels) {\n <lx-option\n (select)=\"editor.chain().setHeading({ level }).focus().run()\"\n [hasSelectedState]=\"editor.isActive('heading', { level })\"\n [selected]=\"editor.isActive('heading', { level })\"\n >\n @if (!editor.isActive('heading', { level })) {\n <i class=\"far fa-heading dropdown-icon\"></i>\n }\n <span>{{ NAME + '.headingOption' | translate }} {{ level }}</span>\n </lx-option>\n }\n\n <lx-option\n (select)=\"this.editor.chain().setParagraph().focus().run()\"\n [hasSelectedState]=\"editor.isActive('paragraph')\"\n [selected]=\"editor.isActive('paragraph')\"\n >\n @if (!editor.isActive('paragraph')) {\n <i class=\"far fa-paragraph dropdown-icon\"></i>\n }\n <span>{{ NAME + '.paragraphOption' | translate }}</span>\n </lx-option>\n </lx-options-dropdown>\n }\n\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('bold' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.bold' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [disabled]=\"editor.isActive('header')\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleBold().focus().run()\"\n [pressed]=\"editor.isActive('bold')\"\n icon=\"fa-bold\"\n ></button>\n }\n\n @if ('italic' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.italic' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleItalic().focus().run()\"\n [pressed]=\"editor.isActive('italic')\"\n icon=\"fa-italic\"\n ></button>\n }\n\n @if ('underline' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.underline' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleUnderline().focus().run()\"\n [pressed]=\"editor.isActive('underline')\"\n icon=\"fa-underline\"\n ></button>\n }\n\n @if ('strike' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.strikethrough' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleStrike().focus().run()\"\n [pressed]=\"editor.isActive('strike')\"\n icon=\"fa-strikethrough\"\n ></button>\n }\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('bulletList' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.list' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleBulletList().run()\"\n [pressed]=\"editor.isActive('listItem')\"\n icon=\"fa-list\"\n ></button>\n }\n\n @if ('orderedList' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.orderedList' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleOrderedList().run()\"\n [pressed]=\"editor.isActive('orderedList')\"\n icon=\"fa-list-ol\"\n ></button>\n }\n\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('textAlign' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.alignLeft' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('left').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'left' })\"\n icon=\"fa-align-left\"\n ></button>\n\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.alignCenter' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('center').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'center' })\"\n icon=\"fa-align-center\"\n ></button>\n\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.alignRight' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('right').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'right' })\"\n icon=\"fa-align-right\"\n ></button>\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.justify' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('justify').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'justify' })\"\n icon=\"fa-align-justify\"\n ></button>\n }\n\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('link' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.link' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().openLinkModal().run()\"\n [pressed]=\"editor.isActive('link')\"\n icon=\"fa-link\"\n ></button>\n <span class=\"separator\"></span>\n }\n </div>\n\n @let tableEnabled = 'table' | lxExtensionEnabled: editor;\n @let codeEnabled = 'codeBlock' | lxExtensionEnabled: editor;\n\n @if (tableEnabled || codeEnabled) {\n <div class=\"toolbarItemGroup\">\n @if (codeEnabled) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.code' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().setCodeBlock().run()\"\n [pressed]=\"editor.isActive('codeBlock')\"\n icon=\"fa-code\"\n ></button>\n }\n\n @if ('lxDiagram' | lxExtensionEnabled: editor) {\n <ng-content select=\".diagram-btn\" />\n }\n\n @if (tableEnabled) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.table' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"insertTable()\"\n [pressed]=\"editor.isActive('table')\"\n icon=\"fa-table\"\n [disabled]=\"editor.isActive('table')\"\n ></button>\n }\n\n <span class=\"separator\"></span>\n </div>\n }\n</div>\n\n@if (('link' | lxExtensionEnabled: editor) && (isLinkModalOpen$ | async)) {\n <lx-link-modal [editor]=\"editor\" />\n}\n", styles: [":host{width:100%;display:flex;border-bottom:#99a5bb 1px solid;padding:8px}.toolbar{display:flex;flex-wrap:wrap;width:100%}.toolbar .toolbarItemGroup{display:flex;align-items:center;gap:2px}.toolbar .toolbarItemGroup:not(.toolbarItemGroup:has(.toolbarItem)){display:none}.toolbar .toolbarItemGroup:last-child .separator{display:none}.toolbar .dropdown-icon{margin-left:-4px;margin-right:8px}.toolbar .toolbar-heading{width:100px}.toolbar .toolbarItem{text-wrap:nowrap;color:#2a303d;border:none;height:24px}.toolbar .toolbarItem:hover:not(disabled){border:none}.toolbar .toolbarItem.pressed{background:#f0f2f5!important}.toolbar .toolbarItem.square{border:none!important}.toolbar .separator{width:1px;height:16px;margin:0 4px 0 2px;border:#c2c9d6 .5px solid}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "component", type: OptionsDropdownComponent, selector: "lx-options-dropdown", inputs: ["align", "closeOnScroll", "disabled", "maxHeight", "closeOnSelect", "overlayPositioning", "dropdownClass", "isFlexEnabledTriggerContainer"], outputs: ["opened", "closed", "closedWithoutSelection"] }, { kind: "component", type: ButtonComponent, selector: "button[lx-button]", inputs: ["size", "color", "mode", "pressed", "selected", "square", "circle", "disabled", "icon", "endIcon", "showSpinner"] }, { kind: "directive", type: KeyboardActionSourceDirective, selector: "[lxKeyboardActionSource]", exportAs: ["keyboardActionSource"] }, { kind: "component", type: OptionComponent, selector: "lx-option", inputs: ["selected", "isHighlighted", "disabled", "value", "hasSelectedState", "selectIcon"], outputs: ["select", "highlight", "selectedClick", "keyDownAction"] }, { kind: "pipe", type: ExtensionEnabledPipe, name: "lxExtensionEnabled" }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: LinkModalComponent, selector: "lx-link-modal", inputs: ["editor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9138
10439
  }
9139
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MaxLengthCounterDirective, decorators: [{
10440
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorToolbarComponent, decorators: [{
10441
+ type: Component,
10442
+ args: [{ selector: 'lx-rich-text-editor-toolbar', imports: [
10443
+ AsyncPipe,
10444
+ OptionsDropdownComponent,
10445
+ ButtonComponent,
10446
+ KeyboardActionSourceDirective,
10447
+ OptionComponent,
10448
+ ExtensionEnabledPipe,
10449
+ TranslatePipe,
10450
+ LinkModalComponent
10451
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"toolbar\">\n <div class=\"toolbarItemGroup\">\n @if ('heading' | lxExtensionEnabled: editor) {\n <lx-options-dropdown>\n <button\n class=\"toolbarItem toolbar-heading\"\n lxKeyboardActionSource\n lx-button\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"false\"\n [pressed]=\"editor.isActive('heading') || editor.isActive('paragraph')\"\n endIcon=\"fa-angle-down\"\n [disabled]=\"!editor.isActive('heading') && !editor.isActive('paragraph')\"\n >\n {{ editor.isActive('heading') ? `${NAME + '.heading' | translate} ${editor.getAttributes('heading')['level']}` : `${NAME + '.paragraph' | translate}` }}\n </button>\n\n @for (level of headingLevels; track headingLevels) {\n <lx-option\n (select)=\"editor.chain().setHeading({ level }).focus().run()\"\n [hasSelectedState]=\"editor.isActive('heading', { level })\"\n [selected]=\"editor.isActive('heading', { level })\"\n >\n @if (!editor.isActive('heading', { level })) {\n <i class=\"far fa-heading dropdown-icon\"></i>\n }\n <span>{{ NAME + '.headingOption' | translate }} {{ level }}</span>\n </lx-option>\n }\n\n <lx-option\n (select)=\"this.editor.chain().setParagraph().focus().run()\"\n [hasSelectedState]=\"editor.isActive('paragraph')\"\n [selected]=\"editor.isActive('paragraph')\"\n >\n @if (!editor.isActive('paragraph')) {\n <i class=\"far fa-paragraph dropdown-icon\"></i>\n }\n <span>{{ NAME + '.paragraphOption' | translate }}</span>\n </lx-option>\n </lx-options-dropdown>\n }\n\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('bold' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.bold' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [disabled]=\"editor.isActive('header')\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleBold().focus().run()\"\n [pressed]=\"editor.isActive('bold')\"\n icon=\"fa-bold\"\n ></button>\n }\n\n @if ('italic' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.italic' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleItalic().focus().run()\"\n [pressed]=\"editor.isActive('italic')\"\n icon=\"fa-italic\"\n ></button>\n }\n\n @if ('underline' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.underline' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleUnderline().focus().run()\"\n [pressed]=\"editor.isActive('underline')\"\n icon=\"fa-underline\"\n ></button>\n }\n\n @if ('strike' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.strikethrough' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleStrike().focus().run()\"\n [pressed]=\"editor.isActive('strike')\"\n icon=\"fa-strikethrough\"\n ></button>\n }\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('bulletList' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.list' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleBulletList().run()\"\n [pressed]=\"editor.isActive('listItem')\"\n icon=\"fa-list\"\n ></button>\n }\n\n @if ('orderedList' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.orderedList' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().toggleOrderedList().run()\"\n [pressed]=\"editor.isActive('orderedList')\"\n icon=\"fa-list-ol\"\n ></button>\n }\n\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('textAlign' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.alignLeft' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('left').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'left' })\"\n icon=\"fa-align-left\"\n ></button>\n\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.alignCenter' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('center').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'center' })\"\n icon=\"fa-align-center\"\n ></button>\n\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.alignRight' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('right').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'right' })\"\n icon=\"fa-align-right\"\n ></button>\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.justify' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().focus().setTextAlign('justify').run()\"\n [pressed]=\"editor.isActive({ textAlign: 'justify' })\"\n icon=\"fa-align-justify\"\n ></button>\n }\n\n <span class=\"separator\"></span>\n </div>\n\n <div class=\"toolbarItemGroup\">\n @if ('link' | lxExtensionEnabled: editor) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.link' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().openLinkModal().run()\"\n [pressed]=\"editor.isActive('link')\"\n icon=\"fa-link\"\n ></button>\n <span class=\"separator\"></span>\n }\n </div>\n\n @let tableEnabled = 'table' | lxExtensionEnabled: editor;\n @let codeEnabled = 'codeBlock' | lxExtensionEnabled: editor;\n\n @if (tableEnabled || codeEnabled) {\n <div class=\"toolbarItemGroup\">\n @if (codeEnabled) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.code' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"editor.chain().setCodeBlock().run()\"\n [pressed]=\"editor.isActive('codeBlock')\"\n icon=\"fa-code\"\n ></button>\n }\n\n @if ('lxDiagram' | lxExtensionEnabled: editor) {\n <ng-content select=\".diagram-btn\" />\n }\n\n @if (tableEnabled) {\n <button\n class=\"toolbarItem\"\n lx-button\n [attr.aria-label]=\"NAME + '.table' | translate\"\n [mode]=\"'outline'\"\n [size]=\"'medium'\"\n [square]=\"true\"\n (click)=\"insertTable()\"\n [pressed]=\"editor.isActive('table')\"\n icon=\"fa-table\"\n [disabled]=\"editor.isActive('table')\"\n ></button>\n }\n\n <span class=\"separator\"></span>\n </div>\n }\n</div>\n\n@if (('link' | lxExtensionEnabled: editor) && (isLinkModalOpen$ | async)) {\n <lx-link-modal [editor]=\"editor\" />\n}\n", styles: [":host{width:100%;display:flex;border-bottom:#99a5bb 1px solid;padding:8px}.toolbar{display:flex;flex-wrap:wrap;width:100%}.toolbar .toolbarItemGroup{display:flex;align-items:center;gap:2px}.toolbar .toolbarItemGroup:not(.toolbarItemGroup:has(.toolbarItem)){display:none}.toolbar .toolbarItemGroup:last-child .separator{display:none}.toolbar .dropdown-icon{margin-left:-4px;margin-right:8px}.toolbar .toolbar-heading{width:100px}.toolbar .toolbarItem{text-wrap:nowrap;color:#2a303d;border:none;height:24px}.toolbar .toolbarItem:hover:not(disabled){border:none}.toolbar .toolbarItem.pressed{background:#f0f2f5!important}.toolbar .toolbarItem.square{border:none!important}.toolbar .separator{width:1px;height:16px;margin:0 4px 0 2px;border:#c2c9d6 .5px solid}\n"] }]
10452
+ }], propDecorators: { editor: [{
10453
+ type: Input,
10454
+ args: [{ required: true }]
10455
+ }] } });
10456
+
10457
+ class RichTextEditorComponent {
10458
+ constructor() {
10459
+ this.mode = input.required();
10460
+ this.outputFormat = input.required();
10461
+ this.additionalFeatures = input([]);
10462
+ this.ariaLabelledBy = input(null);
10463
+ this.maxHeight = input(null);
10464
+ this.customExtensions = input([]);
10465
+ this.blur = output();
10466
+ this.value = null;
10467
+ this.disabled = false;
10468
+ this.editor = inject(TipTapEditorDirective).editor;
10469
+ this.onChange = (_value) => { };
10470
+ this.onTouched = () => { };
10471
+ }
10472
+ ngOnDestroy() {
10473
+ this.editor().destroy();
10474
+ }
10475
+ writeValue(value) {
10476
+ this.value = value;
10477
+ }
10478
+ registerOnChange(fn) {
10479
+ this.onChange = fn;
10480
+ }
10481
+ registerOnTouched(fn) {
10482
+ this.onTouched = fn;
10483
+ }
10484
+ setDisabledState(isDisabled) {
10485
+ this.disabled = isDisabled;
10486
+ }
10487
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10488
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: RichTextEditorComponent, isStandalone: true, selector: "lx-rich-text-editor", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: true, transformFunction: null }, outputFormat: { classPropertyName: "outputFormat", publicName: "outputFormat", isSignal: true, isRequired: true, transformFunction: null }, additionalFeatures: { classPropertyName: "additionalFeatures", publicName: "additionalFeatures", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, customExtensions: { classPropertyName: "customExtensions", publicName: "customExtensions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { blur: "blur" }, host: { properties: { "style.--editor-max-height": "maxHeight()" } }, providers: [
10489
+ ExtensionsBuilder,
10490
+ {
10491
+ provide: NG_VALUE_ACCESSOR,
10492
+ useExisting: forwardRef(() => RichTextEditorComponent),
10493
+ multi: true
10494
+ }
10495
+ ], hostDirectives: [{ directive: TipTapEditorDirective, inputs: ["outputFormat", "outputFormat", "additionalFeatures", "additionalFeatures", "ariaLabelledBy", "ariaLabelledBy", "customExtensions", "customExtensions", "mode", "mode"] }, { directive: TrackingDirective, outputs: ["trackEvent", "trackEvent"] }], ngImport: i0, template: "<div [class.viewMode]=\"mode() === 'view'\" [class.editMode]=\"mode() === 'edit'\" [class.disabled]=\"disabled\">\n @if (mode() === 'edit' && !disabled) {\n <lx-rich-text-editor-toolbar (mousedown)=\"$event.preventDefault()\" [editor]=\"editor()\">\n <ng-content select=\".diagram-btn\" ngProjectAs=\".diagram-btn\" />\n </lx-rich-text-editor-toolbar>\n }\n <tiptap-editor\n [editor]=\"editor()\"\n [outputFormat]=\"outputFormat()\"\n [ngModel]=\"value\"\n (ngModelChange)=\"onChange($event)\"\n (blur)=\"blur.emit()\"\n [disabled]=\"mode() === 'view' || disabled\"\n />\n\n @if (('truncate' | lxExtensionEnabled: editor()) && mode() === 'view') {\n <lx-truncate-button [editor]=\"editor()\" />\n }\n</div>\n\n@if ('table' | lxExtensionEnabled: editor()) {\n <lx-table-bubble-menu [editor]=\"editor()\" />\n}\n\n<ng-content />\n", styles: [":host{display:flex;width:100%}.editorButton{margin:0 5px}.editMode{width:100%;border:1px solid #99a5bb;background-color:#fff;border-radius:2px}:host ::ng-deep .editMode .ProseMirror{padding:15px 18.9px;resize:vertical;max-height:var(--editor-max-height, auto)}.viewMode{width:100%}.disabled{background-color:#f0f2f5;color:#61779d}:host ::ng-deep .ProseMirror{outline-width:0;border-radius:2px;overflow:auto}:host ::ng-deep .ProseMirror-gapcursor:after{width:1px;height:14px;border-left:1px solid #000}:host ::ng-deep .ProseMirror p:first-child{margin-top:0}:host ::ng-deep .ProseMirror p:last-child{margin-bottom:0}:host ::ng-deep .ProseMirror h1:first-child,:host ::ng-deep .ProseMirror h2:first-child,:host ::ng-deep .ProseMirror h3:first-child,:host ::ng-deep .ProseMirror h4:first-child,:host ::ng-deep .ProseMirror h5:first-child,:host ::ng-deep .ProseMirror h6:first-child{margin-top:0}:host ::ng-deep .ProseMirror h1:last-child,:host ::ng-deep .ProseMirror h2:last-child,:host ::ng-deep .ProseMirror h3:last-child,:host ::ng-deep .ProseMirror h4:last-child,:host ::ng-deep .ProseMirror h5:last-child,:host ::ng-deep .ProseMirror h6:last-child{margin-bottom:0}:host ::ng-deep .ProseMirror h1{font-size:var(--lxFontHeader1Size)}:host ::ng-deep .ProseMirror h2{font-size:var(--lxFontHeader2Size)}:host ::ng-deep .ProseMirror h3{font-size:var(--lxFontHeader3Size)}:host ::ng-deep .ProseMirror h4{font-size:var(--lxFontHeader4Size)}:host ::ng-deep .ProseMirror .tableWrapper{margin:20px 0}:host ::ng-deep .ProseMirror .tableWrapper h1,:host ::ng-deep .ProseMirror .tableWrapper h2,:host ::ng-deep .ProseMirror .tableWrapper h3,:host ::ng-deep .ProseMirror .tableWrapper h4,:host ::ng-deep .ProseMirror .tableWrapper h5,:host ::ng-deep .ProseMirror .tableWrapper h6{margin-top:0}:host ::ng-deep .ProseMirror .tableWrapper table{border-collapse:collapse;margin:0;table-layout:fixed;width:100%}:host ::ng-deep .ProseMirror .tableWrapper table th,:host ::ng-deep .ProseMirror .tableWrapper table td{border:1px solid #c2c9d6;box-sizing:border-box;min-width:1em;padding:12px;position:relative;vertical-align:top}:host ::ng-deep .ProseMirror .tableWrapper table th>*,:host ::ng-deep .ProseMirror .tableWrapper table td>*{margin-bottom:0}:host ::ng-deep .ProseMirror .tableWrapper table th{background-color:#c2c9d6;font-weight:700;text-align:left}:host ::ng-deep .ProseMirror .tableWrapper table p{margin:0}:host ::ng-deep .ProseMirror .tableWrapper table .selectedCell:after{content:\"\";position:absolute;inset:0;pointer-events:none;z-index:2}:host ::ng-deep .ProseMirror .tableWrapper table .column-resize-handle{background-color:#b2bccc;bottom:-2px;right:-2px;position:absolute;top:0;width:4px;pointer-events:none}:host ::ng-deep .ProseMirror .selectedCell{background-color:#e1e5eb}:host ::ng-deep .ProseMirror .selector-column,:host ::ng-deep .ProseMirror .selector-row{all:unset;display:flex;align-items:center;justify-content:center;background-color:#f0f2f5;cursor:pointer;position:absolute;z-index:10;margin-left:-1px;margin-top:-.5px}:host ::ng-deep .ProseMirror .selector-column:hover,:host ::ng-deep .ProseMirror .selector-row:hover{background-color:#e1e5eb}:host ::ng-deep .ProseMirror .selector-column{width:calc(100% + 1px);border-left:1px solid #e1e5eb;height:11px;top:-11px;left:0}:host ::ng-deep .ProseMirror .selector-column.first{border-left:none}:host ::ng-deep .ProseMirror .selector-column.selected{background-color:#b2bccc;border-color:transparent}:host ::ng-deep .ProseMirror .selector-column.selected:before{content:\"\";border-bottom:2px dotted #fff;width:.6rem}:host ::ng-deep .ProseMirror .selector-column:hover:before{content:\"\";border-bottom:2px dotted #61779d;width:.6rem}:host ::ng-deep .ProseMirror .selector-column.selected:before{border-bottom:2px dotted #fff}:host ::ng-deep .ProseMirror .selector-row{height:calc(100% + 1px);border-top:1px solid #e1e5eb;width:11px;left:-11px;top:0}:host ::ng-deep .ProseMirror .selector-row.first{border-top:none}:host ::ng-deep .ProseMirror .selector-row.selected{background-color:#b2bccc;border-color:transparent}:host ::ng-deep .ProseMirror .selector-row.selected:before{content:\"\";border-left:2px dotted #fff;height:.575rem}:host ::ng-deep .ProseMirror .selector-row:hover:before{content:\"\";border-left:2px dotted #61779d;height:.575rem}:host ::ng-deep .ProseMirror .selector-row.selected:before{border-left:2px dotted #fff}\n"], dependencies: [{ kind: "directive", type: EditorDirective, selector: "tiptap-editor[editor]", inputs: ["editor", "outputFormat"], outputs: ["blur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: RichTextEditorToolbarComponent, selector: "lx-rich-text-editor-toolbar", inputs: ["editor"] }, { kind: "component", type: TableBubbleMenuComponent, selector: "lx-table-bubble-menu", inputs: ["editor"] }, { kind: "pipe", type: ExtensionEnabledPipe, name: "lxExtensionEnabled" }, { kind: "component", type: TruncateButtonComponent, selector: "lx-truncate-button", inputs: ["editor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10496
+ }
10497
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorComponent, decorators: [{
10498
+ type: Component,
10499
+ args: [{ selector: 'lx-rich-text-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
10500
+ EditorDirective,
10501
+ FormsModule,
10502
+ RichTextEditorToolbarComponent,
10503
+ TableBubbleMenuComponent,
10504
+ ExtensionEnabledPipe,
10505
+ TruncateButtonComponent
10506
+ ], hostDirectives: [
10507
+ {
10508
+ directive: TipTapEditorDirective,
10509
+ inputs: ['outputFormat', 'additionalFeatures', 'ariaLabelledBy', 'customExtensions', 'mode']
10510
+ },
10511
+ {
10512
+ directive: TrackingDirective,
10513
+ outputs: ['trackEvent']
10514
+ }
10515
+ ], providers: [
10516
+ ExtensionsBuilder,
10517
+ {
10518
+ provide: NG_VALUE_ACCESSOR,
10519
+ useExisting: forwardRef(() => RichTextEditorComponent),
10520
+ multi: true
10521
+ }
10522
+ ], host: {
10523
+ '[style.--editor-max-height]': 'maxHeight()'
10524
+ }, template: "<div [class.viewMode]=\"mode() === 'view'\" [class.editMode]=\"mode() === 'edit'\" [class.disabled]=\"disabled\">\n @if (mode() === 'edit' && !disabled) {\n <lx-rich-text-editor-toolbar (mousedown)=\"$event.preventDefault()\" [editor]=\"editor()\">\n <ng-content select=\".diagram-btn\" ngProjectAs=\".diagram-btn\" />\n </lx-rich-text-editor-toolbar>\n }\n <tiptap-editor\n [editor]=\"editor()\"\n [outputFormat]=\"outputFormat()\"\n [ngModel]=\"value\"\n (ngModelChange)=\"onChange($event)\"\n (blur)=\"blur.emit()\"\n [disabled]=\"mode() === 'view' || disabled\"\n />\n\n @if (('truncate' | lxExtensionEnabled: editor()) && mode() === 'view') {\n <lx-truncate-button [editor]=\"editor()\" />\n }\n</div>\n\n@if ('table' | lxExtensionEnabled: editor()) {\n <lx-table-bubble-menu [editor]=\"editor()\" />\n}\n\n<ng-content />\n", styles: [":host{display:flex;width:100%}.editorButton{margin:0 5px}.editMode{width:100%;border:1px solid #99a5bb;background-color:#fff;border-radius:2px}:host ::ng-deep .editMode .ProseMirror{padding:15px 18.9px;resize:vertical;max-height:var(--editor-max-height, auto)}.viewMode{width:100%}.disabled{background-color:#f0f2f5;color:#61779d}:host ::ng-deep .ProseMirror{outline-width:0;border-radius:2px;overflow:auto}:host ::ng-deep .ProseMirror-gapcursor:after{width:1px;height:14px;border-left:1px solid #000}:host ::ng-deep .ProseMirror p:first-child{margin-top:0}:host ::ng-deep .ProseMirror p:last-child{margin-bottom:0}:host ::ng-deep .ProseMirror h1:first-child,:host ::ng-deep .ProseMirror h2:first-child,:host ::ng-deep .ProseMirror h3:first-child,:host ::ng-deep .ProseMirror h4:first-child,:host ::ng-deep .ProseMirror h5:first-child,:host ::ng-deep .ProseMirror h6:first-child{margin-top:0}:host ::ng-deep .ProseMirror h1:last-child,:host ::ng-deep .ProseMirror h2:last-child,:host ::ng-deep .ProseMirror h3:last-child,:host ::ng-deep .ProseMirror h4:last-child,:host ::ng-deep .ProseMirror h5:last-child,:host ::ng-deep .ProseMirror h6:last-child{margin-bottom:0}:host ::ng-deep .ProseMirror h1{font-size:var(--lxFontHeader1Size)}:host ::ng-deep .ProseMirror h2{font-size:var(--lxFontHeader2Size)}:host ::ng-deep .ProseMirror h3{font-size:var(--lxFontHeader3Size)}:host ::ng-deep .ProseMirror h4{font-size:var(--lxFontHeader4Size)}:host ::ng-deep .ProseMirror .tableWrapper{margin:20px 0}:host ::ng-deep .ProseMirror .tableWrapper h1,:host ::ng-deep .ProseMirror .tableWrapper h2,:host ::ng-deep .ProseMirror .tableWrapper h3,:host ::ng-deep .ProseMirror .tableWrapper h4,:host ::ng-deep .ProseMirror .tableWrapper h5,:host ::ng-deep .ProseMirror .tableWrapper h6{margin-top:0}:host ::ng-deep .ProseMirror .tableWrapper table{border-collapse:collapse;margin:0;table-layout:fixed;width:100%}:host ::ng-deep .ProseMirror .tableWrapper table th,:host ::ng-deep .ProseMirror .tableWrapper table td{border:1px solid #c2c9d6;box-sizing:border-box;min-width:1em;padding:12px;position:relative;vertical-align:top}:host ::ng-deep .ProseMirror .tableWrapper table th>*,:host ::ng-deep .ProseMirror .tableWrapper table td>*{margin-bottom:0}:host ::ng-deep .ProseMirror .tableWrapper table th{background-color:#c2c9d6;font-weight:700;text-align:left}:host ::ng-deep .ProseMirror .tableWrapper table p{margin:0}:host ::ng-deep .ProseMirror .tableWrapper table .selectedCell:after{content:\"\";position:absolute;inset:0;pointer-events:none;z-index:2}:host ::ng-deep .ProseMirror .tableWrapper table .column-resize-handle{background-color:#b2bccc;bottom:-2px;right:-2px;position:absolute;top:0;width:4px;pointer-events:none}:host ::ng-deep .ProseMirror .selectedCell{background-color:#e1e5eb}:host ::ng-deep .ProseMirror .selector-column,:host ::ng-deep .ProseMirror .selector-row{all:unset;display:flex;align-items:center;justify-content:center;background-color:#f0f2f5;cursor:pointer;position:absolute;z-index:10;margin-left:-1px;margin-top:-.5px}:host ::ng-deep .ProseMirror .selector-column:hover,:host ::ng-deep .ProseMirror .selector-row:hover{background-color:#e1e5eb}:host ::ng-deep .ProseMirror .selector-column{width:calc(100% + 1px);border-left:1px solid #e1e5eb;height:11px;top:-11px;left:0}:host ::ng-deep .ProseMirror .selector-column.first{border-left:none}:host ::ng-deep .ProseMirror .selector-column.selected{background-color:#b2bccc;border-color:transparent}:host ::ng-deep .ProseMirror .selector-column.selected:before{content:\"\";border-bottom:2px dotted #fff;width:.6rem}:host ::ng-deep .ProseMirror .selector-column:hover:before{content:\"\";border-bottom:2px dotted #61779d;width:.6rem}:host ::ng-deep .ProseMirror .selector-column.selected:before{border-bottom:2px dotted #fff}:host ::ng-deep .ProseMirror .selector-row{height:calc(100% + 1px);border-top:1px solid #e1e5eb;width:11px;left:-11px;top:0}:host ::ng-deep .ProseMirror .selector-row.first{border-top:none}:host ::ng-deep .ProseMirror .selector-row.selected{background-color:#b2bccc;border-color:transparent}:host ::ng-deep .ProseMirror .selector-row.selected:before{content:\"\";border-left:2px dotted #fff;height:.575rem}:host ::ng-deep .ProseMirror .selector-row:hover:before{content:\"\";border-left:2px dotted #61779d;height:.575rem}:host ::ng-deep .ProseMirror .selector-row.selected:before{border-left:2px dotted #fff}\n"] }]
10525
+ }] });
10526
+
10527
+ class FocusEditorDirective {
10528
+ constructor() {
10529
+ this.editor = inject(TipTapEditorDirective).editor;
10530
+ }
10531
+ ngOnInit() {
10532
+ this.editor().on('create', () => {
10533
+ this.editor().commands.focus('start');
10534
+ });
10535
+ }
10536
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusEditorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10537
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: FocusEditorDirective, isStandalone: true, selector: "lx-rich-text-editor[lxFocusEditor]", ngImport: i0 }); }
10538
+ }
10539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusEditorDirective, decorators: [{
9140
10540
  type: Directive,
9141
10541
  args: [{
9142
- selector: '[lxMaxLengthCounter]'
10542
+ selector: 'lx-rich-text-editor[lxFocusEditor]'
9143
10543
  }]
9144
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1$5.NgControl, decorators: [{
9145
- type: Self
9146
- }] }], propDecorators: { lxMaxLengthCounter: [{
10544
+ }] });
10545
+
10546
+ class HighlightTermDirective {
10547
+ constructor() {
10548
+ this.lxHighlightTerm = input();
10549
+ this.editor = inject(TipTapEditorDirective).editor;
10550
+ effect(() => {
10551
+ this.editor().view.dispatch(this.editor().view.state.tr.setMeta(highlightTermStatePluginKey, {
10552
+ term: this.lxHighlightTerm()
10553
+ }));
10554
+ });
10555
+ }
10556
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HighlightTermDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10557
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.6", type: HighlightTermDirective, isStandalone: true, selector: "lx-rich-text-editor[lxHighlightTerm]", inputs: { lxHighlightTerm: { classPropertyName: "lxHighlightTerm", publicName: "lxHighlightTerm", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
10558
+ }
10559
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HighlightTermDirective, decorators: [{
10560
+ type: Directive,
10561
+ args: [{
10562
+ selector: 'lx-rich-text-editor[lxHighlightTerm]'
10563
+ }]
10564
+ }], ctorParameters: () => [] });
10565
+
10566
+ class TruncateDirective {
10567
+ constructor() {
10568
+ this.lxTruncate = input();
10569
+ this.editor = inject(TipTapEditorDirective).editor;
10570
+ effect(() => {
10571
+ const options = this.lxTruncate();
10572
+ if (options) {
10573
+ this.editor().view.dispatch(this.editor().view.state.tr.setMeta(truncatePluginKey, options));
10574
+ }
10575
+ });
10576
+ }
10577
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10578
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.6", type: TruncateDirective, isStandalone: true, selector: "lx-rich-text-editor[lxTruncate]", inputs: { lxTruncate: { classPropertyName: "lxTruncate", publicName: "lxTruncate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
10579
+ }
10580
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateDirective, decorators: [{
10581
+ type: Directive,
10582
+ args: [{
10583
+ selector: 'lx-rich-text-editor[lxTruncate]'
10584
+ }]
10585
+ }], ctorParameters: () => [] });
10586
+
10587
+ // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
10588
+ class AngularNodeViewComponent {
10589
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: AngularNodeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10590
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: AngularNodeViewComponent, isStandalone: false, selector: "ng-component", inputs: { editor: "editor", node: "node", decorations: "decorations", selected: "selected", extension: "extension", getPos: "getPos", updateAttributes: "updateAttributes", deleteNode: "deleteNode" }, ngImport: i0, template: '', isInline: true }); }
10591
+ }
10592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: AngularNodeViewComponent, decorators: [{
10593
+ type: Component,
10594
+ args: [{
10595
+ template: '',
10596
+ // eslint-disable-next-line @angular-eslint/prefer-standalone
10597
+ standalone: false
10598
+ }]
10599
+ }], propDecorators: { editor: [{
9147
10600
  type: Input
9148
- }], lxMaxLengthCounterRef: [{
10601
+ }], node: [{
10602
+ type: Input
10603
+ }], decorations: [{
10604
+ type: Input
10605
+ }], selected: [{
10606
+ type: Input
10607
+ }], extension: [{
10608
+ type: Input
10609
+ }], getPos: [{
10610
+ type: Input
10611
+ }], updateAttributes: [{
10612
+ type: Input
10613
+ }], deleteNode: [{
9149
10614
  type: Input
9150
10615
  }] } });
9151
-
9152
- class Sorting {
9153
- constructor() {
9154
- this.key = '';
9155
- this.order = 'asc';
10616
+
10617
+ class AngularRenderer {
10618
+ constructor(ViewComponent, injector, props) {
10619
+ this.applicationRef = injector.get(ApplicationRef);
10620
+ this.componentRef = createComponent(ViewComponent, {
10621
+ environmentInjector: this.applicationRef.injector,
10622
+ elementInjector: injector
10623
+ });
10624
+ // set input props to the component
10625
+ this.updateProps(props);
10626
+ this.applicationRef.attachView(this.componentRef.hostView);
10627
+ }
10628
+ get instance() {
10629
+ return this.componentRef.instance;
10630
+ }
10631
+ get elementRef() {
10632
+ return this.componentRef.injector.get(ElementRef);
10633
+ }
10634
+ get dom() {
10635
+ return this.elementRef.nativeElement;
10636
+ }
10637
+ updateProps(props) {
10638
+ Object.entries(props).forEach(([key, value]) => {
10639
+ this.componentRef.setInput(key, value);
10640
+ });
10641
+ }
10642
+ detectChanges() {
10643
+ this.componentRef.changeDetectorRef.detectChanges();
10644
+ }
10645
+ destroy() {
10646
+ this.componentRef.destroy();
10647
+ this.applicationRef.detachView(this.componentRef.hostView);
9156
10648
  }
9157
10649
  }
9158
10650
 
9159
- /**
9160
- * Due to limitations of the native html datepicker this validator is needed:
9161
- * In the datepicker the min and max values only apply if the date is altered e.g. via scrolling.
9162
- * It is still possible to manually input a value lower or higher than min and max value.
9163
- */
9164
- function ValidateDateInForeseeableFuture(control) {
9165
- // matches yyyy-mm-dd
9166
- const dateRegex = /^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/;
9167
- if (control.value && dateRegex.test(control.value)) {
9168
- const today = formatDate(new Date(), 'yyyy-MM-dd', 'en');
9169
- const maxDate = '2999-12-31';
9170
- if (control.value < today || control.value > maxDate) {
9171
- return {
9172
- dateInForseeableFuture: {
9173
- valid: false
9174
- }
10651
+ class AngularNodeView extends NodeView {
10652
+ mount() {
10653
+ const injector = this.options.injector;
10654
+ const props = {
10655
+ editor: this.editor,
10656
+ node: this.node,
10657
+ decorations: this.decorations,
10658
+ selected: false,
10659
+ extension: this.extension,
10660
+ getPos: () => this.getPos(),
10661
+ updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
10662
+ deleteNode: () => this.deleteNode()
10663
+ };
10664
+ this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this);
10665
+ this.editor.on('selectionUpdate', this.handleSelectionUpdate);
10666
+ // create renderer
10667
+ this.renderer = new AngularRenderer(this.component, injector, props);
10668
+ // Register drag handler
10669
+ if (this.extension.config.draggable) {
10670
+ this.renderer.elementRef.nativeElement.ondragstart = (e) => {
10671
+ this.onDragStart(e);
9175
10672
  };
9176
10673
  }
9177
- else {
9178
- return null;
10674
+ this.contentDOMElement = this.node.isLeaf ? null : document.createElement(this.node.isInline ? 'span' : 'div');
10675
+ if (this.contentDOMElement) {
10676
+ // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
10677
+ // With this fix it seems to work fine
10678
+ // See: https://github.com/ueberdosis/tiptap/issues/1197
10679
+ this.contentDOMElement.style.whiteSpace = 'inherit';
10680
+ // Required for editable node views
10681
+ // The content won't be rendered if `editable` is set to `false`
10682
+ this.renderer.detectChanges();
9179
10683
  }
10684
+ this.appendContendDom();
9180
10685
  }
9181
- return null;
9182
- }
9183
-
9184
- function stringIsInArray(array, stringToFind, valueMapFunction) {
9185
- const valueToFind = valueMapFunction ? valueMapFunction(stringToFind).toLocaleLowerCase() : stringToFind.toLowerCase();
9186
- return array.map((item) => item.toLocaleLowerCase()).indexOf(valueToFind) > -1;
9187
- }
9188
- /**
9189
- * Validates that a string is not inside an array of strings
9190
- */
9191
- function ValidateStringNotInArray(array, valueMapFunction, validatorName = 'stringNotInArray') {
9192
- return function (control) {
9193
- if (!control.value) {
10686
+ get dom() {
10687
+ return this.renderer.dom;
10688
+ }
10689
+ get contentDOM() {
10690
+ if (this.node.isLeaf) {
9194
10691
  return null;
9195
10692
  }
9196
- if (stringIsInArray(array, control.value, valueMapFunction)) {
9197
- return {
9198
- [validatorName]: true
9199
- };
10693
+ return this.contentDOMElement;
10694
+ }
10695
+ appendContendDom() {
10696
+ const contentElement = this.dom.querySelector('[data-node-view-content]');
10697
+ if (this.contentDOMElement && contentElement && !contentElement.contains(this.contentDOMElement)) {
10698
+ contentElement.appendChild(this.contentDOMElement);
9200
10699
  }
9201
- return null;
9202
- };
9203
- }
9204
- function ValidateStringNotInArrayAsync(array$, valueMapFunction, validatorName = 'stringNotInArrayAsync') {
9205
- return function (control) {
9206
- if (!control.value) {
9207
- return of(null);
10700
+ }
10701
+ handleSelectionUpdate() {
10702
+ const { from, to } = this.editor.state.selection;
10703
+ if (from <= this.getPos() && to >= this.getPos() + this.node.nodeSize) {
10704
+ this.selectNode();
9208
10705
  }
9209
- return array$.pipe(map$1((array) => ValidateStringNotInArray(array, valueMapFunction, validatorName)(control)));
9210
- };
10706
+ else {
10707
+ this.deselectNode();
10708
+ }
10709
+ }
10710
+ update(node, decorations) {
10711
+ const updateProps = () => {
10712
+ this.renderer.updateProps({ node, decorations });
10713
+ };
10714
+ if (this.options.update) {
10715
+ const oldNode = this.node;
10716
+ const oldDecorations = this.decorations;
10717
+ this.node = node;
10718
+ this.decorations = decorations;
10719
+ return this.options.update({
10720
+ oldNode,
10721
+ oldDecorations,
10722
+ newNode: node,
10723
+ newDecorations: decorations,
10724
+ updateProps: () => updateProps()
10725
+ });
10726
+ }
10727
+ if (node.type !== this.node.type) {
10728
+ return false;
10729
+ }
10730
+ if (node === this.node && this.decorations === decorations) {
10731
+ return true;
10732
+ }
10733
+ this.node = node;
10734
+ this.decorations = decorations;
10735
+ updateProps();
10736
+ return true;
10737
+ }
10738
+ selectNode() {
10739
+ this.renderer.updateProps({ selected: true });
10740
+ }
10741
+ deselectNode() {
10742
+ this.renderer.updateProps({ selected: false });
10743
+ }
10744
+ destroy() {
10745
+ this.renderer.destroy();
10746
+ this.editor.off('selectionUpdate', this.handleSelectionUpdate);
10747
+ this.contentDOMElement = null;
10748
+ }
9211
10749
  }
10750
+ const AngularNodeViewRenderer = (ViewComponent, options) => {
10751
+ return (props) => {
10752
+ return new AngularNodeView(ViewComponent, props, options);
10753
+ };
10754
+ };
9212
10755
 
9213
- const MODAL_CLOSE = new InjectionToken('MODAL_CLOSE');
9214
10756
  /**
9215
- * An enum to track how the modal was closed
9216
- * Escape - Esc key press
9217
- * CloseButton - top right close button (x)
9218
- * OutsideClick - click outside the modal
9219
- * Other - modal close with the trigger through closeModal$ subject
10757
+ * Removes markdown syntax from text.
10758
+ * e.g '### This is a heading' will become 'This is a heading'
10759
+ *
10760
+ * It does not remove links. Links can be post processed by LxLinkifyPipe or LxUnLinkifyPipe
9220
10761
  */
9221
- var ModalCloseClickLocation;
9222
- (function (ModalCloseClickLocation) {
9223
- ModalCloseClickLocation["Escape"] = "escape";
9224
- ModalCloseClickLocation["CloseButton"] = "closeButton";
9225
- ModalCloseClickLocation["OutsideClick"] = "outsideClick";
9226
- ModalCloseClickLocation["CancelButton"] = "cancelButton";
9227
- ModalCloseClickLocation["Other"] = "other";
9228
- })(ModalCloseClickLocation || (ModalCloseClickLocation = {}));
9229
-
9230
- class ModalFooterComponent {
9231
- constructor() {
9232
- this.border = false;
9233
- }
9234
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9235
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: ModalFooterComponent, isStandalone: true, selector: "lx-modal-footer", inputs: { border: "border" }, ngImport: i0, template: "<div class=\"footerContainer\" [class.border]=\"border\">\n <ng-content />\n</div>\n", styles: [":host{display:block;text-align:right;--modal-footer-padding-left: 90px;--modal-footer-padding-right: 0;--modal-footer-display: block}:host-context(.fullscreen) .footerContainer{padding-left:var(--modal-footer-padding-left);padding-right:var(--modal-footer-padding-right);height:70px}:host-context(.dialog) .footerContainer,:host-context(.dialog-large) .footerContainer{padding:16px;height:64px;border-radius:0 0 6px 6px;display:var(--modal-footer-display)}.footerContainer.border{border-top:1px solid #cfd5df}\n"] }); }
9236
- }
9237
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalFooterComponent, decorators: [{
9238
- type: Component,
9239
- args: [{ selector: 'lx-modal-footer', template: "<div class=\"footerContainer\" [class.border]=\"border\">\n <ng-content />\n</div>\n", styles: [":host{display:block;text-align:right;--modal-footer-padding-left: 90px;--modal-footer-padding-right: 0;--modal-footer-display: block}:host-context(.fullscreen) .footerContainer{padding-left:var(--modal-footer-padding-left);padding-right:var(--modal-footer-padding-right);height:70px}:host-context(.dialog) .footerContainer,:host-context(.dialog-large) .footerContainer{padding:16px;height:64px;border-radius:0 0 6px 6px;display:var(--modal-footer-display)}.footerContainer.border{border-top:1px solid #cfd5df}\n"] }]
9240
- }], propDecorators: { border: [{
9241
- type: Input
9242
- }] } });
9243
-
9244
- class ModalHeaderComponent {
9245
- constructor() {
9246
- this.title = '';
9247
- this.bottomBorder = true;
10762
+ class RemoveMarkdownPipe {
10763
+ transform(markdown) {
10764
+ return markdownToText(markdown);
9248
10765
  }
9249
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9250
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: ModalHeaderComponent, isStandalone: true, selector: "lx-modal-header", inputs: { title: "title", bottomBorder: "bottomBorder" }, ngImport: i0, template: "<div class=\"headerContainer\" [class.bottomBorder]=\"bottomBorder\">\n <ng-content />\n @if (title.length > 0) {\n <h1 class=\"lx-heading-2\">{{ title }}</h1>\n }\n</div>\n", styles: [":host{display:block}:host.lxModalHeaderOneLine h1{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:var(--lx-modal-header-max-width)}:host-context(.fullscreen) .headerContainer{padding:24px 90px;border-bottom:none}:host-context(.fullscreen) .headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}:host-context(.dialog) .headerContainer,:host-context(.dialog-large) .headerContainer{padding:24px 16px;border-radius:6px 6px 0 0}:host-context(.dialog) .headerContainer h1,:host-context(.dialog-large) .headerContainer h1{word-break:break-word}h1{margin:0 auto;padding:0;color:#2a303d;text-align:center}.headerContainer{display:flex}.headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}\n"] }); }
9251
- }
9252
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalHeaderComponent, decorators: [{
9253
- type: Component,
9254
- args: [{ selector: 'lx-modal-header', template: "<div class=\"headerContainer\" [class.bottomBorder]=\"bottomBorder\">\n <ng-content />\n @if (title.length > 0) {\n <h1 class=\"lx-heading-2\">{{ title }}</h1>\n }\n</div>\n", styles: [":host{display:block}:host.lxModalHeaderOneLine h1{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:var(--lx-modal-header-max-width)}:host-context(.fullscreen) .headerContainer{padding:24px 90px;border-bottom:none}:host-context(.fullscreen) .headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}:host-context(.dialog) .headerContainer,:host-context(.dialog-large) .headerContainer{padding:24px 16px;border-radius:6px 6px 0 0}:host-context(.dialog) .headerContainer h1,:host-context(.dialog-large) .headerContainer h1{word-break:break-word}h1{margin:0 auto;padding:0;color:#2a303d;text-align:center}.headerContainer{display:flex}.headerContainer.bottomBorder{border-bottom:1px solid #cfd5df}\n"] }]
9255
- }], propDecorators: { title: [{
9256
- type: Input
9257
- }], bottomBorder: [{
9258
- type: Input
9259
- }] } });
9260
-
9261
- class ModalContentDirective {
9262
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
9263
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: ModalContentDirective, isStandalone: true, selector: "[lxModalContent]", ngImport: i0 }); }
10766
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RemoveMarkdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
10767
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: RemoveMarkdownPipe, isStandalone: true, name: "lxRemoveMarkdown" }); }
9264
10768
  }
9265
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, decorators: [{
9266
- type: Directive,
10769
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RemoveMarkdownPipe, decorators: [{
10770
+ type: Pipe,
9267
10771
  args: [{
9268
- selector: '[lxModalContent]'
10772
+ name: 'lxRemoveMarkdown'
9269
10773
  }]
9270
10774
  }] });
10775
+ function markdownToText(md) {
10776
+ let output = md || '';
10777
+ try {
10778
+ output = output
10779
+ // Remove horizontal rules (stripListHeaders conflict with this rule, which is why it has been moved to the top)
10780
+ .replace(/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/gm, '')
10781
+ // stripListLeaders
10782
+ .replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
10783
+ // Strikethrough
10784
+ .replace(/~~/g, '')
10785
+ // Fenced codeblocks with backticks
10786
+ .replace(/```(?:.*)\n([\s\S]*?)```/g, (_, code) => code.trim())
10787
+ // Remove HTML tags
10788
+ .replace(/<[^>]*>/g, '')
10789
+ // Remove images
10790
+ .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
10791
+ // Remove blockquotes
10792
+ .replace(/^(\n)?\s{0,3}>\s?/gm, '$1')
10793
+ // Remove atx-style headers
10794
+ .replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, '$1$3$4$6')
10795
+ // Remove * emphasis
10796
+ .replace(/([\*]+)(\S)(.*?\S)??\1/g, '$2$3')
10797
+ // Remove _ emphasis. Unlike *, _ emphasis gets rendered only if
10798
+ // 1. Either there is a whitespace character before opening _ and after closing _.
10799
+ // 2. Or _ is at the start/end of the string.
10800
+ .replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, '$1$3$4$5')
10801
+ // Remove single-line code blocks (already handled multiline above )
10802
+ .replace(/(`{3,})(.*?)\1/gm, '$2')
10803
+ // Remove inline code
10804
+ .replace(/`(.+?)`/g, '$1');
10805
+ }
10806
+ catch (e) {
10807
+ return md;
10808
+ }
10809
+ return output;
10810
+ }
9271
10811
 
9272
- /**
9273
- * This documentation provides details on the usage and configuration of the Modal.
9274
- *
9275
- * ## Usage
9276
- *
9277
- * 1. Import `LxModalModule` and `LxCoreUiModule` modules from `@leanix/components` in your module where you want to use the component.
9278
- *
9279
- * ```ts
9280
- * import { LxModalModule, LxCoreUiModule } from '@leanix/components';
9281
- * ```
9282
- *
9283
- * 2. Use the **lx-modal** component in your template with the parameters described below.
9284
- *
9285
- * - **`open`**: Whether the modal is open or closed.
9286
- * - **`size`**: 'dialog' | 'dialog-large'.
9287
- * - **`verticalScroll`**: Whether the modal is scrollable or not.
9288
- * - **`showHeader`**: Whether the modal has a header or not.
9289
- * - **`showFooter`**: Whether the modal has a footer or not.
9290
- * - **`showCloseButton`**: Whether to show the close button.
9291
- * - **`showBackButton`**: Whether to show the back button.
9292
- *
9293
- * 3. Use optional **lx-modal-header** component in your template with the parameters described below.
9294
- *
9295
- * - **`title`**: Title of the modal.
9296
- * - **`subtitle`**: Subtitle of the modal.
9297
- * - **`bottomBorder`**: Whether to show a bottom border.
9298
- *
9299
- * 4. Use optional **lx-modal-footer** component in your template with the parameters described below.
9300
- *
9301
- * - **`border`**: Whether to show the footer at the bottom of the modal.
9302
- *
9303
- * **GLOBAL PROVIDERS** required for this component:
9304
- * - `provideAnimations()`
9305
- *
9306
- * **ATTENTION - SCROLLABLE DIALOG**:
9307
- * The <lx-modal> component when used as "dialog" is not designed to work with a
9308
- * scrollable body (via `overflow: auto | scroll`) in combination with dropdowns.
9309
- * The overflow on the body will also clip the dropdowns, which is expected.
9310
- *
9311
- * Reasoning:
9312
- * The contents within the dialog should be just a few elements which fit and
9313
- * justify the usage of a dialog. If the content is larger than the dialog, and thus
9314
- * requires scrolling, we should discuss whether to put it into a dialog at all
9315
- * and rather think about putting the content on a separate route or
9316
- * using the fullscreen version of the modal.
9317
- */
9318
- class ModalComponent {
9319
- get content() {
9320
- return this.explicitContent || this.implicitContent;
9321
- }
9322
- /** @internal */
9323
- onEscape() {
9324
- if (this.open && this.showCloseButton) {
9325
- this.closeModal(ModalCloseClickLocation.Escape);
9326
- }
9327
- }
9328
- constructor(overlay, renderer, closeModal$, focusTrap) {
9329
- this.overlay = overlay;
10812
+ class MaxLengthCounterDirective {
10813
+ constructor(el, renderer, ngControl) {
10814
+ this.el = el;
9330
10815
  this.renderer = renderer;
9331
- this.closeModal$ = closeModal$;
9332
- this.focusTrap = focusTrap;
9333
- /** @internal */
9334
- this.NAME = 'ModalComponent';
9335
- /** Whether the modal is open or closed. */
9336
- this.open = false;
9337
- /** Whether to show the close button. */
9338
- this.showCloseButton = true;
9339
- /** Whether to show the back button. */
9340
- this.showBackButton = false;
9341
- /*
9342
- * If true, then the content area scrolls vertically instead of expanding its height.
9343
- * This can be a problem if the content has dropdowns or date inputs, but can be good if the content has a huge amount of text.
9344
- */
9345
- this.verticalScroll = false;
9346
- /** The size of the modal. */
9347
- this.size = 'fullscreen';
9348
- /**
9349
- * Minimum width of the modal.
9350
- *
9351
- * _NB: Some modal implementations rely on this minWidth being 600px_
9352
- */
9353
- this.minWidth = '600px';
9354
- /** Whether the modal is a focus trap. */
9355
- this.isFocusTrap = false;
9356
- /** Event emitted when the modal is closed. */
9357
- this.close = new EventEmitter();
9358
- /** Event emitted when the back button is clicked. */
9359
- this.back = new EventEmitter();
9360
- /** @internal */
9361
- this.closeLocation = ModalCloseClickLocation;
9362
- /** @internal */
9363
- this.destroyed$ = new Subject();
10816
+ this.ngControl = ngControl;
10817
+ this.lxMaxLengthCounter = null;
10818
+ this.counterElement = null;
10819
+ this.destroy$ = new Subject();
9364
10820
  }
9365
10821
  ngOnInit() {
9366
- this.closeModal$
9367
- ?.pipe(takeUntil(this.destroyed$))
9368
- .subscribe((closeLocation) => this.closeModal(closeLocation));
9369
- if (this.size === 'fullscreen') {
9370
- this.overlayRef = this.overlay.create({
9371
- panelClass: this.size,
9372
- width: '100%',
9373
- height: '100vh'
9374
- });
9375
- }
9376
- else if (this.size === 'dialog-large') {
9377
- const positionStrategy = this.overlay.position().global().top('4vh').centerHorizontally();
9378
- this.overlayRef = this.overlay.create({
9379
- panelClass: this.size,
9380
- positionStrategy,
9381
- hasBackdrop: true,
9382
- width: '90%',
9383
- height: '90vh'
9384
- });
10822
+ this.setupMaxLengthValidation();
10823
+ this.connectCounter();
10824
+ this.updateCounter();
10825
+ this.subscribeToValueChanges();
10826
+ this.setupInputRestriction();
10827
+ this.checkForInitialOversize();
10828
+ }
10829
+ ngOnDestroy() {
10830
+ this.destroy$.next();
10831
+ this.destroy$.complete();
10832
+ }
10833
+ setupMaxLengthValidation() {
10834
+ const maxLength = this.getMaxLength();
10835
+ const control = this.ngControl.control;
10836
+ if (maxLength !== null && control) {
10837
+ const validators = control.validator ? [control.validator, Validators.maxLength(maxLength)] : Validators.maxLength(maxLength);
10838
+ control.setValidators(validators);
10839
+ control.updateValueAndValidity();
9385
10840
  }
9386
- else {
9387
- // size 'dialog'
9388
- const positionStrategy = this.overlay.position().global().top('8vh').centerHorizontally();
9389
- this.overlayRef = this.overlay.create({
9390
- panelClass: this.size,
9391
- minWidth: this.minWidth,
9392
- positionStrategy,
9393
- hasBackdrop: true,
9394
- scrollStrategy: this.overlay.scrollStrategies.block()
9395
- });
10841
+ }
10842
+ getMaxLength() {
10843
+ if (this.lxMaxLengthCounter) {
10844
+ return this.lxMaxLengthCounter;
9396
10845
  }
9397
- if (this.size !== 'fullscreen') {
9398
- this.overlayRef
9399
- .backdropClick()
9400
- .pipe(takeUntil(this.destroyed$))
9401
- .subscribe(() => this.closeModal(ModalCloseClickLocation.OutsideClick));
10846
+ const control = this.ngControl.control;
10847
+ const validatorFn = control?.validator;
10848
+ const errors = validatorFn?.(new FormControl({ length: Infinity }));
10849
+ const requiredLength = errors?.['maxlength']['requiredLength'];
10850
+ this.lxMaxLengthCounter = requiredLength ? requiredLength.toString() : null;
10851
+ if (this.lxMaxLengthCounter === null) {
10852
+ console.warn('lxMaxLength directive is used without a value or a control with a maxLength validator');
9402
10853
  }
10854
+ return requiredLength;
9403
10855
  }
9404
- ngOnChanges() {
9405
- if (this.open && this.overlayRef && !this.overlayRef.hasAttached()) {
9406
- this.openModal();
9407
- }
9408
- if (!this.open && this.overlayRef && this.overlayRef.hasAttached()) {
9409
- this.closeModal(ModalCloseClickLocation.Other);
10856
+ connectCounter() {
10857
+ this.el.nativeElement.classList.add('lx-max-length-counter-input');
10858
+ if (this.lxMaxLengthCounterRef) {
10859
+ this.counterElement = this.lxMaxLengthCounterRef;
9410
10860
  }
9411
- if (this.open && this.overlayRef && this.overlayRef.hasAttached() && this.isFocusTrap) {
9412
- this.trapFocusInModal(this.overlayRef.hostElement);
10861
+ else {
10862
+ this.createCounter();
9413
10863
  }
9414
10864
  }
9415
- ngAfterViewInit() {
9416
- if (this.open) {
9417
- timer(0)
9418
- .pipe(takeUntil(this.destroyed$))
9419
- .subscribe(() => this.openModal());
10865
+ createCounter() {
10866
+ this.counterElement = this.renderer.createElement('span');
10867
+ this.renderer.addClass(this.counterElement, 'lx-max-length-counter');
10868
+ const nextSibling = this.el.nativeElement.nextSibling;
10869
+ if (nextSibling) {
10870
+ this.renderer.insertBefore(this.el.nativeElement.parentNode, this.counterElement, nextSibling);
9420
10871
  }
9421
- }
9422
- /** @internal */
9423
- openModal() {
9424
- this.oldOverflow = document.documentElement.style.overflowY;
9425
- if (this.size === 'fullscreen') {
9426
- this.renderer.setStyle(document.documentElement, 'overflowY', 'hidden');
10872
+ else {
10873
+ this.renderer.appendChild(this.el.nativeElement.parentNode, this.counterElement);
9427
10874
  }
9428
- this.overlayRef.attach(this.cdkPortal);
9429
- this.trapFocusInModal(this.overlayRef.hostElement);
9430
- }
9431
- /** @internal */
9432
- emitBack() {
9433
- this.back.emit();
10875
+ this.destroy$.subscribe(() => {
10876
+ this.renderer.removeChild(this.el.nativeElement.parentNode, this.counterElement);
10877
+ });
9434
10878
  }
9435
- ngOnDestroy() {
9436
- this.destroyed$.next();
9437
- if (this.size === 'fullscreen') {
9438
- this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
10879
+ updateCounter() {
10880
+ if (this.counterElement) {
10881
+ const currentLength = this.ngControl.value ? this.ngControl.value.length : 0;
10882
+ const maxLength = this.getMaxLength();
10883
+ this.renderer.setProperty(this.counterElement, 'textContent', `${currentLength} / ${maxLength}`);
9439
10884
  }
9440
- if (this.overlayRef) {
9441
- this.overlayRef.dispose();
10885
+ }
10886
+ subscribeToValueChanges() {
10887
+ const control = this.ngControl.control;
10888
+ if (control) {
10889
+ control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
10890
+ this.updateCounter();
10891
+ });
9442
10892
  }
9443
10893
  }
9444
- /** @internal */
9445
- async closeModal(closeLocation) {
9446
- if (!this.canModalBeClosed || (await this.canModalBeClosed(closeLocation))) {
9447
- this.open = false;
9448
- this.overlayRef.detach();
9449
- if (this.size === 'fullscreen') {
9450
- this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
9451
- }
9452
- this.close.emit(typeof closeLocation === 'boolean' ? ModalCloseClickLocation.Other : closeLocation);
10894
+ setupInputRestriction() {
10895
+ const maxLength = this.getMaxLength();
10896
+ if (maxLength !== null) {
10897
+ this.renderer.listen(this.el.nativeElement, 'beforeinput', (event) => {
10898
+ const input = event.target;
10899
+ const selectionStart = input.selectionStart || 0;
10900
+ const selectionEnd = input.selectionEnd || 0;
10901
+ const newValue = input.value.slice(0, selectionStart) + (event.data || '') + input.value.slice(selectionEnd);
10902
+ // Prevent input if the new value exceeds the max length and grows
10903
+ if (newValue.length > maxLength && newValue.length > input.value.length) {
10904
+ event.preventDefault();
10905
+ }
10906
+ });
10907
+ this.renderer.listen(this.el.nativeElement, 'input', () => {
10908
+ this.updateCounter();
10909
+ });
9453
10910
  }
9454
10911
  }
9455
- trapFocusInModal(hostElement) {
9456
- this.focusTrap.create(hostElement);
10912
+ checkForInitialOversize() {
10913
+ const maxLength = this.getMaxLength();
10914
+ const value = this.ngControl.value;
10915
+ if (maxLength !== null && value && value.length > maxLength) {
10916
+ this.ngControl.control?.markAsDirty({ onlySelf: true });
10917
+ this.ngControl.control?.updateValueAndValidity({ onlySelf: true });
10918
+ }
9457
10919
  }
9458
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalComponent, deps: [{ token: i1$2.Overlay }, { token: i0.Renderer2 }, { token: MODAL_CLOSE, optional: true }, { token: i2.ConfigurableFocusTrapFactory }], target: i0.ɵɵFactoryTarget.Component }); }
9459
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: ModalComponent, isStandalone: true, selector: "lx-modal", inputs: { open: "open", showCloseButton: "showCloseButton", showBackButton: "showBackButton", verticalScroll: "verticalScroll", size: "size", minWidth: "minWidth", isFocusTrap: "isFocusTrap", canModalBeClosed: "canModalBeClosed" }, outputs: { close: "close", back: "back" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, queries: [{ propertyName: "header", first: true, predicate: ModalHeaderComponent, descendants: true }, { propertyName: "footer", first: true, predicate: ModalFooterComponent, descendants: true }, { propertyName: "explicitContent", first: true, predicate: ModalContentDirective, descendants: true, read: TemplateRef, static: true }], viewQueries: [{ propertyName: "cdkPortal", first: true, predicate: CdkPortal, descendants: true, static: true }, { propertyName: "implicitContent", first: true, predicate: ["implicitContent"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-template cdkPortal>\n @if (open) {\n <div\n role=\"dialog\"\n class=\"lxmodal\"\n [class.lxmodal--fullscreen]=\"size === 'fullscreen'\"\n [class.lxmodal--dialog]=\"size === 'dialog'\"\n [class.lxmodal--dialog-large]=\"size === 'dialog-large'\"\n [class.lxmodal--withFooter]=\"!!footer\"\n [class.lxmodal--verticalScroll]=\"verticalScroll\"\n @modal\n >\n @if (size === 'fullscreen' && showBackButton) {\n <div (click)=\"emitBack()\" (keyup.enter)=\"emitBack()\" tabindex=\"0\" role=\"button\" class=\"fal fa-long-arrow-left\"></div>\n }\n @if (showCloseButton) {\n <!-- TODO lx-button doesn't comply with modal close button style yet -->\n <!-- eslint-disable-next-line @nx/workspace-no-icon-in-button-content -->\n <button\n (click)=\"closeModal(closeLocation.CloseButton)\"\n [attr.aria-label]=\"NAME + '.close' | translate\"\n class=\"fal fa-times closeButton\"\n ></button>\n }\n @if (header) {\n <ng-content select=\"lx-modal-header\" />\n }\n <div class=\"modalContentContainer\" [class.lxThinScrollbar]=\"verticalScroll\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n @if (footer) {\n <ng-content select=\"lx-modal-footer\" />\n }\n </div>\n }\n</ng-template>\n<ng-template #implicitContent>\n <ng-content />\n</ng-template>\n", styles: ["@keyframes subtleScaleUpKeyFrames{0%{transform:scale(.95);opacity:0}}.lxmodal{background:#fff;width:100%}.lxmodal--withFooter.lxmodal--fullscreen.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--withFooter.lxmodal--fullscreen:not(.lxmodal--verticalScroll) .modalContentContainer{bottom:70px;overflow:hidden}.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--fullscreen{height:100%;display:flex;flex-direction:column}.lxmodal--fullscreen .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;right:36px;top:12px;z-index:1}.lxmodal--fullscreen .closeButton:before{cursor:pointer}.lxmodal--fullscreen .closeButton:hover,.lxmodal--fullscreen .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .closeButton:focus{outline:0}.lxmodal--fullscreen .fa-long-arrow-left{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;left:36px;top:16px}.lxmodal--fullscreen .fa-long-arrow-left:before{cursor:pointer}.lxmodal--fullscreen .fa-long-arrow-left:hover,.lxmodal--fullscreen .fa-long-arrow-left:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .fa-long-arrow-left:focus{outline:0}.lxmodal--dialog,.lxmodal--dialog-large{display:block;position:relative;border-radius:6px;box-shadow:0 8px 20px #0000003d}.lxmodal--dialog .modalContentContainer,.lxmodal--dialog-large .modalContentContainer{position:relative}.lxmodal--dialog .closeButton,.lxmodal--dialog-large .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;height:32px;width:32px;z-index:999;right:10px;top:20px}.lxmodal--dialog .closeButton:before,.lxmodal--dialog-large .closeButton:before{cursor:pointer}.lxmodal--dialog .closeButton:hover,.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:hover,.lxmodal--dialog-large .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:focus{outline:0}.lxmodal--dialog .modalContentContainer{padding:16px 16px 0}.lxmodal--dialog.lxmodal--verticalScroll .modalContentContainer{padding:16px;max-height:calc(84vh - 136px)}.lxmodal--dialog-large .modalContentContainer{padding:16px;height:calc(100% - 136px)}.modalContentContainer{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i3.CdkPortal, selector: "[cdkPortal]", exportAs: ["cdkPortal"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: CommonModule }], animations: [
9460
- trigger('modal', [
9461
- transition(':enter', [style({ opacity: 0 }), animate('0.15s', style({ opacity: 1 }))]),
9462
- transition(':leave', animate('0.15s', style({ opacity: 0 })))
9463
- ])
9464
- ] }); }
10920
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MaxLengthCounterDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1$5.NgControl, self: true }], target: i0.ɵɵFactoryTarget.Directive }); }
10921
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: MaxLengthCounterDirective, isStandalone: true, selector: "[lxMaxLengthCounter]", inputs: { lxMaxLengthCounter: "lxMaxLengthCounter", lxMaxLengthCounterRef: "lxMaxLengthCounterRef" }, ngImport: i0 }); }
9465
10922
  }
9466
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalComponent, decorators: [{
9467
- type: Component,
9468
- args: [{ selector: 'lx-modal', animations: [
9469
- trigger('modal', [
9470
- transition(':enter', [style({ opacity: 0 }), animate('0.15s', style({ opacity: 1 }))]),
9471
- transition(':leave', animate('0.15s', style({ opacity: 0 })))
9472
- ])
9473
- ], imports: [PortalModule, NgTemplateOutlet, TranslateModule, CommonModule], template: "<ng-template cdkPortal>\n @if (open) {\n <div\n role=\"dialog\"\n class=\"lxmodal\"\n [class.lxmodal--fullscreen]=\"size === 'fullscreen'\"\n [class.lxmodal--dialog]=\"size === 'dialog'\"\n [class.lxmodal--dialog-large]=\"size === 'dialog-large'\"\n [class.lxmodal--withFooter]=\"!!footer\"\n [class.lxmodal--verticalScroll]=\"verticalScroll\"\n @modal\n >\n @if (size === 'fullscreen' && showBackButton) {\n <div (click)=\"emitBack()\" (keyup.enter)=\"emitBack()\" tabindex=\"0\" role=\"button\" class=\"fal fa-long-arrow-left\"></div>\n }\n @if (showCloseButton) {\n <!-- TODO lx-button doesn't comply with modal close button style yet -->\n <!-- eslint-disable-next-line @nx/workspace-no-icon-in-button-content -->\n <button\n (click)=\"closeModal(closeLocation.CloseButton)\"\n [attr.aria-label]=\"NAME + '.close' | translate\"\n class=\"fal fa-times closeButton\"\n ></button>\n }\n @if (header) {\n <ng-content select=\"lx-modal-header\" />\n }\n <div class=\"modalContentContainer\" [class.lxThinScrollbar]=\"verticalScroll\">\n <ng-container *ngTemplateOutlet=\"content\" />\n </div>\n @if (footer) {\n <ng-content select=\"lx-modal-footer\" />\n }\n </div>\n }\n</ng-template>\n<ng-template #implicitContent>\n <ng-content />\n</ng-template>\n", styles: ["@keyframes subtleScaleUpKeyFrames{0%{transform:scale(.95);opacity:0}}.lxmodal{background:#fff;width:100%}.lxmodal--withFooter.lxmodal--fullscreen.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--withFooter.lxmodal--fullscreen:not(.lxmodal--verticalScroll) .modalContentContainer{bottom:70px;overflow:hidden}.lxmodal--verticalScroll .modalContentContainer{overflow-y:auto;padding:16px}.lxmodal--fullscreen{height:100%;display:flex;flex-direction:column}.lxmodal--fullscreen .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;right:36px;top:12px;z-index:1}.lxmodal--fullscreen .closeButton:before{cursor:pointer}.lxmodal--fullscreen .closeButton:hover,.lxmodal--fullscreen .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .closeButton:focus{outline:0}.lxmodal--fullscreen .fa-long-arrow-left{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;width:48px;height:48px;line-height:48px;left:36px;top:16px}.lxmodal--fullscreen .fa-long-arrow-left:before{cursor:pointer}.lxmodal--fullscreen .fa-long-arrow-left:hover,.lxmodal--fullscreen .fa-long-arrow-left:focus{color:#526179;background-color:#eaedf1}.lxmodal--fullscreen .fa-long-arrow-left:focus{outline:0}.lxmodal--dialog,.lxmodal--dialog-large{display:block;position:relative;border-radius:6px;box-shadow:0 8px 20px #0000003d}.lxmodal--dialog .modalContentContainer,.lxmodal--dialog-large .modalContentContainer{position:relative}.lxmodal--dialog .closeButton,.lxmodal--dialog-large .closeButton{border:0;background:transparent;position:absolute;text-align:center;transition:color,background-color .18s;transition-delay:.1s;transition-timing-function:ease;border-radius:50%;color:#61779d;font-size:26px;height:32px;width:32px;z-index:999;right:10px;top:20px}.lxmodal--dialog .closeButton:before,.lxmodal--dialog-large .closeButton:before{cursor:pointer}.lxmodal--dialog .closeButton:hover,.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:hover,.lxmodal--dialog-large .closeButton:focus{color:#526179;background-color:#eaedf1}.lxmodal--dialog .closeButton:focus,.lxmodal--dialog-large .closeButton:focus{outline:0}.lxmodal--dialog .modalContentContainer{padding:16px 16px 0}.lxmodal--dialog.lxmodal--verticalScroll .modalContentContainer{padding:16px;max-height:calc(84vh - 136px)}.lxmodal--dialog-large .modalContentContainer{padding:16px;height:calc(100% - 136px)}.modalContentContainer{flex:1}\n"] }]
9474
- }], ctorParameters: () => [{ type: i1$2.Overlay }, { type: i0.Renderer2 }, { type: i5.Observable, decorators: [{
9475
- type: Optional
9476
- }, {
9477
- type: Inject,
9478
- args: [MODAL_CLOSE]
9479
- }] }, { type: i2.ConfigurableFocusTrapFactory }], propDecorators: { open: [{
9480
- type: Input
9481
- }], showCloseButton: [{
9482
- type: Input
9483
- }], showBackButton: [{
9484
- type: Input
9485
- }], verticalScroll: [{
9486
- type: Input
9487
- }], size: [{
9488
- type: Input
9489
- }], minWidth: [{
9490
- type: Input
9491
- }], isFocusTrap: [{
10923
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MaxLengthCounterDirective, decorators: [{
10924
+ type: Directive,
10925
+ args: [{
10926
+ selector: '[lxMaxLengthCounter]'
10927
+ }]
10928
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1$5.NgControl, decorators: [{
10929
+ type: Self
10930
+ }] }], propDecorators: { lxMaxLengthCounter: [{
9492
10931
  type: Input
9493
- }], canModalBeClosed: [{
10932
+ }], lxMaxLengthCounterRef: [{
9494
10933
  type: Input
9495
- }], close: [{
9496
- type: Output
9497
- }], back: [{
9498
- type: Output
9499
- }], header: [{
9500
- type: ContentChild,
9501
- args: [ModalHeaderComponent]
9502
- }], footer: [{
9503
- type: ContentChild,
9504
- args: [ModalFooterComponent]
9505
- }], cdkPortal: [{
9506
- type: ViewChild,
9507
- args: [CdkPortal, { static: true }]
9508
- }], implicitContent: [{
9509
- type: ViewChild,
9510
- args: ['implicitContent', { static: true }]
9511
- }], explicitContent: [{
9512
- type: ContentChild,
9513
- args: [ModalContentDirective, { read: TemplateRef, static: true }]
9514
- }], onEscape: [{
9515
- type: HostListener,
9516
- args: ['document:keydown.escape']
9517
10934
  }] } });
9518
10935
 
10936
+ class Sorting {
10937
+ constructor() {
10938
+ this.key = '';
10939
+ this.order = 'asc';
10940
+ }
10941
+ }
10942
+
10943
+ /**
10944
+ * Due to limitations of the native html datepicker this validator is needed:
10945
+ * In the datepicker the min and max values only apply if the date is altered e.g. via scrolling.
10946
+ * It is still possible to manually input a value lower or higher than min and max value.
10947
+ */
10948
+ function ValidateDateInForeseeableFuture(control) {
10949
+ // matches yyyy-mm-dd
10950
+ const dateRegex = /^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/;
10951
+ if (control.value && dateRegex.test(control.value)) {
10952
+ const today = formatDate(new Date(), 'yyyy-MM-dd', 'en');
10953
+ const maxDate = '2999-12-31';
10954
+ if (control.value < today || control.value > maxDate) {
10955
+ return {
10956
+ dateInForseeableFuture: {
10957
+ valid: false
10958
+ }
10959
+ };
10960
+ }
10961
+ else {
10962
+ return null;
10963
+ }
10964
+ }
10965
+ return null;
10966
+ }
10967
+
10968
+ function stringIsInArray(array, stringToFind, valueMapFunction) {
10969
+ const valueToFind = valueMapFunction ? valueMapFunction(stringToFind).toLocaleLowerCase() : stringToFind.toLowerCase();
10970
+ return array.map((item) => item.toLocaleLowerCase()).indexOf(valueToFind) > -1;
10971
+ }
10972
+ /**
10973
+ * Validates that a string is not inside an array of strings
10974
+ */
10975
+ function ValidateStringNotInArray(array, valueMapFunction, validatorName = 'stringNotInArray') {
10976
+ return function (control) {
10977
+ if (!control.value) {
10978
+ return null;
10979
+ }
10980
+ if (stringIsInArray(array, control.value, valueMapFunction)) {
10981
+ return {
10982
+ [validatorName]: true
10983
+ };
10984
+ }
10985
+ return null;
10986
+ };
10987
+ }
10988
+ function ValidateStringNotInArrayAsync(array$, valueMapFunction, validatorName = 'stringNotInArrayAsync') {
10989
+ return function (control) {
10990
+ if (!control.value) {
10991
+ return of(null);
10992
+ }
10993
+ return array$.pipe(map$1((array) => ValidateStringNotInArray(array, valueMapFunction, validatorName)(control)));
10994
+ };
10995
+ }
10996
+
9519
10997
  const MODAL_MODULE_EXPORTS = [ModalComponent, ModalHeaderComponent, ModalFooterComponent, ModalContentDirective];
9520
10998
  class LxModalModule {
9521
10999
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: LxModalModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -10200,5 +11678,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
10200
11678
  * Generated bundle index. Do not edit.
10201
11679
  */
10202
11680
 
10203
- export { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, AVATAR_COLORS, AVATAR_SIZE_MAPPING, AfterViewInitDirective, AutocloseDirective, AutocloseGroupService, AutofocusDirective, AvatarComponent, AvatarGroupComponent, BACKSPACE, BadgeComponent, BannerComponent, BaseSelectDirective, BasicDropdownComponent, BasicDropdownItemComponent, BrPipe, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CORE_MODULE_EXPORTS, CURRENCY_SYMBOL_MAP, CardComponent, CdkOptionsDropdownComponent, CdkOptionsSubDropdownComponent, CollapsibleComponent, ContenteditableDirective, ContrastColorPipe, CounterComponent, CurrencyInputComponent, CurrencySymbolComponent, CustomDatePipe, DATEPICKER_CONTROL_VALUE_ACCESSOR, DATE_FN_LOCALE, DATE_FORMATS, DEFAULT_IMAGE_ID, DateFormatter, DateInputComponent, DatePickerComponent, DatepickerConfig, DatepickerUiModule, DragAndDropListComponent, DragAndDropListItemComponent, END, ENTER, ESCAPE, EllipsisComponent, EmptyStateComponent, ErrorMessageComponent, FORMS_MODULE_EXPORTS, FORM_CONTROL_ERROR_DISPLAY_STRATEGY, FORM_CONTROL_ERROR_NAMESPACE, FilterSelectionPipe, FilterTermPipe, FormErrorComponent, FormErrorDirective, FormSubmitDirective, FormatNumberPipe, GLOBAL_TRANSLATION_OPTIONS, HOME, HighlightRangePipe, HighlightTermPipe, ICON_MAP, IMAGE_READER, IconComponent, IconScaleComponent, InputComponent, KeyboardActionSourceDirective, KeyboardSelectAction, KeyboardSelectDirective, LOCALE_FN, LX_BUTTON_USE_SAP_ICONS, LX_ELLIPSIS_DEBOUNCE_ON_RESIZE, LxCoreUiModule, LxDragAndDropListModule, LxFormsModule, LxIsUuidPipe, LxLinkifyPipe, LxModalModule, LxPopoverUiModule, LxTabUiModule, LxTimeAgo, LxTranslatePipe, LxUnlinkifyPipe, MODAL_CLOSE, MODAL_MODULE_EXPORTS, MarkInvalidDirective, MarkdownPipe, MaxLengthCounterDirective, ModalCloseClickLocation, ModalComponent, ModalContentDirective, ModalFooterComponent, ModalHeaderComponent, MultiSelectComponent, NbspPipe, OptionComponent, OptionGroupComponent, OptionGroupDropdownComponent, OptionsDropdownComponent, OptionsSubDropdownComponent, PickerComponent, PickerOptionComponent, PickerTriggerDirective, PillItemComponent, PillListComponent, PopoverClickDirective, PopoverComponent, PopoverContentDirective, PopoverHoverDirective, RELEVANCE_SORTING_KEY, ResizeObserverService, ResponsiveInputComponent, SPACE, SelectDropdownDirective, SelectableItemDirective, SelectedOptionDirective, SingleSelectComponent, SkeletonComponent, SortPipe, Sorting, SortingDropdownComponent, SortingDropdownTriggerComponent, SpinnerComponent, StepperComponent, SwitchComponent, TAB, TabComponent, TabGroupComponent, TableComponent, TableHeaderComponent, TinySpinnerComponent, TokenComponent, TokenizerComponent, TokenizerOverflowPopoverComponent, TooltipComponent, TooltipDirective, TranslationAfterPipe, TranslationBeforePipe, TranslationBetweenPipe, UnescapeCurlyBracesPipe, ValidateDateInForeseeableFuture, ValidateStringNotInArray, ValidateStringNotInArrayAsync, getContrastColor, getInitialsUrl, getKeyboardNavigationEvents, getTranslationParts, highlightText, isValidHexColor, isValidX, isValidY, provideFormControlErrorDisplayStrategy, provideFormControlErrorNamespace, shorthandHexHandle, stopKeyboardEventPropagation };
11681
+ export { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, AVATAR_COLORS, AVATAR_SIZE_MAPPING, AfterViewInitDirective, AngularNodeViewComponent, AngularNodeViewRenderer, AutocloseDirective, AutocloseGroupService, AutofocusDirective, AvatarComponent, AvatarGroupComponent, BACKSPACE, BadgeComponent, BannerComponent, BaseSelectDirective, BasicDropdownComponent, BasicDropdownItemComponent, BrPipe, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CORE_MODULE_EXPORTS, CURRENCY_SYMBOL_MAP, CardComponent, CdkOptionsDropdownComponent, CdkOptionsSubDropdownComponent, CollapsibleComponent, ContenteditableDirective, ContrastColorPipe, CounterComponent, CurrencyInputComponent, CurrencySymbolComponent, CustomDatePipe, DATEPICKER_CONTROL_VALUE_ACCESSOR, DATE_FN_LOCALE, DATE_FORMATS, DEFAULT_IMAGE_ID, DateFormatter, DateInputComponent, DatePickerComponent, DatepickerConfig, DatepickerUiModule, DragAndDropListComponent, DragAndDropListItemComponent, END, ENTER, ESCAPE, EllipsisComponent, EmptyStateComponent, ErrorMessageComponent, FORMS_MODULE_EXPORTS, FORM_CONTROL_ERROR_DISPLAY_STRATEGY, FORM_CONTROL_ERROR_NAMESPACE, FilterSelectionPipe, FilterTermPipe, FocusEditorDirective, FormErrorComponent, FormErrorDirective, FormSubmitDirective, FormatNumberPipe, GLOBAL_TRANSLATION_OPTIONS, HOME, HighlightRangePipe, HighlightTermDirective, HighlightTermPipe, ICON_MAP, IMAGE_READER, IconComponent, IconScaleComponent, InputComponent, KeyboardActionSourceDirective, KeyboardSelectAction, KeyboardSelectDirective, LOCALE_FN, LX_BUTTON_USE_SAP_ICONS, LX_ELLIPSIS_DEBOUNCE_ON_RESIZE, LxCoreUiModule, LxDragAndDropListModule, LxFormsModule, LxIsUuidPipe, LxLinkifyPipe, LxModalModule, LxPopoverUiModule, LxTabUiModule, LxTimeAgo, LxTranslatePipe, LxUnlinkifyPipe, MODAL_CLOSE, MODAL_MODULE_EXPORTS, MarkInvalidDirective, MarkdownPipe, MaxLengthCounterDirective, ModalCloseClickLocation, ModalComponent, ModalContentDirective, ModalFooterComponent, ModalHeaderComponent, MultiSelectComponent, NbspPipe, OptionComponent, OptionGroupComponent, OptionGroupDropdownComponent, OptionsDropdownComponent, OptionsSubDropdownComponent, PickerComponent, PickerOptionComponent, PickerTriggerDirective, PillItemComponent, PillListComponent, PopoverClickDirective, PopoverComponent, PopoverContentDirective, PopoverHoverDirective, RELEVANCE_SORTING_KEY, RemoveMarkdownPipe, ResizeObserverService, ResponsiveInputComponent, RichTextEditorComponent, SPACE, SelectDropdownDirective, SelectableItemDirective, SelectedOptionDirective, SingleSelectComponent, SkeletonComponent, SortPipe, Sorting, SortingDropdownComponent, SortingDropdownTriggerComponent, SpinnerComponent, StepperComponent, SwitchComponent, TAB, TabComponent, TabGroupComponent, TableComponent, TableHeaderComponent, TinySpinnerComponent, TipTapEditorDirective, TokenComponent, TokenizerComponent, TokenizerOverflowPopoverComponent, TooltipComponent, TooltipDirective, TrackingDirective, TranslationAfterPipe, TranslationBeforePipe, TranslationBetweenPipe, TruncateDirective, UnescapeCurlyBracesPipe, ValidateDateInForeseeableFuture, ValidateStringNotInArray, ValidateStringNotInArrayAsync, getContrastColor, getInitialsUrl, getKeyboardNavigationEvents, getTranslationParts, highlightText, isValidHexColor, isValidX, isValidY, markdownToText, provideFormControlErrorDisplayStrategy, provideFormControlErrorNamespace, shorthandHexHandle, stopKeyboardEventPropagation };
10204
11682
  //# sourceMappingURL=leanix-components.mjs.map