New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@cocreate/scroll

Package Overview
Dependencies
Maintainers
1
Versions
208
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cocreate/scroll - npm Package Compare versions

Comparing version
1.13.4
to
1.14.0
+5
-6
.github/workflows/automated.yml

@@ -25,9 +25,9 @@ name: Automated Workflow

- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 14
node-version: 22 # Required for the latest semantic-release plugins
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v3
uses: cycjimmy/semantic-release-action@v4 # Update to v4 for better Node 20+ support
id: semantic

@@ -40,3 +40,3 @@ with:

env:
GITHUB_TOKEN: "${{ secrets.GITHUB }}"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" # Use the built-in token if possible
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"

@@ -46,2 +46,1 @@ outputs:

new_release_version: "${{ steps.semantic.outputs.new_release_version }}"

@@ -0,1 +1,13 @@

# [1.14.0](https://github.com/CoCreate-app/CoCreate-scroll/compare/v1.13.4...v1.14.0) (2026-01-15)
### Bug Fixes
* update worklow ([ac9e74a](https://github.com/CoCreate-app/CoCreate-scroll/commit/ac9e74a15fd8c7d27dc5809130ea0ee99c46777f))
### Features
* comprehensive initalization of ([cecbfce](https://github.com/CoCreate-app/CoCreate-scroll/commit/cecbfcec1d9d7f34b9d4a339d5e5ed3e8f38c363))
## [1.13.4](https://github.com/CoCreate-app/CoCreate-scroll/compare/v1.13.3...v1.13.4) (2025-09-07)

@@ -2,0 +14,0 @@

{
"name": "@cocreate/scroll",
"version": "1.13.4",
"version": "1.14.0",
"description": "A simple scroll component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.",

@@ -5,0 +5,0 @@ "keywords": [

+313
-287
import Observer from "@cocreate/observer";
import Actions from "@cocreate/actions";
const selector =
"[scroll], [scroll-to], [scrollable-x], [scrollable-y], [scroll-up], [scroll-down], [scroll-top], [scroll-bottom], [scroll-limbo], [scroll-intersect], [scrolling]";
const CoCreateScroll = {
delta: 3,
observer: null,
timer: null,
firedEvents: new Map(),
delta: 3,
observer: null,
// timer: null, // Removed: Timer needs to be instance-specific, not global
firedEvents: new Map(),
init: function () {
let elements = document.querySelectorAll(
`[scroll], [scroll-to], [scrollable-x], [scrollable-y]`
);
this.__initIntersectionObserver();
this.initElements(elements);
},
init: function () {
let elements = document.querySelectorAll(selector);
this.__initIntersectionObserver();
this.initElements(elements);
},
initElements: function (elements) {
for (let el of elements) this.initElement(el);
},
initElements: function (elements) {
for (let el of elements) this.initElement(el);
},
initElement: function (element) {
const self = this;
const upSize = this.__getSize(element.getAttribute("scroll-up"));
const downSize = this.__getSize(element.getAttribute("scroll-down"));
const attrName = element.getAttribute("scroll-attribute") || "class";
const targetSelector = element.getAttribute("scroll-query");
const scrollSelector = element.getAttribute("scroll-element");
const intersectValue = element.getAttribute("scroll-intersect");
const scrollTo = element.getAttribute("scroll-to");
initElement: function (element) {
const self = this;
const upSize = this.__getSize(element.getAttribute("scroll-up"));
const downSize = this.__getSize(element.getAttribute("scroll-down"));
const attrName = element.getAttribute("scroll-attribute") || "class";
const targetSelector = element.getAttribute("scroll-query");
const scrollSelector = element.getAttribute("scroll-element");
const intersectValue = element.getAttribute("scroll-intersect");
const scrollTo = element.getAttribute("scroll-to");
updateScrollableAttributes(element);
updateScrollableAttributes(element);
let values =
element.getAttribute("scroll") ||
element.getAttribute("scroll-value");
if (values || values === "")
values = values.split(",").map((x) => x.trim());
let values =
element.getAttribute("scroll") ||
element.getAttribute("scroll-value");
if (values || values === "")
values = values.split(",").map((x) => x.trim());
let scrollInfo = {
attrName: attrName,
values: values,
upSize: upSize,
downSize: downSize,
scrollTop: element.getAttribute("scroll-top"),
scrollLimbo: element.getAttribute("scroll-limbo"),
scrollBottom: element.getAttribute("scroll-bottom"),
scrolling: element.getAttribute("scrolling"),
scrollTo
};
let scrollInfo = {
attrName: attrName,
values: values,
upSize: upSize,
downSize: downSize,
scrollTop: element.getAttribute("scroll-top"),
scrollLimbo: element.getAttribute("scroll-limbo"),
scrollBottom: element.getAttribute("scroll-bottom"),
scrolling: element.getAttribute("scrolling"),
scrollTo,
timer: null // Added: Store timer here to persist across events
};
let elements = [element];
if (targetSelector) {
elements = document.querySelectorAll(targetSelector);
}
let elements = [element];
if (targetSelector) {
elements = document.querySelectorAll(targetSelector);
}
elements.forEach((el) => {
el.scrollStatus = { currentPos: 0 };
});
elements.forEach((el) => {
el.scrollStatus = { currentPos: 0 };
});
// this.__runScrollEvent(element, scrollInfo);
// this.__runScrollEvent(element, scrollInfo);
let scrollableElements;
if (scrollSelector)
scrollableElements = document.querySelectorAll(scrollSelector);
else if (element.hasAttribute("scroll-element"))
scrollableElements = [element];
let scrollableElements;
if (scrollSelector)
scrollableElements = document.querySelectorAll(scrollSelector);
else if (element.hasAttribute("scroll-element"))
scrollableElements = [element];
if (scrollableElements) {
for (let scrollableEl of scrollableElements) {
scrollableEl.addEventListener("scroll", function (event) {
self._scrollEvent(
elements,
element,
scrollInfo,
scrollableEl
);
});
}
} else {
// this.WindowInit = true;
window.addEventListener("scroll", function (event) {
self._scrollEvent(elements, element, scrollInfo);
});
}
if (scrollableElements) {
for (let scrollableEl of scrollableElements) {
scrollableEl.addEventListener("scroll", function (event) {
self._scrollEvent(
elements,
element,
scrollInfo,
scrollableEl
);
});
}
} else {
// this.WindowInit = true;
window.addEventListener("scroll", function (event) {
self._scrollEvent(elements, element, scrollInfo);
});
}
if (intersectValue && window.IntersectionObserver && this.observer) {
this.observer.observe(element);
}
if (intersectValue && window.IntersectionObserver && this.observer) {
this.observer.observe(element);
}
if (scrollTo) {
this.setScrollPosition(element, scrollTo);
}
},
if (scrollTo) {
this.setScrollPosition(element, scrollTo);
}
},
_scrollEvent: function (elements, element, scrollInfo, scrollableEl) {
const self = this;
if (!element.scrollStatus) return;
let scrollEl = scrollableEl || window;
if (
Math.abs(
scrollEl.scrollTop ||
scrollEl.scrollY - element.scrollStatus.currentPos
) <= self.delta
) {
return;
}
_scrollEvent: function (elements, element, scrollInfo, scrollableEl) {
const self = this;
if (!element.scrollStatus) return;
let scrollEl = scrollableEl || window;
if (
Math.abs(
scrollEl.scrollTop ||
scrollEl.scrollY - element.scrollStatus.currentPos
) <= self.delta
) {
return;
}
let timer = null;
if (timer != null) {
clearTimeout(timer);
}
// Fix: Use the timer stored in scrollInfo so it persists
if (scrollInfo.timer != null) {
clearTimeout(scrollInfo.timer);
}
elements.forEach((el) => {
self.__setScrolling(el, scrollInfo, false);
self.__runScrollEvent(el, scrollInfo, scrollableEl);
});
elements.forEach((el) => {
self.__setScrolling(el, scrollInfo, false);
self.__runScrollEvent(el, scrollInfo, scrollableEl);
});
timer = setTimeout(function () {
elements.forEach((el) => {
self.__setScrolling(el, scrollInfo, true);
});
}, 500);
},
scrollInfo.timer = setTimeout(function () {
elements.forEach((el) => {
self.__setScrolling(el, scrollInfo, true);
});
}, 500);
},
setScrollPosition: function (element, scrollTo) {
if (!scrollTo) return;
if (scrollTo.includes("top")) {
element.scrollTop = 0;
} else if (scrollTo.includes("bottom")) {
element.scrollTop = element.scrollHeight;
}
setScrollPosition: function (element, scrollTo) {
if (!scrollTo) return;
if (scrollTo.includes("top")) {
element.scrollTop = 0;
} else if (scrollTo.includes("bottom")) {
element.scrollTop = element.scrollHeight;
}
if (scrollTo.includes("left")) {
element.scrollLeft = 0;
} else if (scrollTo.includes("right")) {
element.scrollLeft = element.scrollWidth;
}
},
if (scrollTo.includes("left")) {
element.scrollLeft = 0;
} else if (scrollTo.includes("right")) {
element.scrollLeft = element.scrollWidth;
}
},
__initIntersectionObserver: function () {
const self = this;
this.observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
let element = entry.target;
const attrName =
element.getAttribute("scroll-attribute") || "class";
const targetSelector = element.getAttribute("scroll-query");
const intersectValue = element.getAttribute("scroll-intersect");
__initIntersectionObserver: function () {
const self = this;
this.observer = new IntersectionObserver((entries) => {
// Deduplicate: Keep only the latest entry for each target element
const uniqueEntries = new Map();
entries.forEach((entry) => {
uniqueEntries.set(entry.target, entry);
});
let targetElements = [element];
if (targetSelector) {
targetElements = document.querySelectorAll(targetSelector);
}
if (entry.isIntersecting > 0) {
targetElements.forEach((el) =>
self.__addAttributeValue(el, attrName, intersectValue)
);
} else {
targetElements.forEach((el) =>
self.__removeAttrbuteValue(el, attrName, intersectValue)
);
}
});
});
},
// Process only the unique, latest entries
uniqueEntries.forEach((entry) => {
let element = entry.target;
const attrName =
element.getAttribute("scroll-attribute") || "class";
const targetSelector = element.getAttribute("scroll-query");
const intersectValue = element.getAttribute("scroll-intersect");
__setScrolling: function (element, info, stopped = false) {
const { scrolling, attrName } = info;
if (stopped) {
this.__removeAttrbuteValue(element, attrName, scrolling);
} else {
this.__addAttributeValue(element, attrName, scrolling);
}
},
let targetElements = [element];
if (targetSelector) {
targetElements = document.querySelectorAll(targetSelector);
}
if (entry.isIntersecting) {
targetElements.forEach((el) =>
self.__addAttributeValue(el, attrName, intersectValue)
);
} else {
targetElements.forEach((el) =>
self.__removeAttributeValue(el, attrName, intersectValue)
);
}
});
});
},
__runScrollEvent: function (element, info, scrollableEl) {
if (!element.scrollStatus) return;
const currentPos = element.scrollStatus.currentPos;
let scrollY, scrollHeight, innerHeight;
if (scrollableEl) {
scrollY = scrollableEl.scrollTop;
scrollHeight = scrollableEl.scrollHeight;
innerHeight = scrollableEl.clientHeight;
} else {
scrollY = window.scrollY;
scrollHeight = document.body.scrollHeight;
innerHeight = window.innerHeight;
}
const {
upSize,
downSize,
attrName,
values,
scrollTop,
scrollBottom,
scrollLimbo
} = info;
__setScrolling: function (element, info, stopped = false) {
const { scrolling, attrName } = info;
if (stopped) {
this.__removeAttributeValue(element, attrName, scrolling);
} else {
this.__addAttributeValue(element, attrName, scrolling);
}
},
let newTime = new Date().getTime();
if ((values && !info.datetime) || newTime - info.datetime > 200) {
info["datetime"] = newTime;
__runScrollEvent: function (element, info, scrollableEl) {
if (!element.scrollStatus) return;
const currentPos = element.scrollStatus.currentPos;
let scrollY, scrollHeight, innerHeight;
if (scrollableEl) {
scrollY = scrollableEl.scrollTop;
scrollHeight = scrollableEl.scrollHeight;
innerHeight = scrollableEl.clientHeight;
} else {
scrollY = window.scrollY;
scrollHeight = document.body.scrollHeight;
innerHeight = window.innerHeight;
}
const {
upSize,
downSize,
attrName,
values,
scrollTop,
scrollBottom,
scrollLimbo
} = info;
if (upSize <= currentPos - scrollY) {
this.__addAttributeValue(element, attrName, values[0]);
this.__removeAttrbuteValue(element, attrName, values[1]);
} else if (downSize <= scrollY - currentPos) {
this.__removeAttrbuteValue(element, attrName, values[0]);
this.__addAttributeValue(element, attrName, values[1]);
}
}
let newTime = new Date().getTime();
if ((values && !info.datetime) || newTime - info.datetime > 200) {
info["datetime"] = newTime;
//. scroll top case
if (scrollY <= this.delta) {
if (values) {
this.__removeAttrbuteValue(element, attrName, values[0]);
this.__removeAttrbuteValue(element, attrName, values[1]);
}
this.__addAttributeValue(element, attrName, scrollTop);
} else {
this.__removeAttrbuteValue(element, attrName, scrollTop);
}
if (upSize <= currentPos - scrollY) {
this.__addAttributeValue(element, attrName, values[0]);
this.__removeAttributeValue(element, attrName, values[1]);
} else if (downSize <= scrollY - currentPos) {
this.__removeAttributeValue(element, attrName, values[0]);
this.__addAttributeValue(element, attrName, values[1]);
}
}
//. scroll bottom case
// if ((window.innerHeight + scrollY) >= document.body.scrollHeight) {
if (innerHeight + scrollY >= scrollHeight) {
// this.__removeAttrbuteValue(element, attrName, values[0]);
// this.__removeAttrbuteValue(element, attrName, values[1]);
//. scroll top case
if (scrollY <= this.delta) {
if (values) {
this.__removeAttributeValue(element, attrName, values[0]);
this.__removeAttributeValue(element, attrName, values[1]);
}
this.__addAttributeValue(element, attrName, scrollTop);
} else {
this.__removeAttributeValue(element, attrName, scrollTop);
}
this.__addAttributeValue(element, attrName, scrollBottom);
} else {
this.__removeAttrbuteValue(element, attrName, scrollBottom);
}
//. scroll bottom case
// if ((window.innerHeight + scrollY) >= document.body.scrollHeight) {
if (innerHeight + scrollY >= scrollHeight) {
// this.__removeAttributeValue(element, attrName, values[0]);
// this.__removeAttributeValue(element, attrName, values[1]);
// if (scrollY != 0 && (scrollY + window.innerHeight) != document.body.scrollHeight){
if (scrollY != 0 && scrollY + innerHeight != scrollHeight) {
this.__addAttributeValue(element, attrName, scrollLimbo);
} else {
this.__removeAttrbuteValue(element, attrName, scrollLimbo);
}
this.__addAttributeValue(element, attrName, scrollBottom);
} else {
this.__removeAttributeValue(element, attrName, scrollBottom);
}
element.scrollStatus.currentPos = scrollY;
},
// if (scrollY != 0 && (scrollY + window.innerHeight) != document.body.scrollHeight){
if (scrollY != 0 && scrollY + innerHeight != scrollHeight) {
this.__addAttributeValue(element, attrName, scrollLimbo);
} else {
this.__removeAttributeValue(element, attrName, scrollLimbo);
}
__addAttributeValue: function (element, attrName, value) {
if (!value) return;
let check = new RegExp("(\\s|^)" + value + "(\\s|$)");
let attrValue = element.getAttribute(attrName) || "";
element.scrollStatus.currentPos = scrollY;
},
if (!check.test(attrValue)) {
if (attrName === "class") attrValue += " " + value;
else attrValue = value;
element.setAttribute(attrName, attrValue);
}
},
__addAttributeValue: function (element, attrName, value) {
if (!value) return;
// Optimization: Use classList if the attribute is 'class'
if (attrName === "class") {
element.classList.add(value);
return;
}
__removeAttrbuteValue: function (element, attrName, value) {
if (!value) return;
let check = new RegExp("(\\s|^)" + value + "(\\s|$)");
let attrValue = element.getAttribute(attrName) || "";
let check = new RegExp("(\\s|^)" + value + "(\\s|$)");
let attrValue = element.getAttribute(attrName) || "";
if (check.test(attrValue)) {
attrValue = attrValue.replace(check, " ").trim();
element.setAttribute(attrName, attrValue);
}
},
if (!check.test(attrValue)) {
// Logic note: For non-class attributes, we append with a space
// This mimics class-like behavior for custom attributes
if (attrValue.length > 0) attrValue += " " + value;
else attrValue = value;
element.setAttribute(attrName, attrValue);
}
},
__getSize: function (attrValue, isWidth) {
let size = 0;
if (!attrValue) {
return 0;
}
__removeAttributeValue: function (element, attrName, value) {
if (!value) return;
// Optimization: Use classList if the attribute is 'class'
if (attrName === "class") {
element.classList.remove(value);
return;
}
if (attrValue.includes("%")) {
size = attrValue.replace("%", "").trim();
size = Number(size) || 0;
let check = new RegExp("(\\s|^)" + value + "(\\s|$)");
let attrValue = element.getAttribute(attrName) || "";
size = isWidth
? window.innerWidth / size
: window.innerHeight / size;
} else {
size = attrValue.replace("px", "").trim();
size = Number(size) || 0;
}
if (check.test(attrValue)) {
attrValue = attrValue.replace(check, " ").trim();
element.setAttribute(attrName, attrValue);
}
},
return size;
}
__getSize: function (attrValue, isWidth) {
let size = 0;
if (!attrValue) {
return 0;
}
if (attrValue.includes("%")) {
size = attrValue.replace("%", "").trim();
size = Number(size) || 0;
// Fix: Logic was inverted (dividing screen by size).
// Changed to standard percentage calculation: size% of screen.
size = isWidth
? window.innerWidth * (size / 100)
: window.innerHeight * (size / 100);
} else {
size = attrValue.replace("px", "").trim();
size = Number(size) || 0;
}
return size;
}
};
function updateScrollableAttributes(element) {
if (element.hasAttribute("scrollable-y")) {
if (element.scrollWidth > element.clientWidth)
element.setAttribute("scrollable-y", "true");
else element.setAttribute("scrollable-y", "false");
}
if (element.hasAttribute("scrollable-y")) {
if (element.scrollWidth > element.clientWidth)
element.setAttribute("scrollable-y", "true");
else element.setAttribute("scrollable-y", "false");
}
if (element.hasAttribute("scrollable-x")) {
if (element.scrollHeight > element.clientHeight)
element.setAttribute("scrollable-x", "true");
else element.setAttribute("scrollable-x", "false");
}
if (element.hasAttribute("scrollable-x")) {
if (element.scrollHeight > element.clientHeight)
element.setAttribute("scrollable-x", "true");
else element.setAttribute("scrollable-x", "false");
}
}
Observer.init({
name: "CoCreateScrollCreate",
types: ["addedNodes"],
selector: "[scroll], [scroll-to], [scrollable-x], [scrollable-y]",
callback: function (mutation) {
CoCreateScroll.initElement(mutation.target);
}
name: "CoCreateScrollCreate",
types: ["addedNodes"],
selector: selector,
callback: function (mutation) {
CoCreateScroll.initElement(mutation.target);
}
});
Observer.init({
name: "CoCreateScrollAttributes",
types: ["attributes"],
attributeFilter: ["scroll-to"],
// target: selector, // blocks mutations when applied
callback: function (mutation) {
CoCreateScroll.setScrollPosition(
mutation.target,
mutation.target.getAttribute("scroll-to")
);
}
name: "CoCreateScrollAttributes",
types: ["attributes"],
attributeFilter: ["scroll-to"],
// target: selector, // blocks mutations when applied
callback: function (mutation) {
CoCreateScroll.setScrollPosition(
mutation.target,
mutation.target.getAttribute("scroll-to")
);
}
});
Observer.init({
name: "CoCreateScrollAttributes",
types: ["attributes"],
attributeFilter: ["scrollable-x", "scrollable-y"],
// target: selector, // blocks mutations when applied
callback: function (mutation) {
if (
mutation.oldValue !==
mutation.target.getAttribute(mutation.attributeName)
)
updateScrollableAttributes(mutation.target);
}
name: "CoCreateScrollAttributes",
types: ["attributes"],
attributeFilter: ["scrollable-x", "scrollable-y"],
// target: selector, // blocks mutations when applied
callback: function (mutation) {
if (
mutation.oldValue !==
mutation.target.getAttribute(mutation.attributeName)
)
updateScrollableAttributes(mutation.target);
}
});
Actions.init({
name: "scroll-to",
callback: (action) => {
// CoCreateScroll.setScrollPosition(mutation.target)
}
name: "scroll-to",
callback: (action) => {
// CoCreateScroll.setScrollPosition(mutation.target)
}
});

@@ -348,2 +374,2 @@

export default CoCreateScroll;
export default CoCreateScroll;