@api-client/ui 0.5.7 → 0.5.9

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 (27) hide show
  1. package/build/src/elements/mention-textarea/internals/MentionTextArea.d.ts +225 -0
  2. package/build/src/elements/mention-textarea/internals/MentionTextArea.d.ts.map +1 -0
  3. package/build/src/elements/mention-textarea/internals/MentionTextArea.js +1065 -0
  4. package/build/src/elements/mention-textarea/internals/MentionTextArea.js.map +1 -0
  5. package/build/src/elements/mention-textarea/internals/MentionTextArea.styles.d.ts +3 -0
  6. package/build/src/elements/mention-textarea/internals/MentionTextArea.styles.d.ts.map +1 -0
  7. package/build/src/elements/mention-textarea/internals/MentionTextArea.styles.js +274 -0
  8. package/build/src/elements/mention-textarea/internals/MentionTextArea.styles.js.map +1 -0
  9. package/build/src/elements/mention-textarea/ui-mention-textarea.d.ts +13 -0
  10. package/build/src/elements/mention-textarea/ui-mention-textarea.d.ts.map +1 -0
  11. package/build/src/elements/mention-textarea/ui-mention-textarea.js +28 -0
  12. package/build/src/elements/mention-textarea/ui-mention-textarea.js.map +1 -0
  13. package/build/src/md/chip/internals/Chip.styles.d.ts.map +1 -1
  14. package/build/src/md/chip/internals/Chip.styles.js +2 -0
  15. package/build/src/md/chip/internals/Chip.styles.js.map +1 -1
  16. package/demo/elements/index.html +3 -0
  17. package/demo/elements/mention-textarea/index.html +19 -0
  18. package/demo/elements/mention-textarea/index.ts +205 -0
  19. package/package.json +2 -2
  20. package/src/elements/mention-textarea/internals/MentionTextArea.styles.ts +274 -0
  21. package/src/elements/mention-textarea/internals/MentionTextArea.ts +1068 -0
  22. package/src/elements/mention-textarea/ui-mention-textarea.ts +18 -0
  23. package/src/md/chip/internals/Chip.styles.ts +2 -0
  24. package/test/elements/http/CertificateAdd.test.ts +0 -3
  25. package/test/elements/mention-textarea/MentionTextArea.basic.test.ts +114 -0
  26. package/test/elements/mention-textarea/MentionTextArea.test.ts +613 -0
  27. package/tsconfig.json +1 -1
@@ -0,0 +1,1065 @@
1
+ import { __esDecorate, __runInitializers } from "tslib";
2
+ import { html, LitElement, nothing } from 'lit';
3
+ import { property, query, state } from 'lit/decorators.js';
4
+ import { classMap } from 'lit/directives/class-map.js';
5
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
+ import reactive from '../../../decorators/reactive.js';
7
+ import '../../../md/chip/ui-chip.js';
8
+ /**
9
+ * A NodeFilter mask that includes text nodes and elements.
10
+ * It is used with the NodeWalker
11
+ */
12
+ const ShowMask = NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT;
13
+ let MentionTextArea = (() => {
14
+ let _classSuper = LitElement;
15
+ let _instanceExtraInitializers = [];
16
+ let _label_decorators;
17
+ let _label_initializers = [];
18
+ let _label_extraInitializers = [];
19
+ let _supportingText_decorators;
20
+ let _supportingText_initializers = [];
21
+ let _supportingText_extraInitializers = [];
22
+ let _disabled_decorators;
23
+ let _disabled_initializers = [];
24
+ let _disabled_extraInitializers = [];
25
+ let _invalid_decorators;
26
+ let _invalid_initializers = [];
27
+ let _invalid_extraInitializers = [];
28
+ let _name_decorators;
29
+ let _name_initializers = [];
30
+ let _name_extraInitializers = [];
31
+ let _required_decorators;
32
+ let _required_initializers = [];
33
+ let _required_extraInitializers = [];
34
+ let _placeholder_decorators;
35
+ let _placeholder_initializers = [];
36
+ let _placeholder_extraInitializers = [];
37
+ let _set_value_decorators;
38
+ let _suggestions_decorators;
39
+ let _suggestions_initializers = [];
40
+ let _suggestions_extraInitializers = [];
41
+ let _mentionTrigger_decorators;
42
+ let _mentionTrigger_initializers = [];
43
+ let _mentionTrigger_extraInitializers = [];
44
+ let _minQueryLength_decorators;
45
+ let _minQueryLength_initializers = [];
46
+ let _minQueryLength_extraInitializers = [];
47
+ let _editorElement_decorators;
48
+ let _editorElement_initializers = [];
49
+ let _editorElement_extraInitializers = [];
50
+ let _suggestionsPopover_decorators;
51
+ let _suggestionsPopover_initializers = [];
52
+ let _suggestionsPopover_extraInitializers = [];
53
+ let _isShowingSuggestions_decorators;
54
+ let _isShowingSuggestions_initializers = [];
55
+ let _isShowingSuggestions_extraInitializers = [];
56
+ let _filteredSuggestions_decorators;
57
+ let _filteredSuggestions_initializers = [];
58
+ let _filteredSuggestions_extraInitializers = [];
59
+ let _selectedSuggestionIndex_decorators;
60
+ let _selectedSuggestionIndex_initializers = [];
61
+ let _selectedSuggestionIndex_extraInitializers = [];
62
+ let _hasContent_decorators;
63
+ let _hasContent_initializers = [];
64
+ let _hasContent_extraInitializers = [];
65
+ let _isLabelFloating_decorators;
66
+ let _isLabelFloating_initializers = [];
67
+ let _isLabelFloating_extraInitializers = [];
68
+ let _isEditorFocus_decorators;
69
+ let _isEditorFocus_initializers = [];
70
+ let _isEditorFocus_extraInitializers = [];
71
+ return class MentionTextArea extends _classSuper {
72
+ static {
73
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
74
+ _label_decorators = [property({ type: String })];
75
+ _supportingText_decorators = [property({ type: String, attribute: 'supporting-text' })];
76
+ _disabled_decorators = [property({ type: Boolean, reflect: true })];
77
+ _invalid_decorators = [property({ type: Boolean, reflect: true })];
78
+ _name_decorators = [property({ type: String })];
79
+ _required_decorators = [property({ type: Boolean })];
80
+ _placeholder_decorators = [property({ type: String })];
81
+ _set_value_decorators = [property({ type: String })];
82
+ _suggestions_decorators = [property({ type: Array, attribute: false })];
83
+ _mentionTrigger_decorators = [property({ type: String, attribute: 'mention-trigger' })];
84
+ _minQueryLength_decorators = [property({ type: Number, attribute: 'min-query-length' })];
85
+ _editorElement_decorators = [query('.editor')];
86
+ _suggestionsPopover_decorators = [query('.suggestions-popover')];
87
+ _isShowingSuggestions_decorators = [state(), reactive()];
88
+ _filteredSuggestions_decorators = [reactive()];
89
+ _selectedSuggestionIndex_decorators = [state()];
90
+ _hasContent_decorators = [state()];
91
+ _isLabelFloating_decorators = [state()];
92
+ _isEditorFocus_decorators = [state()];
93
+ __esDecorate(this, null, _label_decorators, { kind: "accessor", name: "label", static: false, private: false, access: { has: obj => "label" in obj, get: obj => obj.label, set: (obj, value) => { obj.label = value; } }, metadata: _metadata }, _label_initializers, _label_extraInitializers);
94
+ __esDecorate(this, null, _supportingText_decorators, { kind: "accessor", name: "supportingText", static: false, private: false, access: { has: obj => "supportingText" in obj, get: obj => obj.supportingText, set: (obj, value) => { obj.supportingText = value; } }, metadata: _metadata }, _supportingText_initializers, _supportingText_extraInitializers);
95
+ __esDecorate(this, null, _disabled_decorators, { kind: "accessor", name: "disabled", static: false, private: false, access: { has: obj => "disabled" in obj, get: obj => obj.disabled, set: (obj, value) => { obj.disabled = value; } }, metadata: _metadata }, _disabled_initializers, _disabled_extraInitializers);
96
+ __esDecorate(this, null, _invalid_decorators, { kind: "accessor", name: "invalid", static: false, private: false, access: { has: obj => "invalid" in obj, get: obj => obj.invalid, set: (obj, value) => { obj.invalid = value; } }, metadata: _metadata }, _invalid_initializers, _invalid_extraInitializers);
97
+ __esDecorate(this, null, _name_decorators, { kind: "accessor", name: "name", static: false, private: false, access: { has: obj => "name" in obj, get: obj => obj.name, set: (obj, value) => { obj.name = value; } }, metadata: _metadata }, _name_initializers, _name_extraInitializers);
98
+ __esDecorate(this, null, _required_decorators, { kind: "accessor", name: "required", static: false, private: false, access: { has: obj => "required" in obj, get: obj => obj.required, set: (obj, value) => { obj.required = value; } }, metadata: _metadata }, _required_initializers, _required_extraInitializers);
99
+ __esDecorate(this, null, _placeholder_decorators, { kind: "accessor", name: "placeholder", static: false, private: false, access: { has: obj => "placeholder" in obj, get: obj => obj.placeholder, set: (obj, value) => { obj.placeholder = value; } }, metadata: _metadata }, _placeholder_initializers, _placeholder_extraInitializers);
100
+ __esDecorate(this, null, _set_value_decorators, { kind: "setter", name: "value", static: false, private: false, access: { has: obj => "value" in obj, set: (obj, value) => { obj.value = value; } }, metadata: _metadata }, null, _instanceExtraInitializers);
101
+ __esDecorate(this, null, _suggestions_decorators, { kind: "accessor", name: "suggestions", static: false, private: false, access: { has: obj => "suggestions" in obj, get: obj => obj.suggestions, set: (obj, value) => { obj.suggestions = value; } }, metadata: _metadata }, _suggestions_initializers, _suggestions_extraInitializers);
102
+ __esDecorate(this, null, _mentionTrigger_decorators, { kind: "accessor", name: "mentionTrigger", static: false, private: false, access: { has: obj => "mentionTrigger" in obj, get: obj => obj.mentionTrigger, set: (obj, value) => { obj.mentionTrigger = value; } }, metadata: _metadata }, _mentionTrigger_initializers, _mentionTrigger_extraInitializers);
103
+ __esDecorate(this, null, _minQueryLength_decorators, { kind: "accessor", name: "minQueryLength", static: false, private: false, access: { has: obj => "minQueryLength" in obj, get: obj => obj.minQueryLength, set: (obj, value) => { obj.minQueryLength = value; } }, metadata: _metadata }, _minQueryLength_initializers, _minQueryLength_extraInitializers);
104
+ __esDecorate(this, null, _editorElement_decorators, { kind: "accessor", name: "editorElement", static: false, private: false, access: { has: obj => "editorElement" in obj, get: obj => obj.editorElement, set: (obj, value) => { obj.editorElement = value; } }, metadata: _metadata }, _editorElement_initializers, _editorElement_extraInitializers);
105
+ __esDecorate(this, null, _suggestionsPopover_decorators, { kind: "accessor", name: "suggestionsPopover", static: false, private: false, access: { has: obj => "suggestionsPopover" in obj, get: obj => obj.suggestionsPopover, set: (obj, value) => { obj.suggestionsPopover = value; } }, metadata: _metadata }, _suggestionsPopover_initializers, _suggestionsPopover_extraInitializers);
106
+ __esDecorate(this, null, _isShowingSuggestions_decorators, { kind: "accessor", name: "isShowingSuggestions", static: false, private: false, access: { has: obj => "isShowingSuggestions" in obj, get: obj => obj.isShowingSuggestions, set: (obj, value) => { obj.isShowingSuggestions = value; } }, metadata: _metadata }, _isShowingSuggestions_initializers, _isShowingSuggestions_extraInitializers);
107
+ __esDecorate(this, null, _filteredSuggestions_decorators, { kind: "accessor", name: "filteredSuggestions", static: false, private: false, access: { has: obj => "filteredSuggestions" in obj, get: obj => obj.filteredSuggestions, set: (obj, value) => { obj.filteredSuggestions = value; } }, metadata: _metadata }, _filteredSuggestions_initializers, _filteredSuggestions_extraInitializers);
108
+ __esDecorate(this, null, _selectedSuggestionIndex_decorators, { kind: "accessor", name: "selectedSuggestionIndex", static: false, private: false, access: { has: obj => "selectedSuggestionIndex" in obj, get: obj => obj.selectedSuggestionIndex, set: (obj, value) => { obj.selectedSuggestionIndex = value; } }, metadata: _metadata }, _selectedSuggestionIndex_initializers, _selectedSuggestionIndex_extraInitializers);
109
+ __esDecorate(this, null, _hasContent_decorators, { kind: "accessor", name: "hasContent", static: false, private: false, access: { has: obj => "hasContent" in obj, get: obj => obj.hasContent, set: (obj, value) => { obj.hasContent = value; } }, metadata: _metadata }, _hasContent_initializers, _hasContent_extraInitializers);
110
+ __esDecorate(this, null, _isLabelFloating_decorators, { kind: "accessor", name: "isLabelFloating", static: false, private: false, access: { has: obj => "isLabelFloating" in obj, get: obj => obj.isLabelFloating, set: (obj, value) => { obj.isLabelFloating = value; } }, metadata: _metadata }, _isLabelFloating_initializers, _isLabelFloating_extraInitializers);
111
+ __esDecorate(this, null, _isEditorFocus_decorators, { kind: "accessor", name: "isEditorFocus", static: false, private: false, access: { has: obj => "isEditorFocus" in obj, get: obj => obj.isEditorFocus, set: (obj, value) => { obj.isEditorFocus = value; } }, metadata: _metadata }, _isEditorFocus_initializers, _isEditorFocus_extraInitializers);
112
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
113
+ }
114
+ /**
115
+ * Shadow root configuration for the component.
116
+ * Uses 'open' mode for accessibility and delegates focus to enable proper focus management.
117
+ */
118
+ static shadowRootOptions = {
119
+ mode: 'open',
120
+ delegatesFocus: true,
121
+ };
122
+ #label_accessor_storage = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _label_initializers, ''
123
+ /**
124
+ * Supporting text displayed below the input
125
+ */
126
+ ));
127
+ /**
128
+ * The label text displayed as placeholder/floating label
129
+ */
130
+ get label() { return this.#label_accessor_storage; }
131
+ set label(value) { this.#label_accessor_storage = value; }
132
+ #supportingText_accessor_storage = (__runInitializers(this, _label_extraInitializers), __runInitializers(this, _supportingText_initializers, ''
133
+ /**
134
+ * Whether the component is disabled
135
+ */
136
+ ));
137
+ /**
138
+ * Supporting text displayed below the input
139
+ */
140
+ get supportingText() { return this.#supportingText_accessor_storage; }
141
+ set supportingText(value) { this.#supportingText_accessor_storage = value; }
142
+ #disabled_accessor_storage = (__runInitializers(this, _supportingText_extraInitializers), __runInitializers(this, _disabled_initializers, false
143
+ /**
144
+ * Whether the component is in an invalid state
145
+ */
146
+ ));
147
+ /**
148
+ * Whether the component is disabled
149
+ */
150
+ get disabled() { return this.#disabled_accessor_storage; }
151
+ set disabled(value) { this.#disabled_accessor_storage = value; }
152
+ #invalid_accessor_storage = (__runInitializers(this, _disabled_extraInitializers), __runInitializers(this, _invalid_initializers, false
153
+ /**
154
+ * The name attribute for form integration
155
+ */
156
+ ));
157
+ /**
158
+ * Whether the component is in an invalid state
159
+ */
160
+ get invalid() { return this.#invalid_accessor_storage; }
161
+ set invalid(value) { this.#invalid_accessor_storage = value; }
162
+ #name_accessor_storage = (__runInitializers(this, _invalid_extraInitializers), __runInitializers(this, _name_initializers, ''
163
+ /**
164
+ * Whether the input is required
165
+ */
166
+ ));
167
+ /**
168
+ * The name attribute for form integration
169
+ */
170
+ get name() { return this.#name_accessor_storage; }
171
+ set name(value) { this.#name_accessor_storage = value; }
172
+ #required_accessor_storage = (__runInitializers(this, _name_extraInitializers), __runInitializers(this, _required_initializers, false
173
+ /**
174
+ * Placeholder text shown when input is empty
175
+ */
176
+ ));
177
+ /**
178
+ * Whether the input is required
179
+ */
180
+ get required() { return this.#required_accessor_storage; }
181
+ set required(value) { this.#required_accessor_storage = value; }
182
+ #placeholder_accessor_storage = (__runInitializers(this, _required_extraInitializers), __runInitializers(this, _placeholder_initializers, ''));
183
+ /**
184
+ * Placeholder text shown when input is empty
185
+ */
186
+ get placeholder() { return this.#placeholder_accessor_storage; }
187
+ set placeholder(value) { this.#placeholder_accessor_storage = value; }
188
+ get value() {
189
+ return this._value;
190
+ }
191
+ set value(newValue) {
192
+ const oldValue = this._value;
193
+ if (newValue === this._value)
194
+ return; // No change, skip update
195
+ this._value = newValue;
196
+ this.requestUpdate('value', oldValue);
197
+ // Only sync editor from value when set externally and element is ready
198
+ if (this.editorElement) {
199
+ this.syncEditorFromValue();
200
+ }
201
+ }
202
+ #suggestions_accessor_storage = (__runInitializers(this, _placeholder_extraInitializers), __runInitializers(this, _suggestions_initializers, []
203
+ /**
204
+ * Character that triggers mention suggestions (default: '@')
205
+ */
206
+ ));
207
+ /**
208
+ * Available suggestions for mentions
209
+ */
210
+ get suggestions() { return this.#suggestions_accessor_storage; }
211
+ set suggestions(value) { this.#suggestions_accessor_storage = value; }
212
+ #mentionTrigger_accessor_storage = (__runInitializers(this, _suggestions_extraInitializers), __runInitializers(this, _mentionTrigger_initializers, '@'
213
+ /**
214
+ * Minimum characters after trigger to show suggestions
215
+ */
216
+ ));
217
+ /**
218
+ * Character that triggers mention suggestions (default: '@')
219
+ */
220
+ get mentionTrigger() { return this.#mentionTrigger_accessor_storage; }
221
+ set mentionTrigger(value) { this.#mentionTrigger_accessor_storage = value; }
222
+ #minQueryLength_accessor_storage = (__runInitializers(this, _mentionTrigger_extraInitializers), __runInitializers(this, _minQueryLength_initializers, 0));
223
+ /**
224
+ * Minimum characters after trigger to show suggestions
225
+ */
226
+ get minQueryLength() { return this.#minQueryLength_accessor_storage; }
227
+ set minQueryLength(value) { this.#minQueryLength_accessor_storage = value; }
228
+ #editorElement_accessor_storage = (__runInitializers(this, _minQueryLength_extraInitializers), __runInitializers(this, _editorElement_initializers, void 0));
229
+ get editorElement() { return this.#editorElement_accessor_storage; }
230
+ set editorElement(value) { this.#editorElement_accessor_storage = value; }
231
+ #suggestionsPopover_accessor_storage = (__runInitializers(this, _editorElement_extraInitializers), __runInitializers(this, _suggestionsPopover_initializers, void 0));
232
+ get suggestionsPopover() { return this.#suggestionsPopover_accessor_storage; }
233
+ set suggestionsPopover(value) { this.#suggestionsPopover_accessor_storage = value; }
234
+ #isShowingSuggestions_accessor_storage = (__runInitializers(this, _suggestionsPopover_extraInitializers), __runInitializers(this, _isShowingSuggestions_initializers, false));
235
+ get isShowingSuggestions() { return this.#isShowingSuggestions_accessor_storage; }
236
+ set isShowingSuggestions(value) { this.#isShowingSuggestions_accessor_storage = value; }
237
+ #filteredSuggestions_accessor_storage = (__runInitializers(this, _isShowingSuggestions_extraInitializers), __runInitializers(this, _filteredSuggestions_initializers, []));
238
+ get filteredSuggestions() { return this.#filteredSuggestions_accessor_storage; }
239
+ set filteredSuggestions(value) { this.#filteredSuggestions_accessor_storage = value; }
240
+ #selectedSuggestionIndex_accessor_storage = (__runInitializers(this, _filteredSuggestions_extraInitializers), __runInitializers(this, _selectedSuggestionIndex_initializers, 0));
241
+ get selectedSuggestionIndex() { return this.#selectedSuggestionIndex_accessor_storage; }
242
+ set selectedSuggestionIndex(value) { this.#selectedSuggestionIndex_accessor_storage = value; }
243
+ #hasContent_accessor_storage = (__runInitializers(this, _selectedSuggestionIndex_extraInitializers), __runInitializers(this, _hasContent_initializers, false));
244
+ get hasContent() { return this.#hasContent_accessor_storage; }
245
+ set hasContent(value) { this.#hasContent_accessor_storage = value; }
246
+ #isLabelFloating_accessor_storage = (__runInitializers(this, _hasContent_extraInitializers), __runInitializers(this, _isLabelFloating_initializers, false));
247
+ get isLabelFloating() { return this.#isLabelFloating_accessor_storage; }
248
+ set isLabelFloating(value) { this.#isLabelFloating_accessor_storage = value; }
249
+ #isEditorFocus_accessor_storage = (__runInitializers(this, _isLabelFloating_extraInitializers), __runInitializers(this, _isEditorFocus_initializers, false));
250
+ get isEditorFocus() { return this.#isEditorFocus_accessor_storage; }
251
+ set isEditorFocus(value) { this.#isEditorFocus_accessor_storage = value; }
252
+ currentMentionQuery = (__runInitializers(this, _isEditorFocus_extraInitializers), '');
253
+ currentMentionStart = -1;
254
+ mentionMap = new Map();
255
+ mutationObserver;
256
+ _value = ''; // Internal value storage
257
+ /**
258
+ * A read-only list of the names of the fields currently mentioned in the textarea.
259
+ */
260
+ get mentions() {
261
+ const dependencies = new Set();
262
+ if (!this.value) {
263
+ return [];
264
+ }
265
+ const regex = /@\{([^}]+)\}/g;
266
+ let match;
267
+ while ((match = regex.exec(this.value)) !== null) {
268
+ dependencies.add(match[1]);
269
+ }
270
+ return Array.from(dependencies);
271
+ }
272
+ connectedCallback() {
273
+ super.connectedCallback();
274
+ // Setup mutation observer to watch for changes in the editor
275
+ this.mutationObserver = new MutationObserver(() => {
276
+ this.syncValueFromEditor();
277
+ });
278
+ }
279
+ disconnectedCallback() {
280
+ super.disconnectedCallback();
281
+ this.mutationObserver?.disconnect();
282
+ }
283
+ firstUpdated() {
284
+ // Start observing mutations in the editor
285
+ if (this.editorElement && this.mutationObserver) {
286
+ this.mutationObserver.observe(this.editorElement, {
287
+ childList: true,
288
+ subtree: true,
289
+ characterData: true,
290
+ });
291
+ }
292
+ // Setup initial content
293
+ this.updateComplete.then(() => {
294
+ this.syncEditorFromValue();
295
+ });
296
+ }
297
+ willUpdate(changedProperties) {
298
+ super.willUpdate(changedProperties);
299
+ // No need to sync editor from value here since setter handles it
300
+ if (changedProperties.has('suggestions')) {
301
+ this.updateFilteredSuggestions();
302
+ }
303
+ if (changedProperties.has('selectedSuggestionIndex')) {
304
+ const index = this.selectedSuggestionIndex;
305
+ if (index >= 0) {
306
+ this.suggestionsPopover?.querySelector(`.suggestion-item:nth-child(${index + 1})`)?.scrollIntoView({
307
+ behavior: 'smooth',
308
+ block: 'nearest',
309
+ });
310
+ }
311
+ }
312
+ }
313
+ /**
314
+ * Get the plain text content without field references
315
+ * @returns The plain text content with field names instead of references
316
+ */
317
+ getPlainText() {
318
+ let text = this.value;
319
+ // Replace field references with their display names
320
+ text = text.replace(/@\{([^}]+)\}/g, (match, fieldId) => {
321
+ const field = this.suggestions.find((f) => f.id === fieldId);
322
+ return field ? field.label : fieldId;
323
+ });
324
+ return text;
325
+ }
326
+ /**
327
+ * Sets the caret position at the beginning of a specific text node
328
+ */
329
+ setCaretPositionAtTextNode(textNode, offset = 0) {
330
+ const selection = window.getSelection();
331
+ if (!selection)
332
+ return;
333
+ const range = document.createRange();
334
+ range.setStart(textNode, offset);
335
+ range.setEnd(textNode, offset);
336
+ selection.removeAllRanges();
337
+ selection.addRange(range);
338
+ }
339
+ /**
340
+ * Synchronizes the value property from the editor content
341
+ */
342
+ syncValueFromEditor() {
343
+ const newValue = this.getValueFromEditor();
344
+ if (newValue !== this._value) {
345
+ // Update internal value directly to avoid triggering setter
346
+ this._value = newValue;
347
+ this.requestUpdate('value');
348
+ this.dispatchEvent(new Event('input', { bubbles: true }));
349
+ }
350
+ this.updateContentState();
351
+ }
352
+ /**
353
+ * Gets the value from editor content, converting chips to @{mention} format
354
+ */
355
+ getValueFromEditor() {
356
+ const childNodes = Array.from(this.editorElement.childNodes);
357
+ let result = '';
358
+ for (const node of childNodes) {
359
+ if (node.nodeType === Node.TEXT_NODE) {
360
+ result += node.textContent || '';
361
+ }
362
+ else if (node.nodeType === Node.ELEMENT_NODE) {
363
+ const element = node;
364
+ if (element.classList.contains('mention-chip')) {
365
+ const mentionId = element.getAttribute('data-mention-id');
366
+ if (mentionId) {
367
+ result += `@{${mentionId}}`;
368
+ }
369
+ }
370
+ else {
371
+ result += element.textContent || '';
372
+ }
373
+ }
374
+ }
375
+ return result;
376
+ }
377
+ /**
378
+ * Synchronizes the editor content from the value property
379
+ */
380
+ syncEditorFromValue() {
381
+ if (!this.editorElement)
382
+ return;
383
+ const fragments = this.parseValueToFragments(this._value);
384
+ this.editorElement.innerHTML = '';
385
+ for (const fragment of fragments) {
386
+ if (fragment.type === 'text') {
387
+ this.editorElement.appendChild(document.createTextNode(fragment.content));
388
+ }
389
+ else if (fragment.type === 'mention') {
390
+ if (fragment.mentionId) {
391
+ let mention = this.mentionMap.get(fragment.mentionId);
392
+ // If mention not in map, try to find it in suggestions
393
+ if (!mention) {
394
+ mention = this.suggestions.find((s) => s.id === fragment.mentionId);
395
+ if (mention) {
396
+ // Add to map for future use
397
+ this.mentionMap.set(mention.id, mention);
398
+ }
399
+ }
400
+ if (mention) {
401
+ const chipElement = this.createMentionChip(mention);
402
+ this.editorElement.appendChild(chipElement);
403
+ }
404
+ else {
405
+ // Fallback to text if mention not found anywhere
406
+ this.editorElement.appendChild(document.createTextNode(`@{${fragment.mentionId}}`));
407
+ }
408
+ }
409
+ }
410
+ }
411
+ this.updateContentState();
412
+ }
413
+ /**
414
+ * Parses value string into text and mention fragments
415
+ */
416
+ parseValueToFragments(value) {
417
+ const fragments = [];
418
+ const mentionRegex = /@\{([^}]+)\}/g;
419
+ let lastIndex = 0;
420
+ let match;
421
+ while ((match = mentionRegex.exec(value)) !== null) {
422
+ // Add text before mention
423
+ if (match.index > lastIndex) {
424
+ fragments.push({
425
+ type: 'text',
426
+ content: value.substring(lastIndex, match.index),
427
+ });
428
+ }
429
+ // Add mention
430
+ fragments.push({
431
+ type: 'mention',
432
+ content: match[0],
433
+ mentionId: match[1],
434
+ });
435
+ lastIndex = mentionRegex.lastIndex;
436
+ }
437
+ // Add remaining text
438
+ if (lastIndex < value.length) {
439
+ fragments.push({
440
+ type: 'text',
441
+ content: value.substring(lastIndex),
442
+ });
443
+ }
444
+ return fragments;
445
+ }
446
+ /**
447
+ * Creates a mention chip element
448
+ */
449
+ createMentionChip(mention) {
450
+ const wrapper = document.createElement('span');
451
+ wrapper.className = 'mention-chip';
452
+ wrapper.setAttribute('data-mention-id', mention.id);
453
+ wrapper.setAttribute('contenteditable', 'false');
454
+ const chip = document.createElement('ui-chip');
455
+ chip.setAttribute('type', 'input');
456
+ chip.setAttribute('removable', 'true');
457
+ chip.textContent = mention.label;
458
+ chip.addEventListener('remove', () => {
459
+ this.removeMention(wrapper, mention);
460
+ });
461
+ wrapper.appendChild(chip);
462
+ return wrapper;
463
+ }
464
+ /**
465
+ * Updates content state flags
466
+ */
467
+ updateContentState() {
468
+ const hasAnyContent = this.editorElement.childNodes.length > 0 && (this.editorElement.textContent?.trim().length || 0) > 0;
469
+ this.hasContent = hasAnyContent;
470
+ this.isLabelFloating = hasAnyContent || this.isShowingSuggestions || this.isEditorFocus;
471
+ }
472
+ /**
473
+ * Handles input events in the editor
474
+ */
475
+ handleEditorInput(event) {
476
+ event.stopPropagation();
477
+ // Check for mention trigger
478
+ this.checkForMentionTrigger();
479
+ // Sync value
480
+ this.syncValueFromEditor();
481
+ }
482
+ /**
483
+ * Handles keydown events in the editor
484
+ */
485
+ handleEditorKeyDown(event) {
486
+ if (this.isShowingSuggestions) {
487
+ this.handleSuggestionKeyDown(event);
488
+ }
489
+ }
490
+ /**
491
+ * Handles keydown events when suggestions are visible
492
+ */
493
+ handleSuggestionKeyDown(event) {
494
+ switch (event.key) {
495
+ case 'ArrowUp':
496
+ event.preventDefault();
497
+ this.selectedSuggestionIndex = Math.max(0, this.selectedSuggestionIndex - 1);
498
+ break;
499
+ case 'ArrowDown':
500
+ event.preventDefault();
501
+ this.selectedSuggestionIndex = Math.min(this.filteredSuggestions.length - 1, this.selectedSuggestionIndex + 1);
502
+ break;
503
+ case 'Enter':
504
+ case 'Tab':
505
+ event.preventDefault();
506
+ this.selectSuggestion(this.filteredSuggestions[this.selectedSuggestionIndex]);
507
+ break;
508
+ case 'Escape':
509
+ event.preventDefault();
510
+ this.hideSuggestions();
511
+ break;
512
+ }
513
+ }
514
+ /**
515
+ * Handles focus events
516
+ */
517
+ handleEditorFocus() {
518
+ this.isEditorFocus = true;
519
+ this.updateContentState();
520
+ }
521
+ /**
522
+ * Handles blur events
523
+ */
524
+ handleEditorBlur(event) {
525
+ // Don't hide suggestions if focus moved to suggestions popover
526
+ if (event.relatedTarget && this.suggestionsPopover?.contains(event.relatedTarget)) {
527
+ return;
528
+ }
529
+ this.isEditorFocus = false;
530
+ this.hideSuggestions();
531
+ this.updateContentState();
532
+ this.dispatchEvent(new Event('change', { bubbles: true }));
533
+ }
534
+ handleEditorPaste(event) {
535
+ // Prevent default paste behavior to handle mentions properly
536
+ event.preventDefault();
537
+ const pastedContent = event.clipboardData?.getData('text/plain');
538
+ if (!pastedContent)
539
+ return;
540
+ const selection = window.getSelection();
541
+ if (!selection || selection.rangeCount === 0)
542
+ return;
543
+ // Get the current range
544
+ let range;
545
+ if ('getComposedRanges' in selection && typeof selection.getComposedRanges === 'function' && this.shadowRoot) {
546
+ const composedRanges = selection.getComposedRanges({ shadowRoots: [this.shadowRoot] });
547
+ if (composedRanges.length === 0)
548
+ return;
549
+ range = composedRanges[0];
550
+ }
551
+ else {
552
+ range = selection.getRangeAt(0);
553
+ }
554
+ // Convert StaticRange to Range if needed for deleteContents
555
+ let workingRange;
556
+ if (range instanceof StaticRange) {
557
+ workingRange = document.createRange();
558
+ workingRange.setStart(range.startContainer, range.startOffset);
559
+ workingRange.setEnd(range.endContainer, range.endOffset);
560
+ }
561
+ else {
562
+ workingRange = range;
563
+ }
564
+ // Delete the current selection (if any)
565
+ workingRange.deleteContents();
566
+ // Insert the pasted text at the current position
567
+ const textNode = document.createTextNode(pastedContent);
568
+ workingRange.insertNode(textNode);
569
+ // Position cursor at the end of the inserted text
570
+ workingRange.setStartAfter(textNode);
571
+ workingRange.setEndAfter(textNode);
572
+ selection.removeAllRanges();
573
+ selection.addRange(workingRange);
574
+ // Hide suggestions if showing
575
+ this.hideSuggestions();
576
+ // Sync value and trigger events
577
+ this.syncValueFromEditor();
578
+ }
579
+ /**
580
+ * Checks if the current caret position indicates a mention trigger
581
+ */
582
+ checkForMentionTrigger() {
583
+ const selection = window.getSelection();
584
+ if (!selection || selection.rangeCount === 0) {
585
+ this.hideSuggestions();
586
+ return;
587
+ }
588
+ // Use getComposedRanges() for shadow DOM support, fallback to getRangeAt()
589
+ let range;
590
+ if ('getComposedRanges' in selection && typeof selection.getComposedRanges === 'function' && this.shadowRoot) {
591
+ const composedRanges = selection.getComposedRanges({ shadowRoots: [this.shadowRoot] });
592
+ if (composedRanges.length === 0) {
593
+ this.hideSuggestions();
594
+ return;
595
+ }
596
+ range = composedRanges[0];
597
+ }
598
+ else {
599
+ range = selection.getRangeAt(0);
600
+ }
601
+ const caretContainer = range.startContainer;
602
+ const caretOffset = range.startOffset;
603
+ // Ensure the caret is within our editor element
604
+ if (!this.editorElement.contains(caretContainer)) {
605
+ this.hideSuggestions();
606
+ return;
607
+ }
608
+ // Only look for triggers in text nodes
609
+ if (caretContainer.nodeType !== Node.TEXT_NODE) {
610
+ this.hideSuggestions();
611
+ return;
612
+ }
613
+ const textContent = caretContainer.textContent || '';
614
+ // Look backwards from caret to find mention trigger
615
+ let mentionStart = -1;
616
+ for (let i = caretOffset - 1; i >= 0; i--) {
617
+ const char = textContent[i];
618
+ if (char === this.mentionTrigger) {
619
+ mentionStart = i;
620
+ break;
621
+ }
622
+ if (char === ' ' || char === '\n') {
623
+ break;
624
+ }
625
+ }
626
+ if (mentionStart >= 0) {
627
+ const query = textContent.substring(mentionStart + 1, caretOffset);
628
+ if (query.length >= this.minQueryLength) {
629
+ this.currentMentionQuery = query;
630
+ this.currentMentionStart = this.getGlobalTextPosition(caretContainer, mentionStart);
631
+ this.showSuggestions();
632
+ this.updateFilteredSuggestions();
633
+ }
634
+ else {
635
+ this.hideSuggestions();
636
+ }
637
+ }
638
+ else {
639
+ this.hideSuggestions();
640
+ }
641
+ }
642
+ walkerNodeFilter(node) {
643
+ if (node.nodeType === Node.TEXT_NODE) {
644
+ return NodeFilter.FILTER_ACCEPT;
645
+ }
646
+ else if (node.nodeType === Node.ELEMENT_NODE) {
647
+ const element = node;
648
+ if (element.classList.contains('mention-chip')) {
649
+ // Accept mention chips as single characters
650
+ return NodeFilter.FILTER_ACCEPT;
651
+ }
652
+ else if (element.parentElement?.classList.contains('mention-chip')) {
653
+ // It's a chip, ignore the entire tree
654
+ return NodeFilter.FILTER_REJECT;
655
+ }
656
+ }
657
+ return NodeFilter.FILTER_SKIP;
658
+ }
659
+ /**
660
+ * Gets the global text position within the editor for a position within a text node
661
+ */
662
+ getGlobalTextPosition(textNode, offsetInNode) {
663
+ let position = 0;
664
+ const walker = document.createTreeWalker(this.editorElement, ShowMask, this.walkerNodeFilter);
665
+ let node = walker.nextNode();
666
+ while (node && node !== textNode) {
667
+ if (node.nodeType === Node.TEXT_NODE) {
668
+ position += node.textContent?.length || 0;
669
+ }
670
+ else if (node.nodeType === Node.ELEMENT_NODE) {
671
+ const element = node;
672
+ if (element.classList?.contains('mention-chip')) {
673
+ position += 1; // Count chip as 1 character
674
+ }
675
+ }
676
+ node = walker.nextNode();
677
+ }
678
+ return position + offsetInNode;
679
+ }
680
+ /**
681
+ * Finds the DOM node and offset that corresponds to a global text position
682
+ */
683
+ findNodeAtGlobalPosition(globalPosition) {
684
+ let currentPosition = 0;
685
+ const walker = document.createTreeWalker(this.editorElement, ShowMask, this.walkerNodeFilter);
686
+ let node = walker.nextNode();
687
+ while (node) {
688
+ if (node.nodeType === Node.TEXT_NODE) {
689
+ const textLength = node.textContent?.length || 0;
690
+ if (currentPosition + textLength >= globalPosition) {
691
+ return {
692
+ node,
693
+ offset: globalPosition - currentPosition,
694
+ };
695
+ }
696
+ currentPosition += textLength;
697
+ }
698
+ else if (node.nodeType === Node.ELEMENT_NODE) {
699
+ const element = node;
700
+ if (element.classList?.contains('mention-chip')) {
701
+ if (currentPosition === globalPosition) {
702
+ // Position is right before the chip
703
+ const parentNode = element.parentNode;
704
+ if (parentNode) {
705
+ return {
706
+ node: parentNode,
707
+ offset: Array.from(parentNode.childNodes).indexOf(element),
708
+ };
709
+ }
710
+ }
711
+ currentPosition += 1; // Count chip as 1 character
712
+ }
713
+ }
714
+ node = walker.nextNode();
715
+ }
716
+ // Position is beyond the end, return the last position
717
+ const lastChild = this.editorElement.lastChild;
718
+ if (lastChild) {
719
+ if (lastChild.nodeType === Node.TEXT_NODE) {
720
+ return {
721
+ node: lastChild,
722
+ offset: lastChild.textContent?.length || 0,
723
+ };
724
+ }
725
+ else {
726
+ return {
727
+ node: this.editorElement,
728
+ offset: this.editorElement.childNodes.length,
729
+ };
730
+ }
731
+ }
732
+ return null;
733
+ }
734
+ /**
735
+ * Shows the suggestions popover
736
+ */
737
+ showSuggestions() {
738
+ if (!this.isShowingSuggestions) {
739
+ this.isShowingSuggestions = true;
740
+ this.selectedSuggestionIndex = 0;
741
+ this.updateContentState();
742
+ // Use Popover API to show the popover
743
+ this.updateComplete.then(() => {
744
+ if (this.suggestionsPopover) {
745
+ this.suggestionsPopover.showPopover();
746
+ this.positionSuggestions();
747
+ }
748
+ });
749
+ }
750
+ }
751
+ /**
752
+ * Hides the suggestions popover
753
+ */
754
+ hideSuggestions() {
755
+ if (this.isShowingSuggestions) {
756
+ this.isShowingSuggestions = false;
757
+ this.currentMentionQuery = '';
758
+ this.currentMentionStart = -1;
759
+ this.updateContentState();
760
+ // Use Popover API to hide the popover
761
+ if (this.suggestionsPopover) {
762
+ this.suggestionsPopover.hidePopover();
763
+ }
764
+ }
765
+ }
766
+ /**
767
+ * Updates filtered suggestions based on current query
768
+ */
769
+ updateFilteredSuggestions() {
770
+ if (!this.currentMentionQuery) {
771
+ this.filteredSuggestions = this.suggestions.slice(0, 10); // Limit to 10
772
+ return;
773
+ }
774
+ const query = this.currentMentionQuery.toLowerCase();
775
+ this.filteredSuggestions = this.suggestions
776
+ .filter((suggestion) => suggestion.label.toLowerCase().includes(query) ||
777
+ suggestion.description?.toLowerCase().includes(query) ||
778
+ suggestion.suffix?.toLowerCase().includes(query))
779
+ .slice(0, 10); // Limit to 10
780
+ // Reset selection if needed
781
+ if (this.selectedSuggestionIndex >= this.filteredSuggestions.length) {
782
+ this.selectedSuggestionIndex = 0;
783
+ }
784
+ }
785
+ /**
786
+ * Positions the suggestions popover using the Popover API anchor positioning
787
+ */
788
+ positionSuggestions() {
789
+ if (!this.suggestionsPopover)
790
+ return;
791
+ const selection = window.getSelection();
792
+ if (!selection || selection.rangeCount === 0)
793
+ return;
794
+ // Use getComposedRanges() for shadow DOM support, fallback to getRangeAt()
795
+ let range;
796
+ if ('getComposedRanges' in selection && typeof selection.getComposedRanges === 'function' && this.shadowRoot) {
797
+ const composedRanges = selection.getComposedRanges({ shadowRoots: [this.shadowRoot] });
798
+ if (composedRanges.length === 0)
799
+ return;
800
+ range = composedRanges[0];
801
+ }
802
+ else {
803
+ range = selection.getRangeAt(0);
804
+ }
805
+ // Convert StaticRange to Range if needed for getBoundingClientRect
806
+ let workingRange;
807
+ if (range instanceof StaticRange) {
808
+ workingRange = document.createRange();
809
+ workingRange.setStart(range.startContainer, range.startOffset);
810
+ workingRange.setEnd(range.endContainer, range.endOffset);
811
+ }
812
+ else {
813
+ workingRange = range;
814
+ }
815
+ const rect = workingRange.getBoundingClientRect();
816
+ // Position suggestions below the caret using manual positioning as fallback
817
+ // The Popover API will handle proper layering and overflow handling
818
+ this.suggestionsPopover.style.left = `${rect.left}px`;
819
+ this.suggestionsPopover.style.top = `${rect.bottom + 4}px`;
820
+ }
821
+ /**
822
+ * Selects a suggestion and inserts it as a mention
823
+ */
824
+ selectSuggestion(suggestion) {
825
+ if (!suggestion)
826
+ return;
827
+ // Add to mention map
828
+ this.mentionMap.set(suggestion.id, suggestion);
829
+ // Find the position where the mention trigger starts
830
+ const mentionStart = this.currentMentionStart;
831
+ // Find the DOM node and offset for the mention start position
832
+ const mentionStartInfo = this.findNodeAtGlobalPosition(mentionStart);
833
+ if (!mentionStartInfo) {
834
+ // Fallback: insert at the end
835
+ const chip = this.createMentionChip(suggestion);
836
+ const afterTextNode = document.createTextNode('');
837
+ this.editorElement.appendChild(chip);
838
+ this.editorElement.appendChild(afterTextNode);
839
+ this.setCaretPositionAtTextNode(afterTextNode, 0);
840
+ this.hideSuggestions();
841
+ this.syncValueFromEditor();
842
+ this.dispatchEvent(new CustomEvent('mention-insert', {
843
+ detail: {
844
+ suggestion,
845
+ trigger: this.mentionTrigger + this.currentMentionQuery,
846
+ position: mentionStart,
847
+ },
848
+ bubbles: true,
849
+ }));
850
+ return;
851
+ }
852
+ // If the target is not a text node, insert at the container level
853
+ if (mentionStartInfo.node.nodeType !== Node.TEXT_NODE) {
854
+ const chip = this.createMentionChip(suggestion);
855
+ const container = mentionStartInfo.node;
856
+ const beforeNode = container.childNodes[mentionStartInfo.offset];
857
+ // Create an empty text node for caret positioning
858
+ const afterTextNode = document.createTextNode('');
859
+ if (beforeNode) {
860
+ container.insertBefore(chip, beforeNode);
861
+ container.insertBefore(afterTextNode, beforeNode);
862
+ }
863
+ else {
864
+ container.appendChild(chip);
865
+ container.appendChild(afterTextNode);
866
+ }
867
+ // Position caret at the beginning of the after text node
868
+ this.setCaretPositionAtTextNode(afterTextNode, 0);
869
+ this.hideSuggestions();
870
+ this.syncValueFromEditor();
871
+ this.dispatchEvent(new CustomEvent('mention-insert', {
872
+ detail: {
873
+ suggestion,
874
+ trigger: this.mentionTrigger + this.currentMentionQuery,
875
+ position: mentionStart,
876
+ },
877
+ bubbles: true,
878
+ }));
879
+ return;
880
+ }
881
+ // We have a text node - split it and insert the chip
882
+ const textNode = mentionStartInfo.node;
883
+ const splitOffset = mentionStartInfo.offset;
884
+ // Create the mention chip
885
+ const chip = this.createMentionChip(suggestion);
886
+ // Split the text node at the mention start position
887
+ const beforeText = textNode.textContent?.substring(0, splitOffset) || '';
888
+ const afterText = textNode.textContent?.substring(splitOffset + 1) || ''; // +1 to skip the trigger
889
+ // Replace the original text node with the split content
890
+ const parentNode = textNode.parentNode;
891
+ if (parentNode) {
892
+ // Create new text nodes - always create afterTextNode even if empty
893
+ const beforeTextNode = beforeText ? document.createTextNode(beforeText) : null;
894
+ const afterTextNode = document.createTextNode(afterText); // Always create, even if empty
895
+ // Insert the nodes in order
896
+ if (beforeTextNode) {
897
+ parentNode.insertBefore(beforeTextNode, textNode);
898
+ }
899
+ parentNode.insertBefore(chip, textNode);
900
+ parentNode.insertBefore(afterTextNode, textNode);
901
+ // Remove the original text node
902
+ parentNode.removeChild(textNode);
903
+ // Position caret at the beginning of the after text node
904
+ this.setCaretPositionAtTextNode(afterTextNode, 0);
905
+ }
906
+ else {
907
+ // Fallback: append to editor and create a text node for caret positioning
908
+ const afterTextNode = document.createTextNode('');
909
+ this.editorElement.appendChild(chip);
910
+ this.editorElement.appendChild(afterTextNode);
911
+ this.setCaretPositionAtTextNode(afterTextNode, 0);
912
+ }
913
+ // Hide suggestions
914
+ this.hideSuggestions();
915
+ // Sync value and dispatch events
916
+ this.syncValueFromEditor();
917
+ this.dispatchEvent(new CustomEvent('mention-insert', {
918
+ detail: {
919
+ suggestion,
920
+ trigger: this.mentionTrigger + this.currentMentionQuery,
921
+ position: mentionStart,
922
+ },
923
+ bubbles: true,
924
+ }));
925
+ }
926
+ /**
927
+ * Removes a mention chip
928
+ */
929
+ removeMention(chipElement, mention) {
930
+ const position = this.getElementPosition(chipElement);
931
+ chipElement.remove();
932
+ this.syncValueFromEditor();
933
+ this.dispatchEvent(new CustomEvent('mention-remove', {
934
+ detail: {
935
+ suggestion: mention,
936
+ position,
937
+ },
938
+ bubbles: true,
939
+ }));
940
+ }
941
+ /**
942
+ * Gets the text position of an element
943
+ */
944
+ getElementPosition(element) {
945
+ let position = 0;
946
+ let currentNode = this.editorElement.firstChild;
947
+ while (currentNode && currentNode !== element) {
948
+ if (currentNode.nodeType === Node.TEXT_NODE) {
949
+ position += currentNode.textContent?.length || 0;
950
+ }
951
+ else if (currentNode.nodeType === Node.ELEMENT_NODE) {
952
+ const element = currentNode;
953
+ if (element.classList?.contains('mention-chip')) {
954
+ position += 1; // Count chip as 1 character
955
+ }
956
+ }
957
+ currentNode = currentNode.nextSibling;
958
+ }
959
+ return position;
960
+ }
961
+ /**
962
+ * Handles clicking on a suggestion
963
+ */
964
+ handleSuggestionClick(suggestion, event) {
965
+ event.preventDefault();
966
+ this.selectSuggestion(suggestion);
967
+ this.focus();
968
+ }
969
+ /**
970
+ * Renders a suggestion item
971
+ */
972
+ renderSuggestion(suggestion, index) {
973
+ const isSelected = index === this.selectedSuggestionIndex;
974
+ const classes = classMap({
975
+ 'suggestion-item': true,
976
+ 'selected': isSelected,
977
+ });
978
+ return html `
979
+ <button
980
+ class="${classes}"
981
+ @click="${(e) => this.handleSuggestionClick(suggestion, e)}"
982
+ @mouseenter="${() => (this.selectedSuggestionIndex = index)}"
983
+ >
984
+ <div class="suggestion-content">
985
+ <div class="suggestion-headline">${suggestion.label}</div>
986
+ ${suggestion.description
987
+ ? html ` <div class="suggestion-supporting-text">${suggestion.description}</div> `
988
+ : nothing}
989
+ </div>
990
+ ${suggestion.suffix ? html ` <div class="suggestion-suffix">${suggestion.suffix}</div> ` : nothing}
991
+ </button>
992
+ `;
993
+ }
994
+ /**
995
+ * Renders the suggestions popover
996
+ */
997
+ renderSuggestions() {
998
+ return html `
999
+ <div class="suggestions-popover" popover="manual">
1000
+ ${this.isShowingSuggestions && this.filteredSuggestions.length > 0
1001
+ ? this.filteredSuggestions.map((suggestion, index) => this.renderSuggestion(suggestion, index))
1002
+ : nothing}
1003
+ </div>
1004
+ `;
1005
+ }
1006
+ render() {
1007
+ const surfaceClasses = classMap({
1008
+ 'surface': true,
1009
+ 'has-content': this.hasContent,
1010
+ });
1011
+ const labelClasses = classMap({
1012
+ label: true,
1013
+ floating: this.isLabelFloating,
1014
+ });
1015
+ return html `
1016
+ <div class="${surfaceClasses}">
1017
+ <div class="container"></div>
1018
+ <div class="content">
1019
+ <div class="body">
1020
+ ${this.label ? html `<div class="${labelClasses}">${this.label}</div>` : nothing}
1021
+ <div
1022
+ class="editor"
1023
+ contenteditable="${!this.disabled}"
1024
+ data-placeholder="${ifDefined(this.placeholder)}"
1025
+ @input="${this.handleEditorInput}"
1026
+ @keydown="${this.handleEditorKeyDown}"
1027
+ @focus="${this.handleEditorFocus}"
1028
+ @blur="${this.handleEditorBlur}"
1029
+ @paste="${this.handleEditorPaste}"
1030
+ role="textbox"
1031
+ aria-label="${ifDefined(this.label)}"
1032
+ aria-multiline="true"
1033
+ aria-required="${this.required}"
1034
+ aria-invalid="${this.invalid}"
1035
+ tabindex="${this.disabled ? -1 : 0}"
1036
+ ></div>
1037
+ </div>
1038
+ </div>
1039
+ ${this.renderSuggestions()}
1040
+ </div>
1041
+ ${this.supportingText ? html ` <div class="supporting-text">${this.supportingText}</div> ` : nothing}
1042
+ `;
1043
+ }
1044
+ };
1045
+ })();
1046
+ /**
1047
+ * A material design textarea component that supports @mentions with suggestions.
1048
+ *
1049
+ * Features:
1050
+ * - Material Design styling
1051
+ * - @mention triggers with customizable suggestions
1052
+ * - Inline pill/chip rendering for mentions
1053
+ * - Proper caret management
1054
+ * - Keyboard navigation for suggestions
1055
+ * - Overflow container support
1056
+ * - Generic design for extensibility
1057
+ * - Accessibility support
1058
+ *
1059
+ * @fires mention-insert - When a mention is inserted
1060
+ * @fires mention-remove - When a mention is removed
1061
+ * @fires input - When the input value changes
1062
+ * @fires change - When the input loses focus and value has changed
1063
+ */
1064
+ export default MentionTextArea;
1065
+ //# sourceMappingURL=MentionTextArea.js.map