@spider-analyzer/timeline 5.0.2 → 5.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spider-analyzer/timeline",
3
- "version": "5.0.2",
3
+ "version": "5.0.4",
4
4
  "description": "React graphical component to display metric over time with a time selection feature.",
5
5
  "author": "Thibaut Raballand <spider.analyzer@gmail.com> (https://spider-analyzer.io)",
6
6
  "license": "MIT",
package/src/TimeLine.tsx CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  useCallback,
4
4
  useEffect,
5
5
  useImperativeHandle,
6
+ useMemo,
6
7
  useRef,
7
8
  useState,
8
9
  } from 'react';
@@ -875,8 +876,9 @@ const TimeLine = forwardRef<TimeLineHandle, any>(function TimeLineWrapper(props,
875
876
  const handleLoadDefault = useCallback(() => {
876
877
  const ret = props.onLoadDefaultDomain?.();
877
878
  const apply = (d: any) => {
878
- if (!d) return;
879
+ if (!d || !d.min || !d.max) return;
879
880
  const m = domainToMoments(d, zone);
881
+ if (!m.min || !m.max) return;
880
882
  setStack([m]);
881
883
  if (props.onDomainChange) {
882
884
  props.onDomainChange({ min: m.min.toDate(), max: m.max.toDate() });
@@ -886,27 +888,66 @@ const TimeLine = forwardRef<TimeLineHandle, any>(function TimeLineWrapper(props,
886
888
  else if (ret) apply(ret);
887
889
  }, [props.onLoadDefaultDomain, props.onDomainChange, zone]);
888
890
 
889
- // Build the moment-typed props the inner expects. Apply the same
890
- // defaults the inner destructures locally so that ref-path accesses
891
- // (propsRef.current.xAxis.spaceBetweenTicks etc.) never trip on an
892
- // undefined consumer prop.
891
+ // Normalize incoming props into moment-typed values the inner expects.
892
+ // MEMOIZE on the raw consumer reference so that a parent re-render
893
+ // with an unchanged `histo` (etc.) doesn't hand the inner a fresh
894
+ // object otherwise the inner's `histo !== prev` effect fires every
895
+ // render → setState → re-render → infinite loop (observed in
896
+ // Network-View: x-axis never stabilizes, loader never clears).
897
+ const timeSpanMoments = useMemo(
898
+ () => (props.timeSpan ? timeSpanToMoments(props.timeSpan, zone) : undefined),
899
+ [props.timeSpan, zone],
900
+ );
901
+ const maxDomainMoments = useMemo(
902
+ () => (props.maxDomain ? domainToMoments(props.maxDomain, zone) : undefined),
903
+ [props.maxDomain, zone],
904
+ );
905
+ const histoMoments = useMemo(
906
+ () => (props.histo?.items
907
+ ? { ...props.histo, items: props.histo.items.map((it: any) => ({ ...it, time: toMoment(it.time, zone) })) }
908
+ : props.histo),
909
+ [props.histo, zone],
910
+ );
911
+ const qualityMoments = useMemo(
912
+ () => (props.quality?.items
913
+ ? { ...props.quality, items: props.quality.items.map((it: any) => ({ ...it, time: toMoment(it.time, zone) })) }
914
+ : props.quality),
915
+ [props.quality, zone],
916
+ );
917
+ const biggestVisibleDomainDur = useMemo(
918
+ () => (props.biggestVisibleDomain != null ? toDuration(props.biggestVisibleDomain) : undefined),
919
+ [props.biggestVisibleDomain],
920
+ );
921
+ const biggestTimeSpanDur = useMemo(
922
+ () => (props.biggestTimeSpan != null ? toDuration(props.biggestTimeSpan) : undefined),
923
+ [props.biggestTimeSpan],
924
+ );
925
+ const smallestResolutionDur = useMemo(
926
+ () => (props.smallestResolution != null ? toDuration(props.smallestResolution) : undefined),
927
+ [props.smallestResolution],
928
+ );
929
+ const xAxisMerged = useMemo(
930
+ () => ({ ...xAxisDefault, ...(props.xAxis ?? {}) }),
931
+ [props.xAxis],
932
+ );
933
+ const marginMerged = useMemo(
934
+ () => ({ ...marginDefault, ...(props.margin ?? {}) }),
935
+ [props.margin],
936
+ );
937
+
893
938
  const innerProps: any = {
894
939
  ...props,
895
- xAxis: { ...xAxisDefault, ...(props.xAxis ?? {}) },
940
+ xAxis: xAxisMerged,
896
941
  yAxis: props.yAxis ?? {},
897
- margin: { ...marginDefault, ...(props.margin ?? {}) },
942
+ margin: marginMerged,
898
943
  domains: stack,
899
- timeSpan: props.timeSpan ? timeSpanToMoments(props.timeSpan, zone) : undefined,
900
- maxDomain: props.maxDomain ? domainToMoments(props.maxDomain, zone) : undefined,
901
- histo: props.histo?.items
902
- ? { ...props.histo, items: props.histo.items.map((it: any) => ({ ...it, time: toMoment(it.time, zone) })) }
903
- : props.histo,
904
- quality: props.quality?.items
905
- ? { ...props.quality, items: props.quality.items.map((it: any) => ({ ...it, time: toMoment(it.time, zone) })) }
906
- : props.quality,
907
- biggestVisibleDomain: props.biggestVisibleDomain != null ? toDuration(props.biggestVisibleDomain) : undefined,
908
- biggestTimeSpan: props.biggestTimeSpan != null ? toDuration(props.biggestTimeSpan) : undefined,
909
- smallestResolution: props.smallestResolution != null ? toDuration(props.smallestResolution) : undefined,
944
+ timeSpan: timeSpanMoments,
945
+ maxDomain: maxDomainMoments,
946
+ histo: histoMoments,
947
+ quality: qualityMoments,
948
+ biggestVisibleDomain: biggestVisibleDomainDur,
949
+ biggestTimeSpan: biggestTimeSpanDur,
950
+ smallestResolution: smallestResolutionDur,
910
951
  onUpdateDomains: handleUpdateDomains,
911
952
  onLoadDefaultDomain: handleLoadDefault,
912
953
  // onLoadHisto: object shape + Date instants
package/src/time.ts CHANGED
@@ -23,6 +23,14 @@ export function toMoment(x: Instant, zone?: string): Moment {
23
23
  return m;
24
24
  }
25
25
 
26
+ // Null-preserving variant: undefined/null stay undefined instead of
27
+ // becoming `moment()` (= now). Optional domain bounds like maxDomain.min
28
+ // rely on this to remain "unset".
29
+ function toMomentOpt(x: Instant | null | undefined, zone?: string): Moment | undefined {
30
+ if (x == null) return undefined;
31
+ return toMoment(x, zone);
32
+ }
33
+
26
34
  export function toDuration(x: Millis): Duration {
27
35
  if (moment.isDuration(x)) return x as Duration;
28
36
  return moment.duration(x as number);
@@ -35,12 +43,12 @@ export function fromMoment(m: Moment | null | undefined): Date | null {
35
43
  export interface DateDomain { min: Date; max: Date; }
36
44
  export interface DateTimeSpan { start: Date; stop: Date; }
37
45
 
38
- export function domainToMoments(d: { min: Instant; max: Instant }, zone?: string) {
39
- return { min: toMoment(d.min, zone), max: toMoment(d.max, zone) };
46
+ export function domainToMoments(d: { min?: Instant | null; max?: Instant | null }, zone?: string) {
47
+ return { min: toMomentOpt(d.min, zone), max: toMomentOpt(d.max, zone) };
40
48
  }
41
49
 
42
- export function timeSpanToMoments(t: { start: Instant; stop: Instant }, zone?: string) {
43
- return { start: toMoment(t.start, zone), stop: toMoment(t.stop, zone) };
50
+ export function timeSpanToMoments(t: { start?: Instant | null; stop?: Instant | null }, zone?: string) {
51
+ return { start: toMomentOpt(t.start, zone), stop: toMomentOpt(t.stop, zone) };
44
52
  }
45
53
 
46
54
  export function domainToDates(d: { min: Moment; max: Moment }): DateDomain {