@kalyx/core 1.0.0-rc.0 → 1.0.0-rc.11
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/CHANGELOG.md +235 -0
- package/README.md +30 -0
- package/dist/index.cjs +35 -26
- package/dist/index.d.cts +40 -5
- package/dist/index.d.ts +40 -5
- package/dist/index.js +34 -26
- package/package.json +11 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# @kalyx/core
|
|
2
|
+
|
|
3
|
+
## 1.0.0-rc.11
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c8a6609: fix(rangepicker): announce next selection target and final range to screen readers
|
|
8
|
+
|
|
9
|
+
`<RangePicker.Calendar>` now announces context-aware messages through its existing `role="status"` live region:
|
|
10
|
+
- After the first click (start), it announces `<formatted-date>. Now select end date.` so screen-reader users know the next click commits the other endpoint.
|
|
11
|
+
- After the second click (end), it announces `Range selected: <start> – <end>` instead of just the bare date — matching the swap-if-before behaviour so the announcement always reflects what was committed.
|
|
12
|
+
- Week-mode commits now share the same `Range selected: ...` prefix for consistency.
|
|
13
|
+
|
|
14
|
+
The two new strings are wired through `RangePickerLabels.selectingEnd` and `RangePickerLabels.rangeSelected` with English defaults, and they are fully overridable via the existing `labels` prop for i18n. `@kalyx/core` gets a `minor` bump because `RangePickerLabels` gained required fields (with defaults supplied by `DEFAULT_RANGEPICKER_LABELS`); any consumer constructing a literal `RangePickerLabels` from scratch will need to add the two keys.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 19ac1c0: fix(core): allow `generateMinutes` step values up to 60
|
|
19
|
+
|
|
20
|
+
`generateMinutes(step)` rejected any step above 30, which prevented legitimate cases like `step=45` (quarter-and-three-quarters past the hour) and `step=60` (on-the-hour only). The slot-generation loop already works for any 1–60 integer, so the upper bound is now 60 with the same error message format. Steps `0`, `61+`, and negative values still throw. No callers in `@kalyx/react` relied on the previous narrower bound.
|
|
21
|
+
|
|
22
|
+
## 1.0.0-rc.10
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- 4629384: chore(oss): unify node engines to >=20 and add public repository metadata
|
|
27
|
+
- `@kalyx/react` and `@kalyx/core` now require Node `>=20`, matching the root workspace and CI. This was the de-facto requirement; only the published manifests still claimed `>=18`.
|
|
28
|
+
- Root `package.json` now exposes `homepage`, `repository`, and `bugs` so `npm info` and the npm registry page link back to the GitHub repo.
|
|
29
|
+
- `.github/PULL_REQUEST_TEMPLATE.md` bundle ceiling updated `15KB → 16 KB` to match the post-rc.8 limit advertised in README and CI.
|
|
30
|
+
- `.gitignore` ignores `.codegraph/`, `.serena/`, and `.tmp-*/` (MCP server caches and worktree scratchpads).
|
|
31
|
+
|
|
32
|
+
## 1.0.0-rc.7
|
|
33
|
+
|
|
34
|
+
### Minor Changes
|
|
35
|
+
|
|
36
|
+
- 0eca2e8: Two new `DatePicker.Calendar` / `RangePicker.Calendar` props plus an ISO-week utility:
|
|
37
|
+
- **`showWeekNumber`** — render an ISO 8601 week-number column (1–53) on the left of the grid. The column uses `<th scope="row" aria-hidden="true">` so it doesn't participate in the WAI-ARIA grid data region; keyboard navigation across date cells is unchanged. New className slots: `weekNumberHeader`, `weekNumber`.
|
|
38
|
+
- **`fixedWeeks`** — when true, always render 6 rows (42 cells) regardless of the month. Useful for popover layouts that need a stable height across month navigation.
|
|
39
|
+
|
|
40
|
+
Both also accepted on `CalendarOptions` (the `getCalendarDays` core util gains `fixedWeeks`).
|
|
41
|
+
|
|
42
|
+
New core export: **`getISOWeekNumber(iso)`** — pure UTC computation, no date-fns dep. Anchored to the Thursday of the week (so the same week always returns the same number regardless of `weekStartsOn`).
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<DatePicker value={date} onChange={setDate}>
|
|
46
|
+
<DatePicker.Input />
|
|
47
|
+
<DatePicker.Popover>
|
|
48
|
+
<DatePicker.Calendar showWeekNumber fixedWeeks />
|
|
49
|
+
</DatePicker.Popover>
|
|
50
|
+
</DatePicker>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Bundle impact: +0.46 KB ESM gzip (13.96 → 14.42 KB). Still well under the 15 KB ceiling.
|
|
54
|
+
|
|
55
|
+
- d62c84e: `DisabledRule` gains a programmatic `filter` variant — pass any predicate `(iso: ISODateString) => boolean` to disable arbitrary days that don't fit the declarative `before` / `after` / `dayOfWeek` / `date` rules.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const holidays = new Set(['2026-01-01T00:00:00.000Z', '2026-12-25T00:00:00.000Z']);
|
|
59
|
+
|
|
60
|
+
<DatePicker
|
|
61
|
+
disabled={[
|
|
62
|
+
{ dayOfWeek: [0, 6] }, // weekends
|
|
63
|
+
{ filter: (iso) => holidays.has(iso) }, // holidays
|
|
64
|
+
]}
|
|
65
|
+
>
|
|
66
|
+
…
|
|
67
|
+
</DatePicker>;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The new variant slots into the existing `isDateDisabled` evaluation (short-circuits on first match) and works with keyboard-navigation disabled-skip in `DatePicker.Calendar` / `RangePicker.Calendar` with no further changes. Equivalent to `react-datepicker`'s `filterDate` prop and MUI X DatePicker's `shouldDisableDate`. Bundle impact: 0 KB (still 13.96 KB ESM gzip).
|
|
71
|
+
|
|
72
|
+
## 1.0.0-rc.6
|
|
73
|
+
|
|
74
|
+
### Patch Changes
|
|
75
|
+
|
|
76
|
+
- abc56ac: Security: pin transitive `fast-uri` to `>=3.1.2` and `@babel/plugin-transform-modules-systemjs` to `>=7.29.4` via `pnpm.overrides`.
|
|
77
|
+
|
|
78
|
+
Resolves three Code Scanning alerts on `pnpm-lock.yaml`:
|
|
79
|
+
- `fast-uri@3.1.0` — [GHSA-v39h-62p7-jpjc](https://osv.dev/GHSA-v39h-62p7-jpjc) (CVE-2026-6322), first patched in `3.1.2`.
|
|
80
|
+
- `fast-uri@3.1.0` — [GHSA-q3j6-qgpj-74h6](https://osv.dev/GHSA-q3j6-qgpj-74h6) (CVE-2026-6321), first patched in `3.1.1`.
|
|
81
|
+
- `@babel/plugin-transform-modules-systemjs@7.29.0` — [GHSA-fv7c-fp4j-7gwp](https://osv.dev/GHSA-fv7c-fp4j-7gwp) (CVE-2026-44728), first patched in `7.29.4` on the 7.x line.
|
|
82
|
+
|
|
83
|
+
All three packages are transitive build-time dependencies (ajv → fast-uri, Babel preset-env → systemjs plugin); no public API impact.
|
|
84
|
+
|
|
85
|
+
## 1.0.0-rc.4
|
|
86
|
+
|
|
87
|
+
### Patch Changes
|
|
88
|
+
|
|
89
|
+
- df97687: P1 audit follow-ups for v1.0-rc:
|
|
90
|
+
- **SSR hydration safety in 4 commit/drilldown grids** — `DatePicker.MonthGrid`, `DatePicker.YearGrid`, `MonthPicker.Grid`, and `YearPicker.Grid` previously called `adapter.today()` directly inside their render bodies, producing a server/client clock-mismatch hydration warning across day boundaries (and intermittently wrong "today" highlights in tz-different SSR setups). Today is now snapshotted via `useState(null)` + post-mount `useEffect`, so the server output and the first client render agree, and the highlight settles on the first effect tick.
|
|
91
|
+
- **`AmPmToggle` now follows the WAI-ARIA radiogroup pattern** — Arrow / Home / End / Space / Enter move and commit selection between AM and PM, and `tabIndex` is roving (only the checked radio is in the tab order). Previously both buttons were tabbable and arrow keys were ignored.
|
|
92
|
+
- **`DatePicker.Preset` / `RangePicker.Preset` now use `aria-pressed`** instead of `role="option"` + `aria-selected`. `role="option"` is invalid outside `role="listbox"` / `role="combobox"`, so axe was flagging the previous markup. Active state still appears on `data-active` for CSS targeting.
|
|
93
|
+
- **`RangePicker.Calendar` no longer advertises `aria-multiselectable="true"`** — a date range is one selection (two endpoints), not a multi-select grid.
|
|
94
|
+
- **Test stability** — `useRangePicker` `respects disabled rules` test pinned to April 2026 via `defaultValue` so the calendar grid contains the expected weekend day regardless of the system clock (was failing once the clock crossed into May).
|
|
95
|
+
- **`labels.ts` test coverage** — first unit tests for the default-label exports.
|
|
96
|
+
|
|
97
|
+
Behavioral notes for users (none of these are breaking for code that follows the documented `data-*` styling contract):
|
|
98
|
+
- If you targeted Preset buttons via `[aria-selected="true"]` in CSS, switch to `[aria-pressed="true"]` or `[data-active]`.
|
|
99
|
+
- If you targeted the range grid via `[aria-multiselectable]`, that attribute is gone; use `[role="grid"]` on the calendar root instead.
|
|
100
|
+
|
|
101
|
+
## 1.0.0-rc.3
|
|
102
|
+
|
|
103
|
+
### Patch Changes
|
|
104
|
+
|
|
105
|
+
- 3587b13: Remove unused English-hardcoded weekday utilities from `utils/date.ts`:
|
|
106
|
+
- `WEEKDAY_LABELS` (constant)
|
|
107
|
+
- `getOrderedWeekdays()` (function)
|
|
108
|
+
|
|
109
|
+
Both were internal exports (never exposed via `@kalyx/core` public `index.ts`) and had no consumers anywhere in the workspace. They were superseded by the locale-aware `getWeekdayNames(locale, weekStartsOn)` in `utils/locale.ts`, which uses `Intl.DateTimeFormat` to produce the same shape with multi-language support.
|
|
110
|
+
|
|
111
|
+
No public API surface changed.
|
|
112
|
+
|
|
113
|
+
## 1.0.0-rc.2
|
|
114
|
+
|
|
115
|
+
### Patch Changes
|
|
116
|
+
|
|
117
|
+
- aadb512: Security: pin transitive `postcss` to `>=8.5.10` via `pnpm.overrides`.
|
|
118
|
+
|
|
119
|
+
Two `postcss` versions in `pnpm-lock.yaml` (`8.4.31` from a `postcss-load-config` chain and `8.5.9` from the `tsup` chain) were affected by [GHSA-qx2v-qp2m-jg93](https://osv.dev/GHSA-qx2v-qp2m-jg93) (CVSS 6.1 — improper newline handling that lets crafted input bypass quote escapes). Both are now resolved to `8.5.10`+. The OSV scanner workflow (which auto-creates issues #23 / #24 / #27) now reports zero advisories.
|
|
120
|
+
|
|
121
|
+
- 21f3c1f: Resolve v1.0-rc release-blocking defects (P0):
|
|
122
|
+
- **`"use client"` directive** — bundle is now marked as a React Server Component client boundary via tsup banner. Next.js App Router consumers no longer have to wrap each import.
|
|
123
|
+
- **Stable `today()`/`now()` initialization** — `viewMonth`/`focusedDate` `useState` calls in `DatePicker`/`RangePicker`/`DateTimePicker` Roots now use lazy initializers, so the adapter isn't called on every render.
|
|
124
|
+
- **`@kalyx/core` version sync** — bumped from `1.0.0-rc.0` to `1.0.0-rc.1` to match `@kalyx/react`.
|
|
125
|
+
- **`@kalyx/core` package contents** — `LICENSE` and `CHANGELOG.md` are now included in the npm tarball (`files` field).
|
|
126
|
+
- **Form auto-submit blocked when calendar open** — pressing Enter inside `DatePicker.Input`/`RangePicker.Input`/`DateTimePicker.Input` while the popover is open no longer submits the surrounding `<form>`.
|
|
127
|
+
- **`aria-haspopup="dialog"` on Trigger** — completes the WAI-ARIA combobox/dialog pattern.
|
|
128
|
+
- **Disabled cells skipped during keyboard navigation** — Calendar arrow keys / PageUp/Down / Home / End now step over disabled days and stop only when no enabled day is reachable.
|
|
129
|
+
|
|
130
|
+
- 3228533: P1 v1.0-rc API/a11y/docs improvements:
|
|
131
|
+
- **Popover focus-out close** — `usePopover` now closes the popover when focus leaves the floating layer and the reference element (Tab through). Matches the Radix/Ark dismissable layer pattern.
|
|
132
|
+
- **`name` prop + hidden form input** — `DatePicker.Input` accepts a `name` prop. When set, a hidden `<input type="hidden">` is rendered alongside the visible input so the value participates in native form submission and integrates with `react-hook-form` Controller-less flows.
|
|
133
|
+
- **IME composition handling** — `DatePicker.Input` now defers parsing during IME composition (`compositionstart` / `compositionend`). Previously, partial Korean / Japanese / Chinese input was repeatedly re-parsed and the user's text disappeared.
|
|
134
|
+
- **README parity** — Korean README now has the "Styling with Tailwind CSS" and "Using data attributes" sections that were missing. Version table and bundle-size claim corrected to `v1.0.0-rc.1` / `11.57 KB`.
|
|
135
|
+
- **Package metadata** — `peerDependenciesMeta`, `engines.node`, and `publishConfig.provenance` added to both `@kalyx/react` and `@kalyx/core`.
|
|
136
|
+
- **`@kalyx/react` description** — corrected from "under 10 KB gzipped" (false claim) to "≤12 KB gzipped".
|
|
137
|
+
|
|
138
|
+
- b6129ed: P2 polish for v1.0-rc:
|
|
139
|
+
- **Calendar grid `aria-rowindex` / `aria-colindex` / `aria-rowcount` / `aria-colcount`** — `DatePicker.Calendar` and `RangePicker.Calendar` now expose grid coordinates so screenreaders announce position ("row 3 of 6, column 4 of 7") during keyboard navigation.
|
|
140
|
+
- **`displayName` on all `forwardRef` components** — `DatePicker.Input`, `DatePicker.Trigger`, `RangePicker.Input`, `TimePicker.Input`, `DateTimePicker.Input` now render with their public dot-notation name in React DevTools.
|
|
141
|
+
- **JSDoc on `DatePicker.Input` and `DatePicker.Trigger`** — public API surface for the most-used components has explanatory docstrings.
|
|
142
|
+
- **`addYears` leap-day regression tests** — locked the date-fns clamp behavior (2024-02-29 + 1y → 2025-02-28, not March 1).
|
|
143
|
+
- **DST fall-back ambiguous-hour regression test** — captures the current behavior of `setTimeInTimezone` for 2026-11-01 01:30 America/New_York so silent drift surfaces as a test failure.
|
|
144
|
+
- **Test count claim corrected** — root and core `CLAUDE.md` previously claimed "1,000+ unit tests"; actual count is ~140 in core, 374 across the workspace.
|
|
145
|
+
|
|
146
|
+
## 1.0.0-rc.0
|
|
147
|
+
|
|
148
|
+
### Major Changes
|
|
149
|
+
|
|
150
|
+
- ca7180e: chore: v1.0 milestone — API freeze.
|
|
151
|
+
|
|
152
|
+
Kalyx v1.0 declares the public API stable. This is a milestone release bundling the v0.5 surface additions (MonthPicker, YearPicker, WeekPicker, DatePicker.Presets, `onOpenChange`/`onCalendarNavigate` event callbacks) with an explicit commitment to semantic versioning going forward.
|
|
153
|
+
|
|
154
|
+
### What v1.0 commits to
|
|
155
|
+
- **Public API surface** — exports from `@kalyx/react` and `@kalyx/core` listed in their `index.ts` files. Any breaking change requires a major bump.
|
|
156
|
+
- **Compositional structure** — Root + subcomponent names (`DatePicker.Input`, `DatePicker.Calendar`, …) are stable. Removal or renaming requires a major bump.
|
|
157
|
+
- **Value semantics** — ISO 8601 UTC strings for single dates, `DateRange` `{start, end}` for ranges. `displayTimezone` behavior (civil-midnight-in-tz for date selection) is stable.
|
|
158
|
+
- **Accessibility contracts** — role/aria-\* attributes emitted by each component are stable.
|
|
159
|
+
|
|
160
|
+
### What v1.0 does NOT freeze
|
|
161
|
+
- Internal implementation details (non-exported functions, component file layout).
|
|
162
|
+
- CSS class name strings on elements — no classes are applied by default; only when a consumer passes them via `classNames` props.
|
|
163
|
+
- Error message text.
|
|
164
|
+
- Peer dependency version ranges (may expand to cover new React majors).
|
|
165
|
+
|
|
166
|
+
### Breaking changes vs 0.4.x
|
|
167
|
+
|
|
168
|
+
None. v1.0 is API-compatible with 0.4.x — existing code continues to work. The major bump communicates stability commitment, not breakage.
|
|
169
|
+
|
|
170
|
+
## 0.4.0
|
|
171
|
+
|
|
172
|
+
### Minor Changes
|
|
173
|
+
|
|
174
|
+
- 104bbf2: feat: full `displayTimezone` support across all pickers (v0.4)
|
|
175
|
+
|
|
176
|
+
All four pickers (`DatePicker`, `RangePicker`, `TimePicker`, `DateTimePicker`) and their corresponding hooks (`useDatePicker`, `useRangePicker`, `useTimePicker`) now accept a `displayTimezone` prop/option.
|
|
177
|
+
|
|
178
|
+
When set, the value stored via `onChange` is the **civil midnight of the selected day in the target timezone** (in UTC-ISO form), eliminating the classic "day off by one" bug that affects picker libraries bound to `new Date()`. Input formatting, calendar highlighting, and the time-of-day controls all follow the display timezone — including DST-aware offsets for zones like `America/New_York` and `Europe/London`.
|
|
179
|
+
|
|
180
|
+
`DateFnsAdapter` now honors the `timezone` argument on `format`, `isSameDay`, `startOfDay`, and `today` (previously declared-but-ignored). Core also exposes new helpers:
|
|
181
|
+
- `civilMidnightFromUtcDay(iso, tz)`
|
|
182
|
+
- `getTimeInTimezone(iso, tz)`
|
|
183
|
+
- `setTimeInTimezone(iso, partial, tz)`
|
|
184
|
+
|
|
185
|
+
No breaking changes — omitting `displayTimezone` keeps the existing UTC semantics.
|
|
186
|
+
|
|
187
|
+
### Patch Changes
|
|
188
|
+
|
|
189
|
+
- b3a8897: perf: mark `@kalyx/core` as `sideEffects: false` so downstream bundlers can tree-shake unused exports. Safe because the package is purely functional (no module-level side effects).
|
|
190
|
+
|
|
191
|
+
## 0.3.0
|
|
192
|
+
|
|
193
|
+
### Minor Changes
|
|
194
|
+
|
|
195
|
+
- 669391b: Improve code quality, performance, and stability
|
|
196
|
+
- Enforce UTC timezone suffix in ISO regex
|
|
197
|
+
- Extract shared usePopover and useListboxNavigation hooks
|
|
198
|
+
- Add Intl.DateTimeFormat caching for locale/timezone utilities
|
|
199
|
+
- Memoize disabledRules to prevent unnecessary context re-creation
|
|
200
|
+
- Add try-catch around adapter.format() for error resilience
|
|
201
|
+
- Cancel requestAnimationFrame on unmount in listbox navigation
|
|
202
|
+
- Remove unused parseInputValue format parameter
|
|
203
|
+
- Boost test coverage: 87% → 92%
|
|
204
|
+
- Fix bundle size measurement to report both ESM and CJS
|
|
205
|
+
|
|
206
|
+
## 0.2.2
|
|
207
|
+
|
|
208
|
+
### Patch Changes
|
|
209
|
+
|
|
210
|
+
- ebf4fd7: Add repository/homepage/bugs/keywords metadata to @kalyx/core for npm provenance validation
|
|
211
|
+
|
|
212
|
+
## 0.2.1
|
|
213
|
+
|
|
214
|
+
### Patch Changes
|
|
215
|
+
|
|
216
|
+
- fe0e63e: Add full documentation site (Docusaurus, EN/KO), rewrite READMEs for npm, fix CI pnpm version to 10
|
|
217
|
+
|
|
218
|
+
## 0.2.0
|
|
219
|
+
|
|
220
|
+
### Minor Changes
|
|
221
|
+
|
|
222
|
+
- e9bb9e8: Initial release of Kalyx — headless, SSR-safe React DatePicker library.
|
|
223
|
+
|
|
224
|
+
Features:
|
|
225
|
+
- DatePicker: single date selection with Calendar, Input, Trigger, Popover
|
|
226
|
+
- RangePicker: date range selection with auto-swap and hover preview
|
|
227
|
+
- TimePicker: 12h/24h mode, minute step, HourList/MinuteList/AmPmToggle
|
|
228
|
+
- DateTimePicker: combined date+time via context bridging (reuses existing components)
|
|
229
|
+
- useDatePicker, useRangePicker, useTimePicker hooks for custom UIs
|
|
230
|
+
- WAI-ARIA compliant: grid, dialog, combobox, listbox, radiogroup patterns
|
|
231
|
+
- SSR safe: verified with Next.js 15 App Router
|
|
232
|
+
- Zero CSS: style with classNames prop and data-\* attributes
|
|
233
|
+
- ISO 8601 UTC strings only (no native Date objects)
|
|
234
|
+
- Bundle: 7.71KB gzip (target ≤12KB)
|
|
235
|
+
- 185 unit/integration tests passing
|
package/README.md
CHANGED
|
@@ -73,6 +73,36 @@ import {
|
|
|
73
73
|
} from '@kalyx/core';
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Timezone helpers
|
|
77
|
+
|
|
78
|
+
DST-aware timezone utilities used by every picker when `displayTimezone` is set.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {
|
|
82
|
+
formatInTimezone,
|
|
83
|
+
startOfDayInTimezone,
|
|
84
|
+
isSameDayInTimezone,
|
|
85
|
+
todayInTimezone,
|
|
86
|
+
getTimezoneOffsetMinutes,
|
|
87
|
+
civilMidnightFromUtcDay,
|
|
88
|
+
getTimeInTimezone,
|
|
89
|
+
setTimeInTimezone,
|
|
90
|
+
} from '@kalyx/core';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Accessibility labels
|
|
94
|
+
|
|
95
|
+
Default ARIA labels (English). Override via the `labels` prop on any picker Root.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import {
|
|
99
|
+
DEFAULT_DATEPICKER_LABELS,
|
|
100
|
+
DEFAULT_RANGEPICKER_LABELS,
|
|
101
|
+
DEFAULT_TIMEPICKER_LABELS,
|
|
102
|
+
DEFAULT_DATETIMEPICKER_LABELS,
|
|
103
|
+
} from '@kalyx/core';
|
|
104
|
+
```
|
|
105
|
+
|
|
76
106
|
## Principles
|
|
77
107
|
|
|
78
108
|
- **All dates are ISO 8601 UTC strings** — never `Date` objects.
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
generateHours: () => generateHours,
|
|
35
35
|
generateMinutes: () => generateMinutes,
|
|
36
36
|
getCalendarDays: () => getCalendarDays,
|
|
37
|
+
getISOWeekNumber: () => getISOWeekNumber,
|
|
37
38
|
getMonthName: () => getMonthName,
|
|
38
39
|
getTime: () => getTime,
|
|
39
40
|
getTimeInTimezone: () => getTimeInTimezone,
|
|
@@ -82,9 +83,7 @@ function getCachedPartsFormatter(timeZone) {
|
|
|
82
83
|
}
|
|
83
84
|
function partsInTimezone(utc, timeZone) {
|
|
84
85
|
const dtf = getCachedPartsFormatter(timeZone);
|
|
85
|
-
const parts = Object.fromEntries(
|
|
86
|
-
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
87
|
-
);
|
|
86
|
+
const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
|
|
88
87
|
return {
|
|
89
88
|
year: Number(parts.year),
|
|
90
89
|
month: Number(parts.month),
|
|
@@ -135,14 +134,9 @@ function todayInTimezone(timeZone) {
|
|
|
135
134
|
}
|
|
136
135
|
function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
|
|
137
136
|
const utc = (0, import_date_fns.parseISO)(gridUtcIso);
|
|
138
|
-
const probe = new Date(
|
|
139
|
-
utc.getUTCFullYear(),
|
|
140
|
-
|
|
141
|
-
utc.getUTCDate(),
|
|
142
|
-
12,
|
|
143
|
-
0,
|
|
144
|
-
0
|
|
145
|
-
)).toISOString();
|
|
137
|
+
const probe = new Date(
|
|
138
|
+
Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
|
|
139
|
+
).toISOString();
|
|
146
140
|
return startOfDayInTimezone(probe, timeZone);
|
|
147
141
|
}
|
|
148
142
|
function getTimeInTimezone(iso, timeZone) {
|
|
@@ -154,7 +148,14 @@ function setTimeInTimezone(iso, partial, timeZone) {
|
|
|
154
148
|
const targetHours = partial.hours ?? p.hour;
|
|
155
149
|
const targetMinutes = partial.minutes ?? p.minute;
|
|
156
150
|
const targetSeconds = partial.seconds ?? p.second;
|
|
157
|
-
const civilEpoch = Date.UTC(
|
|
151
|
+
const civilEpoch = Date.UTC(
|
|
152
|
+
p.year,
|
|
153
|
+
p.month - 1,
|
|
154
|
+
p.day,
|
|
155
|
+
targetHours,
|
|
156
|
+
targetMinutes,
|
|
157
|
+
targetSeconds
|
|
158
|
+
);
|
|
158
159
|
const probe1 = new Date(civilEpoch).toISOString();
|
|
159
160
|
const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
|
|
160
161
|
const realEpoch1 = civilEpoch - offset1 * 6e4;
|
|
@@ -193,15 +194,9 @@ function utcStartOfWeek(d, weekStartsOn) {
|
|
|
193
194
|
}
|
|
194
195
|
function utcEndOfWeek(d, weekStartsOn) {
|
|
195
196
|
const start = utcStartOfWeek(d, weekStartsOn);
|
|
196
|
-
return new Date(
|
|
197
|
-
start.getUTCFullYear(),
|
|
198
|
-
|
|
199
|
-
start.getUTCDate() + 6,
|
|
200
|
-
23,
|
|
201
|
-
59,
|
|
202
|
-
59,
|
|
203
|
-
999
|
|
204
|
-
));
|
|
197
|
+
return new Date(
|
|
198
|
+
Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
|
|
199
|
+
);
|
|
205
200
|
}
|
|
206
201
|
var DateFnsAdapter = {
|
|
207
202
|
parse(value) {
|
|
@@ -315,7 +310,8 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
315
310
|
disabled = [],
|
|
316
311
|
range,
|
|
317
312
|
rangeHover,
|
|
318
|
-
timezone
|
|
313
|
+
timezone,
|
|
314
|
+
fixedWeeks = false
|
|
319
315
|
} = options;
|
|
320
316
|
const todayISO = today ?? adapter.today(timezone);
|
|
321
317
|
const monthStart = adapter.startOfMonth(monthISO);
|
|
@@ -345,12 +341,20 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
345
341
|
current = adapter.addDays(current, 1);
|
|
346
342
|
}
|
|
347
343
|
weeks.push(days);
|
|
348
|
-
if (!adapter.isSameMonth(current, monthISO) && week >= 3) {
|
|
344
|
+
if (!fixedWeeks && !adapter.isSameMonth(current, monthISO) && week >= 3) {
|
|
349
345
|
break;
|
|
350
346
|
}
|
|
351
347
|
}
|
|
352
348
|
return weeks;
|
|
353
349
|
}
|
|
350
|
+
function getISOWeekNumber(iso) {
|
|
351
|
+
const d = new Date(iso);
|
|
352
|
+
const utc = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
353
|
+
const isoDay = utc.getUTCDay() || 7;
|
|
354
|
+
utc.setUTCDate(utc.getUTCDate() + 4 - isoDay);
|
|
355
|
+
const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
|
|
356
|
+
return Math.ceil(((utc.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
357
|
+
}
|
|
354
358
|
function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
|
|
355
359
|
if (!range) return { start: null, end: null };
|
|
356
360
|
const { start, end } = range;
|
|
@@ -394,6 +398,8 @@ function isDateDisabled(iso, rules, adapter) {
|
|
|
394
398
|
if (adapter.isAfter(iso, rule.after)) return true;
|
|
395
399
|
} else if ("dayOfWeek" in rule) {
|
|
396
400
|
if (rule.dayOfWeek.includes(adapter.getDay(iso))) return true;
|
|
401
|
+
} else if ("filter" in rule) {
|
|
402
|
+
if (rule.filter(iso)) return true;
|
|
397
403
|
}
|
|
398
404
|
}
|
|
399
405
|
return false;
|
|
@@ -489,8 +495,8 @@ function generateHours(format = "24h") {
|
|
|
489
495
|
return Array.from({ length: 24 }, (_, i) => i);
|
|
490
496
|
}
|
|
491
497
|
function generateMinutes(step = 1) {
|
|
492
|
-
if (step < 1 || step >
|
|
493
|
-
throw new Error(`[generateMinutes] step must be between 1 and
|
|
498
|
+
if (step < 1 || step > 60) {
|
|
499
|
+
throw new Error(`[generateMinutes] step must be between 1 and 60, got ${step}`);
|
|
494
500
|
}
|
|
495
501
|
const result = [];
|
|
496
502
|
for (let i = 0; i < 60; i += step) {
|
|
@@ -584,7 +590,9 @@ var DEFAULT_RANGEPICKER_LABELS = {
|
|
|
584
590
|
popoverLabel: "Choose date range",
|
|
585
591
|
startInput: "Start date",
|
|
586
592
|
endInput: "End date",
|
|
587
|
-
presetsGroup: "Date range presets"
|
|
593
|
+
presetsGroup: "Date range presets",
|
|
594
|
+
selectingEnd: "Now select end date",
|
|
595
|
+
rangeSelected: "Range selected"
|
|
588
596
|
};
|
|
589
597
|
var DEFAULT_TIMEPICKER_LABELS = {
|
|
590
598
|
timeInput: "Time",
|
|
@@ -615,6 +623,7 @@ var DEFAULT_DATETIMEPICKER_LABELS = {
|
|
|
615
623
|
generateHours,
|
|
616
624
|
generateMinutes,
|
|
617
625
|
getCalendarDays,
|
|
626
|
+
getISOWeekNumber,
|
|
618
627
|
getMonthName,
|
|
619
628
|
getTime,
|
|
620
629
|
getTimeInTimezone,
|
package/dist/index.d.cts
CHANGED
|
@@ -15,7 +15,8 @@ type ISODateString = string;
|
|
|
15
15
|
* { before: '2026-01-01T00:00:00.000Z' }, // disable before Jan 1
|
|
16
16
|
* { after: '2026-12-31T00:00:00.000Z' }, // disable after Dec 31
|
|
17
17
|
* { date: '2026-06-15T00:00:00.000Z' }, // disable a specific date
|
|
18
|
-
* { dayOfWeek: [0, 6] },
|
|
18
|
+
* { dayOfWeek: [0, 6] }, // disable weekends
|
|
19
|
+
* { filter: (iso) => holidays.has(iso) }, // programmatic filter
|
|
19
20
|
* ];
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
@@ -27,6 +28,8 @@ type DisabledRule = {
|
|
|
27
28
|
after: ISODateString;
|
|
28
29
|
} | {
|
|
29
30
|
dayOfWeek: number[];
|
|
31
|
+
} | {
|
|
32
|
+
filter: (iso: ISODateString) => boolean;
|
|
30
33
|
};
|
|
31
34
|
/** Date range (RangePicker) */
|
|
32
35
|
interface DateRange {
|
|
@@ -108,6 +111,12 @@ interface CalendarOptions {
|
|
|
108
111
|
* midnight form while the grid iterates in UTC.
|
|
109
112
|
*/
|
|
110
113
|
timezone?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Always emit exactly six weeks (42 days). The default (`false`) emits 4–6 weeks
|
|
116
|
+
* depending on the month, breaking after the last week containing a current-month day.
|
|
117
|
+
* Setting `true` is useful for layouts that need a fixed-height grid.
|
|
118
|
+
*/
|
|
119
|
+
fixedWeeks?: boolean;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
/**
|
|
@@ -146,6 +155,16 @@ declare const DateFnsAdapter: DateAdapter;
|
|
|
146
155
|
* ```
|
|
147
156
|
*/
|
|
148
157
|
declare function getCalendarDays(monthISO: string, adapter: DateAdapter, options?: CalendarOptions): CalendarGrid;
|
|
158
|
+
/**
|
|
159
|
+
* Returns the ISO 8601 week number (1-53) of the given date. ISO weeks start on Monday;
|
|
160
|
+
* week 1 is the week containing the year's first Thursday. The computation works in UTC
|
|
161
|
+
* to match the calendar grid's iteration semantics.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* getISOWeekNumber('2026-01-01T00:00:00.000Z'); // 1 (Jan 1 2026 is a Thursday → ISO week 1)
|
|
165
|
+
* getISOWeekNumber('2026-12-31T00:00:00.000Z'); // 53
|
|
166
|
+
*/
|
|
167
|
+
declare function getISOWeekNumber(iso: ISODateString): number;
|
|
149
168
|
/**
|
|
150
169
|
* Checks whether the given date matches any disable rule.
|
|
151
170
|
*/
|
|
@@ -215,9 +234,15 @@ declare function to24Hour(hours12: number, period: 'AM' | 'PM'): number;
|
|
|
215
234
|
declare function generateHours(format?: '12h' | '24h'): number[];
|
|
216
235
|
/**
|
|
217
236
|
* Builds a minutes list at the given step.
|
|
218
|
-
*
|
|
219
|
-
* step=
|
|
220
|
-
* step=
|
|
237
|
+
*
|
|
238
|
+
* - `step=1` → `[0, 1, 2, ..., 59]`
|
|
239
|
+
* - `step=15` → `[0, 15, 30, 45]`
|
|
240
|
+
* - `step=5` → `[0, 5, 10, ..., 55]`
|
|
241
|
+
* - `step=45` → `[0, 45]`
|
|
242
|
+
* - `step=60` → `[0]` (on-the-hour only)
|
|
243
|
+
*
|
|
244
|
+
* Steps above 60 are rejected because they always collapse to `[0]` — useful UX
|
|
245
|
+
* is impossible past that point.
|
|
221
246
|
*/
|
|
222
247
|
declare function generateMinutes(step?: number): number[];
|
|
223
248
|
/**
|
|
@@ -380,6 +405,16 @@ interface RangePickerLabels extends DatePickerLabels {
|
|
|
380
405
|
startInput: string;
|
|
381
406
|
endInput: string;
|
|
382
407
|
presetsGroup: string;
|
|
408
|
+
/**
|
|
409
|
+
* Screen-reader prompt appended after the start date is picked, telling the user
|
|
410
|
+
* the next click commits the end of the range.
|
|
411
|
+
*/
|
|
412
|
+
selectingEnd: string;
|
|
413
|
+
/**
|
|
414
|
+
* Screen-reader prefix announced when both endpoints are committed, e.g.
|
|
415
|
+
* `"Range selected: Jan 5, 2026 – Jan 12, 2026"`.
|
|
416
|
+
*/
|
|
417
|
+
rangeSelected: string;
|
|
383
418
|
}
|
|
384
419
|
interface TimePickerLabels {
|
|
385
420
|
timeInput: string;
|
|
@@ -397,4 +432,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
|
|
|
397
432
|
declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
|
|
398
433
|
declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
|
|
399
434
|
|
|
400
|
-
export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
|
435
|
+
export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getISOWeekNumber, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ type ISODateString = string;
|
|
|
15
15
|
* { before: '2026-01-01T00:00:00.000Z' }, // disable before Jan 1
|
|
16
16
|
* { after: '2026-12-31T00:00:00.000Z' }, // disable after Dec 31
|
|
17
17
|
* { date: '2026-06-15T00:00:00.000Z' }, // disable a specific date
|
|
18
|
-
* { dayOfWeek: [0, 6] },
|
|
18
|
+
* { dayOfWeek: [0, 6] }, // disable weekends
|
|
19
|
+
* { filter: (iso) => holidays.has(iso) }, // programmatic filter
|
|
19
20
|
* ];
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
@@ -27,6 +28,8 @@ type DisabledRule = {
|
|
|
27
28
|
after: ISODateString;
|
|
28
29
|
} | {
|
|
29
30
|
dayOfWeek: number[];
|
|
31
|
+
} | {
|
|
32
|
+
filter: (iso: ISODateString) => boolean;
|
|
30
33
|
};
|
|
31
34
|
/** Date range (RangePicker) */
|
|
32
35
|
interface DateRange {
|
|
@@ -108,6 +111,12 @@ interface CalendarOptions {
|
|
|
108
111
|
* midnight form while the grid iterates in UTC.
|
|
109
112
|
*/
|
|
110
113
|
timezone?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Always emit exactly six weeks (42 days). The default (`false`) emits 4–6 weeks
|
|
116
|
+
* depending on the month, breaking after the last week containing a current-month day.
|
|
117
|
+
* Setting `true` is useful for layouts that need a fixed-height grid.
|
|
118
|
+
*/
|
|
119
|
+
fixedWeeks?: boolean;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
/**
|
|
@@ -146,6 +155,16 @@ declare const DateFnsAdapter: DateAdapter;
|
|
|
146
155
|
* ```
|
|
147
156
|
*/
|
|
148
157
|
declare function getCalendarDays(monthISO: string, adapter: DateAdapter, options?: CalendarOptions): CalendarGrid;
|
|
158
|
+
/**
|
|
159
|
+
* Returns the ISO 8601 week number (1-53) of the given date. ISO weeks start on Monday;
|
|
160
|
+
* week 1 is the week containing the year's first Thursday. The computation works in UTC
|
|
161
|
+
* to match the calendar grid's iteration semantics.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* getISOWeekNumber('2026-01-01T00:00:00.000Z'); // 1 (Jan 1 2026 is a Thursday → ISO week 1)
|
|
165
|
+
* getISOWeekNumber('2026-12-31T00:00:00.000Z'); // 53
|
|
166
|
+
*/
|
|
167
|
+
declare function getISOWeekNumber(iso: ISODateString): number;
|
|
149
168
|
/**
|
|
150
169
|
* Checks whether the given date matches any disable rule.
|
|
151
170
|
*/
|
|
@@ -215,9 +234,15 @@ declare function to24Hour(hours12: number, period: 'AM' | 'PM'): number;
|
|
|
215
234
|
declare function generateHours(format?: '12h' | '24h'): number[];
|
|
216
235
|
/**
|
|
217
236
|
* Builds a minutes list at the given step.
|
|
218
|
-
*
|
|
219
|
-
* step=
|
|
220
|
-
* step=
|
|
237
|
+
*
|
|
238
|
+
* - `step=1` → `[0, 1, 2, ..., 59]`
|
|
239
|
+
* - `step=15` → `[0, 15, 30, 45]`
|
|
240
|
+
* - `step=5` → `[0, 5, 10, ..., 55]`
|
|
241
|
+
* - `step=45` → `[0, 45]`
|
|
242
|
+
* - `step=60` → `[0]` (on-the-hour only)
|
|
243
|
+
*
|
|
244
|
+
* Steps above 60 are rejected because they always collapse to `[0]` — useful UX
|
|
245
|
+
* is impossible past that point.
|
|
221
246
|
*/
|
|
222
247
|
declare function generateMinutes(step?: number): number[];
|
|
223
248
|
/**
|
|
@@ -380,6 +405,16 @@ interface RangePickerLabels extends DatePickerLabels {
|
|
|
380
405
|
startInput: string;
|
|
381
406
|
endInput: string;
|
|
382
407
|
presetsGroup: string;
|
|
408
|
+
/**
|
|
409
|
+
* Screen-reader prompt appended after the start date is picked, telling the user
|
|
410
|
+
* the next click commits the end of the range.
|
|
411
|
+
*/
|
|
412
|
+
selectingEnd: string;
|
|
413
|
+
/**
|
|
414
|
+
* Screen-reader prefix announced when both endpoints are committed, e.g.
|
|
415
|
+
* `"Range selected: Jan 5, 2026 – Jan 12, 2026"`.
|
|
416
|
+
*/
|
|
417
|
+
rangeSelected: string;
|
|
383
418
|
}
|
|
384
419
|
interface TimePickerLabels {
|
|
385
420
|
timeInput: string;
|
|
@@ -397,4 +432,4 @@ declare const DEFAULT_RANGEPICKER_LABELS: RangePickerLabels;
|
|
|
397
432
|
declare const DEFAULT_TIMEPICKER_LABELS: TimePickerLabels;
|
|
398
433
|
declare const DEFAULT_DATETIMEPICKER_LABELS: DateTimePickerLabels;
|
|
399
434
|
|
|
400
|
-
export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
|
435
|
+
export { type CalendarDay, type CalendarGrid, type CalendarOptions, type CalendarWeek, DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, type DateAdapter, DateFnsAdapter, type DatePickerLabels, type DateRange, type DateTimePickerLabels, type DisabledRule, type ISODateString, type RangePickerLabels, type TimePickerLabels, type TimeValue, type WeekStartsOn, type WeekdayInfo, civilMidnightFromUtcDay, formatFullDate, formatInTimezone, formatMonthYear, formatTimeFromISO, formatTimeString, generateHours, generateMinutes, getCalendarDays, getISOWeekNumber, getMonthName, getTime, getTimeInTimezone, getTimezoneOffsetMinutes, getWeekdayNames, isDateDisabled, isSameDayInTimezone, isSameTime, maxDate, minDate, normalizeISO, parseInputValue, parseTimeString, setTime, setTimeInTimezone, startOfDayInTimezone, to12Hour, to24Hour, todayInTimezone };
|
package/dist/index.js
CHANGED
|
@@ -32,9 +32,7 @@ function getCachedPartsFormatter(timeZone) {
|
|
|
32
32
|
}
|
|
33
33
|
function partsInTimezone(utc, timeZone) {
|
|
34
34
|
const dtf = getCachedPartsFormatter(timeZone);
|
|
35
|
-
const parts = Object.fromEntries(
|
|
36
|
-
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
37
|
-
);
|
|
35
|
+
const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
|
|
38
36
|
return {
|
|
39
37
|
year: Number(parts.year),
|
|
40
38
|
month: Number(parts.month),
|
|
@@ -85,14 +83,9 @@ function todayInTimezone(timeZone) {
|
|
|
85
83
|
}
|
|
86
84
|
function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
|
|
87
85
|
const utc = parseISO(gridUtcIso);
|
|
88
|
-
const probe = new Date(
|
|
89
|
-
utc.getUTCFullYear(),
|
|
90
|
-
|
|
91
|
-
utc.getUTCDate(),
|
|
92
|
-
12,
|
|
93
|
-
0,
|
|
94
|
-
0
|
|
95
|
-
)).toISOString();
|
|
86
|
+
const probe = new Date(
|
|
87
|
+
Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
|
|
88
|
+
).toISOString();
|
|
96
89
|
return startOfDayInTimezone(probe, timeZone);
|
|
97
90
|
}
|
|
98
91
|
function getTimeInTimezone(iso, timeZone) {
|
|
@@ -104,7 +97,14 @@ function setTimeInTimezone(iso, partial, timeZone) {
|
|
|
104
97
|
const targetHours = partial.hours ?? p.hour;
|
|
105
98
|
const targetMinutes = partial.minutes ?? p.minute;
|
|
106
99
|
const targetSeconds = partial.seconds ?? p.second;
|
|
107
|
-
const civilEpoch = Date.UTC(
|
|
100
|
+
const civilEpoch = Date.UTC(
|
|
101
|
+
p.year,
|
|
102
|
+
p.month - 1,
|
|
103
|
+
p.day,
|
|
104
|
+
targetHours,
|
|
105
|
+
targetMinutes,
|
|
106
|
+
targetSeconds
|
|
107
|
+
);
|
|
108
108
|
const probe1 = new Date(civilEpoch).toISOString();
|
|
109
109
|
const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
|
|
110
110
|
const realEpoch1 = civilEpoch - offset1 * 6e4;
|
|
@@ -143,15 +143,9 @@ function utcStartOfWeek(d, weekStartsOn) {
|
|
|
143
143
|
}
|
|
144
144
|
function utcEndOfWeek(d, weekStartsOn) {
|
|
145
145
|
const start = utcStartOfWeek(d, weekStartsOn);
|
|
146
|
-
return new Date(
|
|
147
|
-
start.getUTCFullYear(),
|
|
148
|
-
|
|
149
|
-
start.getUTCDate() + 6,
|
|
150
|
-
23,
|
|
151
|
-
59,
|
|
152
|
-
59,
|
|
153
|
-
999
|
|
154
|
-
));
|
|
146
|
+
return new Date(
|
|
147
|
+
Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
|
|
148
|
+
);
|
|
155
149
|
}
|
|
156
150
|
var DateFnsAdapter = {
|
|
157
151
|
parse(value) {
|
|
@@ -265,7 +259,8 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
265
259
|
disabled = [],
|
|
266
260
|
range,
|
|
267
261
|
rangeHover,
|
|
268
|
-
timezone
|
|
262
|
+
timezone,
|
|
263
|
+
fixedWeeks = false
|
|
269
264
|
} = options;
|
|
270
265
|
const todayISO = today ?? adapter.today(timezone);
|
|
271
266
|
const monthStart = adapter.startOfMonth(monthISO);
|
|
@@ -295,12 +290,20 @@ function getCalendarDays(monthISO, adapter, options = {}) {
|
|
|
295
290
|
current = adapter.addDays(current, 1);
|
|
296
291
|
}
|
|
297
292
|
weeks.push(days);
|
|
298
|
-
if (!adapter.isSameMonth(current, monthISO) && week >= 3) {
|
|
293
|
+
if (!fixedWeeks && !adapter.isSameMonth(current, monthISO) && week >= 3) {
|
|
299
294
|
break;
|
|
300
295
|
}
|
|
301
296
|
}
|
|
302
297
|
return weeks;
|
|
303
298
|
}
|
|
299
|
+
function getISOWeekNumber(iso) {
|
|
300
|
+
const d = new Date(iso);
|
|
301
|
+
const utc = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
|
302
|
+
const isoDay = utc.getUTCDay() || 7;
|
|
303
|
+
utc.setUTCDate(utc.getUTCDate() + 4 - isoDay);
|
|
304
|
+
const yearStart = new Date(Date.UTC(utc.getUTCFullYear(), 0, 1));
|
|
305
|
+
return Math.ceil(((utc.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
306
|
+
}
|
|
304
307
|
function normalizeRangeForDisplay(range, hover, adapter, _timezone) {
|
|
305
308
|
if (!range) return { start: null, end: null };
|
|
306
309
|
const { start, end } = range;
|
|
@@ -344,6 +347,8 @@ function isDateDisabled(iso, rules, adapter) {
|
|
|
344
347
|
if (adapter.isAfter(iso, rule.after)) return true;
|
|
345
348
|
} else if ("dayOfWeek" in rule) {
|
|
346
349
|
if (rule.dayOfWeek.includes(adapter.getDay(iso))) return true;
|
|
350
|
+
} else if ("filter" in rule) {
|
|
351
|
+
if (rule.filter(iso)) return true;
|
|
347
352
|
}
|
|
348
353
|
}
|
|
349
354
|
return false;
|
|
@@ -439,8 +444,8 @@ function generateHours(format = "24h") {
|
|
|
439
444
|
return Array.from({ length: 24 }, (_, i) => i);
|
|
440
445
|
}
|
|
441
446
|
function generateMinutes(step = 1) {
|
|
442
|
-
if (step < 1 || step >
|
|
443
|
-
throw new Error(`[generateMinutes] step must be between 1 and
|
|
447
|
+
if (step < 1 || step > 60) {
|
|
448
|
+
throw new Error(`[generateMinutes] step must be between 1 and 60, got ${step}`);
|
|
444
449
|
}
|
|
445
450
|
const result = [];
|
|
446
451
|
for (let i = 0; i < 60; i += step) {
|
|
@@ -534,7 +539,9 @@ var DEFAULT_RANGEPICKER_LABELS = {
|
|
|
534
539
|
popoverLabel: "Choose date range",
|
|
535
540
|
startInput: "Start date",
|
|
536
541
|
endInput: "End date",
|
|
537
|
-
presetsGroup: "Date range presets"
|
|
542
|
+
presetsGroup: "Date range presets",
|
|
543
|
+
selectingEnd: "Now select end date",
|
|
544
|
+
rangeSelected: "Range selected"
|
|
538
545
|
};
|
|
539
546
|
var DEFAULT_TIMEPICKER_LABELS = {
|
|
540
547
|
timeInput: "Time",
|
|
@@ -564,6 +571,7 @@ export {
|
|
|
564
571
|
generateHours,
|
|
565
572
|
generateMinutes,
|
|
566
573
|
getCalendarDays,
|
|
574
|
+
getISOWeekNumber,
|
|
567
575
|
getMonthName,
|
|
568
576
|
getTime,
|
|
569
577
|
getTimeInTimezone,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kalyx/core",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.11",
|
|
4
4
|
"description": "Kalyx core — platform-agnostic date logic, IANA timezone helpers, and the DateAdapter contract used by @kalyx/react",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "jiji-hoon96",
|
|
@@ -42,12 +42,21 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
|
-
"dist"
|
|
45
|
+
"dist",
|
|
46
|
+
"CHANGELOG.md",
|
|
47
|
+
"LICENSE"
|
|
46
48
|
],
|
|
47
49
|
"dependencies": {
|
|
48
50
|
"date-fns": "^4.0.0",
|
|
49
51
|
"date-fns-tz": "^3.0.0"
|
|
50
52
|
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20.0.0"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"provenance": true
|
|
59
|
+
},
|
|
51
60
|
"scripts": {
|
|
52
61
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
53
62
|
"typecheck": "tsc --noEmit",
|