diff --git a/projects/core/components/data-list/data-list.directive.ts b/projects/core/components/data-list/data-list.directive.ts index 397e9abbb2b7..94723a9dc039 100644 --- a/projects/core/components/data-list/data-list.directive.ts +++ b/projects/core/components/data-list/data-list.directive.ts @@ -1,6 +1,13 @@ -import {Directive} from '@angular/core'; +import {Directive, Provider, Type} from '@angular/core'; @Directive({ selector: 'ng-template[tuiDataList]', }) export class TuiDataListDirective {} + +export function tuiAsDataList(useExisting: Type): Provider { + return { + provide: TuiDataListDirective, + useExisting, + }; +} diff --git a/projects/demo/src/modules/components/multi-select/examples/10/index.html b/projects/demo/src/modules/components/multi-select/examples/10/index.html new file mode 100644 index 000000000000..30d46b105cb4 --- /dev/null +++ b/projects/demo/src/modules/components/multi-select/examples/10/index.html @@ -0,0 +1,37 @@ + + Star Wars persons + + + + + + Star Wars persons + + + diff --git a/projects/demo/src/modules/components/multi-select/examples/10/index.ts b/projects/demo/src/modules/components/multi-select/examples/10/index.ts new file mode 100644 index 000000000000..ffca8fc3af82 --- /dev/null +++ b/projects/demo/src/modules/components/multi-select/examples/10/index.ts @@ -0,0 +1,34 @@ +import {Component} from '@angular/core'; +import {FormControl} from '@angular/forms'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {encapsulation} from '@demo/emulate/encapsulation'; +import {TuiBooleanHandler} from '@taiga-ui/cdk'; + +@Component({ + selector: 'tui-multi-select-example-10', + templateUrl: './index.html', + changeDetection, + encapsulation, +}) +export class TuiMultiSelectExample10 { + 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']; + + disableHandler: TuiBooleanHandler = item => item.startsWith('Broccoli'); +} diff --git a/projects/demo/src/modules/components/multi-select/multi-select.component.ts b/projects/demo/src/modules/components/multi-select/multi-select.component.ts index b0a9d6d8b7f9..aec98e2ed17f 100644 --- a/projects/demo/src/modules/components/multi-select/multi-select.component.ts +++ b/projects/demo/src/modules/components/multi-select/multi-select.component.ts @@ -95,6 +95,11 @@ export class ExampleTuiMultiSelectComponent extends AbstractExampleTuiControl { HTML: import('./examples/9/index.html?raw'), }; + readonly example10: TuiDocExample = { + TypeScript: import('./examples/10/index.ts?raw'), + HTML: import('./examples/10/index.html?raw'), + }; + override labelOutside = true; readonly items = [ diff --git a/projects/demo/src/modules/components/multi-select/multi-select.module.ts b/projects/demo/src/modules/components/multi-select/multi-select.module.ts index 02b28c1f9a23..4adcfc2fb01a 100644 --- a/projects/demo/src/modules/components/multi-select/multi-select.module.ts +++ b/projects/demo/src/modules/components/multi-select/multi-select.module.ts @@ -33,6 +33,7 @@ import {TuiMultiSelectExample6} from './examples/6'; import {TuiMultiSelectExample7} from './examples/7'; import {TuiMultiSelectExample8} from './examples/8'; import {TuiMultiSelectExample9} from './examples/9'; +import {TuiMultiSelectExample10} from './examples/10'; import {ExampleTuiMultiSelectComponent} from './multi-select.component'; @NgModule({ @@ -70,6 +71,7 @@ import {ExampleTuiMultiSelectComponent} from './multi-select.component'; TuiMultiSelectExample7, TuiMultiSelectExample8, TuiMultiSelectExample9, + TuiMultiSelectExample10, ], exports: [ExampleTuiMultiSelectComponent], }) diff --git a/projects/demo/src/modules/components/multi-select/multi-select.template.html b/projects/demo/src/modules/components/multi-select/multi-select.template.html index f59ec85e3829..4017ac3235af 100644 --- a/projects/demo/src/modules/components/multi-select/multi-select.template.html +++ b/projects/demo/src/modules/components/multi-select/multi-select.template.html @@ -97,6 +97,14 @@ > + + + + diff --git a/projects/kit/components/input-tag/input-tag.component.ts b/projects/kit/components/input-tag/input-tag.component.ts index d84303497798..a5d95245fbf4 100644 --- a/projects/kit/components/input-tag/input-tag.component.ts +++ b/projects/kit/components/input-tag/input-tag.component.ts @@ -157,6 +157,10 @@ export class TuiInputTagComponent @tuiDefaultProp() placeholder = ''; + @Input() + @tuiDefaultProp() + removable = true; + @Input() @tuiDefaultProp() disabledItemHandler: TuiBooleanHandler | string> = diff --git a/projects/kit/components/input-tag/input-tag.template.html b/projects/kit/components/input-tag/input-tag.template.html index 8ac1fa5e70b5..641922b3d946 100644 --- a/projects/kit/components/input-tag/input-tag.template.html +++ b/projects/kit/components/input-tag/input-tag.template.html @@ -61,7 +61,7 @@ [disabled]="computedDisabled || disabledItemHandler(item)" [editable]="editable && !readOnly" [hoverable]="!readOnly" - [removable]="!readOnly" + [removable]="!readOnly && removable" [separator]="separator" [maxLength]="maxLength" [size]="controller.size" @@ -110,6 +110,7 @@ +
@ContentChild(TUI_DATA_LIST_ACCESSOR as any) private readonly accessor?: TuiDataListAccessor; + @ContentChild(AbstractTuiNativeMultiSelect, {static: true}) + private readonly nativeSelect?: AbstractTuiNativeMultiSelect; + @ViewChild(TuiHostedDropdownComponent) private readonly hostedDropdown?: TuiHostedDropdownComponent; @@ -142,6 +147,8 @@ export class TuiMultiSelectComponent private readonly options: TuiMultiSelectOptions, @Inject(TUI_TEXTFIELD_WATCHED_CONTROLLER) readonly controller: TuiTextfieldController, + @Inject(TUI_IS_MOBILE) + readonly isMobile: boolean, ) { super(control, changeDetectorRef); } @@ -165,6 +172,10 @@ export class TuiMultiSelectComponent return !!this.input?.focused || !!this.hostedDropdown?.focused; } + get nativeDropdownMode(): boolean { + return !!this.nativeSelect && this.isMobile; + } + get computedValue(): readonly T[] { return this.computedGroup ? EMPTY_ARRAY : this.value; } @@ -254,8 +265,11 @@ export class TuiMultiSelectComponent this.updateValue(value.map(({item}) => item)); } + onValueChange(value: readonly T[]): void { + this.updateValue(value); + } + onSearch(search: string | null): void { - this.open = true; this.updateSearch(search); } diff --git a/projects/kit/components/multi-select/multi-select.directive.ts b/projects/kit/components/multi-select/multi-select.directive.ts new file mode 100644 index 000000000000..67fae8fb3458 --- /dev/null +++ b/projects/kit/components/multi-select/multi-select.directive.ts @@ -0,0 +1,28 @@ +import {Directive} from '@angular/core'; +import {TuiBooleanHandler} from '@taiga-ui/cdk'; +import {AbstractTuiTextfieldHost, tuiAsTextfieldHost} from '@taiga-ui/core'; + +import type {TuiMultiSelectComponent} from './multi-select.component'; + +@Directive({ + selector: 'tui-multi-select', + providers: [tuiAsTextfieldHost(TuiMultiSelectDirective)], +}) +export class TuiMultiSelectDirective extends AbstractTuiTextfieldHost< + TuiMultiSelectComponent +> { + override get readOnly(): boolean { + return true; + } + + disableItemHandler: TuiBooleanHandler = item => + this.host.disabledItemHandler(item); + + onValueChange(_: string): void { + // + } + + onSelectionChange(value: string[]): void { + this.host.onValueChange(value); + } +} diff --git a/projects/kit/components/multi-select/multi-select.module.ts b/projects/kit/components/multi-select/multi-select.module.ts index 5f5c80c98813..85fa0a927003 100644 --- a/projects/kit/components/multi-select/multi-select.module.ts +++ b/projects/kit/components/multi-select/multi-select.module.ts @@ -16,14 +16,18 @@ import { TuiWrapperModule, } from '@taiga-ui/core'; import {TuiArrowModule} from '@taiga-ui/kit/components/arrow'; +import {TuiDataListWrapperModule} from '@taiga-ui/kit/components/data-list-wrapper'; import {TuiInputTagModule} from '@taiga-ui/kit/components/input-tag'; import {TuiMultiSelectOptionModule} from '@taiga-ui/kit/components/multi-select-option'; import {PolymorpheusModule} from '@tinkoff/ng-polymorpheus'; import {TuiHideSelectedPipe} from './hide-selected.pipe'; import {TuiMultiSelectComponent} from './multi-select.component'; +import {TuiMultiSelectDirective} from './multi-select.directive'; import {TuiMultiSelectGroupComponent} from './multi-select-group/multi-select-group.component'; import {TuiMultiSelectGroupDirective} from './multi-select-group/multi-select-group.directive'; +import {TuiNativeMultiSelectComponent} from './native-multi-select/native-multi-select.component'; +import {TuiNativeMultiSelectGroupComponent} from './native-multi-select/native-multi-select-group.component'; @NgModule({ imports: [ @@ -40,6 +44,8 @@ import {TuiMultiSelectGroupDirective} from './multi-select-group/multi-select-gr TuiHostedDropdownModule, TuiInputTagModule, TuiMultiSelectOptionModule, + TuiDataListWrapperModule, + TuiMapperPipeModule, TuiLinkModule, TuiDataListModule, TuiTextfieldControllerModule, @@ -49,12 +55,18 @@ import {TuiMultiSelectGroupDirective} from './multi-select-group/multi-select-gr TuiMultiSelectGroupComponent, TuiMultiSelectGroupDirective, TuiHideSelectedPipe, + TuiNativeMultiSelectComponent, + TuiNativeMultiSelectGroupComponent, + TuiMultiSelectDirective, ], exports: [ TuiMultiSelectComponent, TuiMultiSelectGroupComponent, TuiMultiSelectGroupDirective, TuiHideSelectedPipe, + TuiMultiSelectDirective, + TuiNativeMultiSelectComponent, + TuiNativeMultiSelectGroupComponent, ], }) export class TuiMultiSelectModule {} diff --git a/projects/kit/components/multi-select/multi-select.style.less b/projects/kit/components/multi-select/multi-select.style.less index a49c6a9d4f98..61acf2768fbb 100644 --- a/projects/kit/components/multi-select/multi-select.style.less +++ b/projects/kit/components/multi-select/multi-select.style.less @@ -71,4 +71,8 @@ .t-arrow { pointer-events: auto; cursor: pointer; + + &_native-dropdown { + pointer-events: none; + } } diff --git a/projects/kit/components/multi-select/multi-select.template.html b/projects/kit/components/multi-select/multi-select.template.html index 5f89174b97b7..d703425746db 100644 --- a/projects/kit/components/multi-select/multi-select.template.html +++ b/projects/kit/components/multi-select/multi-select.template.html @@ -1,6 +1,6 @@ + + + + + +
{{ text }} diff --git a/projects/kit/components/multi-select/native-multi-select/native-multi-select-group.component.ts b/projects/kit/components/multi-select/native-multi-select/native-multi-select-group.component.ts new file mode 100644 index 000000000000..43c4f1bed3ca --- /dev/null +++ b/projects/kit/components/multi-select/native-multi-select/native-multi-select-group.component.ts @@ -0,0 +1,38 @@ +import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; +import {tuiAsDataList} from '@taiga-ui/core'; + +import {AbstractTuiNativeMultiSelect} from './native-multi-select'; + +@Component({ + selector: 'select[multiple][tuiSelect][labels]', + templateUrl: './native-multi-select-group.template.html', + providers: [ + tuiAsDataList(TuiNativeMultiSelectGroupComponent), + { + provide: TemplateRef, + deps: [TuiNativeMultiSelectGroupComponent], + useFactory: ({datalist}: TuiNativeMultiSelectGroupComponent) => datalist, + }, + { + provide: AbstractTuiNativeMultiSelect, + useExisting: TuiNativeMultiSelectGroupComponent, + }, + ], + host: { + '[attr.aria-invalid]': 'host.invalid', + '[disabled]': 'host.disabled || control.readOnly', + '[tabIndex]': 'host.focusable ? 0 : -1', + '(change)': 'onValueChange()', + '(click.stop.silent)': '0', + '(mousedown.stop.silent)': '0', + }, + styleUrls: ['./native-multi-select.style.less'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TuiNativeMultiSelectGroupComponent extends AbstractTuiNativeMultiSelect { + @Input() + items: readonly string[][] | null = []; + + @Input() + labels: readonly string[] = []; +} diff --git a/projects/kit/components/multi-select/native-multi-select/native-multi-select-group.template.html b/projects/kit/components/multi-select/native-multi-select/native-multi-select-group.template.html new file mode 100644 index 000000000000..f9699f105912 --- /dev/null +++ b/projects/kit/components/multi-select/native-multi-select/native-multi-select-group.template.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/projects/kit/components/multi-select/native-multi-select/native-multi-select.component.ts b/projects/kit/components/multi-select/native-multi-select/native-multi-select.component.ts new file mode 100644 index 000000000000..cf55395434be --- /dev/null +++ b/projects/kit/components/multi-select/native-multi-select/native-multi-select.component.ts @@ -0,0 +1,35 @@ +import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; +import {tuiAsDataList} from '@taiga-ui/core'; + +import {AbstractTuiNativeMultiSelect} from './native-multi-select'; + +@Component({ + selector: 'select[multiple][tuiSelect]:not([labels])', + templateUrl: './native-multi-select.template.html', + providers: [ + tuiAsDataList(TuiNativeMultiSelectComponent), + { + provide: TemplateRef, + deps: [TuiNativeMultiSelectComponent], + useFactory: ({datalist}: TuiNativeMultiSelectComponent) => datalist, + }, + { + provide: AbstractTuiNativeMultiSelect, + useExisting: TuiNativeMultiSelectComponent, + }, + ], + host: { + '[attr.aria-invalid]': 'host.invalid', + '[disabled]': 'host.disabled || control.readOnly', + '[tabIndex]': 'host.focusable ? 0 : -1', + '(change)': 'onValueChange()', + '(click.stop.silent)': '0', + '(mousedown.stop.silent)': '0', + }, + styleUrls: ['./native-multi-select.style.less'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TuiNativeMultiSelectComponent extends AbstractTuiNativeMultiSelect { + @Input() + items: string[] | null = []; +} diff --git a/projects/kit/components/multi-select/native-multi-select/native-multi-select.style.less b/projects/kit/components/multi-select/native-multi-select/native-multi-select.style.less new file mode 100644 index 000000000000..8ecda8dcf45d --- /dev/null +++ b/projects/kit/components/multi-select/native-multi-select/native-multi-select.style.less @@ -0,0 +1,6 @@ +@import 'taiga-ui-local'; + +:host { + .fullsize(); + opacity: 0; +} diff --git a/projects/kit/components/multi-select/native-multi-select/native-multi-select.template.html b/projects/kit/components/multi-select/native-multi-select/native-multi-select.template.html new file mode 100644 index 000000000000..daa7b17c20c6 --- /dev/null +++ b/projects/kit/components/multi-select/native-multi-select/native-multi-select.template.html @@ -0,0 +1,15 @@ + + + + diff --git a/projects/kit/components/multi-select/native-multi-select/native-multi-select.ts b/projects/kit/components/multi-select/native-multi-select/native-multi-select.ts new file mode 100644 index 000000000000..7a75a9724fec --- /dev/null +++ b/projects/kit/components/multi-select/native-multi-select/native-multi-select.ts @@ -0,0 +1,44 @@ +import { + ChangeDetectorRef, + Directive, + ElementRef, + HostBinding, + Inject, + TemplateRef, + ViewChild, +} from '@angular/core'; +import {AbstractTuiControl, TuiIdService, TuiMapper} from '@taiga-ui/cdk'; +import {TUI_TEXTFIELD_HOST, TuiDataListDirective} from '@taiga-ui/core'; + +import {TuiMultiSelectDirective} from '../multi-select.directive'; + +@Directive() +export abstract class AbstractTuiNativeMultiSelect { + @ViewChild(TuiDataListDirective, {read: TemplateRef, static: true}) + readonly datalist: TemplateRef | null = null; + + constructor( + @Inject(TUI_TEXTFIELD_HOST) readonly host: TuiMultiSelectDirective, + @Inject(AbstractTuiControl) readonly control: AbstractTuiControl, + @Inject(ElementRef) private readonly elementRef: ElementRef, + @Inject(TuiIdService) + private readonly idService: TuiIdService, + @Inject(ChangeDetectorRef) readonly cdr: ChangeDetectorRef, + ) {} + + @HostBinding(`id`) + get id(): string { + return this.elementRef.nativeElement.id || this.idService.generate(); + } + + selectedMapper: TuiMapper = (option, value) => + value.includes(option); + + onValueChange(): void { + const {selectedOptions} = this.elementRef.nativeElement; + + this.host.onSelectionChange( + Array.from(selectedOptions).map(option => option.value), + ); + } +} 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 index d2a24100fe7e..640ac7cb2027 100644 --- a/projects/kit/components/select/native-select/native-select-group.component.ts +++ b/projects/kit/components/select/native-select/native-select-group.component.ts @@ -1,17 +1,13 @@ import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; -import {TuiDataListDirective} from '@taiga-ui/core'; +import {tuiAsDataList} from '@taiga-ui/core'; import {AbstractTuiNativeSelect} from './native-select'; @Component({ - selector: 'select[tuiSelect][labels]', + selector: 'select[tuiSelect][labels]:not([multiple])', templateUrl: './native-select-group.template.html', providers: [ - { - provide: TuiDataListDirective, - deps: [TuiNativeSelectGroupComponent], - useExisting: TuiNativeSelectGroupComponent, - }, + tuiAsDataList(TuiNativeSelectGroupComponent), { provide: TemplateRef, deps: [TuiNativeSelectGroupComponent], diff --git a/projects/kit/components/select/native-select/native-select.component.ts b/projects/kit/components/select/native-select/native-select.component.ts index feae74b26ad7..7efef4839ba6 100644 --- a/projects/kit/components/select/native-select/native-select.component.ts +++ b/projects/kit/components/select/native-select/native-select.component.ts @@ -1,17 +1,13 @@ import {ChangeDetectionStrategy, Component, Input, TemplateRef} from '@angular/core'; -import {TuiDataListDirective} from '@taiga-ui/core'; +import {tuiAsDataList} from '@taiga-ui/core'; import {AbstractTuiNativeSelect} from './native-select'; @Component({ - selector: 'select[tuiSelect]:not([labels])', + selector: 'select[tuiSelect]:not([labels]):not([multiple])', templateUrl: './native-select.template.html', providers: [ - { - provide: TuiDataListDirective, - deps: [TuiNativeSelectComponent], - useExisting: TuiNativeSelectComponent, - }, + tuiAsDataList(TuiNativeSelectComponent), { provide: TemplateRef, deps: [TuiNativeSelectComponent], diff --git a/projects/kit/components/select/select.template.html b/projects/kit/components/select/select.template.html index 0faa7d698526..78ba451af3c1 100644 --- a/projects/kit/components/select/select.template.html +++ b/projects/kit/components/select/select.template.html @@ -29,12 +29,10 @@ > - - - +