@ceebee/ui 1.9.0 → 1.10.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/THIRD_PARTY_NOTICES.md +19 -1
- package/dist/client.d.ts +331 -1
- package/dist/client.js +516 -51
- package/dist/styles.css +235 -0
- package/package.json +3 -2
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Third-party notices
|
|
2
2
|
|
|
3
|
-
`@ceebee/ui` includes software from
|
|
3
|
+
`@ceebee/ui` includes software from the projects below. Most are MIT-licensed; the one Apache-2.0
|
|
4
|
+
dependency names its licence in its own section.
|
|
4
5
|
|
|
5
6
|
## Ant Design
|
|
6
7
|
|
|
@@ -34,3 +35,20 @@ Copyright © 2018-present iamkun
|
|
|
34
35
|
|
|
35
36
|
Date and time components use `dayjs` 1.11.18 as their date engine. It is licensed under the MIT
|
|
36
37
|
License.
|
|
38
|
+
|
|
39
|
+
## @dnd-kit
|
|
40
|
+
|
|
41
|
+
Copyright © 2021, Claudéric Demers
|
|
42
|
+
|
|
43
|
+
`Board` in `@ceebee/ui/client` uses `@dnd-kit/core` 6.3.1, `@dnd-kit/sortable` 10.0.0 and
|
|
44
|
+
`@dnd-kit/utilities` 3.2.2 as its drag-and-drop runtime. They are licensed under the MIT License.
|
|
45
|
+
|
|
46
|
+
## Lightweight Charts™
|
|
47
|
+
|
|
48
|
+
Copyright © 2023 TradingView, Inc.
|
|
49
|
+
|
|
50
|
+
`ProgressCurve` in `@ceebee/ui/client` draws its canvas with `lightweight-charts` 5.2.1, a
|
|
51
|
+
dependency loaded on demand. It is licensed under the **Apache License, Version 2.0**; a copy is in
|
|
52
|
+
the package at `node_modules/lightweight-charts/LICENSE`, and the attribution is retained here
|
|
53
|
+
rather than painted into a consumer's chart, where the library's `attributionLogo` option is turned
|
|
54
|
+
off.
|
package/dist/client.d.ts
CHANGED
|
@@ -493,6 +493,336 @@ declare const Board: typeof BoardRoot & {
|
|
|
493
493
|
Skeleton: typeof BoardSkeleton;
|
|
494
494
|
};
|
|
495
495
|
|
|
496
|
+
/**
|
|
497
|
+
* What a dated series means, with no chart and no DOM in sight.
|
|
498
|
+
*
|
|
499
|
+
* Kept pure for the usual reason and one extra: these charts are drawn on a **canvas**, so nothing
|
|
500
|
+
* about them is inspectable from the outside. These functions are the only testable description of
|
|
501
|
+
* what the picture claims, and they are also what feeds the text alternative a screen reader reads
|
|
502
|
+
* and what a forced-colors viewer gets instead of the bitmap.
|
|
503
|
+
*/
|
|
504
|
+
/** One reading: a day, and a value on that day. */
|
|
505
|
+
interface SeriesPoint {
|
|
506
|
+
/** `YYYY-MM-DD`. A calendar day, not an instant. */
|
|
507
|
+
day: string;
|
|
508
|
+
value: number;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* `YYYY-MM-DD`, or null when it is not a day at all.
|
|
512
|
+
*
|
|
513
|
+
* The pattern is not the check: `2026-02-30` matches it and is not a day. The round-trip through `Date`
|
|
514
|
+
* settles that — but **an impossible month makes an Invalid Date, and `toISOString()` throws on one**
|
|
515
|
+
* rather than returning anything falsy. So the time value is tested before it is formatted; otherwise
|
|
516
|
+
* `2026-13-01` takes the whole chart down instead of being ignored.
|
|
517
|
+
*/
|
|
518
|
+
declare function asDay(value: string): string | null;
|
|
519
|
+
/**
|
|
520
|
+
* The points as the chart substrate demands them: ascending, and one per day.
|
|
521
|
+
*
|
|
522
|
+
* This is not tidying. The library **throws** on a repeated or out-of-order time rather than ignoring
|
|
523
|
+
* it, so a source that reports the same day twice — a correction, two crews reporting the same site —
|
|
524
|
+
* would take the whole screen down. The later value wins, because a correction is the point of sending
|
|
525
|
+
* the day again.
|
|
526
|
+
*/
|
|
527
|
+
declare function seriesPoints(points: readonly SeriesPoint[]): SeriesPoint[];
|
|
528
|
+
/** The value a series holds on a day: its last reading at or before it, or null before it starts. */
|
|
529
|
+
declare function valueOn(points: readonly SeriesPoint[], day: string): number | null;
|
|
530
|
+
/**
|
|
531
|
+
* Every day any series reports, with each series' value on it — the accessible rendering, not a caption.
|
|
532
|
+
*
|
|
533
|
+
* A canvas has no DOM, so this is the *only* thing a screen reader can be given, and it is what a
|
|
534
|
+
* forced-colors viewer sees in place of the bitmap. It is therefore the same data, not a summary. A
|
|
535
|
+
* value is carried forward from the last reading: a day nobody reported is not a day of nothing, and
|
|
536
|
+
* showing it as zero would say the measure collapsed.
|
|
537
|
+
*/
|
|
538
|
+
declare function alignRows(series: readonly {
|
|
539
|
+
key: string;
|
|
540
|
+
points: readonly SeriesPoint[];
|
|
541
|
+
}[]): {
|
|
542
|
+
day: string;
|
|
543
|
+
values: Record<string, number | null>;
|
|
544
|
+
}[];
|
|
545
|
+
/** The span the axis has to cover, or null when there is nothing to draw. */
|
|
546
|
+
declare function seriesSpan(series: readonly {
|
|
547
|
+
points: readonly SeriesPoint[];
|
|
548
|
+
}[]): {
|
|
549
|
+
from: string;
|
|
550
|
+
to: string;
|
|
551
|
+
} | null;
|
|
552
|
+
/**
|
|
553
|
+
* The reported day closest to a given one, or null when nothing was reported.
|
|
554
|
+
*
|
|
555
|
+
* A marker attaches to a data point — the substrate has no vertical line of its own — so anything a
|
|
556
|
+
* chart wants to mark has to land on a day some series actually reports.
|
|
557
|
+
*/
|
|
558
|
+
declare function nearestDay(days: readonly string[], day: string): string | null;
|
|
559
|
+
/** One decimal, because a figure read to four is false precision on a number somebody estimated. */
|
|
560
|
+
declare function round1(value: number): number;
|
|
561
|
+
|
|
562
|
+
/** The colours a canvas is drawn in, resolved from Tokens rather than written down. */
|
|
563
|
+
interface ChartPalette {
|
|
564
|
+
text: string;
|
|
565
|
+
muted: string;
|
|
566
|
+
grid: string;
|
|
567
|
+
background: string;
|
|
568
|
+
font: string;
|
|
569
|
+
/** One entry per series, in the order the series were given. */
|
|
570
|
+
series: string[];
|
|
571
|
+
/** Where a baseline series sits above its base, and below it. */
|
|
572
|
+
above: string;
|
|
573
|
+
below: string;
|
|
574
|
+
}
|
|
575
|
+
/** How thick, and solid or dashed. A plan reads as a reference; delivery reads as the fact. */
|
|
576
|
+
type SeriesEmphasis = 'reference' | 'primary';
|
|
577
|
+
interface SeriesSpec {
|
|
578
|
+
/** Stable key, used in the accessible table and to look a value up. */
|
|
579
|
+
key: string;
|
|
580
|
+
/** What a reader calls it. */
|
|
581
|
+
label: string;
|
|
582
|
+
points: SeriesPoint[];
|
|
583
|
+
emphasis?: SeriesEmphasis;
|
|
584
|
+
/** The Token this series takes its colour from, e.g. `--cb-tone-brand`. */
|
|
585
|
+
colorToken: string;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* A fixed vertical range, when autoscale would lie.
|
|
589
|
+
*
|
|
590
|
+
* A percentage chart pins 0–100: autoscale stretches a job at 30% to the full height of the plot,
|
|
591
|
+
* which reads as *nearly there* — the opposite of the truth. A money chart normally does not pin,
|
|
592
|
+
* because there is no natural ceiling.
|
|
593
|
+
*/
|
|
594
|
+
interface ValueRange {
|
|
595
|
+
min: number;
|
|
596
|
+
max: number;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* A baseline: the value the series is read against, coloured differently above and below.
|
|
600
|
+
*
|
|
601
|
+
* Zero on a cash projection is the obvious one — the day the balance crosses it is the whole point of
|
|
602
|
+
* the chart — and a minimum buffer is the useful one, because a business that must keep 50 juta on hand
|
|
603
|
+
* is in trouble well before it reaches zero.
|
|
604
|
+
*/
|
|
605
|
+
interface Baseline {
|
|
606
|
+
value: number;
|
|
607
|
+
label?: string;
|
|
608
|
+
}
|
|
609
|
+
interface TimeSeriesChartProps {
|
|
610
|
+
/** Accessible name. It is the chart's title as a screen reader announces it. */
|
|
611
|
+
label: string;
|
|
612
|
+
series: SeriesSpec[];
|
|
613
|
+
/** Turns a value into the text on the price scale and in the table. */
|
|
614
|
+
format: (value: number) => string;
|
|
615
|
+
range?: ValueRange;
|
|
616
|
+
/** Draws one series against a base value, shaded above and below. Only the first series is drawn. */
|
|
617
|
+
baseline?: Baseline;
|
|
618
|
+
/** A day to mark, and what to call it. The mark lands on the nearest day any series reports. */
|
|
619
|
+
mark?: {
|
|
620
|
+
day: string;
|
|
621
|
+
label: string;
|
|
622
|
+
};
|
|
623
|
+
height?: number;
|
|
624
|
+
emptyLabel?: string;
|
|
625
|
+
tableLabel?: string;
|
|
626
|
+
dayLabel?: string;
|
|
627
|
+
loading?: boolean;
|
|
628
|
+
className?: string;
|
|
629
|
+
}
|
|
630
|
+
interface TimeSeriesChartSkeletonProps {
|
|
631
|
+
height?: number;
|
|
632
|
+
label?: string;
|
|
633
|
+
className?: string;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* A dated chart: any number of series over calendar days, or one series read against a baseline.
|
|
638
|
+
*
|
|
639
|
+
* It is drawn on a canvas, and that has one consequence which shapes this whole file: **the picture is
|
|
640
|
+
* not in the DOM.** A screen reader finds nothing, a forced-colors viewer gets a bitmap the system
|
|
641
|
+
* cannot recolour, and no test can look at it. So the table below the plot is not a caption or a
|
|
642
|
+
* fallback — it is the chart's other rendering, always present, built from the same numbers, and it is
|
|
643
|
+
* what a forced-colors viewer sees *instead of* the canvas.
|
|
644
|
+
*
|
|
645
|
+
* This is the primitive the library's dated Compositions are built from. It carries no domain meaning:
|
|
646
|
+
* it does not know what a plan is or what money is, only how to draw dated values honestly and how to
|
|
647
|
+
* say the same thing in words.
|
|
648
|
+
*/
|
|
649
|
+
declare function TimeSeriesChart({ label, series, format, range, baseline, mark, height, emptyLabel, tableLabel, dayLabel, loading, className, }: TimeSeriesChartProps): react.JSX.Element;
|
|
650
|
+
|
|
651
|
+
/** Holds the chart's geometry while the readings load, so the page does not jump when they land. */
|
|
652
|
+
declare function TimeSeriesChartSkeleton({ height, label, className, }: TimeSeriesChartSkeletonProps): react.JSX.Element;
|
|
653
|
+
|
|
654
|
+
interface BalanceCurveProps {
|
|
655
|
+
/** Accessible name. It is the chart's title as a screen reader announces it. */
|
|
656
|
+
label: string;
|
|
657
|
+
/** The running balance, by day. One point per day the balance is known for. */
|
|
658
|
+
balances: SeriesPoint[];
|
|
659
|
+
/**
|
|
660
|
+
* How a value is written. The library holds no currency: a product passes its own formatter, so the
|
|
661
|
+
* same chart serves rupiah, a percentage of a budget, or litres of diesel.
|
|
662
|
+
*/
|
|
663
|
+
format: (value: number) => string;
|
|
664
|
+
/**
|
|
665
|
+
* The line the balance is read against, and what to call it. Defaults to zero.
|
|
666
|
+
*
|
|
667
|
+
* Not always zero, and that is the point: a business that must keep a minimum on hand is in trouble
|
|
668
|
+
* well before it reaches nothing.
|
|
669
|
+
*/
|
|
670
|
+
threshold?: {
|
|
671
|
+
value: number;
|
|
672
|
+
label?: string;
|
|
673
|
+
};
|
|
674
|
+
height?: number;
|
|
675
|
+
seriesLabel?: string;
|
|
676
|
+
emptyLabel?: string;
|
|
677
|
+
tableLabel?: string;
|
|
678
|
+
/** How the reading is worded. Each takes the formatted figures the component computed. */
|
|
679
|
+
belowLabel?: (from: string, lowest: string, days: number) => string;
|
|
680
|
+
clearLabel?: (lowest: string) => string;
|
|
681
|
+
loading?: boolean;
|
|
682
|
+
className?: string;
|
|
683
|
+
}
|
|
684
|
+
interface BalanceCurveSkeletonProps {
|
|
685
|
+
height?: number;
|
|
686
|
+
label?: string;
|
|
687
|
+
className?: string;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** Holds the chart's geometry while the balances load, so the page does not jump when they land. */
|
|
691
|
+
declare function BalanceCurveSkeleton({ height, label, className, }: BalanceCurveSkeletonProps): react.JSX.Element;
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* A running balance over days, read against a line: a cash projection, a budget remaining, a tank.
|
|
695
|
+
*
|
|
696
|
+
* The substrate draws this as a **baseline** series rather than a line, which is not decoration: it
|
|
697
|
+
* shades above and below the threshold in the semantic colours, so the stretch that is in trouble is
|
|
698
|
+
* coloured by its own values instead of by a tag somebody has to read. Seven red tags in a row say
|
|
699
|
+
* nothing about how far down the dip goes; a shaded area says it at a glance.
|
|
700
|
+
*
|
|
701
|
+
* The sentence above the plot is the other half. A chart shows the shape; the sentence gives the two
|
|
702
|
+
* figures a decision needs — the day it goes under and the lowest it gets — so nobody has to read a
|
|
703
|
+
* position off an axis to act on it.
|
|
704
|
+
*/
|
|
705
|
+
declare function BalanceCurveRoot({ label, balances, format, threshold, height, seriesLabel, emptyLabel, tableLabel, belowLabel, clearLabel, loading, className, }: BalanceCurveProps): react.JSX.Element;
|
|
706
|
+
/** The Composition and its Skeleton, so a loading page keeps the chart's geometry. */
|
|
707
|
+
declare const BalanceCurve: typeof BalanceCurveRoot & {
|
|
708
|
+
Skeleton: typeof BalanceCurveSkeleton;
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* What a running balance says about itself.
|
|
713
|
+
*
|
|
714
|
+
* A balance chart exists to answer two questions a list of days cannot: **when does this dip below the
|
|
715
|
+
* line, and how far down does it go.** Both are computed here rather than left to the reader, because
|
|
716
|
+
* the reader's alternative is scanning thirty rows — which is exactly the thing the chart replaces.
|
|
717
|
+
*/
|
|
718
|
+
|
|
719
|
+
interface BalanceReading {
|
|
720
|
+
/** The lowest balance over the window, and the day it happens. Null when there is nothing to read. */
|
|
721
|
+
lowest: number | null;
|
|
722
|
+
lowestDay: string | null;
|
|
723
|
+
/** The first day the balance is below the threshold, or null when it never is. */
|
|
724
|
+
firstBelowDay: string | null;
|
|
725
|
+
/** How many days sit below the threshold. Zero is the answer somebody is hoping for. */
|
|
726
|
+
daysBelow: number;
|
|
727
|
+
/** The balance on the last day of the window. */
|
|
728
|
+
closing: number | null;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* The reading, against a threshold.
|
|
732
|
+
*
|
|
733
|
+
* The threshold is not always zero, and that is the point of it being a parameter: a business that must
|
|
734
|
+
* keep fifty million on hand is in trouble well before it reaches nothing, and a chart drawn against
|
|
735
|
+
* zero would tell it everything is fine right up to the day it is not.
|
|
736
|
+
*
|
|
737
|
+
* "Below" is strict. A balance sitting exactly on the threshold has met it, and reporting that as a
|
|
738
|
+
* breach would cry wolf on the one day the business did precisely what it planned to.
|
|
739
|
+
*/
|
|
740
|
+
declare function balanceReading(points: readonly SeriesPoint[], threshold: number): BalanceReading;
|
|
741
|
+
|
|
742
|
+
/** One reading: a day, and the share of the work complete by then. */
|
|
743
|
+
interface CurvePoint {
|
|
744
|
+
/** `YYYY-MM-DD`. A calendar day, not an instant — progress is reported for a day's work. */
|
|
745
|
+
day: string;
|
|
746
|
+
/** 0–100. */
|
|
747
|
+
percent: number;
|
|
748
|
+
}
|
|
749
|
+
interface CurveReading {
|
|
750
|
+
plannedPercent: number | null;
|
|
751
|
+
actualPercent: number | null;
|
|
752
|
+
/** Actual minus planned, in points. Negative is behind. Null when either side is unknown. */
|
|
753
|
+
gap: number | null;
|
|
754
|
+
}
|
|
755
|
+
/** The library's generic point shape, from this Block's `percent`-named one. */
|
|
756
|
+
declare function toPoints(points: readonly CurvePoint[]): SeriesPoint[];
|
|
757
|
+
/**
|
|
758
|
+
* The reading on a given day: what was planned, what was reached, and the gap.
|
|
759
|
+
*
|
|
760
|
+
* Both sides carry the last report forward — a day nobody reported is not a day of no progress, and
|
|
761
|
+
* drawing it as zero would say the work went backwards. A plan states its shape at the points it
|
|
762
|
+
* states them, so it is read the same way.
|
|
763
|
+
*/
|
|
764
|
+
declare function readingOn(planned: readonly CurvePoint[], actual: readonly CurvePoint[], day: string): CurveReading;
|
|
765
|
+
/** Every day either series reports, with both values and the gap — what a screen reader is given. */
|
|
766
|
+
declare function curveRows(planned: readonly CurvePoint[], actual: readonly CurvePoint[]): {
|
|
767
|
+
day: string;
|
|
768
|
+
plannedPercent: number | null;
|
|
769
|
+
actualPercent: number | null;
|
|
770
|
+
gap: number | null;
|
|
771
|
+
}[];
|
|
772
|
+
|
|
773
|
+
interface ProgressCurveProps {
|
|
774
|
+
/** What the plan said would be complete, by day. */
|
|
775
|
+
planned: CurvePoint[];
|
|
776
|
+
/** What was actually reported complete, by day. */
|
|
777
|
+
actual: CurvePoint[];
|
|
778
|
+
/** Accessible name. It is the chart's title as a screen reader announces it, so name the job. */
|
|
779
|
+
label: string;
|
|
780
|
+
/**
|
|
781
|
+
* `YYYY-MM-DD`. The day the reading above the plot is taken on — both series carry their last value
|
|
782
|
+
* at or before it forward. Without it the reading is taken on the last day anything was reported.
|
|
783
|
+
*
|
|
784
|
+
* It is deliberately not drawn on the plot: a mark attaches to a data point, and today is usually not
|
|
785
|
+
* one. What is marked is the last report (see `lastReportLabel`).
|
|
786
|
+
*/
|
|
787
|
+
today?: string;
|
|
788
|
+
height?: number;
|
|
789
|
+
plannedLabel?: string;
|
|
790
|
+
actualLabel?: string;
|
|
791
|
+
/** Text on the mark sitting at the last reported day. */
|
|
792
|
+
lastReportLabel?: string;
|
|
793
|
+
/** Shown in place of the chart when neither series has a single usable day. */
|
|
794
|
+
emptyLabel?: string;
|
|
795
|
+
/** The table a screen reader reads, and what a forced-colors viewer sees instead of the canvas. */
|
|
796
|
+
tableLabel?: string;
|
|
797
|
+
aheadLabel?: string;
|
|
798
|
+
behindLabel?: string;
|
|
799
|
+
onTrackLabel?: string;
|
|
800
|
+
loading?: boolean;
|
|
801
|
+
className?: string;
|
|
802
|
+
}
|
|
803
|
+
interface ProgressCurveSkeletonProps {
|
|
804
|
+
height?: number;
|
|
805
|
+
label?: string;
|
|
806
|
+
className?: string;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** Holds the chart's geometry while the readings load, so the page does not jump when they land. */
|
|
810
|
+
declare function ProgressCurveSkeleton({ height, label, className, }: ProgressCurveSkeletonProps): react.JSX.Element;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* An S-curve: what the plan said would be done by each day, against what was.
|
|
814
|
+
*
|
|
815
|
+
* The canvas, the Token reading, the forced-colors path and the accessible table all belong to
|
|
816
|
+
* `TimeSeriesChart`. What is here is the part that is about *progress*: a scale pinned to 0–100%,
|
|
817
|
+
* because autoscale stretches a job at 30% to the full height of the plot and reads as "nearly
|
|
818
|
+
* there"; and the reading in words above it, which is the one number somebody opened this to find.
|
|
819
|
+
*/
|
|
820
|
+
declare function ProgressCurveRoot({ planned, actual, label, today, height, plannedLabel, actualLabel, lastReportLabel, emptyLabel, tableLabel, aheadLabel, behindLabel, onTrackLabel, loading, className, }: ProgressCurveProps): react.JSX.Element;
|
|
821
|
+
/** The Composition and its Skeleton, so a loading page keeps the chart's geometry. */
|
|
822
|
+
declare const ProgressCurve: typeof ProgressCurveRoot & {
|
|
823
|
+
Skeleton: typeof ProgressCurveSkeleton;
|
|
824
|
+
};
|
|
825
|
+
|
|
496
826
|
interface DiagramSkeletonProps {
|
|
497
827
|
label?: string;
|
|
498
828
|
}
|
|
@@ -583,4 +913,4 @@ declare const DiagramEditor: typeof DiagramEditorRoot & {
|
|
|
583
913
|
Skeleton: typeof DiagramSkeleton;
|
|
584
914
|
};
|
|
585
915
|
|
|
586
|
-
export { Board, type BoardCard, type BoardColumn, type BoardLabels, type BoardMove, type BoardMoveResult, type BoardPosition, type BoardProps, type BoardShape, type BoardSkeletonProps, type CeebeeAntStyleCache, CeebeeAntStyleProvider, type CeebeeAntStyleProviderProps, Checklist, type ChecklistProps, type ChecklistTask, type Command, CommandPalette, type CommandPaletteProps, DEFAULT_LABELS, Diagram, type DiagramEdge, DiagramEditor, type DiagramEditorProps, type DiagramNode, type DiagramPosition, type DiagramProps, type DiagramRemoval, type DiagramRenameTarget, type DiagramShape, type DiagramSkeletonProps, type DurationToken, type Labels, LabelsProvider, type LabelsProviderProps, Modal, type ModalProps, type MotionHelpers, MotionProvider, type MotionProviderProps, type MotionSettings, type NavItem, type NavSection, type PaletteCommand, PanZoomCanvas, type PanZoomCanvasProps, type PanZoomCanvasSkeletonProps, type RankedCommand, Reveal, type RevealProps, Sidebar, type SidebarProps, type SpringPreset, Stagger, type StaggerProps, StickerGroup, type StickerGroupProps, type StickerGroupSkeletonProps, type StickerItem, ThemeBridge, type ThemeBridgeProps, type ThemeChoice, ThemeProvider, type ThemeProviderProps, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, TopBar, type TopBarProps, createCeebeeAntStyleCache, extractCeebeeAntStyles, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
|
|
916
|
+
export { BalanceCurve, type BalanceCurveProps, BalanceCurveSkeleton, type BalanceCurveSkeletonProps, type BalanceReading, type Baseline, Board, type BoardCard, type BoardColumn, type BoardLabels, type BoardMove, type BoardMoveResult, type BoardPosition, type BoardProps, type BoardShape, type BoardSkeletonProps, type CeebeeAntStyleCache, CeebeeAntStyleProvider, type CeebeeAntStyleProviderProps, type ChartPalette, Checklist, type ChecklistProps, type ChecklistTask, type Command, CommandPalette, type CommandPaletteProps, type CurvePoint, type CurveReading, DEFAULT_LABELS, Diagram, type DiagramEdge, DiagramEditor, type DiagramEditorProps, type DiagramNode, type DiagramPosition, type DiagramProps, type DiagramRemoval, type DiagramRenameTarget, type DiagramShape, type DiagramSkeletonProps, type DurationToken, type Labels, LabelsProvider, type LabelsProviderProps, Modal, type ModalProps, type MotionHelpers, MotionProvider, type MotionProviderProps, type MotionSettings, type NavItem, type NavSection, type PaletteCommand, PanZoomCanvas, type PanZoomCanvasProps, type PanZoomCanvasSkeletonProps, ProgressCurve, type ProgressCurveProps, ProgressCurveSkeleton, type ProgressCurveSkeletonProps, type RankedCommand, Reveal, type RevealProps, type SeriesEmphasis, type SeriesPoint, type SeriesSpec, Sidebar, type SidebarProps, type SpringPreset, Stagger, type StaggerProps, StickerGroup, type StickerGroupProps, type StickerGroupSkeletonProps, type StickerItem, ThemeBridge, type ThemeBridgeProps, type ThemeChoice, ThemeProvider, type ThemeProviderProps, TimeSeriesChart, type TimeSeriesChartProps, TimeSeriesChartSkeleton, type TimeSeriesChartSkeletonProps, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, TopBar, type TopBarProps, type ValueRange, alignRows, asDay, balanceReading, createCeebeeAntStyleCache, curveRows, extractCeebeeAntStyles, filterCommands, groupCommands, nearestDay, rankCommand, readingOn, round1, seriesPoints, seriesSpan, toPoints, useLabels, useMotionSettings, useTheme, useToast, valueOn };
|
package/dist/client.js
CHANGED
|
@@ -122,6 +122,66 @@ function ModalRoot({
|
|
|
122
122
|
}
|
|
123
123
|
var Modal = Object.assign(ModalRoot, Modal$1);
|
|
124
124
|
|
|
125
|
+
// src/lib/css-probe.ts
|
|
126
|
+
function createCssProbe(root) {
|
|
127
|
+
const probe = document.createElement("span");
|
|
128
|
+
probe.style.position = "fixed";
|
|
129
|
+
probe.style.pointerEvents = "none";
|
|
130
|
+
probe.style.visibility = "hidden";
|
|
131
|
+
root.append(probe);
|
|
132
|
+
return {
|
|
133
|
+
color: (name) => resolveCssColor(probe, name),
|
|
134
|
+
length: (name) => resolveCssLength(probe, name),
|
|
135
|
+
done: () => probe.remove()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function resolveCssLength(probe, name) {
|
|
139
|
+
probe.style.width = `var(${name})`;
|
|
140
|
+
const value = Number.parseFloat(getComputedStyle(probe).width);
|
|
141
|
+
probe.style.removeProperty("width");
|
|
142
|
+
return Number.isFinite(value) ? value : void 0;
|
|
143
|
+
}
|
|
144
|
+
function resolveCssColor(probe, name) {
|
|
145
|
+
probe.style.color = `var(${name})`;
|
|
146
|
+
const value = getComputedStyle(probe).color;
|
|
147
|
+
probe.style.removeProperty("color");
|
|
148
|
+
if (!value) return void 0;
|
|
149
|
+
if (value.startsWith("var(")) return void 0;
|
|
150
|
+
if (/^(?:#|rgb|hsl|hsv)/i.test(value)) return value;
|
|
151
|
+
const canvas = document.createElement("canvas");
|
|
152
|
+
canvas.width = 1;
|
|
153
|
+
canvas.height = 1;
|
|
154
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
155
|
+
if (!context) return void 0;
|
|
156
|
+
context.clearRect(0, 0, 1, 1);
|
|
157
|
+
context.fillStyle = value;
|
|
158
|
+
context.fillRect(0, 0, 1, 1);
|
|
159
|
+
const [red = 0, green = 0, blue = 0, alpha = 255] = context.getImageData(0, 0, 1, 1).data;
|
|
160
|
+
return `rgba(${red}, ${green}, ${blue}, ${alpha / 255})`;
|
|
161
|
+
}
|
|
162
|
+
var SKIN_LINK_ID = "cb-skin";
|
|
163
|
+
function watchTokens(onChange) {
|
|
164
|
+
const rootObserver = new MutationObserver(onChange);
|
|
165
|
+
rootObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
|
166
|
+
const headObserver = new MutationObserver((records) => {
|
|
167
|
+
const skinChanged = records.some((record) => [...record.addedNodes, ...record.removedNodes].some((node) => node instanceof HTMLElement && node.id === SKIN_LINK_ID));
|
|
168
|
+
if (skinChanged) onChange();
|
|
169
|
+
});
|
|
170
|
+
headObserver.observe(document.head, { childList: true });
|
|
171
|
+
const onSkinLoad = (event) => {
|
|
172
|
+
if (event.target instanceof HTMLElement && event.target.id === SKIN_LINK_ID) onChange();
|
|
173
|
+
};
|
|
174
|
+
document.addEventListener("load", onSkinLoad, true);
|
|
175
|
+
const dark = window.matchMedia("(prefers-color-scheme: dark)");
|
|
176
|
+
dark.addEventListener("change", onChange);
|
|
177
|
+
return () => {
|
|
178
|
+
rootObserver.disconnect();
|
|
179
|
+
headObserver.disconnect();
|
|
180
|
+
document.removeEventListener("load", onSkinLoad, true);
|
|
181
|
+
dark.removeEventListener("change", onChange);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
125
185
|
// src/theme/ant-theme-seeds.generated.ts
|
|
126
186
|
var generatedCeebeeAntSeeds = {
|
|
127
187
|
"ceebee": {
|
|
@@ -792,7 +852,6 @@ var THEME_MODE_COOKIE = "cb-theme-mode";
|
|
|
792
852
|
function serializeThemeModeCookie(mode) {
|
|
793
853
|
return `${THEME_MODE_COOKIE}=${mode}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
794
854
|
}
|
|
795
|
-
var SKIN_LINK_ID = "cb-skin";
|
|
796
855
|
function ThemeBridge({
|
|
797
856
|
children,
|
|
798
857
|
mode,
|
|
@@ -806,27 +865,12 @@ function ThemeBridge({
|
|
|
806
865
|
}, []);
|
|
807
866
|
useEffect(() => {
|
|
808
867
|
refresh();
|
|
809
|
-
const
|
|
810
|
-
rootObserver.observe(document.documentElement, {
|
|
811
|
-
attributes: true,
|
|
812
|
-
attributeFilter: ["data-theme"]
|
|
813
|
-
});
|
|
814
|
-
const headObserver = new MutationObserver((records) => {
|
|
815
|
-
const skinChanged = records.some((record) => [...record.addedNodes, ...record.removedNodes].some((node) => node instanceof HTMLElement && node.id === SKIN_LINK_ID));
|
|
816
|
-
if (skinChanged) refresh();
|
|
817
|
-
});
|
|
818
|
-
headObserver.observe(document.head, { childList: true });
|
|
819
|
-
const onSkinLoad = (event) => {
|
|
820
|
-
if (event.target instanceof HTMLElement && event.target.id === SKIN_LINK_ID) refresh();
|
|
821
|
-
};
|
|
822
|
-
document.addEventListener("load", onSkinLoad, true);
|
|
868
|
+
const stopWatchingTokens = watchTokens(refresh);
|
|
823
869
|
const coarsePointer = window.matchMedia("(pointer: coarse)");
|
|
824
870
|
const onPointerChange = () => refresh();
|
|
825
871
|
coarsePointer.addEventListener("change", onPointerChange);
|
|
826
872
|
return () => {
|
|
827
|
-
|
|
828
|
-
headObserver.disconnect();
|
|
829
|
-
document.removeEventListener("load", onSkinLoad, true);
|
|
873
|
+
stopWatchingTokens();
|
|
830
874
|
coarsePointer.removeEventListener("change", onPointerChange);
|
|
831
875
|
};
|
|
832
876
|
}, [refresh]);
|
|
@@ -853,13 +897,9 @@ function mergeComponents(base, override) {
|
|
|
853
897
|
return merged;
|
|
854
898
|
}
|
|
855
899
|
function readCeebeeThemeToken(root) {
|
|
856
|
-
const probe =
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
probe.style.visibility = "hidden";
|
|
860
|
-
root.append(probe);
|
|
861
|
-
const color = (name) => resolveCssColor(probe, name);
|
|
862
|
-
const length = (name) => resolveCssLength(probe, name);
|
|
900
|
+
const probe = createCssProbe(root);
|
|
901
|
+
const color = (name) => probe.color(name);
|
|
902
|
+
const length = (name) => probe.length(name);
|
|
863
903
|
const tokens = {
|
|
864
904
|
colorPrimary: color("--cb-tone-brand"),
|
|
865
905
|
colorInfo: color("--cb-tone-info"),
|
|
@@ -919,7 +959,7 @@ function readCeebeeThemeToken(root) {
|
|
|
919
959
|
dotActiveBorderColor: trackBg
|
|
920
960
|
};
|
|
921
961
|
}
|
|
922
|
-
probe.
|
|
962
|
+
probe.done();
|
|
923
963
|
return {
|
|
924
964
|
token: Object.fromEntries(
|
|
925
965
|
Object.entries(tokens).filter(([, value]) => value !== void 0)
|
|
@@ -927,30 +967,6 @@ function readCeebeeThemeToken(root) {
|
|
|
927
967
|
components
|
|
928
968
|
};
|
|
929
969
|
}
|
|
930
|
-
function resolveCssLength(probe, name) {
|
|
931
|
-
probe.style.width = `var(${name})`;
|
|
932
|
-
const value = Number.parseFloat(getComputedStyle(probe).width);
|
|
933
|
-
probe.style.removeProperty("width");
|
|
934
|
-
return Number.isFinite(value) ? value : void 0;
|
|
935
|
-
}
|
|
936
|
-
function resolveCssColor(probe, name) {
|
|
937
|
-
probe.style.color = `var(${name})`;
|
|
938
|
-
const value = getComputedStyle(probe).color;
|
|
939
|
-
probe.style.removeProperty("color");
|
|
940
|
-
if (!value) return void 0;
|
|
941
|
-
if (value.startsWith("var(")) return void 0;
|
|
942
|
-
if (/^(?:#|rgb|hsl|hsv)/i.test(value)) return value;
|
|
943
|
-
const canvas = document.createElement("canvas");
|
|
944
|
-
canvas.width = 1;
|
|
945
|
-
canvas.height = 1;
|
|
946
|
-
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
947
|
-
if (!context) return void 0;
|
|
948
|
-
context.clearRect(0, 0, 1, 1);
|
|
949
|
-
context.fillStyle = value;
|
|
950
|
-
context.fillRect(0, 0, 1, 1);
|
|
951
|
-
const [red = 0, green = 0, blue = 0, alpha = 255] = context.getImageData(0, 0, 1, 1).data;
|
|
952
|
-
return `rgba(${red}, ${green}, ${blue}, ${alpha / 255})`;
|
|
953
|
-
}
|
|
954
970
|
function CeebeeAntStyleProvider({ cache, children }) {
|
|
955
971
|
return /* @__PURE__ */ jsx(StyleProvider, { cache, children });
|
|
956
972
|
}
|
|
@@ -2035,6 +2051,455 @@ function useNarrow(query) {
|
|
|
2035
2051
|
return narrow;
|
|
2036
2052
|
}
|
|
2037
2053
|
var Board = Object.assign(BoardRoot, { Skeleton: BoardSkeleton });
|
|
2054
|
+
|
|
2055
|
+
// src/data/time-series/time-series.chart.ts
|
|
2056
|
+
async function mountTimeSeries(host, shape, palette) {
|
|
2057
|
+
const { BaselineSeries, ColorType, LineSeries, LineStyle, createChart, createSeriesMarkers } = await import('lightweight-charts');
|
|
2058
|
+
const chart = createChart(host, {
|
|
2059
|
+
autoSize: true,
|
|
2060
|
+
/* A dashboard chart is read, not traded. Panning and zooming would let somebody scroll the data
|
|
2061
|
+
off the screen and leave them looking at a fragment with no way back, so the whole span is
|
|
2062
|
+
fitted and the viewport is fixed. */
|
|
2063
|
+
handleScroll: false,
|
|
2064
|
+
handleScale: false
|
|
2065
|
+
});
|
|
2066
|
+
const scale = shape.range ? () => ({ priceRange: { minValue: shape.range?.min ?? 0, maxValue: shape.range?.max ?? 0 } }) : void 0;
|
|
2067
|
+
const common = { priceLineVisible: false, lastValueVisible: false, autoscaleInfoProvider: scale };
|
|
2068
|
+
const drawn = shape.baseline ? [
|
|
2069
|
+
chart.addSeries(BaselineSeries, {
|
|
2070
|
+
...common,
|
|
2071
|
+
baseValue: { type: "price", price: shape.baseline.value },
|
|
2072
|
+
lineWidth: 2
|
|
2073
|
+
})
|
|
2074
|
+
] : shape.series.map(
|
|
2075
|
+
(series) => chart.addSeries(LineSeries, {
|
|
2076
|
+
...common,
|
|
2077
|
+
lineWidth: series.emphasis === "reference" ? 2 : 3,
|
|
2078
|
+
lineStyle: series.emphasis === "reference" ? LineStyle.Dashed : LineStyle.Solid
|
|
2079
|
+
})
|
|
2080
|
+
);
|
|
2081
|
+
const markedSeries = drawn.at(-1);
|
|
2082
|
+
const markers = markedSeries ? createSeriesMarkers(markedSeries, []) : null;
|
|
2083
|
+
let current = palette;
|
|
2084
|
+
const apply = (next) => {
|
|
2085
|
+
current = next;
|
|
2086
|
+
chart.applyOptions({
|
|
2087
|
+
layout: {
|
|
2088
|
+
background: { type: ColorType.Solid, color: next.background },
|
|
2089
|
+
textColor: next.text,
|
|
2090
|
+
fontFamily: next.font,
|
|
2091
|
+
/* Apache-2.0 asks that the NOTICE travel with the distribution, not that a logo be painted
|
|
2092
|
+
into a consumer's dashboard. The attribution lives in THIRD_PARTY_NOTICES.md. */
|
|
2093
|
+
attributionLogo: false
|
|
2094
|
+
},
|
|
2095
|
+
grid: { vertLines: { color: next.grid }, horzLines: { color: next.grid } },
|
|
2096
|
+
rightPriceScale: { borderColor: next.grid, scaleMargins: { top: 0.1, bottom: 0.08 } },
|
|
2097
|
+
timeScale: { borderColor: next.grid, fixLeftEdge: true, fixRightEdge: true },
|
|
2098
|
+
crosshair: { vertLine: { color: next.muted }, horzLine: { color: next.muted } },
|
|
2099
|
+
localization: { priceFormatter: shape.format }
|
|
2100
|
+
});
|
|
2101
|
+
drawn.forEach((series, index) => {
|
|
2102
|
+
if (shape.baseline) {
|
|
2103
|
+
series.applyOptions({
|
|
2104
|
+
topLineColor: next.above,
|
|
2105
|
+
topFillColor1: next.above,
|
|
2106
|
+
topFillColor2: next.background,
|
|
2107
|
+
bottomLineColor: next.below,
|
|
2108
|
+
bottomFillColor1: next.background,
|
|
2109
|
+
bottomFillColor2: next.below,
|
|
2110
|
+
crosshairMarkerBorderColor: next.background
|
|
2111
|
+
});
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
series.applyOptions({
|
|
2115
|
+
color: next.series[index] ?? next.text,
|
|
2116
|
+
crosshairMarkerBorderColor: next.background
|
|
2117
|
+
});
|
|
2118
|
+
});
|
|
2119
|
+
};
|
|
2120
|
+
apply(palette);
|
|
2121
|
+
return {
|
|
2122
|
+
setData(series) {
|
|
2123
|
+
drawn.forEach((drawnSeries, index) => {
|
|
2124
|
+
const points = series[index]?.points ?? [];
|
|
2125
|
+
drawnSeries.setData(points.map((point) => ({ time: point.day, value: point.value })));
|
|
2126
|
+
});
|
|
2127
|
+
chart.timeScale().fitContent();
|
|
2128
|
+
},
|
|
2129
|
+
mark(day, text) {
|
|
2130
|
+
markers?.setMarkers(day === null ? [] : [{
|
|
2131
|
+
time: day,
|
|
2132
|
+
position: "belowBar",
|
|
2133
|
+
shape: "arrowUp",
|
|
2134
|
+
color: current.series[current.series.length - 1] ?? current.text,
|
|
2135
|
+
text
|
|
2136
|
+
}]);
|
|
2137
|
+
},
|
|
2138
|
+
applyPalette: apply,
|
|
2139
|
+
destroy() {
|
|
2140
|
+
chart.remove();
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// src/data/time-series/time-series.math.ts
|
|
2146
|
+
function asDay(value) {
|
|
2147
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
2148
|
+
const at = (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).getTime();
|
|
2149
|
+
if (Number.isNaN(at)) return null;
|
|
2150
|
+
return new Date(at).toISOString().startsWith(value) ? value : null;
|
|
2151
|
+
}
|
|
2152
|
+
function seriesPoints(points) {
|
|
2153
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
2154
|
+
for (const point of points) {
|
|
2155
|
+
if (asDay(point.day) === null) continue;
|
|
2156
|
+
byDay.set(point.day, point.value);
|
|
2157
|
+
}
|
|
2158
|
+
return [...byDay].map(([day, value]) => ({ day, value })).sort((a, b) => a.day.localeCompare(b.day));
|
|
2159
|
+
}
|
|
2160
|
+
function valueOn(points, day) {
|
|
2161
|
+
const reached = seriesPoints(points).filter((point) => point.day <= day);
|
|
2162
|
+
const last = reached[reached.length - 1];
|
|
2163
|
+
return last ? last.value : null;
|
|
2164
|
+
}
|
|
2165
|
+
function alignRows(series) {
|
|
2166
|
+
const days = [...new Set(series.flatMap((one) => seriesPoints(one.points).map((point) => point.day)))].sort();
|
|
2167
|
+
return days.map((day) => {
|
|
2168
|
+
const values = {};
|
|
2169
|
+
for (const one of series) values[one.key] = valueOn(one.points, day);
|
|
2170
|
+
return { day, values };
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
function seriesSpan(series) {
|
|
2174
|
+
const days = series.flatMap((one) => seriesPoints(one.points).map((point) => point.day)).sort();
|
|
2175
|
+
const from = days[0];
|
|
2176
|
+
const to = days[days.length - 1];
|
|
2177
|
+
return from && to ? { from, to } : null;
|
|
2178
|
+
}
|
|
2179
|
+
function nearestDay(days, day) {
|
|
2180
|
+
const known = days.filter((candidate) => asDay(candidate) !== null);
|
|
2181
|
+
if (known.length === 0 || asDay(day) === null) return null;
|
|
2182
|
+
const distance2 = (candidate) => Math.abs((/* @__PURE__ */ new Date(`${candidate}T00:00:00Z`)).getTime() - (/* @__PURE__ */ new Date(`${day}T00:00:00Z`)).getTime());
|
|
2183
|
+
return known.reduce((closest, candidate) => distance2(candidate) < distance2(closest) ? candidate : closest);
|
|
2184
|
+
}
|
|
2185
|
+
function round1(value) {
|
|
2186
|
+
return Math.round(value * 10) / 10;
|
|
2187
|
+
}
|
|
2188
|
+
function TimeSeriesChartSkeleton({
|
|
2189
|
+
height = 260,
|
|
2190
|
+
label = "Loading chart",
|
|
2191
|
+
className
|
|
2192
|
+
}) {
|
|
2193
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("cb-chart", "cb-chart--skeleton", className), role: "status", "aria-label": label, children: [
|
|
2194
|
+
/* @__PURE__ */ jsx("span", { className: "cb-chart__skeleton-head", "aria-hidden": "true" }),
|
|
2195
|
+
/* @__PURE__ */ jsx("span", { className: "cb-chart__skeleton-plot", style: { height }, "aria-hidden": "true" })
|
|
2196
|
+
] });
|
|
2197
|
+
}
|
|
2198
|
+
function TimeSeriesChart({
|
|
2199
|
+
label,
|
|
2200
|
+
series,
|
|
2201
|
+
format,
|
|
2202
|
+
range,
|
|
2203
|
+
baseline,
|
|
2204
|
+
mark,
|
|
2205
|
+
height = 260,
|
|
2206
|
+
emptyLabel = "Nothing has been reported yet.",
|
|
2207
|
+
tableLabel = "Readings by day",
|
|
2208
|
+
dayLabel = "Day",
|
|
2209
|
+
loading = false,
|
|
2210
|
+
className
|
|
2211
|
+
}) {
|
|
2212
|
+
const host = useRef(null);
|
|
2213
|
+
const [forcedColors, setForcedColors] = useState(false);
|
|
2214
|
+
const cleaned = useMemo(
|
|
2215
|
+
() => series.map((one) => ({ ...one, points: seriesPoints(one.points) })),
|
|
2216
|
+
[series]
|
|
2217
|
+
);
|
|
2218
|
+
const rows = useMemo(() => alignRows(cleaned), [cleaned]);
|
|
2219
|
+
const empty = rows.length === 0;
|
|
2220
|
+
const markDay = useMemo(
|
|
2221
|
+
() => mark ? nearestDay(rows.map((row) => row.day), mark.day) : null,
|
|
2222
|
+
[rows, mark]
|
|
2223
|
+
);
|
|
2224
|
+
useEffect(() => {
|
|
2225
|
+
const query = window.matchMedia("(forced-colors: active)");
|
|
2226
|
+
const read = () => setForcedColors(query.matches);
|
|
2227
|
+
read();
|
|
2228
|
+
query.addEventListener("change", read);
|
|
2229
|
+
return () => query.removeEventListener("change", read);
|
|
2230
|
+
}, []);
|
|
2231
|
+
const tokens = useMemo(() => cleaned.map((one) => one.colorToken), [cleaned]);
|
|
2232
|
+
useEffect(() => {
|
|
2233
|
+
const element = host.current;
|
|
2234
|
+
if (!element || empty || forcedColors || loading) return;
|
|
2235
|
+
let chart = null;
|
|
2236
|
+
let cancelled = false;
|
|
2237
|
+
let palette = readPalette(element, tokens);
|
|
2238
|
+
if (!palette) return;
|
|
2239
|
+
void mountTimeSeries(element, { series: cleaned, format, range, baseline }, palette).then((mounted) => {
|
|
2240
|
+
if (cancelled) {
|
|
2241
|
+
mounted.destroy();
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
chart = mounted;
|
|
2245
|
+
const current = readPalette(element, tokens) ?? palette;
|
|
2246
|
+
if (current) mounted.applyPalette(current);
|
|
2247
|
+
mounted.setData(cleaned);
|
|
2248
|
+
mounted.mark(markDay, mark?.label ?? "");
|
|
2249
|
+
});
|
|
2250
|
+
const stopWatchingTokens = watchTokens(() => {
|
|
2251
|
+
const next = readPalette(element, tokens);
|
|
2252
|
+
if (!next) return;
|
|
2253
|
+
palette = next;
|
|
2254
|
+
chart?.applyPalette(next);
|
|
2255
|
+
});
|
|
2256
|
+
return () => {
|
|
2257
|
+
cancelled = true;
|
|
2258
|
+
stopWatchingTokens();
|
|
2259
|
+
chart?.destroy();
|
|
2260
|
+
chart = null;
|
|
2261
|
+
};
|
|
2262
|
+
}, [cleaned, tokens, format, range, baseline, markDay, mark, empty, forcedColors, loading]);
|
|
2263
|
+
if (loading) return /* @__PURE__ */ jsx(TimeSeriesChartSkeleton, { height, className });
|
|
2264
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("cb-chart", className), "data-forced-colors": forcedColors || void 0, children: [
|
|
2265
|
+
empty ? /* @__PURE__ */ jsx("p", { className: "cb-chart__empty", style: { minHeight: height }, children: emptyLabel }) : null,
|
|
2266
|
+
empty || forcedColors ? null : /* @__PURE__ */ jsx("div", { ref: host, className: "cb-chart__canvas", style: { height }, role: "img", "aria-label": label }),
|
|
2267
|
+
empty ? null : /* @__PURE__ */ jsx("div", { className: "cb-chart__rows", children: /* @__PURE__ */ jsxs("table", { className: "cb-chart__table", children: [
|
|
2268
|
+
/* @__PURE__ */ jsx("caption", { children: tableLabel }),
|
|
2269
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
2270
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: dayLabel }),
|
|
2271
|
+
cleaned.map((one) => /* @__PURE__ */ jsx("th", { scope: "col", children: one.label }, one.key))
|
|
2272
|
+
] }) }),
|
|
2273
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { "data-marked": row.day === markDay || void 0, children: [
|
|
2274
|
+
/* @__PURE__ */ jsx("th", { scope: "row", children: row.day }),
|
|
2275
|
+
cleaned.map((one) => {
|
|
2276
|
+
const value = row.values[one.key];
|
|
2277
|
+
return /* @__PURE__ */ jsx("td", { children: value === null || value === void 0 ? "\u2014" : format(value) }, one.key);
|
|
2278
|
+
})
|
|
2279
|
+
] }, row.day)) })
|
|
2280
|
+
] }) })
|
|
2281
|
+
] });
|
|
2282
|
+
}
|
|
2283
|
+
function readPalette(host, seriesTokens) {
|
|
2284
|
+
const probe = createCssProbe(host);
|
|
2285
|
+
const text = probe.color("--cb-fg-muted");
|
|
2286
|
+
const muted = probe.color("--cb-fg-subtle");
|
|
2287
|
+
const grid = probe.color("--cb-border");
|
|
2288
|
+
const background = probe.color("--cb-surface");
|
|
2289
|
+
const above = probe.color("--cb-tone-success");
|
|
2290
|
+
const below = probe.color("--cb-tone-danger");
|
|
2291
|
+
const seriesColors = seriesTokens.map((token) => probe.color(token));
|
|
2292
|
+
probe.done();
|
|
2293
|
+
const font = getComputedStyle(host).fontFamily;
|
|
2294
|
+
if (!text || !muted || !grid || !background || !above || !below || !font) return null;
|
|
2295
|
+
if (seriesColors.some((colour) => colour === void 0)) return null;
|
|
2296
|
+
return {
|
|
2297
|
+
text,
|
|
2298
|
+
muted,
|
|
2299
|
+
grid,
|
|
2300
|
+
background,
|
|
2301
|
+
font,
|
|
2302
|
+
above,
|
|
2303
|
+
below,
|
|
2304
|
+
series: seriesColors.filter((colour) => colour !== void 0)
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/data/balance-curve/balance-curve.math.ts
|
|
2309
|
+
function balanceReading(points, threshold) {
|
|
2310
|
+
const ordered = seriesPoints(points);
|
|
2311
|
+
if (ordered.length === 0) {
|
|
2312
|
+
return { lowest: null, lowestDay: null, firstBelowDay: null, daysBelow: 0, closing: null };
|
|
2313
|
+
}
|
|
2314
|
+
let lowestPoint = ordered[0];
|
|
2315
|
+
for (const point of ordered) {
|
|
2316
|
+
if (lowestPoint === void 0 || point.value < lowestPoint.value) lowestPoint = point;
|
|
2317
|
+
}
|
|
2318
|
+
const below = ordered.filter((point) => point.value < threshold);
|
|
2319
|
+
return {
|
|
2320
|
+
lowest: lowestPoint?.value ?? null,
|
|
2321
|
+
lowestDay: lowestPoint?.day ?? null,
|
|
2322
|
+
firstBelowDay: below[0]?.day ?? null,
|
|
2323
|
+
daysBelow: below.length,
|
|
2324
|
+
closing: ordered.at(-1)?.value ?? null
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
function BalanceCurveSkeleton({
|
|
2328
|
+
height = 260,
|
|
2329
|
+
label = "Loading balance",
|
|
2330
|
+
className
|
|
2331
|
+
}) {
|
|
2332
|
+
return /* @__PURE__ */ jsx(TimeSeriesChartSkeleton, { height, label, className });
|
|
2333
|
+
}
|
|
2334
|
+
function BalanceCurveRoot({
|
|
2335
|
+
label,
|
|
2336
|
+
balances,
|
|
2337
|
+
format,
|
|
2338
|
+
threshold = { value: 0 },
|
|
2339
|
+
height = 260,
|
|
2340
|
+
seriesLabel = "Balance",
|
|
2341
|
+
emptyLabel = "Nothing to project yet.",
|
|
2342
|
+
tableLabel = "Balance by day",
|
|
2343
|
+
belowLabel = (from, lowest, days) => `Below the line from ${from} \u2014 lowest ${lowest}, ${days} day(s) under.`,
|
|
2344
|
+
clearLabel = (lowest) => `Stays above the line. Lowest point ${lowest}.`,
|
|
2345
|
+
loading = false,
|
|
2346
|
+
className
|
|
2347
|
+
}) {
|
|
2348
|
+
const points = useMemo(() => seriesPoints(balances), [balances]);
|
|
2349
|
+
const reading = useMemo(() => balanceReading(points, threshold.value), [points, threshold.value]);
|
|
2350
|
+
const series = useMemo(
|
|
2351
|
+
() => [{ key: "balance", label: seriesLabel, points, emphasis: "primary", colorToken: "--cb-tone-brand" }],
|
|
2352
|
+
[points, seriesLabel]
|
|
2353
|
+
);
|
|
2354
|
+
if (loading) return /* @__PURE__ */ jsx(BalanceCurveSkeleton, { height, className });
|
|
2355
|
+
const breached = reading.firstBelowDay !== null;
|
|
2356
|
+
return /* @__PURE__ */ jsxs("figure", { className: cn("cb-balance-curve", className), children: [
|
|
2357
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "cb-balance-curve__head", children: [
|
|
2358
|
+
/* @__PURE__ */ jsx("span", { className: "cb-balance-curve__label", children: label }),
|
|
2359
|
+
threshold.label ? /* @__PURE__ */ jsx("span", { className: "cb-balance-curve__threshold", children: threshold.label }) : null
|
|
2360
|
+
] }),
|
|
2361
|
+
reading.lowest === null ? null : /* @__PURE__ */ jsx("p", { className: "cb-balance-curve__reading", "data-state": breached ? "below" : "clear", children: breached && reading.firstBelowDay ? belowLabel(reading.firstBelowDay, format(reading.lowest), reading.daysBelow) : clearLabel(format(reading.lowest)) }),
|
|
2362
|
+
/* @__PURE__ */ jsx(
|
|
2363
|
+
TimeSeriesChart,
|
|
2364
|
+
{
|
|
2365
|
+
label,
|
|
2366
|
+
series,
|
|
2367
|
+
format,
|
|
2368
|
+
baseline: threshold,
|
|
2369
|
+
mark: reading.lowestDay ? { day: reading.lowestDay, label: format(reading.lowest ?? 0) } : void 0,
|
|
2370
|
+
height,
|
|
2371
|
+
emptyLabel,
|
|
2372
|
+
tableLabel
|
|
2373
|
+
}
|
|
2374
|
+
)
|
|
2375
|
+
] });
|
|
2376
|
+
}
|
|
2377
|
+
var BalanceCurve = Object.assign(BalanceCurveRoot, { Skeleton: BalanceCurveSkeleton });
|
|
2378
|
+
|
|
2379
|
+
// src/data/progress-curve/progress-curve.math.ts
|
|
2380
|
+
function toPoints(points) {
|
|
2381
|
+
return seriesPoints(points.map((point) => ({ day: point.day, value: point.percent })));
|
|
2382
|
+
}
|
|
2383
|
+
function readingOn(planned, actual, day) {
|
|
2384
|
+
const plannedPercent = valueOn(toPoints(planned), day);
|
|
2385
|
+
const actualPercent = valueOn(toPoints(actual), day);
|
|
2386
|
+
return {
|
|
2387
|
+
plannedPercent,
|
|
2388
|
+
actualPercent,
|
|
2389
|
+
gap: plannedPercent === null || actualPercent === null ? null : round1(actualPercent - plannedPercent)
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
function curveRows(planned, actual) {
|
|
2393
|
+
return alignRows([
|
|
2394
|
+
{ key: "planned", points: toPoints(planned) },
|
|
2395
|
+
{ key: "actual", points: toPoints(actual) }
|
|
2396
|
+
]).map((row) => {
|
|
2397
|
+
const plannedPercent = row.values.planned ?? null;
|
|
2398
|
+
const actualPercent = row.values.actual ?? null;
|
|
2399
|
+
return {
|
|
2400
|
+
day: row.day,
|
|
2401
|
+
plannedPercent,
|
|
2402
|
+
actualPercent,
|
|
2403
|
+
gap: plannedPercent === null || actualPercent === null ? null : round1(actualPercent - plannedPercent)
|
|
2404
|
+
};
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
function ProgressCurveSkeleton({
|
|
2408
|
+
height = 260,
|
|
2409
|
+
label = "Loading progress",
|
|
2410
|
+
className
|
|
2411
|
+
}) {
|
|
2412
|
+
return /* @__PURE__ */ jsx(TimeSeriesChartSkeleton, { height, label, className });
|
|
2413
|
+
}
|
|
2414
|
+
function ProgressCurveRoot({
|
|
2415
|
+
planned,
|
|
2416
|
+
actual,
|
|
2417
|
+
label,
|
|
2418
|
+
today,
|
|
2419
|
+
height = 260,
|
|
2420
|
+
plannedLabel = "Planned",
|
|
2421
|
+
actualLabel = "Actual",
|
|
2422
|
+
lastReportLabel = "Last report",
|
|
2423
|
+
emptyLabel = "Nothing has been reported yet.",
|
|
2424
|
+
tableLabel = "Progress by day",
|
|
2425
|
+
aheadLabel = "ahead of plan",
|
|
2426
|
+
behindLabel = "behind plan",
|
|
2427
|
+
onTrackLabel = "on plan",
|
|
2428
|
+
loading = false,
|
|
2429
|
+
className
|
|
2430
|
+
}) {
|
|
2431
|
+
const rows = useMemo(() => curveRows(planned, actual), [planned, actual]);
|
|
2432
|
+
const actualPoints = useMemo(() => toPoints(actual), [actual]);
|
|
2433
|
+
const lastReport = actualPoints.at(-1)?.day ?? null;
|
|
2434
|
+
const latest = useMemo(() => {
|
|
2435
|
+
if (!today) return rows[rows.length - 1];
|
|
2436
|
+
const day = asDay(today);
|
|
2437
|
+
return day === null ? rows[rows.length - 1] : { day, ...readingOn(planned, actual, day) };
|
|
2438
|
+
}, [today, rows, planned, actual]);
|
|
2439
|
+
const series = useMemo(
|
|
2440
|
+
() => [
|
|
2441
|
+
{ key: "planned", label: plannedLabel, points: toPoints(planned), emphasis: "reference", colorToken: "--cb-fg-subtle" },
|
|
2442
|
+
{ key: "actual", label: actualLabel, points: toPoints(actual), emphasis: "primary", colorToken: "--cb-tone-brand" }
|
|
2443
|
+
],
|
|
2444
|
+
[planned, actual, plannedLabel, actualLabel]
|
|
2445
|
+
);
|
|
2446
|
+
if (loading) return /* @__PURE__ */ jsx(ProgressCurveSkeleton, { height, className });
|
|
2447
|
+
return /* @__PURE__ */ jsxs("figure", { className: cn("cb-progress-curve", className), children: [
|
|
2448
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "cb-progress-curve__head", children: [
|
|
2449
|
+
/* @__PURE__ */ jsx("span", { className: "cb-progress-curve__label", children: label }),
|
|
2450
|
+
/* @__PURE__ */ jsx("span", { className: "cb-chart-legend", children: series.map((one) => /* @__PURE__ */ jsx("span", { className: "cb-chart-legend__key", "data-emphasis": one.emphasis, "data-series": one.key, children: one.label }, one.key)) })
|
|
2451
|
+
] }),
|
|
2452
|
+
latest ? /* @__PURE__ */ jsxs("p", { className: "cb-progress-curve__reading", "data-state": stateOf(latest.gap), children: [
|
|
2453
|
+
/* @__PURE__ */ jsx("strong", { children: percent(latest.actualPercent) }),
|
|
2454
|
+
" ",
|
|
2455
|
+
actualLabel.toLowerCase(),
|
|
2456
|
+
" \xB7 ",
|
|
2457
|
+
percent(latest.plannedPercent),
|
|
2458
|
+
" ",
|
|
2459
|
+
plannedLabel.toLowerCase(),
|
|
2460
|
+
latest.gap === null ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2461
|
+
" \u2014 ",
|
|
2462
|
+
Math.abs(latest.gap),
|
|
2463
|
+
" ",
|
|
2464
|
+
gapWord(latest.gap, { aheadLabel, behindLabel, onTrackLabel })
|
|
2465
|
+
] }),
|
|
2466
|
+
/* @__PURE__ */ jsxs("span", { className: "cb-progress-curve__on-day", children: [
|
|
2467
|
+
" (",
|
|
2468
|
+
latest.day,
|
|
2469
|
+
")"
|
|
2470
|
+
] })
|
|
2471
|
+
] }) : null,
|
|
2472
|
+
/* @__PURE__ */ jsx(
|
|
2473
|
+
TimeSeriesChart,
|
|
2474
|
+
{
|
|
2475
|
+
label,
|
|
2476
|
+
series,
|
|
2477
|
+
format: percentOf,
|
|
2478
|
+
range: { min: 0, max: 100 },
|
|
2479
|
+
mark: lastReport ? { day: lastReport, label: lastReportLabel } : void 0,
|
|
2480
|
+
height,
|
|
2481
|
+
emptyLabel,
|
|
2482
|
+
tableLabel
|
|
2483
|
+
}
|
|
2484
|
+
)
|
|
2485
|
+
] });
|
|
2486
|
+
}
|
|
2487
|
+
var ProgressCurve = Object.assign(ProgressCurveRoot, { Skeleton: ProgressCurveSkeleton });
|
|
2488
|
+
var percentOf = (value) => `${Math.round(value)}%`;
|
|
2489
|
+
function percent(value) {
|
|
2490
|
+
return value === null ? "\u2014" : `${round1(value)}%`;
|
|
2491
|
+
}
|
|
2492
|
+
function stateOf(gap) {
|
|
2493
|
+
if (gap === null) return "unknown";
|
|
2494
|
+
if (gap < 0) return "behind";
|
|
2495
|
+
if (gap > 0) return "ahead";
|
|
2496
|
+
return "on-plan";
|
|
2497
|
+
}
|
|
2498
|
+
function gapWord(gap, words) {
|
|
2499
|
+
if (gap < 0) return words.behindLabel;
|
|
2500
|
+
if (gap > 0) return words.aheadLabel;
|
|
2501
|
+
return words.onTrackLabel;
|
|
2502
|
+
}
|
|
2038
2503
|
function DiagramNodeView({ data, selected }) {
|
|
2039
2504
|
return /* @__PURE__ */ jsxs(
|
|
2040
2505
|
"div",
|
|
@@ -2342,4 +2807,4 @@ function DiagramEditorRoot(props) {
|
|
|
2342
2807
|
}
|
|
2343
2808
|
var DiagramEditor = Object.assign(DiagramEditorRoot, { Skeleton: DiagramSkeleton });
|
|
2344
2809
|
|
|
2345
|
-
export { Board, CeebeeAntStyleProvider, Checklist, CommandPalette, DEFAULT_LABELS, Diagram, DiagramEditor, LabelsProvider, Modal, MotionProvider, PanZoomCanvas, Reveal, Sidebar, Stagger, StickerGroup, ThemeBridge, ThemeProvider, ToastProvider, TopBar, createCeebeeAntStyleCache, extractCeebeeAntStyles, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
|
|
2810
|
+
export { BalanceCurve, BalanceCurveSkeleton, Board, CeebeeAntStyleProvider, Checklist, CommandPalette, DEFAULT_LABELS, Diagram, DiagramEditor, LabelsProvider, Modal, MotionProvider, PanZoomCanvas, ProgressCurve, ProgressCurveSkeleton, Reveal, Sidebar, Stagger, StickerGroup, ThemeBridge, ThemeProvider, TimeSeriesChart, TimeSeriesChartSkeleton, ToastProvider, TopBar, alignRows, asDay, balanceReading, createCeebeeAntStyleCache, curveRows, extractCeebeeAntStyles, filterCommands, groupCommands, nearestDay, rankCommand, readingOn, round1, seriesPoints, seriesSpan, toPoints, useLabels, useMotionSettings, useTheme, useToast, valueOn };
|
package/dist/styles.css
CHANGED
|
@@ -930,6 +930,21 @@ svg.react-flow__connectionline {
|
|
|
930
930
|
.cb-container--lg { max-width: 76rem; }
|
|
931
931
|
.cb-container--full { max-width: none; }
|
|
932
932
|
|
|
933
|
+
/* Text for a screen reader and no one else. It is in the layout file rather than beside any one
|
|
934
|
+
component because more than one already asked for it: `cb-visually-hidden` was being written in
|
|
935
|
+
markup with no rule behind it, so the text it hides was simply visible. */
|
|
936
|
+
.cb-visually-hidden:not(:focus):not(:active) {
|
|
937
|
+
position: absolute;
|
|
938
|
+
width: 1px;
|
|
939
|
+
height: 1px;
|
|
940
|
+
margin: -1px;
|
|
941
|
+
padding: 0;
|
|
942
|
+
overflow: hidden;
|
|
943
|
+
clip-path: inset(50%);
|
|
944
|
+
white-space: nowrap;
|
|
945
|
+
border: 0;
|
|
946
|
+
}
|
|
947
|
+
|
|
933
948
|
/* foundation/page-container/page-container.css */
|
|
934
949
|
.cb-page-container { display: flex; flex-direction: column; gap: var(--cb-space-4); padding-block: var(--cb-space-5); color: var(--cb-fg); font-family: var(--cb-font-sans); }
|
|
935
950
|
.cb-page-container__breadcrumb { color: var(--cb-fg-muted); font-size: var(--cb-text-sm); }
|
|
@@ -1360,6 +1375,50 @@ svg.react-flow__connectionline {
|
|
|
1360
1375
|
line-height: var(--cb-leading-normal);
|
|
1361
1376
|
}
|
|
1362
1377
|
|
|
1378
|
+
/* data/balance-curve/balance-curve.css */
|
|
1379
|
+
.cb-balance-curve {
|
|
1380
|
+
display: flex;
|
|
1381
|
+
flex-direction: column;
|
|
1382
|
+
gap: var(--cb-space-2);
|
|
1383
|
+
margin: 0;
|
|
1384
|
+
font-family: var(--cb-font-sans);
|
|
1385
|
+
color: var(--cb-fg);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
.cb-balance-curve__head {
|
|
1389
|
+
display: flex;
|
|
1390
|
+
flex-wrap: wrap;
|
|
1391
|
+
align-items: baseline;
|
|
1392
|
+
justify-content: space-between;
|
|
1393
|
+
gap: var(--cb-space-2);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
.cb-balance-curve__label {
|
|
1397
|
+
font-size: var(--cb-text-md);
|
|
1398
|
+
font-weight: 600;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.cb-balance-curve__threshold {
|
|
1402
|
+
font-size: var(--cb-text-xs);
|
|
1403
|
+
color: var(--cb-fg-muted);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.cb-balance-curve__reading {
|
|
1407
|
+
margin: 0;
|
|
1408
|
+
font-size: var(--cb-text-sm);
|
|
1409
|
+
font-variant-numeric: tabular-nums;
|
|
1410
|
+
color: var(--cb-fg-muted);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/* The one place a balance chart spends colour weight: the sentence that says it goes under. The plot
|
|
1414
|
+
already carries the same meaning in its shading, so this is reinforcement, not the only signal. */
|
|
1415
|
+
.cb-balance-curve__reading[data-state="below"] {
|
|
1416
|
+
color: var(--cb-tone-danger);
|
|
1417
|
+
font-weight: 600;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
.cb-balance-curve__reading[data-state="clear"] { color: var(--cb-tone-success); }
|
|
1421
|
+
|
|
1363
1422
|
/* data/board/board.css */
|
|
1364
1423
|
/* Board — columns of cards a person moves by pointer or by keyboard.
|
|
1365
1424
|
Every value is a Token. The drag lift, the gap animation and the drop marker all have a
|
|
@@ -2141,6 +2200,50 @@ svg.react-flow__connectionline {
|
|
|
2141
2200
|
.cb-pan-zoom-canvas__toolbar { border-color: CanvasText; }
|
|
2142
2201
|
}
|
|
2143
2202
|
|
|
2203
|
+
/* data/progress-curve/progress-curve.css */
|
|
2204
|
+
.cb-progress-curve {
|
|
2205
|
+
display: flex;
|
|
2206
|
+
flex-direction: column;
|
|
2207
|
+
gap: var(--cb-space-2);
|
|
2208
|
+
margin: 0;
|
|
2209
|
+
font-family: var(--cb-font-sans);
|
|
2210
|
+
color: var(--cb-fg);
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
.cb-progress-curve__head {
|
|
2214
|
+
display: flex;
|
|
2215
|
+
flex-wrap: wrap;
|
|
2216
|
+
align-items: baseline;
|
|
2217
|
+
justify-content: space-between;
|
|
2218
|
+
gap: var(--cb-space-2);
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
.cb-progress-curve__label {
|
|
2222
|
+
font-size: var(--cb-text-md);
|
|
2223
|
+
font-weight: 600;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
/* The legend marks take the colour of the series they name — the same Tokens the canvas is drawn from,
|
|
2227
|
+
so the two renderings cannot drift apart. */
|
|
2228
|
+
.cb-progress-curve .cb-chart-legend__key[data-series="planned"] { color: var(--cb-fg-subtle); }
|
|
2229
|
+
.cb-progress-curve .cb-chart-legend__key[data-series="actual"] { color: var(--cb-tone-brand); }
|
|
2230
|
+
|
|
2231
|
+
.cb-progress-curve__reading {
|
|
2232
|
+
margin: 0;
|
|
2233
|
+
font-size: var(--cb-text-sm);
|
|
2234
|
+
font-variant-numeric: tabular-nums;
|
|
2235
|
+
color: var(--cb-fg-muted);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
.cb-progress-curve__reading strong {
|
|
2239
|
+
font-size: var(--cb-text-xl);
|
|
2240
|
+
color: var(--cb-fg);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
.cb-progress-curve__reading[data-state="behind"] { color: var(--cb-tone-danger); }
|
|
2244
|
+
.cb-progress-curve__reading[data-state="ahead"] { color: var(--cb-tone-success); }
|
|
2245
|
+
.cb-progress-curve__on-day { color: var(--cb-fg-subtle); }
|
|
2246
|
+
|
|
2144
2247
|
/* data/sticker-group/sticker-group.css */
|
|
2145
2248
|
.cb-sticker-group {
|
|
2146
2249
|
display: flex;
|
|
@@ -2230,6 +2333,138 @@ svg.react-flow__connectionline {
|
|
|
2230
2333
|
}
|
|
2231
2334
|
}
|
|
2232
2335
|
|
|
2336
|
+
/* data/time-series/time-series.css */
|
|
2337
|
+
.cb-chart {
|
|
2338
|
+
display: flex;
|
|
2339
|
+
flex-direction: column;
|
|
2340
|
+
gap: var(--cb-space-2);
|
|
2341
|
+
font-family: var(--cb-font-sans);
|
|
2342
|
+
color: var(--cb-fg);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
.cb-chart__canvas {
|
|
2346
|
+
inline-size: 100%;
|
|
2347
|
+
border-radius: var(--cb-radius-md);
|
|
2348
|
+
overflow: hidden;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
.cb-chart__empty {
|
|
2352
|
+
display: grid;
|
|
2353
|
+
place-items: center;
|
|
2354
|
+
margin: 0;
|
|
2355
|
+
padding: var(--cb-space-4);
|
|
2356
|
+
border: var(--cb-border-width) dashed var(--cb-border);
|
|
2357
|
+
border-radius: var(--cb-radius-md);
|
|
2358
|
+
color: var(--cb-fg-muted);
|
|
2359
|
+
font-size: var(--cb-text-sm);
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
/* The chart's other rendering. Hidden from sight, never from the accessibility tree: a canvas gives a
|
|
2363
|
+
screen reader nothing at all, so this table is the chart as far as one is concerned.
|
|
2364
|
+
|
|
2365
|
+
The geometry is on the wrapper rather than on the table, because a table cannot be shrunk: it
|
|
2366
|
+
refuses to go under its content's width and height whatever is asked of it, so these rules on the
|
|
2367
|
+
table itself left an invisible box the size of the data lying across the plot. */
|
|
2368
|
+
.cb-chart__rows {
|
|
2369
|
+
position: absolute;
|
|
2370
|
+
inline-size: 1px;
|
|
2371
|
+
block-size: 1px;
|
|
2372
|
+
margin: -1px;
|
|
2373
|
+
padding: 0;
|
|
2374
|
+
overflow: hidden;
|
|
2375
|
+
clip-path: inset(50%);
|
|
2376
|
+
white-space: nowrap;
|
|
2377
|
+
border: 0;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
/* Forced colors: the canvas is replaced by the table rather than sitting there as a bitmap the system
|
|
2381
|
+
cannot recolour. The component stops rendering the canvas at all in this mode, so this rule only has
|
|
2382
|
+
to bring the table back into view. */
|
|
2383
|
+
@media (forced-colors: active) {
|
|
2384
|
+
.cb-chart__rows {
|
|
2385
|
+
position: static;
|
|
2386
|
+
inline-size: 100%;
|
|
2387
|
+
block-size: auto;
|
|
2388
|
+
margin: 0;
|
|
2389
|
+
overflow: visible;
|
|
2390
|
+
clip-path: none;
|
|
2391
|
+
white-space: normal;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
.cb-chart__table {
|
|
2395
|
+
inline-size: 100%;
|
|
2396
|
+
border-collapse: collapse;
|
|
2397
|
+
font-size: var(--cb-text-sm);
|
|
2398
|
+
font-variant-numeric: tabular-nums;
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
.cb-chart__table caption {
|
|
2402
|
+
text-align: start;
|
|
2403
|
+
padding-block-end: var(--cb-space-1);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
.cb-chart__table th,
|
|
2407
|
+
.cb-chart__table td {
|
|
2408
|
+
border: var(--cb-border-width) solid;
|
|
2409
|
+
padding: var(--cb-space-1) var(--cb-space-2);
|
|
2410
|
+
text-align: end;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
.cb-chart__table th[scope="row"],
|
|
2414
|
+
.cb-chart__table th[scope="col"]:first-child {
|
|
2415
|
+
text-align: start;
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
.cb-chart__table tr[data-marked] { font-weight: 700; }
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
.cb-chart--skeleton { gap: var(--cb-space-2); }
|
|
2422
|
+
|
|
2423
|
+
.cb-chart__skeleton-head,
|
|
2424
|
+
.cb-chart__skeleton-plot {
|
|
2425
|
+
display: block;
|
|
2426
|
+
background: var(--cb-bg-subtle);
|
|
2427
|
+
border-radius: var(--cb-radius-sm);
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
.cb-chart__skeleton-head {
|
|
2431
|
+
block-size: var(--cb-text-md);
|
|
2432
|
+
inline-size: calc(var(--cb-space-8) * 3);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
.cb-chart__skeleton-plot { border-radius: var(--cb-radius-md); }
|
|
2436
|
+
|
|
2437
|
+
/* A legend mark carries the same treatment as the line it names: a reference is thin and dashed, the
|
|
2438
|
+
primary series solid and thick. Colour is never the only difference — dash versus solid survives a
|
|
2439
|
+
monochrome print and every kind of colour blindness. */
|
|
2440
|
+
.cb-chart-legend {
|
|
2441
|
+
display: flex;
|
|
2442
|
+
flex-wrap: wrap;
|
|
2443
|
+
gap: var(--cb-space-3);
|
|
2444
|
+
font-size: var(--cb-text-xs);
|
|
2445
|
+
color: var(--cb-fg-muted);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
.cb-chart-legend__key {
|
|
2449
|
+
display: inline-flex;
|
|
2450
|
+
align-items: center;
|
|
2451
|
+
gap: var(--cb-space-1);
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
.cb-chart-legend__key::before {
|
|
2455
|
+
content: '';
|
|
2456
|
+
inline-size: var(--cb-space-4);
|
|
2457
|
+
block-size: 0;
|
|
2458
|
+
border-block-start-width: var(--cb-border-width);
|
|
2459
|
+
border-block-start-style: solid;
|
|
2460
|
+
border-color: currentColor;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
.cb-chart-legend__key[data-emphasis="reference"]::before { border-block-start-style: dashed; }
|
|
2464
|
+
.cb-chart-legend__key[data-emphasis="primary"]::before {
|
|
2465
|
+
border-block-start-width: calc(var(--cb-border-width) * 2);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2233
2468
|
/* nav/nav.css */
|
|
2234
2469
|
.cb-menu-positioner {
|
|
2235
2470
|
position: absolute;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ceebee/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Ceebee's design system: tokens, themed primitives, motion, and onboarding.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"@dnd-kit/utilities": "3.2.2",
|
|
61
61
|
"@xyflow/react": "12.11.6",
|
|
62
62
|
"antd": "6.6.1",
|
|
63
|
-
"dayjs": "1.11.18"
|
|
63
|
+
"dayjs": "1.11.18",
|
|
64
|
+
"lightweight-charts": "5.2.1"
|
|
64
65
|
},
|
|
65
66
|
"scripts": {
|
|
66
67
|
"build": "tsup && node ./stamp-client.mjs && node ./build-css.mjs",
|