@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.
- package/fesm2022/symphiq-components.mjs +119 -34
- package/fesm2022/symphiq-components.mjs.map +1 -1
- package/index.d.ts +38 -32
- package/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1906,10 +1906,13 @@ class MetricFormatterService {
|
|
|
1906
1906
|
|
|
1907
1907
|
class HeaderScrollService {
|
|
1908
1908
|
constructor() {
|
|
1909
|
-
this.DEFAULT_COLLAPSE_THRESHOLD =
|
|
1910
|
-
this.DEFAULT_EXPAND_THRESHOLD =
|
|
1911
|
-
this.DEFAULT_STATE_CHANGE_COOLDOWN =
|
|
1912
|
-
this.DEFAULT_DEBOUNCE_DELAY =
|
|
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.
|
|
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
|
-
|
|
1953
|
-
|
|
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
|
|
1990
|
+
let desiredState = currentState;
|
|
1957
1991
|
if (!currentState && scrollPosition > this.config.collapseThreshold) {
|
|
1958
|
-
|
|
1992
|
+
desiredState = true;
|
|
1959
1993
|
}
|
|
1960
1994
|
else if (currentState && scrollPosition < this.config.expandThreshold) {
|
|
1961
|
-
|
|
1995
|
+
desiredState = false;
|
|
1962
1996
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
if (scrollDelta < 5) {
|
|
1966
|
-
this.scrollStabilityCount++;
|
|
1997
|
+
if (currentState === desiredState) {
|
|
1998
|
+
return;
|
|
1967
1999
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
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 (
|
|
1972
|
-
this.
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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;
|