2017-11-10 02:57:53 +08:00
|
|
|
import { on } from "ember-addons/ember-computed-decorators";
|
|
|
|
|
2017-10-20 03:51:08 +08:00
|
|
|
export default Ember.Mixin.create({
|
|
|
|
init() {
|
|
|
|
this._super();
|
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
this._previousScrollParentOverflow = null;
|
|
|
|
this._previousCSSContext = null;
|
2018-03-22 18:29:55 +08:00
|
|
|
this.selectionSelector = ".choice";
|
2017-11-21 18:53:09 +08:00
|
|
|
this.filterInputSelector = ".filter-input";
|
|
|
|
this.rowSelector = ".select-kit-row";
|
|
|
|
this.collectionSelector = ".select-kit-collection";
|
|
|
|
this.headerSelector = ".select-kit-header";
|
|
|
|
this.bodySelector = ".select-kit-body";
|
|
|
|
this.wrapperSelector = ".select-kit-wrapper";
|
2017-12-28 23:12:45 +08:00
|
|
|
this.scrollableParentSelector = ".modal-body";
|
2018-06-15 23:03:24 +08:00
|
|
|
this.fixedPlaceholderSelector = `.select-kit-fixed-placeholder-${
|
|
|
|
this.elementId
|
|
|
|
}`;
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$findRowByValue(value) {
|
|
|
|
return this.$(`${this.rowSelector}[data-value='${value}']`);
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$header() {
|
|
|
|
return this.$(this.headerSelector);
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$body() {
|
|
|
|
return this.$(this.bodySelector);
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$wrapper() {
|
|
|
|
return this.$(this.wrapperSelector);
|
|
|
|
},
|
2017-12-28 23:12:45 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$collection() {
|
|
|
|
return this.$(this.collectionSelector);
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$scrollableParent() {
|
|
|
|
return $(this.scrollableParentSelector);
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$fixedPlaceholder() {
|
|
|
|
return $(this.fixedPlaceholderSelector);
|
|
|
|
},
|
2017-12-28 23:12:45 +08:00
|
|
|
|
|
|
|
$rows() {
|
|
|
|
return this.$(`${this.rowSelector}:not(.no-content):not(.is-hidden)`);
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$highlightedRow() {
|
|
|
|
return this.$rows().filter(".is-highlighted");
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$selectedRow() {
|
|
|
|
return this.$rows().filter(".is-selected");
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
$filterInput() {
|
|
|
|
return this.$(this.filterInputSelector);
|
|
|
|
},
|
2017-11-10 02:57:53 +08:00
|
|
|
|
|
|
|
@on("didRender")
|
2017-12-28 23:12:45 +08:00
|
|
|
_adjustPosition() {
|
2017-11-10 02:57:53 +08:00
|
|
|
this._applyFixedPosition();
|
|
|
|
this._applyDirection();
|
|
|
|
this._positionWrapper();
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2017-11-10 02:57:53 +08:00
|
|
|
@on("willDestroyElement")
|
|
|
|
_clearState() {
|
2017-12-28 23:12:45 +08:00
|
|
|
this.$fixedPlaceholder().remove();
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2017-11-10 02:57:53 +08:00
|
|
|
// use to collapse and remove focus
|
2017-11-21 18:53:09 +08:00
|
|
|
close(event) {
|
2017-11-10 02:57:53 +08:00
|
|
|
this.setProperties({ isFocused: false });
|
2018-08-04 04:41:37 +08:00
|
|
|
this.collapse(event);
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2018-03-05 17:55:20 +08:00
|
|
|
focus() {
|
2018-07-24 00:19:40 +08:00
|
|
|
this.focusFilterOrHeader();
|
2018-03-22 18:29:55 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
// try to focus filter and fallback to header if not present
|
|
|
|
focusFilterOrHeader() {
|
2018-04-03 04:50:20 +08:00
|
|
|
const context = this;
|
2018-03-22 18:29:55 +08:00
|
|
|
// next so we are sure it finised expand/collapse
|
|
|
|
Ember.run.next(() => {
|
|
|
|
Ember.run.schedule("afterRender", () => {
|
2018-07-24 00:19:40 +08:00
|
|
|
if (!context.$filterInput() || !context.$filterInput().is(":visible")) {
|
|
|
|
if (context.$header()) {
|
|
|
|
context.$header().focus();
|
|
|
|
} else {
|
|
|
|
$(context.element).focus();
|
|
|
|
}
|
2018-03-22 18:29:55 +08:00
|
|
|
} else {
|
2018-04-03 04:50:20 +08:00
|
|
|
context.$filterInput().focus();
|
2018-03-22 18:29:55 +08:00
|
|
|
}
|
|
|
|
});
|
2017-11-22 01:07:10 +08:00
|
|
|
});
|
2017-11-22 18:29:30 +08:00
|
|
|
},
|
2017-10-20 03:51:08 +08:00
|
|
|
|
2017-11-22 18:29:30 +08:00
|
|
|
expand() {
|
2018-03-22 18:29:55 +08:00
|
|
|
if (this.get("isExpanded")) return;
|
2018-06-15 23:03:24 +08:00
|
|
|
this.setProperties({
|
|
|
|
isExpanded: true,
|
|
|
|
renderedBodyOnce: true,
|
|
|
|
isFocused: true
|
|
|
|
});
|
2018-03-22 18:29:55 +08:00
|
|
|
this.focusFilterOrHeader();
|
2018-03-05 17:55:20 +08:00
|
|
|
this.autoHighlight();
|
2018-03-23 01:35:46 +08:00
|
|
|
this._boundaryActionHandler("onExpand", this);
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2017-11-10 02:57:53 +08:00
|
|
|
collapse() {
|
|
|
|
this.set("isExpanded", false);
|
2018-08-04 04:41:37 +08:00
|
|
|
|
|
|
|
Ember.run.next(() => {
|
|
|
|
Ember.run.schedule("afterRender", () => this._removeFixedPosition());
|
|
|
|
this._boundaryActionHandler("onCollapse", this);
|
|
|
|
});
|
2017-10-20 03:51:08 +08:00
|
|
|
},
|
|
|
|
|
2017-11-10 02:57:53 +08:00
|
|
|
// lose focus of the component in two steps
|
2017-11-21 18:53:09 +08:00
|
|
|
// first collapse and keep focus and then remove focus
|
|
|
|
unfocus(event) {
|
2018-03-22 18:29:55 +08:00
|
|
|
if (this.get("isExpanded")) {
|
2017-11-21 18:53:09 +08:00
|
|
|
this.collapse(event);
|
|
|
|
this.focus(event);
|
2017-11-10 02:57:53 +08:00
|
|
|
} else {
|
2017-11-21 18:53:09 +08:00
|
|
|
this.close(event);
|
2017-11-10 02:57:53 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-11-21 18:53:09 +08:00
|
|
|
_destroyEvent(event) {
|
2017-10-20 03:51:08 +08:00
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2017-11-10 02:57:53 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_applyDirection() {
|
|
|
|
let options = { left: "auto", bottom: "auto", top: "auto" };
|
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
const discourseHeader = $(".d-header")[0];
|
2018-06-15 23:03:24 +08:00
|
|
|
const discourseHeaderHeight = discourseHeader
|
|
|
|
? discourseHeader.getBoundingClientRect().top +
|
|
|
|
this._computedStyle(discourseHeader, "height")
|
|
|
|
: 0;
|
2017-12-28 23:12:45 +08:00
|
|
|
const bodyHeight = this._computedStyle(this.$body()[0], "height");
|
|
|
|
const componentHeight = this._computedStyle(this.get("element"), "height");
|
|
|
|
const offsetTop = this.get("element").getBoundingClientRect().top;
|
|
|
|
const offsetBottom = this.get("element").getBoundingClientRect().bottom;
|
2018-03-01 18:53:14 +08:00
|
|
|
const windowWidth = $(window).width();
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
if (
|
|
|
|
this.get("fullWidthOnMobile") &&
|
|
|
|
(this.site && this.site.isMobileDevice)
|
|
|
|
) {
|
2017-11-10 02:57:53 +08:00
|
|
|
const margin = 10;
|
|
|
|
const relativeLeft = this.$().offset().left - $(window).scrollLeft();
|
|
|
|
options.left = margin - relativeLeft;
|
|
|
|
options.width = windowWidth - margin * 2;
|
|
|
|
options.maxWidth = options.minWidth = "unset";
|
|
|
|
} else {
|
2018-06-15 23:03:24 +08:00
|
|
|
const parentWidth = this.$scrollableParent().length
|
|
|
|
? this.$scrollableParent().width()
|
|
|
|
: windowWidth;
|
2017-12-28 23:12:45 +08:00
|
|
|
const bodyWidth = this._computedStyle(this.$body()[0], "width");
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-03-01 18:53:14 +08:00
|
|
|
let marginToEdge;
|
|
|
|
if (this.$scrollableParent().length) {
|
2018-06-15 23:03:24 +08:00
|
|
|
marginToEdge =
|
|
|
|
this.$().offset().left - this.$scrollableParent().offset().left;
|
2018-03-01 18:53:14 +08:00
|
|
|
} else {
|
|
|
|
marginToEdge = this.get("element").getBoundingClientRect().left;
|
|
|
|
}
|
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
const enoughMarginToOppositeEdge =
|
|
|
|
parentWidth - marginToEdge - bodyWidth + this.get("horizontalOffset") >
|
|
|
|
0;
|
2018-03-01 18:53:14 +08:00
|
|
|
if (enoughMarginToOppositeEdge) {
|
|
|
|
this.setProperties({ isLeftAligned: true, isRightAligned: false });
|
|
|
|
options.left = this.get("horizontalOffset");
|
|
|
|
options.right = "unset";
|
2017-11-10 02:57:53 +08:00
|
|
|
} else {
|
2018-03-01 18:53:14 +08:00
|
|
|
this.setProperties({ isLeftAligned: false, isRightAligned: true });
|
|
|
|
options.left = "unset";
|
|
|
|
options.right = this.get("horizontalOffset");
|
2017-11-10 02:57:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
const fullHeight =
|
|
|
|
this.get("verticalOffset") + bodyHeight + componentHeight;
|
2017-12-28 23:12:45 +08:00
|
|
|
const hasBelowSpace = $(window).height() - offsetBottom - fullHeight > 0;
|
|
|
|
const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight > 0;
|
|
|
|
const headerHeight = this._computedStyle(this.$header()[0], "height");
|
2017-11-10 02:57:53 +08:00
|
|
|
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
|
|
|
|
this.setProperties({ isBelow: true, isAbove: false });
|
2017-12-28 23:12:45 +08:00
|
|
|
options.top = headerHeight + this.get("verticalOffset");
|
2017-11-10 02:57:53 +08:00
|
|
|
} else {
|
|
|
|
this.setProperties({ isBelow: false, isAbove: true });
|
2017-12-28 23:12:45 +08:00
|
|
|
options.bottom = headerHeight + this.get("verticalOffset");
|
2017-11-10 02:57:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
this.$body().css(options);
|
|
|
|
},
|
|
|
|
|
|
|
|
_applyFixedPosition() {
|
2017-12-28 23:12:45 +08:00
|
|
|
if (this.get("isExpanded") !== true) return;
|
2018-03-01 18:53:14 +08:00
|
|
|
if (this.$fixedPlaceholder().length) return;
|
|
|
|
if (!this.$scrollableParent().length) return;
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
const width = this._computedStyle(this.get("element"), "width");
|
|
|
|
const height = this._computedStyle(this.get("element"), "height");
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
this._previousScrollParentOverflow =
|
|
|
|
this._previousScrollParentOverflow ||
|
|
|
|
this.$scrollableParent().css("overflow");
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
this._previousCSSContext = this._previousCSSContext || {
|
|
|
|
width,
|
2017-11-10 02:57:53 +08:00
|
|
|
minWidth: this.$().css("min-width"),
|
2017-12-28 23:12:45 +08:00
|
|
|
maxWidth: this.$().css("max-width"),
|
|
|
|
top: this.$().css("top"),
|
|
|
|
left: this.$().css("left"),
|
|
|
|
marginLeft: this.$().css("margin-left"),
|
|
|
|
marginRight: this.$().css("margin-right"),
|
|
|
|
position: this.$().css("position")
|
2017-11-10 02:57:53 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const componentStyles = {
|
2017-12-28 23:12:45 +08:00
|
|
|
top: this.get("element").getBoundingClientRect().top,
|
2017-11-10 02:57:53 +08:00
|
|
|
width,
|
2017-12-28 23:12:45 +08:00
|
|
|
left: this.get("element").getBoundingClientRect().left,
|
|
|
|
marginLeft: 0,
|
|
|
|
marginRight: 0,
|
2017-11-10 02:57:53 +08:00
|
|
|
minWidth: "unset",
|
2017-12-28 23:12:45 +08:00
|
|
|
maxWidth: "unset",
|
|
|
|
position: "fixed"
|
2017-11-10 02:57:53 +08:00
|
|
|
};
|
|
|
|
|
2018-06-15 23:03:24 +08:00
|
|
|
const $placeholderTemplate = $(
|
|
|
|
`<div class='select-kit-fixed-placeholder-${this.elementId}'></div>`
|
|
|
|
);
|
2017-12-28 23:12:45 +08:00
|
|
|
$placeholderTemplate.css({
|
|
|
|
display: "inline-block",
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
"vertical-align": "middle"
|
|
|
|
});
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
this.$()
|
2018-06-15 23:03:24 +08:00
|
|
|
.before($placeholderTemplate)
|
|
|
|
.css(componentStyles);
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
this.$scrollableParent().css({ overflow: "hidden" });
|
2017-11-10 02:57:53 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_removeFixedPosition() {
|
2017-12-28 23:12:45 +08:00
|
|
|
this.$fixedPlaceholder().remove();
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
if (!this.element || this.isDestroying || this.isDestroyed) return;
|
|
|
|
if (this.$scrollableParent().length === 0) return;
|
|
|
|
|
|
|
|
this.$().css(this._previousCSSContext || {});
|
2018-06-15 23:03:24 +08:00
|
|
|
this.$scrollableParent().css(
|
|
|
|
"overflow",
|
|
|
|
this._previousScrollParentOverflow || {}
|
|
|
|
);
|
2017-11-10 02:57:53 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
_positionWrapper() {
|
2017-12-28 23:12:45 +08:00
|
|
|
const elementWidth = this._computedStyle(this.get("element"), "width");
|
|
|
|
const headerHeight = this._computedStyle(this.$header()[0], "height");
|
|
|
|
const bodyHeight = this._computedStyle(this.$body()[0], "height");
|
2017-11-10 02:57:53 +08:00
|
|
|
|
2017-12-28 23:12:45 +08:00
|
|
|
this.$wrapper().css({
|
|
|
|
width: elementWidth,
|
|
|
|
height: headerHeight + bodyHeight
|
2017-11-10 02:57:53 +08:00
|
|
|
});
|
|
|
|
},
|
2017-12-28 23:12:45 +08:00
|
|
|
|
|
|
|
_isRTL() {
|
|
|
|
return $("html").css("direction") === "rtl";
|
|
|
|
},
|
|
|
|
|
|
|
|
_computedStyle(element, style) {
|
|
|
|
if (!element) return 0;
|
|
|
|
|
|
|
|
let value;
|
|
|
|
|
|
|
|
if (window.getComputedStyle) {
|
|
|
|
value = window.getComputedStyle(element, null)[style];
|
|
|
|
} else {
|
|
|
|
value = $(element).css(style);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this._getFloat(value);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getFloat(value) {
|
|
|
|
value = parseFloat(value);
|
|
|
|
return $.isNumeric(value) ? value : 0;
|
|
|
|
}
|
2017-10-20 03:51:08 +08:00
|
|
|
});
|