react-big-schedule 6.1.1-beta.0 → 7.1.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
@@ -17,7 +17,7 @@ Unlock the potential of your React projects with react-big-schedule and revoluti
17
17
 
18
18
  ### This project code is forked from:
19
19
 
20
- - [react-big-scheduler](https://stephenchou1017.github.io/scheduler/#/).
20
+ - [react-big-scheduler](https://github.com/StephenChou1017/react-big-scheduler).
21
21
  - [react-big-scheduler-stch](https://github.com/hbatalhaStch/react-big-scheduler).
22
22
 
23
23
  ## Installation
@@ -93,7 +93,7 @@ Special thanks to the following contributors:
93
93
 
94
94
  We would like to acknowledge the following projects for their inspiration and contributions:
95
95
 
96
- - [react-big-scheduler](https://stephenchou1017.github.io/scheduler/#/).
96
+ - [react-big-scheduler](https://github.com/StephenChou1017/react-big-scheduler).
97
97
  - [react-big-scheduler-stch](https://github.com/hbatalhaStch/react-big-scheduler).
98
98
 
99
99
  ## Roadmap
@@ -240,10 +240,10 @@ constructor(date=dayjs().format(DATE_FORMAT), viewType = ViewType.Week,
240
240
  view type, and will render the time window of the `2022-12` month in `ViewType.Month` view type.
241
241
  - `viewType` is the initial view type, now Scheduler supports `Day`, `Week`, `Month`, `Quarter`, `Year` 5 built-in view types,
242
242
  in addition Scheduler now supports `Custom`, `Custom1`, `Custom2` 3 custom view types at the same time, in which you can control
243
- the time window yourself, refer to [this example](https://stephenchou1017.github.io/scheduler/#/customtimewindow). `viewType`,
243
+ the time window yourself, refer to [this example](https://react-big-schedule.vercel.app/custom-time). `viewType`,
244
244
  `showAgenda` and `isEventPerspective` are a group which should be contained in the SchedulerData.config.views array,
245
245
  and they together decide which view should be rendered. When `showAgenda` and `isEventPerspective` are both `false`,
246
- Scheduler will render the resource view, refer to [this example](https://stephenchou1017.github.io/scheduler/#/views).
246
+ Scheduler will render the resource view, refer to [this example](https://react-big-schedule.vercel.app/basic).
247
247
  - `showAgenda` is a bool value, if true, Scheduler will display the agenda view of current view type. Agenda view is
248
248
  read only.
249
249
  - `isEventPerspective` is a bool value, if true, Scheduler will display the task view of current view type. In
@@ -410,7 +410,7 @@ addResource(resource);
410
410
  ```
411
411
 
412
412
  Add the `resource` to the `SchedulerData.resources`, make sure that `resource.id` is not duplicated. Refer
413
- to [this example](https://stephenchou1017.github.io/scheduler/#/addresource).
413
+ to [this example](https://react-big-schedule.vercel.app/customization).
414
414
 
415
415
  #### addEventGroup
416
416
 
@@ -529,7 +529,7 @@ getViewEndDate();
529
529
 
530
530
  Returns a dayjs object with the endDate of the currently selected view.
531
531
 
532
- ### Locale support(Refer to [this example](https://stephenchou1017.github.io/scheduler/#/locale) for details.)
532
+ ### Locale support(Refer to [this example](https://react-big-schedule.vercel.app/custom-time) for details.)
533
533
 
534
534
  #### SchedulerData.config.resourceName
535
535
 
@@ -567,25 +567,26 @@ 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
 
586
587
  The max height of Scheduler. If the desired height is bigger than the max height, the header row of Scheduler will be
587
588
  frozen and vertical scroll bar will appear, but this won't happen when the max height is set to `0`. Refer
588
- to [this example](https://stephenchou1017.github.io/scheduler/#/freezefirstrow).
589
+ to [this example](https://react-big-schedule.vercel.app/customization).
589
590
 
590
591
  **If you are using `responsiveByParent`, you can omit this setting or set it to `0` to ensure the Scheduler height conforms to the parent container.**
591
592
  When conforming to the parent container, the Scheduler will fill the available space and scroll bars will appear as needed.
@@ -619,7 +620,7 @@ Width of Scheduler table cells in resource view and task view of different view
619
620
  #### dayMaxEvents, weekMaxEvents, monthMaxEvents, yearMaxEvents, quarterMaxEvents
620
621
 
621
622
  Max events count of a cell in resource view and task view of different view types. A '+N more' will appear when exceeded.
622
- Refer to [this example](https://stephenchou1017.github.io/scheduler/#/addmore).
623
+ Refer to [this example](https://react-big-schedule.vercel.app/add-more).
623
624
 
624
625
  #### eventItemHeight
625
626
 
@@ -696,13 +697,13 @@ Controls whether to create new event item in resource view and task view.
696
697
  #### crossResourceMove
697
698
 
698
699
  Controls whether to cross-slot move an event item in resource view and task view. If `false`, the `slotId` and `slotName`
699
- won't change in the `moveEvent` method. Refer to [this example](https://stephenchou1017.github.io/scheduler/#/nocrossslotmove).
700
+ won't change in the `moveEvent` method. Refer to [this example](https://react-big-schedule.vercel.app/customization).
700
701
 
701
702
  #### checkConflict
702
703
 
703
704
  Controls whether to check conflicts when creating, resizing or moving an event item in resource view and task view. If
704
705
  `true`, Scheduler will call the `conflictOccurred` function if given. Refer to
705
- [this example](https://stephenchou1017.github.io/scheduler/#/overlapcheck).
706
+ [this example](https://react-big-schedule.vercel.app/customization).
706
707
 
707
708
  #### scrollToSpecialDayjsEnabled
708
709
 
@@ -792,7 +793,7 @@ getSummary(schedulerData, headerEvents, slotId, slotName, headerStart, headerEnd
792
793
  ```
793
794
 
794
795
  Method that defines the summary text displayed in the Scheduler cells.Refer
795
- to [this example](https://stephenchou1017.github.io/scheduler/#/summary).
796
+ to [this example](https://react-big-schedule.vercel.app/customization).
796
797
 
797
798
  #### getCustomDateFunc
798
799
 
@@ -801,7 +802,7 @@ getCustomDate(schedulerData, num, (date = undefined));
801
802
  ```
802
803
 
803
804
  Method that controls the start and end of time window when current view type is Custom, Custom1 or Custom2.Refer
804
- to [this example](https://stephenchou1017.github.io/scheduler/#/customtimewindow).
805
+ to [this example](https://react-big-schedule.vercel.app/custom-time).
805
806
 
806
807
  #### getNonAgendaViewBodyCellBgColorFunc
807
808
 
@@ -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
@@ -932,7 +950,7 @@ rightCustomHeader: PropTypes.object;
932
950
  ```
933
951
 
934
952
  Component you need to put in the Scheduler header, it could be a div or a react component. Refer
935
- to [this example](https://stephenchou1017.github.io/scheduler/#/customheader).
953
+ to [this example](https://react-big-schedule.vercel.app/customization).
936
954
 
937
955
  #### conflictOccurred
938
956
 
@@ -951,7 +969,7 @@ eventItemTemplateResolver: PropTypes.func;
951
969
  eventItemTemplateResolver(schedulerData, event, bgColor, isStart, isEnd, mustAddCssClass, mustBeHeight, agendaMaxEventWidth);
952
970
  ```
953
971
 
954
- Use this function, you can customize the event style. Refer to [this example](https://stephenchou1017.github.io/scheduler/#/customeventstyle).
972
+ Use this function, you can customize the event style. Refer to [this example](https://react-big-schedule.vercel.app/customization).
955
973
 
956
974
  ### eventItemPopoverTemplateResolver
957
975
 
@@ -960,7 +978,7 @@ eventItemPopoverTemplateResolver: PropTypes.func;
960
978
  eventItemPopoverTemplateResolver(schedulerData, eventItem, title, start, end, statusColor);
961
979
  ```
962
980
 
963
- Use this function, you can customize the event's popover style. Refer to [this example](https://stephenchou1017.github.io/scheduler/#/custompopover).
981
+ Use this function, you can customize the event's popover style. Refer to [this example](https://react-big-schedule.vercel.app/customization).
964
982
 
965
983
  #### slotItemTemplateResolver
966
984
 
@@ -978,7 +996,7 @@ nonAgendaCellHeaderTemplateResolver: PropTypes.func;
978
996
  nonAgendaCellHeaderTemplateResolver(schedulerData, item, formattedDateItems, style);
979
997
  ```
980
998
 
981
- Use this function, you can customize the table header cell style. Refer to [this example](https://stephenchou1017.github.io/scheduler/#/customtableheaders).
999
+ Use this function, you can customize the table header cell style. Refer to [this example](https://react-big-schedule.vercel.app/customization).
982
1000
 
983
1001
  #### onScrollLeft, onScrollRight
984
1002
 
@@ -989,7 +1007,7 @@ onScrollRight: PropTypes.func;
989
1007
  onScrollRight(schedulerData, schedulerContent, maxScrollLeft);
990
1008
  ```
991
1009
 
992
- Callback function fired when the scheduler content div scrolls to leftmost or rightmost. Refer to [this example](https://stephenchou1017.github.io/scheduler/#/infinitescroll).
1010
+ Callback function fired when the scheduler content div scrolls to leftmost or rightmost. Refer to [this example](https://react-big-schedule.vercel.app/customization).
993
1011
 
994
1012
  #### onScrollTop, onScrollBottom
995
1013
 
@@ -1000,7 +1018,7 @@ onScrollBottom: PropTypes.func;
1000
1018
  onScrollBottom(schedulerData, schedulerContent, maxScrollTop);
1001
1019
  ```
1002
1020
 
1003
- Callback function fired when the scheduler content div scrolls to topmost or bottommost. Refer to [this example](https://stephenchou1017.github.io/scheduler/#/infinitescroll).
1021
+ Callback function fired when the scheduler content div scrolls to topmost or bottommost. Refer to [this example](https://react-big-schedule.vercel.app/customization).
1004
1022
 
1005
1023
  #### slotClickedFunc
1006
1024
 
@@ -1009,7 +1027,7 @@ slotClickedFunc: PropTypes.func;
1009
1027
  ```
1010
1028
 
1011
1029
  If it's set, slots will be clickable, and will fire this function when a slot is clicked. Refer
1012
- to [this example](https://stephenchou1017.github.io/scheduler/#/resourceclickable).
1030
+ to [this example](https://react-big-schedule.vercel.app/customization).
1013
1031
 
1014
1032
  #### dndSources
1015
1033
 
@@ -1019,7 +1037,7 @@ dndSources: PropTypes.array;
1019
1037
 
1020
1038
  DnDSource array that registered to Scheduler. Use [DnDSource](https://github.com/ansulagrawal/react-big-schedule/blob/master/src/components/DnDSource.js),
1021
1039
  we can simplify the drag and drop coding in React-Big-Scheduler. Refer
1022
- to [this example](https://stephenchou1017.github.io/scheduler/#/draganddrop).
1040
+ to [this example](https://react-big-schedule.vercel.app/drag-and-drop).
1023
1041
 
1024
1042
  #### onSetAddMoreState
1025
1043
 
@@ -1030,7 +1048,7 @@ onSetAddMoreState(newState);
1030
1048
 
1031
1049
  Callback function fired when a '+N more' is clicked, is used to control the visibility and the position of the `AddMorePopover`.
1032
1050
  `newState` is a json such as {headerItem: headerItem, left: 20, top: 20, height: 100}. Refer
1033
- to [this example](https://stephenchou1017.github.io/scheduler/#/addmore).
1051
+ to [this example](https://react-big-schedule.vercel.app/add-more).
1034
1052
 
1035
1053
  #### subtitleGetter
1036
1054
 
@@ -720,7 +720,8 @@ EventItem.propTypes = {
720
720
  viewEvent2Click: PropTypes.func,
721
721
  viewEvent2Text: PropTypes.string,
722
722
  conflictOccurred: PropTypes.func,
723
- eventItemTemplateResolver: PropTypes.func
723
+ eventItemTemplateResolver: PropTypes.func,
724
+ eventItemPopoverTemplateResolver: PropTypes.func
724
725
  };
725
726
  EventItem.defaultProps = {
726
727
  isDragging: undefined,
@@ -736,5 +737,6 @@ EventItem.defaultProps = {
736
737
  viewEvent2Click: undefined,
737
738
  viewEvent2Text: undefined,
738
739
  conflictOccurred: undefined,
739
- eventItemTemplateResolver: undefined
740
+ eventItemTemplateResolver: undefined,
741
+ eventItemPopoverTemplateResolver: undefined
740
742
  };
@@ -29,6 +29,7 @@ class ResourceEvents extends PureComponent {
29
29
  viewEvent2Text: PropTypes.string,
30
30
  newEvent: PropTypes.func,
31
31
  eventItemTemplateResolver: PropTypes.func,
32
+ eventItemPopoverTemplateResolver: PropTypes.func,
32
33
  onSelectionChange: PropTypes.func,
33
34
  isRowSelected: PropTypes.bool,
34
35
  selectionPreview: PropTypes.shape({
@@ -268,7 +269,7 @@ class ResourceEvents extends PureComponent {
268
269
  rightIndex
269
270
  } = this.state;
270
271
  this.cleanupDragInteraction();
271
- if (headers.length === 0 || !resourceEvents.headerItems || resourceEvents.headerItems.length === 0) {
272
+ const resetSelection = () => {
272
273
  this.setState({
273
274
  startX: 0,
274
275
  leftIndex: 0,
@@ -284,17 +285,33 @@ class ResourceEvents extends PureComponent {
284
285
  left: 0,
285
286
  width: 0
286
287
  });
288
+ };
289
+ if (headers.length === 0 || !resourceEvents.headerItems || resourceEvents.headerItems.length === 0) {
290
+ resetSelection();
287
291
  return;
288
292
  }
293
+
294
+ // a cell width of 0 (container not measured yet) makes the drag indexes NaN or Infinity
295
+ const toIndex = (value, fallback) => Number.isFinite(value) ? value : fallback;
289
296
  const maxLeftIndex = headers.length - 1;
290
- const safeLeftIndex = Math.max(0, Math.min(leftIndex, maxLeftIndex));
297
+ const safeLeftIndex = Math.max(0, Math.min(toIndex(leftIndex, 0), maxLeftIndex));
291
298
  const maxRightIndex = Math.min(headers.length, resourceEvents.headerItems.length);
292
- let safeRightIndex = Math.max(1, rightIndex, safeLeftIndex + 1);
299
+ let safeRightIndex = Math.max(1, toIndex(rightIndex, 1), safeLeftIndex + 1);
293
300
  safeRightIndex = Math.min(safeRightIndex, maxRightIndex);
301
+
302
+ // headers and headerItems can be out of step while the view rebuilds; drop the selection instead of throwing
303
+ if (!headers[safeLeftIndex] || !resourceEvents.headerItems[safeRightIndex - 1]) {
304
+ resetSelection();
305
+ return;
306
+ }
307
+ const isVertical = schedulerData.isVerticalResourceView();
308
+ // vertical view: rows are time slots and columns are resources, so the roles swap
309
+ const selectedRowIds = this.getSelectedResourceIds();
294
310
  let startTime, endTime;
295
- if (schedulerData.isVerticalResourceView()) {
296
- startTime = resourceEvents.slotId;
297
- endTime = localeDayjs(new Date(startTime)).add(config.minuteStep, 'minutes').format(DATETIME_FORMAT);
311
+ if (isVertical) {
312
+ startTime = selectedRowIds[0] ?? resourceEvents.slotId;
313
+ const lastRowId = selectedRowIds[selectedRowIds.length - 1] ?? startTime;
314
+ endTime = localeDayjs(new Date(lastRowId)).add(config.minuteStep, 'minutes').format(DATETIME_FORMAT);
298
315
  } else {
299
316
  startTime = headers[safeLeftIndex].time;
300
317
  endTime = resourceEvents.headerItems[safeRightIndex - 1].end;
@@ -304,7 +321,7 @@ class ResourceEvents extends PureComponent {
304
321
  }
305
322
 
306
323
  // Get selected resource IDs
307
- const selectedResourceIds = this.getSelectedResourceIds();
324
+ const selectedResourceIds = isVertical ? headers.slice(safeLeftIndex, safeRightIndex).map(header => header.id) : selectedRowIds;
308
325
  const slotId = selectedResourceIds.length > 0 ? selectedResourceIds[0] : resourceEvents.slotId;
309
326
  const slotName = selectedResourceIds.length > 0 ? schedulerData.getResourceById(slotId)?.name || slotId : resourceEvents.slotName;
310
327
  this.setState({
@@ -571,6 +588,7 @@ class ResourceEvents extends PureComponent {
571
588
  viewEvent2Click: this.props.viewEvent2Click,
572
589
  viewEvent2Text: this.props.viewEvent2Text,
573
590
  eventItemTemplateResolver: this.props.eventItemTemplateResolver,
591
+ eventItemPopoverTemplateResolver: this.props.eventItemPopoverTemplateResolver,
574
592
  subtitleGetter: this.props.subtitleGetter,
575
593
  updateEventStart: this.props.updateEventStart,
576
594
  updateEventEnd: this.props.updateEventEnd,
@@ -23,6 +23,8 @@ export default class SchedulerData {
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;
@@ -370,7 +372,8 @@ export default class SchedulerData {
370
372
  return !!contentCellWidth.endsWith && contentCellWidth.endsWith('%');
371
373
  }
372
374
  getSchedulerWidth() {
373
- 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;
374
377
  return this.isSchedulerResponsive() ? parseInt(baseWidth * Number(this.config.schedulerWidth.slice(0, -1)) / 100, 10) : this.config.schedulerWidth;
375
378
  }
376
379
  getSchedulerHeight() {
@@ -385,10 +388,16 @@ export default class SchedulerData {
385
388
  // fallback to max height if set, else just use base height
386
389
  return this.config.schedulerMaxHeight || validBaseHeight;
387
390
  }
388
- getResourceTableWidth() {
391
+
392
+ // configured resource column width in px (config may be a percentage of the scheduler width)
393
+ _getConfiguredResourceTableWidth() {
389
394
  const resourceTableConfigWidth = this.getResourceTableConfigWidth();
395
+ return this.isResourceViewResponsive() ? parseInt(this.getSchedulerWidth() * Number(resourceTableConfigWidth.slice(0, -1)) / 100, 10) : resourceTableConfigWidth;
396
+ }
397
+ getResourceTableWidth() {
390
398
  const schedulerWidth = this.getSchedulerWidth();
391
- 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
392
401
  if (this.isSchedulerResponsive() && this.getContentTableWidth() + resourceTableWidth < schedulerWidth) resourceTableWidth = schedulerWidth - this.getContentTableWidth();
393
402
  return resourceTableWidth;
394
403
  }
@@ -397,9 +406,10 @@ export default class SchedulerData {
397
406
  const schedulerWidth = this.getSchedulerWidth();
398
407
  const cellWidth = this.isContentViewResponsive() ? parseInt(schedulerWidth * Number(contentCellConfigWidth.slice(0, -1)) / 100, 10) : contentCellConfigWidth;
399
408
 
400
- // vertical view: resources are the columns, so they share the width the time column doesn't need
401
- if (this.isVerticalResourceView() && this.isSchedulerResponsive() && !this.isResourceViewResponsive() && this.headers?.length) {
402
- const fillWidth = Math.floor((schedulerWidth - this.getResourceTableConfigWidth()) / this.headers.length);
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);
403
413
  return Math.max(cellWidth, fillWidth);
404
414
  }
405
415
  return cellWidth;
@@ -603,6 +613,12 @@ export default class SchedulerData {
603
613
  const configProperty = viewConfigMap[this.viewType] || 'customCellWidth';
604
614
  return this.config[configProperty];
605
615
  }
616
+ _setMeasuredByContainer(measuredByContainer) {
617
+ if (this.measuredByContainer !== measuredByContainer) {
618
+ this.measuredByContainer = measuredByContainer;
619
+ this.bumpVersion();
620
+ }
621
+ }
606
622
  _setDocumentWidth(documentWidth) {
607
623
  if (documentWidth >= 0 && documentWidth !== this.documentWidth) {
608
624
  this.documentWidth = documentWidth;
@@ -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, {
@@ -41,11 +41,21 @@ const initDndContext = (schedulerData, dndSources) => {
41
41
  * @returns {JSX.Element} The root scheduler table element containing the rendered scheduler UI.
42
42
  */
43
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
+ };
44
52
  function Scheduler(props) {
45
53
  const {
46
54
  schedulerData,
47
55
  dndSources,
48
56
  parentRef,
57
+ className,
58
+ style,
49
59
  prevClick,
50
60
  nextClick,
51
61
  onViewChange,
@@ -72,6 +82,7 @@ function Scheduler(props) {
72
82
  });
73
83
  const [, setRenderTrigger] = useState(0);
74
84
  const schedulerRootRef = useRef(null);
85
+ const containerRef = useRef(null);
75
86
 
76
87
  // Layout/header refs - declare before setSchedulerHeaderRef useCallback
77
88
  const schedulerHeaderRef = useRef(null);
@@ -110,54 +121,39 @@ function Scheduler(props) {
110
121
  }
111
122
  return undefined;
112
123
  }, [schedulerData]);
113
- const onWindowResize = useCallback(() => {
114
- schedulerData.beginBatch();
115
- try {
116
- schedulerData._setDocumentWidth(document.documentElement.clientWidth);
117
- schedulerData._setDocumentHeight(document.documentElement.clientHeight);
118
- } finally {
119
- schedulerData.endBatch();
120
- }
121
- }, [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.
122
127
  useEffect(() => {
123
- 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 = () => {
124
136
  schedulerData.beginBatch();
125
137
  try {
126
- schedulerData._setDocumentWidth(document.documentElement.clientWidth);
127
- 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
+ }
128
143
  } finally {
129
144
  schedulerData.endBatch();
130
145
  }
131
- window.addEventListener('resize', onWindowResize);
132
- return () => window.removeEventListener('resize', onWindowResize);
133
- }
134
- }, [schedulerData, parentRef, onWindowResize]);
135
- useEffect(() => {
136
- const parentEl = parentRef?.current;
137
- if (parentRef !== undefined && schedulerData.config.responsiveByParent && parentEl) {
138
- const updateParentSize = () => {
139
- schedulerData.beginBatch();
140
- try {
141
- schedulerData._setDocumentWidth(parentEl.clientWidth);
142
- schedulerData._setDocumentHeight(parentEl.clientHeight);
143
- } finally {
144
- schedulerData.endBatch();
145
- }
146
- };
147
- updateParentSize();
148
-
149
- // Disconnect any previous observer to prevent memory leaks
150
- if (ulObserverRef.current) {
151
- ulObserverRef.current.disconnect();
152
- }
153
- ulObserverRef.current = new ResizeObserver(updateParentSize);
154
- ulObserverRef.current.observe(parentEl);
155
- return () => {
156
- if (ulObserverRef.current) {
157
- ulObserverRef.current.disconnect();
158
- }
159
- };
160
- }
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
+ };
161
157
  }, [parentRef, schedulerData]);
162
158
  useEffect(() => {
163
159
  if (schedulerData.config.responsiveByParent && schedulerHeaderEl) {
@@ -368,10 +364,11 @@ function Scheduler(props) {
368
364
  viewEvent2Text: props.viewEvent2Text,
369
365
  newEvent: props.newEvent,
370
366
  eventItemTemplateResolver: props.eventItemTemplateResolver,
367
+ eventItemPopoverTemplateResolver: props.eventItemPopoverTemplateResolver,
371
368
  onSelectionChange: handleSelectionChange,
372
369
  isRowSelected: selectedIdsSet.has(item.slotId),
373
370
  selectionPreview: selectionPreview
374
- }, item.slotId)), [displayRenderData, schedulerData, schedulerDataVersion, eventDndSource, dndContext, props.onSetAddMoreState, props.updateEventStart, props.updateEventEnd, props.moveEvent, props.movingEvent, props.conflictOccurred, props.subtitleGetter, props.eventItemClick, props.viewEventClick, props.viewEventText, props.viewEvent2Click, props.viewEvent2Text, props.newEvent, props.eventItemTemplateResolver, handleSelectionChange, selectedIdsSet, selectionPreview]);
371
+ }, item.slotId)), [displayRenderData, schedulerData, schedulerDataVersion, eventDndSource, dndContext, props.onSetAddMoreState, props.updateEventStart, props.updateEventEnd, props.moveEvent, props.movingEvent, props.conflictOccurred, props.subtitleGetter, props.eventItemClick, props.viewEventClick, props.viewEventText, props.viewEvent2Click, props.viewEvent2Text, props.newEvent, props.eventItemTemplateResolver, props.eventItemPopoverTemplateResolver, handleSelectionChange, selectedIdsSet, selectionPreview]);
375
372
  let tbodyContent = /*#__PURE__*/_jsx("tr", {});
376
373
  if (showAgenda) {
377
374
  tbodyContent = /*#__PURE__*/_jsx(AgendaView, {
@@ -606,31 +603,38 @@ function Scheduler(props) {
606
603
  width: `${width}px`,
607
604
  tableLayout: 'fixed'
608
605
  }), [width]);
609
- return /*#__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
606
+ return /*#__PURE__*/_jsx("div", {
607
+ ref: containerRef,
608
+ className: className ? `rbs-container ${className}` : 'rbs-container',
609
+ style: style,
610
+ children: /*#__PURE__*/_jsxs("table", {
611
+ id: "rbs-root",
612
+ className: `rbs ${schedulerData.isVerticalResourceView() ? 'vertical-view' : ''}`,
613
+ style: rootTableStyle,
614
+ ref: schedulerRootRef,
615
+ children: [resourceColumnWidth !== undefined && /*#__PURE__*/_jsxs("colgroup", {
616
+ children: [/*#__PURE__*/_jsx("col", {
617
+ style: {
618
+ width: resourceColumnWidth
619
+ }
620
+ }), /*#__PURE__*/_jsx("col", {})]
621
+ }), /*#__PURE__*/_jsx("thead", {
622
+ children: /*#__PURE__*/_jsx("tr", {
623
+ children: /*#__PURE__*/_jsx("td", {
624
+ colSpan: "2",
625
+ children: schedulerHeader
626
+ })
625
627
  })
626
- })
627
- }), /*#__PURE__*/_jsx("tbody", {
628
- children: tbodyContent
629
- })]
628
+ }), /*#__PURE__*/_jsx("tbody", {
629
+ children: tbodyContent
630
+ })]
631
+ })
630
632
  });
631
633
  }
632
634
  Scheduler.propTypes = {
633
635
  parentRef: PropTypes.object,
636
+ className: PropTypes.string,
637
+ style: PropTypes.object,
634
638
  schedulerData: PropTypes.object.isRequired,
635
639
  prevClick: PropTypes.func.isRequired,
636
640
  nextClick: PropTypes.func.isRequired,
@@ -652,6 +656,7 @@ Scheduler.propTypes = {
652
656
  viewEvent2Text: PropTypes.string,
653
657
  conflictOccurred: PropTypes.func,
654
658
  eventItemTemplateResolver: PropTypes.func,
659
+ eventItemPopoverTemplateResolver: PropTypes.func,
655
660
  dndSources: PropTypes.array,
656
661
  slotClickedFunc: PropTypes.func,
657
662
  toggleExpandFunc: PropTypes.func,
@@ -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,
@@ -82,6 +82,14 @@
82
82
  margin-top: -1%;
83
83
  }
84
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
+
85
93
  /* Tables */
86
94
  .rbs table.resource-table,
87
95
  .rbs table.scheduler-bg-table,
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.1-beta.0",
3
+ "version": "7.1.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",