Skip to content

Commit

Permalink
fix(core,platform): multi input and combobox tokenizer behaviour (#9571)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
Multi-combobox and Multi-input components for both libraries (core and platform) are now following [guidelines logic regarding reviewing tokens](https://experience.sap.com/fiori-design-web/multiinput/#reviewing-tokens)
  • Loading branch information
N1XUS authored Mar 24, 2023
1 parent 1a6fa0a commit 7430869
Show file tree
Hide file tree
Showing 25 changed files with 450 additions and 127 deletions.
6 changes: 0 additions & 6 deletions libs/core/src/lib/list/list-component.token.ts

This file was deleted.

3 changes: 1 addition & 2 deletions libs/core/src/lib/list/list-item/list-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ import { startWith, takeUntil } from 'rxjs/operators';
import { KeyUtil } from '@fundamental-ngx/cdk/utils';
import { LIST_ITEM_COMPONENT, ListItemInterface } from '@fundamental-ngx/cdk/utils';
import { ENTER, SPACE } from '@angular/cdk/keycodes';
import { FD_LIST_UNREAD_INDICATOR } from '../list-component.token';
import { ListFocusItem } from '../list-focus-item.model';
import { ButtonComponent, FD_BUTTON_COMPONENT } from '@fundamental-ngx/core/button';
import { Nullable } from '@fundamental-ngx/cdk/utils';
import { ListUnreadIndicator } from '../list-unread-indicator.interface';
import { FD_LIST_LINK_DIRECTIVE } from '../tokens';
import { FD_LIST_LINK_DIRECTIVE, FD_LIST_UNREAD_INDICATOR } from '../tokens';

let listItemUniqueId = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { FocusableOption } from '@angular/cdk/a11y';
import { Subject } from 'rxjs';
import { ListNavigationItemArrowDirective } from '../directives/list-navigation-item-arrow.directive';
import { ListNavigationItemTextDirective } from '../directives/list-navigation-item-text.directive';
import { FD_LIST_COMPONENT } from '../list-component.token';
import { FD_LIST_COMPONENT } from '../tokens';
import { ListComponentInterface } from '../list-component.interface';

@Component({
Expand Down
2 changes: 1 addition & 1 deletion libs/core/src/lib/list/list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
ContentDensityMode
} from '@fundamental-ngx/core/content-density';
import { ListComponentInterface } from './list-component.interface';
import { FD_LIST_COMPONENT, FD_LIST_UNREAD_INDICATOR } from './list-component.token';
import { FD_LIST_COMPONENT, FD_LIST_UNREAD_INDICATOR } from './tokens';
import { ListUnreadIndicator } from './list-unread-indicator.interface';

/**
Expand Down
2 changes: 1 addition & 1 deletion libs/core/src/lib/list/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export const FD_LIST_MESSAGE_DIRECTIVE = new InjectionToken('FdListMessageDirect
export const FD_LIST_LINK_DIRECTIVE = new InjectionToken('FdListLinkDirective');
export const FD_LIST_COMPONENT = new InjectionToken<ListComponentInterface>('ListComponent');

export const FD_LIST_UNREAD_INDICATOR = new InjectionToken('ListUnreadIndicator');
export const FD_LIST_UNREAD_INDICATOR = new InjectionToken<ListComponentInterface>('ListUnreadIndicator');
5 changes: 2 additions & 3 deletions libs/core/src/lib/multi-combobox/base-multi-combobox.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
RangeSelector
} from '@fundamental-ngx/cdk/utils';
import { ContentDensityObserver } from '@fundamental-ngx/core/content-density';
import { FormItemControl } from '@fundamental-ngx/core/form';
import equal from 'fast-deep-equal';
import { BehaviorSubject, skip, startWith, Subscription, takeUntil, timer } from 'rxjs';
import {
Expand Down Expand Up @@ -112,7 +111,7 @@ export abstract class BaseMultiCombobox<T = any> {
abstract isGroup: boolean;
abstract inputText: string;

abstract searchInputElement: Nullable<FormItemControl>;
abstract searchInputElement: Nullable<ElementRef<HTMLInputElement>>;

abstract selectionChange: EventEmitter<MultiComboboxSelectionChangeEvent>;
abstract dataReceived: EventEmitter<void>;
Expand Down Expand Up @@ -408,7 +407,7 @@ export abstract class BaseMultiCombobox<T = any> {

/** @hidden */
protected _focusToSearchField(): void {
this.searchInputElement?.elmRef?.nativeElement.focus();
this.searchInputElement?.nativeElement.focus();
}

/** @hidden */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
<fd-tokenizer
*ngIf="_selectedSuggestions"
[tokenizerFocusable]="false"
[compactCollapse]="true"
#tokenizer
class="fd-multi-combobox-tokenizer-custom"
[class.fd-multi-combobox-tokenizer-custom--compact]="contentDensityObserver.isCompact"
(moreClickedEvent)="_moreClicked()"
Expand Down Expand Up @@ -81,13 +83,14 @@
[(ngModel)]="inputText"
(ngModelChange)="_searchTermChanged()"
[placeholder]="this._cva.placeholder"
(focus)="this._cva.onTouched()"
(blur)="!mobile && _onBlur($event)"
(focus)="this._cva.onTouched(); tokenizer._showAllTokens()"
(blur)="!mobile && _onBlur($event); tokenizer._hideTokens()"
[attr.aria-expanded]="isOpen"
[readonly]="this._cva.readonly"
[attr.aria-readonly]="this._cva.readonly"
[attr.aria-required]="this._cva.required"
[displayFn]="_displayFn"
class="fd-tokenizer__input"
/>
</fd-tokenizer>
</fd-input-group>
Expand Down
7 changes: 3 additions & 4 deletions libs/core/src/lib/multi-combobox/multi-combobox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import equal from 'fast-deep-equal';
import { Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { contentDensityObserverProviders } from '@fundamental-ngx/core/content-density';
import { FormItemControl } from '@fundamental-ngx/core/form';
import { TokenizerComponent } from '@fundamental-ngx/core/token';

import { SelectableOptionItem, OptionItem } from '@fundamental-ngx/cdk/forms';
Expand Down Expand Up @@ -245,8 +244,8 @@ export class MultiComboboxComponent<T = any> extends BaseMultiCombobox<T> implem
private readonly listComponent: ListComponentInterface;

/** @hidden */
@ViewChild('searchInputElement')
readonly searchInputElement: Nullable<FormItemControl>;
@ViewChild('searchInputElement', { read: ElementRef })
readonly searchInputElement: Nullable<ElementRef<HTMLInputElement>>;

/** @hidden */
@ContentChildren(TemplateDirective)
Expand Down Expand Up @@ -661,7 +660,7 @@ export class MultiComboboxComponent<T = any> extends BaseMultiCombobox<T> implem
*/
_handleListFocusEscape(direction: FocusEscapeDirection): void {
if (direction === 'up') {
this.searchInputElement?.elmRef?.nativeElement.focus();
this._focusToSearchField();
}
}

Expand Down
3 changes: 3 additions & 0 deletions libs/core/src/lib/multi-input/multi-input.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
(addOnButtonClicked)="_addOnButtonClicked($event)"
>
<fd-tokenizer
#tokenizer
[compactCollapse]="compactCollapse"
[open]="open"
[tokenizerFocusable]="false"
Expand Down Expand Up @@ -84,6 +85,8 @@
[attr.aria-labelledby]="ariaLabelledBy"
(keydown)="_handleInputKeydown($event)"
[attr.id]="inputId"
(focus)="tokenizer._showAllTokens()"
(blur)="tokenizer._hideTokens()"
/>
</fd-tokenizer>
</fd-input-group>
Expand Down
1 change: 1 addition & 0 deletions libs/core/src/lib/multi-input/multi-input.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

.fd-input {
&.fd-multi-input-tokenizer-input {
min-width: 4rem;
margin-top: 0;
margin-bottom: 0;
padding-left: 0;
Expand Down
2 changes: 1 addition & 1 deletion libs/core/src/lib/multi-input/multi-input.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export class MultiInputComponent

/** Whether to use cozy visuals but compact collapsing behavior. */
@Input()
compactCollapse = false;
compactCollapse = true;

/** Max height of the popover. Any overflowing elements will be accessible through scrolling. */
@Input()
Expand Down
32 changes: 29 additions & 3 deletions libs/core/src/lib/token/token.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Component,
ElementRef,
EventEmitter,
inject,
Input,
OnDestroy,
Output,
Expand All @@ -13,10 +14,11 @@ import {
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import { Subscription } from 'rxjs';
import { KeyUtil } from '@fundamental-ngx/cdk/utils';
import { fromEvent, Subscription } from 'rxjs';
import { DestroyedService, KeyUtil } from '@fundamental-ngx/cdk/utils';
import { ENTER, SPACE } from '@angular/cdk/keycodes';
import { ContentDensityObserver, contentDensityObserverProviders } from '@fundamental-ngx/core/content-density';
import { takeUntil } from 'rxjs/operators';

/**
* A token is used to represent contextualizing information.
Expand All @@ -28,7 +30,10 @@ import { ContentDensityObserver, contentDensityObserverProviders } from '@fundam
styleUrls: ['./token.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [contentDensityObserverProviders()]
providers: [contentDensityObserverProviders(), DestroyedService],
host: {
'[style.max-width.%]': '100'
}
})
export class TokenComponent implements AfterViewInit, OnDestroy {
/** Whether the token is disabled. */
Expand Down Expand Up @@ -103,9 +108,18 @@ export class TokenComponent implements AfterViewInit, OnDestroy {
// eslint-disable-next-line @angular-eslint/no-output-on-prefix
onTokenKeydown = new EventEmitter<KeyboardEvent>();

/**
* Emitted when token element received or lost focus.
*/
@Output()
elementFocused = new EventEmitter<boolean>();

/** @hidden */
totalCount: number;

/** @hidden */
private readonly _destroy$ = inject(DestroyedService);

/** @hidden */
constructor(
public elementRef: ElementRef,
Expand All @@ -116,6 +130,18 @@ export class TokenComponent implements AfterViewInit, OnDestroy {
/** @hidden */
ngAfterViewInit(): void {
this._viewContainer.createEmbeddedView(this._content);

fromEvent(this.tokenWrapperElement.nativeElement, 'focus')
.pipe(takeUntil(this._destroy$))
.subscribe(() => {
this.elementFocused.emit(true);
});

fromEvent(this.tokenWrapperElement.nativeElement, 'blur')
.pipe(takeUntil(this._destroy$))
.subscribe(() => {
this.elementFocused.emit(false);
});
}

/** @hidden */
Expand Down
13 changes: 5 additions & 8 deletions libs/core/src/lib/token/tokenizer.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[class.fd-tokenizer--compact]="compact"
>
<div class="fd-tokenizer__inner" #tokenizerInner>
<div role="listbox">
<div role="listbox" [style.width]="_tokensContainerWidth">
<ng-content select="fd-token"></ng-content>
</div>

Expand Down Expand Up @@ -52,20 +52,17 @@
<ng-template #moreElement>
<span
(click)="moreClicked()"
*ngIf="
(moreTokensLeft.length > 0 || moreTokensRight.length > 0 || hiddenCozyTokenCount > 0) &&
!open &&
!_tokenizerHasFocus
"
*ngIf="_showMoreElement"
#moreElementSpan
class="fd-tokenizer-more"
role="button"
tabindex="0"
>
<ng-container *ngIf="compact || compactCollapse">
{{ 'coreTokenizer.moreLabel' | fdTranslate: { count: moreTokensLeft.length + moreTokensRight.length } }}
{{ 'coreTokenizer.moreLabel' | fdTranslate : { count: moreTokensLeft.length + moreTokensRight.length } }}
</ng-container>
<ng-container *ngIf="!compact && !compactCollapse">
{{ 'coreTokenizer.moreLabel' | fdTranslate: { count: hiddenCozyTokenCount } }}
{{ 'coreTokenizer.moreLabel' | fdTranslate : { count: hiddenCozyTokenCount } }}
</ng-container>
</span>
</ng-template>
9 changes: 4 additions & 5 deletions libs/core/src/lib/token/tokenizer.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('TokenizerComponent', () => {

it('should addEventListener to input during ngAfterViewInit and handle keydown', async () => {
spyOn(component, 'handleKeyDown');
await whenStable(fixture);
component.ngAfterViewInit();

await whenStable(fixture);
Expand Down Expand Up @@ -232,17 +233,15 @@ describe('TokenizerComponent', () => {
component.tokenList.forEach((token) => {
spyOn(token.tokenWrapperElement.nativeElement, 'getBoundingClientRect').and.returnValue({ width: 1 });
});
spyOn(component.input.nativeElement, 'getBoundingClientRect').and.returnValue({ width: 1 });
spyOn(component.input.nativeElement, 'getBoundingClientRect').and.returnValue({ width: 1 } as DOMRect);
});

it('should handle ngAfterContentInit', () => {
it('should handle resize', () => {
spyOn(component.elementRef().nativeElement, 'getBoundingClientRect').and.returnValue({ width: 1 });
spyOn(component, 'onResize');

component.ngAfterContentInit();
component.onResize();

expect(component.previousElementWidth).toBe(1);
expect(component.onResize).toHaveBeenCalled();
});

it('should get the hidden cozy token count AfterViewChecked', async () => {
Expand Down
Loading

0 comments on commit 7430869

Please sign in to comment.