@dxos/react-ui-calendar 0.9.0 → 0.9.1-main.c7dcc2e112

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.
Files changed (29) hide show
  1. package/dist/lib/browser/index.mjs +582 -107
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +582 -107
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Calendar/Calendar.d.ts +25 -35
  8. package/dist/types/src/components/Calendar/Calendar.d.ts.map +1 -1
  9. package/dist/types/src/components/Calendar/Calendar.stories.d.ts +2 -0
  10. package/dist/types/src/components/Calendar/Calendar.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Calendar/Week.d.ts +30 -0
  12. package/dist/types/src/components/Calendar/Week.d.ts.map +1 -0
  13. package/dist/types/src/components/Calendar/Weekdays.d.ts +18 -0
  14. package/dist/types/src/components/Calendar/Weekdays.d.ts.map +1 -0
  15. package/dist/types/src/components/Calendar/context.d.ts +40 -0
  16. package/dist/types/src/components/Calendar/context.d.ts.map +1 -0
  17. package/dist/types/src/components/Calendar/util.d.ts +36 -0
  18. package/dist/types/src/components/Calendar/util.d.ts.map +1 -1
  19. package/dist/types/src/components/Calendar/util.test.d.ts +2 -0
  20. package/dist/types/src/components/Calendar/util.test.d.ts.map +1 -0
  21. package/dist/types/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +12 -12
  23. package/src/components/Calendar/Calendar.stories.tsx +43 -3
  24. package/src/components/Calendar/Calendar.tsx +117 -96
  25. package/src/components/Calendar/Week.tsx +488 -0
  26. package/src/components/Calendar/Weekdays.tsx +57 -0
  27. package/src/components/Calendar/context.ts +60 -0
  28. package/src/components/Calendar/util.test.ts +90 -0
  29. package/src/components/Calendar/util.ts +92 -1
@@ -2,7 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Day, differenceInCalendarDays } from 'date-fns';
5
+ import { type Day, differenceInCalendarDays, startOfDay } from 'date-fns';
6
+
7
+ /**
8
+ * Fixed origin (row 0, column 0 anchor) for the infinite month grid. All row-index math is relative
9
+ * to this epoch, so any view that reports a row index to the shared context must use the same value.
10
+ */
11
+ export const gridEpoch = new Date('1970-01-01');
6
12
 
7
13
  export const getDate = (start: Date, weekNumber: number, dayOfWeek: number, weekStartsOn: Day): Date => {
8
14
  const result = new Date(start);
@@ -38,3 +44,88 @@ export const isSameDay = (date1: Date, date2: Date | undefined): boolean => {
38
44
  date1.getDate() === date2.getDate()
39
45
  );
40
46
  };
47
+
48
+ //
49
+ // Time-grid (week view) helpers.
50
+ //
51
+
52
+ export const MINUTES_PER_DAY = 24 * 60;
53
+
54
+ /** Default snap granularity for create/move/resize gestures, in minutes. */
55
+ export const SNAP_MINUTES = 15;
56
+
57
+ /** Minutes elapsed since the start of `date`'s day (0 .. 1439). */
58
+ export const minutesOfDay = (date: Date): number => (date.getTime() - startOfDay(date).getTime()) / 60_000;
59
+
60
+ /** Return a new Date on the same calendar day as `date`, at `minutes` past midnight. */
61
+ export const setMinutesOfDay = (date: Date, minutes: number): Date =>
62
+ new Date(startOfDay(date).getTime() + minutes * 60_000);
63
+
64
+ /** Convert a vertical offset (px from the top of the day column) into minutes past midnight. */
65
+ export const yToMinutes = (y: number, hourHeight: number): number => (y / hourHeight) * 60;
66
+
67
+ /** Convert minutes past midnight into a vertical offset (px from the top of the day column). */
68
+ export const minutesToY = (minutes: number, hourHeight: number): number => (minutes / 60) * hourHeight;
69
+
70
+ /** Round `minutes` to the nearest `step`, clamped to a single day. */
71
+ export const snapMinutes = (minutes: number, step: number = SNAP_MINUTES): number => {
72
+ const snapped = Math.round(minutes / step) * step;
73
+ return Math.max(0, Math.min(MINUTES_PER_DAY, snapped));
74
+ };
75
+
76
+ /**
77
+ * Side-by-side layout slot for an overlapping-event cluster: the event occupies
78
+ * `1 / columnCount` of the day column width, offset by `columnIndex` columns.
79
+ */
80
+ export type EventLayout = { columnIndex: number; columnCount: number };
81
+
82
+ /**
83
+ * Compute side-by-side columns for events within a single day. Events that overlap in
84
+ * time (transitively) form a cluster and split the column width evenly; non-overlapping
85
+ * events each get the full width. Input order is irrelevant; results are keyed by index
86
+ * into `events`.
87
+ */
88
+ export const layoutDayEvents = <T extends { start: Date; end: Date }>(events: T[]): Map<number, EventLayout> => {
89
+ const layout = new Map<number, EventLayout>();
90
+ // Preserve original indices so callers can map results back to their event list.
91
+ const ordered = events
92
+ .map((event, index) => ({ index, start: event.start.getTime(), end: event.end.getTime() }))
93
+ .sort((a, b) => a.start - b.start);
94
+
95
+ let cluster: { index: number; start: number; end: number }[] = [];
96
+ let clusterMax = -Infinity;
97
+
98
+ const flush = () => {
99
+ if (cluster.length === 0) {
100
+ return;
101
+ }
102
+ // Greedy column packing: place each event in the first column whose previous event has ended.
103
+ const columnEnds: number[] = [];
104
+ const assigned = cluster.map(({ index, start, end }) => {
105
+ let column = columnEnds.findIndex((columnEnd) => columnEnd <= start);
106
+ if (column === -1) {
107
+ column = columnEnds.length;
108
+ }
109
+ columnEnds[column] = end;
110
+ return { index, column };
111
+ });
112
+ const columnCount = columnEnds.length;
113
+ for (const { index, column } of assigned) {
114
+ layout.set(index, { columnIndex: column, columnCount });
115
+ }
116
+ cluster = [];
117
+ clusterMax = -Infinity;
118
+ };
119
+
120
+ for (const entry of ordered) {
121
+ if (cluster.length > 0 && entry.start >= clusterMax) {
122
+ // No overlap with the running cluster — close it out before starting fresh.
123
+ flush();
124
+ }
125
+ cluster.push(entry);
126
+ clusterMax = Math.max(clusterMax, entry.end);
127
+ }
128
+ flush();
129
+
130
+ return layout;
131
+ };