@charcuterie/ui 3.0.0 → 3.2.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 (130) hide show
  1. package/README.md +10 -0
  2. package/dist/Badge/Badge.d.ts +33 -3
  3. package/dist/Badge/Badge.d.ts.map +1 -1
  4. package/dist/Badge/Badge.js +20 -2
  5. package/dist/Badge/Badge.js.map +1 -1
  6. package/dist/Board/Board.d.ts +119 -0
  7. package/dist/Board/Board.d.ts.map +1 -0
  8. package/dist/Board/Board.js +165 -0
  9. package/dist/Board/Board.js.map +1 -0
  10. package/dist/Board/BoardCard.d.ts +120 -0
  11. package/dist/Board/BoardCard.d.ts.map +1 -0
  12. package/dist/Board/BoardCard.js +77 -0
  13. package/dist/Board/BoardCard.js.map +1 -0
  14. package/dist/Board/BoardLaneList.d.ts +103 -0
  15. package/dist/Board/BoardLaneList.d.ts.map +1 -0
  16. package/dist/Board/BoardLaneList.js +69 -0
  17. package/dist/Board/BoardLaneList.js.map +1 -0
  18. package/dist/Board/boardMove.d.ts +116 -0
  19. package/dist/Board/boardMove.d.ts.map +1 -0
  20. package/dist/Board/boardMove.js +116 -0
  21. package/dist/Board/boardMove.js.map +1 -0
  22. package/dist/Board/useBoardDrag.d.ts +44 -0
  23. package/dist/Board/useBoardDrag.d.ts.map +1 -0
  24. package/dist/Board/useBoardDrag.js +237 -0
  25. package/dist/Board/useBoardDrag.js.map +1 -0
  26. package/dist/Checkbox/Checkbox.d.ts.map +1 -1
  27. package/dist/Checkbox/Checkbox.js +15 -4
  28. package/dist/Checkbox/Checkbox.js.map +1 -1
  29. package/dist/DataTable/DataTable.d.ts +159 -0
  30. package/dist/DataTable/DataTable.d.ts.map +1 -0
  31. package/dist/DataTable/DataTable.js +197 -0
  32. package/dist/DataTable/DataTable.js.map +1 -0
  33. package/dist/DatePicker/DateGrid.d.ts +79 -0
  34. package/dist/DatePicker/DateGrid.d.ts.map +1 -0
  35. package/dist/DatePicker/DateGrid.js +220 -0
  36. package/dist/DatePicker/DateGrid.js.map +1 -0
  37. package/dist/DatePicker/DatePicker.d.ts +138 -0
  38. package/dist/DatePicker/DatePicker.d.ts.map +1 -0
  39. package/dist/DatePicker/DatePicker.js +384 -0
  40. package/dist/DatePicker/DatePicker.js.map +1 -0
  41. package/dist/DatePicker/datePresets.d.ts +29 -0
  42. package/dist/DatePicker/datePresets.d.ts.map +1 -0
  43. package/dist/DatePicker/datePresets.js +28 -0
  44. package/dist/DatePicker/datePresets.js.map +1 -0
  45. package/dist/DatePicker/parseDateInput.d.ts +106 -0
  46. package/dist/DatePicker/parseDateInput.d.ts.map +1 -0
  47. package/dist/DatePicker/parseDateInput.js +419 -0
  48. package/dist/DatePicker/parseDateInput.js.map +1 -0
  49. package/dist/DatePicker/plainDate.d.ts +167 -0
  50. package/dist/DatePicker/plainDate.d.ts.map +1 -0
  51. package/dist/DatePicker/plainDate.js +272 -0
  52. package/dist/DatePicker/plainDate.js.map +1 -0
  53. package/dist/Dialog/Dialog.d.ts +12 -2
  54. package/dist/Dialog/Dialog.d.ts.map +1 -1
  55. package/dist/Dialog/Dialog.js +2 -2
  56. package/dist/Dialog/Dialog.js.map +1 -1
  57. package/dist/MarkdownEditor/MarkdownEditor.d.ts +147 -0
  58. package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
  59. package/dist/MarkdownEditor/MarkdownEditor.js +492 -0
  60. package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -0
  61. package/dist/MarkdownEditor/markdownCommands.d.ts +202 -0
  62. package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -0
  63. package/dist/MarkdownEditor/markdownCommands.js +379 -0
  64. package/dist/MarkdownEditor/markdownCommands.js.map +1 -0
  65. package/dist/MarkdownEditor/markdownSpans.d.ts +92 -0
  66. package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -0
  67. package/dist/MarkdownEditor/markdownSpans.js +303 -0
  68. package/dist/MarkdownEditor/markdownSpans.js.map +1 -0
  69. package/dist/Modal/Modal.d.ts +6 -2
  70. package/dist/Modal/Modal.d.ts.map +1 -1
  71. package/dist/Modal/Modal.js +1 -1
  72. package/dist/Modal/Modal.js.map +1 -1
  73. package/dist/Overlay/OverlayPanel.d.ts +13 -2
  74. package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
  75. package/dist/Overlay/OverlayPanel.js +5 -1
  76. package/dist/Overlay/OverlayPanel.js.map +1 -1
  77. package/dist/categoricalStyles.d.ts +53 -0
  78. package/dist/categoricalStyles.d.ts.map +1 -0
  79. package/dist/categoricalStyles.js +195 -0
  80. package/dist/categoricalStyles.js.map +1 -0
  81. package/dist/index.d.ts +22 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +11 -0
  84. package/dist/index.js.map +1 -1
  85. package/package.json +2 -2
  86. package/src/Badge/Badge.mdx +46 -0
  87. package/src/Badge/Badge.stories.tsx +98 -4
  88. package/src/Badge/Badge.test.tsx +157 -2
  89. package/src/Badge/Badge.tsx +49 -3
  90. package/src/Board/Board.mdx +199 -0
  91. package/src/Board/Board.stories.tsx +707 -0
  92. package/src/Board/Board.test.tsx +310 -0
  93. package/src/Board/Board.tsx +333 -0
  94. package/src/Board/BoardCard.tsx +290 -0
  95. package/src/Board/BoardLaneList.tsx +275 -0
  96. package/src/Board/boardMove.test.ts +213 -0
  97. package/src/Board/boardMove.ts +225 -0
  98. package/src/Board/useBoardDrag.ts +413 -0
  99. package/src/Checkbox/Checkbox.tsx +34 -3
  100. package/src/DataTable/DataTable.mdx +123 -0
  101. package/src/DataTable/DataTable.stories.tsx +411 -0
  102. package/src/DataTable/DataTable.test.tsx +386 -0
  103. package/src/DataTable/DataTable.tsx +497 -0
  104. package/src/DatePicker/DateGrid.tsx +467 -0
  105. package/src/DatePicker/DatePicker.mdx +170 -0
  106. package/src/DatePicker/DatePicker.stories.tsx +268 -0
  107. package/src/DatePicker/DatePicker.test.tsx +455 -0
  108. package/src/DatePicker/DatePicker.tsx +852 -0
  109. package/src/DatePicker/datePresets.ts +34 -0
  110. package/src/DatePicker/parseDateInput.test.ts +187 -0
  111. package/src/DatePicker/parseDateInput.ts +629 -0
  112. package/src/DatePicker/plainDate.test.ts +375 -0
  113. package/src/DatePicker/plainDate.ts +465 -0
  114. package/src/Dialog/Dialog.stories.tsx +48 -0
  115. package/src/Dialog/Dialog.test.tsx +30 -0
  116. package/src/Dialog/Dialog.tsx +13 -1
  117. package/src/MarkdownEditor/MarkdownEditor.mdx +189 -0
  118. package/src/MarkdownEditor/MarkdownEditor.stories.tsx +267 -0
  119. package/src/MarkdownEditor/MarkdownEditor.test.tsx +627 -0
  120. package/src/MarkdownEditor/MarkdownEditor.tsx +862 -0
  121. package/src/MarkdownEditor/markdownCommands.test.ts +384 -0
  122. package/src/MarkdownEditor/markdownCommands.ts +566 -0
  123. package/src/MarkdownEditor/markdownSpans.test.ts +217 -0
  124. package/src/MarkdownEditor/markdownSpans.ts +458 -0
  125. package/src/Modal/Modal.tsx +7 -1
  126. package/src/Overlay/OverlayPanel.tsx +17 -1
  127. package/src/argTypes.storyHelpers.ts +39 -0
  128. package/src/categoricalStyles.ts +244 -0
  129. package/src/index.ts +89 -0
  130. package/src/sourceRules.test.ts +52 -1
@@ -0,0 +1,167 @@
1
+ /**
2
+ * A calendar date, and deliberately not an instant.
3
+ *
4
+ * ### The bug this file exists to make impossible
5
+ *
6
+ * A due date is not a moment. "This is due on the 19th" is true in
7
+ * Denver and true in Tokyo, and it does not become the 18th because
8
+ * a laptop crossed a meridian. The classic date-picker defect is
9
+ * that the value is a `Date` — which is an instant, a count of
10
+ * milliseconds — and every read of it goes through the *browser's*
11
+ * timezone. Pick the 19th at 23:30 in Denver, store
12
+ * `new Date("2026-08-19").toISOString()`, and the server, the next
13
+ * device, and the same device after a flight all disagree about
14
+ * which day you meant. It is a one-day error, it only appears near
15
+ * midnight or after travel, and it is invisible in every test
16
+ * written at noon.
17
+ *
18
+ * So the value here is a **plain date**: a year, a month, and a day,
19
+ * with no time, no offset, and no zone. It is stored as an ISO
20
+ * `YYYY-MM-DD` string — which is exactly `<input type="date">`'s own
21
+ * `value` format, so a consumer can move between this component and
22
+ * the native control without a migration.
23
+ *
24
+ * ### There is no `Date` arithmetic in here at all
25
+ *
26
+ * Not "carefully-written `Date` arithmetic" — none. Every operation
27
+ * below is integer maths on a **day number**: the count of days
28
+ * since 1970-01-01, computed with Howard Hinnant's `days_from_civil`
29
+ * / `civil_from_days` (public domain, the algorithm C++20's
30
+ * `<chrono>` and Rust's `chrono` both use). Adding a day is `+ 1`.
31
+ * That is a proleptic Gregorian calendar with no DST, no leap
32
+ * seconds, and no zone, which is what a calendar date actually is.
33
+ *
34
+ * A `Date` appears in exactly two places, both of them named and
35
+ * both of them fenced:
36
+ *
37
+ * - `getLocalPlainDate`, which is the **one** conversion from an
38
+ * instant to a calendar date, and which requires you to say
39
+ * which zone's calendar you mean.
40
+ * - `formatPlainDate` and its callers, which hand `Intl` a date
41
+ * built at UTC midnight and format it with `timeZone: "UTC"`, so
42
+ * the formatter cannot shift the day either.
43
+ *
44
+ * `plainDate.test.ts` re-runs the whole suite under four `TZ`
45
+ * values, including UTC+14 and UTC-11, and asserts every answer is
46
+ * byte-identical.
47
+ *
48
+ * ### Why not `Temporal`
49
+ *
50
+ * `Temporal.PlainDate` is this type, done properly, and it is
51
+ * available in the Node this repo builds on. It is not available in
52
+ * every browser a published package has to run in, and the polyfill
53
+ * is ~50 KB gz — an order of magnitude more than this entire
54
+ * component. The functions below are the ~80 lines of it a date
55
+ * picker needs. When `Temporal` is baseline, this file becomes a
56
+ * thin adapter and the public API — ISO strings — does not change,
57
+ * which is the reason the API is ISO strings.
58
+ */
59
+ /** Year, month (1-12), day (1-31). No time, no zone. */
60
+ export type PlainDate = {
61
+ day: number;
62
+ month: number;
63
+ year: number;
64
+ };
65
+ /**
66
+ * A start and an end, either of which may be absent — because a
67
+ * half-picked range is a real state a user spends time in, not an
68
+ * error. `start` with a null `end` is "I have clicked once".
69
+ */
70
+ export type DateRange = {
71
+ end: null | string;
72
+ start: null | string;
73
+ };
74
+ export declare const getIsLeapYear: (year: number) => boolean;
75
+ export declare const getDaysInMonth: (year: number, month: number) => number;
76
+ export declare const getIsValidPlainDate: ({ day, month, year, }: PlainDate) => boolean;
77
+ /**
78
+ * Days since 1970-01-01, by Hinnant's `days_from_civil`.
79
+ *
80
+ * Every other operation in this file is expressed through this and
81
+ * its inverse, which is why "add a month, then add 40 days, then
82
+ * ask what weekday it is" cannot drift: there is one representation
83
+ * and it is an integer.
84
+ */
85
+ export declare const getDayNumber: ({ day, month, year, }: PlainDate) => number;
86
+ /** The inverse — Hinnant's `civil_from_days`. */
87
+ export declare const getPlainDateFromDayNumber: (dayNumber: number) => PlainDate;
88
+ export declare const addDays: (date: PlainDate, days: number) => PlainDate;
89
+ /**
90
+ * Month arithmetic clamps the day rather than overflowing, which is
91
+ * the answer every calendar app gives and the one `Date` does not:
92
+ * `new Date(2026, 0, 31)` plus a month is **3 March**, because 31
93
+ * February rolls forward. One month after 31 January is 28
94
+ * February.
95
+ */
96
+ export declare const addMonths: (date: PlainDate, months: number) => PlainDate;
97
+ export declare const comparePlainDates: (first: PlainDate, second: PlainDate) => number;
98
+ export declare const getIsSameDay: (first: PlainDate, second: PlainDate) => boolean;
99
+ /**
100
+ * Whole days from `from` to `to`. Docket's staleness threshold is
101
+ * counted in days, and this is the subtraction that counts it —
102
+ * with no hour-of-day and no DST to make "14 days" occasionally mean
103
+ * 13.958.
104
+ */
105
+ export declare const getDaysBetween: (from: PlainDate, to: PlainDate) => number;
106
+ export declare const clampPlainDate: (date: PlainDate, minimum?: PlainDate, maximum?: PlainDate) => PlainDate;
107
+ export declare const getIsWithinRange: (date: PlainDate, minimum?: PlainDate, maximum?: PlainDate) => boolean;
108
+ /**
109
+ * 0 is Sunday, 6 is Saturday — the numbering `Intl`'s `weekday`
110
+ * option and `Date.prototype.getDay` both use, so the two never
111
+ * need reconciling at a call site.
112
+ *
113
+ * Day number 0 (1970-01-01) was a Thursday, which is the `+ 4`.
114
+ */
115
+ export declare const getWeekday: (date: PlainDate) => number;
116
+ export declare const toIsoDate: (date: PlainDate) => string;
117
+ /**
118
+ * `null` rather than a throw, and `null` rather than a "best
119
+ * effort" date. A string that is not a calendar date has no nearest
120
+ * calendar date, and inventing one is the silent-guess failure this
121
+ * component is built to avoid.
122
+ */
123
+ export declare const parseIsoDate: (isoDate: null | string | undefined) => null | PlainDate;
124
+ export type PlainDateFormatOptions = Intl.DateTimeFormatOptions & {
125
+ locale?: string;
126
+ };
127
+ /**
128
+ * `timeZone: "UTC"` is forced, and it is not configurable.
129
+ *
130
+ * The instant above is UTC midnight, so formatting it in any other
131
+ * zone renders the previous or the next day — which is the same
132
+ * one-day error as storing an instant, arriving through the display
133
+ * layer instead. There is no legitimate reason to format a calendar
134
+ * date in a zone: the date *is* the answer.
135
+ */
136
+ export declare const formatPlainDate: (date: PlainDate, { locale, ...options }?: PlainDateFormatOptions) => string;
137
+ /**
138
+ * The one instant-to-calendar-date conversion in the library, and it
139
+ * makes you name the zone.
140
+ *
141
+ * Defaulting `timeZone` to the device's own resolved zone is right
142
+ * for "what is today on this screen"; passing one explicitly is
143
+ * right for "what is today for this household". Either way the
144
+ * question is asked out loud, which is the difference between this
145
+ * and `new Date().toISOString().slice(0, 10)` — a line that appears
146
+ * in three of the fleet's repos and is wrong for everybody west of
147
+ * Greenwich after 17:00.
148
+ */
149
+ export declare const getLocalPlainDate: (now: Date, timeZone?: string) => PlainDate;
150
+ export declare const getFirstDayOfWeek: (locale?: string) => number;
151
+ /**
152
+ * The seven weekday names, rotated so index 0 is the locale's own
153
+ * first day. A fixed sample week is formatted rather than a
154
+ * hand-written table, so every locale the browser knows is covered
155
+ * and nothing here needs translating.
156
+ */
157
+ export declare const getWeekdayNames: ({ firstDayOfWeek, locale, weekday, }: {
158
+ firstDayOfWeek: number;
159
+ locale?: string;
160
+ weekday: "long" | "narrow" | "short";
161
+ }) => string[];
162
+ /** January-first month names, for the parser and the header. */
163
+ export declare const getMonthNames: ({ locale, month, }: {
164
+ locale?: string;
165
+ month: "long" | "short";
166
+ }) => string[];
167
+ //# sourceMappingURL=plainDate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainDate.d.ts","sourceRoot":"","sources":["../../src/DatePicker/plainDate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,272 @@
1
+ /**
2
+ * A calendar date, and deliberately not an instant.
3
+ *
4
+ * ### The bug this file exists to make impossible
5
+ *
6
+ * A due date is not a moment. "This is due on the 19th" is true in
7
+ * Denver and true in Tokyo, and it does not become the 18th because
8
+ * a laptop crossed a meridian. The classic date-picker defect is
9
+ * that the value is a `Date` — which is an instant, a count of
10
+ * milliseconds — and every read of it goes through the *browser's*
11
+ * timezone. Pick the 19th at 23:30 in Denver, store
12
+ * `new Date("2026-08-19").toISOString()`, and the server, the next
13
+ * device, and the same device after a flight all disagree about
14
+ * which day you meant. It is a one-day error, it only appears near
15
+ * midnight or after travel, and it is invisible in every test
16
+ * written at noon.
17
+ *
18
+ * So the value here is a **plain date**: a year, a month, and a day,
19
+ * with no time, no offset, and no zone. It is stored as an ISO
20
+ * `YYYY-MM-DD` string — which is exactly `<input type="date">`'s own
21
+ * `value` format, so a consumer can move between this component and
22
+ * the native control without a migration.
23
+ *
24
+ * ### There is no `Date` arithmetic in here at all
25
+ *
26
+ * Not "carefully-written `Date` arithmetic" — none. Every operation
27
+ * below is integer maths on a **day number**: the count of days
28
+ * since 1970-01-01, computed with Howard Hinnant's `days_from_civil`
29
+ * / `civil_from_days` (public domain, the algorithm C++20's
30
+ * `<chrono>` and Rust's `chrono` both use). Adding a day is `+ 1`.
31
+ * That is a proleptic Gregorian calendar with no DST, no leap
32
+ * seconds, and no zone, which is what a calendar date actually is.
33
+ *
34
+ * A `Date` appears in exactly two places, both of them named and
35
+ * both of them fenced:
36
+ *
37
+ * - `getLocalPlainDate`, which is the **one** conversion from an
38
+ * instant to a calendar date, and which requires you to say
39
+ * which zone's calendar you mean.
40
+ * - `formatPlainDate` and its callers, which hand `Intl` a date
41
+ * built at UTC midnight and format it with `timeZone: "UTC"`, so
42
+ * the formatter cannot shift the day either.
43
+ *
44
+ * `plainDate.test.ts` re-runs the whole suite under four `TZ`
45
+ * values, including UTC+14 and UTC-11, and asserts every answer is
46
+ * byte-identical.
47
+ *
48
+ * ### Why not `Temporal`
49
+ *
50
+ * `Temporal.PlainDate` is this type, done properly, and it is
51
+ * available in the Node this repo builds on. It is not available in
52
+ * every browser a published package has to run in, and the polyfill
53
+ * is ~50 KB gz — an order of magnitude more than this entire
54
+ * component. The functions below are the ~80 lines of it a date
55
+ * picker needs. When `Temporal` is baseline, this file becomes a
56
+ * thin adapter and the public API — ISO strings — does not change,
57
+ * which is the reason the API is ISO strings.
58
+ */
59
+ const MONTH_LENGTHS = [
60
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
61
+ ];
62
+ const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
63
+ export const getIsLeapYear = (year) => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
64
+ export const getDaysInMonth = (year, month) => month === 2 && getIsLeapYear(year)
65
+ ? 29
66
+ : (MONTH_LENGTHS[month - 1] ?? 30);
67
+ export const getIsValidPlainDate = ({ day, month, year, }) => Number.isInteger(year) &&
68
+ Number.isInteger(month) &&
69
+ Number.isInteger(day) &&
70
+ year >= 1 &&
71
+ year <= 9999 &&
72
+ month >= 1 &&
73
+ month <= 12 &&
74
+ day >= 1 &&
75
+ day <= getDaysInMonth(year, month);
76
+ /**
77
+ * Days since 1970-01-01, by Hinnant's `days_from_civil`.
78
+ *
79
+ * Every other operation in this file is expressed through this and
80
+ * its inverse, which is why "add a month, then add 40 days, then
81
+ * ask what weekday it is" cannot drift: there is one representation
82
+ * and it is an integer.
83
+ */
84
+ export const getDayNumber = ({ day, month, year, }) => {
85
+ const shiftedYear = year - (month <= 2 ? 1 : 0);
86
+ const era = Math.floor(shiftedYear / 400);
87
+ const yearOfEra = shiftedYear - era * 400;
88
+ const dayOfYear = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) +
89
+ day -
90
+ 1;
91
+ const dayOfEra = yearOfEra * 365 +
92
+ Math.floor(yearOfEra / 4) -
93
+ Math.floor(yearOfEra / 100) +
94
+ dayOfYear;
95
+ return era * 146097 + dayOfEra - 719468;
96
+ };
97
+ /** The inverse — Hinnant's `civil_from_days`. */
98
+ export const getPlainDateFromDayNumber = (dayNumber) => {
99
+ const shifted = dayNumber + 719468;
100
+ const era = Math.floor(shifted / 146097);
101
+ const dayOfEra = shifted - era * 146097;
102
+ const yearOfEra = Math.floor((dayOfEra -
103
+ Math.floor(dayOfEra / 1460) +
104
+ Math.floor(dayOfEra / 36524) -
105
+ Math.floor(dayOfEra / 146096)) /
106
+ 365);
107
+ const dayOfYear = dayOfEra -
108
+ (365 * yearOfEra +
109
+ Math.floor(yearOfEra / 4) -
110
+ Math.floor(yearOfEra / 100));
111
+ const monthPrime = Math.floor((5 * dayOfYear + 2) / 153);
112
+ const day = dayOfYear - Math.floor((153 * monthPrime + 2) / 5) + 1;
113
+ const month = monthPrime + (monthPrime < 10 ? 3 : -9);
114
+ return {
115
+ day,
116
+ month,
117
+ year: yearOfEra + era * 400 + (month <= 2 ? 1 : 0),
118
+ };
119
+ };
120
+ export const addDays = (date, days) => getPlainDateFromDayNumber(getDayNumber(date) + days);
121
+ /**
122
+ * Month arithmetic clamps the day rather than overflowing, which is
123
+ * the answer every calendar app gives and the one `Date` does not:
124
+ * `new Date(2026, 0, 31)` plus a month is **3 March**, because 31
125
+ * February rolls forward. One month after 31 January is 28
126
+ * February.
127
+ */
128
+ export const addMonths = (date, months) => {
129
+ const monthIndex = date.year * 12 + (date.month - 1) + months;
130
+ const year = Math.floor(monthIndex / 12);
131
+ const month = monthIndex - year * 12 + 1;
132
+ return {
133
+ day: Math.min(date.day, getDaysInMonth(year, month)),
134
+ month,
135
+ year,
136
+ };
137
+ };
138
+ export const comparePlainDates = (first, second) => getDayNumber(first) - getDayNumber(second);
139
+ export const getIsSameDay = (first, second) => comparePlainDates(first, second) === 0;
140
+ /**
141
+ * Whole days from `from` to `to`. Docket's staleness threshold is
142
+ * counted in days, and this is the subtraction that counts it —
143
+ * with no hour-of-day and no DST to make "14 days" occasionally mean
144
+ * 13.958.
145
+ */
146
+ export const getDaysBetween = (from, to) => getDayNumber(to) - getDayNumber(from);
147
+ export const clampPlainDate = (date, minimum, maximum) => {
148
+ if (minimum && comparePlainDates(date, minimum) < 0) {
149
+ return minimum;
150
+ }
151
+ if (maximum && comparePlainDates(date, maximum) > 0) {
152
+ return maximum;
153
+ }
154
+ return date;
155
+ };
156
+ export const getIsWithinRange = (date, minimum, maximum) => (!minimum || comparePlainDates(date, minimum) >= 0) &&
157
+ (!maximum || comparePlainDates(date, maximum) <= 0);
158
+ /**
159
+ * 0 is Sunday, 6 is Saturday — the numbering `Intl`'s `weekday`
160
+ * option and `Date.prototype.getDay` both use, so the two never
161
+ * need reconciling at a call site.
162
+ *
163
+ * Day number 0 (1970-01-01) was a Thursday, which is the `+ 4`.
164
+ */
165
+ export const getWeekday = (date) => {
166
+ const dayNumber = getDayNumber(date);
167
+ return ((((dayNumber % 7) + 7) % 7) + 4) % 7;
168
+ };
169
+ const pad = (value, length) => String(value).padStart(length, "0");
170
+ export const toIsoDate = (date) => `${pad(date.year, 4)}-${pad(date.month, 2)}-${pad(date.day, 2)}`;
171
+ /**
172
+ * `null` rather than a throw, and `null` rather than a "best
173
+ * effort" date. A string that is not a calendar date has no nearest
174
+ * calendar date, and inventing one is the silent-guess failure this
175
+ * component is built to avoid.
176
+ */
177
+ export const parseIsoDate = (isoDate) => {
178
+ if (!isoDate) {
179
+ return null;
180
+ }
181
+ const match = ISO_DATE_PATTERN.exec(isoDate);
182
+ if (!match) {
183
+ return null;
184
+ }
185
+ const date = {
186
+ day: Number(match[3]),
187
+ month: Number(match[2]),
188
+ year: Number(match[1]),
189
+ };
190
+ return getIsValidPlainDate(date) ? date : null;
191
+ };
192
+ /**
193
+ * A `Date` pinned at UTC midnight, for `Intl` and for nothing else.
194
+ *
195
+ * `Date.UTC` is not used, because it maps years 0-99 onto 1900-1999
196
+ * — a two-digit year silently becomes the twentieth century inside
197
+ * the formatter, which is a bug the *parser* takes an explicit
198
+ * position on and the formatter has no business re-deciding.
199
+ */
200
+ const toUtcInstant = (date) => {
201
+ const instant = new Date(0);
202
+ instant.setUTCFullYear(date.year, date.month - 1, date.day);
203
+ instant.setUTCHours(0, 0, 0, 0);
204
+ return instant;
205
+ };
206
+ /**
207
+ * `timeZone: "UTC"` is forced, and it is not configurable.
208
+ *
209
+ * The instant above is UTC midnight, so formatting it in any other
210
+ * zone renders the previous or the next day — which is the same
211
+ * one-day error as storing an instant, arriving through the display
212
+ * layer instead. There is no legitimate reason to format a calendar
213
+ * date in a zone: the date *is* the answer.
214
+ */
215
+ export const formatPlainDate = (date, { locale, ...options } = {}) => new Intl.DateTimeFormat(locale, {
216
+ ...options,
217
+ timeZone: "UTC",
218
+ }).format(toUtcInstant(date));
219
+ /**
220
+ * The one instant-to-calendar-date conversion in the library, and it
221
+ * makes you name the zone.
222
+ *
223
+ * Defaulting `timeZone` to the device's own resolved zone is right
224
+ * for "what is today on this screen"; passing one explicitly is
225
+ * right for "what is today for this household". Either way the
226
+ * question is asked out loud, which is the difference between this
227
+ * and `new Date().toISOString().slice(0, 10)` — a line that appears
228
+ * in three of the fleet's repos and is wrong for everybody west of
229
+ * Greenwich after 17:00.
230
+ */
231
+ export const getLocalPlainDate = (now, timeZone) => {
232
+ const parts = new Intl.DateTimeFormat("en-US", {
233
+ day: "2-digit",
234
+ month: "2-digit",
235
+ timeZone,
236
+ year: "numeric",
237
+ }).formatToParts(now);
238
+ const read = (type) => Number(parts.find((part) => part.type === type)?.value ??
239
+ "0");
240
+ return {
241
+ day: read("day"),
242
+ month: read("month"),
243
+ year: read("year"),
244
+ };
245
+ };
246
+ export const getFirstDayOfWeek = (locale) => {
247
+ const resolved = new Intl.Locale(locale ??
248
+ new Intl.DateTimeFormat().resolvedOptions().locale);
249
+ const firstDay = resolved.getWeekInfo?.().firstDay ??
250
+ resolved.weekInfo?.firstDay;
251
+ // `getWeekInfo` numbers Monday 1 through Sunday 7; this file
252
+ // numbers Sunday 0 through Saturday 6.
253
+ return firstDay === undefined ? 0 : firstDay % 7;
254
+ };
255
+ /**
256
+ * The seven weekday names, rotated so index 0 is the locale's own
257
+ * first day. A fixed sample week is formatted rather than a
258
+ * hand-written table, so every locale the browser knows is covered
259
+ * and nothing here needs translating.
260
+ */
261
+ export const getWeekdayNames = ({ firstDayOfWeek, locale, weekday, }) => {
262
+ // 2026-08-16 is a Sunday, so `+ index` walks Sunday to Saturday.
263
+ const sundayDayNumber = getDayNumber({
264
+ day: 16,
265
+ month: 8,
266
+ year: 2026,
267
+ });
268
+ return Array.from({ length: 7 }, (_unused, index) => formatPlainDate(getPlainDateFromDayNumber(sundayDayNumber + ((firstDayOfWeek + index) % 7)), { locale, weekday }));
269
+ };
270
+ /** January-first month names, for the parser and the header. */
271
+ export const getMonthNames = ({ locale, month, }) => Array.from({ length: 12 }, (_unused, index) => formatPlainDate({ day: 1, month: index + 1, year: 2026 }, { locale, month }));
272
+ //# sourceMappingURL=plainDate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainDate.js","sourceRoot":"","sources":["../../src/DatePicker/plainDate.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import type { ReactNode, RefObject } from "react";
2
2
  export type DialogSize = "full" | "lg" | "md" | "sm" | "xl";
3
3
  export type DialogProps = {
4
4
  /**
@@ -17,6 +17,16 @@ export type DialogProps = {
17
17
  * Escape and a backdrop press both close. Off for the one honest
18
18
  * case — a destructive confirm the user has to answer.
19
19
  */
20
+ /**
21
+ * Which element takes the caret when the dialog opens.
22
+ *
23
+ * A dialog wrapping a form wants its first field, not the Close
24
+ * button the focus manager would otherwise pick — and a consumer
25
+ * cannot fix that from outside, because focusing in its own
26
+ * effect races the manager and loses. Docket's capture dialog is
27
+ * the case: every keystroke landed on "Close".
28
+ */
29
+ initialFocus?: RefObject<HTMLElement | null>;
20
30
  isDismissable?: boolean;
21
31
  isVisible: boolean;
22
32
  /**
@@ -49,5 +59,5 @@ export type DialogProps = {
49
59
  * "dialog" and nothing else, which is what every hand-rolled modal in
50
60
  * the inventory does.
51
61
  */
52
- export declare const Dialog: ({ children, className, footer, heading, headingLevel, isDismissable, isVisible, onClose, size, }: DialogProps) => ReactNode;
62
+ export declare const Dialog: ({ children, className, footer, heading, headingLevel, initialFocus, isDismissable, isVisible, onClose, size, }: DialogProps) => ReactNode;
53
63
  //# sourceMappingURL=Dialog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMtC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,GAAI,kGAUpB,WAAW,KAAG,SA4DhB,CAAA"}
1
+ {"version":3,"file":"Dialog.d.ts","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAMjD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxB;;;OAGG;IACH;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,CAAA;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,MAAM,GAAI,gHAWpB,WAAW,KAAG,SA6DhB,CAAA"}
@@ -44,10 +44,10 @@ const SIZE_CLASS = {
44
44
  * "dialog" and nothing else, which is what every hand-rolled modal in
45
45
  * the inventory does.
46
46
  */
47
- export const Dialog = ({ children, className, footer, heading, headingLevel = 2, isDismissable = true, isVisible, onClose, size = "md", }) => {
47
+ export const Dialog = ({ children, className, footer, heading, headingLevel = 2, initialFocus, isDismissable = true, isVisible, onClose, size = "md", }) => {
48
48
  const headingId = useUniqueId();
49
49
  const Heading = `h${headingLevel}`;
50
- return (_jsxs(Modal, { "aria-labelledby": headingId, className: toClassName(SIZE_CLASS[size], className), isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, children: [_jsxs("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4",
50
+ return (_jsxs(Modal, { "aria-labelledby": headingId, className: toClassName(SIZE_CLASS[size], className), initialFocus: initialFocus, isDismissable: isDismissable, isVisible: isVisible, onClose: onClose, children: [_jsxs("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border-subtle p-4", children: [_jsx(Heading, { className: "font-semibold text-lg leading-tight", id: headingId, children: heading }), isDismissable ? (_jsx(Button, { appearance: "ghost", intent: "neutral", onClick: onClose, size: "sm", children: "Close" })) : null] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4",
51
51
  // biome-ignore lint/a11y/noNoninteractiveTabindex: a scroll container must be focusable or a keyboard user can see the pane and cannot scroll it — axe's `scrollable-region-focusable`, which fails this component without the line. Same call, and the same wording, as `LogViewer`'s scroll pane. The rule is right about `<div>`s in general and wrong about scrollers.
52
52
  tabIndex: 0, children: children }), footer ? (_jsx("footer", { className: "flex shrink-0 flex-wrap justify-end gap-2 border-t border-border-subtle p-4", children: footer })) : null] }));
53
53
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA+B/C;;;;GAIG;AACH,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,MAAC,KAAK,uBACa,SAAS,EAC1B,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACnD,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,aAEhB,kBAAQ,SAAS,EAAC,oFAAoF,aACpG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,EAcT,cACE,SAAS,EAAC,kCAAkC;gBAC5C,2WAA2W;gBAC3W,QAAQ,EAAE,CAAC,YAEV,QAAQ,GACL,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,6EAA6E,YAC5F,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"Dialog.js","sourceRoot":"","sources":["../../src/Dialog/Dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAsB,CAAA;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAyC/C;;;;GAIG;AACH,MAAM,UAAU,GAA+B;IAC7C,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,gCAAgC;IACpC,iEAAiE;IACjE,8DAA8D;IAC9D,6DAA6D;IAC7D,kEAAkE;IAClE,+DAA+D;IAC/D,+DAA+D;IAC/D,EAAE,EAAE,gCAAgC;IACpC,IAAI,EAAE,0DAA0D;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EACrB,QAAQ,EACR,SAAS,EACT,MAAM,EACN,OAAO,EACP,YAAY,GAAG,CAAC,EAChB,YAAY,EACZ,aAAa,GAAG,IAAI,EACpB,SAAS,EACT,OAAO,EACP,IAAI,GAAG,IAAI,GACC,EAAa,EAAE;IAC3B,MAAM,SAAS,GAAG,WAAW,EAAE,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,YAAY,EAAW,CAAA;IAE3C,OAAO,CACL,MAAC,KAAK,uBACa,SAAS,EAC1B,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,EACnD,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,aAEhB,kBAAQ,SAAS,EAAC,oFAAoF,aACpG,KAAC,OAAO,IACN,SAAS,EAAC,qCAAqC,EAC/C,EAAE,EAAE,SAAS,YAEZ,OAAO,GACA,EAET,aAAa,CAAC,CAAC,CAAC,CACf,KAAC,MAAM,IACL,UAAU,EAAC,OAAO,EAClB,MAAM,EAAC,SAAS,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,EAAC,IAAI,sBAGF,CACV,CAAC,CAAC,CAAC,IAAI,IACD,EAcT,cACE,SAAS,EAAC,kCAAkC;gBAC5C,2WAA2W;gBAC3W,QAAQ,EAAE,CAAC,YAEV,QAAQ,GACL,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,iBAAQ,SAAS,EAAC,6EAA6E,YAC5F,MAAM,GACA,CACV,CAAC,CAAC,CAAC,IAAI,IACF,CACT,CAAA;AACH,CAAC,CAAA"}