diff --git a/.eslintrc b/.eslintrc index 4268f85bb..123d9b0c0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,6 +2,8 @@ parser: babel-eslint extends: - jason/react - prettier +plugins: + - react-hooks env: es6: true rules: @@ -15,3 +17,5 @@ rules: varsIgnorePattern: ^_, argsIgnorePattern: ^_, }] + react-hooks/rules-of-hooks: error + react-hooks/exhaustive-deps: warn diff --git a/.size-snapshot.json b/.size-snapshot.json index ed467b52f..b6e1a03e1 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,25 +1,25 @@ { "./dist/react-big-calendar.js": { - "bundled": 507436, - "minified": 149016, - "gzipped": 45496 + "bundled": 509547, + "minified": 149932, + "gzipped": 45769 }, "./dist/react-big-calendar.min.js": { - "bundled": 444328, - "minified": 130089, - "gzipped": 41174 + "bundled": 446246, + "minified": 130919, + "gzipped": 41339 }, "dist/react-big-calendar.esm.js": { - "bundled": 174497, - "minified": 83179, - "gzipped": 20739, + "bundled": 176376, + "minified": 84316, + "gzipped": 21023, "treeshaked": { "rollup": { - "code": 60400, - "import_statements": 1578 + "code": 60196, + "import_statements": 1590 }, "webpack": { - "code": 64923 + "code": 64714 } } } diff --git a/README.md b/README.md index 18d03ade1..1059d5577 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,9 @@ const MyCalendar = props => ( ## Custom Styling -Out of the box you can include the compiled css files and be up and running. But, sometimes, you may want to style -Big Calendar to match your application styling. For this reason SASS files are included with Big Calendar. +Out of the box, you can include the compiled CSS files and be up and running. But, sometimes, you may want to style +Big Calendar to match your application styling. For this reason, SASS files are included with Big Calendar. + ``` @import 'react-big-calendar/lib/sass/styles'; @@ -126,4 +127,4 @@ Big Calendar. Carefully test each change accordingly. ## Join us on Reactiflux Discord -Join us on [Reactiflux Discord](https://discord.gg/uJsgpkC) community under the channel #react-big-calendar if you have any questions. +Join us on [Reactiflux Discord](https://discord.gg/PPgj6tb) community under the channel #libraries if you have any questions. diff --git a/examples/demos/dnd.js b/examples/demos/dnd.js index 16bc2e8f2..ebe0ef1ee 100644 --- a/examples/demos/dnd.js +++ b/examples/demos/dnd.js @@ -12,16 +12,39 @@ class Dnd extends React.Component { super(props) this.state = { events: events, + displayDragItemInCell: true, } this.moveEvent = this.moveEvent.bind(this) this.newEvent = this.newEvent.bind(this) } - moveEvent({ event, start, end, isAllDay: droppedOnAllDaySlot }) { + handleDragStart = event => { + this.setState({ draggedEvent: event }) + } + + dragFromOutsideItem = () => { + return this.state.draggedEvent + } + + onDropFromOutside = ({ start, end, allDay }) => { + const { draggedEvent } = this.state + + const event = { + id: draggedEvent.id, + title: draggedEvent.title, + start, + end, + allDay: allDay, + } + + this.setState({ draggedEvent: null }) + this.moveEvent({ event, start, end }) + } + + moveEvent = ({ event, start, end, isAllDay: droppedOnAllDaySlot }) => { const { events } = this.state - const idx = events.indexOf(event) let allDay = event.allDay if (!event.allDay && droppedOnAllDaySlot) { @@ -30,10 +53,11 @@ class Dnd extends React.Component { allDay = false } - const updatedEvent = { ...event, start, end, allDay } - - const nextEvents = [...events] - nextEvents.splice(idx, 1, updatedEvent) + const nextEvents = events.map(existingEvent => { + return existingEvent.id == event.id + ? { ...existingEvent, start, end } + : existingEvent + }) this.setState({ events: nextEvents, @@ -86,6 +110,12 @@ class Dnd extends React.Component { onDragStart={console.log} defaultView={Views.MONTH} defaultDate={new Date(2015, 3, 12)} + popup={true} + dragFromOutsideItem={ + this.state.displayDragItemInCell ? this.dragFromOutsideItem : null + } + onDropFromOutside={this.onDropFromOutside} + handleDragStart={this.handleDragStart} /> ) } diff --git a/package.json b/package.json index 67c88d9e0..131c17ae8 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/intljusticemission/react-big-calendar.git" + "url": "git+https://github.com/jquense/react-big-calendar.git" }, "license": "MIT", "main": "lib/index.js", @@ -95,6 +95,7 @@ "eslint-import-resolver-webpack": "^0.10.1", "eslint-plugin-import": "^2.14.0", "eslint-plugin-react": "^7.11.1", + "eslint-plugin-react-hooks": "^4.0.4", "font-awesome": "^4.7.0", "globalize": "^0.1.1", "husky": "^0.14.3", @@ -108,10 +109,10 @@ "postcss": "^7.0.16", "postcss-cli": "^6.1.2", "prettier": "^1.15.1", - "react": "^16.6.1", + "react": "^16.13.1", "react-bootstrap": "^0.32.4", "react-docgen": "^3.0.0-rc.1", - "react-dom": "^16.6.1", + "react-dom": "^16.13.1", "react-tackle-box": "^2.1.0", "rimraf": "^2.4.2", "rollup": "^1.1.0", @@ -134,8 +135,8 @@ "invariant": "^2.2.4", "lodash": "^4.17.11", "lodash-es": "^4.17.11", - "memoize-one": "^4.0.3", - "prop-types": "^15.6.2", + "memoize-one": "^5.1.1", + "prop-types": "^15.7.2", "react-overlays": "^2.0.0-0", "uncontrollable": "^7.0.0" }, @@ -143,9 +144,9 @@ "babel-core": "7.0.0-bridge.0" }, "bugs": { - "url": "https://github.com/intljusticemission/react-big-calendar/issues" + "url": "https://github.com/jquense/react-big-calendar/issues" }, "readme": "ERROR: No README data found!", - "homepage": "https://github.com/intljusticemission/react-big-calendar#readme", + "homepage": "https://github.com/jquense/react-big-calendar#readme", "_id": "react-big-calendar@0.22.1" } diff --git a/src/Agenda.js b/src/Agenda.js index 46e5feeca..e8e5a9b8e 100644 --- a/src/Agenda.js +++ b/src/Agenda.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types' -import React from 'react' +import React, { useRef, useEffect } from 'react' import addClass from 'dom-helpers/addClass' import removeClass from 'dom-helpers/removeClass' import getWidth from 'dom-helpers/width' @@ -10,75 +10,28 @@ import { navigate } from './utils/constants' import { inRange } from './utils/eventLevels' import { isSelected } from './utils/selection' -class Agenda extends React.Component { - constructor(props) { - super(props) - this.headerRef = React.createRef() - this.dateColRef = React.createRef() - this.timeColRef = React.createRef() - this.contentRef = React.createRef() - this.tbodyRef = React.createRef() - } - - componentDidMount() { - this._adjustHeader() - } - - componentDidUpdate() { - this._adjustHeader() - } - - render() { - let { length, date, events, accessors, localizer } = this.props - let { messages } = localizer - let end = dates.add(date, length, 'day') - - let range = dates.range(date, end, 'day') - - events = events.filter(event => inRange(event, date, end, accessors)) - - events.sort((a, b) => +accessors.start(a) - +accessors.start(b)) - - return ( -
- {events.length !== 0 ? ( - - - - - - - - - -
- {messages.date} - - {messages.time} - {messages.event}
-
- - - {range.map((day, idx) => this.renderDay(day, events, idx))} - -
-
-
- ) : ( - {messages.noEventsInRange} - )} -
- ) - } - - renderDay = (day, events, dayKey) => { - let { - selected, - getters, - accessors, - localizer, - components: { event: Event, date: AgendaDate }, - } = this.props +function Agenda({ + selected, + getters, + accessors, + localizer, + components, + length, + date, + events, +}) { + const headerRef = useRef(null) + const dateColRef = useRef(null) + const timeColRef = useRef(null) + const contentRef = useRef(null) + const tbodyRef = useRef(null) + + useEffect(() => { + _adjustHeader() + }) + + const renderDay = (day, events, dayKey) => { + const { event: Event, date: AgendaDate } = components events = events.filter(e => inRange(e, dates.startOf(day, 'day'), dates.endOf(day, 'day'), accessors) @@ -117,9 +70,7 @@ class Agenda extends React.Component { style={userProps.style} > {first} - - {this.timeRangeLabel(day, event)} - + {timeRangeLabel(day, event)} {Event ? : title} @@ -128,9 +79,7 @@ class Agenda extends React.Component { }, []) } - timeRangeLabel = (day, event) => { - let { accessors, localizer, components } = this.props - + const timeRangeLabel = (day, event) => { let labelClass = '', TimeComponent = components.time, label = localizer.messages.allDay @@ -164,27 +113,25 @@ class Agenda extends React.Component { ) } - _adjustHeader = () => { - if (!this.tbodyRef.current) return + const _adjustHeader = () => { + if (!tbodyRef.current) return - let header = this.headerRef.current - let firstRow = this.tbodyRef.current.firstChild + let header = headerRef.current + let firstRow = tbodyRef.current.firstChild if (!firstRow) return let isOverflowing = - this.contentRef.current.scrollHeight > - this.contentRef.current.clientHeight - let widths = this._widths || [] - - this._widths = [ - getWidth(firstRow.children[0]), - getWidth(firstRow.children[1]), - ] - - if (widths[0] !== this._widths[0] || widths[1] !== this._widths[1]) { - this.dateColRef.current.style.width = this._widths[0] + 'px' - this.timeColRef.current.style.width = this._widths[1] + 'px' + contentRef.current.scrollHeight > contentRef.current.clientHeight + + let _widths = [] + let widths = _widths + + _widths = [getWidth(firstRow.children[0]), getWidth(firstRow.children[1])] + + if (widths[0] !== _widths[0] || widths[1] !== _widths[1]) { + dateColRef.current.style.width = _widths[0] + 'px' + timeColRef.current.style.width = _widths[1] + 'px' } if (isOverflowing) { @@ -194,6 +141,46 @@ class Agenda extends React.Component { removeClass(header, 'rbc-header-overflowing') } } + + let { messages } = localizer + let end = dates.add(date, length, 'day') + + let range = dates.range(date, end, 'day') + + events = events.filter(event => inRange(event, date, end, accessors)) + + events.sort((a, b) => +accessors.start(a) - +accessors.start(b)) + + return ( +
+ {events.length !== 0 ? ( + + + + + + + + + +
+ {messages.date} + + {messages.time} + {messages.event}
+
+ + + {range.map((day, idx) => renderDay(day, events, idx))} + +
+
+
+ ) : ( + {messages.noEventsInRange} + )} +
+ ) } Agenda.propTypes = { diff --git a/src/BackgroundCells.js b/src/BackgroundCells.js index 31e44a22b..7b3d492d9 100644 --- a/src/BackgroundCells.js +++ b/src/BackgroundCells.js @@ -160,6 +160,7 @@ class BackgroundCells extends React.Component { action, bounds, box, + resourceId: this.props.resourceId, }) } } @@ -183,6 +184,7 @@ BackgroundCells.propTypes = { range: PropTypes.arrayOf(PropTypes.instanceOf(Date)), rtl: PropTypes.bool, type: PropTypes.string, + resourceId: PropTypes.any, } export default BackgroundCells diff --git a/src/Calendar.js b/src/Calendar.js index 83480d991..884e8fa06 100644 --- a/src/Calendar.js +++ b/src/Calendar.js @@ -250,7 +250,7 @@ class Calendar extends React.Component { /** * * ```js - * (dates: Date[] | { start: Date; end: Date }, view?: 'month'|'week'|'work_week'|'day'|'agenda') => void + * (dates: Date[] | { start: Date; end: Date }, view: 'month'|'week'|'work_week'|'day'|'agenda'|undefined) => void * ``` * * Callback fired when the visible date range changes. Returns an Array of dates @@ -312,6 +312,15 @@ class Calendar extends React.Component { */ onDoubleClickEvent: PropTypes.func, + /** + * Callback fired when a focused calendar event recieves a key press. + * + * ```js + * (event: Object, e: SyntheticEvent) => void + * ``` + */ + onKeyPressEvent: PropTypes.func, + /** * Callback fired when dragging a selection in the Time views. * @@ -454,7 +463,7 @@ class Calendar extends React.Component { longPressThreshold: PropTypes.number, /** - * Determines the selectable time increments in week and day views + * Determines the selectable time increments in week and day views, in minutes. */ step: PropTypes.number, @@ -493,16 +502,16 @@ class Calendar extends React.Component { * (date: Date, resourceId: (number|string)) => { className?: string, style?: Object } * ``` */ - slotPropGetter: PropTypes.func, - - /** - * Optionally provide a function that returns an object of props to be applied - * to the time-slot group node. Useful to dynamically change the sizing of time nodes. - * ```js - * () => { style?: Object } - * ``` - */ - slotGroupPropGetter: PropTypes.func, + slotPropGetter: PropTypes.func, + + /** + * Optionally provide a function that returns an object of props to be applied + * to the time-slot group node. Useful to dynamically change the sizing of time nodes. + * ```js + * () => { style?: Object } + * ``` + */ + slotGroupPropGetter: PropTypes.func, /** * Optionally provide a function that returns an object of className or style props @@ -784,8 +793,8 @@ class Calendar extends React.Component { resourceIdAccessor, resourceTitleAccessor, eventPropGetter, - slotPropGetter, - slotGroupPropGetter, + slotPropGetter, + slotGroupPropGetter, dayPropGetter, view, views, @@ -804,9 +813,9 @@ class Calendar extends React.Component { eventProp: (...args) => (eventPropGetter && eventPropGetter(...args)) || {}, slotProp: (...args) => - (slotPropGetter && slotPropGetter(...args)) || {}, - slotGroupProp: (...args) => - (slotGroupPropGetter && slotGroupPropGetter(...args)) || {}, + (slotPropGetter && slotPropGetter(...args)) || {}, + slotGroupProp: (...args) => + (slotGroupPropGetter && slotGroupPropGetter(...args)) || {}, dayProp: (...args) => (dayPropGetter && dayPropGetter(...args)) || {}, }, components: defaults(components[view] || {}, omit(components, names), { @@ -930,6 +939,7 @@ class Calendar extends React.Component { onDrillDown={this.handleDrillDown} onSelectEvent={this.handleSelectEvent} onDoubleClickEvent={this.handleDoubleClickEvent} + onKeyPressEvent={this.handleKeyPressEvent} onSelectSlot={this.handleSelectSlot} onShowMore={onShowMore} /> @@ -997,6 +1007,10 @@ class Calendar extends React.Component { notify(this.props.onDoubleClickEvent, args) } + handleKeyPressEvent = (...args) => { + notify(this.props.onKeyPressEvent, args) + } + handleSelectSlot = slotInfo => { notify(this.props.onSelectSlot, slotInfo) } diff --git a/src/DateContentRow.js b/src/DateContentRow.js index a0954c44c..931defeca 100644 --- a/src/DateContentRow.js +++ b/src/DateContentRow.js @@ -113,6 +113,7 @@ class DateContentRow extends React.Component { onSelectStart, onSelectEnd, onDoubleClick, + onKeyPress, resourceId, longPressThreshold, isAllDay, @@ -133,6 +134,7 @@ class DateContentRow extends React.Component { components, onSelect, onDoubleClick, + onKeyPress, resourceId, slotMetrics: metrics, } @@ -152,6 +154,7 @@ class DateContentRow extends React.Component { onSelectSlot={this.handleSelectSlot} components={components} longPressThreshold={longPressThreshold} + resourceId={resourceId} />
@@ -199,6 +202,7 @@ DateContentRow.propTypes = { onSelectEnd: PropTypes.func, onSelectStart: PropTypes.func, onDoubleClick: PropTypes.func, + onKeyPress: PropTypes.func, dayPropGetter: PropTypes.func, getNow: PropTypes.func.isRequired, diff --git a/src/DayColumn.js b/src/DayColumn.js index 8474a0eda..a3a2c04ed 100644 --- a/src/DayColumn.js +++ b/src/DayColumn.js @@ -99,6 +99,7 @@ class DayColumn extends React.Component { if (current >= min && current <= max) { const top = this.slotMetrics.getCurrentTimePosition(current) + this.intervalTriggered = true this.setState({ timeIndicatorPosition: top }) } else { this.clearTimeIndicatorInterval() @@ -163,7 +164,7 @@ class DayColumn extends React.Component { {localizer.format(selectDates, 'selectRangeFormat')}
)} - {isNow && ( + {isNow && this.intervalTriggered && (
this._select(event, e)} onDoubleClick={e => this._doubleClick(event, e)} + onKeyPress={e => this._keyPress(event, e)} /> ) }) @@ -349,7 +351,7 @@ class DayColumn extends React.Component { while (dates.lte(current, endDate)) { slots.push(current) - current = dates.add(current, this.props.step, 'minutes') + current = new Date(+current + this.props.step * 60 * 1000) // using Date ensures not to create an endless loop the day DST begins } notify(this.props.onSelectSlot, { @@ -370,6 +372,10 @@ class DayColumn extends React.Component { _doubleClick = (...args) => { notify(this.props.onDoubleClickEvent, args) } + + _keyPress = (...args) => { + notify(this.props.onKeyPressEvent, args) + } } DayColumn.propTypes = { @@ -401,6 +407,7 @@ DayColumn.propTypes = { onSelectSlot: PropTypes.func.isRequired, onSelectEvent: PropTypes.func.isRequired, onDoubleClickEvent: PropTypes.func.isRequired, + onKeyPressEvent: PropTypes.func.isRequired, className: PropTypes.string, dragThroughEvents: PropTypes.bool, diff --git a/src/EventCell.js b/src/EventCell.js index ff59ed8b8..e83b6e46a 100644 --- a/src/EventCell.js +++ b/src/EventCell.js @@ -13,6 +13,7 @@ class EventCell extends React.Component { isAllDay, onSelect, onDoubleClick, + onKeyPress, localizer, continuesPrior, continuesAfter, @@ -69,6 +70,7 @@ class EventCell extends React.Component { })} onClick={e => onSelect && onSelect(event, e)} onDoubleClick={e => onDoubleClick && onDoubleClick(event, e)} + onKeyPress={e => onKeyPress && onKeyPress(event, e)} > {typeof children === 'function' ? children(content) : content}
@@ -94,6 +96,7 @@ EventCell.propTypes = { onSelect: PropTypes.func, onDoubleClick: PropTypes.func, + onKeyPress: PropTypes.func, } export default EventCell diff --git a/src/EventRowMixin.js b/src/EventRowMixin.js index 6c2efb81a..589e16a1d 100644 --- a/src/EventRowMixin.js +++ b/src/EventRowMixin.js @@ -18,6 +18,7 @@ export default { onSelect: PropTypes.func, onDoubleClick: PropTypes.func, + onKeyPress: PropTypes.func, }, defaultProps: { @@ -33,6 +34,7 @@ export default { getters, onSelect, onDoubleClick, + onKeyPress, localizer, slotMetrics, components, @@ -50,6 +52,7 @@ export default { components={components} onSelect={onSelect} onDoubleClick={onDoubleClick} + onKeyPress={onKeyPress} continuesPrior={continuesPrior} continuesAfter={continuesAfter} slotStart={slotMetrics.first} diff --git a/src/Month.js b/src/Month.js index eb8cb769f..f358cd809 100644 --- a/src/Month.js +++ b/src/Month.js @@ -132,6 +132,7 @@ class MonthView extends React.Component { onShowMore={this.handleShowMore} onSelect={this.handleSelectEvent} onDoubleClick={this.handleDoubleClickEvent} + onKeyPress={this.handleKeyPressEvent} onSelectSlot={this.handleSelectSlot} longPressThreshold={longPressThreshold} rtl={this.props.rtl} @@ -214,11 +215,14 @@ class MonthView extends React.Component { components={components} localizer={localizer} position={overlay.position} + show={this.overlayDisplay} events={overlay.events} slotStart={overlay.date} slotEnd={overlay.end} onSelect={this.handleSelectEvent} onDoubleClick={this.handleDoubleClickEvent} + onKeyPress={this.handleKeyPressEvent} + handleDragStart={this.props.handleDragStart} /> )} @@ -255,6 +259,11 @@ class MonthView extends React.Component { notify(this.props.onDoubleClickEvent, args) } + handleKeyPressEvent = (...args) => { + this.clearSelection() + notify(this.props.onKeyPressEvent, args) + } + handleShowMore = (events, date, cell, slot, target) => { const { popup, onDrillDown, onShowMore, getDrilldownView } = this.props //cancel any pending selections so only the event click goes through. @@ -273,6 +282,12 @@ class MonthView extends React.Component { notify(onShowMore, [events, date, slot]) } + overlayDisplay = () => { + this.setState({ + overlay: null, + }) + } + selectDates(slotInfo) { let slots = this._pendingSelection.slice() @@ -323,11 +338,13 @@ MonthView.propTypes = { onSelectSlot: PropTypes.func, onSelectEvent: PropTypes.func, onDoubleClickEvent: PropTypes.func, + onKeyPressEvent: PropTypes.func, onShowMore: PropTypes.func, onDrillDown: PropTypes.func, getDrilldownView: PropTypes.func.isRequired, popup: PropTypes.bool, + handleDragStart: PropTypes.func, popupOffset: PropTypes.oneOfType([ PropTypes.number, diff --git a/src/Popup.js b/src/Popup.js index 43e8f590c..7421ea45b 100644 --- a/src/Popup.js +++ b/src/Popup.js @@ -38,6 +38,7 @@ class Popup extends React.Component { components, onSelect, onDoubleClick, + onKeyPress, slotStart, slotEnd, localizer, @@ -73,11 +74,15 @@ class Popup extends React.Component { accessors={accessors} components={components} onDoubleClick={onDoubleClick} + onKeyPress={onKeyPress} continuesPrior={dates.lt(accessors.end(event), slotStart, 'day')} continuesAfter={dates.gte(accessors.start(event), slotEnd, 'day')} slotStart={slotStart} slotEnd={slotEnd} selected={isSelected(event, selected)} + draggable={true} + onDragStart={() => this.props.handleDragStart(event)} + onDragEnd={() => this.props.show()} /> ))} @@ -103,6 +108,9 @@ Popup.propTypes = { localizer: PropTypes.object.isRequired, onSelect: PropTypes.func, onDoubleClick: PropTypes.func, + onKeyPress: PropTypes.func, + handleDragStart: PropTypes.func, + show: PropTypes.func, slotStart: PropTypes.instanceOf(Date), slotEnd: PropTypes.number, popperRef: PropTypes.oneOfType([ diff --git a/src/TimeGrid.js b/src/TimeGrid.js index 84a00436d..867baa554 100644 --- a/src/TimeGrid.js +++ b/src/TimeGrid.js @@ -101,6 +101,7 @@ export default class TimeGrid extends Component { start: slots[0], end: slots[slots.length - 1], action: slotInfo.action, + resourceId: slotInfo.resourceId, }) } @@ -221,6 +222,7 @@ export default class TimeGrid extends Component { onSelectSlot={this.handleSelectAllDaySlot} onSelectEvent={this.handleSelectAlldayEvent} onDoubleClickEvent={this.props.onDoubleClickEvent} + onKeyPressEvent={this.props.onKeyPressEvent} onDrillDown={this.props.onDrillDown} getDrilldownView={this.props.getDrilldownView} /> @@ -337,6 +339,7 @@ TimeGrid.propTypes = { onSelectStart: PropTypes.func, onSelectEvent: PropTypes.func, onDoubleClickEvent: PropTypes.func, + onKeyPressEvent: PropTypes.func, onDrillDown: PropTypes.func, getDrilldownView: PropTypes.func.isRequired, diff --git a/src/TimeGridEvent.js b/src/TimeGridEvent.js index b0ccfbaa9..c50a06ab2 100644 --- a/src/TimeGridEvent.js +++ b/src/TimeGridEvent.js @@ -20,6 +20,7 @@ function TimeGridEvent(props) { getters, onClick, onDoubleClick, + onKeyPress, components: { event: Event, eventWrapper: EventWrapper }, } = props let title = accessors.title(event) @@ -44,6 +45,7 @@ function TimeGridEvent(props) {
@@ -180,6 +181,7 @@ class TimeGridHeader extends React.Component { localizer={localizer} onSelect={this.props.onSelectEvent} onDoubleClick={this.props.onDoubleClickEvent} + onKeyPress={this.props.onKeyPressEvent} onSelectSlot={this.props.onSelectSlot} longPressThreshold={this.props.longPressThreshold} /> @@ -212,6 +214,7 @@ TimeGridHeader.propTypes = { onSelectSlot: PropTypes.func, onSelectEvent: PropTypes.func, onDoubleClickEvent: PropTypes.func, + onKeyPressEvent: PropTypes.func, onDrillDown: PropTypes.func, getDrilldownView: PropTypes.func.isRequired, scrollRef: PropTypes.any, diff --git a/src/addons/dragAndDrop/EventContainerWrapper.js b/src/addons/dragAndDrop/EventContainerWrapper.js index ec0cc985b..53eb96f76 100644 --- a/src/addons/dragAndDrop/EventContainerWrapper.js +++ b/src/addons/dragAndDrop/EventContainerWrapper.js @@ -133,6 +133,7 @@ class EventContainerWrapper extends React.Component { _selectable = () => { let node = findDOMNode(this) + let isBeingDragged = false let selector = (this._selector = new Selection(() => node.closest('.rbc-time-view') )) @@ -177,16 +178,22 @@ class EventContainerWrapper extends React.Component { this.handleDropFromOutside(point, bounds) }) - selector.on('selectStart', () => this.context.draggable.onStart()) + selector.on('selectStart', () => { + isBeingDragged = true + this.context.draggable.onStart() + }) selector.on('select', point => { const bounds = getBoundsForNode(node) - + isBeingDragged = false if (!this.state.event || !pointInColumn(bounds, point)) return this.handleInteractionEnd() }) - selector.on('click', () => this.context.draggable.onEnd(null)) + selector.on('click', () => { + if (isBeingDragged) this.reset() + this.context.draggable.onEnd(null) + }) selector.on('reset', () => { this.reset() diff --git a/src/localizers/date-fns.js b/src/localizers/date-fns.js index 7a45a97cc..e43e026b7 100644 --- a/src/localizers/date-fns.js +++ b/src/localizers/date-fns.js @@ -22,7 +22,7 @@ let weekRangeFormat = ({ start, end }, culture, local) => export let formats = { dateFormat: 'dd', - dayFormat: 'dd ddd', + dayFormat: 'dd eee', weekdayFormat: 'cccc', selectRangeFormat: timeRangeFormat, @@ -33,11 +33,11 @@ export let formats = { timeGutterFormat: 'p', monthHeaderFormat: 'MMMM yyyy', - dayHeaderFormat: 'dddd MMM dd', + dayHeaderFormat: 'cccc MMM dd', dayRangeHeaderFormat: weekRangeFormat, agendaHeaderFormat: dateRangeFormat, - agendaDateFormat: 'ddd MMM dd', + agendaDateFormat: 'ccc MMM dd', agendaTimeFormat: 'p', agendaTimeRangeFormat: timeRangeFormat, } diff --git a/src/utils/DateSlotMetrics.js b/src/utils/DateSlotMetrics.js index 269d56ee9..873680653 100644 --- a/src/utils/DateSlotMetrics.js +++ b/src/utils/DateSlotMetrics.js @@ -4,7 +4,8 @@ import { eventSegments, endOfRange, eventLevels } from './eventLevels' let isSegmentInSlot = (seg, slot) => seg.left <= slot && seg.right >= slot -const isEqual = (a, b) => a.range === b.range && a.events === b.events +const isEqual = (a, b) => + a[0].range === b[0].range && a[0].events === b[0].events export function getSlotMetrics() { return memoize(options => { diff --git a/src/utils/DayEventLayout.js b/src/utils/DayEventLayout.js index 4ee883772..c12fa5862 100644 --- a/src/utils/DayEventLayout.js +++ b/src/utils/DayEventLayout.js @@ -21,7 +21,7 @@ export function getStyledEvents({ dayLayoutAlgorithm, // one of DefaultAlgorithms keys // or custom function }) { - let algorithm = null + let algorithm = dayLayoutAlgorithm if (dayLayoutAlgorithm in DefaultAlgorithms) algorithm = DefaultAlgorithms[dayLayoutAlgorithm] diff --git a/src/utils/TimeSlots.js b/src/utils/TimeSlots.js index ce9bc06b1..4132c10c3 100644 --- a/src/utils/TimeSlots.js +++ b/src/utils/TimeSlots.js @@ -131,7 +131,7 @@ export function getSlotMetrics({ min: start, max: end, step, timeslots }) { const rangeStartMin = positionFromDate(rangeStart) const rangeEndMin = positionFromDate(rangeEnd) const top = - rangeEndMin - rangeStartMin < step && !dates.eq(end, rangeEnd) + rangeEndMin > step * numSlots && !dates.eq(end, rangeEnd) ? ((rangeStartMin - step) / (step * numSlots)) * 100 : (rangeStartMin / (step * numSlots)) * 100 diff --git a/test/utils/TimeSlots.test.js b/test/utils/TimeSlots.test.js index 9ec0d2971..770ec2dfc 100644 --- a/test/utils/TimeSlots.test.js +++ b/test/utils/TimeSlots.test.js @@ -2,8 +2,8 @@ import { getSlotMetrics } from '../../src/utils/TimeSlots' import * as dates from '../../src/utils/dates' describe('getSlotMetrics', () => { - const min = dates.startOf(new Date(), 'day') - const max = dates.endOf(new Date(), 'day') + const min = dates.startOf(new Date(2018, 0, 29, 0, 0, 0), 'day') + const max = dates.endOf(new Date(2018, 0, 29, 59, 59, 59), 'day') const slotMetrics = getSlotMetrics({ min, max, step: 60, timeslots: 1 }) test('getSlotMetrics.closestSlotToPosition: always returns timeslot if valid percentage is given', () => { expect(slotMetrics.closestSlotToPosition(0)).toBeDefined() @@ -24,8 +24,8 @@ describe('getSlotMetrics', () => { }) describe('getRange', () => { - const min = dates.startOf(new Date(), 'day') - const max = dates.endOf(new Date(), 'day') + const min = dates.startOf(new Date(2018, 0, 29, 0, 0, 0), 'day') + const max = dates.endOf(new Date(2018, 0, 29, 59, 59, 59), 'day') const slotMetrics = getSlotMetrics({ min, max, step: 60, timeslots: 1 }) test('getRange: 15 minute start of day appointment stays within calendar', () => { diff --git a/yarn.lock b/yarn.lock index b0839071a..4ff68d9b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1898,9 +1898,9 @@ acorn-walk@^6.0.1: integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw== acorn@^5.5.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.1, acorn@^6.0.5, acorn@^6.0.7, acorn@^6.1.1: version "6.1.1" @@ -3606,7 +3606,7 @@ commander@2.17.x: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@^2.14.1, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0, commander@~2.20.0: +commander@^2.14.1, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== @@ -5065,6 +5065,11 @@ eslint-plugin-import@^2.14.0: read-pkg-up "^2.0.0" resolve "^1.10.0" +eslint-plugin-react-hooks@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz#aed33b4254a41b045818cacb047b81e6df27fa58" + integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== + eslint-plugin-react@^7.11.1: version "7.13.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz#bc13fd7101de67996ea51b33873cd9dc2b7e5758" @@ -6210,13 +6215,14 @@ handle-thing@^2.0.0: integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== handlebars@^4.0.3, handlebars@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== + version "4.7.6" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" + integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== dependencies: + minimist "^1.2.5" neo-async "^2.6.0" - optimist "^0.6.1" source-map "^0.6.1" + wordwrap "^1.0.0" optionalDependencies: uglify-js "^3.1.4" @@ -8531,10 +8537,10 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memoize-one@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906" - integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA== +memoize-one@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== memoizerific@^1.11.3: version "1.11.3" @@ -8776,15 +8782,10 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== minipass@^2.2.1, minipass@^2.3.4: version "2.3.5" @@ -8944,9 +8945,9 @@ negotiator@0.6.2: integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== neo-async@^2.5.0, neo-async@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" - integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: version "2.1.0" @@ -9380,14 +9381,6 @@ opn@^5.4.0, opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optimize-css-assets-webpack-plugin@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.1.tgz#9eb500711d35165b45e7fd60ba2df40cb3eb9159" @@ -10691,7 +10684,17 @@ react-docgen@^3.0.0, react-docgen@^3.0.0-rc.1: node-dir "^0.1.10" recast "^0.16.0" -react-dom@^16.6.1, react-dom@^16.8.1: +react-dom@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-dom@^16.8.1: version "16.8.6" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== @@ -10874,7 +10877,16 @@ react-transition-group@^2.0.0, react-transition-group@^2.2.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react@^16.6.1, react@^16.8.1: +react@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +react@^16.8.1: version "16.8.6" resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== @@ -11654,6 +11666,14 @@ scheduler@^0.13.6: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^0.4.5: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" @@ -12844,12 +12864,9 @@ uglify-js@3.4.x: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.5.11" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.11.tgz#833442c0aa29b3a7d34344c7c63adaa3f3504f6a" - integrity sha512-izPJg8RsSyqxbdnqX36ExpbH3K7tDBsAU/VfNv89VkMFy3z39zFjunQGsSHOlGlyIfGLGprGeosgQno3bo2/Kg== - dependencies: - commander "~2.20.0" - source-map "~0.6.1" + version "3.10.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.4.tgz#dd680f5687bc0d7a93b14a3482d16db6eba2bfbb" + integrity sha512-kBFT3U4Dcj4/pJ52vfjCSfyLyvG9VYYuGYPmrPvAxRw/i7xHiT4VvCev+uiEMcEEiu6UNB6KgWmGtSUYIWScbw== uncontrollable@^5.0.0: version "5.1.0" @@ -13446,12 +13463,7 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - -wordwrap@~1.0.0: +wordwrap@^1.0.0, wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=