@aquera/nile-visualization 2.9.9 → 2.9.11

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.
@@ -133,20 +133,20 @@ export const styles = css `
133
133
  }
134
134
 
135
135
  @container (max-width: 400px) {
136
- .nile-chart-header {
136
+ .nile-chart-card:not(.nile-chart-card--kpi) .nile-chart-header {
137
137
  padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-2xl, var(--ng-spacing-2xl)) var(--nile-spacing-lg, var(--ng-spacing-lg));
138
138
  }
139
- .nile-chart-header.nile-chart-header--compact {
139
+ .nile-chart-card:not(.nile-chart-card--kpi) .nile-chart-header.nile-chart-header--compact {
140
140
  padding: var(--nile-spacing-lg, var(--ng-spacing-lg)) var(--nile-spacing-2xl, var(--ng-spacing-2xl));
141
141
  }
142
142
  }
143
143
 
144
144
  @container (max-width: 280px) {
145
- .nile-chart-header {
145
+ .nile-chart-card:not(.nile-chart-card--kpi) .nile-chart-header {
146
146
  padding: var(--nile-spacing-lg, var(--ng-spacing-lg)) var(--nile-spacing-xl, var(--ng-spacing-xl));
147
147
  gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
148
148
  }
149
- .nile-chart-header.nile-chart-header--compact {
149
+ .nile-chart-card:not(.nile-chart-card--kpi) .nile-chart-header.nile-chart-header--compact {
150
150
  padding: var(--nile-spacing-md, var(--ng-spacing-md)) var(--nile-spacing-xl, var(--ng-spacing-xl));
151
151
  }
152
152
  }
@@ -468,21 +468,41 @@ export const styles = css `
468
468
  pointer-events: none;
469
469
  }
470
470
 
471
+ /* When loading, the card becomes a flex column that fills the host so the
472
+ skeleton can shrink with the container instead of forcing a fixed size. */
473
+ .nile-chart-card--loading {
474
+ display: flex;
475
+ flex-direction: column;
476
+ height: 100%;
477
+ }
478
+ .nile-chart-card--loading .nile-chart-wrapper {
479
+ flex: 1 1 auto;
480
+ min-height: 0;
481
+ }
482
+ .nile-chart-card--loading .nile-chart-inner {
483
+ height: 100%;
484
+ box-sizing: border-box;
485
+ }
486
+
471
487
  .nile-chart-skeleton {
472
488
  display: flex;
473
489
  flex-direction: column;
474
490
  gap: 0;
475
491
  padding: var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-xl, var(--ng-spacing-xl));
476
- min-height: var(--nile-chart-skeleton-height, 300px);
492
+ height: 100%;
493
+ box-sizing: border-box;
494
+ overflow: hidden;
495
+ min-width: 0;
477
496
  }
478
497
 
479
498
  .nile-chart-skeleton-body {
480
499
  display: flex;
481
500
  flex-direction: column;
482
501
  justify-content: space-around;
483
- flex: 1;
502
+ flex: 1 1 auto;
503
+ min-height: 0;
484
504
  gap: var(--nile-spacing-14px, var(--ng-spacing-lg));
485
- padding-left: 44px;
505
+ padding-left: calc(var(--nile-spacing-5xl, var(--ng-spacing-5xl)) + var(--nile-spacing-xs, var(--ng-spacing-xs)));
486
506
  position: relative;
487
507
  }
488
508
 
@@ -493,8 +513,8 @@ export const styles = css `
493
513
  left: 34px;
494
514
  top: var(--nile-spacing-xs, var(--ng-spacing-xs));
495
515
  bottom: var(--nile-spacing-xs, var(--ng-spacing-xs));
496
- width: var(--nile-border-width-2, var(--ng-stroke-width-2));
497
- border-radius: var(--nile-border-width-1, var(--ng-stroke-width-1));
516
+ width: 4px;
517
+ border-radius: 2px;
498
518
  background: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
499
519
  }
500
520
 
@@ -502,59 +522,415 @@ export const styles = css `
502
522
  display: flex;
503
523
  align-items: center;
504
524
  gap: var(--nile-spacing-md, var(--ng-spacing-md));
505
- height: var(--nile-spacing-3xl, var(--ng-spacing-3xl));
525
+ flex: 1 1 0;
526
+ min-height: 0;
527
+ max-height: var(--nile-spacing-3xl, var(--ng-spacing-3xl));
528
+ }
529
+
530
+ .nile-chart-skeleton-row .nile-chart-skeleton-ylabel-sk { flex-shrink: 0; }
531
+ /* Horizontal bar — uses <nile-skeleton-loader>. The host takes flex-basis
532
+ w% (inline style); ::part(base) forces the inner wrapper to fill the
533
+ host so the shimmer rectangle spans the full w% of the row. */
534
+ .nile-chart-skeleton-bar-sk::part(base) {
535
+ width: 100%;
536
+ height: 100%;
506
537
  }
538
+ .nile-chart-skeleton-row:nth-child(1) .nile-chart-skeleton-bar-rect { animation-delay: 0ms; }
539
+ .nile-chart-skeleton-row:nth-child(2) .nile-chart-skeleton-bar-rect { animation-delay: 100ms; }
540
+ .nile-chart-skeleton-row:nth-child(3) .nile-chart-skeleton-bar-rect { animation-delay: 200ms; }
541
+ .nile-chart-skeleton-row:nth-child(4) .nile-chart-skeleton-bar-rect { animation-delay: 300ms; }
542
+ .nile-chart-skeleton-row:nth-child(5) .nile-chart-skeleton-bar-rect { animation-delay: 400ms; }
507
543
 
508
- .nile-chart-skeleton-ylabel {
509
- width: var(--nile-height-26px, 26px);
510
- height: var(--nile-spacing-10px, var(--ng-spacing-md-alt));
511
- border-radius: var(--nile-radius-radius-sm, var(--ng-radius-xs));
544
+ /* Horizontal x-axis labels row */
545
+ .nile-chart-skeleton-xaxis-row {
546
+ display: flex;
547
+ justify-content: space-around;
548
+ padding-left: calc(var(--nile-spacing-5xl, var(--ng-spacing-5xl)) + var(--nile-spacing-xs, var(--ng-spacing-xs)));
549
+ margin-top: var(--nile-spacing-14px, var(--ng-spacing-lg));
512
550
  flex-shrink: 0;
551
+ min-width: 0;
552
+ gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
553
+ overflow: hidden;
554
+ }
555
+ .nile-chart-skeleton-xaxis-chip {
556
+ flex: 0 1 auto;
557
+ min-width: 0;
558
+ width: 30px;
559
+ max-width: 100%;
560
+ height: 10px;
513
561
  background: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
562
+ border-radius: var(--nile-radius-radius-md, var(--ng-radius-md));
514
563
  animation: nile-skeleton-blink 1.2s ease-in-out infinite;
515
564
  }
565
+ .nile-chart-skeleton-xaxis-row--columns .nile-chart-skeleton-xaxis-chip {
566
+ width: 24px;
567
+ }
568
+
569
+ /* Compact paddings + y-axis offset for narrow widths so the skeleton
570
+ doesn't overflow when the chart is shrunk. */
571
+ @container (max-width: 400px) {
572
+ .nile-chart-skeleton {
573
+ padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-2xl, var(--ng-spacing-2xl)) var(--nile-spacing-lg, var(--ng-spacing-lg));
574
+ }
575
+ .nile-chart-skeleton-body { padding-left: var(--nile-spacing-4xl, var(--ng-spacing-4xl)); }
576
+ .nile-chart-skeleton-body::before { left: var(--nile-spacing-3xl, var(--ng-spacing-3xl)); }
577
+ .nile-chart-skeleton-xaxis-row { padding-left: var(--nile-spacing-4xl, var(--ng-spacing-4xl)); }
578
+ }
516
579
 
517
- .nile-chart-skeleton-bar {
518
- height: var(--nile-spacing-2xl, var(--ng-spacing-2xl));
519
- width: var(--w, 60%);
520
- border-radius: 0 var(--nile-radius-radius-sm, var(--ng-radius-xs)) var(--nile-radius-radius-sm, var(--ng-radius-xs)) 0;
580
+ @container (max-width: 280px) {
581
+ .nile-chart-skeleton {
582
+ padding: var(--nile-spacing-lg, var(--ng-spacing-lg)) var(--nile-spacing-xl, var(--ng-spacing-xl));
583
+ }
584
+ .nile-chart-skeleton-body { padding-left: var(--nile-spacing-2xl, var(--ng-spacing-2xl)); }
585
+ .nile-chart-skeleton-body::before { left: var(--nile-spacing-14px, var(--ng-spacing-lg-alt)); }
586
+ .nile-chart-skeleton-xaxis-row {
587
+ padding-left: var(--nile-spacing-2xl, var(--ng-spacing-2xl));
588
+ gap: var(--nile-spacing-xs, var(--ng-spacing-xs));
589
+ }
590
+ .nile-chart-skeleton-ylabel { display: none; }
591
+ }
592
+
593
+ @container (max-width: 220px) {
594
+ .nile-chart-skeleton-xaxis-row { display: none; }
595
+ }
596
+
597
+ @keyframes nile-skeleton-blink {
598
+ 0%, 100% { opacity: 0.5; }
599
+ 50% { opacity: 1; }
600
+ }
601
+
602
+ /* ── Variant: columns (vertical bars) — wraps nile-skeleton-loader ── */
603
+ .nile-chart-skeleton-columns {
604
+ display: flex;
605
+ align-items: flex-end;
606
+ justify-content: space-between;
607
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
608
+ flex: 1 1 auto;
609
+ min-height: 0;
610
+ padding-left: 28px;
611
+ position: relative;
612
+ }
613
+ .nile-chart-skeleton-columns::before,
614
+ .nile-chart-skeleton-columns::after {
615
+ content: '';
616
+ position: absolute;
617
+ background: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
618
+ border-radius: 1px;
619
+ }
620
+ .nile-chart-skeleton-columns::before { left: 18px; top: 0; bottom: 0; width: 2px; }
621
+ .nile-chart-skeleton-columns::after { left: 18px; right: 0; bottom: 0; height: 2px; }
622
+ .nile-chart-skeleton-col-cell {
623
+ flex: 1 1 0;
624
+ min-width: 0;
625
+ height: 60%;
626
+ display: flex;
627
+ align-items: stretch;
628
+ border-radius: var(--nile-radius-radius-sm, var(--ng-radius-xs)) var(--nile-radius-radius-sm, var(--ng-radius-xs)) 0 0;
629
+ overflow: hidden;
630
+ }
631
+ .nile-chart-skeleton-xaxis-row--columns { padding-left: 28px; }
632
+
633
+ .nile-chart-skeleton-fill {
634
+ width: 100%;
635
+ height: 100%;
521
636
  background: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
637
+ border-radius: inherit;
522
638
  animation: nile-skeleton-blink 1.2s ease-in-out infinite;
523
639
  }
524
640
 
525
- /* Staggered wave across the bars */
526
- .nile-chart-skeleton-row:nth-child(1) .nile-chart-skeleton-bar { animation-delay: 0ms; }
527
- .nile-chart-skeleton-row:nth-child(2) .nile-chart-skeleton-bar { animation-delay: 100ms; }
528
- .nile-chart-skeleton-row:nth-child(3) .nile-chart-skeleton-bar { animation-delay: 200ms; }
529
- .nile-chart-skeleton-row:nth-child(4) .nile-chart-skeleton-bar { animation-delay: 300ms; }
530
- .nile-chart-skeleton-row:nth-child(5) .nile-chart-skeleton-bar { animation-delay: 400ms; }
641
+ /* ── Variant: line ── */
642
+ .nile-chart-skeleton-line {
643
+ flex: 1 1 auto;
644
+ min-height: 0;
645
+ padding-left: 28px;
646
+ position: relative;
647
+ }
648
+ .nile-chart-skeleton-line::before {
649
+ content: '';
650
+ position: absolute;
651
+ left: 18px; top: 0; bottom: 0;
652
+ width: 2px;
653
+ border-radius: 1px;
654
+ background: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
655
+ }
656
+ .nile-chart-skeleton-line svg { width: 100%; height: 100%; display: block; }
657
+ .nile-chart-skeleton-line-area {
658
+ fill: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
659
+ opacity: 0.35;
660
+ animation: nile-skeleton-blink 1.6s ease-in-out infinite;
661
+ }
662
+ .nile-chart-skeleton-line-stroke {
663
+ fill: none;
664
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
665
+ stroke-width: 1.6;
666
+ stroke-linecap: round;
667
+ stroke-linejoin: round;
668
+ animation: nile-skeleton-blink 1.2s ease-in-out infinite;
669
+ }
531
670
 
532
- /* Horizontal x-axis labels row */
533
- .nile-chart-skeleton-xaxis-row {
671
+ /* Trendline: faint guide line + actuals stroke + dashed forecast + split rule */
672
+ .nile-chart-skeleton-trend-line {
673
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
674
+ stroke-width: 0.8;
675
+ opacity: 0.45;
676
+ }
677
+ .nile-chart-skeleton-trend-forecast {
678
+ fill: none;
679
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
680
+ stroke-width: 1.6;
681
+ stroke-linecap: round;
682
+ stroke-dasharray: 3 3;
683
+ animation: nile-skeleton-blink 1.2s ease-in-out infinite;
684
+ animation-delay: 200ms;
685
+ }
686
+ .nile-chart-skeleton-trend-divider {
687
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
688
+ stroke-width: 0.5;
689
+ stroke-dasharray: 1 2;
690
+ opacity: 0.5;
691
+ }
692
+
693
+ /* Anomaly: halo pulses around outlier dots, dot itself blinks */
694
+ .nile-chart-skeleton-anomaly-dot {
695
+ fill: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
696
+ animation: nile-skeleton-blink 1.1s ease-in-out infinite;
697
+ }
698
+ .nile-chart-skeleton-anomaly-halo {
699
+ fill: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
700
+ opacity: 0.25;
701
+ transform-origin: center;
702
+ transform-box: fill-box;
703
+ animation: nile-skeleton-anomaly-pulse 1.8s ease-in-out infinite;
704
+ }
705
+ .nile-chart-skeleton-anomaly-halo:nth-of-type(1) { animation-delay: 0ms; }
706
+ .nile-chart-skeleton-anomaly-halo:nth-of-type(2) { animation-delay: 300ms; }
707
+ .nile-chart-skeleton-anomaly-halo:nth-of-type(3) { animation-delay: 600ms; }
708
+ @keyframes nile-skeleton-anomaly-pulse {
709
+ 0%, 100% { opacity: 0.15; transform: scale(0.7); }
710
+ 50% { opacity: 0.35; transform: scale(1.2); }
711
+ }
712
+
713
+ /* ── Variant: pie / donut ── */
714
+ .nile-chart-skeleton-pie-wrap {
534
715
  display: flex;
535
- justify-content: space-around;
536
- padding-left: 44px;
537
- margin-top: var(--nile-spacing-14px, var(--ng-spacing-lg));
716
+ align-items: center;
717
+ justify-content: center;
718
+ gap: var(--nile-spacing-3xl, var(--ng-spacing-3xl));
719
+ flex: 1 1 auto;
720
+ min-height: 0;
721
+ container-type: size;
722
+ }
723
+ .nile-chart-skeleton-pie {
724
+ width: min(60cqmin, var(--nile-width-208px, 208px));
725
+ aspect-ratio: 1;
726
+ border-radius: 50%;
727
+ background: conic-gradient(
728
+ var(--nile-colors-neutral-400, var(--ng-colors-border-secondary)) 0 25%,
729
+ var(--nile-colors-neutral-300, #e5e7eb) 25% 55%,
730
+ var(--nile-colors-neutral-400, var(--ng-colors-border-secondary)) 55% 80%,
731
+ var(--nile-colors-neutral-300, #e5e7eb) 80% 100%
732
+ );
733
+ mask: radial-gradient(circle, transparent 28%, black 30%);
734
+ -webkit-mask: radial-gradient(circle, transparent 28%, black 30%);
735
+ animation: nile-skeleton-spin 6s linear infinite, nile-skeleton-blink 1.6s ease-in-out infinite;
736
+ }
737
+ .nile-chart-skeleton-legend {
738
+ display: flex;
739
+ flex-direction: column;
740
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
741
+ flex-shrink: 0;
742
+ }
743
+ .nile-chart-skeleton-legend-row {
744
+ display: flex;
745
+ align-items: center;
746
+ gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
747
+ }
748
+ @container (max-width: 320px) {
749
+ .nile-chart-skeleton-legend { display: none; }
538
750
  }
751
+ @keyframes nile-skeleton-spin { to { transform: rotate(360deg); } }
539
752
 
540
- .nile-chart-skeleton-xlabel {
541
- height: var(--nile-spacing-10px, var(--ng-spacing-md-alt));
542
- width: var(--nile-height-30px, 30px);
543
- border-radius: var(--nile-radius-radius-sm, var(--ng-radius-xs));
753
+ /* ── Variant: scatter ── */
754
+ .nile-chart-skeleton-scatter {
755
+ flex: 1 1 auto;
756
+ min-height: 0;
757
+ position: relative;
758
+ padding-left: 28px;
759
+ }
760
+ .nile-chart-skeleton-scatter::before,
761
+ .nile-chart-skeleton-scatter::after {
762
+ content: '';
763
+ position: absolute;
544
764
  background: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
765
+ border-radius: 1px;
766
+ }
767
+ .nile-chart-skeleton-scatter::before { left: 18px; top: 0; bottom: 0; width: 2px; }
768
+ .nile-chart-skeleton-scatter::after { left: 18px; right: 0; bottom: 0; height: 2px; }
769
+ .nile-chart-skeleton-dot-pos {
770
+ position: absolute;
771
+ transform: translate(-50%, -50%);
772
+ display: inline-flex;
773
+ }
774
+
775
+ /* ── Variant: heatmap ── */
776
+ .nile-chart-skeleton-heatmap {
777
+ display: flex;
778
+ flex-direction: column;
779
+ gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
780
+ flex: 1 1 auto;
781
+ min-height: 0;
782
+ }
783
+ .nile-chart-skeleton-heatmap-row {
784
+ display: flex;
785
+ gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
786
+ flex: 1 1 0;
787
+ min-height: 0;
788
+ }
789
+ .nile-chart-skeleton-heatmap-cell {
790
+ flex: 1 1 0;
791
+ min-width: 0;
792
+ display: flex;
793
+ border-radius: var(--nile-radius-radius-md, var(--ng-radius-md));
794
+ overflow: hidden;
795
+ }
796
+
797
+ /* ── Variant: radar ── */
798
+ .nile-chart-skeleton-radar-wrap {
799
+ display: flex;
800
+ align-items: center;
801
+ justify-content: center;
802
+ flex: 1 1 auto;
803
+ min-height: 0;
804
+ min-width: 0;
805
+ container-type: size;
806
+ overflow: hidden;
807
+ }
808
+ .nile-chart-skeleton-radar-svg {
809
+ width: min(78cqmin, var(--nile-width-240px, 240px));
810
+ height: min(78cqmin, var(--nile-width-240px, 240px));
811
+ display: block;
812
+ }
813
+ .nile-chart-skeleton-radar-grid {
814
+ fill: none;
815
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
816
+ stroke-width: 0.8;
817
+ stroke-linejoin: round;
818
+ opacity: 0.7;
819
+ }
820
+ .nile-chart-skeleton-radar-spoke {
821
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
822
+ stroke-width: 0.6;
823
+ opacity: 0.5;
824
+ }
825
+ .nile-chart-skeleton-radar-data {
826
+ fill: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
827
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
828
+ stroke-width: 1.2;
829
+ stroke-linejoin: round;
830
+ opacity: 0.45;
831
+ animation: nile-skeleton-blink 1.4s ease-in-out infinite;
832
+ transform-origin: 50px 50px;
833
+ transform-box: fill-box;
834
+ }
835
+ .nile-chart-skeleton-radar-dot {
836
+ fill: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
545
837
  animation: nile-skeleton-blink 1.2s ease-in-out infinite;
546
- animation-delay: var(--d, 0ms);
547
838
  }
839
+ .nile-chart-skeleton-radar-dot:nth-child(5) { animation-delay: 0ms; }
840
+ .nile-chart-skeleton-radar-dot:nth-child(6) { animation-delay: 120ms; }
841
+ .nile-chart-skeleton-radar-dot:nth-child(7) { animation-delay: 240ms; }
842
+ .nile-chart-skeleton-radar-dot:nth-child(8) { animation-delay: 360ms; }
843
+ .nile-chart-skeleton-radar-dot:nth-child(9) { animation-delay: 480ms; }
548
844
 
549
- @keyframes nile-skeleton-blink {
550
- 0%, 100% { opacity: 0.5; }
551
- 50% { opacity: 1; }
845
+ /* ── Variant: timeline ── */
846
+ .nile-chart-skeleton-timeline {
847
+ display: flex;
848
+ flex-direction: column;
849
+ gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
850
+ flex: 1 1 auto;
851
+ min-height: 0;
852
+ justify-content: space-around;
853
+ overflow: hidden;
854
+ }
855
+ .nile-chart-skeleton-tl-row {
856
+ position: relative;
857
+ flex: 1 1 0;
858
+ min-height: 0;
859
+ max-height: var(--nile-height-12px, var(--ng-height-12px));
860
+ }
861
+ .nile-chart-skeleton-tl-bar-pos {
862
+ position: absolute;
863
+ top: 0; bottom: 0;
864
+ display: inline-flex;
865
+ border-radius: var(--nile-radius-radius-lg, var(--ng-radius-sm));
866
+ }
867
+
868
+ /* ── Variant: kpi ── */
869
+ .nile-chart-skeleton-kpi {
870
+ display: flex;
871
+ flex-direction: column;
872
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
873
+ flex: 1 1 auto;
874
+ min-height: 0;
875
+ justify-content: center;
876
+ }
877
+ .nile-chart-skeleton-kpi-spark {
878
+ width: 100%;
879
+ height: 40px;
880
+ margin-top: var(--nile-spacing-sm, var(--ng-spacing-sm));
881
+ }
882
+ .nile-chart-skeleton-kpi-spark path {
883
+ fill: none;
884
+ stroke: var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
885
+ stroke-width: 1.6;
886
+ stroke-linecap: round;
887
+ stroke-linejoin: round;
888
+ animation: nile-skeleton-blink 1.2s ease-in-out infinite;
889
+ }
890
+
891
+ /* ── Variant: grid (table) ── */
892
+ .nile-chart-skeleton-grid {
893
+ display: flex;
894
+ flex-direction: column;
895
+ gap: var(--nile-spacing-sm, var(--ng-spacing-sm));
896
+ flex: 1 1 auto;
897
+ min-height: 0;
898
+ }
899
+ .nile-chart-skeleton-grid-head,
900
+ .nile-chart-skeleton-grid-row {
901
+ display: flex;
902
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
903
+ align-items: center;
904
+ flex: 1 1 0;
905
+ min-height: 0;
906
+ max-height: 28px;
907
+ }
908
+ .nile-chart-skeleton-grid-head {
909
+ border-bottom: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
910
+ padding-bottom: var(--nile-spacing-sm, var(--ng-spacing-sm));
911
+ }
912
+ .nile-chart-skeleton-grid-head nile-skeleton-loader,
913
+ .nile-chart-skeleton-grid-row nile-skeleton-loader {
914
+ flex: 1 1 0;
915
+ min-width: 0;
916
+ }
917
+
918
+ /* ── Variant: filter ── */
919
+ .nile-chart-skeleton-filter {
920
+ display: flex;
921
+ flex-direction: column;
922
+ flex: 1 1 auto;
923
+ min-height: 0;
924
+ justify-content: center;
552
925
  }
553
926
 
554
927
  @media (prefers-reduced-motion: reduce) {
555
- .nile-chart-skeleton-bar,
556
- .nile-chart-skeleton-ylabel,
557
- .nile-chart-skeleton-xlabel {
928
+ .nile-chart-skeleton-line-area,
929
+ .nile-chart-skeleton-line-stroke,
930
+ .nile-chart-skeleton-pie,
931
+ .nile-chart-skeleton-radar-data,
932
+ .nile-chart-skeleton-radar-dot,
933
+ .nile-chart-skeleton-kpi-spark path {
558
934
  animation: none;
559
935
  opacity: 0.7;
560
936
  }
@@ -75,6 +75,7 @@ export declare class NileChart extends NileElement {
75
75
  chartTypeAttr: string;
76
76
  /** Summary/insight text — shown as the AI panel's opening message when the chat is opened. */
77
77
  summary: string;
78
+ aiBorder: boolean;
78
79
  /**
79
80
  * Controls which items appear in the actions menu. All items are opt-in —
80
81
  * only items explicitly set to `true` are shown. Merged with (and takes
@@ -4,6 +4,7 @@ import { html, nothing } from 'lit';
4
4
  import NileElement from '../internal/nile-element.js';
5
5
  import { styles, tooltipCss } from './nile-chart.css.js';
6
6
  import { nileChartConfig } from './nile-chart-config-builder.js';
7
+ import { renderChartSkeleton } from './nile-chart-skeleton.js';
7
8
  import { convertConfig } from '../internal/chart-adapters.js';
8
9
  import { deepMerge } from '../internal/utils.js';
9
10
  import { initNileChartExporting, getHighcharts } from '../internal/highcharts-provider.js';
@@ -144,6 +145,7 @@ let NileChart = class NileChart extends NileElement {
144
145
  this.chartTypeAttr = '';
145
146
  /** Summary/insight text — shown as the AI panel's opening message when the chat is opened. */
146
147
  this.summary = '';
148
+ this.aiBorder = false;
147
149
  /**
148
150
  * Controls which items appear in the actions menu. All items are opt-in —
149
151
  * only items explicitly set to `true` are shown. Merged with (and takes
@@ -1713,8 +1715,11 @@ let NileChart = class NileChart extends NileElement {
1713
1715
  }
1714
1716
  case 'filter': {
1715
1717
  const promote = this.isPromotableFilter;
1718
+ const filterConfig = this.aiBorder
1719
+ ? { ...config, controls: config.controls.map(c => ({ ...c, aiBorder: c.aiBorder ?? true })) }
1720
+ : config;
1716
1721
  return html `<nile-filter-chart
1717
- .config=${{ chart: config }}
1722
+ .config=${{ chart: filterConfig }}
1718
1723
  ?hide-control-headers=${promote}
1719
1724
  @nile-change="${(e) => this.emit('nile-change', e.detail)}"
1720
1725
  ></nile-filter-chart>`;
@@ -1726,27 +1731,13 @@ let NileChart = class NileChart extends NileElement {
1726
1731
  }
1727
1732
  }
1728
1733
  renderSkeleton() {
1729
- return html `
1730
- <div class="nile-chart-skeleton" aria-busy="true" aria-label="Loading chart">
1731
- <div class="nile-chart-skeleton-body">
1732
- ${[78, 55, 91, 42, 68].map(w => html `
1733
- <div class="nile-chart-skeleton-row">
1734
- <div class="nile-chart-skeleton-ylabel"></div>
1735
- <div class="nile-chart-skeleton-bar" style="--w: ${w}%"></div>
1736
- </div>
1737
- `)}
1738
- </div>
1739
- <div class="nile-chart-skeleton-xaxis-row">
1740
- ${[0, 1, 2, 3, 4].map(i => html `<div class="nile-chart-skeleton-xlabel" style="--d: ${i * 80}ms"></div>`)}
1741
- </div>
1742
- </div>
1743
- `;
1734
+ return renderChartSkeleton(this.activeConfig?.type);
1744
1735
  }
1745
1736
  render() {
1746
1737
  const isLoading = this.loading || (this.activeConfig?.loading ?? false);
1747
1738
  const isGrid = this.activeConfig?.type === 'grid';
1748
1739
  return html `
1749
- <div class="nile-chart-card ${isGrid ? 'nile-chart-card--grid' : ''}" part="chart-card">
1740
+ <div class="nile-chart-card ${isGrid ? 'nile-chart-card--grid' : ''} ${isLoading ? 'nile-chart-card--loading' : ''} ${this.activeConfig?.type === 'kpi' ? 'nile-chart-card--kpi' : ''}" part="chart-card">
1750
1741
  ${this.renderHeader()}
1751
1742
  <div class="nile-chart-wrapper">
1752
1743
  <div class="nile-chart-inner ${this.activeConfig?.type === 'kpi' ? 'nile-chart-inner--kpi' : ''} ${this.activeConfig?.type === 'filter' ? 'nile-chart-inner--filter' : ''}" part="chart-inner">
@@ -1783,6 +1774,9 @@ __decorate([
1783
1774
  __decorate([
1784
1775
  property({ type: String })
1785
1776
  ], NileChart.prototype, "summary", void 0);
1777
+ __decorate([
1778
+ property({ type: Boolean, reflect: true, attribute: 'ai-border' })
1779
+ ], NileChart.prototype, "aiBorder", void 0);
1786
1780
  __decorate([
1787
1781
  property({ type: Object })
1788
1782
  ], NileChart.prototype, "menu", void 0);
@@ -363,6 +363,15 @@ export const styles = css `
363
363
  display: block;
364
364
  width: 100%;
365
365
  border-radius: var(--nile-radius-radius-2xl, var(--ng-radius-lg));
366
+ padding: 0;
367
+ background: transparent;
368
+ }
369
+
370
+ .fc-prompt .fc-prompt__inner {
371
+ border: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
372
+ }
373
+
374
+ .fc-prompt--ai-border {
366
375
  padding: 2px;
367
376
  background: var(
368
377
  --fc-prompt-gradient,
@@ -381,24 +390,12 @@ export const styles = css `
381
390
  box-shadow: 0 6px 6px -6px color-mix(in srgb, var(--ng-color-blue-500) 45%, transparent);
382
391
  }
383
392
 
384
-
385
- .fc-prompt:focus-within {
386
- box-shadow: 0 8px 8px -6px color-mix(in srgb, var(--ng-color-blue-500) 60%, transparent);
387
- }
388
-
389
- .fc-prompt--no-ai-border {
390
- padding: 0;
391
- background: transparent;
392
- animation: none;
393
- box-shadow: none;
394
- }
395
-
396
- .fc-prompt--no-ai-border:focus-within {
397
- box-shadow: none;
393
+ .fc-prompt--ai-border .fc-prompt__inner {
394
+ border: 0;
398
395
  }
399
396
 
400
- .fc-prompt--no-ai-border .fc-prompt__inner {
401
- border: 1px solid var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
397
+ .fc-prompt--ai-border:focus-within {
398
+ box-shadow: 0 8px 8px -6px color-mix(in srgb, var(--ng-color-blue-500) 60%, transparent);
402
399
  }
403
400
 
404
401
  .fc-prompt--error {
@@ -622,7 +619,6 @@ export const styles = css `
622
619
  width: 100%;
623
620
  border: 0;
624
621
  outline: none;
625
- background: transparent;
626
622
  color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
627
623
  caret-color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
628
624
  }
@@ -764,7 +760,6 @@ export const styles = css `
764
760
 
765
761
  @media (prefers-reduced-motion: reduce) {
766
762
  .fc-prompt {
767
- /* Respect any consumer-supplied gradient — just stop animating it. */
768
763
  animation: none;
769
764
  }
770
765
  }
@@ -701,7 +701,7 @@ export function renderPrompt(host, ctrl) {
701
701
  };
702
702
  const classes = [
703
703
  'fc-prompt',
704
- ctrl.noAiBorder ? 'fc-prompt--no-ai-border' : '',
704
+ ctrl.aiBorder ? 'fc-prompt--ai-border' : '',
705
705
  error ? 'fc-prompt--error' : '',
706
706
  ].filter(Boolean).join(' ');
707
707
  const onFocus = () => host.setPromptFocused(ctrl.id, true);
@@ -276,8 +276,7 @@ export interface FilterControl {
276
276
  gradientColors?: string[];
277
277
  gradientDirection?: string;
278
278
  gradientSpeedMs?: number;
279
- /** Prompt variant only. When true, disables the animated AI gradient border. */
280
- noAiBorder?: boolean;
279
+ aiBorder?: boolean;
281
280
  /**
282
281
  * Prompt variant only. Shows the inline clear (×) icon inside the input
283
282
  * when the value is non-empty. Defaults to `false` (hidden). Set `true` to