@aquera/nile-visualization 2.9.15 → 2.9.16

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/README.md CHANGED
@@ -36,6 +36,9 @@ To run a local development server that serves the basic demo located in `demo/in
36
36
 
37
37
  In this section, you can find the updates for each release of `nile-visualization`. It's a good practice to maintain detailed release notes to help users and developers understand what changes have been made from one version to another and how these changes might affect their projects.
38
38
 
39
+ #### Version 2.9.16 (June 03, 2026)
40
+ - Nile Chart: Added Portal Property in Menu. (UIF-1253)
41
+
39
42
  #### Version 2.9.15 (June 02, 2026)
40
43
  - Nile Filter Chart: Filter chip fixes (UIF-1250)
41
44
 
@@ -1,2 +1,3 @@
1
1
  export declare const tooltipCss = "\n .nile-chart-header-tooltip {\n position: fixed;\n display: none;\n transform: translate(-50%, calc(-100% - 10px));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));\n font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));\n line-height: 1.4;\n padding: 4px 10px;\n border-radius: 6px;\n border: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n pointer-events: none;\n white-space: nowrap;\n z-index: 2147483647;\n box-shadow: var(--nile-box-shadow-3, var(--ng-shadow-sm));\n }\n .nile-chart-header-tooltip::after {\n content: '';\n position: absolute;\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border: 5px solid transparent;\n border-top-color: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n";
2
+ export declare function injectGlobalChartMenuOverrides(): void;
2
3
  export declare const styles: import("lit").CSSResult;
@@ -28,6 +28,58 @@ export const tooltipCss = `
28
28
  border-top-color: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
29
29
  }
30
30
  `;
31
+ const MENU_OVERRIDE_CSS = `
32
+ nile-menu.nile-chart-actions-menu {
33
+ min-width: var(--nile-width-140px, var(--ng-height-140px, 140px));
34
+ }
35
+
36
+ nile-menu.nile-chart-actions-menu nile-menu-item.nile-chart-actions-menu-item::part(base) {
37
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
38
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
39
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
40
+ color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
41
+ }
42
+
43
+ nile-menu.nile-chart-actions-menu nile-menu-item.nile-chart-actions-menu-item[active]::part(label) {
44
+ color: var(--nile-colors-primary-600, var(--ng-colors-fg-brand-primary-600));
45
+ font-weight: 600;
46
+ }
47
+
48
+ nile-menu.nile-chart-actions-menu .nile-chart-actions-menu-glyph {
49
+ flex-shrink: 0;
50
+ display: inline-flex;
51
+ color: var(--nile-colors-neutral-700, var(--ng-colors-text-quaternary-500));
52
+ }
53
+
54
+ nile-menu.nile-chart-actions-menu .nile-chart-actions-menu-separator {
55
+ height: var(--nile-border-width-1, var(--ng-stroke-width-1, 1px));
56
+ background: var(--nile-colors-neutral-200, var(--ng-colors-border-secondary));
57
+ margin: var(--nile-spacing-md, var(--ng-spacing-md, 8px));
58
+ }
59
+
60
+ nile-menu.nile-chart-actions-menu .nile-chart-actions-menu-group-header {
61
+ display: block;
62
+ padding: var(--nile-spacing-md, var(--ng-spacing-md, 8px)) var(--nile-spacing-14px, var(--ng-spacing-3-5, 14px)) var(--nile-spacing-xs, var(--ng-spacing-xs, 4px));
63
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
64
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
65
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
66
+ color: var(--nile-colors-neutral-500, var(--ng-colors-text-tertiary-600));
67
+ pointer-events: none;
68
+ user-select: none;
69
+ }
70
+ `;
71
+ export function injectGlobalChartMenuOverrides() {
72
+ if (typeof document === 'undefined')
73
+ return;
74
+ const STYLE_ID = 'nile-chart-actions-menu-overrides';
75
+ if (document.getElementById(STYLE_ID))
76
+ return;
77
+ const style = document.createElement('style');
78
+ style.id = STYLE_ID;
79
+ style.textContent = MENU_OVERRIDE_CSS;
80
+ document.head?.appendChild(style);
81
+ }
82
+ injectGlobalChartMenuOverrides();
31
83
  export const styles = css `
32
84
  :host {
33
85
  display: block;
@@ -321,7 +373,7 @@ export const styles = css `
321
373
  /* ── Actions Menu (in header) ── */
322
374
 
323
375
  .nile-chart-menu-anchor {
324
- position: relative;
376
+ display: inline-flex;
325
377
  }
326
378
 
327
379
  .nile-chart-menu-trigger {
@@ -349,76 +401,7 @@ export const styles = css `
349
401
  outline-offset: var(--nile-spacing-1px, var(--ng-outline-offset-1));
350
402
  }
351
403
 
352
- .nile-chart-menu-dropdown {
353
- position: absolute;
354
- top: 100%;
355
- right: 0;
356
- margin-top: var(--nile-spacing-xs, var(--ng-spacing-xs));
357
- min-width: var(--nile-width-140px, var(--ng-height-140px));
358
- background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));
359
- border: var(--nile-border-width-1, var(--ng-stroke-width-1)) solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
360
- border-radius: var(--nile-radius-radius-xl, var(--ng-radius-md));
361
- box-shadow: var(--nile-box-shadow-7, var(--ng-shadow-md));
362
- padding: var(--nile-spacing-xs, var(--ng-spacing-xs)) 0;
363
- overflow: hidden;
364
- z-index: 10;
365
- }
366
-
367
- .nile-chart-menu-item {
368
- display: flex;
369
- align-items: center;
370
- gap: var(--nile-spacing-6px, var(--ng-spacing-sm));
371
- width: 100%;
372
- padding: var(--nile-spacing-md, var(--ng-spacing-md)) var(--nile-spacing-14px, var(--ng-spacing-3-5));
373
- border: none;
374
- background: none;
375
- color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
376
- font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
377
- font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
378
- font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
379
- text-align: left;
380
- cursor: pointer;
381
- transition: background var(--nile-transition-duration-short, var(--ng-transition-duration-fast)) ease;
382
- }
383
-
384
- .nile-chart-menu-item-glyph {
385
- flex-shrink: 0;
386
- color: var(--nile-colors-neutral-700, var(--ng-colors-text-quaternary-500));
387
- }
388
-
389
- .nile-chart-menu-item:hover {
390
- background: var(--nile-colors-neutral-100, var(--ng-colors-bg-secondary));
391
- color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
392
- }
393
-
394
- .nile-chart-menu-item.active {
395
- color: var(--nile-colors-primary-600, var(--ng-colors-fg-brand-primary-600));
396
- font-weight: 600;
397
- }
398
-
399
- .nile-chart-menu-item:focus-visible {
400
- outline: var(--nile-border-width-2, var(--ng-stroke-width-2)) solid var(--nile-colors-primary-600, var(--ng-colors-fg-brand-primary-600));
401
- outline-offset: calc(var(--nile-spacing-2px, var(--ng-outline-offset-2)) * -1);
402
- }
403
-
404
- .nile-chart-menu-separator {
405
- height: var(--nile-border-width-1, var(--ng-stroke-width-1));
406
- background: var(--nile-colors-neutral-200, var(--ng-colors-border-secondary));
407
- margin: var(--nile-spacing-md, var(--ng-spacing-md));
408
- }
409
-
410
- .nile-chart-menu-group-header {
411
- display: block;
412
- padding: var(--nile-spacing-md, var(--ng-spacing-md)) var(--nile-spacing-14px, var(--ng-spacing-3-5)) var(--nile-spacing-xs, var(--ng-spacing-xs));
413
- font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
414
- font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
415
- font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
416
- color: var(--nile-colors-neutral-500, var(--ng-colors-text-tertiary-600));
417
- pointer-events: none;
418
- user-select: none;
419
- }
420
-
421
- /* ── AI Chat Trigger (in header) ── */
404
+
422
405
 
423
406
  .nile-ai-trigger {
424
407
  display: inline-flex;
@@ -88,7 +88,6 @@ export declare class NileChart extends NileElement {
88
88
  menu: NileChartMenuConfig | null;
89
89
  private activeType;
90
90
  private activeConfig;
91
- private menuOpen;
92
91
  private chatOpen;
93
92
  private _hcChart;
94
93
  /** True when elements are projected into the `header` slot (default title/subtitle hidden). */
@@ -108,7 +107,7 @@ export declare class NileChart extends NileElement {
108
107
  private resolveConfig;
109
108
  private resolvedConfig;
110
109
  protected updated(changedProperties: PropertyValues): void;
111
- private toggleMenu;
110
+ private closeMenu;
112
111
  private toggleChat;
113
112
  /** Push an AI response into the chat panel. Call this from your AI handler. */
114
113
  addAiResponse(text: string): void;
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
2
2
  import { customElement, property, query, state } from 'lit/decorators.js';
3
3
  import { html, nothing } from 'lit';
4
4
  import NileElement from '../internal/nile-element.js';
5
- import { styles, tooltipCss } from './nile-chart.css.js';
5
+ import { styles, tooltipCss, injectGlobalChartMenuOverrides } from './nile-chart.css.js';
6
6
  import { nileChartConfig } from './nile-chart-config-builder.js';
7
7
  import { renderChartSkeleton } from './nile-chart-skeleton.js';
8
8
  import { convertConfig } from '../internal/chart-adapters.js';
@@ -158,7 +158,6 @@ let NileChart = class NileChart extends NileElement {
158
158
  this.menu = null;
159
159
  this.activeType = null;
160
160
  this.activeConfig = null;
161
- this.menuOpen = false;
162
161
  this.chatOpen = false;
163
162
  this._hcChart = null;
164
163
  /** True when elements are projected into the `header` slot (default title/subtitle hidden). */
@@ -168,7 +167,6 @@ let NileChart = class NileChart extends NileElement {
168
167
  this.handleOutsideClick = (e) => {
169
168
  const path = e.composedPath();
170
169
  if (!path.includes(this)) {
171
- this.menuOpen = false;
172
170
  this.chatOpen = false;
173
171
  return;
174
172
  }
@@ -210,6 +208,7 @@ let NileChart = class NileChart extends NileElement {
210
208
  connectedCallback() {
211
209
  super.connectedCallback();
212
210
  ensureHeaderTooltipStyles();
211
+ injectGlobalChartMenuOverrides();
213
212
  const tip = document.createElement('div');
214
213
  tip.className = 'nile-chart-header-tooltip';
215
214
  document.body.appendChild(tip);
@@ -262,9 +261,9 @@ let NileChart = class NileChart extends NileElement {
262
261
  this.appearance = resolved.appearance;
263
262
  }
264
263
  }
265
- toggleMenu(e) {
266
- e.stopPropagation();
267
- this.menuOpen = !this.menuOpen;
264
+ closeMenu() {
265
+ const dropdown = this.shadowRoot?.querySelector('.nile-chart-menu-anchor');
266
+ dropdown?.hide?.();
268
267
  }
269
268
  toggleChat(e) {
270
269
  e.stopPropagation();
@@ -291,7 +290,7 @@ let NileChart = class NileChart extends NileElement {
291
290
  const converted = convertConfig(this.resolvedConfig, toType);
292
291
  this.activeType = toType;
293
292
  this.activeConfig = converted;
294
- this.menuOpen = false;
293
+ this.closeMenu();
295
294
  this.emit('nile-chart-type-change', {
296
295
  fromType,
297
296
  toType,
@@ -401,7 +400,7 @@ let NileChart = class NileChart extends NileElement {
401
400
  }
402
401
  }
403
402
  async viewFullscreen() {
404
- this.menuOpen = false;
403
+ this.closeMenu();
405
404
  await this.ensureExporting();
406
405
  if (this._hcChart?.fullscreen?.open) {
407
406
  this._hcChart.fullscreen.open();
@@ -411,25 +410,26 @@ let NileChart = class NileChart extends NileElement {
411
410
  target.requestFullscreen?.();
412
411
  }
413
412
  async printChart() {
414
- this.menuOpen = false;
413
+ this.closeMenu();
415
414
  await this.ensureExporting();
416
415
  this._hcChart?.print?.();
417
416
  }
418
417
  async exportChart(type) {
419
- this.menuOpen = false;
418
+ this.closeMenu();
420
419
  await this.ensureExporting();
421
420
  const filename = (this.headerTitle || 'chart').replace(/[^a-z0-9_-]+/gi, '_');
422
421
  this._hcChart?.exportChartLocal?.({ type, filename, local: true });
423
422
  }
424
423
  async downloadCsv() {
425
- this.menuOpen = false;
424
+ this.closeMenu();
426
425
  await this.ensureExporting();
427
426
  this._hcChart?.downloadCSV?.();
428
427
  }
429
428
  _renderMenuItem(item, hasChart) {
430
429
  const glyph = item.glyph
431
430
  ? html `<nile-glyph
432
- class="nile-chart-menu-item-glyph"
431
+ slot="prefix"
432
+ class="nile-chart-actions-menu-glyph"
433
433
  name=${item.glyph}
434
434
  method=${item.method ?? 'stroke'}
435
435
  color=${item.color ?? 'var(--nile-colors-dark-500,var(--ng-colors-fg-secondary-700))'}
@@ -438,22 +438,22 @@ let NileChart = class NileChart extends NileElement {
438
438
  : nothing;
439
439
  if (item.type === 'custom') {
440
440
  return html `
441
- ${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
442
- <button type="button" class="nile-chart-menu-item" role="menuitem"
443
- @click=${() => { this.menuOpen = false; this.emit('nile-menu-change', { id: item.id }); }}>
441
+ ${item.divider ? html `<div class="nile-chart-actions-menu-separator" role="separator"></div>` : nothing}
442
+ <nile-menu-item class="nile-chart-actions-menu-item" value=${`custom:${item.id}`}
443
+ @click=${() => { this.closeMenu(); this.emit('nile-menu-change', { id: item.id }); }}>
444
444
  ${glyph}${item.label}
445
- </button>
445
+ </nile-menu-item>
446
446
  `;
447
447
  }
448
448
  if (!hasChart)
449
449
  return nothing;
450
450
  return html `
451
- ${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
452
- ${item.fullscreen ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.viewFullscreen()}>${glyph}${item.label ?? 'Fullscreen'}</button>` : nothing}
453
- ${item.print ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.printChart()}>${glyph}${item.label ?? 'Print'}</button>` : nothing}
454
- ${item.downloadPng ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.exportChart('image/png')}>${glyph}${item.label ?? 'Download PNG'}</button>` : nothing}
455
- ${item.downloadSvg ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.exportChart('image/svg+xml')}>${glyph}${item.label ?? 'Download SVG'}</button>` : nothing}
456
- ${item.downloadCsv ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.downloadCsv()}>${glyph}${item.label ?? 'Download CSV'}</button>` : nothing}
451
+ ${item.divider ? html `<div class="nile-chart-actions-menu-separator" role="separator"></div>` : nothing}
452
+ ${item.fullscreen ? html `<nile-menu-item class="nile-chart-actions-menu-item" value="fullscreen" @click=${() => this.viewFullscreen()}>${glyph}${item.label ?? 'Fullscreen'}</nile-menu-item>` : nothing}
453
+ ${item.print ? html `<nile-menu-item class="nile-chart-actions-menu-item" value="print" @click=${() => this.printChart()}>${glyph}${item.label ?? 'Print'}</nile-menu-item>` : nothing}
454
+ ${item.downloadPng ? html `<nile-menu-item class="nile-chart-actions-menu-item" value="download-png" @click=${() => this.exportChart('image/png')}>${glyph}${item.label ?? 'Download PNG'}</nile-menu-item>` : nothing}
455
+ ${item.downloadSvg ? html `<nile-menu-item class="nile-chart-actions-menu-item" value="download-svg" @click=${() => this.exportChart('image/svg+xml')}>${glyph}${item.label ?? 'Download SVG'}</nile-menu-item>` : nothing}
456
+ ${item.downloadCsv ? html `<nile-menu-item class="nile-chart-actions-menu-item" value="download-csv" @click=${() => this.downloadCsv()}>${glyph}${item.label ?? 'Download CSV'}</nile-menu-item>` : nothing}
457
457
  `;
458
458
  }
459
459
  renderActionsMenu() {
@@ -469,41 +469,44 @@ let NileChart = class NileChart extends NileElement {
469
469
  ];
470
470
  const hasChart = !!this._hcChart;
471
471
  const hasAnyItems = !!(types?.length || allItems.length);
472
+ if (!hasAnyItems) {
473
+ return html `
474
+ <button type="button" class="nile-chart-menu-trigger" aria-label="Chart actions">
475
+ <nile-glyph name="options" size="16"></nile-glyph>
476
+ </button>
477
+ `;
478
+ }
472
479
  return html `
473
- <div class="nile-chart-menu-anchor">
480
+ <nile-dropdown class="nile-chart-menu-anchor" placement="bottom-end" distance="4" portal>
474
481
  <button
475
482
  type="button"
483
+ slot="trigger"
476
484
  class="nile-chart-menu-trigger"
477
- aria-haspopup="true"
478
- aria-expanded=${this.menuOpen ? 'true' : 'false'}
479
485
  aria-label="Chart actions"
480
- @click=${(e) => this.toggleMenu(e)}
481
486
  >
482
487
  <nile-glyph name="options" size="16"></nile-glyph>
483
488
  </button>
484
- ${this.menuOpen && hasAnyItems ? html `
485
- <div class="nile-chart-menu-dropdown" role="menu">
486
- ${types?.length ? html `
487
- ${types.map(type => html `
488
- <button type="button" class="nile-chart-menu-item ${type === this.activeType ? 'active' : ''}" role="menuitem" @click=${() => this.switchType(type)}>
489
- ${chartTypeLabel(type)}
490
- </button>
491
- `)}
492
- ${allItems.length ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
493
- ` : nothing}
494
- ${allItems.map(item => {
489
+ <nile-menu class="nile-chart-actions-menu" part="dropdown">
490
+ ${types?.length ? html `
491
+ ${types.map(type => html `
492
+ <nile-menu-item class="nile-chart-actions-menu-item" value=${`type:${type}`} ?active=${type === this.activeType} @click=${() => this.switchType(type)}>
493
+ ${chartTypeLabel(type)}
494
+ </nile-menu-item>
495
+ `)}
496
+ ${allItems.length ? html `<div class="nile-chart-actions-menu-separator" role="separator"></div>` : nothing}
497
+ ` : nothing}
498
+ ${allItems.map(item => {
495
499
  if (item.type === 'group') {
496
500
  return html `
497
- ${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
498
- <span class="nile-chart-menu-group-header" role="presentation">${item.label}</span>
499
- ${item.items.map(child => this._renderMenuItem(child, hasChart))}
500
- `;
501
+ ${item.divider ? html `<div class="nile-chart-actions-menu-separator" role="separator" part="seperator"></div>` : nothing}
502
+ <span class="nile-chart-actions-menu-group-header" role="presentation" part="header">${item.label}</span>
503
+ ${item.items.map(child => this._renderMenuItem(child, hasChart))}
504
+ `;
501
505
  }
502
506
  return this._renderMenuItem(item, hasChart);
503
507
  })}
504
- </div>
505
- ` : nothing}
506
- </div>
508
+ </nile-menu>
509
+ </nile-dropdown>
507
510
  `;
508
511
  }
509
512
  renderAiTrigger() {
@@ -1805,9 +1808,6 @@ __decorate([
1805
1808
  __decorate([
1806
1809
  state()
1807
1810
  ], NileChart.prototype, "activeConfig", void 0);
1808
- __decorate([
1809
- state()
1810
- ], NileChart.prototype, "menuOpen", void 0);
1811
1811
  __decorate([
1812
1812
  state()
1813
1813
  ], NileChart.prototype, "chatOpen", void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aquera/nile-visualization",
3
- "version": "2.9.15",
3
+ "version": "2.9.16",
4
4
  "description": "A visualization Library for the Nile Design System",
5
5
  "license": "MIT",
6
6
  "author": "Aquera Inc",