@leanix/components 0.4.587 → 0.4.588

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 +1922 -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,1962 @@ 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: `
9737
+ @if (showToggleButton && activated) {
9738
+ <button lx-button color="primary" mode="link" (click)="onClick($event)">
9739
+ {{ NAME + key | translate }}
9740
+ </button>
9741
+ }
9742
+ `, isInline: true, 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 }); }
9743
+ }
9744
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateButtonComponent, decorators: [{
9745
+ type: Component,
9746
+ args: [{
9747
+ selector: 'lx-truncate-button',
9748
+ template: `
9749
+ @if (showToggleButton && activated) {
9750
+ <button lx-button color="primary" mode="link" (click)="onClick($event)">
9751
+ {{ NAME + key | translate }}
9752
+ </button>
9753
+ }
9754
+ `,
9755
+ changeDetection: ChangeDetectionStrategy.OnPush,
9756
+ imports: [ButtonComponent, TranslatePipe]
9757
+ }]
9758
+ }], propDecorators: { editor: [{
9759
+ type: Input,
9760
+ args: [{ required: true }]
9761
+ }] } });
9762
+
9763
+ const Truncate = Extension.create({
9764
+ name: 'truncate',
9765
+ addOptions() {
9766
+ return {
9767
+ maxLines: 1,
9768
+ showToggleButton: true
9769
+ };
9770
+ },
9771
+ addProseMirrorPlugins() {
9772
+ const { editor } = this;
9773
+ return [truncate(editor)];
9774
+ }
9775
+ });
9776
+
9777
+ class ExtensionsBuilder {
9778
+ constructor() {
9779
+ this.injector = inject(Injector);
9780
+ this.pluginRegistry = {
9781
+ Bold: [Bold],
9782
+ Truncate: [Truncate],
9783
+ Heading: [Heading.configure({ levels: [1, 2, 3, 4] })],
9784
+ Italic: [Italic],
9785
+ Underline: [Underline],
9786
+ Strike: [Strike],
9787
+ List: [BulletList, OrderedList],
9788
+ TextAlign: [TextAlign.configure({ types: ['heading', 'paragraph'] })],
9789
+ Code: [Code, CodeBlock],
9790
+ HighlightTerm: [HighlightTerm],
9791
+ Table: [CustomTable, CustomTableCell, CustomTableHeader, CustomTableRow],
9792
+ Link: [CustomLink]
9793
+ };
9794
+ }
9795
+ build(features, output) {
9796
+ const extensions = [Document, Dropcursor, Gapcursor, History, HardBreak, ListItem, Paragraph, Text, Tracking];
9797
+ if (output === 'markdown') {
9798
+ extensions.push(...[
9799
+ Markdown.configure({
9800
+ transformPastedText: true, // Allow to paste markdown text in the editor
9801
+ breaks: true
9802
+ })
9803
+ ]);
9804
+ }
9805
+ if (output === 'json') {
9806
+ extensions.push(...[HorizontalRule]);
9807
+ }
9808
+ for (const feature of features) {
9809
+ extensions.push(...(this.pluginRegistry[feature] ?? []));
9810
+ }
9811
+ return extensions;
9812
+ }
9813
+ buildCustomExtensions(extensionFactories) {
9814
+ const extensions = [];
9815
+ for (const extension of extensionFactories) {
9816
+ const node = extension(this.injector);
9817
+ if (node) {
9818
+ extensions.push(node);
9819
+ }
9820
+ }
9821
+ return extensions;
9822
+ }
9823
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionsBuilder, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
9824
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionsBuilder }); }
9825
+ }
9826
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionsBuilder, decorators: [{
9827
+ type: Injectable
9828
+ }] });
9829
+
9830
+ const EDITOR_FEATURES = {
9831
+ markdown: ['Bold', 'Italic', 'List', 'Truncate', 'Link', 'Strike', 'Underline', 'HighlightTerm'],
9832
+ json: ['Heading', 'Bold', 'Italic', 'Link', 'Underline', 'Strike', 'List', 'TextAlign', 'Code', 'Table']
9833
+ };
9834
+
9835
+ class TipTapEditorDirective {
9836
+ constructor() {
9837
+ this.NAME = 'TipTapEditorDirective';
9838
+ this.outputFormat = input.required();
9839
+ this.ariaLabelledBy = input(null);
9840
+ this.additionalFeatures = input([]);
9841
+ this.customExtensions = input([]);
9842
+ this.mode = input.required();
9843
+ this.features = computed(() => EDITOR_FEATURES[this.outputFormat()]);
9844
+ this.attributes = computed(() => {
9845
+ const attributes = {
9846
+ role: 'textbox' // make conenteditable div behave as textarea, needed for a11y
9847
+ };
9848
+ if (this.ariaLabelledBy()) {
9849
+ attributes['aria-labelledby'] = this.ariaLabelledBy();
9850
+ }
9851
+ return attributes;
9852
+ });
9853
+ this.editor = computed(() => {
9854
+ const extensions = [
9855
+ ...this.extensionsBuilder.build([...this.features(), ...this.additionalFeatures()], this.outputFormat()),
9856
+ ...this.extensionsBuilder.buildCustomExtensions(this.customExtensions())
9857
+ ];
9858
+ return new Editor({
9859
+ extensions,
9860
+ editorProps: {
9861
+ attributes: {
9862
+ ...this.attributes()
9863
+ }
9864
+ }
9865
+ });
9866
+ });
9867
+ this.extensionsBuilder = inject(ExtensionsBuilder);
9868
+ afterRenderEffect(() => {
9869
+ this.editor().storage['mode'] = this.mode();
9870
+ });
9871
+ }
9872
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TipTapEditorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
9873
+ 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 }); }
9874
+ }
9875
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TipTapEditorDirective, decorators: [{
9876
+ type: Directive,
9877
+ args: [{
9878
+ selector: 'lx-rich-text-editor[lxEditor]'
9879
+ }]
9880
+ }], ctorParameters: () => [] });
9881
+
9882
+ class TrackingDirective {
9883
+ constructor() {
9884
+ this.trackEvent = output();
9885
+ this.editor = inject(TipTapEditorDirective).editor;
9886
+ afterRenderEffect((onCleanup) => {
9887
+ this.editor().on('transaction', ({ transaction }) => {
9888
+ const meta = transaction.getMeta(trackingPluginKey);
9889
+ if (meta) {
9890
+ this.trackEvent.emit(meta);
9891
+ }
9892
+ onCleanup(() => {
9893
+ this.editor().off('transaction');
9894
+ });
9895
+ });
9896
+ });
9897
+ }
9898
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TrackingDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
9899
+ 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 }); }
9900
+ }
9901
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TrackingDirective, decorators: [{
9902
+ type: Directive,
9903
+ args: [{
9904
+ selector: 'lx-rich-text-editor[lxTracking]'
9905
+ }]
9906
+ }], ctorParameters: () => [] });
9907
+
9908
+ class EditorDirective {
9909
+ constructor(elRef, renderer, changeDetectorRef) {
9910
+ this.elRef = elRef;
9911
+ this.renderer = renderer;
9912
+ this.changeDetectorRef = changeDetectorRef;
9913
+ this.blur = new EventEmitter();
9914
+ this.onChange = () => {
9915
+ /** */
9916
+ };
9917
+ this.onTouched = () => {
9918
+ /** */
9919
+ };
9920
+ this.handleChange = ({ editor, transaction }) => {
9921
+ if (!transaction.docChanged) {
9922
+ return;
9923
+ }
9924
+ // Needed for ChangeDetectionStrategy.OnPush to get notified about changes
9925
+ this.changeDetectorRef.markForCheck();
9926
+ if (this.outputFormat === 'markdown') {
9927
+ this.onChange(editor.storage['markdown'].getMarkdown());
9928
+ return;
9929
+ }
9930
+ this.onChange(editor.getJSON());
9931
+ };
9932
+ }
9933
+ // Writes a new value to the element.
9934
+ // This methods is called when programmatic changes from model to view are requested.
9935
+ writeValue(value) {
9936
+ this.editor.chain().setContent(value, false).run();
9937
+ }
9938
+ // Registers a callback function that is called when the control's value changes in the UI.
9939
+ registerOnChange(fn) {
9940
+ this.onChange = fn;
9941
+ }
9942
+ // Registers a callback function that is called by the forms API on initialization to update the form model on blur.
9943
+ registerOnTouched(fn) {
9944
+ this.onTouched = fn;
9945
+ }
9946
+ // Called by the forms api to enable or disable the element
9947
+ setDisabledState(isDisabled) {
9948
+ this.editor.setEditable(!isDisabled);
9949
+ this.renderer.setProperty(this.elRef.nativeElement, 'disabled', isDisabled);
9950
+ }
9951
+ ngOnInit() {
9952
+ // take the inner contents and clear the block
9953
+ const { innerHTML } = this.elRef.nativeElement;
9954
+ this.elRef.nativeElement.innerHTML = '';
9955
+ // insert the editor in the dom
9956
+ this.elRef.nativeElement.append(...Array.from(this.editor.options.element.childNodes));
9957
+ // update the options for the editor
9958
+ this.editor.setOptions({ element: this.elRef.nativeElement });
9959
+ // update content to the editor
9960
+ if (innerHTML) {
9961
+ this.editor.chain().setContent(innerHTML, false).run();
9962
+ }
9963
+ // register blur handler to update `touched` property
9964
+ this.editor.on('blur', () => {
9965
+ this.blur.emit();
9966
+ this.onTouched();
9967
+ });
9968
+ // register update handler to listen to changes on update
9969
+ this.editor.on('update', this.handleChange);
9970
+ // Needed for ChangeDetectionStrategy.OnPush to get notified
9971
+ this.editor.on('selectionUpdate', () => this.changeDetectorRef.markForCheck());
9972
+ }
9973
+ ngAfterViewInit() {
9974
+ this.changeDetectorRef.detectChanges();
9975
+ }
9976
+ 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 }); }
9977
+ 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: [
9978
+ {
9979
+ provide: NG_VALUE_ACCESSOR,
9980
+ useExisting: forwardRef(() => EditorDirective),
9981
+ multi: true
9982
+ }
9983
+ ], ngImport: i0 }); }
9984
+ }
9985
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EditorDirective, decorators: [{
9986
+ type: Directive,
9987
+ args: [{
9988
+ // eslint-disable-next-line @angular-eslint/directive-selector
9989
+ selector: 'tiptap-editor[editor]',
9990
+ providers: [
9991
+ {
9992
+ provide: NG_VALUE_ACCESSOR,
9993
+ useExisting: forwardRef(() => EditorDirective),
9994
+ multi: true
9995
+ }
9996
+ ]
9997
+ }]
9998
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.ChangeDetectorRef }], propDecorators: { editor: [{
9999
+ type: Input,
10000
+ args: [{ required: true }]
10001
+ }], outputFormat: [{
10002
+ type: Input,
10003
+ args: [{ required: true }]
10004
+ }], blur: [{
10005
+ type: Output
10006
+ }] } });
10007
+
10008
+ class ExtensionEnabledPipe {
10009
+ transform(name, editor) {
10010
+ return !!editor.extensionManager.extensions.find((extension) => extension.name === name);
10011
+ }
10012
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionEnabledPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
10013
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: ExtensionEnabledPipe, isStandalone: true, name: "lxExtensionEnabled" }); }
10014
+ }
10015
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ExtensionEnabledPipe, decorators: [{
10016
+ type: Pipe,
10017
+ args: [{
10018
+ name: 'lxExtensionEnabled'
10019
+ }]
10020
+ }] });
10021
+
10022
+ class ModalFooterComponent {
10023
+ constructor() {
10024
+ this.border = false;
10025
+ }
10026
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10027
+ 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"] }); }
10028
+ }
10029
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalFooterComponent, decorators: [{
10030
+ type: Component,
10031
+ 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"] }]
10032
+ }], propDecorators: { border: [{
10033
+ type: Input
10034
+ }] } });
10035
+
10036
+ class ModalHeaderComponent {
10037
+ constructor() {
10038
+ this.title = '';
10039
+ this.bottomBorder = true;
10040
+ }
10041
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10042
+ 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"] }); }
10043
+ }
10044
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalHeaderComponent, decorators: [{
10045
+ type: Component,
10046
+ 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"] }]
10047
+ }], propDecorators: { title: [{
10048
+ type: Input
10049
+ }], bottomBorder: [{
10050
+ type: Input
10051
+ }] } });
10052
+
10053
+ class ModalContentDirective {
10054
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10055
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: ModalContentDirective, isStandalone: true, selector: "[lxModalContent]", ngImport: i0 }); }
10056
+ }
10057
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, decorators: [{
10058
+ type: Directive,
10059
+ args: [{
10060
+ selector: '[lxModalContent]'
10061
+ }]
10062
+ }] });
10063
+
10064
+ const MODAL_CLOSE = new InjectionToken('MODAL_CLOSE');
10065
+ /**
10066
+ * An enum to track how the modal was closed
10067
+ * Escape - Esc key press
10068
+ * CloseButton - top right close button (x)
10069
+ * OutsideClick - click outside the modal
10070
+ * Other - modal close with the trigger through closeModal$ subject
10071
+ */
10072
+ var ModalCloseClickLocation;
10073
+ (function (ModalCloseClickLocation) {
10074
+ ModalCloseClickLocation["Escape"] = "escape";
10075
+ ModalCloseClickLocation["CloseButton"] = "closeButton";
10076
+ ModalCloseClickLocation["OutsideClick"] = "outsideClick";
10077
+ ModalCloseClickLocation["CancelButton"] = "cancelButton";
10078
+ ModalCloseClickLocation["Other"] = "other";
10079
+ })(ModalCloseClickLocation || (ModalCloseClickLocation = {}));
10080
+
10081
+ /**
10082
+ * This documentation provides details on the usage and configuration of the Modal.
10083
+ *
10084
+ * ## Usage
10085
+ *
10086
+ * 1. Import `LxModalModule` and `LxCoreUiModule` modules from `@leanix/components` in your module where you want to use the component.
10087
+ *
10088
+ * ```ts
10089
+ * import { LxModalModule, LxCoreUiModule } from '@leanix/components';
10090
+ * ```
10091
+ *
10092
+ * 2. Use the **lx-modal** component in your template with the parameters described below.
10093
+ *
10094
+ * - **`open`**: Whether the modal is open or closed.
10095
+ * - **`size`**: 'dialog' | 'dialog-large'.
10096
+ * - **`verticalScroll`**: Whether the modal is scrollable or not.
10097
+ * - **`showHeader`**: Whether the modal has a header or not.
10098
+ * - **`showFooter`**: Whether the modal has a footer or not.
10099
+ * - **`showCloseButton`**: Whether to show the close button.
10100
+ * - **`showBackButton`**: Whether to show the back button.
10101
+ *
10102
+ * 3. Use optional **lx-modal-header** component in your template with the parameters described below.
10103
+ *
10104
+ * - **`title`**: Title of the modal.
10105
+ * - **`subtitle`**: Subtitle of the modal.
10106
+ * - **`bottomBorder`**: Whether to show a bottom border.
10107
+ *
10108
+ * 4. Use optional **lx-modal-footer** component in your template with the parameters described below.
10109
+ *
10110
+ * - **`border`**: Whether to show the footer at the bottom of the modal.
10111
+ *
10112
+ * **GLOBAL PROVIDERS** required for this component:
10113
+ * - `provideAnimations()`
10114
+ *
10115
+ * **ATTENTION - SCROLLABLE DIALOG**:
10116
+ * The <lx-modal> component when used as "dialog" is not designed to work with a
10117
+ * scrollable body (via `overflow: auto | scroll`) in combination with dropdowns.
10118
+ * The overflow on the body will also clip the dropdowns, which is expected.
10119
+ *
10120
+ * Reasoning:
10121
+ * The contents within the dialog should be just a few elements which fit and
10122
+ * justify the usage of a dialog. If the content is larger than the dialog, and thus
10123
+ * requires scrolling, we should discuss whether to put it into a dialog at all
10124
+ * and rather think about putting the content on a separate route or
10125
+ * using the fullscreen version of the modal.
10126
+ */
10127
+ class ModalComponent {
10128
+ get content() {
10129
+ return this.explicitContent || this.implicitContent;
10130
+ }
10131
+ /** @internal */
10132
+ onEscape() {
10133
+ if (this.open && this.showCloseButton) {
10134
+ this.closeModal(ModalCloseClickLocation.Escape);
10135
+ }
10136
+ }
10137
+ constructor(overlay, renderer, closeModal$, focusTrap) {
10138
+ this.overlay = overlay;
9031
10139
  this.renderer = renderer;
9032
- this.ngControl = ngControl;
9033
- this.lxMaxLengthCounter = null;
9034
- this.counterElement = null;
9035
- this.destroy$ = new Subject();
10140
+ this.closeModal$ = closeModal$;
10141
+ this.focusTrap = focusTrap;
10142
+ /** @internal */
10143
+ this.NAME = 'ModalComponent';
10144
+ /** Whether the modal is open or closed. */
10145
+ this.open = false;
10146
+ /** Whether to show the close button. */
10147
+ this.showCloseButton = true;
10148
+ /** Whether to show the back button. */
10149
+ this.showBackButton = false;
10150
+ /*
10151
+ * If true, then the content area scrolls vertically instead of expanding its height.
10152
+ * 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.
10153
+ */
10154
+ this.verticalScroll = false;
10155
+ /** The size of the modal. */
10156
+ this.size = 'fullscreen';
10157
+ /**
10158
+ * Minimum width of the modal.
10159
+ *
10160
+ * _NB: Some modal implementations rely on this minWidth being 600px_
10161
+ */
10162
+ this.minWidth = '600px';
10163
+ /** Whether the modal is a focus trap. */
10164
+ this.isFocusTrap = false;
10165
+ /** Event emitted when the modal is closed. */
10166
+ this.close = new EventEmitter();
10167
+ /** Event emitted when the back button is clicked. */
10168
+ this.back = new EventEmitter();
10169
+ /** @internal */
10170
+ this.closeLocation = ModalCloseClickLocation;
10171
+ /** @internal */
10172
+ this.destroyed$ = new Subject();
10173
+ }
10174
+ ngOnInit() {
10175
+ this.closeModal$
10176
+ ?.pipe(takeUntil(this.destroyed$))
10177
+ .subscribe((closeLocation) => this.closeModal(closeLocation));
10178
+ if (this.size === 'fullscreen') {
10179
+ this.overlayRef = this.overlay.create({
10180
+ panelClass: this.size,
10181
+ width: '100%',
10182
+ height: '100vh'
10183
+ });
10184
+ }
10185
+ else if (this.size === 'dialog-large') {
10186
+ const positionStrategy = this.overlay.position().global().top('4vh').centerHorizontally();
10187
+ this.overlayRef = this.overlay.create({
10188
+ panelClass: this.size,
10189
+ positionStrategy,
10190
+ hasBackdrop: true,
10191
+ width: '90%',
10192
+ height: '90vh'
10193
+ });
10194
+ }
10195
+ else {
10196
+ // size 'dialog'
10197
+ const positionStrategy = this.overlay.position().global().top('8vh').centerHorizontally();
10198
+ this.overlayRef = this.overlay.create({
10199
+ panelClass: this.size,
10200
+ minWidth: this.minWidth,
10201
+ positionStrategy,
10202
+ hasBackdrop: true,
10203
+ scrollStrategy: this.overlay.scrollStrategies.block()
10204
+ });
10205
+ }
10206
+ if (this.size !== 'fullscreen') {
10207
+ this.overlayRef
10208
+ .backdropClick()
10209
+ .pipe(takeUntil(this.destroyed$))
10210
+ .subscribe(() => this.closeModal(ModalCloseClickLocation.OutsideClick));
10211
+ }
10212
+ }
10213
+ ngOnChanges() {
10214
+ if (this.open && this.overlayRef && !this.overlayRef.hasAttached()) {
10215
+ this.openModal();
10216
+ }
10217
+ if (!this.open && this.overlayRef && this.overlayRef.hasAttached()) {
10218
+ this.closeModal(ModalCloseClickLocation.Other);
10219
+ }
10220
+ if (this.open && this.overlayRef && this.overlayRef.hasAttached() && this.isFocusTrap) {
10221
+ this.trapFocusInModal(this.overlayRef.hostElement);
10222
+ }
10223
+ }
10224
+ ngAfterViewInit() {
10225
+ if (this.open) {
10226
+ timer(0)
10227
+ .pipe(takeUntil(this.destroyed$))
10228
+ .subscribe(() => this.openModal());
10229
+ }
10230
+ }
10231
+ /** @internal */
10232
+ openModal() {
10233
+ this.oldOverflow = document.documentElement.style.overflowY;
10234
+ if (this.size === 'fullscreen') {
10235
+ this.renderer.setStyle(document.documentElement, 'overflowY', 'hidden');
10236
+ }
10237
+ this.overlayRef.attach(this.cdkPortal);
10238
+ this.trapFocusInModal(this.overlayRef.hostElement);
10239
+ }
10240
+ /** @internal */
10241
+ emitBack() {
10242
+ this.back.emit();
10243
+ }
10244
+ ngOnDestroy() {
10245
+ this.destroyed$.next();
10246
+ if (this.size === 'fullscreen') {
10247
+ this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
10248
+ }
10249
+ if (this.overlayRef) {
10250
+ this.overlayRef.dispose();
10251
+ }
10252
+ }
10253
+ /** @internal */
10254
+ async closeModal(closeLocation) {
10255
+ if (!this.canModalBeClosed || (await this.canModalBeClosed(closeLocation))) {
10256
+ this.open = false;
10257
+ this.overlayRef.detach();
10258
+ if (this.size === 'fullscreen') {
10259
+ this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
10260
+ }
10261
+ this.close.emit(typeof closeLocation === 'boolean' ? ModalCloseClickLocation.Other : closeLocation);
10262
+ }
10263
+ }
10264
+ trapFocusInModal(hostElement) {
10265
+ this.focusTrap.create(hostElement);
10266
+ }
10267
+ 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 }); }
10268
+ 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: [
10269
+ trigger('modal', [
10270
+ transition(':enter', [style({ opacity: 0 }), animate('0.15s', style({ opacity: 1 }))]),
10271
+ transition(':leave', animate('0.15s', style({ opacity: 0 })))
10272
+ ])
10273
+ ] }); }
10274
+ }
10275
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalComponent, decorators: [{
10276
+ type: Component,
10277
+ args: [{ selector: 'lx-modal', animations: [
10278
+ trigger('modal', [
10279
+ transition(':enter', [style({ opacity: 0 }), animate('0.15s', style({ opacity: 1 }))]),
10280
+ transition(':leave', animate('0.15s', style({ opacity: 0 })))
10281
+ ])
10282
+ ], 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"] }]
10283
+ }], ctorParameters: () => [{ type: i1$2.Overlay }, { type: i0.Renderer2 }, { type: i5.Observable, decorators: [{
10284
+ type: Optional
10285
+ }, {
10286
+ type: Inject,
10287
+ args: [MODAL_CLOSE]
10288
+ }] }, { type: i2.ConfigurableFocusTrapFactory }], propDecorators: { open: [{
10289
+ type: Input
10290
+ }], showCloseButton: [{
10291
+ type: Input
10292
+ }], showBackButton: [{
10293
+ type: Input
10294
+ }], verticalScroll: [{
10295
+ type: Input
10296
+ }], size: [{
10297
+ type: Input
10298
+ }], minWidth: [{
10299
+ type: Input
10300
+ }], isFocusTrap: [{
10301
+ type: Input
10302
+ }], canModalBeClosed: [{
10303
+ type: Input
10304
+ }], close: [{
10305
+ type: Output
10306
+ }], back: [{
10307
+ type: Output
10308
+ }], header: [{
10309
+ type: ContentChild,
10310
+ args: [ModalHeaderComponent]
10311
+ }], footer: [{
10312
+ type: ContentChild,
10313
+ args: [ModalFooterComponent]
10314
+ }], cdkPortal: [{
10315
+ type: ViewChild,
10316
+ args: [CdkPortal, { static: true }]
10317
+ }], implicitContent: [{
10318
+ type: ViewChild,
10319
+ args: ['implicitContent', { static: true }]
10320
+ }], explicitContent: [{
10321
+ type: ContentChild,
10322
+ args: [ModalContentDirective, { read: TemplateRef, static: true }]
10323
+ }], onEscape: [{
10324
+ type: HostListener,
10325
+ args: ['document:keydown.escape']
10326
+ }] } });
10327
+
10328
+ class UrlValidatorDirective {
10329
+ validate(control) {
10330
+ const urlPattern = /^(https?:\/\/)?([\w-]+\.)*[\w-]+\.[a-z]{2,}(\/[^\s]*)?$/i;
10331
+ return urlPattern.test(control.value || '') ? null : { invalidUrl: true };
9036
10332
  }
9037
- ngOnInit() {
9038
- this.setupMaxLengthValidation();
9039
- this.connectCounter();
9040
- this.updateCounter();
9041
- this.subscribeToValueChanges();
9042
- this.setupInputRestriction();
9043
- this.checkForInitialOversize();
10333
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UrlValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10334
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: UrlValidatorDirective, isStandalone: true, selector: "[lxUrl][ngModel]", providers: [
10335
+ {
10336
+ provide: NG_VALIDATORS,
10337
+ useExisting: UrlValidatorDirective,
10338
+ multi: true
10339
+ }
10340
+ ], ngImport: i0 }); }
10341
+ }
10342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UrlValidatorDirective, decorators: [{
10343
+ type: Directive,
10344
+ args: [{
10345
+ selector: '[lxUrl][ngModel]',
10346
+ providers: [
10347
+ {
10348
+ provide: NG_VALIDATORS,
10349
+ useExisting: UrlValidatorDirective,
10350
+ multi: true
10351
+ }
10352
+ ]
10353
+ }]
10354
+ }] });
10355
+
10356
+ class LinkModalComponent {
10357
+ constructor() {
10358
+ this.NAME = 'LinkModalComponent';
10359
+ this.ngForm = viewChild.required(NgForm);
10360
+ this.cd = inject(ChangeDetectorRef);
9044
10361
  }
9045
- ngOnDestroy() {
9046
- this.destroy$.next();
9047
- this.destroy$.complete();
10362
+ get open() {
10363
+ return linkPluginKey.getState(this.editor.state).open;
9048
10364
  }
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
- }
10365
+ get text() {
10366
+ return linkPluginKey.getState(this.editor.state).text;
9057
10367
  }
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;
10368
+ get url() {
10369
+ return linkPluginKey.getState(this.editor.state).url;
9071
10370
  }
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
- }
10371
+ removeLink() {
10372
+ const editorChain = this.editor.chain().focus().extendMarkRange('link');
10373
+ editorChain.focus().extendMarkRange('link').unsetLink().run();
10374
+ this.closeModal();
9080
10375
  }
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);
10376
+ saveLink() {
10377
+ if (!this.ngForm().valid) {
10378
+ this.ngForm().form.markAllAsTouched();
10379
+ return;
10380
+ }
10381
+ const formData = this.ngForm().value;
10382
+ const href = ensureHttpProtocol(formData.url);
10383
+ this.editor.chain().focus().extendMarkRange('link').run(); // Ensure that selection is expanded to the whole link mark
10384
+ const { from, to, empty } = this.editor.state.selection;
10385
+ const tr = this.editor.state.tr;
10386
+ const linkMark = this.editor.schema.marks['link'].create({ href });
10387
+ if (empty) {
10388
+ tr.insertText(formData.text, from);
9087
10389
  }
9088
10390
  else {
9089
- this.renderer.appendChild(this.el.nativeElement.parentNode, this.counterElement);
10391
+ tr.insertText(formData.text, from, to);
9090
10392
  }
9091
- this.destroy$.subscribe(() => {
9092
- this.renderer.removeChild(this.el.nativeElement.parentNode, this.counterElement);
9093
- });
10393
+ tr.addMark(from, from + formData.text.length, linkMark);
10394
+ this.editor.view.dispatch(tr);
10395
+ this.closeModal();
9094
10396
  }
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
- }
10397
+ closeModal() {
10398
+ dispatchLinkState(this.editor, { open: false, url: null, text: null });
9101
10399
  }
9102
- subscribeToValueChanges() {
9103
- const control = this.ngControl.control;
9104
- if (control) {
9105
- control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
9106
- this.updateCounter();
9107
- });
9108
- }
10400
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: LinkModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10401
+ 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 }); }
10402
+ }
10403
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: LinkModalComponent, decorators: [{
10404
+ type: Component,
10405
+ args: [{ selector: 'lx-link-modal', imports: [
10406
+ ModalComponent,
10407
+ ModalHeaderComponent,
10408
+ ModalFooterComponent,
10409
+ TranslateModule,
10410
+ FormsModule,
10411
+ ButtonComponent,
10412
+ UrlValidatorDirective,
10413
+ ErrorMessageComponent,
10414
+ MarkInvalidDirective,
10415
+ AutofocusDirective
10416
+ ], 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"] }]
10417
+ }], propDecorators: { editor: [{
10418
+ type: Input,
10419
+ args: [{ required: true }]
10420
+ }] } });
10421
+ const ensureHttpProtocol = (url) => {
10422
+ if (!url)
10423
+ return '';
10424
+ return url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`;
10425
+ };
10426
+
10427
+ class RichTextEditorToolbarComponent {
10428
+ constructor() {
10429
+ this.NAME = 'RichTextEditorToolbarComponent';
10430
+ this.headingLevels = [];
10431
+ this.cd = inject(ChangeDetectorRef);
10432
+ this.isLinkModalOpen$ = new BehaviorSubject(false);
10433
+ this.detectChanges = () => {
10434
+ this.cd.markForCheck();
10435
+ };
10436
+ this.update = (event) => {
10437
+ if (event.transaction.getMeta(linkPluginKey)) {
10438
+ this.isLinkModalOpen$.next(linkPluginKey.getState(event.editor.state).open);
10439
+ }
10440
+ };
9109
10441
  }
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
- }
10442
+ ngOnInit() {
10443
+ this.headingLevels = this.editor.extensionManager.extensions.find((ext) => ext.name === 'heading')?.options?.levels || [];
10444
+ this.editor.on('selectionUpdate', this.detectChanges);
10445
+ this.editor.on('transaction', this.update);
9127
10446
  }
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
- }
10447
+ ngOnDestroy() {
10448
+ this.editor.off('selectionUpdate', this.detectChanges);
10449
+ this.editor.off('transaction', this.update);
9135
10450
  }
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 }); }
10451
+ insertTable() {
10452
+ this.editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: false }).track('INSERT_TABLE').run();
10453
+ }
10454
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10455
+ 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
10456
  }
9139
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MaxLengthCounterDirective, decorators: [{
10457
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorToolbarComponent, decorators: [{
10458
+ type: Component,
10459
+ args: [{ selector: 'lx-rich-text-editor-toolbar', imports: [
10460
+ AsyncPipe,
10461
+ OptionsDropdownComponent,
10462
+ ButtonComponent,
10463
+ KeyboardActionSourceDirective,
10464
+ OptionComponent,
10465
+ ExtensionEnabledPipe,
10466
+ TranslatePipe,
10467
+ LinkModalComponent
10468
+ ], 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"] }]
10469
+ }], propDecorators: { editor: [{
10470
+ type: Input,
10471
+ args: [{ required: true }]
10472
+ }] } });
10473
+
10474
+ class RichTextEditorComponent {
10475
+ constructor() {
10476
+ this.mode = input.required();
10477
+ this.outputFormat = input.required();
10478
+ this.additionalFeatures = input([]);
10479
+ this.ariaLabelledBy = input(null);
10480
+ this.maxHeight = input(null);
10481
+ this.customExtensions = input([]);
10482
+ this.blur = output();
10483
+ this.value = null;
10484
+ this.disabled = false;
10485
+ this.editor = inject(TipTapEditorDirective).editor;
10486
+ this.onChange = (_value) => { };
10487
+ this.onTouched = () => { };
10488
+ }
10489
+ ngOnDestroy() {
10490
+ this.editor().destroy();
10491
+ }
10492
+ writeValue(value) {
10493
+ this.value = value;
10494
+ }
10495
+ registerOnChange(fn) {
10496
+ this.onChange = fn;
10497
+ }
10498
+ registerOnTouched(fn) {
10499
+ this.onTouched = fn;
10500
+ }
10501
+ setDisabledState(isDisabled) {
10502
+ this.disabled = isDisabled;
10503
+ }
10504
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10505
+ 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: [
10506
+ ExtensionsBuilder,
10507
+ {
10508
+ provide: NG_VALUE_ACCESSOR,
10509
+ useExisting: forwardRef(() => RichTextEditorComponent),
10510
+ multi: true
10511
+ }
10512
+ ], 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 }); }
10513
+ }
10514
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RichTextEditorComponent, decorators: [{
10515
+ type: Component,
10516
+ args: [{ selector: 'lx-rich-text-editor', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
10517
+ EditorDirective,
10518
+ FormsModule,
10519
+ RichTextEditorToolbarComponent,
10520
+ TableBubbleMenuComponent,
10521
+ ExtensionEnabledPipe,
10522
+ TruncateButtonComponent
10523
+ ], hostDirectives: [
10524
+ {
10525
+ directive: TipTapEditorDirective,
10526
+ inputs: ['outputFormat', 'additionalFeatures', 'ariaLabelledBy', 'customExtensions', 'mode']
10527
+ },
10528
+ {
10529
+ directive: TrackingDirective,
10530
+ outputs: ['trackEvent']
10531
+ }
10532
+ ], providers: [
10533
+ ExtensionsBuilder,
10534
+ {
10535
+ provide: NG_VALUE_ACCESSOR,
10536
+ useExisting: forwardRef(() => RichTextEditorComponent),
10537
+ multi: true
10538
+ }
10539
+ ], host: {
10540
+ '[style.--editor-max-height]': 'maxHeight()'
10541
+ }, 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"] }]
10542
+ }] });
10543
+
10544
+ class FocusEditorDirective {
10545
+ constructor() {
10546
+ this.editor = inject(TipTapEditorDirective).editor;
10547
+ }
10548
+ ngOnInit() {
10549
+ this.editor().on('create', () => {
10550
+ this.editor().commands.focus('start');
10551
+ });
10552
+ }
10553
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusEditorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10554
+ 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 }); }
10555
+ }
10556
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FocusEditorDirective, decorators: [{
9140
10557
  type: Directive,
9141
10558
  args: [{
9142
- selector: '[lxMaxLengthCounter]'
10559
+ selector: 'lx-rich-text-editor[lxFocusEditor]'
9143
10560
  }]
9144
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1$5.NgControl, decorators: [{
9145
- type: Self
9146
- }] }], propDecorators: { lxMaxLengthCounter: [{
10561
+ }] });
10562
+
10563
+ class HighlightTermDirective {
10564
+ constructor() {
10565
+ this.lxHighlightTerm = input();
10566
+ this.editor = inject(TipTapEditorDirective).editor;
10567
+ effect(() => {
10568
+ this.editor().view.dispatch(this.editor().view.state.tr.setMeta(highlightTermStatePluginKey, {
10569
+ term: this.lxHighlightTerm()
10570
+ }));
10571
+ });
10572
+ }
10573
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HighlightTermDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10574
+ 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 }); }
10575
+ }
10576
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HighlightTermDirective, decorators: [{
10577
+ type: Directive,
10578
+ args: [{
10579
+ selector: 'lx-rich-text-editor[lxHighlightTerm]'
10580
+ }]
10581
+ }], ctorParameters: () => [] });
10582
+
10583
+ class TruncateDirective {
10584
+ constructor() {
10585
+ this.lxTruncate = input();
10586
+ this.editor = inject(TipTapEditorDirective).editor;
10587
+ effect(() => {
10588
+ const options = this.lxTruncate();
10589
+ if (options) {
10590
+ this.editor().view.dispatch(this.editor().view.state.tr.setMeta(truncatePluginKey, options));
10591
+ }
10592
+ });
10593
+ }
10594
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
10595
+ 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 }); }
10596
+ }
10597
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TruncateDirective, decorators: [{
10598
+ type: Directive,
10599
+ args: [{
10600
+ selector: 'lx-rich-text-editor[lxTruncate]'
10601
+ }]
10602
+ }], ctorParameters: () => [] });
10603
+
10604
+ // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
10605
+ class AngularNodeViewComponent {
10606
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: AngularNodeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10607
+ 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 }); }
10608
+ }
10609
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: AngularNodeViewComponent, decorators: [{
10610
+ type: Component,
10611
+ args: [{
10612
+ template: '',
10613
+ // eslint-disable-next-line @angular-eslint/prefer-standalone
10614
+ standalone: false
10615
+ }]
10616
+ }], propDecorators: { editor: [{
9147
10617
  type: Input
9148
- }], lxMaxLengthCounterRef: [{
10618
+ }], node: [{
10619
+ type: Input
10620
+ }], decorations: [{
10621
+ type: Input
10622
+ }], selected: [{
10623
+ type: Input
10624
+ }], extension: [{
10625
+ type: Input
10626
+ }], getPos: [{
10627
+ type: Input
10628
+ }], updateAttributes: [{
10629
+ type: Input
10630
+ }], deleteNode: [{
9149
10631
  type: Input
9150
10632
  }] } });
9151
-
9152
- class Sorting {
9153
- constructor() {
9154
- this.key = '';
9155
- this.order = 'asc';
10633
+
10634
+ class AngularRenderer {
10635
+ constructor(ViewComponent, injector, props) {
10636
+ this.applicationRef = injector.get(ApplicationRef);
10637
+ this.componentRef = createComponent(ViewComponent, {
10638
+ environmentInjector: this.applicationRef.injector,
10639
+ elementInjector: injector
10640
+ });
10641
+ // set input props to the component
10642
+ this.updateProps(props);
10643
+ this.applicationRef.attachView(this.componentRef.hostView);
10644
+ }
10645
+ get instance() {
10646
+ return this.componentRef.instance;
10647
+ }
10648
+ get elementRef() {
10649
+ return this.componentRef.injector.get(ElementRef);
10650
+ }
10651
+ get dom() {
10652
+ return this.elementRef.nativeElement;
10653
+ }
10654
+ updateProps(props) {
10655
+ Object.entries(props).forEach(([key, value]) => {
10656
+ this.componentRef.setInput(key, value);
10657
+ });
10658
+ }
10659
+ detectChanges() {
10660
+ this.componentRef.changeDetectorRef.detectChanges();
10661
+ }
10662
+ destroy() {
10663
+ this.componentRef.destroy();
10664
+ this.applicationRef.detachView(this.componentRef.hostView);
9156
10665
  }
9157
10666
  }
9158
10667
 
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
- }
10668
+ class AngularNodeView extends NodeView {
10669
+ mount() {
10670
+ const injector = this.options.injector;
10671
+ const props = {
10672
+ editor: this.editor,
10673
+ node: this.node,
10674
+ decorations: this.decorations,
10675
+ selected: false,
10676
+ extension: this.extension,
10677
+ getPos: () => this.getPos(),
10678
+ updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
10679
+ deleteNode: () => this.deleteNode()
10680
+ };
10681
+ this.handleSelectionUpdate = this.handleSelectionUpdate.bind(this);
10682
+ this.editor.on('selectionUpdate', this.handleSelectionUpdate);
10683
+ // create renderer
10684
+ this.renderer = new AngularRenderer(this.component, injector, props);
10685
+ // Register drag handler
10686
+ if (this.extension.config.draggable) {
10687
+ this.renderer.elementRef.nativeElement.ondragstart = (e) => {
10688
+ this.onDragStart(e);
9175
10689
  };
9176
10690
  }
9177
- else {
9178
- return null;
10691
+ this.contentDOMElement = this.node.isLeaf ? null : document.createElement(this.node.isInline ? 'span' : 'div');
10692
+ if (this.contentDOMElement) {
10693
+ // For some reason the whiteSpace prop is not inherited properly in Chrome and Safari
10694
+ // With this fix it seems to work fine
10695
+ // See: https://github.com/ueberdosis/tiptap/issues/1197
10696
+ this.contentDOMElement.style.whiteSpace = 'inherit';
10697
+ // Required for editable node views
10698
+ // The content won't be rendered if `editable` is set to `false`
10699
+ this.renderer.detectChanges();
9179
10700
  }
10701
+ this.appendContendDom();
9180
10702
  }
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) {
10703
+ get dom() {
10704
+ return this.renderer.dom;
10705
+ }
10706
+ get contentDOM() {
10707
+ if (this.node.isLeaf) {
9194
10708
  return null;
9195
10709
  }
9196
- if (stringIsInArray(array, control.value, valueMapFunction)) {
9197
- return {
9198
- [validatorName]: true
9199
- };
10710
+ return this.contentDOMElement;
10711
+ }
10712
+ appendContendDom() {
10713
+ const contentElement = this.dom.querySelector('[data-node-view-content]');
10714
+ if (this.contentDOMElement && contentElement && !contentElement.contains(this.contentDOMElement)) {
10715
+ contentElement.appendChild(this.contentDOMElement);
9200
10716
  }
9201
- return null;
9202
- };
9203
- }
9204
- function ValidateStringNotInArrayAsync(array$, valueMapFunction, validatorName = 'stringNotInArrayAsync') {
9205
- return function (control) {
9206
- if (!control.value) {
9207
- return of(null);
10717
+ }
10718
+ handleSelectionUpdate() {
10719
+ const { from, to } = this.editor.state.selection;
10720
+ if (from <= this.getPos() && to >= this.getPos() + this.node.nodeSize) {
10721
+ this.selectNode();
9208
10722
  }
9209
- return array$.pipe(map$1((array) => ValidateStringNotInArray(array, valueMapFunction, validatorName)(control)));
9210
- };
10723
+ else {
10724
+ this.deselectNode();
10725
+ }
10726
+ }
10727
+ update(node, decorations) {
10728
+ const updateProps = () => {
10729
+ this.renderer.updateProps({ node, decorations });
10730
+ };
10731
+ if (this.options.update) {
10732
+ const oldNode = this.node;
10733
+ const oldDecorations = this.decorations;
10734
+ this.node = node;
10735
+ this.decorations = decorations;
10736
+ return this.options.update({
10737
+ oldNode,
10738
+ oldDecorations,
10739
+ newNode: node,
10740
+ newDecorations: decorations,
10741
+ updateProps: () => updateProps()
10742
+ });
10743
+ }
10744
+ if (node.type !== this.node.type) {
10745
+ return false;
10746
+ }
10747
+ if (node === this.node && this.decorations === decorations) {
10748
+ return true;
10749
+ }
10750
+ this.node = node;
10751
+ this.decorations = decorations;
10752
+ updateProps();
10753
+ return true;
10754
+ }
10755
+ selectNode() {
10756
+ this.renderer.updateProps({ selected: true });
10757
+ }
10758
+ deselectNode() {
10759
+ this.renderer.updateProps({ selected: false });
10760
+ }
10761
+ destroy() {
10762
+ this.renderer.destroy();
10763
+ this.editor.off('selectionUpdate', this.handleSelectionUpdate);
10764
+ this.contentDOMElement = null;
10765
+ }
9211
10766
  }
10767
+ const AngularNodeViewRenderer = (ViewComponent, options) => {
10768
+ return (props) => {
10769
+ return new AngularNodeView(ViewComponent, props, options);
10770
+ };
10771
+ };
9212
10772
 
9213
- const MODAL_CLOSE = new InjectionToken('MODAL_CLOSE');
9214
10773
  /**
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
10774
+ * Removes markdown syntax from text.
10775
+ * e.g '### This is a heading' will become 'This is a heading'
10776
+ *
10777
+ * It does not remove links. Links can be post processed by LxLinkifyPipe or LxUnLinkifyPipe
9220
10778
  */
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;
10779
+ class RemoveMarkdownPipe {
10780
+ transform(markdown) {
10781
+ return markdownToText(markdown);
9248
10782
  }
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 }); }
10783
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RemoveMarkdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
10784
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: RemoveMarkdownPipe, isStandalone: true, name: "lxRemoveMarkdown" }); }
9264
10785
  }
9265
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ModalContentDirective, decorators: [{
9266
- type: Directive,
10786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RemoveMarkdownPipe, decorators: [{
10787
+ type: Pipe,
9267
10788
  args: [{
9268
- selector: '[lxModalContent]'
10789
+ name: 'lxRemoveMarkdown'
9269
10790
  }]
9270
10791
  }] });
10792
+ function markdownToText(md) {
10793
+ let output = md || '';
10794
+ try {
10795
+ output = output
10796
+ // Remove horizontal rules (stripListHeaders conflict with this rule, which is why it has been moved to the top)
10797
+ .replace(/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/gm, '')
10798
+ // stripListLeaders
10799
+ .replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
10800
+ // Strikethrough
10801
+ .replace(/~~/g, '')
10802
+ // Fenced codeblocks with backticks
10803
+ .replace(/```(?:.*)\n([\s\S]*?)```/g, (_, code) => code.trim())
10804
+ // Remove HTML tags
10805
+ .replace(/<[^>]*>/g, '')
10806
+ // Remove images
10807
+ .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
10808
+ // Remove blockquotes
10809
+ .replace(/^(\n)?\s{0,3}>\s?/gm, '$1')
10810
+ // Remove atx-style headers
10811
+ .replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, '$1$3$4$6')
10812
+ // Remove * emphasis
10813
+ .replace(/([\*]+)(\S)(.*?\S)??\1/g, '$2$3')
10814
+ // Remove _ emphasis. Unlike *, _ emphasis gets rendered only if
10815
+ // 1. Either there is a whitespace character before opening _ and after closing _.
10816
+ // 2. Or _ is at the start/end of the string.
10817
+ .replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, '$1$3$4$5')
10818
+ // Remove single-line code blocks (already handled multiline above )
10819
+ .replace(/(`{3,})(.*?)\1/gm, '$2')
10820
+ // Remove inline code
10821
+ .replace(/`(.+?)`/g, '$1');
10822
+ }
10823
+ catch (e) {
10824
+ return md;
10825
+ }
10826
+ return output;
10827
+ }
9271
10828
 
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;
10829
+ class MaxLengthCounterDirective {
10830
+ constructor(el, renderer, ngControl) {
10831
+ this.el = el;
9330
10832
  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();
10833
+ this.ngControl = ngControl;
10834
+ this.lxMaxLengthCounter = null;
10835
+ this.counterElement = null;
10836
+ this.destroy$ = new Subject();
9364
10837
  }
9365
10838
  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
- });
10839
+ this.setupMaxLengthValidation();
10840
+ this.connectCounter();
10841
+ this.updateCounter();
10842
+ this.subscribeToValueChanges();
10843
+ this.setupInputRestriction();
10844
+ this.checkForInitialOversize();
10845
+ }
10846
+ ngOnDestroy() {
10847
+ this.destroy$.next();
10848
+ this.destroy$.complete();
10849
+ }
10850
+ setupMaxLengthValidation() {
10851
+ const maxLength = this.getMaxLength();
10852
+ const control = this.ngControl.control;
10853
+ if (maxLength !== null && control) {
10854
+ const validators = control.validator ? [control.validator, Validators.maxLength(maxLength)] : Validators.maxLength(maxLength);
10855
+ control.setValidators(validators);
10856
+ control.updateValueAndValidity();
9385
10857
  }
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
- });
10858
+ }
10859
+ getMaxLength() {
10860
+ if (this.lxMaxLengthCounter) {
10861
+ return this.lxMaxLengthCounter;
9396
10862
  }
9397
- if (this.size !== 'fullscreen') {
9398
- this.overlayRef
9399
- .backdropClick()
9400
- .pipe(takeUntil(this.destroyed$))
9401
- .subscribe(() => this.closeModal(ModalCloseClickLocation.OutsideClick));
10863
+ const control = this.ngControl.control;
10864
+ const validatorFn = control?.validator;
10865
+ const errors = validatorFn?.(new FormControl({ length: Infinity }));
10866
+ const requiredLength = errors?.['maxlength']['requiredLength'];
10867
+ this.lxMaxLengthCounter = requiredLength ? requiredLength.toString() : null;
10868
+ if (this.lxMaxLengthCounter === null) {
10869
+ console.warn('lxMaxLength directive is used without a value or a control with a maxLength validator');
9402
10870
  }
10871
+ return requiredLength;
9403
10872
  }
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);
10873
+ connectCounter() {
10874
+ this.el.nativeElement.classList.add('lx-max-length-counter-input');
10875
+ if (this.lxMaxLengthCounterRef) {
10876
+ this.counterElement = this.lxMaxLengthCounterRef;
9410
10877
  }
9411
- if (this.open && this.overlayRef && this.overlayRef.hasAttached() && this.isFocusTrap) {
9412
- this.trapFocusInModal(this.overlayRef.hostElement);
10878
+ else {
10879
+ this.createCounter();
9413
10880
  }
9414
10881
  }
9415
- ngAfterViewInit() {
9416
- if (this.open) {
9417
- timer(0)
9418
- .pipe(takeUntil(this.destroyed$))
9419
- .subscribe(() => this.openModal());
10882
+ createCounter() {
10883
+ this.counterElement = this.renderer.createElement('span');
10884
+ this.renderer.addClass(this.counterElement, 'lx-max-length-counter');
10885
+ const nextSibling = this.el.nativeElement.nextSibling;
10886
+ if (nextSibling) {
10887
+ this.renderer.insertBefore(this.el.nativeElement.parentNode, this.counterElement, nextSibling);
9420
10888
  }
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');
10889
+ else {
10890
+ this.renderer.appendChild(this.el.nativeElement.parentNode, this.counterElement);
9427
10891
  }
9428
- this.overlayRef.attach(this.cdkPortal);
9429
- this.trapFocusInModal(this.overlayRef.hostElement);
9430
- }
9431
- /** @internal */
9432
- emitBack() {
9433
- this.back.emit();
10892
+ this.destroy$.subscribe(() => {
10893
+ this.renderer.removeChild(this.el.nativeElement.parentNode, this.counterElement);
10894
+ });
9434
10895
  }
9435
- ngOnDestroy() {
9436
- this.destroyed$.next();
9437
- if (this.size === 'fullscreen') {
9438
- this.renderer.setStyle(document.documentElement, 'overflowY', this.oldOverflow);
10896
+ updateCounter() {
10897
+ if (this.counterElement) {
10898
+ const currentLength = this.ngControl.value ? this.ngControl.value.length : 0;
10899
+ const maxLength = this.getMaxLength();
10900
+ this.renderer.setProperty(this.counterElement, 'textContent', `${currentLength} / ${maxLength}`);
9439
10901
  }
9440
- if (this.overlayRef) {
9441
- this.overlayRef.dispose();
10902
+ }
10903
+ subscribeToValueChanges() {
10904
+ const control = this.ngControl.control;
10905
+ if (control) {
10906
+ control.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
10907
+ this.updateCounter();
10908
+ });
9442
10909
  }
9443
10910
  }
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);
10911
+ setupInputRestriction() {
10912
+ const maxLength = this.getMaxLength();
10913
+ if (maxLength !== null) {
10914
+ this.renderer.listen(this.el.nativeElement, 'beforeinput', (event) => {
10915
+ const input = event.target;
10916
+ const selectionStart = input.selectionStart || 0;
10917
+ const selectionEnd = input.selectionEnd || 0;
10918
+ const newValue = input.value.slice(0, selectionStart) + (event.data || '') + input.value.slice(selectionEnd);
10919
+ // Prevent input if the new value exceeds the max length and grows
10920
+ if (newValue.length > maxLength && newValue.length > input.value.length) {
10921
+ event.preventDefault();
10922
+ }
10923
+ });
10924
+ this.renderer.listen(this.el.nativeElement, 'input', () => {
10925
+ this.updateCounter();
10926
+ });
9453
10927
  }
9454
10928
  }
9455
- trapFocusInModal(hostElement) {
9456
- this.focusTrap.create(hostElement);
10929
+ checkForInitialOversize() {
10930
+ const maxLength = this.getMaxLength();
10931
+ const value = this.ngControl.value;
10932
+ if (maxLength !== null && value && value.length > maxLength) {
10933
+ this.ngControl.control?.markAsDirty({ onlySelf: true });
10934
+ this.ngControl.control?.updateValueAndValidity({ onlySelf: true });
10935
+ }
9457
10936
  }
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
- ] }); }
10937
+ 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 }); }
10938
+ 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
10939
  }
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: [{
10940
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MaxLengthCounterDirective, decorators: [{
10941
+ type: Directive,
10942
+ args: [{
10943
+ selector: '[lxMaxLengthCounter]'
10944
+ }]
10945
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1$5.NgControl, decorators: [{
10946
+ type: Self
10947
+ }] }], propDecorators: { lxMaxLengthCounter: [{
9492
10948
  type: Input
9493
- }], canModalBeClosed: [{
10949
+ }], lxMaxLengthCounterRef: [{
9494
10950
  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
10951
  }] } });
9518
10952
 
10953
+ class Sorting {
10954
+ constructor() {
10955
+ this.key = '';
10956
+ this.order = 'asc';
10957
+ }
10958
+ }
10959
+
10960
+ /**
10961
+ * Due to limitations of the native html datepicker this validator is needed:
10962
+ * In the datepicker the min and max values only apply if the date is altered e.g. via scrolling.
10963
+ * It is still possible to manually input a value lower or higher than min and max value.
10964
+ */
10965
+ function ValidateDateInForeseeableFuture(control) {
10966
+ // matches yyyy-mm-dd
10967
+ const dateRegex = /^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/;
10968
+ if (control.value && dateRegex.test(control.value)) {
10969
+ const today = formatDate(new Date(), 'yyyy-MM-dd', 'en');
10970
+ const maxDate = '2999-12-31';
10971
+ if (control.value < today || control.value > maxDate) {
10972
+ return {
10973
+ dateInForseeableFuture: {
10974
+ valid: false
10975
+ }
10976
+ };
10977
+ }
10978
+ else {
10979
+ return null;
10980
+ }
10981
+ }
10982
+ return null;
10983
+ }
10984
+
10985
+ function stringIsInArray(array, stringToFind, valueMapFunction) {
10986
+ const valueToFind = valueMapFunction ? valueMapFunction(stringToFind).toLocaleLowerCase() : stringToFind.toLowerCase();
10987
+ return array.map((item) => item.toLocaleLowerCase()).indexOf(valueToFind) > -1;
10988
+ }
10989
+ /**
10990
+ * Validates that a string is not inside an array of strings
10991
+ */
10992
+ function ValidateStringNotInArray(array, valueMapFunction, validatorName = 'stringNotInArray') {
10993
+ return function (control) {
10994
+ if (!control.value) {
10995
+ return null;
10996
+ }
10997
+ if (stringIsInArray(array, control.value, valueMapFunction)) {
10998
+ return {
10999
+ [validatorName]: true
11000
+ };
11001
+ }
11002
+ return null;
11003
+ };
11004
+ }
11005
+ function ValidateStringNotInArrayAsync(array$, valueMapFunction, validatorName = 'stringNotInArrayAsync') {
11006
+ return function (control) {
11007
+ if (!control.value) {
11008
+ return of(null);
11009
+ }
11010
+ return array$.pipe(map$1((array) => ValidateStringNotInArray(array, valueMapFunction, validatorName)(control)));
11011
+ };
11012
+ }
11013
+
9519
11014
  const MODAL_MODULE_EXPORTS = [ModalComponent, ModalHeaderComponent, ModalFooterComponent, ModalContentDirective];
9520
11015
  class LxModalModule {
9521
11016
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: LxModalModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
@@ -10200,5 +11695,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
10200
11695
  * Generated bundle index. Do not edit.
10201
11696
  */
10202
11697
 
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 };
11698
+ 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
11699
  //# sourceMappingURL=leanix-components.mjs.map