(this.container = el)}
- className="react-calendar-timeline"
+
- {this.header(
- canvasTimeStart,
- canvasTimeEnd,
- canvasWidth,
- minUnit,
- timeSteps,
- headerLabelGroupHeight,
- headerLabelHeight
- )}
- {sidebarWidth > 0 && this.sidebar(height, groupHeights, headerHeight)}
-
-
+
(this.container = el)}
+ className="react-calendar-timeline"
+ >
+ {this.renderHeaders()}
- {
- this.props.scrollRef(el);
- this.scrollComponent = el
- }}
- width={width}
- height={height}
- onZoom={this.changeZoom}
- onWheelZoom={this.handleWheelZoom}
- traditionalZoom={traditionalZoom}
- onScroll={this.onScroll}
- isInteractingWithItem={isInteractingWithItem}
- >
-
- {this.items(
- canvasTimeStart,
- zoom,
- canvasTimeEnd,
- canvasWidth,
- minUnit,
- dimensionItems,
- groupHeights,
- groupTops
- )}
- {this.columns(
- canvasTimeStart,
- canvasTimeEnd,
- canvasWidth,
- minUnit,
- timeSteps,
- height,
- headerHeight
- )}
- {this.rows(canvasWidth, groupHeights, groups)}
- {this.infoLabel()}
- {this.childrenWithProps(
- canvasTimeStart,
- canvasTimeEnd,
- canvasWidth,
- dimensionItems,
- groupHeights,
- groupTops,
- height,
- headerHeight,
- visibleTimeStart,
- visibleTimeEnd,
- minUnit,
- timeSteps
- )}
-
-
+ {sidebarWidth > 0 ? this.sidebar(height, groupHeights) : null}
+
+
+ {this.items(
+ canvasTimeStart,
+ zoom,
+ canvasTimeEnd,
+ canvasWidth,
+ minUnit,
+ dimensionItems,
+ groupHeights,
+ groupTops
+ )}
+ {this.columns(
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ minUnit,
+ timeSteps,
+ height,
+ headerHeight
+ )}
+ {this.rows(canvasWidth, groupHeights, groups)}
+ {this.infoLabel()}
+ {this.childrenWithProps(
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ dimensionItems,
+ groupHeights,
+ groupTops,
+ height,
+ headerHeight,
+ visibleTimeStart,
+ visibleTimeEnd,
+ minUnit,
+ timeSteps
+ )}
+
+
+ {rightSidebarWidth > 0
+ ? this.rightSidebar(height, groupHeights)
+ : null}
- {rightSidebarWidth > 0 && this.rightSidebar(height, groupHeights, headerHeight)}
-
+
)
}
-}
+}
\ No newline at end of file
diff --git a/src/lib/Timeline.scss b/src/lib/Timeline.scss
index a55a0c04d..7fd88e0f5 100644
--- a/src/lib/Timeline.scss
+++ b/src/lib/Timeline.scss
@@ -71,77 +71,6 @@ $weekend: rgba(250, 246, 225, 0.5);
}
}
- .rct-header {
- margin: 0;
- overflow-x: hidden;
- z-index: 90;
-
- .rct-top-header,
- .rct-bottom-header {
- position: relative;
- }
-
- .rct-label-group {
- padding: 0 5px;
- position: absolute;
- top: 0;
- font-size: 14px;
- text-align: center;
- cursor: pointer;
- border-left: $thick-border-width solid $border-color;
- color: $header-color;
- background: $header-background-color;
- border-bottom: $border-width solid $border-color;
- cursor: pointer;
- &.rct-has-right-sidebar {
- border-right: ($thick-border-width / 2) solid $border-color;
- border-left: ($thick-border-width / 2) solid $border-color;
- }
-
- & > span {
- position: sticky;
- left: 5px;
- right: 5px;
- }
- }
-
- .rct-label {
- position: absolute;
- // overflow: hidden;
- text-align: center;
- cursor: pointer;
- border-left: $border-width solid $border-color;
- color: $lower-header-color;
- background: $lower-header-background-color;
- border-bottom: $border-width solid $border-color;
- cursor: pointer;
-
- &.rct-label-only {
- color: $header-color;
- background: $header-background-color;
- }
-
- &.rct-first-of-type {
- border-left: $thick-border-width solid $border-color;
- }
- }
- }
-
- .rct-sidebar-header {
- margin: 0;
- color: $sidebar-color;
- background: $sidebar-background-color;
- border-right: $border-width solid $border-color;
- box-sizing: border-box;
- border-bottom: $border-width solid $border-color;
- overflow: hidden;
-
- &.rct-sidebar-right {
- border-right: 0;
- border-left: $border-width solid $border-color;
- }
- }
-
.rct-sidebar {
overflow: hidden;
white-space: normal; // was set to nowrap in .rct-outer
@@ -179,7 +108,7 @@ $weekend: rgba(250, 246, 225, 0.5);
.rct-vertical-lines {
.rct-vl {
position: absolute;
- border-left: 1px solid $border-color;
+ border-right: 1px solid $border-color;
z-index: 30;
&.rct-vl-first {
border-left-width: 2px;
diff --git a/src/lib/columns/Columns.js b/src/lib/columns/Columns.js
index 00077c085..86bcb82d0 100644
--- a/src/lib/columns/Columns.js
+++ b/src/lib/columns/Columns.js
@@ -2,17 +2,23 @@ import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { iterateTimes } from '../utility/calendar'
+import { TimelineStateConsumer } from '../timeline/TimelineStateContext'
-export default class Columns extends Component {
+const passThroughPropTypes = {
+ canvasTimeStart: PropTypes.number.isRequired,
+ canvasTimeEnd: PropTypes.number.isRequired,
+ canvasWidth: PropTypes.number.isRequired,
+ lineCount: PropTypes.number.isRequired,
+ minUnit: PropTypes.string.isRequired,
+ timeSteps: PropTypes.object.isRequired,
+ height: PropTypes.number.isRequired,
+ verticalLineClassNamesForTime: PropTypes.func
+}
+
+class Columns extends Component {
static propTypes = {
- canvasTimeStart: PropTypes.number.isRequired,
- canvasTimeEnd: PropTypes.number.isRequired,
- canvasWidth: PropTypes.number.isRequired,
- lineCount: PropTypes.number.isRequired,
- minUnit: PropTypes.string.isRequired,
- timeSteps: PropTypes.object.isRequired,
- height: PropTypes.number.isRequired,
- verticalLineClassNamesForTime: PropTypes.func
+ ...passThroughPropTypes,
+ getLeftOffsetFromDate: PropTypes.func.isRequired
}
shouldComponentUpdate(nextProps) {
@@ -37,7 +43,8 @@ export default class Columns extends Component {
minUnit,
timeSteps,
height,
- verticalLineClassNamesForTime
+ verticalLineClassNamesForTime,
+ getLeftOffsetFromDate
} = this.props
const ratio = canvasWidth / (canvasTimeEnd - canvasTimeStart)
@@ -49,13 +56,8 @@ export default class Columns extends Component {
minUnit,
timeSteps,
(time, nextTime) => {
- const left = Math.round((time.valueOf() - canvasTimeStart) * ratio, -2)
const minUnitValue = time.get(minUnit === 'day' ? 'date' : minUnit)
const firstOfType = minUnitValue === (minUnit === 'day' ? 1 : 0)
- const lineWidth = firstOfType ? 2 : 1
- const labelWidth =
- Math.ceil((nextTime.valueOf() - time.valueOf()) * ratio) - lineWidth
- const leftPush = firstOfType ? -1 : 0
let classNamesForTime = []
if (verticalLineClassNamesForTime) {
@@ -74,6 +76,8 @@ export default class Columns extends Component {
: '') +
classNamesForTime.join(' ')
+ const left = getLeftOffsetFromDate(time.valueOf())
+ const right = getLeftOffsetFromDate(nextTime.valueOf())
lines.push(
@@ -93,3 +97,19 @@ export default class Columns extends Component {
return
{lines}
}
}
+
+const ColumnsWrapper = ({ ...props }) => {
+ return (
+
+ {({ getLeftOffsetFromDate }) => (
+
+ )}
+
+ )
+}
+
+ColumnsWrapper.defaultProps = {
+ ...passThroughPropTypes
+}
+
+export default ColumnsWrapper
diff --git a/src/lib/default-config.js b/src/lib/default-config.js
index 7523af171..d3d6cbc7a 100644
--- a/src/lib/default-config.js
+++ b/src/lib/default-config.js
@@ -20,6 +20,40 @@ export const defaultTimeSteps = {
year: 1
}
+export const defaultHeaderFormats = {
+ year: {
+ long: 'YYYY',
+ mediumLong: 'YYYY',
+ medium: 'YYYY',
+ short: 'YY'
+ },
+ month: {
+ long: 'MMMM YYYY',
+ mediumLong: 'MMMM',
+ medium: 'MMMM',
+ short: 'MM/YY'
+ },
+ day: {
+ long: 'dddd, LL',
+ mediumLong: 'dddd, LL',
+ medium: 'dd D',
+ short: 'D'
+ },
+ hour: {
+ long: 'dddd, LL, HH:00',
+ mediumLong: 'L, HH:00',
+ medium: 'HH:00',
+ short: 'HH'
+ },
+ minute: {
+ long: 'HH:mm',
+ mediumLong: 'HH:mm',
+ medium: 'HH:mm',
+ short: 'mm',
+ }
+}
+
+//TODO: delete this
export const defaultHeaderLabelFormats = {
yearShort: 'YY',
yearLong: 'YYYY',
@@ -36,6 +70,7 @@ export const defaultHeaderLabelFormats = {
time: 'LLL'
}
+//TODO: delete this
export const defaultSubHeaderLabelFormats = {
yearShort: 'YY',
yearLong: 'YYYY',
diff --git a/src/lib/headers/CustomHeader.js b/src/lib/headers/CustomHeader.js
new file mode 100644
index 000000000..b1cc46103
--- /dev/null
+++ b/src/lib/headers/CustomHeader.js
@@ -0,0 +1,247 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { TimelineHeadersConsumer } from './HeadersContext'
+import { TimelineStateConsumer } from '../timeline/TimelineStateContext'
+import { iterateTimes, calculateXPositionForTime } from '../utility/calendar'
+
+export class CustomHeader extends React.Component {
+ static propTypes = {
+ //component props
+ children: PropTypes.func.isRequired,
+ unit: PropTypes.string.isRequired,
+ timeSteps: PropTypes.object.isRequired,
+ //Timeline context
+ visibleTimeStart: PropTypes.number.isRequired,
+ visibleTimeEnd: PropTypes.number.isRequired,
+ canvasTimeStart: PropTypes.number.isRequired,
+ canvasTimeEnd: PropTypes.number.isRequired,
+ canvasWidth: PropTypes.number.isRequired,
+ showPeriod: PropTypes.func.isRequired,
+ props: PropTypes.object,
+ getLeftOffsetFromDate: PropTypes.func.isRequired,
+ }
+ constructor(props) {
+ super(props)
+ const {
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ unit,
+ timeSteps,
+ showPeriod,
+ getLeftOffsetFromDate,
+ } = props
+ const ratio = this.calculateRatio(
+ canvasWidth,
+ canvasTimeEnd,
+ canvasTimeStart
+ )
+ const intervals = this.getHeaderIntervals({
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ unit,
+ timeSteps,
+ showPeriod,
+ getLeftOffsetFromDate,
+ })
+
+ this.state = {
+ intervals,
+ ratio
+ }
+ }
+
+ shouldComponentUpdate(nextProps) {
+ if (
+ nextProps.canvasTimeStart !== this.props.canvasTimeStart ||
+ nextProps.canvasTimeEnd !== this.props.canvasTimeEnd ||
+ nextProps.canvasWidth !== this.props.canvasWidth ||
+ nextProps.unit !== this.props.unit ||
+ nextProps.timeSteps !== this.props.timeSteps ||
+ nextProps.showPeriod !== this.props.showPeriod ||
+ nextProps.children !== this.props.children
+ ) {
+ return true
+ }
+ return false
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (
+ nextProps.canvasTimeStart !== this.props.canvasTimeStart ||
+ nextProps.canvasTimeEnd !== this.props.canvasTimeEnd ||
+ nextProps.canvasWidth !== this.props.canvasWidth ||
+ nextProps.unit !== this.props.unit ||
+ nextProps.timeSteps !== this.props.timeSteps ||
+ nextProps.showPeriod !== this.props.showPeriod
+ ) {
+ const {
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ unit,
+ timeSteps,
+ showPeriod,
+ getLeftOffsetFromDate,
+ } = nextProps
+ const ratio = this.calculateRatio(
+ canvasWidth,
+ canvasTimeEnd,
+ canvasTimeStart
+ )
+ const intervals = this.getHeaderIntervals({
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ unit,
+ timeSteps,
+ showPeriod,
+ getLeftOffsetFromDate,
+ })
+
+ this.setState({ intervals, ratio })
+ }
+ }
+
+ getHeaderIntervals = ({
+ canvasTimeStart,
+ canvasTimeEnd,
+ unit,
+ timeSteps,
+ getLeftOffsetFromDate,
+ }) => {
+ const intervals = []
+ iterateTimes(
+ canvasTimeStart,
+ canvasTimeEnd,
+ unit,
+ timeSteps,
+ (startTime, endTime) => {
+ const left = getLeftOffsetFromDate(startTime.valueOf())
+ const right = getLeftOffsetFromDate(endTime.valueOf())
+ const width = right-left
+ intervals.push({
+ startTime,
+ endTime,
+ labelWidth: width,
+ left,
+ })
+ }
+ )
+ return intervals
+ }
+
+ rootProps = {
+ style: {
+ position: 'relative'
+ }
+ }
+
+ getRootProps = (props = {}) => {
+ const { style } = props
+ return {
+ style: Object.assign({}, style ? style : {}, {
+ position: 'relative',
+ width: this.props.canvasWidth
+ })
+ }
+ }
+
+ getIntervalProps = (props = {}) => {
+ const { interval, style } = props
+ if (!interval) throw new Error("you should provide interval to the prop getter")
+ const { startTime, labelWidth, left } = interval
+ return {
+ style: this.getIntervalStyle({
+ style,
+ startTime,
+ labelWidth,
+ canvasTimeStart: this.props.canvasTimeStart,
+ unit: this.props.unit,
+ ratio: this.state.ratio,
+ left,
+ }),
+ key: `label-${startTime.valueOf()}`
+ }
+ }
+
+ calculateRatio(canvasWidth, canvasTimeEnd, canvasTimeStart) {
+ return canvasWidth / (canvasTimeEnd - canvasTimeStart)
+ }
+
+ getIntervalStyle = ({ startTime, canvasTimeStart, ratio, unit, left,labelWidth, style, }) => {
+
+ return {
+ ...style,
+ left,
+ width: labelWidth,
+ position: 'absolute'
+ }
+ }
+
+ getStateAndHelpers = () => {
+ const {
+ canvasTimeStart,
+ canvasTimeEnd,
+ unit,
+ showPeriod,
+ timelineWidth,
+ visibleTimeStart,
+ visibleTimeEnd
+ } = this.props
+ //TODO: only evaluate on changing params
+ return {
+ timelineContext: {
+ timelineWidth,
+ visibleTimeStart,
+ visibleTimeEnd,
+ canvasTimeStart,
+ canvasTimeEnd
+ },
+ headerContext: {
+ unit,
+ intervals: this.state.intervals
+ },
+ getRootProps: this.getRootProps,
+ getIntervalProps: this.getIntervalProps,
+ showPeriod
+ }
+ }
+
+ render() {
+ const props = this.getStateAndHelpers()
+ return this.props.children(props, this.props.props)
+ }
+}
+
+const CustomHeaderWrapper = ({ children, unit, props }) => (
+
+ {({ getTimelineState, showPeriod, getLeftOffsetFromDate }) => {
+ const timelineState = getTimelineState()
+ return (
+
+ {({ timeSteps }) => (
+
+ )}
+
+ )
+ }}
+
+)
+
+CustomHeaderWrapper.propTypes = {
+ children: PropTypes.func.isRequired,
+ unit: PropTypes.string,
+ props: PropTypes.object,
+}
+
+export default CustomHeaderWrapper
diff --git a/src/lib/headers/DateHeader.js b/src/lib/headers/DateHeader.js
new file mode 100644
index 000000000..d5a1d3962
--- /dev/null
+++ b/src/lib/headers/DateHeader.js
@@ -0,0 +1,173 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { TimelineStateConsumer } from '../timeline/TimelineStateContext'
+import CustomHeader from './CustomHeader'
+import { getNextUnit } from '../utility/calendar'
+import { defaultHeaderFormats } from '../default-config'
+import Interval from './Interval'
+
+class DateHeader extends React.PureComponent {
+ static propTypes = {
+ primaryHeader: PropTypes.bool,
+ secondaryHeader: PropTypes.bool,
+ unit: PropTypes.string,
+ style: PropTypes.object,
+ className: PropTypes.string,
+ timelineUnit: PropTypes.string,
+ labelFormat: PropTypes.oneOfType([
+ PropTypes.func,
+ PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+ PropTypes.string
+ ]).isRequired,
+ intervalRenderer: PropTypes.func,
+ props: PropTypes.object,
+ }
+
+ getHeaderUnit = () => {
+ if (this.props.unit) {
+ return this.props.unit
+ } else if (this.props.primaryHeader) {
+ return getNextUnit(this.props.timelineUnit)
+ }
+ return this.props.timelineUnit
+ }
+
+ render() {
+ const unit = this.getHeaderUnit()
+ const {props} = this.props;
+ return (
+
+ {({
+ headerContext: { intervals },
+ getRootProps,
+ getIntervalProps,
+ showPeriod
+ }, props) => {
+ const unit = this.getHeaderUnit()
+
+ return (
+
+ {intervals.map(interval => {
+ const intervalText = this.getLabelFormat(
+ [interval.startTime, interval.endTime],
+ unit,
+ interval.labelWidth
+ )
+ return (
+
+ )
+ })}
+
+ )
+ }}
+
+ )
+ }
+
+ getRootStyle = () => {
+ return {
+ height: 30,
+ ...this.props.style
+ }
+ }
+
+ getLabelFormat(interval, unit, labelWidth) {
+ const { labelFormat } = this.props
+ if (typeof labelFormat === 'string') {
+ const startTime = interval[0]
+ return startTime.format(labelFormat)
+ } else if (typeof labelFormat === 'object') {
+ return formatLabel(interval, unit, labelWidth, labelFormat)
+ } else if (typeof labelFormat === 'function') {
+ return labelFormat(interval, unit, labelWidth)
+ } else {
+ throw new Error('labelFormat should be function, object or string')
+ }
+ }
+}
+
+const DateHeaderWrapper = ({
+ primaryHeader,
+ secondaryHeader,
+ unit,
+ labelFormat,
+ style,
+ className,
+ intervalRenderer,
+ props,
+}) => (
+
+ {({ getTimelineState }) => {
+ const timelineState = getTimelineState()
+ return (
+
+ )
+ }}
+
+)
+
+DateHeaderWrapper.propTypes = {
+ style: PropTypes.object,
+ className: PropTypes.string,
+ primaryHeader: PropTypes.bool,
+ secondaryHeader: PropTypes.bool,
+ unit: PropTypes.string,
+ labelFormat: PropTypes.oneOfType([
+ PropTypes.func,
+ PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+ PropTypes.string
+ ]),
+ intervalRenderer: PropTypes.func,
+ props: PropTypes.object,
+}
+
+DateHeaderWrapper.defaultProps = {
+ secondaryHeader: true,
+ labelFormat: formatLabel
+}
+
+function formatLabel(
+ [timeStart, timeEnd],
+ unit,
+ labelWidth,
+ formatOptions = defaultHeaderFormats
+) {
+ let format
+ if (labelWidth >= 150) {
+ format = formatOptions[unit]['long']
+ } else if (labelWidth >= 100) {
+ format = formatOptions[unit]['mediumLong']
+ } else if (labelWidth >= 50) {
+ format = formatOptions[unit]['medium']
+ } else {
+ format = formatOptions[unit]['short']
+ }
+ return timeStart.format(format)
+}
+
+export default DateHeaderWrapper
diff --git a/src/lib/headers/HeadersContext.js b/src/lib/headers/HeadersContext.js
new file mode 100644
index 000000000..d2a6830fe
--- /dev/null
+++ b/src/lib/headers/HeadersContext.js
@@ -0,0 +1,42 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import createReactContext from 'create-react-context'
+import { noop } from '../utility/generic'
+
+const defaultContextState = {
+ registerScroll: () => {
+ // eslint-disable-next-line
+ console.warn('default registerScroll header used')
+ return noop
+ },
+ rightSidebarWidth: 0,
+ leftSidebarWidth: 150,
+ timeSteps: {}
+}
+
+const { Consumer, Provider } = createReactContext(defaultContextState)
+
+
+export class TimelineHeadersProvider extends React.Component {
+ static propTypes = {
+ children: PropTypes.element.isRequired,
+ rightSidebarWidth: PropTypes.number,
+ leftSidebarWidth: PropTypes.number.isRequired,
+ //TODO: maybe this should be skipped?
+ timeSteps: PropTypes.object.isRequired,
+ registerScroll: PropTypes.func.isRequired,
+ }
+
+
+ render() {
+ const contextValue = {
+ rightSidebarWidth: this.props.rightSidebarWidth,
+ leftSidebarWidth: this.props.leftSidebarWidth,
+ timeSteps: this.props.timeSteps,
+ registerScroll: this.props.registerScroll,
+ }
+ return
{this.props.children}
+ }
+}
+
+export const TimelineHeadersConsumer = Consumer
diff --git a/src/lib/headers/Interval.js b/src/lib/headers/Interval.js
new file mode 100644
index 000000000..7da76f3a7
--- /dev/null
+++ b/src/lib/headers/Interval.js
@@ -0,0 +1,84 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { getNextUnit } from '../utility/calendar'
+import { composeEvents } from '../utility/events'
+
+class Interval extends React.PureComponent {
+ static propTypes = {
+ intervalRenderer: PropTypes.func,
+ unit: PropTypes.string.isRequired,
+ interval: PropTypes.object.isRequired,
+ showPeriod: PropTypes.func.isRequired,
+ intervalText: PropTypes.string.isRequired,
+ primaryHeader: PropTypes.bool.isRequired,
+ secondaryHeader: PropTypes.bool.isRequired,
+ getIntervalProps: PropTypes.func.isRequired,
+ props: PropTypes.object
+ }
+
+ getIntervalStyle = () => {
+ return {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor:
+ this.props.secondaryHeader && !this.props.primaryHeader
+ ? 'rgb(240, 240, 240)'
+ : 'initial',
+ height: '100%',
+ borderLeft: this.props.primaryHeader
+ ? '1px solid #bbb'
+ : '2px solid #bbb',
+ borderRight: this.props.primaryHeader ? '1px solid #bbb' : 'none',
+ borderBottom: '1px solid #bbb',
+ color: this.props.primaryHeader ? '#fff' : 'initial',
+ cursor: 'pointer',
+ fontSize: '14px'
+ }
+ }
+
+ onIntervalClick = () => {
+ const { primaryHeader, interval, unit, showPeriod } = this.props
+ if (primaryHeader) {
+ const nextUnit = getNextUnit(unit)
+ const newStartTime = interval.startTime.clone().startOf(nextUnit)
+ const newEndTime = interval.startTime.clone().endOf(nextUnit)
+ showPeriod(newStartTime, newEndTime)
+ } else {
+ showPeriod(interval.startTime, interval.endTime)
+ }
+ }
+
+ getIntervalProps = (props={}) => {
+ return {
+ ...this.props.getIntervalProps({
+ interval: this.props.interval,
+ ...props
+ }),
+ onClick: composeEvents(this.onIntervalClick, props.onClick)
+ }
+ }
+
+ render() {
+ const { intervalText, interval, intervalRenderer, props } = this.props
+ if (intervalRenderer)
+ return intervalRenderer({
+ getIntervalProps: this.getIntervalProps,
+ intervalContext: {
+ interval,
+ intervalText
+ }
+ }, props)
+ return (
+
+ {intervalText}
+
+ )
+ }
+}
+
+export default Interval
diff --git a/src/lib/headers/ItemHeader.js b/src/lib/headers/ItemHeader.js
new file mode 100644
index 000000000..e97137db0
--- /dev/null
+++ b/src/lib/headers/ItemHeader.js
@@ -0,0 +1,238 @@
+import React from 'react'
+import { TimelineStateConsumer } from '../timeline/TimelineStateContext'
+import CustomHeader from './CustomHeader'
+import PropTypes from 'prop-types'
+import {
+ getItemDimensions,
+ stackGroup
+} from '../utility/calendar'
+import { _get } from '../utility/generic'
+
+const passThroughPropTypes = {
+ style: PropTypes.object,
+ className: PropTypes.string,
+ props: PropTypes.object,
+ items: PropTypes.arrayOf(PropTypes.object).isRequired,
+ itemHeight: PropTypes.number,
+ stackItems: PropTypes.bool,
+ itemRenderer: PropTypes.func,
+}
+
+class ItemHeader extends React.PureComponent {
+ static propTypes = {
+ visibleTimeStart: PropTypes.number.isRequired,
+ visibleTimeEnd: PropTypes.number.isRequired,
+ canvasTimeStart: PropTypes.number.isRequired,
+ canvasTimeEnd: PropTypes.number.isRequired,
+ canvasWidth: PropTypes.number.isRequired,
+ keys: PropTypes.object.isRequired,
+ ...passThroughPropTypes
+ }
+
+ static defaultProps = {
+ itemHeight: 30,
+ stackItems: false,
+ itemRenderer: ({ item, getRootProps }) => {
+ return (
+
+ {item.title}
+
+ )
+ }
+ }
+
+ getStateAndHelpers = (props, item, itemDimensions) => {
+ const {
+ canvasWidth: timelineWidth,
+ visibleTimeStart,
+ visibleTimeEnd,
+ canvasTimeStart,
+ canvasTimeEnd,
+ itemHeight
+ } = props
+ return {
+ timelineContext: {
+ timelineWidth,
+ visibleTimeStart,
+ visibleTimeEnd,
+ canvasTimeStart,
+ canvasTimeEnd
+ },
+ item,
+ itemContext: {
+ dimensions: itemDimensions,
+ width: itemDimensions.width
+ },
+ itemHeight
+ }
+ }
+
+ render() {
+ const {
+ keys,
+ items,
+ itemHeight,
+ itemRenderer,
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ stackItems
+ } = this.props
+ const itemDimensions = items.map(item => {
+ return getItemDimensions({
+ item,
+ keys,
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ groupOrders: {},
+ lineHeight: itemHeight,
+ itemHeightRatio: 1
+ })
+ })
+
+ const { groupHeight } = stackGroup(
+ itemDimensions,
+ stackItems,
+ itemHeight,
+ 0
+ )
+ const height = Math.max(itemHeight, groupHeight)
+
+ return (
+
+ {({ getRootProps }) => {
+ return (
+
+ {items.map(item => {
+ const itemId = _get(item, keys.itemIdKey)
+ const dimensions = itemDimensions.find(
+ itemDimension => itemDimension.id === itemId
+ ).dimensions
+ return (
+
+ )
+ })}
+
+ )
+ }}
+
+ )
+ }
+
+ getRootStyles(height) {
+ return {
+ ...this.props.style,
+ height
+ }
+ }
+}
+
+class Item extends React.PureComponent {
+ static propTypes = {
+ item: PropTypes.object.isRequired,
+ timelineContext: PropTypes.shape({
+ timelineWidth: PropTypes.number,
+ visibleTimeStart: PropTypes.number,
+ visibleTimeEnd: PropTypes.number,
+ canvasTimeStart: PropTypes.number,
+ canvasTimeEnd: PropTypes.number
+ }).isRequired,
+ itemContext: PropTypes.shape({
+ dimensions: PropTypes.object,
+ width: PropTypes.number
+ }).isRequired,
+ itemRenderer: passThroughPropTypes['itemRenderer'],
+ itemHeight: passThroughPropTypes['itemHeight'],
+ props: PropTypes.object,
+ }
+
+ getStyles = (style = {}, dimensions, itemHeight) => {
+ return {
+ position: 'absolute',
+ left: dimensions.left,
+ top: dimensions.top,
+ width: dimensions.width,
+ height: itemHeight,
+ ...style
+ }
+ }
+
+ getRootProps = (props = {}) => {
+ const { style, ...rest } = props
+ return {
+ style: this.getStyles(
+ style,
+ this.props.itemContext.dimensions,
+ this.props.itemHeight
+ ),
+ rest
+ }
+ }
+
+ render() {
+ const { item, timelineContext, itemContext, props } = this.props
+ return this.props.itemRenderer({
+ item,
+ timelineContext,
+ itemContext,
+ getRootProps: this.getRootProps,
+ props,
+ })
+ }
+}
+
+const ItemHeaderWrapper = ({
+ style,
+ className,
+ props,
+ items,
+ stackItems,
+ itemHeight,
+ itemRenderer
+}) => (
+
+ {({ getTimelineState }) => {
+ const timelineState = getTimelineState()
+ return (
+
+ )
+ }}
+
+)
+
+ItemHeaderWrapper.propTypes = {
+ ...passThroughPropTypes
+}
+
+export default ItemHeaderWrapper
diff --git a/src/lib/headers/SidebarHeader.js b/src/lib/headers/SidebarHeader.js
new file mode 100644
index 000000000..3ad4aa603
--- /dev/null
+++ b/src/lib/headers/SidebarHeader.js
@@ -0,0 +1,68 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { TimelineHeadersConsumer } from './HeadersContext'
+import { LEFT_VARIANT, RIGHT_VARIANT } from './constants'
+
+class SidebarHeader extends React.PureComponent {
+ static propTypes = {
+ children: PropTypes.func.isRequired,
+ rightSidebarWidth: PropTypes.number,
+ leftSidebarWidth: PropTypes.number.isRequired,
+ variant: PropTypes.string,
+ props: PropTypes.object
+ }
+
+ getRootProps = (props = {}) => {
+ const { style } = props
+ const width =
+ this.props.variant === RIGHT_VARIANT
+ ? this.props.rightSidebarWidth
+ : this.props.leftSidebarWidth
+ return {
+ style: {
+ width,
+ ...style
+ }
+ }
+ }
+
+ getStateAndHelpers = () => {
+ return {
+ getRootProps: this.getRootProps
+ }
+ }
+
+ render() {
+ const props = this.getStateAndHelpers()
+ return this.props.children(props, this.props.props)
+ }
+}
+
+const SidebarWrapper = ({ children, variant, props }) => (
+
+ {({ leftSidebarWidth, rightSidebarWidth }) => {
+ return (
+
+ )
+ }}
+
+)
+
+SidebarWrapper.propTypes = {
+ children: PropTypes.func.isRequired,
+ variant: PropTypes.string,
+ props: PropTypes.object
+}
+
+SidebarWrapper.defaultProps = {
+ variant: LEFT_VARIANT,
+ children: ({ getRootProps }) =>
+}
+
+export default SidebarWrapper
diff --git a/src/lib/headers/TimelineHeaders.js b/src/lib/headers/TimelineHeaders.js
new file mode 100644
index 000000000..080513253
--- /dev/null
+++ b/src/lib/headers/TimelineHeaders.js
@@ -0,0 +1,126 @@
+import React from 'react'
+import { TimelineHeadersConsumer } from './HeadersContext'
+import PropTypes from 'prop-types'
+import SidebarHeader from './SidebarHeader'
+import { RIGHT_VARIANT, LEFT_VARIANT } from './constants'
+import { TimelineStateConsumer } from '../timeline/TimelineStateContext';
+class TimelineHeaders extends React.PureComponent {
+ static propTypes = {
+ registerScroll: PropTypes.func.isRequired,
+ leftSidebarWidth: PropTypes.number.isRequired,
+ rightSidebarWidth: PropTypes.number.isRequired,
+ style: PropTypes.object,
+ className: PropTypes.string,
+ calendarHeaderStyle: PropTypes.object,
+ calendarHeaderClassName: PropTypes.string,
+ width: PropTypes.number.isRequired
+ }
+
+ constructor(props) {
+ super(props)
+ }
+
+ getRootStyle = () => {
+ return {
+ background: '#c52020',
+ borderBottom: '1px solid #bbb',
+ ...this.props.style,
+ display: 'flex',
+ width: 'max-content'
+ }
+ }
+
+ getCalendarHeaderStyle = () => {
+ const {
+ leftSidebarWidth,
+ rightSidebarWidth,
+ calendarHeaderStyle
+ } = this.props
+ return {
+ border: '1px solid #bbb',
+ ...calendarHeaderStyle,
+ overflow: 'hidden',
+ width: this.props.width
+ }
+ }
+
+ render() {
+ let rightSidebarHeader
+ let leftSidebarHeader
+ let calendarHeaders = []
+ const children = Array.isArray(this.props.children)
+ ? this.props.children.filter(c => c)
+ : [this.props.children]
+ React.Children.map(children, child => {
+ if (
+ child.type === SidebarHeader &&
+ child.props.variant === RIGHT_VARIANT
+ ) {
+ rightSidebarHeader = child
+ } else if (
+ child.type === SidebarHeader &&
+ child.props.variant === LEFT_VARIANT
+ ) {
+ leftSidebarHeader = child
+ } else {
+ calendarHeaders.push(child)
+ }
+ })
+ return (
+
+ {leftSidebarHeader}
+
+ {calendarHeaders}
+
+ {rightSidebarHeader}
+
+ )
+ }
+}
+
+const TimelineHeadersWrapper = ({
+ children,
+ style,
+ className,
+ calendarHeaderStyle,
+ calendarHeaderClassName
+}) => (
+
+ {({ getTimelineState, showPeriod }) => {
+ const state = getTimelineState()
+ return (
+
+ {({ leftSidebarWidth, rightSidebarWidth, registerScroll }) => {
+ return (
+
+ )
+ }}
+
+ )
+ }}
+
+)
+
+TimelineHeadersWrapper.propTypes = {
+ style: PropTypes.object,
+ className: PropTypes.string,
+ calendarHeaderStyle: PropTypes.object,
+ calendarHeaderClassName: PropTypes.string
+}
+
+export default TimelineHeadersWrapper
diff --git a/src/lib/headers/constants.js b/src/lib/headers/constants.js
new file mode 100644
index 000000000..6f7a4686c
--- /dev/null
+++ b/src/lib/headers/constants.js
@@ -0,0 +1,3 @@
+export const LEFT_VARIANT= 'left'
+export const RIGHT_VARIANT= 'right'
+
diff --git a/src/lib/items/Item.js b/src/lib/items/Item.js
index af11c26b2..a9b095009 100644
--- a/src/lib/items/Item.js
+++ b/src/lib/items/Item.js
@@ -113,7 +113,7 @@ export default class Item extends Component {
nextProps.canMove !== this.props.canMove ||
nextProps.canResizeLeft !== this.props.canResizeLeft ||
nextProps.canResizeRight !== this.props.canResizeRight ||
- nextProps.dimensions !== this.props.dimensions
+ !deepObjectCompare(nextProps.dimensions, this.props.dimensions)
return shouldUpdate
}
@@ -261,7 +261,6 @@ export default class Item extends Component {
if (this.state.dragging) {
let dragTime = this.dragTime(e)
let dragGroupDelta = this.dragGroupDelta(e)
-
if (this.props.moveResizeValidator) {
dragTime = this.props.moveResizeValidator(
'move',
@@ -384,7 +383,9 @@ export default class Item extends Component {
}
})
.on('tap', e => {
- this.actualClick(e, e.pointerType === 'mouse' ? 'click' : 'touch')
+ if(e.pointerType !== 'mouse'){
+ this.actualClick(e, 'touch')
+ }
})
this.setState({
@@ -414,7 +415,6 @@ export default class Item extends Component {
componentDidUpdate(prevProps) {
this.cacheDataFromProps(this.props)
-
let { interactMounted } = this.state
const couldDrag = prevProps.selected && this.canMove(prevProps)
const couldResizeLeft =
@@ -427,46 +427,40 @@ export default class Item extends Component {
const willBeAbleToResizeRight =
this.props.selected && this.canResizeRight(this.props)
- if (this.props.selected && !interactMounted) {
- this.mountInteract()
- interactMounted = true
- }
-
- if (
- interactMounted &&
- (couldResizeLeft !== willBeAbleToResizeLeft ||
- couldResizeRight !== willBeAbleToResizeRight)
- ) {
- const leftResize = this.props.useResizeHandle ? this.dragLeft : true
- const rightResize = this.props.useResizeHandle ? this.dragRight : true
-
- interact(this.item).resizable({
- enabled: willBeAbleToResizeLeft || willBeAbleToResizeRight,
- edges: {
- top: false,
- bottom: false,
- left: willBeAbleToResizeLeft && leftResize,
- right: willBeAbleToResizeRight && rightResize
- }
- })
- }
- if (interactMounted && couldDrag !== willBeAbleToDrag) {
- interact(this.item).draggable({ enabled: willBeAbleToDrag })
+ if(!!this.item){
+ if (this.props.selected && !interactMounted) {
+ this.mountInteract()
+ interactMounted = true
+ }
+ if (
+ interactMounted &&
+ (couldResizeLeft !== willBeAbleToResizeLeft ||
+ couldResizeRight !== willBeAbleToResizeRight)
+ ) {
+ const leftResize = this.props.useResizeHandle ? this.dragLeft : true
+ const rightResize = this.props.useResizeHandle ? this.dragRight : true
+
+ interact(this.item).resizable({
+ enabled: willBeAbleToResizeLeft || willBeAbleToResizeRight,
+ edges: {
+ top: false,
+ bottom: false,
+ left: willBeAbleToResizeLeft && leftResize,
+ right: willBeAbleToResizeRight && rightResize
+ }
+ })
+ }
+ if (interactMounted && couldDrag !== willBeAbleToDrag) {
+ interact(this.item).draggable({ enabled: willBeAbleToDrag })
+ }
}
- }
-
- onMouseDown = e => {
- if (!this.state.interactMounted) {
- e.preventDefault()
- this.startedClicking = true
+ else{
+ interactMounted= false;
}
- }
+ this.setState({
+ interactMounted,
+ })
- onMouseUp = e => {
- if (!this.state.interactMounted && this.startedClicking) {
- this.startedClicking = false
- this.actualClick(e, 'click')
- }
}
onTouchStart = e => {
@@ -519,13 +513,12 @@ export default class Item extends Component {
ref: this.getItemRef,
title: this.itemDivTitle,
className: classNames + ` ${props.className ? props.className : ''}`,
- onMouseDown: composeEvents(this.onMouseDown, props.onMouseDown),
- onMouseUp: composeEvents(this.onMouseUp, props.onMouseUp),
onTouchStart: composeEvents(this.onTouchStart, props.onTouchStart),
onTouchEnd: composeEvents(this.onTouchEnd, props.onTouchEnd),
onDoubleClick: composeEvents(this.handleDoubleClick, props.onDoubleClick),
onContextMenu: composeEvents(this.handleContextMenu, props.onContextMenu),
- style: Object.assign({}, this.getItemStyle(props))
+ style: Object.assign({}, this.getItemStyle(props)),
+ onClick : (e) => {this.actualClick(e, 'click')}
}
}
diff --git a/src/lib/layout/Header.js b/src/lib/layout/Header.js
deleted file mode 100644
index 711e53251..000000000
--- a/src/lib/layout/Header.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-import TimelineElementsHeader from './TimelineElementsHeader'
-
-class Header extends Component {
- static propTypes = {
- hasRightSidebar: PropTypes.bool.isRequired,
- showPeriod: PropTypes.func.isRequired,
- canvasTimeStart: PropTypes.number.isRequired,
- canvasTimeEnd: PropTypes.number.isRequired,
- canvasWidth: PropTypes.number.isRequired,
- minUnit: PropTypes.string.isRequired,
- timeSteps: PropTypes.object.isRequired,
- width: PropTypes.number.isRequired,
- headerLabelFormats: PropTypes.object.isRequired,
- subHeaderLabelFormats: PropTypes.object.isRequired,
- stickyOffset: PropTypes.number,
- stickyHeader: PropTypes.bool.isRequired,
- headerLabelGroupHeight: PropTypes.number.isRequired,
- headerLabelHeight: PropTypes.number.isRequired,
- leftSidebarHeader: PropTypes.node,
- rightSidebarHeader: PropTypes.node,
- leftSidebarWidth: PropTypes.number,
- rightSidebarWidth: PropTypes.number,
- headerRef: PropTypes.func.isRequired,
- scrollHeaderRef: PropTypes.func.isRequired
- }
-
- render() {
- const {
- width,
- stickyOffset,
- stickyHeader,
- headerRef,
- scrollHeaderRef,
- hasRightSidebar,
- showPeriod,
- canvasTimeStart,
- canvasTimeEnd,
- canvasWidth,
- minUnit,
- timeSteps,
- headerLabelFormats,
- subHeaderLabelFormats,
- headerLabelGroupHeight,
- headerLabelHeight,
- leftSidebarHeader,
- rightSidebarHeader,
- leftSidebarWidth,
- rightSidebarWidth
- } = this.props
-
- const headerStyle = {
- top: stickyHeader ? stickyOffset || 0 : 0
- }
-
- const headerClass = stickyHeader ? 'header-sticky' : ''
-
- const leftSidebar = leftSidebarHeader && leftSidebarWidth > 0 && (
-
- {leftSidebarHeader}
-
- )
-
- const rightSidebar = rightSidebarHeader && rightSidebarWidth > 0 && (
-
- {rightSidebarHeader}
-
- )
-
- return (
-
- {leftSidebar}
-
-
-
- {rightSidebar}
-
- )
- }
-}
-
-export default Header
diff --git a/src/lib/layout/TimelineElementsHeader.js b/src/lib/layout/TimelineElementsHeader.js
deleted file mode 100644
index d5e51efad..000000000
--- a/src/lib/layout/TimelineElementsHeader.js
+++ /dev/null
@@ -1,249 +0,0 @@
-import PropTypes from 'prop-types'
-import React, { Component } from 'react'
-import moment from 'moment'
-
-import { iterateTimes, getNextUnit } from '../utility/calendar'
-
-export default class TimelineElementsHeader extends Component {
- static propTypes = {
- hasRightSidebar: PropTypes.bool.isRequired,
- showPeriod: PropTypes.func.isRequired,
- canvasTimeStart: PropTypes.number.isRequired,
- canvasTimeEnd: PropTypes.number.isRequired,
- canvasWidth: PropTypes.number.isRequired,
- minUnit: PropTypes.string.isRequired,
- timeSteps: PropTypes.object.isRequired,
- width: PropTypes.number.isRequired,
- headerLabelFormats: PropTypes.object.isRequired,
- subHeaderLabelFormats: PropTypes.object.isRequired,
- headerLabelGroupHeight: PropTypes.number.isRequired,
- headerLabelHeight: PropTypes.number.isRequired,
- scrollHeaderRef: PropTypes.func.isRequired
- }
-
- constructor(props) {
- super(props)
-
- this.state = {
- touchTarget: null,
- touchActive: false
- }
- }
-
- handleHeaderMouseDown(evt) {
- //dont bubble so that we prevent our scroll component
- //from knowing about it
- evt.stopPropagation()
- }
-
- headerLabel(time, unit, width) {
- const { headerLabelFormats: f } = this.props
-
- if (unit === 'year') {
- return time.format(width < 46 ? f.yearShort : f.yearLong)
- } else if (unit === 'month') {
- return time.format(
- width < 65
- ? f.monthShort
- : width < 75
- ? f.monthMedium
- : width < 120 ? f.monthMediumLong : f.monthLong
- )
- } else if (unit === 'day') {
- return time.format(width < 150 ? f.dayShort : f.dayLong)
- } else if (unit === 'hour') {
- return time.format(
- width < 50
- ? f.hourShort
- : width < 130
- ? f.hourMedium
- : width < 150 ? f.hourMediumLong : f.hourLong
- )
- } else {
- return time.format(f.time)
- }
- }
-
- subHeaderLabel(time, unit, width) {
- const { subHeaderLabelFormats: f } = this.props
-
- if (unit === 'year') {
- return time.format(width < 46 ? f.yearShort : f.yearLong)
- } else if (unit === 'month') {
- return time.format(
- width < 37 ? f.monthShort : width < 85 ? f.monthMedium : f.monthLong
- )
- } else if (unit === 'day') {
- return time.format(
- width < 47
- ? f.dayShort
- : width < 80 ? f.dayMedium : width < 120 ? f.dayMediumLong : f.dayLong
- )
- } else if (unit === 'hour') {
- return time.format(width < 50 ? f.hourShort : f.hourLong)
- } else if (unit === 'minute') {
- return time.format(width < 60 ? f.minuteShort : f.minuteLong)
- } else {
- return time.get(unit)
- }
- }
-
- handlePeriodClick = (time, unit) => {
- if (time && unit) {
- this.props.showPeriod(moment(time - 0), unit)
- }
- }
-
- shouldComponentUpdate(nextProps) {
- const willUpate =
- nextProps.canvasTimeStart != this.props.canvasTimeStart ||
- nextProps.canvasTimeEnd != this.props.canvasTimeEnd ||
- nextProps.width != this.props.width ||
- nextProps.canvasWidth != this.props.canvasWidth ||
- nextProps.subHeaderLabelFormats != this.props.subHeaderLabelFormats ||
- nextProps.headerLabelFormats != this.props.headerLabelFormats ||
- nextProps.hasRightSidebar != this.props.hasRightSidebar
-
- return willUpate
- }
-
- render() {
- const {
- canvasTimeStart,
- canvasTimeEnd,
- canvasWidth,
- minUnit,
- timeSteps,
- headerLabelGroupHeight,
- headerLabelHeight,
- hasRightSidebar
- } = this.props
-
- const ratio = canvasWidth / (canvasTimeEnd - canvasTimeStart)
- const twoHeaders = minUnit !== 'year'
-
- const topHeaderLabels = []
- // add the top header
- if (twoHeaders) {
- const nextUnit = getNextUnit(minUnit)
-
- iterateTimes(
- canvasTimeStart,
- canvasTimeEnd,
- nextUnit,
- timeSteps,
- (time, nextTime) => {
- const left = Math.round((time.valueOf() - canvasTimeStart) * ratio)
- const right = Math.round(
- (nextTime.valueOf() - canvasTimeStart) * ratio
- )
-
- const labelWidth = right - left
- // this width applies to the content in the header
- // it simulates stickyness where the content is fixed in the center
- // of the label. when the labelWidth is less than visible time range,
- // have label content fill the entire width
- const contentWidth = Math.min(labelWidth, canvasWidth)
-
- topHeaderLabels.push(
-
this.handlePeriodClick(time, nextUnit)}
- style={{
- left: `${left - 1}px`,
- width: `${labelWidth}px`,
- height: `${headerLabelGroupHeight}px`,
- lineHeight: `${headerLabelGroupHeight}px`,
- cursor: 'pointer'
- }}
- >
-
- {this.headerLabel(time, nextUnit, labelWidth)}
-
-
- )
- }
- )
- }
-
- const bottomHeaderLabels = []
- iterateTimes(
- canvasTimeStart,
- canvasTimeEnd,
- minUnit,
- timeSteps,
- (time, nextTime) => {
- const left = Math.round((time.valueOf() - canvasTimeStart) * ratio)
- const minUnitValue = time.get(minUnit === 'day' ? 'date' : minUnit)
- const firstOfType = minUnitValue === (minUnit === 'day' ? 1 : 0)
- const labelWidth = Math.round(
- (nextTime.valueOf() - time.valueOf()) * ratio
- )
- const leftCorrect = firstOfType ? 1 : 0
-
- bottomHeaderLabels.push(
-
this.handlePeriodClick(time, minUnit)}
- style={{
- left: `${left - leftCorrect}px`,
- width: `${labelWidth}px`,
- height: `${
- minUnit === 'year'
- ? headerLabelGroupHeight + headerLabelHeight
- : headerLabelHeight
- }px`,
- lineHeight: `${
- minUnit === 'year'
- ? headerLabelGroupHeight + headerLabelHeight
- : headerLabelHeight
- }px`,
- fontSize: `${
- labelWidth > 30 ? '14' : labelWidth > 20 ? '12' : '10'
- }px`,
- cursor: 'pointer'
- }}
- >
- {this.subHeaderLabel(time, minUnit, labelWidth)}
-
- )
- }
- )
-
- let headerStyle = {
- height: `${headerLabelGroupHeight + headerLabelHeight}px`
- }
-
- return (
-
-
- {topHeaderLabels}
-
-
- {bottomHeaderLabels}
-
-
- )
- }
-}
diff --git a/src/lib/markers/MarkerCanvas.js b/src/lib/markers/MarkerCanvas.js
index d41750c95..21d1697c6 100644
--- a/src/lib/markers/MarkerCanvas.js
+++ b/src/lib/markers/MarkerCanvas.js
@@ -17,7 +17,7 @@ const staticStyles = {
* Renders registered markers and exposes a mouse over listener for
* CursorMarkers to subscribe to
*/
-class MarkerCanvas extends React.Component {
+class MarkerCanvas extends React.PureComponent {
static propTypes = {
getDateFromLeftOffsetPosition: PropTypes.func.isRequired,
children: PropTypes.node
diff --git a/src/lib/row/GroupRow.js b/src/lib/row/GroupRow.js
index 65aaa7ff3..2b0c333bd 100644
--- a/src/lib/row/GroupRow.js
+++ b/src/lib/row/GroupRow.js
@@ -1,29 +1,36 @@
-import React, { Component } from 'react'
+import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import PreventClickOnDrag from '../interaction/PreventClickOnDrag'
-class GroupRow extends Component {
+class GroupRow extends PureComponent {
static propTypes = {
onClick: PropTypes.func.isRequired,
onDoubleClick: PropTypes.func.isRequired,
onContextMenu: PropTypes.func.isRequired,
isEvenRow: PropTypes.bool.isRequired,
- style: PropTypes.object.isRequired,
clickTolerance: PropTypes.number.isRequired,
group: PropTypes.object.isRequired,
- horizontalLineClassNamesForGroup: PropTypes.func
+ horizontalLineClassNamesForGroup: PropTypes.func,
+ order: PropTypes.number.isRequired,
+ canvasWidth: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired,
+
}
+ onGroupRowContextMenuClick = evt => this.props.onContextMenu(evt, this.props.order);
+
+ onGroupRowClick = evt => this.props.onClick(evt, this.props.order)
+
+ onGroupRowDoubleClick =evt => this.props.onDoubleClick(evt, this.props.order)
+
render() {
const {
- onContextMenu,
- onDoubleClick,
isEvenRow,
- style,
- onClick,
clickTolerance,
horizontalLineClassNamesForGroup,
- group
+ group,
+ canvasWidth,
+ height,
} = this.props
let classNamesForGroup = [];
@@ -32,16 +39,19 @@ class GroupRow extends Component {
}
return (
-
+
)
}
}
-export default GroupRow
+export default GroupRow
\ No newline at end of file
diff --git a/src/lib/row/GroupRows.js b/src/lib/row/GroupRows.js
index 6f29a72b7..4ea498177 100644
--- a/src/lib/row/GroupRows.js
+++ b/src/lib/row/GroupRows.js
@@ -12,7 +12,7 @@ export default class GroupRows extends Component {
clickTolerance: PropTypes.number.isRequired,
groups: PropTypes.array.isRequired,
horizontalLineClassNamesForGroup: PropTypes.func,
- onRowContextClick: PropTypes.func.isRequired,
+ onRowContextClick: PropTypes.func.isRequired
}
shouldComponentUpdate(nextProps) {
@@ -34,25 +34,23 @@ export default class GroupRows extends Component {
clickTolerance,
groups,
horizontalLineClassNamesForGroup,
- onRowContextClick,
+ onRowContextClick
} = this.props
let lines = []
-
for (let i = 0; i < lineCount; i++) {
lines.push(
onRowContextClick(evt, i)}
- onClick={evt => onRowClick(evt, i)}
- onDoubleClick={evt => onRowDoubleClick(evt, i)}
+ onContextMenu={onRowContextClick}
+ onClick={onRowClick}
+ onDoubleClick={onRowDoubleClick}
key={`horizontal-line-${i}`}
isEvenRow={i % 2 === 0}
group={groups[i]}
horizontalLineClassNamesForGroup={horizontalLineClassNamesForGroup}
- style={{
- width: `${canvasWidth}px`,
- height: `${groupHeights[i] - 1}px`
- }}
+ canvasWidth={canvasWidth}
+ height={groupHeights[i]}
/>
)
}
diff --git a/src/lib/scroll/ScrollElement.js b/src/lib/scroll/ScrollElement.js
index cbd8b7b8f..70dc1e8e0 100644
--- a/src/lib/scroll/ScrollElement.js
+++ b/src/lib/scroll/ScrollElement.js
@@ -1,8 +1,8 @@
-import React, { Component } from 'react'
+import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { getParentPosition } from '../utility/dom-helpers'
-class ScrollElement extends Component {
+class ScrollElement extends PureComponent {
static propTypes = {
children: PropTypes.element.isRequired,
width: PropTypes.number.isRequired,
diff --git a/src/lib/timeline/TimelineStateContext.js b/src/lib/timeline/TimelineStateContext.js
index a58de362e..15cd24369 100644
--- a/src/lib/timeline/TimelineStateContext.js
+++ b/src/lib/timeline/TimelineStateContext.js
@@ -23,6 +23,9 @@ const defaultContextState = {
},
getDateFromLeftOffsetPosition: () => {
console.warn('"getDateFromLeftOffsetPosition" default func is being used')
+ },
+ showPeriod: () => {
+ console.warn('"showPeriod" default func is being used')
}
}
/* eslint-enable */
@@ -37,7 +40,12 @@ export class TimelineStateProvider extends React.Component {
visibleTimeEnd: PropTypes.number.isRequired,
canvasTimeStart: PropTypes.number.isRequired,
canvasTimeEnd: PropTypes.number.isRequired,
- canvasWidth: PropTypes.number.isRequired
+ canvasWidth: PropTypes.number.isRequired,
+ showPeriod: PropTypes.func.isRequired,
+ timelineUnit: PropTypes.string.isRequired,
+ timelineWidth: PropTypes.number.isRequired,
+ keys:PropTypes.object.isRequired,
+ width: PropTypes.number.isRequired
}
constructor(props) {
@@ -47,13 +55,35 @@ export class TimelineStateProvider extends React.Component {
timelineContext: {
getTimelineState: this.getTimelineState,
getLeftOffsetFromDate: this.getLeftOffsetFromDate,
- getDateFromLeftOffsetPosition: this.getDateFromLeftOffsetPosition
+ getDateFromLeftOffsetPosition: this.getDateFromLeftOffsetPosition,
+ showPeriod: this.props.showPeriod,
}
}
}
getTimelineState = () => {
- return this.state.timelineState // REVIEW: return copy or object.freeze?
+ const {
+ visibleTimeStart,
+ visibleTimeEnd,
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ timelineUnit,
+ timelineWidth,
+ keys,
+ width,
+ } = this.props
+ return {
+ visibleTimeStart,
+ visibleTimeEnd,
+ canvasTimeStart,
+ canvasTimeEnd,
+ canvasWidth,
+ timelineUnit,
+ timelineWidth,
+ keys,
+ width,
+ } // REVIEW,
}
getLeftOffsetFromDate = date => {
diff --git a/src/lib/utility/calendar.js b/src/lib/utility/calendar.js
index 7d68cff41..12d8a68cb 100644
--- a/src/lib/utility/calendar.js
+++ b/src/lib/utility/calendar.js
@@ -68,8 +68,9 @@ export function iterateTimes(start, end, unit, timeSteps, callback) {
}
while (time.valueOf() < end) {
- let nextTime = moment(time).add(timeSteps[unit] || 1, `${unit}s`)
- callback(time, nextTime)
+ let nextTime = moment(time).add(timeSteps[unit] || 1, `${unit}s`).startOf(`${unit}s`);
+ let endTime = moment(time).endOf(`${unit}s`)
+ callback(time, endTime)
time = nextTime
}
}
@@ -148,10 +149,13 @@ export function getNextUnit(unit) {
minute: 'hour',
hour: 'day',
day: 'month',
- month: 'year'
+ month: 'year',
+ year: 'year'
}
-
- return nextUnits[unit] || ''
+ if (!nextUnits[unit]) {
+ throw new Error(`unit ${unit} in not acceptable`)
+ }
+ return nextUnits[unit]
}
/**
@@ -172,7 +176,7 @@ export function calculateInteractionNewTimes({
isDragging,
isResizing,
resizingEdge,
- resizeTime
+ resizeTime,
}) {
const originalItemRange = itemTimeEnd - itemTimeStart
const itemStart =
@@ -296,6 +300,30 @@ export function collision(a, b, lineHeight, collisionPadding = EPSILON) {
)
}
+/**
+ * Calculate the position of a given item for a group that each in a line
+ * is being stacked
+ */
+export function groupStackInLines(
+ lineHeight,
+ item,
+ items,
+ groupHeight,
+ groupTop,
+ itemIndex
+) {
+ // calculate non-overlapping positions
+ let verticalMargin = lineHeight - item.dimensions.height
+ if (item.dimensions.stack && item.dimensions.top === null) {
+ item.dimensions.top = groupTop + verticalMargin + itemIndex * lineHeight
+ }
+ return {
+ groupHeight: lineHeight * items.length,
+ verticalMargin,
+ itemTop: item.dimensions.top
+ }
+}
+
/**
* Calculate the position of a given item for a group that
* is being stacked
@@ -310,7 +338,7 @@ export function groupStack(
) {
// calculate non-overlapping positions
let curHeight = groupHeight
- let verticalMargin = lineHeight - item.dimensions.height
+ let verticalMargin = (lineHeight - item.dimensions.height) / 2
if (item.dimensions.stack && item.dimensions.top === null) {
item.dimensions.top = groupTop + verticalMargin
curHeight = Math.max(curHeight, lineHeight)
@@ -336,7 +364,7 @@ export function groupStack(
item.dimensions.top = collidingItem.dimensions.top + lineHeight
curHeight = Math.max(
curHeight,
- item.dimensions.top + item.dimensions.height - groupTop
+ item.dimensions.top + item.dimensions.height + verticalMargin - groupTop
)
}
} while (collidingItem)
@@ -346,7 +374,6 @@ export function groupStack(
verticalMargin,
itemTop: item.dimensions.top
}
-
}
// Calculate the position of this item for a group that is not being stacked
@@ -396,7 +423,7 @@ export function stackAll(itemsDimensions, groupOrders, lineHeight, stackItems) {
if (group.height) {
groupHeights.push(group.height)
} else {
- groupHeights.push(Math.max(groupHeight + verticalMargin, lineHeight))
+ groupHeights.push(Math.max(groupHeight, lineHeight))
}
}
return {
@@ -407,19 +434,24 @@ export function stackAll(itemsDimensions, groupOrders, lineHeight, stackItems) {
}
/**
- *
- * @param {*} itemsDimensions
- * @param {*} isGroupStacked
- * @param {*} lineHeight
- * @param {*} groupTop
+ *
+ * @param {*} itemsDimensions
+ * @param {*} isGroupStacked
+ * @param {*} lineHeight
+ * @param {*} groupTop
*/
-export function stackGroup(itemsDimensions, isGroupStacked, lineHeight, groupTop) {
+export function stackGroup(
+ itemsDimensions,
+ isGroupStacked,
+ lineHeight,
+ groupTop
+) {
var groupHeight = 0
var verticalMargin = 0
// Find positions for each item in group
for (let itemIndex = 0; itemIndex < itemsDimensions.length; itemIndex++) {
let r = {}
- if (isGroupStacked) {
+ if (isGroupStacked === 'space') {
r = groupStack(
lineHeight,
itemsDimensions[itemIndex],
@@ -428,8 +460,22 @@ export function stackGroup(itemsDimensions, isGroupStacked, lineHeight, groupTop
groupTop,
itemIndex
)
+ } else if (isGroupStacked === 'lines') {
+ r = groupStackInLines(
+ lineHeight,
+ itemsDimensions[itemIndex],
+ itemsDimensions,
+ groupHeight,
+ groupTop,
+ itemIndex
+ )
} else {
- r = groupNoStack(lineHeight, itemsDimensions[itemIndex], groupHeight, groupTop)
+ r = groupNoStack(
+ lineHeight,
+ itemsDimensions[itemIndex],
+ groupHeight,
+ groupTop
+ )
}
groupHeight = r.groupHeight
verticalMargin = r.verticalMargin
diff --git a/webpack.config.js b/webpack.config.js
index 3f1dc0ceb..439be1be9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const path = require('path')
const port = process.env.PORT || 8888
const config = {
- devtool: 'cheap-eval-source-map',
+ devtool: 'source-map',
context: path.join(__dirname, './demo'),
entry: {
// vendor: ['react', 'react-dom', 'faker', 'interactjs', 'moment'],
diff --git a/yarn.lock b/yarn.lock
index ea234bb40..2ce970fe8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4841,6 +4841,10 @@ mem@^1.1.0:
dependencies:
mimic-fn "^1.0.0"
+memoize-one@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.3.tgz#cdfdd942853f1a1b4c71c5336b8c49da0bf0273c"
+
memory-fs@^0.4.0, memory-fs@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"