@eric-emg/symphiq-components 1.2.506 → 1.2.508

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.
@@ -1906,10 +1906,13 @@ class MetricFormatterService {
1906
1906
 
1907
1907
  class HeaderScrollService {
1908
1908
  constructor() {
1909
- this.DEFAULT_COLLAPSE_THRESHOLD = 150;
1910
- this.DEFAULT_EXPAND_THRESHOLD = 10;
1911
- this.DEFAULT_STATE_CHANGE_COOLDOWN = 300;
1912
- this.DEFAULT_DEBOUNCE_DELAY = 150;
1909
+ this.DEFAULT_COLLAPSE_THRESHOLD = 180;
1910
+ this.DEFAULT_EXPAND_THRESHOLD = 5;
1911
+ this.DEFAULT_STATE_CHANGE_COOLDOWN = 500;
1912
+ this.DEFAULT_DEBOUNCE_DELAY = 250;
1913
+ this.HEADER_HEIGHT_DIFF = 48;
1914
+ this.STABILITY_THRESHOLD = 8;
1915
+ this.DEBUG = true;
1913
1916
  this.isScrolled = signal(false, ...(ngDevMode ? [{ debugName: "isScrolled" }] : []));
1914
1917
  this.scrollProgress = signal(0, ...(ngDevMode ? [{ debugName: "scrollProgress" }] : []));
1915
1918
  this.scrollTimeout = null;
@@ -1917,7 +1920,9 @@ class HeaderScrollService {
1917
1920
  this.isProgrammaticScroll = false;
1918
1921
  this.lastScrollPosition = 0;
1919
1922
  this.scrollStabilityCount = 0;
1920
- this.STABILITY_THRESHOLD = 3;
1923
+ this.scrollDirection = 'none';
1924
+ this.consecutiveDirectionCount = 0;
1925
+ this.stateChangeCount = 0;
1921
1926
  this.config = {
1922
1927
  collapseThreshold: this.DEFAULT_COLLAPSE_THRESHOLD,
1923
1928
  expandThreshold: this.DEFAULT_EXPAND_THRESHOLD,
@@ -1925,78 +1930,158 @@ class HeaderScrollService {
1925
1930
  debounceDelay: this.DEFAULT_DEBOUNCE_DELAY
1926
1931
  };
1927
1932
  }
1933
+ log(message, data) {
1934
+ if (this.DEBUG) {
1935
+ const timestamp = new Date().toISOString().split('T')[1];
1936
+ console.log(`[HeaderScroll ${timestamp}] ${message}`, data ?? '');
1937
+ }
1938
+ }
1928
1939
  configure(config) {
1929
1940
  this.config = {
1930
1941
  ...this.config,
1931
1942
  ...config
1932
1943
  };
1944
+ this.log('Configured', { config: this.config });
1933
1945
  }
1934
1946
  handleScroll(embedded = false) {
1935
1947
  if (embedded) {
1936
1948
  return;
1937
1949
  }
1938
1950
  const scrollPosition = window.scrollY;
1951
+ const now = Date.now();
1939
1952
  const windowHeight = window.innerHeight;
1940
1953
  const documentHeight = document.documentElement.scrollHeight;
1941
1954
  const maxScroll = documentHeight - windowHeight;
1942
1955
  const progress = maxScroll > 0 ? (scrollPosition / maxScroll) * 100 : 0;
1943
1956
  this.scrollProgress.set(Math.min(100, Math.max(0, progress)));
1944
1957
  if (this.isProgrammaticScroll) {
1958
+ this.log('Ignored: programmatic scroll', { scrollPosition });
1945
1959
  return;
1946
1960
  }
1947
- const now = Date.now();
1948
1961
  const timeSinceLastChange = now - this.lastStateChangeTime;
1949
1962
  if (timeSinceLastChange < this.config.stateChangeCooldown) {
1963
+ this.log('Ignored: cooldown active', {
1964
+ timeSinceLastChange,
1965
+ cooldown: this.config.stateChangeCooldown,
1966
+ scrollPosition
1967
+ });
1950
1968
  return;
1951
1969
  }
1952
- if (this.scrollTimeout) {
1953
- clearTimeout(this.scrollTimeout);
1970
+ const scrollDelta = scrollPosition - this.lastScrollPosition;
1971
+ const absScrollDelta = Math.abs(scrollDelta);
1972
+ const newDirection = scrollDelta > 2 ? 'down' : scrollDelta < -2 ? 'up' : 'none';
1973
+ if (newDirection !== 'none') {
1974
+ if (newDirection === this.scrollDirection) {
1975
+ this.consecutiveDirectionCount++;
1976
+ }
1977
+ else {
1978
+ this.consecutiveDirectionCount = 1;
1979
+ this.scrollDirection = newDirection;
1980
+ }
1954
1981
  }
1982
+ if (absScrollDelta < 3) {
1983
+ this.scrollStabilityCount++;
1984
+ }
1985
+ else {
1986
+ this.scrollStabilityCount = 0;
1987
+ }
1988
+ this.lastScrollPosition = scrollPosition;
1955
1989
  const currentState = this.isScrolled();
1956
- let newState = currentState;
1990
+ let desiredState = currentState;
1957
1991
  if (!currentState && scrollPosition > this.config.collapseThreshold) {
1958
- newState = true;
1992
+ desiredState = true;
1959
1993
  }
1960
1994
  else if (currentState && scrollPosition < this.config.expandThreshold) {
1961
- newState = false;
1995
+ desiredState = false;
1962
1996
  }
1963
- const scrollDelta = Math.abs(scrollPosition - this.lastScrollPosition);
1964
- this.lastScrollPosition = scrollPosition;
1965
- if (scrollDelta < 5) {
1966
- this.scrollStabilityCount++;
1997
+ if (currentState === desiredState) {
1998
+ return;
1967
1999
  }
1968
- else {
1969
- this.scrollStabilityCount = 0;
2000
+ const isStable = this.scrollStabilityCount >= this.STABILITY_THRESHOLD;
2001
+ const hasConsistentDirection = this.consecutiveDirectionCount >= 3;
2002
+ const directionMatchesIntent = (desiredState && this.scrollDirection === 'down') ||
2003
+ (!desiredState && this.scrollDirection === 'up');
2004
+ this.log('State change candidate', {
2005
+ scrollPosition,
2006
+ currentState: currentState ? 'collapsed' : 'expanded',
2007
+ desiredState: desiredState ? 'collapsed' : 'expanded',
2008
+ stability: this.scrollStabilityCount,
2009
+ direction: this.scrollDirection,
2010
+ directionCount: this.consecutiveDirectionCount,
2011
+ isStable,
2012
+ hasConsistentDirection,
2013
+ directionMatchesIntent
2014
+ });
2015
+ if (!isStable && !hasConsistentDirection) {
2016
+ this.log('Blocked: insufficient stability or direction consistency');
2017
+ return;
1970
2018
  }
1971
- if (currentState !== newState && this.scrollStabilityCount >= this.STABILITY_THRESHOLD) {
1972
- this.scrollTimeout = setTimeout(() => {
1973
- const finalPosition = window.scrollY;
1974
- const finalCurrentState = this.isScrolled();
1975
- let finalState = finalCurrentState;
1976
- if (!finalCurrentState && finalPosition > this.config.collapseThreshold) {
1977
- finalState = true;
1978
- }
1979
- else if (finalCurrentState && finalPosition < this.config.expandThreshold) {
1980
- finalState = false;
1981
- }
1982
- if (this.isScrolled() !== finalState) {
1983
- this.isScrolled.set(finalState);
1984
- this.lastStateChangeTime = Date.now();
1985
- this.scrollStabilityCount = 0;
1986
- }
1987
- }, this.config.debounceDelay);
2019
+ if (!directionMatchesIntent && !isStable) {
2020
+ this.log('Blocked: direction does not match intent and not stable');
2021
+ return;
2022
+ }
2023
+ if (this.scrollTimeout) {
2024
+ clearTimeout(this.scrollTimeout);
1988
2025
  }
2026
+ this.scrollTimeout = setTimeout(() => {
2027
+ const finalPosition = window.scrollY;
2028
+ const finalCurrentState = this.isScrolled();
2029
+ let finalDesiredState = finalCurrentState;
2030
+ if (!finalCurrentState && finalPosition > this.config.collapseThreshold) {
2031
+ finalDesiredState = true;
2032
+ }
2033
+ else if (finalCurrentState && finalPosition < this.config.expandThreshold) {
2034
+ finalDesiredState = false;
2035
+ }
2036
+ if (finalCurrentState === finalDesiredState) {
2037
+ this.log('Debounce check: no change needed', { finalPosition, finalCurrentState });
2038
+ return;
2039
+ }
2040
+ this.stateChangeCount++;
2041
+ this.log('STATE CHANGE EXECUTING', {
2042
+ stateChangeNumber: this.stateChangeCount,
2043
+ from: finalCurrentState ? 'collapsed' : 'expanded',
2044
+ to: finalDesiredState ? 'collapsed' : 'expanded',
2045
+ finalPosition,
2046
+ willCompensate: finalDesiredState
2047
+ });
2048
+ this.isProgrammaticScroll = true;
2049
+ this.isScrolled.set(finalDesiredState);
2050
+ this.lastStateChangeTime = Date.now();
2051
+ this.scrollStabilityCount = 0;
2052
+ this.consecutiveDirectionCount = 0;
2053
+ if (finalDesiredState) {
2054
+ const beforeScroll = window.scrollY;
2055
+ window.scrollBy(0, this.HEADER_HEIGHT_DIFF);
2056
+ this.log('Scroll compensation applied', {
2057
+ before: beforeScroll,
2058
+ after: window.scrollY,
2059
+ compensation: this.HEADER_HEIGHT_DIFF
2060
+ });
2061
+ }
2062
+ setTimeout(() => {
2063
+ this.isProgrammaticScroll = false;
2064
+ this.lastScrollPosition = window.scrollY;
2065
+ this.log('Programmatic scroll lock released', { scrollPosition: window.scrollY });
2066
+ }, 400);
2067
+ }, this.config.debounceDelay);
1989
2068
  }
1990
2069
  resetHeaderState() {
2070
+ this.log('resetHeaderState called');
1991
2071
  this.isProgrammaticScroll = true;
1992
2072
  setTimeout(() => {
1993
2073
  this.isProgrammaticScroll = false;
1994
2074
  this.isScrolled.set(false);
2075
+ this.log('resetHeaderState complete');
1995
2076
  }, 800);
1996
2077
  }
1997
2078
  resetState() {
2079
+ this.log('resetState called');
1998
2080
  this.isScrolled.set(false);
1999
2081
  this.scrollProgress.set(0);
2082
+ this.scrollStabilityCount = 0;
2083
+ this.consecutiveDirectionCount = 0;
2084
+ this.scrollDirection = 'none';
2000
2085
  if (this.scrollTimeout) {
2001
2086
  clearTimeout(this.scrollTimeout);
2002
2087
  this.scrollTimeout = null;