diff --git a/projects/core/components/primitive-textfield/primitive-textfield.template.html b/projects/core/components/primitive-textfield/primitive-textfield.template.html index 7aeb5b48a44b..dd89a83ed2ab 100644 --- a/projects/core/components/primitive-textfield/primitive-textfield.template.html +++ b/projects/core/components/primitive-textfield/primitive-textfield.template.html @@ -16,6 +16,7 @@ (tuiAutofilledChange)="onAutofilled($event)" > + + + Character + + + + + Food + + + + + diff --git a/projects/demo/src/modules/components/select/examples/11/index.ts b/projects/demo/src/modules/components/select/examples/11/index.ts new file mode 100644 index 000000000000..894aa62bf8c2 --- /dev/null +++ b/projects/demo/src/modules/components/select/examples/11/index.ts @@ -0,0 +1,31 @@ +import {Component} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {encapsulation} from '@demo/emulate/encapsulation'; + +@Component({ + selector: `tui-select-example-11`, + templateUrl: `./index.html`, + changeDetection, + encapsulation, +}) +export class TuiSelectExample11 { + itemControl = new FormControl(); + itemGroupControl = new FormControl(); + + items = [ + `Luke Skywalker`, + `Leia Organa Solo`, + `Darth Vader`, + `Han Solo`, + `Obi-Wan Kenobi`, + `Yoda`, + ]; + + groupItems = [ + [`Caesar`, `Greek`, `Apple and Chicken`], + [`Broccoli Cheddar`, `Chicken and Rice`, `Chicken Noodle`], + ]; + + labels = [`Salad`, `Soup`]; +} diff --git a/projects/demo/src/modules/components/select/select.component.ts b/projects/demo/src/modules/components/select/select.component.ts index 79b701119cfe..c4444b5cc00e 100644 --- a/projects/demo/src/modules/components/select/select.component.ts +++ b/projects/demo/src/modules/components/select/select.component.ts @@ -106,6 +106,11 @@ export class ExampleTuiSelectComponent extends AbstractExampleTuiControl { HTML: import(`./examples/10/index.html?raw`), }; + readonly example11: TuiDocExample = { + TypeScript: import(`./examples/11/index.ts?raw`), + HTML: import(`./examples/11/index.html?raw`), + }; + readonly items = [new Account(`Ruble`, 500), new Account(`Dollar`, 237)]; readonly valueTemplateVariants = [``, `Template`]; diff --git a/projects/demo/src/modules/components/select/select.module.ts b/projects/demo/src/modules/components/select/select.module.ts index d9202a40a3b7..41329f081a4f 100644 --- a/projects/demo/src/modules/components/select/select.module.ts +++ b/projects/demo/src/modules/components/select/select.module.ts @@ -42,6 +42,7 @@ import {TuiSelectExample8} from './examples/8'; import {TuiSelectExample9} from './examples/9'; import {ExampleMyAccountComponent} from './examples/9/account/my-account.component'; import {TuiSelectExample10} from './examples/10'; +import {TuiSelectExample11} from './examples/11'; import {ExampleTuiSelectComponent} from './select.component'; @NgModule({ @@ -91,6 +92,7 @@ import {ExampleTuiSelectComponent} from './select.component'; TuiSelectExample8, TuiSelectExample9, TuiSelectExample10, + TuiSelectExample11, ], exports: [ExampleTuiSelectComponent], }) diff --git a/projects/demo/src/modules/components/select/select.template.html b/projects/demo/src/modules/components/select/select.template.html index 7f0d1d6d1f19..bb4e26d441b3 100644 --- a/projects/demo/src/modules/components/select/select.template.html +++ b/projects/demo/src/modules/components/select/select.template.html @@ -101,6 +101,21 @@ > + + + + You can enable native select on mobile devices by putting + select + inside with + tuiSelect + directive as shown below + + + diff --git a/projects/kit/components/select/index.ts b/projects/kit/components/select/index.ts index bea9eb96ef59..5fa64673b254 100644 --- a/projects/kit/components/select/index.ts +++ b/projects/kit/components/select/index.ts @@ -1,3 +1,6 @@ +export * from './native-select/native-select'; +export * from './native-select/native-select.component'; +export * from './native-select/native-select-group.component'; export * from './select.component'; export * from './select.directive'; export * from './select.module'; diff --git a/projects/kit/components/select/native-select/native-select-group.component.ts b/projects/kit/components/select/native-select/native-select-group.component.ts new file mode 100644 index 000000000000..fa9bcf9f27a1 --- /dev/null +++ b/projects/kit/components/select/native-select/native-select-group.component.ts @@ -0,0 +1,42 @@ +import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; +import {TuiDataListDirective} from '@taiga-ui/core'; + +import {AbstractTuiNativeSelect} from './native-select'; + +@Component({ + selector: `select[tuiSelect][labels]`, + templateUrl: `./native-select-group.template.html`, + providers: [ + { + provide: TuiDataListDirective, + deps: [TuiNativeSelectGroupComponent], + useExisting: TuiNativeSelectGroupComponent, + }, + { + provide: TemplateRef, + deps: [TuiNativeSelectGroupComponent], + useFactory: ({datalist}: TuiNativeSelectGroupComponent) => datalist, + }, + { + provide: AbstractTuiNativeSelect, + useExisting: TuiNativeSelectGroupComponent, + }, + ], + host: { + '[attr.aria-invalid]': `host.invalid`, + '[disabled]': `host.disabled`, + '[tabIndex]': `host.focusable ? 0 : -1`, + '[readOnly]': `host.readOnly`, + '[value]': `host.value`, + '(change)': `host.onValueChange($event.target.value)`, + }, + styleUrls: [`./native-select.style.less`], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TuiNativeSelectGroupComponent extends AbstractTuiNativeSelect { + @Input() + items: readonly string[][] | null = []; + + @Input() + labels: readonly string[] = []; +} diff --git a/projects/kit/components/select/native-select/native-select-group.template.html b/projects/kit/components/select/native-select/native-select-group.template.html new file mode 100644 index 000000000000..7eadd005f1cf --- /dev/null +++ b/projects/kit/components/select/native-select/native-select-group.template.html @@ -0,0 +1,16 @@ + + + + diff --git a/projects/kit/components/select/native-select/native-select.component.ts b/projects/kit/components/select/native-select/native-select.component.ts new file mode 100644 index 000000000000..8c763623d5bd --- /dev/null +++ b/projects/kit/components/select/native-select/native-select.component.ts @@ -0,0 +1,39 @@ +import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; +import {TuiDataListDirective} from '@taiga-ui/core'; + +import {AbstractTuiNativeSelect} from './native-select'; + +@Component({ + selector: `select[tuiSelect]:not([labels])`, + templateUrl: `./native-select.template.html`, + providers: [ + { + provide: TuiDataListDirective, + deps: [TuiNativeSelectComponent], + useExisting: TuiNativeSelectComponent, + }, + { + provide: TemplateRef, + deps: [TuiNativeSelectComponent], + useFactory: ({datalist}: TuiNativeSelectComponent) => datalist, + }, + { + provide: AbstractTuiNativeSelect, + useExisting: TuiNativeSelectComponent, + }, + ], + host: { + '[attr.aria-invalid]': `host.invalid`, + '[disabled]': `host.disabled`, + '[tabIndex]': `host.focusable ? 0 : -1`, + '[readOnly]': `host.readOnly`, + '[value]': `host.value`, + '(change)': `host.onValueChange($event.target.value)`, + }, + styleUrls: [`./native-select.style.less`], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TuiNativeSelectComponent extends AbstractTuiNativeSelect { + @Input() + items: readonly string[] | null = []; +} diff --git a/projects/kit/components/select/native-select/native-select.style.less b/projects/kit/components/select/native-select/native-select.style.less new file mode 100644 index 000000000000..8ecda8dcf45d --- /dev/null +++ b/projects/kit/components/select/native-select/native-select.style.less @@ -0,0 +1,6 @@ +@import 'taiga-ui-local'; + +:host { + .fullsize(); + opacity: 0; +} diff --git a/projects/kit/components/select/native-select/native-select.template.html b/projects/kit/components/select/native-select/native-select.template.html new file mode 100644 index 000000000000..c0bb2efebc5b --- /dev/null +++ b/projects/kit/components/select/native-select/native-select.template.html @@ -0,0 +1,10 @@ + + diff --git a/projects/kit/components/select/native-select/native-select.ts b/projects/kit/components/select/native-select/native-select.ts new file mode 100644 index 000000000000..1dba2639b95e --- /dev/null +++ b/projects/kit/components/select/native-select/native-select.ts @@ -0,0 +1,30 @@ +import { + Directive, + ElementRef, + HostBinding, + Inject, + TemplateRef, + ViewChild, +} from '@angular/core'; +import {TuiIdService} from '@taiga-ui/cdk'; +import {TUI_TEXTFIELD_HOST, TuiDataListDirective, TuiTextfieldHost} from '@taiga-ui/core'; + +@Directive() +export abstract class AbstractTuiNativeSelect { + @ViewChild(TuiDataListDirective, {read: TemplateRef, static: true}) + readonly datalist: TemplateRef | null = null; + + constructor( + @Inject(TUI_TEXTFIELD_HOST) readonly host: TuiTextfieldHost, + @Inject(ElementRef) private readonly elementRef: ElementRef, + @Inject(TuiIdService) + private readonly idService: TuiIdService, + ) { + this.host.process(this.elementRef.nativeElement); + } + + @HostBinding(`id`) + get id(): string { + return this.elementRef.nativeElement.id || this.idService.generate(); + } +} diff --git a/projects/kit/components/select/select.component.ts b/projects/kit/components/select/select.component.ts index af190316ff8b..88d9f7c044a1 100644 --- a/projects/kit/components/select/select.component.ts +++ b/projects/kit/components/select/select.component.ts @@ -13,6 +13,7 @@ import { import {NgControl} from '@angular/forms'; import { AbstractTuiNullableControl, + TUI_IS_MOBILE, TuiActiveZoneDirective, tuiAsControl, tuiAsFocusableItemAccessor, @@ -41,6 +42,7 @@ import {FIXED_DROPDOWN_CONTROLLER_PROVIDER} from '@taiga-ui/kit/providers'; import {TUI_ITEMS_HANDLERS, TuiItemsHandlers} from '@taiga-ui/kit/tokens'; import {PolymorpheusContent} from '@tinkoff/ng-polymorpheus'; +import {AbstractTuiNativeSelect} from './native-select/native-select'; import {TUI_SELECT_OPTIONS, TuiSelectOptions} from './select-options'; @Component({ @@ -66,6 +68,9 @@ export class TuiSelectComponent @ViewChild(TuiHostedDropdownComponent) private readonly hostedDropdown?: TuiHostedDropdownComponent; + @ContentChild(AbstractTuiNativeSelect, {static: true}) + private readonly nativeSelect?: AbstractTuiNativeSelect; + @Input() @tuiDefaultProp() stringify: TuiItemsHandlers['stringify'] = this.itemsHandlers.stringify; @@ -98,6 +103,8 @@ export class TuiSelectComponent private readonly itemsHandlers: TuiItemsHandlers, @Inject(TUI_SELECT_OPTIONS) private readonly options: TuiSelectOptions, + @Inject(TUI_IS_MOBILE) + readonly isMobile: boolean, ) { super(control, changeDetectorRef); } @@ -119,6 +126,10 @@ export class TuiSelectComponent ); } + get nativeDropdownMode(): boolean { + return !!this.nativeSelect && this.isMobile; + } + get computedValue(): string { return this.value === null ? `` : this.stringify(this.value) || ` `; } @@ -127,9 +138,11 @@ export class TuiSelectComponent return this.valueContent || this.computedValue; } - onValueChange(value: string): void { + onValueChange(value: T): void { if (!value) { this.updateValue(null); + } else { + this.updateValue(value || null); } } diff --git a/projects/kit/components/select/select.module.ts b/projects/kit/components/select/select.module.ts index 2a90d7aecb53..0abc1b9a7ed9 100644 --- a/projects/kit/components/select/select.module.ts +++ b/projects/kit/components/select/select.module.ts @@ -2,15 +2,19 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {TuiActiveZoneModule} from '@taiga-ui/cdk'; import { + TuiDataListModule, TuiHostedDropdownModule, TuiPrimitiveTextfieldModule, TuiTextfieldComponent, TuiTextfieldControllerModule, } from '@taiga-ui/core'; import {TuiArrowModule} from '@taiga-ui/kit/components/arrow'; +import {TuiDataListWrapperModule} from '@taiga-ui/kit/components/data-list-wrapper'; import {TuiSelectOptionModule} from '@taiga-ui/kit/components/select-option'; import {PolymorpheusModule} from '@tinkoff/ng-polymorpheus'; +import {TuiNativeSelectComponent} from './native-select/native-select.component'; +import {TuiNativeSelectGroupComponent} from './native-select/native-select-group.component'; import {TuiSelectComponent} from './select.component'; import {TuiSelectDirective} from './select.directive'; @@ -24,8 +28,21 @@ import {TuiSelectDirective} from './select.directive'; TuiSelectOptionModule, TuiArrowModule, TuiTextfieldControllerModule, + TuiDataListWrapperModule, + TuiDataListModule, + ], + declarations: [ + TuiSelectComponent, + TuiSelectDirective, + TuiNativeSelectComponent, + TuiNativeSelectGroupComponent, + ], + exports: [ + TuiSelectComponent, + TuiSelectDirective, + TuiTextfieldComponent, + TuiNativeSelectComponent, + TuiNativeSelectGroupComponent, ], - declarations: [TuiSelectComponent, TuiSelectDirective], - exports: [TuiSelectComponent, TuiSelectDirective, TuiTextfieldComponent], }) export class TuiSelectModule {} diff --git a/projects/kit/components/select/select.template.html b/projects/kit/components/select/select.template.html index 74ba479fdd33..ad4555dfb94b 100644 --- a/projects/kit/components/select/select.template.html +++ b/projects/kit/components/select/select.template.html @@ -1,6 +1,6 @@ @@ -18,7 +18,7 @@ [disabled]="computedDisabled" [focusable]="computedFocusable" [value]="computedValue" - (valueChange)="onValueChange($event)" + (valueChange)="onValueChange($any($event))" (keydown.delete.prevent)="onKeyDownDelete()" (keydown.backspace.prevent)="onKeyDownDelete()" > @@ -27,6 +27,21 @@ select="input" ngProjectAs="input" > + + + + + + + + +