reblend-ui
Version:
Utilities for creating robust overlay components
100 lines (98 loc) • 3.83 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.default = exports.Position = void 0;
var _reblendHooks = require("reblend-hooks");
var _useIntersectionObserver = require("reblend-hooks/useIntersectionObserver");
var _reblendjs = require("reblendjs");
var _scrollParent = require("dom-helpers/scrollParent");
/** Accepts all options an IntersectionObserver accepts */
let Position = exports.Position = /*#__PURE__*/function (Position) {
Position[Position["UNKNOWN"] = 0] = "UNKNOWN";
Position[Position["BEFORE"] = 1] = "BEFORE";
Position[Position["INSIDE"] = 2] = "INSIDE";
Position[Position["AFTER"] = 3] = "AFTER";
return Position;
}({});
function toCss(margin) {
if (!margin || typeof margin === 'string') return margin;
const {
top = 0,
right = 0,
bottom = 0,
left = 0
} = margin;
return `${top}px ${right}px ${bottom}px ${left}px`;
}
const findRoot = el => (0, _scrollParent.default)(el, true);
function useWaypoint(element, callback, options = {}) {
const {
rootMargin,
threshold,
scrollDirection = 'vertical'
} = options;
this.state.rootMargin = rootMargin;
this.state.threshold = threshold;
this.state.scrollDirection = scrollDirection;
let {
root
} = options;
this.state.root = root;
const handler = _reblendHooks.useEventCallback.bind(this)(callback);
this.state.handler = handler;
const prevPositionRef = _reblendjs.useRef.bind(this)(null);
this.state.prevPositionRef = prevPositionRef;
if (this.state.root === 'scrollParent') {
this.state.root = findRoot;
}
const scrollParent = _reblendjs.useMemo.bind(this)(() => element && typeof this.state.root === 'function' ? this.state.root(element) : null, "scrollParent", (() => [element, this.state.root]).bind(this));
this.state.scrollParent = scrollParent;
let realRoot = typeof this.state.root === 'function' ? this.state.scrollParent : this.state.root;
this.state.realRoot = realRoot;
if (this.state.realRoot && this.state.realRoot.nodeType === document.DOCUMENT_NODE) {
// explicit undefined means "use the viewport", instead of `null`
// which means "no root yet". This works around a bug in safari
// where document is not accepted in older versions,
// or is accepted but doesn't work (as of v14)
this.state.realRoot = undefined;
}
_useIntersectionObserver.default.bind(this)(
// We change the meaning of explicit null to "not provided yet"
// this is to allow easier synchronizing between element and roots derived
// from it. Otherwise if the root updates later an observer will be created
// for the document and then for the root
element, ([entry], observer) => {
if (!entry) return;
const [start, end, point] = this.state.scrollDirection === 'vertical' ? ['top', 'bottom', 'y'] : ['left', 'right', 'x'];
const {
[point]: coord
} = entry.boundingClientRect;
const rootStart = entry.rootBounds?.[start] || 0;
const rootEnd = entry.rootBounds?.[end] || 0;
// The position may remain UNKNOWN if the root
// is 0 width/height or everything is hidden.
let position = Position.UNKNOWN;
if (entry.isIntersecting) {
position = Position.INSIDE;
} else if (coord > rootEnd) {
position = Position.AFTER;
} else if (coord < rootStart) {
position = Position.BEFORE;
}
const previousPosition = this.state.prevPositionRef.current;
if (previousPosition === position) {
return;
}
this.state.handler({
position,
previousPosition
}, entry, observer);
this.state.prevPositionRef.current = position;
}, {
threshold: this.state.threshold,
root: this.state.realRoot,
rootMargin: toCss(this.state.rootMargin)
});
return;
}
/* @Reblend: Transformed from function to class */
var _default = exports.default = useWaypoint;