@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.
- package/README.md +10 -0
- package/dist/Badge/Badge.d.ts +33 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +20 -2
- package/dist/Badge/Badge.js.map +1 -1
- package/dist/Board/Board.d.ts +119 -0
- package/dist/Board/Board.d.ts.map +1 -0
- package/dist/Board/Board.js +165 -0
- package/dist/Board/Board.js.map +1 -0
- package/dist/Board/BoardCard.d.ts +120 -0
- package/dist/Board/BoardCard.d.ts.map +1 -0
- package/dist/Board/BoardCard.js +77 -0
- package/dist/Board/BoardCard.js.map +1 -0
- package/dist/Board/BoardLaneList.d.ts +103 -0
- package/dist/Board/BoardLaneList.d.ts.map +1 -0
- package/dist/Board/BoardLaneList.js +69 -0
- package/dist/Board/BoardLaneList.js.map +1 -0
- package/dist/Board/boardMove.d.ts +116 -0
- package/dist/Board/boardMove.d.ts.map +1 -0
- package/dist/Board/boardMove.js +116 -0
- package/dist/Board/boardMove.js.map +1 -0
- package/dist/Board/useBoardDrag.d.ts +44 -0
- package/dist/Board/useBoardDrag.d.ts.map +1 -0
- package/dist/Board/useBoardDrag.js +237 -0
- package/dist/Board/useBoardDrag.js.map +1 -0
- package/dist/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/Checkbox/Checkbox.js +15 -4
- package/dist/Checkbox/Checkbox.js.map +1 -1
- package/dist/DataTable/DataTable.d.ts +159 -0
- package/dist/DataTable/DataTable.d.ts.map +1 -0
- package/dist/DataTable/DataTable.js +197 -0
- package/dist/DataTable/DataTable.js.map +1 -0
- package/dist/DatePicker/DateGrid.d.ts +79 -0
- package/dist/DatePicker/DateGrid.d.ts.map +1 -0
- package/dist/DatePicker/DateGrid.js +220 -0
- package/dist/DatePicker/DateGrid.js.map +1 -0
- package/dist/DatePicker/DatePicker.d.ts +138 -0
- package/dist/DatePicker/DatePicker.d.ts.map +1 -0
- package/dist/DatePicker/DatePicker.js +384 -0
- package/dist/DatePicker/DatePicker.js.map +1 -0
- package/dist/DatePicker/datePresets.d.ts +29 -0
- package/dist/DatePicker/datePresets.d.ts.map +1 -0
- package/dist/DatePicker/datePresets.js +28 -0
- package/dist/DatePicker/datePresets.js.map +1 -0
- package/dist/DatePicker/parseDateInput.d.ts +106 -0
- package/dist/DatePicker/parseDateInput.d.ts.map +1 -0
- package/dist/DatePicker/parseDateInput.js +419 -0
- package/dist/DatePicker/parseDateInput.js.map +1 -0
- package/dist/DatePicker/plainDate.d.ts +167 -0
- package/dist/DatePicker/plainDate.d.ts.map +1 -0
- package/dist/DatePicker/plainDate.js +272 -0
- package/dist/DatePicker/plainDate.js.map +1 -0
- package/dist/Dialog/Dialog.d.ts +12 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -2
- package/dist/Dialog/Dialog.js.map +1 -1
- package/dist/MarkdownEditor/MarkdownEditor.d.ts +147 -0
- package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
- package/dist/MarkdownEditor/MarkdownEditor.js +492 -0
- package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts +202 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -0
- package/dist/MarkdownEditor/markdownCommands.js +379 -0
- package/dist/MarkdownEditor/markdownCommands.js.map +1 -0
- package/dist/MarkdownEditor/markdownSpans.d.ts +92 -0
- package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -0
- package/dist/MarkdownEditor/markdownSpans.js +303 -0
- package/dist/MarkdownEditor/markdownSpans.js.map +1 -0
- package/dist/Modal/Modal.d.ts +6 -2
- package/dist/Modal/Modal.d.ts.map +1 -1
- package/dist/Modal/Modal.js +1 -1
- package/dist/Modal/Modal.js.map +1 -1
- package/dist/Overlay/OverlayPanel.d.ts +13 -2
- package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
- package/dist/Overlay/OverlayPanel.js +5 -1
- package/dist/Overlay/OverlayPanel.js.map +1 -1
- package/dist/categoricalStyles.d.ts +53 -0
- package/dist/categoricalStyles.d.ts.map +1 -0
- package/dist/categoricalStyles.js +195 -0
- package/dist/categoricalStyles.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Badge/Badge.mdx +46 -0
- package/src/Badge/Badge.stories.tsx +98 -4
- package/src/Badge/Badge.test.tsx +157 -2
- package/src/Badge/Badge.tsx +49 -3
- package/src/Board/Board.mdx +199 -0
- package/src/Board/Board.stories.tsx +707 -0
- package/src/Board/Board.test.tsx +310 -0
- package/src/Board/Board.tsx +333 -0
- package/src/Board/BoardCard.tsx +290 -0
- package/src/Board/BoardLaneList.tsx +275 -0
- package/src/Board/boardMove.test.ts +213 -0
- package/src/Board/boardMove.ts +225 -0
- package/src/Board/useBoardDrag.ts +413 -0
- package/src/Checkbox/Checkbox.tsx +34 -3
- package/src/DataTable/DataTable.mdx +123 -0
- package/src/DataTable/DataTable.stories.tsx +411 -0
- package/src/DataTable/DataTable.test.tsx +386 -0
- package/src/DataTable/DataTable.tsx +497 -0
- package/src/DatePicker/DateGrid.tsx +467 -0
- package/src/DatePicker/DatePicker.mdx +170 -0
- package/src/DatePicker/DatePicker.stories.tsx +268 -0
- package/src/DatePicker/DatePicker.test.tsx +455 -0
- package/src/DatePicker/DatePicker.tsx +852 -0
- package/src/DatePicker/datePresets.ts +34 -0
- package/src/DatePicker/parseDateInput.test.ts +187 -0
- package/src/DatePicker/parseDateInput.ts +629 -0
- package/src/DatePicker/plainDate.test.ts +375 -0
- package/src/DatePicker/plainDate.ts +465 -0
- package/src/Dialog/Dialog.stories.tsx +48 -0
- package/src/Dialog/Dialog.test.tsx +30 -0
- package/src/Dialog/Dialog.tsx +13 -1
- package/src/MarkdownEditor/MarkdownEditor.mdx +189 -0
- package/src/MarkdownEditor/MarkdownEditor.stories.tsx +267 -0
- package/src/MarkdownEditor/MarkdownEditor.test.tsx +627 -0
- package/src/MarkdownEditor/MarkdownEditor.tsx +862 -0
- package/src/MarkdownEditor/markdownCommands.test.ts +384 -0
- package/src/MarkdownEditor/markdownCommands.ts +566 -0
- package/src/MarkdownEditor/markdownSpans.test.ts +217 -0
- package/src/MarkdownEditor/markdownSpans.ts +458 -0
- package/src/Modal/Modal.tsx +7 -1
- package/src/Overlay/OverlayPanel.tsx +17 -1
- package/src/argTypes.storyHelpers.ts +39 -0
- package/src/categoricalStyles.ts +244 -0
- package/src/index.ts +89 -0
- 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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"}
|
package/dist/Dialog/Dialog.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/Dialog/Dialog.js
CHANGED
|
@@ -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;
|
|
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"}
|