@aquera/nile-visualization 0.6.0 → 0.7.0

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.
@@ -1,7 +1,11 @@
1
1
  import type { ChartConfigType } from './chart-config.type.js';
2
2
  import type { AqConfigType } from './aq-config.type.js';
3
- /** Input to nileChartConfig(). Discriminated on chart.type. */
4
- export interface NileChartConfigInputType {
3
+ import type { NileChartConfigBase } from '../../nile-chart/nile-chart-config.js';
4
+ /**
5
+ * Input to nileChartConfig(). Discriminated on chart.type.
6
+ * Card-level fields may sit on chart, in aq, or on the root (merged: root, then chart, then aq).
7
+ */
8
+ export interface NileChartConfigInputType extends Partial<NileChartConfigBase> {
5
9
  chart: ChartConfigType;
6
10
  aq?: AqConfigType;
7
11
  }
@@ -1,8 +1,9 @@
1
1
  /** Merges separated chart + aq config into a flat NileChartConfig. */
2
2
  export function nileChartConfig(input) {
3
- const { chart, aq } = input;
3
+ const { chart, aq, ...fromRoot } = input;
4
4
  const { ai, switchable, ...rest } = aq ?? {};
5
5
  return {
6
+ ...fromRoot,
6
7
  ...chart,
7
8
  ...rest,
8
9
  ...(ai && { ai }),
@@ -19,7 +19,6 @@ export const styles = css `
19
19
  box-shadow: var(--nile-box-shadow-3, var(--ng-shadow-sm));
20
20
  transition: box-shadow var(--nile-transition-duration-default, var(--ng-transition-duration-default)) ease;
21
21
  }
22
-
23
22
  .chart-card:hover {
24
23
  box-shadow: var(--nile-box-shadow-7, var(--ng-shadow-md));
25
24
  }
@@ -28,8 +27,10 @@ export const styles = css `
28
27
 
29
28
  .chart-header {
30
29
  display: flex;
30
+ flex-direction: row;
31
31
  align-items: center;
32
32
  justify-content: space-between;
33
+ gap: var(--nile-spacing-lg, var(--ng-spacing-lg));
33
34
  position: relative;
34
35
  z-index: 10;
35
36
  padding: var(--nile-spacing-2xl, var(--ng-spacing-2xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-xl, var(--ng-spacing-xl));
@@ -38,16 +39,24 @@ export const styles = css `
38
39
  border-radius: var(--nile-radius-radius-3xl, var(--ng-radius-xl)) var(--nile-radius-radius-3xl, var(--ng-radius-xl)) 0 0;
39
40
  }
40
41
 
42
+ .chart-header.chart-header--compact {
43
+ padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-lg, var(--ng-spacing-lg));
44
+ }
45
+
41
46
  .chart-header-titles {
47
+ display: flex;
48
+ flex-direction: column;
49
+ align-items: flex-start;
50
+ justify-content: center;
42
51
  min-width: 0;
43
- flex: 1;
52
+ flex: 1 1 auto;
44
53
  }
45
54
 
46
55
  .chart-header-title {
47
56
  margin: 0;
48
57
  font-family: var(--nile-font-family-serif-colfax-medium, var(--ng-font-family-display));
49
- font-size: var(--nile-type-scale-6, var(--ng-font-size-text-xl));
50
- font-weight: var(--nile-font-weight-semi-bold, var(--ng-font-weight-semibold));
58
+ font-size: var(--nile-type-scale-6, var(--ng-font-size-text-l));
59
+ font-weight: var(--nile-font-weight-semi-bold, var(--ng-font-weight-semibold));
51
60
  color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
52
61
  line-height: 1.3;
53
62
  }
@@ -66,7 +75,7 @@ export const styles = css `
66
75
  align-items: center;
67
76
  gap: var(--nile-spacing-xs, var(--ng-spacing-xs));
68
77
  flex-shrink: 0;
69
- margin-left: var(--nile-spacing-lg, var(--ng-spacing-lg));
78
+ align-self: center;
70
79
  }
71
80
 
72
81
  /* ── Card Body ── */
@@ -287,7 +296,12 @@ export const styles = css `
287
296
  background: transparent;
288
297
  color: var(--nile-colors-primary-600, var(--ng-colors-fg-brand-primary-600));
289
298
  }
290
-
299
+ .chart-inner--kpi {
300
+ overflow-x: hidden;
301
+ overflow-y: auto;
302
+ -webkit-overflow-scrolling: touch;
303
+ contain: none;
304
+ }
291
305
  .ai-trigger.active {
292
306
  background: var(--nile-colors-primary-100, var(--ng-colors-bg-brand-primary));
293
307
  color: var(--nile-colors-primary-700, var(--ng-colors-bg-brand-solid-hover));
@@ -329,13 +329,14 @@ let NileChart = class NileChart extends NileElement {
329
329
  const title = this.headerTitle;
330
330
  const subtitle = this.headerSubtitle;
331
331
  const showDefaultTitles = !this.hasHeaderSlotContent && !!(title || subtitle);
332
+ const headerCompact = !subtitle?.trim();
332
333
  return html `
333
- <div class="chart-header">
334
+ <div class="chart-header ${headerCompact ? 'chart-header--compact' : ''}">
334
335
  <div class="chart-header-titles">
335
336
  <slot name="header" @slotchange=${this.onHeaderSlotChange}></slot>
336
337
  ${showDefaultTitles
337
338
  ? html `
338
- ${title ? html `<h3 class="chart-header-title">${title}</h3>` : nothing}
339
+ ${title ? html `<p class="chart-header-title">${title}</p>` : nothing}
339
340
  ${subtitle ? html `<p class="chart-header-subtitle">${subtitle}</p>` : nothing}
340
341
  `
341
342
  : nothing}
@@ -851,6 +852,7 @@ let NileChart = class NileChart extends NileElement {
851
852
  case 'kpi': {
852
853
  const k = config;
853
854
  return html `<nile-kpi-chart
855
+ embed-in-nile-chart
854
856
  .config=${{
855
857
  chart: {
856
858
  type: 'kpi',
@@ -891,12 +893,14 @@ let NileChart = class NileChart extends NileElement {
891
893
  ${this.renderHeader()}
892
894
  <div class="chart-wrapper">
893
895
  <div class="chart-inner">
896
+ <div
897
+ class="chart-inner ${this.activeConfig?.type === 'kpi' ? 'chart-inner--kpi' : ''}"
898
+ >
894
899
  ${this.activeConfig ? this.renderChartContent() : html `<slot></slot>`}
895
900
  ${this.renderAiPanel()}
896
901
  </div>
897
902
  </div>
898
903
  <slot name="footer"></slot>
899
- <slot name="footer"></slot>
900
904
  </div>
901
905
  `;
902
906
  }
@@ -1,18 +1,20 @@
1
1
  import { css } from 'lit';
2
2
  export const styles = css `
3
3
  :host {
4
- display: block;
4
+ display: flex;
5
+ flex-direction: column;
5
6
  width: 100%;
6
7
  position: relative;
8
+ box-sizing: border-box;
7
9
  }
8
10
 
9
11
  :host([hidden]) {
10
12
  display: none;
11
13
  }
12
14
 
13
- /* ── Card variant ── */
14
-
15
- .kpi-card {
15
+ /* Card / gauge chrome on the host when used alone (inside nile-chart, embed-in-nile-chart skips this). */
16
+ :host([variant='card']:not([embed-in-nile-chart])),
17
+ :host([variant='gauge']:not([embed-in-nile-chart])) {
16
18
  background: transparent;
17
19
  border: var(--nile-border-width-1, var(--ng-stroke-width-1)) solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
18
20
  border-radius: var(--nile-radius-radius-3xl, var(--ng-radius-xl));
@@ -20,13 +22,14 @@ export const styles = css `
20
22
  transition: box-shadow var(--nile-transition-duration-default, var(--ng-transition-duration-default)) ease;
21
23
  }
22
24
 
23
- .kpi-card:hover {
25
+ :host([variant='card']:not([embed-in-nile-chart]):hover),
26
+ :host([variant='gauge']:not([embed-in-nile-chart]):hover) {
24
27
  box-shadow: var(--nile-box-shadow-7, var(--ng-shadow-md));
25
28
  }
26
29
 
27
- /* ── Base layout ── */
28
-
29
30
  .kpi {
31
+ flex: 1 1 auto;
32
+
30
33
  display: flex;
31
34
  flex-direction: column;
32
35
  gap: var(--nile-spacing-md, var(--ng-spacing-md));
@@ -52,7 +55,7 @@ export const styles = css `
52
55
  .kpi-value {
53
56
  margin: 0;
54
57
  font-family: var(--nile-font-family-serif-colfax-medium, var(--ng-font-family-display));
55
- font-size: 36px;
58
+ font-size: clamp(1.25rem, 2.5vw + 0.75rem, 36px);
56
59
  font-weight: var(--nile-font-weight-semi-bold, var(--ng-font-weight-semibold));
57
60
  color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
58
61
  line-height: 1.2;
@@ -115,7 +118,8 @@ export const styles = css `
115
118
 
116
119
  .kpi-sparkline {
117
120
  width: 100%;
118
- height: 48px;
121
+ flex: 0 1 48px;
122
+ min-height: 22px;
119
123
  margin-top: var(--nile-spacing-xs, var(--ng-spacing-xs));
120
124
  }
121
125
 
@@ -127,8 +131,11 @@ export const styles = css `
127
131
  }
128
132
 
129
133
  .kpi-gauge-container {
130
- width: 160px;
131
- height: 160px;
134
+ width: 100%;
135
+ max-width: 160px;
136
+ aspect-ratio: 1;
137
+ flex: 0 1 160px;
138
+ min-width: 72px;
132
139
  margin: 0 auto;
133
140
  }
134
141
 
@@ -153,6 +160,7 @@ export const styles = css `
153
160
  /* ── Loading state ── */
154
161
 
155
162
  .chart-loading {
163
+ flex: 1 1 auto;
156
164
  display: flex;
157
165
  align-items: center;
158
166
  justify-content: center;
@@ -24,8 +24,8 @@ export interface ChartKpiSeparatedPayload {
24
24
  gaugeColor?: string;
25
25
  loading?: boolean;
26
26
  options?: Highcharts.Options;
27
- /** Optional min height when embedded in `<nile-chart>` (maps to host `min-height`). */
28
- height?: string;
27
+ /** Box size when a height is set (host min-height and height). */
28
+ height?: string | number;
29
29
  }
30
30
  /** Separated `{ chart, aq }` input for `<nile-kpi-chart>`. */
31
31
  export interface NileKpiConfigInputType {
@@ -89,12 +89,18 @@ export declare class NileKpiChart extends NileElement {
89
89
  loading: boolean;
90
90
  /** Highcharts options override for the sparkline or gauge. */
91
91
  options: Highcharts.Options;
92
+ /**
93
+ * Set by nile-chart: skip host border/shadow (variant card/gauge) so the parent chart-card is the only frame.
94
+ */
95
+ embedInNileChart: boolean;
96
+ private formatCssLength;
92
97
  /** Apply `{ chart, aq }` to individual properties. */
93
98
  private applyConfig;
94
99
  connectedCallback(): void;
95
100
  disconnectedCallback(): void;
96
101
  protected firstUpdated(): void;
97
102
  protected updated(changedProperties: PropertyValues): void;
103
+ private syncSparklineChartSize;
98
104
  private setupResizeObserver;
99
105
  private buildSparklineOptions;
100
106
  private buildGaugeOptions;
@@ -47,6 +47,23 @@ let NileKpiChart = class NileKpiChart extends NileElement {
47
47
  this.loading = false;
48
48
  /** Highcharts options override for the sparkline or gauge. */
49
49
  this.options = {};
50
+ /**
51
+ * Set by nile-chart: skip host border/shadow (variant card/gauge) so the parent chart-card is the only frame.
52
+ */
53
+ this.embedInNileChart = false;
54
+ }
55
+ formatCssLength(value) {
56
+ if (value == null)
57
+ return null;
58
+ if (typeof value === 'number') {
59
+ return Number.isFinite(value) ? `${value}px` : null;
60
+ }
61
+ const s = String(value).trim();
62
+ if (!s)
63
+ return null;
64
+ if (/^-?\d*\.?\d+$/.test(s))
65
+ return `${s}px`;
66
+ return s;
50
67
  }
51
68
  /** Apply `{ chart, aq }` to individual properties. */
52
69
  applyConfig(cfg) {
@@ -86,11 +103,19 @@ let NileKpiChart = class NileKpiChart extends NileElement {
86
103
  this.loading = c.loading;
87
104
  if (c.options !== undefined)
88
105
  this.options = c.options;
89
- if (c.height !== undefined) {
90
- if (c.height)
91
- this.style.minHeight = c.height;
92
- else
106
+ if ('height' in c) {
107
+ const h = this.formatCssLength(c.height);
108
+ if (h) {
109
+ /* min-height only: host can grow with content; sparkline shrinks via flex + setSize */
110
+ this.style.minHeight = h;
111
+ this.style.removeProperty('height');
112
+ this.style.removeProperty('max-height');
113
+ }
114
+ else {
93
115
  this.style.removeProperty('min-height');
116
+ this.style.removeProperty('height');
117
+ this.style.removeProperty('max-height');
118
+ }
94
119
  }
95
120
  }
96
121
  if (aq) {
@@ -125,6 +150,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
125
150
  if (sparklineProps.some(p => changedProperties.has(p))) {
126
151
  if (this.sparklineChart) {
127
152
  this.sparklineChart.update(this.buildSparklineOptions(), true, true);
153
+ requestAnimationFrame(() => this.syncSparklineChartSize());
128
154
  }
129
155
  else {
130
156
  this.initSparkline();
@@ -140,9 +166,16 @@ let NileKpiChart = class NileKpiChart extends NileElement {
140
166
  }
141
167
  }
142
168
  }
169
+ syncSparklineChartSize() {
170
+ if (!this.sparklineChart || !this.sparklineContainer)
171
+ return;
172
+ const rect = this.sparklineContainer.getBoundingClientRect();
173
+ const h = Math.max(22, Math.round(rect.height));
174
+ this.sparklineChart.setSize(null, h, false);
175
+ }
143
176
  setupResizeObserver() {
144
177
  this.resizeObserver = new ResizeObserver(() => {
145
- this.sparklineChart?.reflow();
178
+ this.syncSparklineChartSize();
146
179
  this.gaugeChart?.reflow();
147
180
  });
148
181
  if (this.sparklineContainer)
@@ -252,6 +285,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
252
285
  this._hc = await getHighcharts();
253
286
  this.destroySparkline();
254
287
  this.sparklineChart = this._hc.chart(this.sparklineContainer, this.buildSparklineOptions());
288
+ requestAnimationFrame(() => this.syncSparklineChartSize());
255
289
  this.emit('nile-chart-ready', { chart: this.sparklineChart });
256
290
  }
257
291
  async initGauge() {
@@ -326,10 +360,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
326
360
  if (this.loading) {
327
361
  return html `<div class="chart-loading">Loading...</div>`;
328
362
  }
329
- const useCard = this.variant === 'card' || this.variant === 'gauge';
330
- if (useCard) {
331
- return html `<div class="kpi-card">${this.renderContent()}</div>`;
332
- }
363
+ /* Same DOM as inside nile-chart: one surface; card/gauge chrome is on :host when standalone. */
333
364
  return this.renderContent();
334
365
  }
335
366
  };
@@ -394,6 +425,9 @@ __decorate([
394
425
  __decorate([
395
426
  property({ type: Object })
396
427
  ], NileKpiChart.prototype, "options", void 0);
428
+ __decorate([
429
+ property({ type: Boolean, reflect: true, attribute: 'embed-in-nile-chart' })
430
+ ], NileKpiChart.prototype, "embedInNileChart", void 0);
397
431
  NileKpiChart = __decorate([
398
432
  customElement('nile-kpi-chart')
399
433
  ], NileKpiChart);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aquera/nile-visualization",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "A visualization Library for the Nile Design System",
5
5
  "license": "MIT",
6
6
  "author": "Aquera Inc",