@iyulab/components 1.26.0 → 1.27.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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/_virtual/_glob-assets_raw/calendar.svg.02c3abd8.js +4 -0
- package/dist/components/date-picker/UDatePicker.d.ts +70 -0
- package/dist/components/date-picker/UDatePicker.js +306 -0
- package/dist/components/date-picker/UDatePicker.styles.d.ts +1 -0
- package/dist/components/date-picker/UDatePicker.styles.js +147 -0
- package/dist/components/input/UInput.js +1 -1
- package/dist/components/menu-item/UMenuItem.js +1 -1
- package/dist/components/select/USelect.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -3
- package/dist/react/UDatePicker.d.ts +13 -0
- package/dist/react/UDatePicker.js +12 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/utilities/format.d.ts +25 -0
- package/dist/utilities/format.js +61 -0
- package/dist/utilities/icons.js +2 -0
- package/package.json +1 -1
- package/skills/iyulab-components/references/components/date-picker.md +71 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.27.0] - 2026-08-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`formatNumber`/`formatCurrency`/`formatDate` utilities** (`utilities/format.ts`). Thin
|
|
8
|
+
wrappers around `Intl.NumberFormat`/`Intl.DateTimeFormat` that default to the active
|
|
9
|
+
`Locale.get()` locale. `formatCurrency` has no default currency — callers must pass one
|
|
10
|
+
explicitly.
|
|
11
|
+
- **`u-date-picker`** — a single-date picker form control (`UFormControlElement`), matching
|
|
12
|
+
the value contract of a native `input[type=date]` (ISO `YYYY-MM-DD`). Renders a calendar
|
|
13
|
+
grid in a popover with full keyboard navigation (arrow keys, Home/End, Enter/Space, Escape)
|
|
14
|
+
following the WAI-ARIA Date Picker Dialog pattern. `min`/`max` mark out-of-range days
|
|
15
|
+
`aria-disabled` (focusable but not selectable, so keyboard users can still perceive and
|
|
16
|
+
navigate past them) and drive `rangeUnderflow`/`rangeOverflow` validity. Date ranges and
|
|
17
|
+
`datetime-local` are out of scope for this release.
|
|
18
|
+
|
|
3
19
|
## [1.26.0] - 2026-08-07
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ npx skills add ./node_modules/@iyulab/components
|
|
|
70
70
|
|
|
71
71
|
**Buttons & Actions** — `u-button`, `u-button-group`, `u-icon-button`, `u-copy-button`, `u-chip`
|
|
72
72
|
|
|
73
|
-
**Form Controls** — `u-input`, `u-textarea`, `u-select`, `u-checkbox`, `u-radio`, `u-switch`, `u-slider`, `u-rating`, `u-field`, `u-form`, `u-option`
|
|
73
|
+
**Form Controls** — `u-input`, `u-textarea`, `u-select`, `u-date-picker`, `u-checkbox`, `u-radio`, `u-switch`, `u-slider`, `u-rating`, `u-field`, `u-form`, `u-option`
|
|
74
74
|
|
|
75
75
|
**Overlay & Floating** — `u-dialog`, `u-drawer`, `u-popover`, `u-tooltip`
|
|
76
76
|
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region \0glob-assets_raw/calendar.svg.02c3abd8
|
|
2
|
+
var calendar_svg_default = "<svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n>\n <path stroke=\"none\" d=\"M0 0h24v24H0z\" fill=\"none\"/>\n <path d=\"M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12z\" />\n <path d=\"M16 3v4\" />\n <path d=\"M8 3v4\" />\n <path d=\"M4 11h16\" />\n <path d=\"M11 15h1\" />\n <path d=\"M12 15v3\" />\n</svg>\n";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { calendar_svg_default as default };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PropertyValues } from 'lit';
|
|
2
|
+
import { UFormControlElement } from '../UFormControlElement.js';
|
|
3
|
+
import { UPopover } from '../popover/UPopover.js';
|
|
4
|
+
/**
|
|
5
|
+
* A single-date-selection form control. The value follows the same convention as the
|
|
6
|
+
* native `input[type=date]`: an ISO `YYYY-MM-DD` string.
|
|
7
|
+
*
|
|
8
|
+
* The calendar week always starts on Sunday, regardless of locale — harmless for the
|
|
9
|
+
* locales this library currently ships (en/ko), but not correct for locales where Monday
|
|
10
|
+
* (most of Europe) or Saturday is conventional. Fix when a consumer needs it: derive the
|
|
11
|
+
* first day of week from `Intl.Locale(locale).weekInfo?.firstDay`, falling back to Sunday
|
|
12
|
+
* where unsupported.
|
|
13
|
+
*
|
|
14
|
+
* @csspart field - the u-field element
|
|
15
|
+
* @csspart container - the element wrapping the trigger area
|
|
16
|
+
* @csspart popover - the popover element showing the calendar
|
|
17
|
+
* @csspart calendar - the calendar container
|
|
18
|
+
* @csspart calendar-header - the month navigation header
|
|
19
|
+
* @csspart calendar-title - the "Month Year" title
|
|
20
|
+
* @csspart calendar-weekdays - the weekday header row
|
|
21
|
+
* @csspart calendar-grid - the date grid
|
|
22
|
+
* @csspart day - a date cell button
|
|
23
|
+
*
|
|
24
|
+
* @cssprop --date-picker-popover-width - width of the calendar popover (default: 296px, independent of trigger width — a fixed-width calendar reads more naturally)
|
|
25
|
+
*
|
|
26
|
+
* @event change - fires when the user clicks a date cell, confirms via keyboard, or clicks the clear button.
|
|
27
|
+
* Programmatic value assignment does not fire it (same contract as native form controls).
|
|
28
|
+
*/
|
|
29
|
+
export declare class UDatePicker extends UFormControlElement<string> {
|
|
30
|
+
static styles: import('lit').CSSResultGroup[];
|
|
31
|
+
/** Minimum value (ISO YYYY-MM-DD) — dates before this cannot be selected. */
|
|
32
|
+
min?: string;
|
|
33
|
+
/** Maximum value (ISO YYYY-MM-DD) — dates after this cannot be selected. */
|
|
34
|
+
max?: string;
|
|
35
|
+
/** Whether to show the clear button */
|
|
36
|
+
clearable: boolean;
|
|
37
|
+
/** Placeholder text (shown on the trigger when there is no value) */
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
containerEl?: HTMLElement;
|
|
40
|
+
popoverEl?: UPopover;
|
|
41
|
+
/** Unique id wiring the combobox's `aria-controls` to the calendar dialog — mirrors USelect's `listboxId`. */
|
|
42
|
+
private readonly calendarId;
|
|
43
|
+
private open;
|
|
44
|
+
private viewDate;
|
|
45
|
+
private focusedDate;
|
|
46
|
+
private grabFocusOnUpdate;
|
|
47
|
+
protected updated(changed: PropertyValues): void;
|
|
48
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
49
|
+
private renderCalendar;
|
|
50
|
+
private renderDay;
|
|
51
|
+
private isOutOfRange;
|
|
52
|
+
private focusDayButton;
|
|
53
|
+
private selectDay;
|
|
54
|
+
private handlePrevMonth;
|
|
55
|
+
private handleNextMonth;
|
|
56
|
+
private navigateMonth;
|
|
57
|
+
private handleDayKeydown;
|
|
58
|
+
private moveFocus;
|
|
59
|
+
private handlePopoverShow;
|
|
60
|
+
private handlePopoverHide;
|
|
61
|
+
private handleClearClick;
|
|
62
|
+
private emitChange;
|
|
63
|
+
protected setValidity(): void;
|
|
64
|
+
reset(): void;
|
|
65
|
+
}
|
|
66
|
+
declare global {
|
|
67
|
+
interface HTMLElementTagNameMap {
|
|
68
|
+
'u-date-picker': UDatePicker;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
|
|
2
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
|
|
3
|
+
import { UFormControlElement } from "../UFormControlElement.js";
|
|
4
|
+
import "../icon/UIcon.js";
|
|
5
|
+
import "../icon-button/UIconButton.js";
|
|
6
|
+
import { Locale } from "../../utilities/Locale.js";
|
|
7
|
+
import "../field/UField.js";
|
|
8
|
+
import { UPopover } from "../popover/UPopover.js";
|
|
9
|
+
import { formatDate } from "../../utilities/format.js";
|
|
10
|
+
import { styles } from "./UDatePicker.styles.js";
|
|
11
|
+
import { html } from "lit";
|
|
12
|
+
import { customElement, property, query, state } from "lit/decorators.js";
|
|
13
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
14
|
+
//#region src/components/date-picker/UDatePicker.ts
|
|
15
|
+
function parseISODate(iso) {
|
|
16
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
17
|
+
return new Date(y, m - 1, d);
|
|
18
|
+
}
|
|
19
|
+
function toISODate(date) {
|
|
20
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
21
|
+
}
|
|
22
|
+
function isSameDay(a, b) {
|
|
23
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
24
|
+
}
|
|
25
|
+
function startOfMonth(date) {
|
|
26
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
27
|
+
}
|
|
28
|
+
function addMonths(date, delta) {
|
|
29
|
+
return new Date(date.getFullYear(), date.getMonth() + delta, 1);
|
|
30
|
+
}
|
|
31
|
+
function addDays(date, delta) {
|
|
32
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta);
|
|
33
|
+
}
|
|
34
|
+
function daysInMonth(date) {
|
|
35
|
+
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
|
36
|
+
}
|
|
37
|
+
/** Cells to render for the calendar grid — leading `null`s pad the previous month's weekday offset. */
|
|
38
|
+
function buildMonthGrid(viewDate) {
|
|
39
|
+
const startOffset = startOfMonth(viewDate).getDay();
|
|
40
|
+
const total = daysInMonth(viewDate);
|
|
41
|
+
const cells = [];
|
|
42
|
+
for (let i = 0; i < startOffset; i++) cells.push(null);
|
|
43
|
+
for (let d = 1; d <= total; d++) cells.push(new Date(viewDate.getFullYear(), viewDate.getMonth(), d));
|
|
44
|
+
return cells;
|
|
45
|
+
}
|
|
46
|
+
/** Splits the flat cell list into 7-day weeks — the `role="row"` grouping the APG grid pattern expects. */
|
|
47
|
+
function chunkWeeks(cells) {
|
|
48
|
+
const weeks = [];
|
|
49
|
+
for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
|
|
50
|
+
return weeks;
|
|
51
|
+
}
|
|
52
|
+
/** 2023-01-01 was a Sunday — a fixed reference date that always yields Sun..Sat order regardless of today. */
|
|
53
|
+
function getWeekdayLabels(locale) {
|
|
54
|
+
const formatter = new Intl.DateTimeFormat(locale ?? Locale.get(), { weekday: "narrow" });
|
|
55
|
+
const sunday = new Date(2023, 0, 1);
|
|
56
|
+
return Array.from({ length: 7 }, (_, i) => formatter.format(addDays(sunday, i)));
|
|
57
|
+
}
|
|
58
|
+
var UDatePicker = class UDatePicker extends UFormControlElement {
|
|
59
|
+
constructor(..._args) {
|
|
60
|
+
super(..._args);
|
|
61
|
+
this.clearable = false;
|
|
62
|
+
this.calendarId = `u-date-picker-calendar-${Math.random().toString(36).slice(2, 8)}`;
|
|
63
|
+
this.open = false;
|
|
64
|
+
this.viewDate = startOfMonth(/* @__PURE__ */ new Date());
|
|
65
|
+
this.focusedDate = /* @__PURE__ */ new Date();
|
|
66
|
+
this.grabFocusOnUpdate = false;
|
|
67
|
+
this.handlePrevMonth = () => this.navigateMonth(-1);
|
|
68
|
+
this.handleNextMonth = () => this.navigateMonth(1);
|
|
69
|
+
this.handleDayKeydown = (e, date) => {
|
|
70
|
+
switch (e.key) {
|
|
71
|
+
case "ArrowRight":
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
this.moveFocus(date, 1);
|
|
74
|
+
break;
|
|
75
|
+
case "ArrowLeft":
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
this.moveFocus(date, -1);
|
|
78
|
+
break;
|
|
79
|
+
case "ArrowDown":
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
this.moveFocus(date, 7);
|
|
82
|
+
break;
|
|
83
|
+
case "ArrowUp":
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
this.moveFocus(date, -7);
|
|
86
|
+
break;
|
|
87
|
+
case "Home":
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
this.moveFocus(date, -date.getDay());
|
|
90
|
+
break;
|
|
91
|
+
case "End":
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
this.moveFocus(date, 6 - date.getDay());
|
|
94
|
+
break;
|
|
95
|
+
case "Enter":
|
|
96
|
+
case " ":
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
this.selectDay(date);
|
|
99
|
+
break;
|
|
100
|
+
case "Escape":
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
this.popoverEl?.hide();
|
|
103
|
+
this.containerEl?.focus();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
this.handlePopoverShow = () => {
|
|
107
|
+
this.open = true;
|
|
108
|
+
this.grabFocusOnUpdate = true;
|
|
109
|
+
const base = this.value ? parseISODate(this.value) : /* @__PURE__ */ new Date();
|
|
110
|
+
this.viewDate = startOfMonth(base);
|
|
111
|
+
this.focusedDate = base;
|
|
112
|
+
};
|
|
113
|
+
this.handlePopoverHide = () => {
|
|
114
|
+
this.open = false;
|
|
115
|
+
};
|
|
116
|
+
this.handleClearClick = (e) => {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
e.stopPropagation();
|
|
119
|
+
const hadValue = !!this.value;
|
|
120
|
+
this.value = void 0;
|
|
121
|
+
if (hadValue) this.emitChange();
|
|
122
|
+
this.containerEl?.focus();
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
static {
|
|
126
|
+
this.styles = [super.styles, styles];
|
|
127
|
+
}
|
|
128
|
+
updated(changed) {
|
|
129
|
+
super.updated(changed);
|
|
130
|
+
if (changed.has("value")) this.internals?.setFormValue(this.value ?? "");
|
|
131
|
+
if (changed.has("open") && this.open || changed.has("focusedDate") && this.open && this.grabFocusOnUpdate) {
|
|
132
|
+
this.grabFocusOnUpdate = false;
|
|
133
|
+
this.popoverEl?.updateComplete.then(() => this.focusDayButton(this.focusedDate));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
render() {
|
|
137
|
+
const displayText = this.value ? formatDate(this.value) : "";
|
|
138
|
+
return html`
|
|
139
|
+
<u-field part="field"
|
|
140
|
+
?required=${this.required}
|
|
141
|
+
?disabled=${this.disabled}
|
|
142
|
+
?invalid=${this.invalid}
|
|
143
|
+
.label=${this.label}
|
|
144
|
+
.description=${this.description}
|
|
145
|
+
.validationMessage=${this.validationMessage}
|
|
146
|
+
>
|
|
147
|
+
<div class="container" part="container"
|
|
148
|
+
tabindex=${this.disabled ? "-1" : "0"}
|
|
149
|
+
role="combobox"
|
|
150
|
+
aria-haspopup="dialog"
|
|
151
|
+
aria-expanded=${this.open}
|
|
152
|
+
aria-label=${ifDefined(this.label)}
|
|
153
|
+
aria-description=${ifDefined(this.description)}
|
|
154
|
+
aria-controls=${this.calendarId}
|
|
155
|
+
>
|
|
156
|
+
<span class="text-content ${!displayText ? "placeholder" : ""}">${displayText || this.placeholder || ""}</span>
|
|
157
|
+
<u-icon class="suffix-item"
|
|
158
|
+
?hidden=${!this.clearable || !this.value || this.disabled || this.readonly}
|
|
159
|
+
lib="internal"
|
|
160
|
+
name="x"
|
|
161
|
+
@click=${this.handleClearClick}
|
|
162
|
+
></u-icon>
|
|
163
|
+
<u-icon class="suffix-item"
|
|
164
|
+
lib="internal"
|
|
165
|
+
name="calendar"
|
|
166
|
+
></u-icon>
|
|
167
|
+
</div>
|
|
168
|
+
</u-field>
|
|
169
|
+
|
|
170
|
+
<u-popover part="popover"
|
|
171
|
+
id=${this.calendarId}
|
|
172
|
+
role="dialog"
|
|
173
|
+
aria-label="Choose date"
|
|
174
|
+
for=".container"
|
|
175
|
+
trigger="click"
|
|
176
|
+
strategy="fixed"
|
|
177
|
+
placement="bottom-start"
|
|
178
|
+
offset="4"
|
|
179
|
+
@show=${this.handlePopoverShow}
|
|
180
|
+
@hide=${this.handlePopoverHide}
|
|
181
|
+
>
|
|
182
|
+
${this.open ? this.renderCalendar() : ""}
|
|
183
|
+
</u-popover>
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
renderCalendar() {
|
|
187
|
+
const cells = buildMonthGrid(this.viewDate);
|
|
188
|
+
const monthLabel = formatDate(this.viewDate, {
|
|
189
|
+
year: "numeric",
|
|
190
|
+
month: "long"
|
|
191
|
+
});
|
|
192
|
+
const weekdayLabels = getWeekdayLabels();
|
|
193
|
+
return html`
|
|
194
|
+
<div class="calendar" part="calendar">
|
|
195
|
+
<div class="calendar-header" part="calendar-header">
|
|
196
|
+
<u-icon-button lib="internal" name="chevron-left" aria-label="Previous month" @click=${this.handlePrevMonth}></u-icon-button>
|
|
197
|
+
<span class="calendar-title" part="calendar-title">${monthLabel}</span>
|
|
198
|
+
<u-icon-button lib="internal" name="chevron-right" aria-label="Next month" @click=${this.handleNextMonth}></u-icon-button>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="calendar-weekdays" part="calendar-weekdays" role="row">
|
|
201
|
+
${weekdayLabels.map((w) => html`<span class="weekday" role="columnheader">${w}</span>`)}
|
|
202
|
+
</div>
|
|
203
|
+
<div class="calendar-grid" part="calendar-grid" role="grid">
|
|
204
|
+
${chunkWeeks(cells).map((week) => html`
|
|
205
|
+
<div class="calendar-week" part="calendar-week" role="row">
|
|
206
|
+
${week.map((date) => date ? this.renderDay(date) : html`<span class="day-empty" role="gridcell" aria-hidden="true"></span>`)}
|
|
207
|
+
</div>
|
|
208
|
+
`)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
`;
|
|
212
|
+
}
|
|
213
|
+
renderDay(date) {
|
|
214
|
+
const selected = this.value ? isSameDay(date, parseISODate(this.value)) : false;
|
|
215
|
+
const focused = isSameDay(date, this.focusedDate);
|
|
216
|
+
const today = isSameDay(date, /* @__PURE__ */ new Date());
|
|
217
|
+
const outOfRange = this.isOutOfRange(date);
|
|
218
|
+
return html`
|
|
219
|
+
<button type="button" class="day" part="day"
|
|
220
|
+
role="gridcell"
|
|
221
|
+
data-iso=${toISODate(date)}
|
|
222
|
+
tabindex=${focused ? 0 : -1}
|
|
223
|
+
aria-selected=${selected}
|
|
224
|
+
aria-disabled=${outOfRange}
|
|
225
|
+
?data-today=${today}
|
|
226
|
+
@click=${() => this.selectDay(date)}
|
|
227
|
+
@keydown=${(e) => this.handleDayKeydown(e, date)}
|
|
228
|
+
@focus=${() => {
|
|
229
|
+
this.focusedDate = date;
|
|
230
|
+
}}
|
|
231
|
+
>${date.getDate()}</button>
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
isOutOfRange(date) {
|
|
235
|
+
if (this.min && date.getTime() < parseISODate(this.min).getTime()) return true;
|
|
236
|
+
if (this.max && date.getTime() > parseISODate(this.max).getTime()) return true;
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
focusDayButton(date) {
|
|
240
|
+
const iso = toISODate(date);
|
|
241
|
+
this.renderRoot.querySelector(`button.day[data-iso="${iso}"]`)?.focus();
|
|
242
|
+
}
|
|
243
|
+
selectDay(date) {
|
|
244
|
+
if (this.isOutOfRange(date)) return;
|
|
245
|
+
const iso = toISODate(date);
|
|
246
|
+
const changed = iso !== this.value;
|
|
247
|
+
this.value = iso;
|
|
248
|
+
if (changed) this.emitChange();
|
|
249
|
+
this.popoverEl?.hide();
|
|
250
|
+
this.containerEl?.focus();
|
|
251
|
+
}
|
|
252
|
+
navigateMonth(delta) {
|
|
253
|
+
this.grabFocusOnUpdate = false;
|
|
254
|
+
const next = addMonths(this.viewDate, delta);
|
|
255
|
+
this.viewDate = next;
|
|
256
|
+
const clampedDay = Math.min(this.focusedDate.getDate(), daysInMonth(next));
|
|
257
|
+
this.focusedDate = new Date(next.getFullYear(), next.getMonth(), clampedDay);
|
|
258
|
+
}
|
|
259
|
+
moveFocus(from, deltaDays) {
|
|
260
|
+
this.grabFocusOnUpdate = true;
|
|
261
|
+
const next = addDays(from, deltaDays);
|
|
262
|
+
if (next.getMonth() !== this.viewDate.getMonth() || next.getFullYear() !== this.viewDate.getFullYear()) this.viewDate = startOfMonth(next);
|
|
263
|
+
this.focusedDate = next;
|
|
264
|
+
}
|
|
265
|
+
emitChange() {
|
|
266
|
+
if (!this.novalidate) this.validate();
|
|
267
|
+
this.dispatchEvent(new Event("change", {
|
|
268
|
+
bubbles: true,
|
|
269
|
+
composed: true
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
setValidity() {
|
|
273
|
+
let flags = {};
|
|
274
|
+
let message = "";
|
|
275
|
+
if (this.required && !this.value) {
|
|
276
|
+
flags = { valueMissing: true };
|
|
277
|
+
message = Locale.getValue("valueMissing");
|
|
278
|
+
} else if (this.value && this.min && parseISODate(this.value).getTime() < parseISODate(this.min).getTime()) {
|
|
279
|
+
flags = { rangeUnderflow: true };
|
|
280
|
+
message = Locale.getValue("rangeUnderflow", { min: this.min });
|
|
281
|
+
} else if (this.value && this.max && parseISODate(this.value).getTime() > parseISODate(this.max).getTime()) {
|
|
282
|
+
flags = { rangeOverflow: true };
|
|
283
|
+
message = Locale.getValue("rangeOverflow", { max: this.max });
|
|
284
|
+
}
|
|
285
|
+
this.commit(flags, message, this.containerEl ?? void 0);
|
|
286
|
+
}
|
|
287
|
+
reset() {
|
|
288
|
+
this.value = void 0;
|
|
289
|
+
this.invalid = false;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UDatePicker.prototype, "min", void 0);
|
|
293
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UDatePicker.prototype, "max", void 0);
|
|
294
|
+
__decorate([property({
|
|
295
|
+
type: Boolean,
|
|
296
|
+
reflect: true
|
|
297
|
+
}), __decorateMetadata("design:type", Boolean)], UDatePicker.prototype, "clearable", void 0);
|
|
298
|
+
__decorate([property({ type: String }), __decorateMetadata("design:type", String)], UDatePicker.prototype, "placeholder", void 0);
|
|
299
|
+
__decorate([query(".container", true), __decorateMetadata("design:type", typeof HTMLElement === "undefined" ? Object : HTMLElement)], UDatePicker.prototype, "containerEl", void 0);
|
|
300
|
+
__decorate([query("u-popover", true), __decorateMetadata("design:type", typeof UPopover === "undefined" ? Object : UPopover)], UDatePicker.prototype, "popoverEl", void 0);
|
|
301
|
+
__decorate([state(), __decorateMetadata("design:type", Boolean)], UDatePicker.prototype, "open", void 0);
|
|
302
|
+
__decorate([state(), __decorateMetadata("design:type", typeof Date === "undefined" ? Object : Date)], UDatePicker.prototype, "viewDate", void 0);
|
|
303
|
+
__decorate([state(), __decorateMetadata("design:type", typeof Date === "undefined" ? Object : Date)], UDatePicker.prototype, "focusedDate", void 0);
|
|
304
|
+
UDatePicker = __decorate([customElement("u-date-picker")], UDatePicker);
|
|
305
|
+
//#endregion
|
|
306
|
+
export { UDatePicker };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import('lit').CSSResult;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
//#region src/components/date-picker/UDatePicker.styles.ts
|
|
3
|
+
var styles = css`
|
|
4
|
+
:host {
|
|
5
|
+
--date-picker-popover-width: 296px;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
:host {
|
|
9
|
+
position: relative;
|
|
10
|
+
display: var(--u-date-picker-display, inline-block);
|
|
11
|
+
width: var(--u-date-picker-width, auto);
|
|
12
|
+
color: var(--u-txt-color, #212121);
|
|
13
|
+
font-size: inherit;
|
|
14
|
+
font-family: var(--u-font-base);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.container {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: row;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 0.4em;
|
|
22
|
+
padding: 0.3em 0.6em;
|
|
23
|
+
border: 1px solid var(--u-input-border-color, #E0E0E0);
|
|
24
|
+
border-radius: 0.25em;
|
|
25
|
+
background-color: var(--u-input-bg-color, #FFFFFF);
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition: border-color var(--u-duration-normal, 220ms) var(--u-ease-standard, cubic-bezier(0.2, 0, 0, 1)),
|
|
28
|
+
box-shadow var(--u-duration-normal, 220ms) var(--u-ease-standard, cubic-bezier(0.2, 0, 0, 1));
|
|
29
|
+
}
|
|
30
|
+
:host([readonly]) .container,
|
|
31
|
+
:host([disabled]) .container {
|
|
32
|
+
cursor: not-allowed;
|
|
33
|
+
border-color: var(--u-border-color-weak, #EEEEEE);
|
|
34
|
+
background-color: var(--u-bg-color-disabled, #FAFAFA);
|
|
35
|
+
}
|
|
36
|
+
:host(:not([readonly]):not([disabled])) .container:hover {
|
|
37
|
+
box-shadow: 0 0 0 1px var(--u-input-border-color-hover, #BDBDBD);
|
|
38
|
+
}
|
|
39
|
+
:host(:not([readonly]):not([disabled])) .container:focus-within {
|
|
40
|
+
box-shadow: 0 0 0 1px var(--u-input-border-color-focus, #1565C0);
|
|
41
|
+
}
|
|
42
|
+
:host([invalid]:not([readonly]):not([disabled])) .container {
|
|
43
|
+
box-shadow: 0 0 0 1px var(--u-input-border-color-invalid, #C62828);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.text-content {
|
|
47
|
+
flex: 1 0 auto;
|
|
48
|
+
min-width: 0;
|
|
49
|
+
font-size: 1em;
|
|
50
|
+
line-height: 1.5;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
}
|
|
55
|
+
.text-content.placeholder {
|
|
56
|
+
color: var(--u-txt-color-weak, #757575);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.suffix-item {
|
|
60
|
+
color: var(--u-icon-color, #616161);
|
|
61
|
+
font-size: 1em;
|
|
62
|
+
transition: color var(--u-duration-normal, 220ms) var(--u-ease-standard, cubic-bezier(0.2, 0, 0, 1));
|
|
63
|
+
}
|
|
64
|
+
.suffix-item:hover {
|
|
65
|
+
color: var(--u-icon-color-hover, #1565C0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
u-popover {
|
|
69
|
+
width: var(--date-picker-popover-width);
|
|
70
|
+
padding: 8px;
|
|
71
|
+
border: 1px solid var(--u-border-color, #E0E0E0);
|
|
72
|
+
border-radius: var(--u-radius-lg, 6px);
|
|
73
|
+
background-color: var(--u-panel-bg-color, #FFFFFF);
|
|
74
|
+
box-shadow: var(--u-shadow-lg, 0 4px 12px rgba(0, 0, 0, 0.16), 0 2px 4px rgba(0, 0, 0, 0.06));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.calendar-header {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: row;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: space-between;
|
|
82
|
+
padding: 0 4px 8px;
|
|
83
|
+
}
|
|
84
|
+
.calendar-title {
|
|
85
|
+
font-size: var(--u-text-label-size, 13px);
|
|
86
|
+
font-weight: var(--u-text-label-weight, 600);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.calendar-weekdays,
|
|
90
|
+
.calendar-grid {
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: repeat(7, 1fr);
|
|
93
|
+
}
|
|
94
|
+
.calendar-weekdays {
|
|
95
|
+
padding-bottom: 4px;
|
|
96
|
+
}
|
|
97
|
+
/* role="row" wrapper for APG grid semantics — display:contents keeps it out of the
|
|
98
|
+
CSS grid track so its day cells still lay out as direct grid items. */
|
|
99
|
+
.calendar-week {
|
|
100
|
+
display: contents;
|
|
101
|
+
}
|
|
102
|
+
.weekday {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
font-size: var(--u-text-caption-size, 12px);
|
|
107
|
+
color: var(--u-txt-color-weak, #757575);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.day,
|
|
111
|
+
.day-empty {
|
|
112
|
+
aspect-ratio: 1;
|
|
113
|
+
}
|
|
114
|
+
.day {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
border: none;
|
|
119
|
+
border-radius: var(--u-radius-circle, 50%);
|
|
120
|
+
background: transparent;
|
|
121
|
+
color: var(--u-txt-color, #212121);
|
|
122
|
+
font-size: 1em;
|
|
123
|
+
font-family: inherit;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
}
|
|
126
|
+
.day:hover:not([aria-disabled="true"]) {
|
|
127
|
+
background-color: var(--u-bg-color-hover, #F5F5F5);
|
|
128
|
+
}
|
|
129
|
+
.day:focus-visible {
|
|
130
|
+
outline: none;
|
|
131
|
+
box-shadow: 0 0 0 2px var(--u-input-border-color-focus, #1565C0);
|
|
132
|
+
}
|
|
133
|
+
.day[data-today] {
|
|
134
|
+
font-weight: 700;
|
|
135
|
+
color: var(--u-primary-color, #1976D2);
|
|
136
|
+
}
|
|
137
|
+
.day[aria-selected="true"] {
|
|
138
|
+
background-color: var(--u-primary-color, #1976D2);
|
|
139
|
+
color: var(--u-primary-txt-color, #FFFFFF);
|
|
140
|
+
}
|
|
141
|
+
.day[aria-disabled="true"] {
|
|
142
|
+
color: var(--u-txt-color-disabled, #BDBDBD);
|
|
143
|
+
cursor: not-allowed;
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
//#endregion
|
|
147
|
+
export { styles };
|
|
@@ -5,8 +5,8 @@ import "../spinner/USpinner.js";
|
|
|
5
5
|
import "../icon/UIcon.js";
|
|
6
6
|
import { Locale } from "../../utilities/Locale.js";
|
|
7
7
|
import "../field/UField.js";
|
|
8
|
-
import { UOption } from "../option/UOption.js";
|
|
9
8
|
import { UPopover } from "../popover/UPopover.js";
|
|
9
|
+
import { UOption } from "../option/UOption.js";
|
|
10
10
|
import { styles } from "./UInput.styles.js";
|
|
11
11
|
import { html } from "lit";
|
|
12
12
|
import { customElement, property, query, state } from "lit/decorators.js";
|
|
@@ -2,8 +2,8 @@ import { UElement } from "../UElement.js";
|
|
|
2
2
|
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorateMetadata.js";
|
|
3
3
|
import __decorate from "../../_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.js";
|
|
4
4
|
import "../icon/UIcon.js";
|
|
5
|
-
import { UDivider } from "../divider/UDivider.js";
|
|
6
5
|
import "../popover/UPopover.js";
|
|
6
|
+
import { UDivider } from "../divider/UDivider.js";
|
|
7
7
|
import { styles } from "./UMenuItem.styles.js";
|
|
8
8
|
import { html } from "lit";
|
|
9
9
|
import { customElement, property, state } from "lit/decorators.js";
|
|
@@ -5,8 +5,8 @@ import "../spinner/USpinner.js";
|
|
|
5
5
|
import "../icon/UIcon.js";
|
|
6
6
|
import { Locale } from "../../utilities/Locale.js";
|
|
7
7
|
import "../field/UField.js";
|
|
8
|
-
import { UOption } from "../option/UOption.js";
|
|
9
8
|
import { UPopover } from "../popover/UPopover.js";
|
|
9
|
+
import { UOption } from "../option/UOption.js";
|
|
10
10
|
import { styles } from "./USelect.styles.js";
|
|
11
11
|
import { html } from "lit";
|
|
12
12
|
import { customElement, property, query, state } from "lit/decorators.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from './components/carousel/UCarousel.js';
|
|
|
14
14
|
export * from './components/checkbox/UCheckbox.js';
|
|
15
15
|
export * from './components/chip/UChip.js';
|
|
16
16
|
export * from './components/copy-button/UCopyButton.js';
|
|
17
|
+
export * from './components/date-picker/UDatePicker.js';
|
|
17
18
|
export * from './components/dialog/UDialog.js';
|
|
18
19
|
export * from './components/divider/UDivider.js';
|
|
19
20
|
export * from './components/drawer/UDrawer.js';
|
|
@@ -59,6 +60,7 @@ export * from './utilities/BrowserStorage.js';
|
|
|
59
60
|
export * from './utilities/converters.js';
|
|
60
61
|
export * from './utilities/Dialog.js';
|
|
61
62
|
export * from './utilities/elements.js';
|
|
63
|
+
export * from './utilities/format.js';
|
|
62
64
|
export * from './utilities/icons.js';
|
|
63
65
|
export * from './utilities/Locale.js';
|
|
64
66
|
export * from './utilities/OverlayManager.js';
|
package/dist/index.js
CHANGED
|
@@ -24,14 +24,16 @@ import { UCheckbox } from "./components/checkbox/UCheckbox.js";
|
|
|
24
24
|
import { UTag } from "./components/tag/UTag.js";
|
|
25
25
|
import { UChip } from "./components/chip/UChip.js";
|
|
26
26
|
import { UCopyButton } from "./components/copy-button/UCopyButton.js";
|
|
27
|
+
import { UField } from "./components/field/UField.js";
|
|
28
|
+
import { UPopover } from "./components/popover/UPopover.js";
|
|
29
|
+
import { formatCurrency, formatDate, formatNumber } from "./utilities/format.js";
|
|
30
|
+
import { UDatePicker } from "./components/date-picker/UDatePicker.js";
|
|
27
31
|
import { UDialog } from "./components/dialog/UDialog.js";
|
|
28
32
|
import { UDivider } from "./components/divider/UDivider.js";
|
|
29
33
|
import { UDrawer } from "./components/drawer/UDrawer.js";
|
|
30
34
|
import { UExpander } from "./components/expander/UExpander.js";
|
|
31
|
-
import { UField } from "./components/field/UField.js";
|
|
32
35
|
import { UForm } from "./components/form/UForm.js";
|
|
33
36
|
import { UOption } from "./components/option/UOption.js";
|
|
34
|
-
import { UPopover } from "./components/popover/UPopover.js";
|
|
35
37
|
import { UInput } from "./components/input/UInput.js";
|
|
36
38
|
import { UMenuItem } from "./components/menu-item/UMenuItem.js";
|
|
37
39
|
import { UMenu } from "./components/menu/UMenu.js";
|
|
@@ -55,4 +57,4 @@ import { BrowserStorage } from "./utilities/BrowserStorage.js";
|
|
|
55
57
|
import { Dialog } from "./utilities/Dialog.js";
|
|
56
58
|
import { Theme } from "./utilities/Theme.js";
|
|
57
59
|
import { Toast } from "./utilities/Toast.js";
|
|
58
|
-
export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, getDefaultBaseUrl, getParentElement, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
|
|
60
|
+
export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDatePicker, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, formatCurrency, formatDate, formatNumber, getDefaultBaseUrl, getParentElement, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { UDatePicker as UDatePickerElement } from '../components/date-picker/UDatePicker';
|
|
3
|
+
|
|
4
|
+
export declare const UDatePicker: React.ForwardRefExoticComponent<
|
|
5
|
+
Omit<Partial<UDatePickerElement>, keyof React.HTMLAttributes<UDatePickerElement>>
|
|
6
|
+
& Omit<React.HTMLAttributes<UDatePickerElement>, 'onChange'>
|
|
7
|
+
& React.RefAttributes<UDatePickerElement>
|
|
8
|
+
& {
|
|
9
|
+
onChange?: (event: CustomEvent) => void;
|
|
10
|
+
}
|
|
11
|
+
>;
|
|
12
|
+
|
|
13
|
+
export type UDatePickerProps = React.ComponentProps<typeof UDatePicker>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createComponent } from '@lit/react';
|
|
3
|
+
import { UDatePicker as UDatePickerElement } from '../components/date-picker/UDatePicker.js';
|
|
4
|
+
|
|
5
|
+
export const UDatePicker = createComponent({
|
|
6
|
+
react: React,
|
|
7
|
+
tagName: 'u-date-picker',
|
|
8
|
+
elementClass: UDatePickerElement,
|
|
9
|
+
events: {
|
|
10
|
+
onChange: 'change',
|
|
11
|
+
},
|
|
12
|
+
});
|
package/dist/react/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { UExpander, UExpanderProps } from './UExpander';
|
|
|
30
30
|
export { UDrawer, UDrawerProps } from './UDrawer';
|
|
31
31
|
export { UDivider, UDividerProps } from './UDivider';
|
|
32
32
|
export { UDialog, UDialogProps } from './UDialog';
|
|
33
|
+
export { UDatePicker, UDatePickerProps } from './UDatePicker';
|
|
33
34
|
export { UCopyButton, UCopyButtonProps } from './UCopyButton';
|
|
34
35
|
export { UChip, UChipProps } from './UChip';
|
|
35
36
|
export { UCheckbox, UCheckboxProps } from './UCheckbox';
|
package/dist/react/index.js
CHANGED
|
@@ -30,6 +30,7 @@ export { UExpander } from './UExpander.js';
|
|
|
30
30
|
export { UDrawer } from './UDrawer.js';
|
|
31
31
|
export { UDivider } from './UDivider.js';
|
|
32
32
|
export { UDialog } from './UDialog.js';
|
|
33
|
+
export { UDatePicker } from './UDatePicker.js';
|
|
33
34
|
export { UCopyButton } from './UCopyButton.js';
|
|
34
35
|
export { UChip } from './UChip.js';
|
|
35
36
|
export { UCheckbox } from './UCheckbox.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LocaleTag } from './Locale.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wraps `Intl.NumberFormat` with the active locale (`Locale.get()` if omitted).
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatNumber(value: number, options?: Intl.NumberFormatOptions, locale?: LocaleTag): string;
|
|
6
|
+
/**
|
|
7
|
+
* Formats a number as currency. The `currency` code is **required and has no default** —
|
|
8
|
+
* the caller must always specify it (e.g. `'KRW'`, `'USD'`). Currency selection is domain knowledge,
|
|
9
|
+
* and this utility does not assume a default.
|
|
10
|
+
*
|
|
11
|
+
* @note If `options` contains `currency` or `style`, they will override the explicit `currency` argument.
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions, locale?: LocaleTag): string;
|
|
14
|
+
/**
|
|
15
|
+
* Wraps `Intl.DateTimeFormat` with the active locale. Accepts a Date object or
|
|
16
|
+
* an ISO `YYYY-MM-DD` date string (parsed as local time, not UTC).
|
|
17
|
+
*
|
|
18
|
+
* A value that can't be resolved to a real date (malformed string, or an
|
|
19
|
+
* already-Invalid `Date`) degrades to `String(value)` rather than throwing —
|
|
20
|
+
* `Intl.DateTimeFormat.format()` throws `RangeError` on an Invalid Date, and this
|
|
21
|
+
* utility is called from render paths where an uncaught throw blanks the whole
|
|
22
|
+
* component. Same degrade-instead-of-throw contract as `formatCurrency`'s
|
|
23
|
+
* missing-`currency` fallback.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatDate(value: Date | string, options?: Intl.DateTimeFormatOptions, locale?: LocaleTag): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Locale } from "./Locale.js";
|
|
2
|
+
//#region src/utilities/format.ts
|
|
3
|
+
/**
|
|
4
|
+
* Parses "YYYY-MM-DD" as midnight in the **local** timezone.
|
|
5
|
+
* Using `Date.parse("YYYY-MM-DD")` (UTC interpretation) shifts the date
|
|
6
|
+
* back a day in negative-UTC-offset regions — split y/m/d and construct
|
|
7
|
+
* `new Date(y, m-1, d)` directly instead.
|
|
8
|
+
*
|
|
9
|
+
* A full ISO datetime (e.g. `2026-02-24T09:00:00Z`) is unambiguous — it carries
|
|
10
|
+
* its own timezone — so the local-time-safe split isn't needed there; when the
|
|
11
|
+
* split doesn't yield three numbers, fall back to native parsing instead of
|
|
12
|
+
* producing an Invalid Date.
|
|
13
|
+
*/
|
|
14
|
+
function parseISODate(iso) {
|
|
15
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
16
|
+
return Number.isNaN(y) || Number.isNaN(m) || Number.isNaN(d) ? new Date(iso) : new Date(y, m - 1, d);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolves a Date or ISO date string to a Date object.
|
|
20
|
+
*/
|
|
21
|
+
function resolve(value) {
|
|
22
|
+
return typeof value === "string" ? parseISODate(value) : value;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Wraps `Intl.NumberFormat` with the active locale (`Locale.get()` if omitted).
|
|
26
|
+
*/
|
|
27
|
+
function formatNumber(value, options, locale) {
|
|
28
|
+
return new Intl.NumberFormat(locale ?? Locale.get(), options).format(value);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Formats a number as currency. The `currency` code is **required and has no default** —
|
|
32
|
+
* the caller must always specify it (e.g. `'KRW'`, `'USD'`). Currency selection is domain knowledge,
|
|
33
|
+
* and this utility does not assume a default.
|
|
34
|
+
*
|
|
35
|
+
* @note If `options` contains `currency` or `style`, they will override the explicit `currency` argument.
|
|
36
|
+
*/
|
|
37
|
+
function formatCurrency(value, currency, options, locale) {
|
|
38
|
+
return new Intl.NumberFormat(locale ?? Locale.get(), {
|
|
39
|
+
style: "currency",
|
|
40
|
+
currency,
|
|
41
|
+
...options
|
|
42
|
+
}).format(value);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Wraps `Intl.DateTimeFormat` with the active locale. Accepts a Date object or
|
|
46
|
+
* an ISO `YYYY-MM-DD` date string (parsed as local time, not UTC).
|
|
47
|
+
*
|
|
48
|
+
* A value that can't be resolved to a real date (malformed string, or an
|
|
49
|
+
* already-Invalid `Date`) degrades to `String(value)` rather than throwing —
|
|
50
|
+
* `Intl.DateTimeFormat.format()` throws `RangeError` on an Invalid Date, and this
|
|
51
|
+
* utility is called from render paths where an uncaught throw blanks the whole
|
|
52
|
+
* component. Same degrade-instead-of-throw contract as `formatCurrency`'s
|
|
53
|
+
* missing-`currency` fallback.
|
|
54
|
+
*/
|
|
55
|
+
function formatDate(value, options, locale) {
|
|
56
|
+
const date = resolve(value);
|
|
57
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
58
|
+
return new Intl.DateTimeFormat(locale ?? Locale.get(), options).format(date);
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { formatCurrency, formatDate, formatNumber };
|
package/dist/utilities/icons.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import alert_circle_fill_svg_default from "../_virtual/_glob-assets_raw/alert-circle-fill.svg.1e438750.js";
|
|
2
2
|
import alert_triangle_fill_svg_default from "../_virtual/_glob-assets_raw/alert-triangle-fill.svg.1a8797c4.js";
|
|
3
3
|
import bell_fill_svg_default from "../_virtual/_glob-assets_raw/bell-fill.svg.d37c41d9.js";
|
|
4
|
+
import calendar_svg_default from "../_virtual/_glob-assets_raw/calendar.svg.02c3abd8.js";
|
|
4
5
|
import check_svg_default from "../_virtual/_glob-assets_raw/check.svg.4690b01e.js";
|
|
5
6
|
import chevron_down_svg_default from "../_virtual/_glob-assets_raw/chevron-down.svg.0ccd06ea.js";
|
|
6
7
|
import chevron_left_svg_default from "../_virtual/_glob-assets_raw/chevron-left.svg.b4601050.js";
|
|
@@ -41,6 +42,7 @@ var InternalIconBundle = new Map(Object.entries(/* #__PURE__ */ Object.assign({
|
|
|
41
42
|
"../assets/icons/alert-circle-fill.svg": alert_circle_fill_svg_default,
|
|
42
43
|
"../assets/icons/alert-triangle-fill.svg": alert_triangle_fill_svg_default,
|
|
43
44
|
"../assets/icons/bell-fill.svg": bell_fill_svg_default,
|
|
45
|
+
"../assets/icons/calendar.svg": calendar_svg_default,
|
|
44
46
|
"../assets/icons/check.svg": check_svg_default,
|
|
45
47
|
"../assets/icons/chevron-down.svg": chevron_down_svg_default,
|
|
46
48
|
"../assets/icons/chevron-left.svg": chevron_left_svg_default,
|
package/package.json
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# u-date-picker
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
import '@iyulab/components/dist/components/date-picker/UDatePicker.js';
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
**Tag:** `u-date-picker`
|
|
8
|
+
|
|
9
|
+
Single-date selection with a popover calendar. The value follows the same convention as the native `input[type=date]`: an ISO `YYYY-MM-DD` string. Form-associated.
|
|
10
|
+
|
|
11
|
+
> The calendar week always starts on Sunday, regardless of locale.
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<u-date-picker name="start-date" label="Start date"></u-date-picker>
|
|
15
|
+
|
|
16
|
+
<!-- Clearable with a bounded range -->
|
|
17
|
+
<u-date-picker name="due-date" label="Due date" clearable min="2026-01-01" max="2026-12-31"></u-date-picker>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Properties
|
|
23
|
+
|
|
24
|
+
| Property | Type | Default | Reflect | Description |
|
|
25
|
+
|----------|------|---------|---------|-------------|
|
|
26
|
+
| `value` | `string` | — | — | Selected date (ISO `YYYY-MM-DD`) |
|
|
27
|
+
| `min` | `string` | — | — | Minimum selectable date (ISO `YYYY-MM-DD`) |
|
|
28
|
+
| `max` | `string` | — | — | Maximum selectable date (ISO `YYYY-MM-DD`) |
|
|
29
|
+
| `clearable` | `boolean` | `false` | ✓ | Show clear button |
|
|
30
|
+
| `placeholder` | `string` | — | — | Placeholder text |
|
|
31
|
+
| `disabled` | `boolean` | `false` | ✓ | Disable |
|
|
32
|
+
| `readonly` | `boolean` | `false` | ✓ | Read-only |
|
|
33
|
+
| `required` | `boolean` | `false` | ✓ | Required |
|
|
34
|
+
| `invalid` | `boolean` | `false` | ✓ | Validation failed |
|
|
35
|
+
| `name` | `string` | — | — | Form field name |
|
|
36
|
+
| `label` | `string` | — | — | Field label |
|
|
37
|
+
| `description` | `string` | — | — | Helper text |
|
|
38
|
+
| `validationMessage` | `string` | — | — | Custom validation message |
|
|
39
|
+
|
|
40
|
+
## Events
|
|
41
|
+
|
|
42
|
+
| Event | Description |
|
|
43
|
+
|-------|-------------|
|
|
44
|
+
| `change` | Fires when the user clicks a date cell, confirms via keyboard, or clicks the clear button. Programmatic value assignment does not fire it. |
|
|
45
|
+
|
|
46
|
+
## Methods
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
|--------|--------------|
|
|
50
|
+
| `validate()` | Validate; sets `invalid` |
|
|
51
|
+
| `reset()` | Reset value |
|
|
52
|
+
|
|
53
|
+
## CSS Parts
|
|
54
|
+
|
|
55
|
+
| Part | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `field` | The `u-field` element |
|
|
58
|
+
| `container` | The element wrapping the trigger area |
|
|
59
|
+
| `popover` | The popover element showing the calendar |
|
|
60
|
+
| `calendar` | The calendar container |
|
|
61
|
+
| `calendar-header` | The month navigation header |
|
|
62
|
+
| `calendar-title` | The "Month Year" title |
|
|
63
|
+
| `calendar-weekdays` | The weekday header row |
|
|
64
|
+
| `calendar-grid` | The date grid |
|
|
65
|
+
| `day` | A date cell button |
|
|
66
|
+
|
|
67
|
+
## CSS Custom Properties
|
|
68
|
+
|
|
69
|
+
| Property | Description |
|
|
70
|
+
|----------|-------------|
|
|
71
|
+
| `--date-picker-popover-width` | Width of the calendar popover (default: 296px, independent of trigger width — a fixed-width calendar reads more naturally) |
|