Skip to content

Commit 295b9a9

Browse files
rahmanunvergjulivan
authored andcommitted
feat(calendar-web): doubleClick handling, selected focus, update widget to module doc
1 parent e2baa8c commit 295b9a9

File tree

3 files changed

+96
-21
lines changed

3 files changed

+96
-21
lines changed

packages/pluggableWidgets/calendar-web/src/helpers/CalendarPropsBuilder.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ export class CalendarPropsBuilder {
4141
private readonly minTime: Date;
4242
private readonly maxTime: Date;
4343

44+
// Keeps the currently focused/selected event. Updated on every selection.
45+
private selectedEvent?: CalendarEvent;
46+
/**
47+
* Stores the event that should be ignored on the next onSelectEvent call.
48+
* This is set when a double-click is detected so we can ignore the
49+
* secondary click that React-Big-Calendar dispatches as part of the same
50+
* interaction. After the click has been ignored, the field is cleared so
51+
* that future selections are handled normally.
52+
*/
53+
private ignoreNextClickFor?: CalendarEvent;
54+
4455
constructor(private props: CalendarContainerProps) {
4556
this.isCustomView = props.view === "custom";
4657
this.defaultView = this.isCustomView ? props.defaultViewCustom : props.defaultViewStandard;
@@ -75,10 +86,14 @@ export class CalendarPropsBuilder {
7586
allDayAccessor: (event: CalendarEvent) => event.allDay,
7687
endAccessor: (event: CalendarEvent) => event.end,
7788
eventPropGetter,
89+
// @ts-expect-error – navigatable prop not yet in typings but exists in runtime component
90+
navigatable: true,
7891
onEventDrop: this.handleEventDropOrResize,
7992
onEventResize: this.handleEventDropOrResize,
8093
onNavigate: this.handleRangeChange,
81-
onSelectEvent: this.handleEditEvent,
94+
onSelectEvent: this.handleSelectEvent,
95+
onDoubleClickEvent: this.handleDoubleClickEvent,
96+
onKeyPressEvent: this.handleKeyPressEvent,
8297
onSelectSlot: this.handleCreateEvent,
8398
startAccessor: (event: CalendarEvent) => event.start,
8499
titleAccessor: (event: CalendarEvent) => event.title,
@@ -197,13 +212,51 @@ export class CalendarPropsBuilder {
197212
}
198213
};
199214

200-
private handleEditEvent = (event: CalendarEvent): void => {
215+
/**
216+
* Called when an event is single-clicked (selected).
217+
* First click only stores the selection; a consecutive click on the same event triggers edit.
218+
*/
219+
private handleSelectEvent = (event: CalendarEvent): void => {
220+
if (this.ignoreNextClickFor === event) {
221+
// Skip this click – it belongs to a double-click we've already handled.
222+
this.ignoreNextClickFor = undefined;
223+
return;
224+
}
225+
226+
if (this.selectedEvent === event) {
227+
// Second click on the already-selected event => open edit panel.
228+
this.invokeEdit(event);
229+
} else {
230+
// Update current selection.
231+
this.selectedEvent = event;
232+
}
233+
};
234+
235+
/**
236+
* Fast double click should open edit immediately.
237+
*/
238+
private handleDoubleClickEvent = (event: CalendarEvent): void => {
239+
// Open edit immediately & prevent the subsequent single-click handler from firing edit again.
240+
this.invokeEdit(event);
241+
this.ignoreNextClickFor = event;
242+
};
243+
244+
/**
245+
* When the event has focus, pressing Enter should open edit.
246+
*/
247+
private handleKeyPressEvent = (event: CalendarEvent, e: any): void => {
248+
if (e.key === "Enter") {
249+
this.invokeEdit(event);
250+
}
251+
};
252+
253+
private invokeEdit(event: CalendarEvent): void {
201254
const action = this.props.onEditEvent?.get(event.item);
202255

203256
if (action?.canExecute) {
204257
action.execute();
205258
}
206-
};
259+
}
207260

208261
private handleCreateEvent = (slotInfo: { start: Date; end: Date; action: string }): void => {
209262
const action = this.props.onCreateEvent;

packages/pluggableWidgets/calendar-web/src/utils/calendar-utils.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@ import {
1616
differenceInCalendarDays
1717
} from "date-fns";
1818

19+
// Utility to lighten hex colors. Accepts #RGB or #RRGGBB.
20+
function lightenColor(color: string, amount = 0.2): string {
21+
if (color.startsWith("#")) {
22+
let hex = color.slice(1);
23+
if (hex.length === 3) {
24+
hex = hex
25+
.split("")
26+
.map(c => c + c)
27+
.join("");
28+
}
29+
if (hex.length === 6) {
30+
/* eslint-disable no-bitwise */
31+
const num = parseInt(hex, 16);
32+
const r = Math.min(255, Math.round(((num >> 16) & 0xff) * (1 + amount)));
33+
const g = Math.min(255, Math.round(((num >> 8) & 0xff) * (1 + amount)));
34+
const b = Math.min(255, Math.round((num & 0xff) * (1 + amount)));
35+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
36+
/* eslint-enable no-bitwise */
37+
}
38+
}
39+
// Fallback: return same color
40+
return color;
41+
}
42+
1943
export {
2044
format,
2145
parse,
@@ -39,9 +63,21 @@ type EventPropGetterReturnType = {
3963
| undefined;
4064
};
4165

42-
export function eventPropGetter(event: CalendarEvent): EventPropGetterReturnType {
66+
export function eventPropGetter(
67+
event: CalendarEvent,
68+
_start?: Date,
69+
_end?: Date,
70+
isSelected?: boolean
71+
): EventPropGetterReturnType {
72+
if (!event.color) {
73+
// Let RBC handle default styling
74+
return { style: undefined };
75+
}
76+
77+
const backgroundColor = isSelected ? lightenColor(event.color, 0.25) : event.color;
78+
4379
return {
44-
style: event.color ? { backgroundColor: event.color } : undefined
80+
style: { backgroundColor }
4581
};
4682
}
4783

packages/pluggableWidgets/calendar-web/typings/CalendarProps.d.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,7 @@
44
* @author Mendix Widgets Framework Team
55
*/
66
import { CSSProperties } from "react";
7-
import {
8-
ActionValue,
9-
DynamicValue,
10-
EditableValue,
11-
ListValue,
12-
Option,
13-
ListActionValue,
14-
ListAttributeValue,
15-
ListExpressionValue
16-
} from "mendix";
7+
import { ActionValue, DynamicValue, EditableValue, ListValue, Option, ListActionValue, ListAttributeValue, ListExpressionValue } from "mendix";
178

189
export type TitleTypeEnum = "attribute" | "expression";
1910

@@ -70,12 +61,7 @@ export interface CalendarContainerProps {
7061
eventDataAttribute?: EditableValue<string>;
7162
onEditEvent?: ListActionValue;
7263
onCreateEvent?: ActionValue<{ startDate: Option<Date>; endDate: Option<Date>; allDay: Option<boolean> }>;
73-
onDragDropResize?: ListActionValue<{
74-
oldStart: Option<Date>;
75-
oldEnd: Option<Date>;
76-
newStart: Option<Date>;
77-
newEnd: Option<Date>;
78-
}>;
64+
onDragDropResize?: ListActionValue<{ oldStart: Option<Date>; oldEnd: Option<Date>; newStart: Option<Date>; newEnd: Option<Date> }>;
7965
onViewRangeChange?: ActionValue<{ rangeStart: Option<Date>; rangeEnd: Option<Date>; currentView: Option<string> }>;
8066
widthUnit: WidthUnitEnum;
8167
width: number;

0 commit comments

Comments
 (0)