@super-calendar/native 2.4.0 → 2.6.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/dist/picker.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_MonthList = require("./MonthList-Aczb2RSY.js");
2
+ const require_MonthList = require("./MonthList-Gu6p64fE.js");
3
3
  let _super_calendar_core = require("@super-calendar/core");
4
4
  exports.CalendarThemeProvider = require_MonthList.CalendarThemeProvider;
5
5
  exports.DefaultMonthEvent = require_MonthList.DefaultMonthEvent;
package/dist/picker.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { d as darkTheme, f as defaultTheme, i as MonthView, m as useCalendarTheme, n as DefaultMonthEvent, p as mergeTheme, r as MonthPager, t as MonthList, u as CalendarThemeProvider } from "./MonthList-zPLklHAY.mjs";
1
+ import { d as darkTheme, f as defaultTheme, i as MonthView, m as useCalendarTheme, n as DefaultMonthEvent, p as mergeTheme, r as MonthPager, t as MonthList, u as CalendarThemeProvider } from "./MonthList-CMzYn9T0.mjs";
2
2
  import { buildMonthGrid, buildMonthWeeks, daySelectionState, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, minutesIntoDay, nextDateRange, useDateRange, useMonthGrid } from "@super-calendar/core";
3
3
  export { CalendarThemeProvider, DefaultMonthEvent, MonthList, MonthPager, MonthView, buildMonthGrid, buildMonthWeeks, darkTheme, daySelectionState, defaultTheme, getIsToday, getWeekDays, isDateSelectable, isRangeEndpoint, isSameCalendarDay, isWeekend, isWithinDateRange, mergeTheme, minutesIntoDay, nextDateRange, useCalendarTheme, useDateRange, useMonthGrid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@super-calendar/native",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Gesture-driven, virtualized month / week / day calendar and date picker for React Native (and web via react-native-web).",
5
5
  "keywords": [
6
6
  "agenda",
@@ -64,7 +64,7 @@
64
64
  "access": "public"
65
65
  },
66
66
  "dependencies": {
67
- "@super-calendar/core": "2.4.0"
67
+ "@super-calendar/core": "2.6.0"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@legendapp/list": ">=3",
@@ -5,10 +5,12 @@ import {
5
5
  endOfDay,
6
6
  endOfMonth,
7
7
  endOfWeek,
8
+ endOfYear,
8
9
  type Locale,
9
10
  startOfDay,
10
11
  startOfMonth,
11
12
  startOfWeek,
13
+ startOfYear,
12
14
  } from "date-fns";
13
15
  import { type ReactElement, useCallback, useMemo } from "react";
14
16
  import type { StyleProp, ViewStyle } from "react-native";
@@ -31,6 +33,7 @@ import {
31
33
  getViewDays,
32
34
  } from "@super-calendar/core";
33
35
  import { type SlotStyleProps } from "../utils/slots";
36
+ import { YearView, type YearViewSlot } from "./YearView";
34
37
  import { Agenda, type AgendaSlot } from "./Agenda";
35
38
  import { DefaultEvent } from "./DefaultEvent";
36
39
  import { MonthPager } from "./MonthPager";
@@ -49,7 +52,7 @@ import {
49
52
  * month pager, the grid slots reach the time grid, and the agenda slots reach
50
53
  * the schedule view; slots for views that aren't rendered are ignored.
51
54
  */
52
- export type CalendarSlot = MonthViewSlot | TimeGridSlot | AgendaSlot;
55
+ export type CalendarSlot = MonthViewSlot | TimeGridSlot | AgendaSlot | YearViewSlot;
53
56
 
54
57
  /** Props for the {@link Calendar} component. */
55
58
  export type CalendarProps<T> = SlotStyleProps<CalendarSlot> & {
@@ -85,8 +88,10 @@ export type CalendarProps<T> = SlotStyleProps<CalendarSlot> & {
85
88
  * drag-to-move and drag-to-resize working while hiding the visible indicator.
86
89
  */
87
90
  showDragHandle?: boolean;
88
- /** Tap a day cell (month mode) — e.g. drill into the day view. */
91
+ /** Tap a day cell (month and year modes) — e.g. drill into the day view. */
89
92
  onPressDay?: (date: Date) => void;
93
+ /** Tap a month's title in the year view — e.g. jump to that month. */
94
+ onPressMonth?: (month: Date) => void;
90
95
  /** Long-press a day cell (month mode). */
91
96
  onLongPressDay?: (date: Date) => void;
92
97
  /** Tap the "+N more" overflow label in a month cell. */
@@ -261,6 +266,12 @@ function visibleRange(
261
266
  endOfWeek(endOfMonth(date), { weekStartsOn }),
262
267
  ];
263
268
  }
269
+ if (mode === "year") {
270
+ return [
271
+ startOfWeek(startOfYear(date), { weekStartsOn }),
272
+ endOfWeek(endOfYear(date), { weekStartsOn }),
273
+ ];
274
+ }
264
275
  const days = getViewDays(mode, date, weekStartsOn, numberOfDays, false, weekEndsOn);
265
276
  return [startOfDay(days[0]), endOfDay(days[days.length - 1])];
266
277
  }
@@ -283,6 +294,12 @@ function expansionRange(
283
294
  addWeeks(endOfWeek(endOfMonth(addMonths(date, 1)), { weekStartsOn }), 1),
284
295
  ];
285
296
  }
297
+ if (mode === "year") {
298
+ return [
299
+ startOfWeek(startOfYear(date), { weekStartsOn }),
300
+ endOfWeek(endOfYear(date), { weekStartsOn }),
301
+ ];
302
+ }
286
303
  if (mode === "schedule") {
287
304
  // The agenda lists forward from the anchor date; three months is a sensible
288
305
  // default look-ahead. Pre-expand for a different window.
@@ -332,6 +349,7 @@ export function Calendar<T>({
332
349
  dragStepMinutes,
333
350
  showDragHandle,
334
351
  onPressDay,
352
+ onPressMonth,
335
353
  onLongPressDay,
336
354
  onPressMore,
337
355
  onPressCell,
@@ -523,6 +541,18 @@ export function Calendar<T>({
523
541
  classNames={classNames}
524
542
  styles={styleOverrides}
525
543
  />
544
+ ) : mode === "year" ? (
545
+ <YearView
546
+ date={date}
547
+ events={displayEvents}
548
+ weekStartsOn={weekStartsOn}
549
+ locale={locale}
550
+ activeDate={activeDate}
551
+ onPressDay={onPressDay}
552
+ onPressMonth={onPressMonth}
553
+ classNames={classNames}
554
+ styles={styleOverrides}
555
+ />
526
556
  ) : mode === "schedule" ? (
527
557
  <Agenda
528
558
  events={displayEvents}
@@ -2,6 +2,8 @@ import type { ComponentType, ReactElement } from "react";
2
2
  import { useCallback, useEffect, useMemo, useRef } from "react";
3
3
  import {
4
4
  type AccessibilityActionEvent,
5
+ type GestureResponderEvent,
6
+ Platform,
5
7
  Pressable,
6
8
  ScrollView,
7
9
  StyleSheet,
@@ -12,6 +14,8 @@ import { Gesture, GestureDetector } from "react-native-gesture-handler";
12
14
  import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
13
15
  import {
14
16
  type CalendarEvent,
17
+ cellRangeFromDrag,
18
+ closedHourBands,
15
19
  eventTimeLabel,
16
20
  formatHour,
17
21
  layoutDayEvents,
@@ -23,7 +27,11 @@ import { useCalendarTheme } from "../theme";
23
27
 
24
28
  // Native long-press duration (ms) before a bar is picked up to move.
25
29
  const MOVE_ACTIVATE_MS = 250;
30
+ // Web: activate the create sweep past a small drag instead of a long-press, so
31
+ // a plain mouse drag creates (mirrors the time grid).
32
+ const DRAG_ACTIVATE_PX = 8;
26
33
  const MINUTES_PER_HOUR = 60;
34
+ const isWeb = Platform.OS === "web";
27
35
 
28
36
  /** A schedulable lane (room, person, machine) events are grouped under. */
29
37
  export interface Resource {
@@ -81,12 +89,35 @@ export interface ResourceTimelineProps<T = unknown> {
81
89
  renderEvent?: ComponentType<ResourceEventArgs<T>>;
82
90
  /** Tap an event. */
83
91
  onPressEvent?: (event: CalendarEvent<T>) => void;
92
+ /**
93
+ * Long-press an event. When `onDragEvent` is also set, a long-press picks the
94
+ * bar up to move instead, so this fires only for non-draggable bars.
95
+ */
96
+ onLongPressEvent?: (event: CalendarEvent<T>) => void;
84
97
  /**
85
98
  * Enables drag-to-move and edge-resize along the time axis: long-press a bar to
86
99
  * move it, or drag its right edge to resize. Called with the proposed new
87
100
  * start/end; return `false` to reject the drop (it snaps back).
88
101
  */
89
102
  onDragEvent?: (event: CalendarEvent<T>, start: Date, end: Date) => void | boolean;
103
+ /** Tap empty lane space; called with the snapped time and the lane's resource. */
104
+ onPressCell?: (at: Date, resource: Resource) => void;
105
+ /**
106
+ * Long-press empty lane space. When `onCreateEvent` is also set, the long-press
107
+ * starts the create drag instead, so this fires only without it.
108
+ */
109
+ onLongPressCell?: (at: Date, resource: Resource) => void;
110
+ /**
111
+ * Long-press empty lane space, then drag along the time axis to sweep out a new
112
+ * event; called with the swept start/end and the lane's resource.
113
+ */
114
+ onCreateEvent?: (start: Date, end: Date, resource: Resource) => void;
115
+ /**
116
+ * Shade the hours outside this window, per lane. Same shape as the Calendar's
117
+ * `businessHours` plus the lane's resource, so per-resource opening hours work
118
+ * (return `null` for a fully closed lane). A date-only function is accepted.
119
+ */
120
+ businessHours?: (date: Date, resource: Resource) => { start: number; end: number } | null;
90
121
  /** Snap dragged events to this many minutes (default 15). */
91
122
  dragStepMinutes?: number;
92
123
  }
@@ -154,6 +185,7 @@ type ResourceBarProps<T> = {
154
185
  snapMinutes: number;
155
186
  Renderer: ComponentType<ResourceEventArgs<T>>;
156
187
  onPress: () => void;
188
+ onLongPress?: () => void;
157
189
  onDragEvent: (event: CalendarEvent<T>, start: Date, end: Date) => void | boolean;
158
190
  theme: ReturnType<typeof useCalendarTheme>;
159
191
  };
@@ -174,6 +206,7 @@ function ResourceBar<T>({
174
206
  snapMinutes,
175
207
  Renderer,
176
208
  onPress,
209
+ onLongPress,
177
210
  onDragEvent,
178
211
  theme,
179
212
  }: ResourceBarProps<T>): ReactElement {
@@ -311,6 +344,9 @@ function ResourceBar<T>({
311
344
  <GestureDetector gesture={moveGesture}>
312
345
  <Pressable
313
346
  onPress={onPress}
347
+ // When the bar is draggable, the move gesture claims the long-press
348
+ // first; this fires only for non-draggable (disabled) bars.
349
+ onLongPress={onLongPress}
314
350
  accessibilityRole="button"
315
351
  accessibilityLabel={pe.event.title}
316
352
  accessibilityActions={barActions}
@@ -342,6 +378,171 @@ function ResourceBar<T>({
342
378
  );
343
379
  }
344
380
 
381
+ type LaneInteractionLayerProps = {
382
+ resource: Resource;
383
+ /** Time flows down instead of across. */
384
+ vertical: boolean;
385
+ /** Pixels per hour along the time axis. */
386
+ hourSize: number;
387
+ startHour: number;
388
+ date: Date;
389
+ snapMinutes: number;
390
+ onPressCell?: (at: Date, resource: Resource) => void;
391
+ onLongPressCell?: (at: Date, resource: Resource) => void;
392
+ onCreateEvent?: (start: Date, end: Date, resource: Resource) => void;
393
+ theme: ReturnType<typeof useCalendarTheme>;
394
+ };
395
+
396
+ // The tap/long-press/create surface behind a lane's bars, mirroring the time
397
+ // grid's cell layer: taps snap to `snapMinutes`, and (when `onCreateEvent` is
398
+ // set) a long-press starts a drag that sweeps out a ghost along the time axis.
399
+ // Hidden from screen readers — it's a pointer convenience, not the accessible
400
+ // path.
401
+ function LaneInteractionLayer({
402
+ resource,
403
+ vertical,
404
+ hourSize,
405
+ startHour,
406
+ date,
407
+ snapMinutes,
408
+ onPressCell,
409
+ onLongPressCell,
410
+ onCreateEvent,
411
+ theme,
412
+ }: LaneInteractionLayerProps): ReactElement {
413
+ const ghostStart = useSharedValue(0);
414
+ const ghostSize = useSharedValue(0);
415
+ const ghostVisible = useSharedValue(0);
416
+
417
+ const timeAt = useCallback(
418
+ (px: number) => cellRangeFromDrag(date, px, px, hourSize, startHour, snapMinutes)?.start,
419
+ [date, hourSize, startHour, snapMinutes],
420
+ );
421
+ const commitCreate = useCallback(
422
+ (fromPx: number, toPx: number) => {
423
+ const range = cellRangeFromDrag(date, fromPx, toPx, hourSize, startHour, snapMinutes);
424
+ if (range) onCreateEvent?.(range.start, range.end, resource);
425
+ },
426
+ [date, hourSize, startHour, snapMinutes, onCreateEvent, resource],
427
+ );
428
+
429
+ const pressAt = useCallback(
430
+ (px: number) => {
431
+ const at = timeAt(px);
432
+ if (at) onPressCell?.(at, resource);
433
+ },
434
+ [timeAt, onPressCell, resource],
435
+ );
436
+
437
+ // Web: a GestureDetector swallows the Pressable's presses on react-native-web,
438
+ // so taps come back through a Tap gesture, and the create sweep activates past
439
+ // a small drag instead of a long-press so a plain mouse drag creates. Both
440
+ // mirror the time grid's cell layer.
441
+ const laneGesture = useMemo(() => {
442
+ const pan = onCreateEvent
443
+ ? Gesture.Pan()
444
+ .onStart((e) => {
445
+ ghostStart.value = vertical ? e.y : e.x;
446
+ ghostSize.value = 0;
447
+ ghostVisible.value = 1;
448
+ })
449
+ .onUpdate((e) => {
450
+ ghostSize.value = (vertical ? e.y : e.x) - ghostStart.value;
451
+ })
452
+ // `success` is false when the gesture is cancelled/failed after it
453
+ // activated (RNGH still calls onEnd); only a real end should create.
454
+ .onEnd((e, success) => {
455
+ ghostVisible.value = 0;
456
+ if (success) runOnJS(commitCreate)(ghostStart.value, vertical ? e.y : e.x);
457
+ })
458
+ .onFinalize(() => {
459
+ ghostVisible.value = 0;
460
+ })
461
+ : null;
462
+ const activatedPan = pan
463
+ ? isWeb
464
+ ? vertical
465
+ ? pan.activeOffsetY([-DRAG_ACTIVATE_PX, DRAG_ACTIVATE_PX])
466
+ : pan.activeOffsetX([-DRAG_ACTIVATE_PX, DRAG_ACTIVATE_PX])
467
+ : pan.activateAfterLongPress(MOVE_ACTIVATE_MS)
468
+ : null;
469
+ const tap =
470
+ isWeb && onPressCell
471
+ ? Gesture.Tap().onEnd((e) => {
472
+ runOnJS(pressAt)(vertical ? e.y : e.x);
473
+ })
474
+ : null;
475
+ if (activatedPan && tap) return Gesture.Exclusive(activatedPan, tap);
476
+ return activatedPan ?? tap;
477
+ }, [
478
+ onCreateEvent,
479
+ onPressCell,
480
+ vertical,
481
+ commitCreate,
482
+ pressAt,
483
+ ghostStart,
484
+ ghostSize,
485
+ ghostVisible,
486
+ ]);
487
+
488
+ const ghostStyle = useAnimatedStyle(() => {
489
+ // Snap the preview to the same grid the commit resolves to, so what you see
490
+ // is what cellRangeFromDrag will create.
491
+ const stepPx = (snapMinutes / MINUTES_PER_HOUR) * hourSize;
492
+ const snap = (v: number) => (stepPx > 0 ? Math.round(v / stepPx) * stepPx : v);
493
+ const a = snap(ghostStart.value);
494
+ const b = snap(ghostStart.value + ghostSize.value);
495
+ const from = Math.min(a, b);
496
+ const size = Math.max(Math.abs(b - a), 2);
497
+ return vertical
498
+ ? { opacity: ghostVisible.value, top: from, height: size }
499
+ : { opacity: ghostVisible.value, left: from, width: size };
500
+ }, [vertical, hourSize, snapMinutes]);
501
+
502
+ const pxOf = (e: GestureResponderEvent) =>
503
+ vertical ? e.nativeEvent.locationY : e.nativeEvent.locationX;
504
+ const handlePress = onPressCell ? (e: GestureResponderEvent) => pressAt(pxOf(e)) : undefined;
505
+ // When create is on, a long-press starts the create drag, so don't also fire
506
+ // the consumer's long-press handler (mirrors the time grid).
507
+ const handleLongPress =
508
+ onLongPressCell && !onCreateEvent
509
+ ? (e: GestureResponderEvent) => {
510
+ const at = timeAt(pxOf(e));
511
+ if (at) onLongPressCell(at, resource);
512
+ }
513
+ : undefined;
514
+
515
+ const layer = (
516
+ <Pressable
517
+ testID="resource-cell-layer"
518
+ style={StyleSheet.absoluteFill}
519
+ onPress={handlePress}
520
+ onLongPress={handleLongPress}
521
+ accessibilityElementsHidden
522
+ importantForAccessibility="no"
523
+ />
524
+ );
525
+ return (
526
+ <>
527
+ {laneGesture ? <GestureDetector gesture={laneGesture}>{layer}</GestureDetector> : layer}
528
+ {onCreateEvent ? (
529
+ <Animated.View
530
+ testID="resource-create-ghost"
531
+ pointerEvents="none"
532
+ style={[
533
+ vertical ? styles.vcreateGhost : styles.hcreateGhost,
534
+ {
535
+ backgroundColor: theme.colors.eventBackground,
536
+ borderColor: theme.colors.todayBackground,
537
+ },
538
+ ghostStyle,
539
+ ]}
540
+ />
541
+ ) : null}
542
+ </>
543
+ );
544
+ }
545
+
345
546
  /**
346
547
  * A horizontal resource timeline: rows are resources (rooms, people, machines)
347
548
  * and the x-axis is one day's hours. Events sit in their resource's row, and
@@ -375,11 +576,22 @@ export function ResourceTimeline<T = unknown>({
375
576
  ampm = false,
376
577
  renderEvent,
377
578
  onPressEvent,
579
+ onLongPressEvent,
378
580
  onDragEvent,
581
+ onPressCell,
582
+ onLongPressCell,
583
+ onCreateEvent,
584
+ businessHours,
379
585
  dragStepMinutes = 15,
380
586
  }: ResourceTimelineProps<T>): ReactElement {
381
587
  const theme = useCalendarTheme();
382
588
  const Renderer = renderEvent ?? DefaultBar;
589
+ const cellEnabled = !!onPressCell || !!onLongPressCell || !!onCreateEvent;
590
+ // The closed-hours bands to shade in a lane, resolved per resource.
591
+ const bandsFor = (resource: Resource) =>
592
+ businessHours
593
+ ? closedHourBands(date, (d) => businessHours(d, resource), startHour, endHour)
594
+ : [];
383
595
 
384
596
  const hours = useMemo(
385
597
  () => Array.from({ length: Math.max(0, endHour - startHour) }, (_, i) => startHour + i),
@@ -448,6 +660,22 @@ export function ResourceTimeline<T = unknown>({
448
660
  theme.containers.resourceRow,
449
661
  ]}
450
662
  >
663
+ {/* Closed-hours shade, behind the grid lines and bars. */}
664
+ {bandsFor(resource).map((b) => (
665
+ <View
666
+ key={`shade-${b.start}`}
667
+ pointerEvents="none"
668
+ testID="resource-hours-shade"
669
+ style={[
670
+ styles.vshadeBand,
671
+ {
672
+ top: (b.start - startHour) * hourHeight,
673
+ height: (b.end - b.start) * hourHeight,
674
+ backgroundColor: theme.colors.outsideHoursBackground,
675
+ },
676
+ ]}
677
+ />
678
+ ))}
451
679
  {hours.slice(1).map((h) => (
452
680
  <View
453
681
  key={h}
@@ -461,6 +689,20 @@ export function ResourceTimeline<T = unknown>({
461
689
  ]}
462
690
  />
463
691
  ))}
692
+ {cellEnabled ? (
693
+ <LaneInteractionLayer
694
+ resource={resource}
695
+ vertical
696
+ hourSize={hourHeight}
697
+ startHour={startHour}
698
+ date={date}
699
+ snapMinutes={dragStepMinutes}
700
+ onPressCell={onPressCell}
701
+ onLongPressCell={onLongPressCell}
702
+ onCreateEvent={onCreateEvent}
703
+ theme={theme}
704
+ />
705
+ ) : null}
464
706
  {positioned.map((pe, idx) => {
465
707
  const top =
466
708
  clamp(pe.startHours - startHour, 0, endHour - startHour) * hourHeight;
@@ -488,6 +730,9 @@ export function ResourceTimeline<T = unknown>({
488
730
  snapMinutes={dragStepMinutes}
489
731
  Renderer={Renderer}
490
732
  onPress={onPress}
733
+ onLongPress={
734
+ onLongPressEvent ? () => onLongPressEvent(pe.event) : undefined
735
+ }
491
736
  onDragEvent={onDragEvent}
492
737
  theme={theme}
493
738
  />
@@ -497,6 +742,9 @@ export function ResourceTimeline<T = unknown>({
497
742
  <Pressable
498
743
  key={idx}
499
744
  onPress={onPress}
745
+ onLongPress={
746
+ onLongPressEvent ? () => onLongPressEvent(pe.event) : undefined
747
+ }
500
748
  accessibilityRole="button"
501
749
  accessibilityLabel={pe.event.title}
502
750
  style={{ position: "absolute", left, width, top, height, padding: 1 }}
@@ -564,6 +812,22 @@ export function ResourceTimeline<T = unknown>({
564
812
  </Text>
565
813
  </View>
566
814
  <View style={{ width: trackWidth }}>
815
+ {/* Closed-hours shade, behind the grid lines and bars. */}
816
+ {bandsFor(resource).map((b) => (
817
+ <View
818
+ key={`shade-${b.start}`}
819
+ pointerEvents="none"
820
+ testID="resource-hours-shade"
821
+ style={[
822
+ styles.hshadeBand,
823
+ {
824
+ left: (b.start - startHour) * hourWidth,
825
+ width: (b.end - b.start) * hourWidth,
826
+ backgroundColor: theme.colors.outsideHoursBackground,
827
+ },
828
+ ]}
829
+ />
830
+ ))}
567
831
  {/* Hour grid lines */}
568
832
  {hours.slice(1).map((h) => (
569
833
  <View
@@ -575,6 +839,20 @@ export function ResourceTimeline<T = unknown>({
575
839
  ]}
576
840
  />
577
841
  ))}
842
+ {cellEnabled ? (
843
+ <LaneInteractionLayer
844
+ resource={resource}
845
+ vertical={false}
846
+ hourSize={hourWidth}
847
+ startHour={startHour}
848
+ date={date}
849
+ snapMinutes={dragStepMinutes}
850
+ onPressCell={onPressCell}
851
+ onLongPressCell={onLongPressCell}
852
+ onCreateEvent={onCreateEvent}
853
+ theme={theme}
854
+ />
855
+ ) : null}
578
856
  {positioned.map((pe, idx) => {
579
857
  const left = clamp(pe.startHours - startHour, 0, endHour - startHour) * hourWidth;
580
858
  const right =
@@ -599,6 +877,9 @@ export function ResourceTimeline<T = unknown>({
599
877
  snapMinutes={dragStepMinutes}
600
878
  Renderer={Renderer}
601
879
  onPress={onPress}
880
+ onLongPress={
881
+ onLongPressEvent ? () => onLongPressEvent(pe.event) : undefined
882
+ }
602
883
  onDragEvent={onDragEvent}
603
884
  theme={theme}
604
885
  />
@@ -608,6 +889,7 @@ export function ResourceTimeline<T = unknown>({
608
889
  <Pressable
609
890
  key={idx}
610
891
  onPress={onPress}
892
+ onLongPress={onLongPressEvent ? () => onLongPressEvent(pe.event) : undefined}
611
893
  accessibilityRole="button"
612
894
  accessibilityLabel={pe.event.title}
613
895
  style={{
@@ -679,4 +961,27 @@ const styles = StyleSheet.create({
679
961
  vcolumn: { flex: 1, borderLeftWidth: StyleSheet.hairlineWidth },
680
962
  vhourLabel: { position: "absolute", right: 6, fontSize: 10 },
681
963
  vgridLine: { position: "absolute", left: 0, right: 0, height: StyleSheet.hairlineWidth },
964
+ // The drag-to-create preview, sized along the time axis by the gesture. It
965
+ // sits above the bars (like the dom renderer and the time grid) so the sweep
966
+ // stays visible when it crosses an existing event.
967
+ vcreateGhost: {
968
+ position: "absolute",
969
+ left: 2,
970
+ right: 2,
971
+ borderWidth: 1,
972
+ borderRadius: 6,
973
+ opacity: 0,
974
+ zIndex: 2,
975
+ },
976
+ hcreateGhost: {
977
+ position: "absolute",
978
+ top: 2,
979
+ bottom: 2,
980
+ borderWidth: 1,
981
+ borderRadius: 6,
982
+ opacity: 0,
983
+ zIndex: 2,
984
+ },
985
+ hshadeBand: { position: "absolute", top: 0, bottom: 0 },
986
+ vshadeBand: { position: "absolute", left: 0, right: 0 },
682
987
  });