@fullcalendar/scrollgrid
Advanced tools
Comparing version 6.1.15 to 7.0.0-beta.0
/*! | ||
FullCalendar ScrollGrid Plugin v6.1.15 | ||
FullCalendar ScrollGrid Plugin v7.0.0-beta.0 | ||
Docs & License: https://fullcalendar.io/docs/premium | ||
(c) 2024 Adam Shaw | ||
*/ | ||
FullCalendar.ScrollGrid = (function (exports, core, premiumCommonPlugin, internal$1, preact) { | ||
FullCalendar.ScrollGrid = (function (exports, core, premiumCommonPlugin, internal$1) { | ||
'use strict'; | ||
@@ -13,233 +13,2 @@ | ||
// TODO: assume the el has no borders? | ||
function getScrollCanvasOrigin(scrollEl) { | ||
let rect = scrollEl.getBoundingClientRect(); | ||
let edges = internal$1.computeEdges(scrollEl); // TODO: pass in isRtl? | ||
return { | ||
left: rect.left + edges.borderLeft + edges.scrollbarLeft - getScrollFromLeftEdge(scrollEl), | ||
top: rect.top + edges.borderTop - scrollEl.scrollTop, | ||
}; | ||
} | ||
function getScrollFromLeftEdge(el) { | ||
let scrollLeft = el.scrollLeft; | ||
let computedStyles = window.getComputedStyle(el); // TODO: pass in isRtl instead? | ||
if (computedStyles.direction === 'rtl') { | ||
switch (getRtlScrollSystem()) { | ||
case 'negative': | ||
scrollLeft *= -1; // convert to 'reverse'. fall through... | ||
case 'reverse': // scrollLeft is distance between scrollframe's right edge scrollcanvas's right edge | ||
scrollLeft = el.scrollWidth - scrollLeft - el.clientWidth; | ||
} | ||
} | ||
return scrollLeft; | ||
} | ||
function setScrollFromLeftEdge(el, scrollLeft) { | ||
let computedStyles = window.getComputedStyle(el); // TODO: pass in isRtl instead? | ||
if (computedStyles.direction === 'rtl') { | ||
switch (getRtlScrollSystem()) { | ||
case 'reverse': | ||
scrollLeft = el.scrollWidth - scrollLeft; | ||
break; | ||
case 'negative': | ||
scrollLeft = -(el.scrollWidth - scrollLeft); | ||
break; | ||
} | ||
} | ||
el.scrollLeft = scrollLeft; | ||
} | ||
// Horizontal Scroll System Detection | ||
// ---------------------------------------------------------------------------------------------- | ||
let _rtlScrollSystem; | ||
function getRtlScrollSystem() { | ||
return _rtlScrollSystem || (_rtlScrollSystem = detectRtlScrollSystem()); | ||
} | ||
function detectRtlScrollSystem() { | ||
let el = document.createElement('div'); | ||
el.style.position = 'absolute'; | ||
el.style.top = '-1000px'; | ||
el.style.width = '100px'; // must be at least the side of scrollbars or you get inaccurate values (#7335) | ||
el.style.height = '100px'; // " | ||
el.style.overflow = 'scroll'; | ||
el.style.direction = 'rtl'; | ||
let innerEl = document.createElement('div'); | ||
innerEl.style.width = '200px'; | ||
innerEl.style.height = '200px'; | ||
el.appendChild(innerEl); | ||
document.body.appendChild(el); | ||
let system; | ||
if (el.scrollLeft > 0) { | ||
system = 'positive'; // scroll is a positive number from the left edge | ||
} | ||
else { | ||
el.scrollLeft = 1; | ||
if (el.scrollLeft > 0) { | ||
system = 'reverse'; // scroll is a positive number from the right edge | ||
} | ||
else { | ||
system = 'negative'; // scroll is a negative number from the right edge | ||
} | ||
} | ||
internal$1.removeElement(el); | ||
return system; | ||
} | ||
const STICKY_SELECTOR = '.fc-sticky'; | ||
/* | ||
Goes beyond mere position:sticky, allows horizontal centering | ||
REQUIREMENT: fc-sticky elements, if the fc-sticky className is taken away, should NOT have relative or absolute positioning. | ||
This is because we attach the coords with JS, and the VDOM might take away the fc-sticky class but doesn't know kill the positioning. | ||
TODO: don't query text-align:center. isn't compatible with flexbox centering. instead, check natural X coord within parent container | ||
*/ | ||
class StickyScrolling { | ||
constructor(scrollEl, isRtl) { | ||
this.scrollEl = scrollEl; | ||
this.isRtl = isRtl; | ||
this.updateSize = () => { | ||
let { scrollEl } = this; | ||
let els = internal$1.findElements(scrollEl, STICKY_SELECTOR); | ||
let elGeoms = this.queryElGeoms(els); | ||
let viewportWidth = scrollEl.clientWidth; | ||
assignStickyPositions(els, elGeoms, viewportWidth); | ||
}; | ||
} | ||
queryElGeoms(els) { | ||
let { scrollEl, isRtl } = this; | ||
let canvasOrigin = getScrollCanvasOrigin(scrollEl); | ||
let elGeoms = []; | ||
for (let el of els) { | ||
let parentBound = internal$1.translateRect(internal$1.computeInnerRect(el.parentNode, true, true), // weird way to call this!!! | ||
-canvasOrigin.left, -canvasOrigin.top); | ||
let elRect = el.getBoundingClientRect(); | ||
let computedStyles = window.getComputedStyle(el); | ||
let textAlign = window.getComputedStyle(el.parentNode).textAlign; // ask the parent | ||
let naturalBound = null; | ||
if (textAlign === 'start') { | ||
textAlign = isRtl ? 'right' : 'left'; | ||
} | ||
else if (textAlign === 'end') { | ||
textAlign = isRtl ? 'left' : 'right'; | ||
} | ||
if (computedStyles.position !== 'sticky') { | ||
naturalBound = internal$1.translateRect(elRect, -canvasOrigin.left - (parseFloat(computedStyles.left) || 0), // could be 'auto' | ||
-canvasOrigin.top - (parseFloat(computedStyles.top) || 0)); | ||
} | ||
elGeoms.push({ | ||
parentBound, | ||
naturalBound, | ||
elWidth: elRect.width, | ||
elHeight: elRect.height, | ||
textAlign, | ||
}); | ||
} | ||
return elGeoms; | ||
} | ||
} | ||
function assignStickyPositions(els, elGeoms, viewportWidth) { | ||
els.forEach((el, i) => { | ||
let { textAlign, elWidth, parentBound } = elGeoms[i]; | ||
let parentWidth = parentBound.right - parentBound.left; | ||
let left; | ||
if (textAlign === 'center' && | ||
parentWidth > viewportWidth) { | ||
left = (viewportWidth - elWidth) / 2; | ||
} | ||
else { // if parent container can be completely in view, we don't need stickiness | ||
left = ''; | ||
} | ||
internal$1.applyStyle(el, { | ||
left, | ||
right: left, | ||
top: 0, | ||
}); | ||
}); | ||
} | ||
class ClippedScroller extends internal$1.BaseComponent { | ||
constructor() { | ||
super(...arguments); | ||
this.elRef = preact.createRef(); | ||
this.state = { | ||
xScrollbarWidth: 0, | ||
yScrollbarWidth: 0, | ||
}; | ||
this.handleScroller = (scroller) => { | ||
this.scroller = scroller; | ||
internal$1.setRef(this.props.scrollerRef, scroller); | ||
}; | ||
this.handleSizing = () => { | ||
let { props } = this; | ||
if (props.overflowY === 'scroll-hidden') { | ||
this.setState({ yScrollbarWidth: this.scroller.getYScrollbarWidth() }); | ||
} | ||
if (props.overflowX === 'scroll-hidden') { | ||
this.setState({ xScrollbarWidth: this.scroller.getXScrollbarWidth() }); | ||
} | ||
}; | ||
} | ||
render() { | ||
let { props, state, context } = this; | ||
let isScrollbarOnLeft = context.isRtl && internal$1.getIsRtlScrollbarOnLeft(); | ||
let overcomeLeft = 0; | ||
let overcomeRight = 0; | ||
let overcomeBottom = 0; | ||
let { overflowX, overflowY } = props; | ||
if (props.forPrint) { | ||
overflowX = 'visible'; | ||
overflowY = 'visible'; | ||
} | ||
if (overflowX === 'scroll-hidden') { | ||
overcomeBottom = state.xScrollbarWidth; | ||
} | ||
if (overflowY === 'scroll-hidden') { | ||
if (state.yScrollbarWidth != null) { | ||
if (isScrollbarOnLeft) { | ||
overcomeLeft = state.yScrollbarWidth; | ||
} | ||
else { | ||
overcomeRight = state.yScrollbarWidth; | ||
} | ||
} | ||
} | ||
return (preact.createElement("div", { ref: this.elRef, className: 'fc-scroller-harness' + (props.liquid ? ' fc-scroller-harness-liquid' : '') }, | ||
preact.createElement(internal$1.Scroller, { ref: this.handleScroller, elRef: this.props.scrollerElRef, overflowX: overflowX === 'scroll-hidden' ? 'scroll' : overflowX, overflowY: overflowY === 'scroll-hidden' ? 'scroll' : overflowY, overcomeLeft: overcomeLeft, overcomeRight: overcomeRight, overcomeBottom: overcomeBottom, maxHeight: typeof props.maxHeight === 'number' | ||
? (props.maxHeight + (overflowX === 'scroll-hidden' ? state.xScrollbarWidth : 0)) | ||
: '', liquid: props.liquid, liquidIsAbsolute: true }, props.children))); | ||
} | ||
componentDidMount() { | ||
this.handleSizing(); | ||
this.context.addResizeHandler(this.handleSizing); | ||
} | ||
getSnapshotBeforeUpdate(prevProps) { | ||
if (this.props.forPrint && !prevProps.forPrint) { | ||
return { simulateScrollLeft: this.scroller.el.scrollLeft }; | ||
} | ||
return {}; | ||
} | ||
componentDidUpdate(prevProps, prevState, snapshot) { | ||
const { props, scroller: { el: scrollerEl } } = this; | ||
if (!internal$1.isPropsEqual(prevProps, props)) { // an external change? | ||
this.handleSizing(); | ||
} | ||
if (snapshot.simulateScrollLeft !== undefined) { | ||
scrollerEl.style.left = -snapshot.simulateScrollLeft + 'px'; | ||
} | ||
else if (!props.forPrint && prevProps.forPrint) { | ||
const restoredScrollLeft = -parseInt(scrollerEl.style.left); | ||
scrollerEl.style.left = ''; | ||
scrollerEl.scrollLeft = restoredScrollLeft; | ||
} | ||
} | ||
componentWillUnmount() { | ||
this.context.removeResizeHandler(this.handleSizing); | ||
} | ||
needsXScrolling() { | ||
return this.scroller.needsXScrolling(); | ||
} | ||
needsYScrolling() { | ||
return this.scroller.needsYScrolling(); | ||
} | ||
} | ||
const WHEEL_EVENT_NAMES = 'wheel mousewheel DomMouseScroll MozMousePixelScroll'.split(' '); | ||
@@ -288,3 +57,3 @@ /* | ||
for (let eventName of WHEEL_EVENT_NAMES) { | ||
el.addEventListener(eventName, this.handleWheel); | ||
el.addEventListener(eventName, this.handleWheel, { passive: true }); | ||
} | ||
@@ -298,3 +67,3 @@ } | ||
for (let eventName of WHEEL_EVENT_NAMES) { | ||
el.removeEventListener(eventName, this.handleWheel); | ||
el.removeEventListener(eventName, this.handleWheel, { passive: true }); | ||
} | ||
@@ -333,9 +102,23 @@ } | ||
class ScrollSyncer { | ||
constructor(isVertical, scrollEls) { | ||
this.isVertical = isVertical; | ||
this.scrollEls = scrollEls; | ||
class ScrollerSyncer { | ||
constructor(isHorizontal = false) { | ||
this.isHorizontal = isHorizontal; | ||
this.scrollers = []; // TODO: move away from requiring Scroller | ||
this.scrollListeners = []; | ||
this.isPaused = false; | ||
this.scrollListeners = scrollEls.map((el) => this.bindScroller(el)); | ||
} | ||
handleChildren(scrollers, isRtl) { | ||
if (!internal$1.isArraysEqual(this.scrollers, scrollers)) { | ||
this.destroy(); | ||
this.scrollers = scrollers; | ||
const scrollListeners = []; | ||
for (const scroller of scrollers) { | ||
if (scroller) { // could be null | ||
scrollListeners.push(this.bindScroller(scroller.el)); | ||
} | ||
} | ||
this.scrollListeners = scrollListeners; | ||
} | ||
this.isRtl = isRtl; | ||
} | ||
destroy() { | ||
@@ -346,4 +129,29 @@ for (let scrollListener of this.scrollListeners) { | ||
} | ||
get x() { | ||
const { scrollListeners, masterEl, isRtl } = this; | ||
const el = masterEl || (scrollListeners.length ? scrollListeners[0].el : undefined); | ||
return internal$1.getNormalizedScrollX(el, isRtl); | ||
} | ||
get y() { | ||
const { scrollListeners, masterEl } = this; | ||
const el = masterEl || (scrollListeners.length ? scrollListeners[0].el : undefined); | ||
return el.scrollTop; | ||
} | ||
scrollTo({ x, y }) { | ||
this.isPaused = true; | ||
const { scrollListeners, isRtl } = this; | ||
if (y != null) { | ||
for (let scrollListener of scrollListeners) { | ||
scrollListener.el.scrollTop = y; | ||
} | ||
} | ||
if (x != null) { | ||
for (let scrollListener of scrollListeners) { | ||
internal$1.setNormalizedScrollX(scrollListener.el, isRtl, x); | ||
} | ||
} | ||
this.isPaused = false; | ||
} | ||
bindScroller(el) { | ||
let { scrollEls, isVertical } = this; | ||
let { isHorizontal } = this; | ||
let scrollListener = new ScrollListener(el); | ||
@@ -356,9 +164,10 @@ const onScroll = (isWheel, isTouch) => { | ||
if (this.masterEl === el) { // dealing with current | ||
for (let otherEl of scrollEls) { | ||
for (let scrollListener of this.scrollListeners) { | ||
const otherEl = scrollListener.el; | ||
if (otherEl !== el) { | ||
if (isVertical) { | ||
otherEl.scrollTop = el.scrollTop; | ||
if (isHorizontal) { | ||
otherEl.scrollLeft = el.scrollLeft; | ||
} | ||
else { | ||
otherEl.scrollLeft = el.scrollLeft; | ||
otherEl.scrollTop = el.scrollTop; | ||
} | ||
@@ -387,492 +196,16 @@ } | ||
} | ||
/* | ||
will normalize the scrollLeft value | ||
*/ | ||
forceScrollLeft(scrollLeft) { | ||
this.isPaused = true; | ||
for (let listener of this.scrollListeners) { | ||
setScrollFromLeftEdge(listener.el, scrollLeft); | ||
} | ||
this.isPaused = false; | ||
} | ||
forceScrollTop(top) { | ||
this.isPaused = true; | ||
for (let listener of this.scrollListeners) { | ||
listener.el.scrollTop = top; | ||
} | ||
this.isPaused = false; | ||
} | ||
} | ||
internal$1.config.SCROLLGRID_RESIZE_INTERVAL = 500; | ||
/* | ||
TODO: make <ScrollGridSection> subcomponent | ||
NOTE: doesn't support collapsibleWidth (which is sortof a hack anyway) | ||
*/ | ||
class ScrollGrid extends internal$1.BaseComponent { | ||
constructor() { | ||
super(...arguments); | ||
this.compileColGroupStats = internal$1.memoizeArraylike(compileColGroupStat, isColGroupStatsEqual); | ||
this.renderMicroColGroups = internal$1.memoizeArraylike(internal$1.renderMicroColGroup); // yucky to memoize VNodes, but much more efficient for consumers | ||
this.clippedScrollerRefs = new internal$1.RefMap(); | ||
// doesn't hold non-scrolling els used just for padding | ||
this.scrollerElRefs = new internal$1.RefMap(this._handleScrollerEl.bind(this)); | ||
this.chunkElRefs = new internal$1.RefMap(this._handleChunkEl.bind(this)); | ||
this.scrollSyncersBySection = {}; | ||
this.scrollSyncersByColumn = {}; | ||
// for row-height-syncing | ||
this.rowUnstableMap = new Map(); // no need to groom. always self-cancels | ||
this.rowInnerMaxHeightMap = new Map(); | ||
this.anyRowHeightsChanged = false; | ||
this.recentSizingCnt = 0; | ||
this.state = { | ||
shrinkWidths: [], | ||
forceYScrollbars: false, | ||
forceXScrollbars: false, | ||
scrollerClientWidths: {}, | ||
scrollerClientHeights: {}, | ||
sectionRowMaxHeights: [], | ||
}; | ||
this.handleSizing = (isForcedResize, sectionRowMaxHeightsChanged) => { | ||
if (!this.allowSizing()) { | ||
return; | ||
} | ||
if (!sectionRowMaxHeightsChanged) { // something else changed, probably external | ||
this.anyRowHeightsChanged = true; | ||
} | ||
let otherState = {}; | ||
// if reacting to self-change of sectionRowMaxHeightsChanged, or not stable, don't do anything | ||
if (isForcedResize || (!sectionRowMaxHeightsChanged && !this.rowUnstableMap.size)) { | ||
otherState.sectionRowMaxHeights = this.computeSectionRowMaxHeights(); | ||
} | ||
this.setState(Object.assign(Object.assign({ shrinkWidths: this.computeShrinkWidths() }, this.computeScrollerDims()), otherState), () => { | ||
if (!this.rowUnstableMap.size) { | ||
this.updateStickyScrolling(); // needs to happen AFTER final positioning committed to DOM | ||
} | ||
}); | ||
}; | ||
this.handleRowHeightChange = (rowEl, isStable) => { | ||
let { rowUnstableMap, rowInnerMaxHeightMap } = this; | ||
if (!isStable) { | ||
rowUnstableMap.set(rowEl, true); | ||
} | ||
else { | ||
rowUnstableMap.delete(rowEl); | ||
let innerMaxHeight = getRowInnerMaxHeight(rowEl); | ||
if (!rowInnerMaxHeightMap.has(rowEl) || rowInnerMaxHeightMap.get(rowEl) !== innerMaxHeight) { | ||
rowInnerMaxHeightMap.set(rowEl, innerMaxHeight); | ||
this.anyRowHeightsChanged = true; | ||
} | ||
if (!rowUnstableMap.size && this.anyRowHeightsChanged) { | ||
this.anyRowHeightsChanged = false; | ||
this.setState({ | ||
sectionRowMaxHeights: this.computeSectionRowMaxHeights(), | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
render() { | ||
let { props, state, context } = this; | ||
let { shrinkWidths } = state; | ||
let colGroupStats = this.compileColGroupStats(props.colGroups.map((colGroup) => [colGroup])); | ||
let microColGroupNodes = this.renderMicroColGroups(colGroupStats.map((stat, i) => [stat.cols, shrinkWidths[i]])); | ||
let classNames = internal$1.getScrollGridClassNames(props.liquid, context); | ||
this.getDims(); | ||
// TODO: make DRY | ||
let sectionConfigs = props.sections; | ||
let configCnt = sectionConfigs.length; | ||
let configI = 0; | ||
let currentConfig; | ||
let headSectionNodes = []; | ||
let bodySectionNodes = []; | ||
let footSectionNodes = []; | ||
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') { | ||
headSectionNodes.push(this.renderSection(currentConfig, configI, colGroupStats, microColGroupNodes, state.sectionRowMaxHeights, true)); | ||
configI += 1; | ||
} | ||
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') { | ||
bodySectionNodes.push(this.renderSection(currentConfig, configI, colGroupStats, microColGroupNodes, state.sectionRowMaxHeights, false)); | ||
configI += 1; | ||
} | ||
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') { | ||
footSectionNodes.push(this.renderSection(currentConfig, configI, colGroupStats, microColGroupNodes, state.sectionRowMaxHeights, true)); | ||
configI += 1; | ||
} | ||
const isBuggy = !internal$1.getCanVGrowWithinCell(); // see NOTE in SimpleScrollGrid | ||
const roleAttrs = { role: 'rowgroup' }; | ||
return preact.createElement('table', { | ||
ref: props.elRef, | ||
role: 'grid', | ||
className: classNames.join(' '), | ||
}, renderMacroColGroup(colGroupStats, shrinkWidths), Boolean(!isBuggy && headSectionNodes.length) && preact.createElement('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && preact.createElement('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && preact.createElement('tfoot', roleAttrs, ...footSectionNodes), isBuggy && preact.createElement('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes)); | ||
} | ||
renderSection(sectionConfig, sectionIndex, colGroupStats, microColGroupNodes, sectionRowMaxHeights, isHeader) { | ||
if ('outerContent' in sectionConfig) { | ||
return (preact.createElement(preact.Fragment, { key: sectionConfig.key }, sectionConfig.outerContent)); | ||
} | ||
return (preact.createElement("tr", { key: sectionConfig.key, role: "presentation", className: internal$1.getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, sectionConfig.chunks.map((chunkConfig, i) => this.renderChunk(sectionConfig, sectionIndex, colGroupStats[i], microColGroupNodes[i], chunkConfig, i, (sectionRowMaxHeights[sectionIndex] || [])[i] || [], isHeader)))); | ||
} | ||
renderChunk(sectionConfig, sectionIndex, colGroupStat, microColGroupNode, chunkConfig, chunkIndex, rowHeights, isHeader) { | ||
if ('outerContent' in chunkConfig) { | ||
return (preact.createElement(preact.Fragment, { key: chunkConfig.key }, chunkConfig.outerContent)); | ||
} | ||
let { state } = this; | ||
let { scrollerClientWidths, scrollerClientHeights } = state; | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let index = sectionIndex * chunksPerSection + chunkIndex; | ||
let sideScrollIndex = (!this.context.isRtl || internal$1.getIsRtlScrollbarOnLeft()) ? chunksPerSection - 1 : 0; | ||
let isVScrollSide = chunkIndex === sideScrollIndex; | ||
let isLastSection = sectionIndex === sectionCnt - 1; | ||
let forceXScrollbars = isLastSection && state.forceXScrollbars; // NOOOO can result in `null` | ||
let forceYScrollbars = isVScrollSide && state.forceYScrollbars; // NOOOO can result in `null` | ||
let allowXScrolling = colGroupStat && colGroupStat.allowXScrolling; // rename? | ||
let allowYScrolling = internal$1.getAllowYScrolling(this.props, sectionConfig); // rename? do in section func? | ||
let chunkVGrow = internal$1.getSectionHasLiquidHeight(this.props, sectionConfig); // do in section func? | ||
let expandRows = sectionConfig.expandRows && chunkVGrow; | ||
let tableMinWidth = (colGroupStat && colGroupStat.totalColMinWidth) || ''; | ||
let content = internal$1.renderChunkContent(sectionConfig, chunkConfig, { | ||
tableColGroupNode: microColGroupNode, | ||
tableMinWidth, | ||
clientWidth: scrollerClientWidths[index] !== undefined ? scrollerClientWidths[index] : null, | ||
clientHeight: scrollerClientHeights[index] !== undefined ? scrollerClientHeights[index] : null, | ||
expandRows, | ||
syncRowHeights: Boolean(sectionConfig.syncRowHeights), | ||
rowSyncHeights: rowHeights, | ||
reportRowHeightChange: this.handleRowHeightChange, | ||
}, isHeader); | ||
let overflowX = forceXScrollbars ? (isLastSection ? 'scroll' : 'scroll-hidden') : | ||
!allowXScrolling ? 'hidden' : | ||
(isLastSection ? 'auto' : 'scroll-hidden'); | ||
let overflowY = forceYScrollbars ? (isVScrollSide ? 'scroll' : 'scroll-hidden') : | ||
!allowYScrolling ? 'hidden' : | ||
(isVScrollSide ? 'auto' : 'scroll-hidden'); | ||
// it *could* be possible to reduce DOM wrappers by only doing a ClippedScroller when allowXScrolling or allowYScrolling, | ||
// but if these values were to change, the inner components would be unmounted/remounted because of the parent change. | ||
content = (preact.createElement(ClippedScroller, { ref: this.clippedScrollerRefs.createRef(index), scrollerElRef: this.scrollerElRefs.createRef(index), overflowX: overflowX, overflowY: overflowY, forPrint: this.props.forPrint, liquid: chunkVGrow, maxHeight: sectionConfig.maxHeight }, content)); | ||
return preact.createElement(isHeader ? 'th' : 'td', { | ||
key: chunkConfig.key, | ||
ref: this.chunkElRefs.createRef(index), | ||
role: 'presentation', | ||
}, content); | ||
} | ||
componentDidMount() { | ||
this.getStickyScrolling = internal$1.memoizeArraylike(initStickyScrolling); | ||
this.getScrollSyncersBySection = internal$1.memoizeHashlike(initScrollSyncer.bind(this, true), null, destroyScrollSyncer); | ||
this.getScrollSyncersByColumn = internal$1.memoizeHashlike(initScrollSyncer.bind(this, false), null, destroyScrollSyncer); | ||
this.updateScrollSyncers(); | ||
this.handleSizing(false); | ||
this.context.addResizeHandler(this.handleSizing); | ||
} | ||
componentDidUpdate(prevProps, prevState) { | ||
this.updateScrollSyncers(); | ||
// TODO: need better solution when state contains non-sizing things | ||
this.handleSizing(false, prevState.sectionRowMaxHeights !== this.state.sectionRowMaxHeights); | ||
} | ||
componentWillUnmount() { | ||
this.context.removeResizeHandler(this.handleSizing); | ||
this.destroyScrollSyncers(); | ||
} | ||
allowSizing() { | ||
let now = new Date(); | ||
if (!this.lastSizingDate || | ||
now.valueOf() > this.lastSizingDate.valueOf() + internal$1.config.SCROLLGRID_RESIZE_INTERVAL) { | ||
this.lastSizingDate = now; | ||
this.recentSizingCnt = 0; | ||
return true; | ||
} | ||
return (this.recentSizingCnt += 1) <= 10; | ||
} | ||
computeShrinkWidths() { | ||
let colGroupStats = this.compileColGroupStats(this.props.colGroups.map((colGroup) => [colGroup])); | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let cnt = sectionCnt * chunksPerSection; | ||
let shrinkWidths = []; | ||
colGroupStats.forEach((colGroupStat, i) => { | ||
if (colGroupStat.hasShrinkCol) { | ||
let chunkEls = this.chunkElRefs.collect(i, cnt, chunksPerSection); // in one col | ||
shrinkWidths[i] = internal$1.computeShrinkWidth(chunkEls); | ||
} | ||
}); | ||
return shrinkWidths; | ||
} | ||
// has the side effect of grooming rowInnerMaxHeightMap | ||
// TODO: somehow short-circuit if there are no new height changes | ||
computeSectionRowMaxHeights() { | ||
let newHeightMap = new Map(); | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let sectionRowMaxHeights = []; | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { | ||
let sectionConfig = this.props.sections[sectionI]; | ||
let assignableHeights = []; // chunk, row | ||
if (sectionConfig && sectionConfig.syncRowHeights) { | ||
let rowHeightsByChunk = []; | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let index = sectionI * chunksPerSection + chunkI; | ||
let rowHeights = []; | ||
let chunkEl = this.chunkElRefs.currentMap[index]; | ||
if (chunkEl) { | ||
rowHeights = internal$1.findElements(chunkEl, '.fc-scrollgrid-sync-table tr').map((rowEl) => { | ||
let max = getRowInnerMaxHeight(rowEl); | ||
newHeightMap.set(rowEl, max); | ||
return max; | ||
}); | ||
} | ||
else { | ||
rowHeights = []; | ||
} | ||
rowHeightsByChunk.push(rowHeights); | ||
} | ||
let rowCnt = rowHeightsByChunk[0].length; | ||
let isEqualRowCnt = true; | ||
for (let chunkI = 1; chunkI < chunksPerSection; chunkI += 1) { | ||
let isOuterContent = sectionConfig.chunks[chunkI] && sectionConfig.chunks[chunkI].outerContent !== undefined; // can be null | ||
if (!isOuterContent && rowHeightsByChunk[chunkI].length !== rowCnt) { // skip outer content | ||
isEqualRowCnt = false; | ||
break; | ||
} | ||
} | ||
if (!isEqualRowCnt) { | ||
let chunkHeightSums = []; | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
chunkHeightSums.push(sumNumbers(rowHeightsByChunk[chunkI]) + rowHeightsByChunk[chunkI].length); | ||
} | ||
let maxTotalSum = Math.max(...chunkHeightSums); | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let rowInChunkCnt = rowHeightsByChunk[chunkI].length; | ||
let rowInChunkTotalHeight = maxTotalSum - rowInChunkCnt; // subtract border | ||
// height of non-first row. we do this to avoid rounding, because it's unreliable within a table | ||
let rowInChunkHeightOthers = Math.floor(rowInChunkTotalHeight / rowInChunkCnt); | ||
// whatever is leftover goes to the first row | ||
let rowInChunkHeightFirst = rowInChunkTotalHeight - rowInChunkHeightOthers * (rowInChunkCnt - 1); | ||
let rowInChunkHeights = []; | ||
let row = 0; | ||
if (row < rowInChunkCnt) { | ||
rowInChunkHeights.push(rowInChunkHeightFirst); | ||
row += 1; | ||
} | ||
while (row < rowInChunkCnt) { | ||
rowInChunkHeights.push(rowInChunkHeightOthers); | ||
row += 1; | ||
} | ||
assignableHeights.push(rowInChunkHeights); | ||
} | ||
} | ||
else { | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
assignableHeights.push([]); | ||
} | ||
for (let row = 0; row < rowCnt; row += 1) { | ||
let rowHeightsAcrossChunks = []; | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let h = rowHeightsByChunk[chunkI][row]; | ||
if (h != null) { // protect against outerContent | ||
rowHeightsAcrossChunks.push(h); | ||
} | ||
} | ||
let maxHeight = Math.max(...rowHeightsAcrossChunks); | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
assignableHeights[chunkI].push(maxHeight); | ||
} | ||
} | ||
} | ||
} | ||
sectionRowMaxHeights.push(assignableHeights); | ||
} | ||
this.rowInnerMaxHeightMap = newHeightMap; | ||
return sectionRowMaxHeights; | ||
} | ||
computeScrollerDims() { | ||
let scrollbarWidth = internal$1.getScrollbarWidths(); | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let sideScrollI = (!this.context.isRtl || internal$1.getIsRtlScrollbarOnLeft()) ? chunksPerSection - 1 : 0; | ||
let lastSectionI = sectionCnt - 1; | ||
let currentScrollers = this.clippedScrollerRefs.currentMap; | ||
let scrollerEls = this.scrollerElRefs.currentMap; | ||
let forceYScrollbars = false; | ||
let forceXScrollbars = false; | ||
let scrollerClientWidths = {}; | ||
let scrollerClientHeights = {}; | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { // along edge | ||
let index = sectionI * chunksPerSection + sideScrollI; | ||
let scroller = currentScrollers[index]; | ||
if (scroller && scroller.needsYScrolling()) { | ||
forceYScrollbars = true; | ||
break; | ||
} | ||
} | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { // along last row | ||
let index = lastSectionI * chunksPerSection + chunkI; | ||
let scroller = currentScrollers[index]; | ||
if (scroller && scroller.needsXScrolling()) { | ||
forceXScrollbars = true; | ||
break; | ||
} | ||
} | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let index = sectionI * chunksPerSection + chunkI; | ||
let scrollerEl = scrollerEls[index]; | ||
if (scrollerEl) { | ||
// TODO: weird way to get this. need harness b/c doesn't include table borders | ||
let harnessEl = scrollerEl.parentNode; | ||
scrollerClientWidths[index] = Math.floor(harnessEl.getBoundingClientRect().width - ((chunkI === sideScrollI && forceYScrollbars) | ||
? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future | ||
: 0)); | ||
scrollerClientHeights[index] = Math.floor(harnessEl.getBoundingClientRect().height - ((sectionI === lastSectionI && forceXScrollbars) | ||
? scrollbarWidth.x // use global because scroller might not have scrollbars yet but will need them in future | ||
: 0)); | ||
} | ||
} | ||
} | ||
return { forceYScrollbars, forceXScrollbars, scrollerClientWidths, scrollerClientHeights }; | ||
} | ||
updateStickyScrolling() { | ||
let { isRtl } = this.context; | ||
let argsByKey = this.scrollerElRefs.getAll().map((scrollEl) => [scrollEl, isRtl]); | ||
this.getStickyScrolling(argsByKey) | ||
.forEach((stickyScrolling) => stickyScrolling.updateSize()); | ||
} | ||
updateScrollSyncers() { | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let cnt = sectionCnt * chunksPerSection; | ||
let scrollElsBySection = {}; | ||
let scrollElsByColumn = {}; | ||
let scrollElMap = this.scrollerElRefs.currentMap; | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { | ||
let startIndex = sectionI * chunksPerSection; | ||
let endIndex = startIndex + chunksPerSection; | ||
scrollElsBySection[sectionI] = internal$1.collectFromHash(scrollElMap, startIndex, endIndex, 1); // use the filtered | ||
} | ||
for (let col = 0; col < chunksPerSection; col += 1) { | ||
scrollElsByColumn[col] = this.scrollerElRefs.collect(col, cnt, chunksPerSection); // DON'T use the filtered | ||
} | ||
this.scrollSyncersBySection = this.getScrollSyncersBySection(scrollElsBySection); | ||
this.scrollSyncersByColumn = this.getScrollSyncersByColumn(scrollElsByColumn); | ||
} | ||
destroyScrollSyncers() { | ||
internal$1.mapHash(this.scrollSyncersBySection, destroyScrollSyncer); | ||
internal$1.mapHash(this.scrollSyncersByColumn, destroyScrollSyncer); | ||
} | ||
getChunkConfigByIndex(index) { | ||
let chunksPerSection = this.getDims()[1]; | ||
let sectionI = Math.floor(index / chunksPerSection); | ||
let chunkI = index % chunksPerSection; | ||
let sectionConfig = this.props.sections[sectionI]; | ||
return sectionConfig && sectionConfig.chunks[chunkI]; | ||
} | ||
forceScrollLeft(col, scrollLeft) { | ||
let scrollSyncer = this.scrollSyncersByColumn[col]; | ||
if (scrollSyncer) { | ||
scrollSyncer.forceScrollLeft(scrollLeft); | ||
} | ||
} | ||
forceScrollTop(sectionI, scrollTop) { | ||
let scrollSyncer = this.scrollSyncersBySection[sectionI]; | ||
if (scrollSyncer) { | ||
scrollSyncer.forceScrollTop(scrollTop); | ||
} | ||
} | ||
_handleChunkEl(chunkEl, key) { | ||
let chunkConfig = this.getChunkConfigByIndex(parseInt(key, 10)); | ||
if (chunkConfig) { // null if section disappeared. bad, b/c won't null-set the elRef | ||
internal$1.setRef(chunkConfig.elRef, chunkEl); | ||
} | ||
} | ||
_handleScrollerEl(scrollerEl, key) { | ||
let chunkConfig = this.getChunkConfigByIndex(parseInt(key, 10)); | ||
if (chunkConfig) { // null if section disappeared. bad, b/c won't null-set the elRef | ||
internal$1.setRef(chunkConfig.scrollerElRef, scrollerEl); | ||
} | ||
} | ||
getDims() { | ||
let sectionCnt = this.props.sections.length; | ||
let chunksPerSection = sectionCnt ? this.props.sections[0].chunks.length : 0; | ||
return [sectionCnt, chunksPerSection]; | ||
} | ||
} | ||
ScrollGrid.addStateEquality({ | ||
shrinkWidths: internal$1.isArraysEqual, | ||
scrollerClientWidths: internal$1.isPropsEqual, | ||
scrollerClientHeights: internal$1.isPropsEqual, | ||
}); | ||
function sumNumbers(numbers) { | ||
let sum = 0; | ||
for (let n of numbers) { | ||
sum += n; | ||
} | ||
return sum; | ||
} | ||
function getRowInnerMaxHeight(rowEl) { | ||
let innerHeights = internal$1.findElements(rowEl, '.fc-scrollgrid-sync-inner').map(getElHeight); | ||
if (innerHeights.length) { | ||
return Math.max(...innerHeights); | ||
} | ||
return 0; | ||
} | ||
function getElHeight(el) { | ||
return el.offsetHeight; // better to deal with integers, for rounding, for PureComponent | ||
} | ||
function renderMacroColGroup(colGroupStats, shrinkWidths) { | ||
let children = colGroupStats.map((colGroupStat, i) => { | ||
let width = colGroupStat.width; | ||
if (width === 'shrink') { | ||
width = colGroupStat.totalColWidth + internal$1.sanitizeShrinkWidth(shrinkWidths[i]) + 1; // +1 for border :( | ||
} | ||
return ( // eslint-disable-next-line react/jsx-key | ||
preact.createElement("col", { style: { width } })); | ||
}); | ||
return preact.createElement('colgroup', {}, ...children); | ||
} | ||
function compileColGroupStat(colGroupConfig) { | ||
let totalColWidth = sumColProp(colGroupConfig.cols, 'width'); // excludes "shrink" | ||
let totalColMinWidth = sumColProp(colGroupConfig.cols, 'minWidth'); | ||
let hasShrinkCol = internal$1.hasShrinkWidth(colGroupConfig.cols); | ||
let allowXScrolling = colGroupConfig.width !== 'shrink' && Boolean(totalColWidth || totalColMinWidth || hasShrinkCol); | ||
return { | ||
hasShrinkCol, | ||
totalColWidth, | ||
totalColMinWidth, | ||
allowXScrolling, | ||
cols: colGroupConfig.cols, | ||
width: colGroupConfig.width, | ||
}; | ||
} | ||
function sumColProp(cols, propName) { | ||
let total = 0; | ||
for (let col of cols) { | ||
let val = col[propName]; | ||
if (typeof val === 'number') { | ||
total += val * (col.span || 1); | ||
} | ||
} | ||
return total; | ||
} | ||
const COL_GROUP_STAT_EQUALITY = { | ||
cols: internal$1.isColPropsEqual, | ||
}; | ||
function isColGroupStatsEqual(stat0, stat1) { | ||
return internal$1.compareObjs(stat0, stat1, COL_GROUP_STAT_EQUALITY); | ||
} | ||
// for memoizers... | ||
function initScrollSyncer(isVertical, ...scrollEls) { | ||
return new ScrollSyncer(isVertical, scrollEls); | ||
} | ||
function destroyScrollSyncer(scrollSyncer) { | ||
scrollSyncer.destroy(); | ||
} | ||
function initStickyScrolling(scrollEl, isRtl) { | ||
return new StickyScrolling(scrollEl, isRtl); | ||
} | ||
var plugin = core.createPlugin({ | ||
name: '@fullcalendar/scrollgrid', | ||
premiumReleaseDate: '2024-07-12', | ||
premiumReleaseDate: '2024-10-01', | ||
deps: [premiumCommonPlugin__default["default"]], | ||
scrollGridImpl: ScrollGrid, | ||
scrollerSyncerClass: ScrollerSyncer | ||
}); | ||
// new | ||
var internal = { | ||
__proto__: null, | ||
ScrollGrid: ScrollGrid | ||
ScrollerSyncer: ScrollerSyncer | ||
}; | ||
@@ -889,2 +222,2 @@ | ||
})({}, FullCalendar, FullCalendar.PremiumCommon, FullCalendar.Internal, FullCalendar.Preact); | ||
})({}, FullCalendar, FullCalendar.PremiumCommon, FullCalendar.Internal); |
/*! | ||
FullCalendar ScrollGrid Plugin v6.1.15 | ||
FullCalendar ScrollGrid Plugin v7.0.0-beta.0 | ||
Docs & License: https://fullcalendar.io/docs/premium | ||
(c) 2024 Adam Shaw | ||
*/ | ||
FullCalendar.ScrollGrid=function(e,t,l,s,i){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}var o=r(l);function n(e){let t=e.scrollLeft;if("rtl"===window.getComputedStyle(e).direction)switch(a()){case"negative":t*=-1;case"reverse":t=e.scrollWidth-t-e.clientWidth}return t}function h(e,t){if("rtl"===window.getComputedStyle(e).direction)switch(a()){case"reverse":t=e.scrollWidth-t;break;case"negative":t=-(e.scrollWidth-t)}e.scrollLeft=t}let c;function a(){return c||(c=function(){let e=document.createElement("div");e.style.position="absolute",e.style.top="-1000px",e.style.width="100px",e.style.height="100px",e.style.overflow="scroll",e.style.direction="rtl";let t,l=document.createElement("div");l.style.width="200px",l.style.height="200px",e.appendChild(l),document.body.appendChild(e),e.scrollLeft>0?t="positive":(e.scrollLeft=1,t=e.scrollLeft>0?"reverse":"negative");return s.removeElement(e),t}())}class d{constructor(e,t){this.scrollEl=e,this.isRtl=t,this.updateSize=()=>{let{scrollEl:e}=this,t=s.findElements(e,".fc-sticky");!function(e,t,l){e.forEach((e,i)=>{let r,{textAlign:o,elWidth:n,parentBound:h}=t[i],c=h.right-h.left;r="center"===o&&c>l?(l-n)/2:"",s.applyStyle(e,{left:r,right:r,top:0})})}(t,this.queryElGeoms(t),e.clientWidth)}}queryElGeoms(e){let{scrollEl:t,isRtl:l}=this,i=function(e){let t=e.getBoundingClientRect(),l=s.computeEdges(e);return{left:t.left+l.borderLeft+l.scrollbarLeft-n(e),top:t.top+l.borderTop-e.scrollTop}}(t),r=[];for(let t of e){let e=s.translateRect(s.computeInnerRect(t.parentNode,!0,!0),-i.left,-i.top),o=t.getBoundingClientRect(),n=window.getComputedStyle(t),h=window.getComputedStyle(t.parentNode).textAlign,c=null;"start"===h?h=l?"right":"left":"end"===h&&(h=l?"left":"right"),"sticky"!==n.position&&(c=s.translateRect(o,-i.left-(parseFloat(n.left)||0),-i.top-(parseFloat(n.top)||0))),r.push({parentBound:e,naturalBound:c,elWidth:o.width,elHeight:o.height,textAlign:h})}return r}}class u extends s.BaseComponent{constructor(){super(...arguments),this.elRef=i.createRef(),this.state={xScrollbarWidth:0,yScrollbarWidth:0},this.handleScroller=e=>{this.scroller=e,s.setRef(this.props.scrollerRef,e)},this.handleSizing=()=>{let{props:e}=this;"scroll-hidden"===e.overflowY&&this.setState({yScrollbarWidth:this.scroller.getYScrollbarWidth()}),"scroll-hidden"===e.overflowX&&this.setState({xScrollbarWidth:this.scroller.getXScrollbarWidth()})}}render(){let{props:e,state:t,context:l}=this,r=l.isRtl&&s.getIsRtlScrollbarOnLeft(),o=0,n=0,h=0,{overflowX:c,overflowY:a}=e;return e.forPrint&&(c="visible",a="visible"),"scroll-hidden"===c&&(h=t.xScrollbarWidth),"scroll-hidden"===a&&null!=t.yScrollbarWidth&&(r?o=t.yScrollbarWidth:n=t.yScrollbarWidth),i.createElement("div",{ref:this.elRef,className:"fc-scroller-harness"+(e.liquid?" fc-scroller-harness-liquid":"")},i.createElement(s.Scroller,{ref:this.handleScroller,elRef:this.props.scrollerElRef,overflowX:"scroll-hidden"===c?"scroll":c,overflowY:"scroll-hidden"===a?"scroll":a,overcomeLeft:o,overcomeRight:n,overcomeBottom:h,maxHeight:"number"==typeof e.maxHeight?e.maxHeight+("scroll-hidden"===c?t.xScrollbarWidth:0):"",liquid:e.liquid,liquidIsAbsolute:!0},e.children))}componentDidMount(){this.handleSizing(),this.context.addResizeHandler(this.handleSizing)}getSnapshotBeforeUpdate(e){return this.props.forPrint&&!e.forPrint?{simulateScrollLeft:this.scroller.el.scrollLeft}:{}}componentDidUpdate(e,t,l){const{props:i,scroller:{el:r}}=this;if(s.isPropsEqual(e,i)||this.handleSizing(),void 0!==l.simulateScrollLeft)r.style.left=-l.simulateScrollLeft+"px";else if(!i.forPrint&&e.forPrint){const e=-parseInt(r.style.left);r.style.left="",r.scrollLeft=e}}componentWillUnmount(){this.context.removeResizeHandler(this.handleSizing)}needsXScrolling(){return this.scroller.needsXScrolling()}needsYScrolling(){return this.scroller.needsYScrolling()}}const f="wheel mousewheel DomMouseScroll MozMousePixelScroll".split(" ");class p{constructor(e){this.el=e,this.emitter=new s.Emitter,this.isScrolling=!1,this.isTouching=!1,this.isRecentlyWheeled=!1,this.isRecentlyScrolled=!1,this.wheelWaiter=new s.DelayedRunner(this._handleWheelWaited.bind(this)),this.scrollWaiter=new s.DelayedRunner(this._handleScrollWaited.bind(this)),this.handleScroll=()=>{this.startScroll(),this.emitter.trigger("scroll",this.isRecentlyWheeled,this.isTouching),this.isRecentlyScrolled=!0,this.scrollWaiter.request(500)},this.handleWheel=()=>{this.isRecentlyWheeled=!0,this.wheelWaiter.request(500)},this.handleTouchStart=()=>{this.isTouching=!0},this.handleTouchEnd=()=>{this.isTouching=!1,this.isRecentlyScrolled||this.endScroll()},e.addEventListener("scroll",this.handleScroll),e.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),e.addEventListener("touchend",this.handleTouchEnd);for(let t of f)e.addEventListener(t,this.handleWheel)}destroy(){let{el:e}=this;e.removeEventListener("scroll",this.handleScroll),e.removeEventListener("touchstart",this.handleTouchStart,{passive:!0}),e.removeEventListener("touchend",this.handleTouchEnd);for(let t of f)e.removeEventListener(t,this.handleWheel)}startScroll(){this.isScrolling||(this.isScrolling=!0,this.emitter.trigger("scrollStart",this.isRecentlyWheeled,this.isTouching))}endScroll(){this.isScrolling&&(this.emitter.trigger("scrollEnd"),this.isScrolling=!1,this.isRecentlyScrolled=!0,this.isRecentlyWheeled=!1,this.scrollWaiter.clear(),this.wheelWaiter.clear())}_handleScrollWaited(){this.isRecentlyScrolled=!1,this.isTouching||this.endScroll()}_handleWheelWaited(){this.isRecentlyWheeled=!1}}class g{constructor(e,t){this.isVertical=e,this.scrollEls=t,this.isPaused=!1,this.scrollListeners=t.map(e=>this.bindScroller(e))}destroy(){for(let e of this.scrollListeners)e.destroy()}bindScroller(e){let{scrollEls:t,isVertical:l}=this,s=new p(e);return s.emitter.on("scroll",(s,i)=>{if(!this.isPaused&&((!this.masterEl||this.masterEl!==e&&(s||i))&&this.assignMaster(e),this.masterEl===e))for(let s of t)s!==e&&(l?s.scrollTop=e.scrollTop:s.scrollLeft=e.scrollLeft)}),s.emitter.on("scrollEnd",()=>{this.masterEl===e&&(this.masterEl=null)}),s}assignMaster(e){this.masterEl=e;for(let t of this.scrollListeners)t.el!==e&&t.endScroll()}forceScrollLeft(e){this.isPaused=!0;for(let t of this.scrollListeners)h(t.el,e);this.isPaused=!1}forceScrollTop(e){this.isPaused=!0;for(let t of this.scrollListeners)t.el.scrollTop=e;this.isPaused=!1}}s.config.SCROLLGRID_RESIZE_INTERVAL=500;class S extends s.BaseComponent{constructor(){super(...arguments),this.compileColGroupStats=s.memoizeArraylike(w,W),this.renderMicroColGroups=s.memoizeArraylike(s.renderMicroColGroup),this.clippedScrollerRefs=new s.RefMap,this.scrollerElRefs=new s.RefMap(this._handleScrollerEl.bind(this)),this.chunkElRefs=new s.RefMap(this._handleChunkEl.bind(this)),this.scrollSyncersBySection={},this.scrollSyncersByColumn={},this.rowUnstableMap=new Map,this.rowInnerMaxHeightMap=new Map,this.anyRowHeightsChanged=!1,this.recentSizingCnt=0,this.state={shrinkWidths:[],forceYScrollbars:!1,forceXScrollbars:!1,scrollerClientWidths:{},scrollerClientHeights:{},sectionRowMaxHeights:[]},this.handleSizing=(e,t)=>{if(!this.allowSizing())return;t||(this.anyRowHeightsChanged=!0);let l={};(e||!t&&!this.rowUnstableMap.size)&&(l.sectionRowMaxHeights=this.computeSectionRowMaxHeights()),this.setState(Object.assign(Object.assign({shrinkWidths:this.computeShrinkWidths()},this.computeScrollerDims()),l),()=>{this.rowUnstableMap.size||this.updateStickyScrolling()})},this.handleRowHeightChange=(e,t)=>{let{rowUnstableMap:l,rowInnerMaxHeightMap:s}=this;if(t){l.delete(e);let t=y(e);s.has(e)&&s.get(e)===t||(s.set(e,t),this.anyRowHeightsChanged=!0),!l.size&&this.anyRowHeightsChanged&&(this.anyRowHeightsChanged=!1,this.setState({sectionRowMaxHeights:this.computeSectionRowMaxHeights()}))}else l.set(e,!0)}}render(){let{props:e,state:t,context:l}=this,{shrinkWidths:r}=t,o=this.compileColGroupStats(e.colGroups.map(e=>[e])),n=this.renderMicroColGroups(o.map((e,t)=>[e.cols,r[t]])),h=s.getScrollGridClassNames(e.liquid,l);this.getDims();let c,a=e.sections,d=a.length,u=0,f=[],p=[],g=[];for(;u<d&&"header"===(c=a[u]).type;)f.push(this.renderSection(c,u,o,n,t.sectionRowMaxHeights,!0)),u+=1;for(;u<d&&"body"===(c=a[u]).type;)p.push(this.renderSection(c,u,o,n,t.sectionRowMaxHeights,!1)),u+=1;for(;u<d&&"footer"===(c=a[u]).type;)g.push(this.renderSection(c,u,o,n,t.sectionRowMaxHeights,!0)),u+=1;const S=!s.getCanVGrowWithinCell(),m={role:"rowgroup"};return i.createElement("table",{ref:e.elRef,role:"grid",className:h.join(" ")},function(e,t){let l=e.map((e,l)=>{let r=e.width;return"shrink"===r&&(r=e.totalColWidth+s.sanitizeShrinkWidth(t[l])+1),i.createElement("col",{style:{width:r}})});return i.createElement("colgroup",{},...l)}(o,r),Boolean(!S&&f.length)&&i.createElement("thead",m,...f),Boolean(!S&&p.length)&&i.createElement("tbody",m,...p),Boolean(!S&&g.length)&&i.createElement("tfoot",m,...g),S&&i.createElement("tbody",m,...f,...p,...g))}renderSection(e,t,l,r,o,n){return"outerContent"in e?i.createElement(i.Fragment,{key:e.key},e.outerContent):i.createElement("tr",{key:e.key,role:"presentation",className:s.getSectionClassNames(e,this.props.liquid).join(" ")},e.chunks.map((s,i)=>this.renderChunk(e,t,l[i],r[i],s,i,(o[t]||[])[i]||[],n)))}renderChunk(e,t,l,r,o,n,h,c){if("outerContent"in o)return i.createElement(i.Fragment,{key:o.key},o.outerContent);let{state:a}=this,{scrollerClientWidths:d,scrollerClientHeights:f}=a,[p,g]=this.getDims(),S=t*g+n,m=n===(!this.context.isRtl||s.getIsRtlScrollbarOnLeft()?g-1:0),y=t===p-1,R=y&&a.forceXScrollbars,w=m&&a.forceYScrollbars,C=l&&l.allowXScrolling,E=s.getAllowYScrolling(this.props,e),W=s.getSectionHasLiquidHeight(this.props,e),b=e.expandRows&&W,k=l&&l.totalColMinWidth||"",x=s.renderChunkContent(e,o,{tableColGroupNode:r,tableMinWidth:k,clientWidth:void 0!==d[S]?d[S]:null,clientHeight:void 0!==f[S]?f[S]:null,expandRows:b,syncRowHeights:Boolean(e.syncRowHeights),rowSyncHeights:h,reportRowHeightChange:this.handleRowHeightChange},c),M=R?y?"scroll":"scroll-hidden":C?y?"auto":"scroll-hidden":"hidden",v=w?m?"scroll":"scroll-hidden":E?m?"auto":"scroll-hidden":"hidden";return x=i.createElement(u,{ref:this.clippedScrollerRefs.createRef(S),scrollerElRef:this.scrollerElRefs.createRef(S),overflowX:M,overflowY:v,forPrint:this.props.forPrint,liquid:W,maxHeight:e.maxHeight},x),i.createElement(c?"th":"td",{key:o.key,ref:this.chunkElRefs.createRef(S),role:"presentation"},x)}componentDidMount(){this.getStickyScrolling=s.memoizeArraylike(x),this.getScrollSyncersBySection=s.memoizeHashlike(b.bind(this,!0),null,k),this.getScrollSyncersByColumn=s.memoizeHashlike(b.bind(this,!1),null,k),this.updateScrollSyncers(),this.handleSizing(!1),this.context.addResizeHandler(this.handleSizing)}componentDidUpdate(e,t){this.updateScrollSyncers(),this.handleSizing(!1,t.sectionRowMaxHeights!==this.state.sectionRowMaxHeights)}componentWillUnmount(){this.context.removeResizeHandler(this.handleSizing),this.destroyScrollSyncers()}allowSizing(){let e=new Date;return!this.lastSizingDate||e.valueOf()>this.lastSizingDate.valueOf()+s.config.SCROLLGRID_RESIZE_INTERVAL?(this.lastSizingDate=e,this.recentSizingCnt=0,!0):(this.recentSizingCnt+=1)<=10}computeShrinkWidths(){let e=this.compileColGroupStats(this.props.colGroups.map(e=>[e])),[t,l]=this.getDims(),i=t*l,r=[];return e.forEach((e,t)=>{if(e.hasShrinkCol){let e=this.chunkElRefs.collect(t,i,l);r[t]=s.computeShrinkWidth(e)}}),r}computeSectionRowMaxHeights(){let e=new Map,[t,l]=this.getDims(),i=[];for(let r=0;r<t;r+=1){let t=this.props.sections[r],o=[];if(t&&t.syncRowHeights){let i=[];for(let t=0;t<l;t+=1){let o=r*l+t,n=[],h=this.chunkElRefs.currentMap[o];n=h?s.findElements(h,".fc-scrollgrid-sync-table tr").map(t=>{let l=y(t);return e.set(t,l),l}):[],i.push(n)}let n=i[0].length,h=!0;for(let e=1;e<l;e+=1){if(!(t.chunks[e]&&void 0!==t.chunks[e].outerContent)&&i[e].length!==n){h=!1;break}}if(h){for(let e=0;e<l;e+=1)o.push([]);for(let e=0;e<n;e+=1){let t=[];for(let s=0;s<l;s+=1){let l=i[s][e];null!=l&&t.push(l)}let s=Math.max(...t);for(let e=0;e<l;e+=1)o[e].push(s)}}else{let e=[];for(let t=0;t<l;t+=1)e.push(m(i[t])+i[t].length);let t=Math.max(...e);for(let e=0;e<l;e+=1){let l=i[e].length,s=t-l,r=Math.floor(s/l),n=s-r*(l-1),h=[],c=0;for(c<l&&(h.push(n),c+=1);c<l;)h.push(r),c+=1;o.push(h)}}}i.push(o)}return this.rowInnerMaxHeightMap=e,i}computeScrollerDims(){let e=s.getScrollbarWidths(),[t,l]=this.getDims(),i=!this.context.isRtl||s.getIsRtlScrollbarOnLeft()?l-1:0,r=t-1,o=this.clippedScrollerRefs.currentMap,n=this.scrollerElRefs.currentMap,h=!1,c=!1,a={},d={};for(let e=0;e<t;e+=1){let t=o[e*l+i];if(t&&t.needsYScrolling()){h=!0;break}}for(let e=0;e<l;e+=1){let t=o[r*l+e];if(t&&t.needsXScrolling()){c=!0;break}}for(let s=0;s<t;s+=1)for(let t=0;t<l;t+=1){let o=s*l+t,u=n[o];if(u){let l=u.parentNode;a[o]=Math.floor(l.getBoundingClientRect().width-(t===i&&h?e.y:0)),d[o]=Math.floor(l.getBoundingClientRect().height-(s===r&&c?e.x:0))}}return{forceYScrollbars:h,forceXScrollbars:c,scrollerClientWidths:a,scrollerClientHeights:d}}updateStickyScrolling(){let{isRtl:e}=this.context,t=this.scrollerElRefs.getAll().map(t=>[t,e]);this.getStickyScrolling(t).forEach(e=>e.updateSize())}updateScrollSyncers(){let[e,t]=this.getDims(),l=e*t,i={},r={},o=this.scrollerElRefs.currentMap;for(let l=0;l<e;l+=1){let e=l*t,r=e+t;i[l]=s.collectFromHash(o,e,r,1)}for(let e=0;e<t;e+=1)r[e]=this.scrollerElRefs.collect(e,l,t);this.scrollSyncersBySection=this.getScrollSyncersBySection(i),this.scrollSyncersByColumn=this.getScrollSyncersByColumn(r)}destroyScrollSyncers(){s.mapHash(this.scrollSyncersBySection,k),s.mapHash(this.scrollSyncersByColumn,k)}getChunkConfigByIndex(e){let t=this.getDims()[1],l=Math.floor(e/t),s=e%t,i=this.props.sections[l];return i&&i.chunks[s]}forceScrollLeft(e,t){let l=this.scrollSyncersByColumn[e];l&&l.forceScrollLeft(t)}forceScrollTop(e,t){let l=this.scrollSyncersBySection[e];l&&l.forceScrollTop(t)}_handleChunkEl(e,t){let l=this.getChunkConfigByIndex(parseInt(t,10));l&&s.setRef(l.elRef,e)}_handleScrollerEl(e,t){let l=this.getChunkConfigByIndex(parseInt(t,10));l&&s.setRef(l.scrollerElRef,e)}getDims(){let e=this.props.sections.length;return[e,e?this.props.sections[0].chunks.length:0]}}function m(e){let t=0;for(let l of e)t+=l;return t}function y(e){let t=s.findElements(e,".fc-scrollgrid-sync-inner").map(R);return t.length?Math.max(...t):0}function R(e){return e.offsetHeight}function w(e){let t=C(e.cols,"width"),l=C(e.cols,"minWidth"),i=s.hasShrinkWidth(e.cols);return{hasShrinkCol:i,totalColWidth:t,totalColMinWidth:l,allowXScrolling:"shrink"!==e.width&&Boolean(t||l||i),cols:e.cols,width:e.width}}function C(e,t){let l=0;for(let s of e){let e=s[t];"number"==typeof e&&(l+=e*(s.span||1))}return l}S.addStateEquality({shrinkWidths:s.isArraysEqual,scrollerClientWidths:s.isPropsEqual,scrollerClientHeights:s.isPropsEqual});const E={cols:s.isColPropsEqual};function W(e,t){return s.compareObjs(e,t,E)}function b(e,...t){return new g(e,t)}function k(e){e.destroy()}function x(e,t){return new d(e,t)}var M=t.createPlugin({name:"@fullcalendar/scrollgrid",premiumReleaseDate:"2024-07-12",deps:[o.default],scrollGridImpl:S}),v={__proto__:null,ScrollGrid:S};return t.globalPlugins.push(M),e.Internal=v,e.default=M,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,FullCalendar.PremiumCommon,FullCalendar.Internal,FullCalendar.Preact); | ||
FullCalendar.ScrollGrid=function(e,l,s,t){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}var r=i(s);const o="wheel mousewheel DomMouseScroll MozMousePixelScroll".split(" ");class h{constructor(e){this.el=e,this.emitter=new t.Emitter,this.isScrolling=!1,this.isTouching=!1,this.isRecentlyWheeled=!1,this.isRecentlyScrolled=!1,this.wheelWaiter=new t.DelayedRunner(this._handleWheelWaited.bind(this)),this.scrollWaiter=new t.DelayedRunner(this._handleScrollWaited.bind(this)),this.handleScroll=()=>{this.startScroll(),this.emitter.trigger("scroll",this.isRecentlyWheeled,this.isTouching),this.isRecentlyScrolled=!0,this.scrollWaiter.request(500)},this.handleWheel=()=>{this.isRecentlyWheeled=!0,this.wheelWaiter.request(500)},this.handleTouchStart=()=>{this.isTouching=!0},this.handleTouchEnd=()=>{this.isTouching=!1,this.isRecentlyScrolled||this.endScroll()},e.addEventListener("scroll",this.handleScroll),e.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),e.addEventListener("touchend",this.handleTouchEnd);for(let l of o)e.addEventListener(l,this.handleWheel,{passive:!0})}destroy(){let{el:e}=this;e.removeEventListener("scroll",this.handleScroll),e.removeEventListener("touchstart",this.handleTouchStart,{passive:!0}),e.removeEventListener("touchend",this.handleTouchEnd);for(let l of o)e.removeEventListener(l,this.handleWheel,{passive:!0})}startScroll(){this.isScrolling||(this.isScrolling=!0,this.emitter.trigger("scrollStart",this.isRecentlyWheeled,this.isTouching))}endScroll(){this.isScrolling&&(this.emitter.trigger("scrollEnd"),this.isScrolling=!1,this.isRecentlyScrolled=!0,this.isRecentlyWheeled=!1,this.scrollWaiter.clear(),this.wheelWaiter.clear())}_handleScrollWaited(){this.isRecentlyScrolled=!1,this.isTouching||this.endScroll()}_handleWheelWaited(){this.isRecentlyWheeled=!1}}class n{constructor(e=!1){this.isHorizontal=e,this.scrollers=[],this.scrollListeners=[],this.isPaused=!1}handleChildren(e,l){if(!t.isArraysEqual(this.scrollers,e)){this.destroy(),this.scrollers=e;const l=[];for(const s of e)s&&l.push(this.bindScroller(s.el));this.scrollListeners=l}this.isRtl=l}destroy(){for(let e of this.scrollListeners)e.destroy()}get x(){const{scrollListeners:e,masterEl:l,isRtl:s}=this,i=l||(e.length?e[0].el:void 0);return t.getNormalizedScrollX(i,s)}get y(){const{scrollListeners:e,masterEl:l}=this;return(l||(e.length?e[0].el:void 0)).scrollTop}scrollTo({x:e,y:l}){this.isPaused=!0;const{scrollListeners:s,isRtl:i}=this;if(null!=l)for(let e of s)e.el.scrollTop=l;if(null!=e)for(let l of s)t.setNormalizedScrollX(l.el,i,e);this.isPaused=!1}bindScroller(e){let{isHorizontal:l}=this,s=new h(e);return s.emitter.on("scroll",(s,t)=>{if(!this.isPaused&&((!this.masterEl||this.masterEl!==e&&(s||t))&&this.assignMaster(e),this.masterEl===e))for(let s of this.scrollListeners){const t=s.el;t!==e&&(l?t.scrollLeft=e.scrollLeft:t.scrollTop=e.scrollTop)}}),s.emitter.on("scrollEnd",()=>{this.masterEl===e&&(this.masterEl=null)}),s}assignMaster(e){this.masterEl=e;for(let l of this.scrollListeners)l.el!==e&&l.endScroll()}}var c=l.createPlugin({name:"@fullcalendar/scrollgrid",premiumReleaseDate:"2024-10-01",deps:[r.default],scrollerSyncerClass:n}),a={__proto__:null,ScrollerSyncer:n};return l.globalPlugins.push(c),e.Internal=a,e.default=c,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,FullCalendar.PremiumCommon,FullCalendar.Internal); |
import { createPlugin } from '@fullcalendar/core/index.js'; | ||
import premiumCommonPlugin from '@fullcalendar/premium-common/index.js'; | ||
import { ScrollGrid } from './internal.js'; | ||
import { ScrollerSyncer } from './internal.js'; | ||
import '@fullcalendar/core/internal.js'; | ||
import '@fullcalendar/core/preact.js'; | ||
var index = createPlugin({ | ||
name: '@fullcalendar/scrollgrid', | ||
premiumReleaseDate: '2024-07-12', | ||
premiumReleaseDate: '2024-10-01', | ||
deps: [premiumCommonPlugin], | ||
scrollGridImpl: ScrollGrid, | ||
scrollerSyncerClass: ScrollerSyncer | ||
}); | ||
export { index as default }; |
@@ -1,74 +0,44 @@ | ||
import { CssDimValue } from '@fullcalendar/core'; | ||
import { BaseComponent, ScrollGridProps, ScrollGridSectionConfig, ScrollGridChunkConfig, ColProps } from '@fullcalendar/core/internal'; | ||
import { VNode } from '@fullcalendar/core/preact'; | ||
import { Emitter, ScrollerSyncerInterface, Scroller } from '@fullcalendar/core/internal'; | ||
interface ScrollGridState { | ||
shrinkWidths: number[]; | ||
forceYScrollbars: boolean; | ||
forceXScrollbars: boolean; | ||
scrollerClientWidths: { | ||
[index: string]: number; | ||
}; | ||
scrollerClientHeights: { | ||
[index: string]: number; | ||
}; | ||
sectionRowMaxHeights: number[][][]; | ||
declare class ScrollListener { | ||
el: HTMLElement; | ||
emitter: Emitter<any>; | ||
private isScrolling; | ||
private isTouching; | ||
private isRecentlyWheeled; | ||
private isRecentlyScrolled; | ||
private wheelWaiter; | ||
private scrollWaiter; | ||
constructor(el: HTMLElement); | ||
destroy(): void; | ||
private startScroll; | ||
endScroll(): void; | ||
handleScroll: () => void; | ||
_handleScrollWaited(): void; | ||
handleWheel: () => void; | ||
_handleWheelWaited(): void; | ||
handleTouchStart: () => void; | ||
handleTouchEnd: () => void; | ||
} | ||
interface ColGroupStat { | ||
hasShrinkCol: boolean; | ||
totalColWidth: number; | ||
totalColMinWidth: number; | ||
allowXScrolling: boolean; | ||
width?: CssDimValue; | ||
cols: ColProps[]; | ||
declare class ScrollerSyncer implements ScrollerSyncerInterface { | ||
private isHorizontal; | ||
private scrollers; | ||
private scrollListeners; | ||
private masterEl; | ||
private isPaused; | ||
private isRtl; | ||
constructor(isHorizontal?: boolean); | ||
handleChildren(scrollers: Scroller[], isRtl: boolean): void; | ||
destroy(): void; | ||
get x(): number; | ||
get y(): number; | ||
scrollTo({ x, y }: { | ||
x?: number; | ||
y?: number; | ||
}): void; | ||
bindScroller(el: HTMLElement): ScrollListener; | ||
assignMaster(el: HTMLElement): void; | ||
} | ||
declare class ScrollGrid extends BaseComponent<ScrollGridProps, ScrollGridState> { | ||
private compileColGroupStats; | ||
private renderMicroColGroups; | ||
private clippedScrollerRefs; | ||
private scrollerElRefs; | ||
private chunkElRefs; | ||
private getStickyScrolling; | ||
private getScrollSyncersBySection; | ||
private getScrollSyncersByColumn; | ||
private scrollSyncersBySection; | ||
private scrollSyncersByColumn; | ||
private rowUnstableMap; | ||
private rowInnerMaxHeightMap; | ||
private anyRowHeightsChanged; | ||
private lastSizingDate; | ||
private recentSizingCnt; | ||
state: ScrollGridState; | ||
render(): VNode; | ||
renderSection(sectionConfig: ScrollGridSectionConfig, sectionIndex: number, colGroupStats: ColGroupStat[], microColGroupNodes: VNode[], sectionRowMaxHeights: number[][][], isHeader: boolean): VNode; | ||
renderChunk(sectionConfig: ScrollGridSectionConfig, sectionIndex: number, colGroupStat: ColGroupStat | undefined, microColGroupNode: VNode | undefined, chunkConfig: ScrollGridChunkConfig, chunkIndex: number, rowHeights: number[], isHeader: boolean): VNode; | ||
componentDidMount(): void; | ||
componentDidUpdate(prevProps: ScrollGridProps, prevState: ScrollGridState): void; | ||
componentWillUnmount(): void; | ||
handleSizing: (isForcedResize: boolean, sectionRowMaxHeightsChanged?: boolean) => void; | ||
allowSizing(): boolean; | ||
handleRowHeightChange: (rowEl: HTMLTableRowElement, isStable: boolean) => void; | ||
computeShrinkWidths(): number[]; | ||
private computeSectionRowMaxHeights; | ||
computeScrollerDims(): { | ||
forceYScrollbars: boolean; | ||
forceXScrollbars: boolean; | ||
scrollerClientWidths: { | ||
[index: string]: number; | ||
}; | ||
scrollerClientHeights: { | ||
[index: string]: number; | ||
}; | ||
}; | ||
updateStickyScrolling(): void; | ||
updateScrollSyncers(): void; | ||
destroyScrollSyncers(): void; | ||
getChunkConfigByIndex(index: number): ScrollGridChunkConfig; | ||
forceScrollLeft(col: number, scrollLeft: number): void; | ||
forceScrollTop(sectionI: number, scrollTop: number): void; | ||
_handleChunkEl(chunkEl: HTMLTableCellElement | null, key: string): void; | ||
_handleScrollerEl(scrollerEl: HTMLElement | null, key: string): void; | ||
getDims(): number[]; | ||
} | ||
export { ScrollGrid }; | ||
export { ScrollerSyncer }; |
778
internal.js
@@ -1,235 +0,3 @@ | ||
import { computeEdges, removeElement, findElements, translateRect, computeInnerRect, applyStyle, BaseComponent, setRef, getIsRtlScrollbarOnLeft, Scroller, isPropsEqual, Emitter, DelayedRunner, config, memoizeArraylike, renderMicroColGroup, RefMap, getScrollGridClassNames, getCanVGrowWithinCell, getSectionClassNames, getAllowYScrolling, getSectionHasLiquidHeight, renderChunkContent, memoizeHashlike, computeShrinkWidth, getScrollbarWidths, collectFromHash, mapHash, isArraysEqual, sanitizeShrinkWidth, hasShrinkWidth, compareObjs, isColPropsEqual } from '@fullcalendar/core/internal.js'; | ||
import { createRef, createElement, Fragment } from '@fullcalendar/core/preact.js'; | ||
import { Emitter, DelayedRunner, isArraysEqual, getNormalizedScrollX, setNormalizedScrollX } from '@fullcalendar/core/internal.js'; | ||
// TODO: assume the el has no borders? | ||
function getScrollCanvasOrigin(scrollEl) { | ||
let rect = scrollEl.getBoundingClientRect(); | ||
let edges = computeEdges(scrollEl); // TODO: pass in isRtl? | ||
return { | ||
left: rect.left + edges.borderLeft + edges.scrollbarLeft - getScrollFromLeftEdge(scrollEl), | ||
top: rect.top + edges.borderTop - scrollEl.scrollTop, | ||
}; | ||
} | ||
function getScrollFromLeftEdge(el) { | ||
let scrollLeft = el.scrollLeft; | ||
let computedStyles = window.getComputedStyle(el); // TODO: pass in isRtl instead? | ||
if (computedStyles.direction === 'rtl') { | ||
switch (getRtlScrollSystem()) { | ||
case 'negative': | ||
scrollLeft *= -1; // convert to 'reverse'. fall through... | ||
case 'reverse': // scrollLeft is distance between scrollframe's right edge scrollcanvas's right edge | ||
scrollLeft = el.scrollWidth - scrollLeft - el.clientWidth; | ||
} | ||
} | ||
return scrollLeft; | ||
} | ||
function setScrollFromLeftEdge(el, scrollLeft) { | ||
let computedStyles = window.getComputedStyle(el); // TODO: pass in isRtl instead? | ||
if (computedStyles.direction === 'rtl') { | ||
switch (getRtlScrollSystem()) { | ||
case 'reverse': | ||
scrollLeft = el.scrollWidth - scrollLeft; | ||
break; | ||
case 'negative': | ||
scrollLeft = -(el.scrollWidth - scrollLeft); | ||
break; | ||
} | ||
} | ||
el.scrollLeft = scrollLeft; | ||
} | ||
// Horizontal Scroll System Detection | ||
// ---------------------------------------------------------------------------------------------- | ||
let _rtlScrollSystem; | ||
function getRtlScrollSystem() { | ||
return _rtlScrollSystem || (_rtlScrollSystem = detectRtlScrollSystem()); | ||
} | ||
function detectRtlScrollSystem() { | ||
let el = document.createElement('div'); | ||
el.style.position = 'absolute'; | ||
el.style.top = '-1000px'; | ||
el.style.width = '100px'; // must be at least the side of scrollbars or you get inaccurate values (#7335) | ||
el.style.height = '100px'; // " | ||
el.style.overflow = 'scroll'; | ||
el.style.direction = 'rtl'; | ||
let innerEl = document.createElement('div'); | ||
innerEl.style.width = '200px'; | ||
innerEl.style.height = '200px'; | ||
el.appendChild(innerEl); | ||
document.body.appendChild(el); | ||
let system; | ||
if (el.scrollLeft > 0) { | ||
system = 'positive'; // scroll is a positive number from the left edge | ||
} | ||
else { | ||
el.scrollLeft = 1; | ||
if (el.scrollLeft > 0) { | ||
system = 'reverse'; // scroll is a positive number from the right edge | ||
} | ||
else { | ||
system = 'negative'; // scroll is a negative number from the right edge | ||
} | ||
} | ||
removeElement(el); | ||
return system; | ||
} | ||
const STICKY_SELECTOR = '.fc-sticky'; | ||
/* | ||
Goes beyond mere position:sticky, allows horizontal centering | ||
REQUIREMENT: fc-sticky elements, if the fc-sticky className is taken away, should NOT have relative or absolute positioning. | ||
This is because we attach the coords with JS, and the VDOM might take away the fc-sticky class but doesn't know kill the positioning. | ||
TODO: don't query text-align:center. isn't compatible with flexbox centering. instead, check natural X coord within parent container | ||
*/ | ||
class StickyScrolling { | ||
constructor(scrollEl, isRtl) { | ||
this.scrollEl = scrollEl; | ||
this.isRtl = isRtl; | ||
this.updateSize = () => { | ||
let { scrollEl } = this; | ||
let els = findElements(scrollEl, STICKY_SELECTOR); | ||
let elGeoms = this.queryElGeoms(els); | ||
let viewportWidth = scrollEl.clientWidth; | ||
assignStickyPositions(els, elGeoms, viewportWidth); | ||
}; | ||
} | ||
queryElGeoms(els) { | ||
let { scrollEl, isRtl } = this; | ||
let canvasOrigin = getScrollCanvasOrigin(scrollEl); | ||
let elGeoms = []; | ||
for (let el of els) { | ||
let parentBound = translateRect(computeInnerRect(el.parentNode, true, true), // weird way to call this!!! | ||
-canvasOrigin.left, -canvasOrigin.top); | ||
let elRect = el.getBoundingClientRect(); | ||
let computedStyles = window.getComputedStyle(el); | ||
let textAlign = window.getComputedStyle(el.parentNode).textAlign; // ask the parent | ||
let naturalBound = null; | ||
if (textAlign === 'start') { | ||
textAlign = isRtl ? 'right' : 'left'; | ||
} | ||
else if (textAlign === 'end') { | ||
textAlign = isRtl ? 'left' : 'right'; | ||
} | ||
if (computedStyles.position !== 'sticky') { | ||
naturalBound = translateRect(elRect, -canvasOrigin.left - (parseFloat(computedStyles.left) || 0), // could be 'auto' | ||
-canvasOrigin.top - (parseFloat(computedStyles.top) || 0)); | ||
} | ||
elGeoms.push({ | ||
parentBound, | ||
naturalBound, | ||
elWidth: elRect.width, | ||
elHeight: elRect.height, | ||
textAlign, | ||
}); | ||
} | ||
return elGeoms; | ||
} | ||
} | ||
function assignStickyPositions(els, elGeoms, viewportWidth) { | ||
els.forEach((el, i) => { | ||
let { textAlign, elWidth, parentBound } = elGeoms[i]; | ||
let parentWidth = parentBound.right - parentBound.left; | ||
let left; | ||
if (textAlign === 'center' && | ||
parentWidth > viewportWidth) { | ||
left = (viewportWidth - elWidth) / 2; | ||
} | ||
else { // if parent container can be completely in view, we don't need stickiness | ||
left = ''; | ||
} | ||
applyStyle(el, { | ||
left, | ||
right: left, | ||
top: 0, | ||
}); | ||
}); | ||
} | ||
class ClippedScroller extends BaseComponent { | ||
constructor() { | ||
super(...arguments); | ||
this.elRef = createRef(); | ||
this.state = { | ||
xScrollbarWidth: 0, | ||
yScrollbarWidth: 0, | ||
}; | ||
this.handleScroller = (scroller) => { | ||
this.scroller = scroller; | ||
setRef(this.props.scrollerRef, scroller); | ||
}; | ||
this.handleSizing = () => { | ||
let { props } = this; | ||
if (props.overflowY === 'scroll-hidden') { | ||
this.setState({ yScrollbarWidth: this.scroller.getYScrollbarWidth() }); | ||
} | ||
if (props.overflowX === 'scroll-hidden') { | ||
this.setState({ xScrollbarWidth: this.scroller.getXScrollbarWidth() }); | ||
} | ||
}; | ||
} | ||
render() { | ||
let { props, state, context } = this; | ||
let isScrollbarOnLeft = context.isRtl && getIsRtlScrollbarOnLeft(); | ||
let overcomeLeft = 0; | ||
let overcomeRight = 0; | ||
let overcomeBottom = 0; | ||
let { overflowX, overflowY } = props; | ||
if (props.forPrint) { | ||
overflowX = 'visible'; | ||
overflowY = 'visible'; | ||
} | ||
if (overflowX === 'scroll-hidden') { | ||
overcomeBottom = state.xScrollbarWidth; | ||
} | ||
if (overflowY === 'scroll-hidden') { | ||
if (state.yScrollbarWidth != null) { | ||
if (isScrollbarOnLeft) { | ||
overcomeLeft = state.yScrollbarWidth; | ||
} | ||
else { | ||
overcomeRight = state.yScrollbarWidth; | ||
} | ||
} | ||
} | ||
return (createElement("div", { ref: this.elRef, className: 'fc-scroller-harness' + (props.liquid ? ' fc-scroller-harness-liquid' : '') }, | ||
createElement(Scroller, { ref: this.handleScroller, elRef: this.props.scrollerElRef, overflowX: overflowX === 'scroll-hidden' ? 'scroll' : overflowX, overflowY: overflowY === 'scroll-hidden' ? 'scroll' : overflowY, overcomeLeft: overcomeLeft, overcomeRight: overcomeRight, overcomeBottom: overcomeBottom, maxHeight: typeof props.maxHeight === 'number' | ||
? (props.maxHeight + (overflowX === 'scroll-hidden' ? state.xScrollbarWidth : 0)) | ||
: '', liquid: props.liquid, liquidIsAbsolute: true }, props.children))); | ||
} | ||
componentDidMount() { | ||
this.handleSizing(); | ||
this.context.addResizeHandler(this.handleSizing); | ||
} | ||
getSnapshotBeforeUpdate(prevProps) { | ||
if (this.props.forPrint && !prevProps.forPrint) { | ||
return { simulateScrollLeft: this.scroller.el.scrollLeft }; | ||
} | ||
return {}; | ||
} | ||
componentDidUpdate(prevProps, prevState, snapshot) { | ||
const { props, scroller: { el: scrollerEl } } = this; | ||
if (!isPropsEqual(prevProps, props)) { // an external change? | ||
this.handleSizing(); | ||
} | ||
if (snapshot.simulateScrollLeft !== undefined) { | ||
scrollerEl.style.left = -snapshot.simulateScrollLeft + 'px'; | ||
} | ||
else if (!props.forPrint && prevProps.forPrint) { | ||
const restoredScrollLeft = -parseInt(scrollerEl.style.left); | ||
scrollerEl.style.left = ''; | ||
scrollerEl.scrollLeft = restoredScrollLeft; | ||
} | ||
} | ||
componentWillUnmount() { | ||
this.context.removeResizeHandler(this.handleSizing); | ||
} | ||
needsXScrolling() { | ||
return this.scroller.needsXScrolling(); | ||
} | ||
needsYScrolling() { | ||
return this.scroller.needsYScrolling(); | ||
} | ||
} | ||
const WHEEL_EVENT_NAMES = 'wheel mousewheel DomMouseScroll MozMousePixelScroll'.split(' '); | ||
@@ -278,3 +46,3 @@ /* | ||
for (let eventName of WHEEL_EVENT_NAMES) { | ||
el.addEventListener(eventName, this.handleWheel); | ||
el.addEventListener(eventName, this.handleWheel, { passive: true }); | ||
} | ||
@@ -288,3 +56,3 @@ } | ||
for (let eventName of WHEEL_EVENT_NAMES) { | ||
el.removeEventListener(eventName, this.handleWheel); | ||
el.removeEventListener(eventName, this.handleWheel, { passive: true }); | ||
} | ||
@@ -323,9 +91,23 @@ } | ||
class ScrollSyncer { | ||
constructor(isVertical, scrollEls) { | ||
this.isVertical = isVertical; | ||
this.scrollEls = scrollEls; | ||
class ScrollerSyncer { | ||
constructor(isHorizontal = false) { | ||
this.isHorizontal = isHorizontal; | ||
this.scrollers = []; // TODO: move away from requiring Scroller | ||
this.scrollListeners = []; | ||
this.isPaused = false; | ||
this.scrollListeners = scrollEls.map((el) => this.bindScroller(el)); | ||
} | ||
handleChildren(scrollers, isRtl) { | ||
if (!isArraysEqual(this.scrollers, scrollers)) { | ||
this.destroy(); | ||
this.scrollers = scrollers; | ||
const scrollListeners = []; | ||
for (const scroller of scrollers) { | ||
if (scroller) { // could be null | ||
scrollListeners.push(this.bindScroller(scroller.el)); | ||
} | ||
} | ||
this.scrollListeners = scrollListeners; | ||
} | ||
this.isRtl = isRtl; | ||
} | ||
destroy() { | ||
@@ -336,4 +118,29 @@ for (let scrollListener of this.scrollListeners) { | ||
} | ||
get x() { | ||
const { scrollListeners, masterEl, isRtl } = this; | ||
const el = masterEl || (scrollListeners.length ? scrollListeners[0].el : undefined); | ||
return getNormalizedScrollX(el, isRtl); | ||
} | ||
get y() { | ||
const { scrollListeners, masterEl } = this; | ||
const el = masterEl || (scrollListeners.length ? scrollListeners[0].el : undefined); | ||
return el.scrollTop; | ||
} | ||
scrollTo({ x, y }) { | ||
this.isPaused = true; | ||
const { scrollListeners, isRtl } = this; | ||
if (y != null) { | ||
for (let scrollListener of scrollListeners) { | ||
scrollListener.el.scrollTop = y; | ||
} | ||
} | ||
if (x != null) { | ||
for (let scrollListener of scrollListeners) { | ||
setNormalizedScrollX(scrollListener.el, isRtl, x); | ||
} | ||
} | ||
this.isPaused = false; | ||
} | ||
bindScroller(el) { | ||
let { scrollEls, isVertical } = this; | ||
let { isHorizontal } = this; | ||
let scrollListener = new ScrollListener(el); | ||
@@ -346,9 +153,10 @@ const onScroll = (isWheel, isTouch) => { | ||
if (this.masterEl === el) { // dealing with current | ||
for (let otherEl of scrollEls) { | ||
for (let scrollListener of this.scrollListeners) { | ||
const otherEl = scrollListener.el; | ||
if (otherEl !== el) { | ||
if (isVertical) { | ||
otherEl.scrollTop = el.scrollTop; | ||
if (isHorizontal) { | ||
otherEl.scrollLeft = el.scrollLeft; | ||
} | ||
else { | ||
otherEl.scrollLeft = el.scrollLeft; | ||
otherEl.scrollTop = el.scrollTop; | ||
} | ||
@@ -377,482 +185,4 @@ } | ||
} | ||
/* | ||
will normalize the scrollLeft value | ||
*/ | ||
forceScrollLeft(scrollLeft) { | ||
this.isPaused = true; | ||
for (let listener of this.scrollListeners) { | ||
setScrollFromLeftEdge(listener.el, scrollLeft); | ||
} | ||
this.isPaused = false; | ||
} | ||
forceScrollTop(top) { | ||
this.isPaused = true; | ||
for (let listener of this.scrollListeners) { | ||
listener.el.scrollTop = top; | ||
} | ||
this.isPaused = false; | ||
} | ||
} | ||
config.SCROLLGRID_RESIZE_INTERVAL = 500; | ||
/* | ||
TODO: make <ScrollGridSection> subcomponent | ||
NOTE: doesn't support collapsibleWidth (which is sortof a hack anyway) | ||
*/ | ||
class ScrollGrid extends BaseComponent { | ||
constructor() { | ||
super(...arguments); | ||
this.compileColGroupStats = memoizeArraylike(compileColGroupStat, isColGroupStatsEqual); | ||
this.renderMicroColGroups = memoizeArraylike(renderMicroColGroup); // yucky to memoize VNodes, but much more efficient for consumers | ||
this.clippedScrollerRefs = new RefMap(); | ||
// doesn't hold non-scrolling els used just for padding | ||
this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this)); | ||
this.chunkElRefs = new RefMap(this._handleChunkEl.bind(this)); | ||
this.scrollSyncersBySection = {}; | ||
this.scrollSyncersByColumn = {}; | ||
// for row-height-syncing | ||
this.rowUnstableMap = new Map(); // no need to groom. always self-cancels | ||
this.rowInnerMaxHeightMap = new Map(); | ||
this.anyRowHeightsChanged = false; | ||
this.recentSizingCnt = 0; | ||
this.state = { | ||
shrinkWidths: [], | ||
forceYScrollbars: false, | ||
forceXScrollbars: false, | ||
scrollerClientWidths: {}, | ||
scrollerClientHeights: {}, | ||
sectionRowMaxHeights: [], | ||
}; | ||
this.handleSizing = (isForcedResize, sectionRowMaxHeightsChanged) => { | ||
if (!this.allowSizing()) { | ||
return; | ||
} | ||
if (!sectionRowMaxHeightsChanged) { // something else changed, probably external | ||
this.anyRowHeightsChanged = true; | ||
} | ||
let otherState = {}; | ||
// if reacting to self-change of sectionRowMaxHeightsChanged, or not stable, don't do anything | ||
if (isForcedResize || (!sectionRowMaxHeightsChanged && !this.rowUnstableMap.size)) { | ||
otherState.sectionRowMaxHeights = this.computeSectionRowMaxHeights(); | ||
} | ||
this.setState(Object.assign(Object.assign({ shrinkWidths: this.computeShrinkWidths() }, this.computeScrollerDims()), otherState), () => { | ||
if (!this.rowUnstableMap.size) { | ||
this.updateStickyScrolling(); // needs to happen AFTER final positioning committed to DOM | ||
} | ||
}); | ||
}; | ||
this.handleRowHeightChange = (rowEl, isStable) => { | ||
let { rowUnstableMap, rowInnerMaxHeightMap } = this; | ||
if (!isStable) { | ||
rowUnstableMap.set(rowEl, true); | ||
} | ||
else { | ||
rowUnstableMap.delete(rowEl); | ||
let innerMaxHeight = getRowInnerMaxHeight(rowEl); | ||
if (!rowInnerMaxHeightMap.has(rowEl) || rowInnerMaxHeightMap.get(rowEl) !== innerMaxHeight) { | ||
rowInnerMaxHeightMap.set(rowEl, innerMaxHeight); | ||
this.anyRowHeightsChanged = true; | ||
} | ||
if (!rowUnstableMap.size && this.anyRowHeightsChanged) { | ||
this.anyRowHeightsChanged = false; | ||
this.setState({ | ||
sectionRowMaxHeights: this.computeSectionRowMaxHeights(), | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
render() { | ||
let { props, state, context } = this; | ||
let { shrinkWidths } = state; | ||
let colGroupStats = this.compileColGroupStats(props.colGroups.map((colGroup) => [colGroup])); | ||
let microColGroupNodes = this.renderMicroColGroups(colGroupStats.map((stat, i) => [stat.cols, shrinkWidths[i]])); | ||
let classNames = getScrollGridClassNames(props.liquid, context); | ||
this.getDims(); | ||
// TODO: make DRY | ||
let sectionConfigs = props.sections; | ||
let configCnt = sectionConfigs.length; | ||
let configI = 0; | ||
let currentConfig; | ||
let headSectionNodes = []; | ||
let bodySectionNodes = []; | ||
let footSectionNodes = []; | ||
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') { | ||
headSectionNodes.push(this.renderSection(currentConfig, configI, colGroupStats, microColGroupNodes, state.sectionRowMaxHeights, true)); | ||
configI += 1; | ||
} | ||
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') { | ||
bodySectionNodes.push(this.renderSection(currentConfig, configI, colGroupStats, microColGroupNodes, state.sectionRowMaxHeights, false)); | ||
configI += 1; | ||
} | ||
while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') { | ||
footSectionNodes.push(this.renderSection(currentConfig, configI, colGroupStats, microColGroupNodes, state.sectionRowMaxHeights, true)); | ||
configI += 1; | ||
} | ||
const isBuggy = !getCanVGrowWithinCell(); // see NOTE in SimpleScrollGrid | ||
const roleAttrs = { role: 'rowgroup' }; | ||
return createElement('table', { | ||
ref: props.elRef, | ||
role: 'grid', | ||
className: classNames.join(' '), | ||
}, renderMacroColGroup(colGroupStats, shrinkWidths), Boolean(!isBuggy && headSectionNodes.length) && createElement('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && createElement('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && createElement('tfoot', roleAttrs, ...footSectionNodes), isBuggy && createElement('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes)); | ||
} | ||
renderSection(sectionConfig, sectionIndex, colGroupStats, microColGroupNodes, sectionRowMaxHeights, isHeader) { | ||
if ('outerContent' in sectionConfig) { | ||
return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent)); | ||
} | ||
return (createElement("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, sectionConfig.chunks.map((chunkConfig, i) => this.renderChunk(sectionConfig, sectionIndex, colGroupStats[i], microColGroupNodes[i], chunkConfig, i, (sectionRowMaxHeights[sectionIndex] || [])[i] || [], isHeader)))); | ||
} | ||
renderChunk(sectionConfig, sectionIndex, colGroupStat, microColGroupNode, chunkConfig, chunkIndex, rowHeights, isHeader) { | ||
if ('outerContent' in chunkConfig) { | ||
return (createElement(Fragment, { key: chunkConfig.key }, chunkConfig.outerContent)); | ||
} | ||
let { state } = this; | ||
let { scrollerClientWidths, scrollerClientHeights } = state; | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let index = sectionIndex * chunksPerSection + chunkIndex; | ||
let sideScrollIndex = (!this.context.isRtl || getIsRtlScrollbarOnLeft()) ? chunksPerSection - 1 : 0; | ||
let isVScrollSide = chunkIndex === sideScrollIndex; | ||
let isLastSection = sectionIndex === sectionCnt - 1; | ||
let forceXScrollbars = isLastSection && state.forceXScrollbars; // NOOOO can result in `null` | ||
let forceYScrollbars = isVScrollSide && state.forceYScrollbars; // NOOOO can result in `null` | ||
let allowXScrolling = colGroupStat && colGroupStat.allowXScrolling; // rename? | ||
let allowYScrolling = getAllowYScrolling(this.props, sectionConfig); // rename? do in section func? | ||
let chunkVGrow = getSectionHasLiquidHeight(this.props, sectionConfig); // do in section func? | ||
let expandRows = sectionConfig.expandRows && chunkVGrow; | ||
let tableMinWidth = (colGroupStat && colGroupStat.totalColMinWidth) || ''; | ||
let content = renderChunkContent(sectionConfig, chunkConfig, { | ||
tableColGroupNode: microColGroupNode, | ||
tableMinWidth, | ||
clientWidth: scrollerClientWidths[index] !== undefined ? scrollerClientWidths[index] : null, | ||
clientHeight: scrollerClientHeights[index] !== undefined ? scrollerClientHeights[index] : null, | ||
expandRows, | ||
syncRowHeights: Boolean(sectionConfig.syncRowHeights), | ||
rowSyncHeights: rowHeights, | ||
reportRowHeightChange: this.handleRowHeightChange, | ||
}, isHeader); | ||
let overflowX = forceXScrollbars ? (isLastSection ? 'scroll' : 'scroll-hidden') : | ||
!allowXScrolling ? 'hidden' : | ||
(isLastSection ? 'auto' : 'scroll-hidden'); | ||
let overflowY = forceYScrollbars ? (isVScrollSide ? 'scroll' : 'scroll-hidden') : | ||
!allowYScrolling ? 'hidden' : | ||
(isVScrollSide ? 'auto' : 'scroll-hidden'); | ||
// it *could* be possible to reduce DOM wrappers by only doing a ClippedScroller when allowXScrolling or allowYScrolling, | ||
// but if these values were to change, the inner components would be unmounted/remounted because of the parent change. | ||
content = (createElement(ClippedScroller, { ref: this.clippedScrollerRefs.createRef(index), scrollerElRef: this.scrollerElRefs.createRef(index), overflowX: overflowX, overflowY: overflowY, forPrint: this.props.forPrint, liquid: chunkVGrow, maxHeight: sectionConfig.maxHeight }, content)); | ||
return createElement(isHeader ? 'th' : 'td', { | ||
key: chunkConfig.key, | ||
ref: this.chunkElRefs.createRef(index), | ||
role: 'presentation', | ||
}, content); | ||
} | ||
componentDidMount() { | ||
this.getStickyScrolling = memoizeArraylike(initStickyScrolling); | ||
this.getScrollSyncersBySection = memoizeHashlike(initScrollSyncer.bind(this, true), null, destroyScrollSyncer); | ||
this.getScrollSyncersByColumn = memoizeHashlike(initScrollSyncer.bind(this, false), null, destroyScrollSyncer); | ||
this.updateScrollSyncers(); | ||
this.handleSizing(false); | ||
this.context.addResizeHandler(this.handleSizing); | ||
} | ||
componentDidUpdate(prevProps, prevState) { | ||
this.updateScrollSyncers(); | ||
// TODO: need better solution when state contains non-sizing things | ||
this.handleSizing(false, prevState.sectionRowMaxHeights !== this.state.sectionRowMaxHeights); | ||
} | ||
componentWillUnmount() { | ||
this.context.removeResizeHandler(this.handleSizing); | ||
this.destroyScrollSyncers(); | ||
} | ||
allowSizing() { | ||
let now = new Date(); | ||
if (!this.lastSizingDate || | ||
now.valueOf() > this.lastSizingDate.valueOf() + config.SCROLLGRID_RESIZE_INTERVAL) { | ||
this.lastSizingDate = now; | ||
this.recentSizingCnt = 0; | ||
return true; | ||
} | ||
return (this.recentSizingCnt += 1) <= 10; | ||
} | ||
computeShrinkWidths() { | ||
let colGroupStats = this.compileColGroupStats(this.props.colGroups.map((colGroup) => [colGroup])); | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let cnt = sectionCnt * chunksPerSection; | ||
let shrinkWidths = []; | ||
colGroupStats.forEach((colGroupStat, i) => { | ||
if (colGroupStat.hasShrinkCol) { | ||
let chunkEls = this.chunkElRefs.collect(i, cnt, chunksPerSection); // in one col | ||
shrinkWidths[i] = computeShrinkWidth(chunkEls); | ||
} | ||
}); | ||
return shrinkWidths; | ||
} | ||
// has the side effect of grooming rowInnerMaxHeightMap | ||
// TODO: somehow short-circuit if there are no new height changes | ||
computeSectionRowMaxHeights() { | ||
let newHeightMap = new Map(); | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let sectionRowMaxHeights = []; | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { | ||
let sectionConfig = this.props.sections[sectionI]; | ||
let assignableHeights = []; // chunk, row | ||
if (sectionConfig && sectionConfig.syncRowHeights) { | ||
let rowHeightsByChunk = []; | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let index = sectionI * chunksPerSection + chunkI; | ||
let rowHeights = []; | ||
let chunkEl = this.chunkElRefs.currentMap[index]; | ||
if (chunkEl) { | ||
rowHeights = findElements(chunkEl, '.fc-scrollgrid-sync-table tr').map((rowEl) => { | ||
let max = getRowInnerMaxHeight(rowEl); | ||
newHeightMap.set(rowEl, max); | ||
return max; | ||
}); | ||
} | ||
else { | ||
rowHeights = []; | ||
} | ||
rowHeightsByChunk.push(rowHeights); | ||
} | ||
let rowCnt = rowHeightsByChunk[0].length; | ||
let isEqualRowCnt = true; | ||
for (let chunkI = 1; chunkI < chunksPerSection; chunkI += 1) { | ||
let isOuterContent = sectionConfig.chunks[chunkI] && sectionConfig.chunks[chunkI].outerContent !== undefined; // can be null | ||
if (!isOuterContent && rowHeightsByChunk[chunkI].length !== rowCnt) { // skip outer content | ||
isEqualRowCnt = false; | ||
break; | ||
} | ||
} | ||
if (!isEqualRowCnt) { | ||
let chunkHeightSums = []; | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
chunkHeightSums.push(sumNumbers(rowHeightsByChunk[chunkI]) + rowHeightsByChunk[chunkI].length); | ||
} | ||
let maxTotalSum = Math.max(...chunkHeightSums); | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let rowInChunkCnt = rowHeightsByChunk[chunkI].length; | ||
let rowInChunkTotalHeight = maxTotalSum - rowInChunkCnt; // subtract border | ||
// height of non-first row. we do this to avoid rounding, because it's unreliable within a table | ||
let rowInChunkHeightOthers = Math.floor(rowInChunkTotalHeight / rowInChunkCnt); | ||
// whatever is leftover goes to the first row | ||
let rowInChunkHeightFirst = rowInChunkTotalHeight - rowInChunkHeightOthers * (rowInChunkCnt - 1); | ||
let rowInChunkHeights = []; | ||
let row = 0; | ||
if (row < rowInChunkCnt) { | ||
rowInChunkHeights.push(rowInChunkHeightFirst); | ||
row += 1; | ||
} | ||
while (row < rowInChunkCnt) { | ||
rowInChunkHeights.push(rowInChunkHeightOthers); | ||
row += 1; | ||
} | ||
assignableHeights.push(rowInChunkHeights); | ||
} | ||
} | ||
else { | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
assignableHeights.push([]); | ||
} | ||
for (let row = 0; row < rowCnt; row += 1) { | ||
let rowHeightsAcrossChunks = []; | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let h = rowHeightsByChunk[chunkI][row]; | ||
if (h != null) { // protect against outerContent | ||
rowHeightsAcrossChunks.push(h); | ||
} | ||
} | ||
let maxHeight = Math.max(...rowHeightsAcrossChunks); | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
assignableHeights[chunkI].push(maxHeight); | ||
} | ||
} | ||
} | ||
} | ||
sectionRowMaxHeights.push(assignableHeights); | ||
} | ||
this.rowInnerMaxHeightMap = newHeightMap; | ||
return sectionRowMaxHeights; | ||
} | ||
computeScrollerDims() { | ||
let scrollbarWidth = getScrollbarWidths(); | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let sideScrollI = (!this.context.isRtl || getIsRtlScrollbarOnLeft()) ? chunksPerSection - 1 : 0; | ||
let lastSectionI = sectionCnt - 1; | ||
let currentScrollers = this.clippedScrollerRefs.currentMap; | ||
let scrollerEls = this.scrollerElRefs.currentMap; | ||
let forceYScrollbars = false; | ||
let forceXScrollbars = false; | ||
let scrollerClientWidths = {}; | ||
let scrollerClientHeights = {}; | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { // along edge | ||
let index = sectionI * chunksPerSection + sideScrollI; | ||
let scroller = currentScrollers[index]; | ||
if (scroller && scroller.needsYScrolling()) { | ||
forceYScrollbars = true; | ||
break; | ||
} | ||
} | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { // along last row | ||
let index = lastSectionI * chunksPerSection + chunkI; | ||
let scroller = currentScrollers[index]; | ||
if (scroller && scroller.needsXScrolling()) { | ||
forceXScrollbars = true; | ||
break; | ||
} | ||
} | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { | ||
for (let chunkI = 0; chunkI < chunksPerSection; chunkI += 1) { | ||
let index = sectionI * chunksPerSection + chunkI; | ||
let scrollerEl = scrollerEls[index]; | ||
if (scrollerEl) { | ||
// TODO: weird way to get this. need harness b/c doesn't include table borders | ||
let harnessEl = scrollerEl.parentNode; | ||
scrollerClientWidths[index] = Math.floor(harnessEl.getBoundingClientRect().width - ((chunkI === sideScrollI && forceYScrollbars) | ||
? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future | ||
: 0)); | ||
scrollerClientHeights[index] = Math.floor(harnessEl.getBoundingClientRect().height - ((sectionI === lastSectionI && forceXScrollbars) | ||
? scrollbarWidth.x // use global because scroller might not have scrollbars yet but will need them in future | ||
: 0)); | ||
} | ||
} | ||
} | ||
return { forceYScrollbars, forceXScrollbars, scrollerClientWidths, scrollerClientHeights }; | ||
} | ||
updateStickyScrolling() { | ||
let { isRtl } = this.context; | ||
let argsByKey = this.scrollerElRefs.getAll().map((scrollEl) => [scrollEl, isRtl]); | ||
this.getStickyScrolling(argsByKey) | ||
.forEach((stickyScrolling) => stickyScrolling.updateSize()); | ||
} | ||
updateScrollSyncers() { | ||
let [sectionCnt, chunksPerSection] = this.getDims(); | ||
let cnt = sectionCnt * chunksPerSection; | ||
let scrollElsBySection = {}; | ||
let scrollElsByColumn = {}; | ||
let scrollElMap = this.scrollerElRefs.currentMap; | ||
for (let sectionI = 0; sectionI < sectionCnt; sectionI += 1) { | ||
let startIndex = sectionI * chunksPerSection; | ||
let endIndex = startIndex + chunksPerSection; | ||
scrollElsBySection[sectionI] = collectFromHash(scrollElMap, startIndex, endIndex, 1); // use the filtered | ||
} | ||
for (let col = 0; col < chunksPerSection; col += 1) { | ||
scrollElsByColumn[col] = this.scrollerElRefs.collect(col, cnt, chunksPerSection); // DON'T use the filtered | ||
} | ||
this.scrollSyncersBySection = this.getScrollSyncersBySection(scrollElsBySection); | ||
this.scrollSyncersByColumn = this.getScrollSyncersByColumn(scrollElsByColumn); | ||
} | ||
destroyScrollSyncers() { | ||
mapHash(this.scrollSyncersBySection, destroyScrollSyncer); | ||
mapHash(this.scrollSyncersByColumn, destroyScrollSyncer); | ||
} | ||
getChunkConfigByIndex(index) { | ||
let chunksPerSection = this.getDims()[1]; | ||
let sectionI = Math.floor(index / chunksPerSection); | ||
let chunkI = index % chunksPerSection; | ||
let sectionConfig = this.props.sections[sectionI]; | ||
return sectionConfig && sectionConfig.chunks[chunkI]; | ||
} | ||
forceScrollLeft(col, scrollLeft) { | ||
let scrollSyncer = this.scrollSyncersByColumn[col]; | ||
if (scrollSyncer) { | ||
scrollSyncer.forceScrollLeft(scrollLeft); | ||
} | ||
} | ||
forceScrollTop(sectionI, scrollTop) { | ||
let scrollSyncer = this.scrollSyncersBySection[sectionI]; | ||
if (scrollSyncer) { | ||
scrollSyncer.forceScrollTop(scrollTop); | ||
} | ||
} | ||
_handleChunkEl(chunkEl, key) { | ||
let chunkConfig = this.getChunkConfigByIndex(parseInt(key, 10)); | ||
if (chunkConfig) { // null if section disappeared. bad, b/c won't null-set the elRef | ||
setRef(chunkConfig.elRef, chunkEl); | ||
} | ||
} | ||
_handleScrollerEl(scrollerEl, key) { | ||
let chunkConfig = this.getChunkConfigByIndex(parseInt(key, 10)); | ||
if (chunkConfig) { // null if section disappeared. bad, b/c won't null-set the elRef | ||
setRef(chunkConfig.scrollerElRef, scrollerEl); | ||
} | ||
} | ||
getDims() { | ||
let sectionCnt = this.props.sections.length; | ||
let chunksPerSection = sectionCnt ? this.props.sections[0].chunks.length : 0; | ||
return [sectionCnt, chunksPerSection]; | ||
} | ||
} | ||
ScrollGrid.addStateEquality({ | ||
shrinkWidths: isArraysEqual, | ||
scrollerClientWidths: isPropsEqual, | ||
scrollerClientHeights: isPropsEqual, | ||
}); | ||
function sumNumbers(numbers) { | ||
let sum = 0; | ||
for (let n of numbers) { | ||
sum += n; | ||
} | ||
return sum; | ||
} | ||
function getRowInnerMaxHeight(rowEl) { | ||
let innerHeights = findElements(rowEl, '.fc-scrollgrid-sync-inner').map(getElHeight); | ||
if (innerHeights.length) { | ||
return Math.max(...innerHeights); | ||
} | ||
return 0; | ||
} | ||
function getElHeight(el) { | ||
return el.offsetHeight; // better to deal with integers, for rounding, for PureComponent | ||
} | ||
function renderMacroColGroup(colGroupStats, shrinkWidths) { | ||
let children = colGroupStats.map((colGroupStat, i) => { | ||
let width = colGroupStat.width; | ||
if (width === 'shrink') { | ||
width = colGroupStat.totalColWidth + sanitizeShrinkWidth(shrinkWidths[i]) + 1; // +1 for border :( | ||
} | ||
return ( // eslint-disable-next-line react/jsx-key | ||
createElement("col", { style: { width } })); | ||
}); | ||
return createElement('colgroup', {}, ...children); | ||
} | ||
function compileColGroupStat(colGroupConfig) { | ||
let totalColWidth = sumColProp(colGroupConfig.cols, 'width'); // excludes "shrink" | ||
let totalColMinWidth = sumColProp(colGroupConfig.cols, 'minWidth'); | ||
let hasShrinkCol = hasShrinkWidth(colGroupConfig.cols); | ||
let allowXScrolling = colGroupConfig.width !== 'shrink' && Boolean(totalColWidth || totalColMinWidth || hasShrinkCol); | ||
return { | ||
hasShrinkCol, | ||
totalColWidth, | ||
totalColMinWidth, | ||
allowXScrolling, | ||
cols: colGroupConfig.cols, | ||
width: colGroupConfig.width, | ||
}; | ||
} | ||
function sumColProp(cols, propName) { | ||
let total = 0; | ||
for (let col of cols) { | ||
let val = col[propName]; | ||
if (typeof val === 'number') { | ||
total += val * (col.span || 1); | ||
} | ||
} | ||
return total; | ||
} | ||
const COL_GROUP_STAT_EQUALITY = { | ||
cols: isColPropsEqual, | ||
}; | ||
function isColGroupStatsEqual(stat0, stat1) { | ||
return compareObjs(stat0, stat1, COL_GROUP_STAT_EQUALITY); | ||
} | ||
// for memoizers... | ||
function initScrollSyncer(isVertical, ...scrollEls) { | ||
return new ScrollSyncer(isVertical, scrollEls); | ||
} | ||
function destroyScrollSyncer(scrollSyncer) { | ||
scrollSyncer.destroy(); | ||
} | ||
function initStickyScrolling(scrollEl, isRtl) { | ||
return new StickyScrolling(scrollEl, isRtl); | ||
} | ||
export { ScrollGrid }; | ||
export { ScrollerSyncer }; |
{ | ||
"name": "@fullcalendar/scrollgrid", | ||
"version": "6.1.15", | ||
"version": "7.0.0-beta.0", | ||
"title": "FullCalendar ScrollGrid Plugin", | ||
"description": "Tabular data chunked into scrollable panes", | ||
"dependencies": { | ||
"@fullcalendar/premium-common": "~6.1.15" | ||
"@fullcalendar/premium-common": "7.0.0-beta.0" | ||
}, | ||
"peerDependencies": { | ||
"@fullcalendar/core": "~6.1.15" | ||
"@fullcalendar/core": "7.0.0-beta.0" | ||
}, | ||
@@ -12,0 +12,0 @@ "type": "module", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
30929
640
1
1
+ Added@fullcalendar/core@7.0.0-beta.0(transitive)
+ Added@fullcalendar/premium-common@7.0.0-beta.0(transitive)
+ Addedpreact@10.23.2(transitive)
- Removed@fullcalendar/core@6.1.15(transitive)
- Removed@fullcalendar/premium-common@6.1.15(transitive)
- Removedpreact@10.12.1(transitive)