@lekoala/date-picker 0.3.0 → 0.5.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 +281 -263
- package/custom-elements.json +30 -0
- package/dist/date-picker.css +51 -46
- package/dist/date-picker.js +685 -204
- package/dist/date-picker.min.css +1 -1
- package/dist/date-picker.min.js +13 -13
- package/dist/date-picker.standalone.min.js +65 -60
- package/dist/types/control-classes.d.ts +30 -0
- package/dist/types/control-classes.d.ts.map +1 -0
- package/dist/types/date-calendar.d.ts +49 -3
- package/dist/types/date-calendar.d.ts.map +1 -1
- package/dist/types/date-field.d.ts +12 -4
- package/dist/types/date-field.d.ts.map +1 -1
- package/dist/types/date-picker.d.ts +106 -9
- package/dist/types/date-picker.d.ts.map +1 -1
- package/dist/types/date-range.d.ts +102 -20
- package/dist/types/date-range.d.ts.map +1 -1
- package/dist/types/date.d.ts +14 -2
- package/dist/types/date.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/intl.d.ts.map +1 -1
- package/dist/types/source.d.ts +1 -0
- package/dist/types/source.d.ts.map +1 -1
- package/docs/ACCESSIBILITY.md +6 -2
- package/docs/API.md +430 -358
- package/docs/DECISIONS.md +68 -49
- package/docs/MIGRATION-FLATPICKR.md +1 -1
- package/docs/TESTING.md +1 -1
- package/docs/USE_CASES.md +156 -141
- package/package.json +8 -8
- package/src/control-classes.js +50 -0
- package/src/date-calendar.js +188 -54
- package/src/date-field.js +9 -5
- package/src/date-picker.css +51 -46
- package/src/date-picker.js +392 -83
- package/src/date-range.js +184 -30
- package/src/date.js +57 -3
- package/src/index.js +1 -0
- package/src/intl.js +52 -17
- package/src/source.js +14 -4
package/docs/DECISIONS.md
CHANGED
|
@@ -1,49 +1,68 @@
|
|
|
1
|
-
# Decisions
|
|
2
|
-
|
|
3
|
-
Frozen contracts for the
|
|
4
|
-
|
|
5
|
-
## D1 — Calendar events are flat, verb-named
|
|
6
|
-
|
|
7
|
-
Never merged with the `value` concept (`dateactivate` is activation, not `value`).
|
|
8
|
-
|
|
9
|
-
- `date-calendar`: `dateactivate`, `datechange`, `dateinvalid`, `displaychange`, `dateloadstart`, `dateloadend`, `dateloaderror`.
|
|
10
|
-
- `date-picker`: `valuechange`, `open`, `close
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
1
|
+
# Decisions
|
|
2
|
+
|
|
3
|
+
Frozen contracts for the public surface. A change to a decision below is a breaking change and needs a proposal, not an opportunistic edit.
|
|
4
|
+
|
|
5
|
+
## D1 — Calendar events are flat, verb-named
|
|
6
|
+
|
|
7
|
+
Never merged with the `value` concept (`dateactivate` is activation, not `value`).
|
|
8
|
+
|
|
9
|
+
- `date-calendar`: `dateactivate`, `datefocus`, `datechange`, `dateinvalid`, `displaychange`, `dateloadstart`, `dateloadend`, `dateloaderror`.
|
|
10
|
+
- `date-picker`: `valuechange`, `open`, `close`, plus `rangechange` in range mode.
|
|
11
|
+
|
|
12
|
+
The observation verbs stay distinct: `datefocus` observes navigation, `dateactivate` requests an activation (and is cancelable), `datechange` observes a selection. `datefocus` is named after the user event rather than after the `focusedDate` setter, so a programmatic assignment that moves no DOM focus never announces one.
|
|
13
|
+
|
|
14
|
+
No `dp:` namespace prefix on element events, even though `@lekoala/combobox` namespaces its events (`combobox:*`).
|
|
15
|
+
|
|
16
|
+
Rationale: the flat names describe a distinct concept (`dateactivate`), never collide with native input events, and are already wired through the docs, demos and tests. A namespace is not worth the churn for this package.
|
|
17
|
+
|
|
18
|
+
## D2 — `selection="none"` is the final spelling
|
|
19
|
+
|
|
20
|
+
The navigation-only calendar keeps `selection="none"`.
|
|
21
|
+
|
|
22
|
+
`selection` remains a value attribute (`single` | `none`); it is how the mini calendar speaks the navigation contract: accepted activation dispatches `dateactivate`, `value` is untouched.
|
|
23
|
+
|
|
24
|
+
## D3 — CSS: `--dp-*` is public, `.dp-*` is not
|
|
25
|
+
|
|
26
|
+
> `--dp-*` is the public styling namespace. `.dp-*` identifies component-owned DOM but is not a public styling API unless explicitly documented.
|
|
27
|
+
|
|
28
|
+
- The namespace prefix is `dp`, final. There are no `--lko-dp-*` or `.lko-dp-*` classes. A shared `--lko-*` token space is reserved for tokens actually shared between LeKoala packages, not as a per-component prefix.
|
|
29
|
+
- The `--dp-*` custom properties (background, foreground, accent, border, radius, day-size, gap, …) are the **public customization surface**. They are listed in `custom-elements.json` and are stable.
|
|
30
|
+
- Every `.dp-*` class is **internal DOM** and may change, move or disappear without a semver bump. This includes `.dp-day`, `.dp-grid`, `.dp-picker-panel` and the day-extra container.
|
|
31
|
+
- `renderDay()` is a public extension seam, but the name of the container its output is placed into is **not** promised. Consumers style their own returned nodes and assign their own class names.
|
|
32
|
+
- Demos, tests and internal code may reference `.dp-*` classes; their use there does not turn those classes into public API.
|
|
33
|
+
|
|
34
|
+
Context: DOM-backed styling hooks become API the moment they are documented — every internal rename then breaks consumers. We prefer to expose hooks (`--dp-*`, `renderDay()`, events) and keep the rendered anatomy free to evolve.
|
|
35
|
+
|
|
36
|
+
## D4 — `<date-picker>` opens on focus; `open` is read-only
|
|
37
|
+
|
|
38
|
+
- The picker opens its popover when the input receives focus (`open-on-focus`, default `true`; set `open-on-focus="false"` to opt out).
|
|
39
|
+
- Opening on focus **never steals keyboard focus**: the user can start typing immediately. The grid takes focus only on ArrowDown, on the calendar trigger, or after the user moves the pointer into the popup.
|
|
40
|
+
- Closing returns focus to the control that opened the popover (field or trigger) — the usual popover convention. Range mode is the exception: focus follows the active bound, because a first pick moves the workflow to the other bound.
|
|
41
|
+
- The calendar trigger is overlaid inside the field's own box, like the native `input[type=time]` indicator. The field reserves its place (`padding-inline-end`), keeps its border/background, and owns the focus ring for the pair; the trigger is a real button with an inner ring of its own. A range bound owns its own trigger; the two share one popup, calendar, source and state.
|
|
42
|
+
- `show()` / `hide()` are the control surface. `open` is a **read-only getter** reflecting the popover. There is no `open` attribute: the popover element is the single machine of truth and a second state machine would only drift.
|
|
43
|
+
- `readonly` mirrors native semantics: the field is non-editable, the value is still submitted, and the picker cannot change it — the calendar trigger is disabled. `disabled` additionally unsubmits the field. `readonly` is never used to mean "no typing, but calendar selection allowed".
|
|
44
|
+
- Clearing the editable input clears the canonical value (the component renders no internal clear button). Form resets follow the native `defaultValue` contract. This matches the Open UI datepicker `clear` concept: clearing is a text-editing action, not a component-owned affordance.
|
|
45
|
+
|
|
46
|
+
## D5 — Time is a native companion, not picker state
|
|
47
|
+
|
|
48
|
+
- Time fields are authored native `input[type=time]` marked `[data-time-start]` / `[data-time-end]`; `name`, `value`, `required`, `min`, `max`, `step` and submission stay native. There is no hidden time input and no time field controller.
|
|
49
|
+
- `picker.value` / `picker.range` remain date-only (`YYYY-MM-DD`); the picker never constructs a datetime, a timestamp or a timezone-bearing value.
|
|
50
|
+
- The picker only coordinates from/to order (`start <= end`, equality allowed); duration rules stay application-owned.
|
|
51
|
+
- Custom time UI and slot picking stay outside the package.
|
|
52
|
+
|
|
53
|
+
## D6 — Calendar interaction never produces an inverted range
|
|
54
|
+
|
|
55
|
+
Pointing and typing are different contracts, deliberately.
|
|
56
|
+
|
|
57
|
+
- **Interaction** (click, keyboard, endpoint drag) always yields an ordered range. While a range is being created, the second selection is sorted against the anchor: `10` then `5` commits `5 -> 10`, and `10` then `10` a one-day range. No `dateinvalid` is fired merely because the second point is earlier.
|
|
58
|
+
- **Text entry** may be temporarily inverted. Typing `start` past `end` keeps both values and reports `rangeOrderStart` / `rangeOrderEnd` on the modified bound, because a half-typed pair is a normal editing state.
|
|
59
|
+
- An explicit change to **one bound of an already complete pair** keeps that bound's identity: moving `end` before `start` is refused rather than silently rewriting the other field.
|
|
60
|
+
- Because one interaction can move both bounds at once, a transition describes the whole resulting pair (`{ status, endpoint, changedEndpoints, range, activeEndpoint }`) and is applied in a single write: one `rangechange`, with synthetic `input` / `change` only on the bounds that actually moved.
|
|
61
|
+
- Hover, keyboard focus and endpoint drag read the **same projection** (`previewRange()`) that the commit applies, so what the band promises is what the next selection commits. A preview writes nothing: no `rangechange`, no field write, no validation, no source request.
|
|
62
|
+
- Click, keyboard and drop all end in that one range machine and in the same `ensureDate` plus cancelable `dateactivate` path, so an application veto cannot be bypassed by choosing another input device.
|
|
63
|
+
|
|
64
|
+
## D7 — Author classes own header control styling
|
|
65
|
+
|
|
66
|
+
`controlClasses` and `setDefaultControlClasses()` are the public hooks for styling the month, year and navigation roles with a host design system. A role with non-empty author classes receives no library skin, including in forced colors. Layout remains component-owned. Roles without author classes retain the default skin.
|
|
67
|
+
|
|
68
|
+
The `.dp-*` namespace stays internal (D3); it is reserved and ignored in author class lists. Day grid cells and the picker trigger are outside this API. There is no HTML attribute.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Flatpickr / Formidable migration map
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This project was scoped against the existing `FlatpickrInput` wrapper rather than against Flatpickr's full feature list.
|
|
4
4
|
|
|
5
5
|
## Directly covered
|
|
6
6
|
|
package/docs/TESTING.md
CHANGED
|
@@ -68,6 +68,6 @@ Keep explicit tests for these regressions:
|
|
|
68
68
|
|
|
69
69
|
## CI
|
|
70
70
|
|
|
71
|
-
The included workflow uses Bun 1.4.
|
|
71
|
+
The included workflow uses Bun 1.4.3 and runs static checks/unit tests before Playwright.
|
|
72
72
|
|
|
73
73
|
`bun.lock` is committed. Keep installs frozen in CI (`bun install --frozen-lockfile`) so dependency resolution stays deterministic. Regenerate the lock with `bun install` when the manifest changes and commit it in the same change.
|
package/docs/USE_CASES.md
CHANGED
|
@@ -1,141 +1,156 @@
|
|
|
1
|
-
# Use cases
|
|
2
|
-
|
|
3
|
-
These cases define the product more strongly than a feature checklist. A proposed API change should be tested against them before being accepted.
|
|
4
|
-
|
|
5
|
-
## U1 — Editable localized date field
|
|
6
|
-
|
|
7
|
-
A user can type `06/09/2026` in `fr-BE`, open the calendar, select the same date visually, and submit `2026-09-06` to the server.
|
|
8
|
-
|
|
9
|
-
Acceptance:
|
|
10
|
-
|
|
11
|
-
- input remains editable;
|
|
12
|
-
- label still targets the real input;
|
|
13
|
-
- required/custom validity still works;
|
|
14
|
-
- display formatting does not leak into submitted data.
|
|
15
|
-
|
|
16
|
-
## U2 — Inline selector
|
|
17
|
-
|
|
18
|
-
An inline calendar owns one selected date.
|
|
19
|
-
|
|
20
|
-
Acceptance:
|
|
21
|
-
|
|
22
|
-
- month/year navigation does not change the value;
|
|
23
|
-
- arrow-key focus does not change the value;
|
|
24
|
-
- click/Enter/Space activates and selects;
|
|
25
|
-
- today, focused and selected are visually/semantically distinct.
|
|
26
|
-
|
|
27
|
-
## U3 — Mini calendar navigator
|
|
28
|
-
|
|
29
|
-
An inline `selection="none"` calendar navigates a separate agenda/calendar.
|
|
30
|
-
|
|
31
|
-
Acceptance:
|
|
32
|
-
|
|
33
|
-
- there may be no selected value at all;
|
|
34
|
-
- changing month/year only changes what the mini calendar displays;
|
|
35
|
-
- activating a date dispatches `dateactivate` and the application calls `calendarView.gotoDate(date)`;
|
|
36
|
-
- outside-month days remain activatable;
|
|
37
|
-
- six-row height is available as presentation, not baked into month math;
|
|
38
|
-
- ISO week numbers are available;
|
|
39
|
-
- a narrow/compact presentation can use short month names (`month-format="short"`).
|
|
40
|
-
|
|
41
|
-
## U4 — Constrained dates
|
|
42
|
-
|
|
43
|
-
Example: only the next 90 days, weekdays only.
|
|
44
|
-
|
|
45
|
-
Acceptance:
|
|
46
|
-
|
|
47
|
-
- min/max affect both activation and typed validation;
|
|
48
|
-
- custom disabled days can still receive keyboard focus so the reason can be announced;
|
|
49
|
-
- disabled activation never changes the value;
|
|
50
|
-
- previous/next controls stop at bounded months.
|
|
51
|
-
|
|
52
|
-
## U5 — Linked start/end dates
|
|
53
|
-
|
|
54
|
-
Two fields represent a range.
|
|
55
|
-
|
|
56
|
-
Acceptance:
|
|
57
|
-
|
|
58
|
-
- end cannot validate before start;
|
|
59
|
-
- start cannot validate after end;
|
|
60
|
-
- changing one side revalidates the other;
|
|
61
|
-
- the component never silently rewrites the sibling value;
|
|
62
|
-
- each picker remains independently usable and replaceable.
|
|
63
|
-
|
|
64
|
-
## U6 — Remote availability
|
|
65
|
-
|
|
66
|
-
Changing the displayed month loads per-day state from a backend.
|
|
67
|
-
|
|
68
|
-
Acceptance:
|
|
69
|
-
|
|
70
|
-
- load receives the actual rendered range;
|
|
71
|
-
- superseded navigation aborts the previous request;
|
|
72
|
-
- state can disable a day or describe it;
|
|
73
|
-
- loading logic is independent of URL/backend format;
|
|
74
|
-
- a typed date can call `ensureDate()` before validation.
|
|
75
|
-
|
|
76
|
-
## U7 — Morning / afternoon indicators
|
|
77
|
-
|
|
78
|
-
A date can carry richer state such as morning/afternoon availability.
|
|
79
|
-
|
|
80
|
-
Acceptance:
|
|
81
|
-
|
|
82
|
-
- the business fields remain opaque to core logic;
|
|
83
|
-
- visual badges/dots live inside the owned day cell;
|
|
84
|
-
- accessible meaning is supplied through `description`;
|
|
85
|
-
- custom rendering cannot replace focus/ARIA semantics.
|
|
86
|
-
|
|
87
|
-
## U8 — Keyboard-only operation
|
|
88
|
-
|
|
89
|
-
Everything above works without a pointing device.
|
|
90
|
-
|
|
91
|
-
Acceptance:
|
|
92
|
-
|
|
93
|
-
- one grid cell in the Tab sequence;
|
|
94
|
-
- arrows, Home/End, PageUp/PageDown, Shift+PageUp/PageDown work;
|
|
95
|
-
- changing display follows keyboard focus when focus crosses a month;
|
|
96
|
-
- activation requires Enter/Space;
|
|
97
|
-
- Escape closes the picker and restores a sensible focus target;
|
|
98
|
-
- month/year selects remain normal native controls.
|
|
99
|
-
|
|
100
|
-
## U9 — Two fields, one shared calendar
|
|
101
|
-
|
|
102
|
-
A form has two editable date fields (arrival/departure) that share a single calendar surface inside one picker popup.
|
|
103
|
-
|
|
104
|
-
Acceptance:
|
|
105
|
-
|
|
106
|
-
- each field stays independently editable and localized, with its own hidden ISO value for submission;
|
|
107
|
-
- opening from a bound targets that bound: selecting a first date commits it, moves the active endpoint to the other bound without closing, and a second selection commits and closes;
|
|
108
|
-
- once an ordered range is complete, activating before it extends the start and activating after it extends the end, regardless of the opening field; the opposite bound stays unchanged and readonly/disabled bounds remain protected;
|
|
109
|
-
- a preloaded range shows the band on open and is never reordered;
|
|
110
|
-
- month/year navigation and arrow-key focus never mutate either bound;
|
|
111
|
-
- an inverted range (start after end) keeps both entered values and surfaces an order error on the bound that was just modified; changing either side revalidates both;
|
|
112
|
-
- form reset restores both fields in one resync;
|
|
113
|
-
- a stale async availability response arriving after the active endpoint changed never commits a value or closes the popup;
|
|
114
|
-
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
|
|
1
|
+
# Use cases
|
|
2
|
+
|
|
3
|
+
These cases define the product more strongly than a feature checklist. A proposed API change should be tested against them before being accepted.
|
|
4
|
+
|
|
5
|
+
## U1 — Editable localized date field
|
|
6
|
+
|
|
7
|
+
A user can type `06/09/2026` in `fr-BE`, open the calendar, select the same date visually, and submit `2026-09-06` to the server.
|
|
8
|
+
|
|
9
|
+
Acceptance:
|
|
10
|
+
|
|
11
|
+
- input remains editable;
|
|
12
|
+
- label still targets the real input;
|
|
13
|
+
- required/custom validity still works;
|
|
14
|
+
- display formatting does not leak into submitted data.
|
|
15
|
+
|
|
16
|
+
## U2 — Inline selector
|
|
17
|
+
|
|
18
|
+
An inline calendar owns one selected date.
|
|
19
|
+
|
|
20
|
+
Acceptance:
|
|
21
|
+
|
|
22
|
+
- month/year navigation does not change the value;
|
|
23
|
+
- arrow-key focus does not change the value;
|
|
24
|
+
- click/Enter/Space activates and selects;
|
|
25
|
+
- today, focused and selected are visually/semantically distinct.
|
|
26
|
+
|
|
27
|
+
## U3 — Mini calendar navigator
|
|
28
|
+
|
|
29
|
+
An inline `selection="none"` calendar navigates a separate agenda/calendar.
|
|
30
|
+
|
|
31
|
+
Acceptance:
|
|
32
|
+
|
|
33
|
+
- there may be no selected value at all;
|
|
34
|
+
- changing month/year only changes what the mini calendar displays;
|
|
35
|
+
- activating a date dispatches `dateactivate` and the application calls `calendarView.gotoDate(date)`;
|
|
36
|
+
- outside-month days remain activatable;
|
|
37
|
+
- six-row height is available as presentation, not baked into month math;
|
|
38
|
+
- ISO week numbers are available;
|
|
39
|
+
- a narrow/compact presentation can use short month names (`month-format="short"`).
|
|
40
|
+
|
|
41
|
+
## U4 — Constrained dates
|
|
42
|
+
|
|
43
|
+
Example: only the next 90 days, weekdays only.
|
|
44
|
+
|
|
45
|
+
Acceptance:
|
|
46
|
+
|
|
47
|
+
- min/max affect both activation and typed validation;
|
|
48
|
+
- custom disabled days can still receive keyboard focus so the reason can be announced;
|
|
49
|
+
- disabled activation never changes the value;
|
|
50
|
+
- previous/next controls stop at bounded months.
|
|
51
|
+
|
|
52
|
+
## U5 — Linked start/end dates
|
|
53
|
+
|
|
54
|
+
Two fields represent a range.
|
|
55
|
+
|
|
56
|
+
Acceptance:
|
|
57
|
+
|
|
58
|
+
- end cannot validate before start;
|
|
59
|
+
- start cannot validate after end;
|
|
60
|
+
- changing one side revalidates the other;
|
|
61
|
+
- the component never silently rewrites the sibling value;
|
|
62
|
+
- each picker remains independently usable and replaceable.
|
|
63
|
+
|
|
64
|
+
## U6 — Remote availability
|
|
65
|
+
|
|
66
|
+
Changing the displayed month loads per-day state from a backend.
|
|
67
|
+
|
|
68
|
+
Acceptance:
|
|
69
|
+
|
|
70
|
+
- load receives the actual rendered range;
|
|
71
|
+
- superseded navigation aborts the previous request;
|
|
72
|
+
- state can disable a day or describe it;
|
|
73
|
+
- loading logic is independent of URL/backend format;
|
|
74
|
+
- a typed date can call `ensureDate()` before validation.
|
|
75
|
+
|
|
76
|
+
## U7 — Morning / afternoon indicators
|
|
77
|
+
|
|
78
|
+
A date can carry richer state such as morning/afternoon availability.
|
|
79
|
+
|
|
80
|
+
Acceptance:
|
|
81
|
+
|
|
82
|
+
- the business fields remain opaque to core logic;
|
|
83
|
+
- visual badges/dots live inside the owned day cell;
|
|
84
|
+
- accessible meaning is supplied through `description`;
|
|
85
|
+
- custom rendering cannot replace focus/ARIA semantics.
|
|
86
|
+
|
|
87
|
+
## U8 — Keyboard-only operation
|
|
88
|
+
|
|
89
|
+
Everything above works without a pointing device.
|
|
90
|
+
|
|
91
|
+
Acceptance:
|
|
92
|
+
|
|
93
|
+
- one grid cell in the Tab sequence;
|
|
94
|
+
- arrows, Home/End, PageUp/PageDown, Shift+PageUp/PageDown work;
|
|
95
|
+
- changing display follows keyboard focus when focus crosses a month;
|
|
96
|
+
- activation requires Enter/Space;
|
|
97
|
+
- Escape closes the picker and restores a sensible focus target;
|
|
98
|
+
- month/year selects remain normal native controls.
|
|
99
|
+
|
|
100
|
+
## U9 — Two fields, one shared calendar
|
|
101
|
+
|
|
102
|
+
A form has two editable date fields (arrival/departure) that share a single calendar surface inside one picker popup.
|
|
103
|
+
|
|
104
|
+
Acceptance:
|
|
105
|
+
|
|
106
|
+
- each field stays independently editable and localized, with its own hidden ISO value for submission;
|
|
107
|
+
- opening from a bound targets that bound: selecting a first date commits it, moves the active endpoint to the other bound without closing, and a second selection commits and closes;
|
|
108
|
+
- once an ordered range is complete, activating before it extends the start and activating after it extends the end, regardless of the opening field; the opposite bound stays unchanged and readonly/disabled bounds remain protected;
|
|
109
|
+
- a preloaded range shows the band on open and is never reordered;
|
|
110
|
+
- month/year navigation and arrow-key focus never mutate either bound;
|
|
111
|
+
- an inverted range (start after end) keeps both entered values and surfaces an order error on the bound that was just modified; changing either side revalidates both;
|
|
112
|
+
- form reset restores both fields in one resync;
|
|
113
|
+
- a stale async availability response arriving after the active endpoint changed never commits a value or closes the popup;
|
|
114
|
+
- calendar interaction never produces an inverted range: while a range is being created, a second selection before the first is sorted (`10` then `5` commits `5 -> 10`) in one atomic update; an explicit change to one bound of an already complete pair keeps that bound's identity instead;
|
|
115
|
+
- pointer hover and keyboard navigation project the range the next selection would commit onto the band, without changing any value, and a selection commits exactly what was projected;
|
|
116
|
+
- the endpoints of a complete range can be dragged to adjust it, through the same projection, the same availability check and the same cancelable activation as a click;
|
|
117
|
+
- only the bounds are validated; per-night availability stays application-owned.
|
|
118
|
+
|
|
119
|
+
## U10 — Date with native time companions
|
|
120
|
+
|
|
121
|
+
A form pairs a date with one or two native times (appointment at `09:30`, from/to on the same day).
|
|
122
|
+
|
|
123
|
+
Acceptance:
|
|
124
|
+
|
|
125
|
+
- times stay native `input[type=time]` fields with their own names and submission;
|
|
126
|
+
- `value` stays a `YYYY-MM-DD` date;
|
|
127
|
+
- an inverted from/to surfaces an order error on the modified bound;
|
|
128
|
+
- a missing or disabled time lifts the order constraint;
|
|
129
|
+
- calendar selection and form reset keep the entered times.
|
|
130
|
+
|
|
131
|
+
## U11 — Host design system controls
|
|
132
|
+
|
|
133
|
+
The calendar header uses application-owned control classes without styling internal `.dp-*` selectors.
|
|
134
|
+
|
|
135
|
+
Acceptance:
|
|
136
|
+
|
|
137
|
+
- author classes survive every render;
|
|
138
|
+
- the library applies no border, background, caret, padding, hover, disabled or focus skin to a role with author classes;
|
|
139
|
+
- controls stretch to equal heights, the year keeps its grid-track width and long month names truncate;
|
|
140
|
+
- labels, keyboard navigation and focus restoration are unchanged;
|
|
141
|
+
- controls without author classes keep the default skin.
|
|
142
|
+
|
|
143
|
+
## Not a use case for this package
|
|
144
|
+
|
|
145
|
+
Do not grow the API to cover these without a separate design:
|
|
146
|
+
|
|
147
|
+
- time slots;
|
|
148
|
+
- duration picking;
|
|
149
|
+
- timezone conversion;
|
|
150
|
+
- appointment recurrence;
|
|
151
|
+
- resource scheduling;
|
|
152
|
+
- drag/drop;
|
|
153
|
+
- event rendering;
|
|
154
|
+
- multi-day scheduler views.
|
|
155
|
+
|
|
156
|
+
Those belong to `@lekoala/calendar` or application logic.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lekoala/date-picker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Native-first date picker and inline calendar primitives",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"homepage": "https://github.com/lekoala/date-picker",
|
|
22
22
|
"bugs": "https://github.com/lekoala/date-picker/issues",
|
|
23
|
-
"packageManager": "bun@1.4.
|
|
23
|
+
"packageManager": "bun@1.4.3",
|
|
24
24
|
"keywords": [
|
|
25
25
|
"date-picker",
|
|
26
26
|
"calendar",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
"typecheck": "tsc -p jsconfig.json",
|
|
144
144
|
"types": "tsc -p tsconfig.types.json",
|
|
145
145
|
"test": "bun test test/unit",
|
|
146
|
-
"test:syntax": "node --check src/date.js && node --check src/intl.js && node --check src/calendar-model.js && node --check src/source.js && node --check src/messages.js && node --check src/date-calendar.js && node --check src/date-field.js && node --check src/date-picker.js && node --check src/date-range.js && node --check src/time.js && node --check src/index.js && node --check src/define.js",
|
|
146
|
+
"test:syntax": "node --check src/date.js && node --check src/intl.js && node --check src/calendar-model.js && node --check src/source.js && node --check src/messages.js && node --check src/control-classes.js && node --check src/date-calendar.js && node --check src/date-field.js && node --check src/date-picker.js && node --check src/date-range.js && node --check src/time.js && node --check src/index.js && node --check src/define.js",
|
|
147
147
|
"test:types": "tsc -p test/types/tsconfig.json",
|
|
148
148
|
"test:browser": "playwright test browser --project chromium",
|
|
149
149
|
"test:browser:accessibility": "playwright test test/browser/picker.spec.js test/browser/focus.spec.js test/browser/calendar.spec.js test/browser/form-state.spec.js test/browser/surfaces.spec.js",
|
|
@@ -162,13 +162,13 @@
|
|
|
162
162
|
"prepublishOnly": "bun run verify"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
|
-
"@biomejs/biome": "2.5.
|
|
166
|
-
"@lekoala/calendar": "0.
|
|
167
|
-
"@playwright/test": "1.
|
|
168
|
-
"temporal-polyfill": "^1.0.
|
|
165
|
+
"@biomejs/biome": "2.5.15",
|
|
166
|
+
"@lekoala/calendar": "0.2.0",
|
|
167
|
+
"@playwright/test": "1.64.0",
|
|
168
|
+
"temporal-polyfill": "^1.0.5",
|
|
169
169
|
"typescript": "7.0.2"
|
|
170
170
|
},
|
|
171
171
|
"dependencies": {
|
|
172
|
-
"@lekoala/floating": "^0.
|
|
172
|
+
"@lekoala/floating": "^0.3.1"
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** @typedef {{ month: string, year: string, nav: string }} ControlClasses */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize author class tokens. The dp-* namespace belongs to the component.
|
|
5
|
+
* @param {unknown} value
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeControlClass(value) {
|
|
8
|
+
if (typeof value !== "string") return "";
|
|
9
|
+
return [
|
|
10
|
+
...new Set(
|
|
11
|
+
value
|
|
12
|
+
.split(/\s+/)
|
|
13
|
+
.filter(
|
|
14
|
+
(token) =>
|
|
15
|
+
token &&
|
|
16
|
+
!token.startsWith("dp-") &&
|
|
17
|
+
![...token].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127),
|
|
18
|
+
),
|
|
19
|
+
),
|
|
20
|
+
].join(" ");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @param {ControlClasses} a @param {ControlClasses} b */
|
|
24
|
+
export function sameControlClasses(a, b) {
|
|
25
|
+
return a.month === b.month && a.year === b.year && a.nav === b.nav;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @type {ControlClasses} */
|
|
29
|
+
let defaults = { month: "", year: "", nav: "" };
|
|
30
|
+
|
|
31
|
+
export function getDefaultControlClasses() {
|
|
32
|
+
return { ...defaults };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @param {Partial<ControlClasses> | null} classes */
|
|
36
|
+
export function resolveControlClasses(classes) {
|
|
37
|
+
const result = getDefaultControlClasses();
|
|
38
|
+
for (const role of /** @type {const} */ (["month", "year", "nav"])) {
|
|
39
|
+
if (classes && Object.hasOwn(classes, role)) result[role] = normalizeControlClass(classes[role]);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Set defaults for subsequently created elements (existing elements are unchanged).
|
|
46
|
+
* @param {Partial<ControlClasses>} classes
|
|
47
|
+
*/
|
|
48
|
+
export function setDefaultControlClasses(classes) {
|
|
49
|
+
defaults = resolveControlClasses(classes);
|
|
50
|
+
}
|