react-big-schedule 6.1.1-beta.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
@@ -291,10 +291,14 @@ 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 isVertical = schedulerData.isVerticalResourceView();
295
+ // vertical view: rows are time slots and columns are resources, so the roles swap
296
+ const selectedRowIds = this.getSelectedResourceIds();
294
297
  let startTime, endTime;
295
- if (schedulerData.isVerticalResourceView()) {
296
- startTime = resourceEvents.slotId;
297
- endTime = localeDayjs(new Date(startTime)).add(config.minuteStep, 'minutes').format(DATETIME_FORMAT);
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);
298
302
  } else {
299
303
  startTime = headers[safeLeftIndex].time;
300
304
  endTime = resourceEvents.headerItems[safeRightIndex - 1].end;
@@ -304,7 +308,7 @@ class ResourceEvents extends PureComponent {
304
308
  }
305
309
 
306
310
  // Get selected resource IDs
307
- const selectedResourceIds = this.getSelectedResourceIds();
311
+ const selectedResourceIds = isVertical ? headers.slice(safeLeftIndex, safeRightIndex).map(header => header.id) : selectedRowIds;
308
312
  const slotId = selectedResourceIds.length > 0 ? selectedResourceIds[0] : resourceEvents.slotId;
309
313
  const slotName = selectedResourceIds.length > 0 ? schedulerData.getResourceById(slotId)?.name || slotId : resourceEvents.slotName;
310
314
  this.setState({
@@ -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) {
@@ -606,31 +602,38 @@ function Scheduler(props) {
606
602
  width: `${width}px`,
607
603
  tableLayout: 'fixed'
608
604
  }), [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
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
+ })
625
626
  })
626
- })
627
- }), /*#__PURE__*/_jsx("tbody", {
628
- children: tbodyContent
629
- })]
627
+ }), /*#__PURE__*/_jsx("tbody", {
628
+ children: tbodyContent
629
+ })]
630
+ })
630
631
  });
631
632
  }
632
633
  Scheduler.propTypes = {
633
634
  parentRef: PropTypes.object,
635
+ className: PropTypes.string,
636
+ style: PropTypes.object,
634
637
  schedulerData: PropTypes.object.isRequired,
635
638
  prevClick: PropTypes.func.isRequired,
636
639
  nextClick: PropTypes.func.isRequired,
@@ -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.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",