FIX: select-kit refactoring

- improve mini-tag-chooser keyboard behavior
- all multil select now respond to select all and left/right arrows
- improve events handling
- many minor tweaks
This commit is contained in:
Joffrey JAFFEUX
2018-03-22 11:29:55 +01:00
committed by GitHub
parent e053697cfa
commit d48542796e
54 changed files with 717 additions and 573 deletions

View File

@ -10,7 +10,7 @@ export default DropdownSelectBox.extend({
headerIcon: "thumbs-o-up", headerIcon: "thumbs-o-up",
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
content.name = `${I18n.t("admin.flags.agree")}...`; content.name = `${I18n.t("admin.flags.agree")}...`;
return content; return content;
}, },

View File

@ -1,5 +1,6 @@
import DropdownSelectBox from "select-kit/components/dropdown-select-box"; import DropdownSelectBox from "select-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
const { get } = Ember;
export default DropdownSelectBox.extend({ export default DropdownSelectBox.extend({
classNames: ["delete-flag", "admin-delete-flag-dropdown"], classNames: ["delete-flag", "admin-delete-flag-dropdown"],
@ -8,7 +9,7 @@ export default DropdownSelectBox.extend({
headerIcon: "trash-o", headerIcon: "trash-o",
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
content.name = `${I18n.t("admin.flags.delete")}...`; content.name = `${I18n.t("admin.flags.delete")}...`;
return content; return content;
}, },
@ -55,7 +56,7 @@ export default DropdownSelectBox.extend({
mutateValue(value) { mutateValue(value) {
const computedContentItem = this.get("computedContent").findBy("value", value); const computedContentItem = this.get("computedContent").findBy("value", value);
Ember.get(computedContentItem, "originalContent.action")(); get(computedContentItem, "originalContent.action")();
}, },
actions: { actions: {

View File

@ -18,9 +18,9 @@ export default MultiSelectComponent.extend({
}, },
computeContentItem(contentItem, name) { computeContentItem(contentItem, name) {
let computedContent = this.baseComputedContentItem(contentItem, name); let computedContentItem = this._super(contentItem, name);
computedContent.locked = contentItem.automatic; computedContentItem.locked = contentItem.automatic;
return computedContent; return computedContentItem;
}, },
mutateValues(values) { mutateValues(values) {

View File

@ -59,7 +59,7 @@ export default ComboBoxComponent.extend({
}, },
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
if (this.get("hasSelection")) { if (this.get("hasSelection")) {
const category = Category.findById(content.value); const category = Category.findById(content.value);

View File

@ -68,7 +68,7 @@ export default ComboBoxComponent.extend({
}, },
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
if (this.get("hasSelection")) { if (this.get("hasSelection")) {
const category = Category.findById(content.value); const category = Category.findById(content.value);

View File

@ -6,8 +6,8 @@ export default ComboBoxSelectBoxHeaderComponent.extend({
layoutName: "select-kit/templates/components/category-drop/category-drop-header", layoutName: "select-kit/templates/components/category-drop/category-drop-header",
classNames: "category-drop-header", classNames: "category-drop-header",
classNameBindings: ['categoryStyleClass'], classNameBindings: ["categoryStyleClass"],
categoryStyleClass: Ember.computed.alias('site.category_style'), categoryStyleClass: Ember.computed.alias("site.category_style"),
@computed("computedContent.value", "computedContent.name") @computed("computedContent.value", "computedContent.name")
category(value, name) { category(value, name) {

View File

@ -1,20 +1,15 @@
import NotificationOptionsComponent from "select-kit/components/notifications-button"; import NotificationOptionsComponent from "select-kit/components/notifications-button";
import computed from "ember-addons/ember-computed-decorators";
export default NotificationOptionsComponent.extend({ export default NotificationOptionsComponent.extend({
pluginApiIdentifiers: ["category-notifications-button"], pluginApiIdentifiers: ["category-notifications-button"],
classNames: "category-notifications-button", classNames: "category-notifications-button",
isHidden: Ember.computed.or("category.deleted", "site.isMobileDevice"), isHidden: Ember.computed.or("category.deleted", "site.isMobileDevice"),
headerIcon: Ember.computed.alias("iconForSelectedDetails"),
i18nPrefix: "category.notifications", i18nPrefix: "category.notifications",
showFullTitle: false, showFullTitle: false,
allowInitialValueMutation: false, allowInitialValueMutation: false,
mutateValue(value) { mutateValue(value) {
this.get("category").setNotification(value); this.get("category").setNotification(value);
},
@computed("iconForSelectedDetails")
headerIcon(iconForSelectedDetails) {
return [iconForSelectedDetails];
} }
}); });

View File

@ -1,5 +1,5 @@
import SingleSelectComponent from "select-kit/components/single-select"; import SingleSelectComponent from "select-kit/components/single-select";
import { on } from "ember-addons/ember-computed-decorators"; import { on, default as computed } from "ember-addons/ember-computed-decorators";
export default SingleSelectComponent.extend({ export default SingleSelectComponent.extend({
pluginApiIdentifiers: ["combo-box"], pluginApiIdentifiers: ["combo-box"],
@ -12,16 +12,19 @@ export default SingleSelectComponent.extend({
clearable: false, clearable: false,
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
content.hasSelection = this.get("hasSelection"); content.hasSelection = this.get("hasSelection");
return content; return content;
}, },
@computed("isExpanded", "caretUpIcon", "caretDownIcon")
caretIcon(isExpanded, caretUpIcon, caretDownIcon) {
return isExpanded ? caretUpIcon : caretDownIcon;
},
@on("didReceiveAttrs") @on("didReceiveAttrs")
_setComboBoxOptions() { _setComboBoxOptions() {
this.get("headerComponentOptions").setProperties({ this.get("headerComponentOptions").setProperties({
caretUpIcon: this.get("caretUpIcon"),
caretDownIcon: this.get("caretDownIcon"),
clearable: this.get("clearable"), clearable: this.get("clearable"),
}); });
} }

View File

@ -8,14 +8,5 @@ export default SelectKitHeaderComponent.extend({
clearable: Ember.computed.alias("options.clearable"), clearable: Ember.computed.alias("options.clearable"),
caretUpIcon: Ember.computed.alias("options.caretUpIcon"), caretUpIcon: Ember.computed.alias("options.caretUpIcon"),
caretDownIcon: Ember.computed.alias("options.caretDownIcon"), caretDownIcon: Ember.computed.alias("options.caretDownIcon"),
shouldDisplayClearableButton: Ember.computed.and("clearable", "computedContent.hasSelection")
@computed("isExpanded", "caretUpIcon", "caretDownIcon")
caretIcon(isExpanded, caretUpIcon, caretDownIcon) {
return isExpanded === true ? caretUpIcon : caretDownIcon;
},
@computed("clearable", "computedContent.hasSelection")
shouldDisplayClearableButton(clearable, hasSelection) {
return clearable === true && hasSelection === true;
}
}); });

View File

@ -45,7 +45,7 @@ export default DropdownSelectBoxComponent.extend({
}, },
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
switch (this.get("action")) { switch (this.get("action")) {
case PRIVATE_MESSAGE: case PRIVATE_MESSAGE:

View File

@ -21,7 +21,7 @@ export default SingleSelectComponent.extend({
}, },
didClickOutside() { didClickOutside() {
if (this.get("isExpanded") === false) return; if (!this.get("isExpanded")) return;
this.close(); this.close();
}, },

View File

@ -10,6 +10,6 @@ export default SelectKitHeaderComponent.extend({
@computed("options.showFullTitle") @computed("options.showFullTitle")
btnClassName(showFullTitle) { btnClassName(showFullTitle) {
return `btn ${showFullTitle ? 'btn-icon-text' : 'no-text btn-icon'}`; return `btn ${showFullTitle ? "btn-icon-text" : "no-text btn-icon"}`;
} }
}); });

View File

@ -120,19 +120,19 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
headerComponent: "future-date-input-selector/future-date-input-selector-header", headerComponent: "future-date-input-selector/future-date-input-selector-header",
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
content.datetime = this._computeDatetimeForValue(this.get("computedValue")); content.datetime = this._computeDatetimeForValue(this.get("computedValue"));
content.name = this.get("selectedComputedContent.name") || content.name; content.name = this.get("selection.name") || content.name;
content.hasSelection = this.get("hasSelection"); content.hasSelection = this.get("hasSelection");
content.icons = this._computeIconsForValue(this.get("computedValue")); content.icons = this._computeIconsForValue(this.get("computedValue"));
return content; return content;
}, },
computeContentItem(contentItem, name) { computeContentItem(contentItem, name) {
let item = this.baseComputedContentItem(contentItem, name); let computedContentItem = this._super(contentItem, name);
item.datetime = this._computeDatetimeForValue(contentItem.id); computedContentItem.datetime = this._computeDatetimeForValue(contentItem.id);
item.icons = this._computeIconsForValue(contentItem.id); computedContentItem.icons = this._computeIconsForValue(contentItem.id);
return item; return computedContentItem;
}, },
computeContent() { computeContent() {

View File

@ -1,4 +1,5 @@
import MultiSelectComponent from "select-kit/components/multi-select"; import MultiSelectComponent from "select-kit/components/multi-select";
const { isNone, makeArray } = Ember;
export default MultiSelectComponent.extend({ export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["list-setting"], pluginApiIdentifiers: ["list-setting"],
@ -11,7 +12,7 @@ export default MultiSelectComponent.extend({
init() { init() {
this._super(); this._super();
if (!Ember.isNone(this.get("settingName"))) { if (!isNone(this.get("settingName"))) {
this.set("nameProperty", this.get("settingName")); this.set("nameProperty", this.get("settingName"));
} }
@ -24,13 +25,13 @@ export default MultiSelectComponent.extend({
computeContent() { computeContent() {
let content; let content;
if (Ember.isNone(this.get("choices"))) { if (isNone(this.get("choices"))) {
content = this.get("settingValue").split(this.get("tokenSeparator"));; content = this.get("settingValue").split(this.get("tokenSeparator"));;
} else { } else {
content = this.get("choices"); content = this.get("choices");
} }
return Ember.makeArray(content).filter(c => c); return makeArray(content).filter(c => c);
}, },
mutateValues(values) { mutateValues(values) {

View File

@ -13,7 +13,7 @@ export default ComboBox.extend(Tags, {
classNameBindings: ["noTags"], classNameBindings: ["noTags"],
verticalOffset: 3, verticalOffset: 3,
filterable: true, filterable: true,
noTags: Ember.computed.empty("selectedTags"), noTags: Ember.computed.empty("selection"),
allowAny: true, allowAny: true,
caretUpIcon: Ember.computed.alias("caretIcon"), caretUpIcon: Ember.computed.alias("caretIcon"),
caretDownIcon: Ember.computed.alias("caretIcon"), caretDownIcon: Ember.computed.alias("caretIcon"),
@ -24,6 +24,7 @@ export default ComboBox.extend(Tags, {
this._super(); this._super();
this.set("termMatchesForbidden", false); this.set("termMatchesForbidden", false);
this.selectionSelector = ".selected-tag";
this.set("templateForRow", (rowComponent) => { this.set("templateForRow", (rowComponent) => {
const tag = rowComponent.get("computedContent"); const tag = rowComponent.get("computedContent");
@ -36,22 +37,13 @@ export default ComboBox.extend(Tags, {
this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic"))); this.set("limit", parseInt(this.get("limit") || this.get("siteSettings.max_tags_per_topic")));
}, },
@computed("limitReached") @computed("hasReachedLimit")
caretIcon(limitReached) { caretIcon(hasReachedLimit) {
return limitReached ? null : "plus"; return hasReachedLimit ? null : "plus";
},
@computed("selectedTags.[]", "limit")
limitReached(selectedTags, limit) {
if (selectedTags.length >= limit) {
return true;
}
return false;
}, },
@computed("tags") @computed("tags")
selectedTags(tags) { selection(tags) {
return makeArray(tags); return makeArray(tags);
}, },
@ -62,12 +54,12 @@ export default ComboBox.extend(Tags, {
didRender() { didRender() {
this._super(); this._super();
$(".select-kit-body").on("click.mini-tag-chooser", ".selected-tag", (event) => { this.$(".select-kit-body").on("click.mini-tag-chooser", ".selected-tag", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.send("removeTag", $(event.target).attr("data-value")); this.destroyTags($(event.target).attr("data-value"));
}); });
$(".select-kit-header").on("focus.mini-tag-chooser", ".selected-name", (event) => { this.$(".select-kit-header").on("focus.mini-tag-chooser", ".selected-name", (event) => {
event.stopImmediatePropagation(); event.stopImmediatePropagation();
this.focus(event); this.focus(event);
}); });
@ -76,63 +68,51 @@ export default ComboBox.extend(Tags, {
willDestroyElement() { willDestroyElement() {
this._super(); this._super();
$(".select-kit-body").off("click.mini-tag-chooser"); this.$(".select-kit-body").off("click.mini-tag-chooser");
$(".select-kit-header").off("focus.mini-tag-chooser"); this.$(".select-kit-header").off("focus.mini-tag-chooser");
}, },
didPressEscape(event) { // we are directly mutatings tags to define the current selection
const $lastSelectedTag = $(".selected-tag.selected:last");
if ($lastSelectedTag && this.get("isExpanded")) {
$lastSelectedTag.removeClass("selected");
this._destroyEvent(event);
} else {
this._super(event);
}
},
backspaceFromFilter(event) {
this.didPressBackspace(event);
},
// we are relying on selectedTags and not on value
// to define the current selection
mutateValue() {}, mutateValue() {},
didPressBackspace() { didPressTab(event) {
if (!this.get("isExpanded")) { if (this.get("isLoading")) {
this.expand(); this._destroyEvent(event);
return; return false;
} }
const $lastSelectedTag = $(".selected-tag:last"); if (isEmpty(this.get("filter")) && !this.get("highlighted")) {
this.$header().focus();
if (!isEmpty(this.get("filter"))) { this.close(event);
$lastSelectedTag.removeClass("is-highlighted"); return true;
return;
} }
if (!$lastSelectedTag.length) return; if (this.get("highlighted") && this.get("isExpanded")) {
this._destroyEvent(event);
if (!$lastSelectedTag.hasClass("is-highlighted")) { this.focus();
$lastSelectedTag.addClass("is-highlighted"); this.select(this.get("highlighted"));
return false;
} else { } else {
this.send("removeTag", $lastSelectedTag.attr("data-value")); this.close(event);
} }
return true;
}, },
@computed("tags.[]", "filter") @computed("tags.[]", "filter", "highlightedSelection.[]")
collectionHeader(tags, filter) { collectionHeader(tags, filter, highlightedSelection) {
if (!isEmpty(tags)) { if (!isEmpty(tags)) {
let output = ""; let output = "";
// if we have more than x tags we will also filter the selection
if (tags.length >= 20) { if (tags.length >= 20) {
tags = tags.filter(t => t.indexOf(filter) >= 0); tags = tags.filter(t => t.indexOf(filter) >= 0);
} }
tags.map((tag) => { tags.map((tag) => {
const isHighlighted = highlightedSelection.includes(tag);
output += ` output += `
<button class="selected-tag" data-value="${tag}"> <button aria-label="${tag}" title="${tag}" class="selected-tag ${isHighlighted ? 'is-highlighted' : ''}" data-value="${tag}">
${tag} ${tag}
</button> </button>
`; `;
@ -143,10 +123,11 @@ export default ComboBox.extend(Tags, {
}, },
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
const joinedTags = this.get("selectedTags").join(", ");
if (isEmpty(this.get("selectedTags"))) { const joinedTags = this.get("selection").join(", ");
if (isEmpty(this.get("selection"))) {
content.label = I18n.t("tagging.choose_for_topic"); content.label = I18n.t("tagging.choose_for_topic");
} else { } else {
content.label = joinedTags; content.label = joinedTags;
@ -157,45 +138,13 @@ export default ComboBox.extend(Tags, {
return content; return content;
}, },
actions: { _prepareSearch(query) {
removeTag(tag) {
let tags = this.get("selectedTags");
delete tags[tags.indexOf(tag)];
this.set("tags", tags.filter(t => t));
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200));
},
onExpand() {
if (isEmpty(this.get("collectionComputedContent"))) {
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200));
}
},
onFilter(filter) {
filter = isEmpty(filter) ? null : filter;
this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200));
},
onSelect(tag) {
if (isEmpty(this.get("selectedTags"))) {
this.set("tags", makeArray(tag));
} else {
this.set("tags", this.get("selectedTags").concat(tag));
}
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50));
this.autoHighlight();
}
},
prepareSearch(query) {
const data = { const data = {
q: query, q: query,
limit: this.get("siteSettings.max_tag_search_results"), limit: this.get("siteSettings.max_tag_search_results"),
categoryId: this.get("categoryId") categoryId: this.get("categoryId")
}; };
if (this.get("selectedTags")) data.selected_tags = this.get("selectedTags").slice(0, 100); if (this.get("selection")) data.selected_tags = this.get("selection").slice(0, 100);
if (!this.get("everyTag")) data.filterForInput = true; if (!this.get("everyTag")) data.filterForInput = true;
this.searchTags("/tags/filter/search", data, this._transformJson); this.searchTags("/tags/filter/search", data, this._transformJson);
@ -210,10 +159,42 @@ export default ComboBox.extend(Tags, {
results = results.sort((a, b) => a.id > b.id); results = results.sort((a, b) => a.id > b.id);
} }
results = results.filter(r => !context.get("selectedTags").includes(r.id)); results = results.filter(r => !context.get("selection").includes(r.id));
return results.map(result => { return results.map(result => {
return { id: result.text, name: result.text, count: result.count }; return { id: result.text, name: result.text, count: result.count };
}); });
} },
destroyTags(tags) {
tags = Ember.makeArray(tags);
this.get("tags").removeObjects(tags);
this._prepareSearch(this.get("filter"));
},
didDeselect(tags) {
this.destroyTags(tags);
},
actions: {
onSelect(tag) {
this.set("tags", makeArray(this.get("tags")).concat(tag));
this._prepareSearch(this.get("filter"));
this.autoHighlight();
},
onExpand() {
if (isEmpty(this.get("collectionComputedContent"))) {
this.set("searchDebounce", run.debounce(this, this._prepareSearch, this.get("filter"), 350));
}
},
onFilter(filter) {
// we start loading right away so we avoid updating createRow multiple times
this.startLoading();
filter = isEmpty(filter) ? null : filter;
this.set("searchDebounce", run.debounce(this, this._prepareSearch, filter, 350));
}
},
}); });

View File

@ -2,5 +2,5 @@ import SelectKitHeaderComponent from "select-kit/components/select-kit/select-ki
export default SelectKitHeaderComponent.extend({ export default SelectKitHeaderComponent.extend({
layoutName: "select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header", layoutName: "select-kit/templates/components/mini-tag-chooser/mini-tag-chooser-header",
classNames: "mini-tag-chooser-header", classNames: "mini-tag-chooser-header"
}); });

View File

@ -1,7 +1,7 @@
import SelectKitComponent from "select-kit/components/select-kit"; import SelectKitComponent from "select-kit/components/select-kit";
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
import { on } from "ember-addons/ember-computed-decorators"; import { on } from "ember-addons/ember-computed-decorators";
const { get, isNone, isEmpty, makeArray } = Ember; const { get, isNone, isEmpty, makeArray, run } = Ember;
import { import {
applyOnSelectPluginApiCallbacks applyOnSelectPluginApiCallbacks
} from "select-kit/mixins/plugin-api"; } from "select-kit/mixins/plugin-api";
@ -17,11 +17,15 @@ export default SelectKitComponent.extend({
autoFilterable: true, autoFilterable: true,
selectedNameComponent: "multi-select/selected-name", selectedNameComponent: "multi-select/selected-name",
filterIcon: null, filterIcon: null,
filterComponent: "multi-select/multi-select-filter",
computedValues: null,
values: null,
init() { init() {
this._super(); this._super();
this.set("computedValues", []); this.set("computedValues", []);
if (isNone(this.get("values"))) { this.set("values", []); } if (isNone(this.get("values"))) { this.set("values", []); }
this.set("headerComponentOptions", Ember.Object.create({ this.set("headerComponentOptions", Ember.Object.create({
@ -37,7 +41,7 @@ export default SelectKitComponent.extend({
@on("didReceiveAttrs") @on("didReceiveAttrs")
_compute() { _compute() {
Ember.run.scheduleOnce("afterRender", () => { run.scheduleOnce("afterRender", () => {
this.willComputeAttributes(); this.willComputeAttributes();
let content = this.get("content") || []; let content = this.get("content") || [];
let asyncContent = this.get("asyncContent") || []; let asyncContent = this.get("asyncContent") || [];
@ -51,8 +55,6 @@ export default SelectKitComponent.extend({
values = this.willComputeValues(values); values = this.willComputeValues(values);
values = this.computeValues(values); values = this.computeValues(values);
values = this._beforeDidComputeValues(values); values = this._beforeDidComputeValues(values);
this._setHeaderComputedContent();
this._setCollectionHeaderComputedContent();
this.didComputeContent(content); this.didComputeContent(content);
this.didComputeAsyncContent(asyncContent); this.didComputeAsyncContent(asyncContent);
this.didComputeValues(values); this.didComputeValues(values);
@ -62,7 +64,7 @@ export default SelectKitComponent.extend({
@computed("filter", "shouldDisplayCreateRow") @computed("filter", "shouldDisplayCreateRow")
createRowComputedContent(filter, shouldDisplayCreateRow) { createRowComputedContent(filter, shouldDisplayCreateRow) {
if (shouldDisplayCreateRow === true) { if (shouldDisplayCreateRow) {
let content = this.createContentFromInput(filter); let content = this.createContentFromInput(filter);
return this.computeContentItem(content, { created: true }); return this.computeContentItem(content, { created: true });
} }
@ -88,13 +90,11 @@ export default SelectKitComponent.extend({
didComputeValues(values) { return values; }, didComputeValues(values) { return values; },
mutateAttributes() { mutateAttributes() {
if (this.get("isDestroyed") || this.get("isDestroying")) return; run.next(() => {
if (this.get("isDestroyed") || this.get("isDestroying")) return;
Ember.run.next(() => {
this.mutateContent(this.get("computedContent")); this.mutateContent(this.get("computedContent"));
this.mutateValues(this.get("computedValues")); this.mutateValues(this.get("computedValues"));
this._setCollectionHeaderComputedContent();
this._setHeaderComputedContent();
}); });
}, },
mutateValues(computedValues) { mutateValues(computedValues) {
@ -139,10 +139,10 @@ export default SelectKitComponent.extend({
return computedContent; return computedContent;
}, },
baseHeaderComputedContent() { computeHeaderContent() {
return { return {
title: this.get("title"), title: this.get("title"),
selectedComputedContents: this.get("selectedComputedContents") selection: this.get("selection")
}; };
}, },
@ -153,81 +153,12 @@ export default SelectKitComponent.extend({
}; };
}, },
@computed("limit", "computedValues.[]")
limitReached(limit, computedValues) {
if (!limit) return false;
return computedValues.length >= limit;
},
validateSelect() { validateSelect() {
return this._super() && !this.get("limitReached"); return this._super() && !this.get("hasReachedLimit");
},
didPressBackspace(event) {
this.expand(event);
this.keyDown(event);
this._destroyEvent(event);
},
didPressEscape(event) {
const $highlighted = this.$(".selected-name.is-highlighted");
if ($highlighted.length > 0) {
$highlighted.removeClass("is-highlighted");
}
this._super(event);
},
keyDown(event) {
if (!isEmpty(this.get("filter"))) return;
const keyCode = event.keyCode || event.which;
const $filterInput = this.$filterInput();
// select all choices
if (this.get("hasSelection") && event.metaKey === true && keyCode === 65) {
this.$(".choices .selected-name:not(.is-locked)").addClass("is-highlighted");
return false;
}
// clear selection when multiple
if (this.$(".selected-name.is-highlighted").length >= 1 && keyCode === this.keys.BACKSPACE) {
const highlightedComputedContents = [];
$.each(this.$(".selected-name.is-highlighted"), (i, el) => {
const computedContent = this._findComputedContentItemByGuid($(el).attr("data-guid"));
if (!Ember.isNone(computedContent)) { highlightedComputedContents.push(computedContent); }
});
this.send("deselect", highlightedComputedContents);
return;
}
// try to remove last item from the list
if (keyCode === this.keys.BACKSPACE) {
let $lastSelectedValue = $(this.$(".choices .selected-name:not(.is-locked)").last());
if ($lastSelectedValue.length === 0) { return; }
if ($filterInput.not(":visible") && $lastSelectedValue.length > 0) {
$lastSelectedValue.trigger("backspace");
return false;
}
if ($filterInput.val() === "") {
if ($filterInput.is(":focus")) {
if ($lastSelectedValue.length > 0) { $lastSelectedValue.trigger("backspace"); }
} else {
if ($lastSelectedValue.length > 0) {
$lastSelectedValue.trigger("backspace");
} else {
$filterInput.focus();
}
}
}
}
}, },
@computed("computedValues.[]", "computedContent.[]") @computed("computedValues.[]", "computedContent.[]")
selectedComputedContents(computedValues, computedContent) { selection(computedValues, computedContent) {
const selected = []; const selected = [];
computedValues.forEach(v => { computedValues.forEach(v => {
@ -238,88 +169,123 @@ export default SelectKitComponent.extend({
return selected; return selected;
}, },
@computed("selectedComputedContents.[]") @computed("selection.[]")
hasSelection(selectedComputedContents) { return !Ember.isEmpty(selectedComputedContents); }, hasSelection(selection) { return !isEmpty(selection); },
didPressTab(event) {
if (isEmpty(this.get("filter")) && !this.get("highlighted")) {
this.$header().focus();
this.close(event);
return true;
}
if (this.get("highlighted") && this.get("isExpanded")) {
this._destroyEvent(event);
this.focus();
this.select(this.get("highlighted"));
return false;
} else {
this.close(event);
}
return true;
},
autoHighlight() { autoHighlight() {
Ember.run.schedule("afterRender", () => { run.schedule("afterRender", () => {
if (!this.get("isExpanded")) return; if (!this.get("isExpanded")) return;
if (!this.get("renderedBodyOnce")) return; if (!this.get("renderedBodyOnce")) return;
if (!isNone(this.get("highlightedValue"))) return; if (this.get("highlighted")) return;
if (isEmpty(this.get("collectionComputedContent"))) { if (isEmpty(this.get("collectionComputedContent"))) {
if (this.get("createRowComputedContent")) { if (this.get("createRowComputedContent")) {
this.send("highlight", this.get("createRowComputedContent")); this.highlight(this.get("createRowComputedContent"));
} else if (this.get("noneRowComputedContent") && this.get("hasSelection")) { } else if (this.get("noneRowComputedContent") && this.get("hasSelection")) {
this.send("highlight", this.get("noneRowComputedContent")); this.highlight(this.get("noneRowComputedContent"));
} }
} else { } else {
this.send("highlight", this.get("collectionComputedContent.firstObject")); this.highlight(this.get("collectionComputedContent.firstObject"));
} }
}); });
}, },
didSelect() { select(computedContentItem) {
this.focus(); if (!computedContentItem || computedContentItem.__sk_row_type === "noneRow") {
this.autoHighlight(); this.clearSelection();
return;
}
applyOnSelectPluginApiCallbacks( if (computedContentItem.__sk_row_type === "createRow") {
this.get("pluginApiIdentifiers"),
this.get("computedValue"),
this
);
this._boundaryActionHandler("onSelect", this.get("computedValue"));
},
willDeselect() {
this.clearFilter();
this.set("highlightedValue", null);
},
didDeselect(rowComputedContentItems) {
this.focus();
this.autoHighlight();
this._boundaryActionHandler("onDeselect", rowComputedContentItems);
},
actions: {
clearSelection() {
this.send("deselect", this.get("selectedComputedContents"));
this._boundaryActionHandler("onClearSelection");
},
create(computedContentItem) {
if (!this.get("computedValues").includes(computedContentItem.value) && if (!this.get("computedValues").includes(computedContentItem.value) &&
this.validateCreate(computedContentItem.value)) { this.validateCreate(computedContentItem.value)) {
this.willCreate(computedContentItem);
computedContentItem.__sk_row_type = null;
this.get("computedContent").pushObject(computedContentItem); this.get("computedContent").pushObject(computedContentItem);
this._boundaryActionHandler("onCreate");
this.send("select", computedContentItem); run.schedule("afterRender", () => {
this.didCreate(computedContentItem);
this._boundaryActionHandler("onCreate");
});
this.select(computedContentItem);
return;
} else { } else {
this._boundaryActionHandler("onCreateFailure"); this._boundaryActionHandler("onCreateFailure");
return;
} }
},
select(computedContentItem) {
this.willSelect(computedContentItem);
if (this.validateSelect(computedContentItem)) {
this.get("computedValues").pushObject(computedContentItem.value);
Ember.run.next(() => this.mutateAttributes());
Ember.run.schedule("afterRender", () => this.didSelect(computedContentItem));
} else {
this._boundaryActionHandler("onSelectFailure");
}
},
deselect(rowComputedContentItems) {
rowComputedContentItems = Ember.makeArray(rowComputedContentItems);
const generatedComputedContents = this._filterRemovableComputedContents(makeArray(rowComputedContentItems));
this.willDeselect(rowComputedContentItems);
this.get("computedValues").removeObjects(rowComputedContentItems.map(r => r.value));
this.get("computedContent").removeObjects(generatedComputedContents);
Ember.run.next(() => this.mutateAttributes());
Ember.run.schedule("afterRender", () => this.didDeselect(rowComputedContentItems));
} }
if (this.validateSelect(computedContentItem)) {
this.willSelect(computedContentItem);
this.clearFilter();
this.setProperties({ highlighted: null });
this.get("computedValues").pushObject(computedContentItem.value);
run.next(() => this.mutateAttributes());
run.schedule("afterRender", () => {
this.didSelect(computedContentItem);
applyOnSelectPluginApiCallbacks(
this.get("pluginApiIdentifiers"),
computedContentItem.value,
this
);
this.autoHighlight();
this._boundaryActionHandler("onSelect", computedContentItem.value);
});
} else {
this._boundaryActionHandler("onSelectFailure");
}
},
deselect(rowComputedContentItems) {
this.willDeselect(rowComputedContentItems);
rowComputedContentItems = makeArray(rowComputedContentItems);
const generatedComputedContents = this._filterRemovableComputedContents(makeArray(rowComputedContentItems));
this.set("highlighted", null);
this.set("highlightedSelection", []);
this.get("computedValues").removeObjects(rowComputedContentItems.map(r => r.value));
this.get("computedContent").removeObjects(generatedComputedContents);
run.next(() => this.mutateAttributes());
run.schedule("afterRender", () => {
this.didDeselect(rowComputedContentItems);
this.autoHighlight();
});
},
close(event) {
this.clearHighlightSelection();
this._super(event);
},
unfocus(event) {
this.clearHighlightSelection();
this._super(event);
} }
}); });

View File

@ -0,0 +1,14 @@
import computed from "ember-addons/ember-computed-decorators";
const { isEmpty } = Ember;
import SelectKitFilterComponent from "select-kit/components/select-kit/select-kit-filter";
export default SelectKitFilterComponent.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-filter",
classNames: ["multi-select-filter"],
@computed("placeholder", "hasSelection")
computedPlaceholder(placeholder, hasSelection) {
if (hasSelection) return "";
return isEmpty(placeholder) ? "" : I18n.t(placeholder);
}
});

View File

@ -34,12 +34,12 @@ export default SelectKitHeaderComponent.extend({
$filter.width(availableSpace - parentRightPadding * 4); $filter.width(availableSpace - parentRightPadding * 4);
}, },
@computed("computedContent.selectedComputedContents.[]") @computed("computedContent.selection.[]")
names(selection) { names(selection) {
return Ember.makeArray(selection).map(s => s.name).join(","); return Ember.makeArray(selection).map(s => s.name).join(",");
}, },
@computed("computedContent.selectedComputedContents.[]") @computed("computedContent.selection.[]")
values(selection) { values(selection) {
return Ember.makeArray(selection).map(s => s.value).join(","); return Ember.makeArray(selection).map(s => s.value).join(",");
} }

View File

@ -28,20 +28,6 @@ export default Ember.Component.extend({
return null; return null;
}, },
didInsertElement() {
this._super();
$(this.element).on("backspace.selected-name", () => {
this._handleBackspace();
});
},
willDestroyElement() {
this._super();
$(this.element).off("backspace.selected-name");
},
label: Ember.computed.or("computedContent.label", "title", "name"), label: Ember.computed.or("computedContent.label", "title", "name"),
name: Ember.computed.alias("computedContent.name"), name: Ember.computed.alias("computedContent.name"),
@ -52,19 +38,14 @@ export default Ember.Component.extend({
return this.getWithDefault("computedContent.locked", false); return this.getWithDefault("computedContent.locked", false);
}), }),
click() { @computed("computedContent", "highlightedSelection.[]")
if (this.get("isLocked") === true) return false; isHighlighted(computedContent, highlightedSelection) {
this.sendAction("deselect", [this.get("computedContent")]); return highlightedSelection.includes(this.get("computedContent"));
return false;
}, },
_handleBackspace() { click() {
if (this.get("isLocked") === true) return false; if (this.get("isLocked")) return false;
this.sendAction("onClickSelectionItem", [this.get("computedContent")]);
if (this.get("isHighlighted")) { return false;
this.sendAction("deselect", [this.get("computedContent")]);
} else {
this.set("isHighlighted", true);
}
} }
}); });

View File

@ -13,9 +13,5 @@ export default CategoryRowComponent.extend({
allowUncategorized: true, allowUncategorized: true,
hideParent: true hideParent: true
}).htmlSafe(); }).htmlSafe();
},
click() {
this.sendAction("clearSelection");
} }
}); });

View File

@ -22,7 +22,7 @@ export default DropdownSelectBoxComponent.extend({
iconForSelectedDetails: Ember.computed.alias("selectedDetails.icon"), iconForSelectedDetails: Ember.computed.alias("selectedDetails.icon"),
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
content.name = I18n.t(`${this.get("i18nPrefix")}.${this.get("selectedDetails.key")}.title`); content.name = I18n.t(`${this.get("i18nPrefix")}.${this.get("selectedDetails.key")}.title`);
content.hasSelection = this.get("hasSelection"); content.hasSelection = this.get("hasSelection");
return content; return content;

View File

@ -1,4 +1,5 @@
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box"; import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
import computed from "ember-addons/ember-computed-decorators";
export default DropdownSelectBoxComponent.extend({ export default DropdownSelectBoxComponent.extend({
classNames: ["period-chooser"], classNames: ["period-chooser"],
@ -8,6 +9,11 @@ export default DropdownSelectBoxComponent.extend({
value: Ember.computed.alias("period"), value: Ember.computed.alias("period"),
isHidden: Ember.computed.alias("showPeriods"), isHidden: Ember.computed.alias("showPeriods"),
@computed("isExpanded")
caretIcon(isExpanded) {
return isExpanded ? "caret-up" : "caret-down";
},
actions: { actions: {
onSelect() { onSelect() {
this.sendAction("action", this.get("computedValue")); this.sendAction("action", this.get("computedValue"));

View File

@ -1,12 +1,6 @@
import DropdownSelectBoxHeaderomponent from "select-kit/components/dropdown-select-box/dropdown-select-box-header"; import DropdownSelectBoxHeaderomponent from "select-kit/components/dropdown-select-box/dropdown-select-box-header";
import computed from 'ember-addons/ember-computed-decorators';
export default DropdownSelectBoxHeaderomponent.extend({ export default DropdownSelectBoxHeaderomponent.extend({
layoutName: "select-kit/templates/components/period-chooser/period-chooser-header", layoutName: "select-kit/templates/components/period-chooser/period-chooser-header",
classNames: "period-chooser-header", classNames: "period-chooser-header"
@computed("isExpanded")
caretIcon(isExpanded) {
return isExpanded ? "caret-up" : "caret-down";
}
}); });

View File

@ -10,7 +10,7 @@ export default DropdownSelectBoxComponent.extend({
autoHighlight() {}, autoHighlight() {},
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
const pinnedGlobally = this.get("topic.pinned_globally"); const pinnedGlobally = this.get("topic.pinned_globally");
const pinned = this.get("computedValue"); const pinned = this.get("computedValue");
const globally = pinnedGlobally ? "_globally" : ""; const globally = pinnedGlobally ? "_globally" : "";

View File

@ -1,4 +1,4 @@
const { isNone, run } = Ember; const { get, isNone, run, isEmpty, makeArray } = Ember;
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
import UtilsMixin from "select-kit/mixins/utils"; import UtilsMixin from "select-kit/mixins/utils";
import DomHelpersMixin from "select-kit/mixins/dom-helpers"; import DomHelpersMixin from "select-kit/mixins/dom-helpers";
@ -7,7 +7,6 @@ import PluginApiMixin from "select-kit/mixins/plugin-api";
import { import {
applyContentPluginApiCallbacks, applyContentPluginApiCallbacks,
applyHeaderContentPluginApiCallbacks, applyHeaderContentPluginApiCallbacks,
applyOnSelectPluginApiCallbacks,
applyCollectionHeaderCallbacks applyCollectionHeaderCallbacks
} from "select-kit/mixins/plugin-api"; } from "select-kit/mixins/plugin-api";
@ -26,6 +25,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
"isLeftAligned", "isLeftAligned",
"isRightAligned", "isRightAligned",
"hasSelection", "hasSelection",
"hasReachedLimit",
], ],
isDisabled: false, isDisabled: false,
isExpanded: false, isExpanded: false,
@ -37,13 +37,13 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
renderedFilterOnce: false, renderedFilterOnce: false,
tabindex: 0, tabindex: 0,
none: null, none: null,
highlightedValue: null, highlighted: null,
valueAttribute: "id", valueAttribute: "id",
nameProperty: "name", nameProperty: "name",
autoFilterable: false, autoFilterable: false,
filterable: false, filterable: false,
filter: "", filter: "",
previousFilter: null, previousFilter: "",
filterPlaceholder: "select_kit.filter_placeholder", filterPlaceholder: "select_kit.filter_placeholder",
filterIcon: "search", filterIcon: "search",
headerIcon: null, headerIcon: null,
@ -70,6 +70,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
allowContentReplacement: false, allowContentReplacement: false,
collectionHeader: null, collectionHeader: null,
allowAutoSelectFirst: true, allowAutoSelectFirst: true,
highlightedSelection: null,
init() { init() {
this._super(); this._super();
@ -78,6 +79,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
this.set("headerComponentOptions", Ember.Object.create()); this.set("headerComponentOptions", Ember.Object.create());
this.set("rowComponentOptions", Ember.Object.create()); this.set("rowComponentOptions", Ember.Object.create());
this.set("computedContent", []); this.set("computedContent", []);
this.set("highlightedSelection", []);
if (this.site && this.site.isMobileDevice) { if (this.site && this.site.isMobileDevice) {
this.setProperties({ this.setProperties({
@ -99,6 +101,22 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
} }
}, },
keyDown(event) {
if (!isEmpty(this.get("filter"))) return true;
const keyCode = event.keyCode || event.which;
if (event.metaKey === true && keyCode === this.keys.A) {
this.didPressSelectAll();
return false;
}
if (keyCode === this.keys.BACKSPACE) {
this.didPressBackspace();
return false;
}
},
willDestroyElement() { willDestroyElement() {
this.removeObserver(`content.@each.${this.get("nameProperty")}`, this, this._compute); this.removeObserver(`content.@each.${this.get("nameProperty")}`, this, this._compute);
this.removeObserver(`content.[]`, this, this._compute); this.removeObserver(`content.[]`, this, this._compute);
@ -132,28 +150,7 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
}, },
didComputeAsyncContent() {}, didComputeAsyncContent() {},
computeHeaderContent() {
return this.baseHeaderComputedContent();
},
computeContentItem(contentItem, options) { computeContentItem(contentItem, options) {
return this.baseComputedContentItem(contentItem, options);
},
computeAsyncContentItem(contentItem, options) {
return this.computeContentItem(contentItem, options);
},
@computed("isAsync", "filteredAsyncComputedContent.[]", "filteredComputedContent.[]")
collectionComputedContent(isAsync, filteredAsyncComputedContent, filteredComputedContent) {
return isAsync ? filteredAsyncComputedContent : filteredComputedContent;
},
validateCreate() { return true; },
validateSelect() { return true; },
baseComputedContentItem(contentItem, options) {
let originalContent; let originalContent;
options = options || {}; options = options || {};
const name = options.name; const name = options.name;
@ -166,13 +163,39 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
originalContent = contentItem; originalContent = contentItem;
} }
return { let computedContentItem = {
value: this._castInteger(this.valueForContentItem(contentItem)), value: this._castInteger(this.valueForContentItem(contentItem)),
name: name || this._nameForContent(contentItem), name: name || this._nameForContent(contentItem),
locked: false, locked: false,
created: options.created || false, created: options.created || false,
__sk_row_type: options.created ? "createRow" : null,
originalContent originalContent
}; };
return computedContentItem;
},
computeAsyncContentItem(contentItem, options) {
return this.computeContentItem(contentItem, options);
},
@computed("isAsync", "isLoading", "filteredAsyncComputedContent.[]", "filteredComputedContent.[]")
collectionComputedContent(isAsync, isLoading, filteredAsyncComputedContent, filteredComputedContent) {
if (isAsync) {
return isLoading ? [] : filteredAsyncComputedContent;
} else {
return filteredComputedContent;
}
},
validateCreate() { return !this.get("hasReachedLimit"); },
validateSelect() { return !this.get("hasReachedLimit"); },
@computed("limit", "selection.[]")
hasReachedLimit(limit, selection) {
if (!limit) return false;
return selection.length >= limit;
}, },
@computed("shouldFilter", "allowAny", "filter") @computed("shouldFilter", "allowAny", "filter")
@ -182,16 +205,16 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
return false; return false;
}, },
@computed("filter", "collectionComputedContent.[]") @computed("filter", "collectionComputedContent.[]", "isLoading")
noContentRow(filter, collectionComputedContent) { noContentRow(filter, collectionComputedContent, isLoading) {
if (filter.length > 0 && collectionComputedContent.length === 0) { if (filter.length > 0 && collectionComputedContent.length === 0 && !isLoading) {
return I18n.t("select_kit.no_content"); return I18n.t("select_kit.no_content");
} }
}, },
@computed("limitReached", "limit") @computed("hasReachedLimit", "limit")
maxContentRow(limitReached, limit) { maxContentRow(hasReachedLimit, limit) {
if (limitReached) { if (hasReachedLimit) {
return I18n.t("select_kit.max_content_reached", { count: limit }); return I18n.t("select_kit.max_content_reached", { count: limit });
} }
}, },
@ -204,9 +227,9 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
return false; return false;
}, },
@computed("computedValue", "filter", "collectionComputedContent.[]", "limitReached") @computed("computedValue", "filter", "collectionComputedContent.[]", "hasReachedLimit", "isLoading")
shouldDisplayCreateRow(computedValue, filter, collectionComputedContent, limitReached) { shouldDisplayCreateRow(computedValue, filter, collectionComputedContent, hasReachedLimit, isLoading) {
if (limitReached) return false; if (isLoading || hasReachedLimit) return false;
if (collectionComputedContent.map(c => c.value).includes(filter)) return false; if (collectionComputedContent.map(c => c.value).includes(filter)) return false;
if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true; if (this.get("allowAny") && filter.length > 0 && this.validateCreate(filter)) return true;
return false; return false;
@ -216,7 +239,9 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
createRowComputedContent(filter, shouldDisplayCreateRow) { createRowComputedContent(filter, shouldDisplayCreateRow) {
if (shouldDisplayCreateRow) { if (shouldDisplayCreateRow) {
let content = this.createContentFromInput(filter); let content = this.createContentFromInput(filter);
return this.computeContentItem(content, { created: true }); let computedContentItem = this.computeContentItem(content, { created: true });
computedContentItem.__sk_row_type = "createRow";
return computedContentItem;
} }
}, },
@ -238,90 +263,100 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
@computed("none") @computed("none")
noneRowComputedContent(none) { noneRowComputedContent(none) {
if (isNone(none)) { return null; } if (isNone(none)) { return null; }
let noneRowComputedContent;
switch (typeof none) { switch (typeof none) {
case "string": case "string":
return this.computeContentItem(this.noneValue, { noneRowComputedContent = this.computeContentItem(this.noneValue, {
name: (I18n.t(none) || "").htmlSafe() name: (I18n.t(none) || "").htmlSafe()
}); });
break;
default: default:
return this.computeContentItem(none); noneRowComputedContent = this.computeContentItem(none);
} }
noneRowComputedContent.__sk_row_type = "noneRow";
return noneRowComputedContent;
}, },
createContentFromInput(input) { return input; }, createContentFromInput(input) { return input; },
willSelect() { highlightSelection(items) {
this.clearFilter(); this.propertyWillChange("highlightedSelection");
this.set("highlightedValue", null); this.set("highlightedSelection", makeArray(items));
}, this.propertyDidChange("highlightedSelection");
didSelect() {
this.collapse();
this.focus();
applyOnSelectPluginApiCallbacks(
this.get("pluginApiIdentifiers"),
this.get("computedValue"),
this
);
this._boundaryActionHandler("onSelect", this.get("computedValue"));
}, },
willDeselect() { clearHighlightSelection() {
this.clearFilter(); this.highlightSelection([]);
this.set("highlightedValue", null);
},
didDeselect(rowComputedContentItem) {
this.collapse();
this.focus();
this._boundaryActionHandler("onDeselect", rowComputedContentItem);
}, },
willSelect() {},
didSelect() {},
willCreate() {},
didCreate() {},
willDeselect() {},
didDeselect() {},
clearFilter() { clearFilter() {
this.$filterInput().val(""); this.$filterInput().val("");
this.setProperties({ filter: "" }); this.setProperties({ filter: "", previousFilter: "" });
}, },
startLoading() { startLoading() {
this.set("isLoading", true); this.set("isLoading", true);
this.set("highlighted", null);
this._boundaryActionHandler("onStartLoading"); this._boundaryActionHandler("onStartLoading");
}, },
stopLoading() { stopLoading() {
this.focus();
this.set("isLoading", false); this.set("isLoading", false);
this._boundaryActionHandler("onStopLoading"); this._boundaryActionHandler("onStopLoading");
}, },
_setCollectionHeaderComputedContent() { @computed("selection.[]", "isExpanded", "filter", "highlightedSelection.[]")
const collectionHeaderComputedContent = applyCollectionHeaderCallbacks( collectionHeaderComputedContent() {
return applyCollectionHeaderCallbacks(
this.get("pluginApiIdentifiers"), this.get("pluginApiIdentifiers"),
this.get("collectionHeader"), this.get("collectionHeader"),
this this
); );
this.set("collectionHeaderComputedContent", collectionHeaderComputedContent);
}, },
_setHeaderComputedContent() { @computed("selection.[]", "isExpanded", "headerIcon")
const headerComputedContent = applyHeaderContentPluginApiCallbacks( headerComputedContent() {
return applyHeaderContentPluginApiCallbacks(
this.get("pluginApiIdentifiers"), this.get("pluginApiIdentifiers"),
this.computeHeaderContent(), this.computeHeaderContent(),
this this
); );
this.set("headerComputedContent", headerComputedContent);
}, },
_boundaryActionHandler(actionName, ...params) { _boundaryActionHandler(actionName, ...params) {
if (Ember.get(this.actions, actionName)) { if (get(this.actions, actionName)) {
run.next(() => this.send(actionName, ...params)); run.next(() => this.send(actionName, ...params));
} else if (this.get(actionName)) { } else if (this.get(actionName)) {
run.next(() => this.get(actionName)()); run.next(() => this.get(actionName)());
} }
}, },
highlight(computedContent) {
this.set("highlighted", computedContent);
this._boundaryActionHandler("onHighlight", computedContent);
},
clearSelection() {
this.deselect(this.get("selection"));
this.focus();
},
actions: { actions: {
toggle() { onToggle() {
this._boundaryActionHandler("onToggle", this); this.clearHighlightSelection();
if (this.get("isExpanded")) { if (this.get("isExpanded")) {
this._boundaryActionHandler("onCollapse", this); this._boundaryActionHandler("onCollapse", this);
@ -332,16 +367,29 @@ export default Ember.Component.extend(UtilsMixin, PluginApiMixin, DomHelpersMixi
} }
}, },
highlight(rowComputedContent) { onClickRow(computedContentItem) {
this.set("highlightedValue", rowComputedContent.value); this.didClickRow(computedContentItem);
this._boundaryActionHandler("onHighlight", rowComputedContent);
}, },
filterComputedContent(filter) { onClickSelectionItem(computedContentItem) {
this.didClickSelectionItem(computedContentItem);
},
onClearSelection() {
this.clearSelection();
},
onMouseoverRow(computedContentItem) {
this.highlight(computedContentItem);
},
onFilterComputedContent(filter) {
if (filter === this.get("previousFilter")) return; if (filter === this.get("previousFilter")) return;
this.clearHighlightSelection();
this.setProperties({ this.setProperties({
highlightedValue: null, highlighted: null,
renderedFilterOnce: true, renderedFilterOnce: true,
previousFilter: filter, previousFilter: filter,
filter filter

View File

@ -7,9 +7,8 @@ export default Ember.Component.extend({
classNameBindings: ["isFocused", "isHidden"], classNameBindings: ["isFocused", "isHidden"],
isHidden: Ember.computed.not("shouldDisplayFilter"), isHidden: Ember.computed.not("shouldDisplayFilter"),
@computed("placeholder", "hasSelection") @computed("placeholder")
computedPlaceholder(placeholder, hasSelection) { computedPlaceholder(placeholder) {
if (hasSelection) return "";
return isEmpty(placeholder) ? "" : I18n.t(placeholder); return isEmpty(placeholder) ? "" : I18n.t(placeholder);
} }
}); });

View File

@ -1,4 +1,5 @@
import computed from 'ember-addons/ember-computed-decorators'; import computed from "ember-addons/ember-computed-decorators";
const { isEmpty, makeArray } = Ember;
export default Ember.Component.extend({ export default Ember.Component.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-header", layoutName: "select-kit/templates/components/select-kit/select-kit-header",
@ -33,10 +34,10 @@ export default Ember.Component.extend({
@computed("computedContent.icon", "computedContent.icons") @computed("computedContent.icon", "computedContent.icons")
icons(icon, icons) { icons(icon, icons) {
return Ember.makeArray(icon).concat(icons).filter(i => !Ember.isEmpty(i)); return makeArray(icon).concat(icons).filter(i => !isEmpty(i));
}, },
click() { click() {
this.sendAction("toggle"); this.sendAction("onToggle");
} }
}); });

View File

@ -2,9 +2,5 @@ import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-r
export default SelectKitRowComponent.extend({ export default SelectKitRowComponent.extend({
layoutName: "select-kit/templates/components/select-kit/select-kit-row", layoutName: "select-kit/templates/components/select-kit/select-kit-row",
classNames: "none", classNames: "none"
click() {
this.sendAction("clearSelection");
}
}); });

View File

@ -13,7 +13,8 @@ export default Ember.Component.extend(UtilsMixin, {
"title", "title",
"value:data-value", "value:data-value",
"name:data-name", "name:data-name",
"ariaLabel:aria-label" "ariaLabel:aria-label",
"guid:data-guid"
], ],
classNameBindings: ["isHighlighted", "isSelected"], classNameBindings: ["isHighlighted", "isSelected"],
@ -27,6 +28,9 @@ export default Ember.Component.extend(UtilsMixin, {
return null; return null;
}, },
@computed("computedContent")
guid(computedContent) { return Ember.guidFor(computedContent); },
label: Ember.computed.or("computedContent.label", "title", "name"), label: Ember.computed.or("computedContent.label", "title", "name"),
name: Ember.computed.alias("computedContent.name"), name: Ember.computed.alias("computedContent.name"),
@ -39,7 +43,7 @@ export default Ember.Component.extend(UtilsMixin, {
@on("didReceiveAttrs") @on("didReceiveAttrs")
_setSelectionState() { _setSelectionState() {
this.set("isSelected", this.get("computedValue") === this.get("value")); this.set("isSelected", this.get("computedValue") === this.get("value"));
this.set("isHighlighted", this.get("highlightedValue") === this.get("value")); this.set("isHighlighted", this.get("highlighted.value") === this.get("value"));
}, },
@on("willDestroyElement") @on("willDestroyElement")
@ -57,14 +61,14 @@ export default Ember.Component.extend(UtilsMixin, {
}, },
mouseEnter() { mouseEnter() {
this.set("hoverDebounce", run.debounce(this, this._sendHighlightAction, 32)); this.set("hoverDebounce", run.debounce(this, this._sendMouseoverAction, 32));
}, },
click() { click() {
this.sendAction("select", this.get("computedContent")); this.sendAction("onClickRow", this.get("computedContent"));
}, },
_sendHighlightAction() { _sendMouseoverAction() {
this.sendAction("highlight", this.get("computedContent")); this.sendAction("onMouseoverRow", this.get("computedContent"));
} }
}); });

View File

@ -1,6 +1,10 @@
import SelectKitComponent from "select-kit/components/select-kit"; import SelectKitComponent from "select-kit/components/select-kit";
import { default as computed, on } from 'ember-addons/ember-computed-decorators'; import { default as computed, on } from 'ember-addons/ember-computed-decorators';
const { get, isNone, isEmpty, isPresent, run } = Ember; const { get, isNone, isEmpty, isPresent, run, makeArray } = Ember;
import {
applyOnSelectPluginApiCallbacks,
} from "select-kit/mixins/plugin-api";
export default SelectKitComponent.extend({ export default SelectKitComponent.extend({
pluginApiIdentifiers: ["single-select"], pluginApiIdentifiers: ["single-select"],
@ -32,20 +36,15 @@ export default SelectKitComponent.extend({
this.didComputeAttributes(); this.didComputeAttributes();
if (this.get("allowInitialValueMutation")) this.mutateAttributes(); if (this.get("allowInitialValueMutation")) this.mutateAttributes();
this._setCollectionHeaderComputedContent();
this._setHeaderComputedContent();
}); });
}, },
mutateAttributes() { mutateAttributes() {
if (this.get("isDestroyed") || this.get("isDestroying")) return;
run.next(() => { run.next(() => {
if (this.get("isDestroyed") || this.get("isDestroying")) return;
this.mutateContent(this.get("computedContent")); this.mutateContent(this.get("computedContent"));
this.mutateValue(this.get("computedValue")); this.mutateValue(this.get("computedValue"));
this._setCollectionHeaderComputedContent();
this._setHeaderComputedContent();
}); });
}, },
mutateContent() {}, mutateContent() {},
@ -84,12 +83,12 @@ export default SelectKitComponent.extend({
}); });
}, },
baseHeaderComputedContent() { computeHeaderContent() {
return { return {
title: this.get("title"), title: this.get("title"),
icons: Ember.makeArray(this.getWithDefault("headerIcon", [])), icons: makeArray(this.getWithDefault("headerIcon", [])),
value: this.get("selectedComputedContent.value"), value: this.get("selection.value"),
name: this.get("selectedComputedContent.name") || this.get("noneRowComputedContent.name") name: this.get("selection.name") || this.get("noneRowComputedContent.name")
}; };
}, },
@ -120,84 +119,118 @@ export default SelectKitComponent.extend({
}, },
@computed("computedValue", "computedContent.[]") @computed("computedValue", "computedContent.[]")
selectedComputedContent(computedValue, computedContent) { selection(computedValue, computedContent) {
return computedContent.findBy("value", computedValue); return computedContent.findBy("value", computedValue);
}, },
@computed("selectedComputedContent") @computed("selection")
hasSelection(selectedComputedContent) { hasSelection(selection) {
return selectedComputedContent !== this.get("noneRowComputedContent") && return selection !== this.get("noneRowComputedContent") && !isNone(selection);
!Ember.isNone(selectedComputedContent);
}, },
@computed("computedValue", "filter", "collectionComputedContent.[]", "limitReached") @computed("computedValue", "filter", "collectionComputedContent.[]", "hasReachedLimit")
shouldDisplayCreateRow(computedValue, filter) { shouldDisplayCreateRow(computedValue, filter) {
return this._super() && computedValue !== filter; return this._super() && computedValue !== filter;
}, },
autoHighlight() { autoHighlight() {
run.schedule("afterRender", () => { run.schedule("afterRender", () => {
if (!isNone(this.get("highlightedValue"))) return; if (this.get("shouldDisplayCreateRow")) {
this.highlight(this.get("createRowComputedContent"));
const filteredComputedContent = this.get("filteredComputedContent");
const displayCreateRow = this.get("shouldDisplayCreateRow");
const none = this.get("noneRowComputedContent");
if (this.get("hasSelection") && isEmpty(this.get("filter"))) {
this.send("highlight", this.get("selectedComputedContent"));
return; return;
} }
if (isNone(this.get("highlightedValue")) && !isEmpty(filteredComputedContent)) { if (!isEmpty(this.get("filter")) && !isEmpty(this.get("collectionComputedContent"))) {
this.send("highlight", get(filteredComputedContent, "firstObject")); this.highlight(this.get("collectionComputedContent.firstObject"));
return; return;
} }
if (displayCreateRow && isEmpty(filteredComputedContent)) { if (!this.get("isAsync") && this.get("hasSelection") && isEmpty(this.get("filter"))) {
this.send("highlight", this.get("createRowComputedContent")); this.highlight(get(makeArray(this.get("selection")), "firstObject"));
return;
} }
else if (!isEmpty(filteredComputedContent)) {
this.send("highlight", get(filteredComputedContent, "firstObject")); if (!this.get("isAsync") && !this.get("hasSelection") && isEmpty(this.get("filter")) && !isEmpty(this.get("collectionComputedContent"))) {
this.highlight(this.get("collectionComputedContent.firstObject"));
return;
} }
else if (isEmpty(filteredComputedContent) && isPresent(none) && !displayCreateRow) {
this.send("highlight", none); if (isPresent(this.get("noneRowComputedContent"))) {
this.highlight(this.get("noneRowComputedContent"));
return;
} }
}); });
}, },
actions: { select(computedContentItem) {
clearSelection() { if (!computedContentItem || computedContentItem.__sk_row_type === "noneRow") {
this.send("deselect", this.get("selectedComputedContent")); this.clearSelection();
this._boundaryActionHandler("onClearSelection"); return;
}, }
create(computedContentItem) { if (computedContentItem.__sk_row_type === "createRow") {
if (this.get("computedValue") !== computedContentItem.value && if (this.get("computedValue") !== computedContentItem.value &&
this.validateCreate(computedContentItem.value)) { this.validateCreate(computedContentItem.value)) {
this.willCreate(computedContentItem);
computedContentItem.__sk_row_type = null;
this.get("computedContent").pushObject(computedContentItem); this.get("computedContent").pushObject(computedContentItem);
this._boundaryActionHandler("onCreate");
this.send("select", computedContentItem); run.schedule("afterRender", () => {
this.didCreate(computedContentItem);
this._boundaryActionHandler("onCreate");
});
this.select(computedContentItem);
return;
} else { } else {
this._boundaryActionHandler("onCreateFailure"); this._boundaryActionHandler("onCreateFailure");
return;
} }
},
select(rowComputedContentItem) {
if (this.validateSelect(rowComputedContentItem)) {
this.willSelect(rowComputedContentItem);
this.set("computedValue", rowComputedContentItem.value);
this.mutateAttributes();
run.schedule("afterRender", () => this.didSelect(rowComputedContentItem));
} else {
this._boundaryActionHandler("onSelectFailure");
}
},
deselect(rowComputedContentItem) {
this.willDeselect(rowComputedContentItem);
this.set("computedValue", null);
this.mutateAttributes();
run.schedule("afterRender", () => this.didDeselect(rowComputedContentItem));
} }
if (this.validateSelect(computedContentItem)) {
this.willSelect(computedContentItem);
this.clearFilter();
this.setProperties({ highlighted: null, computedValue: computedContentItem.value });
run.next(() => this.mutateAttributes());
run.schedule("afterRender", () => {
this.didSelect(computedContentItem);
applyOnSelectPluginApiCallbacks(
this.get("pluginApiIdentifiers"),
computedContentItem.value,
this
);
this._boundaryActionHandler("onSelect", computedContentItem.value);
this.autoHighlight();
});
} else {
this._boundaryActionHandler("onSelectFailure");
}
},
deselect(computedContentItem) {
makeArray(computedContentItem).forEach((item) => {
this.willDeselect(item);
this.clearFilter();
this.setProperties({
computedValue: null,
highlighted: null,
highlightedSelection: []
});
run.next(() => this.mutateAttributes());
run.schedule("afterRender", () => {
this.didDeselect(item);
this._boundaryActionHandler("onDeselect", item);
this.autoHighlight();
});
});
} }
}); });

View File

@ -58,7 +58,7 @@ export default MultiSelectComponent.extend(Tags, {
actions: { actions: {
onFilter(filter) { onFilter(filter) {
this.expand(); this.expand();
this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, filter, 200));
}, },
onExpand() { onExpand() {
@ -66,15 +66,15 @@ export default MultiSelectComponent.extend(Tags, {
}, },
onDeselect() { onDeselect() {
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, this.get("filter"), 200));
}, },
onSelect() { onSelect() {
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, this.get("filter"), 50));
} }
}, },
prepareSearch(query) { _prepareSearch(query) {
const selectedTags = makeArray(this.get("values")).filter(t => t); const selectedTags = makeArray(this.get("values")).filter(t => t);
const data = { const data = {

View File

@ -39,7 +39,7 @@ export default ComboBoxComponent.extend({
}, },
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
if (!content.value) { if (!content.value) {
if (this.get("noTagsSelected")) { if (this.get("noTagsSelected")) {

View File

@ -42,25 +42,25 @@ export default MultiSelectComponent.extend(Tags, {
actions: { actions: {
onFilter(filter) { onFilter(filter) {
this.expand(); this.expand();
this.set("searchDebounce", run.debounce(this, this.prepareSearch, filter, 200)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, filter, 200));
}, },
onExpand() { onExpand() {
if (isEmpty(this.get("collectionComputedContent"))) { if (isEmpty(this.get("collectionComputedContent"))) {
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, this.get("filter"), 200));
} }
}, },
onDeselect() { onDeselect() {
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 200)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, this.get("filter"), 200));
}, },
onSelect() { onSelect() {
this.set("searchDebounce", run.debounce(this, this.prepareSearch, this.get("filter"), 50)); this.set("searchDebounce", run.debounce(this, this._prepareSearch, this.get("filter"), 50));
} }
}, },
prepareSearch(query) { _prepareSearch(query) {
const data = { const data = {
q: query, q: query,
limit: this.get("siteSettings.max_tag_search_results") limit: this.get("siteSettings.max_tag_search_results")

View File

@ -14,7 +14,7 @@ export default DropdownSelectBoxComponent.extend({
mutateValue(value) { mutateValue(value) {
this.sendAction("onPopupMenuAction", value); this.sendAction("onPopupMenuAction", value);
this.setProperties({ value: null, highlightedValue: null }); this.setProperties({ value: null, highlighted: null });
}, },
computeContent(content) { computeContent(content) {

View File

@ -8,7 +8,7 @@ export default ComboBoxComponent.extend({
allowInitialValueMutation: false, allowInitialValueMutation: false,
computeHeaderContent() { computeHeaderContent() {
let content = this.baseHeaderComputedContent(); let content = this._super();
content.name = I18n.t("topic.controls"); content.name = I18n.t("topic.controls");
return content; return content;
}, },
@ -45,7 +45,7 @@ export default ComboBoxComponent.extend({
return; return;
} }
const refresh = () => this.send("deselect", value); const refresh = () => this.deselect(this.get("selection"));
switch(value) { switch(value) {
case "invite": case "invite":

View File

@ -6,6 +6,7 @@ export default Ember.Mixin.create({
this._previousScrollParentOverflow = null; this._previousScrollParentOverflow = null;
this._previousCSSContext = null; this._previousCSSContext = null;
this.selectionSelector = ".choice";
this.filterInputSelector = ".filter-input"; this.filterInputSelector = ".filter-input";
this.rowSelector = ".select-kit-row"; this.rowSelector = ".select-kit-row";
this.collectionSelector = ".select-kit-collection"; this.collectionSelector = ".select-kit-collection";
@ -58,36 +59,42 @@ export default Ember.Mixin.create({
this.setProperties({ isFocused: false }); this.setProperties({ isFocused: false });
}, },
// try to focus filter and fallback to header if not present
focus() { focus() {
Ember.run.schedule("afterRender", () => { Ember.run.schedule("afterRender", () => {
if ((!this.get("filterable")) || !this.$filterInput().is(":visible")) { this.$header().focus();
this.$header().focus(); });
} else { },
this.$filterInput().focus();
} // try to focus filter and fallback to header if not present
focusFilterOrHeader() {
// next so we are sure it finised expand/collapse
Ember.run.next(() => {
Ember.run.schedule("afterRender", () => {
if (!this.$filterInput().is(":visible")) {
this.$header().focus();
} else {
this.$filterInput().focus();
}
});
}); });
}, },
expand() { expand() {
if (this.get("isExpanded") === true) return; if (this.get("isExpanded")) return;
this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true }); this.setProperties({ isExpanded: true, renderedBodyOnce: true, isFocused: true });
this._setCollectionHeaderComputedContent(); this.focusFilterOrHeader();
this._setHeaderComputedContent();
this.focus();
this.autoHighlight(); this.autoHighlight();
}, },
collapse() { collapse() {
this.set("isExpanded", false); this.set("isExpanded", false);
Ember.run.schedule("afterRender", () => this._removeFixedPosition() ); Ember.run.schedule("afterRender", () => this._removeFixedPosition() );
this._setHeaderComputedContent();
}, },
// lose focus of the component in two steps // lose focus of the component in two steps
// first collapse and keep focus and then remove focus // first collapse and keep focus and then remove focus
unfocus(event) { unfocus(event) {
if (this.get("isExpanded") === true) { if (this.get("isExpanded")) {
this.collapse(event); this.collapse(event);
this.focus(event); this.focus(event);
} else { } else {

View File

@ -9,6 +9,9 @@ export default Ember.Mixin.create({
UP: 38, UP: 38,
DOWN: 40, DOWN: 40,
BACKSPACE: 8, BACKSPACE: 8,
LEFT: 37,
RIGHT: 39,
A: 65
}; };
}, },
@ -55,7 +58,7 @@ export default Ember.Mixin.create({
this.$header() this.$header()
.on("blur.select-kit", () => { .on("blur.select-kit", () => {
if (this.get("isExpanded") === false && this.get("isFocused") === true) { if (!this.get("isExpanded") && this.get("isFocused")) {
this.close(); this.close();
} }
}) })
@ -68,22 +71,27 @@ export default Ember.Mixin.create({
if (document.activeElement !== this.$header()[0]) return event; if (document.activeElement !== this.$header()[0]) return event;
if (keyCode === this.keys.TAB) this.tabFromHeader(event); if (keyCode === this.keys.TAB && event.shiftKey) { this.unfocus(event); }
if (keyCode === this.keys.BACKSPACE) this.backspaceFromHeader(event); if (keyCode === this.keys.TAB && !event.shiftKey) this.tabFromHeader(event);
if (Ember.isEmpty(this.get("filter")) && keyCode === this.keys.BACKSPACE) this.backspaceFromHeader(event);
if (keyCode === this.keys.ESC) this.escapeFromHeader(event); if (keyCode === this.keys.ESC) this.escapeFromHeader(event);
if (keyCode === this.keys.ENTER) this.enterFromHeader(event); if (keyCode === this.keys.ENTER) this.enterFromHeader(event);
if ([this.keys.UP, this.keys.DOWN].includes(keyCode)) this.upAndDownFromHeader(event); if ([this.keys.UP, this.keys.DOWN].includes(keyCode)) this.upAndDownFromHeader(event);
if (Ember.isEmpty(this.get("filter")) &&
[this.keys.LEFT, this.keys.RIGHT].includes(keyCode)) {
this.leftAndRightFromHeader(event);
}
return event; return event;
}) })
.on("keypress.select-kit", (event) => { .on("keypress.select-kit", (event) => {
const keyCode = event.keyCode || event.which; const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.ENTER) { return true; } if (keyCode === this.keys.ENTER) return true;
if (keyCode === this.keys.TAB) { return true; } if (keyCode === this.keys.TAB) return true;
this.expand(event); this.expand(event);
if (this.get("filterable") === true || this.get("autoFilterable")) { if (this.get("filterable") || this.get("autoFilterable")) {
this.set("renderedFilterOnce", true); this.set("renderedFilterOnce", true);
} }
@ -100,7 +108,7 @@ export default Ember.Mixin.create({
this.$filterInput() this.$filterInput()
.on("change.select-kit", (event) => { .on("change.select-kit", (event) => {
this.send("filterComputedContent", $(event.target).val()); this.send("onFilterComputedContent", $(event.target).val());
}) })
.on("keypress.select-kit", (event) => { .on("keypress.select-kit", (event) => {
event.stopPropagation(); event.stopPropagation();
@ -108,40 +116,83 @@ export default Ember.Mixin.create({
.on("keydown.select-kit", (event) => { .on("keydown.select-kit", (event) => {
const keyCode = event.keyCode || event.which; const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.BACKSPACE && typeof this.backspaceFromFilter === "function") { if (Ember.isEmpty(this.get("filter")) &&
this.backspaceFromFilter(event); keyCode === this.keys.BACKSPACE &&
typeof this.didPressBackspaceFromFilter === "function") {
this.didPressBackspaceFromFilter(event);
}; };
if (keyCode === this.keys.TAB) this.tabFromFilter(event);
if (keyCode === this.keys.TAB && event.shiftKey) { this.unfocus(event); }
if (keyCode === this.keys.TAB && !event.shiftKey) this.tabFromFilter(event);
if (keyCode === this.keys.ESC) this.escapeFromFilter(event); if (keyCode === this.keys.ESC) this.escapeFromFilter(event);
if (keyCode === this.keys.ENTER) this.enterFromFilter(event); if (keyCode === this.keys.ENTER) this.enterFromFilter(event);
if ([this.keys.UP, this.keys.DOWN].includes(keyCode)) this.upAndDownFromFilter(event); if ([this.keys.UP, this.keys.DOWN].includes(keyCode)) this.upAndDownFromFilter(event);
if (Ember.isEmpty(this.get("filter")) &&
[this.keys.LEFT, this.keys.RIGHT].includes(keyCode)) {
this.leftAndRightFromFilter(event);
}
}); });
}, },
didPressTab(event) { didPressTab(event) {
if (this.get("isExpanded") === false) { if (this.$highlightedRow().length && this.get("isExpanded")) {
this.unfocus(event); this.close(event);
} else if (this.$highlightedRow().length === 1) { this.$header().focus();
Ember.run.throttle(this, this._rowClick, this.$highlightedRow(), 150, 150, true); const guid = this.$highlightedRow().attr("data-guid");
this.unfocus(event); this.select(this._findComputedContentItemByGuid(guid));
return true;
}
if (Ember.isEmpty(this.get("filter"))) {
this.close(event);
return true; return true;
} else {
this._destroyEvent(event);
this.unfocus(event);
} }
return true; return true;
}, },
didPressEnter(event) {
if (!this.get("isExpanded")) {
this.expand(event);
} else if (this.$highlightedRow().length) {
this.close(event);
this.$header().focus();
const guid = this.$highlightedRow().attr("data-guid");
this.select(this._findComputedContentItemByGuid(guid));
}
return true;
},
didClickSelectionItem(computedContentItem) {
this.focus();
this.deselect(computedContentItem);
},
didClickRow(computedContentItem) {
this.close(event);
this.focus();
this.select(computedContentItem);
},
didPressEscape(event) { didPressEscape(event) {
this._destroyEvent(event); this._destroyEvent(event);
this.unfocus(event);
if (this.get("highlightedSelection").length && this.get("isExpanded")) {
this.clearHighlightSelection();
} else {
this.unfocus(event);
}
}, },
didPressUpAndDownArrows(event) { didPressUpAndDownArrows(event) {
this._destroyEvent(event); this._destroyEvent(event);
this.clearHighlightSelection();
const keyCode = event.keyCode || event.which; const keyCode = event.keyCode || event.which;
const $rows = this.$rows(); const $rows = this.$rows();
if (this.get("isExpanded") === false) { if (this.get("isExpanded") === false) {
@ -151,9 +202,12 @@ export default Ember.Mixin.create({
this._highlightRow(this.$selectedRow()); this._highlightRow(this.$selectedRow());
return; return;
} }
return;
} }
if ($rows.length <= 0) { return; } if (!$rows.length) { return; }
if ($rows.length === 1) { if ($rows.length === 1) {
this._rowSelection($rows, 0); this._rowSelection($rows, 0);
return; return;
@ -164,28 +218,43 @@ export default Ember.Mixin.create({
Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32); Ember.run.throttle(this, this._moveHighlight, direction, $rows, 32);
}, },
didPressBackspaceFromFilter(event) { this.didPressBackspace(event); },
didPressBackspace(event) { didPressBackspace(event) {
this._destroyEvent(event); if (!this.get("isExpanded")) {
this.expand();
if (event) event.stopImmediatePropagation();
return;
}
this.expand(event); if (!this.get("selection").length) return;
if (this.$filterInput().is(":visible")) { if (!Ember.isEmpty(this.get("filter"))) {
this.$filterInput().focus().trigger(event).trigger("change"); this.clearHighlightSelection();
return;
}
if (!this.get("highlightedSelection").length) {
// try to highlight the last non locked item from the current selection
Ember.makeArray(this.get("selection")).slice().reverse().some(selection => {
if (!Ember.get(selection, "locked")) {
this.highlightSelection(selection);
return true;
}
});
if (event) event.stopImmediatePropagation();
} else {
this.deselect(this.get("highlightedSelection"));
if (event) event.stopImmediatePropagation();
} }
}, },
didPressEnter(event) { didPressSelectAll() {
this._destroyEvent(event); this.highlightSelection(Ember.makeArray(this.get("selection")));
if (this.get("isExpanded") === false) {
this.expand(event);
} else if (this.$highlightedRow().length === 1) {
Ember.run.throttle(this, this._rowClick, this.$highlightedRow(), 150, true);
}
}, },
didClickOutside(event) { didClickOutside(event) {
if ($(event.target).parents(".select-kit").length === 1) { if (this.get("isExpanded") && $(event.target).parents(".select-kit").length) {
this.close(event); this.close(event);
return false; return false;
} }
@ -200,6 +269,38 @@ export default Ember.Mixin.create({
this._destroyEvent(event); this._destroyEvent(event);
}, },
didPressLeftAndRightArrows(event) {
if (!this.get("isExpanded")) {
this.expand();
event.stopImmediatePropagation();
return;
}
if (Ember.isEmpty(this.get("selection"))) return;
const keyCode = event.keyCode || event.which;
if (keyCode === this.keys.LEFT) {
const prev = this.get("highlightedSelection.lastObject");
const indexOfPrev = this.get("selection").indexOf(prev);
if (this.get("selection")[indexOfPrev - 1]) {
this.highlightSelection(this.get("selection")[indexOfPrev - 1]);
} else {
this.highlightSelection(this.get("selection.lastObject"));
}
} else {
const prev = this.get("highlightedSelection.firstObject");
const indexOfNext= this.get("selection").indexOf(prev);
if (this.get("selection")[indexOfNext + 1]) {
this.highlightSelection(this.get("selection")[indexOfNext + 1]);
} else {
this.highlightSelection(this.get("selection.firstObject"));
}
}
},
tabFromHeader(event) { this.didPressTab(event); }, tabFromHeader(event) { this.didPressTab(event); },
tabFromFilter(event) { this.didPressTab(event); }, tabFromFilter(event) { this.didPressTab(event); },
@ -209,6 +310,9 @@ export default Ember.Mixin.create({
upAndDownFromHeader(event) { this.didPressUpAndDownArrows(event); }, upAndDownFromHeader(event) { this.didPressUpAndDownArrows(event); },
upAndDownFromFilter(event) { this.didPressUpAndDownArrows(event); }, upAndDownFromFilter(event) { this.didPressUpAndDownArrows(event); },
leftAndRightFromHeader(event) { this.didPressLeftAndRightArrows(event); },
leftAndRightFromFilter(event) { this.didPressLeftAndRightArrows(event); },
backspaceFromHeader(event) { this.didPressBackspace(event); }, backspaceFromHeader(event) { this.didPressBackspace(event); },
enterFromHeader(event) { this.didPressEnter(event); }, enterFromHeader(event) { this.didPressEnter(event); },
@ -227,8 +331,6 @@ export default Ember.Mixin.create({
this._rowSelection($rows, nextIndex); this._rowSelection($rows, nextIndex);
}, },
_rowClick($row) { $row.click(); },
_rowSelection($rows, nextIndex) { _rowSelection($rows, nextIndex) {
const highlightableValue = $rows.eq(nextIndex).attr("data-value"); const highlightableValue = $rows.eq(nextIndex).attr("data-value");
const $highlightableRow = this.$findRowByValue(highlightableValue); const $highlightableRow = this.$findRowByValue(highlightableValue);

View File

@ -22,17 +22,18 @@ export default Ember.Mixin.create({
data data
}).then(json => { }).then(json => {
self.set("asyncContent", callback(self, json)); self.set("asyncContent", callback(self, json));
self.autoHighlight();
}).catch(error => { }).catch(error => {
popupAjaxError(error); popupAjaxError(error);
}) })
.finally(() => { .finally(() => {
self.stopLoading(); self.stopLoading();
self.autoHighlight(); self.focusFilterOrHeader();
}); });
}, },
validateCreate(term) { validateCreate(term) {
if (this.get("limitReached") || !this.site.get("can_create_tag")) { if (this.get("hasReachedLimit") || !this.site.get("can_create_tag")) {
return false; return false;
} }
@ -47,7 +48,9 @@ export default Ember.Mixin.create({
return false; return false;
} }
if (this.get("asyncContent").map(c => get(c, "id")).includes(term)) { const inCollection = this.get("collectionComputedContent").map(c => get(c, "id")).includes(term);
const inSelection = this.get("selection").map(s => s.toLowerCase()).includes(term);
if (inCollection || inSelection) {
return false; return false;
} }

View File

@ -36,12 +36,20 @@ export default Ember.Mixin.create({
}, },
_findComputedContentItemByGuid(guid) { _findComputedContentItemByGuid(guid) {
return this.get("computedContent").find(c => { if (guidFor(this.get("createRowComputedContent")) === guid) {
return this.get("createRowComputedContent");
}
if (guidFor(this.get("noneRowComputedContent")) === guid) {
return this.get("noneRowComputedContent");
}
return this.get("collectionComputedContent").find(c => {
return guidFor(c) === guid; return guidFor(c) === guid;
}); });
}, },
_filterRemovableComputedContents(computedContent) { _filterRemovableComputedContents(computedContent) {
return computedContent.filter(c => c.created === true); return computedContent.filter(c => c.created);
} }
}); });

View File

@ -5,7 +5,7 @@
</span> </span>
{{#if shouldDisplayClearableButton}} {{#if shouldDisplayClearableButton}}
<button class="btn-clear" {{action clearSelection bubbles=false}}> <button class="btn-clear" {{action onClearSelection bubbles=false}}>
{{d-icon 'times'}} {{d-icon 'times'}}
</button> </button>
{{/if}} {{/if}}

View File

@ -2,6 +2,6 @@
{{#if options.showFullTitle}} {{#if options.showFullTitle}}
<span class="d-button-label selected-name"> <span class="d-button-label selected-name">
{{label}} {{{label}}}
</span> </span>
{{/if}} {{/if}}

View File

@ -15,7 +15,7 @@
{{/if}} {{/if}}
{{#if shouldDisplayClearableButton}} {{#if shouldDisplayClearableButton}}
<button class="btn-clear" {{action clearSelection bubbles=false}}> <button class="btn-clear" {{action onClearSelection bubbles=false}}>
{{d-icon 'times'}} {{d-icon 'times'}}
</button> </button>
{{/if}} {{/if}}

View File

@ -1 +1,3 @@
{{input class="selected-name" value=label readonly="readonly"}} {{input class="selected-name" value=label readonly="readonly" tabindex="-1"}}
{{d-icon caretIcon class="caret-icon"}}

View File

@ -2,10 +2,10 @@
tabindex=tabindex tabindex=tabindex
isFocused=isFocused isFocused=isFocused
isExpanded=isExpanded isExpanded=isExpanded
highlightedSelection=highlightedSelection
onClickSelectionItem=(action "onClickSelectionItem")
computedContent=headerComputedContent computedContent=headerComputedContent
deselect=(action "deselect") onToggle=(action "onToggle")
toggle=(action "toggle")
clearSelection=(action "clearSelection")
options=headerComponentOptions options=headerComponentOptions
}} }}
{{component filterComponent {{component filterComponent
@ -16,12 +16,13 @@
isLoading=isLoading isLoading=isLoading
shouldDisplayFilter=shouldDisplayFilter shouldDisplayFilter=shouldDisplayFilter
isFocused=isFocused isFocused=isFocused
filterComputedContent=(action "filterComputedContent") onFilterComputedContent=(action "onFilterComputedContent")
}} }}
{{/component}} {{/component}}
<div class="select-kit-body"> <div class="select-kit-body">
{{#if renderedBodyOnce}} {{#if renderedBodyOnce}}
{{#unless isLoading}}
{{component collectionComponent {{component collectionComponent
collectionHeaderComputedContent=collectionHeaderComputedContent collectionHeaderComputedContent=collectionHeaderComputedContent
hasSelection=hasSelection hasSelection=hasSelection
@ -34,16 +35,15 @@
templateForRow=templateForRow templateForRow=templateForRow
templateForNoneRow=templateForNoneRow templateForNoneRow=templateForNoneRow
templateForCreateRow=templateForCreateRow templateForCreateRow=templateForCreateRow
clearSelection=(action "clearSelection") onClickRow=(action "onClickRow")
select=(action "select") onMouseoverRow=(action "onMouseoverRow")
highlight=(action "highlight") highlighted=highlighted
create=(action "create")
highlightedValue=highlightedValue
computedValue=computedValue computedValue=computedValue
rowComponentOptions=rowComponentOptions rowComponentOptions=rowComponentOptions
noContentRow=noContentRow noContentRow=noContentRow
maxContentRow=maxContentRow maxContentRow=maxContentRow
}} }}
{{/unless}}
{{/if}} {{/if}}
</div> </div>

View File

@ -1,8 +1,9 @@
<div class="choices"> <div class="choices">
{{#each computedContent.selectedComputedContents as |selectedComputedContent|}} {{#each computedContent.selection as |selection|}}
{{component selectedNameComponent {{component selectedNameComponent
deselect=deselect onClickSelectionItem=onClickSelectionItem
computedContent=selectedComputedContent}} highlightedSelection=highlightedSelection
computedContent=selection}}
{{/each}} {{/each}}
<span class="filter choice" tabindex="-1"> <span class="filter choice" tabindex="-1">
{{yield}} {{yield}}

View File

@ -9,11 +9,11 @@
{{component noneRowComponent {{component noneRowComponent
computedContent=noneRowComputedContent computedContent=noneRowComputedContent
templateForRow=templateForNoneRow templateForRow=templateForNoneRow
highlightedValue=highlightedValue highlighted=highlighted
clearSelection=clearSelection onMouseoverRow=onMouseoverRow
highlight=highlight
computedValue=computedValue computedValue=computedValue
options=rowComponentOptions options=rowComponentOptions
onClickRow=onClickRow
}} }}
{{/if}} {{/if}}
{{/if}} {{/if}}
@ -21,11 +21,11 @@
{{#if createRowComputedContent}} {{#if createRowComputedContent}}
{{component createRowComponent {{component createRowComponent
computedContent=createRowComputedContent computedContent=createRowComputedContent
highlightedValue=highlightedValue highlighted=highlighted
computedValue=computedValue computedValue=computedValue
templateForRow=templateForCreateRow templateForRow=templateForCreateRow
highlight=highlight onMouseoverRow=onMouseoverRow
select=create onClickRow=onClickRow
options=rowComponentOptions options=rowComponentOptions
}} }}
{{/if}} {{/if}}
@ -43,11 +43,11 @@
{{#each collectionComputedContent as |computedContent|}} {{#each collectionComputedContent as |computedContent|}}
{{component rowComponent {{component rowComponent
computedContent=computedContent computedContent=computedContent
highlightedValue=highlightedValue highlighted=highlighted
computedValue=computedValue computedValue=computedValue
templateForRow=templateForRow templateForRow=templateForRow
select=select onClickRow=onClickRow
highlight=highlight onMouseoverRow=onMouseoverRow
options=rowComponentOptions options=rowComponentOptions
}} }}
{{/each}} {{/each}}

View File

@ -2,7 +2,7 @@
tabindex=-1 tabindex=-1
class="filter-input" class="filter-input"
placeholder=computedPlaceholder placeholder=computedPlaceholder
key-up=filterComputedContent key-up=onFilterComputedContent
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
autocapitalize="off" autocapitalize="off"

View File

@ -1,11 +1,11 @@
{{component headerComponent {{component headerComponent
caretIcon=caretIcon
tabindex=tabindex tabindex=tabindex
isFocused=isFocused isFocused=isFocused
isExpanded=isExpanded isExpanded=isExpanded
computedContent=headerComputedContent computedContent=headerComputedContent
deselect=(action "deselect") onToggle=(action "onToggle")
toggle=(action "toggle") onClearSelection=(action "onClearSelection")
clearSelection=(action "clearSelection")
options=headerComponentOptions options=headerComponentOptions
}} }}
@ -18,7 +18,7 @@
shouldDisplayFilter=shouldDisplayFilter shouldDisplayFilter=shouldDisplayFilter
placeholder=filterPlaceholder placeholder=filterPlaceholder
isFocused=isFocused isFocused=isFocused
filterComputedContent=(action "filterComputedContent") onFilterComputedContent=(action "onFilterComputedContent")
}} }}
{{#if renderedBodyOnce}} {{#if renderedBodyOnce}}
@ -34,11 +34,9 @@
templateForRow=templateForRow templateForRow=templateForRow
templateForNoneRow=templateForNoneRow templateForNoneRow=templateForNoneRow
templateForCreateRow=templateForCreateRow templateForCreateRow=templateForCreateRow
clearSelection=(action "clearSelection") onClickRow=(action "onClickRow")
select=(action "select") onMouseoverRow=(action "onMouseoverRow")
highlight=(action "highlight") highlighted=highlighted
create=(action "create")
highlightedValue=highlightedValue
computedValue=computedValue computedValue=computedValue
rowComponentOptions=rowComponentOptions rowComponentOptions=rowComponentOptions
noContentRow=noContentRow noContentRow=noContentRow

View File

@ -2,6 +2,11 @@
&.combo-box { &.combo-box {
&.category-chooser { &.category-chooser {
width: 300px; width: 300px;
.combo-box-header {
padding: 4px;
}
.select-kit-row { .select-kit-row {
display: -webkit-box; display: -webkit-box;
display: -ms-flexbox; display: -ms-flexbox;

View File

@ -3,6 +3,12 @@
&.future-date-input-selector { &.future-date-input-selector {
min-width: 50%; min-width: 50%;
.future-date-input-selector-header {
.btn-clear {
line-height: $line-height-large;
}
}
.future-date-input-selector-datetime { .future-date-input-selector-datetime {
color: lighten($primary, 40%); color: lighten($primary, 40%);
font-size: $font-down-1; font-size: $font-down-1;

View File

@ -72,6 +72,7 @@ componentTest('interactions', {
assert.equal(this.get('categories').length, 3); assert.equal(this.get('categories').length, 3);
}); });
this.get('subject').expand();
this.get('subject').keyboard().backspace(); this.get('subject').keyboard().backspace();
this.get('subject').keyboard().backspace(); this.get('subject').keyboard().backspace();

View File

@ -63,7 +63,7 @@ componentTest('interactions', {
assert.equal(listSetting.header().value(), 'bold,italic,underline'); assert.equal(listSetting.header().value(), 'bold,italic,underline');
}); });
listSetting.fillInFilter('strike'); listSetting.expand().fillInFilter('strike');
andThen(() => { andThen(() => {
assert.equal(listSetting.highlightedRow().value(), 'strike'); assert.equal(listSetting.highlightedRow().value(), 'strike');

View File

@ -66,6 +66,7 @@ componentTest('interactions', {
}); });
this.get('subject').selectRowByValue(3); this.get('subject').selectRowByValue(3);
this.get('subject').expand();
andThen(() => { andThen(() => {
assert.equal( assert.equal(