@lyeve-labs/ui-kit 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/components/AccordionItem.svelte +1 -1
- package/dist/components/Autocomplete.svelte +191 -125
- package/dist/components/Autocomplete.svelte.d.ts +29 -8
- package/dist/components/Button.svelte +26 -4
- package/dist/components/Card.svelte +61 -3
- package/dist/components/Card.svelte.d.ts +24 -2
- package/dist/components/Checkbox.svelte +174 -59
- package/dist/components/Checkbox.svelte.d.ts +20 -3
- package/dist/components/CheckboxGroup.svelte +162 -0
- package/dist/components/CheckboxGroup.svelte.d.ts +51 -0
- package/dist/components/Collapsible.svelte +142 -0
- package/dist/components/Collapsible.svelte.d.ts +32 -0
- package/dist/components/CopyButton.svelte +126 -0
- package/dist/components/CopyButton.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +48 -6
- package/dist/components/DateTimePicker.svelte +337 -0
- package/dist/components/DateTimePicker.svelte.d.ts +26 -0
- package/dist/components/DescriptionList.svelte +78 -0
- package/dist/components/DescriptionList.svelte.d.ts +34 -0
- package/dist/components/Drawer.svelte +15 -4
- package/dist/components/Field.svelte +104 -0
- package/dist/components/Field.svelte.d.ts +46 -0
- package/dist/components/FileInput.svelte +5 -2
- package/dist/components/FormMessage.svelte +85 -0
- package/dist/components/FormMessage.svelte.d.ts +11 -0
- package/dist/components/Input.svelte +1 -1
- package/dist/components/Label.svelte +7 -1
- package/dist/components/Label.svelte.d.ts +6 -0
- package/dist/components/Modal.svelte +25 -8
- package/dist/components/MultiSelect.svelte +199 -109
- package/dist/components/MultiSelect.svelte.d.ts +22 -9
- package/dist/components/NumberInput.svelte +8 -4
- package/dist/components/PageHeader.svelte +37 -4
- package/dist/components/PageHeader.svelte.d.ts +15 -0
- package/dist/components/PageShell.svelte +85 -0
- package/dist/components/PageShell.svelte.d.ts +38 -0
- package/dist/components/Pagination.svelte +58 -17
- package/dist/components/Panel.svelte +101 -0
- package/dist/components/Panel.svelte.d.ts +39 -0
- package/dist/components/PasswordInput.svelte +139 -0
- package/dist/components/PasswordInput.svelte.d.ts +29 -0
- package/dist/components/Radio.svelte +152 -32
- package/dist/components/Radio.svelte.d.ts +16 -1
- package/dist/components/RadioGroup.svelte +118 -71
- package/dist/components/RadioGroup.svelte.d.ts +39 -9
- package/dist/components/SectionHeading.svelte +39 -0
- package/dist/components/SectionHeading.svelte.d.ts +21 -0
- package/dist/components/SegmentedControl.svelte +194 -0
- package/dist/components/SegmentedControl.svelte.d.ts +55 -0
- package/dist/components/Select.svelte +471 -46
- package/dist/components/Select.svelte.d.ts +95 -6
- package/dist/components/SidebarNav.svelte +259 -0
- package/dist/components/SidebarNav.svelte.d.ts +17 -0
- package/dist/components/Stat.svelte +53 -2
- package/dist/components/Stat.svelte.d.ts +31 -0
- package/dist/components/Textarea.svelte +1 -1
- package/dist/components/TimePicker.svelte +480 -0
- package/dist/components/TimePicker.svelte.d.ts +23 -0
- package/dist/components/Toaster.svelte +9 -2
- package/dist/components/Toggle.svelte +5 -1
- package/dist/components/Toggle.svelte.d.ts +2 -0
- package/dist/components/Toolbar.svelte +39 -0
- package/dist/components/Toolbar.svelte.d.ts +26 -0
- package/dist/components/Tooltip.svelte +48 -12
- package/dist/components/TreeView.svelte +339 -0
- package/dist/components/TreeView.svelte.d.ts +37 -0
- package/dist/components/dialog/Dialog.svelte +15 -58
- package/dist/components/dialog/dialog-manager.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog-manager.svelte.js +21 -21
- package/dist/index.d.ts +25 -1
- package/dist/index.js +20 -1
- package/dist/internal/calendar.d.ts +119 -0
- package/dist/internal/calendar.js +225 -0
- package/dist/internal/choice.d.ts +136 -0
- package/dist/internal/choice.js +179 -0
- package/dist/internal/field.d.ts +31 -0
- package/dist/internal/field.js +42 -1
- package/dist/internal/filter.d.ts +80 -0
- package/dist/internal/filter.js +80 -0
- package/dist/internal/layout.d.ts +119 -0
- package/dist/internal/layout.js +132 -0
- package/dist/internal/listbox.svelte.d.ts +77 -0
- package/dist/internal/listbox.svelte.js +438 -0
- package/dist/internal/nav-expansion.svelte.d.ts +36 -0
- package/dist/internal/nav-expansion.svelte.js +144 -0
- package/dist/internal/nav-tree.d.ts +68 -0
- package/dist/internal/nav-tree.js +102 -0
- package/dist/internal/overlay.d.ts +25 -0
- package/dist/internal/overlay.js +92 -0
- package/dist/internal/panel.d.ts +100 -0
- package/dist/internal/panel.js +109 -0
- package/dist/internal/rollup.d.ts +52 -0
- package/dist/internal/rollup.js +67 -0
- package/dist/internal/time.d.ts +103 -0
- package/dist/internal/time.js +166 -0
- package/dist/internal/tree.d.ts +86 -0
- package/dist/internal/tree.js +111 -0
- package/dist/styles/theme.css +66 -25
- package/package.json +4 -2
- package/src/lib/styles/theme.css +66 -25
package/dist/index.d.ts
CHANGED
|
@@ -7,32 +7,55 @@
|
|
|
7
7
|
* is an implementation detail.
|
|
8
8
|
*/
|
|
9
9
|
export { default as Card } from './components/Card.svelte';
|
|
10
|
+
export { default as Panel } from './components/Panel.svelte';
|
|
11
|
+
export { default as PageShell } from './components/PageShell.svelte';
|
|
10
12
|
export { default as PageHeader } from './components/PageHeader.svelte';
|
|
13
|
+
export { default as SectionHeading } from './components/SectionHeading.svelte';
|
|
11
14
|
export { default as Divider } from './components/Divider.svelte';
|
|
12
15
|
export { default as Accordion } from './components/Accordion.svelte';
|
|
13
16
|
export { default as AccordionItem } from './components/AccordionItem.svelte';
|
|
17
|
+
export { default as Collapsible } from './components/Collapsible.svelte';
|
|
14
18
|
export { default as Table } from './components/Table.svelte';
|
|
19
|
+
export { default as DescriptionList } from './components/DescriptionList.svelte';
|
|
20
|
+
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
21
|
+
export { default as TreeView } from './components/TreeView.svelte';
|
|
22
|
+
export type { PageWidth } from './internal/layout.js';
|
|
23
|
+
export type { TreeNode, TriState } from './internal/tree.js';
|
|
15
24
|
export { default as Button } from './components/Button.svelte';
|
|
16
25
|
export { default as ButtonGroup } from './components/ButtonGroup.svelte';
|
|
17
26
|
export { default as Input } from './components/Input.svelte';
|
|
27
|
+
export { default as PasswordInput } from './components/PasswordInput.svelte';
|
|
18
28
|
export { default as Textarea } from './components/Textarea.svelte';
|
|
19
29
|
export { default as NumberInput } from './components/NumberInput.svelte';
|
|
20
30
|
export { default as SearchInput } from './components/SearchInput.svelte';
|
|
21
31
|
export { default as FileInput } from './components/FileInput.svelte';
|
|
22
32
|
export { default as Label } from './components/Label.svelte';
|
|
33
|
+
export { default as Field } from './components/Field.svelte';
|
|
34
|
+
export { default as FormMessage } from './components/FormMessage.svelte';
|
|
35
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
23
36
|
export { default as Select } from './components/Select.svelte';
|
|
24
37
|
export { default as MultiSelect } from './components/MultiSelect.svelte';
|
|
25
38
|
export { default as Autocomplete } from './components/Autocomplete.svelte';
|
|
26
39
|
export { default as DatePicker } from './components/DatePicker.svelte';
|
|
40
|
+
export { default as TimePicker } from './components/TimePicker.svelte';
|
|
41
|
+
export { default as DateTimePicker } from './components/DateTimePicker.svelte';
|
|
27
42
|
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
43
|
+
export { default as CheckboxGroup } from './components/CheckboxGroup.svelte';
|
|
28
44
|
export { default as Radio } from './components/Radio.svelte';
|
|
29
45
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
30
46
|
export { default as Toggle } from './components/Toggle.svelte';
|
|
47
|
+
export type { ChoiceSize, ChoiceVariant, ChoiceOrientation } from './internal/choice.js';
|
|
48
|
+
export type { ChoiceOption } from './components/CheckboxGroup.svelte';
|
|
49
|
+
export type { SelectOption, SelectChangeEvent } from './components/Select.svelte';
|
|
50
|
+
export type { ListOption } from './components/Autocomplete.svelte';
|
|
51
|
+
export type { FilterFn, FilterInput, FilterContext } from './internal/filter.js';
|
|
31
52
|
export { default as Breadcrumb } from './components/Breadcrumb.svelte';
|
|
32
53
|
export { default as Tabs } from './components/Tabs.svelte';
|
|
33
54
|
export { default as Pagination } from './components/Pagination.svelte';
|
|
34
55
|
export { default as StepIndicator } from './components/StepIndicator.svelte';
|
|
35
56
|
export { default as Dropdown } from './components/Dropdown.svelte';
|
|
57
|
+
export { default as SidebarNav } from './components/SidebarNav.svelte';
|
|
58
|
+
export type { NavNode, NavTree } from './internal/nav-tree.js';
|
|
36
59
|
export { default as Modal } from './components/Modal.svelte';
|
|
37
60
|
export { default as Drawer } from './components/Drawer.svelte';
|
|
38
61
|
export { default as Tooltip } from './components/Tooltip.svelte';
|
|
@@ -47,6 +70,7 @@ export { default as Skeleton } from './components/Skeleton.svelte';
|
|
|
47
70
|
export { default as EmptyState } from './components/EmptyState.svelte';
|
|
48
71
|
export { default as Stat } from './components/Stat.svelte';
|
|
49
72
|
export { default as Kbd } from './components/Kbd.svelte';
|
|
73
|
+
export { default as CopyButton } from './components/CopyButton.svelte';
|
|
50
74
|
export { default as Avatar } from './components/Avatar.svelte';
|
|
51
75
|
export { default as AvatarGroup } from './components/AvatarGroup.svelte';
|
|
52
76
|
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
@@ -60,4 +84,4 @@ export { openDialog, closeDialog, dismissDialog, dismissAllDialogs, confirm, set
|
|
|
60
84
|
export type { DialogOptions, DialogEntry, DialogSize } from './components/dialog/types.js';
|
|
61
85
|
export { cn, type ClassValue } from './utils/cn.js';
|
|
62
86
|
export { getTheme, setTheme, toggleTheme, themeBootScript, type Theme } from './utils/theme.js';
|
|
63
|
-
export declare const VERSION = "0.
|
|
87
|
+
export declare const VERSION = "0.13.0";
|
package/dist/index.js
CHANGED
|
@@ -8,25 +8,42 @@
|
|
|
8
8
|
*/
|
|
9
9
|
// ── Layout & structure ─────────────────────────────────────────────────────
|
|
10
10
|
export { default as Card } from './components/Card.svelte';
|
|
11
|
+
export { default as Panel } from './components/Panel.svelte';
|
|
12
|
+
export { default as PageShell } from './components/PageShell.svelte';
|
|
11
13
|
export { default as PageHeader } from './components/PageHeader.svelte';
|
|
14
|
+
export { default as SectionHeading } from './components/SectionHeading.svelte';
|
|
12
15
|
export { default as Divider } from './components/Divider.svelte';
|
|
13
16
|
export { default as Accordion } from './components/Accordion.svelte';
|
|
14
17
|
export { default as AccordionItem } from './components/AccordionItem.svelte';
|
|
18
|
+
export { default as Collapsible } from './components/Collapsible.svelte';
|
|
15
19
|
export { default as Table } from './components/Table.svelte';
|
|
20
|
+
export { default as DescriptionList } from './components/DescriptionList.svelte';
|
|
21
|
+
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
22
|
+
export { default as TreeView } from './components/TreeView.svelte';
|
|
16
23
|
// ── Forms & inputs ─────────────────────────────────────────────────────────
|
|
17
24
|
export { default as Button } from './components/Button.svelte';
|
|
18
25
|
export { default as ButtonGroup } from './components/ButtonGroup.svelte';
|
|
19
26
|
export { default as Input } from './components/Input.svelte';
|
|
27
|
+
export { default as PasswordInput } from './components/PasswordInput.svelte';
|
|
20
28
|
export { default as Textarea } from './components/Textarea.svelte';
|
|
21
29
|
export { default as NumberInput } from './components/NumberInput.svelte';
|
|
22
30
|
export { default as SearchInput } from './components/SearchInput.svelte';
|
|
23
31
|
export { default as FileInput } from './components/FileInput.svelte';
|
|
24
32
|
export { default as Label } from './components/Label.svelte';
|
|
33
|
+
// The field furniture as a component, for a consumer composing a control the
|
|
34
|
+
// kit does not ship. internal/field.ts stays private, and a page that could
|
|
35
|
+
// not reach it hand-copied the class strings instead.
|
|
36
|
+
export { default as Field } from './components/Field.svelte';
|
|
37
|
+
export { default as FormMessage } from './components/FormMessage.svelte';
|
|
38
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
25
39
|
export { default as Select } from './components/Select.svelte';
|
|
26
40
|
export { default as MultiSelect } from './components/MultiSelect.svelte';
|
|
27
41
|
export { default as Autocomplete } from './components/Autocomplete.svelte';
|
|
28
42
|
export { default as DatePicker } from './components/DatePicker.svelte';
|
|
43
|
+
export { default as TimePicker } from './components/TimePicker.svelte';
|
|
44
|
+
export { default as DateTimePicker } from './components/DateTimePicker.svelte';
|
|
29
45
|
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
46
|
+
export { default as CheckboxGroup } from './components/CheckboxGroup.svelte';
|
|
30
47
|
export { default as Radio } from './components/Radio.svelte';
|
|
31
48
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
32
49
|
export { default as Toggle } from './components/Toggle.svelte';
|
|
@@ -36,6 +53,7 @@ export { default as Tabs } from './components/Tabs.svelte';
|
|
|
36
53
|
export { default as Pagination } from './components/Pagination.svelte';
|
|
37
54
|
export { default as StepIndicator } from './components/StepIndicator.svelte';
|
|
38
55
|
export { default as Dropdown } from './components/Dropdown.svelte';
|
|
56
|
+
export { default as SidebarNav } from './components/SidebarNav.svelte';
|
|
39
57
|
// ── Overlays ───────────────────────────────────────────────────────────────
|
|
40
58
|
export { default as Modal } from './components/Modal.svelte';
|
|
41
59
|
export { default as Drawer } from './components/Drawer.svelte';
|
|
@@ -52,6 +70,7 @@ export { default as Skeleton } from './components/Skeleton.svelte';
|
|
|
52
70
|
export { default as EmptyState } from './components/EmptyState.svelte';
|
|
53
71
|
export { default as Stat } from './components/Stat.svelte';
|
|
54
72
|
export { default as Kbd } from './components/Kbd.svelte';
|
|
73
|
+
export { default as CopyButton } from './components/CopyButton.svelte';
|
|
55
74
|
// ── Media ──────────────────────────────────────────────────────────────────
|
|
56
75
|
export { default as Avatar } from './components/Avatar.svelte';
|
|
57
76
|
export { default as AvatarGroup } from './components/AvatarGroup.svelte';
|
|
@@ -71,4 +90,4 @@ export { getTheme, setTheme, toggleTheme, themeBootScript } from './utils/theme.
|
|
|
71
90
|
// ── Version ────────────────────────────────────────────────────────────────
|
|
72
91
|
// Generated from package.json by `pnpm version:sync`. Bump package.json, never
|
|
73
92
|
// this line; the build and the test suite fail when the two disagree.
|
|
74
|
-
export const VERSION = '0.
|
|
93
|
+
export const VERSION = '0.13.0';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Month arithmetic for the date controls, done without the Date parser.
|
|
3
|
+
*
|
|
4
|
+
* The rule this module exists to enforce: no string is ever handed to the Date
|
|
5
|
+
* parser. `new Date('2026-01-02')` is UTC midnight by specification and
|
|
6
|
+
* `new Date('2026-01-02T00:00')` is local midnight, so one helper applied to a
|
|
7
|
+
* date field and to a datetime field lands in two different zones. Read the
|
|
8
|
+
* first form back through the local getters west of UTC and it is 1 January.
|
|
9
|
+
* That is the off by one day, and it appears only for the part of the world
|
|
10
|
+
* that is not on UTC, which is why it survives a developer's own testing.
|
|
11
|
+
*
|
|
12
|
+
* DatePicker binds `value` to an ISO calendar date spelled `YYYY-MM-DD`: four
|
|
13
|
+
* digit year, two digit zero padded month, two digit zero padded day, with the
|
|
14
|
+
* empty string standing for no date. Its `min` and `max` props take the same
|
|
15
|
+
* spelling and are compared as plain strings, which works only because that
|
|
16
|
+
* spelling sorts lexicographically. DateRangePicker and DateTimePicker will
|
|
17
|
+
* carry the same value and need the same arithmetic, and each of the three is a
|
|
18
|
+
* place the zone bug enters, so the arithmetic is stated once here.
|
|
19
|
+
*
|
|
20
|
+
* The numeric Date constructor is avoided as well. `new Date(24, 0, 1)` is
|
|
21
|
+
* 1924, because the constructor remaps years 0 through 99 into the twentieth
|
|
22
|
+
* century, and the year in a date field comes from a user. Everything below is
|
|
23
|
+
* integer arithmetic on the proleptic Gregorian calendar. The one Date in the
|
|
24
|
+
* file is the no argument one inside todayLocal, which is the only place a real
|
|
25
|
+
* clock is needed.
|
|
26
|
+
*
|
|
27
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
28
|
+
*/
|
|
29
|
+
/** A calendar date with no zone and no time. m is 1-12, the month a human says, not the 0-11 the Date constructor takes. */
|
|
30
|
+
export interface CalendarDate {
|
|
31
|
+
y: number;
|
|
32
|
+
m: number;
|
|
33
|
+
d: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reads the `YYYY-MM-DD` DatePicker binds, or null when the string is not one.
|
|
37
|
+
*
|
|
38
|
+
* Shape checks and round trips. `2024-13-45` returns null rather than silently
|
|
39
|
+
* becoming the 2025-02-14 those three numbers normalize to in a Date: a picker
|
|
40
|
+
* that accepted it would open on a month the user never typed and report a
|
|
41
|
+
* value they never chose. A day is rejected when its month does not have it, so
|
|
42
|
+
* `2024-02-30` and `2023-02-29` are both null while `2024-02-29` is a date.
|
|
43
|
+
* Undefined and the empty string are null too, because an unset field is the
|
|
44
|
+
* ordinary state of a date input and not an error.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseISODate(s: string | undefined | null): CalendarDate | null;
|
|
47
|
+
/**
|
|
48
|
+
* Writes the `YYYY-MM-DD` DatePicker binds.
|
|
49
|
+
*
|
|
50
|
+
* Every part is zero padded, including the year, because the min and max
|
|
51
|
+
* comparison in the control is a string comparison: an unpadded `2024-1-5`
|
|
52
|
+
* sorts after `2024-12-01` and the bound then rejects dates inside it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function toISODate(date: CalendarDate): string;
|
|
55
|
+
/**
|
|
56
|
+
* Today in the viewer's own zone, read from the Date's local getters and never
|
|
57
|
+
* from its ISO string.
|
|
58
|
+
*
|
|
59
|
+
* `new Date().toISOString().slice(0, 10)` is the tempting one line version and
|
|
60
|
+
* it is wrong: it converts to UTC first, so a viewer in Jakarta gets yesterday
|
|
61
|
+
* until 7am and a viewer in New York gets tomorrow from the evening on. The
|
|
62
|
+
* date the calendar marks as today has to be the date on the wall behind the
|
|
63
|
+
* viewer.
|
|
64
|
+
*/
|
|
65
|
+
export declare function todayLocal(): CalendarDate;
|
|
66
|
+
/**
|
|
67
|
+
* Days in a month, leap years included.
|
|
68
|
+
*
|
|
69
|
+
* A month outside 1 to 12 has no days and returns 0, so a caller bounding a day
|
|
70
|
+
* against it rejects the month rather than accepting a day in a month that does
|
|
71
|
+
* not exist.
|
|
72
|
+
*/
|
|
73
|
+
export declare function daysInMonth(y: number, m: number): number;
|
|
74
|
+
/** Weekday of the first of the month, 0 for Sunday. */
|
|
75
|
+
export declare function firstWeekday(y: number, m: number): number;
|
|
76
|
+
/**
|
|
77
|
+
* Adds months, clamping the day rather than rolling into the next month.
|
|
78
|
+
*
|
|
79
|
+
* 31 January plus one month is 28 or 29 February, never 2 or 3 March. A Date
|
|
80
|
+
* rolls, so paging a picker forward from the 31st and back again lands the user
|
|
81
|
+
* on a different day than the one they were looking at, and a range whose start
|
|
82
|
+
* was the 31st can end up after its own end.
|
|
83
|
+
*/
|
|
84
|
+
export declare function addMonths(date: CalendarDate, delta: number): CalendarDate;
|
|
85
|
+
/** Adds days across month and year boundaries, leap days included. */
|
|
86
|
+
export declare function addDays(date: CalendarDate, delta: number): CalendarDate;
|
|
87
|
+
/**
|
|
88
|
+
* Orders two dates as -1, 0 or 1.
|
|
89
|
+
*
|
|
90
|
+
* A total order, so a caller can sort and bound without constructing a Date.
|
|
91
|
+
* Returning the field difference would sort just as well and would report 2 for
|
|
92
|
+
* two years apart, which quietly breaks the caller that tests for 1.
|
|
93
|
+
*/
|
|
94
|
+
export declare function compareDates(a: CalendarDate, b: CalendarDate): number;
|
|
95
|
+
/**
|
|
96
|
+
* Inclusive on both ends. Either bound may be null, meaning unbounded.
|
|
97
|
+
*
|
|
98
|
+
* Inclusive because a picker given min and max for a single allowed day has to
|
|
99
|
+
* offer that day. DatePicker compares its bounds as strings today, which agrees
|
|
100
|
+
* with this only while every bound is zero padded.
|
|
101
|
+
*/
|
|
102
|
+
export declare function withinRange(date: CalendarDate, min: CalendarDate | null, max: CalendarDate | null): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* The six week grid a month renders, always 42 cells so the calendar does not
|
|
105
|
+
* change height between months. Cells outside the viewed month carry
|
|
106
|
+
* `outside: true`.
|
|
107
|
+
*
|
|
108
|
+
* DatePicker pads its lead with empty spans and stops at the last day, so
|
|
109
|
+
* February in a year where it starts on a Sunday draws four rows and a 31 day
|
|
110
|
+
* month starting on a Saturday draws six. The popover resizes as the user pages
|
|
111
|
+
* through the year and the day under the pointer moves out from under it. Fixed
|
|
112
|
+
* at 42 because six weeks is the most any month can touch.
|
|
113
|
+
*
|
|
114
|
+
* `weekStartsOn` is 0 for Sunday and 1 for Monday.
|
|
115
|
+
*/
|
|
116
|
+
export declare function monthGrid(y: number, m: number, weekStartsOn?: 0 | 1): {
|
|
117
|
+
date: CalendarDate;
|
|
118
|
+
outside: boolean;
|
|
119
|
+
}[];
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Month arithmetic for the date controls, done without the Date parser.
|
|
3
|
+
*
|
|
4
|
+
* The rule this module exists to enforce: no string is ever handed to the Date
|
|
5
|
+
* parser. `new Date('2026-01-02')` is UTC midnight by specification and
|
|
6
|
+
* `new Date('2026-01-02T00:00')` is local midnight, so one helper applied to a
|
|
7
|
+
* date field and to a datetime field lands in two different zones. Read the
|
|
8
|
+
* first form back through the local getters west of UTC and it is 1 January.
|
|
9
|
+
* That is the off by one day, and it appears only for the part of the world
|
|
10
|
+
* that is not on UTC, which is why it survives a developer's own testing.
|
|
11
|
+
*
|
|
12
|
+
* DatePicker binds `value` to an ISO calendar date spelled `YYYY-MM-DD`: four
|
|
13
|
+
* digit year, two digit zero padded month, two digit zero padded day, with the
|
|
14
|
+
* empty string standing for no date. Its `min` and `max` props take the same
|
|
15
|
+
* spelling and are compared as plain strings, which works only because that
|
|
16
|
+
* spelling sorts lexicographically. DateRangePicker and DateTimePicker will
|
|
17
|
+
* carry the same value and need the same arithmetic, and each of the three is a
|
|
18
|
+
* place the zone bug enters, so the arithmetic is stated once here.
|
|
19
|
+
*
|
|
20
|
+
* The numeric Date constructor is avoided as well. `new Date(24, 0, 1)` is
|
|
21
|
+
* 1924, because the constructor remaps years 0 through 99 into the twentieth
|
|
22
|
+
* century, and the year in a date field comes from a user. Everything below is
|
|
23
|
+
* integer arithmetic on the proleptic Gregorian calendar. The one Date in the
|
|
24
|
+
* file is the no argument one inside todayLocal, which is the only place a real
|
|
25
|
+
* clock is needed.
|
|
26
|
+
*
|
|
27
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
28
|
+
*/
|
|
29
|
+
/** Month lengths in a common year. February is corrected for leap years in daysInMonth. */
|
|
30
|
+
const MONTH_LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
31
|
+
/** The only spelling DatePicker reads or writes. Anything else is not a date. */
|
|
32
|
+
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
33
|
+
/**
|
|
34
|
+
* The full Gregorian rule, not the every fourth year shorthand.
|
|
35
|
+
*
|
|
36
|
+
* A century is a common year unless it divides by 400. Dropping that clause
|
|
37
|
+
* makes 2000 a common year and puts 29 February 2000 out of reach of the
|
|
38
|
+
* control; keeping only the century clause makes 1900 a leap year and invents a
|
|
39
|
+
* day nobody lived through.
|
|
40
|
+
*/
|
|
41
|
+
function isLeapYear(y) {
|
|
42
|
+
return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
|
43
|
+
}
|
|
44
|
+
/** The remainder operator keeps the sign of its left operand, so a negative weekday offset needs the extra turn. */
|
|
45
|
+
function mod7(n) {
|
|
46
|
+
return ((n % 7) + 7) % 7;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Days from 1970-01-01, negative before it.
|
|
50
|
+
*
|
|
51
|
+
* Howard Hinnant's days_from_civil, which is exact for every year the
|
|
52
|
+
* arithmetic can express and never touches a Date. Counting days is what makes
|
|
53
|
+
* addDays a single addition rather than a carry the caller has to write across
|
|
54
|
+
* three fields, and it is what gives firstWeekday an answer for a year under
|
|
55
|
+
* 100, where the Date constructor gives the wrong century.
|
|
56
|
+
*/
|
|
57
|
+
function toDayNumber(date) {
|
|
58
|
+
const shiftedYear = date.y - (date.m <= 2 ? 1 : 0);
|
|
59
|
+
const era = Math.floor(shiftedYear / 400);
|
|
60
|
+
const yearOfEra = shiftedYear - era * 400;
|
|
61
|
+
const dayOfYear = Math.floor((153 * (date.m + (date.m > 2 ? -3 : 9)) + 2) / 5) + date.d - 1;
|
|
62
|
+
const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
|
|
63
|
+
return era * 146097 + dayOfEra - 719468;
|
|
64
|
+
}
|
|
65
|
+
/** The inverse of toDayNumber, so a day count can go back to a date the grid can render. */
|
|
66
|
+
function fromDayNumber(dayNumber) {
|
|
67
|
+
const shifted = dayNumber + 719468;
|
|
68
|
+
const era = Math.floor(shifted / 146097);
|
|
69
|
+
const dayOfEra = shifted - era * 146097;
|
|
70
|
+
const yearOfEra = Math.floor((dayOfEra -
|
|
71
|
+
Math.floor(dayOfEra / 1460) +
|
|
72
|
+
Math.floor(dayOfEra / 36524) -
|
|
73
|
+
Math.floor(dayOfEra / 146096)) /
|
|
74
|
+
365);
|
|
75
|
+
const dayOfYear = dayOfEra - (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
|
|
76
|
+
const monthIndex = Math.floor((5 * dayOfYear + 2) / 153);
|
|
77
|
+
const d = dayOfYear - Math.floor((153 * monthIndex + 2) / 5) + 1;
|
|
78
|
+
const m = monthIndex + (monthIndex < 10 ? 3 : -9);
|
|
79
|
+
return { y: yearOfEra + era * 400 + (m <= 2 ? 1 : 0), m, d };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reads the `YYYY-MM-DD` DatePicker binds, or null when the string is not one.
|
|
83
|
+
*
|
|
84
|
+
* Shape checks and round trips. `2024-13-45` returns null rather than silently
|
|
85
|
+
* becoming the 2025-02-14 those three numbers normalize to in a Date: a picker
|
|
86
|
+
* that accepted it would open on a month the user never typed and report a
|
|
87
|
+
* value they never chose. A day is rejected when its month does not have it, so
|
|
88
|
+
* `2024-02-30` and `2023-02-29` are both null while `2024-02-29` is a date.
|
|
89
|
+
* Undefined and the empty string are null too, because an unset field is the
|
|
90
|
+
* ordinary state of a date input and not an error.
|
|
91
|
+
*/
|
|
92
|
+
export function parseISODate(s) {
|
|
93
|
+
if (!s)
|
|
94
|
+
return null;
|
|
95
|
+
const parts = ISO_DATE.exec(s);
|
|
96
|
+
if (!parts)
|
|
97
|
+
return null;
|
|
98
|
+
const y = Number(parts[1]);
|
|
99
|
+
const m = Number(parts[2]);
|
|
100
|
+
const d = Number(parts[3]);
|
|
101
|
+
if (m < 1 || m > 12)
|
|
102
|
+
return null;
|
|
103
|
+
if (d < 1 || d > daysInMonth(y, m))
|
|
104
|
+
return null;
|
|
105
|
+
return { y, m, d };
|
|
106
|
+
}
|
|
107
|
+
/** Pads a number to a fixed width so the result sorts as text. */
|
|
108
|
+
function pad(n, width) {
|
|
109
|
+
return String(n).padStart(width, '0');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Writes the `YYYY-MM-DD` DatePicker binds.
|
|
113
|
+
*
|
|
114
|
+
* Every part is zero padded, including the year, because the min and max
|
|
115
|
+
* comparison in the control is a string comparison: an unpadded `2024-1-5`
|
|
116
|
+
* sorts after `2024-12-01` and the bound then rejects dates inside it.
|
|
117
|
+
*/
|
|
118
|
+
export function toISODate(date) {
|
|
119
|
+
return `${pad(date.y, 4)}-${pad(date.m, 2)}-${pad(date.d, 2)}`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Today in the viewer's own zone, read from the Date's local getters and never
|
|
123
|
+
* from its ISO string.
|
|
124
|
+
*
|
|
125
|
+
* `new Date().toISOString().slice(0, 10)` is the tempting one line version and
|
|
126
|
+
* it is wrong: it converts to UTC first, so a viewer in Jakarta gets yesterday
|
|
127
|
+
* until 7am and a viewer in New York gets tomorrow from the evening on. The
|
|
128
|
+
* date the calendar marks as today has to be the date on the wall behind the
|
|
129
|
+
* viewer.
|
|
130
|
+
*/
|
|
131
|
+
export function todayLocal() {
|
|
132
|
+
const now = new Date();
|
|
133
|
+
return { y: now.getFullYear(), m: now.getMonth() + 1, d: now.getDate() };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Days in a month, leap years included.
|
|
137
|
+
*
|
|
138
|
+
* A month outside 1 to 12 has no days and returns 0, so a caller bounding a day
|
|
139
|
+
* against it rejects the month rather than accepting a day in a month that does
|
|
140
|
+
* not exist.
|
|
141
|
+
*/
|
|
142
|
+
export function daysInMonth(y, m) {
|
|
143
|
+
if (!Number.isInteger(m) || m < 1 || m > 12)
|
|
144
|
+
return 0;
|
|
145
|
+
if (m === 2)
|
|
146
|
+
return isLeapYear(y) ? 29 : 28;
|
|
147
|
+
return MONTH_LENGTHS[m - 1];
|
|
148
|
+
}
|
|
149
|
+
/** Weekday of the first of the month, 0 for Sunday. */
|
|
150
|
+
export function firstWeekday(y, m) {
|
|
151
|
+
// Day number 0 is 1970-01-01, which was a Thursday, so the count starts four
|
|
152
|
+
// weekdays into the week.
|
|
153
|
+
return mod7(toDayNumber({ y, m, d: 1 }) + 4);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Adds months, clamping the day rather than rolling into the next month.
|
|
157
|
+
*
|
|
158
|
+
* 31 January plus one month is 28 or 29 February, never 2 or 3 March. A Date
|
|
159
|
+
* rolls, so paging a picker forward from the 31st and back again lands the user
|
|
160
|
+
* on a different day than the one they were looking at, and a range whose start
|
|
161
|
+
* was the 31st can end up after its own end.
|
|
162
|
+
*/
|
|
163
|
+
export function addMonths(date, delta) {
|
|
164
|
+
const totalMonths = date.y * 12 + (date.m - 1) + delta;
|
|
165
|
+
const y = Math.floor(totalMonths / 12);
|
|
166
|
+
const m = totalMonths - y * 12 + 1;
|
|
167
|
+
return { y, m, d: Math.min(date.d, daysInMonth(y, m)) };
|
|
168
|
+
}
|
|
169
|
+
/** Adds days across month and year boundaries, leap days included. */
|
|
170
|
+
export function addDays(date, delta) {
|
|
171
|
+
return fromDayNumber(toDayNumber(date) + delta);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Orders two dates as -1, 0 or 1.
|
|
175
|
+
*
|
|
176
|
+
* A total order, so a caller can sort and bound without constructing a Date.
|
|
177
|
+
* Returning the field difference would sort just as well and would report 2 for
|
|
178
|
+
* two years apart, which quietly breaks the caller that tests for 1.
|
|
179
|
+
*/
|
|
180
|
+
export function compareDates(a, b) {
|
|
181
|
+
if (a.y !== b.y)
|
|
182
|
+
return a.y < b.y ? -1 : 1;
|
|
183
|
+
if (a.m !== b.m)
|
|
184
|
+
return a.m < b.m ? -1 : 1;
|
|
185
|
+
if (a.d !== b.d)
|
|
186
|
+
return a.d < b.d ? -1 : 1;
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Inclusive on both ends. Either bound may be null, meaning unbounded.
|
|
191
|
+
*
|
|
192
|
+
* Inclusive because a picker given min and max for a single allowed day has to
|
|
193
|
+
* offer that day. DatePicker compares its bounds as strings today, which agrees
|
|
194
|
+
* with this only while every bound is zero padded.
|
|
195
|
+
*/
|
|
196
|
+
export function withinRange(date, min, max) {
|
|
197
|
+
if (min !== null && compareDates(date, min) < 0)
|
|
198
|
+
return false;
|
|
199
|
+
if (max !== null && compareDates(date, max) > 0)
|
|
200
|
+
return false;
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The six week grid a month renders, always 42 cells so the calendar does not
|
|
205
|
+
* change height between months. Cells outside the viewed month carry
|
|
206
|
+
* `outside: true`.
|
|
207
|
+
*
|
|
208
|
+
* DatePicker pads its lead with empty spans and stops at the last day, so
|
|
209
|
+
* February in a year where it starts on a Sunday draws four rows and a 31 day
|
|
210
|
+
* month starting on a Saturday draws six. The popover resizes as the user pages
|
|
211
|
+
* through the year and the day under the pointer moves out from under it. Fixed
|
|
212
|
+
* at 42 because six weeks is the most any month can touch.
|
|
213
|
+
*
|
|
214
|
+
* `weekStartsOn` is 0 for Sunday and 1 for Monday.
|
|
215
|
+
*/
|
|
216
|
+
export function monthGrid(y, m, weekStartsOn = 0) {
|
|
217
|
+
const lead = mod7(firstWeekday(y, m) - weekStartsOn);
|
|
218
|
+
const start = toDayNumber({ y, m, d: 1 }) - lead;
|
|
219
|
+
const cells = [];
|
|
220
|
+
for (let i = 0; i < 42; i++) {
|
|
221
|
+
const date = fromDayNumber(start + i);
|
|
222
|
+
cells.push({ date, outside: date.y !== y || date.m !== m });
|
|
223
|
+
}
|
|
224
|
+
return cells;
|
|
225
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for how a checkbox or a radio looks and announces
|
|
3
|
+
* itself.
|
|
4
|
+
*
|
|
5
|
+
* Checkbox, Radio and RadioGroup paint one control three ways. The two label
|
|
6
|
+
* wrappers hold the same utilities in two orders, Checkbox marks a required
|
|
7
|
+
* option and Radio marks nothing, RadioGroup rests its box on `border-line`
|
|
8
|
+
* where the other two use `border-line-strong`, and RadioGroup still keeps its
|
|
9
|
+
* focus ring inside the selected ternary. That last one is the defect Checkbox
|
|
10
|
+
* and Radio were already fixed for: selecting an option deleted the only
|
|
11
|
+
* indicator a keyboard user had, and in a list of a hundred options that loses
|
|
12
|
+
* your place entirely. Composing from here means a fourth choice control cannot
|
|
13
|
+
* reintroduce any of it.
|
|
14
|
+
*
|
|
15
|
+
* Not exported from the package entry point - this is an implementation detail.
|
|
16
|
+
*/
|
|
17
|
+
/** The scale of the painted box: sm 14px, md 16px, lg 20px. */
|
|
18
|
+
export type ChoiceSize = 'sm' | 'md' | 'lg';
|
|
19
|
+
/** Inline is a box beside a label. Card is a bordered option surface. */
|
|
20
|
+
export type ChoiceVariant = 'inline' | 'card';
|
|
21
|
+
/** How a group lays its options out. */
|
|
22
|
+
export type ChoiceOrientation = 'vertical' | 'horizontal';
|
|
23
|
+
/**
|
|
24
|
+
* The clickable wrapper. Card is a bordered option the whole surface of which
|
|
25
|
+
* is the target.
|
|
26
|
+
*
|
|
27
|
+
* The cursor is a ternary rather than a `cursor-pointer` that a disabled state
|
|
28
|
+
* appends `cursor-not-allowed` to. Both set the same property, so a class list
|
|
29
|
+
* carrying both resolves by stylesheet order and not by the order written here,
|
|
30
|
+
* which is how a disabled option kept offering a pointer.
|
|
31
|
+
*/
|
|
32
|
+
export declare function choiceWrap(variant: ChoiceVariant, checked: boolean, disabled: boolean): string;
|
|
33
|
+
/**
|
|
34
|
+
* The transparent input that sits over the painted box and carries every native
|
|
35
|
+
* semantic.
|
|
36
|
+
*
|
|
37
|
+
* `sr-only` leaves the input 1x1 and buried under the box that replaces it, so
|
|
38
|
+
* a click aimed at what the user sees lands on nothing. It covers the box
|
|
39
|
+
* instead, and `peer` is what lets the box below read its focus state.
|
|
40
|
+
*/
|
|
41
|
+
export declare const CHOICE_INPUT = "peer absolute inset-0 m-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed";
|
|
42
|
+
/**
|
|
43
|
+
* The painted box or circle. Square for a checkbox, round for a radio, sized by
|
|
44
|
+
* ChoiceSize.
|
|
45
|
+
*
|
|
46
|
+
* The resting border is `border-line-strong`. `border-line` reads 1.25:1
|
|
47
|
+
* against the page, and the border is the only thing identifying an empty
|
|
48
|
+
* checkbox as a control, so at that contrast the control is invisible until it
|
|
49
|
+
* is used. A radio keeps a 2px ring because its selected state is a dot inside
|
|
50
|
+
* the ring rather than a fill, and a 1px ring reads as a smudge beside the dot
|
|
51
|
+
* at 14px.
|
|
52
|
+
*
|
|
53
|
+
* `mixed` paints exactly what `checked` paints. A part-selected parent is on,
|
|
54
|
+
* not a third state with a colour of its own; only the mark it holds differs.
|
|
55
|
+
*/
|
|
56
|
+
export declare function choiceBox(kind: 'checkbox' | 'radio', size: ChoiceSize, checked: boolean, mixed: boolean): string;
|
|
57
|
+
/**
|
|
58
|
+
* The focus ring, stated ONCE and outside every checked branch.
|
|
59
|
+
*
|
|
60
|
+
* The ring lived inside the checked ternary in three components, so choosing an
|
|
61
|
+
* option removed the only indicator a keyboard user had. It is a ring and not a
|
|
62
|
+
* border colour because a selected box already carries a brand border, which
|
|
63
|
+
* leaves a border-only focus state with nothing to say.
|
|
64
|
+
*/
|
|
65
|
+
export declare const CHOICE_FOCUS = "peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-brand peer-focus-visible:outline-offset-2";
|
|
66
|
+
/**
|
|
67
|
+
* The label stack beside or under the box.
|
|
68
|
+
*
|
|
69
|
+
* Checkbox and Radio both build this, in different word orders, which is how
|
|
70
|
+
* two controls in one form ended up with different gaps between a label and its
|
|
71
|
+
* description.
|
|
72
|
+
*/
|
|
73
|
+
export declare const CHOICE_LABEL_STACK = "flex flex-col gap-0.5";
|
|
74
|
+
/**
|
|
75
|
+
* The label text, sized with the control.
|
|
76
|
+
*
|
|
77
|
+
* An option label is not a field label: it names one choice rather than the
|
|
78
|
+
* group, so it stays regular weight and FIELD_LABEL keeps the medium.
|
|
79
|
+
*/
|
|
80
|
+
export declare function choiceLabel(size: ChoiceSize): string;
|
|
81
|
+
/**
|
|
82
|
+
* Secondary line under the label.
|
|
83
|
+
*
|
|
84
|
+
* The same class the hint under a text field uses, taken from field.ts rather
|
|
85
|
+
* than respelled, because a checkbox hint and an input hint sitting in one form
|
|
86
|
+
* have no reason to differ and had already drifted apart once.
|
|
87
|
+
*/
|
|
88
|
+
export declare const CHOICE_DESCRIPTION = "text-xs text-faint";
|
|
89
|
+
/**
|
|
90
|
+
* The icon slot ahead of the label.
|
|
91
|
+
*
|
|
92
|
+
* Fixed to the box size so the label starts at the same x in every row of a
|
|
93
|
+
* list, and given no colour of its own so it inherits the label and dims with
|
|
94
|
+
* the wrapper when the option is disabled.
|
|
95
|
+
*/
|
|
96
|
+
export declare function choiceIcon(size: ChoiceSize): string;
|
|
97
|
+
/**
|
|
98
|
+
* Pixel size for a lucide icon at each ChoiceSize, so a consumer does not
|
|
99
|
+
* guess.
|
|
100
|
+
*
|
|
101
|
+
* A lucide icon takes a number, not a class, so the slot above cannot size it.
|
|
102
|
+
* These are the same 14, 16 and 20 the slot reserves; an icon rendered at any
|
|
103
|
+
* other size overflows the slot or floats inside it.
|
|
104
|
+
*/
|
|
105
|
+
export declare const CHOICE_ICON_PX: Record<ChoiceSize, number>;
|
|
106
|
+
/**
|
|
107
|
+
* The fieldset a group renders, so the set is announced as a group rather than
|
|
108
|
+
* as loose controls.
|
|
109
|
+
*
|
|
110
|
+
* `min-w-0` is load bearing: a fieldset defaults to `min-width: min-content`,
|
|
111
|
+
* so one long option label widened the whole group past its column instead of
|
|
112
|
+
* wrapping.
|
|
113
|
+
*/
|
|
114
|
+
export declare const CHOICE_GROUP = "flex min-w-0 flex-col gap-2";
|
|
115
|
+
/**
|
|
116
|
+
* The row or column the options sit in.
|
|
117
|
+
*
|
|
118
|
+
* A horizontal group gets a wider inline gap than block gap: options read as
|
|
119
|
+
* separate choices across a row and as one list down a column, and an equal gap
|
|
120
|
+
* in both axes makes a wrapped row look like a grid.
|
|
121
|
+
*/
|
|
122
|
+
export declare function choiceGroupList(orientation: ChoiceOrientation): string;
|
|
123
|
+
/**
|
|
124
|
+
* The check and the mixed marks, as SVG path data on the box's own grid.
|
|
125
|
+
*
|
|
126
|
+
* Drawn as paths because the consistency suite rejects a Unicode check outright,
|
|
127
|
+
* and because a font glyph lands at a different optical weight from every other
|
|
128
|
+
* icon in the library. Both sit in a 10 by 8 viewBox centred in the box, at
|
|
129
|
+
* stroke width 1.5 with round caps, so the two marks swap without the box
|
|
130
|
+
* shifting. The mixed bar stops short of the edges so its round caps do not
|
|
131
|
+
* touch the border.
|
|
132
|
+
*/
|
|
133
|
+
export declare const CHOICE_MARK: {
|
|
134
|
+
check: string;
|
|
135
|
+
mixed: string;
|
|
136
|
+
};
|