@eric-emg/symphiq-components 1.2.507 → 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,11 +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 = 400;
1912
- this.DEFAULT_DEBOUNCE_DELAY = 200;
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
1913
  this.HEADER_HEIGHT_DIFF = 48;
1914
+ this.STABILITY_THRESHOLD = 8;
1915
+ this.DEBUG = true;
1914
1916
  this.isScrolled = signal(false, ...(ngDevMode ? [{ debugName: "isScrolled" }] : []));
1915
1917
  this.scrollProgress = signal(0, ...(ngDevMode ? [{ debugName: "scrollProgress" }] : []));
1916
1918
  this.scrollTimeout = null;
@@ -1918,7 +1920,9 @@ class HeaderScrollService {
1918
1920
  this.isProgrammaticScroll = false;
1919
1921
  this.lastScrollPosition = 0;
1920
1922
  this.scrollStabilityCount = 0;
1921
- this.STABILITY_THRESHOLD = 5;
1923
+ this.scrollDirection = 'none';
1924
+ this.consecutiveDirectionCount = 0;
1925
+ this.stateChangeCount = 0;
1922
1926
  this.config = {
1923
1927
  collapseThreshold: this.DEFAULT_COLLAPSE_THRESHOLD,
1924
1928
  expandThreshold: this.DEFAULT_EXPAND_THRESHOLD,
@@ -1926,86 +1930,158 @@ class HeaderScrollService {
1926
1930
  debounceDelay: this.DEFAULT_DEBOUNCE_DELAY
1927
1931
  };
1928
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
+ }
1929
1939
  configure(config) {
1930
1940
  this.config = {
1931
1941
  ...this.config,
1932
1942
  ...config
1933
1943
  };
1944
+ this.log('Configured', { config: this.config });
1934
1945
  }
1935
1946
  handleScroll(embedded = false) {
1936
1947
  if (embedded) {
1937
1948
  return;
1938
1949
  }
1939
1950
  const scrollPosition = window.scrollY;
1951
+ const now = Date.now();
1940
1952
  const windowHeight = window.innerHeight;
1941
1953
  const documentHeight = document.documentElement.scrollHeight;
1942
1954
  const maxScroll = documentHeight - windowHeight;
1943
1955
  const progress = maxScroll > 0 ? (scrollPosition / maxScroll) * 100 : 0;
1944
1956
  this.scrollProgress.set(Math.min(100, Math.max(0, progress)));
1945
1957
  if (this.isProgrammaticScroll) {
1958
+ this.log('Ignored: programmatic scroll', { scrollPosition });
1946
1959
  return;
1947
1960
  }
1948
- const now = Date.now();
1949
1961
  const timeSinceLastChange = now - this.lastStateChangeTime;
1950
1962
  if (timeSinceLastChange < this.config.stateChangeCooldown) {
1963
+ this.log('Ignored: cooldown active', {
1964
+ timeSinceLastChange,
1965
+ cooldown: this.config.stateChangeCooldown,
1966
+ scrollPosition
1967
+ });
1951
1968
  return;
1952
1969
  }
1953
- if (this.scrollTimeout) {
1954
- 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
+ }
1981
+ }
1982
+ if (absScrollDelta < 3) {
1983
+ this.scrollStabilityCount++;
1984
+ }
1985
+ else {
1986
+ this.scrollStabilityCount = 0;
1955
1987
  }
1988
+ this.lastScrollPosition = scrollPosition;
1956
1989
  const currentState = this.isScrolled();
1957
- let newState = currentState;
1990
+ let desiredState = currentState;
1958
1991
  if (!currentState && scrollPosition > this.config.collapseThreshold) {
1959
- newState = true;
1992
+ desiredState = true;
1960
1993
  }
1961
1994
  else if (currentState && scrollPosition < this.config.expandThreshold) {
1962
- newState = false;
1995
+ desiredState = false;
1963
1996
  }
1964
- const scrollDelta = Math.abs(scrollPosition - this.lastScrollPosition);
1965
- this.lastScrollPosition = scrollPosition;
1966
- if (scrollDelta < 5) {
1967
- this.scrollStabilityCount++;
1997
+ if (currentState === desiredState) {
1998
+ return;
1968
1999
  }
1969
- else {
1970
- 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;
1971
2018
  }
1972
- if (currentState !== newState && this.scrollStabilityCount >= this.STABILITY_THRESHOLD) {
1973
- this.scrollTimeout = setTimeout(() => {
1974
- const finalPosition = window.scrollY;
1975
- const finalCurrentState = this.isScrolled();
1976
- let finalState = finalCurrentState;
1977
- if (!finalCurrentState && finalPosition > this.config.collapseThreshold) {
1978
- finalState = true;
1979
- }
1980
- else if (finalCurrentState && finalPosition < this.config.expandThreshold) {
1981
- finalState = false;
1982
- }
1983
- if (this.isScrolled() !== finalState) {
1984
- this.isProgrammaticScroll = true;
1985
- this.isScrolled.set(finalState);
1986
- this.lastStateChangeTime = Date.now();
1987
- this.scrollStabilityCount = 0;
1988
- if (finalState) {
1989
- window.scrollBy(0, this.HEADER_HEIGHT_DIFF);
1990
- }
1991
- setTimeout(() => {
1992
- this.isProgrammaticScroll = false;
1993
- this.lastScrollPosition = window.scrollY;
1994
- }, 350);
1995
- }
1996
- }, this.config.debounceDelay);
2019
+ if (!directionMatchesIntent && !isStable) {
2020
+ this.log('Blocked: direction does not match intent and not stable');
2021
+ return;
1997
2022
  }
2023
+ if (this.scrollTimeout) {
2024
+ clearTimeout(this.scrollTimeout);
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);
1998
2068
  }
1999
2069
  resetHeaderState() {
2070
+ this.log('resetHeaderState called');
2000
2071
  this.isProgrammaticScroll = true;
2001
2072
  setTimeout(() => {
2002
2073
  this.isProgrammaticScroll = false;
2003
2074
  this.isScrolled.set(false);
2075
+ this.log('resetHeaderState complete');
2004
2076
  }, 800);
2005
2077
  }
2006
2078
  resetState() {
2079
+ this.log('resetState called');
2007
2080
  this.isScrolled.set(false);
2008
2081
  this.scrollProgress.set(0);
2082
+ this.scrollStabilityCount = 0;
2083
+ this.consecutiveDirectionCount = 0;
2084
+ this.scrollDirection = 'none';
2009
2085
  if (this.scrollTimeout) {
2010
2086
  clearTimeout(this.scrollTimeout);
2011
2087
  this.scrollTimeout = null;