Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.DS_Store
node_modules
dist
.rpt2_cache
45 changes: 45 additions & 0 deletions dist/components/ScrollSync/ScrollSync.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { FC } from "react";
import { ScrollConfig } from "./ScrollSyncNode";
export interface ScrollSyncProps {
children: React.ReactNode;
/**syncing enable control */
disabled?: boolean;
/** In case we want scroll to be proportionally applied regardless of the width and/or height*/
proportional?: boolean;
}
/**
* node should be scrollable
*/
declare type Node = (EventTarget & HTMLElement) | null;
/**
* node should be scrollable
*/
interface SyncableElement {
node: Node;
scroll: ScrollConfig;
}
interface ScrollingSyncerContextValues {
/**
* register node to be synced with other scrolled nodes
*/
registerNode: (node: SyncableElement, groups: string[]) => void;
/**
* unregister node to stop syncing with other scrolled nodes
*/
unregisterNode: (node: SyncableElement, group: string[]) => void;
/**
* scroll handler for each node.onScroll
*/
onScroll: (e: React.UIEvent<HTMLElement>, groups: string[]) => void;
}
/**
* ScrollingSyncerContext is the context to be handling scrolled nodes
*/
export declare const ScrollingSyncerContext: React.Context<ScrollingSyncerContextValues>;
/**
* ScrollSync component is a context based component,
* that wrappes children to be .Provided with context utils and eventsHandlers
* @param props ScrollSyncProps
*/
export declare const ScrollSync: FC<ScrollSyncProps>;
export default ScrollSync;
32 changes: 32 additions & 0 deletions dist/components/ScrollSync/ScrollSyncNode.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";
/**
* ScrollSyncNode Component
*
* Wrap your content in it to keep its scroll position in sync with other panes
*/
export declare type ScrollConfig = "synced-only" | "syncer-only" | "two-way";
export declare type LockAxis = "X" | "Y" | "XY" | null;
interface ScrollSyncNodeProps {
/**
* Children
*/
children: React.ReactElement<any>;
/**
* Groups to make the children attached to
*/
group?: string | string[];
/**
* If the scrolling is enabled or not
*/
scroll?: ScrollConfig;
/**
* Prevent scroll on current node if axis is locked
*/
selfLockAxis?: LockAxis;
/**
* Callback for scroll handling
*/
onScroll?: (e: React.UIEvent<HTMLElement>) => void;
}
declare const ScrollSyncNode: React.ForwardRefExoticComponent<ScrollSyncNodeProps & React.RefAttributes<EventTarget & HTMLElement>>;
export default ScrollSyncNode;
4 changes: 4 additions & 0 deletions dist/components/ScrollSync/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { default as ScrollSync } from "./ScrollSync";
export { default as ScrollSyncNode } from "./ScrollSyncNode";
declare const _default: {};
export default _default;
3 changes: 3 additions & 0 deletions dist/components/ScrollSync/utils.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LockAxis } from "./ScrollSyncNode";
export declare const toArray: (groups: string | string[]) => string[];
export declare const getMovingAxis: (e: WheelEvent) => LockAxis;
267 changes: 267 additions & 0 deletions dist/index.cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var React = require('react');
var React__default = _interopDefault(React);

/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0

THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.

See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */

var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};

/**
* ScrollingSyncerContext is the context to be handling scrolled nodes
*/
var ScrollingSyncerContext = React__default.createContext({
registerNode: function (_node, _group) { },
unregisterNode: function (_node, _group) { },
onScroll: function (_e, _groups) { },
});
/**
* ScrollSync component is a context based component,
* that wrappes children to be .Provided with context utils and eventsHandlers
* @param props ScrollSyncProps
*/
var ScrollSync = function (_a) {
var _b = _a.disabled, disabled = _b === void 0 ? false : _b, _c = _a.proportional, proportional = _c === void 0 ? true : _c, children = _a.children;
/**
* a map of group: and it's nodes
* {
* groupA: [node1, node2, node3],
* groupB: [node4, node5],
* groupC: [node1, node4],
* }
*/
var nodesRef = React.useRef({});
var elements = nodesRef.current;
/**
* A simple trick to avoid calling `requestAnimationFrame` before the frame is painted, to enhance performance!
*/
var shouldPaintFrameRef = React.useRef(true);
var shouldPaintFrame = shouldPaintFrameRef.current;
/**
* return boolean if the group exists
* @param group to be found
*/
var findGroup = function (group) {
return !!elements[group];
};
/**
* returns found node or undefined
* @param node to be found
* @param group to be searched in
*/
var doesNodeExists = function (node, group) {
var groupExists = findGroup(group);
if (!groupExists)
return false;
var foundNode = elements[group].find(function (element) { return element.node === node; });
if (!foundNode)
return false;
return !!foundNode;
};
/**
* this function will delightly register your node (that uses ScrollSyncNode)
* to be scroll-synced with it's cool other registerd nodes
*
* @param node to be registred
* @param groups to wich groups the node should be registered
*/
var registerNode = function (element, groups) {
groups.forEach(function (group) {
var groupExists = findGroup(group);
if (!groupExists) {
elements[group] = [];
}
elements[group].push(__assign({}, element));
});
};
/**
* this function will delightly UNregister your node (that uses ScrollSyncNode)
* to stop it's scroll-sync with other cool registerd nodes
*
* used now when unmounting nodes
*
* @param element to be registred
* @param groups to wich groups the node should be registered
*/
var unregisterNode = function (element, groups) {
groups.forEach(function (group) {
var doesNodeExist = doesNodeExists(element.node, group);
if (doesNodeExist) {
elements[group].splice(elements[group].findIndex(function (e) { return element.node === e.node; }), 1);
}
});
};
/**
* calculate scrolling percentage of the scrolledNode to be synced with other nodes
* @param scrolledNode !!
* @param node other node to be scroll-synced
*/
var syncScrollPosition = function (scrolledNode, node) {
if (!scrolledNode || !node)
return;
var scrollTop = scrolledNode.scrollTop, scrollHeight = scrolledNode.scrollHeight, offsetHeight = scrolledNode.offsetHeight, scrollLeft = scrolledNode.scrollLeft, scrollWidth = scrolledNode.scrollWidth, offsetLeft = scrolledNode.offsetLeft, offsetWidth = scrolledNode.offsetWidth;
if (!proportional) {
node.scrollLeft = scrollLeft;
node.scrollTop = scrollTop;
return;
}
//calculate percentage of scrolling of the scrolledNode
var percentagePerHeight = scrollTop / (scrollHeight - offsetHeight);
//calculate percentage of scrolling of the scrolledNode
var percentagePerWidth = scrollLeft / (scrollWidth - offsetWidth);
//Apply calculated scrolling
node.scrollTop = Math.round(percentagePerHeight * (node.scrollHeight - node.offsetHeight));
node.scrollLeft = Math.round(percentagePerWidth * (node.scrollWidth - node.offsetWidth));
};
/**
* We sync all other nodes in the registered groups
* @param scrolledNode !!
* @param groups groups to be scroll-synced
*/
var syncScrollPositions = function (scrolledNode, groups) {
groups.forEach(function (group) {
elements[group].forEach(function (element) {
/* For all nodes other than the currently scrolled one */
if (scrolledNode !== element.node) {
var isEnabled = element.scroll === "two-way";
var isSynced = element.scroll === "synced-only";
(isEnabled || isSynced) && syncScrollPosition(scrolledNode, element.node);
}
});
});
};
/**
* check if previous frame was painted and we should paint next
* if we should, then we call `requestAnimationFrame` again
* and then clear the shouldPaintFrame flag till next animation frame
*
* @param node node to be scrolled
* @param groups groups to be scroll-synced
*/
var handleNodeScroll = function (node, groups) {
if (shouldPaintFrame) {
window.requestAnimationFrame(function () {
syncScrollPositions(node, groups);
shouldPaintFrame = true;
});
shouldPaintFrame = false;
}
};
return (React__default.createElement(ScrollingSyncerContext.Provider, { value: {
registerNode: registerNode,
unregisterNode: unregisterNode,
onScroll: function (e, groups) { return !disabled && handleNodeScroll(e.currentTarget, groups); },
} }, React__default.Children.only(children)));
};

var toArray = function (groups) { return [].concat(groups); };
var getMovingAxis = function (e) {
if (e.deltaX > 0 || e.deltaX < 0)
return "X";
if (e.deltaY > 0 || e.deltaY < 0)
return "Y";
if ((e.deltaY > 0 || e.deltaY < 0) && (e.deltaX > 0 || e.deltaX < 0))
return "XY";
return null;
};

var ScrollSyncNode = React.forwardRef(function (props, forwardedRef) {
var children = props.children, _a = props.group, group = _a === void 0 ? "default" : _a, _b = props.scroll, scroll = _b === void 0 ? "two-way" : _b, _c = props.selfLockAxis, selfLockAxis = _c === void 0 ? null : _c, _d = props.onScroll, onNodeScroll = _d === void 0 ? function () { return undefined; } : _d;
var _e = React.useContext(ScrollingSyncerContext), registerNode = _e.registerNode, unregisterNode = _e.unregisterNode, onScroll = _e.onScroll;
var childRef = children.props.ref;
var hasDoubleRef = childRef != null && forwardedRef != null;
if (hasDoubleRef) {
console.warn("scroll-sync-react:\nWARNING: ref used on both ScrollSyncNode and its direct child.\nUsing the ref from the ScrollSyncNode component.");
}
var ref = childRef && !forwardedRef ? childRef : React.useRef(null);
React.useEffect(function () {
if (typeof forwardedRef === "function") {
forwardedRef(ref.current);
}
}, []);
var applySelfLockAxis = function (event) {
var movingAxis = getMovingAxis(event);
if (selfLockAxis === "X" && movingAxis === "X") {
event.preventDefault();
}
else if (selfLockAxis === "Y" && movingAxis === "Y") {
event.preventDefault();
}
else if (selfLockAxis === "XY" && (movingAxis === "XY" || movingAxis === "X" || movingAxis === "Y")) {
event.preventDefault();
}
};
React.useEffect(function () {
var _a;
var syncableElement = { node: ref.current, scroll: scroll };
if (syncableElement)
registerNode(syncableElement, toArray(group));
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.addEventListener("wheel", applySelfLockAxis, { passive: false });
return function () {
var _a;
unregisterNode(syncableElement, toArray(group));
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.removeEventListener("wheel", applySelfLockAxis);
};
}, []);
React.useEffect(function () {
var syncableElement = { node: ref.current, scroll: scroll };
unregisterNode(syncableElement, toArray(group));
registerNode(syncableElement, toArray(group));
return function () { return unregisterNode(syncableElement, toArray(group)); };
}, [scroll, group]);
var isSyncer = scroll === "syncer-only";
var isEnabled = scroll === "two-way";
return React__default.cloneElement(children, {
ref: ref,
onScroll: function (e) {
if (typeof children.props.onScroll === "function") {
children.props.onScroll(e);
}
e.persist();
if (isSyncer || isEnabled) {
onScroll(e, toArray(group));
onNodeScroll(e);
}
},
onWheel: function (e) {
if (typeof children.props.onWheel === "function") {
children.props.onWheel(e);
}
e.persist();
if (isSyncer || isEnabled) {
onScroll(e, toArray(group));
onNodeScroll(e);
}
},
});
});
ScrollSyncNode.displayName = "ScrollSyncNode";

exports.ScrollSync = ScrollSync;
exports.ScrollSyncNode = ScrollSyncNode;
2 changes: 2 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { ScrollSync, ScrollSyncNode } from "./components/ScrollSync";
export { ScrollSync, ScrollSyncNode };
Loading