tempest-react-sdk 0.26.1 → 0.28.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.
Files changed (132) hide show
  1. package/README.md +25 -1
  2. package/bin/lib/css/analyze.mjs +149 -0
  3. package/bin/lib/css/analyze.test.mjs +136 -0
  4. package/bin/lib/css/collect.mjs +152 -0
  5. package/bin/lib/css/findings.mjs +79 -0
  6. package/bin/lib/css/fix.e2e.test.mjs +99 -0
  7. package/bin/lib/css/fix.mjs +138 -0
  8. package/bin/lib/css/fix.test.mjs +108 -0
  9. package/bin/lib/css/index.mjs +16 -0
  10. package/bin/lib/css/parse.mjs +398 -0
  11. package/bin/lib/css/parse.test.mjs +146 -0
  12. package/bin/lib/css/properties.mjs +399 -0
  13. package/bin/lib/css/repetition.mjs +213 -0
  14. package/bin/lib/css/repetition.test.mjs +166 -0
  15. package/bin/lib/css/semantic.mjs +327 -0
  16. package/bin/lib/css/semantic.test.mjs +201 -0
  17. package/bin/lib/css/tokens.mjs +119 -0
  18. package/bin/lib/doctor/doctor.e2e.test.mjs +246 -0
  19. package/bin/tempest.mjs +272 -36
  20. package/dist/charts/scales.cjs +2 -0
  21. package/dist/charts/scales.cjs.map +1 -0
  22. package/dist/charts/scales.js +29 -0
  23. package/dist/charts/scales.js.map +1 -0
  24. package/dist/charts.cjs +1 -1
  25. package/dist/charts.d.ts +91 -0
  26. package/dist/charts.js +9 -8
  27. package/dist/components/BottomNavigation/BottomNavigation.module.cjs.map +1 -1
  28. package/dist/components/BottomNavigation/BottomNavigation.module.js.map +1 -1
  29. package/dist/components/Carousel/Carousel.module.cjs.map +1 -1
  30. package/dist/components/Carousel/Carousel.module.js.map +1 -1
  31. package/dist/components/CodeBlock/CodeBlock.cjs +3 -0
  32. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -0
  33. package/dist/components/CodeBlock/CodeBlock.js +53 -0
  34. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  35. package/dist/components/CodeBlock/CodeBlock.module.cjs +2 -0
  36. package/dist/components/CodeBlock/CodeBlock.module.cjs.map +1 -0
  37. package/dist/components/CodeBlock/CodeBlock.module.js +29 -0
  38. package/dist/components/CodeBlock/CodeBlock.module.js.map +1 -0
  39. package/dist/components/CodeBlock/tokenize.cjs +3 -0
  40. package/dist/components/CodeBlock/tokenize.cjs.map +1 -0
  41. package/dist/components/CodeBlock/tokenize.js +267 -0
  42. package/dist/components/CodeBlock/tokenize.js.map +1 -0
  43. package/dist/components/ImageCropper/ImageCropper.cjs +2 -0
  44. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -0
  45. package/dist/components/ImageCropper/ImageCropper.js +240 -0
  46. package/dist/components/ImageCropper/ImageCropper.js.map +1 -0
  47. package/dist/components/ImageCropper/ImageCropper.module.cjs +2 -0
  48. package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -0
  49. package/dist/components/ImageCropper/ImageCropper.module.js +15 -0
  50. package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -0
  51. package/dist/components/ImageCropper/crop-geometry.cjs +2 -0
  52. package/dist/components/ImageCropper/crop-geometry.cjs.map +1 -0
  53. package/dist/components/ImageCropper/crop-geometry.js +52 -0
  54. package/dist/components/ImageCropper/crop-geometry.js.map +1 -0
  55. package/dist/components/Navbar/Navbar.module.cjs.map +1 -1
  56. package/dist/components/Navbar/Navbar.module.js.map +1 -1
  57. package/dist/components/QRCode/QRCode.cjs +2 -0
  58. package/dist/components/QRCode/QRCode.cjs.map +1 -0
  59. package/dist/components/QRCode/QRCode.js +49 -0
  60. package/dist/components/QRCode/QRCode.js.map +1 -0
  61. package/dist/components/QRCode/QRCode.module.cjs +2 -0
  62. package/dist/components/QRCode/QRCode.module.cjs.map +1 -0
  63. package/dist/components/QRCode/QRCode.module.js +9 -0
  64. package/dist/components/QRCode/QRCode.module.js.map +1 -0
  65. package/dist/components/QRCode/qr-encode.cjs +2 -0
  66. package/dist/components/QRCode/qr-encode.cjs.map +1 -0
  67. package/dist/components/QRCode/qr-encode.js +295 -0
  68. package/dist/components/QRCode/qr-encode.js.map +1 -0
  69. package/dist/components/QRCode/qr-tables.cjs +2 -0
  70. package/dist/components/QRCode/qr-tables.cjs.map +1 -0
  71. package/dist/components/QRCode/qr-tables.js +366 -0
  72. package/dist/components/QRCode/qr-tables.js.map +1 -0
  73. package/dist/components/Scheduler/Scheduler.cjs +2 -0
  74. package/dist/components/Scheduler/Scheduler.cjs.map +1 -0
  75. package/dist/components/Scheduler/Scheduler.js +134 -0
  76. package/dist/components/Scheduler/Scheduler.js.map +1 -0
  77. package/dist/components/Scheduler/Scheduler.module.cjs +2 -0
  78. package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -0
  79. package/dist/components/Scheduler/Scheduler.module.js +26 -0
  80. package/dist/components/Scheduler/Scheduler.module.js.map +1 -0
  81. package/dist/components/Scheduler/scheduler-layout.cjs +2 -0
  82. package/dist/components/Scheduler/scheduler-layout.cjs.map +1 -0
  83. package/dist/components/Scheduler/scheduler-layout.js +97 -0
  84. package/dist/components/Scheduler/scheduler-layout.js.map +1 -0
  85. package/dist/components/ScrollArea/ScrollArea.cjs +1 -1
  86. package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
  87. package/dist/components/ScrollArea/ScrollArea.js +22 -16
  88. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  89. package/dist/components/ScrollArea/ScrollArea.module.cjs.map +1 -1
  90. package/dist/components/ScrollArea/ScrollArea.module.js.map +1 -1
  91. package/dist/components/Sparkline/Sparkline.cjs +2 -0
  92. package/dist/components/Sparkline/Sparkline.cjs.map +1 -0
  93. package/dist/components/Sparkline/Sparkline.js +64 -0
  94. package/dist/components/Sparkline/Sparkline.js.map +1 -0
  95. package/dist/components/Sparkline/Sparkline.module.cjs +2 -0
  96. package/dist/components/Sparkline/Sparkline.module.cjs.map +1 -0
  97. package/dist/components/Sparkline/Sparkline.module.js +10 -0
  98. package/dist/components/Sparkline/Sparkline.module.js.map +1 -0
  99. package/dist/components/Sparkline/sparkline-geometry.cjs +2 -0
  100. package/dist/components/Sparkline/sparkline-geometry.cjs.map +1 -0
  101. package/dist/components/Sparkline/sparkline-geometry.js +56 -0
  102. package/dist/components/Sparkline/sparkline-geometry.js.map +1 -0
  103. package/dist/components/Table/Table.cjs +1 -1
  104. package/dist/components/Table/Table.cjs.map +1 -1
  105. package/dist/components/Table/Table.js +37 -30
  106. package/dist/components/Table/Table.js.map +1 -1
  107. package/dist/components/Table/Table.module.cjs.map +1 -1
  108. package/dist/components/Table/Table.module.js.map +1 -1
  109. package/dist/components/VirtualList/VirtualList.cjs +1 -1
  110. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  111. package/dist/components/VirtualList/VirtualList.js +13 -11
  112. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  113. package/dist/components/VirtualList/VirtualList.module.cjs.map +1 -1
  114. package/dist/components/VirtualList/VirtualList.module.js.map +1 -1
  115. package/dist/hooks/use-scroll-overflow.cjs +2 -0
  116. package/dist/hooks/use-scroll-overflow.cjs.map +1 -0
  117. package/dist/hooks/use-scroll-overflow.js +22 -0
  118. package/dist/hooks/use-scroll-overflow.js.map +1 -0
  119. package/dist/styles.css +1 -1
  120. package/dist/tempest-react-sdk.cjs +1 -1
  121. package/dist/tempest-react-sdk.d.ts +607 -2
  122. package/dist/tempest-react-sdk.js +197 -188
  123. package/dist/theme/create-theme.cjs +3 -3
  124. package/dist/theme/create-theme.cjs.map +1 -1
  125. package/dist/theme/create-theme.js +52 -28
  126. package/dist/theme/create-theme.js.map +1 -1
  127. package/dist/theme/data-viz-ramps.cjs +2 -0
  128. package/dist/theme/data-viz-ramps.cjs.map +1 -0
  129. package/dist/theme/data-viz-ramps.js +58 -0
  130. package/dist/theme/data-viz-ramps.js.map +1 -0
  131. package/package.json +2 -1
  132. 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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"}
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("./ScrollArea.module.cjs");let n=require("react"),r=require("react/jsx-runtime");var i=(0,n.forwardRef)(function({maxHeight:n,orientation:i=`vertical`,className:a,style:o,children:s,...c},l){let u={overflowX:i===`vertical`?`hidden`:`auto`,overflowY:i===`horizontal`?`hidden`:`auto`,...n===void 0?{}:{maxHeight:n},...o};return(0,r.jsx)(`div`,{ref:l,className:e.cn(t.default.root,a),style:u,...c,children:s})});exports.ScrollArea=i;
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-scroll-overflow.cjs"),n=require("./ScrollArea.module.cjs");let r=require("react"),i=require("react/jsx-runtime");var a=(0,r.forwardRef)(function({maxHeight:a,orientation:o=`vertical`,scrollLabel:s=`Área rolável`,className:c,style:l,children:u,...d},f){let p=(0,r.useRef)(null),m=t.useScrollOverflow(p,o),h=(0,r.useCallback)(e=>{p.current=e,typeof f==`function`?f(e):f&&(f.current=e)},[f]),g={overflowX:o===`vertical`?`hidden`:`auto`,overflowY:o===`horizontal`?`hidden`:`auto`,...a===void 0?{}:{maxHeight:a},...l};return(0,i.jsx)(`div`,{ref:h,className:e.cn(n.default.root,c),style:g,tabIndex:m?0:void 0,role:m?`group`:void 0,"aria-label":m?s:void 0,...d,children:u})});exports.ScrollArea=a;
2
2
  //# sourceMappingURL=ScrollArea.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.cjs","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { CSSProperties, HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./ScrollArea.module.css\";\n\nexport type ScrollAreaOrientation = \"vertical\" | \"horizontal\" | \"both\";\n\nexport interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {\n /** Caps the container height; numbers are treated as pixels. */\n maxHeight?: number | string;\n /** Which axis scrolls. Default `\"vertical\"`. */\n orientation?: ScrollAreaOrientation;\n}\n\n/**\n * A styled scroll container that overflows on the chosen axis and renders a\n * thin custom scrollbar (WebKit) while staying fully functional in browsers\n * without scrollbar styling. Forwards `className`, `style` and the ref to the\n * underlying `<div>`.\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n { maxHeight, orientation = \"vertical\", className, style, children, ...props },\n ref,\n) {\n const mergedStyle: CSSProperties = {\n overflowX: orientation === \"vertical\" ? \"hidden\" : \"auto\",\n overflowY: orientation === \"horizontal\" ? \"hidden\" : \"auto\",\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n ...style,\n };\n\n return (\n <div ref={ref} className={cn(styles.root, className)} style={mergedStyle} {...props}>\n {children}\n </div>\n );\n});\n"],"mappings":"iIAoBA,IAAa,GAAA,EAAA,EAAA,WAAA,CAAyD,SAClE,CAAE,YAAW,cAAc,WAAY,YAAW,QAAO,WAAU,GAAG,GACtE,EACF,CACE,IAAM,EAA6B,CAC/B,UAAW,IAAgB,WAAa,SAAW,OACnD,UAAW,IAAgB,aAAe,SAAW,OACrD,GAAI,IAAc,IAAA,GAA4B,CAAC,EAAjB,CAAE,WAAU,EAC1C,GAAG,CACP,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAU,MAAK,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EAAG,MAAO,EAAa,GAAI,EACzE,UACA,CAAA,CAEb,CAAC"}
1
+ {"version":3,"file":"ScrollArea.cjs","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"sourcesContent":["import { forwardRef, useCallback, useRef } from \"react\";\nimport type { CSSProperties, HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useScrollOverflow } from \"@/hooks/use-scroll-overflow\";\nimport styles from \"./ScrollArea.module.css\";\n\nexport type ScrollAreaOrientation = \"vertical\" | \"horizontal\" | \"both\";\n\nexport interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {\n /** Caps the container height; numbers are treated as pixels. */\n maxHeight?: number | string;\n /** Which axis scrolls. Default `\"vertical\"`. */\n orientation?: ScrollAreaOrientation;\n /**\n * Accessible name used while the content actually overflows, when the area\n * takes a tab stop of its own. Defaults to a generic one; name it when the\n * page holds several scroll areas.\n */\n scrollLabel?: string;\n}\n\n/**\n * A styled scroll container that overflows on the chosen axis and renders a\n * thin custom scrollbar (WebKit) while staying fully functional in browsers\n * without scrollbar styling. Forwards `className`, `style` and the ref to the\n * underlying `<div>`.\n *\n * While the content overflows, the container becomes a named, focusable group.\n * A scroll area whose children are plain text holds nothing focusable, so\n * without that a keyboard user can see the scrollbar and has no way to move it.\n * The tab stop disappears again once the content fits, so an area that does not\n * scroll never adds one. Callers may still override `tabIndex` or `role`.\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n {\n maxHeight,\n orientation = \"vertical\",\n scrollLabel = \"Área rolável\",\n className,\n style,\n children,\n ...props\n },\n ref,\n) {\n const innerRef = useRef<HTMLDivElement>(null);\n const scrollable = useScrollOverflow(innerRef, orientation);\n\n /**\n * Keep the forwarded ref working while measuring internally: the caller's\n * ref is the public contract, the inner one is what the observer needs.\n */\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n },\n [ref],\n );\n\n const mergedStyle: CSSProperties = {\n overflowX: orientation === \"vertical\" ? \"hidden\" : \"auto\",\n overflowY: orientation === \"horizontal\" ? \"hidden\" : \"auto\",\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n ...style,\n };\n\n return (\n <div\n ref={setRefs}\n className={cn(styles.root, className)}\n style={mergedStyle}\n tabIndex={scrollable ? 0 : undefined}\n role={scrollable ? \"group\" : undefined}\n aria-label={scrollable ? scrollLabel : undefined}\n {...props}\n >\n {children}\n </div>\n );\n});\n"],"mappings":"kLAiCA,IAAa,GAAA,EAAA,EAAA,WAAA,CAAyD,SAClE,CACI,YACA,cAAc,WACd,cAAc,eACd,YACA,QACA,WACA,GAAG,GAEP,EACF,CACE,IAAM,GAAA,EAAA,EAAA,OAAA,CAAkC,IAAI,EACtC,EAAa,EAAA,kBAAkB,EAAU,CAAW,EAMpD,GAAA,EAAA,EAAA,YAAA,CACD,GAAgC,CAC7B,EAAS,QAAU,EACf,OAAO,GAAQ,WAAY,EAAI,CAAI,EAC9B,IAAK,EAAI,QAAU,EAChC,EACA,CAAC,CAAG,CACR,EAEM,EAA6B,CAC/B,UAAW,IAAgB,WAAa,SAAW,OACnD,UAAW,IAAgB,aAAe,SAAW,OACrD,GAAI,IAAc,IAAA,GAA4B,CAAC,EAAjB,CAAE,WAAU,EAC1C,GAAG,CACP,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EACpC,MAAO,EACP,SAAU,EAAa,EAAI,IAAA,GAC3B,KAAM,EAAa,QAAU,IAAA,GAC7B,aAAY,EAAa,EAAc,IAAA,GACvC,GAAI,EAEH,UACA,CAAA,CAEb,CAAC"}
@@ -1,24 +1,30 @@
1
1
  import { cn as e } from "../../utils/cn.js";
2
- import t from "./ScrollArea.module.js";
3
- import { forwardRef as n } from "react";
4
- import { jsx as r } from "react/jsx-runtime";
2
+ import { useScrollOverflow as t } from "../../hooks/use-scroll-overflow.js";
3
+ import n from "./ScrollArea.module.js";
4
+ import { forwardRef as r, useCallback as i, useRef as a } from "react";
5
+ import { jsx as o } from "react/jsx-runtime";
5
6
  //#region src/components/ScrollArea/ScrollArea.tsx
6
- var i = n(function({ maxHeight: n, orientation: i = "vertical", className: a, style: o, children: s, ...c }, l) {
7
- let u = {
8
- overflowX: i === "vertical" ? "hidden" : "auto",
9
- overflowY: i === "horizontal" ? "hidden" : "auto",
10
- ...n === void 0 ? {} : { maxHeight: n },
11
- ...o
7
+ var s = r(function({ maxHeight: r, orientation: s = "vertical", scrollLabel: c = "Área rolável", className: l, style: u, children: d, ...f }, p) {
8
+ let m = a(null), h = t(m, s), g = i((e) => {
9
+ m.current = e, typeof p == "function" ? p(e) : p && (p.current = e);
10
+ }, [p]), _ = {
11
+ overflowX: s === "vertical" ? "hidden" : "auto",
12
+ overflowY: s === "horizontal" ? "hidden" : "auto",
13
+ ...r === void 0 ? {} : { maxHeight: r },
14
+ ...u
12
15
  };
13
- return /* @__PURE__ */ r("div", {
14
- ref: l,
15
- className: e(t.root, a),
16
- style: u,
17
- ...c,
18
- children: s
16
+ return /* @__PURE__ */ o("div", {
17
+ ref: g,
18
+ className: e(n.root, l),
19
+ style: _,
20
+ tabIndex: h ? 0 : void 0,
21
+ role: h ? "group" : void 0,
22
+ "aria-label": h ? c : void 0,
23
+ ...f,
24
+ children: d
19
25
  });
20
26
  });
21
27
  //#endregion
22
- export { i as ScrollArea };
28
+ export { s as ScrollArea };
23
29
 
24
30
  //# sourceMappingURL=ScrollArea.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.js","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { CSSProperties, HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./ScrollArea.module.css\";\n\nexport type ScrollAreaOrientation = \"vertical\" | \"horizontal\" | \"both\";\n\nexport interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {\n /** Caps the container height; numbers are treated as pixels. */\n maxHeight?: number | string;\n /** Which axis scrolls. Default `\"vertical\"`. */\n orientation?: ScrollAreaOrientation;\n}\n\n/**\n * A styled scroll container that overflows on the chosen axis and renders a\n * thin custom scrollbar (WebKit) while staying fully functional in browsers\n * without scrollbar styling. Forwards `className`, `style` and the ref to the\n * underlying `<div>`.\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n { maxHeight, orientation = \"vertical\", className, style, children, ...props },\n ref,\n) {\n const mergedStyle: CSSProperties = {\n overflowX: orientation === \"vertical\" ? \"hidden\" : \"auto\",\n overflowY: orientation === \"horizontal\" ? \"hidden\" : \"auto\",\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n ...style,\n };\n\n return (\n <div ref={ref} className={cn(styles.root, className)} style={mergedStyle} {...props}>\n {children}\n </div>\n );\n});\n"],"mappings":";;;;;AAoBA,IAAa,IAAa,EAA4C,SAClE,EAAE,cAAW,iBAAc,YAAY,cAAW,UAAO,aAAU,GAAG,KACtE,GACF;CACE,IAAM,IAA6B;EAC/B,WAAW,MAAgB,aAAa,WAAW;EACnD,WAAW,MAAgB,eAAe,WAAW;EACrD,GAAI,MAAc,KAAA,IAA4B,CAAC,IAAjB,EAAE,aAAU;EAC1C,GAAG;CACP;CAEA,OACI,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,EAAO,MAAM,CAAS;EAAG,OAAO;EAAa,GAAI;EACzE;CACA,CAAA;AAEb,CAAC"}
1
+ {"version":3,"file":"ScrollArea.js","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.tsx"],"sourcesContent":["import { forwardRef, useCallback, useRef } from \"react\";\nimport type { CSSProperties, HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { useScrollOverflow } from \"@/hooks/use-scroll-overflow\";\nimport styles from \"./ScrollArea.module.css\";\n\nexport type ScrollAreaOrientation = \"vertical\" | \"horizontal\" | \"both\";\n\nexport interface ScrollAreaProps extends HTMLAttributes<HTMLDivElement> {\n /** Caps the container height; numbers are treated as pixels. */\n maxHeight?: number | string;\n /** Which axis scrolls. Default `\"vertical\"`. */\n orientation?: ScrollAreaOrientation;\n /**\n * Accessible name used while the content actually overflows, when the area\n * takes a tab stop of its own. Defaults to a generic one; name it when the\n * page holds several scroll areas.\n */\n scrollLabel?: string;\n}\n\n/**\n * A styled scroll container that overflows on the chosen axis and renders a\n * thin custom scrollbar (WebKit) while staying fully functional in browsers\n * without scrollbar styling. Forwards `className`, `style` and the ref to the\n * underlying `<div>`.\n *\n * While the content overflows, the container becomes a named, focusable group.\n * A scroll area whose children are plain text holds nothing focusable, so\n * without that a keyboard user can see the scrollbar and has no way to move it.\n * The tab stop disappears again once the content fits, so an area that does not\n * scroll never adds one. Callers may still override `tabIndex` or `role`.\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(function ScrollArea(\n {\n maxHeight,\n orientation = \"vertical\",\n scrollLabel = \"Área rolável\",\n className,\n style,\n children,\n ...props\n },\n ref,\n) {\n const innerRef = useRef<HTMLDivElement>(null);\n const scrollable = useScrollOverflow(innerRef, orientation);\n\n /**\n * Keep the forwarded ref working while measuring internally: the caller's\n * ref is the public contract, the inner one is what the observer needs.\n */\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n },\n [ref],\n );\n\n const mergedStyle: CSSProperties = {\n overflowX: orientation === \"vertical\" ? \"hidden\" : \"auto\",\n overflowY: orientation === \"horizontal\" ? \"hidden\" : \"auto\",\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n ...style,\n };\n\n return (\n <div\n ref={setRefs}\n className={cn(styles.root, className)}\n style={mergedStyle}\n tabIndex={scrollable ? 0 : undefined}\n role={scrollable ? \"group\" : undefined}\n aria-label={scrollable ? scrollLabel : undefined}\n {...props}\n >\n {children}\n </div>\n );\n});\n"],"mappings":";;;;;;AAiCA,IAAa,IAAa,EAA4C,SAClE,EACI,cACA,iBAAc,YACd,iBAAc,gBACd,cACA,UACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAW,EAAuB,IAAI,GACtC,IAAa,EAAkB,GAAU,CAAW,GAMpD,IAAU,GACX,MAAgC;EAE7B,AADA,EAAS,UAAU,GACf,OAAO,KAAQ,aAAY,EAAI,CAAI,IAC9B,MAAK,EAAI,UAAU;CAChC,GACA,CAAC,CAAG,CACR,GAEM,IAA6B;EAC/B,WAAW,MAAgB,aAAa,WAAW;EACnD,WAAW,MAAgB,eAAe,WAAW;EACrD,GAAI,MAAc,KAAA,IAA4B,CAAC,IAAjB,EAAE,aAAU;EAC1C,GAAG;CACP;CAEA,OACI,kBAAC,OAAD;EACI,KAAK;EACL,WAAW,EAAG,EAAO,MAAM,CAAS;EACpC,OAAO;EACP,UAAU,IAAa,IAAI,KAAA;EAC3B,MAAM,IAAa,UAAU,KAAA;EAC7B,cAAY,IAAa,IAAc,KAAA;EACvC,GAAI;EAEH;CACA,CAAA;AAEb,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.module.cjs","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.module.css"],"sourcesContent":[".root {\n /* Overflow is set inline per orientation; this only styles the scrollbar.\n Firefox thin scrollbar; degrades gracefully elsewhere. */\n scrollbar-width: thin;\n scrollbar-color: var(--tempest-border) transparent;\n overscroll-behavior: contain;\n}\n\n.root::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.root::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.root::-webkit-scrollbar-thumb {\n background-color: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .root::-webkit-scrollbar-thumb:hover {\n background-color: var(--tempest-text-muted);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"ScrollArea.module.cjs","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.module.css"],"sourcesContent":[".root {\n /* Overflow is set inline per orientation; this only styles the scrollbar.\n Firefox thin scrollbar; degrades gracefully elsewhere. */\n scrollbar-width: thin;\n scrollbar-color: var(--tempest-border) transparent;\n overscroll-behavior: contain;\n}\n\n.root::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.root::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.root::-webkit-scrollbar-thumb {\n background-color: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .root::-webkit-scrollbar-thumb:hover {\n background-color: var(--tempest-text-muted);\n }\n}\n\n.root:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: 2px;\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollArea.module.js","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.module.css"],"sourcesContent":[".root {\n /* Overflow is set inline per orientation; this only styles the scrollbar.\n Firefox thin scrollbar; degrades gracefully elsewhere. */\n scrollbar-width: thin;\n scrollbar-color: var(--tempest-border) transparent;\n overscroll-behavior: contain;\n}\n\n.root::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.root::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.root::-webkit-scrollbar-thumb {\n background-color: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .root::-webkit-scrollbar-thumb:hover {\n background-color: var(--tempest-text-muted);\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"ScrollArea.module.js","names":[],"sources":["../../../src/components/ScrollArea/ScrollArea.module.css"],"sourcesContent":[".root {\n /* Overflow is set inline per orientation; this only styles the scrollbar.\n Firefox thin scrollbar; degrades gracefully elsewhere. */\n scrollbar-width: thin;\n scrollbar-color: var(--tempest-border) transparent;\n overscroll-behavior: contain;\n}\n\n.root::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.root::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.root::-webkit-scrollbar-thumb {\n background-color: var(--tempest-border);\n border-radius: var(--tempest-radius-full);\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .root::-webkit-scrollbar-thumb:hover {\n background-color: var(--tempest-text-muted);\n }\n}\n\n.root:focus-visible {\n outline: var(--tempest-focus-ring-width) solid var(--tempest-focus-ring-color);\n outline-offset: 2px;\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("./sparkline-geometry.cjs"),n=require("./Sparkline.module.cjs");let r=require("react/jsx-runtime");function i({data:i,width:a=88,height:o=24,variant:s=`line`,color:c=`var(--tempest-chart-1)`,showEnd:l,min:u,max:d,valueFormatter:f,label:p,className:m,...h}){let g=t.sparkPoints({values:i,width:a,height:o,min:u,max:d}),_=p??t.describeSeries(i,f),v=l??s!==`bar`,y=g[g.length-1],b=o-2;return(0,r.jsx)(`span`,{className:e.cn(n.default.wrapper,m),...h,children:(0,r.jsxs)(`svg`,{className:n.default.svg,width:a,height:o,viewBox:`0 0 ${a} ${o}`,role:`img`,"aria-label":_,children:[g.length>0&&s===`area`&&(0,r.jsx)(`path`,{d:t.areaPath(g,b),fill:c,className:n.default.area}),g.length>0&&s!==`bar`&&(0,r.jsx)(`path`,{d:t.linePath(g),fill:`none`,stroke:c,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`}),s===`bar`&&t.barRects(g,{width:a,baselineY:b}).map(e=>(0,r.jsx)(`rect`,{x:e.x,y:e.y,width:e.width,height:e.height,rx:1,fill:c},e.point.index)),v&&y&&(0,r.jsx)(`circle`,{cx:y.x,cy:y.y,r:3,fill:c,stroke:`var(--tempest-bg)`,strokeWidth:2})]})})}exports.Sparkline=i;
2
+ //# sourceMappingURL=Sparkline.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sparkline.cjs","names":[],"sources":["../../../src/components/Sparkline/Sparkline.tsx"],"sourcesContent":["import type { HTMLAttributes } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { areaPath, barRects, describeSeries, linePath, sparkPoints } from \"./sparkline-geometry\";\nimport styles from \"./Sparkline.module.css\";\n\nexport type SparklineVariant = \"line\" | \"area\" | \"bar\";\n\nexport interface SparklineProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The series, in order. Non-finite entries are skipped. */\n data: readonly number[];\n /** Drawing width in px. Default `88`. */\n width?: number;\n /** Drawing height in px. Default `24`. */\n height?: number;\n /** Which mark to draw. Default `\"line\"`. */\n variant?: SparklineVariant;\n /** Any CSS colour. Defaults to the first chart series token. */\n color?: string;\n /** Draw a dot on the last point. Default `true` for line and area. */\n showEnd?: boolean;\n /** Force the low end of the value axis — use it to compare several sparklines. */\n min?: number;\n /** Force the high end. */\n max?: number;\n /** Render a value; used in the accessible description. */\n valueFormatter?: (value: number) => string;\n /**\n * Accessible name. Defaults to a sentence describing the series.\n *\n * Pass one when the surrounding text already says what is plotted — the default\n * still appends the shape, so a caller never ends up with an unlabelled image.\n */\n label?: string;\n}\n\n/**\n * A tiny inline chart: shape only, no axes, no legend.\n *\n * Sized to sit in a table cell or beside a number, which is why it is plain SVG on\n * the root entry rather than a recharts wrapper — a sparkline in a data table should\n * not oblige an app to install a charting library.\n *\n * It carries `role=\"img\"` and a spoken description of the series (direction, ends,\n * extremes). Without that it is an unlabelled image: a sparkline has no axis or\n * legend to fall back on, so a screen reader would reach it and read nothing. Pair it\n * with the number it annotates — the shape is context, never the only way to read the\n * value.\n *\n * @example\n * <Sparkline data={last30Days} />\n * <Sparkline data={revenue} variant=\"area\" width={120} height={32} />\n *\n * // Same axis across a column, so the rows are comparable\n * <Sparkline data={row.series} min={0} max={columnMax} />\n */\nexport function Sparkline({\n data,\n width = 88,\n height = 24,\n variant = \"line\",\n color = \"var(--tempest-chart-1)\",\n showEnd,\n min,\n max,\n valueFormatter,\n label,\n className,\n ...rest\n}: SparklineProps) {\n const points = sparkPoints({ values: data, width, height, min, max });\n const description = label ?? describeSeries(data, valueFormatter);\n const withEnd = showEnd ?? variant !== \"bar\";\n const last = points[points.length - 1];\n\n /*\n * Bars grow from the bottom of the box; line and area close against it. Both use\n * the same edge so a sparkline swapped between variants keeps its footprint.\n */\n const baselineY = height - 2;\n\n return (\n <span className={cn(styles.wrapper, className)} {...rest}>\n <svg\n className={styles.svg}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label={description}\n >\n {points.length > 0 && variant === \"area\" && (\n <path d={areaPath(points, baselineY)} fill={color} className={styles.area} />\n )}\n\n {points.length > 0 && variant !== \"bar\" && (\n <path\n d={linePath(points)}\n fill=\"none\"\n stroke={color}\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n )}\n\n {variant === \"bar\" &&\n barRects(points, { width, baselineY }).map((rect) => (\n <rect\n key={rect.point.index}\n x={rect.x}\n y={rect.y}\n width={rect.width}\n height={rect.height}\n rx={1}\n fill={color}\n />\n ))}\n\n {withEnd && last && (\n /*\n * The ring is drawn in the surface colour rather than as a stroke on\n * the dot: it keeps the marker legible where it sits on top of the\n * line without adding ink that is not data.\n */\n <circle\n cx={last.x}\n cy={last.y}\n r={3}\n fill={color}\n stroke=\"var(--tempest-bg)\"\n strokeWidth={2}\n />\n )}\n </svg>\n </span>\n );\n}\n"],"mappings":"mJAyDA,SAAgB,EAAU,CACtB,OACA,QAAQ,GACR,SAAS,GACT,UAAU,OACV,QAAQ,yBACR,UACA,MACA,MACA,iBACA,QACA,YACA,GAAG,GACY,CACf,IAAM,EAAS,EAAA,YAAY,CAAE,OAAQ,EAAM,QAAO,SAAQ,MAAK,KAAI,CAAC,EAC9D,EAAc,GAAS,EAAA,eAAe,EAAM,CAAc,EAC1D,EAAU,GAAW,IAAY,MACjC,EAAO,EAAO,EAAO,OAAS,GAM9B,EAAY,EAAS,EAE3B,OACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EAAG,GAAI,YAChD,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,IACX,QACC,SACR,QAAS,OAAO,EAAM,GAAG,IACzB,KAAK,MACL,aAAY,WANhB,CAQK,EAAO,OAAS,GAAK,IAAY,SAC9B,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,EAAG,EAAA,SAAS,EAAQ,CAAS,EAAG,KAAM,EAAO,UAAW,EAAA,QAAO,IAAO,CAAA,EAG/E,EAAO,OAAS,GAAK,IAAY,QAC9B,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAG,EAAA,SAAS,CAAM,EAClB,KAAK,OACL,OAAQ,EACR,YAAa,EACb,cAAc,QACd,eAAe,OAClB,CAAA,EAGJ,IAAY,OACT,EAAA,SAAS,EAAQ,CAAE,QAAO,WAAU,CAAC,CAAC,CAAC,IAAK,IACxC,EAAA,EAAA,IAAA,CAAC,OAAD,CAEI,EAAG,EAAK,EACR,EAAG,EAAK,EACR,MAAO,EAAK,MACZ,OAAQ,EAAK,OACb,GAAI,EACJ,KAAM,CACT,EAPQ,EAAK,MAAM,KAOnB,CACJ,EAEJ,GAAW,IAMR,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,GAAI,EAAK,EACT,GAAI,EAAK,EACT,EAAG,EACH,KAAM,EACN,OAAO,oBACP,YAAa,CAChB,CAAA,CAEJ,GACH,CAAA,CAEd"}
@@ -0,0 +1,64 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { areaPath as t, barRects as n, describeSeries as r, linePath as i, sparkPoints as a } from "./sparkline-geometry.js";
3
+ import o from "./Sparkline.module.js";
4
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
5
+ //#region src/components/Sparkline/Sparkline.tsx
6
+ function l({ data: l, width: u = 88, height: d = 24, variant: f = "line", color: p = "var(--tempest-chart-1)", showEnd: m, min: h, max: g, valueFormatter: _, label: v, className: y, ...b }) {
7
+ let x = a({
8
+ values: l,
9
+ width: u,
10
+ height: d,
11
+ min: h,
12
+ max: g
13
+ }), S = v ?? r(l, _), C = m ?? f !== "bar", w = x[x.length - 1], T = d - 2;
14
+ return /* @__PURE__ */ s("span", {
15
+ className: e(o.wrapper, y),
16
+ ...b,
17
+ children: /* @__PURE__ */ c("svg", {
18
+ className: o.svg,
19
+ width: u,
20
+ height: d,
21
+ viewBox: `0 0 ${u} ${d}`,
22
+ role: "img",
23
+ "aria-label": S,
24
+ children: [
25
+ x.length > 0 && f === "area" && /* @__PURE__ */ s("path", {
26
+ d: t(x, T),
27
+ fill: p,
28
+ className: o.area
29
+ }),
30
+ x.length > 0 && f !== "bar" && /* @__PURE__ */ s("path", {
31
+ d: i(x),
32
+ fill: "none",
33
+ stroke: p,
34
+ strokeWidth: 2,
35
+ strokeLinecap: "round",
36
+ strokeLinejoin: "round"
37
+ }),
38
+ f === "bar" && n(x, {
39
+ width: u,
40
+ baselineY: T
41
+ }).map((e) => /* @__PURE__ */ s("rect", {
42
+ x: e.x,
43
+ y: e.y,
44
+ width: e.width,
45
+ height: e.height,
46
+ rx: 1,
47
+ fill: p
48
+ }, e.point.index)),
49
+ C && w && /* @__PURE__ */ s("circle", {
50
+ cx: w.x,
51
+ cy: w.y,
52
+ r: 3,
53
+ fill: p,
54
+ stroke: "var(--tempest-bg)",
55
+ strokeWidth: 2
56
+ })
57
+ ]
58
+ })
59
+ });
60
+ }
61
+ //#endregion
62
+ export { l as Sparkline };
63
+
64
+ //# sourceMappingURL=Sparkline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sparkline.js","names":[],"sources":["../../../src/components/Sparkline/Sparkline.tsx"],"sourcesContent":["import type { HTMLAttributes } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { areaPath, barRects, describeSeries, linePath, sparkPoints } from \"./sparkline-geometry\";\nimport styles from \"./Sparkline.module.css\";\n\nexport type SparklineVariant = \"line\" | \"area\" | \"bar\";\n\nexport interface SparklineProps extends Omit<HTMLAttributes<HTMLSpanElement>, \"children\"> {\n /** The series, in order. Non-finite entries are skipped. */\n data: readonly number[];\n /** Drawing width in px. Default `88`. */\n width?: number;\n /** Drawing height in px. Default `24`. */\n height?: number;\n /** Which mark to draw. Default `\"line\"`. */\n variant?: SparklineVariant;\n /** Any CSS colour. Defaults to the first chart series token. */\n color?: string;\n /** Draw a dot on the last point. Default `true` for line and area. */\n showEnd?: boolean;\n /** Force the low end of the value axis — use it to compare several sparklines. */\n min?: number;\n /** Force the high end. */\n max?: number;\n /** Render a value; used in the accessible description. */\n valueFormatter?: (value: number) => string;\n /**\n * Accessible name. Defaults to a sentence describing the series.\n *\n * Pass one when the surrounding text already says what is plotted — the default\n * still appends the shape, so a caller never ends up with an unlabelled image.\n */\n label?: string;\n}\n\n/**\n * A tiny inline chart: shape only, no axes, no legend.\n *\n * Sized to sit in a table cell or beside a number, which is why it is plain SVG on\n * the root entry rather than a recharts wrapper — a sparkline in a data table should\n * not oblige an app to install a charting library.\n *\n * It carries `role=\"img\"` and a spoken description of the series (direction, ends,\n * extremes). Without that it is an unlabelled image: a sparkline has no axis or\n * legend to fall back on, so a screen reader would reach it and read nothing. Pair it\n * with the number it annotates — the shape is context, never the only way to read the\n * value.\n *\n * @example\n * <Sparkline data={last30Days} />\n * <Sparkline data={revenue} variant=\"area\" width={120} height={32} />\n *\n * // Same axis across a column, so the rows are comparable\n * <Sparkline data={row.series} min={0} max={columnMax} />\n */\nexport function Sparkline({\n data,\n width = 88,\n height = 24,\n variant = \"line\",\n color = \"var(--tempest-chart-1)\",\n showEnd,\n min,\n max,\n valueFormatter,\n label,\n className,\n ...rest\n}: SparklineProps) {\n const points = sparkPoints({ values: data, width, height, min, max });\n const description = label ?? describeSeries(data, valueFormatter);\n const withEnd = showEnd ?? variant !== \"bar\";\n const last = points[points.length - 1];\n\n /*\n * Bars grow from the bottom of the box; line and area close against it. Both use\n * the same edge so a sparkline swapped between variants keeps its footprint.\n */\n const baselineY = height - 2;\n\n return (\n <span className={cn(styles.wrapper, className)} {...rest}>\n <svg\n className={styles.svg}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label={description}\n >\n {points.length > 0 && variant === \"area\" && (\n <path d={areaPath(points, baselineY)} fill={color} className={styles.area} />\n )}\n\n {points.length > 0 && variant !== \"bar\" && (\n <path\n d={linePath(points)}\n fill=\"none\"\n stroke={color}\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n )}\n\n {variant === \"bar\" &&\n barRects(points, { width, baselineY }).map((rect) => (\n <rect\n key={rect.point.index}\n x={rect.x}\n y={rect.y}\n width={rect.width}\n height={rect.height}\n rx={1}\n fill={color}\n />\n ))}\n\n {withEnd && last && (\n /*\n * The ring is drawn in the surface colour rather than as a stroke on\n * the dot: it keeps the marker legible where it sits on top of the\n * line without adding ink that is not data.\n */\n <circle\n cx={last.x}\n cy={last.y}\n r={3}\n fill={color}\n stroke=\"var(--tempest-bg)\"\n strokeWidth={2}\n />\n )}\n </svg>\n </span>\n );\n}\n"],"mappings":";;;;;AAyDA,SAAgB,EAAU,EACtB,SACA,WAAQ,IACR,YAAS,IACT,aAAU,QACV,WAAQ,0BACR,YACA,QACA,QACA,mBACA,UACA,cACA,GAAG,KACY;CACf,IAAM,IAAS,EAAY;EAAE,QAAQ;EAAM;EAAO;EAAQ;EAAK;CAAI,CAAC,GAC9D,IAAc,KAAS,EAAe,GAAM,CAAc,GAC1D,IAAU,KAAW,MAAY,OACjC,IAAO,EAAO,EAAO,SAAS,IAM9B,IAAY,IAAS;CAE3B,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,SAAS,CAAS;EAAG,GAAI;YAChD,kBAAC,OAAD;GACI,WAAW,EAAO;GACX;GACC;GACR,SAAS,OAAO,EAAM,GAAG;GACzB,MAAK;GACL,cAAY;aANhB;IAQK,EAAO,SAAS,KAAK,MAAY,UAC9B,kBAAC,QAAD;KAAM,GAAG,EAAS,GAAQ,CAAS;KAAG,MAAM;KAAO,WAAW,EAAO;IAAO,CAAA;IAG/E,EAAO,SAAS,KAAK,MAAY,SAC9B,kBAAC,QAAD;KACI,GAAG,EAAS,CAAM;KAClB,MAAK;KACL,QAAQ;KACR,aAAa;KACb,eAAc;KACd,gBAAe;IAClB,CAAA;IAGJ,MAAY,SACT,EAAS,GAAQ;KAAE;KAAO;IAAU,CAAC,CAAC,CAAC,KAAK,MACxC,kBAAC,QAAD;KAEI,GAAG,EAAK;KACR,GAAG,EAAK;KACR,OAAO,EAAK;KACZ,QAAQ,EAAK;KACb,IAAI;KACJ,MAAM;IACT,GAPQ,EAAK,MAAM,KAOnB,CACJ;IAEJ,KAAW,KAMR,kBAAC,UAAD;KACI,IAAI,EAAK;KACT,IAAI,EAAK;KACT,GAAG;KACH,MAAM;KACN,QAAO;KACP,aAAa;IAChB,CAAA;GAEJ;;CACH,CAAA;AAEd"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_wrapper_f24dA`,t=`tempest_svg_mpG9R`,n=`tempest_area_-kPmZ`,r={wrapper:e,svg:t,area:n};exports.area=n,exports.default=r,exports.svg=t,exports.wrapper=e;
2
+ //# sourceMappingURL=Sparkline.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sparkline.module.cjs","names":[],"sources":["../../../src/components/Sparkline/Sparkline.module.css"],"sourcesContent":[".wrapper {\n display: inline-flex;\n align-items: center;\n /*\n * `middle` rather than the inline default: a sparkline sits beside a number, and\n * a baseline-aligned SVG hangs low enough to look like it fell off the line.\n */\n vertical-align: middle;\n line-height: 0;\n}\n\n.svg {\n display: block;\n overflow: visible;\n}\n\n/*\n * A wash, never a saturated block — the fill is context for the line, and at full\n * strength it competes with the mark that actually carries the data.\n */\n.area {\n opacity: 0.12;\n}\n"],"mappings":""}
@@ -0,0 +1,10 @@
1
+ //#region src/components/Sparkline/Sparkline.module.css
2
+ var e = "tempest_wrapper_f24dA", t = "tempest_svg_mpG9R", n = "tempest_area_-kPmZ", r = {
3
+ wrapper: e,
4
+ svg: t,
5
+ area: n
6
+ };
7
+ //#endregion
8
+ export { n as area, r as default, t as svg, e as wrapper };
9
+
10
+ //# sourceMappingURL=Sparkline.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sparkline.module.js","names":[],"sources":["../../../src/components/Sparkline/Sparkline.module.css"],"sourcesContent":[".wrapper {\n display: inline-flex;\n align-items: center;\n /*\n * `middle` rather than the inline default: a sparkline sits beside a number, and\n * a baseline-aligned SVG hangs low enough to look like it fell off the line.\n */\n vertical-align: middle;\n line-height: 0;\n}\n\n.svg {\n display: block;\n overflow: visible;\n}\n\n/*\n * A wash, never a saturated block — the fill is context for the line, and at full\n * strength it competes with the mark that actually carries the data.\n */\n.area {\n opacity: 0.12;\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ function e({values:e,width:t,height:n,padding:r=2,min:i,max:a}){let o=e.map((e,t)=>({value:e,index:t})).filter(e=>Number.isFinite(e.value));if(o.length===0||t<=0||n<=0)return[];let s=o.map(e=>e.value),c=i??Math.min(...s),l=(a??Math.max(...s))-c,u=Math.max(0,t-r*2),d=Math.max(0,n-r*2),f=o.length-1;return o.map((e,t)=>{let n=f===0?.5:t/f,i=l===0?.5:(e.value-c)/l;return{x:r+n*u,y:r+(1-Math.min(1,Math.max(0,i)))*d,value:e.value,index:e.index}})}function t(e){if(e.length===0)return``;if(e.length===1){let[t]=e;return`M ${t.x} ${t.y} L ${t.x} ${t.y}`}return e.map((e,t)=>`${t===0?`M`:`L`} ${e.x} ${e.y}`).join(` `)}function n(e,t){if(e.length===0)return``;let n=e.map((e,t)=>`${t===0?`M`:`L`} ${e.x} ${e.y}`).join(` `),r=e[0];return`${n} L ${e[e.length-1].x} ${t} L ${r.x} ${t} Z`}function r(e,{width:t,baselineY:n,gap:r=2}){if(e.length===0)return[];let i=t/e.length,a=Math.max(1,Math.min(24,i-r));return e.map(e=>{let t=Math.max(1,Math.abs(n-e.y));return{x:e.x-a/2,y:Math.min(e.y,n-t),width:a,height:t,point:e}})}function i(e,t=String){let n=e.filter(e=>Number.isFinite(e));if(n.length===0)return`Sem dados`;if(n.length===1)return`Valor único: ${t(n[0])}`;let r=n[0],i=n[n.length-1],a=i>r?`subindo`:i<r?`descendo`:`estável`;return`${n.length} pontos, ${a}. Início ${t(r)}, fim ${t(i)}. Mínimo ${t(Math.min(...n))}, máximo ${t(Math.max(...n))}.`}exports.areaPath=n,exports.barRects=r,exports.describeSeries=i,exports.linePath=t,exports.sparkPoints=e;
2
+ //# sourceMappingURL=sparkline-geometry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sparkline-geometry.cjs","names":[],"sources":["../../../src/components/Sparkline/sparkline-geometry.ts"],"sourcesContent":["/** A point in the sparkline's own coordinate space. */\nexport interface SparkPoint {\n x: number;\n y: number;\n /** The datum this point came from. */\n value: number;\n /** Its position in the input series. */\n index: number;\n}\n\nexport interface SparkGeometryOptions {\n values: readonly number[];\n width: number;\n height: number;\n /** Room reserved on every side for the end marker's ring, so it is never clipped. */\n padding?: number;\n /** Force the low end of the value axis. Defaults to the series minimum. */\n min?: number;\n /** Force the high end. Defaults to the series maximum. */\n max?: number;\n}\n\n/**\n * Project a series onto the drawing box.\n *\n * `y` is inverted — SVG grows downward and a chart grows upward — and a flat series\n * is centred rather than pinned to an edge, which is the honest reading of \"no\n * variation\" and avoids dividing by a zero-height domain.\n *\n * Non-finite values are dropped rather than drawn: a `NaN` in the middle of a path\n * silently voids the whole `d` attribute and the sparkline disappears with no error.\n *\n * @param options - The series and the box to fit it in.\n * @returns The projected points, in input order.\n */\nexport function sparkPoints({\n values,\n width,\n height,\n padding = 2,\n min,\n max,\n}: SparkGeometryOptions): SparkPoint[] {\n const usable = values\n .map((value, index) => ({ value, index }))\n .filter((item) => Number.isFinite(item.value));\n if (usable.length === 0 || width <= 0 || height <= 0) return [];\n\n const numbers = usable.map((item) => item.value);\n const lo = min ?? Math.min(...numbers);\n const hi = max ?? Math.max(...numbers);\n const span = hi - lo;\n\n const innerW = Math.max(0, width - padding * 2);\n const innerH = Math.max(0, height - padding * 2);\n const lastIndex = usable.length - 1;\n\n return usable.map((item, i) => {\n const t = lastIndex === 0 ? 0.5 : i / lastIndex;\n const v = span === 0 ? 0.5 : (item.value - lo) / span;\n return {\n x: padding + t * innerW,\n y: padding + (1 - Math.min(1, Math.max(0, v))) * innerH,\n value: item.value,\n index: item.index,\n };\n });\n}\n\n/** An SVG `d` for a polyline through the points. */\nexport function linePath(points: readonly SparkPoint[]): string {\n if (points.length === 0) return \"\";\n if (points.length === 1) {\n // A lone point has no line; emit a zero-length segment so the round cap\n // still paints a dot rather than nothing at all.\n const [only] = points;\n return `M ${only.x} ${only.y} L ${only.x} ${only.y}`;\n }\n return points.map((p, i) => `${i === 0 ? \"M\" : \"L\"} ${p.x} ${p.y}`).join(\" \");\n}\n\n/**\n * An SVG `d` for the area under the line, closed along the baseline.\n *\n * @param points - The projected points.\n * @param baselineY - Where the fill closes, in the same space as the points.\n */\nexport function areaPath(points: readonly SparkPoint[], baselineY: number): string {\n if (points.length === 0) return \"\";\n const line = points.map((p, i) => `${i === 0 ? \"M\" : \"L\"} ${p.x} ${p.y}`).join(\" \");\n const first = points[0];\n const last = points[points.length - 1];\n return `${line} L ${last.x} ${baselineY} L ${first.x} ${baselineY} Z`;\n}\n\n/** Bar rectangles growing from the baseline, with the band's leftover left as air. */\nexport function barRects(\n points: readonly SparkPoint[],\n { width, baselineY, gap = 2 }: { width: number; baselineY: number; gap?: number },\n): { x: number; y: number; width: number; height: number; point: SparkPoint }[] {\n if (points.length === 0) return [];\n const band = width / points.length;\n // Never fill the slot: the gap between bars is what separates them, not a stroke.\n const barWidth = Math.max(1, Math.min(24, band - gap));\n return points.map((point) => {\n /*\n * The minimum value sits on the baseline, where the raw height is 0. It still\n * needs a visible sliver — but growing *up* from the baseline, not down: the\n * naive `Math.max(1, …)` on height alone leaves the bar hanging one pixel\n * below the axis it is supposed to stand on.\n */\n const height = Math.max(1, Math.abs(baselineY - point.y));\n return {\n x: point.x - barWidth / 2,\n y: Math.min(point.y, baselineY - height),\n width: barWidth,\n height,\n point,\n };\n });\n}\n\n/**\n * A one-sentence description of the series, for the chart's accessible name.\n *\n * A sparkline has no axes and no legend, so without this it is an unlabelled image:\n * a screen reader reaches it and reads nothing. Direction is stated in words rather\n * than implied by the shape.\n *\n * @param values - The series, in order.\n * @param format - How to render a single value.\n * @returns Text suitable for `aria-label`.\n */\nexport function describeSeries(\n values: readonly number[],\n format: (value: number) => string = String,\n): string {\n const usable = values.filter((v) => Number.isFinite(v));\n if (usable.length === 0) return \"Sem dados\";\n if (usable.length === 1) return `Valor único: ${format(usable[0])}`;\n\n const first = usable[0];\n const last = usable[usable.length - 1];\n const direction = last > first ? \"subindo\" : last < first ? \"descendo\" : \"estável\";\n return (\n `${usable.length} pontos, ${direction}. ` +\n `Início ${format(first)}, fim ${format(last)}. ` +\n `Mínimo ${format(Math.min(...usable))}, máximo ${format(Math.max(...usable))}.`\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ //#region src/components/Sparkline/sparkline-geometry.ts
2
+ function e({ values: e, width: t, height: n, padding: r = 2, min: i, max: a }) {
3
+ let o = e.map((e, t) => ({
4
+ value: e,
5
+ index: t
6
+ })).filter((e) => Number.isFinite(e.value));
7
+ if (o.length === 0 || t <= 0 || n <= 0) return [];
8
+ let s = o.map((e) => e.value), c = i ?? Math.min(...s), l = (a ?? Math.max(...s)) - c, u = Math.max(0, t - r * 2), d = Math.max(0, n - r * 2), f = o.length - 1;
9
+ return o.map((e, t) => {
10
+ let n = f === 0 ? .5 : t / f, i = l === 0 ? .5 : (e.value - c) / l;
11
+ return {
12
+ x: r + n * u,
13
+ y: r + (1 - Math.min(1, Math.max(0, i))) * d,
14
+ value: e.value,
15
+ index: e.index
16
+ };
17
+ });
18
+ }
19
+ function t(e) {
20
+ if (e.length === 0) return "";
21
+ if (e.length === 1) {
22
+ let [t] = e;
23
+ return `M ${t.x} ${t.y} L ${t.x} ${t.y}`;
24
+ }
25
+ return e.map((e, t) => `${t === 0 ? "M" : "L"} ${e.x} ${e.y}`).join(" ");
26
+ }
27
+ function n(e, t) {
28
+ if (e.length === 0) return "";
29
+ let n = e.map((e, t) => `${t === 0 ? "M" : "L"} ${e.x} ${e.y}`).join(" "), r = e[0];
30
+ return `${n} L ${e[e.length - 1].x} ${t} L ${r.x} ${t} Z`;
31
+ }
32
+ function r(e, { width: t, baselineY: n, gap: r = 2 }) {
33
+ if (e.length === 0) return [];
34
+ let i = t / e.length, a = Math.max(1, Math.min(24, i - r));
35
+ return e.map((e) => {
36
+ let t = Math.max(1, Math.abs(n - e.y));
37
+ return {
38
+ x: e.x - a / 2,
39
+ y: Math.min(e.y, n - t),
40
+ width: a,
41
+ height: t,
42
+ point: e
43
+ };
44
+ });
45
+ }
46
+ function i(e, t = String) {
47
+ let n = e.filter((e) => Number.isFinite(e));
48
+ if (n.length === 0) return "Sem dados";
49
+ if (n.length === 1) return `Valor único: ${t(n[0])}`;
50
+ let r = n[0], i = n[n.length - 1], a = i > r ? "subindo" : i < r ? "descendo" : "estável";
51
+ return `${n.length} pontos, ${a}. Início ${t(r)}, fim ${t(i)}. Mínimo ${t(Math.min(...n))}, máximo ${t(Math.max(...n))}.`;
52
+ }
53
+ //#endregion
54
+ export { n as areaPath, r as barRects, i as describeSeries, t as linePath, e as sparkPoints };
55
+
56
+ //# sourceMappingURL=sparkline-geometry.js.map