@kalyx/core 1.0.0-rc.0 → 1.0.0-rc.2
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 +125 -0
- package/README.md +30 -0
- package/dist/index.cjs +15 -21
- package/dist/index.js +15 -21
- package/package.json +11 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @kalyx/core
|
|
2
|
+
|
|
3
|
+
## 1.0.0-rc.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- aadb512: Security: pin transitive `postcss` to `>=8.5.10` via `pnpm.overrides`.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
- 21f3c1f: Resolve v1.0-rc release-blocking defects (P0):
|
|
12
|
+
- **`"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.
|
|
13
|
+
- **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.
|
|
14
|
+
- **`@kalyx/core` version sync** — bumped from `1.0.0-rc.0` to `1.0.0-rc.1` to match `@kalyx/react`.
|
|
15
|
+
- **`@kalyx/core` package contents** — `LICENSE` and `CHANGELOG.md` are now included in the npm tarball (`files` field).
|
|
16
|
+
- **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>`.
|
|
17
|
+
- **`aria-haspopup="dialog"` on Trigger** — completes the WAI-ARIA combobox/dialog pattern.
|
|
18
|
+
- **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.
|
|
19
|
+
|
|
20
|
+
- 3228533: P1 v1.0-rc API/a11y/docs improvements:
|
|
21
|
+
- **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.
|
|
22
|
+
- **`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.
|
|
23
|
+
- **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.
|
|
24
|
+
- **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`.
|
|
25
|
+
- **Package metadata** — `peerDependenciesMeta`, `engines.node`, and `publishConfig.provenance` added to both `@kalyx/react` and `@kalyx/core`.
|
|
26
|
+
- **`@kalyx/react` description** — corrected from "under 10 KB gzipped" (false claim) to "≤12 KB gzipped".
|
|
27
|
+
|
|
28
|
+
- b6129ed: P2 polish for v1.0-rc:
|
|
29
|
+
- **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.
|
|
30
|
+
- **`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.
|
|
31
|
+
- **JSDoc on `DatePicker.Input` and `DatePicker.Trigger`** — public API surface for the most-used components has explanatory docstrings.
|
|
32
|
+
- **`addYears` leap-day regression tests** — locked the date-fns clamp behavior (2024-02-29 + 1y → 2025-02-28, not March 1).
|
|
33
|
+
- **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.
|
|
34
|
+
- **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.
|
|
35
|
+
|
|
36
|
+
## 1.0.0-rc.0
|
|
37
|
+
|
|
38
|
+
### Major Changes
|
|
39
|
+
|
|
40
|
+
- ca7180e: chore: v1.0 milestone — API freeze.
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
### What v1.0 commits to
|
|
45
|
+
- **Public API surface** — exports from `@kalyx/react` and `@kalyx/core` listed in their `index.ts` files. Any breaking change requires a major bump.
|
|
46
|
+
- **Compositional structure** — Root + subcomponent names (`DatePicker.Input`, `DatePicker.Calendar`, …) are stable. Removal or renaming requires a major bump.
|
|
47
|
+
- **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.
|
|
48
|
+
- **Accessibility contracts** — role/aria-\* attributes emitted by each component are stable.
|
|
49
|
+
|
|
50
|
+
### What v1.0 does NOT freeze
|
|
51
|
+
- Internal implementation details (non-exported functions, component file layout).
|
|
52
|
+
- CSS class name strings on elements — no classes are applied by default; only when a consumer passes them via `classNames` props.
|
|
53
|
+
- Error message text.
|
|
54
|
+
- Peer dependency version ranges (may expand to cover new React majors).
|
|
55
|
+
|
|
56
|
+
### Breaking changes vs 0.4.x
|
|
57
|
+
|
|
58
|
+
None. v1.0 is API-compatible with 0.4.x — existing code continues to work. The major bump communicates stability commitment, not breakage.
|
|
59
|
+
|
|
60
|
+
## 0.4.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- 104bbf2: feat: full `displayTimezone` support across all pickers (v0.4)
|
|
65
|
+
|
|
66
|
+
All four pickers (`DatePicker`, `RangePicker`, `TimePicker`, `DateTimePicker`) and their corresponding hooks (`useDatePicker`, `useRangePicker`, `useTimePicker`) now accept a `displayTimezone` prop/option.
|
|
67
|
+
|
|
68
|
+
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`.
|
|
69
|
+
|
|
70
|
+
`DateFnsAdapter` now honors the `timezone` argument on `format`, `isSameDay`, `startOfDay`, and `today` (previously declared-but-ignored). Core also exposes new helpers:
|
|
71
|
+
- `civilMidnightFromUtcDay(iso, tz)`
|
|
72
|
+
- `getTimeInTimezone(iso, tz)`
|
|
73
|
+
- `setTimeInTimezone(iso, partial, tz)`
|
|
74
|
+
|
|
75
|
+
No breaking changes — omitting `displayTimezone` keeps the existing UTC semantics.
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- 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).
|
|
80
|
+
|
|
81
|
+
## 0.3.0
|
|
82
|
+
|
|
83
|
+
### Minor Changes
|
|
84
|
+
|
|
85
|
+
- 669391b: Improve code quality, performance, and stability
|
|
86
|
+
- Enforce UTC timezone suffix in ISO regex
|
|
87
|
+
- Extract shared usePopover and useListboxNavigation hooks
|
|
88
|
+
- Add Intl.DateTimeFormat caching for locale/timezone utilities
|
|
89
|
+
- Memoize disabledRules to prevent unnecessary context re-creation
|
|
90
|
+
- Add try-catch around adapter.format() for error resilience
|
|
91
|
+
- Cancel requestAnimationFrame on unmount in listbox navigation
|
|
92
|
+
- Remove unused parseInputValue format parameter
|
|
93
|
+
- Boost test coverage: 87% → 92%
|
|
94
|
+
- Fix bundle size measurement to report both ESM and CJS
|
|
95
|
+
|
|
96
|
+
## 0.2.2
|
|
97
|
+
|
|
98
|
+
### Patch Changes
|
|
99
|
+
|
|
100
|
+
- ebf4fd7: Add repository/homepage/bugs/keywords metadata to @kalyx/core for npm provenance validation
|
|
101
|
+
|
|
102
|
+
## 0.2.1
|
|
103
|
+
|
|
104
|
+
### Patch Changes
|
|
105
|
+
|
|
106
|
+
- fe0e63e: Add full documentation site (Docusaurus, EN/KO), rewrite READMEs for npm, fix CI pnpm version to 10
|
|
107
|
+
|
|
108
|
+
## 0.2.0
|
|
109
|
+
|
|
110
|
+
### Minor Changes
|
|
111
|
+
|
|
112
|
+
- e9bb9e8: Initial release of Kalyx — headless, SSR-safe React DatePicker library.
|
|
113
|
+
|
|
114
|
+
Features:
|
|
115
|
+
- DatePicker: single date selection with Calendar, Input, Trigger, Popover
|
|
116
|
+
- RangePicker: date range selection with auto-swap and hover preview
|
|
117
|
+
- TimePicker: 12h/24h mode, minute step, HourList/MinuteList/AmPmToggle
|
|
118
|
+
- DateTimePicker: combined date+time via context bridging (reuses existing components)
|
|
119
|
+
- useDatePicker, useRangePicker, useTimePicker hooks for custom UIs
|
|
120
|
+
- WAI-ARIA compliant: grid, dialog, combobox, listbox, radiogroup patterns
|
|
121
|
+
- SSR safe: verified with Next.js 15 App Router
|
|
122
|
+
- Zero CSS: style with classNames prop and data-\* attributes
|
|
123
|
+
- ISO 8601 UTC strings only (no native Date objects)
|
|
124
|
+
- Bundle: 7.71KB gzip (target ≤12KB)
|
|
125
|
+
- 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
|
@@ -82,9 +82,7 @@ function getCachedPartsFormatter(timeZone) {
|
|
|
82
82
|
}
|
|
83
83
|
function partsInTimezone(utc, timeZone) {
|
|
84
84
|
const dtf = getCachedPartsFormatter(timeZone);
|
|
85
|
-
const parts = Object.fromEntries(
|
|
86
|
-
dtf.formatToParts(utc).map((p) => [p.type, p.value])
|
|
87
|
-
);
|
|
85
|
+
const parts = Object.fromEntries(dtf.formatToParts(utc).map((p) => [p.type, p.value]));
|
|
88
86
|
return {
|
|
89
87
|
year: Number(parts.year),
|
|
90
88
|
month: Number(parts.month),
|
|
@@ -135,14 +133,9 @@ function todayInTimezone(timeZone) {
|
|
|
135
133
|
}
|
|
136
134
|
function civilMidnightFromUtcDay(gridUtcIso, timeZone) {
|
|
137
135
|
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();
|
|
136
|
+
const probe = new Date(
|
|
137
|
+
Date.UTC(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate(), 12, 0, 0)
|
|
138
|
+
).toISOString();
|
|
146
139
|
return startOfDayInTimezone(probe, timeZone);
|
|
147
140
|
}
|
|
148
141
|
function getTimeInTimezone(iso, timeZone) {
|
|
@@ -154,7 +147,14 @@ function setTimeInTimezone(iso, partial, timeZone) {
|
|
|
154
147
|
const targetHours = partial.hours ?? p.hour;
|
|
155
148
|
const targetMinutes = partial.minutes ?? p.minute;
|
|
156
149
|
const targetSeconds = partial.seconds ?? p.second;
|
|
157
|
-
const civilEpoch = Date.UTC(
|
|
150
|
+
const civilEpoch = Date.UTC(
|
|
151
|
+
p.year,
|
|
152
|
+
p.month - 1,
|
|
153
|
+
p.day,
|
|
154
|
+
targetHours,
|
|
155
|
+
targetMinutes,
|
|
156
|
+
targetSeconds
|
|
157
|
+
);
|
|
158
158
|
const probe1 = new Date(civilEpoch).toISOString();
|
|
159
159
|
const offset1 = getTimezoneOffsetMinutes(probe1, timeZone);
|
|
160
160
|
const realEpoch1 = civilEpoch - offset1 * 6e4;
|
|
@@ -193,15 +193,9 @@ function utcStartOfWeek(d, weekStartsOn) {
|
|
|
193
193
|
}
|
|
194
194
|
function utcEndOfWeek(d, weekStartsOn) {
|
|
195
195
|
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
|
-
));
|
|
196
|
+
return new Date(
|
|
197
|
+
Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + 6, 23, 59, 59, 999)
|
|
198
|
+
);
|
|
205
199
|
}
|
|
206
200
|
var DateFnsAdapter = {
|
|
207
201
|
parse(value) {
|
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) {
|
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.2",
|
|
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": ">=18.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",
|