From 8399ba2afaecf39e918c7e28c78ba0464e84200e Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Sat, 15 Nov 2025 17:17:16 +0300 Subject: [PATCH 1/4] Improve focus handling for form field visibility Refactored focus logic to ensure the field is only focused when visible. Added IntersectionObserver to handle cases where the element is not immediately visible, improving accessibility and user experience. --- .../extensible-form-prop.component.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts index 401bd15c3d..6599647f61 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -155,10 +155,35 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { ngAfterViewInit() { if (this.isFirstGroup && this.first && this.fieldRef) { - this.fieldRef.nativeElement.focus(); + this.focusField(); } } + private focusField() { + const element = this.fieldRef.nativeElement; + + if (this.isElementVisible(element)) { + element.focus(); + return; + } + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && entry.intersectionRatio > 0) { + element.focus(); + observer.disconnect(); + } + }); + }, { threshold: 0.01 }); + + observer.observe(element); + setTimeout(() => observer.disconnect(), 5000); + } + + private isElementVisible(element: HTMLElement): boolean { + return element.offsetParent !== null; + } + getComponent(prop: FormProp): string { return this.service.getComponent(prop); } From 47fec83ae1c95fc918e27e422e0d406aae253734 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Mon, 17 Nov 2025 11:09:07 +0300 Subject: [PATCH 2/4] Simplify initial field focus logic in form prop component Replaces the IntersectionObserver-based focus logic with a simpler requestAnimationFrame approach for focusing the first field. This reduces complexity and potential timing issues when focusing the field after view initialization. --- .../extensible-form-prop.component.ts | 29 ++----------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts index 6599647f61..ca567d0511 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-form/extensible-form-prop.component.ts @@ -155,33 +155,10 @@ export class ExtensibleFormPropComponent implements OnChanges, AfterViewInit { ngAfterViewInit() { if (this.isFirstGroup && this.first && this.fieldRef) { - this.focusField(); - } - } - - private focusField() { - const element = this.fieldRef.nativeElement; - - if (this.isElementVisible(element)) { - element.focus(); - return; - } - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting && entry.intersectionRatio > 0) { - element.focus(); - observer.disconnect(); - } + requestAnimationFrame(() => { + this.fieldRef.nativeElement.focus(); }); - }, { threshold: 0.01 }); - - observer.observe(element); - setTimeout(() => observer.disconnect(), 5000); - } - - private isElementVisible(element: HTMLElement): boolean { - return element.offsetParent !== null; + } } getComponent(prop: FormProp): string { From 212290bf5f4b27588d9a799eb361991ebc7b2f26 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Mon, 17 Nov 2025 12:38:27 +0300 Subject: [PATCH 3/4] Add custom i18n adapter for ng-bootstrap datepicker Introduced DatepickerI18nAdapter to provide localized labels for the ng-bootstrap datepicker using Angular's locale and ABP config state. Registered the adapter as the provider for NgbDatepickerI18n and updated the adapters index export. --- .../lib/adapters/datepicker-i18n.adapter.ts | 38 +++++++++++++++++++ .../theme-shared/src/lib/adapters/index.ts | 1 + .../providers/ng-bootstrap-config.provider.ts | 7 +++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/adapters/datepicker-i18n.adapter.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/adapters/datepicker-i18n.adapter.ts b/npm/ng-packs/packages/theme-shared/src/lib/adapters/datepicker-i18n.adapter.ts new file mode 100644 index 0000000000..c2d4cb90ba --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/adapters/datepicker-i18n.adapter.ts @@ -0,0 +1,38 @@ +import { formatDate } from '@angular/common'; +import { inject, Injectable, LOCALE_ID } from '@angular/core'; +import { NgbDatepickerI18n, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; +import { ConfigStateService } from '@abp/ng.core'; + +@Injectable() +export class DatepickerI18nAdapter extends NgbDatepickerI18n { + private configState = inject(ConfigStateService, { optional: true }); + private defaultLocale = inject(LOCALE_ID); + + private get locale(): string { + return this.configState?.getDeep('localization.currentCulture.cultureName') || this.defaultLocale; + } + + getWeekdayLabel(weekday: number): string { + const date = new Date(2017, 0, weekday + 1); // Monday = 1 + return formatDate(date, 'EEEEE', this.locale); + } + + getWeekLabel(): string { + return ''; + } + + getMonthShortName(month: number): string { + const date = new Date(2017, month - 1, 1); + return formatDate(date, 'MMM', this.locale); + } + + getMonthFullName(month: number): string { + const date = new Date(2017, month - 1, 1); + return formatDate(date, 'MMMM', this.locale); + } + + getDayAriaLabel(date: NgbDateStruct): string { + const d = new Date(date.year, date.month - 1, date.day); + return formatDate(d, 'fullDate', this.locale); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts index a8e37ee1d3..327d789cac 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts @@ -1,3 +1,4 @@ export * from './date-time.adapter'; export * from './date.adapter'; +export * from './datepicker-i18n.adapter'; export * from './time.adapter'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts index 11dc8f7360..86647227a8 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts @@ -1,7 +1,12 @@ import { inject, provideAppInitializer } from '@angular/core'; -import { NgbInputDatepickerConfig, NgbTypeaheadConfig } from '@ng-bootstrap/ng-bootstrap'; +import { NgbDatepickerI18n, NgbInputDatepickerConfig, NgbTypeaheadConfig } from '@ng-bootstrap/ng-bootstrap'; +import { DatepickerI18nAdapter } from '../adapters'; export const NG_BOOTSTRAP_CONFIG_PROVIDERS = [ + { + provide: NgbDatepickerI18n, + useClass: DatepickerI18nAdapter, + }, provideAppInitializer(() => { configureNgBootstrap(); }), From cf2065e1662752ee2ceee4c7d78b86af575ab5a1 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Tue, 18 Nov 2025 11:20:11 +0300 Subject: [PATCH 4/4] Add TimepickerI18nAdapter for ng-bootstrap timepicker Introduces TimepickerI18nAdapter to provide localized AM/PM labels for NgbTimepickerI18n. Updates providers and index exports to register and expose the new adapter. --- .../theme-shared/src/lib/adapters/index.ts | 1 + .../lib/adapters/timepicker-i18n.adapter.ts | 24 +++++++++++++++++++ .../providers/ng-bootstrap-config.provider.ts | 8 +++++-- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 npm/ng-packs/packages/theme-shared/src/lib/adapters/timepicker-i18n.adapter.ts diff --git a/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts index 327d789cac..d082509e0c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/adapters/index.ts @@ -2,3 +2,4 @@ export * from './date-time.adapter'; export * from './date.adapter'; export * from './datepicker-i18n.adapter'; export * from './time.adapter'; +export * from './timepicker-i18n.adapter'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/adapters/timepicker-i18n.adapter.ts b/npm/ng-packs/packages/theme-shared/src/lib/adapters/timepicker-i18n.adapter.ts new file mode 100644 index 0000000000..dd64488e92 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/adapters/timepicker-i18n.adapter.ts @@ -0,0 +1,24 @@ +import { formatDate } from '@angular/common'; +import { inject, Injectable, LOCALE_ID } from '@angular/core'; +import { NgbTimepickerI18n } from '@ng-bootstrap/ng-bootstrap'; +import { ConfigStateService } from '@abp/ng.core'; + +@Injectable() +export class TimepickerI18nAdapter extends NgbTimepickerI18n { + private configState = inject(ConfigStateService, { optional: true }); + private defaultLocale = inject(LOCALE_ID); + + private get locale(): string { + return this.configState?.getDeep('localization.currentCulture.cultureName') || this.defaultLocale; + } + + getMorningPeriod(): string { + const date = new Date(2000, 0, 1, 10, 0, 0); + return formatDate(date, 'a', this.locale); + } + + getAfternoonPeriod(): string { + const date = new Date(2000, 0, 1, 22, 0, 0); + return formatDate(date, 'a', this.locale); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts index 86647227a8..1006990f6a 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/ng-bootstrap-config.provider.ts @@ -1,12 +1,16 @@ import { inject, provideAppInitializer } from '@angular/core'; -import { NgbDatepickerI18n, NgbInputDatepickerConfig, NgbTypeaheadConfig } from '@ng-bootstrap/ng-bootstrap'; -import { DatepickerI18nAdapter } from '../adapters'; +import { NgbDatepickerI18n, NgbInputDatepickerConfig, NgbTypeaheadConfig, NgbTimepickerI18n } from '@ng-bootstrap/ng-bootstrap'; +import { DatepickerI18nAdapter, TimepickerI18nAdapter } from '../adapters'; export const NG_BOOTSTRAP_CONFIG_PROVIDERS = [ { provide: NgbDatepickerI18n, useClass: DatepickerI18nAdapter, }, + { + provide: NgbTimepickerI18n, + useClass: TimepickerI18nAdapter, + }, provideAppInitializer(() => { configureNgBootstrap(); }),