Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
1c5fba3
feat(zoom): Step 01-04 — 200% zoom support for DatePicker
Todor-ads Aug 11, 2026
20e5435
fix(ui5-date-picker): fix min/max validation at 200% zoom
Todor-ads Aug 13, 2026
722a0f9
feat(zoom): DatePicker HZ validation, calendar type toggle, YearPicke…
Todor-ads Aug 18, 2026
5a155fb
feat(zoom): Step 03 — TimePicker high-zoom support
Todor-ads Aug 18, 2026
9beef62
feat(zoom): Step 07 — Calendar standalone high-zoom support
Todor-ads Aug 18, 2026
591adfe
feat(date and time controls): add support 200% zoom
Todor-ads Aug 21, 2026
9d04dd2
feat(date and time controls): add support 200% zoom
Todor-ads Aug 24, 2026
624d3ae
fix(zoom): sync input and dialog at high zoom and fix year picker layout
Todor-ads Aug 25, 2026
72b3dc3
fix(zoom): center time inputs, align top padding and shorten time labels
Todor-ads Aug 25, 2026
6b89ae6
fix(zoom): increase dialog padding to 1rem and prevent horizontal scroll
Todor-ads Sep 1, 2026
4f5b23a
fix(zoom): increase dialog padding to 1rem and prevent horizontal scroll
Todor-ads Sep 1, 2026
355f9fe
fix(ui5-date-picker): fix min/max ISO serialization for non-Gregorian…
Todor-ads Sep 1, 2026
8782cdd
fix(ui5-date-picker): fix non-Gregorian year offset when confirming a…
Todor-ads Sep 1, 2026
d62d157
fix(zoom): fix CI lint and JSDoc errors introduced by high-zoom branch
Todor-ads Sep 1, 2026
7e6c367
fix(zoom): accessibility, init fallback, and selectionMode guard for …
Todor-ads Sep 3, 2026
9e36785
fix(zoom): correct non-Gregorian calendar support in high-zoom date i…
Todor-ads Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ For detailed component architecture, development rules, and testing patterns, se
| CSS selectors | `ui5-tag { }` | `[ui5-tag] { }` |
| Type checks | `instanceof Component` | `isInstanceOfComponent(el)` |
| DOM mutation | `this._ref.value = x` | Template: `<Comp value={x} />` |
| Current date | `new Date()` | `UI5Date.getInstance()` (import from `@ui5/webcomponents-localization/dist/dates/UI5Date.js`) |

## Commit Message Format

Expand Down
2 changes: 1 addition & 1 deletion packages/fiori/test/pages/styles/UserSettingsDialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
.language-region-container{
display: flex;
min-height: 2.5rem;
align-item:flex-start;
align-items:flex-start;
flex-direction: column;
gap: 0.563rem;
}
Expand Down
33 changes: 33 additions & 0 deletions packages/main/src/Calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,12 @@
this._handleResizeBound = this._handleResize.bind(this);
}

override get _shouldWatchZoom(): boolean {
return isPhone();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this for desktop devices?


onEnterDOM() {
super.onEnterDOM();
ResizeHandler.register(document.body, this._handleResizeBound);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we start watch only for mobile devices we won't need this super.onEnterDOM();

this._handleResize();
}
Expand Down Expand Up @@ -421,6 +426,7 @@
}

onExitDOM() {
super.onExitDOM();
ResizeHandler.deregister(document.body, this._handleResizeBound);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same logic should be applied also for super.onExitDOM();

}

Expand Down Expand Up @@ -948,6 +954,33 @@
this._selectedItemType = "None";
}

get _hzDatePickerValue(): string {
const ts = this._selectedDatesTimestamps[0] ?? this._timestamp;
const calDate = CalendarDateComponent.fromTimestamp(ts * 1000, this._primaryCalendarType);
return DateFormat.getDateInstance({ pattern: "yyyy-MM-dd", calendarType: this._primaryCalendarType }).format(calDate.toUTCJSDate()) as string;

Check failure on line 960 in packages/main/src/Calendar.ts

View workflow job for this annotation

GitHub Actions / check

This assertion is unnecessary since it does not change the type of the expression
}

_onHzDatePickerChange(e: CustomEvent<{ value: string, valid: boolean }>) {
if (!e.detail.valid) { return; }
const fmt = DateFormat.getDateInstance({ strictParsing: true, pattern: "yyyy-MM-dd", calendarType: this._primaryCalendarType });
const isoDate = fmt.parse(e.detail.value, true) as Date | null;
if (!isoDate) { return; }
const calDate = CalendarDateComponent.fromLocalJSDate(isoDate, this._primaryCalendarType);
const timestamp = calDate.valueOf() / 1000;
this.timestamp = timestamp;
this._fireEventAndUpdateSelectedDates([timestamp]);
}

get _hzMinISO(): string {
if (!this.minDate) { return ""; }
return DateFormat.getDateInstance({ pattern: "yyyy-MM-dd", calendarType: this._primaryCalendarType }).format(this._minDate.toUTCJSDate()) as string;

Check failure on line 976 in packages/main/src/Calendar.ts

View workflow job for this annotation

GitHub Actions / check

This assertion is unnecessary since it does not change the type of the expression
}

get _hzMaxISO(): string {
if (!this.maxDate) { return ""; }
return DateFormat.getDateInstance({ pattern: "yyyy-MM-dd", calendarType: this._primaryCalendarType }).format(this._maxDate.toUTCJSDate()) as string;

Check failure on line 981 in packages/main/src/Calendar.ts

View workflow job for this annotation

GitHub Actions / check

This assertion is unnecessary since it does not change the type of the expression
}

get _specialDates() {
return this.getSlottedNodes<SpecialCalendarDate>("specialDates");
}
Expand Down
28 changes: 14 additions & 14 deletions packages/main/src/CalendarHeaderTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Calendar from "./Calendar.js";
import Icon from "./Icon.js";
import type { CalendarHeaderHost } from "./CalendarHeaderTypes.js";

import slimArowLeft from "@ui5/webcomponents-icons/dist/slim-arrow-left.js";
import slimArowRight from "@ui5/webcomponents-icons/dist/slim-arrow-right.js";
Expand All @@ -16,7 +16,7 @@ interface CalendarHeaderOptions {
isMultiple?: boolean;
}

export default function CalendarHeaderTemplate(this: Calendar, options?: CalendarHeaderOptions) {
export default function CalendarHeaderTemplate(this: CalendarHeaderHost, options?: CalendarHeaderOptions) {
const headerText = options?.headerText;
const isFirst = options?.isFirst ?? true;
const isLast = options?.isLast ?? true;
Expand All @@ -41,7 +41,7 @@ export default function CalendarHeaderTemplate(this: Calendar, options?: Calenda
);
}

function renderPrevButton(this: Calendar, isFirst: boolean, isMultiple: boolean) {
function renderPrevButton(this: CalendarHeaderHost, isFirst: boolean, isMultiple: boolean) {
if (!isFirst && isMultiple) {
return <div class="ui5-calheader-spacer"></div>;
}
Expand Down Expand Up @@ -70,7 +70,7 @@ function renderPrevButton(this: Calendar, isFirst: boolean, isMultiple: boolean)
}

function renderMiddleButtons(
this: Calendar,
this: CalendarHeaderHost,
headerText: {
monthText: string;
yearText: string;
Expand Down Expand Up @@ -127,15 +127,15 @@ function renderMiddleButtons(
class="ui5-calheader-arrowbtn ui5-calheader-middlebtn"
part="calendar-header-middle-button"
hidden={this._isHeaderYearRangeButtonHidden}
tabindex={0}
role="button"
aria-label={this.accInfo.ariaLabelYearRangeButton}
aria-description={this.accInfo.ariaLabelYearRangeButton}
title={this.accInfo.tooltipYearRangeButton}
aria-keyshortcuts={this.accInfo.keyShortcutYearRangeButton}
onClick={this.onHeaderYearRangeButtonPress}
onKeyDown={this.onYearRangeButtonKeyDown}
onKeyUp={this.onYearRangeButtonKeyUp}
tabindex={this._isHeaderYearRangeButtonReadonly ? -1 : 0}
role={this._isHeaderYearRangeButtonReadonly ? undefined : "button"}
aria-label={this._isHeaderYearRangeButtonReadonly ? undefined : this.accInfo.ariaLabelYearRangeButton}
aria-description={this._isHeaderYearRangeButtonReadonly ? undefined : this.accInfo.ariaLabelYearRangeButton}
title={this._isHeaderYearRangeButtonReadonly ? undefined : this.accInfo.tooltipYearRangeButton}
aria-keyshortcuts={this._isHeaderYearRangeButtonReadonly ? undefined : this.accInfo.keyShortcutYearRangeButton}
onClick={this._isHeaderYearRangeButtonReadonly ? undefined : this.onHeaderYearRangeButtonPress}
onKeyDown={this._isHeaderYearRangeButtonReadonly ? undefined : this.onYearRangeButtonKeyDown}
onKeyUp={this._isHeaderYearRangeButtonReadonly ? undefined : this.onYearRangeButtonKeyUp}
>
<span>{this._headerYearRangeButtonText}</span>
{this.hasSecondaryCalendarType &&
Expand All @@ -146,7 +146,7 @@ function renderMiddleButtons(
);
}

function renderNextButton(this: Calendar, isFirst: boolean, isLast: boolean, isMultiple: boolean) {
function renderNextButton(this: CalendarHeaderHost, isFirst: boolean, isLast: boolean, isMultiple: boolean) {
// In landscape mode, show next button only on last calendar
const isVertical = this._portraitView;
const shouldShowNextButton = !isMultiple || (isVertical ? isFirst : isLast);
Expand Down
48 changes: 48 additions & 0 deletions packages/main/src/CalendarHeaderTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export interface CalendarHeaderHost {
_previousButtonDisabled: boolean;
_nextButtonDisabled: boolean;
_portraitView: boolean;
_isHeaderMonthButtonHidden: boolean;
_isHeaderYearButtonHidden: boolean;
_isHeaderYearRangeButtonHidden: boolean;
_isHeaderYearRangeButtonReadonly?: boolean;
_headerMonthButtonText?: string;
_headerYearButtonText?: string;
_headerYearButtonTextSecType?: string;
_headerYearRangeButtonText?: string;
_headerYearRangeButtonTextSecType?: string;
secondMonthButtonText?: string;
hasSecondaryCalendarType: boolean;
onPrevButtonClick: (e: MouseEvent) => void;
onPrevButtonKeyDown: (e: KeyboardEvent) => void;
onPrevButtonKeyUp: (e: KeyboardEvent) => void;
onNextButtonClick: (e: MouseEvent) => void;
onNextButtonKeyDown: (e: KeyboardEvent) => void;
onNextButtonKeyUp: (e: KeyboardEvent) => void;
onHeaderMonthButtonPress?: (e: Event) => void;
onMonthButtonKeyDown?: (e: KeyboardEvent) => void;
onMonthButtonKeyUp?: (e: KeyboardEvent) => void;
onHeaderYearButtonPress?: (e: Event) => void;
onYearButtonKeyDown?: (e: KeyboardEvent) => void;
onYearButtonKeyUp?: (e: KeyboardEvent) => void;
onHeaderYearRangeButtonPress?: (e: Event) => void;
onYearRangeButtonKeyDown?: (e: KeyboardEvent) => void;
onYearRangeButtonKeyUp?: (e: KeyboardEvent) => void;
accInfo: {
ariaLabelMonthButton?: string;
ariaLabelYearButton?: string;
ariaLabelYearRangeButton?: string;
ariaLabelNextButton?: string;
ariaLabelPrevButton?: string;
keyShortcutMonthButton?: string;
keyShortcutYearButton?: string;
keyShortcutYearRangeButton?: string;
keyShortcutNextButton?: string;
keyShortcutPrevButton?: string;
tooltipMonthButton?: string;
tooltipYearButton?: string;
tooltipYearRangeButton?: string;
tooltipNextButton?: string;
tooltipPrevButton?: string;
};
}
16 changes: 16 additions & 0 deletions packages/main/src/CalendarTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,24 @@ import YearPicker from "./YearPicker.js";
import YearRangePicker from "./YearRangePicker.js";
import CalendarHeaderTemplate from "./CalendarHeaderTemplate.js";
import CalendarSelectionMode from "./types/CalendarSelectionMode.js";
import DatePicker from "./DatePicker.js";

export default function CalendarTemplate(this: Calendar) {
if (this._highZoom && this.selectionMode === "Single") {
return (
<DatePicker
value={this._hzDatePickerValue}
valueFormat="yyyy-MM-dd"
formatPattern={this.formatPattern || "medium"}
primaryCalendarType={this.primaryCalendarType}
secondaryCalendarType={this.secondaryCalendarType}
minDate={this._hzMinISO}
maxDate={this._hzMaxISO}
onChange={this._onHzDatePickerChange}
/>
);
}

const showMultipleMonths = this._monthsToShow > 1 && !this._isDayPickerHidden;
const shouldRenderSeparateHeaders = this._isDefaultHeaderModeInMultipleMonths && !this._portraitView;
const shouldRenderInlineHeaders = this._isDefaultHeaderModeInMultipleMonths && this._portraitView;
Expand Down
30 changes: 30 additions & 0 deletions packages/main/src/DateComponentBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDat
import { getMaxCalendarDate, getMinCalendarDate } from "@ui5/webcomponents-localization/dist/dates/ExtremeDates.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import type CalendarWeekNumbering from "./types/CalendarWeekNumbering.js";
import { isHighZoom, subscribeHighZoom, unsubscribeHighZoom } from "./util/HighZoomWatch.js";

/**
* @class
Expand Down Expand Up @@ -124,10 +125,39 @@ class DateComponentBase extends UI5Element {
_cachedMinDate?: { key: string, value: CalendarDate };
_cachedMaxDate?: { key: string, value: CalendarDate };

/**
* True when the effective viewport width is ≤ 320 px (corresponds to ~200% browser zoom on a phone).
* @private
*/
@property({ type: Boolean, noAttribute: true })
_highZoom = false;

constructor() {
super();
}

/**
* Whether this component reacts to high-zoom (switches its UI at ≤320px). Only the
* top-level pickers and the standalone Calendar do; the internal sub-pickers
* (day/month/year) inherit this base but never consume _highZoom, so they opt out
* to avoid subscribing to the shared zoom watcher.
*/
get _shouldWatchZoom(): boolean {
return false;
}

onEnterDOM() {
if (!this._shouldWatchZoom) { return; }
this._highZoom = isHighZoom();
subscribeHighZoom(this);
}

onExitDOM() {
if (this._shouldWatchZoom) {
unsubscribeHighZoom(this);
}
}

get _primaryCalendarType() {
const localeData = getCachedLocaleDataInstance(getLocale());
return this.primaryCalendarType || getCalendarType() || localeData.getPreferredCalendarType();
Expand Down
Loading
Loading