@adia-ai/web-components 0.6.32 → 0.6.34
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 +44 -0
- package/components/accordion/accordion.css +2 -2
- package/components/action-list/action-list.css +2 -2
- package/components/agent-artifact/agent-artifact.css +31 -31
- package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
- package/components/agent-questions/agent-questions.css +57 -57
- package/components/agent-reasoning/agent-reasoning.css +62 -62
- package/components/agent-suggestions/agent-suggestions.css +4 -4
- package/components/agent-trace/agent-trace.css +53 -53
- package/components/alert/alert.css +41 -41
- package/components/avatar/avatar.css +27 -27
- package/components/badge/badge.css +27 -27
- package/components/block/block.css +16 -16
- package/components/breadcrumb/breadcrumb.css +23 -23
- package/components/button/button.css +101 -91
- package/components/calendar-grid/calendar-grid.a2ui.json +136 -0
- package/components/calendar-grid/calendar-grid.css +226 -0
- package/components/calendar-grid/calendar-grid.d.ts +37 -0
- package/components/calendar-grid/calendar-grid.js +17 -0
- package/components/calendar-grid/calendar-grid.yaml +116 -0
- package/components/calendar-grid/class.js +300 -0
- package/components/calendar-picker/calendar-picker.css +139 -139
- package/components/canvas/canvas.css +12 -12
- package/components/card/card.css +83 -83
- package/components/chart/chart.css +224 -224
- package/components/chart-legend/chart-legend.css +26 -26
- package/components/check/check.css +40 -40
- package/components/code/code.css +125 -125
- package/components/col/col.css +15 -15
- package/components/color-picker/color-picker.css +55 -55
- package/components/combobox/class.js +861 -0
- package/components/combobox/combobox.a2ui.json +363 -0
- package/components/combobox/combobox.css +244 -0
- package/components/combobox/combobox.d.ts +113 -0
- package/components/combobox/combobox.examples.md +59 -0
- package/components/combobox/combobox.js +17 -0
- package/components/combobox/combobox.test.js +181 -0
- package/components/combobox/combobox.yaml +369 -0
- package/components/command/command.css +90 -90
- package/components/date-range-picker/class.js +775 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
- package/components/date-range-picker/date-range-picker.css +178 -0
- package/components/date-range-picker/date-range-picker.d.ts +82 -0
- package/components/date-range-picker/date-range-picker.examples.md +37 -0
- package/components/date-range-picker/date-range-picker.js +17 -0
- package/components/date-range-picker/date-range-picker.test.js +387 -0
- package/components/date-range-picker/date-range-picker.yaml +285 -0
- package/components/datetime-picker/class.js +706 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
- package/components/datetime-picker/datetime-picker.css +150 -0
- package/components/datetime-picker/datetime-picker.d.ts +86 -0
- package/components/datetime-picker/datetime-picker.examples.md +46 -0
- package/components/datetime-picker/datetime-picker.js +17 -0
- package/components/datetime-picker/datetime-picker.test.js +454 -0
- package/components/datetime-picker/datetime-picker.yaml +332 -0
- package/components/demo-toggle/demo-toggle.css +27 -27
- package/components/description-list/description-list.css +18 -18
- package/components/divider/divider.css +24 -24
- package/components/embed/embed.css +6 -6
- package/components/empty-state/empty-state.css +27 -27
- package/components/feed/feed.css +12 -12
- package/components/field/field.css +37 -28
- package/components/field/field.test.js +32 -0
- package/components/fields/fields.css +5 -5
- package/components/grid/grid.css +5 -5
- package/components/heatmap/heatmap.css +63 -63
- package/components/icon/icon.css +12 -12
- package/components/image/image.css +14 -14
- package/components/index.js +8 -0
- package/components/input/input.css +66 -66
- package/components/inspector/inspector.css +6 -6
- package/components/integration-card/class.js +410 -0
- package/components/integration-card/integration-card.a2ui.json +268 -0
- package/components/integration-card/integration-card.css +169 -0
- package/components/integration-card/integration-card.d.ts +63 -0
- package/components/integration-card/integration-card.examples.md +41 -0
- package/components/integration-card/integration-card.js +17 -0
- package/components/integration-card/integration-card.test.js +306 -0
- package/components/integration-card/integration-card.yaml +280 -0
- package/components/kbd/kbd.css +32 -32
- package/components/link/link.css +12 -12
- package/components/list/list.css +8 -8
- package/components/list-window/class.js +688 -0
- package/components/list-window/list-window.a2ui.json +277 -0
- package/components/list-window/list-window.css +124 -0
- package/components/list-window/list-window.d.ts +84 -0
- package/components/list-window/list-window.examples.md +73 -0
- package/components/list-window/list-window.js +17 -0
- package/components/list-window/list-window.test.js +303 -0
- package/components/list-window/list-window.yaml +270 -0
- package/components/menu/menu.css +8 -8
- package/components/modal/modal.css +43 -43
- package/components/nav/nav.css +40 -40
- package/components/nav-group/nav-group.css +52 -52
- package/components/nav-item/nav-item.css +44 -44
- package/components/noodles/noodles.css +31 -31
- package/components/option-card/option-card.css +69 -69
- package/components/otp-input/otp-input.css +30 -30
- package/components/page/page.css +18 -18
- package/components/pagination/pagination.css +61 -61
- package/components/pane/pane.css +57 -57
- package/components/pipeline-status/pipeline-status.css +65 -65
- package/components/popover/popover.css +17 -17
- package/components/progress/progress.css +23 -23
- package/components/progress-row/progress-row.css +17 -17
- package/components/radio/radio.css +39 -39
- package/components/range/range.css +55 -55
- package/components/rating/rating.css +28 -28
- package/components/richtext/richtext.css +133 -133
- package/components/row/row.css +19 -19
- package/components/search/search.css +5 -5
- package/components/segment/segment.css +24 -24
- package/components/segmented/segmented.css +25 -25
- package/components/select/select.css +84 -84
- package/components/skeleton/skeleton.css +14 -14
- package/components/slider/slider.css +46 -46
- package/components/spinner/class.js +69 -0
- package/components/spinner/spinner.a2ui.json +197 -0
- package/components/spinner/spinner.css +165 -0
- package/components/spinner/spinner.d.ts +26 -0
- package/components/spinner/spinner.examples.md +26 -0
- package/components/spinner/spinner.js +17 -0
- package/components/spinner/spinner.test.js +234 -0
- package/components/spinner/spinner.yaml +230 -0
- package/components/stack/stack.css +11 -11
- package/components/stat/stat.css +25 -25
- package/components/step-progress/step-progress.css +20 -20
- package/components/stepper/stepper.css +29 -29
- package/components/stream/stream.css +12 -12
- package/components/swatch/swatch.css +68 -68
- package/components/swiper/swiper.css +57 -57
- package/components/switch/switch.css +52 -52
- package/components/table/class.js +9 -0
- package/components/table/table.a2ui.json +1 -1
- package/components/table/table.css +162 -162
- package/components/table/table.d.ts +1 -1
- package/components/table/table.test.js +53 -0
- package/components/table/table.yaml +13 -1
- package/components/table-toolbar/table-toolbar.css +32 -32
- package/components/tabs/tabs.css +51 -51
- package/components/tag/tag.css +48 -48
- package/components/text/text.css +44 -44
- package/components/textarea/textarea.css +46 -46
- package/components/time-picker/class.js +693 -0
- package/components/time-picker/time-picker.a2ui.json +267 -0
- package/components/time-picker/time-picker.css +122 -0
- package/components/time-picker/time-picker.d.ts +75 -0
- package/components/time-picker/time-picker.examples.md +35 -0
- package/components/time-picker/time-picker.js +17 -0
- package/components/time-picker/time-picker.test.js +287 -0
- package/components/time-picker/time-picker.yaml +256 -0
- package/components/timeline/timeline.css +50 -50
- package/components/toast/toast.css +58 -58
- package/components/toggle-group/toggle-group.css +6 -6
- package/components/toggle-scheme/toggle-scheme.css +2 -2
- package/components/toolbar/toolbar.css +17 -17
- package/components/tooltip/tooltip.css +2 -2
- package/components/tree/tree.css +37 -37
- package/components/upload/upload.css +49 -49
- package/dist/icons-manifest.js +3 -3
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +121 -83
- package/package.json +1 -1
- package/styles/components.css +8 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<calendar-grid-ui>` — Substrate primitive — a single-month calendar grid (month nav + day cells + keyboard nav). Renders inline; no trigger, no popover. Used as a substrate by calendar-picker-ui (popover wrapper), date-range-picker-ui (two side-by-side grids), and datetime-picker-ui (one grid + time pane). For a complete date-picker affordance (trigger + popover + form participation), use calendar-picker-ui.
|
|
3
|
+
*
|
|
4
|
+
* @see https://ui-kit.exe.xyz/site/components/calendar-grid
|
|
5
|
+
*
|
|
6
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
7
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
8
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
9
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
10
|
+
* needed beyond what the yaml `events:` block can express.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { UIElement } from '../../core/element.js';
|
|
14
|
+
|
|
15
|
+
export type CalendarGridChangeEvent = CustomEvent<unknown>;
|
|
16
|
+
export type CalendarGridInputEvent = CustomEvent<unknown>;
|
|
17
|
+
|
|
18
|
+
export class UICalendarGrid extends UIElement {
|
|
19
|
+
/** Disables all interaction. */
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
/** Maximum selectable date in ISO format (YYYY-MM-DD). */
|
|
22
|
+
max: string;
|
|
23
|
+
/** Minimum selectable date in ISO format (YYYY-MM-DD). */
|
|
24
|
+
min: string;
|
|
25
|
+
/** Blocks selection but allows month navigation. */
|
|
26
|
+
readonly: boolean;
|
|
27
|
+
/** Selected date in ISO format (YYYY-MM-DD). */
|
|
28
|
+
value: string;
|
|
29
|
+
|
|
30
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
31
|
+
type: K,
|
|
32
|
+
listener: (this: UICalendarGrid, ev: HTMLElementEventMap[K]) => unknown,
|
|
33
|
+
options?: boolean | AddEventListenerOptions,
|
|
34
|
+
): void;
|
|
35
|
+
addEventListener(type: 'change', listener: (ev: CalendarGridChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
36
|
+
addEventListener(type: 'input', listener: (ev: CalendarGridInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<calendar-grid-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
6
|
+
*
|
|
7
|
+
* import { UICalendarGrid } from '@adia-ai/web-components/components/calendar-grid/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UICalendarGrid } from './class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('calendar-grid-ui', UICalendarGrid);
|
|
16
|
+
|
|
17
|
+
export { UICalendarGrid };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Hand-authored substrate primitive — the inline calendar grid extracted
|
|
2
|
+
# from calendar-picker-ui so date-range-picker-ui + datetime-picker-ui can
|
|
3
|
+
# compose JUST the grid (no trigger, no popover). Edit this file; run
|
|
4
|
+
# `npm run components` to regenerate calendar-grid.a2ui.json.
|
|
5
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
6
|
+
name: UICalendarGrid
|
|
7
|
+
tag: calendar-grid-ui
|
|
8
|
+
status: stable
|
|
9
|
+
component: CalendarGrid
|
|
10
|
+
category: input
|
|
11
|
+
version: 1
|
|
12
|
+
description: >-
|
|
13
|
+
Substrate primitive — a single-month calendar grid (month nav + day
|
|
14
|
+
cells + keyboard nav). Renders inline; no trigger, no popover. Used as
|
|
15
|
+
a substrate by calendar-picker-ui (popover wrapper), date-range-picker-ui
|
|
16
|
+
(two side-by-side grids), and datetime-picker-ui (one grid + time pane).
|
|
17
|
+
For a complete date-picker affordance (trigger + popover + form
|
|
18
|
+
participation), use calendar-picker-ui.
|
|
19
|
+
props:
|
|
20
|
+
value:
|
|
21
|
+
description: Selected date in ISO format (YYYY-MM-DD).
|
|
22
|
+
type: string
|
|
23
|
+
default: ''
|
|
24
|
+
reflect: true
|
|
25
|
+
min:
|
|
26
|
+
description: Minimum selectable date in ISO format (YYYY-MM-DD).
|
|
27
|
+
type: string
|
|
28
|
+
default: ''
|
|
29
|
+
reflect: true
|
|
30
|
+
max:
|
|
31
|
+
description: Maximum selectable date in ISO format (YYYY-MM-DD).
|
|
32
|
+
type: string
|
|
33
|
+
default: ''
|
|
34
|
+
reflect: true
|
|
35
|
+
disabled:
|
|
36
|
+
description: Disables all interaction.
|
|
37
|
+
type: boolean
|
|
38
|
+
default: false
|
|
39
|
+
reflect: true
|
|
40
|
+
readonly:
|
|
41
|
+
description: Blocks selection but allows month navigation.
|
|
42
|
+
type: boolean
|
|
43
|
+
default: false
|
|
44
|
+
reflect: true
|
|
45
|
+
events:
|
|
46
|
+
change:
|
|
47
|
+
description: User selected a date. detail.value is the ISO string.
|
|
48
|
+
input:
|
|
49
|
+
description: Focus moved between days during keyboard nav. detail.focusDate is the ISO string.
|
|
50
|
+
slots: {}
|
|
51
|
+
states:
|
|
52
|
+
- name: disabled
|
|
53
|
+
description: Pointer-events off, reduced opacity, no selection.
|
|
54
|
+
attribute: disabled
|
|
55
|
+
- name: readonly
|
|
56
|
+
description: Navigation allowed, selection blocked.
|
|
57
|
+
attribute: readonly
|
|
58
|
+
traits: []
|
|
59
|
+
tokens:
|
|
60
|
+
--calendar-grid-width:
|
|
61
|
+
description: Grid container width.
|
|
62
|
+
default: 16rem
|
|
63
|
+
--calendar-grid-day-size:
|
|
64
|
+
description: Square cell size for day buttons.
|
|
65
|
+
default: 2rem
|
|
66
|
+
--calendar-grid-day-bg-selected:
|
|
67
|
+
description: Background of the selected day cell.
|
|
68
|
+
default: var(--a-accent)
|
|
69
|
+
--calendar-grid-day-fg-selected:
|
|
70
|
+
description: Text color of the selected day cell.
|
|
71
|
+
default: var(--a-accent-fg)
|
|
72
|
+
--calendar-grid-day-bg-hover:
|
|
73
|
+
description: Background on hover (non-selected, non-outside, non-disabled).
|
|
74
|
+
default: var(--a-bg-hover)
|
|
75
|
+
--calendar-grid-day-today-color:
|
|
76
|
+
description: Accent color used for the today-indicator dot + numeral tint.
|
|
77
|
+
default: var(--a-accent)
|
|
78
|
+
a2ui:
|
|
79
|
+
rules:
|
|
80
|
+
- >-
|
|
81
|
+
Use <CalendarGrid> only as a substrate primitive composed inside a
|
|
82
|
+
higher-level component (date-range picker, datetime picker, custom
|
|
83
|
+
date affordance). For a full single-date input, use
|
|
84
|
+
<CalendarPicker> — it adds a trigger button, popover surface, and
|
|
85
|
+
form-association.
|
|
86
|
+
- >-
|
|
87
|
+
<CalendarGrid> is NOT form-associated. Its emitted `change` event
|
|
88
|
+
is the sole signal to the parent — the parent owns the canonical
|
|
89
|
+
value + form participation.
|
|
90
|
+
anti_patterns:
|
|
91
|
+
- wrong: |
|
|
92
|
+
{"component": "CalendarGrid", "name": "birthdate"}
|
|
93
|
+
why: |
|
|
94
|
+
CalendarGrid is a substrate primitive — it does not participate in
|
|
95
|
+
forms. The `name` attribute has no effect. Use CalendarPicker
|
|
96
|
+
(form-associated) for single-date input.
|
|
97
|
+
fix: |
|
|
98
|
+
{"component": "CalendarPicker", "name": "birthdate"}
|
|
99
|
+
keywords:
|
|
100
|
+
- calendar
|
|
101
|
+
- date
|
|
102
|
+
- grid
|
|
103
|
+
- month
|
|
104
|
+
- day-picker
|
|
105
|
+
- substrate
|
|
106
|
+
synonyms:
|
|
107
|
+
calendar:
|
|
108
|
+
- calendar
|
|
109
|
+
- date
|
|
110
|
+
grid:
|
|
111
|
+
- grid
|
|
112
|
+
- calendar
|
|
113
|
+
related:
|
|
114
|
+
- CalendarPicker
|
|
115
|
+
- DateRangePicker
|
|
116
|
+
- DatetimePicker
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<calendar-grid-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* The auto-register path stays at `@adia-ai/web-components/components/calendar-grid`
|
|
5
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
6
|
+
*
|
|
7
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `<calendar-grid-ui>` — Substrate primitive: a single-month calendar grid.
|
|
12
|
+
*
|
|
13
|
+
* Renders ONLY the inline grid (month nav + day-cells + keyboard nav). No
|
|
14
|
+
* trigger, no popover — used as a substrate by <calendar-picker-ui> (trigger
|
|
15
|
+
* + popover wrapper), <date-range-picker-ui> (two side-by-side grids), and
|
|
16
|
+
* <datetime-picker-ui> (one grid + time pane).
|
|
17
|
+
*
|
|
18
|
+
* <calendar-grid-ui value="2026-05-23" min="2026-01-01" max="2026-12-31">
|
|
19
|
+
* </calendar-grid-ui>
|
|
20
|
+
*
|
|
21
|
+
* Properties:
|
|
22
|
+
* value — selected ISO date (YYYY-MM-DD)
|
|
23
|
+
* min — minimum selectable date (ISO)
|
|
24
|
+
* max — maximum selectable date (ISO)
|
|
25
|
+
* focus-date — the date that holds the roving tabindex (ISO, defaults to value or today)
|
|
26
|
+
* disabled / readonly — disables / blocks selection
|
|
27
|
+
*
|
|
28
|
+
* Events:
|
|
29
|
+
* change — { value: "2026-05-23" } when a date is selected
|
|
30
|
+
* input — emitted as the user navigates without committing
|
|
31
|
+
*
|
|
32
|
+
* Keyboard: ArrowUp/Down/Left/Right navigate days; Enter selects;
|
|
33
|
+
* PageUp/Down jump month; Home/End first/last of month.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { UIElement } from '../../core/element.js';
|
|
37
|
+
|
|
38
|
+
const DAYS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
|
39
|
+
const MONTHS_LONG = [
|
|
40
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
41
|
+
'July', 'August', 'September', 'October', 'November', 'December',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function pad(n) { return String(n).padStart(2, '0'); }
|
|
45
|
+
function toISO(y, m, d) { return `${y}-${pad(m + 1)}-${pad(d)}`; }
|
|
46
|
+
function parseISO(str) {
|
|
47
|
+
if (!str) return null;
|
|
48
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(str).trim());
|
|
49
|
+
if (!m) return null;
|
|
50
|
+
return new Date(+m[1], +m[2] - 1, +m[3]);
|
|
51
|
+
}
|
|
52
|
+
function sameDay(a, b) {
|
|
53
|
+
return a.getFullYear() === b.getFullYear()
|
|
54
|
+
&& a.getMonth() === b.getMonth()
|
|
55
|
+
&& a.getDate() === b.getDate();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class UICalendarGrid extends UIElement {
|
|
59
|
+
// §154 Phosphor icons stamped without consumer markup.
|
|
60
|
+
static requiredIcons = ['caret-left', 'caret-right'];
|
|
61
|
+
|
|
62
|
+
static properties = {
|
|
63
|
+
value: { type: String, default: '', reflect: true },
|
|
64
|
+
min: { type: String, default: '', reflect: true },
|
|
65
|
+
max: { type: String, default: '', reflect: true },
|
|
66
|
+
disabled: { type: Boolean, default: false, reflect: true },
|
|
67
|
+
readonly: { type: Boolean, default: false, reflect: true },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// No html`` template — we render imperatively via #renderCalendar so we can
|
|
71
|
+
// mutate per-day cells without diffing.
|
|
72
|
+
static template = () => null;
|
|
73
|
+
|
|
74
|
+
#viewYear = new Date().getFullYear();
|
|
75
|
+
#viewMonth = new Date().getMonth();
|
|
76
|
+
#focusedDay = null;
|
|
77
|
+
#bound = false;
|
|
78
|
+
|
|
79
|
+
// ── Lifecycle ──────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
connected() {
|
|
82
|
+
this.setAttribute('role', 'group');
|
|
83
|
+
if (!this.hasAttribute('aria-label')) this.setAttribute('aria-label', 'Calendar');
|
|
84
|
+
|
|
85
|
+
// Initialize view to selected date or today.
|
|
86
|
+
const sel = parseISO(this.value);
|
|
87
|
+
if (sel) {
|
|
88
|
+
this.#viewYear = sel.getFullYear();
|
|
89
|
+
this.#viewMonth = sel.getMonth();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!this.#bound) {
|
|
93
|
+
this.#bound = true;
|
|
94
|
+
this.addEventListener('click', this.#onClick);
|
|
95
|
+
this.addEventListener('keydown', this.#onKey);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
disconnected() {
|
|
100
|
+
this.removeEventListener('click', this.#onClick);
|
|
101
|
+
this.removeEventListener('keydown', this.#onKey);
|
|
102
|
+
this.#bound = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
render() {
|
|
106
|
+
// If a selected value changed externally, re-sync the visible month so it
|
|
107
|
+
// contains the selection (without clobbering the user's mid-flight nav).
|
|
108
|
+
const sel = parseISO(this.value);
|
|
109
|
+
if (sel && (sel.getFullYear() !== this.#viewYear || sel.getMonth() !== this.#viewMonth)) {
|
|
110
|
+
this.#viewYear = sel.getFullYear();
|
|
111
|
+
this.#viewMonth = sel.getMonth();
|
|
112
|
+
}
|
|
113
|
+
this.#renderCalendar();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
/** Move the visible month by ±N months. */
|
|
119
|
+
navigate(delta) {
|
|
120
|
+
this.#viewMonth += delta;
|
|
121
|
+
while (this.#viewMonth > 11) { this.#viewMonth -= 12; this.#viewYear++; }
|
|
122
|
+
while (this.#viewMonth < 0) { this.#viewMonth += 12; this.#viewYear--; }
|
|
123
|
+
this.#focusedDay = null;
|
|
124
|
+
this.#renderCalendar();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Programmatically focus a day (1-based) inside the visible month. */
|
|
128
|
+
focusDay(day) {
|
|
129
|
+
const daysInMonth = new Date(this.#viewYear, this.#viewMonth + 1, 0).getDate();
|
|
130
|
+
if (day < 1 || day > daysInMonth) return;
|
|
131
|
+
this.#focusedDay = day;
|
|
132
|
+
this.#renderCalendar();
|
|
133
|
+
this.querySelector('[data-cal-day][data-focused]')?.focus?.();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Internal — render the grid ────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
#renderCalendar() {
|
|
139
|
+
const year = this.#viewYear;
|
|
140
|
+
const month = this.#viewMonth;
|
|
141
|
+
const today = new Date();
|
|
142
|
+
const selected = parseISO(this.value);
|
|
143
|
+
const minDate = parseISO(this.min);
|
|
144
|
+
const maxDate = parseISO(this.max);
|
|
145
|
+
|
|
146
|
+
// Header — Prev / Title / Next
|
|
147
|
+
let h = `<div data-cal-header>
|
|
148
|
+
<button data-cal-prev type="button" aria-label="Previous month" tabindex="-1"><icon-ui name="caret-left"></icon-ui></button>
|
|
149
|
+
<span data-cal-title>${MONTHS_LONG[month]} ${year}</span>
|
|
150
|
+
<button data-cal-next type="button" aria-label="Next month" tabindex="-1"><icon-ui name="caret-right"></icon-ui></button>
|
|
151
|
+
</div>`;
|
|
152
|
+
|
|
153
|
+
// Day-of-week header
|
|
154
|
+
h += `<div data-cal-weekdays>${DAYS.map((d) => `<span>${d}</span>`).join('')}</div>`;
|
|
155
|
+
|
|
156
|
+
// Day grid
|
|
157
|
+
const firstDay = new Date(year, month, 1).getDay();
|
|
158
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
159
|
+
const daysInPrev = new Date(year, month, 0).getDate();
|
|
160
|
+
|
|
161
|
+
h += '<div data-cal-grid>';
|
|
162
|
+
|
|
163
|
+
// Previous-month trailing days (outside, disabled)
|
|
164
|
+
for (let i = firstDay - 1; i >= 0; i--) {
|
|
165
|
+
const day = daysInPrev - i;
|
|
166
|
+
h += `<button type="button" data-cal-day data-outside disabled tabindex="-1">${day}</button>`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Current-month days
|
|
170
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
171
|
+
const date = new Date(year, month, d);
|
|
172
|
+
const iso = toISO(year, month, d);
|
|
173
|
+
const isToday = sameDay(date, today);
|
|
174
|
+
const isSelected = selected && sameDay(date, selected);
|
|
175
|
+
const isDisabled = (minDate && date < minDate) || (maxDate && date > maxDate);
|
|
176
|
+
const isFocused = this.#focusedDay === d;
|
|
177
|
+
|
|
178
|
+
const attrs = [
|
|
179
|
+
'type="button"',
|
|
180
|
+
'data-cal-day',
|
|
181
|
+
`data-date="${iso}"`,
|
|
182
|
+
isToday ? 'data-today' : '',
|
|
183
|
+
isSelected ? 'data-selected' : '',
|
|
184
|
+
isDisabled ? 'disabled' : '',
|
|
185
|
+
isFocused ? 'data-focused' : '',
|
|
186
|
+
`tabindex="${isFocused ? '0' : '-1'}"`,
|
|
187
|
+
].filter(Boolean).join(' ');
|
|
188
|
+
|
|
189
|
+
h += `<button ${attrs}>${d}</button>`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Next-month leading days
|
|
193
|
+
const totalCells = firstDay + daysInMonth;
|
|
194
|
+
const remaining = (7 - (totalCells % 7)) % 7;
|
|
195
|
+
for (let d = 1; d <= remaining; d++) {
|
|
196
|
+
h += `<button type="button" data-cal-day data-outside disabled tabindex="-1">${d}</button>`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
h += '</div>';
|
|
200
|
+
|
|
201
|
+
this.innerHTML = h;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Selection ─────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
#selectDate(iso) {
|
|
207
|
+
if (this.disabled || this.readonly) return;
|
|
208
|
+
this.value = iso;
|
|
209
|
+
// Re-render to reflect new selection.
|
|
210
|
+
this.#renderCalendar();
|
|
211
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
212
|
+
bubbles: true,
|
|
213
|
+
detail: { value: iso },
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Event handlers ────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
#onClick = (e) => {
|
|
220
|
+
if (this.disabled) return;
|
|
221
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
222
|
+
if (!target) return;
|
|
223
|
+
const prev = target.closest('[data-cal-prev]');
|
|
224
|
+
if (prev) { e.stopPropagation(); this.navigate(-1); return; }
|
|
225
|
+
const next = target.closest('[data-cal-next]');
|
|
226
|
+
if (next) { e.stopPropagation(); this.navigate(1); return; }
|
|
227
|
+
const day = target.closest('[data-cal-day][data-date]:not([disabled])');
|
|
228
|
+
if (day) { e.stopPropagation(); this.#selectDate(day.dataset.date); }
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
#onKey = (e) => {
|
|
232
|
+
if (this.disabled) return;
|
|
233
|
+
|
|
234
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
235
|
+
if (this.#focusedDay) {
|
|
236
|
+
e.preventDefault();
|
|
237
|
+
const iso = toISO(this.#viewYear, this.#viewMonth, this.#focusedDay);
|
|
238
|
+
const date = new Date(this.#viewYear, this.#viewMonth, this.#focusedDay);
|
|
239
|
+
const minDate = parseISO(this.min);
|
|
240
|
+
const maxDate = parseISO(this.max);
|
|
241
|
+
if ((!minDate || date >= minDate) && (!maxDate || date <= maxDate)) {
|
|
242
|
+
this.#selectDate(iso);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const arrows = { ArrowUp: -7, ArrowDown: 7, ArrowLeft: -1, ArrowRight: 1 };
|
|
249
|
+
if (arrows[e.key] !== undefined) {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
const daysInMonth = new Date(this.#viewYear, this.#viewMonth + 1, 0).getDate();
|
|
252
|
+
let day = (this.#focusedDay || 1) + arrows[e.key];
|
|
253
|
+
|
|
254
|
+
if (day < 1) {
|
|
255
|
+
this.navigate(-1);
|
|
256
|
+
const prevDays = new Date(this.#viewYear, this.#viewMonth + 1, 0).getDate();
|
|
257
|
+
this.#focusedDay = prevDays + day;
|
|
258
|
+
} else if (day > daysInMonth) {
|
|
259
|
+
this.navigate(1);
|
|
260
|
+
this.#focusedDay = day - daysInMonth;
|
|
261
|
+
} else {
|
|
262
|
+
this.#focusedDay = day;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.#renderCalendar();
|
|
266
|
+
const focused = this.querySelector('[data-cal-day][data-focused]');
|
|
267
|
+
focused?.focus?.();
|
|
268
|
+
this.dispatchEvent(new CustomEvent('input', {
|
|
269
|
+
bubbles: true,
|
|
270
|
+
detail: { focusDate: toISO(this.#viewYear, this.#viewMonth, this.#focusedDay) },
|
|
271
|
+
}));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (e.key === 'PageUp') {
|
|
276
|
+
e.preventDefault();
|
|
277
|
+
this.navigate(-1);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (e.key === 'PageDown') {
|
|
281
|
+
e.preventDefault();
|
|
282
|
+
this.navigate(1);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (e.key === 'Home') {
|
|
286
|
+
e.preventDefault();
|
|
287
|
+
this.#focusedDay = 1;
|
|
288
|
+
this.#renderCalendar();
|
|
289
|
+
this.querySelector('[data-cal-day][data-focused]')?.focus?.();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (e.key === 'End') {
|
|
293
|
+
e.preventDefault();
|
|
294
|
+
this.#focusedDay = new Date(this.#viewYear, this.#viewMonth + 1, 0).getDate();
|
|
295
|
+
this.#renderCalendar();
|
|
296
|
+
this.querySelector('[data-cal-day][data-focused]')?.focus?.();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|