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 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
- Scheduler in the responsive layout:
572
- `actual width of Scheduler = (SchedulerData.documentWidth - SchedulerData.config.besidesWidth) * SchedulerData.config.schedulerWidth`
573
- `SchedulerData.documentWidth` is the window width of browser (or the the parent width in case SchedulerData.config.responsiveByParent
574
- and Scheduler component prop parentRef is passed) and will change automatically when resized.
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
- When true, Scheduler resposiveness will not be determined by the window width of browser but instead by the
579
- width of the of the parent (the parent ref must be passed to the Scheduler component prop named `parentRef`,
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
- slotId,
16
- groupOnly,
17
- rowHeight
18
- }) => {
19
- const rowCells = headers.map((header, index) => {
20
- const key = `${slotId}_${header.time}`;
21
- const style = index === headers.length - 1 ? {} : {
22
- width
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
- if (header.nonWorkingTime) {
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 => /*#__PURE__*/_jsxs("th", {
78
- colSpan: group.colspan,
79
- style: cellStyle,
80
- children: ["W", group.week]
81
- }, `week-${group.year}-${group.week}`));
82
- }, [showWeekNumber, headers, localeDayjs, config.headerBorderColor]);
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 startTime = headers[safeLeftIndex].time;
295
- let endTime = resourceEvents.headerItems[safeRightIndex - 1].end;
296
- if (cellUnit !== CellUnit.Hour) {
297
- endTime = localeDayjs(new Date(resourceEvents.headerItems[safeRightIndex - 1].start)).hour(23).minute(59).second(59).format(DATETIME_FORMAT);
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 = this.getSelectedResourceIds();
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() - 2;
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
- // Force built-in views only; custom views can provide their own cellUnit via custom date behavior.
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 baseWidth = this.documentWidth - this.config.besidesWidth > 0 ? this.documentWidth - this.config.besidesWidth : 0;
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 baseHeight = this.documentHeight - headerHeight - this.config.tableHeaderHeight - (this.config.underneathHeight || 0);
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
- getResourceTableWidth() {
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.isResourceViewResponsive() ? parseInt(schedulerWidth * Number(resourceTableConfigWidth.slice(0, -1)) / 100, 10) : resourceTableConfigWidth;
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
- return this.isContentViewResponsive() ? parseInt(schedulerWidth * Number(contentCellConfigWidth.slice(0, -1)) / 100, 10) : contentCellConfigWidth;
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 headers = [];
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
- headers.push({
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
- headers.push({
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
- headers.push({
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
- headers.push({
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
- headers.push({
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
- headers.push({
835
+ timeSlots.push({
794
836
  time
795
837
  });
796
838
  header = header.add(1, 'years').startOf('year');
797
839
  }
798
840
  }
799
- this.headers = headers;
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: Array(3)
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 = isEventPerspective ? eventGroups : resources;
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.isEventPerspective) {
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
- const headerStart = new Date(header.start);
1076
- const headerEnd = new Date(header.end);
1077
- if (headerEnd > eventStart && headerStart < eventEnd) {
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 = headerStart <= eventStart || index === 0;
1091
- if (render === false) {
1092
- const previousHeader = resourceEvents.headerItems[index - 1];
1093
- const previousHeaderStart = new Date(previousHeader.start);
1094
- const previousHeaderEnd = new Date(previousHeader.end);
1095
- if (previousHeaderEnd <= eventStart || previousHeaderStart >= eventEnd) render = true;
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
  }
@@ -84,6 +84,7 @@ const SchedulerHeader = /*#__PURE__*/React.forwardRef(({
84
84
  align: "middle",
85
85
  justify: "space-between",
86
86
  style: {
87
+ marginInline: 0,
87
88
  ...style
88
89
  },
89
90
  children: [leftCustomHeader, /*#__PURE__*/_jsx(Col, {
@@ -24,88 +24,38 @@ const initDndContext = (schedulerData, dndSources) => {
24
24
  };
25
25
 
26
26
  /**
27
- * Renders the scheduler UI with resource and agenda views, responsive sizing, and drag-and-drop support.
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 - The view model driving all scheduler state,
33
- * configuration, layout data, and rendering logic.
34
- * @param {Array<DnDSource>} [props.dndSources] - Additional drag-and-drop sources merged into
35
- * the scheduler's DnD context on first mount. The DnD context is initialized once and never rebuilt.
36
- * @param {React.RefObject<HTMLElement>} [props.parentRef] - Ref to the parent container element.
37
- * When provided alongside `responsiveByParent: true` in config, a ResizeObserver tracks the
38
- * parent's dimensions and triggers layout updates instead of listening to the window resize event.
39
- * @param {function(SchedulerData):void} props.prevClick - Callback invoked when the user navigates
40
- * to the previous time range.
41
- * @param {function(SchedulerData):void} props.nextClick - Callback invoked when the user navigates
42
- * to the next time range.
43
- * @param {function(SchedulerData, object):void} props.onViewChange - Callback invoked when the
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
- const onWindowResize = useCallback(() => {
174
- schedulerData.beginBatch();
175
- try {
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
- if (schedulerData.isSchedulerResponsive() && !schedulerData.config.responsiveByParent || parentRef === undefined) {
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._setDocumentWidth(document.documentElement.clientWidth);
187
- schedulerData._setDocumentHeight(document.documentElement.clientHeight);
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
- window.addEventListener('resize', onWindowResize);
192
- return () => window.removeEventListener('resize', onWindowResize);
193
- }
194
- }, [schedulerData, parentRef, onWindowResize]);
195
- useEffect(() => {
196
- const parentEl = parentRef?.current;
197
- if (parentRef !== undefined && schedulerData.config.responsiveByParent && parentEl) {
198
- const updateParentSize = () => {
199
- schedulerData.beginBatch();
200
- try {
201
- schedulerData._setDocumentWidth(parentEl.clientWidth);
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() - 1;
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 - 2,
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__*/_jsxs("table", {
665
- id: "rbs-root",
666
- className: "rbs",
667
- style: rootTableStyle,
668
- ref: schedulerRootRef,
669
- children: [/*#__PURE__*/_jsx("thead", {
670
- children: /*#__PURE__*/_jsx("tr", {
671
- children: /*#__PURE__*/_jsx("td", {
672
- colSpan: "2",
673
- children: schedulerHeader
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
- }), /*#__PURE__*/_jsx("tbody", {
677
- children: tbodyContent
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,
@@ -8,7 +8,8 @@ export const ViewType = {
8
8
  Year: 4,
9
9
  Custom: 5,
10
10
  Custom1: 6,
11
- Custom2: 7
11
+ Custom2: 7,
12
+ VerticalResource: 8
12
13
  };
13
14
  export const CellUnit = {
14
15
  Day: 0,
@@ -15,7 +15,7 @@ export default {
15
15
  underneathHeight: 20,
16
16
  schedulerMaxHeight: 0,
17
17
  tableHeaderHeight: 40,
18
- schedulerContentHeight: '500px',
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
  };
@@ -51,7 +51,8 @@
51
51
 
52
52
  .rbs .scheduler-view {
53
53
  border: 1px solid #e9e9e9;
54
- margin: 0 0 0 -1px;
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": "6.1.0",
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",