@tanishqkancharla/diffmap 0.4.0 → 0.6.1
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/README.md +78 -4
- package/package.json +1 -1
- package/src/annotations.ts +17 -1
- package/src/extractDocument.ts +10 -2
- package/src/host/home.md +89 -103
- package/src/host/home.test.ts +11 -1
- package/src/parseFence.ts +81 -12
- package/src/parseViewer.test.ts +147 -4
- package/src/parseViewer.ts +44 -9
- package/src/serve.test.ts +22 -1
- package/src/services.ts +122 -15
- package/src/viewer/CallStackDiff.tsx +29 -33
- package/src/viewer/ChangesCard.tsx +0 -2
- package/src/viewer/CodeBlock.tsx +79 -7
- package/src/viewer/ComarkView.tsx +34 -0
- package/src/viewer/Fence.tsx +4 -0
- package/src/viewer/HtmlBlock.tsx +2 -1
- package/src/viewer/MermaidBlock.tsx +55 -14
- package/src/viewer/ServiceDiagramBlock.tsx +90 -54
- package/src/viewer/ServiceStage.tsx +206 -60
- package/src/viewer/SourceDiffPanel.tsx +138 -42
- package/src/viewer/TableOfContents.tsx +44 -18
- package/src/viewer/TocButton.tsx +28 -0
- package/src/viewer/ViewerApp.tsx +10 -0
- package/src/viewer/diagramDiff.ts +69 -0
- package/src/viewer/diagramLinks.ts +54 -6
- package/src/viewer/notes.tsx +0 -1
- package/src/viewer/pierre.ts +20 -2
- package/src/viewer/serviceLayout.ts +16 -5
- package/vite.config.ts +4 -0
|
@@ -22,14 +22,17 @@ import { style, useStyles } from "purse-styles";
|
|
|
22
22
|
import type { SourceNavigation } from "../annotations.js";
|
|
23
23
|
import {
|
|
24
24
|
flattenServices,
|
|
25
|
+
serviceStateAt,
|
|
25
26
|
type Service,
|
|
26
27
|
type ServiceDiagram,
|
|
27
28
|
type ServiceEvent,
|
|
28
29
|
type ServiceUpdate,
|
|
30
|
+
type Sign,
|
|
29
31
|
} from "../services.js";
|
|
30
32
|
import {
|
|
31
33
|
layoutServices,
|
|
32
34
|
linkId,
|
|
35
|
+
pillText,
|
|
33
36
|
serviceLinks,
|
|
34
37
|
type Point,
|
|
35
38
|
type Rect,
|
|
@@ -53,10 +56,15 @@ type StepNote = {
|
|
|
53
56
|
|
|
54
57
|
type State = Map<string, Map<string, string>>;
|
|
55
58
|
|
|
59
|
+
/** State replayed on each side of a diff; the sides match outside a diff. */
|
|
60
|
+
export type SidedState = { today: State; proposed: State };
|
|
61
|
+
|
|
62
|
+
type Values = { today: Map<string, string>; proposed: Map<string, string> };
|
|
63
|
+
|
|
56
64
|
export type StageProps = {
|
|
57
65
|
diagram: ServiceDiagram;
|
|
58
|
-
state:
|
|
59
|
-
previousState:
|
|
66
|
+
state: SidedState;
|
|
67
|
+
previousState: SidedState;
|
|
60
68
|
changed: Set<string>;
|
|
61
69
|
active: Set<string>;
|
|
62
70
|
events: ServiceEvent[];
|
|
@@ -306,7 +314,10 @@ export function ServiceStage(props: StageProps) {
|
|
|
306
314
|
<ServiceCard
|
|
307
315
|
{...props}
|
|
308
316
|
service={service}
|
|
309
|
-
values={
|
|
317
|
+
values={{
|
|
318
|
+
today: widest.get(service.name)!,
|
|
319
|
+
proposed: widest.get(service.name)!,
|
|
320
|
+
}}
|
|
310
321
|
previous={undefined}
|
|
311
322
|
changed={new Set()}
|
|
312
323
|
/>
|
|
@@ -339,6 +350,7 @@ export function ServiceStage(props: StageProps) {
|
|
|
339
350
|
key={service.name}
|
|
340
351
|
className={box}
|
|
341
352
|
data-depth={depth.get(service.name)}
|
|
353
|
+
data-change={service.sign}
|
|
342
354
|
data-active={props.active.has(service.name)}
|
|
343
355
|
aria-label={service.name}
|
|
344
356
|
style={{
|
|
@@ -351,8 +363,8 @@ export function ServiceStage(props: StageProps) {
|
|
|
351
363
|
<ServiceCard
|
|
352
364
|
{...props}
|
|
353
365
|
service={service}
|
|
354
|
-
values={props.state
|
|
355
|
-
previous={props.previousState
|
|
366
|
+
values={valuesOf(props.state, service.name)}
|
|
367
|
+
previous={valuesOf(props.previousState, service.name)}
|
|
356
368
|
/>
|
|
357
369
|
</section>
|
|
358
370
|
);
|
|
@@ -364,10 +376,10 @@ export function ServiceStage(props: StageProps) {
|
|
|
364
376
|
aria-hidden="true"
|
|
365
377
|
>
|
|
366
378
|
<defs>
|
|
367
|
-
{
|
|
379
|
+
{tones.map((tone) => (
|
|
368
380
|
<marker
|
|
369
|
-
key={
|
|
370
|
-
id={`${markerId}-${
|
|
381
|
+
key={tone.id}
|
|
382
|
+
id={`${markerId}-${tone.id}`}
|
|
371
383
|
viewBox="0 0 10 10"
|
|
372
384
|
refX="9"
|
|
373
385
|
refY="5"
|
|
@@ -375,26 +387,26 @@ export function ServiceStage(props: StageProps) {
|
|
|
375
387
|
markerHeight="7"
|
|
376
388
|
orient="auto-start-reverse"
|
|
377
389
|
>
|
|
378
|
-
<path
|
|
379
|
-
d="M0,1 L9,5 L0,9 z"
|
|
380
|
-
style={{
|
|
381
|
-
fill:
|
|
382
|
-
kind === "active" ? colors.accent[9] : colors.gray[8],
|
|
383
|
-
}}
|
|
384
|
-
/>
|
|
390
|
+
<path d="M0,1 L9,5 L0,9 z" style={{ fill: tone.head }} />
|
|
385
391
|
</marker>
|
|
386
392
|
))}
|
|
387
393
|
</defs>
|
|
388
394
|
{links.map((link) => {
|
|
389
395
|
const route = trimmedRoute(layout, link, parents);
|
|
390
396
|
const sending = (from: string, to: string) =>
|
|
391
|
-
props.events.
|
|
397
|
+
props.events.filter(
|
|
392
398
|
(event) => event.from === from && event.to === to,
|
|
393
399
|
);
|
|
394
400
|
const forward = sending(link.a, link.b);
|
|
395
401
|
const backward = sending(link.b, link.a);
|
|
396
|
-
const
|
|
397
|
-
|
|
402
|
+
const active = forward.length > 0 || backward.length > 0;
|
|
403
|
+
// While its events travel, a link takes their sign.
|
|
404
|
+
const tone = toneFor(
|
|
405
|
+
active,
|
|
406
|
+
active ? commonSign([...forward, ...backward]) : link.sign,
|
|
407
|
+
);
|
|
408
|
+
const marker = (sent: ServiceEvent[]) =>
|
|
409
|
+
`url(#${markerId}-${(sent.length > 0 ? tone : toneFor(false, link.sign)).id})`;
|
|
398
410
|
return (
|
|
399
411
|
<path
|
|
400
412
|
key={link.id}
|
|
@@ -403,10 +415,9 @@ export function ServiceStage(props: StageProps) {
|
|
|
403
415
|
markerEnd={link.forward ? marker(forward) : undefined}
|
|
404
416
|
markerStart={link.backward ? marker(backward) : undefined}
|
|
405
417
|
style={{
|
|
406
|
-
stroke:
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
strokeDasharray: forward || backward ? undefined : "4 4",
|
|
418
|
+
stroke: tone.line,
|
|
419
|
+
strokeWidth: active ? 2 : 1,
|
|
420
|
+
strokeDasharray: active ? undefined : "4 4",
|
|
410
421
|
}}
|
|
411
422
|
/>
|
|
412
423
|
);
|
|
@@ -522,24 +533,30 @@ function EventMarks(
|
|
|
522
533
|
const labelAnchor = useStyles(styles.labelAnchor);
|
|
523
534
|
const pill = useStyles(styles.pill);
|
|
524
535
|
const annotation = props.event.annotation;
|
|
525
|
-
const label = props.event.label === "" ? undefined : props.event
|
|
536
|
+
const label = props.event.label === "" ? undefined : pillText(props.event);
|
|
526
537
|
const lengths = cumulativeLengths(props.points);
|
|
527
538
|
const total = lengths.at(-1)! || 1;
|
|
539
|
+
// A step's events leave in order, each when the one before it arrives.
|
|
540
|
+
const delay = props.phase === "travel" ? (props.index * TRAVEL_MS) / 1000 : 0;
|
|
528
541
|
return (
|
|
529
542
|
<>
|
|
530
543
|
{props.phase === "travel" && (
|
|
531
544
|
<motion.div
|
|
532
545
|
className={packet}
|
|
533
|
-
|
|
546
|
+
data-change={props.event.sign}
|
|
547
|
+
initial={{ x: props.points[0]!.x, y: props.points[0]!.y, opacity: 0 }}
|
|
534
548
|
animate={{
|
|
535
549
|
x: props.points.map((point) => point.x),
|
|
536
550
|
y: props.points.map((point) => point.y),
|
|
551
|
+
opacity: 1,
|
|
537
552
|
}}
|
|
538
553
|
// Even speed along every segment of the route.
|
|
539
554
|
transition={{
|
|
555
|
+
delay,
|
|
540
556
|
duration: TRAVEL_MS / 1000,
|
|
541
557
|
ease: "easeInOut",
|
|
542
558
|
times: lengths.map((length) => length / total),
|
|
559
|
+
opacity: { delay, duration: 0 },
|
|
543
560
|
}}
|
|
544
561
|
/>
|
|
545
562
|
)}
|
|
@@ -551,20 +568,24 @@ function EventMarks(
|
|
|
551
568
|
data-event-label={props.index}
|
|
552
569
|
initial={{ opacity: 0 }}
|
|
553
570
|
animate={{ opacity: 1 }}
|
|
571
|
+
transition={{ delay }}
|
|
554
572
|
>
|
|
555
573
|
{label !== undefined &&
|
|
556
574
|
(annotation === undefined ? (
|
|
557
|
-
<div className={pill}
|
|
575
|
+
<div className={pill} data-change={props.event.sign}>
|
|
576
|
+
{label}
|
|
577
|
+
</div>
|
|
558
578
|
) : (
|
|
559
579
|
<button
|
|
560
580
|
type="button"
|
|
561
581
|
className={pill}
|
|
582
|
+
data-change={props.event.sign}
|
|
562
583
|
aria-pressed={props.selectedAnnotation === annotation}
|
|
563
584
|
aria-controls="source-diff-panel"
|
|
564
585
|
aria-label={`Open source: ${annotation.text}`}
|
|
565
586
|
onClick={() => props.onSelectAnnotation(annotation)}
|
|
566
587
|
>
|
|
567
|
-
{label}
|
|
588
|
+
{label}
|
|
568
589
|
</button>
|
|
569
590
|
))}
|
|
570
591
|
</motion.div>
|
|
@@ -577,8 +598,8 @@ function EventMarks(
|
|
|
577
598
|
function ServiceCard(
|
|
578
599
|
props: {
|
|
579
600
|
service: Service;
|
|
580
|
-
values:
|
|
581
|
-
previous:
|
|
601
|
+
values: Values;
|
|
602
|
+
previous: Values | undefined;
|
|
582
603
|
changed: Set<string>;
|
|
583
604
|
} & SourceNavigation,
|
|
584
605
|
) {
|
|
@@ -595,7 +616,10 @@ function ServiceCard(
|
|
|
595
616
|
<Text
|
|
596
617
|
size="sm"
|
|
597
618
|
fontWeight={600}
|
|
598
|
-
style={{
|
|
619
|
+
style={{
|
|
620
|
+
whiteSpace: "nowrap",
|
|
621
|
+
textDecorationLine: service.sign === "-" ? "line-through" : undefined,
|
|
622
|
+
}}
|
|
599
623
|
data-described={described}
|
|
600
624
|
>
|
|
601
625
|
{service.name}
|
|
@@ -639,13 +663,18 @@ function ServiceCard(
|
|
|
639
663
|
["--key-width" as string]: `${Math.max(...service.state.map(({ key }) => key.length))}ch`,
|
|
640
664
|
}}
|
|
641
665
|
>
|
|
642
|
-
{service.state.map(({ key, description }) => {
|
|
666
|
+
{service.state.map(({ key, description, sign }) => {
|
|
643
667
|
const changed = props.changed.has(`${service.name}.${key}`);
|
|
668
|
+
// A row added or removed in a diff shows only its own side.
|
|
669
|
+
const side = sign === "-" ? "today" : "proposed";
|
|
670
|
+
const today = props.values.today.get(key);
|
|
671
|
+
const proposed = props.values.proposed.get(key);
|
|
644
672
|
return (
|
|
645
673
|
<div
|
|
646
674
|
key={key}
|
|
647
675
|
className={stateRow}
|
|
648
676
|
data-changed={changed}
|
|
677
|
+
data-change={sign}
|
|
649
678
|
data-state-row={`${service.name}.${key}`}
|
|
650
679
|
>
|
|
651
680
|
<Described description={description}>
|
|
@@ -657,18 +686,39 @@ function ServiceCard(
|
|
|
657
686
|
</dt>
|
|
658
687
|
</Described>
|
|
659
688
|
<dd>
|
|
660
|
-
{
|
|
661
|
-
|
|
662
|
-
<
|
|
663
|
-
|
|
689
|
+
{sign === " " && today !== proposed ? (
|
|
690
|
+
<>
|
|
691
|
+
<Text
|
|
692
|
+
size="xs"
|
|
693
|
+
monospace
|
|
694
|
+
style={{ color: colors.red[11] }}
|
|
695
|
+
>
|
|
696
|
+
<s>{today}</s>{" "}
|
|
697
|
+
</Text>
|
|
698
|
+
<Text
|
|
699
|
+
size="xs"
|
|
700
|
+
monospace
|
|
701
|
+
style={{ color: colors.green[11] }}
|
|
702
|
+
>
|
|
703
|
+
{proposed}
|
|
704
|
+
</Text>
|
|
705
|
+
</>
|
|
706
|
+
) : (
|
|
707
|
+
<>
|
|
708
|
+
{changed && (
|
|
709
|
+
<Text size="xs" monospace color="lowContrast">
|
|
710
|
+
<s>{props.previous?.[side].get(key)}</s>{" "}
|
|
711
|
+
</Text>
|
|
712
|
+
)}
|
|
713
|
+
<Text
|
|
714
|
+
size="xs"
|
|
715
|
+
monospace
|
|
716
|
+
color={changed ? "accent" : undefined}
|
|
717
|
+
>
|
|
718
|
+
{props.values[side].get(key)}
|
|
719
|
+
</Text>
|
|
720
|
+
</>
|
|
664
721
|
)}
|
|
665
|
-
<Text
|
|
666
|
-
size="xs"
|
|
667
|
-
monospace
|
|
668
|
-
color={changed ? "accent" : undefined}
|
|
669
|
-
>
|
|
670
|
-
{props.values.get(key)}
|
|
671
|
-
</Text>
|
|
672
722
|
</dd>
|
|
673
723
|
</div>
|
|
674
724
|
);
|
|
@@ -694,30 +744,91 @@ export function Described(props: {
|
|
|
694
744
|
|
|
695
745
|
/**
|
|
696
746
|
* Each state value at its widest, including the struck-through old value a
|
|
697
|
-
* changed row shows beside the new one
|
|
747
|
+
* changed row shows beside the new one, and a diff's Today value beside its
|
|
748
|
+
* Proposed one.
|
|
698
749
|
*/
|
|
699
750
|
function widestState(diagram: ServiceDiagram) {
|
|
700
|
-
const
|
|
701
|
-
flattenServices(diagram.services).map(({ service }) => [
|
|
702
|
-
service.name,
|
|
703
|
-
new Map(service.state.map((entry) => [entry.key, entry.value])),
|
|
704
|
-
]),
|
|
705
|
-
);
|
|
751
|
+
const flat = flattenServices(diagram.services);
|
|
706
752
|
const widest = new Map(
|
|
707
|
-
|
|
753
|
+
flat.map(({ service }) => [service.name, new Map<string, string>()]),
|
|
708
754
|
);
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
755
|
+
const at = (step: number) => ({
|
|
756
|
+
today: serviceStateAt(diagram, step, "today"),
|
|
757
|
+
proposed: serviceStateAt(diagram, step, "proposed"),
|
|
758
|
+
});
|
|
759
|
+
let before = at(0);
|
|
760
|
+
for (let step = 0; step <= diagram.steps.length; step++) {
|
|
761
|
+
const now = step === 0 ? before : at(step);
|
|
762
|
+
// A row updated in this step shows its old value struck through.
|
|
763
|
+
const updated = new Set(
|
|
764
|
+
diagram.steps[step - 1]?.updates.map(
|
|
765
|
+
(update) => `${update.service}.${update.key}`,
|
|
766
|
+
),
|
|
767
|
+
);
|
|
768
|
+
for (const { service } of flat)
|
|
769
|
+
for (const { key } of service.state) {
|
|
770
|
+
const best = widest.get(service.name)!;
|
|
771
|
+
const value = (state: State) => state.get(service.name)!.get(key);
|
|
772
|
+
const today = value(now.today);
|
|
773
|
+
const proposed = value(now.proposed);
|
|
774
|
+
const shown = [
|
|
775
|
+
today === undefined || proposed === undefined || today === proposed
|
|
776
|
+
? (proposed ?? today ?? "")
|
|
777
|
+
: `${today} ${proposed}`,
|
|
778
|
+
];
|
|
779
|
+
if (updated.has(`${service.name}.${key}`))
|
|
780
|
+
shown.push(
|
|
781
|
+
`${value(before.today) ?? ""} ${today ?? ""}`,
|
|
782
|
+
`${value(before.proposed) ?? ""} ${proposed ?? ""}`,
|
|
783
|
+
);
|
|
784
|
+
for (const candidate of shown)
|
|
785
|
+
if (candidate.length > (best.get(key)?.length ?? -1))
|
|
786
|
+
best.set(key, candidate);
|
|
787
|
+
}
|
|
788
|
+
before = now;
|
|
789
|
+
}
|
|
718
790
|
return widest;
|
|
719
791
|
}
|
|
720
792
|
|
|
793
|
+
function valuesOf(state: SidedState, service: string): Values {
|
|
794
|
+
return {
|
|
795
|
+
today: state.today.get(service)!,
|
|
796
|
+
proposed: state.proposed.get(service)!,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** The sign every event shares, or " " when they differ. */
|
|
801
|
+
function commonSign(events: ServiceEvent[]): Sign {
|
|
802
|
+
return events.every((event) => event.sign === events[0]!.sign)
|
|
803
|
+
? events[0]!.sign
|
|
804
|
+
: " ";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/** Link colors: idle or carrying events, and Today only, Proposed only, or both. */
|
|
808
|
+
const tones = (["idle", "active"] as const).flatMap((state) =>
|
|
809
|
+
(["+", "-", " "] as const).map((sign) => {
|
|
810
|
+
const scale =
|
|
811
|
+
sign === "+" ? colors.green : sign === "-" ? colors.red : undefined;
|
|
812
|
+
const active = state === "active";
|
|
813
|
+
const line = active
|
|
814
|
+
? (scale?.[9] ?? colors.accent[9])
|
|
815
|
+
: (scale?.[8] ?? colors.gray[7]);
|
|
816
|
+
return {
|
|
817
|
+
id: `${state}-${{ "+": "added", "-": "removed", " ": "same" }[sign]}`,
|
|
818
|
+
state,
|
|
819
|
+
sign,
|
|
820
|
+
line,
|
|
821
|
+
head: active || scale !== undefined ? line : colors.gray[8],
|
|
822
|
+
};
|
|
823
|
+
}),
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
function toneFor(active: boolean, sign: Sign) {
|
|
827
|
+
return tones.find(
|
|
828
|
+
(tone) => tone.sign === sign && (tone.state === "active") === active,
|
|
829
|
+
)!;
|
|
830
|
+
}
|
|
831
|
+
|
|
721
832
|
function isAncestor(
|
|
722
833
|
parents: Map<string, string | undefined>,
|
|
723
834
|
ancestor: string,
|
|
@@ -806,7 +917,6 @@ const described = {
|
|
|
806
917
|
textDecorationStyle: "dotted" as const,
|
|
807
918
|
textDecorationColor: colors.gray[8],
|
|
808
919
|
textUnderlineOffset: "3px",
|
|
809
|
-
cursor: "help",
|
|
810
920
|
},
|
|
811
921
|
};
|
|
812
922
|
|
|
@@ -878,7 +988,18 @@ const styles = {
|
|
|
878
988
|
"&[data-depth='1'], &[data-depth='3']": {
|
|
879
989
|
backgroundColor: colors.gray[2],
|
|
880
990
|
},
|
|
991
|
+
"&[data-change='+']": {
|
|
992
|
+
borderColor: colors.green[8],
|
|
993
|
+
backgroundColor: colors.green[2],
|
|
994
|
+
},
|
|
995
|
+
"&[data-change='-']": {
|
|
996
|
+
borderColor: colors.red[8],
|
|
997
|
+
borderStyle: "dashed",
|
|
998
|
+
backgroundColor: colors.red[2],
|
|
999
|
+
},
|
|
881
1000
|
"&[data-active='true']": { borderColor: colors.accent[8] },
|
|
1001
|
+
"&[data-active='true'][data-change='+']": { borderColor: colors.green[10] },
|
|
1002
|
+
"&[data-active='true'][data-change='-']": { borderColor: colors.red[10] },
|
|
882
1003
|
}),
|
|
883
1004
|
header: style(
|
|
884
1005
|
flex({ direction: "row", alignItems: "baseline", gap: 3, wrap: true }),
|
|
@@ -886,7 +1007,7 @@ const styles = {
|
|
|
886
1007
|
focusRing(),
|
|
887
1008
|
{
|
|
888
1009
|
minWidth: 0,
|
|
889
|
-
"& > [data-described]": described["&[data-described='true']"],
|
|
1010
|
+
"& > [data-described='true']": described["&[data-described='true']"],
|
|
890
1011
|
},
|
|
891
1012
|
),
|
|
892
1013
|
headerButton: style({
|
|
@@ -928,6 +1049,14 @@ const styles = {
|
|
|
928
1049
|
columnGap: spacing.value(4),
|
|
929
1050
|
paddingInline: spacing.value(2),
|
|
930
1051
|
marginInline: `-${spacing.value(2)}`,
|
|
1052
|
+
"&[data-change='+']": {
|
|
1053
|
+
backgroundColor: colors.greenAlpha[3],
|
|
1054
|
+
boxShadow: `inset 2px 0 ${colors.green[9]}`,
|
|
1055
|
+
},
|
|
1056
|
+
"&[data-change='-']": {
|
|
1057
|
+
backgroundColor: colors.redAlpha[3],
|
|
1058
|
+
boxShadow: `inset 2px 0 ${colors.red[9]}`,
|
|
1059
|
+
},
|
|
931
1060
|
"&[data-changed='true']": { backgroundColor: colors.accentAlpha[3] },
|
|
932
1061
|
}),
|
|
933
1062
|
packet: style(radius.circle, {
|
|
@@ -940,6 +1069,14 @@ const styles = {
|
|
|
940
1069
|
boxShadow: `0 0 0 3px ${colors.accentAlpha[5]}`,
|
|
941
1070
|
pointerEvents: "none",
|
|
942
1071
|
zIndex: 2,
|
|
1072
|
+
"&[data-change='+']": {
|
|
1073
|
+
backgroundColor: colors.green[9],
|
|
1074
|
+
boxShadow: `0 0 0 3px ${colors.greenAlpha[5]}`,
|
|
1075
|
+
},
|
|
1076
|
+
"&[data-change='-']": {
|
|
1077
|
+
backgroundColor: colors.red[9],
|
|
1078
|
+
boxShadow: `0 0 0 3px ${colors.redAlpha[5]}`,
|
|
1079
|
+
},
|
|
943
1080
|
}),
|
|
944
1081
|
labelAnchor: style({
|
|
945
1082
|
position: "absolute",
|
|
@@ -960,6 +1097,15 @@ const styles = {
|
|
|
960
1097
|
border: `1px solid ${colors.accent[7]}`,
|
|
961
1098
|
backgroundColor: backgroundColor.app,
|
|
962
1099
|
color: colors.accent[11],
|
|
1100
|
+
"&[data-change='+']": {
|
|
1101
|
+
borderColor: colors.green[7],
|
|
1102
|
+
color: colors.green[11],
|
|
1103
|
+
},
|
|
1104
|
+
"&[data-change='-']": {
|
|
1105
|
+
borderColor: colors.red[7],
|
|
1106
|
+
borderStyle: "dashed",
|
|
1107
|
+
color: colors.red[11],
|
|
1108
|
+
},
|
|
963
1109
|
"&:is(button)": { cursor: "pointer" },
|
|
964
1110
|
"&:is(button):hover": { backgroundColor: colors.accent[3] },
|
|
965
1111
|
"&[aria-pressed='true']": { backgroundColor: colors.blue[3] },
|