@aquera/nile-visualization 2.3.0 → 2.5.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.
@@ -1584,6 +1584,7 @@ let NileChart = class NileChart extends NileElement {
1584
1584
  const kpiOptions = deepMerge(k.options ?? {}, this.buildExportingOptions());
1585
1585
  return html `<nile-kpi-chart
1586
1586
  embed-in-nile-chart
1587
+ ?fit=${this.hasAttribute('fit')}
1587
1588
  .config=${{
1588
1589
  chart: {
1589
1590
  type: 'kpi',
@@ -1642,7 +1643,7 @@ let NileChart = class NileChart extends NileElement {
1642
1643
  tooltipEnabled: k.tooltipEnabled,
1643
1644
  loading: k.loading,
1644
1645
  options: kpiOptions,
1645
- height: k.height,
1646
+ height: this.hasAttribute('fit') ? undefined : k.height,
1646
1647
  valueFormat: k.valueFormat,
1647
1648
  precision: k.precision,
1648
1649
  unit: k.unit,
@@ -357,5 +357,73 @@ export const styles = css `
357
357
  font-size: var(--nile-type-scale-4, var(--ng-font-size-text-md));
358
358
  line-height: 1;
359
359
  }
360
+
361
+
362
+ .fc-prompt {
363
+ position: relative;
364
+ display: block;
365
+ width: 100%;
366
+ border-radius: var(--nile-radius-radius-2xl, var(--ng-radius-lg));
367
+ padding: 2px;
368
+ background: var(
369
+ --fc-prompt-gradient,
370
+ linear-gradient(
371
+ 90deg,
372
+ #2563eb 0%,
373
+ #3b82f6 18%,
374
+ #06b6d4 36%,
375
+ #6366f1 54%,
376
+ #8b5cf6 72%,
377
+ #2563eb 100%
378
+ )
379
+ );
380
+ background-size: 300% 100%;
381
+ animation: fc-prompt-gradient var(--fc-prompt-gradient-speed, 4.5s) linear infinite;
382
+ box-shadow: 0 6px 6px -6px rgba(59, 130, 246, 0.45);
383
+ }
384
+
385
+
386
+ .fc-prompt:focus-within {
387
+ box-shadow: 0 8px 8px -6px rgba(59, 130, 246, 0.6);
388
+ }
389
+
390
+ .fc-prompt__inner {
391
+ position: relative;
392
+ display: block;
393
+ border-radius: var(--nile-radius-radius-xl, var(--ng-radius-md));
394
+ background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));
395
+ }
396
+
397
+ .fc-prompt__input {
398
+ display: block;
399
+ width: 100%;
400
+ border: 0;
401
+ outline: none;
402
+ background: transparent;
403
+ border-radius: inherit;
404
+ padding: var(--nile-spacing-md, var(--ng-spacing-md)) var(--nile-spacing-lg, var(--ng-spacing-lg));
405
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
406
+ font-size: var(--nile-type-scale-4, var(--ng-font-size-text-md));
407
+ color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
408
+ line-height: 1.4;
409
+ box-sizing: border-box;
410
+ }
411
+
412
+ .fc-prompt__input::placeholder {
413
+ color: var(--nile-colors-neutral-700, var(--ng-colors-text-secondary-700));
414
+ opacity: 0.85;
415
+ }
416
+
417
+ @keyframes fc-prompt-gradient {
418
+ 0% { background-position: 0% 50%; }
419
+ 100% { background-position: 300% 50%; }
420
+ }
421
+
422
+ @media (prefers-reduced-motion: reduce) {
423
+ .fc-prompt {
424
+ /* Respect any consumer-supplied gradient — just stop animating it. */
425
+ animation: none;
426
+ }
427
+ }
360
428
  `;
361
429
  //# sourceMappingURL=nile-filter-chart.css.js.map
@@ -1,14 +1,5 @@
1
1
  import { CSSResultArray, TemplateResult } from 'lit';
2
2
  import NileElement from '../internal/nile-element.js';
3
- import '@aquera/nile-elements/nile-select';
4
- import '@aquera/nile-elements/nile-option';
5
- import '@aquera/nile-elements/nile-input';
6
- import '@aquera/nile-elements/nile-slide-toggle';
7
- import '@aquera/nile-elements/nile-radio-group';
8
- import '@aquera/nile-elements/nile-radio';
9
- import '@aquera/nile-elements/nile-tag';
10
- import '@aquera/nile-elements/nile-button-toggle-group';
11
- import '@aquera/nile-elements/nile-button-toggle';
12
3
  export type NileTagVariant = 'primary' | 'success' | 'normal' | 'warning' | 'error' | 'info';
13
4
  export interface FilterOption {
14
5
  label: string;
@@ -32,7 +23,7 @@ export interface FilterControl {
32
23
  /** Optional subtitle shown below the label. */
33
24
  description?: string;
34
25
  selection: 'single' | 'multi';
35
- variant: 'badge' | 'dropdown' | 'segmented' | 'radio' | 'toggle' | 'slider' | 'search' | 'comparison' | 'threshold' | 'tree' | 'preset';
26
+ variant: 'badge' | 'dropdown' | 'segmented' | 'radio' | 'toggle' | 'slider' | 'search' | 'comparison' | 'threshold' | 'tree' | 'preset' | 'prompt';
36
27
  options?: FilterOption[];
37
28
  value?: string | string[] | number[] | boolean;
38
29
  min?: number;
@@ -41,6 +32,47 @@ export interface FilterControl {
41
32
  prefix?: string;
42
33
  suffix?: string;
43
34
  placeholder?: string;
35
+ placeholders?: string[];
36
+ /** Typewriter speed in ms per character (default 60). */
37
+ typeSpeedMs?: number;
38
+ /** Pause before deleting a typed phrase (default 1400). */
39
+ pauseBeforeDeleteMs?: number;
40
+ /** Pause between phrases after deletion (default 400). */
41
+ pauseBetweenMs?: number;
42
+ /**
43
+ * Prompt variant only. Inline callback invoked on every keystroke with
44
+ * the current value of the input (the typed string) and the control id.
45
+ * Called *in addition to* the `nile-prompt-input` event — use whichever
46
+ * suits the consumer's wiring style.
47
+ */
48
+ onType?: (value: string, id: string) => void;
49
+ /**
50
+ * Prompt variant only. Inline callback invoked when the user presses
51
+ * Enter inside the prompt input. Called in addition to the
52
+ * `nile-prompt-submit` event.
53
+ */
54
+ onSubmit?: (value: string, id: string) => void;
55
+ /**
56
+ * Prompt variant only. Colors used for the animated border gradient.
57
+ * Pass any number of CSS color strings (hex / rgb / hsl / named); they
58
+ * are evenly distributed across the gradient by default. To control
59
+ * stops explicitly, include positions in the strings themselves
60
+ * (e.g. `'#2563eb 0%'`, `'#06b6d4 50%'`).
61
+ *
62
+ * Example: `['#ec4899', '#f97316', '#facc15', '#ec4899']`
63
+ * Default: an indigo → cyan → violet → indigo blue palette.
64
+ */
65
+ gradientColors?: string[];
66
+ /**
67
+ * Prompt variant only. Linear-gradient direction (e.g. `'90deg'`,
68
+ * `'to right'`, `'45deg'`). Default: `'90deg'`.
69
+ */
70
+ gradientDirection?: string;
71
+ /**
72
+ * Prompt variant only. Animation duration in milliseconds for one full
73
+ * gradient sweep. Default: 4500.
74
+ */
75
+ gradientSpeedMs?: number;
44
76
  valueB?: string;
45
77
  operator?: string;
46
78
  thresholdValue?: number | string;
@@ -68,9 +100,17 @@ export declare class NileFilterChart extends NileElement {
68
100
  config: FilterChartSeparatedPayload | null;
69
101
  private selectedValues;
70
102
  private collapsedGroups;
103
+ /** Currently displayed (animated) placeholder text per prompt-variant control id. */
104
+ private _promptPlaceholder;
105
+ /** Active typewriter timers per prompt control id (so we can stop them). */
106
+ private _promptTimers;
71
107
  connectedCallback(): void;
72
108
  disconnectedCallback(): void;
73
109
  updated(changed: Map<string, unknown>): void;
110
+ private _syncPromptAnimations;
111
+ private _startPromptAnimation;
112
+ private _stopPromptAnimation;
113
+ private _stopAllPromptAnimations;
74
114
  private _flatControls;
75
115
  private _initValues;
76
116
  private _set;
@@ -84,6 +124,7 @@ export declare class NileFilterChart extends NileElement {
84
124
  private _renderComparison;
85
125
  private _renderThreshold;
86
126
  private _renderPreset;
127
+ private _renderPrompt;
87
128
  private _renderControl;
88
129
  private _renderGroup;
89
130
  render(): TemplateResult;
@@ -3,22 +3,16 @@ import { html, nothing } from 'lit';
3
3
  import { customElement, property, state } from 'lit/decorators.js';
4
4
  import { styles } from './nile-filter-chart.css.js';
5
5
  import NileElement from '../internal/nile-element.js';
6
- import '@aquera/nile-elements/nile-select';
7
- import '@aquera/nile-elements/nile-option';
8
- import '@aquera/nile-elements/nile-input';
9
- import '@aquera/nile-elements/nile-slide-toggle';
10
- // import '@aquera/nile-elements/nile-slider';
11
- import '@aquera/nile-elements/nile-radio-group';
12
- import '@aquera/nile-elements/nile-radio';
13
- import '@aquera/nile-elements/nile-tag';
14
- import '@aquera/nile-elements/nile-button-toggle-group';
15
- import '@aquera/nile-elements/nile-button-toggle';
16
6
  let NileFilterChart = class NileFilterChart extends NileElement {
17
7
  constructor() {
18
8
  super(...arguments);
19
9
  this.config = null;
20
10
  this.selectedValues = new Map();
21
11
  this.collapsedGroups = new Set();
12
+ /** Currently displayed (animated) placeholder text per prompt-variant control id. */
13
+ this._promptPlaceholder = new Map();
14
+ /** Active typewriter timers per prompt control id (so we can stop them). */
15
+ this._promptTimers = new Map();
22
16
  }
23
17
  static get styles() {
24
18
  return [styles];
@@ -30,14 +24,89 @@ let NileFilterChart = class NileFilterChart extends NileElement {
30
24
  }
31
25
  disconnectedCallback() {
32
26
  super.disconnectedCallback();
27
+ this._stopAllPromptAnimations();
33
28
  this.emit('nile-destroy');
34
29
  }
35
30
  updated(changed) {
36
31
  super.updated(changed);
37
32
  if (changed.has('config')) {
38
33
  this._initValues();
34
+ this._syncPromptAnimations();
39
35
  }
40
36
  }
37
+ // ── Prompt typewriter ────────────────────────────────────────────────────────
38
+ _syncPromptAnimations() {
39
+ const wanted = new Set();
40
+ for (const ctrl of this._flatControls()) {
41
+ if (ctrl.variant === 'prompt') {
42
+ wanted.add(ctrl.id);
43
+ if (!this._promptTimers.has(ctrl.id))
44
+ this._startPromptAnimation(ctrl);
45
+ }
46
+ }
47
+ // Stop animations whose controls were removed from the config
48
+ for (const id of [...this._promptTimers.keys()]) {
49
+ if (!wanted.has(id))
50
+ this._stopPromptAnimation(id);
51
+ }
52
+ }
53
+ _startPromptAnimation(ctrl) {
54
+ const phrases = (ctrl.placeholders && ctrl.placeholders.length
55
+ ? ctrl.placeholders
56
+ : [ctrl.placeholder ?? 'Ask anything…']);
57
+ const typeSpeed = ctrl.typeSpeedMs ?? 60;
58
+ const pauseAfterType = ctrl.pauseBeforeDeleteMs ?? 1400;
59
+ const pauseBetween = ctrl.pauseBetweenMs ?? 400;
60
+ let phraseIdx = 0;
61
+ let charIdx = 0;
62
+ let deleting = false;
63
+ const tick = () => {
64
+ const phrase = phrases[phraseIdx];
65
+ let nextDelay;
66
+ if (!deleting) {
67
+ charIdx++;
68
+ const next = new Map(this._promptPlaceholder).set(ctrl.id, phrase.slice(0, charIdx));
69
+ this._promptPlaceholder = next;
70
+ if (charIdx >= phrase.length) {
71
+ deleting = true;
72
+ nextDelay = pauseAfterType;
73
+ }
74
+ else {
75
+ nextDelay = typeSpeed;
76
+ }
77
+ }
78
+ else {
79
+ charIdx--;
80
+ const next = new Map(this._promptPlaceholder).set(ctrl.id, phrase.slice(0, Math.max(0, charIdx)));
81
+ this._promptPlaceholder = next;
82
+ if (charIdx <= 0) {
83
+ deleting = false;
84
+ phraseIdx = (phraseIdx + 1) % phrases.length;
85
+ nextDelay = pauseBetween;
86
+ }
87
+ else {
88
+ nextDelay = Math.max(20, typeSpeed / 2);
89
+ }
90
+ }
91
+ this._promptTimers.set(ctrl.id, setTimeout(tick, nextDelay));
92
+ };
93
+ // Kick it off
94
+ this._promptPlaceholder = new Map(this._promptPlaceholder).set(ctrl.id, '');
95
+ this._promptTimers.set(ctrl.id, setTimeout(tick, 100));
96
+ }
97
+ _stopPromptAnimation(id) {
98
+ const t = this._promptTimers.get(id);
99
+ if (t)
100
+ clearTimeout(t);
101
+ this._promptTimers.delete(id);
102
+ const next = new Map(this._promptPlaceholder);
103
+ next.delete(id);
104
+ this._promptPlaceholder = next;
105
+ }
106
+ _stopAllPromptAnimations() {
107
+ for (const id of [...this._promptTimers.keys()])
108
+ this._stopPromptAnimation(id);
109
+ }
41
110
  _flatControls() {
42
111
  const entries = this.config?.chart?.controls ?? [];
43
112
  const out = [];
@@ -356,6 +425,75 @@ let NileFilterChart = class NileFilterChart extends NileElement {
356
425
  </button>`)}
357
426
  </div>`;
358
427
  }
428
+ // ── Prompt ───────────────────────────────────────────────────────────────────
429
+ //
430
+ // Renders a free-text input wrapped in a div whose border is an animated
431
+ // multi-stop blue gradient (CSS @keyframes — purely visual, no JS in the
432
+ // animation loop). The placeholder is driven by a typewriter that cycles
433
+ // through `ctrl.placeholders`. Each keystroke fires `nile-change` with the
434
+ // current string; pressing Enter additionally fires `nile-prompt-submit`
435
+ // for AI-style "ask" UIs.
436
+ _renderPrompt(ctrl) {
437
+ const value = String(this.selectedValues.get(ctrl.id) ?? '');
438
+ const animated = this._promptPlaceholder.get(ctrl.id) ?? '';
439
+ // Build the inline style overrides as CSS custom properties — these
440
+ // are picked up by the .fc-prompt rule via var(--fc-prompt-gradient,
441
+ // <default>) / var(--fc-prompt-gradient-speed, 4.5s). Only emit a
442
+ // declaration when the consumer actually supplied an override.
443
+ const styleParts = [];
444
+ if (ctrl.gradientColors && ctrl.gradientColors.length > 0) {
445
+ const dir = ctrl.gradientDirection ?? '90deg';
446
+ styleParts.push(`--fc-prompt-gradient: linear-gradient(${dir}, ${ctrl.gradientColors.join(', ')})`);
447
+ }
448
+ else if (ctrl.gradientDirection) {
449
+ // Direction-only override: keep the default palette but rotate it.
450
+ styleParts.push(`--fc-prompt-gradient: linear-gradient(${ctrl.gradientDirection},` +
451
+ ' #2563eb 0%, #3b82f6 18%, #06b6d4 36%, #6366f1 54%,' +
452
+ ' #8b5cf6 72%, #2563eb 100%)');
453
+ }
454
+ if (typeof ctrl.gradientSpeedMs === 'number' && ctrl.gradientSpeedMs > 0) {
455
+ styleParts.push(`--fc-prompt-gradient-speed: ${ctrl.gradientSpeedMs}ms`);
456
+ }
457
+ const inlineStyle = styleParts.length ? styleParts.join('; ') + ';' : '';
458
+ return html `
459
+ <div class="fc-prompt" part="filter-prompt" style="${inlineStyle}">
460
+ <div class="fc-prompt__inner">
461
+ <input
462
+ class="fc-prompt__input"
463
+ type="text"
464
+ autocomplete="off"
465
+ spellcheck="false"
466
+ .value="${value}"
467
+ placeholder="${animated}"
468
+ @input="${(e) => {
469
+ const t = e.target;
470
+ // Update the internal map (also fires the bulk `nile-change`
471
+ // event with every control's value).
472
+ this._set(ctrl.id, t.value);
473
+ // Dedicated, prompt-specific stream — every keystroke emits
474
+ // just this control's typed string so consumers don't have to
475
+ // sift through the full filters map for live updates (e.g.
476
+ // debounced AI suggestions, auto-complete, character counters).
477
+ this.emit('nile-prompt-input', { id: ctrl.id, value: t.value });
478
+ // Optional inline callback supplied via the config — same
479
+ // payload, shaped as a function call instead of an event.
480
+ if (typeof ctrl.onType === 'function') {
481
+ ctrl.onType(t.value, ctrl.id);
482
+ }
483
+ }}"
484
+ @keydown="${(e) => {
485
+ if (e.key === 'Enter') {
486
+ const t = e.target;
487
+ this.emit('nile-prompt-submit', { id: ctrl.id, value: t.value });
488
+ if (typeof ctrl.onSubmit === 'function') {
489
+ ctrl.onSubmit(t.value, ctrl.id);
490
+ }
491
+ }
492
+ }}"
493
+ />
494
+ </div>
495
+ </div>`;
496
+ }
359
497
  // ── Card wrapper ─────────────────────────────────────────────────────────────
360
498
  _renderControl(ctrl) {
361
499
  let body;
@@ -389,6 +527,9 @@ let NileFilterChart = class NileFilterChart extends NileElement {
389
527
  case 'preset':
390
528
  body = this._renderPreset(ctrl);
391
529
  break;
530
+ case 'prompt':
531
+ body = this._renderPrompt(ctrl);
532
+ break;
392
533
  default: body = html ``;
393
534
  }
394
535
  return html `
@@ -445,6 +586,9 @@ __decorate([
445
586
  __decorate([
446
587
  state()
447
588
  ], NileFilterChart.prototype, "collapsedGroups", void 0);
589
+ __decorate([
590
+ state()
591
+ ], NileFilterChart.prototype, "_promptPlaceholder", void 0);
448
592
  NileFilterChart = __decorate([
449
593
  customElement('nile-filter-chart')
450
594
  ], NileFilterChart);
@@ -176,12 +176,6 @@ export const styles = css `
176
176
  white-space: nowrap;
177
177
  }
178
178
 
179
- @container (max-width: 240px) {
180
- .kpi-trend {
181
- display: none;
182
- }
183
- }
184
-
185
179
  .kpi-trend--up {
186
180
  background: none;
187
181
  color: var(--nile-kpi-trend-up-color);
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@aquera/nile-visualization",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "A visualization Library for the Nile Design System",
5
5
  "license": "MIT",
6
6
  "author": "Aquera Inc",
7
7
  "type": "module",
8
8
  "main": "dist/src/index.js",
9
9
  "module": "dist/src/index.js",
10
+
10
11
  "exports": {
11
12
  ".": "./dist/src/index.js",
12
13
  "./nile-bar-chart": "./dist/src/nile-bar-chart/index.js",
@@ -60,11 +61,15 @@
60
61
  },
61
62
  "peerDependencies": {
62
63
  "highcharts": ">=10.0.0",
64
+ "@aquera/nile-elements": ">=0.1.0",
63
65
  "@aquera/nile-data-grid": ">=0.1.0"
64
66
  },
65
67
  "peerDependenciesMeta": {
68
+ "@aquera/nile-elements": {
69
+ "optional": true
70
+ },
66
71
  "@aquera/nile-data-grid": {
67
- "optional": true
72
+ "optional": true
68
73
  }
69
74
  },
70
75
  "devDependencies": {