@splendidlabz/utils 1.9.1 → 1.10.1

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/dom/actions/index.cjs +75 -73
  3. package/dist/cjs/dom/actions/masonry.cjs +4 -2
  4. package/dist/cjs/dom/actions/prefer-horizontal-scroll.cjs +24 -24
  5. package/dist/cjs/dom/actions/sticky.cjs +4 -2
  6. package/dist/cjs/dom/font-size.cjs +9 -3
  7. package/dist/cjs/dom/index.cjs +81 -66
  8. package/dist/cjs/dom/observers/index.cjs +24 -17
  9. package/dist/cjs/dom/observers/resize-observer.cjs +4 -2
  10. package/dist/cjs/dom/observers/scroll-observer.cjs +24 -17
  11. package/dist/esm/dom/actions/index.js +75 -73
  12. package/dist/esm/dom/actions/masonry.js +4 -2
  13. package/dist/esm/dom/actions/prefer-horizontal-scroll.js +24 -24
  14. package/dist/esm/dom/actions/sticky.js +4 -2
  15. package/dist/esm/dom/font-size.js +8 -3
  16. package/dist/esm/dom/index.js +80 -66
  17. package/dist/esm/dom/observers/index.js +24 -17
  18. package/dist/esm/dom/observers/resize-observer.js +4 -2
  19. package/dist/esm/dom/observers/scroll-observer.js +24 -17
  20. package/dist/types/dom/events.d.cts +2 -2
  21. package/dist/types/dom/font-size.d.cts +3 -2
  22. package/dist/types/dom/index.d.cts +2 -2
  23. package/dist/types/dom/observers/index.d.cts +1 -1
  24. package/dist/types/dom/observers/resize-observer.d.cts +3 -3
  25. package/dist/types/dom/observers/scroll-observer.d.cts +34 -21
  26. package/dist/types/dom/spam-prevention.d.cts +7 -7
  27. package/dist/types/lib/checks.d.cts +1 -1
  28. package/package.json +1 -1
  29. package/src/dom/actions/prefer-horizontal-scroll.js +3 -8
  30. package/src/dom/events.js +1 -1
  31. package/src/dom/font-size.js +9 -4
  32. package/src/dom/observers/resize-observer.js +6 -4
  33. package/src/dom/observers/scroll-observer.js +41 -27
  34. package/src/dom/spam-prevention.js +5 -4
  35. package/src/dom/events.test.js +0 -99
@@ -57,12 +57,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
57
57
  function resizeObserver(target, options) {
58
58
  const { callback, ...opts } = options;
59
59
  const observer = new ResizeObserver(observerFn);
60
- if (target === window) target = document.body;
60
+ if (target === window || target === document)
61
+ target = document.documentElement;
61
62
  useObserverMethodOnTarget(target, observer, "observe", opts);
62
63
  function observerFn(entries) {
63
64
  for (const entry of entries) {
64
65
  if (callback) callback({ entry, entries, observer });
65
- else dispatchEvent(target, "resize-obs", { entry, entries, observer });
66
+ else
67
+ dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
66
68
  }
67
69
  }
68
70
  return {
@@ -57,12 +57,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
57
57
  function resizeObserver(target, options) {
58
58
  const { callback, ...opts } = options;
59
59
  const observer = new ResizeObserver(observerFn);
60
- if (target === window) target = document.body;
60
+ if (target === window || target === document)
61
+ target = document.documentElement;
61
62
  useObserverMethodOnTarget(target, observer, "observe", opts);
62
63
  function observerFn(entries) {
63
64
  for (const entry of entries) {
64
65
  if (callback) callback({ entry, entries, observer });
65
- else dispatchEvent(target, "resize-obs", { entry, entries, observer });
66
+ else
67
+ dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
66
68
  }
67
69
  }
68
70
  return {
@@ -83,40 +85,44 @@ var defaultOptions = {
83
85
  // Float between 0 to 1.
84
86
  tolerance: 0.1,
85
87
  // Float between 0 to 1. Tolerance for event firing
86
- throttle: 16,
87
- // Throttle interval in ms (default: ~60fps)
88
88
  once: false
89
89
  // Only fire threshold callback once
90
90
  };
91
+ function getScrollElement(node) {
92
+ if (node === document || node === window || node === document.documentElement) {
93
+ return document.documentElement;
94
+ }
95
+ return (
96
+ /** @type {Element} */
97
+ node
98
+ );
99
+ }
91
100
  function scrollObserver(node, options = {}) {
92
101
  const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
93
102
  const opts = { ...defaultOptions, ...userOpts };
94
- const { threshold, tolerance, throttle, once } = opts;
95
- const prevScrollDirection = null;
103
+ const { threshold, tolerance, once } = opts;
104
+ let prevScrollDirection = null;
96
105
  let prevScrollTop = 0;
97
106
  let prevScrollPercent = 0;
98
- let lastThrottleTime = 0;
99
107
  let thresholdFired = false;
100
108
  let rafId = null;
101
- const isDocumentScroll = node === document || node === window;
102
- const scrollElement = isDocumentScroll ? document.documentElement : node;
109
+ const isDocumentScroll = node === document || node === window || node === document.documentElement;
110
+ const scrollElement = getScrollElement(node);
111
+ const eventTarget = isDocumentScroll ? window : node;
103
112
  let cachedScrollHeight = 0;
104
113
  let cachedClientHeight = 0;
105
114
  updateCache();
106
115
  const cacheObserver = resizeObserver(scrollElement, {
107
116
  callback: updateCache
108
117
  });
109
- node.addEventListener("scroll", throttledObserve, { passive: true });
118
+ eventTarget.addEventListener("scroll", throttledObserve, { passive: true });
119
+ function throttledObserve() {
120
+ rafId = requestAnimationFrame(observe);
121
+ }
110
122
  function updateCache() {
111
123
  cachedScrollHeight = scrollElement.scrollHeight;
112
124
  cachedClientHeight = scrollElement.clientHeight;
113
125
  }
114
- function throttledObserve() {
115
- const now = Date.now();
116
- if (now - lastThrottleTime < throttle) return;
117
- lastThrottleTime = now;
118
- rafId = requestAnimationFrame(observe);
119
- }
120
126
  function observe() {
121
127
  const scrollTop = scrollElement.scrollTop;
122
128
  if (Math.abs(scrollTop - prevScrollTop) < 1) return;
@@ -155,10 +161,11 @@ function scrollObserver(node, options = {}) {
155
161
  }
156
162
  prevScrollTop = scrollTop;
157
163
  prevScrollPercent = scrollPercent;
164
+ prevScrollDirection = scrollDirection;
158
165
  }
159
166
  return {
160
167
  destroy() {
161
- node.removeEventListener("scroll", throttledObserve);
168
+ eventTarget.removeEventListener("scroll", throttledObserve);
162
169
  if (rafId) cancelAnimationFrame(rafId);
163
170
  cacheObserver.destroy();
164
171
  }
@@ -81,12 +81,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
81
81
  function resizeObserver(target, options) {
82
82
  const { callback, ...opts } = options;
83
83
  const observer = new ResizeObserver(observerFn);
84
- if (target === window) target = document.body;
84
+ if (target === window || target === document)
85
+ target = document.documentElement;
85
86
  useObserverMethodOnTarget(target, observer, "observe", opts);
86
87
  function observerFn(entries) {
87
88
  for (const entry of entries) {
88
89
  if (callback) callback({ entry, entries, observer });
89
- else dispatchEvent(target, "resize-obs", { entry, entries, observer });
90
+ else
91
+ dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
90
92
  }
91
93
  }
92
94
  return {
@@ -134,58 +136,6 @@ function nativeMasonrySupport(node) {
134
136
  return getComputedStyle(node).gridTemplateRows === "masonry";
135
137
  }
136
138
 
137
- // src/dom/bounding-box.js
138
- function boundingBox(element, { containsPadding = true } = {}) {
139
- const box = element.getBoundingClientRect();
140
- const ret = {};
141
- for (const prop in box) ret[prop] = box[prop];
142
- if (!containsPadding) {
143
- const style = getComputedStyle(element);
144
- const paddingTop = parseFloat(style.paddingTop);
145
- const paddingRight = parseFloat(style.paddingRight);
146
- const paddingBottom = parseFloat(style.paddingBottom);
147
- const paddingLeft = parseFloat(style.paddingLeft);
148
- ret.x = ret.x + paddingLeft;
149
- ret.y = ret.y + paddingTop;
150
- ret.width = ret.width - paddingLeft - paddingRight;
151
- ret.height = ret.height - paddingTop - paddingBottom;
152
- ret.top = ret.top + paddingTop;
153
- ret.bottom = ret.bottom - paddingBottom;
154
- ret.right = ret.right - paddingRight;
155
- ret.left = ret.left + paddingLeft;
156
- }
157
- ret.xCenter = (ret.left + ret.right) / 2;
158
- ret.yCenter = (ret.top + ret.bottom) / 2;
159
- return ret;
160
- }
161
- function boundingBoxRelativeToAncestor({ element, ancestorElement }) {
162
- ancestorElement = ancestorElement || element.parentElement;
163
- const box = boundingBox(element);
164
- const ancestorBox = boundingBox(ancestorElement, {
165
- containsPadding: false
166
- });
167
- return {
168
- top: box.top - ancestorBox.top,
169
- right: box.right - ancestorBox.right,
170
- bottom: box.bottom - ancestorBox.bottom,
171
- left: box.left - ancestorBox.left,
172
- width: box.width,
173
- height: box.height,
174
- xCenter: (box.left + box.right) / 2 - ancestorBox.left,
175
- yCenter: (box.top + box.bottom) / 2 - ancestorBox.top
176
- };
177
- }
178
-
179
- // src/dom/css-vars.js
180
- function getCSSVar(element, prop) {
181
- element = getElement(element);
182
- return getComputedStyle(element).getPropertyValue(prop);
183
- }
184
- function setCSSVar(element, prop, value) {
185
- element = getElement(element);
186
- element.style.setProperty(prop, value);
187
- }
188
-
189
139
  // src/lib/objects/omit-empty.js
190
140
  function omitEmpty(obj, shallow = false) {
191
141
  if (typeof obj !== "object" || obj === null) return obj;
@@ -200,25 +150,14 @@ function omitEmpty(obj, shallow = false) {
200
150
  return result;
201
151
  }
202
152
 
203
- // src/dom/ui/scroll-container.js
204
- function findScrollContainer(element, direction = "both") {
205
- if (!element) return;
206
- let parent = element.parentElement;
207
- while (parent) {
208
- const { overflow, overflowX, overflowY } = getComputedStyle(parent);
209
- if (direction === "both") {
210
- if (overflow.includes("auto") || overflow.includes("scroll"))
211
- return parent;
212
- }
213
- if (direction === "horizontal") {
214
- if (overflowX === "auto" || overflowX === "scroll") return parent;
215
- }
216
- if (direction === "vertical") {
217
- if (overflowY === "auto" || overflowY === "scroll") return parent;
218
- }
219
- parent = parent.parentElement;
220
- }
221
- return document.documentElement;
153
+ // src/dom/css-vars.js
154
+ function getCSSVar(element, prop) {
155
+ element = getElement(element);
156
+ return getComputedStyle(element).getPropertyValue(prop);
157
+ }
158
+ function setCSSVar(element, prop, value) {
159
+ element = getElement(element);
160
+ element.style.setProperty(prop, value);
222
161
  }
223
162
 
224
163
  // src/dom/actions/prefer-horizontal-scroll.js
@@ -307,6 +246,69 @@ function isTrackpad(event) {
307
246
  if (event.wheelDeltaY === event.deltaY * -3) return true;
308
247
  }
309
248
 
249
+ // src/dom/bounding-box.js
250
+ function boundingBox(element, { containsPadding = true } = {}) {
251
+ const box = element.getBoundingClientRect();
252
+ const ret = {};
253
+ for (const prop in box) ret[prop] = box[prop];
254
+ if (!containsPadding) {
255
+ const style = getComputedStyle(element);
256
+ const paddingTop = parseFloat(style.paddingTop);
257
+ const paddingRight = parseFloat(style.paddingRight);
258
+ const paddingBottom = parseFloat(style.paddingBottom);
259
+ const paddingLeft = parseFloat(style.paddingLeft);
260
+ ret.x = ret.x + paddingLeft;
261
+ ret.y = ret.y + paddingTop;
262
+ ret.width = ret.width - paddingLeft - paddingRight;
263
+ ret.height = ret.height - paddingTop - paddingBottom;
264
+ ret.top = ret.top + paddingTop;
265
+ ret.bottom = ret.bottom - paddingBottom;
266
+ ret.right = ret.right - paddingRight;
267
+ ret.left = ret.left + paddingLeft;
268
+ }
269
+ ret.xCenter = (ret.left + ret.right) / 2;
270
+ ret.yCenter = (ret.top + ret.bottom) / 2;
271
+ return ret;
272
+ }
273
+ function boundingBoxRelativeToAncestor({ element, ancestorElement }) {
274
+ ancestorElement = ancestorElement || element.parentElement;
275
+ const box = boundingBox(element);
276
+ const ancestorBox = boundingBox(ancestorElement, {
277
+ containsPadding: false
278
+ });
279
+ return {
280
+ top: box.top - ancestorBox.top,
281
+ right: box.right - ancestorBox.right,
282
+ bottom: box.bottom - ancestorBox.bottom,
283
+ left: box.left - ancestorBox.left,
284
+ width: box.width,
285
+ height: box.height,
286
+ xCenter: (box.left + box.right) / 2 - ancestorBox.left,
287
+ yCenter: (box.top + box.bottom) / 2 - ancestorBox.top
288
+ };
289
+ }
290
+
291
+ // src/dom/ui/scroll-container.js
292
+ function findScrollContainer(element, direction = "both") {
293
+ if (!element) return;
294
+ let parent = element.parentElement;
295
+ while (parent) {
296
+ const { overflow, overflowX, overflowY } = getComputedStyle(parent);
297
+ if (direction === "both") {
298
+ if (overflow.includes("auto") || overflow.includes("scroll"))
299
+ return parent;
300
+ }
301
+ if (direction === "horizontal") {
302
+ if (overflowX === "auto" || overflowX === "scroll") return parent;
303
+ }
304
+ if (direction === "vertical") {
305
+ if (overflowY === "auto" || overflowY === "scroll") return parent;
306
+ }
307
+ parent = parent.parentElement;
308
+ }
309
+ return document.documentElement;
310
+ }
311
+
310
312
  // src/dom/actions/sticky.js
311
313
  function sticky(node, props = {}) {
312
314
  const scrollContainer = findScrollContainer(node);
@@ -66,12 +66,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
66
66
  function resizeObserver(target, options) {
67
67
  const { callback, ...opts } = options;
68
68
  const observer = new ResizeObserver(observerFn);
69
- if (target === window) target = document.body;
69
+ if (target === window || target === document)
70
+ target = document.documentElement;
70
71
  useObserverMethodOnTarget(target, observer, "observe", opts);
71
72
  function observerFn(entries) {
72
73
  for (const entry of entries) {
73
74
  if (callback) callback({ entry, entries, observer });
74
- else dispatchEvent(target, "resize-obs", { entry, entries, observer });
75
+ else
76
+ dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
75
77
  }
76
78
  }
77
79
  return {
@@ -1,3 +1,17 @@
1
+ // src/lib/objects/omit-empty.js
2
+ function omitEmpty(obj, shallow = false) {
3
+ if (typeof obj !== "object" || obj === null) return obj;
4
+ if (obj instanceof Date) return obj;
5
+ const result = Array.isArray(obj) ? [] : {};
6
+ for (const [key, value] of Object.entries(obj)) {
7
+ if (value === null || value === void 0 || value === "" || typeof value === "object" && !(value instanceof Date) && Object.keys(value).length === 0) {
8
+ continue;
9
+ }
10
+ result[key] = shallow ? value : omitEmpty(value);
11
+ }
12
+ return result;
13
+ }
14
+
1
15
  // src/dom/get-element.js
2
16
  function getElement(selector) {
3
17
  if (!selector) return;
@@ -5,18 +19,6 @@ function getElement(selector) {
5
19
  return document.querySelector(selector);
6
20
  }
7
21
 
8
- // src/dom/events.js
9
- function addListeners(listeners) {
10
- listeners.forEach(({ node, event, handler }) => {
11
- node.addEventListener(event, handler);
12
- });
13
- }
14
- function removeListeners(listeners) {
15
- listeners.forEach(({ node, event, handler }) => {
16
- node.removeEventListener(event, handler);
17
- });
18
- }
19
-
20
22
  // src/dom/css-vars.js
21
23
  function getCSSVar(element, prop) {
22
24
  element = getElement(element);
@@ -27,18 +29,16 @@ function setCSSVar(element, prop, value) {
27
29
  element.style.setProperty(prop, value);
28
30
  }
29
31
 
30
- // src/lib/objects/omit-empty.js
31
- function omitEmpty(obj, shallow = false) {
32
- if (typeof obj !== "object" || obj === null) return obj;
33
- if (obj instanceof Date) return obj;
34
- const result = Array.isArray(obj) ? [] : {};
35
- for (const [key, value] of Object.entries(obj)) {
36
- if (value === null || value === void 0 || value === "" || typeof value === "object" && !(value instanceof Date) && Object.keys(value).length === 0) {
37
- continue;
38
- }
39
- result[key] = shallow ? value : omitEmpty(value);
40
- }
41
- return result;
32
+ // src/dom/events.js
33
+ function addListeners(listeners) {
34
+ listeners.forEach(({ node, event, handler }) => {
35
+ node.addEventListener(event, handler);
36
+ });
37
+ }
38
+ function removeListeners(listeners) {
39
+ listeners.forEach(({ node, event, handler }) => {
40
+ node.removeEventListener(event, handler);
41
+ });
42
42
  }
43
43
 
44
44
  // src/dom/actions/prefer-horizontal-scroll.js
@@ -74,12 +74,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
74
74
  function resizeObserver(target, options) {
75
75
  const { callback, ...opts } = options;
76
76
  const observer = new ResizeObserver(observerFn);
77
- if (target === window) target = document.body;
77
+ if (target === window || target === document)
78
+ target = document.documentElement;
78
79
  useObserverMethodOnTarget(target, observer, "observe", opts);
79
80
  function observerFn(entries) {
80
81
  for (const entry of entries) {
81
82
  if (callback) callback({ entry, entries, observer });
82
- else dispatchEvent(target, "resize-obs", { entry, entries, observer });
83
+ else
84
+ dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
83
85
  }
84
86
  }
85
87
  return {
@@ -12,13 +12,17 @@ function getUnit(value) {
12
12
  const [, unit] = splitUnit(value);
13
13
  return unit;
14
14
  }
15
+ function rem(multiple = 1) {
16
+ const value = parseFloat(getComputedStyle(document.body)["font-size"]);
17
+ return Math.round(value * multiple);
18
+ }
15
19
  function em(element = document.body, multiple = 1) {
16
20
  const value = parseFloat(getComputedStyle(element)["font-size"]);
17
21
  return Math.round(value * multiple);
18
22
  }
19
- function rem(multiple = 1) {
20
- const value = parseFloat(getComputedStyle(document.body)["font-size"]);
21
- return Math.round(value * multiple);
23
+ function rlh(multiple = 1) {
24
+ const lineHeight = parseFloat(getComputedStyle(document.body)["line-height"]);
25
+ return Math.round(lineHeight * multiple);
22
26
  }
23
27
  function lh(element = document.body, multiple = 1) {
24
28
  const lineHeight = getComputedStyle(element)["line-height"];
@@ -40,5 +44,6 @@ export {
40
44
  getUnit,
41
45
  lh,
42
46
  rem,
47
+ rlh,
43
48
  toPx
44
49
  };
@@ -152,12 +152,14 @@ function useObserverMethodOnTarget(target, observer, method = "observe", options
152
152
  function resizeObserver(target, options) {
153
153
  const { callback, ...opts } = options;
154
154
  const observer = new ResizeObserver(observerFn);
155
- if (target === window) target = document.body;
155
+ if (target === window || target === document)
156
+ target = document.documentElement;
156
157
  useObserverMethodOnTarget(target, observer, "observe", opts);
157
158
  function observerFn(entries) {
158
159
  for (const entry of entries) {
159
160
  if (callback) callback({ entry, entries, observer });
160
- else dispatchEvent(target, "resize-obs", { entry, entries, observer });
161
+ else
162
+ dispatchEvent(entry.target, "resize-obs", { entry, entries, observer });
161
163
  }
162
164
  }
163
165
  return {
@@ -205,36 +207,6 @@ function nativeMasonrySupport(node) {
205
207
  return getComputedStyle(node).gridTemplateRows === "masonry";
206
208
  }
207
209
 
208
- // src/lib/numbers/index.js
209
- function splitUnit(string) {
210
- if (typeof string === "number") return [string, null];
211
- const regex = /(-?\d*\.?\d+)\s*([a-z]+)/i;
212
- const match = string.match(regex);
213
- if (match) return [parseFloat(match[1]), match[2]];
214
- return [parseFloat(string), null];
215
- }
216
-
217
- // src/lib/form/sanitize.js
218
- function sanitize(value, options = {}) {
219
- const { sanitizer, ...rest } = options;
220
- if (!sanitizer) throw new Error("sanitizer function is required");
221
- if (typeof value === "string") return sanitizer(value, rest);
222
- if (Array.isArray(value)) return sanitizeArray(value, { sanitizer, ...rest });
223
- if (value && typeof value === "object") {
224
- return Object.fromEntries(
225
- Object.entries(value).map(([key, val]) => [
226
- key,
227
- sanitize(val, { sanitizer, ...rest })
228
- ])
229
- );
230
- }
231
- return value;
232
- }
233
- function sanitizeArray(arr, { sanitizer, ...rest }) {
234
- if (!sanitizer) throw new Error("sanitizer function is required");
235
- return arr.map((item) => sanitize(item, { sanitizer, ...rest }));
236
- }
237
-
238
210
  // src/lib/objects/omit-empty.js
239
211
  function omitEmpty(obj, shallow = false) {
240
212
  if (typeof obj !== "object" || obj === null) return obj;
@@ -249,6 +221,22 @@ function omitEmpty(obj, shallow = false) {
249
221
  return result;
250
222
  }
251
223
 
224
+ // src/dom/css-vars.js
225
+ function getCSSValue(element, prop) {
226
+ return getCSSVar(element, prop);
227
+ }
228
+ function setCSSValue(element, prop, value) {
229
+ setCSSVar(element, prop, value);
230
+ }
231
+ function getCSSVar(element, prop) {
232
+ element = getElement(element);
233
+ return getComputedStyle(element).getPropertyValue(prop);
234
+ }
235
+ function setCSSVar(element, prop, value) {
236
+ element = getElement(element);
237
+ element.style.setProperty(prop, value);
238
+ }
239
+
252
240
  // src/dom/actions/prefer-horizontal-scroll.js
253
241
  var DEFAULT_OPTIONS = {
254
242
  scrollSnapDelay: 1e3
@@ -504,22 +492,6 @@ function setCookie(name, value, days) {
504
492
  document.cookie = name + "=" + (value || "") + expires + "; path=/";
505
493
  }
506
494
 
507
- // src/dom/css-vars.js
508
- function getCSSValue(element, prop) {
509
- return getCSSVar(element, prop);
510
- }
511
- function setCSSValue(element, prop, value) {
512
- setCSSVar(element, prop, value);
513
- }
514
- function getCSSVar(element, prop) {
515
- element = getElement(element);
516
- return getComputedStyle(element).getPropertyValue(prop);
517
- }
518
- function setCSSVar(element, prop, value) {
519
- element = getElement(element);
520
- element.style.setProperty(prop, value);
521
- }
522
-
523
495
  // src/dom/focusable.js
524
496
  function getFocusableElements(container = document.body) {
525
497
  return {
@@ -635,18 +607,31 @@ function Focusable(container = document.body) {
635
607
  return getFocusableElements(container);
636
608
  }
637
609
 
610
+ // src/lib/numbers/index.js
611
+ function splitUnit(string) {
612
+ if (typeof string === "number") return [string, null];
613
+ const regex = /(-?\d*\.?\d+)\s*([a-z]+)/i;
614
+ const match = string.match(regex);
615
+ if (match) return [parseFloat(match[1]), match[2]];
616
+ return [parseFloat(string), null];
617
+ }
618
+
638
619
  // src/dom/font-size.js
639
620
  function getUnit(value) {
640
621
  const [, unit] = splitUnit(value);
641
622
  return unit;
642
623
  }
624
+ function rem(multiple = 1) {
625
+ const value = parseFloat(getComputedStyle(document.body)["font-size"]);
626
+ return Math.round(value * multiple);
627
+ }
643
628
  function em(element = document.body, multiple = 1) {
644
629
  const value = parseFloat(getComputedStyle(element)["font-size"]);
645
630
  return Math.round(value * multiple);
646
631
  }
647
- function rem(multiple = 1) {
648
- const value = parseFloat(getComputedStyle(document.body)["font-size"]);
649
- return Math.round(value * multiple);
632
+ function rlh(multiple = 1) {
633
+ const lineHeight = parseFloat(getComputedStyle(document.body)["line-height"]);
634
+ return Math.round(lineHeight * multiple);
650
635
  }
651
636
  function lh(element = document.body, multiple = 1) {
652
637
  const lineHeight = getComputedStyle(element)["line-height"];
@@ -819,40 +804,44 @@ var defaultOptions2 = {
819
804
  // Float between 0 to 1.
820
805
  tolerance: 0.1,
821
806
  // Float between 0 to 1. Tolerance for event firing
822
- throttle: 16,
823
- // Throttle interval in ms (default: ~60fps)
824
807
  once: false
825
808
  // Only fire threshold callback once
826
809
  };
810
+ function getScrollElement(node) {
811
+ if (node === document || node === window || node === document.documentElement) {
812
+ return document.documentElement;
813
+ }
814
+ return (
815
+ /** @type {Element} */
816
+ node
817
+ );
818
+ }
827
819
  function scrollObserver(node, options = {}) {
828
820
  const { callback, onScrollDown, onScrollUp, onEnterThreshold, ...userOpts } = options;
829
821
  const opts = { ...defaultOptions2, ...userOpts };
830
- const { threshold, tolerance, throttle, once } = opts;
831
- const prevScrollDirection = null;
822
+ const { threshold, tolerance, once } = opts;
823
+ let prevScrollDirection = null;
832
824
  let prevScrollTop = 0;
833
825
  let prevScrollPercent = 0;
834
- let lastThrottleTime = 0;
835
826
  let thresholdFired = false;
836
827
  let rafId = null;
837
- const isDocumentScroll = node === document || node === window;
838
- const scrollElement = isDocumentScroll ? document.documentElement : node;
828
+ const isDocumentScroll = node === document || node === window || node === document.documentElement;
829
+ const scrollElement = getScrollElement(node);
830
+ const eventTarget = isDocumentScroll ? window : node;
839
831
  let cachedScrollHeight = 0;
840
832
  let cachedClientHeight = 0;
841
833
  updateCache();
842
834
  const cacheObserver = resizeObserver(scrollElement, {
843
835
  callback: updateCache
844
836
  });
845
- node.addEventListener("scroll", throttledObserve, { passive: true });
837
+ eventTarget.addEventListener("scroll", throttledObserve, { passive: true });
838
+ function throttledObserve() {
839
+ rafId = requestAnimationFrame(observe);
840
+ }
846
841
  function updateCache() {
847
842
  cachedScrollHeight = scrollElement.scrollHeight;
848
843
  cachedClientHeight = scrollElement.clientHeight;
849
844
  }
850
- function throttledObserve() {
851
- const now = Date.now();
852
- if (now - lastThrottleTime < throttle) return;
853
- lastThrottleTime = now;
854
- rafId = requestAnimationFrame(observe);
855
- }
856
845
  function observe() {
857
846
  const scrollTop = scrollElement.scrollTop;
858
847
  if (Math.abs(scrollTop - prevScrollTop) < 1) return;
@@ -891,10 +880,11 @@ function scrollObserver(node, options = {}) {
891
880
  }
892
881
  prevScrollTop = scrollTop;
893
882
  prevScrollPercent = scrollPercent;
883
+ prevScrollDirection = scrollDirection;
894
884
  }
895
885
  return {
896
886
  destroy() {
897
- node.removeEventListener("scroll", throttledObserve);
887
+ eventTarget.removeEventListener("scroll", throttledObserve);
898
888
  if (rafId) cancelAnimationFrame(rafId);
899
889
  cacheObserver.destroy();
900
890
  }
@@ -943,6 +933,29 @@ var queryParams = {
943
933
 
944
934
  // src/dom/sanitize.js
945
935
  import DOMPurify from "dompurify";
936
+
937
+ // src/lib/form/sanitize.js
938
+ function sanitize(value, options = {}) {
939
+ const { sanitizer, ...rest } = options;
940
+ if (!sanitizer) throw new Error("sanitizer function is required");
941
+ if (typeof value === "string") return sanitizer(value, rest);
942
+ if (Array.isArray(value)) return sanitizeArray(value, { sanitizer, ...rest });
943
+ if (value && typeof value === "object") {
944
+ return Object.fromEntries(
945
+ Object.entries(value).map(([key, val]) => [
946
+ key,
947
+ sanitize(val, { sanitizer, ...rest })
948
+ ])
949
+ );
950
+ }
951
+ return value;
952
+ }
953
+ function sanitizeArray(arr, { sanitizer, ...rest }) {
954
+ if (!sanitizer) throw new Error("sanitizer function is required");
955
+ return arr.map((item) => sanitize(item, { sanitizer, ...rest }));
956
+ }
957
+
958
+ // src/dom/sanitize.js
946
959
  function sanitize2(value, options = {}) {
947
960
  return sanitize(value, { sanitizer: DOMPurify.sanitize, ...options });
948
961
  }
@@ -1137,6 +1150,7 @@ export {
1137
1150
  rem,
1138
1151
  removeListeners,
1139
1152
  resizeObserver,
1153
+ rlh,
1140
1154
  sanitize2 as sanitize,
1141
1155
  scrollObserver,
1142
1156
  scrollToElement,