tempest-react-sdk 0.26.1 → 0.27.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 +1 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +201 -0
- package/bin/tempest.mjs +103 -29
- package/dist/components/ImageCropper/ImageCropper.cjs +2 -0
- package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -0
- package/dist/components/ImageCropper/ImageCropper.js +240 -0
- package/dist/components/ImageCropper/ImageCropper.js.map +1 -0
- package/dist/components/ImageCropper/ImageCropper.module.cjs +2 -0
- package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -0
- package/dist/components/ImageCropper/ImageCropper.module.js +15 -0
- package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -0
- package/dist/components/ImageCropper/crop-geometry.cjs +2 -0
- package/dist/components/ImageCropper/crop-geometry.cjs.map +1 -0
- package/dist/components/ImageCropper/crop-geometry.js +52 -0
- package/dist/components/ImageCropper/crop-geometry.js.map +1 -0
- package/dist/components/Scheduler/Scheduler.cjs +2 -0
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -0
- package/dist/components/Scheduler/Scheduler.js +134 -0
- package/dist/components/Scheduler/Scheduler.js.map +1 -0
- package/dist/components/Scheduler/Scheduler.module.cjs +2 -0
- package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -0
- package/dist/components/Scheduler/Scheduler.module.js +26 -0
- package/dist/components/Scheduler/Scheduler.module.js.map +1 -0
- package/dist/components/Scheduler/scheduler-layout.cjs +2 -0
- package/dist/components/Scheduler/scheduler-layout.cjs.map +1 -0
- package/dist/components/Scheduler/scheduler-layout.js +97 -0
- package/dist/components/Scheduler/scheduler-layout.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +162 -0
- package/dist/tempest-react-sdk.js +151 -149
- package/package.json +1 -1
- package/template/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduler.js","names":[],"sources":["../../../src/components/Scheduler/Scheduler.tsx"],"sourcesContent":["import { type HTMLAttributes, type ReactNode, useEffect, useMemo, useState } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport {\n dayRange,\n type DayWindow,\n fractionOfWindow,\n hourMarks,\n isSameDay,\n layoutAllDay,\n layoutEvents,\n type SchedulerEvent,\n} from \"./scheduler-layout\";\nimport styles from \"./Scheduler.module.css\";\n\nexport interface SchedulerProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Events to place. Instants are read in the browser's local time. */\n events: readonly SchedulerEvent[];\n /** Any day within the range to show. Default: today. */\n anchor?: Date;\n /** How many consecutive days to render. `1` is a day view, `7` a week. Default `7`. */\n days?: number;\n /** First visible hour, `0`–`23`. Default `8`. */\n startHour?: number;\n /** Last visible hour, `1`–`24`. Default `20`. */\n endHour?: number;\n /** Minutes a click on empty space snaps to. Default `30`. */\n snapMinutes?: number;\n /** Called when an event is activated by click, `Enter` or `Space`. */\n onEventClick?: (event: SchedulerEvent) => void;\n /** Called with the snapped instant when empty space is clicked. */\n onSlotClick?: (start: Date) => void;\n /** Render an event's contents. Defaults to its title and start time. */\n renderEvent?: (event: SchedulerEvent) => ReactNode;\n /** Locale for the day and hour labels. Default `\"pt-BR\"`. */\n locale?: string;\n /** Draw the current-time line. Default `true`. */\n showCurrentTime?: boolean;\n /** Fixed \"now\" for the indicator. Default: the real clock, ticking each minute. */\n now?: Date;\n}\n\n/** Minutes between ticks of the current-time indicator. */\nconst TICK_MS = 60_000;\n\n/**\n * An agenda: events placed on a time grid across consecutive days.\n *\n * `Calendar` is a date *picker* — it answers \"which day?\". This answers \"what is on\n * those days, and when\", which needs a different structure entirely: a vertical time\n * axis, events sized by duration, and overlapping events sitting side by side.\n *\n * That last part is the one worth naming. Overlapping events are grouped into\n * clusters of mutual overlap and every event in a cluster shares one column count,\n * so widths line up; a column is reused the moment it frees, so `9–10`, `9–10`,\n * `10–11` takes two columns and not three. The layout is pure and lives in\n * `scheduler-layout.ts`.\n *\n * Times are local. An event crossing midnight is split into both day columns, and\n * the day range is built by incrementing the calendar day, so a DST boundary does\n * not duplicate or skip a date.\n *\n * @example\n * <Scheduler\n * events={bookings}\n * days={7}\n * startHour={7}\n * endHour={21}\n * onEventClick={(e) => open(e.id)}\n * onSlotClick={(start) => createAt(start)}\n * />\n */\nexport function Scheduler({\n events,\n anchor,\n days: dayCount = 7,\n startHour = 8,\n endHour = 20,\n snapMinutes = 30,\n onEventClick,\n onSlotClick,\n renderEvent,\n locale = \"pt-BR\",\n showCurrentTime = true,\n now,\n className,\n ...rest\n}: SchedulerProps) {\n const [clock, setClock] = useState<Date>(() => now ?? new Date());\n\n /**\n * Keep the current-time line moving.\n *\n * Skipped entirely when `now` is supplied: that is the hook tests and demos use\n * to be deterministic, and a timer would fight it.\n */\n useEffect(() => {\n if (now || !showCurrentTime) return;\n const id = setInterval(() => setClock(new Date()), TICK_MS);\n return () => clearInterval(id);\n }, [now, showCurrentTime]);\n\n const reference = now ?? clock;\n\n const window = useMemo<DayWindow>(\n () => ({ startMinute: startHour * 60, endMinute: endHour * 60 }),\n [startHour, endHour],\n );\n\n /**\n * The calendar day the range starts from, as a stable string.\n *\n * `reference` ticks every minute when the current-time line is live. Depending on\n * the Date itself would re-slice the day range — and therefore relayout every\n * event — once a minute; depending on the day only recomputes at midnight.\n */\n const anchorDay = (anchor ?? reference).toDateString();\n const dayList = useMemo(() => dayRange(new Date(anchorDay), dayCount), [anchorDay, dayCount]);\n\n const placed = useMemo(\n () => layoutEvents({ events, days: dayList, window }),\n [events, dayList, window],\n );\n const allDay = useMemo(() => layoutAllDay({ events, days: dayList }), [events, dayList]);\n const marks = useMemo(() => hourMarks(window), [window]);\n\n const dayLabel = new Intl.DateTimeFormat(locale, { weekday: \"short\", day: \"numeric\" });\n const timeLabel = new Intl.DateTimeFormat(locale, { hour: \"2-digit\", minute: \"2-digit\" });\n\n /** Turn a click's vertical position within a day column into a snapped instant. */\n const slotFromClick = (day: Date, event: React.MouseEvent<HTMLDivElement>): Date => {\n const rect = event.currentTarget.getBoundingClientRect();\n const fraction = rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0;\n const raw = window.startMinute + fraction * (window.endMinute - window.startMinute);\n const snapped = Math.round(raw / snapMinutes) * snapMinutes;\n const clamped = Math.max(window.startMinute, Math.min(window.endMinute, snapped));\n const result = new Date(day);\n result.setHours(0, clamped, 0, 0);\n return result;\n };\n\n const currentFraction = showCurrentTime ? fractionOfWindow(reference, window) : null;\n const todayIndex = dayList.findIndex((day) => isSameDay(day, reference));\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n <div\n className={styles.head}\n style={{ [\"--tempest-scheduler-days\" as string]: dayCount }}\n >\n <span className={styles.gutterHead} />\n {dayList.map((day, index) => (\n <span\n key={day.toISOString()}\n className={cn(styles.dayHead, index === todayIndex && styles.today)}\n >\n {dayLabel.format(day)}\n </span>\n ))}\n </div>\n\n {allDay.length > 0 && (\n <div\n className={styles.allDayLane}\n style={{ [\"--tempest-scheduler-days\" as string]: dayCount }}\n >\n <span className={styles.gutterHead}>Dia inteiro</span>\n <div className={styles.allDayTrack}>\n {allDay.map(({ event, dayIndex, span }) => (\n <button\n key={event.id}\n type=\"button\"\n className={styles.allDayEvent}\n style={{ gridColumn: `${dayIndex + 1} / span ${span}` }}\n onClick={() => onEventClick?.(event)}\n disabled={!onEventClick}\n >\n {event.title}\n </button>\n ))}\n </div>\n </div>\n )}\n\n {/*\n * Focusable because it scrolls vertically: a scroll region that cannot be\n * focused is unreachable by keyboard, which is what `axe`'s\n * `scrollable-region-focusable` rule is about. It needs a name too, or the\n * new tab stop would announce nothing — but `group`, not `region`: a named\n * `region` is a landmark, and two schedulers on one page would then be two\n * identically-named landmarks (`landmark-unique`).\n */}\n <div\n className={styles.body}\n style={{ [\"--tempest-scheduler-days\" as string]: dayCount }}\n tabIndex={0}\n role=\"group\"\n aria-label=\"Grade de horários\"\n >\n <div className={styles.gutter}>\n {marks.map((minute) => (\n <span\n key={minute}\n className={styles.hourLabel}\n style={{\n top: `${((minute - window.startMinute) / (window.endMinute - window.startMinute)) * 100}%`,\n }}\n >\n {timeLabel.format(\n new Date(2026, 0, 1, Math.floor(minute / 60), minute % 60),\n )}\n </span>\n ))}\n </div>\n\n {/*\n * Not `role=\"grid\"`: that requires `row` children, and the events are\n * siblings of the day columns inside one CSS grid — a `row` wrapper\n * would stop the columns being grid items and collapse the layout.\n * A labelled group per day is the honest structure anyway: a screen\n * reader tabs the event buttons and the group name supplies the day.\n */}\n <div className={styles.grid} aria-label=\"Agenda\">\n {dayList.map((day, index) => (\n <div\n key={day.toISOString()}\n className={cn(\n styles.dayColumn,\n index === todayIndex && styles.todayColumn,\n )}\n role=\"group\"\n aria-label={dayLabel.format(day)}\n onClick={\n onSlotClick\n ? (event) => {\n // Only empty space creates: a click that\n // landed on an event is that event's.\n if (event.target !== event.currentTarget) return;\n onSlotClick(slotFromClick(day, event));\n }\n : undefined\n }\n >\n {marks.map((minute) => (\n <span\n key={minute}\n className={styles.hourLine}\n style={{\n top: `${((minute - window.startMinute) / (window.endMinute - window.startMinute)) * 100}%`,\n }}\n />\n ))}\n </div>\n ))}\n\n {placed.map((item) => (\n <button\n key={`${item.event.id}-${item.dayIndex}`}\n type=\"button\"\n className={styles.event}\n style={{\n gridColumn: item.dayIndex + 1,\n top: `${item.top * 100}%`,\n height: `${item.height * 100}%`,\n left: `${(item.column / item.columns) * 100}%`,\n width: `${(1 / item.columns) * 100}%`,\n }}\n onClick={() => onEventClick?.(item.event)}\n disabled={!onEventClick}\n title={`${item.event.title} — ${timeLabel.format(item.event.start)}`}\n >\n {renderEvent ? (\n renderEvent(item.event)\n ) : (\n <>\n <span className={styles.eventTime}>\n {timeLabel.format(item.event.start)}\n </span>\n <span className={styles.eventTitle}>{item.event.title}</span>\n </>\n )}\n </button>\n ))}\n\n {currentFraction !== null && todayIndex >= 0 && (\n <span\n className={styles.nowLine}\n style={{ top: `${currentFraction * 100}%` }}\n aria-hidden\n data-testid=\"scheduler-now\"\n />\n )}\n </div>\n </div>\n </div>\n );\n}\n\nexport type { SchedulerEvent };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=`tempest_wrapper_kV3jq`,t=`tempest_head_rAIW6`,n=`tempest_allDayLane_sFLf5`,r=`tempest_dayHead_qxQ-f`,i=`tempest_today_rs9C1`,a=`tempest_gutterHead_eSRW-`,o=`tempest_allDayTrack_aOePR`,s=`tempest_allDayEvent_oESg4`,c=`tempest_body_GkK47`,l=`tempest_gutter_BHgLa`,u=`tempest_hourLabel_-Jf7a`,d=`tempest_grid_Q4nOo`,f=`tempest_dayColumn_r3HZQ`,p=`tempest_todayColumn_NdkQu`,m=`tempest_hourLine_Wch54`,h=`tempest_event_R6dCw`,g=`tempest_eventTime_HsFmy`,_=`tempest_eventTitle_gZNcv`,v=`tempest_nowLine_AHZfL`,y={wrapper:e,head:t,allDayLane:n,dayHead:r,today:i,gutterHead:a,allDayTrack:o,allDayEvent:s,body:c,gutter:l,hourLabel:u,grid:d,dayColumn:f,todayColumn:p,hourLine:m,event:h,eventTime:g,eventTitle:_,nowLine:v};exports.allDayEvent=s,exports.allDayLane=n,exports.allDayTrack=o,exports.body=c,exports.dayColumn=f,exports.dayHead=r,exports.default=y,exports.event=h,exports.eventTime=g,exports.eventTitle=_,exports.grid=d,exports.gutter=l,exports.gutterHead=a,exports.head=t,exports.hourLabel=u,exports.hourLine=m,exports.nowLine=v,exports.today=i,exports.todayColumn=p,exports.wrapper=e;
|
|
2
|
+
//# sourceMappingURL=Scheduler.module.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduler.module.cjs","names":[],"sources":["../../../src/components/Scheduler/Scheduler.module.css"],"sourcesContent":[".wrapper {\n /*\n * Column widths are declared once here and reused by the header, the all-day\n * lane and the grid — three separate grids that have to line up exactly, and\n * would drift the moment any of them computed its own tracks.\n */\n --tempest-scheduler-gutter: 3.5rem;\n --tempest-scheduler-height: 32rem;\n\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n font-family: var(--tempest-font-sans);\n overflow: hidden;\n}\n\n.head,\n.allDayLane {\n display: grid;\n grid-template-columns: var(--tempest-scheduler-gutter) repeat(\n var(--tempest-scheduler-days),\n minmax(0, 1fr)\n );\n border-bottom: 1px solid var(--tempest-border);\n background-color: var(--tempest-surface);\n}\n\n.dayHead {\n padding: var(--tempest-space-2);\n text-align: center;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n letter-spacing: var(--tempest-tracking-wide);\n text-transform: capitalize;\n border-left: 1px solid var(--tempest-border);\n}\n\n.dayHead.today {\n color: var(--tempest-primary);\n}\n\n.gutterHead {\n padding: var(--tempest-space-2);\n color: var(--tempest-text-muted);\n font-size: 0.625rem;\n text-align: right;\n}\n\n.allDayTrack {\n display: grid;\n grid-template-columns: repeat(var(--tempest-scheduler-days), minmax(0, 1fr));\n grid-column: 2 / -1;\n gap: 2px;\n padding: 2px;\n}\n\n.allDayEvent {\n overflow: hidden;\n padding: 0 var(--tempest-space-2);\n border: none;\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-contrast, #fff);\n font: inherit;\n font-size: var(--tempest-text-xs);\n line-height: 1.5rem;\n text-align: left;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.allDayEvent:disabled {\n cursor: default;\n}\n\n.body {\n display: grid;\n grid-template-columns: var(--tempest-scheduler-gutter) 1fr;\n height: var(--tempest-scheduler-height);\n overflow-y: auto;\n}\n\n.gutter {\n position: relative;\n border-right: 1px solid var(--tempest-border);\n}\n\n.hourLabel {\n position: absolute;\n right: var(--tempest-space-1);\n /*\n * Nudged up by half a line so the label reads as sitting *on* its hour line\n * rather than hanging below it.\n */\n transform: translateY(-50%);\n color: var(--tempest-text-muted);\n font-size: 0.625rem;\n font-variant-numeric: tabular-nums;\n}\n\n.grid {\n position: relative;\n display: grid;\n grid-template-columns: repeat(var(--tempest-scheduler-days), minmax(0, 1fr));\n}\n\n.dayColumn {\n position: relative;\n grid-row: 1;\n border-left: 1px solid var(--tempest-border);\n}\n\n.dayColumn:first-child {\n border-left: none;\n}\n\n.todayColumn {\n background-color: var(--tempest-surface);\n}\n\n.hourLine {\n position: absolute;\n left: 0;\n right: 0;\n border-top: 1px solid var(--tempest-border);\n pointer-events: none;\n}\n\n/*\n * Events live in the same grid as the day columns, one row deep, positioned inside\n * their column by percentage. Being siblings of the columns rather than children is\n * what lets an event overlay the hour lines without clipping.\n */\n.event {\n position: absolute;\n grid-row: 1;\n display: flex;\n flex-direction: column;\n gap: 1px;\n overflow: hidden;\n padding: 2px var(--tempest-space-1);\n border: 1px solid var(--tempest-bg);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-contrast, #fff);\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.event:disabled {\n cursor: default;\n}\n\n.event:focus-visible {\n outline: 2px solid var(--tempest-text);\n outline-offset: -2px;\n}\n\n/*\n * No `opacity` here, deliberately. Dimming the time to signal it as secondary drops\n * it below the AA contrast threshold against the event fill — the browser `axe` sweep\n * caught exactly that (jsdom cannot, since it does not paint). The hierarchy comes\n * from size and weight instead, which cost nothing in contrast.\n */\n.eventTime {\n font-size: 0.625rem;\n font-variant-numeric: tabular-nums;\n}\n\n.eventTitle {\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.nowLine {\n position: absolute;\n left: 0;\n right: 0;\n grid-row: 1;\n grid-column: 1 / -1;\n height: 0;\n border-top: 2px solid var(--tempest-danger, #dc2626);\n pointer-events: none;\n z-index: 1;\n}\n\n@media (max-width: 640px) {\n .wrapper {\n --tempest-scheduler-gutter: 2.75rem;\n }\n\n .dayHead {\n font-size: 0.625rem;\n padding: var(--tempest-space-1);\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/components/Scheduler/Scheduler.module.css
|
|
2
|
+
var e = "tempest_wrapper_kV3jq", t = "tempest_head_rAIW6", n = "tempest_allDayLane_sFLf5", r = "tempest_dayHead_qxQ-f", i = "tempest_today_rs9C1", a = "tempest_gutterHead_eSRW-", o = "tempest_allDayTrack_aOePR", s = "tempest_allDayEvent_oESg4", c = "tempest_body_GkK47", l = "tempest_gutter_BHgLa", u = "tempest_hourLabel_-Jf7a", d = "tempest_grid_Q4nOo", f = "tempest_dayColumn_r3HZQ", p = "tempest_todayColumn_NdkQu", m = "tempest_hourLine_Wch54", h = "tempest_event_R6dCw", g = "tempest_eventTime_HsFmy", _ = "tempest_eventTitle_gZNcv", v = "tempest_nowLine_AHZfL", y = {
|
|
3
|
+
wrapper: e,
|
|
4
|
+
head: t,
|
|
5
|
+
allDayLane: n,
|
|
6
|
+
dayHead: r,
|
|
7
|
+
today: i,
|
|
8
|
+
gutterHead: a,
|
|
9
|
+
allDayTrack: o,
|
|
10
|
+
allDayEvent: s,
|
|
11
|
+
body: c,
|
|
12
|
+
gutter: l,
|
|
13
|
+
hourLabel: u,
|
|
14
|
+
grid: d,
|
|
15
|
+
dayColumn: f,
|
|
16
|
+
todayColumn: p,
|
|
17
|
+
hourLine: m,
|
|
18
|
+
event: h,
|
|
19
|
+
eventTime: g,
|
|
20
|
+
eventTitle: _,
|
|
21
|
+
nowLine: v
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { s as allDayEvent, n as allDayLane, o as allDayTrack, c as body, f as dayColumn, r as dayHead, y as default, h as event, g as eventTime, _ as eventTitle, d as grid, l as gutter, a as gutterHead, t as head, u as hourLabel, m as hourLine, v as nowLine, i as today, p as todayColumn, e as wrapper };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=Scheduler.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduler.module.js","names":[],"sources":["../../../src/components/Scheduler/Scheduler.module.css"],"sourcesContent":[".wrapper {\n /*\n * Column widths are declared once here and reused by the header, the all-day\n * lane and the grid — three separate grids that have to line up exactly, and\n * would drift the moment any of them computed its own tracks.\n */\n --tempest-scheduler-gutter: 3.5rem;\n --tempest-scheduler-height: 32rem;\n\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n font-family: var(--tempest-font-sans);\n overflow: hidden;\n}\n\n.head,\n.allDayLane {\n display: grid;\n grid-template-columns: var(--tempest-scheduler-gutter) repeat(\n var(--tempest-scheduler-days),\n minmax(0, 1fr)\n );\n border-bottom: 1px solid var(--tempest-border);\n background-color: var(--tempest-surface);\n}\n\n.dayHead {\n padding: var(--tempest-space-2);\n text-align: center;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-semibold);\n letter-spacing: var(--tempest-tracking-wide);\n text-transform: capitalize;\n border-left: 1px solid var(--tempest-border);\n}\n\n.dayHead.today {\n color: var(--tempest-primary);\n}\n\n.gutterHead {\n padding: var(--tempest-space-2);\n color: var(--tempest-text-muted);\n font-size: 0.625rem;\n text-align: right;\n}\n\n.allDayTrack {\n display: grid;\n grid-template-columns: repeat(var(--tempest-scheduler-days), minmax(0, 1fr));\n grid-column: 2 / -1;\n gap: 2px;\n padding: 2px;\n}\n\n.allDayEvent {\n overflow: hidden;\n padding: 0 var(--tempest-space-2);\n border: none;\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-contrast, #fff);\n font: inherit;\n font-size: var(--tempest-text-xs);\n line-height: 1.5rem;\n text-align: left;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.allDayEvent:disabled {\n cursor: default;\n}\n\n.body {\n display: grid;\n grid-template-columns: var(--tempest-scheduler-gutter) 1fr;\n height: var(--tempest-scheduler-height);\n overflow-y: auto;\n}\n\n.gutter {\n position: relative;\n border-right: 1px solid var(--tempest-border);\n}\n\n.hourLabel {\n position: absolute;\n right: var(--tempest-space-1);\n /*\n * Nudged up by half a line so the label reads as sitting *on* its hour line\n * rather than hanging below it.\n */\n transform: translateY(-50%);\n color: var(--tempest-text-muted);\n font-size: 0.625rem;\n font-variant-numeric: tabular-nums;\n}\n\n.grid {\n position: relative;\n display: grid;\n grid-template-columns: repeat(var(--tempest-scheduler-days), minmax(0, 1fr));\n}\n\n.dayColumn {\n position: relative;\n grid-row: 1;\n border-left: 1px solid var(--tempest-border);\n}\n\n.dayColumn:first-child {\n border-left: none;\n}\n\n.todayColumn {\n background-color: var(--tempest-surface);\n}\n\n.hourLine {\n position: absolute;\n left: 0;\n right: 0;\n border-top: 1px solid var(--tempest-border);\n pointer-events: none;\n}\n\n/*\n * Events live in the same grid as the day columns, one row deep, positioned inside\n * their column by percentage. Being siblings of the columns rather than children is\n * what lets an event overlay the hour lines without clipping.\n */\n.event {\n position: absolute;\n grid-row: 1;\n display: flex;\n flex-direction: column;\n gap: 1px;\n overflow: hidden;\n padding: 2px var(--tempest-space-1);\n border: 1px solid var(--tempest-bg);\n border-radius: var(--tempest-radius-sm);\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-contrast, #fff);\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.event:disabled {\n cursor: default;\n}\n\n.event:focus-visible {\n outline: 2px solid var(--tempest-text);\n outline-offset: -2px;\n}\n\n/*\n * No `opacity` here, deliberately. Dimming the time to signal it as secondary drops\n * it below the AA contrast threshold against the event fill — the browser `axe` sweep\n * caught exactly that (jsdom cannot, since it does not paint). The hierarchy comes\n * from size and weight instead, which cost nothing in contrast.\n */\n.eventTime {\n font-size: 0.625rem;\n font-variant-numeric: tabular-nums;\n}\n\n.eventTitle {\n font-size: var(--tempest-text-xs);\n font-weight: var(--tempest-weight-medium);\n line-height: var(--tempest-leading-snug);\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.nowLine {\n position: absolute;\n left: 0;\n right: 0;\n grid-row: 1;\n grid-column: 1 / -1;\n height: 0;\n border-top: 2px solid var(--tempest-danger, #dc2626);\n pointer-events: none;\n z-index: 1;\n}\n\n@media (max-width: 640px) {\n .wrapper {\n --tempest-scheduler-gutter: 2.75rem;\n }\n\n .dayHead {\n font-size: 0.625rem;\n padding: var(--tempest-space-1);\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){return e.getHours()*60+e.getMinutes()}function t(e){let t=new Date(e);return t.setHours(0,0,0,0),t}function n(e,t){let n=new Date(e);return n.setDate(n.getDate()+t),n}function r(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}function i(e,r){let i=t(e);return Array.from({length:Math.max(0,r)},(e,t)=>n(i,t))}function a(t,r,i){let a=[];return r.forEach((r,o)=>{let s=r.getTime(),c=n(r,1).getTime();if(t.end.getTime()<=s||t.start.getTime()>=c)return;let l=t.start.getTime()<=s?0:e(t.start),u=t.end.getTime()>=c?1440:e(t.end)||(t.end.getTime()>s?1440:0),d=Math.max(i.startMinute,l),f=Math.min(i.endMinute,u);f<=d||a.push({dayIndex:o,from:d,to:f})}),a}function o(e){let t=[...e].sort((e,t)=>e.from-t.from||e.to-t.to),n=[],r=[],i=-1/0,a=()=>{let e=r.reduce((e,t)=>Math.max(e,t.column+1),0);for(let t of r)t.columns=e;n.push(...r),r=[],i=-1/0};for(let e of t){e.from>=i&&a();let t=new Set(r.filter(t=>t.to>e.from).map(e=>e.column)),n=0;for(;t.has(n);)n+=1;r.push({...e,column:n,columns:1}),i=Math.max(i,e.to)}return a(),n}function s({events:e,days:t,window:n}){let r=n.endMinute-n.startMinute;if(r<=0||t.length===0)return[];let i=new Map;for(let r of e)if(!r.allDay&&!(r.end.getTime()<=r.start.getTime()))for(let e of a(r,t,n)){let t=i.get(e.dayIndex)??[];t.push({event:r,from:e.from,to:e.to}),i.set(e.dayIndex,t)}let s=[];for(let[e,t]of i)for(let i of o(t))s.push({event:i.event,dayIndex:e,top:(i.from-n.startMinute)/r,height:(i.to-i.from)/r,column:i.column,columns:i.columns});return s}function c({events:e,days:t}){if(t.length===0)return[];let r=n(t[t.length-1],1).getTime();return e.filter(e=>e.allDay).filter(e=>e.end.getTime()>t[0].getTime()&&e.start.getTime()<r).map(e=>{let r=t.findIndex(t=>n(t,1).getTime()>e.start.getTime()),i=t.reduce((t,n,r)=>n.getTime()<e.end.getTime()?r:t,0),a=Math.max(0,r);return{event:e,dayIndex:a,span:Math.max(1,i-a+1)}})}function l(e){let t=Math.ceil(e.startMinute/60),n=Math.floor(e.endMinute/60);return Array.from({length:Math.max(0,n-t+1)},(e,n)=>(t+n)*60)}function u(t,n){let r=e(t),i=n.endMinute-n.startMinute;return i<=0||r<n.startMinute||r>n.endMinute?null:(r-n.startMinute)/i}exports.addDays=n,exports.dayRange=i,exports.fractionOfWindow=u,exports.hourMarks=l,exports.isSameDay=r,exports.layoutAllDay=c,exports.layoutEvents=s,exports.minutesOfDay=e,exports.startOfDay=t;
|
|
2
|
+
//# sourceMappingURL=scheduler-layout.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler-layout.cjs","names":[],"sources":["../../../src/components/Scheduler/scheduler-layout.ts"],"sourcesContent":["/** An event on the schedule. `start`/`end` are absolute instants in local time. */\nexport interface SchedulerEvent {\n id: string;\n title: string;\n start: Date;\n end: Date;\n /** Render across the all-day lane instead of the time grid. */\n allDay?: boolean;\n /** Free-form payload the app passes through. */\n data?: Record<string, unknown>;\n}\n\n/** An event placed in the grid: which day, where vertically, which column. */\nexport interface PlacedEvent {\n event: SchedulerEvent;\n /** Index into the rendered day list. */\n dayIndex: number;\n /** Distance from the top of the day column, as a fraction of its height. */\n top: number;\n /** Height as a fraction of the day column. */\n height: number;\n /** Column this event occupies among its overlapping cluster, zero-based. */\n column: number;\n /** How many columns the cluster was split into. */\n columns: number;\n}\n\n/** The visible time window of a day, in minutes from midnight. */\nexport interface DayWindow {\n startMinute: number;\n endMinute: number;\n}\n\n/** Minutes from midnight, in local time. */\nexport function minutesOfDay(date: Date): number {\n return date.getHours() * 60 + date.getMinutes();\n}\n\n/** Midnight local time on the same calendar day as `date`. */\nexport function startOfDay(date: Date): Date {\n const copy = new Date(date);\n copy.setHours(0, 0, 0, 0);\n return copy;\n}\n\n/** `date` shifted by whole days, preserving local wall-clock time. */\nexport function addDays(date: Date, days: number): Date {\n const copy = new Date(date);\n copy.setDate(copy.getDate() + days);\n return copy;\n}\n\n/** Whether two dates fall on the same local calendar day. */\nexport function isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\n/**\n * The consecutive local days a schedule should render.\n *\n * Built by incrementing the calendar day rather than adding 24 h of milliseconds:\n * across a DST boundary a day is 23 or 25 hours long, and millisecond arithmetic\n * would silently produce a duplicated or skipped date.\n *\n * @param anchor - Any instant within the first day.\n * @param count - How many days to produce.\n * @returns Midnight of each day, in order.\n */\nexport function dayRange(anchor: Date, count: number): Date[] {\n const first = startOfDay(anchor);\n return Array.from({ length: Math.max(0, count) }, (_, index) => addDays(first, index));\n}\n\n/**\n * Split an event into one segment per day it touches, clipped to the window.\n *\n * An event that crosses midnight has to appear in both columns, and each piece has\n * to be clipped to that day's visible window — otherwise a 23:00–01:00 booking\n * either vanishes or is drawn far outside its column.\n *\n * @param event - The event to place.\n * @param days - Midnight of each rendered day.\n * @param window - Visible minute range shared by every day.\n * @returns One entry per day the event is visible on.\n */\nfunction segmentsFor(\n event: SchedulerEvent,\n days: Date[],\n window: DayWindow,\n): { dayIndex: number; from: number; to: number }[] {\n const out: { dayIndex: number; from: number; to: number }[] = [];\n days.forEach((day, dayIndex) => {\n const dayStart = day.getTime();\n const dayEnd = addDays(day, 1).getTime();\n if (event.end.getTime() <= dayStart || event.start.getTime() >= dayEnd) return;\n\n const from = event.start.getTime() <= dayStart ? 0 : minutesOfDay(event.start);\n const to =\n event.end.getTime() >= dayEnd\n ? 24 * 60\n : minutesOfDay(event.end) || (event.end.getTime() > dayStart ? 24 * 60 : 0);\n\n const clippedFrom = Math.max(window.startMinute, from);\n const clippedTo = Math.min(window.endMinute, to);\n if (clippedTo <= clippedFrom) return;\n out.push({ dayIndex, from: clippedFrom, to: clippedTo });\n });\n return out;\n}\n\n/**\n * Assign columns so overlapping events sit side by side instead of on top.\n *\n * Events are grouped into *clusters* of mutual overlap — a chain where each event\n * overlaps at least one other — and every event in a cluster is given the same\n * column count. That shared count is what makes the widths line up; assigning\n * columns per pair instead produces the ragged layout where two events claim half\n * the width and a third silently covers one of them.\n *\n * A column is reused as soon as it is free, so `9–10`, `9–10`, `10–11` needs two\n * columns rather than three.\n *\n * @param segments - Same-day segments, in any order.\n * @returns The same segments with `column` and `columns` filled in.\n */\nfunction assignColumns<T extends { from: number; to: number }>(\n segments: T[],\n): (T & { column: number; columns: number })[] {\n const sorted = [...segments].sort((a, b) => a.from - b.from || a.to - b.to);\n const placed: (T & { column: number; columns: number })[] = [];\n\n let cluster: (T & { column: number; columns: number })[] = [];\n let clusterEnd = -Infinity;\n\n /** Freeze the current cluster: everyone in it shares its column count. */\n const closeCluster = (): void => {\n const width = cluster.reduce((max, item) => Math.max(max, item.column + 1), 0);\n for (const item of cluster) item.columns = width;\n placed.push(...cluster);\n cluster = [];\n clusterEnd = -Infinity;\n };\n\n for (const segment of sorted) {\n if (segment.from >= clusterEnd) closeCluster();\n\n // Lowest column whose last event has already ended.\n const taken = new Set(\n cluster.filter((item) => item.to > segment.from).map((i) => i.column),\n );\n let column = 0;\n while (taken.has(column)) column += 1;\n\n cluster.push({ ...segment, column, columns: 1 });\n clusterEnd = Math.max(clusterEnd, segment.to);\n }\n closeCluster();\n\n return placed;\n}\n\n/**\n * Lay out timed events across the rendered days.\n *\n * All-day events are excluded — they belong in their own lane, where a vertical\n * position would be meaningless.\n *\n * @param params.events - Every event, all-day ones included.\n * @param params.days - Midnight of each rendered day.\n * @param params.window - Visible minute range.\n * @returns Placed events, positioned as fractions of the column height.\n */\nexport function layoutEvents({\n events,\n days,\n window,\n}: {\n events: readonly SchedulerEvent[];\n days: Date[];\n window: DayWindow;\n}): PlacedEvent[] {\n const span = window.endMinute - window.startMinute;\n if (span <= 0 || days.length === 0) return [];\n\n const byDay = new Map<number, { event: SchedulerEvent; from: number; to: number }[]>();\n for (const event of events) {\n if (event.allDay) continue;\n if (event.end.getTime() <= event.start.getTime()) continue;\n for (const segment of segmentsFor(event, days, window)) {\n const list = byDay.get(segment.dayIndex) ?? [];\n list.push({ event, from: segment.from, to: segment.to });\n byDay.set(segment.dayIndex, list);\n }\n }\n\n const placed: PlacedEvent[] = [];\n for (const [dayIndex, segments] of byDay) {\n for (const item of assignColumns(segments)) {\n placed.push({\n event: item.event,\n dayIndex,\n top: (item.from - window.startMinute) / span,\n height: (item.to - item.from) / span,\n column: item.column,\n columns: item.columns,\n });\n }\n }\n return placed;\n}\n\n/**\n * All-day events that intersect the rendered range, with their day span.\n *\n * @param params.events - Every event.\n * @param params.days - Midnight of each rendered day.\n * @returns One entry per all-day event, clipped to the visible days.\n */\nexport function layoutAllDay({\n events,\n days,\n}: {\n events: readonly SchedulerEvent[];\n days: Date[];\n}): { event: SchedulerEvent; dayIndex: number; span: number }[] {\n if (days.length === 0) return [];\n const rangeEnd = addDays(days[days.length - 1], 1).getTime();\n\n return events\n .filter((event) => event.allDay)\n .filter(\n (event) => event.end.getTime() > days[0].getTime() && event.start.getTime() < rangeEnd,\n )\n .map((event) => {\n const firstIndex = days.findIndex(\n (day) => addDays(day, 1).getTime() > event.start.getTime(),\n );\n const lastIndex = days.reduce(\n (last, day, index) => (day.getTime() < event.end.getTime() ? index : last),\n 0,\n );\n const dayIndex = Math.max(0, firstIndex);\n return { event, dayIndex, span: Math.max(1, lastIndex - dayIndex + 1) };\n });\n}\n\n/**\n * Hour marks to label the time gutter with.\n *\n * @param window - Visible minute range.\n * @returns Each whole hour inside the window, in minutes from midnight.\n */\nexport function hourMarks(window: DayWindow): number[] {\n const first = Math.ceil(window.startMinute / 60);\n const last = Math.floor(window.endMinute / 60);\n return Array.from({ length: Math.max(0, last - first + 1) }, (_, i) => (first + i) * 60);\n}\n\n/**\n * Where an instant falls within the window, as a fraction of the column height.\n *\n * @param date - The instant.\n * @param window - Visible minute range.\n * @returns A fraction in `0…1`, or `null` when outside the window.\n */\nexport function fractionOfWindow(date: Date, window: DayWindow): number | null {\n const minute = minutesOfDay(date);\n const span = window.endMinute - window.startMinute;\n if (span <= 0 || minute < window.startMinute || minute > window.endMinute) return null;\n return (minute - window.startMinute) / span;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//#region src/components/Scheduler/scheduler-layout.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return e.getHours() * 60 + e.getMinutes();
|
|
4
|
+
}
|
|
5
|
+
function t(e) {
|
|
6
|
+
let t = new Date(e);
|
|
7
|
+
return t.setHours(0, 0, 0, 0), t;
|
|
8
|
+
}
|
|
9
|
+
function n(e, t) {
|
|
10
|
+
let n = new Date(e);
|
|
11
|
+
return n.setDate(n.getDate() + t), n;
|
|
12
|
+
}
|
|
13
|
+
function r(e, t) {
|
|
14
|
+
return e.getFullYear() === t.getFullYear() && e.getMonth() === t.getMonth() && e.getDate() === t.getDate();
|
|
15
|
+
}
|
|
16
|
+
function i(e, r) {
|
|
17
|
+
let i = t(e);
|
|
18
|
+
return Array.from({ length: Math.max(0, r) }, (e, t) => n(i, t));
|
|
19
|
+
}
|
|
20
|
+
function a(t, r, i) {
|
|
21
|
+
let a = [];
|
|
22
|
+
return r.forEach((r, o) => {
|
|
23
|
+
let s = r.getTime(), c = n(r, 1).getTime();
|
|
24
|
+
if (t.end.getTime() <= s || t.start.getTime() >= c) return;
|
|
25
|
+
let l = t.start.getTime() <= s ? 0 : e(t.start), u = t.end.getTime() >= c ? 1440 : e(t.end) || (t.end.getTime() > s ? 1440 : 0), d = Math.max(i.startMinute, l), f = Math.min(i.endMinute, u);
|
|
26
|
+
f <= d || a.push({
|
|
27
|
+
dayIndex: o,
|
|
28
|
+
from: d,
|
|
29
|
+
to: f
|
|
30
|
+
});
|
|
31
|
+
}), a;
|
|
32
|
+
}
|
|
33
|
+
function o(e) {
|
|
34
|
+
let t = [...e].sort((e, t) => e.from - t.from || e.to - t.to), n = [], r = [], i = -Infinity, a = () => {
|
|
35
|
+
let e = r.reduce((e, t) => Math.max(e, t.column + 1), 0);
|
|
36
|
+
for (let t of r) t.columns = e;
|
|
37
|
+
n.push(...r), r = [], i = -Infinity;
|
|
38
|
+
};
|
|
39
|
+
for (let e of t) {
|
|
40
|
+
e.from >= i && a();
|
|
41
|
+
let t = new Set(r.filter((t) => t.to > e.from).map((e) => e.column)), n = 0;
|
|
42
|
+
for (; t.has(n);) n += 1;
|
|
43
|
+
r.push({
|
|
44
|
+
...e,
|
|
45
|
+
column: n,
|
|
46
|
+
columns: 1
|
|
47
|
+
}), i = Math.max(i, e.to);
|
|
48
|
+
}
|
|
49
|
+
return a(), n;
|
|
50
|
+
}
|
|
51
|
+
function s({ events: e, days: t, window: n }) {
|
|
52
|
+
let r = n.endMinute - n.startMinute;
|
|
53
|
+
if (r <= 0 || t.length === 0) return [];
|
|
54
|
+
let i = /* @__PURE__ */ new Map();
|
|
55
|
+
for (let r of e) if (!r.allDay && !(r.end.getTime() <= r.start.getTime())) for (let e of a(r, t, n)) {
|
|
56
|
+
let t = i.get(e.dayIndex) ?? [];
|
|
57
|
+
t.push({
|
|
58
|
+
event: r,
|
|
59
|
+
from: e.from,
|
|
60
|
+
to: e.to
|
|
61
|
+
}), i.set(e.dayIndex, t);
|
|
62
|
+
}
|
|
63
|
+
let s = [];
|
|
64
|
+
for (let [e, t] of i) for (let i of o(t)) s.push({
|
|
65
|
+
event: i.event,
|
|
66
|
+
dayIndex: e,
|
|
67
|
+
top: (i.from - n.startMinute) / r,
|
|
68
|
+
height: (i.to - i.from) / r,
|
|
69
|
+
column: i.column,
|
|
70
|
+
columns: i.columns
|
|
71
|
+
});
|
|
72
|
+
return s;
|
|
73
|
+
}
|
|
74
|
+
function c({ events: e, days: t }) {
|
|
75
|
+
if (t.length === 0) return [];
|
|
76
|
+
let r = n(t[t.length - 1], 1).getTime();
|
|
77
|
+
return e.filter((e) => e.allDay).filter((e) => e.end.getTime() > t[0].getTime() && e.start.getTime() < r).map((e) => {
|
|
78
|
+
let r = t.findIndex((t) => n(t, 1).getTime() > e.start.getTime()), i = t.reduce((t, n, r) => n.getTime() < e.end.getTime() ? r : t, 0), a = Math.max(0, r);
|
|
79
|
+
return {
|
|
80
|
+
event: e,
|
|
81
|
+
dayIndex: a,
|
|
82
|
+
span: Math.max(1, i - a + 1)
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function l(e) {
|
|
87
|
+
let t = Math.ceil(e.startMinute / 60), n = Math.floor(e.endMinute / 60);
|
|
88
|
+
return Array.from({ length: Math.max(0, n - t + 1) }, (e, n) => (t + n) * 60);
|
|
89
|
+
}
|
|
90
|
+
function u(t, n) {
|
|
91
|
+
let r = e(t), i = n.endMinute - n.startMinute;
|
|
92
|
+
return i <= 0 || r < n.startMinute || r > n.endMinute ? null : (r - n.startMinute) / i;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { n as addDays, i as dayRange, u as fractionOfWindow, l as hourMarks, r as isSameDay, c as layoutAllDay, s as layoutEvents, e as minutesOfDay, t as startOfDay };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=scheduler-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler-layout.js","names":[],"sources":["../../../src/components/Scheduler/scheduler-layout.ts"],"sourcesContent":["/** An event on the schedule. `start`/`end` are absolute instants in local time. */\nexport interface SchedulerEvent {\n id: string;\n title: string;\n start: Date;\n end: Date;\n /** Render across the all-day lane instead of the time grid. */\n allDay?: boolean;\n /** Free-form payload the app passes through. */\n data?: Record<string, unknown>;\n}\n\n/** An event placed in the grid: which day, where vertically, which column. */\nexport interface PlacedEvent {\n event: SchedulerEvent;\n /** Index into the rendered day list. */\n dayIndex: number;\n /** Distance from the top of the day column, as a fraction of its height. */\n top: number;\n /** Height as a fraction of the day column. */\n height: number;\n /** Column this event occupies among its overlapping cluster, zero-based. */\n column: number;\n /** How many columns the cluster was split into. */\n columns: number;\n}\n\n/** The visible time window of a day, in minutes from midnight. */\nexport interface DayWindow {\n startMinute: number;\n endMinute: number;\n}\n\n/** Minutes from midnight, in local time. */\nexport function minutesOfDay(date: Date): number {\n return date.getHours() * 60 + date.getMinutes();\n}\n\n/** Midnight local time on the same calendar day as `date`. */\nexport function startOfDay(date: Date): Date {\n const copy = new Date(date);\n copy.setHours(0, 0, 0, 0);\n return copy;\n}\n\n/** `date` shifted by whole days, preserving local wall-clock time. */\nexport function addDays(date: Date, days: number): Date {\n const copy = new Date(date);\n copy.setDate(copy.getDate() + days);\n return copy;\n}\n\n/** Whether two dates fall on the same local calendar day. */\nexport function isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\n/**\n * The consecutive local days a schedule should render.\n *\n * Built by incrementing the calendar day rather than adding 24 h of milliseconds:\n * across a DST boundary a day is 23 or 25 hours long, and millisecond arithmetic\n * would silently produce a duplicated or skipped date.\n *\n * @param anchor - Any instant within the first day.\n * @param count - How many days to produce.\n * @returns Midnight of each day, in order.\n */\nexport function dayRange(anchor: Date, count: number): Date[] {\n const first = startOfDay(anchor);\n return Array.from({ length: Math.max(0, count) }, (_, index) => addDays(first, index));\n}\n\n/**\n * Split an event into one segment per day it touches, clipped to the window.\n *\n * An event that crosses midnight has to appear in both columns, and each piece has\n * to be clipped to that day's visible window — otherwise a 23:00–01:00 booking\n * either vanishes or is drawn far outside its column.\n *\n * @param event - The event to place.\n * @param days - Midnight of each rendered day.\n * @param window - Visible minute range shared by every day.\n * @returns One entry per day the event is visible on.\n */\nfunction segmentsFor(\n event: SchedulerEvent,\n days: Date[],\n window: DayWindow,\n): { dayIndex: number; from: number; to: number }[] {\n const out: { dayIndex: number; from: number; to: number }[] = [];\n days.forEach((day, dayIndex) => {\n const dayStart = day.getTime();\n const dayEnd = addDays(day, 1).getTime();\n if (event.end.getTime() <= dayStart || event.start.getTime() >= dayEnd) return;\n\n const from = event.start.getTime() <= dayStart ? 0 : minutesOfDay(event.start);\n const to =\n event.end.getTime() >= dayEnd\n ? 24 * 60\n : minutesOfDay(event.end) || (event.end.getTime() > dayStart ? 24 * 60 : 0);\n\n const clippedFrom = Math.max(window.startMinute, from);\n const clippedTo = Math.min(window.endMinute, to);\n if (clippedTo <= clippedFrom) return;\n out.push({ dayIndex, from: clippedFrom, to: clippedTo });\n });\n return out;\n}\n\n/**\n * Assign columns so overlapping events sit side by side instead of on top.\n *\n * Events are grouped into *clusters* of mutual overlap — a chain where each event\n * overlaps at least one other — and every event in a cluster is given the same\n * column count. That shared count is what makes the widths line up; assigning\n * columns per pair instead produces the ragged layout where two events claim half\n * the width and a third silently covers one of them.\n *\n * A column is reused as soon as it is free, so `9–10`, `9–10`, `10–11` needs two\n * columns rather than three.\n *\n * @param segments - Same-day segments, in any order.\n * @returns The same segments with `column` and `columns` filled in.\n */\nfunction assignColumns<T extends { from: number; to: number }>(\n segments: T[],\n): (T & { column: number; columns: number })[] {\n const sorted = [...segments].sort((a, b) => a.from - b.from || a.to - b.to);\n const placed: (T & { column: number; columns: number })[] = [];\n\n let cluster: (T & { column: number; columns: number })[] = [];\n let clusterEnd = -Infinity;\n\n /** Freeze the current cluster: everyone in it shares its column count. */\n const closeCluster = (): void => {\n const width = cluster.reduce((max, item) => Math.max(max, item.column + 1), 0);\n for (const item of cluster) item.columns = width;\n placed.push(...cluster);\n cluster = [];\n clusterEnd = -Infinity;\n };\n\n for (const segment of sorted) {\n if (segment.from >= clusterEnd) closeCluster();\n\n // Lowest column whose last event has already ended.\n const taken = new Set(\n cluster.filter((item) => item.to > segment.from).map((i) => i.column),\n );\n let column = 0;\n while (taken.has(column)) column += 1;\n\n cluster.push({ ...segment, column, columns: 1 });\n clusterEnd = Math.max(clusterEnd, segment.to);\n }\n closeCluster();\n\n return placed;\n}\n\n/**\n * Lay out timed events across the rendered days.\n *\n * All-day events are excluded — they belong in their own lane, where a vertical\n * position would be meaningless.\n *\n * @param params.events - Every event, all-day ones included.\n * @param params.days - Midnight of each rendered day.\n * @param params.window - Visible minute range.\n * @returns Placed events, positioned as fractions of the column height.\n */\nexport function layoutEvents({\n events,\n days,\n window,\n}: {\n events: readonly SchedulerEvent[];\n days: Date[];\n window: DayWindow;\n}): PlacedEvent[] {\n const span = window.endMinute - window.startMinute;\n if (span <= 0 || days.length === 0) return [];\n\n const byDay = new Map<number, { event: SchedulerEvent; from: number; to: number }[]>();\n for (const event of events) {\n if (event.allDay) continue;\n if (event.end.getTime() <= event.start.getTime()) continue;\n for (const segment of segmentsFor(event, days, window)) {\n const list = byDay.get(segment.dayIndex) ?? [];\n list.push({ event, from: segment.from, to: segment.to });\n byDay.set(segment.dayIndex, list);\n }\n }\n\n const placed: PlacedEvent[] = [];\n for (const [dayIndex, segments] of byDay) {\n for (const item of assignColumns(segments)) {\n placed.push({\n event: item.event,\n dayIndex,\n top: (item.from - window.startMinute) / span,\n height: (item.to - item.from) / span,\n column: item.column,\n columns: item.columns,\n });\n }\n }\n return placed;\n}\n\n/**\n * All-day events that intersect the rendered range, with their day span.\n *\n * @param params.events - Every event.\n * @param params.days - Midnight of each rendered day.\n * @returns One entry per all-day event, clipped to the visible days.\n */\nexport function layoutAllDay({\n events,\n days,\n}: {\n events: readonly SchedulerEvent[];\n days: Date[];\n}): { event: SchedulerEvent; dayIndex: number; span: number }[] {\n if (days.length === 0) return [];\n const rangeEnd = addDays(days[days.length - 1], 1).getTime();\n\n return events\n .filter((event) => event.allDay)\n .filter(\n (event) => event.end.getTime() > days[0].getTime() && event.start.getTime() < rangeEnd,\n )\n .map((event) => {\n const firstIndex = days.findIndex(\n (day) => addDays(day, 1).getTime() > event.start.getTime(),\n );\n const lastIndex = days.reduce(\n (last, day, index) => (day.getTime() < event.end.getTime() ? index : last),\n 0,\n );\n const dayIndex = Math.max(0, firstIndex);\n return { event, dayIndex, span: Math.max(1, lastIndex - dayIndex + 1) };\n });\n}\n\n/**\n * Hour marks to label the time gutter with.\n *\n * @param window - Visible minute range.\n * @returns Each whole hour inside the window, in minutes from midnight.\n */\nexport function hourMarks(window: DayWindow): number[] {\n const first = Math.ceil(window.startMinute / 60);\n const last = Math.floor(window.endMinute / 60);\n return Array.from({ length: Math.max(0, last - first + 1) }, (_, i) => (first + i) * 60);\n}\n\n/**\n * Where an instant falls within the window, as a fraction of the column height.\n *\n * @param date - The instant.\n * @param window - Visible minute range.\n * @returns A fraction in `0…1`, or `null` when outside the window.\n */\nexport function fractionOfWindow(date: Date, window: DayWindow): number | null {\n const minute = minutesOfDay(date);\n const span = window.endMinute - window.startMinute;\n if (span <= 0 || minute < window.startMinute || minute > window.endMinute) return null;\n return (minute - window.startMinute) / span;\n}\n"],"mappings":"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"}
|