Skip to content
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

Add Chips to paper-menu branch #501

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
306afda
implementing basic chips
mansona Jun 26, 2015
27625f1
adding the ability to include custom templates
mansona Jun 26, 2015
2c98dcb
updating the demo
mansona Jun 26, 2015
b9746ba
fixing deprecation warning
mansona Jun 29, 2015
125e5fa
fixing chips implementation based on new master
mansona Jul 4, 2015
0207bbb
escaping {{item}}
mansona Dec 16, 2015
6ce3c38
fixing scrolling issue
mansona Dec 16, 2015
bd2b51a
adding basic contact chips
mansona Dec 16, 2015
c1fd632
importing chips from angular-material
mansona Jan 25, 2016
6c83bbc
convering template to hasBlock
mansona Feb 2, 2016
93e09d2
cleaning up code to jscs style
mansona Apr 22, 2016
d364acb
fixing jscs
mansona Apr 22, 2016
460adbc
fixing the location of templates
mansona Jun 14, 2016
87e1c1d
putting back overflow hidden that was removed in previous commit
mansona Jun 14, 2016
ed7b91d
action related feedback
mansona Jun 14, 2016
37f1b9d
more action realated feedback
mansona Jun 14, 2016
d6aef6d
Ttrigger add/remove contact actions properly in Chips demo
pauln Sep 18, 2016
973b03e
Fix misplaced comma from my sloppy merge
pauln Sep 20, 2016
06a0de0
Make paper-contact-chips work with new paper-autocomplete (based on e…
pauln Sep 20, 2016
1d5882a
Wrap contact chips in an md-chips tag so that styles are applied corr…
pauln Sep 20, 2016
f2d6fb5
Make chips' "remove" button icons a more suitable size
pauln Sep 20, 2016
068f826
Pass placeholder text from paper-contact-chips through to underlying …
pauln Sep 20, 2016
b4e284e
Update placeholder text in paper-content-chips demo
pauln Sep 20, 2016
0fe0d40
Make contact chips look and act more like a paper-input
pauln Sep 20, 2016
6aa601c
Enable keyboard navigation of chips
pauln Sep 21, 2016
1238bf9
Clear active chip when losing focus
pauln Sep 21, 2016
8695abb
Refactor keyboard navigation in chips to make it easier for contact c…
pauln Sep 21, 2016
dd3f366
Make contact chips keyboard navigable
pauln Sep 21, 2016
f713383
Move autocomplete from contact chips to chips
pauln Sep 22, 2016
5d7e8fc
Add autocomplete demos for regular (non-contact) chips
pauln Sep 22, 2016
c321462
JSHint / JSCS compliance for chips
pauln Sep 22, 2016
ae28fc9
More JSHint / JSCS compliance for chips
pauln Sep 22, 2016
ab4db9a
One last JSHint / JSCS compliance fix for chips
pauln Sep 22, 2016
443d749
Remove requireMatch=true from chips autocomplete demo which shouldn't…
pauln Sep 22, 2016
3b00544
Improvements to focus handling for chips / contact-chips
pauln Sep 28, 2016
3f83ac1
Rename "componentFocused" to "isFocused" in paper-chips / paper-conta…
pauln Sep 30, 2016
e48c1d8
Make chips compatible with latest iteration of paper-autocomplete
pauln Oct 25, 2016
79c5d85
Fix JSCS direct property access violations in Chips
pauln Oct 25, 2016
8606fb4
Fix JSCS direct property access violations in chips demo
pauln Oct 25, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions addon/components/paper-chips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import Ember from 'ember';

const { Component, isEmpty, isPresent, computed, observer, run } = Ember;

export default Component.extend({
tagName: 'md-chips',
classNames: ['md-default-theme'],
activeChip: -1,
focusedElement: 'none',
isFocused: computed('focusedElement', function() {
if (this.get('focusedElement') === 'none') {
return false;
}

return true;
}),
resetTimer: null,
lastItemChosen: false,

handleFocusChange: observer('focusedElement', 'activeChip', function() {
let element = this.get('focusedElement');

if (!this.get('isFocused')) {
this.set('activeChip', -1);
}

if ((element === 'chips' && (this.get('activeChip') !== -1)) || element === 'input') {
this.sendAction('focusIn', window.event);
} else {
this.sendAction('focusOut', window.event);
}
}),

actions: {
addItem(newItem) {
if (this.get('requireMatch')) {
// Don't add a new item - we're set to require a match.
return;
}

if (isPresent(newItem)) {
let item = newItem;

if (isPresent(this.get('searchField'))) {
item = {};
item[this.get('searchField')] = newItem;
}

this.sendAction('addItem', item);
this.set('newChipValue', '');

if (isPresent(this.get('autocomplete'))) {
// We have an autocomplete - reset it once it's closed itself.
this.queueReset();
}
}
},

inputFocus(autocomplete) {
let input = this.getInput();

this.set('focusedElement', 'input');

if (!this.get('content').length && !input.is(':focus')) {
input.focus();
} else {
this.set('activeChip', -1);
}

// Keep track of the autocomplete, so we can force it to close when navigating to chips.
if (isEmpty(this.get('autocomplete')) && input.is('.ember-power-select-typeahead-input')) {
this.set('autocomplete', autocomplete);
}
},

inputBlur() {
if (this.focusMovingTo('.ember-power-select-option')) {
// Focus has shifted to an item - don't mess with this event.
return true;
}

if (this.get('lastItemChosen')) {
// Last item has been chosen; select will be replaced with an input - ignore blur event.
this.set('lastItemChosen', false);
return true;
}

this.closeAutocomplete();

if (!this.focusMovingTo('md-chips-wrap')) {
this.set('focusedElement', 'none');
}
},

chipsFocus() {
this.set('focusedElement', 'chips');
},

chipsBlur() {
if (!this.focusMovingTo(this.getInput())) {
this.set('focusedElement', 'none');
}
},

autocompleteChange(item) {
if (item) {
// Trigger onChange for the new item.
this.sendAction('addItem', item);

this.queueReset();

// Track selection of last item if no match required.
if (this.get('source').length === 1 && !this.get('requireMatch')) {
this.set('lastItemChosen', true);
this.set('autocomplete', null);
}

return true;
}
},

keyDown(event) {
let [input] = this.getInput();
if (!this.get('readOnly') && isEmpty(input.value) && isPresent(this.get('content'))) {
this.keyboardNavigation(event);
if (this.get('activeChip') >= 0) {
this.closeAutocomplete();
}
} else {
// Make sure we don't leave a chip focused while typing.
this.set('activeChip', -1);
this.set('focusedElement', 'input');
}
},

noUnselected(old, event) {
if (['Backspace', 'Delete', 'Del', 'ArrowLeft', 'Left', 'ArrowRight', 'Right'].includes(event.key)) {
this.sendAction('keyDown', event);
} else if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
// Reject printable key presses
event.preventDefault();
event.stopPropagation();
return false;
}

}
},

keyboardNavigation({ key }) {
// No text has been entered, but we have chips; cursor keys should select chips.
let current = this.get('activeChip');
let chips = this.get('content');
let input = this.getInput();

if (['ArrowLeft', 'Left'].includes(key) || (key === 'Backspace' && current === -1)) {
if (current === -1) {
input.blur();
this.$('md-chips-wrap', this.element).focus();
this.set('activeChip', chips.length - 1);
} else if (current > 0) {
this.decrementProperty('activeChip');
}
} else if (['ArrowRight', 'Right'].includes(key)) {
if (current >= 0) {
this.incrementProperty('activeChip');
}

if (this.get('activeChip') >= chips.length) {
this.set('activeChip', -1);
input.focus();
}
} else if (current >= 0 && ['Backspace', 'Delete', 'Del'].includes(key)) {
this.sendAction('removeItem', chips[current]);
if (current >= chips.length) {
this.queueReset();
this.set('activeChip', -1);
}
}
},

resetInput() {
let select = this.get('autocomplete');
let input = this.getInput();

if (input.is('.ember-power-select-typeahead-input') && isPresent(select)) {
// Reset the underlying ember-power-select so that it's ready for another selection.
input.val('');
select.actions.search('');

// Re-open ember-power-select to trigger it to reposition the dropdown.
select.actions.close();
select.actions.open();
}

input.focus();

this.set('focusedElement', 'input');
this.set('resetTimer', null);
},

queueReset() {
if (this.get('resetTimer')) {
run.cancel(this.get('resetTimer'));
}

this.set('resetTimer', run.next(this, this.resetInput));
},

closeAutocomplete() {
if (!isEmpty(this.get('autocomplete')) && !isEmpty(this.get('autocomplete').actions)) {
this.get('autocomplete').actions.close();
}
},

getInput() {
return this.$('.md-chip-input-container input');
},

focusMovingTo(selector) {
if (!isEmpty(event) && !isEmpty(event.relatedTarget) && this.$(event.relatedTarget).is(selector)) {
return true;
}

return false;
}
});
6 changes: 6 additions & 0 deletions addon/components/paper-contact-chips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import PaperChips from 'ember-paper/components/paper-chips';

export default PaperChips.extend({
tagName: 'md-contact-chips',
classNames: ['md-default-theme']
});
3 changes: 3 additions & 0 deletions app/components/paper-chips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import PaperChips from 'ember-paper/components/paper-chips';

export default PaperChips;
3 changes: 3 additions & 0 deletions app/components/paper-contact-chips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import PaperContactChips from 'ember-paper/components/paper-contact-chips';

export default PaperContactChips;
3 changes: 3 additions & 0 deletions app/styles/ember-paper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@

@import './angular-material/components/virtualRepeat/virtual-repeater';

@import './angular-material/components/chips/chips';
@import './angular-material/components/chips/chips-theme';

// Backports of features from future versions of angular-material-source to this version
@import './backports/paper-input';
@import './backports/paper-select';
Expand Down
39 changes: 39 additions & 0 deletions app/templates/components/paper-chips.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<md-chips-wrap class="md-chips {{if isFocused 'md-focused'}}" tabindex="-1" onkeydown={{action 'keyDown'}} onfocus={{action 'chipsFocus'}} onblur={{action 'chipsBlur'}}>
{{#each content as |item index|}}
<md-chip class="md-chip md-default-theme {{if (eq activeChip index) 'md-focused'}}">
<div class="md-chip-content" tabindex="-1" aria-hidden="true">
{{#if hasBlock}}
{{yield item}}
{{else}}
<span>{{item}}</span>
{{/if}}
</div>
<div class="md-chip-remove-container">
{{#unless readOnly}}
<button class="md-chip-remove" {{action (action removeItem item)}} type="button" aria-hidden="true" tabindex="-1">
{{paper-icon icon="clear" size=18}}
<span class="md-visually-hidden"> Remove </span>
</button>
{{/unless}}
</div>
{{#unless readOnly}}
<span class="md-visually-hidden"> Press delete to remove this chip. </span>
{{/unless}}
</md-chip>
{{/each}}
{{#unless readOnly}}
<div class="md-chip-input-container">
{{#if (or requireMatch source)}}
{{#paper-autocomplete closeOnSelect=false onBlur=(action 'inputBlur') onSelectionChange=(action 'autocompleteChange') onFocus=(action 'inputFocus') onOpen=(action 'inputFocus') onCreate=(action 'addItem') placeholder=placeholder options=source searchField=searchField noMatchesMessage=noMatchesMessage as |item|}}
{{#if hasBlock}}
{{yield item}}
{{else}}
<span>{{item}}</span>
{{/if}}
{{/paper-autocomplete}}
{{else}}
{{input tabindex="0" placeholder=placeholder aria-label="Add Tag" value=newChipValue focus-in="inputFocus" focus-out="inputBlur" enter=(action 'addItem' newChipValue)}}
{{/if}}
</div>
{{/unless}}
</md-chips-wrap>
37 changes: 37 additions & 0 deletions app/templates/components/paper-contact-chips.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<md-chips class="md-chips md-contact-chips {{if isFocused 'md-focused'}}">
<md-chips-wrap class="md-chips md-contact-chips {{if isFocused 'md-focused'}}" tabindex="-1" onkeydown={{action 'keyDown'}} onfocus={{action 'chipsFocus'}} onblur={{action 'chipsBlur'}}>
{{#each content as |item index|}}
<md-chip class="md-chip md-default-theme {{if (eq activeChip index) 'md-focused'}}">
<div class="md-chip-content" tabindex="-1" aria-hidden="true">
<div class="md-contact-avatar">
<img src={{item.image}}>
</div>
<span class="md-contact-name">{{item.name}}</span>
</div>
<div class="md-chip-remove-container">
{{#unless readOnly}}
<button class="md-chip-remove" {{action (action removeItem item)}} type="button" aria-hidden="true" tabindex="-1">
{{paper-icon icon="clear" size=18}}
<span class="md-visually-hidden"> Remove </span>
</button>
{{/unless}}
</div>
{{#unless readOnly}}
<span class="md-visually-hidden"> Press delete to remove this chip. </span>
{{/unless}}
</md-chip>
{{/each}}
{{#unless readOnly}}
<div class="md-chip-input-container">
{{#paper-autocomplete closeOnSelect=false onBlur=(action 'inputBlur') onSelectionChange=(action 'autocompleteChange') onFocus=(action 'inputFocus') onOpen=(action 'inputFocus') placeholder=placeholder options=source searchField="email" as |item select|}}
<div class="md-contact-suggestion">
<img src={{item.image}} alt={{item.name}} class="md-contact-avatar">

<span class="md-contact-name">{{item.name}}</span>
<span class="md-contact-email">{{item.email}}</span>
</div>
{{/paper-autocomplete}}
</div>
{{/unless}}
</md-chips-wrap>
</md-chips>
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ module.exports = {
'components/dialog/dialog.scss',
'components/dialog/dialog-theme.scss',

'components/virtualRepeat/virtual-repeater.scss'
'components/virtualRepeat/virtual-repeater.scss',

'components/chips/chips.scss',
'components/chips/chips-theme.scss'
];

var angularScssFiles = new Funnel(this.pathBase(), {
Expand Down
Loading