masonic
Version:
<hr> <div align="center"> <h1 align="center"> 🧱 masonic </h1>
1,632 lines (1,375 loc) • 60.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) :
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Masonic = {}, global.React));
})(this, (function (exports, React) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespace(React);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var RED = 0;
var BLACK = 1;
var NIL = 2;
var DELETE = 0;
var KEEP = 1;
function addInterval(treeNode, high, index) {
var node = treeNode.list;
var prevNode;
while (node) {
if (node.index === index) return false;
if (high > node.high) break;
prevNode = node;
node = node.next;
}
if (!prevNode) treeNode.list = {
index: index,
high: high,
next: node
};
if (prevNode) prevNode.next = {
index: index,
high: high,
next: prevNode.next
};
return true;
}
function removeInterval(treeNode, index) {
var node = treeNode.list;
if (node.index === index) {
if (node.next === null) return DELETE;
treeNode.list = node.next;
return KEEP;
}
var prevNode = node;
node = node.next;
while (node !== null) {
if (node.index === index) {
prevNode.next = node.next;
return KEEP;
}
prevNode = node;
node = node.next;
}
}
var NULL_NODE = {
low: 0,
max: 0,
high: 0,
C: NIL,
// @ts-expect-error
P: undefined,
// @ts-expect-error
R: undefined,
// @ts-expect-error
L: undefined,
// @ts-expect-error
list: undefined
};
NULL_NODE.P = NULL_NODE;
NULL_NODE.L = NULL_NODE;
NULL_NODE.R = NULL_NODE;
function updateMax(node) {
var max = node.high;
if (node.L === NULL_NODE && node.R === NULL_NODE) node.max = max;else if (node.L === NULL_NODE) node.max = Math.max(node.R.max, max);else if (node.R === NULL_NODE) node.max = Math.max(node.L.max, max);else node.max = Math.max(Math.max(node.L.max, node.R.max), max);
}
function updateMaxUp(node) {
var x = node;
while (x.P !== NULL_NODE) {
updateMax(x.P);
x = x.P;
}
}
function rotateLeft(tree, x) {
if (x.R === NULL_NODE) return;
var y = x.R;
x.R = y.L;
if (y.L !== NULL_NODE) y.L.P = x;
y.P = x.P;
if (x.P === NULL_NODE) tree.root = y;else if (x === x.P.L) x.P.L = y;else x.P.R = y;
y.L = x;
x.P = y;
updateMax(x);
updateMax(y);
}
function rotateRight(tree, x) {
if (x.L === NULL_NODE) return;
var y = x.L;
x.L = y.R;
if (y.R !== NULL_NODE) y.R.P = x;
y.P = x.P;
if (x.P === NULL_NODE) tree.root = y;else if (x === x.P.R) x.P.R = y;else x.P.L = y;
y.R = x;
x.P = y;
updateMax(x);
updateMax(y);
}
function replaceNode(tree, x, y) {
if (x.P === NULL_NODE) tree.root = y;else if (x === x.P.L) x.P.L = y;else x.P.R = y;
y.P = x.P;
}
function fixRemove(tree, x) {
var w;
while (x !== NULL_NODE && x.C === BLACK) {
if (x === x.P.L) {
w = x.P.R;
if (w.C === RED) {
w.C = BLACK;
x.P.C = RED;
rotateLeft(tree, x.P);
w = x.P.R;
}
if (w.L.C === BLACK && w.R.C === BLACK) {
w.C = RED;
x = x.P;
} else {
if (w.R.C === BLACK) {
w.L.C = BLACK;
w.C = RED;
rotateRight(tree, w);
w = x.P.R;
}
w.C = x.P.C;
x.P.C = BLACK;
w.R.C = BLACK;
rotateLeft(tree, x.P);
x = tree.root;
}
} else {
w = x.P.L;
if (w.C === RED) {
w.C = BLACK;
x.P.C = RED;
rotateRight(tree, x.P);
w = x.P.L;
}
if (w.R.C === BLACK && w.L.C === BLACK) {
w.C = RED;
x = x.P;
} else {
if (w.L.C === BLACK) {
w.R.C = BLACK;
w.C = RED;
rotateLeft(tree, w);
w = x.P.L;
}
w.C = x.P.C;
x.P.C = BLACK;
w.L.C = BLACK;
rotateRight(tree, x.P);
x = tree.root;
}
}
}
x.C = BLACK;
}
function minimumTree(x) {
while (x.L !== NULL_NODE) x = x.L;
return x;
}
function fixInsert(tree, z) {
var y;
while (z.P.C === RED) {
if (z.P === z.P.P.L) {
y = z.P.P.R;
if (y.C === RED) {
z.P.C = BLACK;
y.C = BLACK;
z.P.P.C = RED;
z = z.P.P;
} else {
if (z === z.P.R) {
z = z.P;
rotateLeft(tree, z);
}
z.P.C = BLACK;
z.P.P.C = RED;
rotateRight(tree, z.P.P);
}
} else {
y = z.P.P.L;
if (y.C === RED) {
z.P.C = BLACK;
y.C = BLACK;
z.P.P.C = RED;
z = z.P.P;
} else {
if (z === z.P.L) {
z = z.P;
rotateRight(tree, z);
}
z.P.C = BLACK;
z.P.P.C = RED;
rotateLeft(tree, z.P.P);
}
}
}
tree.root.C = BLACK;
}
function createIntervalTree() {
var tree = {
root: NULL_NODE,
size: 0
}; // we know these indexes are a consistent, safe way to make look ups
// for our case so it's a solid O(1) alternative to
// the O(log n) searchNode() in typical interval trees
var indexMap = {};
return {
insert: function insert(low, high, index) {
var x = tree.root;
var y = NULL_NODE;
while (x !== NULL_NODE) {
y = x;
if (low === y.low) break;
if (low < x.low) x = x.L;else x = x.R;
}
if (low === y.low && y !== NULL_NODE) {
if (!addInterval(y, high, index)) return;
y.high = Math.max(y.high, high);
updateMax(y);
updateMaxUp(y);
indexMap[index] = y;
tree.size++;
return;
}
var z = {
low: low,
high: high,
max: high,
C: RED,
P: y,
L: NULL_NODE,
R: NULL_NODE,
list: {
index: index,
high: high,
next: null
}
};
if (y === NULL_NODE) {
tree.root = z;
} else {
if (z.low < y.low) y.L = z;else y.R = z;
updateMaxUp(z);
}
fixInsert(tree, z);
indexMap[index] = z;
tree.size++;
},
remove: function remove(index) {
var z = indexMap[index];
if (z === void 0) return;
delete indexMap[index];
var intervalResult = removeInterval(z, index);
if (intervalResult === void 0) return;
if (intervalResult === KEEP) {
z.high = z.list.high;
updateMax(z);
updateMaxUp(z);
tree.size--;
return;
}
var y = z;
var originalYColor = y.C;
var x;
if (z.L === NULL_NODE) {
x = z.R;
replaceNode(tree, z, z.R);
} else if (z.R === NULL_NODE) {
x = z.L;
replaceNode(tree, z, z.L);
} else {
y = minimumTree(z.R);
originalYColor = y.C;
x = y.R;
if (y.P === z) {
x.P = y;
} else {
replaceNode(tree, y, y.R);
y.R = z.R;
y.R.P = y;
}
replaceNode(tree, z, y);
y.L = z.L;
y.L.P = y;
y.C = z.C;
}
updateMax(x);
updateMaxUp(x);
if (originalYColor === BLACK) fixRemove(tree, x);
tree.size--;
},
search: function search(low, high, callback) {
var stack = [tree.root];
while (stack.length !== 0) {
var node = stack.pop();
if (node === NULL_NODE || low > node.max) continue;
if (node.L !== NULL_NODE) stack.push(node.L);
if (node.R !== NULL_NODE) stack.push(node.R);
if (node.low <= high && node.high >= low) {
var curr = node.list;
while (curr !== null) {
if (curr.high >= low) callback(curr.index, node.low);
curr = curr.next;
}
}
}
},
get size() {
return tree.size;
}
};
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
var useLatest = function useLatest(current) {
var storedValue = React__namespace.useRef(current);
React__namespace.useEffect(function () {
storedValue.current = current;
});
return storedValue;
};
var useLatest$1 = useLatest;
var useDebounceCallback = function useDebounceCallback(callback, wait, leading) {
if (wait === void 0) {
wait = 100;
}
if (leading === void 0) {
leading = false;
}
var storedCallback = useLatest$1(callback);
var timeout = React__namespace.useRef();
var deps = [wait, leading, storedCallback]; // Cleans up pending timeouts when the deps change
function _ref() {
timeout.current && clearTimeout(timeout.current);
timeout.current = void 0;
}
React__namespace.useEffect(function () {
return _ref;
}, deps);
function _ref2() {
timeout.current = void 0;
}
return React__namespace.useCallback(function () {
// eslint-disable-next-line prefer-rest-params
var args = arguments;
var current = timeout.current; // Calls on leading edge
if (current === void 0 && leading) {
timeout.current = setTimeout(_ref2, wait); // eslint-disable-next-line prefer-spread
return storedCallback.current.apply(null, args);
} // Clear the timeout every call and start waiting again
current && clearTimeout(current); // Waits for `wait` before invoking the callback
timeout.current = setTimeout(function () {
timeout.current = void 0;
storedCallback.current.apply(null, args);
}, wait);
}, deps);
};
var useDebounce = function useDebounce(initialState, wait, leading) {
var state = React__namespace.useState(initialState);
return [state[0], useDebounceCallback(state[1], wait, leading)];
};
function useEvent(target, type, listener, cleanup) {
var storedListener = React__namespace.useRef(listener);
var storedCleanup = React__namespace.useRef(cleanup);
React__namespace.useEffect(function () {
storedListener.current = listener;
storedCleanup.current = cleanup;
});
React__namespace.useEffect(function () {
var targetEl = target && 'current' in target ? target.current : target;
if (!targetEl) return;
var didUnsubscribe = 0;
function listener() {
if (didUnsubscribe) return;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
storedListener.current.apply(this, args);
}
targetEl.addEventListener(type, listener);
var cleanup = storedCleanup.current;
return function () {
didUnsubscribe = 1;
targetEl.removeEventListener(type, listener);
cleanup && cleanup();
}; // eslint-disable-next-line react-hooks/exhaustive-deps
}, [target, type]);
}
var emptyObj$2 = {};
var win$2 = typeof window === 'undefined' ? null : window;
var wv = win$2 && typeof win$2.visualViewport !== 'undefined' ? win$2.visualViewport : null;
var getSize = function getSize() {
return [document.documentElement.clientWidth, document.documentElement.clientHeight];
};
var useWindowSize = function useWindowSize(options) {
if (options === void 0) {
options = emptyObj$2;
}
var _options = options,
wait = _options.wait,
leading = _options.leading,
_options$initialWidth = _options.initialWidth,
initialWidth = _options$initialWidth === void 0 ? 0 : _options$initialWidth,
_options$initialHeigh = _options.initialHeight,
initialHeight = _options$initialHeigh === void 0 ? 0 : _options$initialHeigh;
var _useDebounce = useDebounce(
/* istanbul ignore next */
typeof document === 'undefined' ? [initialWidth, initialHeight] : getSize, wait, leading),
size = _useDebounce[0],
setDebouncedSize = _useDebounce[1];
var setSize = function setSize() {
return setDebouncedSize(getSize);
};
useEvent(win$2, 'resize', setSize); // @ts-expect-error
useEvent(wv, 'resize', setSize);
useEvent(win$2, 'orientationchange', setSize);
return size;
};
var memoOne = function memoOne(fn, areEqual) {
var equal = areEqual || defaultAreEqual;
var args, value;
return function () {
return !!args && equal(arguments, args) ? value : value = fn.apply(null, args = arguments);
};
};
var memoizeOne = memoOne;
var defaultAreEqual = function defaultAreEqual(current, prev) {
return current[0] === prev[0] && current[1] === prev[1] && current[2] === prev[2] && current[3] === prev[3];
};
var OneKeyMap = function OneKeyMap() {
this.set = void 0;
this.get = void 0;
var key, val;
this.get = function (k) {
return k === key ? val : void 0;
};
this.set = function (k, v) {
key = k;
val = v;
};
};
var OneKeyMap$1 = OneKeyMap;
var createCache = function createCache(obj) {
function _set(k, v) {
cache[k] = v;
}
function _get(k) {
return cache[k];
}
try {
// @ts-ignore
return new obj();
} catch (e) {
var cache = {};
return {
set: _set,
get: _get
};
}
};
var memo = function memo(constructors) {
var depth = constructors.length,
baseCache = createCache(constructors[0]);
var base;
var map;
var i;
var node;
var one = depth === 1; // quicker access for one and two-argument functions
var g1 = function g1(args) {
return (base = baseCache.get(args[0])) === void 0 || one ? base : base.get(args[1]);
};
var s1 = function s1(args, value) {
if (one) baseCache.set(args[0], value);else {
if ((base = baseCache.get(args[0])) === void 0) {
map = createCache(constructors[1]);
map.set(args[1], value);
baseCache.set(args[0], map);
} else {
base.set(args[1], value);
}
}
return value;
};
var g2 = function g2(args) {
node = baseCache;
for (i = 0; i < depth; i++) if ((node = node.get(args[i])) === void 0) return;
return node;
};
var s2 = function s2(args, value) {
node = baseCache;
for (i = 0; i < depth - 1; i++) {
if ((map = node.get(args[i])) === void 0) {
map = createCache(constructors[i + 1]);
node.set(args[i], map);
node = map;
} else {
node = map;
}
}
node.set(args[depth - 1], value);
return value;
};
return depth < 3 ? {
g: g1,
s: s1
} : {
g: g2,
s: s2
};
};
var memoize = function memoize(mapConstructors, fn) {
var item;
var _memo = memo(mapConstructors),
g = _memo.g,
s = _memo.s;
return function () {
return (item = g(arguments)) === void 0 ? s(arguments, fn.apply(null, arguments)) : item;
};
};
var trieMemoize = memoize;
var elementsCache = /*#__PURE__*/new WeakMap();
function useForceUpdate() {
var setState = React__namespace.useState(emptyObj$1)[1];
return React__namespace.useRef(function () {
return setState({});
}).current;
}
var emptyObj$1 = {};
var __reactCreateElement__$2 = React__namespace.createElement;
/**
* This hook handles the render phases of the masonry layout and returns the grid as a React element.
*
* @param options - Options for configuring the masonry layout renderer. See `UseMasonryOptions`.
* @param options.positioner
* @param options.resizeObserver
* @param options.items
* @param options.as
* @param options.id
* @param options.className
* @param options.style
* @param options.role
* @param options.tabIndex
* @param options.containerRef
* @param options.itemAs
* @param options.itemStyle
* @param options.itemHeightEstimate
* @param options.itemKey
* @param options.overscanBy
* @param options.scrollTop
* @param options.isScrolling
* @param options.height
* @param options.render
* @param options.onRender
*/
function useMasonry(_ref) {
var positioner = _ref.positioner,
resizeObserver = _ref.resizeObserver,
items = _ref.items,
_ref$as = _ref.as,
ContainerComponent = _ref$as === void 0 ? "div" : _ref$as,
id = _ref.id,
className = _ref.className,
style = _ref.style,
_ref$role = _ref.role,
role = _ref$role === void 0 ? "grid" : _ref$role,
_ref$tabIndex = _ref.tabIndex,
tabIndex = _ref$tabIndex === void 0 ? 0 : _ref$tabIndex,
containerRef = _ref.containerRef,
_ref$itemAs = _ref.itemAs,
ItemComponent = _ref$itemAs === void 0 ? "div" : _ref$itemAs,
itemStyle = _ref.itemStyle,
_ref$itemHeightEstima = _ref.itemHeightEstimate,
itemHeightEstimate = _ref$itemHeightEstima === void 0 ? 300 : _ref$itemHeightEstima,
_ref$itemKey = _ref.itemKey,
itemKey = _ref$itemKey === void 0 ? defaultGetItemKey : _ref$itemKey,
_ref$overscanBy = _ref.overscanBy,
overscanBy = _ref$overscanBy === void 0 ? 2 : _ref$overscanBy,
scrollTop = _ref.scrollTop,
isScrolling = _ref.isScrolling,
height = _ref.height,
RenderComponent = _ref.render,
onRender = _ref.onRender;
var startIndex = 0;
var stopIndex;
var forceUpdate = useForceUpdate();
var setItemRef = getRefSetter(positioner, resizeObserver);
var itemCount = items.length;
var columnWidth = positioner.columnWidth,
columnCount = positioner.columnCount,
range = positioner.range,
estimateHeight = positioner.estimateHeight,
size = positioner.size,
shortestColumn = positioner.shortestColumn;
var measuredCount = size();
var shortestColumnSize = shortestColumn();
var children = [];
var itemRole = role === "list" ? "listitem" : role === "grid" ? "gridcell" : undefined;
var storedOnRender = useLatest$1(onRender);
overscanBy = height * overscanBy;
var rangeEnd = scrollTop + overscanBy;
var needsFreshBatch = shortestColumnSize < rangeEnd && measuredCount < itemCount;
range( // We overscan in both directions because users scroll both ways,
// though one must admit scrolling down is more common and thus
// we only overscan by half the downward overscan amount
Math.max(0, scrollTop - overscanBy / 2), rangeEnd, function (index, left, top) {
var data = items[index];
var key = itemKey(data, index);
var phaseTwoStyle = {
top: top,
left: left,
width: columnWidth,
writingMode: "horizontal-tb",
position: "absolute"
};
/* istanbul ignore next */
if (typeof process !== "undefined" && "production" !== "production") {
throwWithoutData(data, index);
}
children.push( /*#__PURE__*/__reactCreateElement__$2(ItemComponent, {
key: key,
ref: setItemRef(index),
role: itemRole,
style: typeof itemStyle === "object" && itemStyle !== null ? _extends({}, phaseTwoStyle, itemStyle) : phaseTwoStyle
}, createRenderElement(RenderComponent, index, data, columnWidth)));
if (stopIndex === void 0) {
startIndex = index;
stopIndex = index;
} else {
startIndex = Math.min(startIndex, index);
stopIndex = Math.max(stopIndex, index);
}
});
if (needsFreshBatch) {
var batchSize = Math.min(itemCount - measuredCount, Math.ceil((scrollTop + overscanBy - shortestColumnSize) / itemHeightEstimate * columnCount));
var _index = measuredCount;
var phaseOneStyle = getCachedSize(columnWidth);
for (; _index < measuredCount + batchSize; _index++) {
var _data = items[_index];
var key = itemKey(_data, _index);
/* istanbul ignore next */
if (typeof process !== "undefined" && "production" !== "production") {
throwWithoutData(_data, _index);
}
children.push( /*#__PURE__*/__reactCreateElement__$2(ItemComponent, {
key: key,
ref: setItemRef(_index),
role: itemRole,
style: typeof itemStyle === "object" ? _extends({}, phaseOneStyle, itemStyle) : phaseOneStyle
}, createRenderElement(RenderComponent, _index, _data, columnWidth)));
}
} // Calls the onRender callback if the rendered indices changed
React__namespace.useEffect(function () {
if (typeof storedOnRender.current === "function" && stopIndex !== void 0) storedOnRender.current(startIndex, stopIndex, items);
didEverMount = "1";
}, [startIndex, stopIndex, items, storedOnRender]); // If we needed a fresh batch we should reload our components with the measured
// sizes
React__namespace.useEffect(function () {
if (needsFreshBatch) forceUpdate(); // eslint-disable-next-line
}, [needsFreshBatch, positioner]); // gets the container style object based upon the estimated height and whether or not
// the page is being scrolled
var containerStyle = getContainerStyle(isScrolling, estimateHeight(itemCount, itemHeightEstimate));
return /*#__PURE__*/__reactCreateElement__$2(ContainerComponent, {
ref: containerRef,
key: didEverMount,
id: id,
role: role,
className: className,
tabIndex: tabIndex,
style: typeof style === "object" ? assignUserStyle(containerStyle, style) : containerStyle,
children: children
});
}
/* istanbul ignore next */
function throwWithoutData(data, index) {
if (!data) {
throw new Error("No data was found at index: " + index + "\n\n" + "This usually happens when you've mutated or changed the \"items\" array in a " + "way that makes it shorter than the previous \"items\" array. Masonic knows nothing " + "about your underlying data and when it caches cell positions, it assumes you aren't " + "mutating the underlying \"items\".\n\n" + "See https://codesandbox.io/s/masonic-w-react-router-example-2b5f9?file=/src/index.js for " + "an example that gets around this limitations. For advanced implementations, see " + "https://codesandbox.io/s/masonic-w-react-router-and-advanced-config-example-8em42?file=/src/index.js\n\n" + "If this was the result of your removing an item from your \"items\", see this issue: " + "https://github.com/jaredLunde/masonic/issues/12");
}
} // This is for triggering a remount after SSR has loaded in the client w/ hydrate()
var didEverMount = "0";
//
// Render-phase utilities
// ~5.5x faster than createElement without the memo
var createRenderElement = /*#__PURE__*/trieMemoize([OneKeyMap$1, {}, WeakMap, OneKeyMap$1], function (RenderComponent, index, data, columnWidth) {
return /*#__PURE__*/__reactCreateElement__$2(RenderComponent, {
index: index,
data: data,
width: columnWidth
});
});
var getContainerStyle = /*#__PURE__*/memoizeOne(function (isScrolling, estimateHeight) {
return {
position: "relative",
width: "100%",
maxWidth: "100%",
height: Math.ceil(estimateHeight),
maxHeight: Math.ceil(estimateHeight),
willChange: isScrolling ? "contents" : void 0,
pointerEvents: isScrolling ? "none" : void 0
};
});
var cmp2 = function cmp2(args, pargs) {
return args[0] === pargs[0] && args[1] === pargs[1];
};
var assignUserStyle = /*#__PURE__*/memoizeOne(function (containerStyle, userStyle) {
return _extends({}, containerStyle, userStyle);
}, // @ts-expect-error
cmp2);
function defaultGetItemKey(_, i) {
return i;
} // the below memoizations for for ensuring shallow equal is reliable for pure
// component children
var getCachedSize = /*#__PURE__*/memoizeOne(function (width) {
return {
width: width,
zIndex: -1000,
visibility: "hidden",
position: "absolute",
writingMode: "horizontal-tb"
};
}, function (args, pargs) {
return args[0] === pargs[0];
});
var getRefSetter = /*#__PURE__*/memoizeOne(function (positioner, resizeObserver) {
return function (index) {
return function (el) {
if (el === null) return;
if (resizeObserver) {
resizeObserver.observe(el);
elementsCache.set(el, index);
}
if (positioner.get(index) === void 0) positioner.set(index, el.offsetHeight);
};
};
}, // @ts-expect-error
cmp2);
var u = 'undefined',
win$1 = typeof window !== u ? window : {},
p = typeof performance !== u ? performance : Date,
now$1 = function now() {
return p.now();
},
af = 'AnimationFrame',
Caf = 'cancel' + af,
Raf = 'request' + af,
raf = win$1[Raf] && /*#__PURE__*/win$1[Raf].bind(win$1),
caf = win$1[Caf] && /*#__PURE__*/win$1[Caf].bind(win$1);
function _raf(callback) {
var curr = now$1(),
next = Math.max(lastTime + 1000 / 60, curr);
return setTimeout(function () {
callback(lastTime = next);
}, next - curr);
}
function _caf(h) {
return clearTimeout(h);
}
if (!raf || !caf) {
var lastTime = 0;
raf = _raf;
caf = _caf;
}
/**
* Copyright 2011, Joe Lambert.
* Free to use under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
**/
var clearRequestTimeout = function clearRequestTimeout(handle) {
caf(handle.v || -1);
};
var requestTimeout = function requestTimeout(fn, ms) {
var start = now$1(),
handle = {};
var loop = function loop() {
now$1() - start >= ms ? fn.call(null) : handle.v = raf(loop);
};
handle.v = raf(loop);
return handle;
};
var perf = typeof performance !== 'undefined' ? performance : Date;
var now = function now() {
return perf.now();
};
function useThrottleCallback(callback, fps, leading) {
if (fps === void 0) {
fps = 30;
}
if (leading === void 0) {
leading = false;
}
var storedCallback = useLatest$1(callback);
var ms = 1000 / fps;
var prev = React__namespace.useRef(0);
var trailingTimeout = React__namespace.useRef();
var clearTrailing = function clearTrailing() {
return trailingTimeout.current && clearTimeout(trailingTimeout.current);
};
var deps = [fps, leading, storedCallback]; // Reset any time the deps change
function _ref() {
prev.current = 0;
clearTrailing();
}
React__namespace.useEffect(function () {
return _ref;
}, deps);
return React__namespace.useCallback(function () {
// eslint-disable-next-line prefer-rest-params
var args = arguments;
var rightNow = now();
var call = function call() {
prev.current = rightNow;
clearTrailing();
storedCallback.current.apply(null, args);
};
var current = prev.current; // leading
if (leading && current === 0) return call(); // body
if (rightNow - current > ms) {
if (current > 0) return call();
prev.current = rightNow;
} // trailing
clearTrailing();
trailingTimeout.current = setTimeout(function () {
call();
prev.current = 0;
}, ms);
}, deps);
}
function useThrottle(initialState, fps, leading) {
var state = React__namespace.useState(initialState);
return [state[0], useThrottleCallback(state[1], fps, leading)];
}
var win = typeof window === 'undefined' ? null : window;
var getScrollY = function getScrollY() {
return win.scrollY !== void 0 ? win.scrollY : win.pageYOffset === void 0 ? 0 : win.pageYOffset;
};
var useWindowScroll = function useWindowScroll(fps) {
if (fps === void 0) {
fps = 30;
}
var state = useThrottle(typeof window === 'undefined' ? 0 : getScrollY, fps, true);
useEvent(win, 'scroll', function () {
return state[1](getScrollY());
});
return state[0];
};
var useScrollPosition = useWindowScroll;
/**
* A hook for tracking whether the `window` is currently being scrolled and it's scroll position on
* the y-axis. These values are used for determining which grid cells to render and when
* to add styles to the masonry container that maximize scroll performance.
*
* @param offset - The vertical space in pixels between the top of the grid container and the top
* of the browser `document.documentElement`.
* @param fps - This determines how often (in frames per second) to update the scroll position of the
* browser `window` in state, and as a result the rate the masonry grid recalculates its visible cells.
* The default value of `12` has been very reasonable in my own testing, but if you have particularly
* heavy `render` components it may be prudent to reduce this number.
*/
function useScroller(offset, fps) {
if (offset === void 0) {
offset = 0;
}
if (fps === void 0) {
fps = 12;
}
var scrollTop = useScrollPosition(fps);
var _React$useState = React__namespace.useState(false),
isScrolling = _React$useState[0],
setIsScrolling = _React$useState[1];
var didMount = React__namespace.useRef(0);
React__namespace.useEffect(function () {
if (didMount.current === 1) setIsScrolling(true);
var didUnsubscribe = false;
var to = requestTimeout(function () {
if (didUnsubscribe) return; // This is here to prevent premature bail outs while maintaining high resolution
// unsets. Without it there will always bee a lot of unnecessary DOM writes to style.
setIsScrolling(false);
}, 40 + 1000 / fps);
didMount.current = 1;
return function () {
didUnsubscribe = true;
clearRequestTimeout(to);
};
}, [fps, scrollTop]);
return {
scrollTop: Math.max(0, scrollTop - offset),
isScrolling: isScrolling
};
}
/**
* A heavily-optimized component that updates `useMasonry()` when the scroll position of the browser `window`
* changes. This bare-metal component is used by `<Masonry>` under the hood.
*
* @param props
*/
function MasonryScroller(props) {
// We put this in its own layer because it's the thing that will trigger the most updates
// and we don't want to slower ourselves by cycling through all the functions, objects, and effects
// of other hooks
var _useScroller = useScroller(props.offset, props.scrollFps),
scrollTop = _useScroller.scrollTop,
isScrolling = _useScroller.isScrolling; // This is an update-heavy phase and while we could just Object.assign here,
// it is way faster to inline and there's a relatively low hit to he bundle
// size.
return useMasonry({
scrollTop: scrollTop,
isScrolling: isScrolling,
positioner: props.positioner,
resizeObserver: props.resizeObserver,
items: props.items,
onRender: props.onRender,
as: props.as,
id: props.id,
className: props.className,
style: props.style,
role: props.role,
tabIndex: props.tabIndex,
containerRef: props.containerRef,
itemAs: props.itemAs,
itemStyle: props.itemStyle,
itemHeightEstimate: props.itemHeightEstimate,
itemKey: props.itemKey,
overscanBy: props.overscanBy,
height: props.height,
render: props.render
});
}
if (typeof process !== "undefined" && "production" !== "production") {
MasonryScroller.displayName = "MasonryScroller";
}
var usePassiveLayoutEffect = React__default["default"][typeof document !== 'undefined' && document.createElement !== void 0 ? 'useLayoutEffect' : 'useEffect'];
var useLayoutEffect = usePassiveLayoutEffect;
/**
* A hook for measuring the width of the grid container, as well as its distance
* from the top of the document. These values are necessary to correctly calculate the number/width
* of columns to render, as well as the number of rows to render.
*
* @param elementRef - A `ref` object created by `React.useRef()`. That ref should be provided to the
* `containerRef` property in `useMasonry()`.
* @param deps - You can force this hook to recalculate the `offset` and `width` whenever this
* dependencies list changes. A common dependencies list might look like `[windowWidth, windowHeight]`,
* which would force the hook to recalculate any time the size of the browser `window` changed.
*/
function useContainerPosition(elementRef, deps) {
if (deps === void 0) {
deps = emptyArr$1;
}
var _React$useState = React__namespace.useState({
offset: 0,
width: 0
}),
containerPosition = _React$useState[0],
setContainerPosition = _React$useState[1];
useLayoutEffect(function () {
var current = elementRef.current;
if (current !== null) {
var offset = 0;
var el = current;
do {
offset += el.offsetTop || 0;
el = el.offsetParent;
} while (el);
if (offset !== containerPosition.offset || current.offsetWidth !== containerPosition.width) {
setContainerPosition({
offset: offset,
width: current.offsetWidth
});
}
} // eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
return containerPosition;
}
var emptyArr$1 = [];
/**
* This hook creates the grid cell positioner and cache required by `useMasonry()`. This is
* the meat of the grid's layout algorithm, determining which cells to render at a given scroll
* position, as well as where to place new items in the grid.
*
* @param options - Properties that determine the number of columns in the grid, as well
* as their widths.
* @param options.columnWidth
* @param options.width
* @param deps - This hook will create a new positioner, clearing all existing cached positions,
* whenever the dependencies in this list change.
* @param options.columnGutter
* @param options.rowGutter
* @param options.columnCount
* @param options.maxColumnCount
* @param options.maxColumnWidth
*/
function usePositioner(_ref, deps) {
var width = _ref.width,
_ref$columnWidth = _ref.columnWidth,
columnWidth = _ref$columnWidth === void 0 ? 200 : _ref$columnWidth,
_ref$columnGutter = _ref.columnGutter,
columnGutter = _ref$columnGutter === void 0 ? 0 : _ref$columnGutter,
rowGutter = _ref.rowGutter,
columnCount = _ref.columnCount,
maxColumnCount = _ref.maxColumnCount,
maxColumnWidth = _ref.maxColumnWidth;
if (deps === void 0) {
deps = emptyArr;
}
var initPositioner = function initPositioner() {
var _getColumns = getColumns(width, columnWidth, columnGutter, columnCount, maxColumnCount, maxColumnWidth),
computedColumnWidth = _getColumns[0],
computedColumnCount = _getColumns[1];
return createPositioner(computedColumnCount, computedColumnWidth, columnGutter, rowGutter !== null && rowGutter !== void 0 ? rowGutter : columnGutter);
};
var positionerRef = React__namespace.useRef();
if (positionerRef.current === undefined) positionerRef.current = initPositioner();
var prevDeps = React__namespace.useRef(deps);
var opts = [width, columnWidth, columnGutter, rowGutter, columnCount, maxColumnCount, maxColumnWidth];
var prevOpts = React__namespace.useRef(opts);
var optsChanged = !opts.every(function (item, i) {
return prevOpts.current[i] === item;
});
if (typeof process !== "undefined" && "production" !== "production") {
if (deps.length !== prevDeps.current.length) {
throw new Error("usePositioner(): The length of your dependencies array changed.");
}
} // Create a new positioner when the dependencies or sizes change
// Thanks to https://github.com/khmm12 for pointing this out
// https://github.com/jaredLunde/masonic/pull/41
if (optsChanged || !deps.every(function (item, i) {
return prevDeps.current[i] === item;
})) {
var prevPositioner = positionerRef.current;
var positioner = initPositioner();
prevDeps.current = deps;
prevOpts.current = opts;
if (optsChanged) {
var cacheSize = prevPositioner.size();
for (var _index2 = 0; _index2 < cacheSize; _index2++) {
var pos = prevPositioner.get(_index2);
positioner.set(_index2, pos !== void 0 ? pos.height : 0);
}
}
positionerRef.current = positioner;
}
return positionerRef.current;
}
/**
* Creates a cell positioner for the `useMasonry()` hook. The `usePositioner()` hook uses
* this utility under the hood.
*
* @param columnCount - The number of columns in the grid
* @param columnWidth - The width of each column in the grid
* @param columnGutter - The amount of horizontal space between columns in pixels.
* @param rowGutter - The amount of vertical space between cells within a column in pixels (falls back
* to `columnGutter`).
*/
var createPositioner = function createPositioner(columnCount, columnWidth, columnGutter, rowGutter) {
if (columnGutter === void 0) {
columnGutter = 0;
}
if (rowGutter === void 0) {
rowGutter = columnGutter;
}
// O(log(n)) lookup of cells to render for a given viewport size
// Store tops and bottoms of each cell for fast intersection lookup.
var intervalTree = createIntervalTree(); // Track the height of each column.
// Layout algorithm below always inserts into the shortest column.
var columnHeights = new Array(columnCount); // Used for O(1) item access
var items = []; // Tracks the item indexes within an individual column
var columnItems = new Array(columnCount);
for (var i = 0; i < columnCount; i++) {
columnHeights[i] = 0;
columnItems[i] = [];
}
return {
columnCount: columnCount,
columnWidth: columnWidth,
set: function set(index, height) {
if (height === void 0) {
height = 0;
}
var column = 0; // finds the shortest column and uses it
for (var _i2 = 1; _i2 < columnHeights.length; _i2++) {
if (columnHeights[_i2] < columnHeights[column]) column = _i2;
}
var top = columnHeights[column] || 0;
columnHeights[column] = top + height + rowGutter;
columnItems[column].push(index);
items[index] = {
left: column * (columnWidth + columnGutter),
top: top,
height: height,
column: column
};
intervalTree.insert(top, top + height, index);
},
get: function get(index) {
return items[index];
},
// This only updates items in the specific columns that have changed, on and after the
// specific items that have changed
update: function update(updates) {
var columns = new Array(columnCount);
var i = 0,
j = 0; // determines which columns have items that changed, as well as the minimum index
// changed in that column, as all items after that index will have their positions
// affected by the change
for (; i < updates.length - 1; i++) {
var _index3 = updates[i];
var item = items[_index3];
item.height = updates[++i];
intervalTree.remove(_index3);
intervalTree.insert(item.top, item.top + item.height, _index3);
columns[item.column] = columns[item.column] === void 0 ? _index3 : Math.min(_index3, columns[item.column]);
}
for (i = 0; i < columns.length; i++) {
// bails out if the column didn't change
if (columns[i] === void 0) continue;
var itemsInColumn = columnItems[i]; // the index order is sorted with certainty so binary search is a great solution
// here as opposed to Array.indexOf()
var startIndex = binarySearch(itemsInColumn, columns[i]);
var _index4 = columnItems[i][startIndex];
var startItem = items[_index4];
columnHeights[i] = startItem.top + startItem.height + rowGutter;
for (j = startIndex + 1; j < itemsInColumn.length; j++) {
var _index5 = itemsInColumn[j];
var _item = items[_index5];
_item.top = columnHeights[i];
columnHeights[i] = _item.top + _item.height + rowGutter;
intervalTree.remove(_index5);
intervalTree.insert(_item.top, _item.top + _item.height, _index5);
}
}
},
// Render all cells visible within the viewport range defined.
range: function range(lo, hi, renderCallback) {
return intervalTree.search(lo, hi, function (index, top) {
return renderCallback(index, items[index].left, top);
});
},
estimateHeight: function estimateHeight(itemCount, defaultItemHeight) {
var tallestColumn = Math.max(0, Math.max.apply(null, columnHeights));
return itemCount === intervalTree.size ? tallestColumn : tallestColumn + Math.ceil((itemCount - intervalTree.size) / columnCount) * defaultItemHeight;
},
shortestColumn: function shortestColumn() {
if (columnHeights.length > 1) return Math.min.apply(null, columnHeights);
return columnHeights[0] || 0;
},
size: function size() {
return intervalTree.size;
},
all: function all() {
return items;
}
};
};
/* istanbul ignore next */
var binarySearch = function binarySearch(a, y) {
var l = 0;
var h = a.length - 1;
while (l <= h) {
var m = l + h >>> 1;
var x = a[m];
if (x === y) return m;else if (x <= y) l = m + 1;else h = m - 1;
}
return -1;
};
var getColumns = function getColumns(width, minimumWidth, gutter, columnCount, maxColumnCount, maxColumnWidth) {
if (width === void 0) {
width = 0;
}
if (minimumWidth === void 0) {
minimumWidth = 0;
}
if (gutter === void 0) {
gutter = 8;
}
columnCount = columnCount || Math.min(Math.floor((width + gutter) / (minimumWidth + gutter)), maxColumnCount || Infinity) || 1;
var columnWidth = Math.floor((width - gutter * (columnCount - 1)) / columnCount); // Cap the column width if maxColumnWidth is specified
if (maxColumnWidth !== undefined && columnWidth > maxColumnWidth) {
columnWidth = maxColumnWidth;
}
return [columnWidth, columnCount];
};
var emptyArr = [];
var rafSchd = function rafSchd(fn) {
var lastArgs = [];
var frameId = null;
function _ref() {
frameId = null;
fn.apply(void 0, lastArgs);
}
var wrapperFn = function wrapperFn() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
lastArgs = args;
if (frameId) {
return;
}
frameId = requestAnimationFrame(_ref);
};
wrapperFn.cancel = function () {
if (!frameId) {
return;
}
cancelAnimationFrame(frameId);
frameId = null;
};
return wrapperFn;
};
var rafSchd$1 = rafSchd;
/**
* Creates a resize observer that forces updates to the grid cell positions when mutations are
* made to cells affecting their height.
*
* @param positioner - The masonry cell positioner created by the `usePositioner()` hook.
*/
function useResizeObserver(positioner) {
var forceUpdate = useForceUpdate();
var resizeObserver = createResizeObserver(positioner, forceUpdate); // Cleans up the resize observers when they change or the
// component unmounts
function _ref() {
return resizeObserver.disconnect();
}
React__namespace.useEffect(function () {
return _ref;
}, [resizeObserver]);
return resizeObserver;
}
function _ref2(handler) {
handler.cancel();
}
/**
* Creates a resize observer that fires an `updater` callback whenever the height of
* one or many cells change. The `useResizeObserver()` hook is using this under the hood.
*
* @param positioner - A cell positioner created by the `usePositioner()` hook or the `createPositioner()` utility
* @param updater - A callback that fires whenever one or many cell heights change.
*/
var createResizeObserver = /*#__PURE__*/trieMemoize([WeakMap], // TODO: figure out a way to test this
/* istanbul ignore next */
function (positioner, updater) {
var updates = [];
var update = rafSchd$1(function () {
if (updates.length > 0) {
// Updates the size/positions of the cell with the resize
// observer updates
positioner.update(updates);
updater(updates);
}
updates.length = 0;
});
var commonHandler = function commonHandler(target) {
var height = target.offsetHeight;
if (height > 0) {
var index = elementsCache.get(target);
if (index !== void 0) {
var position = positioner.get(index);
if (position !== void 0 && height !== position.height) updates.push(index, height);
}
}
update();
};
var handlers = new Map();
var handleEntries = function handleEntries(entries) {
var i = 0;
for (; i < entries.length; i++) {
var entry = entries[i];
var index = elementsCache.get(entry.target);
if (index === void 0) continue;
var handler = handlers.get(index);
if (!handler) {
handler = rafSchd$1(commonHandler);
handlers.set(index, handler);
}
handler(entry.target);
}
};
var ro = new ResizeObserver(handleEntries); // Overrides the original disconnect to include cancelling handling the entries.
// Ideally this would be its own method but that would result in a breaking
// change.
var disconnect = ro.disconnect.bind(ro);
ro.disconnect = function () {
disconnect();
handlers.forEach(_ref2);
};
return ro;
});
/**
* A hook that creates a callback for scrolling to a specific index in
* the "items" array.
*
* @param positioner - A positioner created by the `usePositioner()` hook
* @param options - Configuration options
*/
function useScrollToIndex(positioner, options) {
var _latestOptions$curren;
var _options$align = options.align,
align = _options$align === void 0 ? "top" : _options$align,
_options$element = options.element,
element = _options$element === void 0 ? typeof window !== "undefined" && window : _options$element,
_options$offset = options.offset,
offset = _options$offset === void 0 ? 0 : _options$offset,
_options$height = options.height,
height = _options$height === void 0 ? typeof window !== "undefined" ? window.innerHeight : 0 : _options$height;
var latestOptions = useLatest$1({
positioner: positioner,
element: element,
align: align,
offset: offset,
height: height
});
var getTarget = React__namespace.useRef(function () {
var latestElement = latestOptions.current.element;
return latestElement && "current" in latestElement ? latestElement.current : latestElement;
}).current;
var _React$useReducer = React__namespace.useReducer(function (state, action) {
var nextState = {
position: state.position,
index: state.index,
prevTop: state.prevTop
};
/* istanbul ignore next */
if (action.type === "scrollToIndex") {
var _action$value;
return {
position: latestOptions.current.positioner.get((_action$value = action.value) !== null && _action$value !== void 0 ? _action$value : -1),
index: action.value,
prevTop: void 0
};
} else if (action.type === "setPosition") {
nextState.position = action.value;
} else if (action.type === "setPrevTop") {
nextState.prevTop = action.value;
} else if (action.type === "reset") {
return defaultState;
}
return nextState;
}, defaultState),
state = _React$useReducer[0],
dispatch = _React$useReducer[1];
var throttledDispatch = useThrottleCallback(dispatch, 15); // If we find the pos