ten-protons-datepicker 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ten Protons
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,306 @@
1
- # Temporary Holding Version
1
+ # Ten Protons Datepicker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Zero-dependency, accessible, framework-agnostic datepicker. Vanilla TypeScript + a native Web Component.
4
+
5
+ - WAI-ARIA date picker dialog pattern, full keyboard support, live-region announcements
6
+ - Single, range and multiple selection, and an optional time selector, as separate, tree-shakeable modules
7
+ - `valueInUtc`: your backend speaks UTC, the picker shows local time, and the conversion happens inside the picker
8
+ - `Intl`-based localisation, RTL, locale-derived input format, first day of week
9
+ - Manual input that is parsed on blur / Enter and validated against min / max / disabled dates
10
+ - CSS variables, light / dark (`prefers-color-scheme` or `data-theme`), replaceable class names
11
+ - ESM only, strict TypeScript types, no runtime dependencies
12
+
13
+ ```sh
14
+ npm i ten-protons-datepicker
15
+ ```
16
+
17
+ ## Web Component
18
+
19
+ ```html
20
+ <script type="module">
21
+ import 'ten-protons-datepicker/wc';
22
+ import 'ten-protons-datepicker/locales/uk'; // optional UI strings for locale="uk"
23
+ </script>
24
+
25
+ <x-datepicker name="period" mode="range" locale="uk" min="2025-01-01" label="Period"></x-datepicker>
26
+ ```
27
+
28
+ `mode="range"` / `mode="multiple"` and `enable-time` load their module on demand, so single-date pages don't pay for them.
29
+
30
+ | Attribute | Notes |
31
+ | --- | --- |
32
+ | `value` | ISO: `2025-03-05`, range `2025-03-01/2025-03-10`, multiple `2025-03-01,2025-03-05`. Unix timestamps work too; with `output-timestamp` the property returns them. See [Unix timestamps](#unix-timestamps) |
33
+ | `name`, `placeholder`, `label`, `disabled` | `label` is the input's accessible name. Form-associated where `ElementInternals` exists |
34
+ | `mode`, `locale`, `min`, `max`, `date-format`, `first-day`, `position`, `theme` | see [Options](#options) |
35
+ | `number-of-months`, `show-other-months`, `allow-input`, `close-on-select`, `disabled-days`, `output-timestamp`, `enable-time`, `value-in-utc`, `inline` | booleans: present = true, `="false"` = false |
36
+
37
+ ```js
38
+ const el = document.querySelector('x-datepicker');
39
+ el.value; // ISO string of the current selection
40
+ el.options = { disabledDates: (d) => d.getDay() === 0, onChange: console.log };
41
+ el.addEventListener('change', (e) => console.log(e.detail)); // also: open, close, monthChange, yearChange
42
+ await el.ready; // resolves with the underlying Datepicker
43
+ ```
44
+
45
+ Style with `::part(input)`, `::part(toggle)` and the `--tp-*` variables.
46
+
47
+ ## Imperative API
48
+
49
+ ```ts
50
+ import { Datepicker } from 'ten-protons-datepicker';
51
+ import { withRange } from 'ten-protons-datepicker/range';
52
+ import 'ten-protons-datepicker/core.css';
53
+ import 'ten-protons-datepicker/themes.css';
54
+
55
+ const dp = withRange(new Datepicker(input, { locale: 'en-GB', minDate: new Date() }));
56
+
57
+ dp.addEventListener('change', (e) => console.log(e.detail)); // or onChange option
58
+ dp.setRange(start, end);
59
+ dp.getDate(); // Date | Date[] | null (numbers with outputTimestamp)
60
+ dp.destroy();
61
+ ```
62
+
63
+ Methods: `open() close() toggle() destroy() getDate() setDate(date, triggerChange?) setOptions(partial) jumpToDate(date) setRange(start, end)`.
64
+
65
+ `withMultiple` lives in `ten-protons-datepicker/multiple` and `withTime` in `ten-protons-datepicker/time`. Without the helpers, import the module for its side effect and pass `mode: 'range'` / `enableTime: true`. Selecting a mode or `enableTime` without importing its module throws a descriptive error.
66
+
67
+ With `inline: true` the calendar renders inside the given element (any element, not just an input).
68
+
69
+ ### Options
70
+
71
+ | Option | Default | |
72
+ | --- | --- | --- |
73
+ | `mode` | `'single'` | `'single' \| 'range' \| 'multiple'` |
74
+ | `selected`, `defaultDate` | | initial value / month shown when empty |
75
+ | `minDate`, `maxDate` | | `Date`, ISO `yyyy-MM-dd`, or a Unix timestamp in seconds (`DateInput`; also accepted by `selected`, `setDate`) |
76
+ | `disabledDates`, `disabledDaysOfWeek` | | `Date[]` or `(date) => boolean`; `0` = Sunday |
77
+ | `locale` | browser | BCP 47 tag or a `LocaleObject` |
78
+ | `firstDayOfWeek` | from locale | `0`–`6` |
79
+ | `dateFormat` | from locale | tokens: `yyyy yy MMMM MMM MM M dd d EEEE EEE`, quote literals: `d 'de' MMMM` |
80
+ | `inline`, `showOtherMonths`, `numberOfMonths` | `false`, `false`, `1` | `numberOfMonths: 1 \| 2` |
81
+ | `position` | `'auto'` | `auto top bottom left right`; auto flips above when there is no room below |
82
+ | `theme` | auto | `'light' \| 'dark'` |
83
+ | `allowInput` | `true` | `false` makes the input read-only |
84
+ | `closeOnSelect` | `true` (single, range), `false` (multiple, and with `enableTime`) | |
85
+ | `closeOnOutsideClick` | `true` | outside pointer-down or focus |
86
+ | `classes` | | replace any default class name (`ClassNames`) |
87
+ | `enableTime` | `false` | add an `HH:mm:ss` time selector below the calendar, see [Date & time](#date--time). Needs `ten-protons-datepicker/time` |
88
+ | `valueInUtc` | `false` | the backend works in UTC: values going in and out are UTC, the picker shows local time, see [UTC backends](#utc-backends-valueinutc) |
89
+ | `outputTimestamp` | `false` | report Unix timestamps (seconds) instead of `Date`s / ISO strings, see [Unix timestamps](#unix-timestamps) |
90
+ | `isoInput` | | input that receives the ISO value (or the timestamp with `outputTimestamp`), e.g. a hidden field |
91
+ | `anchor`, `appendTo` | input, `document.body` | construction only |
92
+ | `onChange onOpen onClose onMonthChange` | | callbacks; the same events fire as `CustomEvent`s |
93
+
94
+ `setOptions` accepts everything except `inline`, `anchor` and `appendTo`.
95
+
96
+ ## Input handling
97
+
98
+ Typing is parsed on blur or Enter (ISO is always accepted). Separators are matched leniently (`5/3/2025` parses with `dd.MM.yyyy`). Range text is `start – end`, multiple is comma separated.
99
+
100
+ - Valid: the selection, calendar and `isoInput` update and `change` fires.
101
+ - Invalid, disabled or too many dates: on Enter the input is marked `aria-invalid` and keeps its text; on blur it reverts to the last valid value.
102
+ - Empty text clears the selection.
103
+
104
+ `change` also dispatches native bubbling `input` / `change` events on the input, so `v-model`-style bindings work.
105
+
106
+ ## Unix timestamps
107
+
108
+ **Input is automatic, with no option.** Type or paste a Unix timestamp in seconds (9–11 digits, e.g. `1794072736`) and the picker converts it: the input shows the date in your `dateFormat` and the calendar jumps to it. Ranges and multiple dates work too (`1741996800 - 1742428800`), and timestamps are also understood in the web component's `value` attribute, in `selected` / `setDate()` / `minDate` / `maxDate` (as a number or a digit string), and in a prefilled input. Anything with fewer digits (like `20250315`) is still treated as a normal date.
109
+
110
+ **Output is opt-in with `outputTimestamp: true`** (`output-timestamp` on the web component). Everything the picker reports then uses seconds instead of `Date`s / ISO strings:
111
+
112
+ | | `outputTimestamp: false` (default) | `outputTimestamp: true` |
113
+ | --- | --- | --- |
114
+ | `getDate()`, `onChange`, `change` event `detail` | `Date` / `Date[]` / `null` | `number` / `number[]` / `null` |
115
+ | `isoInput` value, web component `value` and form value | `2025-03-15`, `2025-03-10/2025-03-20` | `1741996800`, `1741996800/1742428800` |
116
+ | text shown in the input | formatted with `dateFormat` | formatted with `dateFormat` (unchanged) |
117
+
118
+ All four combinations work:
119
+
120
+ ```ts
121
+ const dp = new Datepicker(input, { dateFormat: 'dd.MM.yyyy', outputTimestamp: true, onChange: (ts) => save(ts) });
122
+
123
+ // timestamp -> timestamp input "1794072736" shows 07.11.2026, getDate() = 1794009600
124
+ // formatted -> timestamp input "07.11.2026" shows 07.11.2026, getDate() = 1794009600
125
+ // with outputTimestamp: false (default):
126
+ // timestamp -> date input "1794072736" shows 07.11.2026, getDate() = Date(2026-11-07)
127
+ // formatted -> date input "07.11.2026" shows 07.11.2026, getDate() = Date(2026-11-07)
128
+ ```
129
+
130
+ (Values shown for a browser in UTC; elsewhere the output is that zone's local midnight of 7 November, e.g. `1794002400` in Europe/Kyiv.)
131
+
132
+ Things to know:
133
+
134
+ - **Day granularity (without `enableTime`).** A date-only picker selects days, not instants; with [`enableTime`](#date--time) the time of day is kept too. A timestamp is converted to the **local** calendar day it falls on, and the timestamp that comes out is the **local midnight** of that day. So `timestamp -> timestamp` normalises the time of day (`1794072736` comes back as `1794009600`), and it is stable from then on: feeding the output back in gives the same value.
135
+ - **Time zones.** "Local" is the browser's time zone, in line with the rest of the picker. A timestamp for UTC midnight (`1741996800` = 2025-03-15T00:00Z) is still March 14 in New York. If your backend stores UTC-midnight timestamps for plain dates, set [`valueInUtc`](#utc-backends-valueinutc): `1741996800` is then 15 March in every time zone.
136
+ - **Seconds only.** Millisecond timestamps (13 digits) are not recognised.
137
+ - **Types.** `DatepickerValue` is `Date | Date[] | number | number[] | null`; narrow it with `typeof` / `instanceof` or check `outputTimestamp` yourself. With the default nothing changes at runtime.
138
+
139
+ ## Date & time
140
+
141
+ `enableTime: true` (`enable-time` on the web component) adds a 24-hour **hours : minutes : seconds** selector below the calendar. It is an optional module, so date-only bundles don't pay for it:
142
+
143
+ ```ts
144
+ import { Datepicker } from 'ten-protons-datepicker';
145
+ import { withTime } from 'ten-protons-datepicker/time'; // or: import 'ten-protons-datepicker/time' and pass enableTime: true
146
+
147
+ const dp = withTime(new Datepicker(input, { dateFormat: 'dd.MM.yyyy', onChange: (d) => save(d) }));
148
+ ```
149
+
150
+ ```html
151
+ <x-datepicker enable-time output-timestamp name="meeting"></x-datepicker> <!-- loads the time module itself -->
152
+ ```
153
+
154
+ How it behaves:
155
+
156
+ - **Picking.** A newly picked day gets the time currently shown in the selector (`00:00:00` until you change it). Editing the selector immediately changes the time of the selected date(s) and fires `change`. Each field is a spinbutton: type digits, `↑` / `↓` (wraps), `Home` / `End`; values are clamped on blur / `Enter`.
157
+ - **The popup stays open** after picking a day so you can set the time (`closeOnSelect` defaults to `false` with `enableTime`). Close it with `Escape`, `Enter` in the input, or a click outside.
158
+ - **Display.** The input shows the date in your `dateFormat` followed by the time as `HH:mm:ss` (24-hour): `15.03.2025 13:07:09`. `dateFormat` only controls the date part. The time format is fixed.
159
+ - **Typing.** `15.03.2025 13:07:09`, `15.03.2025 13:07` (seconds default to 0), ISO `2025-03-15T13:07:09` (or with a space), and Unix timestamps. A date typed **without** a time takes the time currently in the selector. An invalid time (`24:00`, `10:60`) is rejected like any invalid date.
160
+ - **Range and multiple.** One time applies to all selected dates: the selector sets every date at once. Text typed with different times per date (`10.03.2025 09:00:00 - 12.03.2025 18:30:00`) keeps them until you touch the selector.
161
+ - **Limits are day-based.** `minDate`, `maxDate` and the disabled-date options compare calendar days; they don't constrain the time of day.
162
+ - **Screen readers.** The selector is a labelled group (`time` / `hours` / `minutes` / `seconds` strings, overridable in the `locale` object, translated in `locales/uk`), and the selection announcement includes the time.
163
+ - **Switching at runtime.** `setOptions({ enableTime })` adds `00:00:00` to selected dates or drops their time.
164
+
165
+ ### Time and `outputTimestamp`
166
+
167
+ Timestamps are understood on input whatever the options are (see [Unix timestamps](#unix-timestamps)); `outputTimestamp` and `enableTime` decide what comes out:
168
+
169
+ | | `outputTimestamp: false` (default) | `outputTimestamp: true` |
170
+ | --- | --- | --- |
171
+ | **`enableTime: false`** (default) | `Date` at local midnight; ISO `2025-03-15` | timestamp of local midnight: `1741996800` |
172
+ | **`enableTime: true`** | `Date` with the selected time; ISO `2025-03-15T13:07:09` | exact timestamp including the time: `1742044029` |
173
+
174
+ "Out" means `getDate()`, `onChange`, the `change` event `detail`, the `isoInput` value, and the web component's `value` and form value. The text in the input is always the formatted date (plus `HH:mm:ss` with `enableTime`).
175
+
176
+ All combinations, with and without time, with and without timestamp output (values for a browser in UTC):
177
+
178
+ ```ts
179
+ // 1. date only, Date out (the defaults)
180
+ new Datepicker(input, { dateFormat: 'dd.MM.yyyy' });
181
+ // typed "15.03.2025" shows 15.03.2025 getDate() = Date(2025-03-15 00:00:00)
182
+ // typed "1742044029" shows 15.03.2025 getDate() = Date(2025-03-15 00:00:00) the time is dropped
183
+
184
+ // 2. date only, timestamp out
185
+ new Datepicker(input, { dateFormat: 'dd.MM.yyyy', outputTimestamp: true });
186
+ // typed "15.03.2025" shows 15.03.2025 getDate() = 1741996800
187
+ // typed "1742044029" shows 15.03.2025 getDate() = 1741996800 normalised to midnight
188
+
189
+ // 3. date + time, Date out
190
+ withTime(new Datepicker(input, { dateFormat: 'dd.MM.yyyy' }));
191
+ // typed "15.03.2025 13:07:09" shows 15.03.2025 13:07:09 getDate() = Date(2025-03-15 13:07:09)
192
+ // typed "1742044029" shows 15.03.2025 13:07:09 getDate() = Date(2025-03-15 13:07:09)
193
+
194
+ // 4. date + time, timestamp out
195
+ withTime(new Datepicker(input, { dateFormat: 'dd.MM.yyyy', outputTimestamp: true }));
196
+ // typed "15.03.2025 13:07:09" shows 15.03.2025 13:07:09 getDate() = 1742044029
197
+ // typed "1742044029" shows 15.03.2025 13:07:09 getDate() = 1742044029 exact round trip
198
+ ```
199
+
200
+ Ranges work the same way: `withTime(withRange(new Datepicker(input, { outputTimestamp: true })))` returns `[1741597200, 1741804200]` for 10 March 09:00:00 to 12 March 18:30:00 (UTC). In the web component, `<x-datepicker enable-time mode="range">` has the value `2025-03-10T09:00:00/2025-03-12T18:30:00`, or `1741597200/1741804200` with `output-timestamp`.
201
+
202
+ Things to know:
203
+
204
+ - **Local time.** As everywhere in the picker, the time of day is in the browser's time zone, and the timestamps are the instants of those local times. With [`valueInUtc`](#utc-backends-valueinutc) the values are converted to and from UTC for you. A time that doesn't exist (the hour skipped by a daylight-saving change, e.g. `02:30` on 9 March 2025 in New York) is moved to the next valid time (`03:30`), and the input shows that.
205
+ - **Seconds.** Dates are kept to the second; milliseconds are dropped.
206
+
207
+ ## UTC backends (`valueInUtc`)
208
+
209
+ Many backends only speak UTC, while users pick dates in their own time zone. Normally that means every app writes the same conversion code in both directions, and gets it subtly wrong around midnight. Set **`valueInUtc: true`** (`value-in-utc` on the web component) and the picker does it: **everything that goes in or comes out is UTC, everything the user sees and edits is local.**
210
+
211
+ ```ts
212
+ const dp = new Datepicker(input, { valueInUtc: true });
213
+
214
+ dp.setDate(new Date('2025-03-15T00:00:00Z')); // the backend's value: shows 15.03.2025 in every time zone
215
+ dp.getDate().toISOString(); // '2025-03-15T00:00:00.000Z', send it as is
216
+ ```
217
+
218
+ ```html
219
+ <x-datepicker name="due" value-in-utc value="2025-03-15T00:00:00.000Z"></x-datepicker>
220
+ ```
221
+
222
+ Without it, a user in Kyiv (UTC+2) who picks 15 March gets a `Date` for **local** midnight, and `toISOString()` sends `2025-03-14T22:00:00.000Z`: the backend stores the 14th. That is the bug this option removes.
223
+
224
+ What "in and out" covers: `selected`, `defaultDate`, `minDate` / `maxDate`, `disabledDates` (array), `setDate()`, `jumpToDate()`, the `value` attribute, and typed or pasted ISO strings and timestamps, in; `getDate()`, `onChange`, the `change` event, `isoInput` and the web component's `value` / form value, out. The text in the input is always the user's local display and is never converted.
225
+
226
+ What it does depends on whether a time is picked:
227
+
228
+ | | without `valueInUtc` (local) | with `valueInUtc` |
229
+ | --- | --- | --- |
230
+ | **date only**: a picked day is reported as | `Date` at **local** midnight (`2025-03-14T22:00:00.000Z` for 15 March in Kyiv) | `Date` at **UTC midnight** (`2025-03-15T00:00:00.000Z`), in every time zone |
231
+ | **date only**: a value is shown as | the local day of the instant (UTC midnight is 14 March in New York) | its **UTC day** (15 March everywhere) |
232
+ | **with `enableTime`**: Date / timestamp | the instant of the local time | the same instant. Instants need no conversion |
233
+ | **with `enableTime`**: ISO string | `2025-03-15T13:07:09`: local, no zone | `2025-03-15T11:07:09Z`: UTC (for a user in Kyiv) |
234
+ | **with `enableTime`**: ISO string without a zone, in | local time | read as **UTC** (an explicit `Z` or `+02:00` always wins) |
235
+
236
+ Combined with [`outputTimestamp`](#unix-timestamps), for a user in Kyiv (UTC+2) picking 15 March, or 15 March 13:07:09 with `enableTime`:
237
+
238
+ | | `outputTimestamp: false` | `outputTimestamp: true` |
239
+ | --- | --- | --- |
240
+ | date only, local (default) | `Date`, `toISOString()` = `2025-03-14T22:00:00.000Z` | `1741989600` |
241
+ | date only, **`valueInUtc`** | `Date`, `toISOString()` = `2025-03-15T00:00:00.000Z` | `1741996800` |
242
+ | with time, local (default) | `Date`, `toISOString()` = `2025-03-15T11:07:09.000Z`; `isoInput` = `2025-03-15T13:07:09` | `1742036829` |
243
+ | with time, **`valueInUtc`** | `Date`, same instant; `isoInput` = `2025-03-15T11:07:09Z` | `1742036829` |
244
+
245
+ A UTC timestamp for a time of day works in either mode, because a timestamp is an instant. What changes is plain dates (UTC midnight instead of local midnight) and ISO strings (UTC with a `Z` instead of local without a zone).
246
+
247
+ Things to know:
248
+
249
+ - **Pass UTC values in.** With `valueInUtc`, `new Date('2025-03-15')` (UTC midnight) is 15 March, but `new Date(2025, 2, 15)` (local midnight) is 14 March for users east of UTC, which is what "a UTC value" means. This also applies to `minDate`, `maxDate` and `defaultDate`: pass the backend's values, not `new Date()` for "now".
250
+ - **`disabledDates` as a function** and `disabledDaysOfWeek` work on the day the user sees, so a Sunday is a Sunday on screen whatever the time zone.
251
+ - **Date-time ISO accepted forms:** `2025-03-15T11:07:09Z`, with milliseconds (`…09.000Z`, what `toISOString()` produces), with an offset (`…09+02:00`), without a zone (UTC with this option, local without it), or with a space instead of the `T`. A bare `2025-03-15` is a calendar day and needs no conversion, except that a time picker reads it as UTC midnight.
252
+ - **Switching at runtime.** `setOptions({ valueInUtc })` keeps the day or time the user sees and changes what is reported.
253
+
254
+ ## Keyboard
255
+
256
+ | Key | Action |
257
+ | --- | --- |
258
+ | `↓` or `Alt+↓` in the input | open and move focus into the calendar |
259
+ | `← → ↑ ↓` | previous / next day, previous / next week (← → mirror in RTL) |
260
+ | `Home` / `End` | start / end of the week |
261
+ | `PageUp` / `PageDown` | previous / next month; with `Shift`, previous / next year |
262
+ | `Enter` / `Space` | select |
263
+ | `Escape` | close, focus returns to where it came from |
264
+ | `Tab` | cycles inside the dialog (`aria-modal`), including the time fields |
265
+
266
+ Opening with the mouse keeps focus in the input so you can keep typing; opening with the keyboard, the toggle button or `open()` moves focus into the grid.
267
+
268
+ ## Localisation
269
+
270
+ Month and weekday names, the default numeric format, first weekday and direction come from `Intl`. UI strings default to English (`today` is localised through `Intl.RelativeTimeFormat`). The strings are `today clear prevMonth nextMonth label selected` and, for the time selector, `time hours minutes seconds`. Override what you need:
271
+
272
+ ```ts
273
+ new Datepicker(input, { locale: { code: 'de', clear: 'Löschen', prevMonth: 'Voriger Monat', nextMonth: 'Nächster Monat', label: 'Datum wählen', selected: 'Ausgewählt' } });
274
+ ```
275
+
276
+ `months`, `monthsShort`, `weekdays`, `weekdaysShort`, `firstDayOfWeek` and `rtl` can be overridden as well. `registerLocale(obj)` makes a locale available by tag (that's how `ten-protons-datepicker/locales/uk` works).
277
+
278
+ The calendar is always Gregorian, and manual input accepts Arabic-Indic and Persian digits.
279
+
280
+ ## Theming
281
+
282
+ Import `core.css` (and `themes.css` for dark mode). Customise with CSS variables on `.tp-dp`:
283
+ `--tp-bg --tp-fg --tp-muted --tp-border --tp-hover --tp-accent --tp-accent-fg --tp-range --tp-focus --tp-radius --tp-cell --tp-pad --tp-z --tp-shadow --tp-font`.
284
+
285
+ Dark mode follows `prefers-color-scheme` unless `theme: 'light'` / `data-theme="light"`; `theme: 'dark'` forces it. To style with Tailwind or CSS Modules, pass `classes` and skip `core.css`.
286
+
287
+ ## Browsers
288
+
289
+ Current Chrome, Firefox, Safari (14.1+) and Edge. Form association of the Web Component needs `ElementInternals` (Safari 16.4+); everywhere else it still works, but its value isn't submitted with a `<form>`. Use the `change` event or `isoInput` there.
290
+
291
+ ## Not implemented yet
292
+
293
+ Presets and a year / month picker view are not implemented. Month and year are reachable with the previous / next buttons and `PageUp` / `PageDown` (`Shift` for years).
294
+
295
+ ## Development
296
+
297
+ ```sh
298
+ npm install
299
+ npm run typecheck
300
+ npm run build # tsup -> dist/
301
+ npm run demo # build, then serve the repo; open http://localhost:5173/demo/
302
+ ```
303
+
304
+ `demo/index.html` imports from `dist/` and must be served over HTTP (ES modules don't load from `file://`).
305
+
306
+ MIT
@@ -0,0 +1,265 @@
1
+ /** Day of week, `0` = Sunday. */
2
+ type Weekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
3
+ type DatepickerMode = 'single' | 'range' | 'multiple';
4
+ type Position = 'auto' | 'top' | 'bottom' | 'left' | 'right';
5
+ /**
6
+ * What the picker reports: `Date` in single mode, `Date[]` in range/multiple mode, `null` when nothing is
7
+ * selected. With `outputTimestamp` the dates are Unix timestamps in seconds (`number` / `number[]`).
8
+ */
9
+ type DatepickerValue = Date | Date[] | number | number[] | null;
10
+ /** What the picker accepts: a `Date`, an ISO `yyyy-MM-dd` string, or a Unix timestamp in seconds (number or digit string). */
11
+ type DateInput = Date | string | number;
12
+ /** UI strings. Defaults are English; `today` is localised through `Intl.RelativeTimeFormat`. */
13
+ interface LocaleStrings {
14
+ today: string;
15
+ clear: string;
16
+ prevMonth: string;
17
+ nextMonth: string;
18
+ /** Accessible name of the calendar dialog. */
19
+ label: string;
20
+ /** Time selector (`enableTime`): group name and the three field names. */
21
+ time: string;
22
+ hours: string;
23
+ minutes: string;
24
+ seconds: string;
25
+ /** Prefix of the screen-reader announcement, e.g. "Selected: Wednesday, March 5, 2025". */
26
+ selected: string;
27
+ }
28
+ /** Override only what you need; everything else comes from `Intl` for `code`. */
29
+ interface LocaleObject extends Partial<LocaleStrings> {
30
+ /** BCP 47 tag, e.g. `uk`, `en-GB`. */
31
+ code: string;
32
+ firstDayOfWeek?: Weekday;
33
+ rtl?: boolean;
34
+ /** 12 month names, January first. */
35
+ months?: string[];
36
+ monthsShort?: string[];
37
+ /** 7 weekday names, Sunday first. */
38
+ weekdays?: string[];
39
+ weekdaysShort?: string[];
40
+ }
41
+ /** CSS classes used by the calendar. Replace any of them to fully restyle (Tailwind, CSS Modules...). */
42
+ interface ClassNames {
43
+ root: string;
44
+ /** Added to the root when the calendar is a popup (not inline). */
45
+ pop: string;
46
+ months: string;
47
+ head: string;
48
+ title: string;
49
+ nav: string;
50
+ grid: string;
51
+ dow: string;
52
+ day: string;
53
+ out: string;
54
+ today: string;
55
+ selected: string;
56
+ disabled: string;
57
+ inRange: string;
58
+ rangeStart: string;
59
+ rangeEnd: string;
60
+ foot: string;
61
+ btn: string;
62
+ /** Time selector row and its number fields (`enableTime`). */
63
+ time: string;
64
+ timeField: string;
65
+ /** Visually hidden (screen-reader only) text. */
66
+ sr: string;
67
+ }
68
+ interface DatepickerOptions {
69
+ /** `'single'` (default). `'range'` / `'multiple'` require importing their module first. */
70
+ mode?: DatepickerMode;
71
+ /** Initial / new selection. Takes precedence over the input's existing value. */
72
+ selected?: DateInput | DateInput[] | null;
73
+ /** Month shown (and day focused) when nothing is selected. Defaults to today. */
74
+ defaultDate?: DateInput | null;
75
+ /** `Date`, ISO `yyyy-MM-dd` string or Unix timestamp in seconds. */
76
+ minDate?: DateInput | null;
77
+ maxDate?: DateInput | null;
78
+ disabledDates?: Date[] | ((date: Date) => boolean);
79
+ /** `0` = Sunday. */
80
+ disabledDaysOfWeek?: number[];
81
+ /** BCP 47 tag, or a `LocaleObject` with overrides. */
82
+ locale?: string | LocaleObject;
83
+ firstDayOfWeek?: Weekday;
84
+ /**
85
+ * Token format used for display and manual input. Tokens: `yyyy yy MMMM MMM MM M dd d EEEE EEE`;
86
+ * quote literals with `'...'`. Defaults to the locale's numeric order (`dd.MM.yyyy` for `uk`).
87
+ */
88
+ dateFormat?: string;
89
+ /** Render the calendar permanently instead of as a popup. Read at construction only. */
90
+ inline?: boolean;
91
+ showOtherMonths?: boolean;
92
+ numberOfMonths?: 1 | 2;
93
+ position?: Position;
94
+ /** Force `light` / `dark`. Omit to follow `prefers-color-scheme`. */
95
+ theme?: 'light' | 'dark';
96
+ /**
97
+ * Also pick a time (hours : minutes : seconds, 24-hour) in a selector below the calendar. Selected dates keep
98
+ * their time, which is shown as `HH:mm:ss` after the date, and included in `getDate()`, `onChange` and timestamps.
99
+ * Requires `import 'ten-protons-datepicker/time'` (the web component loads it for you). In range / multiple mode
100
+ * one time applies to every selected date. Default `false`.
101
+ */
102
+ enableTime?: boolean;
103
+ /**
104
+ * The backend works in UTC: values going in and out are UTC, while the picker shows and edits the user's local time.
105
+ * Covers `selected`, `setDate()`, `minDate` / `maxDate`, typed or pasted ISO strings and timestamps, and
106
+ * `getDate()`, `onChange`, `isoInput` and the web component's `value`:
107
+ * - with `enableTime`, ISO strings are UTC (`2025-03-15T11:07:09Z`, and without a zone they are read as UTC);
108
+ * - without it, each picked day is reported as UTC midnight and a UTC-midnight value shows as that day in every time zone.
109
+ * Default `false` (local time).
110
+ */
111
+ valueInUtc?: boolean;
112
+ /** Allow typing a date. Default `true`. When `false` the input is read-only. */
113
+ allowInput?: boolean;
114
+ /** Close once a selection is complete. Defaults to `true` for single/range, `false` for multiple and with `enableTime`. */
115
+ closeOnSelect?: boolean;
116
+ closeOnOutsideClick?: boolean;
117
+ /** Replace default CSS class names. */
118
+ classes?: Partial<ClassNames>;
119
+ /**
120
+ * Report Unix timestamps (seconds, local midnight of each selected day) from `getDate()`, `onChange`,
121
+ * the `change` event, `isoInput` and the web component's form value, instead of `Date`s / ISO strings.
122
+ * Day pickers report each day's local midnight; with `enableTime` the timestamp includes the selected time.
123
+ * Typed or pasted timestamps are always understood, whatever this is set to. Default `false`.
124
+ */
125
+ outputTimestamp?: boolean;
126
+ /** Input that receives the ISO value (`yyyy-MM-dd`, or `yyyy-MM-ddTHH:mm:ss` with `enableTime`; ranges as `a/b`, multiple as `a,b`), or timestamps with `outputTimestamp`. */
127
+ isoInput?: HTMLInputElement | null;
128
+ /** Element the popup is positioned against and treated as "inside" for outside clicks. Read at construction. */
129
+ anchor?: HTMLElement;
130
+ /** Where the popup is appended. Defaults to `document.body`. Read at construction. */
131
+ appendTo?: ParentNode;
132
+ onChange?: (dates: DatepickerValue, instance: Datepicker) => void;
133
+ onOpen?: (instance: Datepicker) => void;
134
+ onClose?: (instance: Datepicker) => void;
135
+ onMonthChange?: (date: Date, instance: Datepicker) => void;
136
+ }
137
+ interface DatepickerEventMap {
138
+ change: CustomEvent<DatepickerValue>;
139
+ open: CustomEvent<undefined>;
140
+ close: CustomEvent<undefined>;
141
+ monthChange: CustomEvent<Date>;
142
+ yearChange: CustomEvent<Date>;
143
+ }
144
+
145
+ interface Datepicker {
146
+ addEventListener<K extends keyof DatepickerEventMap>(type: K, listener: (this: Datepicker, ev: DatepickerEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
147
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
148
+ removeEventListener<K extends keyof DatepickerEventMap>(type: K, listener: (this: Datepicker, ev: DatepickerEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
149
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
150
+ }
151
+ /**
152
+ * Accessible datepicker attached to an `<input>` (popup) or any element (`inline: true`).
153
+ *
154
+ * Opens on click or ArrowDown; with `allowInput` the date can be typed and is parsed on blur / Enter.
155
+ * Emits `change`, `open`, `close`, `monthChange` and `yearChange` (as `CustomEvent`s on the instance
156
+ * and through the `on*` callbacks). `change` also fires native `input`/`change` events on the input.
157
+ *
158
+ * ```ts
159
+ * const dp = new Datepicker(input, { locale: 'uk', minDate: new Date() });
160
+ * dp.addEventListener('change', (e) => console.log(e.detail));
161
+ * ```
162
+ */
163
+ declare class Datepicker extends EventTarget {
164
+ /** The element the picker is attached to. */
165
+ readonly el: HTMLElement;
166
+ private readonly input;
167
+ private readonly anchor;
168
+ private readonly id;
169
+ private user;
170
+ private o;
171
+ private loc;
172
+ private mode;
173
+ private fmt;
174
+ private lo;
175
+ private hi;
176
+ private skip;
177
+ private sel;
178
+ /** Unfinished selection (e.g. range start); only the committed `sel` is ever emitted. */
179
+ private pending;
180
+ /** Time of day given to newly picked days (`enableTime`): follows the selection and the time selector. */
181
+ private tod;
182
+ private view;
183
+ private focus;
184
+ private hover;
185
+ private cal;
186
+ private isOpen;
187
+ private destroyed;
188
+ private opener;
189
+ private wasReadOnly;
190
+ private added;
191
+ private offs;
192
+ private openOffs;
193
+ private handlers;
194
+ constructor(el: HTMLElement, options?: DatepickerOptions);
195
+ /** Localised UI strings in use (e.g. to label an external toggle button). */
196
+ get strings(): LocaleStrings;
197
+ /** Opens the popup and moves focus into the calendar. */
198
+ open(): void;
199
+ /** Closes the popup and returns focus to where it came from. */
200
+ close(): void;
201
+ toggle(): void;
202
+ /**
203
+ * Selected date(s): `Date | null` in single mode, `Date[] | null` otherwise (copies). With `outputTimestamp`
204
+ * the dates are Unix timestamps in seconds instead: `number | null` / `number[] | null`. With `valueInUtc`
205
+ * a date-only picker reports each day as UTC midnight.
206
+ */
207
+ getDate(): DatepickerValue;
208
+ /**
209
+ * Replaces the selection. Accepts `Date`s, ISO strings and Unix timestamps in seconds. Invalid dates are
210
+ * ignored; `change` fires only if `triggerChange` is true.
211
+ */
212
+ setDate(date: DateInput | DateInput[] | null, triggerChange?: boolean): void;
213
+ /** Shows the month of `date` and moves the keyboard focus to it (clamped to min/max). */
214
+ jumpToDate(date: Date): void;
215
+ /** Shorthand for `setDate([start, end])`; use in `range` mode. */
216
+ setRange(start: Date, end: Date): void;
217
+ /** Merges options. `inline`, `anchor` and `appendTo` are only read at construction. */
218
+ setOptions(options: Partial<DatepickerOptions>): void;
219
+ /** Removes the calendar, listeners and everything added to the input. The instance is unusable afterwards. */
220
+ destroy(): void;
221
+ private resolve;
222
+ private monthsShown;
223
+ private home;
224
+ private isDisabled;
225
+ private clamp;
226
+ private normalize;
227
+ /** `d` with the picker's time of day; date-only pickers keep their midnights. */
228
+ private stamp;
229
+ private setSel;
230
+ private pick;
231
+ /** The time selector changed: every selected (or pending) date takes the new time of day. */
232
+ private setTime;
233
+ private announce;
234
+ /** Focuses `d` (clamped) and scrolls the visible months to include it. */
235
+ private reveal;
236
+ private setView;
237
+ private move;
238
+ private nav;
239
+ private state;
240
+ private refresh;
241
+ private mount;
242
+ private present;
243
+ private hide;
244
+ private reposition;
245
+ private listenWhileOpen;
246
+ private on;
247
+ private bindInput;
248
+ /** Display text of the committed selection. */
249
+ private text;
250
+ private writeISO;
251
+ private writeInput;
252
+ /**
253
+ * ISO dates / Unix timestamps (always accepted, whatever `dateFormat` is) or text in `dateFormat`. With
254
+ * `enableTime` they carry a time ("15.03.2025 13:07:09"); a date typed without one takes the selector's time.
255
+ */
256
+ private read;
257
+ /**
258
+ * Parses the input text into the selection. Invalid, disabled or too many dates are rejected:
259
+ * on Enter the input is flagged `aria-invalid` and keeps its text; on blur it reverts.
260
+ */
261
+ private commitInput;
262
+ private emit;
263
+ }
264
+
265
+ export { type ClassNames as C, type DateInput as D, type LocaleObject as L, type Position as P, type Weekday as W, Datepicker as a, type DatepickerEventMap as b, type DatepickerMode as c, type DatepickerOptions as d, type DatepickerValue as e, type LocaleStrings as f };
@@ -0,0 +1 @@
1
+ var l=(e,t,n)=>{let a=new Date(2e3,0,1);return a.setFullYear(e,t,n),a.setHours(0,0,0,0),a},d=e=>l(e.getFullYear(),e.getMonth(),e.getDate()),C=e=>l(e.getFullYear(),e.getMonth(),1),$=(e,t)=>l(e.getFullYear(),e.getMonth(),e.getDate()+t),S=(e,t)=>{let n=l(e.getFullYear(),e.getMonth()+t,1);return n.setDate(Math.min(e.getDate(),l(n.getFullYear(),n.getMonth()+1,0).getDate())),n},w=e=>e.getFullYear()*1e4+e.getMonth()*100+e.getDate(),H=e=>l(Math.floor(e/1e4),Math.floor(e/100)%100,e%100),f=(e,t=2)=>String(e).padStart(t,"0"),p=(e,t,n,a=0,o=0,s=0)=>{let r=new Date(0);return r.setUTCFullYear(e,t,n),r.setUTCHours(a,o,s,0),r},x=(e,t,n)=>n&&!t?l(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()):Y(e,t),y=(e,t=false,n=false)=>n&&!t?p(e.getFullYear(),e.getMonth(),e.getDate()):new Date(e),I=(e,t=false,n=false)=>Math.floor(y(e,t,n).getTime()/1e3),N=(e,t=false,n=false)=>{let a=t&&n,[o,s,r,u,D,g]=a?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds()]:[e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds()];return `${f(o,4)}-${f(s+1)}-${f(r)}${t?`T${f(u)}:${f(D)}:${f(g)}${a?"Z":""}`:""}`},F=/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/,M=(e,t=false,n=false)=>{let a=F.exec(e.trim());if(!a)return null;let[o,s,r]=[+a[1],+a[2]-1,+a[3]],[u,D,g]=[+(a[4]??0),+(a[5]??0),+(a[6]??0)],i=l(o,s,r);if(i.getMonth()!==s||i.getDate()!==r||u>23||D>59||g>59)return null;if(a[4]===void 0&&!(t&&n))return i;let c=a[7],m;if(c&&c!=="Z"){let U=(c[0]==="-"?-1:1)*(+c.slice(1,3)*60+ +c.slice(-2));m=new Date(p(o,s,r,u,D,g).getTime()-U*6e4);}else c||n?m=p(o,s,r,u,D,g):(m=i).setHours(u,D,g);return x(m,t,n)},b=(e,t,n)=>{let a=new Date(e*1e3);return isNaN(+a)?null:x(a,t,n)},T=(e,t=false,n=false)=>/^\d{9,11}$/.test(e.trim())?b(+e,t,n):null,h=/\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?|\d{9,11}/g,O=(e,t=false,n=false)=>{let a=(e.match(h)??[]).map(o=>M(o,t,n)??T(o,t,n));return a.every(o=>o!==null)&&!e.replace(h,"").replace(/[\s,;/–—-]/g,"")?a:null},Y=(e,t=false)=>{if(!t)return d(e);let n=new Date(e);return n.setMilliseconds(0),n},Z=(e,t=false,n=false)=>{let a=e instanceof Date?x(e,t,n):typeof e=="string"?M(e,t,n)??T(e,t,n):typeof e=="number"?b(e,t,n):null;return a&&!isNaN(+a)?a:null};export{l as a,d as b,C as c,$ as d,S as e,w as f,H as g,y as h,I as i,N as j,O as k,Y as l,Z as m};
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-ANB32WKT.js';var b={today:"Today",clear:"Clear",prevMonth:"Previous month",nextMonth:"Next month",label:"Choose date",selected:"Selected",time:"Time",hours:"Hours",minutes:"Minutes",seconds:"Seconds"},N={calendar:"gregory"},w=new Map,H=e=>{w.set(e.code.toLowerCase(),e);},s=e=>{let a;return ()=>a??(a=e())},v=e=>e.toLowerCase().replace(/\./g,""),h=e=>Array.from({length:e},(a,p)=>p),P=/^(US|CA|MX|BR|JP|IL|IN|PH|KR|TW|HK|ZA|SA)$/,R=/^(ar|he|iw|fa|ur|ps|sd|ug|yi|dv|ckb)(-|$)/i,E=e=>{try{let a=new Intl.RelativeTimeFormat(e,{numeric:"auto"}).format(0,"day");return a.charAt(0).toLocaleUpperCase(e)+a.slice(1)}catch{return b.today}},U=(e,a$1)=>{let r=(typeof e=="string"?e:e?.code)||typeof navigator<"u"&&navigator.language||"en";try{new Intl.DateTimeFormat(r);}catch{r="en";}let L=r.toLowerCase(),o=typeof e=="object"?e:w.get(L)??w.get(L.split("-")[0]),I={...b,today:E(r)};for(let t of Object.keys(b)){let n=o?.[t];n&&(I[t]=n);}let c=t=>new Intl.DateTimeFormat(r,{...N,...t}),i=new Intl.Locale(r),x=i.getWeekInfo?.()??i.weekInfo,k=(i.getTextInfo?.()??i.textInfo)?.direction,F=i.maximize().region??"",M=c({month:"long",year:"numeric"}),O=c({weekday:"long",day:"numeric",month:"long",year:"numeric"}),A=new Intl.NumberFormat(r),f=[],m=(t,n)=>h(12).map(u=>{let g=a(2e3,u,1);return n?c({day:"numeric",month:t}).formatToParts(g).find(d=>d.type==="month").value:c({month:t}).format(g)}),y={long:s(()=>m("long",true)),short:s(()=>m("short",true))},T={long:s(()=>m("long",false)),short:s(()=>m("short",false))},j={long:s(()=>h(7).map(t=>c({weekday:"long"}).format(a(2e3,0,2+t)))),short:s(()=>h(7).map(t=>c({weekday:"short"}).format(a(2e3,0,2+t))))},$=s(()=>{let t=new Map,n=[y.long(),y.short(),T.long(),T.short(),o?.months,o?.monthsShort];for(let u of n)u?.forEach((g,d)=>t.set(v(g),d));return t}),D=(t,n=false)=>(n?o?.monthsShort??o?.months:o?.months)?.[t]??y[n?"short":"long"]()[t],S=(t,n=false)=>(n?o?.weekdays:o?.weekdaysShort??o?.weekdays)?.[t]??j[n?"long":"short"]()[t],C=s(()=>c({numberingSystem:"latn",year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(a(2e3,10,22)).map(t=>t.type==="year"?"yyyy":t.type==="month"?"MM":t.type==="day"?"dd":t.value.replace(/[‎‏؜]/g,"")).join(""));return {code:r,rtl:o?.rtl??(k?k==="rtl":R.test(r)),firstDay:a$1??o?.firstDayOfWeek??(x?.firstDay??(P.test(F)?7:1))%7,strings:I,get format(){return C()},title:t=>o?.months?`${o.months[t.getMonth()]} ${t.getFullYear()}`:M.format(t),label:t=>o?.months||o?.weekdays?`${S(t.getDay(),true)}, ${t.getDate()} ${D(t.getMonth())} ${t.getFullYear()}`:O.format(t),dow:S,month:D,monthIndex:t=>$().get(v(t))??-1,num:t=>f[t]??(f[t]=A.format(t))}};export{H as a,U as b};
@@ -0,0 +1 @@
1
+ import {f}from'./chunk-ANB32WKT.js';var s=(e,o)=>f(e)-f(o),n={single:{max:1,join:", ",iso:",",autoClose:true,clean:e=>e.slice(0,1),pick:(e,o)=>({sel:[o],complete:true})}},a=(e,o)=>{n[e]=o;},i=e=>n[e];export{s as a,a as b,i as c};
@@ -0,0 +1 @@
1
+ import {c as c$2}from'./chunk-CUGUCENC.js';import {c,b as b$1}from'./chunk-Q2P4Y4T6.js';import {b as b$2}from'./chunk-BHROFXOE.js';import {b,f,c as c$1,i,h,m,l,e,j as j$1,k as k$1,g as g$1,d,a}from'./chunk-ANB32WKT.js';var R=(h,n,t,e$1)=>{if(h.ctrlKey||h.metaKey||h.altKey)return null;let i=(n.getDay()-t+7)%7,s=e$1?-1:1;switch(h.key){case "ArrowLeft":return d(n,-s);case "ArrowRight":return d(n,s);case "ArrowUp":return d(n,-7);case "ArrowDown":return d(n,7);case "Home":return d(n,-i);case "End":return d(n,6-i);case "PageUp":return e(n,h.shiftKey?-12:-1);case "PageDown":return e(n,h.shiftKey?12:1);default:return null}};var z={root:"tp-dp",pop:"tp-pop",months:"tp-months",head:"tp-head",title:"tp-title",nav:"tp-nav",grid:"tp-grid",dow:"tp-dow",day:"tp-day",out:"tp-out",today:"tp-today",selected:"tp-selected",disabled:"tp-disabled",inRange:"tp-in-range",rangeStart:"tp-range-start",rangeEnd:"tp-range-end",foot:"tp-foot",btn:"tp-btn",time:"tp-time",timeField:"tp-time-field",sr:"tp-sr"},g=(h,n="",t={})=>{let e=document.createElement(h);n&&(e.className=n);for(let i in t)e.setAttribute(i,t[i]);return e},j=(...h)=>h.filter(Boolean).join(" "),k=(h,n,t)=>{h.getAttribute(n)!==t&&(t===null?h.removeAttribute(n):h.setAttribute(n,t));},x=class{constructor(n){this.o=n;this.panels=[];this.layoutKey=-1;this.focusTd=null;let{loc:t,cls:e,on:i}=n,s=this.el=g("div",j(e.root,!n.inline&&e.pop),{id:n.id,dir:t.rtl?"rtl":"ltr","aria-label":t.strings.label,...n.inline?{role:"group"}:{role:"dialog","aria-modal":"true"},...n.theme?{"data-theme":n.theme}:{}}),o=g("div",e.months);for(let a=0;a<n.months;a++)o.append(this.panel(a));let r=g("div",e.foot);for(let a of ["today","clear"]){let c=g("button",e.btn,{type:"button","data-a":a});c.textContent=t.strings[a],r.append(c);}this.live=g("div",e.sr,{"aria-live":"polite","aria-atomic":"true"}),this.timeUI=n.time?.({loc:t,cls:e,id:n.id,change:i.time}),s.append(o,...this.timeUI?[this.timeUI.el]:[],r,this.live),this.prev=s.querySelector("[data-a=prev]"),this.next=s.querySelector("[data-a=next]");let l=a=>a.target.closest("td[data-k]");s.addEventListener("click",a=>{let c=l(a);if(c)return i.pick(g$1(+c.dataset.k));let m=a.target.closest("[data-a]")?.dataset.a;m==="prev"?i.nav(-1):m==="next"?i.nav(1):m==="today"?i.today():m==="clear"&&i.clear();}),s.addEventListener("pointerover",a=>{let c=l(a);c&&i.hover(+c.dataset.k);}),s.addEventListener("pointerleave",()=>i.hover(null)),s.addEventListener("keydown",a=>this.onKey(a));}update(n,t=false){let e=f(n.view);e!==this.layoutKey&&(this.layoutKey=e,this.layout(n.view)),this.paint(n),t&&this.focusCell();}focusCell(){this.focusTd?.focus({preventScroll:true});}announce(n){this.live.textContent=n;}panel(n){let{loc:t,cls:e,id:i,months:s}=this.o,o=`${i}-t${n}`,r=y=>g("button",e.nav,{type:"button","data-a":y,"aria-label":t.strings[y==="prev"?"prevMonth":"nextMonth"]}),l=g("h2",e.title,{id:o,"aria-live":"polite"}),a=g("div",e.head);a.append(n===0?r("prev"):g("span",e.nav),l,n===s-1?r("next"):g("span",e.nav));let c=g("tr");for(let y=0;y<7;y++){let D=(t.firstDay+y)%7,v=g("th",e.dow,{scope:"col",abbr:t.dow(D,true)});v.textContent=t.dow(D),c.append(v);}let m=g("thead");m.append(c);let f=g("tbody"),p=g("table",e.grid,{role:"grid","aria-labelledby":o});p.append(m,f),this.panels.push({title:l,body:f,cells:[]});let u=g("div");return u.append(a,p),u}layout(n){let{loc:t,cls:e$1,otherMonths:i}=this.o;this.panels.forEach((s,o)=>{let r=e(n,o),l=d(r,-((r.getDay()-t.firstDay+7)%7)),a=document.createDocumentFragment();s.title.textContent=t.title(r),s.cells=[];for(let c=0;c<6;c++){let m=g("tr"),f$1=false;for(let p=0;p<7;p++){let u=d(l,c*7+p),y=u.getMonth()!==r.getMonth(),D=y&&!i,v=g("td",e$1.day,{role:"gridcell"});D?v.setAttribute("aria-hidden","true"):(f$1=true,v.tabIndex=-1,v.dataset.k=String(f(u)),v.textContent=t.num(u.getDate()),v.setAttribute("aria-label",t.label(u))),s.cells.push({td:v,d:u,k:f(u),out:y,hidden:D}),m.append(v);}f$1||m.setAttribute("aria-hidden","true"),a.append(m);}s.body.replaceChildren(a);});}paint(n){let{cls:t}=this.o,e=f(n.focus),i=n.span;this.focusTd=null;for(let s of this.panels)for(let{td:o,d:r,k:l,out:a,hidden:c}of s.cells){if(c)continue;let m=n.keys.includes(l)||!!i&&!n.preview&&l>=i[0]&&l<=i[1],f=n.disabled(r),p=j(t.day,a&&t.out,l===n.today&&t.today,m&&t.selected,f&&t.disabled,i&&l>i[0]&&l<i[1]&&t.inRange,i&&l===i[0]&&t.rangeStart,i&&l===i[1]&&t.rangeEnd);o.className!==p&&(o.className=p),k(o,"aria-selected",m?"true":null),k(o,"aria-disabled",f?"true":null),k(o,"aria-current",l===n.today?"date":null);let u=l===e&&!a;k(o,"tabindex",u?"0":"-1"),u&&(this.focusTd=o);}this.timeUI?.set(n.time),k(this.prev,"aria-disabled",n.canPrev?null:"true"),k(this.next,"aria-disabled",n.canNext?null:"true");}onKey(n){let{loc:t,on:e,inline:i}=this.o;if(n.key==="Escape")return i?void 0:(n.stopPropagation(),e.close());if(n.key==="Tab"&&!i)return this.trapTab(n);let s=n.target.closest("td[data-k]");if(!s)return;let o=g$1(+s.dataset.k);if(n.key==="Enter"||n.key===" ")return n.preventDefault(),e.pick(o);let r=R(n,o,t.firstDay,t.rtl);r&&(n.preventDefault(),e.move(r));}trapTab(n){let t=[...this.el.querySelectorAll('button, input, td[tabindex="0"]')],[e,i]=[t[0],t[t.length-1]],s=n.shiftKey?e:i;(n.target===s||!t.includes(n.target))&&(n.preventDefault(),(n.shiftKey?i:e)?.focus());}};var G=/'([^']*)'|(yyyy|yy|MMMM|MMM|MM|M|dd|d|EEEE|EEE)|([^'yMdE]+|.)/g,W=/[\p{L}\p{N}]/u,X=/(\d{1,2}):(\d{2})(?::(\d{2}))?/g,M=(h,n=2)=>String(h).padStart(n,"0"),C=h=>`${M(h.getHours())}:${M(h.getMinutes())}:${M(h.getSeconds())}`,Y=(h,n,t)=>n.replace(G,(e,i,s,o)=>{if(!s)return i??o??"";let[r,l,a]=[h.getFullYear(),h.getMonth(),h.getDate()];switch(s){case "yyyy":return M(r,4);case "yy":return M(r%100);case "MMMM":return t.month(l);case "MMM":return t.month(l,true);case "MM":return M(l+1);case "M":return String(l+1);case "dd":return M(a);case "d":return String(a);default:return t.dow(h.getDay(),s==="EEEE")}}),q=(h,n,t,e)=>{let i=[],s="";for(let[,f,p,u]of n.matchAll(G))if(p)p[0]==="E"?s+="(?:\\p{L}+\\.?[^\\p{L}\\p{N}]+)?":(i.push(p),s+=p==="yyyy"?"(\\d{4})":p==="yy"?"(\\d{2})":p.length>2?"(\\p{L}+\\.?)":"(\\d{1,2})");else {let y=f??u??"";s+=W.test(y)?`\\s*(?:${y.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")})?\\s*`:"[^\\p{L}\\p{N}]*";}let o=h.replace(/[٠-٩۰-۹]/g,f=>String(f.charCodeAt(0)&15)),r=[];e&&(o=o.replace(X,(f,p,u,y)=>(r.push([+p,+u,+(y??0)])," ")));let l=new Date,a$1=[],c="",m=0;for(let f of o.matchAll(new RegExp(s,"gu"))){let[p,u,y]=[l.getFullYear(),l.getMonth(),1];i.forEach((v,Q)=>{let w=f[Q+1];v==="yyyy"?p=+w:v==="yy"?p=2e3+ +w>l.getFullYear()+50?1900+ +w:2e3+ +w:v[0]==="d"?y=+w:u=v.length>2?t.monthIndex(w):+w-1;});let D=a(p,u,y);if(u<0||D.getMonth()!==u||D.getDate()!==y)return null;if(e){let v=r.length?r[a$1.length]:e;if(!v||v[0]>23||v[1]>59||v[2]>59)return null;D.setHours(...v);}a$1.push(D),c+=o.slice(m,f.index),m=f.index+f[0].length;}return c+=o.slice(m),a$1.length&&!W.test(c)&&(!r.length||r.length===a$1.length)?a$1:null};var B=(h,n,t,e)=>{let i=h.getBoundingClientRect(),[s,o]=[n.offsetWidth,n.offsetHeight],[r,l]=[document.documentElement.clientWidth,document.documentElement.clientHeight],a=(f,p,u)=>Math.max(8,Math.min(f,u-p-8)),c=e?i.right-s:i.left,m;t==="left"||t==="right"?(c=t==="left"?i.left-s-4:i.right+4,m=i.top):m=t==="top"||t==="auto"&&l-i.bottom<o+4&&i.top>l-i.bottom?i.top-o-4:i.bottom+4,n.style.left=`${a(c,s,r)}px`,n.style.top=`${a(m,o,l)}px`;};var _={mode:"single",position:"auto",numberOfMonths:1,allowInput:true,closeOnOutsideClick:true},Z=0,tt=h=>Object.fromEntries(Object.entries(h).filter(([,n])=>n!==void 0)),J=class extends EventTarget{constructor(t,e={}){super();this.id=`tp-${++Z}`;this.user={};this.o=_;this.fmt="";this.lo=null;this.hi=null;this.skip=null;this.sel=[];this.pending=null;this.tod=[0,0,0];this.view=new Date;this.focus=new Date;this.hover=null;this.cal=null;this.isOpen=false;this.destroyed=false;this.opener=null;this.wasReadOnly=false;this.added=[];this.offs=[];this.openOffs=[];this.handlers={pick:t=>this.pick(t),move:t=>this.move(t),nav:t=>this.nav(t),hover:t=>{t!==this.hover&&(this.hover=t,this.pending&&this.refresh());},today:()=>this.pick(b(new Date)),clear:()=>this.setSel([],true),close:()=>this.hide(true),time:t=>this.setTime(t)};this.isDisabled=t=>{let e=f(t),i=this.o.disabledDates;return this.lo!==null&&e<f(this.lo)||this.hi!==null&&e>f(this.hi)||!!this.o.disabledDaysOfWeek?.includes(t.getDay())||!!this.skip?.has(e)||typeof i=="function"&&i(t)};this.reposition=()=>{this.cal&&this.isOpen&&B(this.anchor,this.cal.el,this.o.position??"auto",this.loc.rtl);};this.el=t,this.input=t instanceof HTMLInputElement?t:null,this.anchor=e.anchor??t,this.resolve(e);let i=e.selected===void 0&&this.input?.value.trim()?this.read(this.input.value):null;this.sel=i?this.mode.clean(i):this.normalize(e.selected),this.sel[0]&&(this.tod=c(this.sel[0])),this.focus=this.clamp(this.sel[0]??this.home()),this.view=c$1(this.focus),this.input&&this.bindInput(this.input),this.sel.length&&this.writeInput(),this.o.inline&&(this.mount(),this.isOpen=true,this.refresh());}get strings(){return this.loc.strings}open(){this.present(true);}close(){this.hide(true);}toggle(){this.isOpen?this.close():this.open();}getDate(){let{outputTimestamp:t,enableTime:e,valueInUtc:i$1}=this.o,s=this.sel.map(o=>t?i(o,e,i$1):h(o,e,i$1));return this.mode.max===1?s[0]??null:s.length?s:null}setDate(t,e=false){if(this.destroyed)return;let i=this.normalize(t);i[0]&&this.reveal(i[0]),this.setSel(i,e);}jumpToDate(t){let e=m(t,!!this.o.enableTime,!!this.o.valueInUtc);this.destroyed||!e||(this.reveal(e),this.refresh());}setRange(t,e){this.setDate([t,e]);}setOptions(t){if(this.destroyed)return;let e=this.text();this.resolve({...this.user,...t}),this.sel="selected"in t?this.normalize(t.selected):this.mode.clean(this.sel.map(s=>l(s,!!this.o.enableTime))),this.sel[0]&&(this.tod=c(this.sel[0])),this.pending=null,this.focus=this.clamp(this.sel[0]??this.focus),this.input&&(this.input.readOnly=this.wasReadOnly||this.o.allowInput===false),e!==this.text()?this.writeInput():this.o.isoInput&&this.writeISO();let i=this.cal?.el.matches(":focus-within");this.cal?.el.remove(),this.cal=null,this.reveal(this.focus),this.isOpen&&(this.mount(),this.cal.el.hidden=false,this.refresh(),this.reposition(),i&&this.cal.focusCell());}destroy(){this.destroyed||(this.destroyed=true,this.openOffs.splice(0).forEach(t=>t()),this.offs.splice(0).forEach(t=>t()),this.cal?.el.remove(),this.cal=null,this.isOpen=false,this.input&&([...this.added,"aria-controls","aria-invalid"].forEach(t=>this.input.removeAttribute(t)),this.input.readOnly=this.wasReadOnly));}resolve(t){let e={..._,...tt(t)},i=c$2(e.mode);if(!i)throw new Error(`[datepicker] mode "${e.mode}" is not registered. Import "ten-protons-datepicker/${e.mode}" first.`);if(e.enableTime&&!b$1())throw new Error('[datepicker] enableTime needs the time module. Import "ten-protons-datepicker/time" first.');this.user=t,this.o=e,this.mode=i,this.loc=b$2(e.locale,e.firstDayOfWeek),this.fmt=e.dateFormat??this.loc.format;let[s,o]=[!!e.enableTime,!!e.valueInUtc];this.lo=m(e.minDate,s,o),this.hi=m(e.maxDate,s,o);let r=e.disabledDates;this.skip=Array.isArray(r)?new Set(r.map(l=>f(m(l,s,o)??l))):null;}monthsShown(){return this.o.numberOfMonths===2?2:1}home(){return m(this.o.defaultDate,!!this.o.enableTime,!!this.o.valueInUtc)??b(new Date)}clamp(t){let e=f(t);return this.lo&&e<f(this.lo)?new Date(this.lo):this.hi&&e>f(this.hi)?new Date(this.hi):t}normalize(t){let e=t==null?[]:Array.isArray(t)?t:[t],[i,s]=[!!this.o.enableTime,!!this.o.valueInUtc];return this.mode.clean(e.map(o=>m(o,i,s)).filter(o=>o!==null))}stamp(t){if(!this.o.enableTime)return t;let e=new Date(t);return e.setHours(...this.tod),e}setSel(t,e){let i=this.o.enableTime?o=>+o:f,s=t.length===this.sel.length&&t.every((o,r)=>i(o)===i(this.sel[r]));this.sel=t,t[0]&&(this.tod=c(t[0])),this.pending=null,this.writeInput(),this.refresh(),e&&!s&&this.emit("change",this.getDate());}pick(t){if(this.isDisabled(t))return this.move(t);let e=this.stamp(t),i=this.mode.pick(this.pending??this.sel,e);if(this.hover=f(t),this.reveal(e),this.announce(i.sel),!i.complete)return this.pending=i.sel,this.refresh(true);this.setSel(i.sel,true),this.o.closeOnSelect??(this.mode.autoClose&&!this.o.enableTime)?this.hide(true):this.cal?.focusCell();}setTime(t){this.tod=t,this.pending?(this.pending=this.pending.map(e=>this.stamp(e)),this.refresh()):this.sel.length&&this.setSel(this.sel.map(e=>this.stamp(e)),true);}announce(t){let{strings:e,label:i}=this.loc,s=o=>this.o.enableTime?`${i(o)} ${C(o)}`:i(o);this.cal?.announce(t.length?`${e.selected}: ${t.map(s).join(", ")}`:"");}reveal(t){this.focus=this.clamp(t);let e$1=this.monthsShown(),i=c$1(this.focus),s=f(i),o=s<f(this.view)?i:s>f(e(this.view,e$1-1))?e(i,1-e$1):this.view;this.lo&&f(o)<f(c$1(this.lo))&&(o=c$1(this.lo)),this.setView(o);}setView(t){let e=this.view;f(t)!==f(e)&&(this.view=t,this.emit("monthChange",new Date(t)),t.getFullYear()!==e.getFullYear()&&this.emit("yearChange",new Date(t)));}move(t){this.reveal(t),this.hover=f(this.focus),this.refresh(true);}nav(t){let e$1=this.state();(t<0?!e$1.canPrev:!e$1.canNext)||(this.focus=this.clamp(e(this.focus,t)),this.setView(e(this.view,t)),this.refresh());}state(){let t=(this.pending??this.sel).map(f);return {view:this.view,focus:this.focus,keys:t,span:this.mode.span?.(t,this.hover)??null,preview:!!this.pending,today:f(new Date),time:this.tod,disabled:this.isDisabled,canPrev:!this.lo||f(this.view)>f(c$1(this.lo)),canNext:!this.hi||f(e(this.view,this.monthsShown()))<=f(this.hi)}}refresh(t=false){this.cal?.update(this.state(),t);}mount(){if(this.cal)return;let t=this.o,e=this.cal=new x({id:this.id,loc:this.loc,cls:{...z,...t.classes},months:this.monthsShown(),otherMonths:!!t.showOtherMonths,inline:!!t.inline,theme:t.theme,time:t.enableTime?b$1():void 0,on:this.handlers});if(t.inline){this.input?this.el.after(e.el):this.el.append(e.el);return}e.el.hidden=true,(t.appendTo??this.el.ownerDocument.body).append(e.el),this.input?.setAttribute("aria-controls",this.id);}present(t){if(this.destroyed||this.o.inline)return;if(this.isOpen)return t?this.cal?.focusCell():void 0;this.mount();let e=this.cal,i=this.el.getRootNode().activeElement;this.opener=i===this.el.ownerDocument.body?null:i,this.reveal(this.sel[0]??this.focus),e.el.hidden=false,this.isOpen=true,this.refresh(),this.reposition(),this.input?.setAttribute("aria-expanded","true"),this.listenWhileOpen(),t&&e.focusCell(),this.emit("open");}hide(t){if(!this.isOpen||this.o.inline)return;let e=this.cal,i=t&&e.el.matches(":focus-within");this.pending=null,this.hover=null,this.isOpen=false,e.el.hidden=true,this.openOffs.splice(0).forEach(s=>s()),this.input?.setAttribute("aria-expanded","false"),i&&(this.opener?.isConnected?this.opener:this.el).focus({preventScroll:true}),this.emit("close");}listenWhileOpen(){let t=this.el.ownerDocument,e=(i,s,o,r=false)=>{i.addEventListener(s,o,r),this.openOffs.push(()=>i.removeEventListener(s,o,r));};if(this.o.closeOnOutsideClick!==false){let i=s=>{let o=s.composedPath();!o.includes(this.cal.el)&&!o.includes(this.anchor)&&this.hide(false);};e(t,"pointerdown",i,true),e(t,"focusin",i,true);}e(t.defaultView??window,"resize",this.reposition),e(t,"scroll",this.reposition,true);}on(t,e,i){t.addEventListener(e,i),this.offs.push(()=>t.removeEventListener(e,i));}bindInput(t){let e=(i,s)=>{t.hasAttribute(i)||(t.setAttribute(i,s),this.added.push(i));};e("aria-haspopup","dialog"),e("aria-expanded","false"),e("autocomplete","off"),this.wasReadOnly=t.readOnly,t.readOnly=this.wasReadOnly||this.o.allowInput===false,this.on(t,"click",()=>this.present(this.o.allowInput===false)),this.on(t,"input",()=>t.removeAttribute("aria-invalid")),this.on(t,"blur",()=>this.commitInput(true)),this.on(t,"keydown",i=>{if(i.key==="ArrowDown")i.preventDefault(),this.isOpen?this.cal?.focusCell():this.present(true);else if(i.key==="Enter"){let s=this.commitInput(false);(!s||this.isOpen)&&i.preventDefault(),s&&this.hide(false);}else i.key==="Escape"&&this.isOpen&&(i.stopPropagation(),this.hide(false));});}text(){return this.sel.map(t=>Y(t,this.fmt,this.loc)+(this.o.enableTime?` ${C(t)}`:"")).join(this.mode.join)}writeISO(){let{isoInput:t,outputTimestamp:e,enableTime:i$1,valueInUtc:s}=this.o;t&&(t.value=this.sel.map(o=>e?i(o,i$1,s):j$1(o,i$1,s)).join(this.mode.iso));}writeInput(){this.input&&(this.input.value=this.text(),this.input.removeAttribute("aria-invalid")),this.writeISO();}read(t){let e=!!this.o.enableTime,i=k$1(t,e,!!this.o.valueInUtc);return i?.length?i:q(t,this.fmt,this.loc,e?this.tod:void 0)}commitInput(t){let e=this.input;if(!e||this.destroyed)return true;let i=e.value.trim();if(i===this.text())return true;let s=[];return i&&(s=this.read(i),s&&(s.length>this.mode.max||s.some(this.isDisabled))&&(s=null)),s?(s=this.mode.clean(s),s[0]&&this.reveal(s[0]),this.setSel(s,true),true):(t?this.writeInput():e.setAttribute("aria-invalid","true"),false)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e}));let i=this.o;t==="change"?(i.onChange?.(e,this),this.input?.dispatchEvent(new Event("input",{bubbles:true})),this.input?.dispatchEvent(new Event("change",{bubbles:true}))):t==="open"?i.onOpen?.(this):t==="close"?i.onClose?.(this):t==="monthChange"&&i.onMonthChange?.(e,this);}};export{J as a};
@@ -0,0 +1 @@
1
+ var t,i=e=>{t=e;},o=()=>t,m=e=>[e.getHours(),e.getMinutes(),e.getSeconds()];export{i as a,o as b,m as c};
package/dist/core.css ADDED
@@ -0,0 +1,58 @@
1
+ .tp-dp{
2
+ --tp-bg:#fff;--tp-fg:#1f2328;--tp-muted:#656d76;--tp-border:#d0d7de;--tp-hover:#eef1f4;
3
+ --tp-accent:#2563eb;--tp-accent-fg:#fff;--tp-range:#dbe7fd;--tp-focus:#2563eb;
4
+ --tp-radius:.5rem;--tp-cell:2.25rem;--tp-pad:.75rem;--tp-z:1000;
5
+ --tp-shadow:0 8px 24px rgba(0,0,0,.15);
6
+ position:relative;box-sizing:border-box;width:max-content;max-width:calc(100vw - 1rem);padding:var(--tp-pad);
7
+ background:var(--tp-bg);color:var(--tp-fg);border:1px solid var(--tp-border);border-radius:var(--tp-radius);
8
+ font:.875rem/1.4 var(--tp-font,inherit)
9
+ }
10
+ .tp-dp[hidden]{display:none}
11
+ .tp-dp *{box-sizing:border-box}
12
+ .tp-pop{position:fixed;top:0;left:0;z-index:var(--tp-z);box-shadow:var(--tp-shadow)}
13
+
14
+ .tp-months{display:flex;gap:1.25rem}
15
+ .tp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}
16
+ .tp-title{flex:1;margin:0;font-size:1em;font-weight:600;text-align:center}
17
+ .tp-nav{display:grid;place-items:center;width:var(--tp-cell);height:var(--tp-cell);padding:0;border:0;border-radius:var(--tp-radius);background:none;color:inherit;cursor:pointer}
18
+ .tp-nav[data-a]:hover{background:var(--tp-hover)}
19
+ .tp-nav[data-a]::before{content:"";width:.5rem;height:.5rem;border:solid currentColor;border-width:0 0 2px 2px;transform:rotate(45deg)}
20
+ .tp-nav[data-a=next]::before{transform:rotate(-135deg)}
21
+ .tp-dp[dir=rtl] .tp-nav[data-a=prev]::before{transform:rotate(-135deg)}
22
+ .tp-dp[dir=rtl] .tp-nav[data-a=next]::before{transform:rotate(45deg)}
23
+ .tp-nav[aria-disabled=true]{opacity:.35;cursor:default}
24
+ .tp-nav[aria-disabled=true]:hover{background:none}
25
+
26
+ .tp-grid{border-collapse:collapse;table-layout:fixed}
27
+ .tp-dow,.tp-day{width:var(--tp-cell);height:var(--tp-cell);padding:0;text-align:center}
28
+ .tp-dow{font-size:.75rem;font-weight:500;color:var(--tp-muted)}
29
+ .tp-day{cursor:pointer;border-radius:var(--tp-radius);font-variant-numeric:tabular-nums}
30
+ .tp-day[aria-hidden]{visibility:hidden}
31
+ .tp-day:hover{background:var(--tp-hover)}
32
+ .tp-day:focus-visible{outline:2px solid var(--tp-focus);outline-offset:-2px}
33
+ .tp-out{color:var(--tp-muted)}
34
+ .tp-today{font-weight:700;box-shadow:inset 0 -2px 0 var(--tp-accent)}
35
+ .tp-disabled{opacity:.35;cursor:not-allowed;background:none!important}
36
+ .tp-in-range{background:var(--tp-range);border-radius:0}
37
+ .tp-range-start,.tp-range-end{background:var(--tp-range)}
38
+ .tp-range-start{border-start-end-radius:0;border-end-end-radius:0}
39
+ .tp-range-end{border-start-start-radius:0;border-end-start-radius:0}
40
+ .tp-range-start.tp-range-end{border-radius:var(--tp-radius)}
41
+ .tp-selected,.tp-selected:hover{background:var(--tp-accent);color:var(--tp-accent-fg)}
42
+
43
+ /* The time row is as wide as one month's day grid (also with two months, where it centres): three equal fields. */
44
+ .tp-time{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:.25rem;width:100%;max-width:calc(7*var(--tp-cell));margin:.5rem auto 0;padding-top:.5rem;border-top:1px solid var(--tp-border)}
45
+ /* `.tp-dp` is in the selector so a page's own `input[type=text]` rules can't stretch or restyle the fields. */
46
+ .tp-dp .tp-time-field{box-sizing:border-box;width:100%;min-width:0;height:var(--tp-cell);margin:0;padding:0 .25rem;border:1px solid var(--tp-border);border-radius:var(--tp-radius);background:var(--tp-bg);color:inherit;font:inherit;text-align:center;font-variant-numeric:tabular-nums}
47
+ .tp-dp .tp-time-field:focus-visible{outline:2px solid var(--tp-focus);outline-offset:1px}
48
+ .tp-time>span{text-align:center;color:var(--tp-muted)}
49
+
50
+ .tp-foot{display:flex;justify-content:space-between;margin-top:.5rem}
51
+ .tp-btn{padding:.25rem .5rem;border:0;border-radius:var(--tp-radius);background:none;color:var(--tp-accent);font:inherit;cursor:pointer}
52
+ .tp-btn:hover{background:var(--tp-hover)}
53
+ .tp-btn:focus-visible,.tp-nav:focus-visible{outline:2px solid var(--tp-focus);outline-offset:1px}
54
+ .tp-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
55
+
56
+ @media (forced-colors:active){
57
+ .tp-selected{forced-color-adjust:none;background:Highlight;color:HighlightText}
58
+ }
@@ -0,0 +1,33 @@
1
+ import { L as LocaleObject } from './Datepicker-BFcQ6Txe.js';
2
+ export { C as ClassNames, D as DateInput, a as Datepicker, b as DatepickerEventMap, c as DatepickerMode, d as DatepickerOptions, e as DatepickerValue, f as LocaleStrings, P as Position, W as Weekday } from './Datepicker-BFcQ6Txe.js';
3
+
4
+ /** Makes a locale available by tag, e.g. for `<x-datepicker locale="uk">`. */
5
+ declare const registerLocale: (locale: LocaleObject) => void;
6
+
7
+ /** Selection strategy. `single` is built in; `range` and `multiple` register themselves when imported. */
8
+ interface Mode {
9
+ /** Most dates a selection can hold. */
10
+ max: number;
11
+ /** Separator between dates in the input text. */
12
+ join: string;
13
+ /** Separator between dates in the ISO value. */
14
+ iso: string;
15
+ /** Close the popup when a selection completes (overridable by `closeOnSelect`). */
16
+ autoClose: boolean;
17
+ /** Sorts / limits / dedupes an arbitrary list of days into a valid selection. */
18
+ clean(dates: Date[]): Date[];
19
+ /** Selection after `day` is activated. `complete: false` keeps it pending (e.g. range start). */
20
+ pick(current: Date[], day: Date): {
21
+ sel: Date[];
22
+ complete: boolean;
23
+ };
24
+ /** Inclusive run of day keys to highlight; `hover` is the day under the pointer / focus. */
25
+ span?(keys: number[], hover: number | null): [number, number] | null;
26
+ }
27
+ declare const byDay: (a: Date, b: Date) => number;
28
+ declare const registerMode: (name: string, mode: Mode) => void;
29
+
30
+ /** Time of day: hours (0-23), minutes, seconds. */
31
+ type Time = [hours: number, minutes: number, seconds: number];
32
+
33
+ export { LocaleObject, type Mode, type Time, byDay, registerLocale, registerMode };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export{a as Datepicker}from'./chunk-LUHEXL2S.js';export{a as byDay,b as registerMode}from'./chunk-CUGUCENC.js';import'./chunk-Q2P4Y4T6.js';export{a as registerLocale}from'./chunk-BHROFXOE.js';import'./chunk-ANB32WKT.js';
@@ -0,0 +1,6 @@
1
+ import { L as LocaleObject } from '../Datepicker-BFcQ6Txe.js';
2
+
3
+ /** Ukrainian UI strings. Month and weekday names come from `Intl`. Importing registers `uk` for `<x-datepicker locale="uk">`. */
4
+ declare const uk: LocaleObject;
5
+
6
+ export { uk };
@@ -0,0 +1 @@
1
+ import {a}from'../chunk-BHROFXOE.js';import'../chunk-ANB32WKT.js';var o={code:"uk",today:"\u0421\u044C\u043E\u0433\u043E\u0434\u043D\u0456",clear:"\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438",prevMonth:"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0456\u0439 \u043C\u0456\u0441\u044F\u0446\u044C",nextMonth:"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0438\u0439 \u043C\u0456\u0441\u044F\u0446\u044C",label:"\u041E\u0431\u0435\u0440\u0456\u0442\u044C \u0434\u0430\u0442\u0443",selected:"\u041E\u0431\u0440\u0430\u043D\u043E",time:"\u0427\u0430\u0441",hours:"\u0413\u043E\u0434\u0438\u043D\u0438",minutes:"\u0425\u0432\u0438\u043B\u0438\u043D\u0438",seconds:"\u0421\u0435\u043A\u0443\u043D\u0434\u0438"};a(o);export{o as uk};
@@ -0,0 +1,6 @@
1
+ import { a as Datepicker } from './Datepicker-BFcQ6Txe.js';
2
+
3
+ /** Switches `dp` to multiple mode: `const dp = withMultiple(new Datepicker(input))`. */
4
+ declare const withMultiple: <T extends Datepicker>(dp: T) => T;
5
+
6
+ export { withMultiple };
@@ -0,0 +1 @@
1
+ import {b,a}from'./chunk-CUGUCENC.js';import {f}from'./chunk-ANB32WKT.js';var m={max:1/0,join:", ",iso:",",autoClose:false,clean:e=>e.filter((o,i)=>e.findIndex(l=>f(l)===f(o))===i).sort(a),pick:(e,o)=>{let i=f(o);return {sel:e.some(r=>f(r)===i)?e.filter(r=>f(r)!==i):[...e,o].sort(a),complete:true}}};b("multiple",m);var c=e=>(e.setOptions({mode:"multiple"}),e);export{c as withMultiple};
@@ -0,0 +1,6 @@
1
+ import { a as Datepicker } from './Datepicker-BFcQ6Txe.js';
2
+
3
+ /** Switches `dp` to range mode: `const dp = withRange(new Datepicker(input))`. */
4
+ declare const withRange: <T extends Datepicker>(dp: T) => T;
5
+
6
+ export { withRange };
package/dist/range.js ADDED
@@ -0,0 +1 @@
1
+ import {b,a}from'./chunk-CUGUCENC.js';import'./chunk-ANB32WKT.js';var o={max:2,join:" \u2013 ",iso:"/",autoClose:true,clean:e=>e.slice(0,2).sort(a),pick:(e,t)=>e.length===1?{sel:[e[0],t].sort(a),complete:true}:{sel:[t],complete:false},span:(e,t)=>e.length===2?[e[0],e[1]]:e.length===1&&t!==null?[Math.min(e[0],t),Math.max(e[0],t)]:null};b("range",o);var l=e=>(e.setOptions({mode:"range"}),e);export{l as withRange};
@@ -0,0 +1,11 @@
1
+ /* Dark follows prefers-color-scheme unless data-theme="light"; data-theme="dark" forces it. */
2
+ .tp-dp[data-theme=dark]{
3
+ --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
4
+ --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
5
+ }
6
+ @media (prefers-color-scheme:dark){
7
+ .tp-dp:not([data-theme=light]){
8
+ --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
9
+ --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
10
+ }
11
+ }
package/dist/time.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { a as Datepicker } from './Datepicker-BFcQ6Txe.js';
2
+
3
+ /** Switches `dp` to date & time: `const dp = withTime(new Datepicker(input))`. */
4
+ declare const withTime: <T extends Datepicker>(dp: T) => T;
5
+
6
+ export { withTime };
package/dist/time.js ADDED
@@ -0,0 +1 @@
1
+ import {a}from'./chunk-Q2P4Y4T6.js';var b=[["hours",23],["minutes",59],["seconds",59]],g=r=>String(r).padStart(2,"0");a(({loc:r,cls:l,change:f})=>{let s=document.createElement("div");s.className=l.time,s.setAttribute("role","group"),s.setAttribute("aria-label",r.strings.time);let a=[0,0,0],m=[];return b.forEach(([c,n],i)=>{let t=document.createElement("input");t.className=l.timeField;let v={type:"text",inputmode:"numeric",size:"2",maxlength:"2",autocomplete:"off",role:"spinbutton","aria-label":r.strings[c],"aria-valuemin":"0","aria-valuemax":String(n)};for(let[e,E]of Object.entries(v))t.setAttribute(e,E);let u=()=>{t.value=g(a[i]),t.setAttribute("aria-valuenow",String(a[i]));},o=e=>{a[i]=Math.min(n,Math.max(0,e)),u(),f([...a]);},p=()=>{let e=parseInt(t.value,10);isNaN(e)?u():o(e);};if(t.addEventListener("input",()=>t.value=t.value.replace(/\D/g,"")),t.addEventListener("change",p),t.addEventListener("focus",()=>t.select()),t.addEventListener("keydown",e=>{if(e.key==="ArrowUp"||e.key==="ArrowDown")o((a[i]+(e.key==="ArrowUp"?1:-1)+n+1)%(n+1));else if(e.key==="Home")o(0);else if(e.key==="End")o(n);else if(e.key==="Enter")p();else return;e.preventDefault();}),i){let e=document.createElement("span");e.textContent=":",e.setAttribute("aria-hidden","true"),s.append(e);}s.append(t),m.push({input:t,render:u});}),{el:s,set(c){a.splice(0,3,...c),m.forEach(({input:n,render:i})=>n.getRootNode().activeElement===n||i());}}});var h=r=>(r.setOptions({enableTime:true}),r);export{h as withTime};
package/dist/wc.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { a as Datepicker, d as DatepickerOptions } from './Datepicker-BFcQ6Txe.js';
2
+
3
+ declare const Base: typeof HTMLElement;
4
+ /**
5
+ * `<x-datepicker>`: an input + calendar toggle with the popup inside its shadow root.
6
+ *
7
+ * Attributes: `value` (ISO or Unix seconds, ranges `a/b`, multiple `a,b`), `name`, `placeholder`, `label`, `disabled`,
8
+ * `mode`, `locale`, `min`, `max`, `date-format`, `first-day`, `position`, `theme`, `number-of-months`,
9
+ * `show-other-months`, `allow-input`, `close-on-select`, `disabled-days`, `output-timestamp`, `enable-time`, `value-in-utc`, `inline`.
10
+ * Events (bubbling, composed): `change`, `open`, `close`, `monthChange`, `yearChange` with `detail`.
11
+ * Style with `::part(input)`, `::part(toggle)` and the `--tp-*` custom properties.
12
+ * Takes part in forms (via `ElementInternals`) in browsers that support form-associated elements.
13
+ */
14
+ declare class XDatepicker extends Base {
15
+ static formAssociated: boolean;
16
+ static observedAttributes: string[];
17
+ /** Resolves with the underlying picker once it exists (extra modes load asynchronously). */
18
+ ready: Promise<Datepicker | null>;
19
+ private picker;
20
+ private extra;
21
+ private gen;
22
+ private readonly internals;
23
+ private readonly root;
24
+ private readonly wrap;
25
+ private readonly input;
26
+ private readonly toggleBtn;
27
+ private readonly iso;
28
+ constructor();
29
+ /** ISO value: `2025-03-05`, `2025-03-01/2025-03-10` (range) or `2025-03-01,2025-03-05` (multiple). */
30
+ get value(): string;
31
+ set value(v: string);
32
+ /** Options that can't be expressed as attributes (callbacks, `disabledDates`, a `LocaleObject`...). */
33
+ set options(o: DatepickerOptions);
34
+ open(): void;
35
+ close(): void;
36
+ toggle(): void;
37
+ connectedCallback(): void;
38
+ disconnectedCallback(): void;
39
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
40
+ formResetCallback(): void;
41
+ formDisabledCallback(disabled: boolean): void;
42
+ private opts;
43
+ private load;
44
+ private init;
45
+ private teardown;
46
+ private apply;
47
+ private setValue;
48
+ private setDisabled;
49
+ private syncForm;
50
+ }
51
+ declare global {
52
+ interface HTMLElementTagNameMap {
53
+ 'x-datepicker': XDatepicker;
54
+ }
55
+ }
56
+
57
+ export { XDatepicker };
package/dist/wc.js ADDED
@@ -0,0 +1,70 @@
1
+ import {a as a$1}from'./chunk-LUHEXL2S.js';import'./chunk-CUGUCENC.js';import'./chunk-Q2P4Y4T6.js';import'./chunk-BHROFXOE.js';import'./chunk-ANB32WKT.js';var d=`.tp-dp{
2
+ --tp-bg:#fff;--tp-fg:#1f2328;--tp-muted:#656d76;--tp-border:#d0d7de;--tp-hover:#eef1f4;
3
+ --tp-accent:#2563eb;--tp-accent-fg:#fff;--tp-range:#dbe7fd;--tp-focus:#2563eb;
4
+ --tp-radius:.5rem;--tp-cell:2.25rem;--tp-pad:.75rem;--tp-z:1000;
5
+ --tp-shadow:0 8px 24px rgba(0,0,0,.15);
6
+ position:relative;box-sizing:border-box;width:max-content;max-width:calc(100vw - 1rem);padding:var(--tp-pad);
7
+ background:var(--tp-bg);color:var(--tp-fg);border:1px solid var(--tp-border);border-radius:var(--tp-radius);
8
+ font:.875rem/1.4 var(--tp-font,inherit)
9
+ }
10
+ .tp-dp[hidden]{display:none}
11
+ .tp-dp *{box-sizing:border-box}
12
+ .tp-pop{position:fixed;top:0;left:0;z-index:var(--tp-z);box-shadow:var(--tp-shadow)}
13
+
14
+ .tp-months{display:flex;gap:1.25rem}
15
+ .tp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}
16
+ .tp-title{flex:1;margin:0;font-size:1em;font-weight:600;text-align:center}
17
+ .tp-nav{display:grid;place-items:center;width:var(--tp-cell);height:var(--tp-cell);padding:0;border:0;border-radius:var(--tp-radius);background:none;color:inherit;cursor:pointer}
18
+ .tp-nav[data-a]:hover{background:var(--tp-hover)}
19
+ .tp-nav[data-a]::before{content:"";width:.5rem;height:.5rem;border:solid currentColor;border-width:0 0 2px 2px;transform:rotate(45deg)}
20
+ .tp-nav[data-a=next]::before{transform:rotate(-135deg)}
21
+ .tp-dp[dir=rtl] .tp-nav[data-a=prev]::before{transform:rotate(-135deg)}
22
+ .tp-dp[dir=rtl] .tp-nav[data-a=next]::before{transform:rotate(45deg)}
23
+ .tp-nav[aria-disabled=true]{opacity:.35;cursor:default}
24
+ .tp-nav[aria-disabled=true]:hover{background:none}
25
+
26
+ .tp-grid{border-collapse:collapse;table-layout:fixed}
27
+ .tp-dow,.tp-day{width:var(--tp-cell);height:var(--tp-cell);padding:0;text-align:center}
28
+ .tp-dow{font-size:.75rem;font-weight:500;color:var(--tp-muted)}
29
+ .tp-day{cursor:pointer;border-radius:var(--tp-radius);font-variant-numeric:tabular-nums}
30
+ .tp-day[aria-hidden]{visibility:hidden}
31
+ .tp-day:hover{background:var(--tp-hover)}
32
+ .tp-day:focus-visible{outline:2px solid var(--tp-focus);outline-offset:-2px}
33
+ .tp-out{color:var(--tp-muted)}
34
+ .tp-today{font-weight:700;box-shadow:inset 0 -2px 0 var(--tp-accent)}
35
+ .tp-disabled{opacity:.35;cursor:not-allowed;background:none!important}
36
+ .tp-in-range{background:var(--tp-range);border-radius:0}
37
+ .tp-range-start,.tp-range-end{background:var(--tp-range)}
38
+ .tp-range-start{border-start-end-radius:0;border-end-end-radius:0}
39
+ .tp-range-end{border-start-start-radius:0;border-end-start-radius:0}
40
+ .tp-range-start.tp-range-end{border-radius:var(--tp-radius)}
41
+ .tp-selected,.tp-selected:hover{background:var(--tp-accent);color:var(--tp-accent-fg)}
42
+
43
+ /* The time row is as wide as one month's day grid (also with two months, where it centres): three equal fields. */
44
+ .tp-time{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:.25rem;width:100%;max-width:calc(7*var(--tp-cell));margin:.5rem auto 0;padding-top:.5rem;border-top:1px solid var(--tp-border)}
45
+ /* \`.tp-dp\` is in the selector so a page's own \`input[type=text]\` rules can't stretch or restyle the fields. */
46
+ .tp-dp .tp-time-field{box-sizing:border-box;width:100%;min-width:0;height:var(--tp-cell);margin:0;padding:0 .25rem;border:1px solid var(--tp-border);border-radius:var(--tp-radius);background:var(--tp-bg);color:inherit;font:inherit;text-align:center;font-variant-numeric:tabular-nums}
47
+ .tp-dp .tp-time-field:focus-visible{outline:2px solid var(--tp-focus);outline-offset:1px}
48
+ .tp-time>span{text-align:center;color:var(--tp-muted)}
49
+
50
+ .tp-foot{display:flex;justify-content:space-between;margin-top:.5rem}
51
+ .tp-btn{padding:.25rem .5rem;border:0;border-radius:var(--tp-radius);background:none;color:var(--tp-accent);font:inherit;cursor:pointer}
52
+ .tp-btn:hover{background:var(--tp-hover)}
53
+ .tp-btn:focus-visible,.tp-nav:focus-visible{outline:2px solid var(--tp-focus);outline-offset:1px}
54
+ .tp-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
55
+
56
+ @media (forced-colors:active){
57
+ .tp-selected{forced-color-adjust:none;background:Highlight;color:HighlightText}
58
+ }
59
+ `;var l=`/* Dark follows prefers-color-scheme unless data-theme="light"; data-theme="dark" forces it. */
60
+ .tp-dp[data-theme=dark]{
61
+ --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
62
+ --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
63
+ }
64
+ @media (prefers-color-scheme:dark){
65
+ .tp-dp:not([data-theme=light]){
66
+ --tp-bg:#161b22;--tp-fg:#e6edf3;--tp-muted:#8d96a0;--tp-border:#30363d;--tp-hover:#21262d;
67
+ --tp-accent:#4c8dff;--tp-accent-fg:#0b1220;--tp-range:#1b2f52;--tp-focus:#79a8ff;--tp-shadow:0 8px 24px rgba(0,0,0,.5)
68
+ }
69
+ }
70
+ `;var f={range:()=>import('./range.js'),multiple:()=>import('./multiple.js')},v=()=>import('./time.js'),o=e=>e??void 0,c=e=>e===null?void 0:Number(e),a=e=>e===null?void 0:e!=="false",y=e=>e===null?void 0:e.split(",").map(Number),u=e=>e.split(/\s*[/,;]\s*/).filter(Boolean),h={mode:["mode",o],locale:["locale",o],min:["minDate",o],max:["maxDate",o],"date-format":["dateFormat",o],"first-day":["firstDayOfWeek",c],position:["position",o],theme:["theme",o],"number-of-months":["numberOfMonths",c],"show-other-months":["showOtherMonths",a],"allow-input":["allowInput",a],"close-on-select":["closeOnSelect",a],"disabled-days":["disabledDaysOfWeek",y],"output-timestamp":["outputTimestamp",a],"enable-time":["enableTime",a],"value-in-utc":["valueInUtc",a],inline:["inline",a]},k=["change","open","close","monthChange","yearChange"],w='<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M8 3v4M16 3v4M3 10h18"/></svg>',x=":host{display:inline-block}:host([hidden]){display:none}.wrap{position:relative;display:flex;align-items:center}input{box-sizing:border-box;width:100%;padding-inline-end:2.25rem;font:inherit}.toggle{position:absolute;inset-inline-end:.25rem;display:grid;place-items:center;width:1.75rem;height:1.75rem;padding:0;border:0;border-radius:.25rem;background:none;color:inherit;cursor:pointer}.toggle:disabled{opacity:.4;cursor:default}:host([inline]) .toggle{display:none}",E=typeof HTMLElement>"u"?class{}:HTMLElement,n=class extends E{constructor(){super();this.ready=Promise.resolve(null);this.picker=null;this.extra={};this.gen=0;this.internals=this.attachInternals?.();let t=this.root=this.attachShadow({mode:"open",delegatesFocus:true});t.innerHTML=`<style>${d}${l}${x}</style><div class="wrap" part="wrap"><input part="input"><button type="button" class="toggle" part="toggle" aria-haspopup="dialog">${w}</button></div>`,this.wrap=t.querySelector(".wrap"),this.input=t.querySelector("input"),this.toggleBtn=t.querySelector("button"),this.iso=document.createElement("input"),this.toggleBtn.addEventListener("click",()=>this.picker?.toggle());}get value(){return this.iso.value}set value(t){this.setAttribute("value",t);}set options(t){this.extra={...this.extra,...t},this.apply();}open(){this.picker?.open();}close(){this.picker?.close();}toggle(){this.picker?.toggle();}connectedCallback(){this.ready=this.init();}disconnectedCallback(){this.teardown();}attributeChangedCallback(t,i,r){if(t==="placeholder")this.input.placeholder=r??"";else if(t==="label")r===null?this.input.removeAttribute("aria-label"):this.input.setAttribute("aria-label",r);else if(t==="disabled")this.setDisabled(r!==null);else if(this.picker)t==="value"?this.setValue(r):t==="inline"?(this.teardown(),this.ready=this.init()):this.apply();else return}formResetCallback(){this.setValue(this.getAttribute("value"));}formDisabledCallback(t){this.setDisabled(t);}opts(){let t={};for(let[i,[r,s]]of Object.entries(h))t[r]=s(this.getAttribute(i));return {...t,...this.extra}}async load({mode:t,enableTime:i}){await Promise.all([t&&f[t]?.(),i&&v()]);}async init(){if(this.picker)return this.picker;let t=++this.gen;if(await this.load(this.opts()),t!==this.gen||!this.isConnected)return null;let i=this.getAttribute("value"),r=this.picker=new a$1(this.input,{...this.opts(),selected:i?u(i):null,isoInput:this.iso,anchor:this.wrap,appendTo:this.root});for(let s of k)r.addEventListener(s,g=>{s==="change"&&this.syncForm(),this.dispatchEvent(new CustomEvent(s,{detail:g.detail,bubbles:true,composed:true}));});return this.toggleBtn.setAttribute("aria-label",r.strings.label),this.setDisabled(this.hasAttribute("disabled")),this.syncForm(),r}teardown(){this.gen++,this.picker?.destroy(),this.picker=null;}async apply(){let t=this.opts();await this.load(t),this.picker&&(this.picker.setOptions(t),this.syncForm(),this.toggleBtn.setAttribute("aria-label",this.picker.strings.label));}setValue(t){this.picker?.setDate(t?u(t):[]),this.syncForm();}setDisabled(t){this.input.disabled=this.toggleBtn.disabled=t,t&&this.picker?.close();}syncForm(){this.internals?.setFormValue?.(this.iso.value||null);}};n.formAssociated=true,n.observedAttributes=[...Object.keys(h),"value","placeholder","label","disabled"];typeof customElements<"u"&&!customElements.get("x-datepicker")&&customElements.define("x-datepicker",n);export{n as XDatepicker};
package/package.json CHANGED
@@ -1,6 +1,79 @@
1
1
  {
2
2
  "name": "ten-protons-datepicker",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency, accessible, tiny datepicker. Vanilla TypeScript + Web Component.",
5
+ "keywords": [
6
+ "datepicker",
7
+ "date-picker",
8
+ "calendar",
9
+ "web-component",
10
+ "a11y",
11
+ "range",
12
+ "zero-dependency"
13
+ ],
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "sideEffects": [
17
+ "./dist/range.js",
18
+ "./dist/multiple.js",
19
+ "./dist/time.js",
20
+ "./dist/wc.js",
21
+ "./dist/locales/*.js",
22
+ "*.css"
23
+ ],
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./range": {
33
+ "types": "./dist/range.d.ts",
34
+ "import": "./dist/range.js"
35
+ },
36
+ "./multiple": {
37
+ "types": "./dist/multiple.d.ts",
38
+ "import": "./dist/multiple.js"
39
+ },
40
+ "./time": {
41
+ "types": "./dist/time.d.ts",
42
+ "import": "./dist/time.js"
43
+ },
44
+ "./wc": {
45
+ "types": "./dist/wc.d.ts",
46
+ "import": "./dist/wc.js"
47
+ },
48
+ "./locales/uk": {
49
+ "types": "./dist/locales/uk.d.ts",
50
+ "import": "./dist/locales/uk.js"
51
+ },
52
+ "./core.css": "./dist/core.css",
53
+ "./themes.css": "./dist/themes.css",
54
+ "./package.json": "./package.json"
55
+ },
56
+ "files": [
57
+ "dist",
58
+ "README.md",
59
+ "LICENSE"
60
+ ],
61
+ "scripts": {
62
+ "typecheck": "tsc --noEmit",
63
+ "build": "tsup",
64
+ "demo": "npm run build && npx --yes serve . -l 5173",
65
+ "prepublishOnly": "npm run typecheck && npm run build"
66
+ },
67
+ "devDependencies": {
68
+ "tsup": "^8.5.0",
69
+ "typescript": "^5.9.0"
70
+ },
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/TenProtons/Ten-Protons-Datepicker.git"
74
+ },
75
+ "homepage": "https://github.com/TenProtons/Ten-Protons-Datepicker#readme",
76
+ "bugs": {
77
+ "url": "https://github.com/TenProtons/Ten-Protons-Datepicker/issues"
78
+ }
79
+ }