diff --git a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.component.ts b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.component.ts index ea10a4c11336a..c0bd9cee8a610 100644 --- a/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.component.ts +++ b/projects/addon-mobile/components/mobile-calendar-dialog/mobile-calendar-dialog.component.ts @@ -21,7 +21,7 @@ export class TuiMobileCalendarDialogComponent { constructor( @Inject(POLYMORPHEUS_CONTEXT) readonly context: TuiDialogContext< - TuiDay | TuiDayRange, + TuiDay | TuiDayRange | readonly TuiDay[], TuiMobileCalendarData | undefined >, ) {} diff --git a/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts b/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts index bafb95e9e99f0..cfde0c4b8fd75 100644 --- a/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts +++ b/projects/addon-mobile/components/mobile-calendar/mobile-calendar.component.ts @@ -38,6 +38,7 @@ import { TUI_CANCEL_WORD, TUI_CHOOSE_DAY_OR_RANGE_TEXTS, TUI_DONE_WORD, + tuiImmutableUpdateMultiDates, } from '@taiga-ui/kit'; import {identity, MonoTypeOperatorFunction, Observable, race, timer} from 'rxjs'; import { @@ -98,9 +99,9 @@ export class TuiMobileCalendarComponent implements AfterViewInit { readonly cancel = new EventEmitter(); @Output() - readonly confirm = new EventEmitter(); + readonly confirm = new EventEmitter(); - value: TuiDay | TuiDayRange | null = null; + value: TuiDay | TuiDayRange | readonly TuiDay[] | null = null; readonly years = Array.from({length: RANGE}, (_, i) => i + STARTING_YEAR); @@ -167,11 +168,13 @@ export class TuiMobileCalendarComponent implements AfterViewInit { return; } - if ( - this.value === null || - this.value instanceof TuiDay || - !this.value.isSingleDay - ) { + if (!(this.value instanceof TuiDayRange) && !(this.value instanceof TuiDay)) { + this.value = tuiImmutableUpdateMultiDates(this.value ?? [], day); + + return; + } + + if (this.value instanceof TuiDay || !this.value?.isSingleDay) { this.value = new TuiDayRange(day, day); return; @@ -242,6 +245,10 @@ export class TuiMobileCalendarComponent implements AfterViewInit { return this.value.year; } + if (!(this.value instanceof TuiDayRange)) { + return this.value?.[0].year ?? this.today.year; + } + return this.value.from.year; } @@ -254,6 +261,14 @@ export class TuiMobileCalendarComponent implements AfterViewInit { return this.value.month + (this.value.year - STARTING_YEAR) * MONTHS_IN_YEAR; } + if (!(this.value instanceof TuiDayRange)) { + return ( + (this.value?.[0].month ?? this.today.month) + + ((this.value?.[0].year ?? this.today.year) - STARTING_YEAR) * + MONTHS_IN_YEAR + ); + } + return ( this.value.from.month + (this.value.from.year - STARTING_YEAR) * MONTHS_IN_YEAR diff --git a/projects/demo/src/modules/app/app.routes.ts b/projects/demo/src/modules/app/app.routes.ts index d25a6924d8885..2f319c69971db 100644 --- a/projects/demo/src/modules/app/app.routes.ts +++ b/projects/demo/src/modules/app/app.routes.ts @@ -513,6 +513,15 @@ export const ROUTES: Routes = [ title: `InputDate`, }, }, + { + path: `components/input-multi-date`, + loadChildren: async () => + (await import(`../components/input-multi-date/input-multi-date.module`)) + .ExampleTuiInputMultiDateModule, + data: { + title: `InputMultiDate`, + }, + }, { path: `components/input-card`, loadChildren: async () => diff --git a/projects/demo/src/modules/app/pages.ts b/projects/demo/src/modules/app/pages.ts index 4bc069e2003d6..c01ffcb7388d9 100644 --- a/projects/demo/src/modules/app/pages.ts +++ b/projects/demo/src/modules/app/pages.ts @@ -412,6 +412,14 @@ export const pages: TuiDocPages = [ `неделя, месяц, год, дата, calendar`, route: `/components/input-date`, }, + { + section: `Components`, + title: `InputMultiDate`, + keywords: + `поле, инпут, форма, ввод, input, календарь, день, ` + + `неделя, месяц, год, дата, calendar, multiple`, + route: `/components/input-multi-date`, + }, { section: `Components`, title: `InputDateRange`, diff --git a/projects/demo/src/modules/components/input-multi-date/examples/1/index.html b/projects/demo/src/modules/components/input-multi-date/examples/1/index.html new file mode 100644 index 0000000000000..e0131b1bf28bc --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/1/index.html @@ -0,0 +1,9 @@ +
+ + Choose a dates + +
diff --git a/projects/demo/src/modules/components/input-multi-date/examples/1/index.ts b/projects/demo/src/modules/components/input-multi-date/examples/1/index.ts new file mode 100644 index 0000000000000..63584e2dbd474 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/1/index.ts @@ -0,0 +1,21 @@ +import {Component} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {encapsulation} from '@demo/emulate/encapsulation'; +import {TuiDay} from '@taiga-ui/cdk'; + +@Component({ + selector: 'tui-input-multi-date-example-1', + templateUrl: './index.html', + changeDetection, + encapsulation, +}) +export class TuiInputMultiDateExample1 { + readonly testForm = new FormGroup({ + testValue: new FormControl([ + new TuiDay(2017, 0, 7), + new TuiDay(2017, 0, 10), + new TuiDay(2017, 0, 15), + ]), + }); +} diff --git a/projects/demo/src/modules/components/input-multi-date/examples/2/index.html b/projects/demo/src/modules/components/input-multi-date/examples/2/index.html new file mode 100644 index 0000000000000..07880717b6a4f --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/2/index.html @@ -0,0 +1,12 @@ +
+ + Choose a dates + +
diff --git a/projects/demo/src/modules/components/input-multi-date/examples/2/index.ts b/projects/demo/src/modules/components/input-multi-date/examples/2/index.ts new file mode 100644 index 0000000000000..037e5f15cb703 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/2/index.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {encapsulation} from '@demo/emulate/encapsulation'; +import {TuiDay} from '@taiga-ui/cdk'; +import {tuiInputDateOptionsProvider} from '@taiga-ui/kit'; + +@Component({ + selector: 'tui-input-multi-date-example-2', + templateUrl: './index.html', + changeDetection, + encapsulation, + providers: [tuiInputDateOptionsProvider({nativePicker: true})], +}) +export class TuiInputMultiDateExample2 { + readonly testForm = new FormGroup({ + testValue: new FormControl([new TuiDay(2017, 0, 15)]), + }); +} diff --git a/projects/demo/src/modules/components/input-multi-date/examples/import/declare-form.md b/projects/demo/src/modules/components/input-multi-date/examples/import/declare-form.md new file mode 100644 index 0000000000000..d5bd0c6887575 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/import/declare-form.md @@ -0,0 +1,12 @@ +```ts +import {FormControl, FormGroup} from '@angular/forms'; + +@Component({ + // ... +}) +export class MyComponent { + testForm = new FormGroup({ + testValue: new FormControl([]), + }); +} +``` diff --git a/projects/demo/src/modules/components/input-multi-date/examples/import/import-module.md b/projects/demo/src/modules/components/input-multi-date/examples/import/import-module.md new file mode 100644 index 0000000000000..8cdbc4a3936f4 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/import/import-module.md @@ -0,0 +1,14 @@ +```ts +import {ReactiveFormsModule} from '@angular/forms'; +import {TuiInputDateModule} from '@taiga-ui/kit'; + +@NgModule({ + imports: [ + // ... + ReactiveFormsModule, + TuiInputDateModule, + ], + // ... +}) +export class MyModule {} +``` diff --git a/projects/demo/src/modules/components/input-multi-date/examples/import/insert-template.md b/projects/demo/src/modules/components/input-multi-date/examples/import/insert-template.md new file mode 100644 index 0000000000000..1e5aa6ec75e43 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/examples/import/insert-template.md @@ -0,0 +1,10 @@ +```html +
+ + Choose a date + +
+``` diff --git a/projects/demo/src/modules/components/input-multi-date/input-multi-date.component.ts b/projects/demo/src/modules/components/input-multi-date/input-multi-date.component.ts new file mode 100644 index 0000000000000..159187210bbed --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/input-multi-date.component.ts @@ -0,0 +1,97 @@ +import {Component, forwardRef} from '@angular/core'; +import {FormControl, Validators} from '@angular/forms'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {TuiDocExample} from '@taiga-ui/addon-doc'; +import { + ALWAYS_FALSE_HANDLER, + TUI_FIRST_DAY, + TUI_LAST_DAY, + TuiBooleanHandler, + TuiDay, +} from '@taiga-ui/cdk'; +import {TUI_DEFAULT_MARKER_HANDLER, TuiMarkerHandler} from '@taiga-ui/core'; +import {TuiNamedDay} from '@taiga-ui/kit'; + +import {AbstractExampleTuiControl} from '../abstract/control'; +import {ABSTRACT_PROPS_ACCESSOR} from '../abstract/inherited-documentation/abstract-props-accessor'; + +const TWO_DOTS: [string, string] = ['var(--tui-primary)', 'var(--tui-info-fill)']; +const ONE_DOT: [string] = ['var(--tui-success-fill)']; + +@Component({ + selector: 'example-tui-input-multi-date', + templateUrl: './input-multi-date.template.html', + changeDetection, + providers: [ + { + provide: ABSTRACT_PROPS_ACCESSOR, + useExisting: forwardRef(() => ExampleTuiInputMultiDateComponent), + }, + ], +}) +export class ExampleTuiInputMultiDateComponent extends AbstractExampleTuiControl { + readonly exampleForm = import('./examples/import/declare-form.md?raw'); + + readonly exampleModule = import('./examples/import/import-module.md?raw'); + + readonly exampleHtml = import('./examples/import/insert-template.md?raw'); + + readonly example1: TuiDocExample = { + TypeScript: import('./examples/1/index.ts?raw'), + HTML: import('./examples/1/index.html?raw'), + }; + + readonly example2: TuiDocExample = { + TypeScript: import('./examples/2/index.ts?raw'), + HTML: import('./examples/2/index.html?raw'), + }; + + minVariants = [ + TUI_FIRST_DAY, + new TuiDay(2017, 2, 5), + new TuiDay(1900, 0, 1), + new TuiDay(new Date().getFullYear() + 3, 1, 1), + ]; + + min = this.minVariants[0]; + + maxVariants = [ + TUI_LAST_DAY, + new TuiDay(2017, 11, 11), + new TuiDay(2020, 2, 5), + new TuiDay(2300, 0, 1), + ]; + + max = this.maxVariants[0]; + + rowsVariants = [Infinity, 10, 3, 2]; + + rows = this.rowsVariants[0]; + + readonly disabledItemHandlerVariants: ReadonlyArray> = [ + ALWAYS_FALSE_HANDLER, + ({day}) => day % 3 === 0, + ]; + + disabledItemHandler = this.disabledItemHandlerVariants[0]; + + readonly itemsVariants = [ + [], + [new TuiNamedDay(TUI_LAST_DAY.append({year: -1}), 'Until today')], + ]; + + readonly markerHandlerVariants: readonly TuiMarkerHandler[] = [ + TUI_DEFAULT_MARKER_HANDLER, + (day: TuiDay) => (day.day % 2 === 0 ? TWO_DOTS : ONE_DOT), + ]; + + markerHandler: TuiMarkerHandler = this.markerHandlerVariants[0]; + + items = this.itemsVariants[0]; + + override cleaner = false; + + expandable = false; + + control = new FormControl([], Validators.required); +} diff --git a/projects/demo/src/modules/components/input-multi-date/input-multi-date.module.ts b/projects/demo/src/modules/components/input-multi-date/input-multi-date.module.ts new file mode 100644 index 0000000000000..49541fb5103f0 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/input-multi-date.module.ts @@ -0,0 +1,56 @@ +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {RouterModule} from '@angular/router'; +import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc'; +import {TuiMobileCalendarDialogModule} from '@taiga-ui/addon-mobile'; +import { + TuiButtonModule, + TuiDropdownModule, + TuiErrorModule, + TuiHintModule, + TuiLinkModule, + TuiNotificationModule, + TuiTextfieldControllerModule, +} from '@taiga-ui/core'; +import { + TuiFieldErrorPipeModule, + TuiInputDateModule, + TuiRadioListModule, + TuiUnfinishedValidatorModule, +} from '@taiga-ui/kit'; + +import {InheritedDocumentationModule} from '../abstract/inherited-documentation/inherited-documentation.module'; +import {TuiInputMultiDateExample1} from './examples/1'; +import {TuiInputMultiDateExample2} from './examples/2'; +import {ExampleTuiInputMultiDateComponent} from './input-multi-date.component'; + +@NgModule({ + imports: [ + TuiAddonDocModule, + InheritedDocumentationModule, + ReactiveFormsModule, + FormsModule, + CommonModule, + TuiLinkModule, + TuiRadioListModule, + TuiButtonModule, + TuiInputDateModule, + TuiMobileCalendarDialogModule, + TuiUnfinishedValidatorModule, + TuiTextfieldControllerModule, + TuiHintModule, + TuiErrorModule, + TuiFieldErrorPipeModule, + TuiNotificationModule, + RouterModule.forChild(tuiGenerateRoutes(ExampleTuiInputMultiDateComponent)), + TuiDropdownModule, + ], + declarations: [ + ExampleTuiInputMultiDateComponent, + TuiInputMultiDateExample1, + TuiInputMultiDateExample2, + ], + exports: [ExampleTuiInputMultiDateComponent], +}) +export class ExampleTuiInputMultiDateModule {} diff --git a/projects/demo/src/modules/components/input-multi-date/input-multi-date.template.html b/projects/demo/src/modules/components/input-multi-date/input-multi-date.template.html new file mode 100644 index 0000000000000..8f7dd7254eb32 --- /dev/null +++ b/projects/demo/src/modules/components/input-multi-date/input-multi-date.template.html @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + Choose a date + + + + + + Disabled state (use + formControl.disable() + ) + + + Expandable + + +
A handler that gets a date and returns true if it is disabled.
+ + Must be a pure function +
+ + A list of preset dates for dropdown + + + A handler that gets date and returns null or a tuple with circled marker colors + + + Minimum date + + + Maximum date + + + Maximum date + +
+ + + + Custom align content by text-align + + +
+ + +

+ Mobile calendar does not use the same dropdown with calendar as desktop uses. It uses digital keyboard. If + you want to open + + mobile calendar + + , add imports of + TuiMobileCalendarDialogModule + and + TuiDialogModule + into your root module. Also, check that you did not forget about + + tui-root + +

+ +
    +
  1. +

    + Import an Angular module for forms and + TuiInputDateModule + in the same module where you want to use our component: +

    + + +
  2. + +
  3. +

    + Declare a form ( + FormGroup + ) or a control ( + FormControl + ) in your component: +

    + + +
  4. + +
  5. + Use + TuiInputDate[multiple] + in template: + + +
  6. +
+
+
diff --git a/projects/kit/components/input-date/index.ts b/projects/kit/components/input-date/index.ts index bf1757ea6bd48..1c5a8de574f78 100644 --- a/projects/kit/components/input-date/index.ts +++ b/projects/kit/components/input-date/index.ts @@ -1,3 +1,7 @@ export * from './input-date.component'; export * from './input-date.directive'; export * from './input-date.module'; +export * from './multi-date/immutable-update'; +export * from './multi-date/input-multi-date.component'; +export * from './multi-date/input-multi-date.directive'; +export * from './native-date/native-date.component'; diff --git a/projects/kit/components/input-date/input-date.component.ts b/projects/kit/components/input-date/input-date.component.ts index 3096ca4cfc9be..57df055a4ecd4 100644 --- a/projects/kit/components/input-date/input-date.component.ts +++ b/projects/kit/components/input-date/input-date.component.ts @@ -63,7 +63,7 @@ import {Observable} from 'rxjs'; import {map, takeUntil} from 'rxjs/operators'; @Component({ - selector: 'tui-input-date', + selector: 'tui-input-date:not([multiple])', templateUrl: './input-date.template.html', styleUrls: ['./input-date.style.less'], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/projects/kit/components/input-date/input-date.directive.ts b/projects/kit/components/input-date/input-date.directive.ts index ad7b5b29154b7..f1137890b3e56 100644 --- a/projects/kit/components/input-date/input-date.directive.ts +++ b/projects/kit/components/input-date/input-date.directive.ts @@ -5,7 +5,7 @@ import {AbstractTuiTextfieldHost, tuiAsTextfieldHost} from '@taiga-ui/core'; import {TuiInputDateComponent} from './input-date.component'; @Directive({ - selector: 'tui-input-date', + selector: 'tui-input-date:not([multiple])', providers: [tuiAsTextfieldHost(TuiInputDateDirective)], }) export class TuiInputDateDirective extends AbstractTuiTextfieldHost { diff --git a/projects/kit/components/input-date/input-date.module.ts b/projects/kit/components/input-date/input-date.module.ts index efdf1a1accdb8..a9e3312ca19cc 100644 --- a/projects/kit/components/input-date/input-date.module.ts +++ b/projects/kit/components/input-date/input-date.module.ts @@ -1,7 +1,8 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; +import {FormsModule} from '@angular/forms'; import {MaskitoModule} from '@maskito/angular'; -import {TuiLetModule, TuiPreventDefaultModule} from '@taiga-ui/cdk'; +import {TuiLetModule, TuiMapperPipeModule, TuiPreventDefaultModule} from '@taiga-ui/cdk'; import { TuiCalendarModule, TuiHostedDropdownModule, @@ -12,11 +13,14 @@ import { TuiTextfieldControllerModule, TuiWrapperModule, } from '@taiga-ui/core'; +import {TuiInputTagModule} from '@taiga-ui/kit/components/input-tag'; import {TuiValueAccessorModule} from '@taiga-ui/kit/directives'; import {PolymorpheusModule} from '@tinkoff/ng-polymorpheus'; import {TuiInputDateComponent} from './input-date.component'; import {TuiInputDateDirective} from './input-date.directive'; +import {TuiInputMultiDateComponent} from './multi-date/input-multi-date.component'; +import {TuiInputMultiDateDirective} from './multi-date/input-multi-date.directive'; import {TuiNativeDateDirective} from './native-date/native-date.component'; @NgModule({ @@ -34,8 +38,23 @@ import {TuiNativeDateDirective} from './native-date/native-date.component'; TuiValueAccessorModule, TuiLetModule, TuiTextfieldControllerModule, + TuiInputTagModule, + FormsModule, + TuiMapperPipeModule, + ], + declarations: [ + TuiInputDateComponent, + TuiInputDateDirective, + TuiInputMultiDateDirective, + TuiNativeDateDirective, + TuiInputMultiDateComponent, + ], + exports: [ + TuiInputDateComponent, + TuiInputDateDirective, + TuiInputMultiDateDirective, + TuiTextfieldComponent, + TuiInputMultiDateComponent, ], - declarations: [TuiInputDateComponent, TuiInputDateDirective, TuiNativeDateDirective], - exports: [TuiInputDateComponent, TuiInputDateDirective, TuiTextfieldComponent], }) export class TuiInputDateModule {} diff --git a/projects/kit/components/input-date/multi-date/immutable-update.ts b/projects/kit/components/input-date/multi-date/immutable-update.ts new file mode 100644 index 0000000000000..105a26c4fa906 --- /dev/null +++ b/projects/kit/components/input-date/multi-date/immutable-update.ts @@ -0,0 +1,10 @@ +import {TuiDay} from '@taiga-ui/cdk'; + +export function tuiImmutableUpdateMultiDates( + value: readonly TuiDay[], + day: TuiDay, +): readonly TuiDay[] { + return value.find(item => item.daySame(day)) + ? value.filter(item => !item.daySame(day)) + : value.concat(day); +} diff --git a/projects/kit/components/input-date/multi-date/input-multi-date.component.ts b/projects/kit/components/input-date/multi-date/input-multi-date.component.ts new file mode 100644 index 0000000000000..e04695c9ac0bc --- /dev/null +++ b/projects/kit/components/input-date/multi-date/input-multi-date.component.ts @@ -0,0 +1,305 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostBinding, + HostListener, + Inject, + Injector, + Input, + Optional, + Self, + Type, + ViewChild, +} from '@angular/core'; +import {NgControl} from '@angular/forms'; +import { + AbstractTuiControl, + AbstractTuiValueTransformer, + ALWAYS_FALSE_HANDLER, + ALWAYS_TRUE_HANDLER, + changeDateSeparator, + TUI_DATE_FORMAT, + TUI_DATE_SEPARATOR, + TUI_IS_MOBILE, + TuiActiveZoneDirective, + tuiAsControl, + tuiAsFocusableItemAccessor, + TuiBooleanHandler, + TuiContextWithImplicit, + tuiDateClamp, + TuiDateMode, + TuiDay, + TuiFocusableElementAccessor, + tuiIsString, + TuiMonth, + TuiTypedMapper, +} from '@taiga-ui/cdk'; +import { + TUI_DEFAULT_MARKER_HANDLER, + TUI_TEXTFIELD_SIZE, + TuiDialogService, + TuiMarkerHandler, + TuiPrimitiveTextfieldComponent, + TuiSizeL, + TuiSizeS, + TuiTextfieldSizeDirective, + TuiWithOptionalMinMax, +} from '@taiga-ui/core'; +import {TuiNamedDay, TuiStringifiableItem} from '@taiga-ui/kit/classes'; +import { + TUI_DATE_TEXTS, + TUI_DATE_VALUE_TRANSFORMER, + TUI_INPUT_DATE_OPTIONS, + TUI_MOBILE_CALENDAR, + tuiDateStreamWithTransformer, + TuiInputDateOptions, +} from '@taiga-ui/kit/tokens'; +import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus'; +import {Observable} from 'rxjs'; +import {map, takeUntil} from 'rxjs/operators'; + +import {tuiImmutableUpdateMultiDates} from './immutable-update'; + +@Component({ + selector: 'tui-input-date[multiple]', + templateUrl: './input-multi-date.template.html', + styleUrls: ['../input-date.style.less'], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + tuiAsFocusableItemAccessor(TuiInputMultiDateComponent), + tuiAsControl(TuiInputMultiDateComponent), + tuiDateStreamWithTransformer(TUI_DATE_VALUE_TRANSFORMER), + ], +}) +export class TuiInputMultiDateComponent + extends AbstractTuiControl + implements TuiWithOptionalMinMax, TuiFocusableElementAccessor +{ + @ViewChild(TuiPrimitiveTextfieldComponent) + private readonly textfield?: TuiPrimitiveTextfieldComponent; + + private month: TuiMonth | null = null; + + @Input() + min: TuiDay | null = this.options.min; + + @Input() + max: TuiDay | null = this.options.max; + + @Input() + disabledItemHandler: TuiBooleanHandler = ALWAYS_FALSE_HANDLER; + + @Input() + markerHandler: TuiMarkerHandler = TUI_DEFAULT_MARKER_HANDLER; + + @Input() + items: readonly TuiNamedDay[] = []; + + @Input() + defaultActiveYearMonth = TuiMonth.currentLocal(); + + @Input() + expandable = false; + + @Input() + @HostBinding('class._editable') + editable = false; + + @Input() + search: string | null = ''; + + @Input() + placeholder = ''; + + @Input() + rows = Infinity; + + @Input() + tagValidator: TuiBooleanHandler = ALWAYS_TRUE_HANDLER; + + open = false; + + readonly type!: TuiContextWithImplicit; + + readonly filler$: Observable = this.dateTexts$.pipe( + map(dateTexts => + changeDateSeparator(dateTexts[this.dateFormat], this.dateSeparator), + ), + ); + + constructor( + @Optional() + @Self() + @Inject(NgControl) + control: NgControl | null, + @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef, + @Inject(Injector) private readonly injector: Injector, + @Inject(TUI_IS_MOBILE) readonly isMobile: boolean, + @Inject(TuiDialogService) private readonly dialogs: TuiDialogService, + @Optional() + @Inject(TUI_MOBILE_CALENDAR) + private readonly mobileCalendar: Type> | null, + @Inject(TUI_DATE_FORMAT) readonly dateFormat: TuiDateMode, + @Inject(TUI_DATE_SEPARATOR) readonly dateSeparator: string, + @Inject(TUI_DATE_TEXTS) + readonly dateTexts$: Observable>, + @Optional() + @Inject(TUI_DATE_VALUE_TRANSFORMER) + override readonly valueTransformer: AbstractTuiValueTransformer< + readonly TuiDay[] + > | null, + @Inject(TUI_INPUT_DATE_OPTIONS) private readonly options: TuiInputDateOptions, + @Inject(TUI_TEXTFIELD_SIZE) + private readonly textfieldSize: TuiTextfieldSizeDirective, + ) { + super(control, cdr, valueTransformer); + } + + @HostListener('click') + onClick(): void { + if (!this.isMobile) { + this.open = !this.open; + } + } + + readonly disabledItemHandlerWrapper: TuiTypedMapper< + [TuiBooleanHandler], + TuiBooleanHandler | string> + > = handler => stringifiable => + tuiIsString(stringifiable) || handler(stringifiable.item); + + @HostBinding('attr.data-size') + get size(): TuiSizeL | TuiSizeS { + return this.textfieldSize.size; + } + + get nativeDropdownMode(): boolean { + return this.isMobile && !this.editable && this.nativePicker; + } + + get computedMin(): TuiDay { + return this.min ?? this.options.min; + } + + get computedMax(): TuiDay { + return this.max ?? this.options.max; + } + + get nativeFocusableElement(): HTMLInputElement | null { + return this.textfield ? this.textfield.nativeFocusableElement : null; + } + + get focused(): boolean { + return !!this.textfield && this.textfield.focused; + } + + get computedMobile(): boolean { + return this.isMobile && (!!this.mobileCalendar || this.nativePicker); + } + + get nativePicker(): boolean { + return this.options.nativePicker; + } + + get calendarIcon(): TuiInputDateOptions['icon'] { + return this.options.icon; + } + + get computedActiveYearMonth(): TuiMonth { + if (this.items[0] && this.value?.find(day => day.daySame(this.items[0].day))) { + return this.items[0].displayDay; + } + + return ( + this.month || + this.value[0] || + tuiDateClamp(this.defaultActiveYearMonth, this.computedMin, this.computedMax) + ); + } + + get nativeValue(): string { + return this.nativeFocusableElement?.value ?? ''; + } + + set nativeValue(value: string) { + if (!this.nativeFocusableElement) { + return; + } + + this.nativeFocusableElement.value = value.toString(); + } + + get canOpen(): boolean { + return this.interactive && !this.computedMobile; + } + + getComputedFiller(filler: string): string { + return this.value.length ? '' : filler; + } + + onIconClick(): void { + if (!this.computedMobile || !this.mobileCalendar) { + return; + } + + this.dialogs + .open( + new PolymorpheusComponent(this.mobileCalendar, this.injector), + { + size: 'fullscreen', + closeable: false, + data: { + single: false, + min: this.min, + max: this.max, + disabledItemHandler: this.disabledItemHandler, + }, + }, + ) + .pipe(takeUntil(this.destroy$)) + .subscribe(value => { + this.value = value; + }); + } + + onValueChange(value: readonly TuiDay[]): void { + this.control?.updateValueAndValidity({emitEvent: false}); + + if (!value.length) { + this.onOpenChange(true); + } + + this.value = value; + } + + onDayClick(value: TuiDay): void { + this.value = tuiImmutableUpdateMultiDates(this.value, value); + } + + onMonthChange(month: TuiMonth): void { + this.month = month; + } + + onOpenChange(open: boolean): void { + this.open = open; + } + + onFocused(focused: boolean): void { + this.updateFocused(focused); + } + + override setDisabledState(): void { + super.setDisabledState(); + this.open = false; + } + + override writeValue(value: readonly TuiDay[]): void { + super.writeValue(value); + this.nativeValue = value.toString(); + } + + protected override getFallbackValue(): readonly TuiDay[] { + return []; + } +} diff --git a/projects/kit/components/input-date/multi-date/input-multi-date.directive.ts b/projects/kit/components/input-date/multi-date/input-multi-date.directive.ts new file mode 100644 index 0000000000000..fe25337b2f0fb --- /dev/null +++ b/projects/kit/components/input-date/multi-date/input-multi-date.directive.ts @@ -0,0 +1,42 @@ +import {Directive} from '@angular/core'; +import {TuiDay} from '@taiga-ui/cdk'; +import {AbstractTuiTextfieldHost, tuiAsTextfieldHost} from '@taiga-ui/core'; + +import {tuiImmutableUpdateMultiDates} from './immutable-update'; +import {TuiInputMultiDateComponent} from './input-multi-date.component'; + +@Directive({ + selector: 'tui-input-date[multiple]', + providers: [tuiAsTextfieldHost(TuiInputMultiDateDirective)], +}) +export class TuiInputMultiDateDirective extends AbstractTuiTextfieldHost { + override get value(): string { + return this.host.value.toString(); + } + + get max(): TuiDay { + return this.host.computedMax; + } + + get min(): TuiDay { + return this.host.computedMin; + } + + onValueChange(value: string): void { + const {year, month, day} = TuiDay.parseRawDateString(value); + const values = tuiImmutableUpdateMultiDates( + this.host.value, + new TuiDay(year, month, day), + ); + + if (!value) { + this.host.nativeValue = ''; + } + + this.host.onValueChange(values); + } + + override process(input: HTMLInputElement): void { + input.inputMode = 'numeric'; + } +} diff --git a/projects/kit/components/input-date/multi-date/input-multi-date.template.html b/projects/kit/components/input-date/multi-date/input-multi-date.template.html new file mode 100644 index 0000000000000..68ec626642708 --- /dev/null +++ b/projects/kit/components/input-date/multi-date/input-multi-date.template.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + + +
+ +
+
+