-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(material-experimental/mdc-chips): Make chips editable by connecting to the mdc web editing interface #19618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
e2b412b
5fc8cff
7c3a7d5
ddf746b
c6cc7d7
31be702
8c5d3e1
b0cf17c
bbf240b
9b716ca
f11ac3b
8e81881
638a6c0
66c5502
98f370e
4009d91
eabea48
b7f068b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import {Component, DebugElement} from '@angular/core'; | ||
import {async, TestBed, ComponentFixture} from '@angular/core/testing'; | ||
import {MatChipEditInput, MatChipsModule} from './index'; | ||
import {By} from '@angular/platform-browser'; | ||
|
||
|
||
describe('MDC-based MatChipEditInput', () => { | ||
const DEFAULT_INITIAL_VALUE = 'INITIAL_VALUE'; | ||
|
||
let fixture: ComponentFixture<any>; | ||
let inputDebugElement: DebugElement; | ||
let inputInstance: MatChipEditInput; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [MatChipsModule], | ||
declarations: [ | ||
ChipEditInputContainer, | ||
], | ||
}); | ||
|
||
TestBed.compileComponents(); | ||
|
||
fixture = TestBed.createComponent(ChipEditInputContainer); | ||
inputDebugElement = fixture.debugElement.query(By.directive(MatChipEditInput))!; | ||
inputInstance = inputDebugElement.injector.get<MatChipEditInput>(MatChipEditInput); | ||
})); | ||
|
||
describe('on initialization', () => { | ||
it('should set the initial input text', () => { | ||
inputInstance.initialize(DEFAULT_INITIAL_VALUE); | ||
expect(inputInstance.getNativeElement().textContent).toEqual(DEFAULT_INITIAL_VALUE); | ||
}); | ||
|
||
it('should focus the input', () => { | ||
inputInstance.initialize(DEFAULT_INITIAL_VALUE); | ||
expect(document.activeElement).toEqual(inputInstance.getNativeElement()); | ||
}); | ||
}); | ||
|
||
it('should update the internal value as it is set', () => { | ||
inputInstance.initialize(DEFAULT_INITIAL_VALUE); | ||
const newValue = 'NEW_VALUE'; | ||
inputInstance.setValue(newValue); | ||
expect(inputInstance.getValue()).toEqual(newValue); | ||
}); | ||
}); | ||
|
||
@Component({ | ||
template: `<span matChipEditInput></span>`, | ||
}) | ||
class ChipEditInputContainer {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import { | ||
Directive, | ||
ElementRef, | ||
} from '@angular/core'; | ||
|
||
/** | ||
* A directive that makes a span editable and exposes functions to modify and retrieve the | ||
* element's contents. | ||
*/ | ||
@Directive({ | ||
selector: 'span[matChipEditInput]', | ||
host: { | ||
'class': 'mdc-chip__primary-action mat-chip-edit-input', | ||
'role': 'textbox', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jelbourn are you aware of any a11y issues with this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really know the a11y nuances of |
||
'tabindex': '-1', | ||
'contenteditable': 'true', | ||
}, | ||
}) | ||
export class MatChipEditInput { | ||
constructor( | ||
private readonly _elementRef: ElementRef, | ||
kowsen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) {} | ||
|
||
initialize(initialValue: string) { | ||
this.getNativeElement().focus(); | ||
this.setValue(initialValue); | ||
} | ||
|
||
getNativeElement(): HTMLElement { | ||
return this._elementRef.nativeElement; | ||
} | ||
|
||
setValue(value: string) { | ||
this.getNativeElement().innerText = value; | ||
this._moveCursorToEndOfInput(); | ||
} | ||
|
||
getValue(): string { | ||
return this.getNativeElement().textContent || ''; | ||
} | ||
|
||
private _moveCursorToEndOfInput() { | ||
const range = document.createRange(); | ||
kowsen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
range.selectNodeContents(this.getNativeElement()); | ||
range.collapse(false); | ||
const sel = window.getSelection()!; | ||
sel.removeAllRanges(); | ||
sel.addRange(range); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,32 @@ | ||
<span class="mdc-chip__ripple"></span> | ||
<ng-container *ngIf="!_isEditing()"> | ||
<span class="mdc-chip__ripple"></span> | ||
|
||
<span matRipple class="mat-mdc-chip-ripple" | ||
[matRippleAnimation]="_rippleAnimation" | ||
[matRippleDisabled]="_isRippleDisabled()" | ||
[matRippleCentered]="_isRippleCentered" | ||
[matRippleTrigger]="_elementRef.nativeElement"></span> | ||
<span matRipple class="mat-mdc-chip-ripple" | ||
[matRippleAnimation]="_rippleAnimation" | ||
[matRippleDisabled]="_isRippleDisabled()" | ||
[matRippleCentered]="_isRippleCentered" | ||
[matRippleTrigger]="_elementRef.nativeElement"></span> | ||
</ng-container> | ||
|
||
<div role="gridcell"> | ||
<div #chipContent tabindex="-1" | ||
class="mat-chip-row-focusable-text-content mat-mdc-focus-indicator"> | ||
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content> | ||
<span class="mdc-chip__text"><ng-content></ng-content></span> | ||
<ng-content select="mat-chip-trailing-icon,[matChipTrailingIcon]"></ng-content> | ||
<div class="mat-chip-content"> | ||
kowsen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<div role="gridcell"> | ||
<div #chipContent tabindex="-1" | ||
class="mat-chip-row-focusable-text-content mat-mdc-focus-indicator mdc-chip__primary-action" | ||
kowsen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[attr.role]="editable ? 'button' : null"> | ||
<ng-content select="mat-chip-avatar, [matChipAvatar]"></ng-content> | ||
<span class="mdc-chip__text"><ng-content></ng-content></span> | ||
<ng-content select="mat-chip-trailing-icon,[matChipTrailingIcon]"></ng-content> | ||
</div> | ||
</div> | ||
<div role="gridcell" *ngIf="removeIcon"> | ||
<ng-content select="[matChipRemove]"></ng-content> | ||
</div> | ||
</div> | ||
<div role="gridcell" *ngIf="removeIcon"> | ||
<ng-content select="[matChipRemove]"></ng-content> | ||
</div> | ||
|
||
<div *ngIf="_isEditing()" role="gridcell" class="mat-chip-edit-input-container"> | ||
kowsen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<ng-content *ngIf="contentEditInput; else defaultMatChipEditInput" | ||
select="[matChipEditInput]"></ng-content> | ||
<ng-template #defaultMatChipEditInput> | ||
<span matChipEditInput></span> | ||
</ng-template> | ||
</div> |
Uh oh!
There was an error while loading. Please reload this page.