@nuralyui/menu 0.0.19 → 0.0.20

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.
package/menu.component.js CHANGED
@@ -12,7 +12,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
12
12
  /* eslint-disable @typescript-eslint/no-explicit-any */
13
13
  /* eslint-disable @typescript-eslint/no-unused-vars */
14
14
  import { LitElement, html, nothing } from 'lit';
15
- import { customElement, property } from 'lit/decorators.js';
15
+ import { customElement, property, state } from 'lit/decorators.js';
16
16
  import { styles } from './menu.style.js';
17
17
  import { NuralyUIBaseMixin } from '@nuralyui/common/mixins';
18
18
  import { StateController, KeyboardController, AccessibilityController } from './controllers/index.js';
@@ -22,10 +22,12 @@ import { StateController, KeyboardController, AccessibilityController } from './
22
22
  * @example
23
23
  * ```html
24
24
  * <nr-menu .items=${menuItems}></nr-menu>
25
+ * <nr-menu .items=${menuItems} arrowPosition="left"></nr-menu>
25
26
  * ```
26
27
  *
27
28
  * @fires change - Menu item selected
28
29
  * @fires action-click - Menu action clicked
30
+ * @fires label-edit - Menu item label edited (detail: { path, oldValue, newValue })
29
31
  *
30
32
  * @slot - Menu items (auto-generated from items property)
31
33
  */
@@ -37,6 +39,18 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
37
39
  this.items = [];
38
40
  /** Menu size variant (small, medium, large) */
39
41
  this.size = "medium" /* MenuSize.Medium */;
42
+ /** Default arrow icon position for submenus (left or right) */
43
+ this.arrowPosition = "right" /* IconPosition.Right */;
44
+ /** Track context menu state */
45
+ this._contextMenuState = null;
46
+ /** Track which menu item is currently being edited (path as string for comparison) */
47
+ this._editingPath = null;
48
+ /** Temporary value while editing */
49
+ this._editingValue = '';
50
+ /** Pending click timeout for double-click detection */
51
+ this._pendingClickTimeout = null;
52
+ /** Path of pending click */
53
+ this._pendingClickPath = null;
40
54
  this._linkIndex = 0;
41
55
  this.stateController = new StateController(this);
42
56
  this.keyboardController = new KeyboardController(this, this.stateController);
@@ -46,9 +60,32 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
46
60
  this._initializeSelectedState();
47
61
  this.accessibilityController.updateAriaAttributes();
48
62
  }
49
- updated() {
63
+ updated(changedProperties) {
64
+ // Re-initialize selection state when items change
65
+ if (changedProperties.has('items')) {
66
+ this._initializeSelectedState();
67
+ // Also open submenus that have 'opened' set to true
68
+ this._initializeOpenedState();
69
+ }
50
70
  this.accessibilityController.updateAriaAttributes();
51
71
  }
72
+ _initializeOpenedState() {
73
+ this._openSubMenusFromItems(this.items, []);
74
+ }
75
+ _openSubMenusFromItems(items, parentPath) {
76
+ for (let index = 0; index < items.length; index++) {
77
+ const item = items[index];
78
+ const currentPath = [...parentPath, index];
79
+ if (item.children) {
80
+ // If this item has 'opened' set to true, open it
81
+ if (item.opened) {
82
+ this.stateController.openSubMenu(currentPath);
83
+ }
84
+ // Recursively check children
85
+ this._openSubMenusFromItems(item.children, currentPath);
86
+ }
87
+ }
88
+ }
52
89
  _initializeSelectedState() {
53
90
  this._linkIndex = 0;
54
91
  this._findSelectedPath(this.items);
@@ -71,7 +108,7 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
71
108
  }
72
109
  return false;
73
110
  }
74
- _handleLinkClick(path, value, event) {
111
+ _handleLinkClick(path, value, editable, event) {
75
112
  var _a;
76
113
  if ((_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.classList.contains('action-icon')) {
77
114
  return;
@@ -82,6 +119,25 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
82
119
  return;
83
120
  }
84
121
  }
122
+ const pathKey = path.join('-');
123
+ // If editable, delay the click to allow double-click detection
124
+ if (editable && (event === null || event === void 0 ? void 0 : event.type) === 'mousedown') {
125
+ // Clear any existing pending click
126
+ if (this._pendingClickTimeout) {
127
+ clearTimeout(this._pendingClickTimeout);
128
+ }
129
+ this._pendingClickPath = pathKey;
130
+ this._pendingClickTimeout = setTimeout(() => {
131
+ if (this._pendingClickPath === pathKey) {
132
+ this._executeClick(path, value);
133
+ this._pendingClickPath = null;
134
+ }
135
+ }, 200); // 200ms delay for double-click detection
136
+ return;
137
+ }
138
+ this._executeClick(path, value);
139
+ }
140
+ _executeClick(path, value) {
85
141
  this.stateController.setSelectedPath(path);
86
142
  this.dispatchEvent(new CustomEvent('change', {
87
143
  bubbles: true,
@@ -90,7 +146,7 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
90
146
  }));
91
147
  this.requestUpdate();
92
148
  }
93
- _handleSubMenuClick(path, value, event) {
149
+ _handleSubMenuClick(path, value, editable, event) {
94
150
  const mouseEvent = event;
95
151
  const target = event.target;
96
152
  // Don't handle if clicking on toggle icon or its parent container
@@ -109,7 +165,25 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
109
165
  // Real mouse click - already handled by mousedown, so return
110
166
  return;
111
167
  }
112
- // This is a mousedown event - highlight and toggle the submenu
168
+ const pathKey = path.join('-');
169
+ // If editable, delay the click to allow double-click detection
170
+ if (editable) {
171
+ // Clear any existing pending click
172
+ if (this._pendingClickTimeout) {
173
+ clearTimeout(this._pendingClickTimeout);
174
+ }
175
+ this._pendingClickPath = pathKey;
176
+ this._pendingClickTimeout = setTimeout(() => {
177
+ if (this._pendingClickPath === pathKey) {
178
+ this._executeSubMenuClick(path, value);
179
+ this._pendingClickPath = null;
180
+ }
181
+ }, 200);
182
+ return;
183
+ }
184
+ this._executeSubMenuClick(path, value);
185
+ }
186
+ _executeSubMenuClick(path, value) {
113
187
  this.stateController.setSelectedPath([]);
114
188
  this.stateController.clearHighlights();
115
189
  this.stateController.setHighlighted(path, true);
@@ -137,12 +211,170 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
137
211
  }
138
212
  _handleActionClick(path, event) {
139
213
  const item = event.detail.item;
214
+ const originalEvent = event.detail.originalEvent;
215
+ const dropdown = event.detail.dropdown;
216
+ // Create a close callback for async actions
217
+ const close = () => {
218
+ if (dropdown) {
219
+ if (typeof dropdown.hide === 'function') {
220
+ dropdown.hide();
221
+ }
222
+ else if (typeof dropdown.close === 'function') {
223
+ dropdown.close();
224
+ }
225
+ }
226
+ };
140
227
  this.dispatchEvent(new CustomEvent('action-click', {
141
- detail: { value: item.value, path, item },
228
+ detail: { value: item.value, path, item, originalEvent, close },
142
229
  composed: true,
143
230
  bubbles: true,
144
231
  }));
145
232
  }
233
+ _handleContextMenu(path, menu, event) {
234
+ var _a, _b;
235
+ if (!((_b = (_a = menu.menu) === null || _a === void 0 ? void 0 : _a.actions) === null || _b === void 0 ? void 0 : _b.length))
236
+ return;
237
+ event.preventDefault();
238
+ event.stopPropagation();
239
+ this._contextMenuState = {
240
+ path,
241
+ x: event.clientX,
242
+ y: event.clientY,
243
+ actions: menu.menu.actions
244
+ };
245
+ this.requestUpdate();
246
+ // Wait for render then show the dropdown
247
+ this.updateComplete.then(() => {
248
+ var _a;
249
+ const dropdown = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('#context-menu-dropdown');
250
+ if (dropdown && typeof dropdown.show === 'function') {
251
+ dropdown.show();
252
+ }
253
+ });
254
+ }
255
+ _handleContextMenuAction(path, event) {
256
+ const item = event.detail.item;
257
+ const originalEvent = event.detail.originalEvent;
258
+ const dropdown = event.detail.dropdown;
259
+ // Create a close callback
260
+ const close = () => {
261
+ this._contextMenuState = null;
262
+ if (dropdown) {
263
+ if (typeof dropdown.hide === 'function') {
264
+ dropdown.hide();
265
+ }
266
+ else if (typeof dropdown.close === 'function') {
267
+ dropdown.close();
268
+ }
269
+ }
270
+ this.requestUpdate();
271
+ };
272
+ this.dispatchEvent(new CustomEvent('action-click', {
273
+ detail: { value: item.value, path, item, originalEvent, close },
274
+ composed: true,
275
+ bubbles: true,
276
+ }));
277
+ close();
278
+ }
279
+ _handleContextMenuClose() {
280
+ this._contextMenuState = null;
281
+ this.requestUpdate();
282
+ }
283
+ _handleDoubleClick(path, menu, event) {
284
+ if (menu.disabled)
285
+ return;
286
+ event.preventDefault();
287
+ event.stopPropagation();
288
+ // Cancel any pending single click
289
+ if (this._pendingClickTimeout) {
290
+ clearTimeout(this._pendingClickTimeout);
291
+ this._pendingClickTimeout = null;
292
+ this._pendingClickPath = null;
293
+ }
294
+ const pathKey = path.join('-');
295
+ this._editingPath = pathKey;
296
+ this._editingValue = menu.text;
297
+ this.requestUpdate();
298
+ // Focus the input after render
299
+ this.updateComplete.then(() => {
300
+ var _a;
301
+ const input = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`input[data-edit-path="${pathKey}"]`);
302
+ if (input) {
303
+ input.focus();
304
+ input.select();
305
+ }
306
+ });
307
+ }
308
+ _handleEditInput(event) {
309
+ event.stopPropagation();
310
+ const input = event.target;
311
+ this._editingValue = input.value;
312
+ }
313
+ _handleEditKeyDown(path, originalText, event) {
314
+ event.stopPropagation(); // Prevent keyboard navigation from capturing keys
315
+ console.log('Edit keydown:', event.key, 'editingValue:', this._editingValue);
316
+ if (event.key === 'Enter') {
317
+ event.preventDefault();
318
+ // Get the current value directly from the input
319
+ const input = event.target;
320
+ this._editingValue = input.value;
321
+ console.log('Saving with value:', this._editingValue);
322
+ this._saveEdit(path, originalText);
323
+ }
324
+ else if (event.key === 'Escape') {
325
+ event.preventDefault();
326
+ this._cancelEdit();
327
+ }
328
+ }
329
+ _handleEditBlur(path, originalText, event) {
330
+ var _a;
331
+ // Check if focus is moving to another element within this component
332
+ const relatedTarget = event.relatedTarget;
333
+ if (relatedTarget && ((_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(relatedTarget))) {
334
+ return; // Don't save if focus is moving within the component
335
+ }
336
+ // Delay to allow click events to process first
337
+ setTimeout(() => {
338
+ if (this._editingPath === path.join('-')) {
339
+ this._saveEdit(path, originalText);
340
+ }
341
+ }, 200);
342
+ }
343
+ _saveEdit(path, originalText) {
344
+ const newValue = this._editingValue.trim();
345
+ console.log('_saveEdit called:', { newValue, originalText, willEmit: newValue && newValue !== originalText });
346
+ if (newValue && newValue !== originalText) {
347
+ const detail = {
348
+ path,
349
+ oldValue: originalText,
350
+ newValue,
351
+ };
352
+ // Call callback if provided
353
+ if (this.onLabelEdit) {
354
+ console.log('Calling onLabelEdit callback');
355
+ this.onLabelEdit(detail);
356
+ }
357
+ // Also dispatch event
358
+ console.log('Dispatching label-edit event from:', this.tagName);
359
+ const event = new CustomEvent('label-edit', {
360
+ bubbles: true,
361
+ composed: true,
362
+ detail,
363
+ });
364
+ this.dispatchEvent(event);
365
+ }
366
+ this._editingPath = null;
367
+ this._editingValue = '';
368
+ this.requestUpdate();
369
+ }
370
+ _cancelEdit() {
371
+ this._editingPath = null;
372
+ this._editingValue = '';
373
+ this.requestUpdate();
374
+ }
375
+ _isEditing(path) {
376
+ return this._editingPath === path.join('-');
377
+ }
146
378
  _isPathSelected(path) {
147
379
  return this.stateController.isPathSelected(path);
148
380
  }
@@ -150,45 +382,60 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
150
382
  return actions.map(action => ({
151
383
  id: action.value,
152
384
  label: action.label,
153
- value: action.value
385
+ value: action.value,
386
+ icon: action.icon,
387
+ additionalData: action.additionalData
154
388
  }));
155
389
  }
156
390
  _renderMenuLink(menu, path) {
157
- var _a, _b;
391
+ var _a, _b, _c;
158
392
  const pathKey = path.join('-');
159
393
  const isSelected = this._isPathSelected(path);
394
+ const isEditing = this._isEditing(path);
160
395
  const linkIndex = this._linkIndex++;
161
396
  return html `
162
- <li
163
- class="menu-link ${isSelected ? 'selected' : ''} ${menu.disabled ? 'disabled' : ''}"
397
+ <li
398
+ class="menu-link ${isSelected ? 'selected' : ''} ${menu.disabled ? 'disabled' : ''} ${isEditing ? 'editing' : ''}"
164
399
  data-path=${pathKey}
165
400
  data-index=${linkIndex}
166
- tabindex="0"
167
- @mousedown=${!menu.disabled ? (e) => this._handleLinkClick(path, menu.text, e) : nothing}
168
- @click=${!menu.disabled ? (e) => this._handleLinkClick(path, menu.text, e) : nothing}>
401
+ tabindex=${isEditing ? -1 : 0}
402
+ @mousedown=${!menu.disabled && !isEditing ? (e) => this._handleLinkClick(path, menu.text, !!menu.editable, e) : nothing}
403
+ @click=${!menu.disabled && !isEditing ? (e) => this._handleLinkClick(path, menu.text, !!menu.editable, e) : nothing}
404
+ @dblclick=${!menu.disabled ? (e) => this._handleDoubleClick(path, menu, e) : nothing}
405
+ @contextmenu=${!menu.disabled && ((_a = menu.menu) === null || _a === void 0 ? void 0 : _a.actions) ? (e) => this._handleContextMenu(path, menu, e) : nothing}>
169
406
  <div class="icon-container">
170
407
  ${menu.icon ? html `
171
408
  ${!menu.text
172
409
  ? html `<div class="icon-only"><nr-icon name="${menu.icon}"></nr-icon></div>`
173
- : html `<nr-icon name="${menu.icon}"></nr-icon>`}
410
+ : html `<nr-icon name="${menu.icon}" size="small"></nr-icon>`}
174
411
  ` : nothing}
175
412
  </div>
176
413
  ${menu.text ? html `
177
414
  <div class="action-text-container">
178
- <div class="text-container">
179
- <span>${menu.text}</span>
415
+ <div class="text-container" @dblclick=${!menu.disabled ? (e) => this._handleDoubleClick(path, menu, e) : nothing}>
416
+ ${isEditing ? html `
417
+ <input
418
+ type="text"
419
+ class="edit-input"
420
+ data-edit-path=${pathKey}
421
+ .value=${this._editingValue}
422
+ @input=${this._handleEditInput}
423
+ @keydown=${(e) => this._handleEditKeyDown(path, menu.text, e)}
424
+ @blur=${(e) => this._handleEditBlur(path, menu.text, e)}
425
+ />
426
+ ` : html `<span>${menu.text}</span>`}
180
427
  </div>
181
428
  <div class="icon-container">
182
- ${((_a = menu.status) === null || _a === void 0 ? void 0 : _a.icon) ? html `
183
- <nr-icon name=${menu.status.icon} class="status-icon"></nr-icon>
429
+ ${((_b = menu.status) === null || _b === void 0 ? void 0 : _b.icon) ? html `
430
+ <nr-icon name=${menu.status.icon} class="status-icon" size="small"></nr-icon>
184
431
  ` : nothing}
185
- ${((_b = menu.menu) === null || _b === void 0 ? void 0 : _b.actions) ? html `
186
- <nr-dropdown
187
- .items=${this._convertActionsToDropdownItems(menu.menu.actions)}
188
- trigger="click"
432
+ ${((_c = menu.menu) === null || _c === void 0 ? void 0 : _c.actions) ? html `
433
+ <nr-dropdown
434
+ .items=${this._convertActionsToDropdownItems(menu.menu.actions)}
435
+ trigger="click"
189
436
  placement="bottom-end"
190
437
  @nr-dropdown-item-click=${(e) => this._handleActionClick(path, e)}>
191
- <nr-icon name="${menu.menu.icon}" class="action-icon" slot="trigger"></nr-icon>
438
+ <nr-icon name="${menu.menu.icon}" class="action-icon" slot="trigger" size="small"></nr-icon>
192
439
  </nr-dropdown>
193
440
  ` : nothing}
194
441
  </div>
@@ -198,20 +445,24 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
198
445
  `;
199
446
  }
200
447
  _renderSubMenu(menu, path) {
201
- var _a, _b;
448
+ var _a, _b, _c;
202
449
  const pathKey = path.join('-');
203
450
  const isOpen = this.stateController.isSubMenuOpen(path) || menu.opened;
204
451
  const isHovered = this.stateController.isSubMenuHovered(path);
205
452
  const isHighlighted = this.stateController.isSubMenuHighlighted(path);
206
453
  const isSelected = menu.selected;
454
+ const isEditing = this._isEditing(path);
455
+ // Determine icon position - use individual menu item setting or fall back to global setting
456
+ const iconPosition = menu.iconPosition || this.arrowPosition;
457
+ const isLeftPosition = iconPosition === "left" /* IconPosition.Left */ || iconPosition === 'left';
207
458
  return html `
208
- <ul
209
- class="sub-menu ${isHighlighted ? 'highlighted' : ''} ${menu.disabled ? 'disabled' : ''} ${isSelected ? 'selected' : ''}"
459
+ <ul
460
+ class="sub-menu ${isHighlighted ? 'highlighted' : ''} ${menu.disabled ? 'disabled' : ''} ${isSelected ? 'selected' : ''} ${isLeftPosition ? 'arrow-left' : 'arrow-right'} ${isEditing ? 'editing' : ''}"
210
461
  data-path=${pathKey}
211
- tabindex="0"
462
+ tabindex=${isEditing ? -1 : 0}
212
463
  @mouseenter=${() => this._handleSubMenuMouseEnter(path)}
213
464
  @mouseleave=${() => this._handleSubMenuMouseLeave(path)}
214
- @click=${!menu.disabled ? (e) => {
465
+ @click=${!menu.disabled && !isEditing ? (e) => {
215
466
  // Handle keyboard activation on the ul element
216
467
  const target = e.target;
217
468
  const mouseEvent = e;
@@ -222,30 +473,51 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
222
473
  this.requestUpdate();
223
474
  }
224
475
  } : nothing}>
225
- <div
476
+ <div
226
477
  class="sub-menu-header"
227
- @mousedown=${!menu.disabled ? (e) => this._handleSubMenuClick(path, menu.text, e) : nothing}
228
- @click=${!menu.disabled ? (e) => this._handleSubMenuClick(path, menu.text, e) : nothing}>
229
- ${menu.icon ? html `<nr-icon class="text-icon" name="${menu.icon}"></nr-icon>` : nothing}
230
- <span>${menu.text}</span>
478
+ @mousedown=${!menu.disabled && !isEditing ? (e) => this._handleSubMenuClick(path, menu.text, !!menu.editable, e) : nothing}
479
+ @click=${!menu.disabled && !isEditing ? (e) => this._handleSubMenuClick(path, menu.text, !!menu.editable, e) : nothing}
480
+ @dblclick=${!menu.disabled && menu.editable ? (e) => this._handleDoubleClick(path, menu, e) : nothing}
481
+ @contextmenu=${!menu.disabled && ((_a = menu.menu) === null || _a === void 0 ? void 0 : _a.actions) ? (e) => this._handleContextMenu(path, menu, e) : nothing}>
482
+ ${isLeftPosition && menu.children && menu.children.length ? html `
483
+ <nr-icon
484
+ id="toggle-icon"
485
+ name="${isOpen ? 'ChevronDown' : 'ChevronRight'}"
486
+ @mousedown=${!menu.disabled ? (e) => this._toggleSubMenu(path, e) : nothing}
487
+ size="small">
488
+ </nr-icon>
489
+ ` : nothing}
490
+ ${menu.icon ? html `<nr-icon class="text-icon" name="${menu.icon}" size="small"></nr-icon>` : nothing}
491
+ ${isEditing ? html `
492
+ <input
493
+ type="text"
494
+ class="edit-input"
495
+ data-edit-path=${pathKey}
496
+ .value=${this._editingValue}
497
+ @input=${this._handleEditInput}
498
+ @keydown=${(e) => this._handleEditKeyDown(path, menu.text, e)}
499
+ @blur=${(e) => this._handleEditBlur(path, menu.text, e)}
500
+ />
501
+ ` : html `<span class="menu-text" @dblclick=${!menu.disabled ? (e) => this._handleDoubleClick(path, menu, e) : nothing}>${menu.text}</span>`}
231
502
  <div class="icons-container">
232
- ${((_a = menu.status) === null || _a === void 0 ? void 0 : _a.icon) ? html `
233
- <nr-icon name=${menu.status.icon} class="status-icon"></nr-icon>
503
+ ${((_b = menu.status) === null || _b === void 0 ? void 0 : _b.icon) ? html `
504
+ <nr-icon name=${menu.status.icon} class="status-icon" size="small"></nr-icon>
234
505
  ` : nothing}
235
- ${(isHighlighted || isHovered) && ((_b = menu.menu) === null || _b === void 0 ? void 0 : _b.actions) ? html `
236
- <nr-dropdown
237
- .items=${this._convertActionsToDropdownItems(menu.menu.actions)}
238
- trigger="click"
506
+ ${(isHighlighted || isHovered) && ((_c = menu.menu) === null || _c === void 0 ? void 0 : _c.actions) ? html `
507
+ <nr-dropdown
508
+ .items=${this._convertActionsToDropdownItems(menu.menu.actions)}
509
+ trigger="click"
239
510
  placement="bottom-end"
240
511
  @nr-dropdown-item-click=${(e) => this._handleActionClick(path, e)}>
241
- <nr-icon name="${menu.menu.icon}" class="action-icon" slot="trigger"></nr-icon>
512
+ <nr-icon name="${menu.menu.icon}" class="action-icon" slot="trigger" size="small"></nr-icon>
242
513
  </nr-dropdown>
243
514
  ` : nothing}
244
- ${menu.children && menu.children.length ? html `
245
- <nr-icon
246
- id="toggle-icon"
247
- name="${isOpen ? 'angle-up' : 'angle-down'}"
248
- @mousedown=${!menu.disabled ? (e) => this._toggleSubMenu(path, e) : nothing}>
515
+ ${!isLeftPosition && menu.children && menu.children.length ? html `
516
+ <nr-icon
517
+ id="toggle-icon"
518
+ name="${isOpen ? 'ChevronDown' : 'ChevronRight'}"
519
+ @mousedown=${!menu.disabled ? (e) => this._toggleSubMenu(path, e) : nothing}
520
+ size="small">
249
521
  </nr-icon>
250
522
  ` : nothing}
251
523
  </div>
@@ -267,12 +539,31 @@ let NrMenuElement = class NrMenuElement extends NuralyUIBaseMixin(LitElement) {
267
539
  }
268
540
  });
269
541
  }
542
+ _renderContextMenu() {
543
+ if (!this._contextMenuState)
544
+ return nothing;
545
+ const { x, y, actions, path } = this._contextMenuState;
546
+ return html `
547
+ <nr-dropdown
548
+ id="context-menu-dropdown"
549
+ .items=${this._convertActionsToDropdownItems(actions)}
550
+ trigger="manual"
551
+ placement="bottom-start"
552
+ style="position: fixed; top: ${y}px; left: ${x}px; z-index: 9999;"
553
+ @nr-dropdown-item-click=${(e) => this._handleContextMenuAction(path, e)}
554
+ @nr-dropdown-close=${() => this._handleContextMenuClose()}
555
+ >
556
+ <span slot="trigger" style="display: block; width: 1px; height: 1px;"></span>
557
+ </nr-dropdown>
558
+ `;
559
+ }
270
560
  render() {
271
561
  this._linkIndex = 0;
272
562
  return html `
273
563
  <ul class="menu-root menu--${this.size}">
274
564
  ${this._renderMenuItems(this.items)}
275
565
  </ul>
566
+ ${this._renderContextMenu()}
276
567
  `;
277
568
  }
278
569
  };
@@ -283,6 +574,21 @@ __decorate([
283
574
  __decorate([
284
575
  property({ type: String })
285
576
  ], NrMenuElement.prototype, "size", void 0);
577
+ __decorate([
578
+ property({ type: String })
579
+ ], NrMenuElement.prototype, "arrowPosition", void 0);
580
+ __decorate([
581
+ property({ type: Object, attribute: false })
582
+ ], NrMenuElement.prototype, "onLabelEdit", void 0);
583
+ __decorate([
584
+ state()
585
+ ], NrMenuElement.prototype, "_contextMenuState", void 0);
586
+ __decorate([
587
+ state()
588
+ ], NrMenuElement.prototype, "_editingPath", void 0);
589
+ __decorate([
590
+ state()
591
+ ], NrMenuElement.prototype, "_editingValue", void 0);
286
592
  NrMenuElement = __decorate([
287
593
  customElement('nr-menu')
288
594
  ], NrMenuElement);