hwc
Version:
A react Headless Week Calendar toolkit to build calendars like Google Calendar.
682 lines (616 loc) • 22.9 kB
JavaScript
import React, { useState, useCallback, useEffect, useMemo, useLayoutEffect, useRef, Fragment } from 'react';
var HwcContext = /*#__PURE__*/React.createContext(null);
var HwcProvider = function HwcProvider(_ref) {
var value = _ref.value,
children = _ref.children;
var Ctx = HwcContext;
return React.createElement(Ctx.Provider, {
value: value
}, children);
};
var useHwcContext = function useHwcContext() {
var ctx = React.useContext(HwcContext);
if (ctx === null) throw new Error('No WeekPicker Context found. Please Wrap Hwc components in <HwcProvider />');
return ctx;
};
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
var MINUTE_IN_MILLIS = 60 * 1000;
var HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
var DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
var posToDate = function posToDate(pos, width, startDay, hourHeight, daysCount) {
var minuteHeight = 60 / hourHeight;
var minutesDay = pos.y * minuteHeight;
var dayInGrid = Math.trunc(pos.x / (width || 1) * daysCount);
return new Date(startDay.getTime() + dayInGrid * DAY_IN_MILLIS + minutesDay * MINUTE_IN_MILLIS);
};
var roundToQuarterHour = function roundToQuarterHour(date) {
var rounded = startOfMinute(date);
var minutes = rounded.getMinutes() + rounded.getHours() * 60;
var roundedMinute = Math.round(minutes / 60 * 4) * 60 / 4;
var d = startOfDay(date);
d.setMinutes(roundedMinute);
return d;
};
var roundToSemiHour = function roundToSemiHour(date) {
var rounded = startOfMinute(date);
var minutes = rounded.getMinutes() + rounded.getHours() * 60;
var roundedMinute = Math.round(minutes / 60 * 2) * 60 / 2;
var d = startOfDay(date);
d.setMinutes(roundedMinute);
return d;
};
var startOfDay = function startOfDay(input) {
var d = new Date(input);
d.setHours(0, 0, 0, 0);
return d;
};
var startOfMinute = function startOfMinute(input) {
var d = new Date(input);
d.setSeconds(0, 0);
return d;
};
var useMoveEvent = function useMoveEvent(date, updateEventHandler) {
var _useState = useState(),
moveState = _useState[0],
setMoveState = _useState[1];
var setEventMoving = useCallback(function (event, evIndex) {
return setMoveState(event !== undefined && evIndex !== undefined ? {
startEvent: event,
evIndex: evIndex,
date: date,
dragDelta: date.getTime() - event.startDate.getTime()
} : undefined);
}, [setMoveState, date]);
useEffect(function () {
if (!moveState) return;
var delta = roundToQuarterHour(new Date(moveState.date.getTime() - date.getTime())).getTime();
updateEventHandler(moveState.evIndex, {
startDate: new Date(moveState.startEvent.startDate.getTime() - delta),
endDate: new Date(moveState.startEvent.endDate.getTime() - delta)
}, moveState.startEvent);
}, [moveState, date, updateEventHandler]);
useEffect(function () {
if (!moveState) return;
var mouseUpHandler = function mouseUpHandler() {
window.removeEventListener('mouseup', mouseUpHandler);
setMoveState(undefined);
};
window.addEventListener('mouseup', mouseUpHandler);
}, [moveState, setMoveState]);
return [setEventMoving];
};
var useResizeEvent = function useResizeEvent(date, updateEventHandler) {
var _useState = useState(),
resizeState = _useState[0],
setResizeState = _useState[1];
var setEventResizing = useCallback(function (event, evIndex, grip) {
return setResizeState(event !== undefined && evIndex !== undefined && grip !== undefined ? {
event: event,
evIndex: evIndex,
date: date,
grip: grip
} : undefined);
}, [setResizeState, date]);
useEffect(function () {
if (!resizeState) return;
var delta = roundToQuarterHour(new Date(resizeState.date.getTime() - date.getTime())).getTime();
updateEventHandler(resizeState.evIndex, {
startDate: new Date(resizeState.grip === 'start' ? resizeState.event.startDate.getTime() - delta : resizeState.event.startDate),
endDate: new Date(resizeState.grip === 'end' ? resizeState.event.endDate.getTime() - delta : resizeState.event.endDate)
});
}, [resizeState, date, updateEventHandler]);
useEffect(function () {
if (!resizeState) return;
var mouseUpHandler = function mouseUpHandler() {
window.removeEventListener('mouseup', mouseUpHandler);
setResizeState(undefined);
};
window.addEventListener('mouseup', mouseUpHandler);
}, [resizeState, setResizeState]);
return [setEventResizing];
};
var useShadowEvent = function useShadowEvent(date) {
var _useState = useState(),
startDragDate = _useState[0],
setStartDragDate = _useState[1];
var shadowEvent = useMemo(function () {
if (!startDragDate) return undefined;
var dates = [roundToQuarterHour(startDragDate), roundToQuarterHour(date)].sort(function (a, b) {
return a.getTime() - b.getTime();
});
return {
startDate: dates[0],
endDate: dates[1]
};
}, [startDragDate, date]);
return [shadowEvent, setStartDragDate];
};
var useHwc = function useHwc(props) {
if (props === void 0) {
props = {};
}
var _props = props,
_props$startDay = _props.startDay,
startDay = _props$startDay === void 0 ? new Date() : _props$startDay,
_props$cellHeight = _props.cellHeight,
cellHeight = _props$cellHeight === void 0 ? 50 : _props$cellHeight,
_props$daysCount = _props.daysCount,
daysCount = _props$daysCount === void 0 ? 7 : _props$daysCount,
_props$events = _props.events,
events = _props$events === void 0 ? [] : _props$events,
_props$onAddEvent = _props.onAddEvent,
onAddEvent = _props$onAddEvent === void 0 ? function () {} : _props$onAddEvent,
_props$onUpdateEvent = _props.onUpdateEvent,
onUpdateEvent = _props$onUpdateEvent === void 0 ? function () {} : _props$onUpdateEvent;
var normalizedStartDay = useMemo(function () {
return startOfDay(startDay);
}, [startDay]); // column witdh
var _useState = useState(0),
width = _useState[0],
setWidth = _useState[1];
var cellWidth = useMemo(function () {
return width / daysCount;
}, [width, daysCount]); // mouse position and date
var _useState2 = useState({
x: 0,
y: 0
}),
pos = _useState2[0],
setPos = _useState2[1];
var date = useMemo(function () {
return posToDate(pos, width, normalizedStartDay, cellHeight, daysCount);
}, [pos, width, normalizedStartDay, cellHeight, daysCount]); // shadow event
var _useShadowEvent = useShadowEvent(date),
shadowEvent = _useShadowEvent[0],
setStartDragDate = _useShadowEvent[1]; // move and resize
var internalUpdateEventHandler = useCallback(function (evIndex, newEvent) {
var ev = events[evIndex];
if (ev.startDate.getTime() !== newEvent.startDate.getTime() || ev.endDate.getTime() !== newEvent.endDate.getTime()) onUpdateEvent(evIndex, _extends({}, ev, newEvent), ev);
}, [events, onUpdateEvent]);
var _useMoveEvent = useMoveEvent(date, internalUpdateEventHandler),
setEventMoving = _useMoveEvent[0];
var _useResizeEvent = useResizeEvent(date, internalUpdateEventHandler),
setEventResizing = _useResizeEvent[0]; // handlers
var addEventHandler = useCallback(function (ev) {
return onAddEvent(ev);
}, [onAddEvent]);
var updateEventHandler = useCallback(function (eventIndex, newEvent, previousEvent) {
return onUpdateEvent(eventIndex, newEvent, previousEvent);
}, [onUpdateEvent]);
var setEventMovingHandler = useCallback(function (evIndex) {
setEventMoving(evIndex !== undefined ? events[evIndex] : undefined, evIndex);
}, [setEventMoving, events]);
var setEventResizingHandler = useCallback(function (evIndex, grip) {
setEventResizing(evIndex !== undefined ? events[evIndex] : undefined, evIndex, evIndex !== undefined ? grip : undefined);
}, [setEventResizing, events]);
return {
pos: pos,
date: date,
cellWidth: cellWidth,
cellHeight: cellHeight,
startDay: normalizedStartDay,
daysCount: daysCount,
events: events,
setPos: setPos,
setWidth: setWidth,
setEventMoving: setEventMovingHandler,
setEventResizing: setEventResizingHandler,
shadowEvent: shadowEvent,
setStartDragDate: setStartDragDate,
addEvent: addEventHandler,
updateEvent: updateEventHandler
};
};
var useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
var _excluded = ["onMouseMove", "onMouseDown", "style", "children"];
var HwcDragPane = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
var _ref$onMouseMove = _ref.onMouseMove,
onMouseMove = _ref$onMouseMove === void 0 ? function () {} : _ref$onMouseMove,
_ref$onMouseDown = _ref.onMouseDown,
onMouseDown = _ref$onMouseDown === void 0 ? function () {} : _ref$onMouseDown,
style = _ref.style,
children = _ref.children,
props = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useHwcContext = useHwcContext(),
setPos = _useHwcContext.setPos,
date = _useHwcContext.date,
setWidth = _useHwcContext.setWidth,
setStartDragDate = _useHwcContext.setStartDragDate,
addEvent = _useHwcContext.addEvent,
shadowEvent = _useHwcContext.shadowEvent,
cellHeight = _useHwcContext.cellHeight;
var _useState = useState(null),
element = _useState[0],
setRef = _useState[1];
useIsomorphicLayoutEffect(function () {
if (!element) return;
var observer = new ResizeObserver(function (_ref2) {
var entry = _ref2[0];
if (entry) setWidth(entry.contentRect.width);
});
observer.observe(element);
return function () {
observer.disconnect();
};
}, [element, setWidth]); // shadow event
var mouseDownDate = useRef();
var mouseDownHandler = useCallback(function (ev) {
if (!(ev.target instanceof Element) || !ev.target.hasAttribute('week-picker-cell')) return; // prevent dnd
ev.preventDefault();
mouseDownDate.current = date;
onMouseDown(ev);
}, [date, onMouseDown]);
var mouseMoveHandler = useCallback(function (ev) {
var offset = ev.currentTarget.getBoundingClientRect();
setPos({
x: ev.clientX - offset.left,
y: ev.clientY - offset.top
});
onMouseMove(ev);
if (mouseDownDate.current && !shadowEvent && roundToQuarterHour(mouseDownDate.current).getTime() !== roundToQuarterHour(date).getTime()) {
setStartDragDate(mouseDownDate.current);
}
}, [setPos, onMouseMove, shadowEvent, date, setStartDragDate]);
useIsomorphicLayoutEffect(function () {
var dragEndHandler = function dragEndHandler() {
if (mouseDownDate.current) {
if (shadowEvent) {
addEvent(shadowEvent);
} else {
var roundedDate = roundToSemiHour(date);
addEvent({
startDate: roundedDate,
endDate: new Date(roundedDate.getTime() + 2 * HOUR_IN_MILLIS)
});
}
}
mouseDownDate.current = undefined;
setStartDragDate(undefined);
};
window.addEventListener('mouseup', dragEndHandler);
return function () {
window.removeEventListener('mouseup', dragEndHandler);
};
}, [setStartDragDate, addEvent, shadowEvent, date]);
return React.createElement("div", Object.assign({}, props, {
style: _extends({
position: 'relative',
height: cellHeight * 24 + "px"
}, style),
onMouseMove: mouseMoveHandler,
onDrag: function onDrag(ev) {
return ev.preventDefault();
},
onMouseDown: mouseDownHandler,
ref: ref || setRef
}), children);
});
var _excluded$1 = ["style", "colStyle", "renderCell"];
var defaultCellRenderer = function defaultCellRenderer(_ref) {
var style = _ref.style;
return React.createElement("div", {
style: style
});
};
var HwcGrid = /*#__PURE__*/React.forwardRef(function (_ref2, ref) {
var style = _ref2.style,
colStyle = _ref2.colStyle,
_ref2$renderCell = _ref2.renderCell,
renderCell = _ref2$renderCell === void 0 ? defaultCellRenderer : _ref2$renderCell,
props = _objectWithoutPropertiesLoose(_ref2, _excluded$1);
var _useHwcContext = useHwcContext(),
startDay = _useHwcContext.startDay,
cellHeight = _useHwcContext.cellHeight,
cellWidth = _useHwcContext.cellWidth,
daysCount = _useHwcContext.daysCount;
var days = new Array(daysCount).fill(0).map(function (_, index) {
return new Date(startDay.getTime() + index * DAY_IN_MILLIS);
});
var lines = new Array(24).fill(0).map(function (_, index) {
return index;
});
return React.createElement("div", Object.assign({}, props, {
ref: ref,
style: _extends({
position: 'absolute',
width: '100%',
display: 'flex'
}, style)
}), days.map(function (d, dIndex) {
return React.createElement("div", {
key: dIndex,
style: _extends({
flex: '0 0 1',
width: '100%'
}, colStyle)
}, lines.map(function (l) {
var x = dIndex === 0 ? 'start' : dIndex === days.length - 1 ? 'end' : undefined;
var y = l === 0 ? 'start' : l === lines.length - 1 ? 'end' : undefined;
return React.createElement(Fragment, {
key: l
}, React.Children.map(renderCell({
cellHeight: cellHeight,
cellWidth: cellWidth,
style: {
height: cellHeight + "px",
borderLeft: x === 'start' ? undefined : "#aaa solid 1px",
borderTop: y === 'start' ? undefined : "#aaa solid 1px",
boxSizing: 'border-box'
},
x: x,
y: y,
day: d,
date: new Date(d.getTime() + l * HOUR_IN_MILLIS)
}), function (node) {
return React.cloneElement(node, {
'week-picker-cell': ''
});
}));
}));
}));
});
var buildIsEventVisibleFilter = function buildIsEventVisibleFilter(startDay, daysCount) {
var startMillis = startOfDay(startDay).getTime();
var endMillis = startMillis + daysCount * DAY_IN_MILLIS;
return function (ev) {
return ev.startDate.getTime() < endMillis && ev.endDate.getTime() > startMillis;
};
};
var getCroppedEventsByDay = function getCroppedEventsByDay(ev) {
var events = [];
var evEndMillis = ev.endDate.getTime();
for (var i = 0; i < 20; i++) {
var lastCroppedEvent = events[events.length - 1];
var nextDayMillis = lastCroppedEvent ? startOfDay(lastCroppedEvent.startDate).getTime() + 2 * DAY_IN_MILLIS : startOfDay(ev.startDate).getTime() + 1 * DAY_IN_MILLIS;
var isOverflow = nextDayMillis < evEndMillis;
events.push(_extends({}, ev, {
startDate: new Date(lastCroppedEvent ? lastCroppedEvent.endDate : ev.startDate),
endDate: new Date(isOverflow ? nextDayMillis : evEndMillis)
}));
if (!isOverflow) break;
}
return events;
};
var useCards = function useCards() {
var _useHwcContext = useHwcContext(),
events = _useHwcContext.events,
startDay = _useHwcContext.startDay,
daysCount = _useHwcContext.daysCount,
cellWidth = _useHwcContext.cellWidth,
cellHeight = _useHwcContext.cellHeight;
var eventsFilter = useMemo(function () {
return buildIsEventVisibleFilter(startDay, daysCount);
}, [startDay, daysCount]);
return useMemo(function () {
return events.map(function (event, index) {
return {
event: event,
index: index
};
}).filter(function (c) {
return eventsFilter(c.event);
}).map(function (c) {
var evts = getCroppedEventsByDay(c.event);
return evts.map(function (ev) {
return _extends({}, c, {
event: ev
});
});
}).map(function (croppeds) {
return croppeds.map(function (card, index) {
return _extends({}, card, {
isFirst: index === 0,
isLast: index === croppeds.length - 1
});
});
}).flat().filter(function (c) {
return eventsFilter(c.event);
}).map(function (c) {
var day = (startOfDay(c.event.startDate).getTime() - startOfDay(startDay).getTime()) / DAY_IN_MILLIS;
var duration = c.event.endDate.getTime() - c.event.startDate.getTime();
var millisFromDayStart = c.event.startDate.getTime() - startOfDay(c.event.startDate).getTime();
var rect = {
width: cellWidth,
height: duration / HOUR_IN_MILLIS * cellHeight,
left: day * cellWidth,
top: millisFromDayStart / HOUR_IN_MILLIS * cellHeight
};
return _extends({}, c, {
rect: rect,
style: {
width: rect.width + "px",
height: rect.height + "px"
}
});
});
}, [events, cellWidth, startDay, cellHeight, eventsFilter]);
};
var useShadowCards = function useShadowCards() {
var _useHwcContext = useHwcContext(),
startDay = _useHwcContext.startDay,
daysCount = _useHwcContext.daysCount,
shadowEvent = _useHwcContext.shadowEvent,
cellWidth = _useHwcContext.cellWidth,
cellHeight = _useHwcContext.cellHeight;
var eventsFilter = useMemo(function () {
return buildIsEventVisibleFilter(startDay, daysCount);
}, [startDay, daysCount]);
return useMemo(function () {
return (shadowEvent ? [{
event: shadowEvent
}] : []).filter(function (c) {
return eventsFilter(c.event);
}).map(function (c) {
var evts = getCroppedEventsByDay(c.event);
return evts.map(function (ev) {
return _extends({}, c, {
event: ev
});
});
}).map(function (croppeds) {
return croppeds.map(function (card, index) {
return _extends({}, card, {
index: index,
isFirst: index === 0,
isLast: index === croppeds.length - 1
});
});
}).flat().filter(function (c) {
return eventsFilter(c.event);
}).map(function (c) {
var day = (startOfDay(c.event.startDate).getTime() - startOfDay(startDay).getTime()) / DAY_IN_MILLIS;
var duration = c.event.endDate.getTime() - c.event.startDate.getTime();
var millisFromDayStart = c.event.startDate.getTime() - startOfDay(c.event.startDate).getTime();
var rect = {
width: cellWidth,
height: duration / HOUR_IN_MILLIS * cellHeight,
left: day * cellWidth,
top: millisFromDayStart / HOUR_IN_MILLIS * cellHeight
};
return _extends({}, c, {
rect: rect,
style: {
width: rect.width + "px",
height: rect.height + "px"
}
});
});
}, [cellWidth, startDay, shadowEvent, cellHeight, eventsFilter]);
};
var GripRenderer = function GripRenderer(_ref) {
var event = _ref.event,
index = _ref.index,
position = _ref.position,
renderGrip = _ref.renderGrip;
var _useHwcContext = useHwcContext(),
setEventResizing = _useHwcContext.setEventResizing;
var style = useMemo(function () {
return {
position: 'absolute',
top: position === 'start' ? '-4px' : undefined,
bottom: position === 'end' ? '-4px' : undefined,
height: '9px',
width: '100%',
cursor: 'ns-resize'
};
}, [position]);
var onMouseDownHandler = useCallback(function (ev) {
ev.preventDefault();
ev.stopPropagation();
setEventResizing(index, position);
}, [setEventResizing, index, position]);
var props = useMemo(function () {
return {
event: event,
index: index,
position: position,
style: style,
elProps: {
style: style,
onMouseDown: onMouseDownHandler
}
};
}, [event, index, position, style, onMouseDownHandler]);
if (renderGrip) return React.createElement(React.Fragment, null, renderGrip(props));
return React.createElement("div", Object.assign({}, props.elProps));
};
var HwcEventsRenderer = function HwcEventsRenderer(_ref) {
var renderCard = _ref.renderCard,
renderShadowCard = _ref.renderShadowCard,
renderGrip = _ref.renderGrip;
var _useHwcContext = useHwcContext(),
setEventMoving = _useHwcContext.setEventMoving;
var cards = useCards();
var shadowCards = useShadowCards();
return React.createElement("div", {
style: {
position: 'relative'
}
}, cards.map(function (cardProps, index) {
return React.createElement("div", {
key: index,
style: _extends({}, cardProps.style, {
position: 'absolute',
left: cardProps.rect.left + "px",
top: cardProps.rect.top + "px"
}),
onMouseDown: function onMouseDown(ev) {
ev.preventDefault();
setEventMoving(cardProps.index);
}
}, renderCard(cardProps), cardProps.isFirst && React.createElement(GripRenderer, {
position: 'start',
event: cardProps.event,
index: cardProps.index,
renderGrip: renderGrip
}), cardProps.isLast && React.createElement(GripRenderer, {
position: 'end',
event: cardProps.event,
index: cardProps.index,
renderGrip: renderGrip
}));
}), shadowCards.map(function (shadowCardProps, index) {
return React.createElement("div", {
key: index,
style: _extends({}, shadowCardProps.style, {
position: 'absolute',
left: shadowCardProps.rect.left + "px",
top: shadowCardProps.rect.top + "px"
})
}, renderShadowCard(shadowCardProps));
}));
};
var HwcHeader = function HwcHeader(_ref) {
var locale = _ref.locale,
_ref$dateFormatOption = _ref.dateFormatOptions,
dateFormatOptions = _ref$dateFormatOption === void 0 ? {
day: '2-digit',
weekday: 'short'
} : _ref$dateFormatOption;
var _useHwcContext = useHwcContext(),
startDay = _useHwcContext.startDay,
daysCount = _useHwcContext.daysCount;
var days = new Array(daysCount).fill(0).map(function (_, index) {
return new Date(startDay.getTime() + index * DAY_IN_MILLIS);
});
return React.createElement("div", {
style: {
display: 'flex'
}
}, days.map(function (d, dIndex) {
return React.createElement("div", {
key: dIndex,
style: {
flex: '0 0 1',
width: '100%'
}
}, d.toLocaleDateString(locale, dateFormatOptions));
}));
};
export { HwcContext, HwcDragPane, HwcEventsRenderer, HwcGrid, HwcHeader, HwcProvider, useHwc, useHwcContext };
//# sourceMappingURL=hwc.esm.js.map