Skip to content

Commit

Permalink
feat(tree-select): multiselect without checkbox + multiselect with sh…
Browse files Browse the repository at this point in the history
…ift and ctrl (#UIM-156) (#400)

* fixed input parameters and focus

* fixed selection with shift

* fixed selection and focus bugs

* fixed tests

* in dev example added parameter that switched on 'multiple' without checkboxes

* selecting in list and tree

* fixed tests

* fixed bugs
  • Loading branch information
lskramarov authored and pimenovoleg committed Feb 17, 2020
1 parent 1cfdb42 commit 99835cb
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 102 deletions.
2 changes: 1 addition & 1 deletion packages/cdk/keycodes/keycodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const MAC_META = 224;

type ModifierKey = 'altKey' | 'shiftKey' | 'ctrlKey' | 'metaKey';

export function hasModifierKey(event: KeyboardEvent, ...modifiers: ModifierKey[]): boolean {
export function hasModifierKey(event: KeyboardEvent | MouseEvent, ...modifiers: ModifierKey[]): boolean {
if (modifiers.length) {
return modifiers.some((modifier) => event[modifier]);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/mosaic-dev/list/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@

<h5>multiple selection</h5>
<mc-list-selection
class="mc-no-select"
multiple="keyboard"
[(ngModel)]="multipleSelected"
(selectionChange)="onSelectionChange($event)">
<mc-list-option value="Disabled" disabled>Disabled</mc-list-option>
<mc-list-option value="Normal">Normal</mc-list-option>
<mc-list-option value="Hovered" class="mc-hovered">Hovered</mc-list-option>
<mc-list-option value="Focused" class="mc-focused">Focused</mc-list-option>
<mc-list-option value="Disabled" disabled>Disabled</mc-list-option>
<mc-list-option value="Selected" class="mc-selected">Selected</mc-list-option>
</mc-list-selection>
<br>
Expand Down
2 changes: 1 addition & 1 deletion packages/mosaic-dev/tree/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class DemoComponent {

filterValue: string = '';

modelValue: any = ['Chrome'];
modelValue: any = [];
// modelValue: any[] = ['rootNode_1', 'Documents', 'Calendar', 'Chrome'];

disableState: boolean = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ export class MomentDateAdapter extends DateAdapter<Moment> {
}

openedRangeDateTime(startDate: Moment | null, endDate: Moment | null, template: IFormatterRangeTemplate) {
console.log('openedRangeDateTime: '); // tslint:disable-line:no-console
if (!moment.isMoment(startDate) && !moment.isMoment(endDate)) {
throw new Error(this.invalidDateErrorText);
}
Expand Down
82 changes: 58 additions & 24 deletions packages/mosaic/list/list-selection.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface McOptionEvent {
host: {
'[attr.tabindex]': 'tabIndex',

class: 'mc-list-option',
class: 'mc-list-option mc-no-select',
'[class.mc-selected]': 'selected',
'[class.mc-focused]': 'hasFocus',
'[class.mc-disabled]': 'disabled',
Expand Down Expand Up @@ -212,7 +212,9 @@ export class McListOption implements OnDestroy, OnInit, IFocusableOption {
handleClick($event) {
if (this.disabled) { return; }

this.listSelection.setFocusedOption(this, $event);
this.listSelection.setSelectedOptionsByClick(
this, hasModifierKey($event, 'shiftKey'), hasModifierKey($event, 'ctrlKey')
);
}

focus() {
Expand Down Expand Up @@ -365,6 +367,11 @@ export class McListSelection extends McListSelectionMixinBase implements CanDisa
this.multipleMode = MultipleMode.CHECKBOX;
}

if (this.multipleMode === MultipleMode.CHECKBOX) {
this.autoSelect = false;
this.noUnselect = false;
}

this._tabIndex = parseInt(tabIndex) || 0;

this.selectionModel = new SelectionModel<McListOption>(this.multiple);
Expand Down Expand Up @@ -453,31 +460,29 @@ export class McListSelection extends McListSelectionMixinBase implements CanDisa
this.keyManager.withScrollSize(Math.floor(this.getHeight() / this.options.first.getHeight()));
}

// Sets the focused option of the selection-list.
setFocusedOption(option: McListOption, $event?: KeyboardEvent): void {
this.keyManager.setActiveItem(option);

const withShift = $event ? hasModifierKey($event, 'shiftKey') : false;
const withCtrl = $event ? hasModifierKey($event, 'ctrlKey') : false;

if (withShift && this.multiple) {
const previousIndex = this.keyManager.previousActiveItemIndex;
const activeIndex = this.keyManager.activeItemIndex;
setSelectedOptionsByClick(option: McListOption, shiftKey: boolean, ctrlKey: boolean): void {
if (shiftKey && this.multiple) {
this.setSelectedOptions(option);
} else if (ctrlKey) {
if (!this.canDeselectLast(option)) { return; }

if (previousIndex < activeIndex) {
this.options.forEach((item, index) => {
if (index >= previousIndex && index <= activeIndex) { item.setSelected(true); }
});
} else {
this.options.forEach((item, index) => {
if (index >= activeIndex && index <= previousIndex) { item.setSelected(true); }
});
this.selectionModel.toggle(option);
} else {
if (this.autoSelect) {
this.options.forEach((item) => item.setSelected(false));
option.setSelected(true);
}
} else if (withCtrl) {
}

if (!this.canDeselectLast(option)) { return; }
this.emitChangeEvent(option);
this.reportValueChange();
}

option.toggle();
setSelectedOptionsByKey(option: McListOption, shiftKey: boolean, ctrlKey: boolean): void {
if (shiftKey && this.multiple) {
this.setSelectedOptions(option);
} else if (ctrlKey) {
if (!this.canDeselectLast(option)) { return; }
} else {
if (this.autoSelect) {
this.options.forEach((item) => item.setSelected(false));
Expand All @@ -489,6 +494,31 @@ export class McListSelection extends McListSelectionMixinBase implements CanDisa
this.reportValueChange();
}

setSelectedOptions(option: McListOption): void {
const selectedOptionState = option.selected;

let fromIndex = this.keyManager.previousActiveItemIndex;
let toIndex = this.keyManager.previousActiveItemIndex = this.keyManager.activeItemIndex;

if (toIndex === fromIndex) { return; }

if (fromIndex > toIndex) {
[fromIndex, toIndex] = [toIndex, fromIndex];
}

this.options
.toArray()
.slice(fromIndex, toIndex + 1)
.filter((item) => !item.disabled)
.forEach((renderedOption) => {
const isLastRenderedOption = renderedOption === this.keyManager.activeItem;

if (isLastRenderedOption && renderedOption.selected && this.noUnselect) { return; }

renderedOption.setSelected(!selectedOptionState);
});
}

// Implemented as part of ControlValueAccessor.
writeValue(values: string[]): void {
if (this.options) {
Expand Down Expand Up @@ -607,7 +637,11 @@ export class McListSelection extends McListSelectionMixinBase implements CanDisa

event.preventDefault();

this.setFocusedOption(this.keyManager.activeItem as McListOption, event);
this.setSelectedOptionsByKey(
this.keyManager.activeItem as McListOption,
hasModifierKey(event, 'shiftKey'),
hasModifierKey(event, 'ctrlKey')
);
}

// Reports a value change to the ControlValueAccessor
Expand Down
7 changes: 2 additions & 5 deletions packages/mosaic/list/list.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#### With default parameters (autoselect="true", no-unselect="true")
<!-- example(list-overview) -->

### Single mode with groups
<!-- example(list-groups) -->

### Multiple mode with checkboxes
<!-- example(list-multiple-checkbox) -->


### Multiple mode without checkboxes
<!-- example(list-multiple-keyboard) -->


### Multiple mode without checkboxes
<!-- example(list-groups) -->
6 changes: 4 additions & 2 deletions packages/mosaic/tree-select/tree-select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ class BasicSelectOnPushPreselected {
template: `
<mc-form-field>
<mc-tree-select
multiple
[multiple]="true"
placeholder="Food"
[formControl]="control">
Expand Down Expand Up @@ -1063,7 +1063,7 @@ class BasicSelectWithoutFormsPreselected {
@Component({
template: `
<mc-form-field>
<mc-tree-select placeholder="Food" [(ngModel)]="selectedFoods" multiple>
<mc-tree-select placeholder="Food" [(ngModel)]="selectedFoods" [multiple]="true">
<mc-tree-selection
[dataSource]="dataSource"
[treeControl]="treeControl">
Expand Down Expand Up @@ -4345,6 +4345,7 @@ describe('McTreeSelect', () => {
it('should be able to select multiple values', fakeAsync(() => {
trigger.click();
fixture.detectChanges();
flush();

const options: NodeListOf<HTMLElement> = overlayContainerElement.querySelectorAll('mc-tree-option');

Expand Down Expand Up @@ -4438,6 +4439,7 @@ describe('McTreeSelect', () => {
it('should not close the panel when clicking on options', fakeAsync(() => {
trigger.click();
fixture.detectChanges();
flush();

expect(testInstance.select.panelOpen).toBe(true);

Expand Down
16 changes: 13 additions & 3 deletions packages/mosaic/tree-select/tree-select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ import {
RIGHT_ARROW,
SPACE,
UP_ARROW,
A, PAGE_UP, PAGE_DOWN
A,
PAGE_UP,
PAGE_DOWN,
hasModifierKey
} from '@ptsecurity/cdk/keycodes';
import { CdkTree } from '@ptsecurity/cdk/tree';
import {
Expand Down Expand Up @@ -487,7 +490,10 @@ export class McTreeSelect extends McTreeSelectMixinBase implements

this.options = this.tree.renderedOptions;
this.tree.autoSelect = this.autoSelect;
this.tree.multipleMode = this.multiple ? MultipleMode.CHECKBOX : null;

if (this.tree.multipleMode === null) {
this.tree.multipleMode = this.multiple ? MultipleMode.CHECKBOX : null;
}

if (this.multiple) {
this.tree.noUnselectLast = false;
Expand Down Expand Up @@ -518,6 +524,7 @@ export class McTreeSelect extends McTreeSelectMixinBase implements
.pipe(takeUntil(this.destroy))
.subscribe((event) => {
if (event.added.length) {
this.tree.keyManager.setFocusOrigin('program');
this.tree.keyManager.setActiveItem(
this.options.find((option) => option.data === event.added[0]) as any
);
Expand Down Expand Up @@ -925,6 +932,7 @@ export class McTreeSelect extends McTreeSelectMixinBase implements
} else {
const previouslyFocusedIndex = this.tree.keyManager.activeItemIndex;

this.tree.keyManager.setFocusOrigin('keyboard');
this.tree.keyManager.onKeydown(event);

if (
Expand All @@ -935,7 +943,9 @@ export class McTreeSelect extends McTreeSelectMixinBase implements
}

if (this.autoSelect && this.tree.keyManager.activeItem) {
this.tree.setSelectedOption(this.tree.keyManager.activeItem);
this.tree.setSelectedOptionsByKey(
this.tree.keyManager.activeItem, hasModifierKey(event, 'shiftKey'), hasModifierKey(event, 'ctrlKey')
);
}
}
}
Expand Down
13 changes: 9 additions & 4 deletions packages/mosaic/tree/tree-option.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
AfterContentInit,
NgZone
} from '@angular/core';
import { FocusOrigin } from '@ptsecurity/cdk/a11y';
import { hasModifierKey } from '@ptsecurity/cdk/keycodes';
import { CdkTreeNode } from '@ptsecurity/cdk/tree';
import { CanDisable } from '@ptsecurity/mosaic/core';
import { Subject } from 'rxjs';
Expand Down Expand Up @@ -164,7 +166,9 @@ export class McTreeOption extends CdkTreeNode<McTreeOption> implements CanDisabl
this.changeDetectorRef.markForCheck();
}

focus() {
focus(focusOrigin?: FocusOrigin) {
if (focusOrigin === 'program') { return; }

if (this.disabled || this.hasFocus) { return; }

this.elementRef.nativeElement.focus();
Expand Down Expand Up @@ -227,9 +231,10 @@ export class McTreeOption extends CdkTreeNode<McTreeOption> implements CanDisabl
this.changeDetectorRef.markForCheck();
this.emitSelectionChangeEvent(true);

if (this.tree.setSelectedOption) {
this.tree.setSelectedOption(this, $event);
}
const shiftKey = $event ? hasModifierKey($event, 'shiftKey') : false;
const ctrlKey = $event ? hasModifierKey($event, 'ctrlKey') : false;

this.tree.setSelectedOptionsByClick(this, shiftKey, ctrlKey);
}
}

Expand Down
Loading

0 comments on commit 99835cb

Please sign in to comment.