@melodicdev/components 1.5.11 → 1.5.13

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.
@@ -5144,32 +5144,51 @@ SpinnerComponent = __decorate([MelodicComponent({
5144
5144
  attributes: ["size", "label"]
5145
5145
  })], SpinnerComponent);
5146
5146
  function buttonTemplate(c) {
5147
- return html`
5148
- <button
5149
- type="${c.type}"
5150
- class=${classMap({
5147
+ const classes = classMap({
5151
5148
  "ml-button": true,
5152
5149
  [`ml-button--${c.variant}`]: true,
5153
5150
  [`ml-button--${c.size}`]: true,
5154
5151
  "ml-button--disabled": c.isDisabled,
5155
5152
  "ml-button--loading": c.loading,
5156
5153
  "ml-button--full-width": c.fullWidth
5157
- })}
5154
+ });
5155
+ const content = html`
5156
+ ${when(c.loading, () => html`
5157
+ <span class="ml-button__spinner">
5158
+ <ml-spinner size="sm"></ml-spinner>
5159
+ </span>
5160
+ `)}
5161
+ <span class="ml-button__content">
5162
+ <slot name="icon-start"></slot>
5163
+ <slot></slot>
5164
+ <slot name="icon-end"></slot>
5165
+ </span>
5166
+ `;
5167
+ if (c.href != null) return html`
5168
+ <a
5169
+ href=${c.isDisabled ? void 0 : c.href}
5170
+ target=${c.target ?? void 0}
5171
+ rel=${c.rel ?? void 0}
5172
+ download=${c.download ?? void 0}
5173
+ class=${classes}
5174
+ role="button"
5175
+ @click=${c.handleClick}
5176
+ aria-disabled=${c.isDisabled ? "true" : "false"}
5177
+ aria-busy=${c.loading ? "true" : "false"}
5178
+ >
5179
+ ${content}
5180
+ </a>
5181
+ `;
5182
+ return html`
5183
+ <button
5184
+ type="${c.type}"
5185
+ class=${classes}
5158
5186
  ?disabled=${c.isDisabled}
5159
5187
  @click=${c.handleClick}
5160
5188
  aria-disabled=${c.isDisabled ? "true" : "false"}
5161
5189
  aria-busy=${c.loading ? "true" : "false"}
5162
5190
  >
5163
- ${when(c.loading, () => html`
5164
- <span class="ml-button__spinner">
5165
- <ml-spinner size="sm"></ml-spinner>
5166
- </span>
5167
- `)}
5168
- <span class="ml-button__content">
5169
- <slot name="icon-start"></slot>
5170
- <slot></slot>
5171
- <slot name="icon-end"></slot>
5172
- </span>
5191
+ ${content}
5173
5192
  </button>
5174
5193
  `;
5175
5194
  }
@@ -5434,6 +5453,10 @@ var ButtonComponent = class ButtonComponent$1 {
5434
5453
  this.disabled = false;
5435
5454
  this.loading = false;
5436
5455
  this.fullWidth = false;
5456
+ this.href = null;
5457
+ this.target = null;
5458
+ this.rel = null;
5459
+ this.download = null;
5437
5460
  this.handleClick = (event) => {
5438
5461
  if (this.isDisabled) {
5439
5462
  event.preventDefault();
@@ -5464,7 +5487,11 @@ ButtonComponent = __decorate([MelodicComponent({
5464
5487
  "type",
5465
5488
  "disabled",
5466
5489
  "loading",
5467
- "full-width"
5490
+ "full-width",
5491
+ "href",
5492
+ "target",
5493
+ "rel",
5494
+ "download"
5468
5495
  ]
5469
5496
  })], ButtonComponent);
5470
5497
  function buttonGroupTemplate(c) {
@@ -9035,57 +9062,146 @@ FormFieldComponent = __decorate([MelodicComponent({
9035
9062
  "required"
9036
9063
  ]
9037
9064
  })], FormFieldComponent);
9065
+ function dayGrid(c) {
9066
+ return html`
9067
+ <div class="ml-calendar__weekdays" role="row">
9068
+ ${repeat(c.weekdays, (d) => d, (d) => html`
9069
+ <span class="ml-calendar__weekday" role="columnheader">${d}</span>
9070
+ `)}
9071
+ </div>
9072
+
9073
+ <div class="ml-calendar__grid" @keydown=${c.handleGridKeyDown}>
9074
+ ${repeat(c.days, (day) => day.iso, (day) => html`
9075
+ <button
9076
+ type="button"
9077
+ class=${classMap({
9078
+ "ml-calendar__day": true,
9079
+ "ml-calendar__day--other-month": !day.isCurrentMonth,
9080
+ "ml-calendar__day--today": day.isToday,
9081
+ "ml-calendar__day--selected": day.isSelected,
9082
+ "ml-calendar__day--disabled": day.isDisabled
9083
+ })}
9084
+ ?disabled=${day.isDisabled || !day.isCurrentMonth}
9085
+ tabindex=${day.isCurrentMonth ? "0" : "-1"}
9086
+ aria-selected=${day.isSelected ? "true" : "false"}
9087
+ aria-label=${day.iso}
9088
+ @click=${() => c.selectDay(day)}
9089
+ >
9090
+ <span class="ml-calendar__day-number">${day.date}</span>
9091
+ ${day.isToday ? html`<span class="ml-calendar__today-dot"></span>` : ""}
9092
+ </button>
9093
+ `)}
9094
+ </div>
9095
+ `;
9096
+ }
9097
+ function monthGrid(c) {
9098
+ return html`
9099
+ <div class="ml-calendar__cell-grid" @keydown=${c.handleGridKeyDown}>
9100
+ ${repeat(c.months, (m) => m.index, (m) => html`
9101
+ <button
9102
+ type="button"
9103
+ class=${classMap({
9104
+ "ml-calendar__cell": true,
9105
+ "ml-calendar__cell--selected": m.isSelected,
9106
+ "ml-calendar__cell--current": m.isCurrent,
9107
+ "ml-calendar__cell--disabled": m.isDisabled
9108
+ })}
9109
+ ?disabled=${m.isDisabled}
9110
+ tabindex="0"
9111
+ aria-selected=${m.isSelected ? "true" : "false"}
9112
+ aria-label=${m.label}
9113
+ @click=${() => c.selectViewMonth(m)}
9114
+ >
9115
+ ${m.label}
9116
+ </button>
9117
+ `)}
9118
+ </div>
9119
+ `;
9120
+ }
9121
+ function yearGrid(c) {
9122
+ return html`
9123
+ <div class="ml-calendar__cell-grid" @keydown=${c.handleGridKeyDown}>
9124
+ ${repeat(c.years, (y) => y.year, (y) => html`
9125
+ <button
9126
+ type="button"
9127
+ class=${classMap({
9128
+ "ml-calendar__cell": true,
9129
+ "ml-calendar__cell--selected": y.isSelected,
9130
+ "ml-calendar__cell--current": y.isCurrent,
9131
+ "ml-calendar__cell--disabled": y.isDisabled
9132
+ })}
9133
+ ?disabled=${y.isDisabled}
9134
+ tabindex=${y.isDisabled ? "-1" : "0"}
9135
+ aria-selected=${y.isSelected ? "true" : "false"}
9136
+ aria-label=${String(y.year)}
9137
+ @click=${() => c.selectViewYear(y)}
9138
+ >
9139
+ ${y.year}
9140
+ </button>
9141
+ `)}
9142
+ </div>
9143
+ `;
9144
+ }
9038
9145
  function calendarTemplate(c) {
9039
9146
  return html`
9040
- <div class="ml-calendar" role="grid" aria-label=${c.monthLabel}>
9147
+ <div class=${classMap({
9148
+ "ml-calendar": true,
9149
+ [`ml-calendar--view-${c.view}`]: true
9150
+ })} role="grid" aria-label=${c.headerLabel}>
9041
9151
  <div class="ml-calendar__header">
9042
9152
  <div class="ml-calendar__nav-group">
9043
- <button type="button" class="ml-calendar__nav" aria-label="Previous year" @click=${c.prevYear}>
9153
+ <button type="button" class="ml-calendar__nav" aria-label="Previous" @click=${c.headerPrevFar}>
9044
9154
  <ml-icon icon="caret-double-left" size="sm"></ml-icon>
9045
9155
  </button>
9046
- <button type="button" class="ml-calendar__nav" aria-label="Previous month" @click=${c.prevMonth}>
9047
- <ml-icon icon="caret-left" size="sm"></ml-icon>
9048
- </button>
9156
+ ${when(c.view === "day", () => html`
9157
+ <button type="button" class="ml-calendar__nav" aria-label="Previous month" @click=${c.prevMonth}>
9158
+ <ml-icon icon="caret-left" size="sm"></ml-icon>
9159
+ </button>
9160
+ `)}
9049
9161
  </div>
9050
- <span class="ml-calendar__month-label">${c.monthLabel}</span>
9162
+
9163
+ ${when(c.view === "day", () => html`
9164
+ <div class="ml-calendar__title">
9165
+ <button
9166
+ type="button"
9167
+ class="ml-calendar__title-btn"
9168
+ aria-label="Select month"
9169
+ aria-expanded="false"
9170
+ @click=${c.openMonthView}
9171
+ >${c.monthLabel}</button>
9172
+ <button
9173
+ type="button"
9174
+ class="ml-calendar__title-btn"
9175
+ aria-label="Select year"
9176
+ aria-expanded="false"
9177
+ @click=${c.openYearView}
9178
+ >${c.viewYear}</button>
9179
+ </div>
9180
+ `, () => html`
9181
+ <button
9182
+ type="button"
9183
+ class="ml-calendar__title-btn ml-calendar__title-btn--wide"
9184
+ aria-label=${c.view === "month" ? "Back to day view" : "Select year"}
9185
+ aria-expanded="true"
9186
+ @click=${c.view === "month" ? c.openMonthView : c.openYearView}
9187
+ >${c.headerLabel}</button>
9188
+ `)}
9189
+
9051
9190
  <div class="ml-calendar__nav-group">
9052
- <button type="button" class="ml-calendar__nav" aria-label="Next month" @click=${c.nextMonth}>
9053
- <ml-icon icon="caret-right" size="sm"></ml-icon>
9054
- </button>
9055
- <button type="button" class="ml-calendar__nav" aria-label="Next year" @click=${c.nextYear}>
9191
+ ${when(c.view === "day", () => html`
9192
+ <button type="button" class="ml-calendar__nav" aria-label="Next month" @click=${c.nextMonth}>
9193
+ <ml-icon icon="caret-right" size="sm"></ml-icon>
9194
+ </button>
9195
+ `)}
9196
+ <button type="button" class="ml-calendar__nav" aria-label="Next" @click=${c.headerNextFar}>
9056
9197
  <ml-icon icon="caret-double-right" size="sm"></ml-icon>
9057
9198
  </button>
9058
9199
  </div>
9059
9200
  </div>
9060
9201
 
9061
- <div class="ml-calendar__weekdays" role="row">
9062
- ${repeat(c.weekdays, (d) => d, (d) => html`
9063
- <span class="ml-calendar__weekday" role="columnheader">${d}</span>
9064
- `)}
9065
- </div>
9066
-
9067
- <div class="ml-calendar__grid">
9068
- ${repeat(c.days, (day) => day.iso, (day) => html`
9069
- <button
9070
- type="button"
9071
- class=${classMap({
9072
- "ml-calendar__day": true,
9073
- "ml-calendar__day--other-month": !day.isCurrentMonth,
9074
- "ml-calendar__day--today": day.isToday,
9075
- "ml-calendar__day--selected": day.isSelected,
9076
- "ml-calendar__day--disabled": day.isDisabled
9077
- })}
9078
- ?disabled=${day.isDisabled || !day.isCurrentMonth}
9079
- tabindex=${day.isCurrentMonth ? "0" : "-1"}
9080
- aria-selected=${day.isSelected ? "true" : "false"}
9081
- aria-label=${day.iso}
9082
- @click=${() => c.selectDay(day)}
9083
- >
9084
- <span class="ml-calendar__day-number">${day.date}</span>
9085
- ${day.isToday ? html`<span class="ml-calendar__today-dot"></span>` : ""}
9086
- </button>
9087
- `)}
9088
- </div>
9202
+ ${when(c.view === "day", () => dayGrid(c))}
9203
+ ${when(c.view === "month", () => monthGrid(c))}
9204
+ ${when(c.view === "year", () => yearGrid(c))}
9089
9205
 
9090
9206
  <div class="ml-calendar__footer">
9091
9207
  <button type="button" class="ml-calendar__today-btn" @click=${c.goToToday}>Today</button>
@@ -9101,10 +9217,28 @@ const calendarStyles = () => css`
9101
9217
  --ml-calendar-width: 280px;
9102
9218
  --ml-calendar-font-family: var(--ml-font-sans);
9103
9219
 
9104
- /* --- Month label --- */
9220
+ /* --- Month/year title --- */
9105
9221
  --ml-calendar-month-font-size: var(--ml-text-sm);
9106
9222
  --ml-calendar-month-font-weight: var(--ml-font-semibold);
9107
9223
  --ml-calendar-month-color: var(--ml-color-text);
9224
+ --ml-calendar-title-btn-padding-x: var(--ml-space-1-5);
9225
+ --ml-calendar-title-btn-padding-y: var(--ml-space-1);
9226
+ --ml-calendar-title-btn-border-radius: var(--ml-radius-md);
9227
+ --ml-calendar-title-btn-hover-bg: var(--ml-color-surface-raised);
9228
+ --ml-calendar-title-btn-hover-color: var(--ml-color-text);
9229
+
9230
+ /* --- Month/year cells --- */
9231
+ --ml-calendar-cell-font-size: var(--ml-text-sm);
9232
+ --ml-calendar-cell-font-weight: var(--ml-font-regular);
9233
+ --ml-calendar-cell-color: var(--ml-color-text);
9234
+ --ml-calendar-cell-border-radius: var(--ml-radius-md);
9235
+ --ml-calendar-cell-hover-bg: var(--ml-color-surface-raised);
9236
+ --ml-calendar-cell-selected-bg: var(--ml-color-primary);
9237
+ --ml-calendar-cell-selected-color: var(--ml-color-text-inverse);
9238
+ --ml-calendar-cell-selected-font-weight: var(--ml-font-semibold);
9239
+ --ml-calendar-cell-selected-hover-bg: var(--ml-color-primary-hover);
9240
+ --ml-calendar-cell-current-font-weight: var(--ml-font-semibold);
9241
+ --ml-calendar-cell-height: 3rem;
9108
9242
 
9109
9243
  /* --- Nav buttons --- */
9110
9244
  --ml-calendar-nav-size: 2rem;
@@ -9178,10 +9312,94 @@ const calendarStyles = () => css`
9178
9312
  gap: 0;
9179
9313
  }
9180
9314
 
9181
- .ml-calendar__month-label {
9315
+ .ml-calendar__title {
9316
+ display: flex;
9317
+ align-items: center;
9318
+ gap: var(--ml-space-1);
9319
+ }
9320
+
9321
+ .ml-calendar__title-btn {
9322
+ border: none;
9323
+ background: none;
9324
+ padding: var(--ml-calendar-title-btn-padding-y) var(--ml-calendar-title-btn-padding-x);
9325
+ border-radius: var(--ml-calendar-title-btn-border-radius);
9326
+ font-family: inherit;
9182
9327
  font-size: var(--ml-calendar-month-font-size);
9183
9328
  font-weight: var(--ml-calendar-month-font-weight);
9184
9329
  color: var(--ml-calendar-month-color);
9330
+ cursor: pointer;
9331
+ transition: background-color var(--ml-calendar-transition-duration) var(--ml-calendar-transition-easing), color var(--ml-calendar-transition-duration) var(--ml-calendar-transition-easing);
9332
+ }
9333
+
9334
+ .ml-calendar__title-btn:hover {
9335
+ background-color: var(--ml-calendar-title-btn-hover-bg);
9336
+ color: var(--ml-calendar-title-btn-hover-color);
9337
+ }
9338
+
9339
+ .ml-calendar__title-btn:focus-visible {
9340
+ outline: none;
9341
+ box-shadow: var(--ml-calendar-focus-shadow);
9342
+ }
9343
+
9344
+ .ml-calendar__title-btn--wide {
9345
+ min-width: 0;
9346
+ }
9347
+
9348
+ /* Month / year cell grid */
9349
+ .ml-calendar__cell-grid {
9350
+ display: grid;
9351
+ grid-template-columns: repeat(3, 1fr);
9352
+ gap: var(--ml-space-1);
9353
+ padding: var(--ml-space-1) 0;
9354
+ }
9355
+
9356
+ .ml-calendar__cell {
9357
+ display: flex;
9358
+ align-items: center;
9359
+ justify-content: center;
9360
+ height: var(--ml-calendar-cell-height);
9361
+ border: none;
9362
+ border-radius: var(--ml-calendar-cell-border-radius);
9363
+ background: none;
9364
+ font-family: inherit;
9365
+ font-size: var(--ml-calendar-cell-font-size);
9366
+ font-weight: var(--ml-calendar-cell-font-weight);
9367
+ color: var(--ml-calendar-cell-color);
9368
+ cursor: pointer;
9369
+ padding: 0;
9370
+ transition:
9371
+ background-color var(--ml-calendar-transition-duration) var(--ml-calendar-transition-easing),
9372
+ color var(--ml-calendar-transition-duration) var(--ml-calendar-transition-easing);
9373
+ }
9374
+
9375
+ .ml-calendar__cell:hover:not(:disabled):not(.ml-calendar__cell--selected) {
9376
+ background-color: var(--ml-calendar-cell-hover-bg);
9377
+ }
9378
+
9379
+ .ml-calendar__cell:focus-visible {
9380
+ outline: none;
9381
+ box-shadow: var(--ml-calendar-focus-shadow);
9382
+ z-index: 1;
9383
+ }
9384
+
9385
+ .ml-calendar__cell--current {
9386
+ font-weight: var(--ml-calendar-cell-current-font-weight);
9387
+ }
9388
+
9389
+ .ml-calendar__cell--selected {
9390
+ background-color: var(--ml-calendar-cell-selected-bg);
9391
+ color: var(--ml-calendar-cell-selected-color);
9392
+ font-weight: var(--ml-calendar-cell-selected-font-weight);
9393
+ }
9394
+
9395
+ .ml-calendar__cell--selected:hover:not(:disabled) {
9396
+ background-color: var(--ml-calendar-cell-selected-hover-bg);
9397
+ }
9398
+
9399
+ .ml-calendar__cell--disabled,
9400
+ .ml-calendar__cell:disabled {
9401
+ opacity: var(--ml-calendar-disabled-opacity);
9402
+ cursor: not-allowed;
9185
9403
  }
9186
9404
 
9187
9405
  .ml-calendar__nav {
@@ -9353,6 +9571,20 @@ var MONTH_NAMES$1 = [
9353
9571
  "November",
9354
9572
  "December"
9355
9573
  ];
9574
+ var MONTH_NAMES_SHORT = [
9575
+ "Jan",
9576
+ "Feb",
9577
+ "Mar",
9578
+ "Apr",
9579
+ "May",
9580
+ "Jun",
9581
+ "Jul",
9582
+ "Aug",
9583
+ "Sep",
9584
+ "Oct",
9585
+ "Nov",
9586
+ "Dec"
9587
+ ];
9356
9588
  var WEEKDAYS = [
9357
9589
  "Su",
9358
9590
  "Mo",
@@ -9362,6 +9594,9 @@ var WEEKDAYS = [
9362
9594
  "Fr",
9363
9595
  "Sa"
9364
9596
  ];
9597
+ var YEARS_PER_PAGE = 12;
9598
+ var DEFAULT_MIN_YEAR_OFFSET = 120;
9599
+ var DEFAULT_MAX_YEAR_OFFSET = 10;
9365
9600
  function toIso(year, month, day) {
9366
9601
  return `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
9367
9602
  }
@@ -9369,13 +9604,25 @@ function todayIso() {
9369
9604
  const d = /* @__PURE__ */ new Date();
9370
9605
  return toIso(d.getFullYear(), d.getMonth(), d.getDate());
9371
9606
  }
9607
+ function parseYear(val) {
9608
+ if (typeof val === "number" && Number.isFinite(val)) return val;
9609
+ if (typeof val === "string" && val.trim() !== "") {
9610
+ const n = Number.parseInt(val, 10);
9611
+ if (Number.isFinite(n)) return n;
9612
+ }
9613
+ return null;
9614
+ }
9372
9615
  var CalendarComponent = class CalendarComponent$1 {
9373
9616
  constructor() {
9374
9617
  this.value = "";
9375
9618
  this.min = "";
9376
9619
  this.max = "";
9620
+ this.minYear = "";
9621
+ this.maxYear = "";
9377
9622
  this.viewMonth = (/* @__PURE__ */ new Date()).getMonth();
9378
9623
  this.viewYear = (/* @__PURE__ */ new Date()).getFullYear();
9624
+ this.view = "day";
9625
+ this.yearPageStart = 0;
9379
9626
  this.prevYear = () => {
9380
9627
  this.viewYear--;
9381
9628
  };
@@ -9394,6 +9641,55 @@ var CalendarComponent = class CalendarComponent$1 {
9394
9641
  this.viewYear++;
9395
9642
  } else this.viewMonth++;
9396
9643
  };
9644
+ this.headerPrev = () => {
9645
+ if (this.view === "year") this.prevYearPage();
9646
+ else if (this.view === "month") this.prevYear();
9647
+ else this.prevMonth();
9648
+ };
9649
+ this.headerNext = () => {
9650
+ if (this.view === "year") this.nextYearPage();
9651
+ else if (this.view === "month") this.nextYear();
9652
+ else this.nextMonth();
9653
+ };
9654
+ this.headerPrevFar = () => {
9655
+ if (this.view === "year") this.prevYearPage();
9656
+ else this.prevYear();
9657
+ };
9658
+ this.headerNextFar = () => {
9659
+ if (this.view === "year") this.nextYearPage();
9660
+ else this.nextYear();
9661
+ };
9662
+ this.prevYearPage = () => {
9663
+ const next = this.yearPageStart - YEARS_PER_PAGE;
9664
+ const minPage = this.computeYearPageStart(this.resolvedMinYear);
9665
+ this.yearPageStart = Math.max(next, minPage);
9666
+ };
9667
+ this.nextYearPage = () => {
9668
+ const next = this.yearPageStart + YEARS_PER_PAGE;
9669
+ const maxPage = this.computeYearPageStart(this.resolvedMaxYear);
9670
+ this.yearPageStart = Math.min(next, maxPage);
9671
+ };
9672
+ this.openMonthView = () => {
9673
+ this.view = this.view === "month" ? "day" : "month";
9674
+ };
9675
+ this.openYearView = () => {
9676
+ if (this.view === "year") {
9677
+ this.view = "day";
9678
+ return;
9679
+ }
9680
+ this.yearPageStart = this.computeYearPageStart(this.viewYear);
9681
+ this.view = "year";
9682
+ };
9683
+ this.selectViewMonth = (month) => {
9684
+ if (month.isDisabled) return;
9685
+ this.viewMonth = month.index;
9686
+ this.view = "day";
9687
+ };
9688
+ this.selectViewYear = (year) => {
9689
+ if (year.isDisabled) return;
9690
+ this.viewYear = year.year;
9691
+ this.view = "month";
9692
+ };
9397
9693
  this.selectDay = (day) => {
9398
9694
  if (day.isDisabled) return;
9399
9695
  this.value = day.iso;
@@ -9409,6 +9705,7 @@ var CalendarComponent = class CalendarComponent$1 {
9409
9705
  const now = /* @__PURE__ */ new Date();
9410
9706
  this.viewMonth = now.getMonth();
9411
9707
  this.viewYear = now.getFullYear();
9708
+ this.view = "day";
9412
9709
  const iso = todayIso();
9413
9710
  if (!this.isDisabled(iso)) {
9414
9711
  this.value = iso;
@@ -9419,12 +9716,56 @@ var CalendarComponent = class CalendarComponent$1 {
9419
9716
  }));
9420
9717
  }
9421
9718
  };
9719
+ this.handleGridKeyDown = (event) => {
9720
+ const key = event.key;
9721
+ if (key !== "ArrowLeft" && key !== "ArrowRight" && key !== "ArrowUp" && key !== "ArrowDown" && key !== "Home" && key !== "End") return;
9722
+ const target = event.target;
9723
+ if (!target || target.tagName !== "BUTTON") return;
9724
+ const grid = target.closest(".ml-calendar__grid, .ml-calendar__cell-grid");
9725
+ if (!grid) return;
9726
+ const cells = Array.from(grid.querySelectorAll("button:not([disabled])")).filter((el) => el.tabIndex !== -1);
9727
+ if (cells.length === 0) return;
9728
+ const idx = cells.indexOf(target);
9729
+ if (idx === -1) return;
9730
+ const cols = this.view === "day" ? 7 : 3;
9731
+ let nextIdx = idx;
9732
+ switch (key) {
9733
+ case "ArrowLeft":
9734
+ nextIdx = idx - 1;
9735
+ break;
9736
+ case "ArrowRight":
9737
+ nextIdx = idx + 1;
9738
+ break;
9739
+ case "ArrowUp":
9740
+ nextIdx = idx - cols;
9741
+ break;
9742
+ case "ArrowDown":
9743
+ nextIdx = idx + cols;
9744
+ break;
9745
+ case "Home":
9746
+ nextIdx = 0;
9747
+ break;
9748
+ case "End":
9749
+ nextIdx = cells.length - 1;
9750
+ break;
9751
+ }
9752
+ if (nextIdx < 0 || nextIdx >= cells.length) {
9753
+ event.preventDefault();
9754
+ return;
9755
+ }
9756
+ event.preventDefault();
9757
+ cells[nextIdx].focus();
9758
+ };
9422
9759
  }
9423
9760
  onInit() {
9424
9761
  this.navigateToValue();
9762
+ this.yearPageStart = this.computeYearPageStart(this.viewYear);
9425
9763
  }
9426
9764
  onAttributeChange(name, _, newVal) {
9427
- if (name === "value" && newVal) this.navigateToValue();
9765
+ if (name === "value" && newVal) {
9766
+ this.navigateToValue();
9767
+ this.yearPageStart = this.computeYearPageStart(this.viewYear);
9768
+ }
9428
9769
  }
9429
9770
  navigateToValue() {
9430
9771
  if (!this.value) return;
@@ -9435,11 +9776,28 @@ var CalendarComponent = class CalendarComponent$1 {
9435
9776
  }
9436
9777
  }
9437
9778
  get monthLabel() {
9438
- return `${MONTH_NAMES$1[this.viewMonth]} ${this.viewYear}`;
9779
+ return MONTH_NAMES$1[this.viewMonth];
9780
+ }
9781
+ get yearLabel() {
9782
+ return String(this.viewYear);
9783
+ }
9784
+ get yearRangeLabel() {
9785
+ return `${this.yearPageStart} – ${this.yearPageStart + YEARS_PER_PAGE - 1}`;
9786
+ }
9787
+ get headerLabel() {
9788
+ if (this.view === "year") return this.yearRangeLabel;
9789
+ if (this.view === "month") return this.yearLabel;
9790
+ return `${this.monthLabel} ${this.viewYear}`;
9439
9791
  }
9440
9792
  get weekdays() {
9441
9793
  return WEEKDAYS;
9442
9794
  }
9795
+ get resolvedMinYear() {
9796
+ return parseYear(this.minYear) ?? (/* @__PURE__ */ new Date()).getFullYear() - DEFAULT_MIN_YEAR_OFFSET;
9797
+ }
9798
+ get resolvedMaxYear() {
9799
+ return parseYear(this.maxYear) ?? (/* @__PURE__ */ new Date()).getFullYear() + DEFAULT_MAX_YEAR_OFFSET;
9800
+ }
9443
9801
  get days() {
9444
9802
  const year = this.viewYear;
9445
9803
  const month = this.viewMonth;
@@ -9498,6 +9856,66 @@ var CalendarComponent = class CalendarComponent$1 {
9498
9856
  }
9499
9857
  return result;
9500
9858
  }
9859
+ get months() {
9860
+ const selectedMonth = this.selectedMonthForYear(this.viewYear);
9861
+ const now = /* @__PURE__ */ new Date();
9862
+ return MONTH_NAMES_SHORT.map((label, index) => ({
9863
+ index,
9864
+ label,
9865
+ isSelected: selectedMonth === index,
9866
+ isCurrent: now.getFullYear() === this.viewYear && now.getMonth() === index,
9867
+ isDisabled: this.isMonthDisabled(this.viewYear, index)
9868
+ }));
9869
+ }
9870
+ get years() {
9871
+ const result = [];
9872
+ const selectedYear = this.selectedYear();
9873
+ const now = (/* @__PURE__ */ new Date()).getFullYear();
9874
+ const minY = this.resolvedMinYear;
9875
+ const maxY = this.resolvedMaxYear;
9876
+ for (let i = 0; i < YEARS_PER_PAGE; i++) {
9877
+ const year = this.yearPageStart + i;
9878
+ const inRange = year >= minY && year <= maxY;
9879
+ result.push({
9880
+ year,
9881
+ isSelected: selectedYear === year,
9882
+ isCurrent: year === now,
9883
+ isDisabled: !inRange,
9884
+ isPlaceholder: !inRange && (year < minY || year > maxY)
9885
+ });
9886
+ }
9887
+ return result;
9888
+ }
9889
+ get canPageYearsBack() {
9890
+ return this.yearPageStart > this.resolvedMinYear;
9891
+ }
9892
+ get canPageYearsForward() {
9893
+ return this.yearPageStart + YEARS_PER_PAGE - 1 < this.resolvedMaxYear;
9894
+ }
9895
+ computeYearPageStart(year) {
9896
+ const minY = this.resolvedMinYear;
9897
+ return minY + Math.floor((year - minY) / YEARS_PER_PAGE) * YEARS_PER_PAGE;
9898
+ }
9899
+ selectedMonthForYear(year) {
9900
+ if (!this.value) return null;
9901
+ const parts = this.value.split("-");
9902
+ if (parts.length !== 3) return null;
9903
+ if (Number.parseInt(parts[0], 10) !== year) return null;
9904
+ return Number.parseInt(parts[1], 10) - 1;
9905
+ }
9906
+ selectedYear() {
9907
+ if (!this.value) return null;
9908
+ const parts = this.value.split("-");
9909
+ if (parts.length !== 3) return null;
9910
+ return Number.parseInt(parts[0], 10);
9911
+ }
9912
+ isMonthDisabled(year, month) {
9913
+ const monthEnd = toIso(year, month, new Date(year, month + 1, 0).getDate());
9914
+ const monthStart = toIso(year, month, 1);
9915
+ if (this.min && monthEnd < this.min) return true;
9916
+ if (this.max && monthStart > this.max) return true;
9917
+ return false;
9918
+ }
9501
9919
  isDisabled(iso) {
9502
9920
  if (this.min && iso < this.min) return true;
9503
9921
  if (this.max && iso > this.max) return true;
@@ -9511,7 +9929,9 @@ CalendarComponent = __decorate([MelodicComponent({
9511
9929
  attributes: [
9512
9930
  "value",
9513
9931
  "min",
9514
- "max"
9932
+ "max",
9933
+ "min-year",
9934
+ "max-year"
9515
9935
  ]
9516
9936
  })], CalendarComponent);
9517
9937
  function datePickerTemplate(c) {
@@ -9563,6 +9983,8 @@ function datePickerTemplate(c) {
9563
9983
  value=${c.value}
9564
9984
  min=${c.min}
9565
9985
  max=${c.max}
9986
+ min-year=${c.minYear}
9987
+ max-year=${c.maxYear}
9566
9988
  @ml:select=${c.handleDateSelect}
9567
9989
  ></ml-calendar>
9568
9990
  </div>
@@ -9819,6 +10241,8 @@ var DatePickerComponent = class DatePickerComponent$1 {
9819
10241
  this.required = false;
9820
10242
  this.min = "";
9821
10243
  this.max = "";
10244
+ this.minYear = "";
10245
+ this.maxYear = "";
9822
10246
  this.isOpen = false;
9823
10247
  this._cleanupAutoUpdate = null;
9824
10248
  this.toggleCalendar = () => {
@@ -9934,7 +10358,9 @@ DatePickerComponent = __decorate([MelodicComponent({
9934
10358
  "disabled",
9935
10359
  "required",
9936
10360
  "min",
9937
- "max"
10361
+ "max",
10362
+ "min-year",
10363
+ "max-year"
9938
10364
  ]
9939
10365
  })], DatePickerComponent);
9940
10366
  function alertTemplate(c) {