PageSourceSearch

https://www.phreesia.com/wp-content/themes/studio-simpatico/assets/js/jazzy/stuck.js?ver=1.0.48

js phreesia.com collected 2026-10-02 04:13:17 UTC 4,639 bytes, 145 lines download raw bytes

1/**
2 * JazzyStuck — detect when position:sticky elements stick
3 *
4 * Adds a class to a `position: sticky` element when it's pinned to its
5 * `top` offset. Use for shadows, borders, background changes, or tighter
6 * padding on stuck headers/subnavs.
7 *
8 * Markup pattern:
9 *   <header data-jazzy-stuck>…</header>
10 *
11 * Required CSS (library does not supply this):
12 *   header { position: sticky; top: 0; }
13 *   header.stuck { box-shadow: 0 2px 8px rgba(0,0,0,.1); }
14 *
15 * Options:
16 *   data-jazzy-stuck-class="is-pinned"  Class toggled when stuck (default: "stuck")
17 *
18 * Programmatic:
19 *   new JazzyStuck('.subnav')                   // selector — observes all matches
20 *   new JazzyStuck(element)                     // single element
21 *   JazzyStuck.init(element, { stuckClass: 'pinned' })
22 *
23 * Events emitted on the element:
24 *   jazzystuck:change  { detail: { stuck: boolean } }
25 *
26 * How it works:
27 *   Uses IntersectionObserver with a negative rootMargin equal to the
28 *   element's CSS `top` value. Fires when the element crosses that
29 *   threshold. The callback reads boundingClientRect.y to decide whether
30 *   the element is currently pinned vs. scrolling.
31 *
32 * Known limitations:
33 *   - Top-sticky only. No detection for bottom/left/right sticky.
34 *   - `top` value is read once at init. Responsive CSS that changes `top`
35 *     at a breakpoint won't re-compute — destroy and re-init if needed.
36 *   - Doesn't detect "unstick at parent bottom" (when sticky containing
37 *     block scrolls past and the element scrolls with it). The class
38 *     stays applied. Not usually visible since the element is offscreen.
39 *
40 * @since 1.0.0
41 */
42(function () {
43	'use strict';
44
45	if (!('IntersectionObserver' in window)) return;
46
47	function JazzyStuck(target, opts) {
48		opts = opts || {};
49		this.stuckClass = opts.stuckClass || 'stuck';
50		this.elements = [];
51		this._observers = [];
52
53		var els;
54		if (target instanceof Element) {
55			els = [target];
56		} else if (typeof target === 'string') {
57			els = Array.from(document.querySelectorAll(target));
58		} else if (target && typeof target.length === 'number') {
59			els = Array.from(target);
60		} else {
61			return;
62		}
63
64		var self = this;
65		els.forEach(function (el) { self._observe(el); });
66	}
67
68	JazzyStuck.prototype._getTopOffset = function (el) {
69		var top = window.getComputedStyle(el).getPropertyValue('top');
70		if (!top) return null;
71		var parsed = parseInt(top, 10);
72		return isNaN(parsed) ? null : parsed + 1;
73	};
74
75	JazzyStuck.prototype._observe = function (el) {
76		var self = this;
77		var topOffset = this._getTopOffset(el);
78
79		// If `top` isn't set, the element isn't actually sticky — skip.
80		if (topOffset === null) return;
81
82		var observer = new IntersectionObserver(function (entries) {
83			// A scroll lock pins the body at a negative top, which moves every
84			// observed element and fires this — but the page hasn't scrolled, so
85			// the stuck state must hold or the header sheds its skin on modal open.
86			if (document.documentElement.classList.contains('dialog-open')) return;
87
88			entries.forEach(function (entry) {
89				var stuck = entry.boundingClientRect.y < topOffset;
90				var wasStuck = entry.target.classList.contains(self.stuckClass);
91
92				entry.target.classList.toggle(self.stuckClass, stuck);
93
94				if (stuck !== wasStuck) {
95					entry.target.dispatchEvent(new CustomEvent('jazzystuck:change', {
96						detail: { stuck: stuck }
97					}));
98				}
99			});
100		}, {
101			root: null,
102			rootMargin: '-' + topOffset + 'px 0px 0px 0px',
103			threshold: [0, 1]
104		});
105
106		observer.observe(el);
107		this.elements.push(el);
108		this._observers.push(observer);
109		el._jazzyStuck = this;
110	};
111
112	JazzyStuck.prototype.destroy = function () {
113		this._observers.forEach(function (o) { o.disconnect(); });
114		this._observers = [];
115		this.elements.forEach(function (el) { delete el._jazzyStuck; });
116		this.elements = [];
117	};
118
119	JazzyStuck.init = function (el, opts) {
120		if (el._jazzyStuck) return el._jazzyStuck;
121		return new JazzyStuck(el, opts);
122	};
123
124	// ------------------------------------------------------------------
125	// Auto-init from data attributes
126	// ------------------------------------------------------------------
127
128	function autoInit() {
129		document.querySelectorAll('[data-jazzy-stuck]').forEach(function (el) {
130			if (el._jazzyStuck) return;
131			var opts = {};
132			var stuckClass = el.getAttribute('data-jazzy-stuck-class');
133			if (stuckClass) opts.stuckClass = stuckClass;
134			new JazzyStuck(el, opts);
135		});
136	}
137
138	if (document.readyState === 'loading') {
139		document.addEventListener('DOMContentLoaded', autoInit);
140	} else {
141		autoInit();
142	}
143
144	window.JazzyStuck = JazzyStuck;
145})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.