@aquera/nile-visualization 0.7.0 → 0.9.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.
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/nile-ai-panel/nile-ai-panel.js +4 -3
- package/dist/src/nile-chart/nile-chart.css.js +98 -0
- package/dist/src/nile-chart/nile-chart.d.ts +12 -0
- package/dist/src/nile-chart/nile-chart.js +38 -5
- package/dist/src/nile-dashboard-viewer/nile-dashboard-viewer.css.js +0 -1
- package/dist/src/nile-executive-summary/index.d.ts +2 -0
- package/dist/src/nile-executive-summary/index.js +2 -0
- package/dist/src/nile-executive-summary/nile-executive-summary-config.d.ts +39 -0
- package/dist/src/nile-executive-summary/nile-executive-summary-config.js +8 -0
- package/dist/src/nile-executive-summary/nile-executive-summary.css.d.ts +9 -0
- package/dist/src/nile-executive-summary/nile-executive-summary.css.js +196 -0
- package/dist/src/nile-executive-summary/nile-executive-summary.d.ts +101 -0
- package/dist/src/nile-executive-summary/nile-executive-summary.js +308 -0
- package/dist/src/nile-kpi-chart/nile-kpi-chart.css.d.ts +5 -0
- package/dist/src/nile-kpi-chart/nile-kpi-chart.css.js +34 -1
- package/dist/src/nile-kpi-chart/nile-kpi-chart.d.ts +14 -2
- package/dist/src/nile-kpi-chart/nile-kpi-chart.js +163 -15
- package/package.json +3 -2
package/dist/src/index.d.ts
CHANGED
|
@@ -100,3 +100,5 @@ export { NileWidgetViewer } from './nile-widget-viewer/index.js';
|
|
|
100
100
|
export type { NileWidgetConfig, NileWidgetChartConfig, NileWidgetKpiConfig, WidgetLayout, } from './nile-widget-viewer/index.js';
|
|
101
101
|
export { NileDashboardViewer } from './nile-dashboard-viewer/index.js';
|
|
102
102
|
export type { NileDashboardConfig } from './nile-dashboard-viewer/index.js';
|
|
103
|
+
export { NileExecutiveSummary } from './nile-executive-summary/index.js';
|
|
104
|
+
export type { NileExecutiveSummaryConfig } from './nile-executive-summary/index.js';
|
package/dist/src/index.js
CHANGED
|
@@ -54,4 +54,5 @@ export { NileAiSender } from './nile-ai-sender/index.js';
|
|
|
54
54
|
export { NileAiPanel } from './nile-ai-panel/index.js';
|
|
55
55
|
export { NileWidgetViewer } from './nile-widget-viewer/index.js';
|
|
56
56
|
export { NileDashboardViewer } from './nile-dashboard-viewer/index.js';
|
|
57
|
+
export { NileExecutiveSummary } from './nile-executive-summary/index.js';
|
|
57
58
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
3
3
|
import { html, nothing } from 'lit';
|
|
4
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
4
5
|
import NileElement from '../internal/nile-element.js';
|
|
5
6
|
import { styles } from './nile-ai-panel.css.js';
|
|
6
7
|
import '../nile-ai-sender/index.js';
|
|
@@ -87,12 +88,12 @@ let NileAiPanel = class NileAiPanel extends NileElement {
|
|
|
87
88
|
return html `
|
|
88
89
|
<div class="messages">
|
|
89
90
|
${this.welcomeMessage
|
|
90
|
-
? html `<div class="message message--assistant">${this.welcomeMessage}</div>`
|
|
91
|
+
? html `<div class="message message--assistant">${unsafeHTML(this.welcomeMessage)}</div>`
|
|
91
92
|
: nothing}
|
|
92
93
|
${this.summaryMessage
|
|
93
|
-
? html `<div class="message message--summary">${this.summaryMessage}</div>`
|
|
94
|
+
? html `<div class="message message--summary">${unsafeHTML(this.summaryMessage)}</div>`
|
|
94
95
|
: nothing}
|
|
95
|
-
${this.messages.map(m => html `<div class="message message--${m.role}">${m.text}</div>`)}
|
|
96
|
+
${this.messages.map(m => html `<div class="message message--${m.role}">${unsafeHTML(m.text)}</div>`)}
|
|
96
97
|
${this.loading
|
|
97
98
|
? html `<div class="typing">
|
|
98
99
|
<span class="typing-dot"></span>
|
|
@@ -333,5 +333,103 @@ export const styles = css `
|
|
|
333
333
|
.ai-panel-overlay[data-open] {
|
|
334
334
|
transform: translateY(0);
|
|
335
335
|
}
|
|
336
|
+
|
|
337
|
+
/* ── Skeleton loader (matches nile-skeleton-loader animation style) ── */
|
|
338
|
+
|
|
339
|
+
:host([loading]) .chart-card {
|
|
340
|
+
pointer-events: none;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.chart-skeleton {
|
|
344
|
+
display: flex;
|
|
345
|
+
flex-direction: column;
|
|
346
|
+
gap: 0;
|
|
347
|
+
padding: var(--nile-spacing-3xl, 24px) var(--nile-spacing-3xl, 24px) var(--nile-spacing-xl, 16px);
|
|
348
|
+
min-height: var(--nile-chart-skeleton-height, 300px);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.chart-skeleton-body {
|
|
352
|
+
display: flex;
|
|
353
|
+
flex-direction: column;
|
|
354
|
+
justify-content: space-around;
|
|
355
|
+
flex: 1;
|
|
356
|
+
gap: var(--nile-spacing-xl, 14px);
|
|
357
|
+
padding-left: 44px;
|
|
358
|
+
position: relative;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Vertical y-axis rule */
|
|
362
|
+
.chart-skeleton-body::before {
|
|
363
|
+
content: '';
|
|
364
|
+
position: absolute;
|
|
365
|
+
left: 34px;
|
|
366
|
+
top: 4px;
|
|
367
|
+
bottom: 4px;
|
|
368
|
+
width: 2px;
|
|
369
|
+
border-radius: 1px;
|
|
370
|
+
background: var(--nile-colors-neutral-400, #e5e7eb);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.chart-skeleton-row {
|
|
374
|
+
display: flex;
|
|
375
|
+
align-items: center;
|
|
376
|
+
gap: var(--nile-spacing-md, 8px);
|
|
377
|
+
height: 24px;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.chart-skeleton-ylabel {
|
|
381
|
+
width: 26px;
|
|
382
|
+
height: 10px;
|
|
383
|
+
border-radius: 3px;
|
|
384
|
+
flex-shrink: 0;
|
|
385
|
+
background: var(--nile-colors-neutral-400, #e5e7eb);
|
|
386
|
+
animation: nile-skeleton-blink 1.2s ease-in-out infinite;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.chart-skeleton-bar {
|
|
390
|
+
height: 20px;
|
|
391
|
+
width: var(--w, 60%);
|
|
392
|
+
border-radius: 0 3px 3px 0;
|
|
393
|
+
background: var(--nile-colors-neutral-400, #e5e7eb);
|
|
394
|
+
animation: nile-skeleton-blink 1.2s ease-in-out infinite;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Staggered wave across the bars */
|
|
398
|
+
.chart-skeleton-row:nth-child(1) .chart-skeleton-bar { animation-delay: 0ms; }
|
|
399
|
+
.chart-skeleton-row:nth-child(2) .chart-skeleton-bar { animation-delay: 100ms; }
|
|
400
|
+
.chart-skeleton-row:nth-child(3) .chart-skeleton-bar { animation-delay: 200ms; }
|
|
401
|
+
.chart-skeleton-row:nth-child(4) .chart-skeleton-bar { animation-delay: 300ms; }
|
|
402
|
+
.chart-skeleton-row:nth-child(5) .chart-skeleton-bar { animation-delay: 400ms; }
|
|
403
|
+
|
|
404
|
+
/* Horizontal x-axis labels row */
|
|
405
|
+
.chart-skeleton-xaxis-row {
|
|
406
|
+
display: flex;
|
|
407
|
+
justify-content: space-around;
|
|
408
|
+
padding-left: 44px;
|
|
409
|
+
margin-top: var(--nile-spacing-xl, 14px);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.chart-skeleton-xlabel {
|
|
413
|
+
height: 10px;
|
|
414
|
+
width: 30px;
|
|
415
|
+
border-radius: 3px;
|
|
416
|
+
background: var(--nile-colors-neutral-400, #e5e7eb);
|
|
417
|
+
animation: nile-skeleton-blink 1.2s ease-in-out infinite;
|
|
418
|
+
animation-delay: var(--d, 0ms);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@keyframes nile-skeleton-blink {
|
|
422
|
+
0%, 100% { opacity: 0.5; }
|
|
423
|
+
50% { opacity: 1; }
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@media (prefers-reduced-motion: reduce) {
|
|
427
|
+
.chart-skeleton-bar,
|
|
428
|
+
.chart-skeleton-ylabel,
|
|
429
|
+
.chart-skeleton-xlabel {
|
|
430
|
+
animation: none;
|
|
431
|
+
opacity: 0.7;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
336
434
|
`;
|
|
337
435
|
//# sourceMappingURL=nile-chart.css.js.map
|
|
@@ -53,6 +53,17 @@ export declare class NileChart extends NileElement {
|
|
|
53
53
|
static styles: CSSResultGroup;
|
|
54
54
|
/** Full chart configuration. Accepts flat NileChartConfig or separated { chart, aq } input. */
|
|
55
55
|
config: NileChartConfig | NileChartConfigInputType | null;
|
|
56
|
+
/**
|
|
57
|
+
* When true, hides the chart and shows a skeleton bar-chart loader.
|
|
58
|
+
* Set to false once data is ready to reveal the chart.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```js
|
|
62
|
+
* chart.loading = true;
|
|
63
|
+
* fetchData().then(data => { chart.config = buildConfig(data); chart.loading = false; });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
loading: boolean;
|
|
56
67
|
/**
|
|
57
68
|
* When set, fills `chart.type` if the config omits it (same values as `chart.type`, e.g. `stacked`, `pie`).
|
|
58
69
|
* Usage: `<nile-chart chart-type="pie" />` plus `config.chart` with series data only.
|
|
@@ -108,6 +119,7 @@ export declare class NileChart extends NileElement {
|
|
|
108
119
|
private renderHeader;
|
|
109
120
|
private renderAiPanel;
|
|
110
121
|
private renderChartContent;
|
|
122
|
+
private renderSkeleton;
|
|
111
123
|
render(): TemplateResult;
|
|
112
124
|
}
|
|
113
125
|
declare global {
|
|
@@ -103,6 +103,17 @@ let NileChart = class NileChart extends NileElement {
|
|
|
103
103
|
super(...arguments);
|
|
104
104
|
/** Full chart configuration. Accepts flat NileChartConfig or separated { chart, aq } input. */
|
|
105
105
|
this.config = null;
|
|
106
|
+
/**
|
|
107
|
+
* When true, hides the chart and shows a skeleton bar-chart loader.
|
|
108
|
+
* Set to false once data is ready to reveal the chart.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```js
|
|
112
|
+
* chart.loading = true;
|
|
113
|
+
* fetchData().then(data => { chart.config = buildConfig(data); chart.loading = false; });
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
this.loading = false;
|
|
106
117
|
/**
|
|
107
118
|
* When set, fills `chart.type` if the config omits it (same values as `chart.type`, e.g. `stacked`, `pie`).
|
|
108
119
|
* Usage: `<nile-chart chart-type="pie" />` plus `config.chart` with series data only.
|
|
@@ -887,16 +898,35 @@ let NileChart = class NileChart extends NileElement {
|
|
|
887
898
|
}
|
|
888
899
|
}
|
|
889
900
|
}
|
|
901
|
+
renderSkeleton() {
|
|
902
|
+
return html `
|
|
903
|
+
<div class="chart-skeleton" aria-busy="true" aria-label="Loading chart">
|
|
904
|
+
<div class="chart-skeleton-body">
|
|
905
|
+
${[78, 55, 91, 42, 68].map(w => html `
|
|
906
|
+
<div class="chart-skeleton-row">
|
|
907
|
+
<div class="chart-skeleton-ylabel"></div>
|
|
908
|
+
<div class="chart-skeleton-bar" style="--w: ${w}%"></div>
|
|
909
|
+
</div>
|
|
910
|
+
`)}
|
|
911
|
+
</div>
|
|
912
|
+
<div class="chart-skeleton-xaxis-row">
|
|
913
|
+
${[0, 1, 2, 3, 4].map(i => html `<div class="chart-skeleton-xlabel" style="--d: ${i * 80}ms"></div>`)}
|
|
914
|
+
</div>
|
|
915
|
+
</div>
|
|
916
|
+
`;
|
|
917
|
+
}
|
|
890
918
|
render() {
|
|
919
|
+
const isLoading = this.loading || (this.activeConfig?.loading ?? false);
|
|
891
920
|
return html `
|
|
892
921
|
<div class="chart-card">
|
|
893
922
|
${this.renderHeader()}
|
|
894
923
|
<div class="chart-wrapper">
|
|
895
|
-
<div class="chart-inner">
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
924
|
+
<div class="chart-inner ${this.activeConfig?.type === 'kpi' ? 'chart-inner--kpi' : ''}">
|
|
925
|
+
${isLoading
|
|
926
|
+
? this.renderSkeleton()
|
|
927
|
+
: this.activeConfig
|
|
928
|
+
? this.renderChartContent()
|
|
929
|
+
: html `<slot></slot>`}
|
|
900
930
|
${this.renderAiPanel()}
|
|
901
931
|
</div>
|
|
902
932
|
</div>
|
|
@@ -909,6 +939,9 @@ NileChart.styles = styles;
|
|
|
909
939
|
__decorate([
|
|
910
940
|
property({ type: Object })
|
|
911
941
|
], NileChart.prototype, "config", void 0);
|
|
942
|
+
__decorate([
|
|
943
|
+
property({ type: Boolean, reflect: true })
|
|
944
|
+
], NileChart.prototype, "loading", void 0);
|
|
912
945
|
__decorate([
|
|
913
946
|
property({ type: String, attribute: 'chart-type' })
|
|
914
947
|
], NileChart.prototype, "chartTypeAttr", void 0);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Configuration interface for the nile-executive-summary component.
|
|
9
|
+
* Assign to the component's `config` property via JavaScript.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```js
|
|
13
|
+
* const el = document.querySelector('nile-executive-summary');
|
|
14
|
+
* el.config = {
|
|
15
|
+
* summary: '<p>Q1 revenue grew <strong>23%</strong> YoY...</p>',
|
|
16
|
+
* buttonLabel: 'View Summary',
|
|
17
|
+
* drawerLabel: 'AI Executive Summary',
|
|
18
|
+
* };
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface NileExecutiveSummaryConfig {
|
|
22
|
+
/** Text or HTML string displayed with the typewriter effect inside the drawer. */
|
|
23
|
+
summary: string;
|
|
24
|
+
/** Label rendered on the built-in trigger nile-button. Default: 'Executive Summary' */
|
|
25
|
+
buttonLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Variant forwarded to the internal nile-button. Default: 'primary'
|
|
28
|
+
* Mirrors nile-button's `variant` property.
|
|
29
|
+
*/
|
|
30
|
+
buttonVariant?: 'primary' | 'secondary' | 'tertiary' | 'caution' | 'ghost' | 'destructive' | 'secondary-grey' | 'secondary-blue';
|
|
31
|
+
/** Title displayed in the drawer header. Default: 'Executive Summary' */
|
|
32
|
+
drawerLabel?: string;
|
|
33
|
+
/** Milliseconds per character for the typing animation. Default: 30 */
|
|
34
|
+
typingSpeed?: number;
|
|
35
|
+
/** Close the drawer when the user presses Escape. Default: true */
|
|
36
|
+
closeOnEscape?: boolean;
|
|
37
|
+
/** Prevent the drawer from closing when the overlay is clicked. Default: false */
|
|
38
|
+
preventOverlayClose?: boolean;
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
export declare const styles: import("lit").CSSResult;
|
|
8
|
+
declare const _default: import("lit").CSSResult[];
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { css } from 'lit';
|
|
8
|
+
export const styles = css `
|
|
9
|
+
:host {
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
-webkit-font-smoothing: antialiased;
|
|
12
|
+
-moz-osx-font-smoothing: grayscale;
|
|
13
|
+
text-rendering: optimizeLegibility;
|
|
14
|
+
display: inline-block;
|
|
15
|
+
position: relative;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:host *,
|
|
19
|
+
:host *::before,
|
|
20
|
+
:host *::after {
|
|
21
|
+
box-sizing: inherit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[hidden] {
|
|
25
|
+
display: none !important;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ─── Trigger button ───────────────────────────────────────────────────── */
|
|
29
|
+
|
|
30
|
+
.es-trigger {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: 6px;
|
|
34
|
+
padding: var(--executive-summary-btn-padding, 8px 14px);
|
|
35
|
+
border: none;
|
|
36
|
+
border-radius: var(--executive-summary-btn-radius, var(--nile-radius-base-standard, 4px));
|
|
37
|
+
background-color: var(--executive-summary-btn-bg, var(--nile-colors-primary-600, #0052a3));
|
|
38
|
+
color: var(--executive-summary-btn-color, var(--nile-colors-white-base, #fff));
|
|
39
|
+
font-family: var(--nile-font-family-body, sans-serif);
|
|
40
|
+
font-size: var(--nile-type-scale-3, 14px);
|
|
41
|
+
font-weight: 500;
|
|
42
|
+
line-height: 1.4;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
|
45
|
+
outline: none;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
user-select: none;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.es-trigger:hover {
|
|
51
|
+
background-color: var(--executive-summary-btn-bg-hover, var(--nile-colors-primary-700, #003d7a));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.es-trigger:focus-visible {
|
|
55
|
+
box-shadow: 0 0 0 3px var(--nile-colors-primary-200, rgba(0, 82, 163, 0.3));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.es-trigger:active {
|
|
59
|
+
background-color: var(--executive-summary-btn-bg-active, var(--nile-colors-primary-800, #002e5b));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* ─── Backdrop wrapper (fixed, full viewport) ──────────────────────────── */
|
|
63
|
+
|
|
64
|
+
.es-backdrop {
|
|
65
|
+
position: fixed;
|
|
66
|
+
top: 0;
|
|
67
|
+
left: 0;
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: 100%;
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
z-index: var(--executive-summary-z-index, 700);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ─── Overlay ──────────────────────────────────────────────────────────── */
|
|
76
|
+
|
|
77
|
+
.es-overlay {
|
|
78
|
+
position: fixed;
|
|
79
|
+
top: 0;
|
|
80
|
+
right: 0;
|
|
81
|
+
bottom: 0;
|
|
82
|
+
left: 0;
|
|
83
|
+
background-color: var(--executive-summary-overlay-color, hsl(240 3.8% 46.1% / 33%));
|
|
84
|
+
pointer-events: all;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* ─── Drawer panel ─────────────────────────────────────────────────────── */
|
|
88
|
+
|
|
89
|
+
.es-panel {
|
|
90
|
+
position: absolute;
|
|
91
|
+
top: 0;
|
|
92
|
+
right: 0;
|
|
93
|
+
bottom: 0;
|
|
94
|
+
width: var(--executive-summary-size, 480px);
|
|
95
|
+
max-width: 100%;
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
z-index: 2;
|
|
99
|
+
background-color: var(--nile-colors-white-base, #fff);
|
|
100
|
+
box-shadow: var(
|
|
101
|
+
--executive-summary-shadow,
|
|
102
|
+
var(
|
|
103
|
+
--nile-box-shadow-2,
|
|
104
|
+
0px 20px 24px -4px rgba(16, 24, 40, 0.08),
|
|
105
|
+
0px 8px 8px -4px rgba(16, 24, 40, 0.03)
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
pointer-events: all;
|
|
109
|
+
outline: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ─── Header ───────────────────────────────────────────────────────────── */
|
|
113
|
+
|
|
114
|
+
.es-header {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
padding: var(--executive-summary-header-spacing, 16px 20px);
|
|
119
|
+
border-bottom: 1px solid var(--nile-colors-neutral-100, #e5e7eb);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.es-title {
|
|
123
|
+
flex: 1 1 auto;
|
|
124
|
+
margin: 0;
|
|
125
|
+
font-family: var(--nile-font-family-body, sans-serif);
|
|
126
|
+
font-size: var(--nile-font-size-rem-small, 16px);
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
line-height: 1.4;
|
|
129
|
+
color: var(--nile-colors-dark-900, #111827);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ─── Close button ─────────────────────────────────────────────────────── */
|
|
133
|
+
|
|
134
|
+
.es-close {
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
display: inline-flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
width: 32px;
|
|
140
|
+
height: 32px;
|
|
141
|
+
padding: 0;
|
|
142
|
+
border: none;
|
|
143
|
+
background: none;
|
|
144
|
+
border-radius: var(--nile-radius-sm, 4px);
|
|
145
|
+
cursor: pointer;
|
|
146
|
+
color: var(--nile-colors-dark-900, #374151);
|
|
147
|
+
transition: background-color 0.15s ease;
|
|
148
|
+
margin-left: 8px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.es-close:hover {
|
|
152
|
+
background-color: var(--nile-colors-neutral-50, #f3f4f6);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.es-close:focus-visible {
|
|
156
|
+
outline: 2px solid var(--nile-colors-primary-600, #0052a3);
|
|
157
|
+
outline-offset: 1px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.es-close svg {
|
|
161
|
+
width: 18px;
|
|
162
|
+
height: 18px;
|
|
163
|
+
stroke: currentColor;
|
|
164
|
+
fill: none;
|
|
165
|
+
stroke-width: 2;
|
|
166
|
+
stroke-linecap: round;
|
|
167
|
+
stroke-linejoin: round;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ─── Body ─────────────────────────────────────────────────────────────── */
|
|
171
|
+
|
|
172
|
+
.es-body {
|
|
173
|
+
flex: 1 1 auto;
|
|
174
|
+
overflow-y: auto;
|
|
175
|
+
padding: var(--executive-summary-body-spacing, 20px);
|
|
176
|
+
-webkit-overflow-scrolling: touch;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* ─── Typed text ───────────────────────────────────────────────────────── */
|
|
180
|
+
|
|
181
|
+
.es-typed-text {
|
|
182
|
+
font-family: var(--nile-font-family-body, sans-serif);
|
|
183
|
+
font-size: var(--nile-type-scale-3, 14px);
|
|
184
|
+
line-height: 1.7;
|
|
185
|
+
color: var(--nile-colors-dark-900, #111827);
|
|
186
|
+
word-break: break-word;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@media (forced-colors: active) {
|
|
190
|
+
.es-panel {
|
|
191
|
+
border: 1px solid ButtonText;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`;
|
|
195
|
+
export default [styles];
|
|
196
|
+
//# sourceMappingURL=nile-executive-summary.css.js.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { CSSResultGroup, TemplateResult } from 'lit';
|
|
8
|
+
import NileElement from '../internal/nile-element.js';
|
|
9
|
+
import type { NileExecutiveSummaryConfig } from './nile-executive-summary-config.js';
|
|
10
|
+
/**
|
|
11
|
+
* Nile Executive Summary component.
|
|
12
|
+
*
|
|
13
|
+
* @tag nile-executive-summary
|
|
14
|
+
*
|
|
15
|
+
* @summary A self-contained drawer component with a built-in trigger button.
|
|
16
|
+
* Place it anywhere — clicking the button slides in a right-side drawer that
|
|
17
|
+
* displays summary content (plain text or HTML) with a typewriter animation.
|
|
18
|
+
* Configured entirely via the `config` JS property.
|
|
19
|
+
*
|
|
20
|
+
* @event nile-show - Emitted when the drawer starts opening.
|
|
21
|
+
* @event nile-after-show - Emitted when the drawer is fully open and typing begins.
|
|
22
|
+
* @event nile-hide - Emitted when the drawer starts closing.
|
|
23
|
+
* @event nile-after-hide - Emitted when the drawer is fully closed.
|
|
24
|
+
* @event nile-typing-complete - Emitted when the typewriter animation finishes.
|
|
25
|
+
* @event nile-init - Emitted when the component connects to the DOM.
|
|
26
|
+
* @event nile-destroy - Emitted when the component disconnects from the DOM.
|
|
27
|
+
*
|
|
28
|
+
* @csspart base - Root wrapper (contains trigger + drawer).
|
|
29
|
+
* @csspart trigger - The built-in button that opens the drawer.
|
|
30
|
+
* @csspart overlay - The dim overlay behind the panel.
|
|
31
|
+
* @csspart panel - The drawer panel.
|
|
32
|
+
* @csspart header - The drawer header.
|
|
33
|
+
* @csspart title - The drawer title.
|
|
34
|
+
* @csspart close - The close button.
|
|
35
|
+
* @csspart body - The drawer body containing the typed summary.
|
|
36
|
+
*
|
|
37
|
+
* @cssproperty --executive-summary-size - Drawer width. Default: 480px.
|
|
38
|
+
* @cssproperty --executive-summary-header-spacing - Header padding. Default: 16px 20px.
|
|
39
|
+
* @cssproperty --executive-summary-body-spacing - Body padding. Default: 20px.
|
|
40
|
+
* @cssproperty --executive-summary-z-index - Z-index of the drawer. Default: 700.
|
|
41
|
+
* @cssproperty --executive-summary-overlay-color - Overlay background colour.
|
|
42
|
+
* @cssproperty --executive-summary-btn-bg - Trigger button background.
|
|
43
|
+
* @cssproperty --executive-summary-btn-color - Trigger button text colour.
|
|
44
|
+
* @cssproperty --executive-summary-btn-radius - Trigger button border-radius.
|
|
45
|
+
*/
|
|
46
|
+
export declare class NileExecutiveSummary extends NileElement {
|
|
47
|
+
static styles: CSSResultGroup;
|
|
48
|
+
private typingTimer;
|
|
49
|
+
private keyDownHandler;
|
|
50
|
+
private originalTrigger;
|
|
51
|
+
private scrollLockActive;
|
|
52
|
+
private backdrop;
|
|
53
|
+
private panel;
|
|
54
|
+
private overlay;
|
|
55
|
+
/**
|
|
56
|
+
* Full configuration. Assign via JavaScript — all fields are optional except `summary`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```js
|
|
60
|
+
* el.config = {
|
|
61
|
+
* summary: '<p>Q1 revenue grew <strong>23%</strong>.</p>',
|
|
62
|
+
* buttonLabel: 'View Summary',
|
|
63
|
+
* drawerLabel: 'AI Executive Summary',
|
|
64
|
+
* typingSpeed: 20,
|
|
65
|
+
* };
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
config: NileExecutiveSummaryConfig | null;
|
|
69
|
+
/**
|
|
70
|
+
* Whether the drawer is currently open.
|
|
71
|
+
* Can be set programmatically; use `show()` / `hide()` for animated transitions.
|
|
72
|
+
*/
|
|
73
|
+
open: boolean;
|
|
74
|
+
/** @internal Characters revealed so far during typing. */
|
|
75
|
+
private displayedText;
|
|
76
|
+
/** @internal Whether typing is still in progress (drives cursor visibility). */
|
|
77
|
+
private isTyping;
|
|
78
|
+
connectedCallback(): void;
|
|
79
|
+
firstUpdated(): void;
|
|
80
|
+
disconnectedCallback(): void;
|
|
81
|
+
private addKeyListener;
|
|
82
|
+
private removeKeyListener;
|
|
83
|
+
private handleKeyDown;
|
|
84
|
+
private lockScroll;
|
|
85
|
+
private unlockScroll;
|
|
86
|
+
private animatePanel;
|
|
87
|
+
private animateOverlay;
|
|
88
|
+
private requestClose;
|
|
89
|
+
private startTyping;
|
|
90
|
+
private stopTyping;
|
|
91
|
+
/** Opens the drawer with animation. Resolves when the animation completes. */
|
|
92
|
+
show(): Promise<void>;
|
|
93
|
+
/** Closes the drawer with animation. Resolves when the animation completes. */
|
|
94
|
+
hide(): Promise<void>;
|
|
95
|
+
render(): TemplateResult;
|
|
96
|
+
}
|
|
97
|
+
declare global {
|
|
98
|
+
interface HTMLElementTagNameMap {
|
|
99
|
+
'nile-executive-summary': NileExecutiveSummary;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2023
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { __decorate } from "tslib";
|
|
8
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
9
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
10
|
+
import { html } from 'lit';
|
|
11
|
+
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
12
|
+
import NileElement from '../internal/nile-element.js';
|
|
13
|
+
import { styles } from './nile-executive-summary.css.js';
|
|
14
|
+
/**
|
|
15
|
+
* Nile Executive Summary component.
|
|
16
|
+
*
|
|
17
|
+
* @tag nile-executive-summary
|
|
18
|
+
*
|
|
19
|
+
* @summary A self-contained drawer component with a built-in trigger button.
|
|
20
|
+
* Place it anywhere — clicking the button slides in a right-side drawer that
|
|
21
|
+
* displays summary content (plain text or HTML) with a typewriter animation.
|
|
22
|
+
* Configured entirely via the `config` JS property.
|
|
23
|
+
*
|
|
24
|
+
* @event nile-show - Emitted when the drawer starts opening.
|
|
25
|
+
* @event nile-after-show - Emitted when the drawer is fully open and typing begins.
|
|
26
|
+
* @event nile-hide - Emitted when the drawer starts closing.
|
|
27
|
+
* @event nile-after-hide - Emitted when the drawer is fully closed.
|
|
28
|
+
* @event nile-typing-complete - Emitted when the typewriter animation finishes.
|
|
29
|
+
* @event nile-init - Emitted when the component connects to the DOM.
|
|
30
|
+
* @event nile-destroy - Emitted when the component disconnects from the DOM.
|
|
31
|
+
*
|
|
32
|
+
* @csspart base - Root wrapper (contains trigger + drawer).
|
|
33
|
+
* @csspart trigger - The built-in button that opens the drawer.
|
|
34
|
+
* @csspart overlay - The dim overlay behind the panel.
|
|
35
|
+
* @csspart panel - The drawer panel.
|
|
36
|
+
* @csspart header - The drawer header.
|
|
37
|
+
* @csspart title - The drawer title.
|
|
38
|
+
* @csspart close - The close button.
|
|
39
|
+
* @csspart body - The drawer body containing the typed summary.
|
|
40
|
+
*
|
|
41
|
+
* @cssproperty --executive-summary-size - Drawer width. Default: 480px.
|
|
42
|
+
* @cssproperty --executive-summary-header-spacing - Header padding. Default: 16px 20px.
|
|
43
|
+
* @cssproperty --executive-summary-body-spacing - Body padding. Default: 20px.
|
|
44
|
+
* @cssproperty --executive-summary-z-index - Z-index of the drawer. Default: 700.
|
|
45
|
+
* @cssproperty --executive-summary-overlay-color - Overlay background colour.
|
|
46
|
+
* @cssproperty --executive-summary-btn-bg - Trigger button background.
|
|
47
|
+
* @cssproperty --executive-summary-btn-color - Trigger button text colour.
|
|
48
|
+
* @cssproperty --executive-summary-btn-radius - Trigger button border-radius.
|
|
49
|
+
*/
|
|
50
|
+
let NileExecutiveSummary = class NileExecutiveSummary extends NileElement {
|
|
51
|
+
constructor() {
|
|
52
|
+
super(...arguments);
|
|
53
|
+
this.typingTimer = null;
|
|
54
|
+
this.originalTrigger = null;
|
|
55
|
+
this.scrollLockActive = false;
|
|
56
|
+
/**
|
|
57
|
+
* Full configuration. Assign via JavaScript — all fields are optional except `summary`.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```js
|
|
61
|
+
* el.config = {
|
|
62
|
+
* summary: '<p>Q1 revenue grew <strong>23%</strong>.</p>',
|
|
63
|
+
* buttonLabel: 'View Summary',
|
|
64
|
+
* drawerLabel: 'AI Executive Summary',
|
|
65
|
+
* typingSpeed: 20,
|
|
66
|
+
* };
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
this.config = null;
|
|
70
|
+
/**
|
|
71
|
+
* Whether the drawer is currently open.
|
|
72
|
+
* Can be set programmatically; use `show()` / `hide()` for animated transitions.
|
|
73
|
+
*/
|
|
74
|
+
this.open = false;
|
|
75
|
+
/** @internal Characters revealed so far during typing. */
|
|
76
|
+
this.displayedText = '';
|
|
77
|
+
/** @internal Whether typing is still in progress (drives cursor visibility). */
|
|
78
|
+
this.isTyping = false;
|
|
79
|
+
}
|
|
80
|
+
// ─── Lifecycle ─────────────────────────────────────────────────────────────
|
|
81
|
+
connectedCallback() {
|
|
82
|
+
super.connectedCallback();
|
|
83
|
+
this.keyDownHandler = this.handleKeyDown.bind(this);
|
|
84
|
+
this.emit('nile-init');
|
|
85
|
+
}
|
|
86
|
+
firstUpdated() {
|
|
87
|
+
this.backdrop.hidden = !this.open;
|
|
88
|
+
}
|
|
89
|
+
disconnectedCallback() {
|
|
90
|
+
super.disconnectedCallback();
|
|
91
|
+
this.stopTyping();
|
|
92
|
+
this.removeKeyListener();
|
|
93
|
+
this.unlockScroll();
|
|
94
|
+
this.emit('nile-destroy');
|
|
95
|
+
}
|
|
96
|
+
// ─── Keyboard / scroll ─────────────────────────────────────────────────────
|
|
97
|
+
addKeyListener() {
|
|
98
|
+
if (this.config?.closeOnEscape ?? true) {
|
|
99
|
+
document.addEventListener('keydown', this.keyDownHandler);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
removeKeyListener() {
|
|
103
|
+
document.removeEventListener('keydown', this.keyDownHandler);
|
|
104
|
+
}
|
|
105
|
+
handleKeyDown(e) {
|
|
106
|
+
if (this.open && e.key === 'Escape') {
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
this.requestClose('keyboard');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
lockScroll() {
|
|
112
|
+
if (!this.scrollLockActive) {
|
|
113
|
+
document.body.style.overflow = 'hidden';
|
|
114
|
+
this.scrollLockActive = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
unlockScroll() {
|
|
118
|
+
if (this.scrollLockActive) {
|
|
119
|
+
document.body.style.overflow = '';
|
|
120
|
+
this.scrollLockActive = false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ─── Animation helpers ──────────────────────────────────────────────────────
|
|
124
|
+
async animatePanel(show) {
|
|
125
|
+
const from = show ? { opacity: '0', translate: '100%' } : { opacity: '1', translate: '0' };
|
|
126
|
+
const to = show ? { opacity: '1', translate: '0' } : { opacity: '0', translate: '100%' };
|
|
127
|
+
await this.panel.animate([from, to], { duration: 250, easing: 'ease', fill: 'forwards' }).finished;
|
|
128
|
+
}
|
|
129
|
+
async animateOverlay(show) {
|
|
130
|
+
const from = show ? { opacity: '0' } : { opacity: '1' };
|
|
131
|
+
const to = show ? { opacity: '1' } : { opacity: '0' };
|
|
132
|
+
await this.overlay.animate([from, to], { duration: 250, fill: 'forwards' }).finished;
|
|
133
|
+
}
|
|
134
|
+
// ─── Close request ──────────────────────────────────────────────────────────
|
|
135
|
+
requestClose(source) {
|
|
136
|
+
const preventOverlayClose = this.config?.preventOverlayClose ?? false;
|
|
137
|
+
if ((source === 'overlay' || source === 'keyboard') && preventOverlayClose)
|
|
138
|
+
return;
|
|
139
|
+
const ev = this.emit('nile-request-close', { cancelable: true, detail: { source } });
|
|
140
|
+
if (ev.defaultPrevented) {
|
|
141
|
+
// Deny-close jiggle
|
|
142
|
+
this.panel.animate([{ scale: '1' }, { scale: '1.01' }, { scale: '1' }], { duration: 250 });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.hide();
|
|
146
|
+
}
|
|
147
|
+
// ─── Typing animation ───────────────────────────────────────────────────────
|
|
148
|
+
startTyping() {
|
|
149
|
+
this.stopTyping();
|
|
150
|
+
this.displayedText = '';
|
|
151
|
+
this.isTyping = true;
|
|
152
|
+
const text = this.config?.summary ?? '';
|
|
153
|
+
const speed = this.config?.typingSpeed ?? 30;
|
|
154
|
+
let index = 0;
|
|
155
|
+
const tick = () => {
|
|
156
|
+
if (index < text.length) {
|
|
157
|
+
this.displayedText += text[index++];
|
|
158
|
+
this.typingTimer = setTimeout(tick, speed);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this.isTyping = false;
|
|
162
|
+
this.emit('nile-typing-complete');
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
tick();
|
|
166
|
+
}
|
|
167
|
+
stopTyping() {
|
|
168
|
+
if (this.typingTimer !== null) {
|
|
169
|
+
clearTimeout(this.typingTimer);
|
|
170
|
+
this.typingTimer = null;
|
|
171
|
+
}
|
|
172
|
+
this.isTyping = false;
|
|
173
|
+
}
|
|
174
|
+
// ─── Open / close ───────────────────────────────────────────────────────────
|
|
175
|
+
/** Opens the drawer with animation. Resolves when the animation completes. */
|
|
176
|
+
async show() {
|
|
177
|
+
if (this.open)
|
|
178
|
+
return;
|
|
179
|
+
this.open = true;
|
|
180
|
+
this.emit('nile-show');
|
|
181
|
+
this.addKeyListener();
|
|
182
|
+
this.lockScroll();
|
|
183
|
+
this.originalTrigger = document.activeElement;
|
|
184
|
+
this.backdrop.hidden = false;
|
|
185
|
+
await Promise.all([this.animatePanel(true), this.animateOverlay(true)]);
|
|
186
|
+
requestAnimationFrame(() => this.panel.focus({ preventScroll: true }));
|
|
187
|
+
this.emit('nile-after-show');
|
|
188
|
+
this.startTyping();
|
|
189
|
+
}
|
|
190
|
+
/** Closes the drawer with animation. Resolves when the animation completes. */
|
|
191
|
+
async hide() {
|
|
192
|
+
if (!this.open)
|
|
193
|
+
return;
|
|
194
|
+
this.open = false;
|
|
195
|
+
this.emit('nile-hide');
|
|
196
|
+
this.removeKeyListener();
|
|
197
|
+
this.stopTyping();
|
|
198
|
+
this.displayedText = '';
|
|
199
|
+
this.unlockScroll();
|
|
200
|
+
await Promise.all([this.animatePanel(false), this.animateOverlay(false)]);
|
|
201
|
+
this.backdrop.hidden = true;
|
|
202
|
+
// Reset animation fill state for next open
|
|
203
|
+
this.panel.getAnimations().forEach(a => a.cancel());
|
|
204
|
+
this.overlay.getAnimations().forEach(a => a.cancel());
|
|
205
|
+
// Restore focus to original trigger
|
|
206
|
+
const trigger = this.originalTrigger;
|
|
207
|
+
if (typeof trigger?.focus === 'function')
|
|
208
|
+
setTimeout(() => trigger.focus());
|
|
209
|
+
this.emit('nile-after-hide');
|
|
210
|
+
}
|
|
211
|
+
// ─── Render ─────────────────────────────────────────────────────────────────
|
|
212
|
+
render() {
|
|
213
|
+
const buttonLabel = this.config?.buttonLabel ?? 'Executive Summary';
|
|
214
|
+
const buttonVariant = this.config?.buttonVariant ?? 'primary';
|
|
215
|
+
const drawerLabel = this.config?.drawerLabel ?? 'Executive Summary';
|
|
216
|
+
return html `
|
|
217
|
+
<div part="base">
|
|
218
|
+
|
|
219
|
+
<!-- Built-in trigger — renders wherever the element is placed -->
|
|
220
|
+
<nile-button
|
|
221
|
+
part="trigger"
|
|
222
|
+
type="button"
|
|
223
|
+
variant=${buttonVariant}
|
|
224
|
+
aria-haspopup="dialog"
|
|
225
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
226
|
+
@click=${this.show}
|
|
227
|
+
>${buttonLabel}</nile-button>
|
|
228
|
+
|
|
229
|
+
<!-- Fixed backdrop: overlay + panel -->
|
|
230
|
+
<div class="es-backdrop">
|
|
231
|
+
|
|
232
|
+
<div
|
|
233
|
+
part="overlay"
|
|
234
|
+
class="es-overlay"
|
|
235
|
+
@click=${() => this.requestClose('overlay')}
|
|
236
|
+
tabindex="-1"
|
|
237
|
+
></div>
|
|
238
|
+
|
|
239
|
+
<div
|
|
240
|
+
part="panel"
|
|
241
|
+
class="es-panel"
|
|
242
|
+
role="dialog"
|
|
243
|
+
aria-modal="true"
|
|
244
|
+
aria-hidden=${this.open ? 'false' : 'true'}
|
|
245
|
+
aria-labelledby="es-title"
|
|
246
|
+
tabindex="0"
|
|
247
|
+
>
|
|
248
|
+
<header part="header" class="es-header">
|
|
249
|
+
<h2 part="title" class="es-title" id="es-title">${drawerLabel}</h2>
|
|
250
|
+
|
|
251
|
+
<button
|
|
252
|
+
part="close"
|
|
253
|
+
class="es-close"
|
|
254
|
+
type="button"
|
|
255
|
+
aria-label="Close"
|
|
256
|
+
@click=${() => this.requestClose('close-button')}
|
|
257
|
+
>
|
|
258
|
+
<!-- X icon (inline SVG — no icon lib dependency) -->
|
|
259
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
260
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
261
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
262
|
+
</svg>
|
|
263
|
+
</button>
|
|
264
|
+
</header>
|
|
265
|
+
|
|
266
|
+
<div part="body" class="es-body">
|
|
267
|
+
<div
|
|
268
|
+
class=${classMap({
|
|
269
|
+
'es-typed-text': true,
|
|
270
|
+
'es-typed-text--typing': this.isTyping,
|
|
271
|
+
})}
|
|
272
|
+
>${unsafeHTML(this.displayedText)}</div>
|
|
273
|
+
<slot></slot>
|
|
274
|
+
</div>
|
|
275
|
+
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
NileExecutiveSummary.styles = styles;
|
|
283
|
+
__decorate([
|
|
284
|
+
query('.es-backdrop')
|
|
285
|
+
], NileExecutiveSummary.prototype, "backdrop", void 0);
|
|
286
|
+
__decorate([
|
|
287
|
+
query('.es-panel')
|
|
288
|
+
], NileExecutiveSummary.prototype, "panel", void 0);
|
|
289
|
+
__decorate([
|
|
290
|
+
query('.es-overlay')
|
|
291
|
+
], NileExecutiveSummary.prototype, "overlay", void 0);
|
|
292
|
+
__decorate([
|
|
293
|
+
property({ type: Object })
|
|
294
|
+
], NileExecutiveSummary.prototype, "config", void 0);
|
|
295
|
+
__decorate([
|
|
296
|
+
property({ type: Boolean, reflect: true })
|
|
297
|
+
], NileExecutiveSummary.prototype, "open", void 0);
|
|
298
|
+
__decorate([
|
|
299
|
+
state()
|
|
300
|
+
], NileExecutiveSummary.prototype, "displayedText", void 0);
|
|
301
|
+
__decorate([
|
|
302
|
+
state()
|
|
303
|
+
], NileExecutiveSummary.prototype, "isTyping", void 0);
|
|
304
|
+
NileExecutiveSummary = __decorate([
|
|
305
|
+
customElement('nile-executive-summary')
|
|
306
|
+
], NileExecutiveSummary);
|
|
307
|
+
export { NileExecutiveSummary };
|
|
308
|
+
//# sourceMappingURL=nile-executive-summary.js.map
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global tooltip styles — injected once into document.head so the tooltip
|
|
3
|
+
* element on document.body is styled independently of the shadow DOM.
|
|
4
|
+
*/
|
|
5
|
+
export declare const tooltipCss = "\n .nile-kpi-tooltip {\n position: fixed;\n display: none;\n transform: translate(-50%, calc(-100% - 10px));\n background: #1D2939;\n color: #fff;\n font-family: system-ui, sans-serif;\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n padding: 5px 10px;\n border-radius: 6px;\n pointer-events: none;\n white-space: nowrap;\n z-index: 2147483647;\n box-shadow: 0 4px 12px rgba(16, 24, 40, 0.22);\n }\n\n .nile-kpi-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:rgb(54, 74, 99);\n }\n";
|
|
1
6
|
export declare const styles: import("lit").CSSResult;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* Global tooltip styles — injected once into document.head so the tooltip
|
|
4
|
+
* element on document.body is styled independently of the shadow DOM.
|
|
5
|
+
*/
|
|
6
|
+
export const tooltipCss = `
|
|
7
|
+
.nile-kpi-tooltip {
|
|
8
|
+
position: fixed;
|
|
9
|
+
display: none;
|
|
10
|
+
transform: translate(-50%, calc(-100% - 10px));
|
|
11
|
+
background: #1D2939;
|
|
12
|
+
color: #fff;
|
|
13
|
+
font-family: system-ui, sans-serif;
|
|
14
|
+
font-size: 12px;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
line-height: 1.4;
|
|
17
|
+
padding: 5px 10px;
|
|
18
|
+
border-radius: 6px;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
white-space: nowrap;
|
|
21
|
+
z-index: 2147483647;
|
|
22
|
+
box-shadow: 0 4px 12px rgba(16, 24, 40, 0.22);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.nile-kpi-tooltip::after {
|
|
26
|
+
content: '';
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: 100%;
|
|
29
|
+
left: 50%;
|
|
30
|
+
transform: translateX(-50%);
|
|
31
|
+
border: 5px solid transparent;
|
|
32
|
+
border-top-color:rgb(54, 74, 99);
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
2
35
|
export const styles = css `
|
|
3
36
|
:host {
|
|
4
37
|
display: flex;
|
|
@@ -29,7 +62,6 @@ export const styles = css `
|
|
|
29
62
|
|
|
30
63
|
.kpi {
|
|
31
64
|
flex: 1 1 auto;
|
|
32
|
-
|
|
33
65
|
display: flex;
|
|
34
66
|
flex-direction: column;
|
|
35
67
|
gap: var(--nile-spacing-md, var(--ng-spacing-md));
|
|
@@ -59,6 +91,7 @@ export const styles = css `
|
|
|
59
91
|
font-weight: var(--nile-font-weight-semi-bold, var(--ng-font-weight-semibold));
|
|
60
92
|
color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
|
|
61
93
|
line-height: 1.2;
|
|
94
|
+
cursor: default;
|
|
62
95
|
}
|
|
63
96
|
|
|
64
97
|
.kpi-prefix,
|
|
@@ -51,6 +51,8 @@ export declare class NileKpiChart extends NileElement {
|
|
|
51
51
|
private sparklineChart;
|
|
52
52
|
private gaugeChart;
|
|
53
53
|
private resizeObserver;
|
|
54
|
+
/** Tooltip element on document.body — outside shadow DOM, never clipped. */
|
|
55
|
+
private _tipEl;
|
|
54
56
|
private sparklineContainer;
|
|
55
57
|
private gaugeContainer;
|
|
56
58
|
/** Full configuration: `{ chart, aq }` (same convention as other Nile charts). */
|
|
@@ -93,8 +95,14 @@ export declare class NileKpiChart extends NileElement {
|
|
|
93
95
|
* Set by nile-chart: skip host border/shadow (variant card/gauge) so the parent chart-card is the only frame.
|
|
94
96
|
*/
|
|
95
97
|
embedInNileChart: boolean;
|
|
98
|
+
private _createTipEl;
|
|
99
|
+
private _showTip;
|
|
100
|
+
private _hideTip;
|
|
96
101
|
private formatCssLength;
|
|
97
|
-
|
|
102
|
+
private parseNumericValue;
|
|
103
|
+
private formatTooltipNumber;
|
|
104
|
+
private inferSparklineTooltipScale;
|
|
105
|
+
private getTooltipContent;
|
|
98
106
|
private applyConfig;
|
|
99
107
|
connectedCallback(): void;
|
|
100
108
|
disconnectedCallback(): void;
|
|
@@ -102,6 +110,8 @@ export declare class NileKpiChart extends NileElement {
|
|
|
102
110
|
protected updated(changedProperties: PropertyValues): void;
|
|
103
111
|
private syncSparklineChartSize;
|
|
104
112
|
private setupResizeObserver;
|
|
113
|
+
private _onSparklineMouseMove;
|
|
114
|
+
private _onSparklineMouseLeave;
|
|
105
115
|
private buildSparklineOptions;
|
|
106
116
|
private buildGaugeOptions;
|
|
107
117
|
private initSparkline;
|
|
@@ -109,8 +119,10 @@ export declare class NileKpiChart extends NileElement {
|
|
|
109
119
|
private destroySparkline;
|
|
110
120
|
private destroyGauge;
|
|
111
121
|
private destroyCharts;
|
|
122
|
+
private _onValueEnter;
|
|
123
|
+
private _onGaugeEnter;
|
|
124
|
+
private _onTipLeave;
|
|
112
125
|
private renderTrend;
|
|
113
|
-
private renderContent;
|
|
114
126
|
render(): TemplateResult;
|
|
115
127
|
}
|
|
116
128
|
declare global {
|
|
@@ -3,7 +3,18 @@ import { customElement, property, query } from 'lit/decorators.js';
|
|
|
3
3
|
import { html, nothing } from 'lit';
|
|
4
4
|
import NileElement from '../internal/nile-element.js';
|
|
5
5
|
import { getHighcharts } from '../internal/highcharts-provider.js';
|
|
6
|
-
import { styles } from './nile-kpi-chart.css.js';
|
|
6
|
+
import { styles, tooltipCss } from './nile-kpi-chart.css.js';
|
|
7
|
+
// Inject tooltip styles into document.head once per page load.
|
|
8
|
+
let _tooltipStylesInjected = false;
|
|
9
|
+
function ensureTooltipStyles() {
|
|
10
|
+
if (_tooltipStylesInjected || typeof document === 'undefined')
|
|
11
|
+
return;
|
|
12
|
+
const style = document.createElement('style');
|
|
13
|
+
style.dataset['nilekpichart'] = '';
|
|
14
|
+
style.textContent = tooltipCss;
|
|
15
|
+
document.head.appendChild(style);
|
|
16
|
+
_tooltipStylesInjected = true;
|
|
17
|
+
}
|
|
7
18
|
let NileKpiChart = class NileKpiChart extends NileElement {
|
|
8
19
|
constructor() {
|
|
9
20
|
super(...arguments);
|
|
@@ -11,6 +22,8 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
11
22
|
this.sparklineChart = null;
|
|
12
23
|
this.gaugeChart = null;
|
|
13
24
|
this.resizeObserver = null;
|
|
25
|
+
/** Tooltip element on document.body — outside shadow DOM, never clipped. */
|
|
26
|
+
this._tipEl = null;
|
|
14
27
|
/** Full configuration: `{ chart, aq }` (same convention as other Nile charts). */
|
|
15
28
|
this.config = null;
|
|
16
29
|
/** Display variant: default (flat), card (bordered container), gauge (Highcharts solid gauge). */
|
|
@@ -51,7 +64,70 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
51
64
|
* Set by nile-chart: skip host border/shadow (variant card/gauge) so the parent chart-card is the only frame.
|
|
52
65
|
*/
|
|
53
66
|
this.embedInNileChart = false;
|
|
67
|
+
// ── Sparkline mousemove tooltip ──────────────────────────────────────────
|
|
68
|
+
this._onSparklineMouseMove = (e) => {
|
|
69
|
+
const chart = this.sparklineChart;
|
|
70
|
+
if (!chart || !this.sparklineContainer)
|
|
71
|
+
return;
|
|
72
|
+
const series = chart.series[0];
|
|
73
|
+
if (!series?.points?.length)
|
|
74
|
+
return;
|
|
75
|
+
const rect = this.sparklineContainer.getBoundingClientRect();
|
|
76
|
+
const mouseXInPlot = e.clientX - rect.left - (chart.plotLeft ?? 0);
|
|
77
|
+
// Snap to nearest point by plotX
|
|
78
|
+
let nearest = series.points[0];
|
|
79
|
+
let minDist = Infinity;
|
|
80
|
+
for (const p of series.points) {
|
|
81
|
+
const dist = Math.abs((p.plotX ?? 0) - mouseXInPlot);
|
|
82
|
+
if (dist < minDist) {
|
|
83
|
+
minDist = dist;
|
|
84
|
+
nearest = p;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const scale = this.inferSparklineTooltipScale();
|
|
88
|
+
const text = this.getTooltipContent((nearest.y ?? 0) * scale);
|
|
89
|
+
const tipX = rect.left + (chart.plotLeft ?? 0) + (nearest.plotX ?? 0);
|
|
90
|
+
const tipY = rect.top + (chart.plotTop ?? 0) + (nearest.plotY ?? 0);
|
|
91
|
+
this._showTip(text, tipX, tipY);
|
|
92
|
+
};
|
|
93
|
+
this._onSparklineMouseLeave = () => {
|
|
94
|
+
this._hideTip();
|
|
95
|
+
};
|
|
96
|
+
// ── Value / Gauge hover handlers ─────────────────────────────────────────
|
|
97
|
+
this._onValueEnter = (e) => {
|
|
98
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
99
|
+
this._showTip(this.getTooltipContent(), rect.left + rect.width / 2, rect.top);
|
|
100
|
+
};
|
|
101
|
+
this._onGaugeEnter = (e) => {
|
|
102
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
103
|
+
this._showTip(this.getTooltipContent(), rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
104
|
+
};
|
|
105
|
+
this._onTipLeave = () => {
|
|
106
|
+
this._hideTip();
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// ── Tooltip ──────────────────────────────────────────────────────────────
|
|
110
|
+
_createTipEl() {
|
|
111
|
+
if (this._tipEl)
|
|
112
|
+
return;
|
|
113
|
+
const el = document.createElement('div');
|
|
114
|
+
el.className = 'nile-kpi-tooltip';
|
|
115
|
+
document.body.appendChild(el);
|
|
116
|
+
this._tipEl = el;
|
|
117
|
+
}
|
|
118
|
+
_showTip(text, x, y) {
|
|
119
|
+
if (!this._tipEl)
|
|
120
|
+
return;
|
|
121
|
+
this._tipEl.textContent = text;
|
|
122
|
+
this._tipEl.style.left = `${x}px`;
|
|
123
|
+
this._tipEl.style.top = `${y}px`;
|
|
124
|
+
this._tipEl.style.display = 'block';
|
|
125
|
+
}
|
|
126
|
+
_hideTip() {
|
|
127
|
+
if (this._tipEl)
|
|
128
|
+
this._tipEl.style.display = 'none';
|
|
54
129
|
}
|
|
130
|
+
// ── Formatting helpers ───────────────────────────────────────────────────
|
|
55
131
|
formatCssLength(value) {
|
|
56
132
|
if (value == null)
|
|
57
133
|
return null;
|
|
@@ -65,7 +141,52 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
65
141
|
return `${s}px`;
|
|
66
142
|
return s;
|
|
67
143
|
}
|
|
68
|
-
|
|
144
|
+
parseNumericValue(v) {
|
|
145
|
+
if (typeof v === 'number')
|
|
146
|
+
return Number.isFinite(v) ? v : null;
|
|
147
|
+
if (typeof v !== 'string')
|
|
148
|
+
return null;
|
|
149
|
+
const cleaned = v.replace(/,/g, '').trim();
|
|
150
|
+
if (!cleaned)
|
|
151
|
+
return null;
|
|
152
|
+
const n = Number(cleaned);
|
|
153
|
+
return Number.isFinite(n) ? n : null;
|
|
154
|
+
}
|
|
155
|
+
formatTooltipNumber(n) {
|
|
156
|
+
const maxFractionDigits = Number.isInteger(n) ? 0 : 6;
|
|
157
|
+
return new Intl.NumberFormat(undefined, {
|
|
158
|
+
useGrouping: true,
|
|
159
|
+
minimumFractionDigits: 0,
|
|
160
|
+
maximumFractionDigits: maxFractionDigits,
|
|
161
|
+
}).format(n);
|
|
162
|
+
}
|
|
163
|
+
inferSparklineTooltipScale() {
|
|
164
|
+
if (!this.sparkline?.length)
|
|
165
|
+
return 1;
|
|
166
|
+
const main = this.parseNumericValue(this.value);
|
|
167
|
+
const last = this.sparkline[this.sparkline.length - 1];
|
|
168
|
+
if (main == null || !Number.isFinite(last) || last === 0)
|
|
169
|
+
return 1;
|
|
170
|
+
const ratio = Math.abs(main / last);
|
|
171
|
+
const candidates = [1000, 1000000];
|
|
172
|
+
for (const c of candidates) {
|
|
173
|
+
if (Math.abs(ratio - c) / c < 0.02)
|
|
174
|
+
return c;
|
|
175
|
+
}
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
getTooltipContent(overrideNumeric) {
|
|
179
|
+
const prefix = this.prefix ?? '';
|
|
180
|
+
const suffix = this.suffix ?? '';
|
|
181
|
+
const numeric = overrideNumeric ?? this.parseNumericValue(this.value) ??
|
|
182
|
+
(this.variant === 'gauge' ? this.gaugeValue : null) ??
|
|
183
|
+
(this.sparkline.length ? this.sparkline[this.sparkline.length - 1] : null);
|
|
184
|
+
const valueText = numeric == null
|
|
185
|
+
? String(this.value ?? '').trim()
|
|
186
|
+
: this.formatTooltipNumber(numeric);
|
|
187
|
+
return `${prefix}${valueText}${suffix}`.trim();
|
|
188
|
+
}
|
|
189
|
+
// ── Config ───────────────────────────────────────────────────────────────
|
|
69
190
|
applyConfig(cfg) {
|
|
70
191
|
const { chart: c, aq } = cfg;
|
|
71
192
|
if (c) {
|
|
@@ -106,7 +227,6 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
106
227
|
if ('height' in c) {
|
|
107
228
|
const h = this.formatCssLength(c.height);
|
|
108
229
|
if (h) {
|
|
109
|
-
/* min-height only: host can grow with content; sparkline shrinks via flex + setSize */
|
|
110
230
|
this.style.minHeight = h;
|
|
111
231
|
this.style.removeProperty('height');
|
|
112
232
|
this.style.removeProperty('max-height');
|
|
@@ -125,13 +245,19 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
125
245
|
this.description = aq.chartSubtitle;
|
|
126
246
|
}
|
|
127
247
|
}
|
|
248
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
128
249
|
connectedCallback() {
|
|
129
250
|
super.connectedCallback();
|
|
251
|
+
ensureTooltipStyles();
|
|
252
|
+
this._createTipEl();
|
|
130
253
|
if (this.config)
|
|
131
254
|
this.applyConfig(this.config);
|
|
132
255
|
}
|
|
133
256
|
disconnectedCallback() {
|
|
134
257
|
super.disconnectedCallback();
|
|
258
|
+
this._hideTip();
|
|
259
|
+
this._tipEl?.remove();
|
|
260
|
+
this._tipEl = null;
|
|
135
261
|
this.destroyCharts();
|
|
136
262
|
this.resizeObserver?.disconnect();
|
|
137
263
|
this.resizeObserver = null;
|
|
@@ -166,6 +292,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
166
292
|
}
|
|
167
293
|
}
|
|
168
294
|
}
|
|
295
|
+
// ── Chart sizing ─────────────────────────────────────────────────────────
|
|
169
296
|
syncSparklineChartSize() {
|
|
170
297
|
if (!this.sparklineChart || !this.sparklineContainer)
|
|
171
298
|
return;
|
|
@@ -183,6 +310,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
183
310
|
if (this.gaugeContainer)
|
|
184
311
|
this.resizeObserver.observe(this.gaugeContainer);
|
|
185
312
|
}
|
|
313
|
+
// ── Chart options ────────────────────────────────────────────────────────
|
|
186
314
|
buildSparklineOptions() {
|
|
187
315
|
const brandColor = this.sparklineColor || '#005EA6';
|
|
188
316
|
const defaults = {
|
|
@@ -278,6 +406,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
278
406
|
...this.options,
|
|
279
407
|
};
|
|
280
408
|
}
|
|
409
|
+
// ── Chart init/destroy ───────────────────────────────────────────────────
|
|
281
410
|
async initSparkline() {
|
|
282
411
|
if (!this.sparkline.length || !this.sparklineContainer)
|
|
283
412
|
return;
|
|
@@ -286,6 +415,8 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
286
415
|
this.destroySparkline();
|
|
287
416
|
this.sparklineChart = this._hc.chart(this.sparklineContainer, this.buildSparklineOptions());
|
|
288
417
|
requestAnimationFrame(() => this.syncSparklineChartSize());
|
|
418
|
+
this.sparklineContainer.addEventListener('mousemove', this._onSparklineMouseMove);
|
|
419
|
+
this.sparklineContainer.addEventListener('mouseleave', this._onSparklineMouseLeave);
|
|
289
420
|
this.emit('nile-chart-ready', { chart: this.sparklineChart });
|
|
290
421
|
}
|
|
291
422
|
async initGauge() {
|
|
@@ -298,6 +429,10 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
298
429
|
this.emit('nile-chart-ready', { chart: this.gaugeChart });
|
|
299
430
|
}
|
|
300
431
|
destroySparkline() {
|
|
432
|
+
if (this.sparklineContainer) {
|
|
433
|
+
this.sparklineContainer.removeEventListener('mousemove', this._onSparklineMouseMove);
|
|
434
|
+
this.sparklineContainer.removeEventListener('mouseleave', this._onSparklineMouseLeave);
|
|
435
|
+
}
|
|
301
436
|
if (this.sparklineChart) {
|
|
302
437
|
this.sparklineChart.destroy();
|
|
303
438
|
this.sparklineChart = null;
|
|
@@ -313,6 +448,7 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
313
448
|
this.destroySparkline();
|
|
314
449
|
this.destroyGauge();
|
|
315
450
|
}
|
|
451
|
+
// ── Render ───────────────────────────────────────────────────────────────
|
|
316
452
|
renderTrend() {
|
|
317
453
|
if (this.trendValue === null)
|
|
318
454
|
return nothing;
|
|
@@ -331,18 +467,37 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
331
467
|
</span>
|
|
332
468
|
`;
|
|
333
469
|
}
|
|
334
|
-
|
|
470
|
+
render() {
|
|
471
|
+
if (this.loading) {
|
|
472
|
+
return html `<div class="chart-loading">Loading...</div>`;
|
|
473
|
+
}
|
|
335
474
|
const isGauge = this.variant === 'gauge';
|
|
336
475
|
return html `
|
|
337
476
|
<div class="kpi ${isGauge ? 'kpi--gauge' : ''}">
|
|
338
477
|
${this.label ? html `<p class="kpi-label">${this.label}</p>` : nothing}
|
|
339
478
|
|
|
340
|
-
${isGauge
|
|
479
|
+
${isGauge
|
|
480
|
+
? html `
|
|
481
|
+
<div
|
|
482
|
+
class="kpi-gauge-container"
|
|
483
|
+
@mouseenter=${this._onGaugeEnter}
|
|
484
|
+
@mouseleave=${this._onTipLeave}
|
|
485
|
+
></div>
|
|
486
|
+
`
|
|
487
|
+
: nothing}
|
|
341
488
|
|
|
342
489
|
<div class="kpi-value-row">
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
490
|
+
${!isGauge
|
|
491
|
+
? html `
|
|
492
|
+
<h2
|
|
493
|
+
class="kpi-value"
|
|
494
|
+
@mouseenter=${this._onValueEnter}
|
|
495
|
+
@mouseleave=${this._onTipLeave}
|
|
496
|
+
>
|
|
497
|
+
${this.prefix ? html `<span class="kpi-prefix">${this.prefix}</span>` : nothing}${this.value}${this.suffix ? html `<span class="kpi-suffix">${this.suffix}</span>` : nothing}
|
|
498
|
+
</h2>
|
|
499
|
+
`
|
|
500
|
+
: nothing}
|
|
346
501
|
${!isGauge ? this.renderTrend() : nothing}
|
|
347
502
|
</div>
|
|
348
503
|
|
|
@@ -356,13 +511,6 @@ let NileKpiChart = class NileKpiChart extends NileElement {
|
|
|
356
511
|
</div>
|
|
357
512
|
`;
|
|
358
513
|
}
|
|
359
|
-
render() {
|
|
360
|
-
if (this.loading) {
|
|
361
|
-
return html `<div class="chart-loading">Loading...</div>`;
|
|
362
|
-
}
|
|
363
|
-
/* Same DOM as inside nile-chart: one surface; card/gauge chrome is on :host when standalone. */
|
|
364
|
-
return this.renderContent();
|
|
365
|
-
}
|
|
366
514
|
};
|
|
367
515
|
NileKpiChart.styles = styles;
|
|
368
516
|
__decorate([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aquera/nile-visualization",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A visualization Library for the Nile Design System",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aquera Inc",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"./nile-kpi-chart": "./dist/src/nile-kpi-chart/index.js",
|
|
43
43
|
"./nile-chart": "./dist/src/nile-chart/index.js",
|
|
44
44
|
"./nile-widget-viewer": "./dist/src/nile-widget-viewer/index.js",
|
|
45
|
-
"./nile-dashboard-viewer": "./dist/src/nile-dashboard-viewer/index.js"
|
|
45
|
+
"./nile-dashboard-viewer": "./dist/src/nile-dashboard-viewer/index.js",
|
|
46
|
+
"./nile-executive-summary": "./dist/src/nile-executive-summary/index.js"
|
|
46
47
|
},
|
|
47
48
|
"files": [
|
|
48
49
|
"dist/src/**/*.js",
|