diff --git a/.gitignore b/.gitignore
index b464aa7dd0f..39031ff879e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -200,3 +200,7 @@ packages/documentation/static/ionic-preview
# artifact from bundle @siemens/ix-icons
###
ix-icons/dist/collection/components/icon/icon.css
+
+# SonarQube
+.scannerwork/
+sonar-project.properties
diff --git a/packages/angular-standalone-test-app/src/app/app.routes.ts b/packages/angular-standalone-test-app/src/app/app.routes.ts
index 8f2e0f1b684..a3c57de30f5 100644
--- a/packages/angular-standalone-test-app/src/app/app.routes.ts
+++ b/packages/angular-standalone-test-app/src/app/app.routes.ts
@@ -1158,6 +1158,11 @@ export const routes: Routes = [
loadComponent: () =>
import('../preview-examples/tile').then((m) => m.default),
},
+ {
+ path: 'time-input-form-test',
+ loadComponent: () =>
+ import('../preview-examples/time-input-form-test').then((m) => m.default),
+ },
{
path: 'timepicker',
loadComponent: () =>
diff --git a/packages/angular-standalone-test-app/src/preview-examples/time-input-form-test.ts b/packages/angular-standalone-test-app/src/preview-examples/time-input-form-test.ts
new file mode 100644
index 00000000000..7355771f6e4
--- /dev/null
+++ b/packages/angular-standalone-test-app/src/preview-examples/time-input-form-test.ts
@@ -0,0 +1,465 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Siemens AG
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IxModule } from '@siemens/ix-angular';
+
+@Component({
+ selector: 'app-time-input-form-test',
+ standalone: true,
+ imports: [IxModule, CommonModule],
+ template: `
+
+
Time Input Validation Tests
+
+ Testing: Required field validation (red border) after blur. Format: HH:MM:SS
+
+
+
+
+
+
+
+
+
+
1️⃣ Required (Standalone)
+
+
+
+
+
+
+
+
+
+
+
+
+
2️⃣ Optional (Standalone)
+
+
+
+
+
+
+
+
+
+
+
+
+
3️⃣ Required (In Form)
+
+
+
+
+
+
4️⃣ Optional (In Form)
+
+
+
+
+
+
5️⃣ Required (NoValidate Form)
+
+
+
+
+
+
6️⃣ Optional (NoValidate Form)
+
+
+
+
+
+
+
+
🪵 Event Log
+
+
+ No events yet... Interact with fields above
+
+
+ {{ log }}
+
+
+
+
+ `,
+ styles: []
+})
+export class TimeInputFormTestComponent {
+ logs: string[] = [];
+
+ private addLog(message: string): void {
+ const timestamp = new Date().toLocaleTimeString();
+ this.logs.push(`[${timestamp}] ${message}`);
+ }
+
+ onValueChange(event: any, emoji: string): void {
+ const value = event?.detail ?? 'empty';
+ this.addLog(`${emoji} Value: "${value}"`);
+ }
+
+ onFocus(emoji: string): void {
+ this.addLog(`${emoji} Focus`);
+ }
+
+ onBlur(emoji: string): void {
+ this.addLog(`${emoji} Blur`);
+ }
+
+ onFormSubmit(event: Event, emoji: string): void {
+ event.preventDefault();
+ this.addLog(`${emoji} Form submitted`);
+ }
+
+ setEmpty(input: any, emoji: string): void {
+ if (input?.nativeElement) {
+ input.nativeElement.value = '';
+ }
+ }
+
+ setValid(input: any, emoji: string): void {
+ if (input?.nativeElement) {
+ input.nativeElement.value = '14:30:00';
+ }
+ }
+
+ setInvalid(input: any, emoji: string): void {
+ if (input?.nativeElement) {
+ input.nativeElement.value = 'invalid';
+ }
+ }
+
+ async triggerManualBlur(input: any, emoji: string): Promise {
+ const element = input?.nativeElement;
+ if (element) {
+ await this.triggerNativeBlur(element);
+ setTimeout(() => this.debugValidationState(element, `${emoji} MANUAL`), 100);
+ }
+ }
+
+ async clearInput(input: any, emoji: string): Promise {
+ const element = input?.nativeElement;
+ if (element && typeof element.clear === 'function') {
+ await element.clear();
+ this.addLog(`${emoji} Cleared`);
+ }
+ }
+
+ clearLogs(): void {
+ this.logs = [];
+ }
+
+ private async debugValidationState(element: any, label: string): Promise {
+ if (!element) {
+ this.addLog(`${label} - Element not available`);
+ return;
+ }
+
+ const classList = Array.from(element.classList);
+ const hasRequiredClass = classList.includes('ix-invalid--required');
+ const required = element.required;
+ const value = element.value;
+
+ let touched = 'unknown';
+ try {
+ if (typeof element.isTouched === 'function') {
+ touched = String(await element.isTouched());
+ }
+ } catch {
+ touched = 'error';
+ }
+
+ this.addLog(`${label} - Req:${required}, Val:"${value}", Touch:${touched}, RedClass:${hasRequiredClass}`);
+ }
+
+ private async triggerNativeBlur(element: any): Promise {
+ if (!element) return;
+ try {
+ const nativeInput = await element.getNativeInputElement();
+ if (nativeInput) {
+ nativeInput.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
+ this.addLog('✅ Native blur triggered');
+ }
+ } catch (error) {
+ this.addLog(`❌ Error: ${error}`);
+ }
+ }
+
+ getLogColor(message: string): string {
+ const isError = message.includes('❌');
+ const isDebug = message.includes('BLUR') || message.includes('FOCUS') || message.includes('MANUAL');
+ if (isError) {
+ return '#d32f2f';
+ } else if (isDebug) {
+ return '#1976d2';
+ }
+ return 'inherit';
+ }
+}
+
+export default TimeInputFormTestComponent;
diff --git a/packages/angular-test-app/src/app/app-routing.module.ts b/packages/angular-test-app/src/app/app-routing.module.ts
index 4edd319db7c..25e6d6e5fc1 100644
--- a/packages/angular-test-app/src/app/app-routing.module.ts
+++ b/packages/angular-test-app/src/app/app-routing.module.ts
@@ -60,6 +60,7 @@ import DateInputLabel from '../preview-examples/date-input-label';
import DateInputReadonly from '../preview-examples/date-input-readonly';
import DateInputValidation from '../preview-examples/date-input-validation';
import DateInputWithSlots from '../preview-examples/date-input-with-slots';
+import DateInputFormTest from '../preview-examples/date-input-form-test';
import Datepicker from '../preview-examples/datepicker';
import DatepickerLocale from '../preview-examples/datepicker-locale';
import DatepickerRange from '../preview-examples/datepicker-range';
@@ -210,6 +211,13 @@ import TextareaRowsCols from '../preview-examples/textarea-rows-cols';
import TextareaValidation from '../preview-examples/textarea-validation';
import ThemeService from '../preview-examples/theme-switcher';
import Tile from '../preview-examples/tile';
+import TimeInput from '../preview-examples/time-input';
+import TimeInputDisabled from '../preview-examples/time-input-disabled';
+import TimeInputFormTest from '../preview-examples/time-input-form-test';
+import TimeInputLabel from '../preview-examples/time-input-label';
+import TimeInputReadonly from '../preview-examples/time-input-readonly';
+import TimeInputValidation from '../preview-examples/time-input-validation';
+import TimeInputWithSlots from '../preview-examples/time-input-with-slots';
import Timepicker from '../preview-examples/timepicker';
import Toast from '../preview-examples/toast';
import ToastCustom from '../preview-examples/toast-custom';
@@ -454,6 +462,10 @@ const routes: Routes = [
path: 'date-input-with-slots',
component: DateInputWithSlots,
},
+ {
+ path: 'date-input-form-test',
+ component: DateInputFormTest,
+ },
{
path: 'datepicker',
component: Datepicker,
@@ -797,6 +809,13 @@ const routes: Routes = [
{ path: 'input-with-slots', component: InputWithSlots },
{ path: 'theme-switcher', component: ThemeService },
{ path: 'tile', component: Tile },
+ { path: 'time-input', component: TimeInput },
+ { path: 'time-input-disabled', component: TimeInputDisabled },
+ { path: 'time-input-form-test', component: TimeInputFormTest },
+ { path: 'time-input-label', component: TimeInputLabel },
+ { path: 'time-input-readonly', component: TimeInputReadonly },
+ { path: 'time-input-validation', component: TimeInputValidation },
+ { path: 'time-input-with-slots', component: TimeInputWithSlots },
{ path: 'timepicker', component: Timepicker },
{ path: 'toggle-button-primary', component: ToggleButtonPrimary },
{ path: 'toggle-button-secondary', component: ToggleButtonSecondary },
@@ -881,6 +900,13 @@ const routes: Routes = [
path: 'tile',
component: Tile,
},
+ { path: 'time-input', component: TimeInput },
+ { path: 'time-input-disabled', component: TimeInputDisabled },
+ { path: 'time-input-form-test', component: TimeInputFormTest },
+ { path: 'time-input-label', component: TimeInputLabel },
+ { path: 'time-input-readonly', component: TimeInputReadonly },
+ { path: 'time-input-validation', component: TimeInputValidation },
+ { path: 'time-input-with-slots', component: TimeInputWithSlots },
{
path: 'timepicker',
component: Timepicker,
diff --git a/packages/angular-test-app/src/app/app.module.ts b/packages/angular-test-app/src/app/app.module.ts
index 8a6762f9f25..cfab6f26a42 100644
--- a/packages/angular-test-app/src/app/app.module.ts
+++ b/packages/angular-test-app/src/app/app.module.ts
@@ -69,6 +69,7 @@ import DateInputLabel from '../preview-examples/date-input-label';
import DateInputReadonly from '../preview-examples/date-input-readonly';
import DateInputValidation from '../preview-examples/date-input-validation';
import DateInputWithSlots from '../preview-examples/date-input-with-slots';
+import DateInputFormTest from '../preview-examples/date-input-form-test';
import Datepicker from '../preview-examples/datepicker';
import DatepickerLocale from '../preview-examples/datepicker-locale';
import DatepickerRange from '../preview-examples/datepicker-range';
@@ -218,6 +219,13 @@ import TextareaRowsCols from '../preview-examples/textarea-rows-cols';
import TextareaValidation from '../preview-examples/textarea-validation';
import ThemeSwitcher from '../preview-examples/theme-switcher';
import Tile from '../preview-examples/tile';
+import TimeInput from '../preview-examples/time-input';
+import TimeInputDisabled from '../preview-examples/time-input-disabled';
+import TimeInputFormTest from '../preview-examples/time-input-form-test';
+import TimeInputLabel from '../preview-examples/time-input-label';
+import TimeInputReadonly from '../preview-examples/time-input-readonly';
+import TimeInputValidation from '../preview-examples/time-input-validation';
+import TimeInputWithSlots from '../preview-examples/time-input-with-slots';
import Timepicker from '../preview-examples/timepicker';
import Toast from '../preview-examples/toast';
import ToastCustom from '../preview-examples/toast-custom';
@@ -299,6 +307,7 @@ import WorkflowVertical from '../preview-examples/workflow-vertical';
DateInputReadonly,
DateInputValidation,
DateInputWithSlots,
+ DateInputFormTest,
ContentExample,
ContentHeader,
ContentHeaderNoBack,
@@ -433,6 +442,13 @@ import WorkflowVertical from '../preview-examples/workflow-vertical';
TextareaLegacy,
ThemeSwitcher,
Tile,
+ TimeInput,
+ TimeInputDisabled,
+ TimeInputFormTest,
+ TimeInputLabel,
+ TimeInputReadonly,
+ TimeInputValidation,
+ TimeInputWithSlots,
Timepicker,
ToastCustom,
ToastPosition,
diff --git a/packages/angular-test-app/src/preview-examples/date-input-form-test.html b/packages/angular-test-app/src/preview-examples/date-input-form-test.html
new file mode 100644
index 00000000000..69fc27ff111
--- /dev/null
+++ b/packages/angular-test-app/src/preview-examples/date-input-form-test.html
@@ -0,0 +1,223 @@
+
+
+
+
Date Input Validation Tests
+
+ Testing: Required field validation (red border) after blur. Format: YYYY-MM-DD
+
+
+
+
+
+
+
+
+
1️⃣ Required (Standalone)
+
+
+
+
+
+
+
+
+
+
+
+
+
2️⃣ Optional (Standalone)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
4️⃣ Date Range
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📋 Event Logs
+
+
+ No events yet. Interact with the date inputs above to see logs here.
+
+
+ {{ log }}
+
+
+
+
diff --git a/packages/angular-test-app/src/preview-examples/date-input-form-test.ts b/packages/angular-test-app/src/preview-examples/date-input-form-test.ts
new file mode 100644
index 00000000000..268dbc3fd60
--- /dev/null
+++ b/packages/angular-test-app/src/preview-examples/date-input-form-test.ts
@@ -0,0 +1,251 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Siemens AG
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+
+@Component({
+ standalone: false,
+ selector: 'app-example',
+ templateUrl: './date-input-form-test.html',
+})
+export default class DateInputFormTest implements AfterViewInit {
+ @ViewChild('requiredInput') requiredInput!: ElementRef;
+ @ViewChild('optionalInput') optionalInput!: ElementRef;
+ @ViewChild('formRequiredInput') formRequiredInput!: ElementRef;
+ @ViewChild('rangeStartInput') rangeStartInput!: ElementRef;
+ @ViewChild('rangeEndInput') rangeEndInput!: ElementRef;
+
+ logs: string[] = [];
+ testForm: FormGroup;
+
+ constructor(private readonly fb: FormBuilder) {
+ this.testForm = this.fb.group({
+ requiredDate: ['', Validators.required],
+ optionalDate: ['']
+ });
+ }
+
+ ngAfterViewInit(): void {
+ this.addLog('🔧 Component initialized with ViewChild references');
+ }
+
+ onValueChange(event: CustomEvent, emoji: string): void {
+ const value = event.detail;
+ this.addLog(`${emoji} Value changed: ${value || 'empty'}`);
+ }
+
+ onFocus(emoji: string): void {
+ this.addLog(`${emoji} 🔍 Focus`);
+ }
+
+ onBlur(emoji: string): void {
+ this.addLog(`${emoji} 😴 Blur`);
+ }
+
+ onFormSubmit(event: Event, emoji: string): void {
+ event.preventDefault();
+ this.addLog(`${emoji} Form submitted`);
+ this.addLog(`Form valid: ${this.testForm.valid}`);
+ this.addLog(`Form values: ${JSON.stringify(this.testForm.value)}`);
+ }
+
+ addLog(message: string): void {
+ const timestamp = new Date().toLocaleTimeString();
+ this.logs.push(`[${timestamp}] ${message}`);
+ if (this.logs.length > 50) {
+ this.logs = this.logs.slice(-50);
+ }
+ }
+
+ clearLogs(): void {
+ this.addLog('🧹 Clear logs button clicked');
+ this.logs = [];
+ }
+
+ setEmpty(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} 🔘 Empty button clicked`);
+ try {
+ const element = inputComponent?.nativeElement;
+
+ if (element) {
+ // Method 1: Set value and dispatch events to trigger validation
+ element.value = '';
+
+ // Trigger input event to notify the component of value change
+ element.dispatchEvent(new Event('input', { bubbles: true }));
+
+ // Trigger change event for additional validation clearing
+ element.dispatchEvent(new Event('change', { bubbles: true }));
+
+ // Trigger blur to ensure validation state updates
+ setTimeout(() => {
+ element.dispatchEvent(new Event('blur', { bubbles: true }));
+ }, 10);
+
+ this.addLog(`${emoji} Cleared via nativeElement with events`);
+
+ // Method 2: Try component-specific clear method if available
+ if (inputComponent.clear && typeof inputComponent.clear === 'function') {
+ inputComponent.clear();
+ this.addLog(`${emoji} Also called component.clear()`);
+ }
+
+ // Method 3: Try setting component value property
+ if (inputComponent.value !== undefined) {
+ inputComponent.value = '';
+ this.addLog(`${emoji} Also set component.value`);
+ }
+
+ } else if (inputComponent?.value !== undefined) {
+ inputComponent.value = '';
+ this.addLog(`${emoji} Set via component.value`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not clear - unknown component structure`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error clearing input: ${error}`);
+ }
+ }
+
+ setValid(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} ✅ Valid button clicked`);
+ try {
+ const validDate = '2024-12-25';
+ if (inputComponent?.nativeElement) {
+ inputComponent.nativeElement.value = validDate;
+ this.addLog(`${emoji} Set via nativeElement.value: ${validDate}`);
+ } else if (inputComponent?.value !== undefined) {
+ inputComponent.value = validDate;
+ this.addLog(`${emoji} Set via component.value: ${validDate}`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not set valid date - unknown component structure`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error setting valid date: ${error}`);
+ }
+ }
+
+ setInvalid(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} ❌ Invalid button clicked`);
+ try {
+ const invalidDate = 'invalid-date';
+ if (inputComponent?.nativeElement) {
+ inputComponent.nativeElement.value = invalidDate;
+ this.addLog(`${emoji} Set via nativeElement.value: ${invalidDate}`);
+ } else if (inputComponent?.value !== undefined) {
+ inputComponent.value = invalidDate;
+ this.addLog(`${emoji} Set via component.value: ${invalidDate}`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not set invalid date - unknown component structure`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error setting invalid date: ${error}`);
+ }
+ }
+
+ triggerManualBlur(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} 🔶 Manual blur button clicked`);
+ try {
+ if (inputComponent?.nativeElement?.blur) {
+ inputComponent.nativeElement.blur();
+ this.addLog(`${emoji} Blur triggered via nativeElement.blur()`);
+ } else if (inputComponent?.blur) {
+ inputComponent.blur();
+ this.addLog(`${emoji} Blur triggered via component.blur()`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not trigger blur - no blur method found`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error triggering blur: ${error}`);
+ }
+ }
+
+ clearInput(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} 🧹 Clear button clicked`);
+
+ try {
+ // Method 1: Try to call the IX component's internal clear method first
+ if (inputComponent && typeof inputComponent.clear === 'function') {
+ inputComponent.clear();
+ this.addLog(`${emoji} ✅ Called IX component.clear() method`);
+ } else {
+ this.addLog(`${emoji} ⚠️ No clear() method found on IX component`);
+ }
+
+ // Method 2: Try calling clear on the native element if it exists
+ const element = inputComponent?.nativeElement;
+ if (element && typeof element.clear === 'function') {
+ element.clear();
+ this.addLog(`${emoji} ✅ Called nativeElement.clear() method`);
+ } else {
+ this.addLog(`${emoji} ⚠️ No clear() method found on native element`);
+ }
+
+ // Method 3: Fallback to setEmpty which sets value and triggers events
+ this.setEmpty(inputComponent, emoji);
+
+ // Method 4: Force validation update by triggering focus and blur
+ setTimeout(() => {
+ if (element && element.focus && element.blur) {
+ element.focus();
+ setTimeout(() => element.blur(), 10);
+ this.addLog(`${emoji} 🔄 Triggered focus/blur for validation update`);
+ }
+ }, 20);
+
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error in clearInput: ${error}`);
+ // Fallback to setEmpty if there's an error
+ this.setEmpty(inputComponent, emoji);
+ }
+ }
+
+ getLogColor(log: string): string {
+ if (log.includes('❌')) return '#f44336';
+ if (log.includes('✅')) return '#4caf50';
+ if (log.includes('🔍')) return '#2196f3';
+ if (log.includes('😴')) return '#ff9800';
+ if (log.includes('⚠️')) return '#ff5722';
+ return '#333';
+ }
+
+ setDateRange(): void {
+ this.addLog('4️⃣ 📅 Setting date range (2024-01-01 to 2024-12-31)');
+ try {
+ if (this.rangeStartInput?.nativeElement) {
+ this.rangeStartInput.nativeElement.value = '2024-01-01';
+ this.addLog('4️⃣ Start date set to January 1, 2024');
+ }
+ if (this.rangeEndInput?.nativeElement) {
+ this.rangeEndInput.nativeElement.value = '2024-12-31';
+ this.addLog('4️⃣ End date set to December 31, 2024');
+ }
+ } catch (error) {
+ this.addLog(`4️⃣ ❌ Error setting date range: ${error}`);
+ }
+ }
+
+ setToday(): void {
+ this.addLog('4️⃣ 📅 Setting to today\'s date');
+ try {
+ const today = new Date().toISOString().split('T')[0]; // Gets YYYY-MM-DD
+ if (this.rangeStartInput?.nativeElement) {
+ this.rangeStartInput.nativeElement.value = today;
+ this.addLog(`4️⃣ Today's date set: ${today}`);
+ }
+ } catch (error) {
+ this.addLog(`4️⃣ ❌ Error setting today's date: ${error}`);
+ }
+ }
+}
diff --git a/packages/angular-test-app/src/preview-examples/time-input-form-test.html b/packages/angular-test-app/src/preview-examples/time-input-form-test.html
new file mode 100644
index 00000000000..9a083ec2efc
--- /dev/null
+++ b/packages/angular-test-app/src/preview-examples/time-input-form-test.html
@@ -0,0 +1,229 @@
+
+
+
+
Time Input Validation Tests
+
+ Testing: Required field validation (red border) after blur. Format: HH:MM:SS
+
+
+
+
+
+
+
+
+
1️⃣ Required (Standalone)
+
+
+
+
+
+
+
+
+
+
+
+
+
2️⃣ Optional (Standalone)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
4️⃣ Time Range
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
📋 Event Logs
+
+
+ No events yet. Interact with the time inputs above to see logs here.
+
+
+ {{ log }}
+
+
+
+
diff --git a/packages/angular-test-app/src/preview-examples/time-input-form-test.ts b/packages/angular-test-app/src/preview-examples/time-input-form-test.ts
new file mode 100644
index 00000000000..dc16b547503
--- /dev/null
+++ b/packages/angular-test-app/src/preview-examples/time-input-form-test.ts
@@ -0,0 +1,268 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Siemens AG
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+
+@Component({
+ standalone: false,
+ selector: 'app-example',
+ templateUrl: './time-input-form-test.html',
+})
+export default class TimeInputFormTest implements AfterViewInit {
+ @ViewChild('requiredInput') requiredInput!: ElementRef;
+ @ViewChild('optionalInput') optionalInput!: ElementRef;
+ @ViewChild('formRequiredInput') formRequiredInput!: ElementRef;
+ @ViewChild('rangeStartInput') rangeStartInput!: ElementRef;
+ @ViewChild('rangeEndInput') rangeEndInput!: ElementRef;
+
+ logs: string[] = [];
+ testForm: FormGroup;
+
+ constructor(private readonly fb: FormBuilder) {
+ this.testForm = this.fb.group({
+ requiredTime: ['', Validators.required],
+ optionalTime: ['']
+ });
+ }
+
+ ngAfterViewInit(): void {
+ this.addLog('🔧 Component initialized with ViewChild references');
+ }
+
+ onValueChange(event: CustomEvent, emoji: string): void {
+ const value = event.detail;
+ this.addLog(`${emoji} Value changed: ${value || 'empty'}`);
+ }
+
+ onFocus(emoji: string): void {
+ this.addLog(`${emoji} 🔍 Focus`);
+ }
+
+ onBlur(emoji: string): void {
+ this.addLog(`${emoji} 😴 Blur`);
+ }
+
+ onFormSubmit(event: Event, emoji: string): void {
+ event.preventDefault();
+ this.addLog(`${emoji} Form submitted`);
+ this.addLog(`Form valid: ${this.testForm.valid}`);
+ this.addLog(`Form values: ${JSON.stringify(this.testForm.value)}`);
+ }
+
+ addLog(message: string): void {
+ const timestamp = new Date().toLocaleTimeString();
+ this.logs.push(`[${timestamp}] ${message}`);
+ if (this.logs.length > 50) {
+ this.logs = this.logs.slice(-50);
+ }
+ }
+
+ clearLogs(): void {
+ this.addLog('🧹 Clear logs button clicked');
+ this.logs = [];
+ }
+
+ setEmpty(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} 🔘 Empty button clicked`);
+ try {
+ const element = inputComponent?.nativeElement;
+
+ if (element) {
+ // Method 1: Set value and dispatch events to trigger validation
+ element.value = '';
+
+ // Trigger input event to notify the component of value change
+ element.dispatchEvent(new Event('input', { bubbles: true }));
+
+ // Trigger change event for additional validation clearing
+ element.dispatchEvent(new Event('change', { bubbles: true }));
+
+ // Trigger blur to ensure validation state updates
+ setTimeout(() => {
+ element.dispatchEvent(new Event('blur', { bubbles: true }));
+ }, 10);
+
+ this.addLog(`${emoji} Cleared via nativeElement with events`);
+
+ // Method 2: Try component-specific clear method if available
+ if (inputComponent.clear && typeof inputComponent.clear === 'function') {
+ inputComponent.clear();
+ this.addLog(`${emoji} Also called component.clear()`);
+ }
+
+ // Method 3: Try setting component value property
+ if (inputComponent.value !== undefined) {
+ inputComponent.value = '';
+ this.addLog(`${emoji} Also set component.value`);
+ }
+
+ } else if (inputComponent?.value !== undefined) {
+ inputComponent.value = '';
+ this.addLog(`${emoji} Set via component.value`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not clear - unknown component structure`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error clearing input: ${error}`);
+ }
+ }
+
+ setValid(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} ✅ Valid button clicked`);
+ try {
+ const validTime = '14:30:00';
+ if (inputComponent?.nativeElement) {
+ inputComponent.nativeElement.value = validTime;
+ this.addLog(`${emoji} Set via nativeElement.value: ${validTime}`);
+ } else if (inputComponent?.value !== undefined) {
+ inputComponent.value = validTime;
+ this.addLog(`${emoji} Set via component.value: ${validTime}`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not set valid time - unknown component structure`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error setting valid time: ${error}`);
+ }
+ }
+
+ setInvalid(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} ❌ Invalid button clicked`);
+ try {
+ const invalidTime = '25:99:99';
+ if (inputComponent?.nativeElement) {
+ inputComponent.nativeElement.value = invalidTime;
+ this.addLog(`${emoji} Set via nativeElement.value: ${invalidTime}`);
+ } else if (inputComponent?.value !== undefined) {
+ inputComponent.value = invalidTime;
+ this.addLog(`${emoji} Set via component.value: ${invalidTime}`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not set invalid time - unknown component structure`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error setting invalid time: ${error}`);
+ }
+ }
+
+ triggerManualBlur(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} 🔶 Manual blur button clicked`);
+ try {
+ if (inputComponent?.nativeElement?.blur) {
+ inputComponent.nativeElement.blur();
+ this.addLog(`${emoji} Blur triggered via nativeElement.blur()`);
+ } else if (inputComponent?.blur) {
+ inputComponent.blur();
+ this.addLog(`${emoji} Blur triggered via component.blur()`);
+ } else {
+ this.addLog(`${emoji} ❌ Could not trigger blur - no blur method found`);
+ console.log('Input component:', inputComponent);
+ }
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error triggering blur: ${error}`);
+ }
+ }
+
+ clearInput(inputComponent: any, emoji: string): void {
+ this.addLog(`${emoji} 🧹 Clear button clicked`);
+
+ try {
+ // Method 1: Try to call the IX component's internal clear method first
+ if (inputComponent && typeof inputComponent.clear === 'function') {
+ inputComponent.clear();
+ this.addLog(`${emoji} ✅ Called IX component.clear() method`);
+ } else {
+ this.addLog(`${emoji} ⚠️ No clear() method found on IX component`);
+ }
+
+ // Method 2: Try calling clear on the native element if it exists
+ const element = inputComponent?.nativeElement;
+ if (element && typeof element.clear === 'function') {
+ element.clear();
+ this.addLog(`${emoji} ✅ Called nativeElement.clear() method`);
+ } else {
+ this.addLog(`${emoji} ⚠️ No clear() method found on native element`);
+ }
+
+ // Method 3: Fallback to setEmpty which sets value and triggers events
+ this.setEmpty(inputComponent, emoji);
+
+ // Method 4: Force validation update by triggering focus and blur
+ setTimeout(() => {
+ if (element && element.focus && element.blur) {
+ element.focus();
+ setTimeout(() => element.blur(), 10);
+ this.addLog(`${emoji} 🔄 Triggered focus/blur for validation update`);
+ }
+ }, 20);
+
+ } catch (error) {
+ this.addLog(`${emoji} ❌ Error in clearInput: ${error}`);
+ // Fallback to setEmpty if there's an error
+ this.setEmpty(inputComponent, emoji);
+ }
+ }
+
+ setTimeRange(): void {
+ this.addLog('4️⃣ ⏰ Setting work hours (9:00 - 17:00)');
+ try {
+ if (this.rangeStartInput?.nativeElement) {
+ this.rangeStartInput.nativeElement.value = '09:00:00';
+ this.addLog('4️⃣ Start time set to 9:00 AM');
+ }
+ if (this.rangeEndInput?.nativeElement) {
+ this.rangeEndInput.nativeElement.value = '17:00:00';
+ this.addLog('4️⃣ End time set to 5:00 PM');
+ }
+ } catch (error) {
+ this.addLog(`4️⃣ ❌ Error setting work hours: ${error}`);
+ }
+ }
+
+ setMeetingTimes(): void {
+ this.addLog('4️⃣ 📅 Setting meeting times (14:00 - 15:30)');
+ try {
+ if (this.rangeStartInput?.nativeElement) {
+ this.rangeStartInput.nativeElement.value = '14:00:00';
+ this.addLog('4️⃣ Meeting start: 2:00 PM');
+ }
+ if (this.rangeEndInput?.nativeElement) {
+ this.rangeEndInput.nativeElement.value = '15:30:00';
+ this.addLog('4️⃣ Meeting end: 3:30 PM');
+ }
+ } catch (error) {
+ this.addLog(`4️⃣ ❌ Error setting meeting times: ${error}`);
+ }
+ }
+
+ setCurrentTime(): void {
+ this.addLog('4️⃣ 🕐 Setting current time');
+ try {
+ const now = new Date();
+ const timeString = now.toTimeString().split(' ')[0]; // Gets HH:MM:SS
+ if (this.rangeStartInput?.nativeElement) {
+ this.rangeStartInput.nativeElement.value = timeString;
+ this.addLog(`4️⃣ Current time set: ${timeString}`);
+ }
+ } catch (error) {
+ this.addLog(`4️⃣ ❌ Error setting current time: ${error}`);
+ }
+ }
+
+ getLogColor(log: string): string {
+ if (log.includes('❌')) return '#f44336';
+ if (log.includes('✅')) return '#4caf50';
+ if (log.includes('🔍')) return '#2196f3';
+ if (log.includes('😴')) return '#ff9800';
+ if (log.includes('⚠️')) return '#ff5722';
+ return '#333';
+ }
+}
\ No newline at end of file
diff --git a/packages/angular/src/components.ts b/packages/angular/src/components.ts
index 34908ffa1f2..2afc815a317 100644
--- a/packages/angular/src/components.ts
+++ b/packages/angular/src/components.ts
@@ -629,7 +629,7 @@ The event payload contains information about the selected date range.
@ProxyCmp({
inputs: ['ariaLabelCalendarButton', 'ariaLabelNextMonthButton', 'ariaLabelPreviousMonthButton', 'disabled', 'format', 'helperText', 'i18nErrorDateUnparsable', 'infoText', 'invalidText', 'label', 'locale', 'maxDate', 'minDate', 'name', 'placeholder', 'readonly', 'required', 'showTextAsTooltip', 'showWeekNumbers', 'suppressSubmitOnEnter', 'textAlignment', 'validText', 'value', 'warningText', 'weekStartIndex'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'focusInput', 'clear']
})
@Component({
selector: 'ix-date-input',
@@ -1326,7 +1326,7 @@ export declare interface IxIconToggleButton extends Components.IxIconToggleButto
@ProxyCmp({
inputs: ['allowedCharactersPattern', 'disabled', 'helperText', 'infoText', 'invalidText', 'label', 'maxLength', 'minLength', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'showTextAsTooltip', 'suppressSubmitOnEnter', 'textAlignment', 'type', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'getValidityState', 'focusInput']
+ methods: ['getNativeInputElement', 'getValidityState', 'focusInput', 'reset']
})
@Component({
selector: 'ix-input',
@@ -2003,7 +2003,7 @@ Can be prevented, in which case only the event is triggered, and the modal remai
@ProxyCmp({
inputs: ['allowEmptyValueChange', 'allowedCharactersPattern', 'disabled', 'helperText', 'infoText', 'invalidText', 'label', 'max', 'min', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'showStepperButtons', 'showTextAsTooltip', 'step', 'suppressSubmitOnEnter', 'textAlignment', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'focusInput', 'reset']
})
@Component({
selector: 'ix-number-input',
@@ -2526,7 +2526,7 @@ export declare interface IxTabs extends Components.IxTabs {
@ProxyCmp({
inputs: ['disabled', 'helperText', 'infoText', 'invalidText', 'label', 'maxLength', 'minLength', 'name', 'placeholder', 'readonly', 'required', 'resizeBehavior', 'showTextAsTooltip', 'textareaCols', 'textareaHeight', 'textareaRows', 'textareaWidth', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'getValidityState', 'focusInput', 'reset']
})
@Component({
selector: 'ix-textarea',
@@ -2590,7 +2590,7 @@ export declare interface IxTile extends Components.IxTile {}
@ProxyCmp({
inputs: ['disabled', 'format', 'helperText', 'hideHeader', 'hourInterval', 'i18nErrorTimeUnparsable', 'i18nHourColumnHeader', 'i18nMillisecondColumnHeader', 'i18nMinuteColumnHeader', 'i18nSecondColumnHeader', 'i18nSelectTime', 'i18nTime', 'infoText', 'invalidText', 'label', 'millisecondInterval', 'minuteInterval', 'name', 'placeholder', 'readonly', 'required', 'secondInterval', 'showTextAsTooltip', 'suppressSubmitOnEnter', 'textAlignment', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'focusInput', 'clear']
})
@Component({
selector: 'ix-time-input',
diff --git a/packages/angular/standalone/src/components.ts b/packages/angular/standalone/src/components.ts
index b395b030597..a7b76861fcf 100644
--- a/packages/angular/standalone/src/components.ts
+++ b/packages/angular/standalone/src/components.ts
@@ -730,7 +730,7 @@ The event payload contains information about the selected date range.
@ProxyCmp({
defineCustomElementFn: defineIxDateInput,
inputs: ['ariaLabelCalendarButton', 'ariaLabelNextMonthButton', 'ariaLabelPreviousMonthButton', 'disabled', 'format', 'helperText', 'i18nErrorDateUnparsable', 'infoText', 'invalidText', 'label', 'locale', 'maxDate', 'minDate', 'name', 'placeholder', 'readonly', 'required', 'showTextAsTooltip', 'showWeekNumbers', 'suppressSubmitOnEnter', 'textAlignment', 'validText', 'value', 'warningText', 'weekStartIndex'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'focusInput', 'clear']
})
@Component({
selector: 'ix-date-input',
@@ -1427,7 +1427,7 @@ export declare interface IxIconToggleButton extends Components.IxIconToggleButto
@ProxyCmp({
defineCustomElementFn: defineIxInput,
inputs: ['allowedCharactersPattern', 'disabled', 'helperText', 'infoText', 'invalidText', 'label', 'maxLength', 'minLength', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'showTextAsTooltip', 'suppressSubmitOnEnter', 'textAlignment', 'type', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'getValidityState', 'focusInput']
+ methods: ['getNativeInputElement', 'getValidityState', 'focusInput', 'reset']
})
@Component({
selector: 'ix-input',
@@ -2104,7 +2104,7 @@ Can be prevented, in which case only the event is triggered, and the modal remai
@ProxyCmp({
defineCustomElementFn: defineIxNumberInput,
inputs: ['allowEmptyValueChange', 'allowedCharactersPattern', 'disabled', 'helperText', 'infoText', 'invalidText', 'label', 'max', 'min', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'showStepperButtons', 'showTextAsTooltip', 'step', 'suppressSubmitOnEnter', 'textAlignment', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'focusInput', 'reset']
})
@Component({
selector: 'ix-number-input',
@@ -2627,7 +2627,7 @@ export declare interface IxTabs extends Components.IxTabs {
@ProxyCmp({
defineCustomElementFn: defineIxTextarea,
inputs: ['disabled', 'helperText', 'infoText', 'invalidText', 'label', 'maxLength', 'minLength', 'name', 'placeholder', 'readonly', 'required', 'resizeBehavior', 'showTextAsTooltip', 'textareaCols', 'textareaHeight', 'textareaRows', 'textareaWidth', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'getValidityState', 'focusInput', 'reset']
})
@Component({
selector: 'ix-textarea',
@@ -2691,7 +2691,7 @@ export declare interface IxTile extends Components.IxTile {}
@ProxyCmp({
defineCustomElementFn: defineIxTimeInput,
inputs: ['disabled', 'format', 'helperText', 'hideHeader', 'hourInterval', 'i18nErrorTimeUnparsable', 'i18nHourColumnHeader', 'i18nMillisecondColumnHeader', 'i18nMinuteColumnHeader', 'i18nSecondColumnHeader', 'i18nSelectTime', 'i18nTime', 'infoText', 'invalidText', 'label', 'millisecondInterval', 'minuteInterval', 'name', 'placeholder', 'readonly', 'required', 'secondInterval', 'showTextAsTooltip', 'suppressSubmitOnEnter', 'textAlignment', 'validText', 'value', 'warningText'],
- methods: ['getNativeInputElement', 'focusInput']
+ methods: ['getNativeInputElement', 'focusInput', 'clear']
})
@Component({
selector: 'ix-time-input',
diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts
index c3ee619d03c..a34fd78973a 100644
--- a/packages/core/src/components.d.ts
+++ b/packages/core/src/components.d.ts
@@ -943,6 +943,10 @@ export namespace Components {
* ARIA label for the previous month icon button Will be set as aria-label on the nested HTML button element
*/
"ariaLabelPreviousMonthButton"?: string;
+ /**
+ * Clears the input field value and resets validation state. Sets the value to empty and removes touched state to suppress validation.
+ */
+ "clear": () => Promise;
/**
* Disabled attribute
* @default false
@@ -1969,6 +1973,10 @@ export namespace Components {
* @default false
*/
"required": boolean;
+ /**
+ * Resets the input field validation state by removing the touched state and clearing validation states while preserving the current value.
+ */
+ "reset": () => Promise;
/**
* Specifies whether to show the text as a tooltip.
*/
@@ -2580,6 +2588,10 @@ export namespace Components {
* @default false
*/
"required": boolean;
+ /**
+ * Resets the input field validation state by removing the touched state and clearing validation states while preserving the current value.
+ */
+ "reset": () => Promise;
/**
* Indicates if the stepper buttons should be shown
*/
@@ -3357,6 +3369,10 @@ export namespace Components {
* Get the native textarea element.
*/
"getNativeInputElement": () => Promise;
+ /**
+ * Returns the validity state of the textarea field.
+ */
+ "getValidityState": () => Promise;
"hasValidValue": () => Promise;
/**
* The helper text for the textarea field.
@@ -3404,6 +3420,10 @@ export namespace Components {
* @default false
*/
"required": boolean;
+ /**
+ * Resets the input field validation state by removing the touched state and clearing validation states while preserving the current value.
+ */
+ "reset": () => Promise;
/**
* Determines the resize behavior of the textarea field. Resizing can be enabled in one direction, both directions or completely disabled.
* @default 'both'
@@ -3455,6 +3475,10 @@ export namespace Components {
* @form-ready
*/
interface IxTimeInput {
+ /**
+ * Clears the input field value and resets validation state. Sets the value to empty and removes touched state to suppress validation.
+ */
+ "clear": () => Promise;
/**
* Disabled attribute
* @default false
diff --git a/packages/core/src/components/date-input/date-input.tsx b/packages/core/src/components/date-input/date-input.tsx
index fc46c3d8ed5..b4fe80d4c29 100644
--- a/packages/core/src/components/date-input/date-input.tsx
+++ b/packages/core/src/components/date-input/date-input.tsx
@@ -14,7 +14,6 @@ import {
Element,
Event,
EventEmitter,
- Host,
Method,
Prop,
State,
@@ -27,7 +26,6 @@ import {
DisposableChangesAndVisibilityObservers,
addDisposableChangesAndVisibilityObservers,
adjustPaddingForStartAndEnd,
- handleSubmitOnEnterKeydown,
} from '../input/input.util';
import {
ClassMutationObserver,
@@ -36,6 +34,21 @@ import {
ValidationResults,
createClassMutationObserver,
getValidationText,
+ shouldSuppressInternalValidation,
+ syncState,
+ watchValue,
+ onInput,
+ onClick,
+ onFocus,
+ onBlur,
+ onKeyDown,
+ getNativeInput,
+ renderFieldWrapper,
+ createEventConfig,
+ createInputMethods,
+ createDropdownMethods,
+ createKeyDownHandler,
+ handleValidationLifecycle,
} from '../utils/input';
import { makeRef } from '../utils/make-ref';
import type { DateInputValidityState } from './date-input.types';
@@ -78,7 +91,13 @@ export class DateInput implements IxInputFieldComponent {
@Prop({ reflect: true, mutable: true }) value?: string = '';
@Watch('value') watchValuePropHandler(newValue: string) {
- this.onInput(newValue);
+ watchValue({
+ newValue,
+ required: this.required,
+ onInput: (value: string) => void this.onInput(value),
+ setTouched: (touched: boolean) => (this.touched = touched),
+ isClearing: this._isClearing,
+ });
}
/**
@@ -113,6 +132,11 @@ export class DateInput implements IxInputFieldComponent {
*/
@Prop() required?: boolean;
+ @Watch('required')
+ onRequiredChange() {
+ this.syncValidationClasses();
+ }
+
/**
* Helper text below the input field
*/
@@ -232,6 +256,7 @@ export class DateInput implements IxInputFieldComponent {
@State() isInfo = false;
@State() isWarning = false;
@State() focus = false;
+ @State() suppressValidation = false;
private readonly slotStartRef = makeRef();
private readonly slotEndRef = makeRef();
@@ -243,6 +268,7 @@ export class DateInput implements IxInputFieldComponent {
private classObserver?: ClassMutationObserver;
private invalidReason?: string;
private touched = false;
+ private _isClearing = false;
private disposableChangesAndVisibilityObservers?: DisposableChangesAndVisibilityObservers;
@@ -300,6 +326,10 @@ export class DateInput implements IxInputFieldComponent {
/** @internal */
@Method()
hasValidValue(): Promise {
+ if (!this.required) {
+ return Promise.resolve(true);
+ }
+
return Promise.resolve(!!this.value);
}
@@ -311,8 +341,12 @@ export class DateInput implements IxInputFieldComponent {
async onInput(value: string | undefined) {
this.value = value;
+ this.suppressValidation = await shouldSuppressInternalValidation(this);
+
if (!value) {
- this.valueChange.emit(value);
+ this.isInputInvalid = false;
+ this.invalidReason = undefined;
+ this.emitChangesAndSync(value);
return;
}
@@ -320,21 +354,53 @@ export class DateInput implements IxInputFieldComponent {
return;
}
+ if (this.suppressValidation) {
+ this.isInputInvalid = false;
+ this.invalidReason = undefined;
+ this.handleValidInput(value);
+ return;
+ }
+
const date = DateTime.fromFormat(value, this.format);
const minDate = DateTime.fromFormat(this.minDate, this.format);
const maxDate = DateTime.fromFormat(this.maxDate, this.format);
- this.isInputInvalid = !date.isValid || date < minDate || date > maxDate;
+ const isDateInvalid = !date.isValid || date < minDate || date > maxDate;
+ this.isInputInvalid = isDateInvalid;
+ this.invalidReason = isDateInvalid
+ ? date.invalidReason || undefined
+ : undefined;
- if (this.isInputInvalid) {
- this.invalidReason = date.invalidReason || undefined;
- this.from = undefined;
+ if (isDateInvalid) {
+ this.handleInvalidInput(value);
} else {
- this.updateFormInternalValue(value);
- this.closeDropdown();
+ this.handleValidInput(value);
}
+ }
+
+ private emitChangesAndSync(value: string | undefined): void {
+ syncState({
+ updateFormInternalValue: (val) => this.updateFormInternalValue(val),
+ valueChange: this.valueChange,
+ value: value,
+ hostElement: this.hostElement,
+ suppressValidation: this.suppressValidation,
+ required: this.required,
+ touched: this.touched,
+ isInputInvalid: this.isInputInvalid,
+ });
+ }
- this.valueChange.emit(value);
+ private handleValidInput(value: string | undefined): void {
+ this.from = value;
+ this.closeDropdown();
+ this.emitChangesAndSync(value);
+ }
+
+ private handleInvalidInput(value: string | undefined): void {
+ this.touched = true;
+ this.from = undefined;
+ this.emitChangesAndSync(value);
}
onCalenderClick(event: Event) {
@@ -355,18 +421,34 @@ export class DateInput implements IxInputFieldComponent {
}
private checkClassList() {
- this.isInvalid = this.hostElement.classList.contains('ix-invalid');
+ this.isInvalid = this.dropdownMethods.checkClassList();
+ }
+
+ private getEventConfig() {
+ return createEventConfig({
+ show: this.show,
+ setTouched: (touched: boolean) => (this.touched = touched),
+ onInput: (value: string) => void this.onInput(value),
+ openDropdown: () => this.openDropdown(),
+ ixFocus: this.ixFocus,
+ ixBlur: this.ixBlur,
+ syncValidationClasses: () => this.syncValidationClasses(),
+ handleInputKeyDown: (event: KeyboardEvent) =>
+ this.handleInputKeyDown(event),
+ alwaysSetTouchedOnBlur: true,
+ });
}
private handleInputKeyDown(event: KeyboardEvent) {
- handleSubmitOnEnterKeydown(
- event,
+ return createKeyDownHandler(
this.suppressSubmitOnEnter,
- this.formInternals.form
- );
+ this.formInternals
+ )(event);
}
private renderInput() {
+ const eventConfig = this.getEventConfig();
+
return (