UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

193 lines (189 loc) 6.05 kB
'use strict'; var polarisTokens = require('@shopify/polaris-tokens'); var debounce = require('../debounce.js'); var shared = require('../../components/shared.js'); var breakpoints = require('../breakpoints.js'); var geometry = require('../geometry.js'); const SIXTY_FPS = 1000 / 60; class StickyManager { constructor(container) { this.stickyItems = []; this.stuckItems = []; this.container = null; this.topBarOffset = 0; this.handleResize = debounce.debounce(() => { this.manageStickyItems(); }, SIXTY_FPS, { leading: true, trailing: true, maxWait: SIXTY_FPS }); this.handleScroll = debounce.debounce(() => { this.manageStickyItems(); }, SIXTY_FPS, { leading: true, trailing: true, maxWait: SIXTY_FPS }); if (container) { this.setContainer(container); } } registerStickyItem(stickyItem) { this.stickyItems.push(stickyItem); } unregisterStickyItem(nodeToRemove) { const nodeIndex = this.stickyItems.findIndex(({ stickyNode }) => nodeToRemove === stickyNode); this.stickyItems.splice(nodeIndex, 1); } getStickyItem(node) { return this.stickyItems.find(({ stickyNode }) => node === stickyNode); } setContainer(el) { this.container = el; if (isDocument(el)) { this.setTopBarOffset(el); } this.container.addEventListener('scroll', this.handleScroll); window.addEventListener('resize', this.handleResize); this.manageStickyItems(); } removeScrollListener() { if (this.container) { this.container.removeEventListener('scroll', this.handleScroll); window.removeEventListener('resize', this.handleResize); } } manageStickyItems() { if (this.stickyItems.length <= 0) { return; } const scrollTop = this.container ? scrollTopFor(this.container) : 0; const containerTop = geometry.getRectForNode(this.container).top + this.topBarOffset; this.stickyItems.forEach(stickyItem => { const { handlePositioning } = stickyItem; const { sticky, top, left, width } = this.evaluateStickyItem(stickyItem, scrollTop, containerTop); this.updateStuckItems(stickyItem, sticky); handlePositioning(sticky, top, left, width); }); } evaluateStickyItem(stickyItem, scrollTop, containerTop) { const { stickyNode, placeHolderNode, boundingElement, offset, disableWhenStacked } = stickyItem; if (disableWhenStacked && breakpoints.stackedContent().matches) { return { sticky: false, top: 0, left: 0, width: 'auto' }; } const stickyOffset = offset ? this.getOffset(stickyNode) + parseInt( // Important: This will not update when the active theme changes. // Update this to `useTheme` once converted to a function component. polarisTokens.themeDefault.space['space-500'], 10) : this.getOffset(stickyNode); const scrollPosition = scrollTop + stickyOffset; const placeHolderNodeCurrentTop = placeHolderNode.getBoundingClientRect().top - containerTop + scrollTop; const top = containerTop + stickyOffset; const width = placeHolderNode.getBoundingClientRect().width; const left = placeHolderNode.getBoundingClientRect().left; let sticky; if (boundingElement == null) { sticky = scrollPosition >= placeHolderNodeCurrentTop; } else { const stickyItemHeight = stickyNode.getBoundingClientRect().height || stickyNode.firstElementChild?.getBoundingClientRect().height || 0; const stickyItemBottomPosition = boundingElement.getBoundingClientRect().bottom - stickyItemHeight + scrollTop - containerTop; sticky = scrollPosition >= placeHolderNodeCurrentTop && scrollPosition < stickyItemBottomPosition; } return { sticky, top, left, width }; } updateStuckItems(item, sticky) { const { stickyNode } = item; if (sticky && !this.isNodeStuck(stickyNode)) { this.addStuckItem(item); } else if (!sticky && this.isNodeStuck(stickyNode)) { this.removeStuckItem(item); } } addStuckItem(stickyItem) { this.stuckItems.push(stickyItem); } removeStuckItem(stickyItem) { const { stickyNode: nodeToRemove } = stickyItem; const nodeIndex = this.stuckItems.findIndex(({ stickyNode }) => nodeToRemove === stickyNode); this.stuckItems.splice(nodeIndex, 1); } getOffset(node) { if (this.stuckItems.length === 0) { return 0; } let offset = 0; let count = 0; const stuckNodesLength = this.stuckItems.length; const nodeRect = geometry.getRectForNode(node); while (count < stuckNodesLength) { const stuckNode = this.stuckItems[count].stickyNode; if (stuckNode !== node) { const stuckNodeRect = geometry.getRectForNode(stuckNode); if (!horizontallyOverlaps(nodeRect, stuckNodeRect)) { offset += geometry.getRectForNode(stuckNode).height; } } else { break; } count++; } return offset; } isNodeStuck(node) { const nodeFound = this.stuckItems.findIndex(({ stickyNode }) => node === stickyNode); return nodeFound >= 0; } setTopBarOffset(container) { const topbarElement = container.querySelector(`:not(${shared.scrollable.selector}) ${shared.dataPolarisTopBar.selector}`); this.topBarOffset = topbarElement ? topbarElement.clientHeight : 0; } } function isDocument(node) { return node === document; } function scrollTopFor(container) { return isDocument(container) ? document.body.scrollTop || document.documentElement.scrollTop : container.scrollTop; } function horizontallyOverlaps(rect1, rect2) { const rect1Left = rect1.left; const rect1Right = rect1.left + rect1.width; const rect2Left = rect2.left; const rect2Right = rect2.left + rect2.width; return rect2Right < rect1Left || rect1Right < rect2Left; } exports.StickyManager = StickyManager;