react-big-schedule 6.1.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -10
- package/dist/components/BodyView.js +24 -10
- package/dist/components/EventItem.js +13 -6
- package/dist/components/HeaderView.js +30 -8
- package/dist/components/ResourceEvents.js +24 -6
- package/dist/components/ResourceView.js +21 -1
- package/dist/components/SchedulerData.js +129 -36
- package/dist/components/SchedulerHeader.js +1 -0
- package/dist/components/index.js +86 -132
- package/dist/config/default.js +2 -1
- package/dist/config/scheduler.js +6 -1
- package/dist/css/style.css +15 -1
- package/dist/index.d.ts +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -567,19 +567,20 @@ Used to resolve the locale string of date label of Scheduler component.(Refer to
|
|
|
567
567
|
|
|
568
568
|
#### schedulerWidth
|
|
569
569
|
|
|
570
|
-
The width of Scheduler. Scheduler uses responsive layout so schedulerWidth should be a percentage
|
|
571
|
-
|
|
572
|
-
`
|
|
573
|
-
|
|
574
|
-
|
|
570
|
+
The width of Scheduler. Scheduler uses responsive layout so schedulerWidth should be a percentage.
|
|
571
|
+
|
|
572
|
+
The Scheduler renders inside a container that is `width: 100%` and `max-height: 100%` of its parent, so it fills the parent
|
|
573
|
+
and follows it when resized (it measures that container with a `ResizeObserver`; `besidesWidth` is no longer needed).
|
|
574
|
+
Resource columns keep their configured width and the time columns stretch to fill the rest.
|
|
575
|
+
|
|
576
|
+
To size it differently, pass `className` (e.g. tailwind classes) or `style` to the `Scheduler` component, for example
|
|
577
|
+
`<Scheduler className="w-1/2 max-h-[600px]" ... />`. Those win over the defaults, which have zero CSS specificity.
|
|
578
|
+
When the parent has a fixed height the Scheduler scrolls inside it, otherwise it grows to fit its rows.
|
|
575
579
|
|
|
576
580
|
#### responsiveByParent
|
|
577
581
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
in case it is not passed resposiveness will fall back to being determined by the window width).
|
|
581
|
-
Meaning:
|
|
582
|
-
`SchedulerData.documentWidth` is the width of the parent and will change automatically when resized
|
|
582
|
+
Only needed to make the Scheduler's *height* follow a parent element: pass that element through the `parentRef` prop of the
|
|
583
|
+
Scheduler component. Without `parentRef` the window height is used.
|
|
583
584
|
|
|
584
585
|
#### schedulerMaxHeight
|
|
585
586
|
|
|
@@ -837,6 +838,23 @@ parentRef: PropTypes.object;
|
|
|
837
838
|
|
|
838
839
|
ref of the component that is the parent of the Scheduler component
|
|
839
840
|
|
|
841
|
+
#### className
|
|
842
|
+
|
|
843
|
+
```js
|
|
844
|
+
className: PropTypes.string;
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
Class names for the Scheduler's container (e.g. tailwind classes such as `w-1/2 max-h-[600px]`). They override the default
|
|
848
|
+
`width: 100%` / `max-height: 100%`.
|
|
849
|
+
|
|
850
|
+
#### style
|
|
851
|
+
|
|
852
|
+
```js
|
|
853
|
+
style: PropTypes.object;
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
Inline style for the Scheduler's container.
|
|
857
|
+
|
|
840
858
|
#### prevClick
|
|
841
859
|
|
|
842
860
|
```js
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render the table body (<tbody>) containing resource rows and cells based on schedulerData.
|
|
6
|
+
* @param {object} schedulerData - Scheduler state and helpers. Expected properties used: `renderData` (array of row descriptors with `slotId`, `render`, `rowHeight`, `groupOnly`, `nonWorkingTime`), `headers` (array of header descriptors with `time` and `nonWorkingTime`), `config` (including `nonWorkingTimeBodyBgColor` and `groupOnlySlotColor`), `behaviors` (optional `getNonAgendaViewBodyCellBgColorFunc`), `getContentCellWidth()` and `isVerticalResourceView()`.
|
|
7
|
+
* @returns {JSX.Element} The rendered `<tbody>` element containing a `<tr>` per rendered row and `<td>` cells for each header with computed inline styles.
|
|
8
|
+
*/
|
|
3
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
10
|
function BodyView({
|
|
5
11
|
schedulerData
|
|
@@ -11,17 +17,25 @@ function BodyView({
|
|
|
11
17
|
behaviors
|
|
12
18
|
} = schedulerData;
|
|
13
19
|
const width = schedulerData.getContentCellWidth();
|
|
14
|
-
const tableRows = renderData.filter(o => o.render).map(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const tableRows = renderData.filter(o => o.render).map(row => {
|
|
21
|
+
const {
|
|
22
|
+
slotId,
|
|
23
|
+
groupOnly,
|
|
24
|
+
rowHeight
|
|
25
|
+
} = row;
|
|
26
|
+
const rowCells = headers.map(header => {
|
|
27
|
+
// vertical view: every header shares one time, the resource id tells the columns apart
|
|
28
|
+
const key = `${slotId}_${header.id ?? header.time}`;
|
|
29
|
+
const style = {
|
|
30
|
+
width,
|
|
31
|
+
minWidth: width
|
|
23
32
|
};
|
|
24
|
-
|
|
33
|
+
const isVertical = schedulerData.isVerticalResourceView();
|
|
34
|
+
if (isVertical) {
|
|
35
|
+
if (row.nonWorkingTime) {
|
|
36
|
+
style.backgroundColor = config.nonWorkingTimeBodyBgColor;
|
|
37
|
+
}
|
|
38
|
+
} else if (header.nonWorkingTime) {
|
|
25
39
|
style.backgroundColor = config.nonWorkingTimeBodyBgColor;
|
|
26
40
|
}
|
|
27
41
|
if (groupOnly) {
|
|
@@ -52,12 +52,14 @@ class EventItem extends Component {
|
|
|
52
52
|
const {
|
|
53
53
|
left,
|
|
54
54
|
top,
|
|
55
|
-
width
|
|
55
|
+
width,
|
|
56
|
+
height
|
|
56
57
|
} = props;
|
|
57
58
|
this.state = {
|
|
58
59
|
left,
|
|
59
60
|
top,
|
|
60
61
|
width,
|
|
62
|
+
height,
|
|
61
63
|
contentMousePosX: 0,
|
|
62
64
|
eventItemLeftRect: 0,
|
|
63
65
|
eventItemRightRect: 0
|
|
@@ -78,13 +80,15 @@ class EventItem extends Component {
|
|
|
78
80
|
const {
|
|
79
81
|
left,
|
|
80
82
|
top,
|
|
81
|
-
width
|
|
83
|
+
width,
|
|
84
|
+
height
|
|
82
85
|
} = this.props;
|
|
83
|
-
if (prevProps.left !== left || prevProps.top !== top || prevProps.width !== width) {
|
|
86
|
+
if (prevProps.left !== left || prevProps.top !== top || prevProps.width !== width || prevProps.height !== height) {
|
|
84
87
|
this.setState({
|
|
85
88
|
left,
|
|
86
89
|
top,
|
|
87
|
-
width
|
|
90
|
+
width,
|
|
91
|
+
height
|
|
88
92
|
});
|
|
89
93
|
}
|
|
90
94
|
|
|
@@ -516,7 +520,8 @@ class EventItem extends Component {
|
|
|
516
520
|
const {
|
|
517
521
|
left,
|
|
518
522
|
width,
|
|
519
|
-
top
|
|
523
|
+
top,
|
|
524
|
+
height
|
|
520
525
|
} = this.state;
|
|
521
526
|
let roundCls;
|
|
522
527
|
const popoverPlacement = config.eventItemPopoverPlacement;
|
|
@@ -552,7 +557,7 @@ class EventItem extends Component {
|
|
|
552
557
|
let eventItemTemplate = /*#__PURE__*/_jsx("div", {
|
|
553
558
|
className: `${roundCls} event-item`,
|
|
554
559
|
style: {
|
|
555
|
-
height: config.eventItemHeight,
|
|
560
|
+
height: height || config.eventItemHeight,
|
|
556
561
|
backgroundColor: bgColor
|
|
557
562
|
},
|
|
558
563
|
children: /*#__PURE__*/_jsx("span", {
|
|
@@ -575,6 +580,7 @@ class EventItem extends Component {
|
|
|
575
580
|
left,
|
|
576
581
|
width,
|
|
577
582
|
top,
|
|
583
|
+
height,
|
|
578
584
|
cursor: 'pointer',
|
|
579
585
|
background: 'none',
|
|
580
586
|
border: 'none',
|
|
@@ -697,6 +703,7 @@ EventItem.propTypes = {
|
|
|
697
703
|
left: PropTypes.number.isRequired,
|
|
698
704
|
width: PropTypes.number.isRequired,
|
|
699
705
|
top: PropTypes.number.isRequired,
|
|
706
|
+
height: PropTypes.number,
|
|
700
707
|
isInPopover: PropTypes.bool.isRequired,
|
|
701
708
|
leftIndex: PropTypes.number.isRequired,
|
|
702
709
|
rightIndex: PropTypes.number.isRequired,
|
|
@@ -32,7 +32,7 @@ function HeaderView({
|
|
|
32
32
|
|
|
33
33
|
// Week number row generation
|
|
34
34
|
const weekNumberRow = useMemo(() => {
|
|
35
|
-
if (!showWeekNumber) return null;
|
|
35
|
+
if (!showWeekNumber || schedulerData.isVerticalResourceView()) return null;
|
|
36
36
|
const weekGroups = [];
|
|
37
37
|
let currentWeekKey = null;
|
|
38
38
|
let currentWeek = null;
|
|
@@ -74,12 +74,19 @@ function HeaderView({
|
|
|
74
74
|
padding: '4px 8px',
|
|
75
75
|
textAlign: 'center'
|
|
76
76
|
};
|
|
77
|
-
return weekGroups.map(group =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
return weekGroups.map(group => {
|
|
78
|
+
if (group.week == null || group.year == null) return null;
|
|
79
|
+
return /*#__PURE__*/_jsxs("th", {
|
|
80
|
+
colSpan: group.colspan,
|
|
81
|
+
style: {
|
|
82
|
+
...cellStyle,
|
|
83
|
+
width: group.colspan * cellWidth,
|
|
84
|
+
minWidth: group.colspan * cellWidth
|
|
85
|
+
},
|
|
86
|
+
children: ["W", group.week]
|
|
87
|
+
}, `week-${group.year}-${group.week}`);
|
|
88
|
+
});
|
|
89
|
+
}, [showWeekNumber, headers, localeDayjs, config.headerBorderColor, cellWidth, schedulerData]);
|
|
83
90
|
|
|
84
91
|
// Extract common style creation logic
|
|
85
92
|
const createCellStyle = useCallback((item, width, isLastCell) => {
|
|
@@ -127,6 +134,21 @@ function HeaderView({
|
|
|
127
134
|
|
|
128
135
|
// Memoize header list generation
|
|
129
136
|
const headerList = useMemo(() => {
|
|
137
|
+
if (schedulerData.isVerticalResourceView()) {
|
|
138
|
+
return headers.map(item => {
|
|
139
|
+
const style = {
|
|
140
|
+
width: cellWidth,
|
|
141
|
+
minWidth: cellWidth
|
|
142
|
+
};
|
|
143
|
+
return /*#__PURE__*/_jsx("th", {
|
|
144
|
+
className: "header3-text",
|
|
145
|
+
style: style,
|
|
146
|
+
children: /*#__PURE__*/_jsx("div", {
|
|
147
|
+
children: item.name
|
|
148
|
+
})
|
|
149
|
+
}, `header-${item.id}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
130
152
|
if (cellUnit === CellUnit.Hour) {
|
|
131
153
|
const result = [];
|
|
132
154
|
const lastIndex = headers.length - minuteStepsInHour;
|
|
@@ -152,7 +174,7 @@ function HeaderView({
|
|
|
152
174
|
const formattedList = cellFormat.split('|').map(format => datetime.format(format));
|
|
153
175
|
return renderCellContent(item, formattedList, style);
|
|
154
176
|
});
|
|
155
|
-
}, [cellUnit, headers, minuteStepsInHour, cellWidth, config, localeDayjs, createCellStyle, getCellFormat, renderCellContent]);
|
|
177
|
+
}, [cellUnit, headers, minuteStepsInHour, cellWidth, config, localeDayjs, createCellStyle, getCellFormat, renderCellContent, schedulerData.isVerticalResourceView]);
|
|
156
178
|
return /*#__PURE__*/_jsxs("thead", {
|
|
157
179
|
children: [weekNumberRow && /*#__PURE__*/_jsx("tr", {
|
|
158
180
|
style: {
|
|
@@ -291,14 +291,24 @@ class ResourceEvents extends PureComponent {
|
|
|
291
291
|
const maxRightIndex = Math.min(headers.length, resourceEvents.headerItems.length);
|
|
292
292
|
let safeRightIndex = Math.max(1, rightIndex, safeLeftIndex + 1);
|
|
293
293
|
safeRightIndex = Math.min(safeRightIndex, maxRightIndex);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
const isVertical = schedulerData.isVerticalResourceView();
|
|
295
|
+
// vertical view: rows are time slots and columns are resources, so the roles swap
|
|
296
|
+
const selectedRowIds = this.getSelectedResourceIds();
|
|
297
|
+
let startTime, endTime;
|
|
298
|
+
if (isVertical) {
|
|
299
|
+
startTime = selectedRowIds[0] ?? resourceEvents.slotId;
|
|
300
|
+
const lastRowId = selectedRowIds[selectedRowIds.length - 1] ?? startTime;
|
|
301
|
+
endTime = localeDayjs(new Date(lastRowId)).add(config.minuteStep, 'minutes').format(DATETIME_FORMAT);
|
|
302
|
+
} else {
|
|
303
|
+
startTime = headers[safeLeftIndex].time;
|
|
304
|
+
endTime = resourceEvents.headerItems[safeRightIndex - 1].end;
|
|
305
|
+
if (cellUnit !== CellUnit.Hour) {
|
|
306
|
+
endTime = localeDayjs(new Date(resourceEvents.headerItems[safeRightIndex - 1].start)).hour(23).minute(59).second(59).format(DATETIME_FORMAT);
|
|
307
|
+
}
|
|
298
308
|
}
|
|
299
309
|
|
|
300
310
|
// Get selected resource IDs
|
|
301
|
-
const selectedResourceIds =
|
|
311
|
+
const selectedResourceIds = isVertical ? headers.slice(safeLeftIndex, safeRightIndex).map(header => header.id) : selectedRowIds;
|
|
302
312
|
const slotId = selectedResourceIds.length > 0 ? selectedResourceIds[0] : resourceEvents.slotId;
|
|
303
313
|
const slotName = selectedResourceIds.length > 0 ? schedulerData.getResourceById(slotId)?.name || slotId : resourceEvents.slotName;
|
|
304
314
|
this.setState({
|
|
@@ -535,7 +545,14 @@ class ResourceEvents extends PureComponent {
|
|
|
535
545
|
} else {
|
|
536
546
|
width = evt.span * cellWidth - (index > 0 ? 5 : 6) > 0 ? evt.span * cellWidth - (index > 0 ? 5 : 6) : 0;
|
|
537
547
|
}
|
|
538
|
-
const top = marginTop + idx * config.eventItemLineHeight;
|
|
548
|
+
const top = schedulerData.isVerticalResourceView() ? 0 : marginTop + idx * config.eventItemLineHeight;
|
|
549
|
+
const height = schedulerData.isVerticalResourceView() ? resourceEvents.rowHeight : undefined;
|
|
550
|
+
if (schedulerData.isVerticalResourceView()) {
|
|
551
|
+
const eventCount = headerItem.count;
|
|
552
|
+
const itemWidth = (cellWidth - (index > 0 ? 5 : 6)) / eventCount;
|
|
553
|
+
left = index * cellWidth + (index > 0 ? 2 : 3) + idx * itemWidth;
|
|
554
|
+
width = itemWidth;
|
|
555
|
+
}
|
|
539
556
|
const eventKey = `${evt.eventItem.id}_${resourceEvents.slotId}_${index}`;
|
|
540
557
|
const eventItem = /*#__PURE__*/_jsx(EventItem, {
|
|
541
558
|
schedulerData: schedulerData,
|
|
@@ -547,6 +564,7 @@ class ResourceEvents extends PureComponent {
|
|
|
547
564
|
left: left,
|
|
548
565
|
width: width,
|
|
549
566
|
top: top,
|
|
567
|
+
height: height,
|
|
550
568
|
leftIndex: index,
|
|
551
569
|
rightIndex: index + evt.span
|
|
552
570
|
// Passing through functional props
|
|
@@ -33,7 +33,7 @@ function ResourceView({
|
|
|
33
33
|
const {
|
|
34
34
|
renderData
|
|
35
35
|
} = schedulerData;
|
|
36
|
-
const width = schedulerData.getResourceTableWidth()
|
|
36
|
+
const width = schedulerData.getResourceTableWidth();
|
|
37
37
|
const paddingBottom = contentScrollbarHeight;
|
|
38
38
|
const displayRenderData = renderData.filter(o => o.render);
|
|
39
39
|
const handleToggleExpand = item => {
|
|
@@ -135,6 +135,26 @@ function ResourceView({
|
|
|
135
135
|
}, item.slotId);
|
|
136
136
|
};
|
|
137
137
|
const resourceList = displayRenderData.map(item => {
|
|
138
|
+
if (schedulerData.isVerticalResourceView()) {
|
|
139
|
+
const timeLabel = schedulerData.localeDayjs(new Date(item.slotName)).format('HH:mm');
|
|
140
|
+
return /*#__PURE__*/_jsx("tr", {
|
|
141
|
+
style: {
|
|
142
|
+
height: item.rowHeight
|
|
143
|
+
},
|
|
144
|
+
children: /*#__PURE__*/_jsx("td", {
|
|
145
|
+
"data-resource-id": item.slotId,
|
|
146
|
+
style: {
|
|
147
|
+
height: item.rowHeight,
|
|
148
|
+
minHeight: item.rowHeight,
|
|
149
|
+
textAlign: 'center'
|
|
150
|
+
},
|
|
151
|
+
className: "header2-text",
|
|
152
|
+
children: /*#__PURE__*/_jsx("span", {
|
|
153
|
+
children: timeLabel
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
}, item.slotId);
|
|
157
|
+
}
|
|
138
158
|
const indents = [];
|
|
139
159
|
for (let i = 0; i < item.indent; i += 1) {
|
|
140
160
|
indents.push(/*#__PURE__*/_jsx("span", {
|
|
@@ -16,13 +16,15 @@ export default class SchedulerData {
|
|
|
16
16
|
this.eventGroups = [];
|
|
17
17
|
this.eventGroupsAutoGenerated = true;
|
|
18
18
|
this.viewType = viewType;
|
|
19
|
-
this.cellUnit = viewType === ViewType.Day ? CellUnit.Hour : CellUnit.Day;
|
|
19
|
+
this.cellUnit = viewType === ViewType.Day || viewType === ViewType.VerticalResource ? CellUnit.Hour : CellUnit.Day;
|
|
20
20
|
this.showAgenda = showAgenda;
|
|
21
21
|
this.isEventPerspective = isEventPerspective;
|
|
22
22
|
this.resizing = false;
|
|
23
23
|
this.scrollToSpecialDayjs = false;
|
|
24
24
|
this.documentWidth = 0;
|
|
25
25
|
this.documentHeight = 0;
|
|
26
|
+
// true once the scheduler measures its own container, so besidesWidth (a window-based offset) is not applied
|
|
27
|
+
this.measuredByContainer = false;
|
|
26
28
|
this.schedulerHeaderHeight = 0;
|
|
27
29
|
this._shouldReloadViewType = false;
|
|
28
30
|
this.version = 0;
|
|
@@ -102,6 +104,9 @@ export default class SchedulerData {
|
|
|
102
104
|
getVersion() {
|
|
103
105
|
return this.version;
|
|
104
106
|
}
|
|
107
|
+
isVerticalResourceView() {
|
|
108
|
+
return this.viewType === ViewType.VerticalResource;
|
|
109
|
+
}
|
|
105
110
|
setSchedulerLocale(preset) {
|
|
106
111
|
if (!preset) return;
|
|
107
112
|
this.localeDayjs.locale(preset);
|
|
@@ -117,6 +122,10 @@ export default class SchedulerData {
|
|
|
117
122
|
setResources(resources) {
|
|
118
123
|
this._validateResource(resources);
|
|
119
124
|
this.resources = Array.from(new Set(resources));
|
|
125
|
+
// vertical view builds its headers from the resources
|
|
126
|
+
if (this.isVerticalResourceView()) {
|
|
127
|
+
this._createHeaders();
|
|
128
|
+
}
|
|
120
129
|
this._createRenderData();
|
|
121
130
|
this.setScrollToSpecialDayjs(true);
|
|
122
131
|
this.bumpVersion();
|
|
@@ -162,6 +171,9 @@ export default class SchedulerData {
|
|
|
162
171
|
const existedResources = this.resources.filter(x => x.id === resource.id);
|
|
163
172
|
if (existedResources.length === 0) {
|
|
164
173
|
this.resources.push(resource);
|
|
174
|
+
if (this.isVerticalResourceView()) {
|
|
175
|
+
this._createHeaders();
|
|
176
|
+
}
|
|
165
177
|
this._createRenderData();
|
|
166
178
|
this.bumpVersion();
|
|
167
179
|
}
|
|
@@ -230,10 +242,11 @@ export default class SchedulerData {
|
|
|
230
242
|
this.showAgenda = showAgenda;
|
|
231
243
|
this.isEventPerspective = isEventPerspective;
|
|
232
244
|
const isCustomView = viewType === ViewType.Custom || viewType === ViewType.Custom1 || viewType === ViewType.Custom2;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (!isCustomView && viewType !== ViewType.Day) {
|
|
245
|
+
const isVerticalView = viewType === ViewType.VerticalResource;
|
|
246
|
+
if (!isCustomView && viewType !== ViewType.Day && !isVerticalView) {
|
|
236
247
|
this.cellUnit = CellUnit.Day;
|
|
248
|
+
} else if (viewType === ViewType.Day || isVerticalView) {
|
|
249
|
+
this.cellUnit = CellUnit.Hour;
|
|
237
250
|
}
|
|
238
251
|
|
|
239
252
|
// If showAgenda or isEventPerspective changed, force reload
|
|
@@ -273,7 +286,7 @@ export default class SchedulerData {
|
|
|
273
286
|
if (now >= start && now < end) {
|
|
274
287
|
date = now.startOf('day');
|
|
275
288
|
}
|
|
276
|
-
if (viewType === ViewType.Day) {
|
|
289
|
+
if (viewType === ViewType.Day || viewType === ViewType.VerticalResource) {
|
|
277
290
|
this.startDate = this.localeDayjs(new Date(date)).startOf('day');
|
|
278
291
|
this.endDate = this.startDate;
|
|
279
292
|
this.cellUnit = CellUnit.Hour;
|
|
@@ -359,13 +372,15 @@ export default class SchedulerData {
|
|
|
359
372
|
return !!contentCellWidth.endsWith && contentCellWidth.endsWith('%');
|
|
360
373
|
}
|
|
361
374
|
getSchedulerWidth() {
|
|
362
|
-
const
|
|
375
|
+
const besidesWidth = this.measuredByContainer ? 0 : this.config.besidesWidth;
|
|
376
|
+
const baseWidth = this.documentWidth - besidesWidth > 0 ? this.documentWidth - besidesWidth : 0;
|
|
363
377
|
return this.isSchedulerResponsive() ? parseInt(baseWidth * Number(this.config.schedulerWidth.slice(0, -1)) / 100, 10) : this.config.schedulerWidth;
|
|
364
378
|
}
|
|
365
379
|
getSchedulerHeight() {
|
|
366
380
|
// Use the header height tracked in SchedulerData (set via _setSchedulerHeaderHeight)
|
|
367
381
|
const headerHeight = this.schedulerHeaderHeight || 0;
|
|
368
|
-
const
|
|
382
|
+
const underneathHeight = this.config.underneathHeight || 0;
|
|
383
|
+
const baseHeight = this.documentHeight - headerHeight - this.config.tableHeaderHeight - underneathHeight;
|
|
369
384
|
const validBaseHeight = baseHeight > 0 ? baseHeight : 0;
|
|
370
385
|
if (this.isSchedulerResponsive() && this.config.schedulerHeight?.endsWith?.('%')) {
|
|
371
386
|
return parseInt(validBaseHeight * Number(this.config.schedulerHeight.slice(0, -1)) / 100, 10);
|
|
@@ -373,17 +388,31 @@ export default class SchedulerData {
|
|
|
373
388
|
// fallback to max height if set, else just use base height
|
|
374
389
|
return this.config.schedulerMaxHeight || validBaseHeight;
|
|
375
390
|
}
|
|
376
|
-
|
|
391
|
+
|
|
392
|
+
// configured resource column width in px (config may be a percentage of the scheduler width)
|
|
393
|
+
_getConfiguredResourceTableWidth() {
|
|
377
394
|
const resourceTableConfigWidth = this.getResourceTableConfigWidth();
|
|
395
|
+
return this.isResourceViewResponsive() ? parseInt(this.getSchedulerWidth() * Number(resourceTableConfigWidth.slice(0, -1)) / 100, 10) : resourceTableConfigWidth;
|
|
396
|
+
}
|
|
397
|
+
getResourceTableWidth() {
|
|
378
398
|
const schedulerWidth = this.getSchedulerWidth();
|
|
379
|
-
let resourceTableWidth = this.
|
|
399
|
+
let resourceTableWidth = this._getConfiguredResourceTableWidth();
|
|
400
|
+
// only the few px left after the cells are stretched go to the resource column
|
|
380
401
|
if (this.isSchedulerResponsive() && this.getContentTableWidth() + resourceTableWidth < schedulerWidth) resourceTableWidth = schedulerWidth - this.getContentTableWidth();
|
|
381
402
|
return resourceTableWidth;
|
|
382
403
|
}
|
|
383
404
|
getContentCellWidth() {
|
|
384
405
|
const contentCellConfigWidth = this.getContentCellConfigWidth();
|
|
385
406
|
const schedulerWidth = this.getSchedulerWidth();
|
|
386
|
-
|
|
407
|
+
const cellWidth = this.isContentViewResponsive() ? parseInt(schedulerWidth * Number(contentCellConfigWidth.slice(0, -1)) / 100, 10) : contentCellConfigWidth;
|
|
408
|
+
|
|
409
|
+
// stretch the cells to fill the container instead of widening the resource column
|
|
410
|
+
if (this.isSchedulerResponsive() && !this.showAgenda && this.headers?.length) {
|
|
411
|
+
const resourceWidth = this.config.resourceViewEnabled === false ? 0 : this._getConfiguredResourceTableWidth();
|
|
412
|
+
const fillWidth = Math.floor((schedulerWidth - resourceWidth) / this.headers.length);
|
|
413
|
+
return Math.max(cellWidth, fillWidth);
|
|
414
|
+
}
|
|
415
|
+
return cellWidth;
|
|
387
416
|
}
|
|
388
417
|
getContentTableWidth() {
|
|
389
418
|
return this.headers.length * this.getContentCellWidth();
|
|
@@ -566,7 +595,8 @@ export default class SchedulerData {
|
|
|
566
595
|
[ViewType.Day]: 'dayResourceTableWidth',
|
|
567
596
|
[ViewType.Month]: 'monthResourceTableWidth',
|
|
568
597
|
[ViewType.Year]: 'yearResourceTableWidth',
|
|
569
|
-
[ViewType.Quarter]: 'quarterResourceTableWidth'
|
|
598
|
+
[ViewType.Quarter]: 'quarterResourceTableWidth',
|
|
599
|
+
[ViewType.VerticalResource]: 'customResourceTableWidth'
|
|
570
600
|
};
|
|
571
601
|
const configProperty = viewConfigMap[this.viewType] || 'customResourceTableWidth';
|
|
572
602
|
return this.config[configProperty];
|
|
@@ -577,11 +607,18 @@ export default class SchedulerData {
|
|
|
577
607
|
[ViewType.Day]: 'dayCellWidth',
|
|
578
608
|
[ViewType.Month]: 'monthCellWidth',
|
|
579
609
|
[ViewType.Year]: 'yearCellWidth',
|
|
580
|
-
[ViewType.Quarter]: 'quarterCellWidth'
|
|
610
|
+
[ViewType.Quarter]: 'quarterCellWidth',
|
|
611
|
+
[ViewType.VerticalResource]: 'customCellWidth'
|
|
581
612
|
};
|
|
582
613
|
const configProperty = viewConfigMap[this.viewType] || 'customCellWidth';
|
|
583
614
|
return this.config[configProperty];
|
|
584
615
|
}
|
|
616
|
+
_setMeasuredByContainer(measuredByContainer) {
|
|
617
|
+
if (this.measuredByContainer !== measuredByContainer) {
|
|
618
|
+
this.measuredByContainer = measuredByContainer;
|
|
619
|
+
this.bumpVersion();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
585
622
|
_setDocumentWidth(documentWidth) {
|
|
586
623
|
if (documentWidth >= 0 && documentWidth !== this.documentWidth) {
|
|
587
624
|
this.documentWidth = documentWidth;
|
|
@@ -686,6 +723,7 @@ export default class SchedulerData {
|
|
|
686
723
|
this.endDate = this.startDate.endOf('isoWeek');
|
|
687
724
|
break;
|
|
688
725
|
case ViewType.Day:
|
|
726
|
+
case ViewType.VerticalResource:
|
|
689
727
|
this.startDate = (date !== undefined ? this.selectDate : this.startDate.add(num, 'days')).startOf('day');
|
|
690
728
|
this.endDate = this.startDate;
|
|
691
729
|
break;
|
|
@@ -716,15 +754,14 @@ export default class SchedulerData {
|
|
|
716
754
|
break;
|
|
717
755
|
}
|
|
718
756
|
}
|
|
719
|
-
|
|
720
|
-
// Previous Code
|
|
721
757
|
_createHeaders() {
|
|
722
|
-
const
|
|
758
|
+
const timeSlots = [];
|
|
723
759
|
let start = this.localeDayjs(new Date(this.startDate));
|
|
724
760
|
let end = this.localeDayjs(new Date(this.endDate));
|
|
725
761
|
let header = start;
|
|
762
|
+
const width = this.getContentCellWidth();
|
|
726
763
|
if (this.showAgenda) {
|
|
727
|
-
|
|
764
|
+
timeSlots.push({
|
|
728
765
|
time: header.format(DATETIME_FORMAT),
|
|
729
766
|
nonWorkingTime: false
|
|
730
767
|
});
|
|
@@ -750,7 +787,7 @@ export default class SchedulerData {
|
|
|
750
787
|
if (hour >= this.config.dayStartFrom && hour <= this.config.dayStopTo) {
|
|
751
788
|
const time = header.format(DATETIME_FORMAT);
|
|
752
789
|
const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
|
|
753
|
-
|
|
790
|
+
timeSlots.push({
|
|
754
791
|
time,
|
|
755
792
|
nonWorkingTime
|
|
756
793
|
});
|
|
@@ -761,12 +798,17 @@ export default class SchedulerData {
|
|
|
761
798
|
} else if (this.cellUnit === CellUnit.Day) {
|
|
762
799
|
while (header >= start && header <= end) {
|
|
763
800
|
const time = header.format(DATETIME_FORMAT);
|
|
801
|
+
const style = {
|
|
802
|
+
width,
|
|
803
|
+
minWidth: width
|
|
804
|
+
};
|
|
764
805
|
const dayOfWeek = header.day();
|
|
765
806
|
if (this.config.displayWeekend || dayOfWeek !== 0 && dayOfWeek !== 6) {
|
|
766
807
|
const nonWorkingTime = this.behaviors.isNonWorkingTimeFunc(this, time);
|
|
767
|
-
|
|
808
|
+
timeSlots.push({
|
|
768
809
|
time,
|
|
769
|
-
nonWorkingTime
|
|
810
|
+
nonWorkingTime,
|
|
811
|
+
style
|
|
770
812
|
});
|
|
771
813
|
}
|
|
772
814
|
header = header.add(1, 'days');
|
|
@@ -774,7 +816,7 @@ export default class SchedulerData {
|
|
|
774
816
|
} else if (this.cellUnit === CellUnit.Week) {
|
|
775
817
|
while (header >= start && header <= end) {
|
|
776
818
|
const time = header.format(DATE_FORMAT);
|
|
777
|
-
|
|
819
|
+
timeSlots.push({
|
|
778
820
|
time
|
|
779
821
|
});
|
|
780
822
|
header = header.add(1, 'weeks').startOf('isoWeek');
|
|
@@ -782,7 +824,7 @@ export default class SchedulerData {
|
|
|
782
824
|
} else if (this.cellUnit === CellUnit.Month) {
|
|
783
825
|
while (header >= start && header <= end) {
|
|
784
826
|
const time = header.format(DATE_FORMAT);
|
|
785
|
-
|
|
827
|
+
timeSlots.push({
|
|
786
828
|
time
|
|
787
829
|
});
|
|
788
830
|
header = header.add(1, 'months').startOf('month');
|
|
@@ -790,13 +832,23 @@ export default class SchedulerData {
|
|
|
790
832
|
} else if (this.cellUnit === CellUnit.Year) {
|
|
791
833
|
while (header >= start && header <= end) {
|
|
792
834
|
const time = header.format(DATE_FORMAT);
|
|
793
|
-
|
|
835
|
+
timeSlots.push({
|
|
794
836
|
time
|
|
795
837
|
});
|
|
796
838
|
header = header.add(1, 'years').startOf('year');
|
|
797
839
|
}
|
|
798
840
|
}
|
|
799
|
-
this.
|
|
841
|
+
this.timeSlots = timeSlots;
|
|
842
|
+
if (this.isVerticalResourceView()) {
|
|
843
|
+
const currentViewDate = this.localeDayjs(new Date(this.startDate)).format(DATETIME_FORMAT);
|
|
844
|
+
this.headers = this.resources.map(r => ({
|
|
845
|
+
...r,
|
|
846
|
+
time: currentViewDate,
|
|
847
|
+
nonWorkingTime: false
|
|
848
|
+
}));
|
|
849
|
+
} else {
|
|
850
|
+
this.headers = timeSlots;
|
|
851
|
+
}
|
|
800
852
|
}
|
|
801
853
|
_createInitHeaderEvents(header) {
|
|
802
854
|
const start = this.localeDayjs(new Date(header.time));
|
|
@@ -815,6 +867,10 @@ export default class SchedulerData {
|
|
|
815
867
|
} else {
|
|
816
868
|
endValue = start.add(1, incrementUnit).format(DATETIME_FORMAT);
|
|
817
869
|
}
|
|
870
|
+
} else if (this.isVerticalResourceView()) {
|
|
871
|
+
// In vertical view, headers are resources. They don't have a time span.
|
|
872
|
+
// But we need a start/end for the headerEvent structure.
|
|
873
|
+
endValue = startValue;
|
|
818
874
|
} else {
|
|
819
875
|
const {
|
|
820
876
|
amount,
|
|
@@ -832,7 +888,12 @@ export default class SchedulerData {
|
|
|
832
888
|
count: 0,
|
|
833
889
|
addMore: 0,
|
|
834
890
|
addMoreIndex: 0,
|
|
835
|
-
events:
|
|
891
|
+
events: [],
|
|
892
|
+
id: header.id || header.time,
|
|
893
|
+
style: header.style || {
|
|
894
|
+
width: this.getContentCellWidth(),
|
|
895
|
+
minWidth: this.getContentCellWidth()
|
|
896
|
+
}
|
|
836
897
|
};
|
|
837
898
|
}
|
|
838
899
|
_createHeaderEvent(render, span, eventItem) {
|
|
@@ -879,7 +940,13 @@ export default class SchedulerData {
|
|
|
879
940
|
this.eventGroups = eventGroups;
|
|
880
941
|
}
|
|
881
942
|
_createInitRenderData(isEventPerspective, eventGroups, resources, headers) {
|
|
882
|
-
const slots =
|
|
943
|
+
const slots = this.isVerticalResourceView() ? this.timeSlots.map(ts => ({
|
|
944
|
+
...ts,
|
|
945
|
+
id: ts.time,
|
|
946
|
+
name: ts.time,
|
|
947
|
+
slotId: ts.time,
|
|
948
|
+
slotName: ts.time
|
|
949
|
+
})) : isEventPerspective ? eventGroups : resources;
|
|
883
950
|
const slotTree = [];
|
|
884
951
|
const slotMap = new Map();
|
|
885
952
|
slots.forEach(slot => {
|
|
@@ -974,7 +1041,7 @@ export default class SchedulerData {
|
|
|
974
1041
|
// _createRenderData. Headers only cover the visible window (and visible days), so the event is
|
|
975
1042
|
// clamped consistently across views, and calendar-aware dayjs math keeps DST days at one cell.
|
|
976
1043
|
_getSpan(startTime, endTime, headers) {
|
|
977
|
-
if (this.showAgenda) return 1;
|
|
1044
|
+
if (this.showAgenda || this.isVerticalResourceView()) return 1;
|
|
978
1045
|
const eventStart = normalizeEventStart(startTime);
|
|
979
1046
|
const eventEnd = normalizeEventEnd(endTime);
|
|
980
1047
|
const {
|
|
@@ -1054,7 +1121,18 @@ export default class SchedulerData {
|
|
|
1054
1121
|
const cellMaxEventsCountValue = 30;
|
|
1055
1122
|
this.events.forEach(item => {
|
|
1056
1123
|
let targetSlotIds = [];
|
|
1057
|
-
if (this.
|
|
1124
|
+
if (this.isVerticalResourceView()) {
|
|
1125
|
+
const eventStart = new Date(item.start);
|
|
1126
|
+
const eventEnd = new Date(item.end);
|
|
1127
|
+
this.timeSlots.forEach(ts => {
|
|
1128
|
+
// Check if the time slot overlaps with the event's time
|
|
1129
|
+
const tsStart = new Date(ts.time);
|
|
1130
|
+
const tsEnd = this.localeDayjs(ts.time).add(this.config.minuteStep, 'minutes').toDate();
|
|
1131
|
+
if (tsEnd > eventStart && tsStart < eventEnd) {
|
|
1132
|
+
targetSlotIds.push(ts.time);
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
} else if (this.isEventPerspective) {
|
|
1058
1136
|
targetSlotIds = [this._getEventSlotId(item)];
|
|
1059
1137
|
} else if (item.resourceIds && item.resourceIds.length > 0) {
|
|
1060
1138
|
// Multi-resource event
|
|
@@ -1072,9 +1150,18 @@ export default class SchedulerData {
|
|
|
1072
1150
|
const eventEnd = normalizeEventEnd(item.end).toDate();
|
|
1073
1151
|
let pos = -1;
|
|
1074
1152
|
resourceEvents.headerItems.forEach((header, index) => {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1153
|
+
let isOverlapping = false;
|
|
1154
|
+
if (this.isVerticalResourceView()) {
|
|
1155
|
+
// In vertical view, headers are resources. Check if event belongs to this resource.
|
|
1156
|
+
const itemResourceIds = item.resourceIds || [item.resourceId];
|
|
1157
|
+
const targetId = header.id;
|
|
1158
|
+
isOverlapping = itemResourceIds.includes(targetId);
|
|
1159
|
+
} else {
|
|
1160
|
+
const headerStart = new Date(header.start);
|
|
1161
|
+
const headerEnd = new Date(header.end);
|
|
1162
|
+
isOverlapping = headerEnd > eventStart && headerStart < eventEnd;
|
|
1163
|
+
}
|
|
1164
|
+
if (isOverlapping) {
|
|
1078
1165
|
header.count += 1;
|
|
1079
1166
|
if (header.count > resourceEvents.rowMaxCount) {
|
|
1080
1167
|
resourceEvents.rowMaxCount = header.count;
|
|
@@ -1087,12 +1174,18 @@ export default class SchedulerData {
|
|
|
1087
1174
|
while (header.events[tmp] !== undefined) tmp += 1;
|
|
1088
1175
|
pos = tmp;
|
|
1089
1176
|
}
|
|
1090
|
-
let render =
|
|
1091
|
-
if (
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1177
|
+
let render = false;
|
|
1178
|
+
if (this.isVerticalResourceView()) {
|
|
1179
|
+
render = true;
|
|
1180
|
+
} else {
|
|
1181
|
+
const headerStart = new Date(header.start);
|
|
1182
|
+
render = headerStart <= eventStart || index === 0;
|
|
1183
|
+
if (render === false) {
|
|
1184
|
+
const previousHeader = resourceEvents.headerItems[index - 1];
|
|
1185
|
+
const previousHeaderStart = new Date(previousHeader.start);
|
|
1186
|
+
const previousHeaderEnd = new Date(previousHeader.end);
|
|
1187
|
+
if (previousHeaderEnd <= eventStart || previousHeaderStart >= eventEnd) render = true;
|
|
1188
|
+
}
|
|
1096
1189
|
}
|
|
1097
1190
|
header.events[pos] = this._createHeaderEvent(render, span, item);
|
|
1098
1191
|
}
|
package/dist/components/index.js
CHANGED
|
@@ -24,88 +24,38 @@ const initDndContext = (schedulerData, dndSources) => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
* Optimized to minimize unnecessary re-renders through memoized callbacks, stable refs, and
|
|
29
|
-
* selectively updated state.
|
|
27
|
+
* Render the full scheduler table including the header toolbar, resource column, timeline header, and all resource event rows.
|
|
30
28
|
*
|
|
31
29
|
* @param {object} props - Component properties.
|
|
32
|
-
* @param {SchedulerData} props.schedulerData -
|
|
33
|
-
*
|
|
34
|
-
* @param {
|
|
35
|
-
*
|
|
36
|
-
* @param {
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @param {function(SchedulerData):void} props.
|
|
40
|
-
*
|
|
41
|
-
* @param {function(SchedulerData):void} props.
|
|
42
|
-
*
|
|
43
|
-
* @
|
|
44
|
-
* view type, agenda toggle, or event perspective changes.
|
|
45
|
-
* @param {function(SchedulerData, string|Date):void} props.onSelectDate - Callback invoked when
|
|
46
|
-
* the user selects a new date from the calendar popover.
|
|
47
|
-
* @param {function(SchedulerData, object):void} [props.onSetAddMoreState] - Callback invoked when
|
|
48
|
-
* a '+N more' overflow indicator is clicked, used to control the AddMorePopover visibility and position.
|
|
49
|
-
* @param {function(SchedulerData, object, string):void} [props.updateEventStart] - Callback invoked
|
|
50
|
-
* when the user resizes the start of an event.
|
|
51
|
-
* @param {function(SchedulerData, object, string):void} [props.updateEventEnd] - Callback invoked
|
|
52
|
-
* when the user resizes the end of an event.
|
|
53
|
-
* @param {function(SchedulerData, object, string, string, string, string):void} [props.moveEvent] -
|
|
54
|
-
* Callback invoked when an event is dragged to a new slot or time range.
|
|
55
|
-
* @param {function} [props.movingEvent] - Callback invoked continuously while an event is being dragged.
|
|
56
|
-
* * @param {function(SchedulerData, object, string, string, string, string, string, string):void}
|
|
57
|
-
* [props.conflictOccurred] -
|
|
58
|
-
* Callback invoked when a scheduling conflict is detected during create, move, or resize.
|
|
59
|
-
* @param {function(SchedulerData, object):string} [props.subtitleGetter] - Returns a subtitle string
|
|
60
|
-
* displayed in the event item popover.
|
|
61
|
-
* @param {function(SchedulerData, object):void} [props.eventItemClick] - Callback invoked when an
|
|
62
|
-
* event item is clicked.
|
|
63
|
-
* @param {function(SchedulerData, object):void} [props.viewEventClick] - Callback invoked when the
|
|
64
|
-
* primary action link in the event popover is clicked.
|
|
65
|
-
* @param {string} [props.viewEventText] - Label text for the primary action link in the event popover.
|
|
66
|
-
* @param {function(SchedulerData, object):void} [props.viewEvent2Click] - Callback invoked when the
|
|
67
|
-
* secondary action link in the event popover is clicked.
|
|
68
|
-
* @param {string} [props.viewEvent2Text] - Label text for the secondary action link in the event popover.
|
|
69
|
-
* @param {function(SchedulerData, string, string, string, string, string, object):void} [props.newEvent] -
|
|
70
|
-
* Callback invoked when a new event is created by clicking and dragging on an empty cell, or when
|
|
71
|
-
* an external item is dropped into the scheduler.
|
|
72
|
-
* @param {function} [props.eventItemTemplateResolver] - Returns a custom JSX element used to render
|
|
73
|
-
* each event item, overriding the default appearance.
|
|
74
|
-
* @param {function(SchedulerData, object):void} [props.slotClickedFunc] - Callback invoked when a
|
|
75
|
-
* resource slot label in the left column is clicked.
|
|
76
|
-
* @param {function(SchedulerData, string):void} [props.toggleExpandFunc] - Callback invoked when a
|
|
77
|
-
* parent resource slot's expand or collapse control is clicked.
|
|
78
|
-
* @param {function} [props.slotItemTemplateResolver] - Returns a custom JSX element used to render
|
|
79
|
-
* each resource slot cell in the left column.
|
|
80
|
-
* @param {function} [props.nonAgendaCellHeaderTemplateResolver] - Returns a custom JSX element used
|
|
81
|
-
* to render individual header cells in the timeline header row.
|
|
82
|
-
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollLeft] - Callback invoked
|
|
83
|
-
* when the scheduler content is scrolled to the leftmost position.
|
|
84
|
-
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollRight] - Callback invoked
|
|
85
|
-
* when the scheduler content is scrolled to the rightmost position.
|
|
86
|
-
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollTop] - Callback invoked
|
|
87
|
-
* when the scheduler content is scrolled to the topmost position.
|
|
88
|
-
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollBottom] - Callback invoked
|
|
89
|
-
* when the scheduler content is scrolled to the bottommost position.
|
|
90
|
-
* @param {React.ReactNode} [props.leftCustomHeader] - Custom content rendered on the left side of
|
|
91
|
-
* the scheduler header toolbar.
|
|
92
|
-
* @param {React.ReactNode} [props.rightCustomHeader] - Custom content rendered on the right side of
|
|
93
|
-
* the scheduler header toolbar.
|
|
94
|
-
* @param {function} [props.CustomResourceHeader] - Optional component rendered inside the resource
|
|
95
|
-
* column header cell, replacing the default resource name label.
|
|
96
|
-
* @param {function} [props.CustomResourceCell] - Optional component rendered inside each resource
|
|
97
|
-
* slot cell in the left column.
|
|
98
|
-
* @param {object} [props.configTableHeaderStyle] - Optional inline style object merged into the
|
|
99
|
-
* resource column header container, used to override default header styling.
|
|
100
|
-
* @returns {JSX.Element} The root scheduler table element containing the header toolbar, resource
|
|
101
|
-
* column, timeline header, and all resource event rows.
|
|
30
|
+
* @param {SchedulerData} props.schedulerData - View model that supplies configuration, layout, and rendering data for the scheduler.
|
|
31
|
+
* @param {Array<DnDSource>} [props.dndSources] - Additional drag-and-drop sources merged into the scheduler's DnD context on first mount.
|
|
32
|
+
* @param {React.RefObject<HTMLElement>} [props.parentRef] - Parent container ref used when `config.responsiveByParent` is true; a ResizeObserver tracks this element's size.
|
|
33
|
+
* @param {function(SchedulerData):void} props.prevClick - Called when navigating to the previous time range.
|
|
34
|
+
* @param {function(SchedulerData):void} props.nextClick - Called when navigating to the next time range.
|
|
35
|
+
* @param {function(SchedulerData, object):void} props.onViewChange - Called when the view type, agenda toggle, or event perspective changes.
|
|
36
|
+
* @param {function(SchedulerData, string|Date):void} props.onSelectDate - Called when a date is selected from the date picker.
|
|
37
|
+
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollLeft] - Called when content reaches the leftmost scroll position.
|
|
38
|
+
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollRight] - Called when content reaches the rightmost scroll position.
|
|
39
|
+
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollTop] - Called when content reaches the topmost scroll position.
|
|
40
|
+
* @param {function(SchedulerData, HTMLElement, number):void} [props.onScrollBottom] - Called when content reaches the bottommost scroll position.
|
|
41
|
+
* @returns {JSX.Element} The root scheduler table element containing the rendered scheduler UI.
|
|
102
42
|
*/
|
|
103
43
|
|
|
44
|
+
// content-box height: clientHeight includes the padding, which the scheduler cannot use
|
|
45
|
+
const getInnerHeight = el => {
|
|
46
|
+
const {
|
|
47
|
+
paddingTop,
|
|
48
|
+
paddingBottom
|
|
49
|
+
} = window.getComputedStyle(el);
|
|
50
|
+
return el.clientHeight - (parseFloat(paddingTop) || 0) - (parseFloat(paddingBottom) || 0);
|
|
51
|
+
};
|
|
104
52
|
function Scheduler(props) {
|
|
105
53
|
const {
|
|
106
54
|
schedulerData,
|
|
107
55
|
dndSources,
|
|
108
56
|
parentRef,
|
|
57
|
+
className,
|
|
58
|
+
style,
|
|
109
59
|
prevClick,
|
|
110
60
|
nextClick,
|
|
111
61
|
onViewChange,
|
|
@@ -132,6 +82,7 @@ function Scheduler(props) {
|
|
|
132
82
|
});
|
|
133
83
|
const [, setRenderTrigger] = useState(0);
|
|
134
84
|
const schedulerRootRef = useRef(null);
|
|
85
|
+
const containerRef = useRef(null);
|
|
135
86
|
|
|
136
87
|
// Layout/header refs - declare before setSchedulerHeaderRef useCallback
|
|
137
88
|
const schedulerHeaderRef = useRef(null);
|
|
@@ -170,54 +121,39 @@ function Scheduler(props) {
|
|
|
170
121
|
}
|
|
171
122
|
return undefined;
|
|
172
123
|
}, [schedulerData]);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
schedulerData._setDocumentWidth(document.documentElement.clientWidth);
|
|
177
|
-
schedulerData._setDocumentHeight(document.documentElement.clientHeight);
|
|
178
|
-
} finally {
|
|
179
|
-
schedulerData.endBatch();
|
|
180
|
-
}
|
|
181
|
-
}, [schedulerData]);
|
|
124
|
+
|
|
125
|
+
// Width comes from the scheduler's own wrapper (100% wide of its parent). `parentRef` only supplies the height
|
|
126
|
+
// when responsiveByParent is on.
|
|
182
127
|
useEffect(() => {
|
|
183
|
-
|
|
128
|
+
const {
|
|
129
|
+
responsiveByParent
|
|
130
|
+
} = schedulerData.config;
|
|
131
|
+
const parentEl = responsiveByParent ? parentRef?.current : null;
|
|
132
|
+
// the wrapper is 100% wide of its parent (padding excluded), so it gives the width in both modes
|
|
133
|
+
const widthEl = containerRef.current;
|
|
134
|
+
if (!widthEl) return undefined;
|
|
135
|
+
const update = () => {
|
|
184
136
|
schedulerData.beginBatch();
|
|
185
137
|
try {
|
|
186
|
-
schedulerData.
|
|
187
|
-
schedulerData.
|
|
138
|
+
schedulerData._setMeasuredByContainer(true);
|
|
139
|
+
schedulerData._setDocumentWidth(widthEl.clientWidth);
|
|
140
|
+
if (responsiveByParent) {
|
|
141
|
+
schedulerData._setDocumentHeight(parentEl ? getInnerHeight(parentEl) : document.documentElement.clientHeight);
|
|
142
|
+
}
|
|
188
143
|
} finally {
|
|
189
144
|
schedulerData.endBatch();
|
|
190
145
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
schedulerData._setDocumentHeight(parentEl.clientHeight);
|
|
203
|
-
} finally {
|
|
204
|
-
schedulerData.endBatch();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
updateParentSize();
|
|
208
|
-
|
|
209
|
-
// Disconnect any previous observer to prevent memory leaks
|
|
210
|
-
if (ulObserverRef.current) {
|
|
211
|
-
ulObserverRef.current.disconnect();
|
|
212
|
-
}
|
|
213
|
-
ulObserverRef.current = new ResizeObserver(updateParentSize);
|
|
214
|
-
ulObserverRef.current.observe(parentEl);
|
|
215
|
-
return () => {
|
|
216
|
-
if (ulObserverRef.current) {
|
|
217
|
-
ulObserverRef.current.disconnect();
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
146
|
+
};
|
|
147
|
+
update();
|
|
148
|
+
ulObserverRef.current = new ResizeObserver(update);
|
|
149
|
+
ulObserverRef.current.observe(widthEl);
|
|
150
|
+
if (parentEl) ulObserverRef.current.observe(parentEl);
|
|
151
|
+
const listenToWindow = responsiveByParent && !parentEl;
|
|
152
|
+
if (listenToWindow) window.addEventListener('resize', update);
|
|
153
|
+
return () => {
|
|
154
|
+
ulObserverRef.current?.disconnect();
|
|
155
|
+
if (listenToWindow) window.removeEventListener('resize', update);
|
|
156
|
+
};
|
|
221
157
|
}, [parentRef, schedulerData]);
|
|
222
158
|
useEffect(() => {
|
|
223
159
|
if (schedulerData.config.responsiveByParent && schedulerHeaderEl) {
|
|
@@ -369,7 +305,7 @@ function Scheduler(props) {
|
|
|
369
305
|
weekNumberRowHeight
|
|
370
306
|
} = config;
|
|
371
307
|
const schedulerDataVersion = schedulerData.getVersion ? schedulerData.getVersion() : 0;
|
|
372
|
-
const schedulerWidth = schedulerData.getContentTableWidth()
|
|
308
|
+
const schedulerWidth = schedulerData.getContentTableWidth();
|
|
373
309
|
const weekNumberRowStyle = useMemo(() => ({
|
|
374
310
|
height: weekNumberRowHeight
|
|
375
311
|
}), [weekNumberRowHeight]);
|
|
@@ -466,7 +402,7 @@ function Scheduler(props) {
|
|
|
466
402
|
height: contentHeight,
|
|
467
403
|
overflowX: 'auto',
|
|
468
404
|
overflowY: 'auto',
|
|
469
|
-
width: resourceTableWidth + resourceScrollbarWidth
|
|
405
|
+
width: resourceTableWidth + resourceScrollbarWidth,
|
|
470
406
|
margin: `0px -${contentScrollbarWidth}px 0px 0px`
|
|
471
407
|
};
|
|
472
408
|
if (config.schedulerMaxHeight > 0) {
|
|
@@ -598,6 +534,7 @@ function Scheduler(props) {
|
|
|
598
534
|
style: schedulerHeadInnerStyle,
|
|
599
535
|
children: /*#__PURE__*/_jsx("table", {
|
|
600
536
|
className: "scheduler-bg-table",
|
|
537
|
+
style: schedulerInnerStyle,
|
|
601
538
|
children: /*#__PURE__*/_jsx(HeaderView, {
|
|
602
539
|
schedulerData: schedulerData,
|
|
603
540
|
schedulerDataVersion: schedulerDataVersion,
|
|
@@ -658,28 +595,45 @@ function Scheduler(props) {
|
|
|
658
595
|
rightCustomHeader: rightCustomHeader,
|
|
659
596
|
leftCustomHeader: leftCustomHeader
|
|
660
597
|
}), [schedulerHeaderStyle, handleViewChange, schedulerData, onSelect, goNext, goBack, rightCustomHeader, leftCustomHeader, setSchedulerHeaderRef]);
|
|
598
|
+
|
|
599
|
+
// table-layout: fixed sizes columns from the first row (the toolbar, colSpan=2), so pin the resource column here
|
|
600
|
+
const resourceColumnWidth = schedulerData.showAgenda || !config.resourceViewEnabled ? undefined : schedulerData.getResourceTableWidth();
|
|
661
601
|
const rootTableStyle = useMemo(() => ({
|
|
662
|
-
width: `${width}px
|
|
602
|
+
width: `${width}px`,
|
|
603
|
+
tableLayout: 'fixed'
|
|
663
604
|
}), [width]);
|
|
664
|
-
return /*#__PURE__*/
|
|
665
|
-
|
|
666
|
-
className:
|
|
667
|
-
style:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
605
|
+
return /*#__PURE__*/_jsx("div", {
|
|
606
|
+
ref: containerRef,
|
|
607
|
+
className: className ? `rbs-container ${className}` : 'rbs-container',
|
|
608
|
+
style: style,
|
|
609
|
+
children: /*#__PURE__*/_jsxs("table", {
|
|
610
|
+
id: "rbs-root",
|
|
611
|
+
className: `rbs ${schedulerData.isVerticalResourceView() ? 'vertical-view' : ''}`,
|
|
612
|
+
style: rootTableStyle,
|
|
613
|
+
ref: schedulerRootRef,
|
|
614
|
+
children: [resourceColumnWidth !== undefined && /*#__PURE__*/_jsxs("colgroup", {
|
|
615
|
+
children: [/*#__PURE__*/_jsx("col", {
|
|
616
|
+
style: {
|
|
617
|
+
width: resourceColumnWidth
|
|
618
|
+
}
|
|
619
|
+
}), /*#__PURE__*/_jsx("col", {})]
|
|
620
|
+
}), /*#__PURE__*/_jsx("thead", {
|
|
621
|
+
children: /*#__PURE__*/_jsx("tr", {
|
|
622
|
+
children: /*#__PURE__*/_jsx("td", {
|
|
623
|
+
colSpan: "2",
|
|
624
|
+
children: schedulerHeader
|
|
625
|
+
})
|
|
674
626
|
})
|
|
675
|
-
})
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
})
|
|
627
|
+
}), /*#__PURE__*/_jsx("tbody", {
|
|
628
|
+
children: tbodyContent
|
|
629
|
+
})]
|
|
630
|
+
})
|
|
679
631
|
});
|
|
680
632
|
}
|
|
681
633
|
Scheduler.propTypes = {
|
|
682
634
|
parentRef: PropTypes.object,
|
|
635
|
+
className: PropTypes.string,
|
|
636
|
+
style: PropTypes.object,
|
|
683
637
|
schedulerData: PropTypes.object.isRequired,
|
|
684
638
|
prevClick: PropTypes.func.isRequired,
|
|
685
639
|
nextClick: PropTypes.func.isRequired,
|
package/dist/config/default.js
CHANGED
package/dist/config/scheduler.js
CHANGED
|
@@ -15,7 +15,7 @@ export default {
|
|
|
15
15
|
underneathHeight: 20,
|
|
16
16
|
schedulerMaxHeight: 0,
|
|
17
17
|
tableHeaderHeight: 40,
|
|
18
|
-
schedulerContentHeight: '
|
|
18
|
+
schedulerContentHeight: '100%',
|
|
19
19
|
responsiveByParent: false,
|
|
20
20
|
agendaResourceTableWidth: 160,
|
|
21
21
|
agendaMaxEventWidth: 100,
|
|
@@ -113,5 +113,10 @@ export default {
|
|
|
113
113
|
viewType: ViewType.Year,
|
|
114
114
|
showAgenda: false,
|
|
115
115
|
isEventPerspective: false
|
|
116
|
+
}, {
|
|
117
|
+
viewName: 'Vertical',
|
|
118
|
+
viewType: ViewType.VerticalResource,
|
|
119
|
+
showAgenda: false,
|
|
120
|
+
isEventPerspective: false
|
|
116
121
|
}]
|
|
117
122
|
};
|
package/dist/css/style.css
CHANGED
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
|
|
52
52
|
.rbs .scheduler-view {
|
|
53
53
|
border: 1px solid #e9e9e9;
|
|
54
|
-
|
|
54
|
+
position: relative;
|
|
55
|
+
left: -1px;
|
|
55
56
|
padding: 0;
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -81,6 +82,14 @@
|
|
|
81
82
|
margin-top: -1%;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
/* Container: fills its parent. :where() keeps specificity at zero so any className (e.g. tailwind) or style prop wins. */
|
|
86
|
+
:where(.rbs-container) {
|
|
87
|
+
width: 100%;
|
|
88
|
+
max-width: 100%;
|
|
89
|
+
max-height: 100%;
|
|
90
|
+
overflow: auto;
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
/* Tables */
|
|
85
94
|
.rbs table.resource-table,
|
|
86
95
|
.rbs table.scheduler-bg-table,
|
|
@@ -90,6 +99,7 @@
|
|
|
90
99
|
margin: 0;
|
|
91
100
|
padding: 0;
|
|
92
101
|
border-spacing: 0;
|
|
102
|
+
border-collapse: collapse;
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
.rbs table.resource-table,
|
|
@@ -165,6 +175,10 @@
|
|
|
165
175
|
padding-right: 20px;
|
|
166
176
|
}
|
|
167
177
|
|
|
178
|
+
.rbs.vertical-view .event-item {
|
|
179
|
+
margin: 0 1px;
|
|
180
|
+
}
|
|
181
|
+
|
|
168
182
|
/* Rounded Borders */
|
|
169
183
|
.rbs .round-all {
|
|
170
184
|
border-radius: 14px;
|
package/dist/index.d.ts
CHANGED
|
@@ -121,6 +121,10 @@ export interface SchedulerProps<EventType extends EventItem = EventItem> {
|
|
|
121
121
|
dndSources?: DnDSource[];
|
|
122
122
|
// biome-ignore lint/suspicious/noExplicitAny: legacy any usage
|
|
123
123
|
parentRef?: React.RefObject<any>;
|
|
124
|
+
/** Class names for the scheduler's container, e.g. tailwind classes. They win over the default 100% width / max height. */
|
|
125
|
+
className?: string;
|
|
126
|
+
/** Inline style for the scheduler's container. */
|
|
127
|
+
style?: CSSProperties;
|
|
124
128
|
// biome-ignore lint/suspicious/noExplicitAny: legacy any usage
|
|
125
129
|
CustomResourceHeader?: React.ComponentType<any>;
|
|
126
130
|
CustomResourceCell?: React.ComponentType<CustomResourceCellProps<EventType>>;
|
|
@@ -326,6 +330,7 @@ export interface State<EventType extends EventItem = EventItem> {
|
|
|
326
330
|
|
|
327
331
|
export interface SchedulerDataConfig {
|
|
328
332
|
schedulerWidth?: `${number}%`;
|
|
333
|
+
/** @deprecated The scheduler measures its own container now, so this offset is ignored. */
|
|
329
334
|
besidesWidth?: number;
|
|
330
335
|
underneathHeight?: number;
|
|
331
336
|
schedulerMaxHeight?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-big-schedule",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "React Big Schedule is a powerful and intuitive scheduler and resource planning solution built with React. Seamlessly integrate this modern browser-compatible component into your applications to effectively manage time, appointments, and resources. With drag-and-drop functionality, interactive UI, and granular views, react-big-schedule empowers users to effortlessly schedule and allocate resources with precision. Enhance productivity and streamline your workflow with this React-based solution, designed to optimize time management and simplify calendar-based operations. Perfect for applications requiring advanced scheduling capabilities, react-big-schedule offers a seamless and intuitive experience for managing appointments, resource allocation, and time slots. Unlock the potential of your React projects with react-big-schedule and revolutionize the way you handle scheduling and resource planning. It is the updated version of react-big-scheduler",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-big-schedule",
|